Skip to content

Commit 5625993

Browse files
committed
Do full initial traversal of args paths
1 parent 19c628a commit 5625993

2 files changed

Lines changed: 82 additions & 56 deletions

File tree

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

Lines changed: 52 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -160,67 +160,61 @@ defmodule Module.Types.Pattern do
160160
{args_paths, vars_paths, vars_deps} = pattern_info
161161

162162
try do
163-
Enum.map_reduce(args_paths, context, fn {version, paths}, context ->
164-
context =
165-
Enum.reduce(paths, context, fn
166-
%{var: var, expr: expr, root: {:arg, index}, path: path}, context ->
167-
actual = Enum.fetch!(types, index)
163+
args_paths
164+
|> Enum.reverse()
165+
|> Enum.reduce({%{}, context}, fn {version, node}, {changed, context} ->
166+
%{var: var, expr: expr, root: root, path: path} = node
167+
168+
{actual, index} =
169+
case root do
170+
{:arg, index} -> {Enum.fetch!(types, index), index}
171+
_ -> {of_pattern_tree(root, context), nil}
172+
end
168173

169-
case of_pattern_var(path, actual, context) do
170-
{:ok, new_type} ->
171-
case Of.refine_head_var(var, new_type, expr, stack, context) do
172-
{:ok, _type, context} ->
173-
context
174-
175-
{:error, old_type, error_context} ->
176-
if match_error?(var, new_type) do
177-
throw(badpattern_error(expr, index, tag, stack, context))
178-
else
179-
throw(badvar_error(var, old_type, new_type, stack, error_context))
180-
end
174+
context =
175+
case of_pattern_var(path, actual, context) do
176+
{:ok, new_type} ->
177+
case Of.refine_head_var(var, new_type, expr, stack, context) do
178+
{:ok, _type, context} ->
179+
context
180+
181+
{:error, old_type, error_context} ->
182+
if match_error?(var, new_type) do
183+
throw(badpattern_error(expr, index, tag, stack, context))
184+
else
185+
throw(badvar_error(var, old_type, new_type, stack, error_context))
181186
end
182-
183-
:error ->
184-
throw(badpattern_error(expr, index, tag, stack, context))
185187
end
186-
end)
187188

188-
{version, context}
189+
:error ->
190+
throw(badpattern_error(expr, index, tag, stack, context))
191+
end
192+
193+
{Map.merge(changed, Map.get(vars_deps, version, %{})), context}
189194
end)
190195
catch
191196
context -> error_vars(pattern_info, context)
192197
else
193198
{changed, context} ->
194-
context =
195-
Enum.reduce(changed, context, fn version, context ->
196-
{_, context} = of_pattern_var_dep(vars_paths, version, stack, context)
197-
context
198-
end)
199-
200199
of_pattern_var_deps(changed, vars_paths, vars_deps, stack, context)
201200
end
202201
end
203202

204-
defp of_pattern_var_deps([], _vars_paths, _vars_deps, _stack, context) do
203+
defp of_pattern_var_deps(changed, _vars_paths, _vars_deps, _stack, context)
204+
when changed == %{} do
205205
context
206206
end
207207

208208
defp of_pattern_var_deps(previous_changed, vars_paths, vars_deps, stack, context) do
209209
{changed, context} =
210210
previous_changed
211-
|> Enum.reduce(%{}, fn version, acc ->
212-
case vars_deps do
213-
%{^version => deps} -> Map.merge(acc, deps)
214-
%{} -> acc
215-
end
216-
end)
217211
|> Map.keys()
218-
|> Enum.reduce({[], context}, fn version, {changed, context} ->
212+
|> Enum.reduce({%{}, context}, fn version, {changed, context} ->
219213
{var_changed?, context} = of_pattern_var_dep(vars_paths, version, stack, context)
220214

221215
case var_changed? do
222216
false -> {changed, context}
223-
true -> {[version | changed], context}
217+
true -> {Map.merge(changed, Map.get(vars_deps, version, %{})), context}
224218
end
225219
end)
226220

@@ -269,32 +263,36 @@ defmodule Module.Types.Pattern do
269263
end
270264
end
271265

272-
defp error_vars({args_paths, vars_paths, _vars_deps}, context) do
273-
callback = fn [%{var: var} | _paths], context ->
266+
defp error_vars({args_paths, _vars_paths, _vars_deps}, context) do
267+
Enum.reduce(args_paths, context, fn {_version, %{var: var}}, context ->
274268
Of.error_var(var, context)
275-
end
276-
277-
context = Enum.reduce(Map.values(args_paths), context, callback)
278-
context = Enum.reduce(Map.values(vars_paths), context, callback)
279-
context
269+
end)
280270
end
281271

282272
defp match_error?({:match, _, __MODULE__}, _type), do: true
283273
defp match_error?(_var, type), do: empty?(type)
284274

285-
defp badmatch_error(var, expr, stack, context) do
286-
context = Of.error_var(var, context)
287-
error(__MODULE__, {:badmatch, expr, context}, error_meta(expr, stack), stack, context)
288-
end
289-
290275
defp badvar_error(var, old_type, new_type, stack, context) do
291276
error = {:badvar, old_type, new_type, var, context}
292277
error(__MODULE__, error, error_meta(var, stack), stack, context)
293278
end
294279

280+
defp badmatch_error(var, expr, stack, context) do
281+
context = Of.error_var(var, context)
282+
error(__MODULE__, {:badmatch, expr, context}, error_meta(expr, stack), stack, context)
283+
end
284+
295285
defp badpattern_error(expr, index, tag, stack, context) do
296286
meta = error_meta(expr, stack)
297-
error(__MODULE__, {:badpattern, meta, expr, index, tag, context}, meta, stack, context)
287+
288+
error =
289+
if index do
290+
{:badpattern, meta, expr, index, tag, context}
291+
else
292+
{:badmatch, expr, context}
293+
end
294+
295+
error(__MODULE__, error, meta, stack, context)
298296
end
299297

300298
defp error_meta(expr, stack) do
@@ -630,21 +628,19 @@ defmodule Module.Types.Pattern do
630628
pattern_info =
631629
case root do
632630
{:arg, _} ->
633-
paths = [node | Map.get(args_paths, version, [])]
634-
args_paths = Map.put(args_paths, version, paths)
635-
{args_paths, vars_paths, vars_deps}
631+
{[{version, node} | args_paths], vars_paths, vars_deps}
636632

637633
{:var, other} ->
638634
paths = [node | Map.get(vars_paths, version, [])]
639635
vars_paths = Map.put(vars_paths, version, paths)
640636
vars_deps = Map.update(vars_deps, version, %{other => []}, &Map.put(&1, other, []))
641637
vars_deps = Map.update(vars_deps, other, %{version => []}, &Map.put(&1, version, []))
642-
{args_paths, vars_paths, vars_deps}
638+
{[{version, node} | args_paths], vars_paths, vars_deps}
643639

644640
_ ->
645641
paths = [node | Map.get(vars_paths, version, [])]
646642
vars_paths = Map.put(vars_paths, version, paths)
647-
{args_paths, vars_paths, vars_deps}
643+
{[{version, node} | args_paths], vars_paths, vars_deps}
648644
end
649645

650646
%{context | pattern_info: pattern_info}
@@ -837,7 +833,7 @@ defmodule Module.Types.Pattern do
837833
# the number of list heads.
838834
# TODO: Consider moving pattern_info into context.vars.
839835
defp init_pattern_info(context) do
840-
%{context | pattern_info: {%{}, %{}, %{}}}
836+
%{context | pattern_info: {[], %{}, %{}}}
841837
end
842838

843839
defp pop_pattern_info(%{pattern_info: pattern_info} = context) do

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

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,36 @@ defmodule Module.Types.PatternTest do
209209
assert typecheck!([x = %{123 => 456}], x) == dynamic(open_map())
210210
assert typecheck!([x = %{123 => 456, foo: :bar}], x) == dynamic(open_map(foo: atom([:bar])))
211211
assert typecheck!([%{foo: :bar = x}], x) == dynamic(atom([:bar]))
212+
213+
assert typecheck!(
214+
[
215+
{:message, %{slug: slug}},
216+
%{assigns: %{app: %{slug: slug} = app}} = root
217+
],
218+
{root, app, slug}
219+
) ==
220+
dynamic(
221+
tuple([
222+
open_map(assigns: open_map(app: open_map(slug: term()))),
223+
open_map(slug: term()),
224+
term()
225+
])
226+
)
227+
228+
assert typecheck!(
229+
[
230+
%{assigns: %{app: %{slug: slug} = app}} = root,
231+
{:message, %{slug: slug}}
232+
],
233+
{root, app, slug}
234+
) ==
235+
dynamic(
236+
tuple([
237+
open_map(assigns: open_map(app: open_map(slug: term()))),
238+
open_map(slug: term()),
239+
term()
240+
])
241+
)
212242
end
213243

214244
test "atom keys in guards" do

0 commit comments

Comments
 (0)