Skip to content

Commit 1a58437

Browse files
committed
Surface errors earlier whenever possible
1 parent 0581d88 commit 1a58437

2 files changed

Lines changed: 38 additions & 46 deletions

File tree

lib/elixir/lib/module/types/pattern.ex

Lines changed: 12 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -458,26 +458,26 @@ defmodule Module.Types.Pattern do
458458

459459
# left = right
460460
defp of_pattern({:=, _meta, [_, _]} = match, path, stack, context) do
461-
{match, version, var} =
461+
{matches, version, var} =
462462
match
463463
|> unpack_match([])
464464
|> Enum.split_while(&(not is_versioned_var(&1)))
465465
|> case do
466-
{match, []} ->
466+
{matches, []} ->
467467
version = make_ref()
468-
{match, version, {:match, [version: version], __MODULE__}}
468+
{matches, version, {:match, [version: version], __MODULE__}}
469469

470470
{pre, [{_, meta, _} = var | post]} ->
471471
version = Keyword.fetch!(meta, :version)
472472
{pre ++ post, version, var}
473473
end
474474

475475
# Pass the current path to build the current var
476-
{expr, context} = of_var(var, version, path, context)
477-
root = %{root: {:var, version}, expr: expr}
476+
context = of_var(var, version, path, context)
477+
root = %{root: {:var, version}, expr: match}
478478

479479
{static, dynamic, context} =
480-
Enum.reduce(match, {[], [], context}, fn pattern, {static, dynamic, context} ->
480+
Enum.reduce(matches, {[], [], context}, fn pattern, {static, dynamic, context} ->
481481
{type, context} = of_pattern(pattern, [root], stack, context)
482482

483483
if is_descr(type) do
@@ -491,14 +491,13 @@ defmodule Module.Types.Pattern do
491491
{Enum.reduce(static, &intersection/2), context}
492492
else
493493
# The dynamic parts have to be recomputed whenever they change
494-
{var, context} =
495-
of_pattern(var, [%{root: {:intersection, dynamic}, expr: expr}], stack, context)
494+
context = of_var(var, version, [%{root: {:intersection, dynamic}, expr: match}], context)
496495

497-
# But the static parts we push down as part of the argument intersection
496+
# And everything else is also pushed as part of the argument intersection
498497
if static == [] do
499-
{var, context}
498+
{{:intersection, dynamic}, context}
500499
else
501-
{{:intersection, [var, Enum.reduce(static, &intersection/2)]}, context}
500+
{{:intersection, [Enum.reduce(static, &intersection/2) | dynamic]}, context}
502501
end
503502
end
504503
end
@@ -601,8 +600,7 @@ defmodule Module.Types.Pattern do
601600
defp of_pattern({name, meta, ctx} = var, path, _stack, context)
602601
when is_atom(name) and is_atom(ctx) do
603602
version = Keyword.fetch!(meta, :version)
604-
{_, context} = of_var(var, version, path, context)
605-
{{:var, version}, context}
603+
{{:var, version}, of_var(var, version, path, context)}
606604
end
607605

608606
defp is_versioned_var({name, _meta, ctx}) when is_atom(name) and is_atom(ctx) and name != :_,
@@ -636,7 +634,7 @@ defmodule Module.Types.Pattern do
636634
{args_paths, vars_paths, vars_deps}
637635
end
638636

639-
{expr, %{context | pattern_info: pattern_info}}
637+
%{context | pattern_info: pattern_info}
640638
end
641639

642640
# TODO: Properly traverse domain keys

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

Lines changed: 26 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -51,24 +51,6 @@ defmodule Module.Types.PatternTest do
5151
"""
5252
end
5353

54-
test "errors on conflicting refinements" do
55-
assert typeerror!([a = b, a = :foo, b = :bar], {a, b}) == ~l"""
56-
incompatible types assigned to "a":
57-
58-
dynamic(:foo) !~ dynamic(:bar)
59-
60-
where "a" was given the types:
61-
62-
# type: dynamic(:foo)
63-
# from: types_test.ex:LINE
64-
a = :foo
65-
66-
# type: dynamic(:bar)
67-
# from: types_test.ex:LINE
68-
a = b
69-
"""
70-
end
71-
7254
test "can be accessed even if they don't match" do
7355
assert typeerror!(
7456
(
@@ -126,32 +108,44 @@ defmodule Module.Types.PatternTest do
126108
x = {:ok, _} = {:error, _, _}
127109
"""
128110

129-
assert typeerror!([x = {:ok, y} = {:error, z, w}], {x, y, z, w}) == ~l"""
130-
this match will never succeed due to incompatible types:
111+
assert typeerror!([{x = {:ok, y} = {:error, z, w}}], {x, y, z, w}) == ~l"""
112+
the following pattern will never match:
131113
132-
x = {:ok, y} = {:error, z, w}
114+
{x = {:ok, y} = {:error, z, w}}
133115
"""
134116

135-
assert typeerror!([{:ok, y} = {:error, z, w}], {y, z, w}) == ~l"""
136-
this match will never succeed due to incompatible types:
117+
assert typeerror!([a = b, a = :foo, b = :bar], {a, b}) == ~l"""
118+
incompatible types assigned to "a":
137119
138-
{:ok, y} = {:error, z, w}
139-
"""
120+
dynamic(:foo) !~ dynamic(:bar)
140121
141-
assert typeerror!([x = {:ok, _}], [_ | _] = x) == ~l"""
142-
the following pattern will never match:
122+
where "a" was given the types:
143123
144-
[_ | _] = x
124+
# type: dynamic(:foo)
125+
# from: types_test.ex:LINE
126+
a = :foo
127+
128+
# type: dynamic(:bar)
129+
# from: types_test.ex:LINE
130+
a = b
131+
"""
145132

146-
because the right-hand side has type:
133+
assert typeerror!([{x, _} = {y, _}, x = :foo, y = :bar], {x, y}) == ~l"""
134+
this match will never succeed due to incompatible types:
147135
148-
dynamic({:ok, term()})
136+
{x, _} = {y, _}
149137
150138
where "x" was given the type:
151139
152-
# type: dynamic({:ok, term()})
140+
# type: dynamic(:foo)
141+
# from: types_test.ex:LINE
142+
x = :foo
143+
144+
where "y" was given the type:
145+
146+
# type: dynamic(:bar)
153147
# from: types_test.ex:LINE
154-
x = {:ok, _}
148+
y = :bar
155149
"""
156150
end
157151
end

0 commit comments

Comments
 (0)