Skip to content

Commit 0581d88

Browse files
committed
More precision in error messages
1 parent 1bd258d commit 0581d88

File tree

3 files changed

+72
-62
lines changed

3 files changed

+72
-62
lines changed

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

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,13 +30,16 @@ 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.
3336
"""
3437
def error_var({_var_name, meta, _var_context}, context) do
3538
version = Keyword.fetch!(meta, :version)
3639

3740
update_in(context.vars[version], fn
3841
%{errored: true} = data -> data
39-
data -> Map.put(%{data | type: error_type()}, :errored, true)
42+
data -> Map.put(%{data | type: error_type(), off_traces: []}, :errored, true)
4043
end)
4144
end
4245

@@ -118,7 +121,8 @@ defmodule Module.Types.Of do
118121
}
119122

120123
if empty?(new_type) do
121-
context = %{context | vars: %{vars | version => Map.put(data, :errored, true)}}
124+
data = Map.put(%{data | type: error_type()}, :errored, true)
125+
context = %{context | vars: %{vars | version => data}}
122126
{:error, old_type, context}
123127
else
124128
context = %{context | vars: %{vars | version => data}}

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

Lines changed: 58 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -62,13 +62,13 @@ defmodule Module.Types.Pattern do
6262
{trees, context} = of_pattern_args_index(patterns, 0, [], stack, context)
6363
{pattern_info, context} = pop_pattern_info(context)
6464

65-
{_, context} =
65+
context =
6666
case of_pattern_args_tree(trees, expected, 0, [], tag, stack, context) do
6767
{:ok, types, context} ->
6868
of_pattern_recur(types, tag, pattern_info, stack, context)
6969

7070
{:error, context} ->
71-
{expected, error_vars(pattern_info, context)}
71+
error_vars(pattern_info, context)
7272
end
7373

7474
{trees, context}
@@ -149,7 +149,7 @@ defmodule Module.Types.Pattern do
149149
defp of_single_pattern_recur(expected, tag, tree, pattern_info, expr, stack, context) do
150150
case of_pattern_intersect(tree, expected, expr, 0, tag, stack, context) do
151151
{:ok, type, context} ->
152-
of_pattern_recur([type], tag, pattern_info, stack, context)
152+
{[type], of_pattern_recur([type], tag, pattern_info, stack, context)}
153153

154154
{:error, context} ->
155155
{[expected], error_vars(pattern_info, context)}
@@ -160,40 +160,44 @@ defmodule Module.Types.Pattern do
160160
{args_paths, vars_paths, vars_deps} = pattern_info
161161

162162
try do
163-
{changed, context} =
164-
Enum.map_reduce(args_paths, context, fn {version, paths}, context ->
165-
context =
166-
Enum.reduce(paths, context, fn
167-
%{var: var, expr: expr, root: {:arg, index}, path: path}, context ->
168-
actual = Enum.fetch!(types, index)
169-
170-
case of_pattern_var(path, actual, context) do
171-
{:ok, type} ->
172-
case Of.refine_head_var(var, type, expr, stack, context) do
173-
{:ok, _type, context} ->
174-
context
175-
176-
{:error, old_type, context} ->
177-
throw({types, badvar_error(var, old_type, type, expr, stack, context)})
178-
end
179-
180-
:error ->
181-
throw({types, badpattern_error(expr, index, tag, stack, context)})
182-
end
183-
end)
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)
184168

185-
{version, context}
186-
end)
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
181+
end
187182

188-
context =
189-
Enum.reduce(changed, context, fn version, context ->
190-
{_, context} = of_pattern_var_dep(vars_paths, version, stack, context)
191-
context
192-
end)
183+
:error ->
184+
throw(badpattern_error(expr, index, tag, stack, context))
185+
end
186+
end)
193187

194-
{types, of_pattern_var_deps(changed, vars_paths, vars_deps, stack, context)}
188+
{version, context}
189+
end)
195190
catch
196-
{types, context} -> {types, error_vars(pattern_info, context)}
191+
context -> error_vars(pattern_info, context)
192+
else
193+
{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+
200+
of_pattern_var_deps(changed, vars_paths, vars_deps, stack, context)
197201
end
198202
end
199203

@@ -227,29 +231,33 @@ defmodule Module.Types.Pattern do
227231
paths = Map.get(vars_paths, version, [])
228232

229233
case context.vars do
230-
%{^version => %{type: current_type} = data} when not is_map_key(data, :errored) ->
234+
%{^version => %{type: old_type} = data} when not is_map_key(data, :errored) ->
231235
try do
232236
Enum.reduce(paths, {false, context}, fn
233237
%{var: var, expr: expr, root: root, path: path}, {var_changed?, context} ->
234238
actual = of_pattern_tree(root, context)
235239

236240
case of_pattern_var(path, actual, context) do
237-
{:ok, type} ->
241+
{:ok, new_type} ->
238242
# Optimization: if current type is already a subtype, there is nothing to refine.
239-
if current_type != term() and subtype?(current_type, type) do
243+
if old_type != term() and subtype?(old_type, new_type) do
240244
{var_changed?, context}
241245
else
242-
case Of.refine_head_var(var, type, expr, stack, context) do
246+
case Of.refine_head_var(var, new_type, expr, stack, context) do
243247
{:ok, _type, context} ->
244248
{true, context}
245249

246-
{:error, _, context} ->
247-
throw(badvar_error(var, current_type, type, expr, stack, context))
250+
{:error, _old_type, error_context} ->
251+
if match_error?(var, new_type) do
252+
throw(badmatch_error(var, expr, stack, context))
253+
else
254+
throw(badvar_error(var, old_type, new_type, stack, error_context))
255+
end
248256
end
249257
end
250258

251259
:error ->
252-
throw(badmatch_error(expr, stack, Of.error_var(var, context)))
260+
throw(badmatch_error(var, expr, stack, context))
253261
end
254262
end)
255263
catch
@@ -271,18 +279,16 @@ defmodule Module.Types.Pattern do
271279
context
272280
end
273281

274-
defp badmatch_error(expr, stack, context) do
282+
defp match_error?({:match, _, __MODULE__}, _type), do: true
283+
defp match_error?(_var, type), do: empty?(type)
284+
285+
defp badmatch_error(var, expr, stack, context) do
286+
context = Of.error_var(var, context)
275287
error(__MODULE__, {:badmatch, expr, context}, error_meta(expr, stack), stack, context)
276288
end
277289

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}
282-
else
283-
{:badvar, old_type, new_type, var, context}
284-
end
285-
290+
defp badvar_error(var, old_type, new_type, stack, context) do
291+
error = {:badvar, old_type, new_type, var, context}
286292
error(__MODULE__, error, error_meta(var, stack), stack, context)
287293
end
288294

@@ -404,8 +410,8 @@ defmodule Module.Types.Pattern do
404410
{:ok, type, context} ->
405411
{type, context}
406412

407-
{:error, old_type, context} ->
408-
{error_type(), badvar_error(var, old_type, expected, expr, stack, context)}
413+
{:error, old_type, error_context} ->
414+
{error_type(), badvar_error(var, old_type, expected, stack, error_context)}
409415
end
410416
end
411417

@@ -838,7 +844,7 @@ defmodule Module.Types.Pattern do
838844
message:
839845
IO.iodata_to_binary([
840846
"""
841-
incompatible types in expression:
847+
this match will never succeed due to incompatible types:
842848
843849
#{expr_to_string(expr) |> indent(4)}
844850
""",

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

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -114,26 +114,26 @@ defmodule Module.Types.PatternTest do
114114
end
115115

116116
test "reports incompatible types" do
117+
assert typeerror!([x = 123 = "123"], x) == ~l"""
118+
the following pattern will never match:
119+
120+
x = 123 = "123"
121+
"""
122+
117123
assert typeerror!([x = {:ok, _} = {:error, _, _}], x) == ~l"""
118124
the following pattern will never match:
119125
120126
x = {:ok, _} = {:error, _, _}
121127
"""
122128

123129
assert typeerror!([x = {:ok, y} = {:error, z, w}], {x, y, z, w}) == ~l"""
124-
incompatible types assigned to "x":
130+
this match will never succeed due to incompatible types:
125131
126-
dynamic() !~ none()
127-
128-
where "x" was given the type:
129-
130-
# type: none()
131-
# from: types_test.ex:LINE
132132
x = {:ok, y} = {:error, z, w}
133133
"""
134134

135135
assert typeerror!([{:ok, y} = {:error, z, w}], {y, z, w}) == ~l"""
136-
incompatible types in expression:
136+
this match will never succeed due to incompatible types:
137137
138138
{:ok, y} = {:error, z, w}
139139
"""

0 commit comments

Comments
 (0)