Skip to content

Commit c6597bc

Browse files
committed
Collapse structs for better pretty printing in different scenarios
1 parent ca6edfd commit c6597bc

File tree

6 files changed

+92
-53
lines changed

6 files changed

+92
-53
lines changed

Diff for: lib/elixir/lib/module/types/apply.ex

+1-1
Original file line numberDiff line numberDiff line change
@@ -944,7 +944,7 @@ defmodule Module.Types.Apply do
944944
defp type_comparison_to_string(fun, left, right) do
945945
{Kernel, fun, [left, right], _} = :elixir_rewrite.erl_to_ex(:erlang, fun, [left, right])
946946

947-
{fun, [], [to_quoted(left), to_quoted(right)]}
947+
{fun, [], [to_quoted(left, collapse_structs: true), to_quoted(right, collapse_structs: true)]}
948948
|> Code.Formatter.to_algebra()
949949
|> Inspect.Algebra.format(98)
950950
|> IO.iodata_to_binary()

Diff for: lib/elixir/lib/module/types/descr.ex

+76-43
Original file line numberDiff line numberDiff line change
@@ -367,16 +367,21 @@ defmodule Module.Types.Descr do
367367

368368
@doc """
369369
Converts a descr to its quoted representation.
370+
371+
## Options
372+
373+
* `:collapse_structs` - do not show struct fields that match
374+
their default type
370375
"""
371-
def to_quoted(descr) do
376+
def to_quoted(descr, opts \\ []) do
372377
if term_type?(descr) do
373378
{:term, [], []}
374379
else
375380
# Dynamic always come first for visibility
376381
{dynamic, descr} =
377382
case :maps.take(:dynamic, descr) do
378383
:error -> {[], descr}
379-
{dynamic, descr} -> {to_quoted(:dynamic, dynamic), descr}
384+
{dynamic, descr} -> {to_quoted(:dynamic, dynamic, opts), descr}
380385
end
381386

382387
# Merge empty list and list together if they both exist
@@ -385,7 +390,7 @@ defmodule Module.Types.Descr do
385390
%{list: list, bitmap: bitmap} when (bitmap &&& @bit_empty_list) != 0 ->
386391
descr = descr |> Map.delete(:list) |> Map.replace!(:bitmap, bitmap - @bit_empty_list)
387392

388-
case list_to_quoted(list, :list) do
393+
case list_to_quoted(list, :list, opts) do
389394
[] -> {[{:empty_list, [], []}], descr}
390395
unions -> {unions, descr}
391396
end
@@ -396,7 +401,9 @@ defmodule Module.Types.Descr do
396401

397402
unions =
398403
dynamic ++
399-
Enum.sort(extra ++ Enum.flat_map(descr, fn {key, value} -> to_quoted(key, value) end))
404+
Enum.sort(
405+
extra ++ Enum.flat_map(descr, fn {key, value} -> to_quoted(key, value, opts) end)
406+
)
400407

401408
case unions do
402409
[] -> {:none, [], []}
@@ -405,19 +412,19 @@ defmodule Module.Types.Descr do
405412
end
406413
end
407414

408-
defp to_quoted(:atom, val), do: atom_to_quoted(val)
409-
defp to_quoted(:bitmap, val), do: bitmap_to_quoted(val)
410-
defp to_quoted(:dynamic, descr), do: dynamic_to_quoted(descr)
411-
defp to_quoted(:map, dnf), do: map_to_quoted(dnf)
412-
defp to_quoted(:list, dnf), do: list_to_quoted(dnf, :non_empty_list)
413-
defp to_quoted(:tuple, dnf), do: tuple_to_quoted(dnf)
415+
defp to_quoted(:atom, val, _opts), do: atom_to_quoted(val)
416+
defp to_quoted(:bitmap, val, _opts), do: bitmap_to_quoted(val)
417+
defp to_quoted(:dynamic, descr, opts), do: dynamic_to_quoted(descr, opts)
418+
defp to_quoted(:map, dnf, opts), do: map_to_quoted(dnf, opts)
419+
defp to_quoted(:list, dnf, opts), do: list_to_quoted(dnf, :non_empty_list, opts)
420+
defp to_quoted(:tuple, dnf, opts), do: tuple_to_quoted(dnf, opts)
414421

415422
@doc """
416423
Converts a descr to its quoted string representation.
417424
"""
418-
def to_quoted_string(descr) do
425+
def to_quoted_string(descr, opts \\ []) do
419426
descr
420-
|> to_quoted()
427+
|> to_quoted(opts)
421428
|> Code.Formatter.to_algebra()
422429
|> Inspect.Algebra.format(98)
423430
|> IO.iodata_to_binary()
@@ -1045,16 +1052,16 @@ defmodule Module.Types.Descr do
10451052
end
10461053
end
10471054

1048-
defp list_to_quoted(dnf, name) do
1055+
defp list_to_quoted(dnf, name, opts) do
10491056
dnf = list_normalize(dnf)
10501057

10511058
for {list_type, last_type, negs} <- dnf, reduce: [] do
10521059
acc ->
10531060
arguments =
10541061
if subtype?(last_type, @empty_list) do
1055-
[to_quoted(list_type)]
1062+
[to_quoted(list_type, opts)]
10561063
else
1057-
[to_quoted(list_type), to_quoted(last_type)]
1064+
[to_quoted(list_type, opts), to_quoted(last_type, opts)]
10581065
end
10591066

10601067
if negs == [] do
@@ -1064,9 +1071,9 @@ defmodule Module.Types.Descr do
10641071
|> Enum.map(fn {ty, lst} ->
10651072
args =
10661073
if subtype?(lst, @empty_list) do
1067-
[to_quoted(ty)]
1074+
[to_quoted(ty, opts)]
10681075
else
1069-
[to_quoted(ty), to_quoted(lst)]
1076+
[to_quoted(ty, opts), to_quoted(lst, opts)]
10701077
end
10711078

10721079
{name, [], args}
@@ -1176,7 +1183,7 @@ defmodule Module.Types.Descr do
11761183
end
11771184
end
11781185

1179-
defp dynamic_to_quoted(descr) do
1186+
defp dynamic_to_quoted(descr, opts) do
11801187
cond do
11811188
term_type?(descr) ->
11821189
[{:dynamic, [], []}]
@@ -1185,7 +1192,7 @@ defmodule Module.Types.Descr do
11851192
[single]
11861193

11871194
true ->
1188-
case to_quoted(descr) do
1195+
case to_quoted(descr, opts) do
11891196
{:none, _meta, []} = none -> [none]
11901197
descr -> [{:dynamic, [], [descr]}]
11911198
end
@@ -1786,51 +1793,77 @@ defmodule Module.Types.Descr do
17861793
end))
17871794
end
17881795

1789-
defp map_to_quoted(dnf) do
1796+
defp map_to_quoted(dnf, opts) do
17901797
dnf
17911798
|> map_normalize()
1792-
|> Enum.map(&map_each_to_quoted/1)
1799+
|> Enum.map(&map_each_to_quoted(&1, opts))
17931800
end
17941801

1795-
defp map_each_to_quoted({tag, positive_map, negative_maps}) do
1802+
defp map_each_to_quoted({tag, positive_map, negative_maps}, opts) do
17961803
case negative_maps do
17971804
[] ->
1798-
map_literal_to_quoted({tag, positive_map})
1805+
map_literal_to_quoted({tag, positive_map}, opts)
17991806

18001807
_ ->
18011808
negative_maps
1802-
|> Enum.map(&map_literal_to_quoted/1)
1809+
|> Enum.map(&map_literal_to_quoted(&1, opts))
18031810
|> Enum.reduce(&{:or, [], [&2, &1]})
18041811
|> Kernel.then(
1805-
&{:and, [], [map_literal_to_quoted({tag, positive_map}), {:not, [], [&1]}]}
1812+
&{:and, [], [map_literal_to_quoted({tag, positive_map}, opts), {:not, [], [&1]}]}
18061813
)
18071814
end
18081815
end
18091816

1810-
def map_literal_to_quoted({:closed, fields}) when map_size(fields) == 0 do
1817+
def map_literal_to_quoted({:closed, fields}, _opts) when map_size(fields) == 0 do
18111818
{:empty_map, [], []}
18121819
end
18131820

1814-
def map_literal_to_quoted({tag, fields}) do
1821+
def map_literal_to_quoted({tag, fields}, opts) do
18151822
case tag do
18161823
:closed ->
18171824
with %{__struct__: struct_descr} <- fields,
18181825
{_, [struct]} <- atom_fetch(struct_descr) do
1826+
fields = Map.delete(fields, :__struct__)
1827+
1828+
fields =
1829+
with true <- Keyword.get(opts, :collapse_structs, false),
1830+
[_ | _] = info <- maybe_struct(struct),
1831+
true <- Enum.all?(info, &is_map_key(fields, &1.field)) do
1832+
Enum.reduce(info, fields, fn %{field: field}, acc ->
1833+
# TODO: This should consider the struct default value
1834+
if Map.fetch!(acc, field) == term() do
1835+
Map.delete(acc, field)
1836+
else
1837+
acc
1838+
end
1839+
end)
1840+
else
1841+
_ -> fields
1842+
end
1843+
18191844
{:%, [],
18201845
[
18211846
literal_to_quoted(struct),
1822-
{:%{}, [], map_fields_to_quoted(tag, Map.delete(fields, :__struct__))}
1847+
{:%{}, [], map_fields_to_quoted(tag, fields, opts)}
18231848
]}
18241849
else
1825-
_ -> {:%{}, [], map_fields_to_quoted(tag, fields)}
1850+
_ -> {:%{}, [], map_fields_to_quoted(tag, fields, opts)}
18261851
end
18271852

18281853
:open ->
1829-
{:%{}, [], [{:..., [], nil} | map_fields_to_quoted(tag, fields)]}
1854+
{:%{}, [], [{:..., [], nil} | map_fields_to_quoted(tag, fields, opts)]}
1855+
end
1856+
end
1857+
1858+
defp maybe_struct(struct) do
1859+
try do
1860+
struct.__info__(:struct)
1861+
rescue
1862+
_ -> nil
18301863
end
18311864
end
18321865

1833-
defp map_fields_to_quoted(tag, map) do
1866+
defp map_fields_to_quoted(tag, map, opts) do
18341867
sorted = Enum.sort(Map.to_list(map))
18351868
keyword? = Inspect.List.keyword?(sorted)
18361869

@@ -1846,9 +1879,9 @@ defmodule Module.Types.Descr do
18461879
{optional?, type} = pop_optional_static(type)
18471880

18481881
cond do
1849-
not optional? -> {key, to_quoted(type)}
1882+
not optional? -> {key, to_quoted(type, opts)}
18501883
empty?(type) -> {key, {:not_set, [], []}}
1851-
true -> {key, {:if_set, [], [to_quoted(type)]}}
1884+
true -> {key, {:if_set, [], [to_quoted(type, opts)]}}
18521885
end
18531886
end
18541887
end
@@ -1969,11 +2002,11 @@ defmodule Module.Types.Descr do
19692002
# This is a cheap optimization that relies on structural equality.
19702003
defp tuple_union(left, right), do: left ++ (right -- left)
19712004

1972-
defp tuple_to_quoted(dnf) do
2005+
defp tuple_to_quoted(dnf, opts) do
19732006
dnf
19742007
|> tuple_simplify()
19752008
|> tuple_fusion()
1976-
|> Enum.map(&tuple_each_to_quoted/1)
2009+
|> Enum.map(&tuple_each_to_quoted(&1, opts))
19772010
end
19782011

19792012
# Given a dnf of tuples, fuses the tuple unions when possible,
@@ -2020,27 +2053,27 @@ defmodule Module.Types.Descr do
20202053
{tag, fused_elements, []}
20212054
end
20222055

2023-
defp tuple_each_to_quoted({tag, positive_tuple, negative_tuples}) do
2056+
defp tuple_each_to_quoted({tag, positive_tuple, negative_tuples}, opts) do
20242057
case negative_tuples do
20252058
[] ->
2026-
tuple_literal_to_quoted({tag, positive_tuple})
2059+
tuple_literal_to_quoted({tag, positive_tuple}, opts)
20272060

20282061
_ ->
20292062
negative_tuples
2030-
|> Enum.map(&tuple_literal_to_quoted/1)
2063+
|> Enum.map(&tuple_literal_to_quoted(&1, opts))
20312064
|> Enum.reduce(&{:or, [], [&2, &1]})
20322065
|> Kernel.then(
2033-
&{:and, [], [tuple_literal_to_quoted({tag, positive_tuple}), {:not, [], [&1]}]}
2066+
&{:and, [], [tuple_literal_to_quoted({tag, positive_tuple}, opts), {:not, [], [&1]}]}
20342067
)
20352068
end
20362069
end
20372070

2038-
defp tuple_literal_to_quoted({:closed, []}), do: {:{}, [], []}
2071+
defp tuple_literal_to_quoted({:closed, []}, _opts), do: {:{}, [], []}
20392072

2040-
defp tuple_literal_to_quoted({tag, elements}) do
2073+
defp tuple_literal_to_quoted({tag, elements}, opts) do
20412074
case tag do
2042-
:closed -> {:{}, [], Enum.map(elements, &to_quoted/1)}
2043-
:open -> {:{}, [], Enum.map(elements, &to_quoted/1) ++ [{:..., [], nil}]}
2075+
:closed -> {:{}, [], Enum.map(elements, &to_quoted(&1, opts))}
2076+
:open -> {:{}, [], Enum.map(elements, &to_quoted(&1, opts)) ++ [{:..., [], nil}]}
20442077
end
20452078
end
20462079

Diff for: lib/elixir/lib/module/types/helpers.ex

+1-1
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ defmodule Module.Types.Helpers do
141141
column: meta[:column],
142142
hints: formatter_hints ++ expr_hints(expr),
143143
formatted_expr: formatted_expr,
144-
formatted_type: Module.Types.Descr.to_quoted_string(type)
144+
formatted_type: Module.Types.Descr.to_quoted_string(type, collapse_structs: true)
145145
}
146146
end)
147147
|> Enum.sort_by(&{&1.line, &1.column})

Diff for: lib/elixir/lib/module/types/of.ex

+1-3
Original file line numberDiff line numberDiff line change
@@ -495,13 +495,11 @@ defmodule Module.Types.Of do
495495
unknown key .#{key} in expression:
496496
497497
#{expr_to_string(expr) |> indent(4)}
498-
""",
499-
empty_if(dot_var?(expr), """
500498
501499
the given type does not have the given key:
502500
503501
#{to_quoted_string(type) |> indent(4)}
504-
"""),
502+
""",
505503
format_traces(traces)
506504
])
507505
}

Diff for: lib/elixir/test/elixir/module/types/expr_test.exs

+8-4
Original file line numberDiff line numberDiff line change
@@ -798,9 +798,9 @@ defmodule Module.Types.ExprTest do
798798
799799
x.foo_bar
800800
801-
where "x" was given the type:
801+
the given type does not have the given key:
802802
803-
# type: dynamic(%URI{
803+
dynamic(%URI{
804804
authority: term(),
805805
fragment: term(),
806806
host: term(),
@@ -810,6 +810,10 @@ defmodule Module.Types.ExprTest do
810810
scheme: term(),
811811
userinfo: term()
812812
})
813+
814+
where "x" was given the type:
815+
816+
# type: dynamic(%URI{})
813817
# from: types_test.ex:LINE-4:43
814818
x = %URI{}
815819
"""
@@ -903,7 +907,7 @@ defmodule Module.Types.ExprTest do
903907
904908
given types:
905909
906-
dynamic(:foo) <= dynamic(%Point{x: term(), y: term(), z: term()})
910+
dynamic(:foo) <= dynamic(%Point{})
907911
908912
where "mod" was given the type:
909913
@@ -919,7 +923,7 @@ defmodule Module.Types.ExprTest do
919923
920924
where "y" was given the type:
921925
922-
# type: dynamic(%Point{x: term(), y: term(), z: term()})
926+
# type: dynamic(%Point{})
923927
# from: types_test.ex:LINE-2
924928
y = %Point{}
925929

Diff for: lib/elixir/test/elixir/module/types/pattern_test.exs

+5-1
Original file line numberDiff line numberDiff line change
@@ -166,9 +166,13 @@ defmodule Module.Types.PatternTest do
166166
167167
x.foo_bar
168168
169+
the given type does not have the given key:
170+
171+
dynamic(%Point{x: term(), y: term(), z: term()})
172+
169173
where "x" was given the type:
170174
171-
# type: dynamic(%Point{x: term(), y: term(), z: term()})
175+
# type: dynamic(%Point{})
172176
# from: types_test.ex:LINE-1
173177
x = %Point{}
174178
"""

0 commit comments

Comments
 (0)