Skip to content

Commit 73fed09

Browse files
committed
Improvements to descr pretty printing
1 parent bcc36c5 commit 73fed09

File tree

2 files changed

+67
-59
lines changed

2 files changed

+67
-59
lines changed

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

Lines changed: 58 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -372,11 +372,18 @@ defmodule Module.Types.Descr do
372372
if term_type?(descr) do
373373
{:term, [], []}
374374
else
375+
# Dynamic always come first for visibility
376+
{dynamic, descr} =
377+
case :maps.take(:dynamic, descr) do
378+
:error -> {[], descr}
379+
{dynamic, descr} -> {to_quoted(:dynamic, dynamic), descr}
380+
end
381+
382+
# Merge empty list and list together if they both exist
375383
{extra, descr} =
376384
case descr do
377-
# Merge empty list and list together if they both exist
378385
%{list: list, bitmap: bitmap} when (bitmap &&& @bit_empty_list) != 0 ->
379-
descr = descr |> Map.delete(:list) |> Map.update!(:bitmap, &(&1 - @bit_empty_list))
386+
descr = descr |> Map.delete(:list) |> Map.replace!(:bitmap, bitmap - @bit_empty_list)
380387

381388
case list_to_quoted(list, :list) do
382389
[] -> {[{:empty_list, [], []}], descr}
@@ -387,9 +394,13 @@ defmodule Module.Types.Descr do
387394
{[], descr}
388395
end
389396

390-
case extra ++ Enum.flat_map(descr, fn {key, value} -> to_quoted(key, value) end) do
397+
unions =
398+
dynamic ++
399+
Enum.sort(extra ++ Enum.flat_map(descr, fn {key, value} -> to_quoted(key, value) end))
400+
401+
case unions do
391402
[] -> {:none, [], []}
392-
unions -> unions |> Enum.sort() |> Enum.reduce(&{:or, [], [&2, &1]})
403+
unions -> Enum.reduce(unions, &{:or, [], [&2, &1]})
393404
end
394405
end
395406
end
@@ -785,17 +796,16 @@ defmodule Module.Types.Descr do
785796

786797
defp atom_to_quoted({:union, a}) do
787798
if :sets.is_subset(@boolset, a) do
788-
:sets.subtract(a, @boolset)
789-
|> :sets.to_list()
790-
|> Enum.sort()
791-
|> Enum.reduce({:boolean, [], []}, &{:or, [], [&2, literal_to_quoted(&1)]})
799+
entries =
800+
:sets.subtract(a, @boolset)
801+
|> :sets.to_list()
802+
|> Enum.map(&literal_to_quoted/1)
803+
804+
[{:boolean, [], []} | entries]
792805
else
793806
:sets.to_list(a)
794-
|> Enum.sort()
795807
|> Enum.map(&literal_to_quoted/1)
796-
|> Enum.reduce(&{:or, [], [&2, &1]})
797808
end
798-
|> List.wrap()
799809
end
800810

801811
defp atom_to_quoted({:negation, a}) do
@@ -1064,11 +1074,7 @@ defmodule Module.Types.Descr do
10641074
|> Enum.reduce(&{:or, [], [&2, &1]})
10651075
|> Kernel.then(
10661076
&[
1067-
{:and, [],
1068-
[
1069-
{name, [], arguments},
1070-
{:not, [], [&1]}
1071-
]}
1077+
{:and, [], [{name, [], arguments}, {:not, [], [&1]}]}
10721078
| acc
10731079
]
10741080
)
@@ -1691,7 +1697,7 @@ defmodule Module.Types.Descr do
16911697
if map_empty_negation?(tag, acc_fields, neg) do
16921698
{acc_fields, acc_negs}
16931699
else
1694-
case all_but_one?(tag, acc_fields, neg_tag, neg_fields) do
1700+
case map_all_but_one?(tag, acc_fields, neg_tag, neg_fields) do
16951701
{:one, diff_key} ->
16961702
{Map.update!(acc_fields, diff_key, &difference(&1, neg_fields[diff_key])),
16971703
acc_negs}
@@ -1714,43 +1720,45 @@ defmodule Module.Types.Descr do
17141720
# 1. Group maps by tags and keys
17151721
# 2. Try fusions for each group until no fusion is found
17161722
# 3. Merge the groups back into a dnf
1717-
dnf
1718-
|> Enum.group_by(fn {tag, fields, _} -> {tag, Map.keys(fields)} end)
1719-
|> Enum.flat_map(fn {_, maps} -> fuse_maps(maps) end)
1723+
{without_negs, with_negs} = Enum.split_with(dnf, fn {_tag, _fields, negs} -> negs == [] end)
1724+
1725+
without_negs =
1726+
without_negs
1727+
|> Enum.group_by(fn {tag, fields, _} -> {tag, Map.keys(fields)} end)
1728+
|> Enum.flat_map(fn {_, maps} -> map_non_negated_fuse(maps) end)
1729+
1730+
without_negs ++ with_negs
17201731
end
17211732

1722-
defp fuse_maps(maps) do
1733+
defp map_non_negated_fuse(maps) do
17231734
Enum.reduce(maps, [], fn map, acc ->
1724-
case Enum.split_while(acc, &fusible_maps?(map, &1)) do
1735+
case Enum.split_while(acc, &non_fusible_maps?(map, &1)) do
17251736
{_, []} ->
17261737
[map | acc]
17271738

17281739
{others, [match | rest]} ->
1729-
fused = fuse_map_pair(map, match)
1740+
fused = map_non_negated_fuse_pair(map, match)
17301741
others ++ [fused | rest]
17311742
end
17321743
end)
17331744
end
17341745

1735-
# Two maps are fusible if they have no negations and differ in at most one element.
1736-
defp fusible_maps?({_, fields1, negs1}, {_, fields2, negs2}) do
1737-
negs1 != [] or negs2 != [] or
1738-
Map.keys(fields1)
1739-
|> Enum.count(fn key -> Map.get(fields1, key) != Map.get(fields2, key) end) > 1
1746+
# Two maps are fusible if they differ in at most one element.
1747+
defp non_fusible_maps?({_, fields1, []}, {_, fields2, []}) do
1748+
Enum.count_until(fields1, fn {key, value} -> Map.fetch!(fields2, key) != value end, 2) > 1
17401749
end
17411750

1742-
defp fuse_map_pair({tag, fields1, []}, {_, fields2, []}) do
1743-
fused_fields =
1744-
Map.new(fields1, fn {key, type1} ->
1745-
type2 = Map.get(fields2, key)
1746-
{key, if(type1 != type2, do: union(type1, type2), else: type1)}
1751+
defp map_non_negated_fuse_pair({tag, fields1, []}, {_, fields2, []}) do
1752+
fields =
1753+
symmetrical_merge(fields1, fields2, fn _k, v1, v2 ->
1754+
if v1 == v2, do: v1, else: union(v1, v2)
17471755
end)
17481756

1749-
{tag, fused_fields, []}
1757+
{tag, fields, []}
17501758
end
17511759

17521760
# If all fields are the same except one, we can optimize map difference.
1753-
defp all_but_one?(tag1, fields1, tag2, fields2) do
1761+
defp map_all_but_one?(tag1, fields1, tag2, fields2) do
17541762
keys1 = Map.keys(fields1)
17551763
keys2 = Map.keys(fields2)
17561764

@@ -1782,10 +1790,6 @@ defmodule Module.Types.Descr do
17821790
dnf
17831791
|> map_normalize()
17841792
|> Enum.map(&map_each_to_quoted/1)
1785-
|> case do
1786-
[] -> []
1787-
dnf -> Enum.reduce(dnf, &{:or, [], [&2, &1]}) |> List.wrap()
1788-
end
17891793
end
17901794

17911795
defp map_each_to_quoted({tag, positive_map, negative_maps}) do
@@ -1970,10 +1974,6 @@ defmodule Module.Types.Descr do
19701974
|> tuple_simplify()
19711975
|> tuple_fusion()
19721976
|> Enum.map(&tuple_each_to_quoted/1)
1973-
|> case do
1974-
[] -> []
1975-
dnf -> Enum.reduce(dnf, &{:or, [], [&2, &1]}) |> List.wrap()
1976-
end
19771977
end
19781978

19791979
# Given a dnf of tuples, fuses the tuple unions when possible,
@@ -1985,34 +1985,37 @@ defmodule Module.Types.Descr do
19851985
# 2. Group tuples by size and tag
19861986
# 3. Try fusions for each group until no fusion is found
19871987
# 4. Merge the groups back into a dnf
1988-
dnf
1989-
|> Enum.group_by(fn {tag, elems, _} -> {tag, length(elems)} end)
1990-
|> Enum.flat_map(fn {_, tuples} -> fuse_tuples(tuples) end)
1988+
{without_negs, with_negs} = Enum.split_with(dnf, fn {_tag, _elems, negs} -> negs == [] end)
1989+
1990+
without_negs =
1991+
without_negs
1992+
|> Enum.group_by(fn {tag, elems, _} -> {tag, length(elems)} end)
1993+
|> Enum.flat_map(fn {_, tuples} -> tuple_non_negated_fuse(tuples) end)
1994+
1995+
without_negs ++ with_negs
19911996
end
19921997

1993-
defp fuse_tuples(tuples) do
1998+
defp tuple_non_negated_fuse(tuples) do
19941999
Enum.reduce(tuples, [], fn tuple, acc ->
1995-
case Enum.split_while(acc, &fusible_tuples?(tuple, &1)) do
2000+
case Enum.split_while(acc, &non_fusible_tuples?(tuple, &1)) do
19962001
{_, []} ->
19972002
[tuple | acc]
19982003

19992004
{others, [match | rest]} ->
2000-
fused = fuse_tuple_pair(tuple, match)
2005+
fused = tuple_non_negated_fuse_pair(tuple, match)
20012006
others ++ [fused | rest]
20022007
end
20032008
end)
20042009
end
20052010

20062011
# Two tuples are fusible if they have no negations and differ in at most one element.
2007-
defp fusible_tuples?({_, elems1, negs1}, {_, elems2, negs2}) do
2008-
negs1 != [] or negs2 != [] or
2009-
Enum.zip(elems1, elems2) |> Enum.count(fn {a, b} -> a != b end) > 1
2012+
defp non_fusible_tuples?({_, elems1, []}, {_, elems2, []}) do
2013+
Enum.zip(elems1, elems2) |> Enum.count_until(fn {a, b} -> a != b end, 2) > 1
20102014
end
20112015

2012-
defp fuse_tuple_pair({tag, elems1, []}, {_, elems2, []}) do
2016+
defp tuple_non_negated_fuse_pair({tag, elems1, []}, {_, elems2, []}) do
20132017
fused_elements =
2014-
Enum.zip(elems1, elems2)
2015-
|> Enum.map(fn {a, b} -> if a != b, do: union(a, b), else: a end)
2018+
Enum.zip_with(elems1, elems2, fn a, b -> if a == b, do: a, else: union(a, b) end)
20162019

20172020
{tag, fused_elements, []}
20182021
end

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

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1184,7 +1184,7 @@ defmodule Module.Types.DescrTest do
11841184

11851185
test "boolean" do
11861186
assert boolean() |> to_quoted_string() == "boolean()"
1187-
assert atom([true, false, :a]) |> to_quoted_string() == "boolean() or :a"
1187+
assert atom([true, false, :a]) |> to_quoted_string() == ":a or boolean()"
11881188
assert atom([true, :a]) |> to_quoted_string() == ":a or true"
11891189
assert difference(atom(), boolean()) |> to_quoted_string() == "atom() and not boolean()"
11901190
end
@@ -1199,7 +1199,7 @@ defmodule Module.Types.DescrTest do
11991199
assert intersection(atom(), dynamic()) |> to_quoted_string() == "dynamic(atom())"
12001200

12011201
assert union(atom([:foo, :bar]), dynamic()) |> to_quoted_string() ==
1202-
"dynamic() or (:bar or :foo)"
1202+
"dynamic() or :bar or :foo"
12031203

12041204
assert intersection(dynamic(), closed_map(a: integer())) |> to_quoted_string() ==
12051205
"dynamic(%{a: integer()})"
@@ -1256,7 +1256,7 @@ defmodule Module.Types.DescrTest do
12561256
assert open_tuple([integer(), atom()]) |> to_quoted_string() == "{integer(), atom(), ...}"
12571257

12581258
assert union(tuple([integer(), atom()]), open_tuple([atom()])) |> to_quoted_string() ==
1259-
"{integer(), atom()} or {atom(), ...}"
1259+
"{atom(), ...} or {integer(), atom()}"
12601260

12611261
assert difference(tuple([integer(), atom()]), open_tuple([atom()])) |> to_quoted_string() ==
12621262
"{integer(), atom()}"
@@ -1352,7 +1352,12 @@ defmodule Module.Types.DescrTest do
13521352
)
13531353
|> dynamic()
13541354
|> to_quoted_string() ==
1355-
"dynamic(\n :error or\n ({%Decimal{coef: integer() or (:NaN or :inf), exp: integer(), sign: integer()}, term()} or\n {%Decimal{coef: :NaN or :inf, exp: integer(), sign: integer()}, binary()})\n)"
1355+
"""
1356+
dynamic(
1357+
:error or {%Decimal{coef: :NaN or :inf, exp: integer(), sign: integer()}, binary()} or
1358+
{%Decimal{coef: :NaN or :inf or integer(), exp: integer(), sign: integer()}, term()}
1359+
)\
1360+
"""
13561361
end
13571362

13581363
test "map" do

0 commit comments

Comments
 (0)