Skip to content

Commit 1bd258d

Browse files
committed
Progress
1 parent 551de4f commit 1bd258d

4 files changed

Lines changed: 112 additions & 63 deletions

File tree

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: 32 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -31,18 +31,36 @@ defmodule Module.Types.Of do
3131
@doc """
3232
Marks a variable with error.
3333
"""
34-
def error_var(var, context) do
34+
def error_var({_var_name, meta, _var_context}, context) do
35+
version = Keyword.fetch!(meta, :version)
36+
37+
update_in(context.vars[version], fn
38+
%{errored: true} = data -> data
39+
data -> Map.put(%{data | type: error_type()}, :errored, true)
40+
end)
41+
end
42+
43+
@doc """
44+
Declares a variable.
45+
"""
46+
def declare_var(var, context) do
3547
{var_name, meta, var_context} = var
3648
version = Keyword.fetch!(meta, :version)
3749

38-
data = %{
39-
type: error_type(),
40-
name: var_name,
41-
context: var_context,
42-
off_traces: []
43-
}
50+
case context.vars do
51+
%{^version => _} ->
52+
context
4453

45-
put_in(context.vars[version], data)
54+
vars ->
55+
data = %{
56+
type: term(),
57+
name: var_name,
58+
context: var_context,
59+
off_traces: []
60+
}
61+
62+
%{context | vars: Map.put(vars, version, data)}
63+
end
4664
end
4765

4866
@doc """
@@ -56,7 +74,7 @@ defmodule Module.Types.Of do
5674
version = Keyword.fetch!(meta, :version)
5775
%{vars: %{^version => %{type: old_type, off_traces: off_traces} = data} = vars} = context
5876

59-
if gradual?(old_type) and type not in [term(), dynamic()] do
77+
if gradual?(old_type) and type not in [term(), dynamic()] and not is_map_key(data, :errored) do
6078
case compatible_intersection(old_type, type) do
6179
{:ok, new_type} when new_type != old_type ->
6280
data = %{
@@ -87,6 +105,9 @@ defmodule Module.Types.Of do
87105
version = Keyword.fetch!(meta, :version)
88106

89107
case context.vars do
108+
%{^version => %{errored: true}} ->
109+
{:ok, error_type(), context}
110+
90111
%{^version => %{type: old_type, off_traces: off_traces} = data} = vars ->
91112
new_type = intersection(type, old_type)
92113

@@ -96,12 +117,11 @@ defmodule Module.Types.Of do
96117
off_traces: new_trace(expr, type, stack, off_traces)
97118
}
98119

99-
context = %{context | vars: %{vars | version => data}}
100-
101-
# We need to return error otherwise it leads to cascading errors
102120
if empty?(new_type) do
121+
context = %{context | vars: %{vars | version => Map.put(data, :errored, true)}}
103122
{:error, old_type, context}
104123
else
124+
context = %{context | vars: %{vars | version => data}}
105125
{:ok, new_type, context}
106126
end
107127

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

Lines changed: 78 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,7 @@ defmodule Module.Types.Pattern do
174174
context
175175

176176
{:error, old_type, context} ->
177-
throw({types, refine_error(var, old_type, type, stack, context)})
177+
throw({types, badvar_error(var, old_type, type, expr, stack, context)})
178178
end
179179

180180
:error ->
@@ -187,22 +187,21 @@ defmodule Module.Types.Pattern do
187187

188188
context =
189189
Enum.reduce(changed, context, fn version, context ->
190-
{_, context} = of_pattern_var_dep(vars_paths, version, tag, stack, context)
190+
{_, context} = of_pattern_var_dep(vars_paths, version, stack, context)
191191
context
192192
end)
193193

194-
{types, of_pattern_var_deps(changed, vars_paths, vars_deps, tag, stack, context)}
194+
{types, of_pattern_var_deps(changed, vars_paths, vars_deps, stack, context)}
195195
catch
196-
{:error, context} -> {types, error_vars(pattern_info, context)}
197196
{types, context} -> {types, error_vars(pattern_info, context)}
198197
end
199198
end
200199

201-
defp of_pattern_var_deps([], _vars_paths, _vars_deps, _tag, _stack, context) do
200+
defp of_pattern_var_deps([], _vars_paths, _vars_deps, _stack, context) do
202201
context
203202
end
204203

205-
defp of_pattern_var_deps(previous_changed, vars_paths, vars_deps, tag, stack, context) do
204+
defp of_pattern_var_deps(previous_changed, vars_paths, vars_deps, stack, context) do
206205
{changed, context} =
207206
previous_changed
208207
|> Enum.reduce(%{}, fn version, acc ->
@@ -213,46 +212,53 @@ defmodule Module.Types.Pattern do
213212
end)
214213
|> Map.keys()
215214
|> Enum.reduce({[], context}, fn version, {changed, context} ->
216-
{var_changed?, context} = of_pattern_var_dep(vars_paths, version, tag, stack, context)
215+
{var_changed?, context} = of_pattern_var_dep(vars_paths, version, stack, context)
217216

218217
case var_changed? do
219218
false -> {changed, context}
220219
true -> {[version | changed], context}
221220
end
222221
end)
223222

224-
of_pattern_var_deps(changed, vars_paths, vars_deps, tag, stack, context)
223+
of_pattern_var_deps(changed, vars_paths, vars_deps, stack, context)
225224
end
226225

227-
defp of_pattern_var_dep(vars_paths, version, tag, stack, context) do
226+
defp of_pattern_var_dep(vars_paths, version, stack, context) do
228227
paths = Map.get(vars_paths, version, [])
229228

230-
Enum.reduce(paths, {false, context}, fn
231-
%{var: var, expr: expr, root: root, path: path}, {var_changed?, context} ->
232-
index = 0
233-
actual = of_pattern_tree(root, context)
234-
235-
case of_pattern_var(path, actual, context) do
236-
{:ok, type} ->
237-
# Optimization: if current type is already a subtype, there is nothing to refine.
238-
with %{^version => %{type: current_type}} <- context.vars,
239-
true <- subtype?(current_type, type) do
240-
{var_changed?, context}
241-
else
242-
_ ->
243-
case Of.refine_head_var(var, type, expr, stack, context) do
244-
{:ok, _type, context} ->
245-
{true, context}
246-
247-
{:error, old_type, context} ->
248-
throw({:error, refine_error(var, old_type, type, stack, context)})
249-
end
250-
end
229+
case context.vars do
230+
%{^version => %{type: current_type} = data} when not is_map_key(data, :errored) ->
231+
try do
232+
Enum.reduce(paths, {false, context}, fn
233+
%{var: var, expr: expr, root: root, path: path}, {var_changed?, context} ->
234+
actual = of_pattern_tree(root, context)
235+
236+
case of_pattern_var(path, actual, context) do
237+
{:ok, type} ->
238+
# Optimization: if current type is already a subtype, there is nothing to refine.
239+
if current_type != term() and subtype?(current_type, type) do
240+
{var_changed?, context}
241+
else
242+
case Of.refine_head_var(var, type, expr, stack, context) do
243+
{:ok, _type, context} ->
244+
{true, context}
251245

252-
:error ->
253-
throw({:error, badpattern_error(expr, index, tag, stack, context)})
246+
{:error, _, context} ->
247+
throw(badvar_error(var, current_type, type, expr, stack, context))
248+
end
249+
end
250+
251+
:error ->
252+
throw(badmatch_error(expr, stack, Of.error_var(var, context)))
253+
end
254+
end)
255+
catch
256+
context -> {false, context}
254257
end
255-
end)
258+
259+
_ ->
260+
{false, context}
261+
end
256262
end
257263

258264
defp error_vars({args_paths, vars_paths, _vars_deps}, context) do
@@ -265,22 +271,34 @@ defmodule Module.Types.Pattern do
265271
context
266272
end
267273

268-
# TODO: May pass the expression as well for more context
269-
defp refine_error({_, meta, _} = var, old_type, type, stack, context) do
270-
error(__MODULE__, {:badvar, old_type, type, var, context}, meta, stack, context)
274+
defp badmatch_error(expr, stack, context) do
275+
error(__MODULE__, {:badmatch, expr, context}, error_meta(expr, stack), stack, context)
271276
end
272277

273-
defp badpattern_error(expr, index, tag, stack, context) do
274-
meta =
275-
if meta = get_meta(expr) do
276-
meta ++ Keyword.take(stack.meta, [:generated, :line, :type_check])
278+
defp badvar_error({var_name, _, var_context} = var, old_type, new_type, expr, stack, context) do
279+
error =
280+
if var_name == :match and var_context == __MODULE__ do
281+
{:badmatch, expr, context}
277282
else
278-
stack.meta
283+
{:badvar, old_type, new_type, var, context}
279284
end
280285

286+
error(__MODULE__, error, error_meta(var, stack), stack, context)
287+
end
288+
289+
defp badpattern_error(expr, index, tag, stack, context) do
290+
meta = error_meta(expr, stack)
281291
error(__MODULE__, {:badpattern, meta, expr, index, tag, context}, meta, stack, context)
282292
end
283293

294+
defp error_meta(expr, stack) do
295+
if meta = get_meta(expr) do
296+
meta ++ Keyword.take(stack.meta, [:generated, :line, :type_check])
297+
else
298+
stack.meta
299+
end
300+
end
301+
284302
defp of_pattern_intersect(tree, expected, expr, index, tag, stack, context) do
285303
actual = of_pattern_tree(tree, context)
286304
type = intersection(actual, expected)
@@ -387,7 +405,7 @@ defmodule Module.Types.Pattern do
387405
{type, context}
388406

389407
{:error, old_type, context} ->
390-
{error_type(), refine_error(var, old_type, expected, stack, context)}
408+
{error_type(), badvar_error(var, old_type, expected, expr, stack, context)}
391409
end
392410
end
393411

@@ -587,6 +605,7 @@ defmodule Module.Types.Pattern do
587605
defp is_versioned_var(_), do: false
588606

589607
defp of_var(var, version, reverse_path, context) do
608+
context = Of.declare_var(var, context)
590609
{args_paths, vars_paths, vars_deps} = context.pattern_info
591610
[%{root: root, expr: expr} | path] = Enum.reverse(reverse_path)
592611
node = %{root: root, var: var, expr: expr, path: path}
@@ -811,7 +830,23 @@ defmodule Module.Types.Pattern do
811830
{pattern_info, %{context | pattern_info: nil}}
812831
end
813832

814-
# TODO: Deal when new_type is none()
833+
def format_diagnostic({:badmatch, expr, context}) do
834+
traces = collect_traces(expr, context)
835+
836+
%{
837+
details: %{typing_traces: traces},
838+
message:
839+
IO.iodata_to_binary([
840+
"""
841+
incompatible types in expression:
842+
843+
#{expr_to_string(expr) |> indent(4)}
844+
""",
845+
format_traces(traces)
846+
])
847+
}
848+
end
849+
815850
def format_diagnostic({:badvar, old_type, new_type, var, context}) do
816851
traces = collect_traces(var, context)
817852

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

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -133,14 +133,8 @@ defmodule Module.Types.PatternTest do
133133
"""
134134

135135
assert typeerror!([{:ok, y} = {:error, z, w}], {y, z, w}) == ~l"""
136-
incompatible types assigned to "match" (context Module.Types.Pattern):
136+
incompatible types in expression:
137137
138-
dynamic() !~ none()
139-
140-
where "match" (context Module.Types.Pattern) was given the type:
141-
142-
# type: none()
143-
# from: types_test.ex:LINE
144138
{:ok, y} = {:error, z, w}
145139
"""
146140

0 commit comments

Comments
 (0)