Skip to content

Commit b85d1c8

Browse files
committed
Print negations whenever most of the descr types are either top or negated
1 parent 4602240 commit b85d1c8

3 files changed

Lines changed: 215 additions & 47 deletions

File tree

lib/elixir/lib/integer.ex

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,45 @@ defmodule Integer do
1818

1919
import Bitwise
2020

21+
@doc """
22+
Counts the number of set bits (1) in the binary representation of a non-negative `integer`.
23+
24+
This operation is known as the Hamming weight or population count.
25+
26+
Raises an `ArithmeticError` if `integer` is negative.
27+
28+
## Examples
29+
30+
iex> Integer.popcount(0)
31+
0
32+
33+
iex> Integer.popcount(1)
34+
1
35+
36+
iex> Integer.popcount(0b10110101)
37+
5
38+
39+
iex> Integer.popcount(255)
40+
8
41+
42+
iex> Integer.popcount(0b1111111111111111)
43+
16
44+
45+
iex> Integer.popcount(-1)
46+
** (ArithmeticError) bad argument in arithmetic expression
47+
48+
"""
49+
@doc since: "1.20.0"
50+
@spec popcount(non_neg_integer) :: non_neg_integer
51+
def popcount(integer) when is_integer(integer) and integer < 0,
52+
do: :erlang.error(:badarith, [integer])
53+
54+
def popcount(integer) when is_integer(integer),
55+
do: popcount(integer, 0)
56+
57+
defp popcount(0, acc), do: acc
58+
defp popcount(n, acc), do: popcount(n &&& n - 1, acc + 1)
59+
2160
@doc """
2261
Determines if `integer` is odd.
2362

lib/elixir/lib/module/types/descr.ex

Lines changed: 99 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -721,18 +721,42 @@ defmodule Module.Types.Descr do
721721
{:term, static, []}
722722

723723
{dynamic, static} ->
724-
# Computing term_type?(difference(dynamic, static)) can be
725-
# expensive, so we check for term type before hand and check
726-
# for :term exclusively in dynamic_to_quoted/2.
727-
if term_type?(dynamic) do
728-
{:term, static, []}
729-
else
730-
# Denormalize functions before we do the difference
731-
{static, dynamic, extra} = fun_denormalize(static, dynamic, opts)
732-
{difference(dynamic, static), static, extra}
724+
cond do
725+
# Computing term_type?(difference(dynamic, static)) can be
726+
# expensive, so we check for term type before hand and check
727+
# for :term exclusively in dynamic_to_quoted/2.
728+
term_type?(dynamic) ->
729+
{:term, static, []}
730+
731+
# We need to check for quoted here before we compute the difference below
732+
quoted = maybe_negated_term_type_to_quoted(static, opts) ->
733+
{dynamic, %{}, [quoted]}
734+
735+
# Denormalize functions (only unions) before we do the difference
736+
true ->
737+
{static, dynamic, extra} = fun_denormalize(static, dynamic, opts)
738+
{difference(dynamic, static), static, extra}
733739
end
734740
end
735741

742+
{static, extra} =
743+
if quoted = maybe_negated_term_type_to_quoted(static, opts) do
744+
{%{}, [quoted | extra]}
745+
else
746+
{static, extra}
747+
end
748+
749+
# Dynamic always come first for visibility
750+
unions =
751+
to_quoted(:dynamic, dynamic, opts) ++ static_non_term_type_to_quoted(static, extra, opts)
752+
753+
case unions do
754+
[] -> {:none, [], []}
755+
unions -> Enum.reduce(unions, &{:or, [], [&2, &1]})
756+
end
757+
end
758+
759+
defp static_non_term_type_to_quoted(static, extra, opts) do
736760
# Merge empty list and list together if they both exist
737761
{extra, static} =
738762
case static do
@@ -748,17 +772,7 @@ defmodule Module.Types.Descr do
748772
{extra, static}
749773
end
750774

751-
# Dynamic always come first for visibility
752-
unions =
753-
to_quoted(:dynamic, dynamic, opts) ++
754-
Enum.sort(
755-
extra ++ Enum.flat_map(static, fn {key, value} -> to_quoted(key, value, opts) end)
756-
)
757-
758-
case unions do
759-
[] -> {:none, [], []}
760-
unions -> Enum.reduce(unions, &{:or, [], [&2, &1]})
761-
end
775+
Enum.sort(extra ++ Enum.flat_map(static, fn {key, value} -> to_quoted(key, value, opts) end))
762776
end
763777

764778
defp to_quoted(:atom, val, _opts), do: atom_to_quoted(val)
@@ -769,6 +783,54 @@ defmodule Module.Types.Descr do
769783
defp to_quoted(:tuple, bdd, opts), do: tuple_to_quoted(bdd, opts)
770784
defp to_quoted(:fun, bdd, opts), do: fun_to_quoted(bdd, opts)
771785

786+
defp maybe_negated_term_type_to_quoted(static, opts) do
787+
if print_as_negated_type?(static) do
788+
static
789+
|> negation()
790+
|> unfold()
791+
|> static_non_term_type_to_quoted([], opts)
792+
|> case do
793+
[] ->
794+
nil
795+
796+
[head | tail] ->
797+
Enum.reduce(tail, {:not, [], [head]}, &{:and, [], [&2, {:not, [], [&1]}]})
798+
end
799+
end
800+
end
801+
802+
# Unions would be printed as negations within negations
803+
defp print_as_negated_type?(%{atom: {:union, _}}), do: false
804+
defp print_as_negated_type?(%{fun: {:union, _}}), do: false
805+
806+
defp print_as_negated_type?(descr) do
807+
# In here we compute the score of negations.
808+
# If we have more than 6, we print it as a negated type.
809+
result =
810+
Enum.sum_by(Map.to_list(descr), fn
811+
{:tuple, bdd} -> print_as_negated_bdd(bdd, @tuple_top)
812+
{:map, bdd} -> print_as_negated_bdd(bdd, @map_top)
813+
{:list, bdd} -> print_as_negated_bdd(bdd, @non_empty_list_top)
814+
{:bitmap, bitmap} -> Integer.popcount(bitmap)
815+
{:atom, {:union, _}} -> -100
816+
{:fun, {:union, _}} -> -100
817+
{_, _} -> 1
818+
end)
819+
820+
result > 8
821+
end
822+
823+
# A bdd leaf can be trivially printed in negated format
824+
# but we don't count it towards the amount of negatives.
825+
defp print_as_negated_bdd(top, top), do: 1
826+
defp print_as_negated_bdd(bdd_leaf(_, _), _top), do: 0
827+
defp print_as_negated_bdd(bdd, top), do: if(negated_bdd?(bdd, top), do: 1, else: -100)
828+
829+
defp negated_bdd?({_, :bdd_bot, :bdd_bot, bdd}, top),
830+
do: bdd in [:bdd_top, top] or negated_bdd?(bdd, top)
831+
832+
defp negated_bdd?(_, _), do: false
833+
772834
@doc """
773835
Converts a descr to its quoted string representation.
774836
@@ -1738,7 +1800,7 @@ defmodule Module.Types.Descr do
17381800
end
17391801

17401802
# Converts the static and dynamic parts of descr to its quoted
1741-
# representation. The goal here is to the opposite of fun_descr
1803+
# representation. The goal here is to do the opposite of fun_descr
17421804
# and put static and dynamic parts back together to improve
17431805
# pretty printing.
17441806
defp fun_denormalize(%{fun: {:union, static_repr}}, %{fun: {:union, dynamic_repr}}, opts) do
@@ -2173,6 +2235,12 @@ defmodule Module.Types.Descr do
21732235
end
21742236
end
21752237

2238+
defp list_difference(bdd_leaf(:term, :term), bdd_leaf(:term, :term)),
2239+
do: :bdd_bot
2240+
2241+
defp list_difference(bdd_leaf(:term, :term), bdd2),
2242+
do: bdd_negation(bdd2)
2243+
21762244
# Computes the difference between two BDD (Binary Decision Diagram) list types.
21772245
# It progressively subtracts each type in bdd2 from all types in bdd1.
21782246
# The algorithm handles three cases:
@@ -2183,22 +2251,20 @@ defmodule Module.Types.Descr do
21832251
# 3. Base case: adds bdd2 type to negations of bdd1 type
21842252
# The result may be larger than the initial bdd1, which is maintained in the accumulator.
21852253
defp list_difference(bdd_leaf(list1, last1) = bdd1, bdd_leaf(list2, last2) = bdd2) do
2186-
cond do
2187-
disjoint?(list1, list2) or disjoint?(last1, last2) ->
2188-
bdd_leaf(list1, last1)
2189-
2190-
subtype?(list1, list2) ->
2191-
if subtype?(last1, last2),
2192-
do: :bdd_bot,
2193-
else: bdd_leaf(list1, difference(last1, last2))
2194-
2195-
true ->
2196-
bdd_difference(bdd1, bdd2)
2254+
if subtype?(list1, list2) do
2255+
if subtype?(last1, last2),
2256+
do: :bdd_bot,
2257+
else: bdd_leaf(list1, difference(last1, last2))
2258+
else
2259+
bdd_difference(bdd1, bdd2, &list_leaf_disjoint?/2)
21972260
end
21982261
end
21992262

22002263
defp list_difference(bdd1, bdd2),
2201-
do: bdd_difference(bdd1, bdd2)
2264+
do: bdd_difference(bdd1, bdd2, &list_leaf_disjoint?/2)
2265+
2266+
defp list_leaf_disjoint?(bdd_leaf(list1, last1), bdd_leaf(list2, last2)),
2267+
do: disjoint?(list1, list2) or disjoint?(last1, last2)
22022268

22032269
defp list_empty?(@non_empty_list_top), do: false
22042270

lib/elixir/test/elixir/module/types/descr_test.exs

Lines changed: 77 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2602,6 +2602,11 @@ defmodule Module.Types.DescrTest do
26022602
end
26032603

26042604
describe "to_quoted" do
2605+
test "none" do
2606+
assert none() |> to_quoted_string() == "none()"
2607+
assert dynamic(none()) |> to_quoted_string() == "none()"
2608+
end
2609+
26052610
test "bitmap" do
26062611
assert union(pid(), bitstring()) |> to_quoted_string() ==
26072612
"bitstring() or pid()"
@@ -2613,15 +2618,15 @@ defmodule Module.Types.DescrTest do
26132618
"(bitstring() and not binary()) or integer()"
26142619
end
26152620

2616-
test "none" do
2617-
assert none() |> to_quoted_string() == "none()"
2618-
assert dynamic(none()) |> to_quoted_string() == "none()"
2619-
end
2621+
test "bitmap (negation)" do
2622+
assert union(pid(), bitstring()) |> negation() |> to_quoted_string() ==
2623+
"not bitstring() and not pid()"
26202624

2621-
test "negation" do
2622-
assert negation(negation(integer())) |> to_quoted_string() == "integer()"
2623-
assert negation(negation(atom([:foo, :bar]))) |> to_quoted_string() == ":bar or :foo"
2624-
assert negation(negation(list(term()))) |> to_quoted_string() == "list(term())"
2625+
assert difference(bitstring(), binary())
2626+
|> union(integer())
2627+
|> negation()
2628+
|> to_quoted_string() ==
2629+
"not (bitstring() and not binary()) and not integer()"
26252630
end
26262631

26272632
test "atom" do
@@ -2641,6 +2646,15 @@ defmodule Module.Types.DescrTest do
26412646
assert difference(atom(), boolean()) |> to_quoted_string() == "atom() and not boolean()"
26422647
end
26432648

2649+
test "atom/boolean (negation)" do
2650+
assert atom() |> negation() |> to_quoted_string() == "not atom()"
2651+
assert atom([:a, :b]) |> negation() |> to_quoted_string() == "not :a and not :b"
2652+
assert boolean() |> negation() |> to_quoted_string() == "not boolean()"
2653+
2654+
assert atom([true, false, :a]) |> negation() |> to_quoted_string() ==
2655+
"not :a and not boolean()"
2656+
end
2657+
26442658
test "dynamic" do
26452659
assert dynamic() |> to_quoted_string() == "dynamic()"
26462660

@@ -2668,7 +2682,21 @@ defmodule Module.Types.DescrTest do
26682682
"dynamic(%{a: integer()})"
26692683
end
26702684

2671-
test "lists" do
2685+
test "dynamic (negation)" do
2686+
assert dynamic(negation(integer())) |> to_quoted_string() == "dynamic(not integer())"
2687+
assert negation(dynamic(integer())) |> to_quoted_string() == "dynamic() or not integer()"
2688+
2689+
assert union(atom(), dynamic(integer())) |> negation() |> to_quoted_string() ==
2690+
"dynamic(not atom()) or (not atom() and not integer())"
2691+
2692+
assert dynamic(union(atom(), integer()))
2693+
|> negation()
2694+
|> union(integer())
2695+
|> to_quoted_string() ==
2696+
"dynamic() or not atom()"
2697+
end
2698+
2699+
test "list" do
26722700
assert list(term()) |> to_quoted_string() == "list(term())"
26732701
assert list(integer()) |> to_quoted_string() == "list(integer())"
26742702

@@ -2720,7 +2748,18 @@ defmodule Module.Types.DescrTest do
27202748
assert to_quoted_string(list_with_tail) == "non_empty_list(atom())"
27212749
end
27222750

2723-
test "tuples" do
2751+
test "list (negation)" do
2752+
assert list(term()) |> negation() |> to_quoted_string() == "not list(term())"
2753+
assert list(negation(integer())) |> to_quoted_string() == "list(not integer())"
2754+
2755+
assert list(term()) |> difference(empty_list()) |> negation() |> to_quoted_string() ==
2756+
"not non_empty_list(term())"
2757+
2758+
assert non_empty_list(integer(), integer()) |> negation() |> to_quoted_string() ==
2759+
"not non_empty_list(integer(), integer())"
2760+
end
2761+
2762+
test "tuple" do
27242763
assert tuple([integer(), atom()]) |> to_quoted_string() == "{integer(), atom()}"
27252764

27262765
assert tuple([integer(), dynamic(atom())]) |> to_quoted_string() ==
@@ -2832,7 +2871,12 @@ defmodule Module.Types.DescrTest do
28322871
"""
28332872
end
28342873

2835-
test "function" do
2874+
test "tuple (negation)" do
2875+
assert tuple([integer()]) |> negation() |> to_quoted_string() == "not {integer()}"
2876+
assert tuple([negation(integer())]) |> to_quoted_string() == "{not integer()}"
2877+
end
2878+
2879+
test "fun" do
28362880
assert fun() |> to_quoted_string() == "fun()"
28372881
assert none_fun(1) |> to_quoted_string() == "(none() -> term())"
28382882

@@ -2868,7 +2912,7 @@ defmodule Module.Types.DescrTest do
28682912
"fun() and not (none(), none(), none() -> term())"
28692913
end
28702914

2871-
test "function with optimized intersections" do
2915+
test "fun with optimized intersections" do
28722916
assert fun([integer()], atom()) |> intersection(none_fun(1)) |> to_quoted_string() ==
28732917
"(integer() -> atom())"
28742918

@@ -2879,7 +2923,7 @@ defmodule Module.Types.DescrTest do
28792923
"(integer() -> atom())"
28802924
end
28812925

2882-
test "function with dynamic signatures" do
2926+
test "fun with dynamic signatures" do
28832927
assert fun([dynamic(integer())], float()) |> to_quoted_string() ==
28842928
"(dynamic(integer()) -> float())"
28852929

@@ -2912,6 +2956,11 @@ defmodule Module.Types.DescrTest do
29122956
"""
29132957
end
29142958

2959+
test "fun (negation)" do
2960+
assert fun([integer()], atom()) |> negation() |> to_quoted_string() ==
2961+
"not (integer() -> atom())"
2962+
end
2963+
29152964
test "map as records" do
29162965
assert empty_map() |> to_quoted_string() == "empty_map()"
29172966
assert open_map() |> to_quoted_string() == "map()"
@@ -3035,14 +3084,28 @@ defmodule Module.Types.DescrTest do
30353084
|> to_quoted_string() == "%{..., a: float(), b: atom(), c: port()}"
30363085
end
30373086

3038-
test "maps as dictionaries" do
3087+
test "map as dictionaries" do
30393088
assert closed_map([{domain_key(:integer), integer()}])
30403089
|> to_quoted_string() == "%{integer() => integer()}"
30413090

30423091
assert closed_map([{domain_key(:integer), not_set()}, {:float, float()}])
30433092
|> to_quoted_string() == "%{integer() => not_set(), float: float()}"
30443093
end
30453094

3095+
test "map (negation)" do
3096+
assert open_map(a: integer()) |> negation() |> to_quoted_string() ==
3097+
"not %{..., a: integer()}"
3098+
3099+
assert open_map(a: negation(integer())) |> to_quoted_string() ==
3100+
"%{..., a: not integer()}"
3101+
3102+
assert closed_map(a: integer()) |> negation() |> to_quoted_string() ==
3103+
"not %{a: integer()}"
3104+
3105+
assert closed_map(a: negation(integer())) |> to_quoted_string() ==
3106+
"%{a: not integer()}"
3107+
end
3108+
30463109
test "structs" do
30473110
assert open_map(__struct__: term()) |> to_quoted_string() ==
30483111
"%{..., __struct__: term()}"

0 commit comments

Comments
 (0)