Skip to content

Commit 2251b1e

Browse files
committed
Collapse structs in type warnings by default
1 parent c58755d commit 2251b1e

File tree

7 files changed

+72
-15
lines changed

7 files changed

+72
-15
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1982,7 +1982,7 @@ defmodule Module.Types.Descr do
19821982
[_ | _] = info <- maybe_struct(struct),
19831983
true <- map_size(fields) == length(info) + 1,
19841984
true <- Enum.all?(info, &is_map_key(fields, &1.field)) do
1985-
collapse? = Keyword.get(opts, :collapse_structs, false)
1985+
collapse? = Keyword.get(opts, :collapse_structs, true)
19861986

19871987
fields =
19881988
for %{field: field} <- info,

lib/elixir/lib/module/types/expr.ex

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -806,11 +806,11 @@ defmodule Module.Types.Expr do
806806
"""
807807
expected a map with key #{inspect(key)} in map update syntax:
808808
809-
#{expr_to_string(expr) |> indent(4)}
809+
#{expr_to_string(expr, collapse_structs: false) |> indent(4)}
810810
811811
but got type:
812812
813-
#{to_quoted_string(type) |> indent(4)}
813+
#{to_quoted_string(type, collapse_structs: false) |> indent(4)}
814814
""",
815815
format_traces(traces)
816816
])

lib/elixir/lib/module/types/helpers.ex

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -251,8 +251,8 @@ defmodule Module.Types.Helpers do
251251
252252
We also undo some macro expressions done by the Kernel module.
253253
"""
254-
def expr_to_string(expr) do
255-
string = prewalk_expr_to_string(expr)
254+
def expr_to_string(expr, opts \\ []) do
255+
string = prewalk_expr_to_string(expr, opts)
256256

257257
case expr do
258258
{_, meta, _} ->
@@ -275,7 +275,9 @@ defmodule Module.Types.Helpers do
275275
end
276276
end
277277

278-
defp prewalk_expr_to_string(expr) do
278+
defp prewalk_expr_to_string(expr, opts) do
279+
collapse_structs? = Keyword.get(opts, :collapse_structs, true)
280+
279281
expr
280282
|> Macro.prewalk(fn
281283
{:%, _, [Range, {:%{}, _, fields}]} = node ->
@@ -289,6 +291,26 @@ defmodule Module.Types.Helpers do
289291
node
290292
end
291293

294+
{:%, struct_meta, [struct, {:%{}, map_meta, fields}]} = node
295+
when collapse_structs? ->
296+
try do
297+
struct.__info__(:struct)
298+
rescue
299+
_ -> node
300+
else
301+
infos ->
302+
filtered =
303+
for {field, value} <- fields, not matches_default?(infos, field, value) do
304+
{field, value}
305+
end
306+
307+
if length(fields) != length(filtered) do
308+
{:%, struct_meta, [struct, {:%{}, map_meta, [{:..., [], []} | filtered]}]}
309+
else
310+
node
311+
end
312+
end
313+
292314
{{:., _, [Elixir.String.Chars, :to_string]}, meta, [arg]} ->
293315
{:to_string, meta, [arg]}
294316

@@ -340,6 +362,13 @@ defmodule Module.Types.Helpers do
340362
|> Macro.to_string()
341363
end
342364

365+
defp matches_default?(infos, field, value) do
366+
case Enum.find(infos, &(&1.field == field)) do
367+
%{default: default} -> Macro.escape(default) == value
368+
_ -> false
369+
end
370+
end
371+
343372
defp erl_to_ex(
344373
:erlang,
345374
:error,

lib/elixir/lib/module/types/of.ex

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -615,11 +615,11 @@ defmodule Module.Types.Of do
615615
"""
616616
unknown key .#{key} in expression:
617617
618-
#{expr_to_string(expr) |> indent(4)}
618+
#{expr_to_string(expr, collapse_structs: false) |> indent(4)}
619619
620620
the given type does not have the given key:
621621
622-
#{to_quoted_string(type) |> indent(4)}
622+
#{to_quoted_string(type, collapse_structs: false) |> indent(4)}
623623
""",
624624
format_traces(traces)
625625
])

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1557,15 +1557,15 @@ defmodule Module.Types.DescrTest do
15571557
"%{__struct__: Another or URI}"
15581558

15591559
assert closed_map(__struct__: atom([Decimal]), coef: term(), exp: term(), sign: term())
1560-
|> to_quoted_string() ==
1560+
|> to_quoted_string(collapse_structs: false) ==
15611561
"%Decimal{sign: term(), coef: term(), exp: term()}"
15621562

15631563
assert closed_map(__struct__: atom([Decimal]), coef: term(), exp: term(), sign: term())
1564-
|> to_quoted_string(collapse_structs: true) ==
1564+
|> to_quoted_string() ==
15651565
"%Decimal{}"
15661566

15671567
assert closed_map(__struct__: atom([Decimal]), coef: term(), exp: term(), sign: integer())
1568-
|> to_quoted_string(collapse_structs: true) ==
1568+
|> to_quoted_string() ==
15691569
"%Decimal{sign: integer()}"
15701570

15711571
# Does not fuse structs

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

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -940,6 +940,34 @@ defmodule Module.Types.ExprTest do
940940

941941
assert [%{type: :variable, name: :x}] = diagnostic.details.typing_traces
942942
end
943+
944+
test "inspect struct definition" do
945+
assert typeerror!(
946+
(
947+
p = %Point{x: 123}
948+
Integer.to_string(p)
949+
)
950+
)
951+
|> strip_ansi() == ~l"""
952+
incompatible types given to Integer.to_string/1:
953+
954+
Integer.to_string(p)
955+
956+
given types:
957+
958+
%Point{x: integer(), y: nil, z: integer()}
959+
960+
but expected one of:
961+
962+
integer()
963+
964+
where "p" was given the type:
965+
966+
# type: %Point{x: integer(), y: nil, z: integer()}
967+
# from: types_test.ex:947
968+
p = %Point{..., x: 123}
969+
"""
970+
end
943971
end
944972

945973
describe "comparison" do

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -443,7 +443,7 @@ defmodule Module.Types.IntegrationTest do
443443
444444
because it is expected to receive type:
445445
446-
dynamic(%Range{first: term(), last: term(), step: term()})
446+
dynamic(%Range{})
447447
448448
typing violation found at:
449449
@@ -475,7 +475,7 @@ defmodule Module.Types.IntegrationTest do
475475
476476
it has type:
477477
478-
-dynamic(%Range{first: term(), last: term(), step: term()})-
478+
-dynamic(%Range{})-
479479
480480
but expected a type that implements the String.Chars protocol, it must be one of:
481481
@@ -499,7 +499,7 @@ defmodule Module.Types.IntegrationTest do
499499
500500
given types:
501501
502-
-dynamic(%Range{first: term(), last: term(), step: term()})-
502+
-dynamic(%Range{})-
503503
504504
but expected a type that implements the String.Chars protocol, it must be one of:
505505
@@ -538,7 +538,7 @@ defmodule Module.Types.IntegrationTest do
538538
539539
it has type:
540540
541-
-dynamic(%Date{year: term(), month: term(), day: term(), calendar: term()})-
541+
-dynamic(%Date{})-
542542
543543
but expected a type that implements the Enumerable protocol, it must be one of:
544544

0 commit comments

Comments
 (0)