Skip to content

Commit 6d43358

Browse files
authored
Refactor variable tracking in patterns to prepare for guards (#15026)
1 parent 3c8fe86 commit 6d43358

6 files changed

Lines changed: 357 additions & 247 deletions

File tree

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2099,9 +2099,9 @@ defmodule Module.Types.Descr do
20992099
defp list_hd_static(%{}), do: none()
21002100

21012101
@doc """
2102-
Returns the tail of a list.
2102+
Returns the tail of a list.
21032103
2104-
For a `non_empty_list(t)`, the tail type is `list(t)`.
2104+
For a `non_empty_list(t)`, the tail type is `list(t)`.
21052105
For an improper list `non_empty_list(t, s)`, the tail type is
21062106
`list(t, s) or s` (either the rest of the list or the terminator)
21072107
"""

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -580,6 +580,7 @@ defmodule Module.Types.Expr do
580580
_ ->
581581
expected = if structs == [], do: @exception, else: Enum.reduce(structs, &union/2)
582582
expr = {:__block__, [type_check: info], [expr]}
583+
context = Of.declare_var(var, context)
583584
{_ok?, _type, context} = Of.refine_head_var(var, expected, expr, stack, context)
584585
context
585586
end

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,7 @@ defmodule Module.Types.Helpers do
148148
version = meta[:version]
149149

150150
case vars do
151-
%{^version => %{off_traces: off_traces, name: name, context: context}} ->
151+
%{^version => %{off_traces: [_ | _] = off_traces, name: name, context: context}} ->
152152
{:ok,
153153
Map.put(versions, version, %{
154154
type: :variable,

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

Lines changed: 38 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -30,19 +30,40 @@ defmodule Module.Types.Of do
3030

3131
@doc """
3232
Marks a variable with error.
33+
34+
This purposedly deletes all traces of the variable,
35+
as it is often invoked when the cause for error is elsewhere.
36+
"""
37+
def error_var({_var_name, meta, _var_context}, context) do
38+
version = Keyword.fetch!(meta, :version)
39+
40+
update_in(context.vars[version], fn
41+
%{errored: true} = data -> data
42+
data -> Map.put(%{data | type: error_type(), off_traces: []}, :errored, true)
43+
end)
44+
end
45+
46+
@doc """
47+
Declares a variable.
3348
"""
34-
def error_var(var, context) do
49+
def declare_var(var, context) do
3550
{var_name, meta, var_context} = var
3651
version = Keyword.fetch!(meta, :version)
3752

38-
data = %{
39-
type: error_type(),
40-
name: var_name,
41-
context: var_context,
42-
off_traces: []
43-
}
53+
case context.vars do
54+
%{^version => _} ->
55+
context
56+
57+
vars ->
58+
data = %{
59+
type: term(),
60+
name: var_name,
61+
context: var_context,
62+
off_traces: []
63+
}
4464

45-
put_in(context.vars[version], data)
65+
%{context | vars: Map.put(vars, version, data)}
66+
end
4667
end
4768

4869
@doc """
@@ -56,7 +77,7 @@ defmodule Module.Types.Of do
5677
version = Keyword.fetch!(meta, :version)
5778
%{vars: %{^version => %{type: old_type, off_traces: off_traces} = data} = vars} = context
5879

59-
if gradual?(old_type) and type not in [term(), dynamic()] do
80+
if gradual?(old_type) and type not in [term(), dynamic()] and not is_map_key(data, :errored) do
6081
case compatible_intersection(old_type, type) do
6182
{:ok, new_type} when new_type != old_type ->
6283
data = %{
@@ -82,11 +103,13 @@ defmodule Module.Types.Of do
82103
because we want to refine types. Otherwise we should
83104
use compatibility.
84105
"""
85-
def refine_head_var(var, type, expr, stack, context) do
86-
{var_name, meta, var_context} = var
106+
def refine_head_var({_, meta, _}, type, expr, stack, context) do
87107
version = Keyword.fetch!(meta, :version)
88108

89109
case context.vars do
110+
%{^version => %{errored: true}} ->
111+
{:ok, error_type(), context}
112+
90113
%{^version => %{type: old_type, off_traces: off_traces} = data} = vars ->
91114
new_type = intersection(type, old_type)
92115

@@ -96,26 +119,14 @@ defmodule Module.Types.Of do
96119
off_traces: new_trace(expr, type, stack, off_traces)
97120
}
98121

99-
context = %{context | vars: %{vars | version => data}}
100-
101-
# We need to return error otherwise it leads to cascading errors
102122
if empty?(new_type) do
103-
{:error, error_type(),
104-
error({:refine_head_var, old_type, type, var, context}, meta, stack, context)}
123+
data = Map.put(%{data | type: error_type()}, :errored, true)
124+
context = %{context | vars: %{vars | version => data}}
125+
{:error, old_type, context}
105126
else
127+
context = %{context | vars: %{vars | version => data}}
106128
{:ok, new_type, context}
107129
end
108-
109-
%{} = vars ->
110-
data = %{
111-
type: type,
112-
name: var_name,
113-
context: var_context,
114-
off_traces: new_trace(expr, type, stack, [])
115-
}
116-
117-
context = %{context | vars: Map.put(vars, version, data)}
118-
{:ok, type, context}
119130
end
120131
end
121132

@@ -546,23 +557,6 @@ defmodule Module.Types.Of do
546557
error(__MODULE__, warning, meta, stack, context)
547558
end
548559

549-
def format_diagnostic({:refine_head_var, old_type, new_type, var, context}) do
550-
traces = collect_traces(var, context)
551-
552-
%{
553-
details: %{typing_traces: traces},
554-
message:
555-
IO.iodata_to_binary([
556-
"""
557-
incompatible types assigned to #{format_var(var)}:
558-
559-
#{to_quoted_string(old_type)} !~ #{to_quoted_string(new_type)}
560-
""",
561-
format_traces(traces)
562-
])
563-
}
564-
end
565-
566560
def format_diagnostic({:badbinary, kind, meta, expr, expected_type, actual_type, context}) do
567561
type = if kind == :match, do: "matching", else: "construction"
568562
hints = if meta[:inferred_bitstring_spec], do: [:inferred_bitstring_spec], else: []

0 commit comments

Comments
 (0)