Skip to content

Commit be0f3ae

Browse files
committed
Propagate types from guards to matches
1 parent aa22242 commit be0f3ae

File tree

2 files changed

+33
-26
lines changed

2 files changed

+33
-26
lines changed

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

Lines changed: 24 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,14 @@ defmodule Module.Types.Pattern do
2929
"""
3030
def of_head(patterns, guards, expected, tag, meta, stack, context) do
3131
stack = %{stack | meta: meta}
32-
{trees, context} = of_pattern_args(patterns, expected, tag, stack, context)
33-
context = of_guards(guards, stack, context)
34-
{trees, context}
32+
33+
case of_pattern_args(patterns, expected, tag, stack, context) do
34+
{trees, changed, context} ->
35+
{trees, of_changed(changed, stack, of_guards(guards, stack, context))}
36+
37+
{trees, context} ->
38+
{trees, context}
39+
end
3540
end
3641

3742
@doc """
@@ -52,21 +57,18 @@ defmodule Module.Types.Pattern do
5257
end
5358

5459
defp of_pattern_args([], [], _tag, _stack, context) do
55-
{[], context}
60+
{[], %{}, context}
5661
end
5762

5863
defp of_pattern_args(patterns, expected, tag, stack, context) do
5964
context = init_pattern_info(context)
6065
{trees, context} = of_pattern_args_zip(patterns, expected, 0, [], stack, context)
6166
{pattern_info, context} = pop_pattern_info(context)
6267

63-
context =
64-
case of_pattern_intersect(trees, 0, [], pattern_info, tag, stack, context) do
65-
{:ok, _types, context} -> context
66-
{:error, context} -> context
67-
end
68-
69-
{trees, context}
68+
case of_pattern_intersect(trees, 0, [], pattern_info, tag, stack, context) do
69+
{_types, changed, context} -> {trees, changed, context}
70+
{:error, context} -> {trees, context}
71+
end
7072
end
7173

7274
defp of_pattern_args_zip([pattern | tail], [expected | types], index, acc, stack, context) do
@@ -91,7 +93,7 @@ defmodule Module.Types.Pattern do
9193
tag = {:match, expected}
9294

9395
case of_pattern_intersect(args, 0, [], pattern_info, tag, stack, context) do
94-
{:ok, [type], context} -> {type, context}
96+
{[type], changed, context} -> {type, of_changed(changed, stack, context)}
9597
{:error, context} -> {expected, context}
9698
end
9799
end
@@ -105,13 +107,10 @@ defmodule Module.Types.Pattern do
105107
{pattern_info, context} = pop_pattern_info(context)
106108
args = [{tree, expected, pattern}]
107109

108-
context =
109-
case of_pattern_intersect(args, 0, [], pattern_info, tag, stack, context) do
110-
{:ok, _types, context} -> context
111-
{:error, context} -> context
112-
end
113-
114-
of_guards(guards, stack, context)
110+
case of_pattern_intersect(args, 0, [], pattern_info, tag, stack, context) do
111+
{_types, changed, context} -> of_changed(changed, stack, of_guards(guards, stack, context))
112+
{:error, context} -> context
113+
end
115114
end
116115

117116
defp of_pattern_intersect([head | tail], index, acc, pattern_info, tag, stack, context) do
@@ -169,28 +168,27 @@ defmodule Module.Types.Pattern do
169168
context -> {:error, error_vars(pattern_info, context)}
170169
else
171170
{changed, context} ->
172-
{:ok, types, of_pattern_recur(changed, stack, context)}
171+
{types, changed, context}
173172
end
174173
end
175174

176-
defp of_pattern_recur(changed, _stack, context)
177-
when changed == %{} do
175+
defp of_changed(changed, _stack, context) when changed == %{} do
178176
context
179177
end
180178

181-
defp of_pattern_recur(previous_changed, stack, context) do
179+
defp of_changed(previous_changed, stack, context) do
182180
{changed, context} =
183181
previous_changed
184182
|> Map.keys()
185183
|> Enum.reduce({%{}, context}, fn version, {changed, context} ->
186-
{new_deps, context} = of_pattern_recur_var(version, stack, context)
184+
{new_deps, context} = of_changed_var(version, stack, context)
187185
{Map.merge(changed, new_deps), context}
188186
end)
189187

190-
of_pattern_recur(changed, stack, context)
188+
of_changed(changed, stack, context)
191189
end
192190

193-
defp of_pattern_recur_var(version, stack, context) do
191+
defp of_changed_var(version, stack, context) do
194192
case context.vars do
195193
%{^version => %{type: old_type, deps: deps, paths: paths} = data}
196194
when not is_map_key(data, :errored) ->

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -602,6 +602,10 @@ defmodule Module.Types.PatternTest do
602602
assert typecheck!([x, y], :erlang.and(length(x) == 3, map_size(y) == 1), {x, y}) ==
603603
dynamic(tuple([list(term()), open_map()]))
604604

605+
# or with mixed checks
606+
assert typecheck!([x], length(x) == 3 or is_map(x), x) ==
607+
dynamic(list(term()))
608+
605609
# or does not propagate
606610
assert typecheck!([x, y], length(x) == 3 or map_size(y) == 1, {x, y}) ==
607611
dynamic(tuple([list(term()), term()]))
@@ -619,6 +623,11 @@ defmodule Module.Types.PatternTest do
619623
dynamic(tuple([list(term()), term()]))
620624
end
621625

626+
test "match propagation" do
627+
assert typecheck!([x = {:ok, y}], is_integer(y), x) ==
628+
dynamic(tuple([atom([:ok]), integer()]))
629+
end
630+
622631
test "errors in guards" do
623632
assert typeerror!([x = {}], is_integer(x), x) == ~l"""
624633
this guard will never succeed:

0 commit comments

Comments
 (0)