Skip to content

Commit 29858c9

Browse files
committed
Only compute error if necessary
1 parent 2a53036 commit 29858c9

File tree

6 files changed

+143
-157
lines changed

6 files changed

+143
-157
lines changed

lib/elixir/lib/module/types.ex

Lines changed: 59 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,8 @@ defmodule Module.Types do
3131
{:ok, _type, context} <- of_body(body, context) do
3232
context.warnings
3333
else
34-
{:error, context} ->
35-
context.warnings
34+
{:error, {type, error, context}} ->
35+
[error_to_warning(type, error, context) | context.warnings]
3636
end
3737
end
3838

@@ -205,6 +205,63 @@ defmodule Module.Types do
205205
{type, context}
206206
end
207207

208+
## ERROR TO WARNING
209+
210+
# Collect relevant information from context and traces to report error
211+
defp error_to_warning(:unable_unify, {left, right, stack}, context) do
212+
{fun, arity} = context.function
213+
line = get_meta(stack.last_expr)[:line]
214+
location = {context.file, line, {context.module, fun, arity}}
215+
216+
traces = type_traces(stack, context)
217+
traces = tag_traces(traces, context)
218+
219+
error = {:unable_unify, left, right, {location, stack.last_expr, traces}}
220+
{Module.Types, error, location}
221+
end
222+
223+
# Collect relevant traces from context.traces using stack.unify_stack
224+
defp type_traces(stack, context) do
225+
# TODO: Do we need the unify_stack or is enough to only get the last variable
226+
# in the stack since we get related variables anyway?
227+
stack =
228+
stack.unify_stack
229+
|> Enum.uniq()
230+
|> Enum.flat_map(&[&1 | related_variables(&1, context.types)])
231+
|> Enum.uniq()
232+
233+
Enum.flat_map(stack, fn var_index ->
234+
with %{^var_index => traces} <- context.traces,
235+
%{^var_index => expr_var} <- context.types_to_vars do
236+
Enum.map(traces, &{expr_var, &1})
237+
else
238+
_other -> []
239+
end
240+
end)
241+
end
242+
243+
defp related_variables(var, types) do
244+
Enum.flat_map(types, fn
245+
{related_var, {:var, ^var}} ->
246+
[related_var | related_variables(related_var, types)]
247+
248+
_ ->
249+
[]
250+
end)
251+
end
252+
253+
# Tag if trace is for a concrete type or type variable
254+
defp tag_traces(traces, context) do
255+
Enum.flat_map(traces, fn {var, {type, expr, location}} ->
256+
with {:var, var_index} <- type,
257+
%{^var_index => expr_var} <- context.types_to_vars do
258+
[{var, {:var, expr_var, expr, location}}]
259+
else
260+
_ -> [{var, {:type, type, expr, location}}]
261+
end
262+
end)
263+
end
264+
208265
## ERROR FORMATTING
209266

210267
def format_warning({:unable_unify, left, right, {location, expr, traces}}) do

lib/elixir/lib/module/types/infer.ex

Lines changed: 10 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ defmodule Module.Types.Infer do
9090
if subtype?(source, target, context) do
9191
{:ok, source, context}
9292
else
93-
error({:unable_unify, source, target}, stack, context)
93+
error(:unable_unify, {source, target, stack}, context)
9494
end
9595
end
9696

@@ -106,9 +106,9 @@ defmodule Module.Types.Infer do
106106

107107
if recursive_type?(type, [], context) do
108108
if var_source? do
109-
error({:unable_unify, {:var, var}, type}, stack, context)
109+
error(:unable_unify, {{:var, var}, type, stack}, context)
110110
else
111-
error({:unable_unify, type, {:var, var}}, stack, context)
111+
error(:unable_unify, {type, {:var, var}, stack}, context)
112112
end
113113
else
114114
{:ok, {:var, var}, context}
@@ -151,7 +151,7 @@ defmodule Module.Types.Infer do
151151
else
152152
left = {:map, [{:required, {:atom, :__struct__}, left_module}]}
153153
right = {:map, [{:required, {:atom, :__struct__}, right_module}]}
154-
error({:unable_unify, left, right}, stack, context)
154+
error(:unable_unify, {left, right, stack}, context)
155155
end
156156
else
157157
_ -> :ok
@@ -190,7 +190,7 @@ defmodule Module.Types.Infer do
190190
{:ok, {:map, pairs}, context}
191191
else
192192
{:error, :unify} ->
193-
error({:unable_unify, {:map, source_pairs}, {:map, target_pairs}}, stack, context)
193+
error(:unable_unify, {{:map, source_pairs}, {:map, target_pairs}, stack}, context)
194194

195195
{:error, context} ->
196196
{:error, context}
@@ -208,7 +208,7 @@ defmodule Module.Types.Infer do
208208
{:error, _reason} ->
209209
source_map = {:map, [{:required, source_key, source_value}]}
210210
target_map = {:map, [{target_kind, target_key, target_value}]}
211-
error({:unable_unify, source_map, target_map}, stack, context)
211+
error(:unable_unify, {source_map, target_map, stack}, context)
212212
end
213213
else
214214
{:error, _reason} -> nil
@@ -228,7 +228,7 @@ defmodule Module.Types.Infer do
228228
{:error, _reason} ->
229229
source_map = {:map, [{source_kind, source_key, source_value}]}
230230
target_map = {:map, [{:required, target_key, target_value}]}
231-
error({:unable_unify, source_map, target_map}, stack, context)
231+
error(:unable_unify, {source_map, target_map, stack}, context)
232232
end
233233
else
234234
{:error, _reason} -> nil
@@ -248,7 +248,7 @@ defmodule Module.Types.Infer do
248248
{:error, _reason} ->
249249
source_map = {:map, [{:optional, source_key, source_value}]}
250250
target_map = {:map, [{:optional, target_key, target_value}]}
251-
error({:unable_unify, source_map, target_map}, stack, context)
251+
error(:unable_unify, {source_map, target_map, stack}, context)
252252
end
253253
else
254254
_ -> nil
@@ -268,7 +268,7 @@ defmodule Module.Types.Infer do
268268
{:error, _reason} ->
269269
source_map = {:map, [{:optional, source_key, source_value}]}
270270
target_map = {:map, [{:optional, target_key, target_value}]}
271-
error({:unable_unify, source_map, target_map}, stack, context)
271+
error(:unable_unify, {source_map, target_map, stack}, context)
272272
end
273273
else
274274
_ -> nil
@@ -499,63 +499,7 @@ defmodule Module.Types.Infer do
499499
def unify_kinds(_, :required), do: :required
500500
def unify_kinds(:optional, :optional), do: :optional
501501

502-
# Collect relevant information from context and traces to report error
503-
# TODO: We should do this lazily since in some cases unification will error
504-
# but we continue attempting unifying other types
505-
defp error({:unable_unify, left, right}, stack, context) do
506-
{fun, arity} = context.function
507-
line = get_meta(stack.last_expr)[:line]
508-
location = {context.file, line, {context.module, fun, arity}}
509-
510-
traces = type_traces(stack, context)
511-
traces = tag_traces(traces, context)
512-
513-
error = {:unable_unify, left, right, {location, stack.last_expr, traces}}
514-
context = update_in(context.warnings, &[{Module.Types, error, location} | &1])
515-
{:error, context}
516-
end
517-
518-
# Collect relevant traces from context.traces using stack.unify_stack
519-
defp type_traces(stack, context) do
520-
# TODO: Do we need the unify_stack or is enough to only get the last variable
521-
# in the stack since we get related variables anyway?
522-
stack =
523-
stack.unify_stack
524-
|> Enum.uniq()
525-
|> Enum.flat_map(&[&1 | related_variables(&1, context.types)])
526-
|> Enum.uniq()
527-
528-
Enum.flat_map(stack, fn var_index ->
529-
with %{^var_index => traces} <- context.traces,
530-
%{^var_index => expr_var} <- context.types_to_vars do
531-
Enum.map(traces, &{expr_var, &1})
532-
else
533-
_other -> []
534-
end
535-
end)
536-
end
537-
538-
defp related_variables(var, types) do
539-
Enum.flat_map(types, fn
540-
{related_var, {:var, ^var}} ->
541-
[related_var | related_variables(related_var, types)]
542-
543-
_ ->
544-
[]
545-
end)
546-
end
547-
548-
# Tag if trace is for a concrete type or type variable
549-
defp tag_traces(traces, context) do
550-
Enum.flat_map(traces, fn {var, {type, expr, location}} ->
551-
with {:var, var_index} <- type,
552-
%{^var_index => expr_var} <- context.types_to_vars do
553-
[{var, {:var, expr_var, expr, location}}]
554-
else
555-
_ -> [{var, {:type, type, expr, location}}]
556-
end
557-
end)
558-
end
502+
defp error(type, reason, context), do: {:error, {type, reason, context}}
559503

560504
# TODO: We should check if structs have keys that do not belong to them.
561505
# This might not be the best place to do it since it will only be

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

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -54,8 +54,8 @@ defmodule Module.Types.ExprTest do
5454
{:ok, Types.lift_type(type, context)}
5555
end
5656

57-
defp lift_result({:error, %{warnings: [{Types, reason, location} | _]}}) do
58-
{:error, {reason, location}}
57+
defp lift_result({:error, {type, reason, _context}}) do
58+
{:error, {type, reason}}
5959
end
6060

6161
defmodule :"Elixir.Module.Types.ExprTest.Struct" do
@@ -168,10 +168,10 @@ defmodule Module.Types.ExprTest do
168168
]}}
169169

170170
assert {:error,
171-
{{:unable_unify,
172-
{:map, [{:required, {:atom, :bar}, {:var, 1}}, {:optional, :dynamic, :dynamic}]},
173-
{:map, [{:required, {:atom, :foo}, {:atom, :foo}}]}, _},
174-
_}} =
171+
{:unable_unify,
172+
{{:map, [{:required, {:atom, :bar}, {:var, 1}}, {:optional, :dynamic, :dynamic}]},
173+
{:map, [{:required, {:atom, :foo}, {:atom, :foo}}]},
174+
_}}} =
175175
quoted_expr(
176176
(
177177
map = %{foo: :foo}
@@ -214,7 +214,7 @@ defmodule Module.Types.ExprTest do
214214
{:required, {:atom, :__struct__}, {:atom, Module.Types.ExprTest.Struct2}}
215215
]}}
216216

217-
assert {:error, {{:unable_unify, _, _, _}, _}} =
217+
assert {:error, {:unable_unify, {_, _, _}}} =
218218
quoted_expr(
219219
[map],
220220
(
@@ -223,7 +223,7 @@ defmodule Module.Types.ExprTest do
223223
)
224224
)
225225

226-
assert {:error, {{:unable_unify, _, _, _}, _}} =
226+
assert {:error, {:unable_unify, {_, _, _}}} =
227227
quoted_expr(
228228
[map],
229229
(
@@ -245,14 +245,16 @@ defmodule Module.Types.ExprTest do
245245
) == {:ok, {:map, [{:required, {:atom, :a}, {:atom, :b}}]}}
246246

247247
assert {:error,
248-
{{:unable_unify, {:map, [{:required, {:atom, :c}, {:atom, :d}}]},
248+
{:unable_unify,
249+
{{:map, [{:required, {:atom, :c}, {:atom, :d}}]},
249250
{:map, [{:required, {:atom, :a}, {:atom, :b}}, {:optional, :dynamic, :dynamic}]},
250-
_}, _}} = quoted_expr(%{a: :b} = %{c: :d})
251+
_}}} = quoted_expr(%{a: :b} = %{c: :d})
251252

252253
assert {:error,
253-
{{:unable_unify, {:map, [{:required, {:atom, :b}, {:atom, :error}}]},
254-
{:map, [{:required, {:var, 0}, {:atom, :ok}}, {:optional, :dynamic, :dynamic}]}, _},
255-
_}} =
254+
{:unable_unify,
255+
{{:map, [{:required, {:atom, :b}, {:atom, :error}}]},
256+
{:map, [{:required, {:var, 0}, {:atom, :ok}}, {:optional, :dynamic, :dynamic}]},
257+
_}}} =
256258
quoted_expr(
257259
(
258260
a = :a
@@ -306,15 +308,15 @@ defmodule Module.Types.ExprTest do
306308
assert quoted_expr([foo], {<<foo::utf8>>, foo}) ==
307309
{:ok, {:tuple, [:binary, {:union, [:integer, :binary]}]}}
308310

309-
assert {:error, {{:unable_unify, :integer, :binary, _}, _}} =
311+
assert {:error, {:unable_unify, {:integer, :binary, _}}} =
310312
quoted_expr(
311313
(
312314
foo = 0
313315
<<foo::binary>>
314316
)
315317
)
316318

317-
assert {:error, {{:unable_unify, :binary, :integer, _}, _}} =
319+
assert {:error, {:unable_unify, {:binary, :integer, _}}} =
318320
quoted_expr([foo], <<foo::binary-0, foo::integer>>)
319321
end
320322
end

0 commit comments

Comments
 (0)