Skip to content

Commit b6a0097

Browse files
committed
Properly track types in lists in pattern matching, closes #15040
1 parent cbe3d72 commit b6a0097

4 files changed

Lines changed: 53 additions & 3 deletions

File tree

lib/elixir/lib/module/types.ex

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -446,6 +446,8 @@ defmodule Module.Types do
446446
warnings: [],
447447
# All vars and their types
448448
vars: %{},
449+
# Stores special metadata need by heads in patterns
450+
heads: %{},
449451
# Variables that are specific to the current environment/conditional
450452
conditional_vars: nil,
451453
# Track metadata specific to matches and guards

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -169,8 +169,8 @@ defmodule Module.Types.Of do
169169
Preserves `context` in first argument while
170170
resetting it to the vars in the second argument.
171171
"""
172-
def reset_vars(context, %{vars: vars, conditional_vars: conditional_vars}),
173-
do: %{context | vars: vars, conditional_vars: conditional_vars}
172+
def reset_vars(context, %{heads: heads, vars: vars, conditional_vars: conditional_vars}),
173+
do: %{context | heads: heads, vars: vars, conditional_vars: conditional_vars}
174174

175175
@doc """
176176
Executes the args with acc using conditional variables.

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

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -354,6 +354,17 @@ defmodule Module.Types.Pattern do
354354
end
355355
end
356356

357+
defp of_pattern_var([{:head, key} | rest], type, context) do
358+
case list_hd(type) do
359+
{:ok, head} ->
360+
%{^key => tree} = context.heads
361+
of_pattern_var(rest, intersection(head, of_pattern_tree(tree, context)), context)
362+
363+
_ ->
364+
:error
365+
end
366+
end
367+
357368
defp of_pattern_var([:tail | rest], type, context) do
358369
case list_tl(type) do
359370
{:ok, tail} -> of_pattern_var(rest, tail, context)
@@ -746,7 +757,18 @@ defmodule Module.Types.Pattern do
746757

747758
result =
748759
Enum.reduce(prefix, {[], [], context}, fn arg, {static, dynamic, context} ->
749-
{type, context} = of_pattern(arg, [:head | path], stack, context)
760+
# These cases don't need to store head information because
761+
# they have no intersection
762+
{type, context} =
763+
if is_number(arg) or is_binary(arg) or is_atom(arg) or arg == [] or is_var(arg) do
764+
of_pattern(arg, [:head | path], stack, context)
765+
else
766+
%{heads: heads} = context
767+
key = map_size(heads)
768+
context = %{context | heads: Map.put(heads, key, nil)}
769+
{type, context} = of_pattern(arg, [{:head, key} | path], stack, context)
770+
{type, put_in(context.heads[key], type)}
771+
end
750772

751773
if is_descr(type) do
752774
{[type | static], dynamic, context}

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

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -344,6 +344,32 @@ defmodule Module.Types.PatternTest do
344344
)
345345
)
346346
end
347+
348+
test "regressions" do
349+
# This is a regression that happens because stacktrace may be
350+
# either three-element or four-element tuples, and it was failing
351+
# when we tried to access the fourth element
352+
assert typecheck!(
353+
try do
354+
raise "oops"
355+
rescue
356+
_ ->
357+
[{__MODULE__, fun, _args_or_arity, info} | _] = __STACKTRACE__
358+
{fun, length(info)}
359+
end
360+
) == tuple([atom(), integer()])
361+
362+
assert typecheck!(
363+
try do
364+
raise "oops"
365+
rescue
366+
_ ->
367+
[tuple | _] = __STACKTRACE__
368+
{__MODULE__, fun, _args_or_arity, info} = tuple
369+
{fun, length(info)}
370+
end
371+
) == tuple([atom(), integer()])
372+
end
347373
end
348374

349375
describe "bitstrings" do

0 commit comments

Comments
 (0)