Skip to content

Commit 462f614

Browse files
committed
Avoid crash when typing violation is detected on dynamic dispatch
1 parent 4b262d8 commit 462f614

File tree

2 files changed

+112
-33
lines changed

2 files changed

+112
-33
lines changed

lib/elixir/lib/module/types/apply.ex

Lines changed: 73 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -269,7 +269,7 @@ defmodule Module.Types.Apply do
269269

270270
case signature(name, arity) do
271271
:none -> {dynamic(), context}
272-
info -> apply_remote(info, args_types, expr, stack, context)
272+
info -> apply_remote(nil, name, info, args_types, expr, stack, context)
273273
end
274274
end
275275

@@ -287,10 +287,14 @@ defmodule Module.Types.Apply do
287287
{value_type, context}
288288

289289
:badtuple ->
290-
{error_type(), badremote_error(expr, [integer(), tuple], stack, context)}
290+
{error_type(),
291+
badremote_error(:erlang, :element, expr, [integer(), tuple], stack, context)}
291292

292-
reason ->
293-
{error_type(), error({reason, expr, tuple, index - 1, context}, meta, stack, context)}
293+
:badindex ->
294+
mfac = mfac(expr, :erlang, :element, 2)
295+
296+
{error_type(),
297+
error({:badindex, mfac, expr, tuple, index - 1, context}, meta, stack, context)}
294298
end
295299
end
296300

@@ -308,10 +312,16 @@ defmodule Module.Types.Apply do
308312
{value_type, context}
309313

310314
:badtuple ->
311-
{error_type(), badremote_error(expr, [integer(), tuple, value], stack, context)}
315+
args_types = [integer(), tuple, value]
316+
317+
{error_type(),
318+
badremote_error(:erlang, :insert_element, expr, args_types, stack, context)}
312319

313-
reason ->
314-
{error_type(), error({reason, expr, tuple, index - 2, context}, meta, stack, context)}
320+
:badindex ->
321+
mfac = mfac(expr, :erlang, :insert_element, 3)
322+
323+
{error_type(),
324+
error({:badindex, mfac, expr, tuple, index - 2, context}, meta, stack, context)}
315325
end
316326
end
317327

@@ -322,10 +332,16 @@ defmodule Module.Types.Apply do
322332
{value_type, context}
323333

324334
:badtuple ->
325-
{error_type(), badremote_error(expr, [integer(), tuple], stack, context)}
335+
args_types = [integer(), tuple]
336+
337+
{error_type(),
338+
badremote_error(:erlang, :delete_element, expr, args_types, stack, context)}
326339

327-
reason ->
328-
{error_type(), error({reason, expr, tuple, index - 1, context}, meta, stack, context)}
340+
:badindex ->
341+
mfac = mfac(expr, :erlang, :delete_element, 2)
342+
343+
{error_type(),
344+
error({:badindex, mfac, expr, tuple, index - 1, context}, meta, stack, context)}
329345
end
330346
end
331347

@@ -340,7 +356,7 @@ defmodule Module.Types.Apply do
340356
{value_type, context}
341357

342358
:badnonemptylist ->
343-
{error_type(), badremote_error(expr, [list], stack, context)}
359+
{error_type(), badremote_error(:erlang, :hd, expr, [list], stack, context)}
344360
end
345361
end
346362

@@ -350,7 +366,7 @@ defmodule Module.Types.Apply do
350366
{value_type, context}
351367

352368
:badnonemptylist ->
353-
{error_type(), badremote_error(expr, [list], stack, context)}
369+
{error_type(), badremote_error(:erlang, :tl, expr, [list], stack, context)}
354370
end
355371
end
356372

@@ -405,30 +421,36 @@ defmodule Module.Types.Apply do
405421
{return(boolean(), args_types, stack), context}
406422
end
407423

408-
def remote(mod, name, args_types, expr, stack, context) do
424+
def remote(mod, fun, args_types, expr, stack, context) do
409425
arity = length(args_types)
410426

411-
case :elixir_rewrite.inline(mod, name, arity) do
412-
{mod, name} ->
413-
remote(mod, name, args_types, expr, stack, context)
427+
case :elixir_rewrite.inline(mod, fun, arity) do
428+
{new_mod, new_fun} ->
429+
expr = inline_meta(expr, mod, fun)
430+
remote(new_mod, new_fun, args_types, expr, stack, context)
414431

415432
false ->
416-
{info, context} = signature(mod, name, arity, elem(expr, 1), stack, context)
417-
apply_remote(info, args_types, expr, stack, context)
433+
{info, context} = signature(mod, fun, arity, elem(expr, 1), stack, context)
434+
apply_remote(mod, fun, info, args_types, expr, stack, context)
418435
end
419436
end
420437

421-
defp apply_remote(info, args_types, expr, stack, context) do
438+
defp apply_remote(mod, fun, info, args_types, expr, stack, context) do
422439
case apply_signature(info, args_types, stack) do
423440
{:ok, _indexes, type} ->
424441
{type, context}
425442

426443
{:error, domain, clauses} ->
427-
error = {:badremote, expr, args_types, domain, clauses, context}
444+
mfac = mfac(expr, mod, fun, length(args_types))
445+
error = {:badremote, mfac, expr, args_types, domain, clauses, context}
428446
{error_type(), error(error, elem(expr, 1), stack, context)}
429447
end
430448
end
431449

450+
defp inline_meta({node, meta, args}, mod, fun) do
451+
{node, [inline: {mod, fun}] ++ meta, args}
452+
end
453+
432454
@doc """
433455
Returns the type of a remote capture.
434456
"""
@@ -718,22 +740,28 @@ defmodule Module.Types.Apply do
718740
error(__MODULE__, warning, meta, stack, context)
719741
end
720742

721-
defp badremote_error({{:., _, [mod, fun]}, meta, _} = expr, args_types, stack, context) do
722-
{_type, domain, [{args, _} | _] = clauses} = signature(mod, fun, length(args_types))
723-
error({:badremote, expr, args_types, domain || args, clauses, context}, meta, stack, context)
743+
defp badremote_error(mod, fun, {_, meta, _} = expr, args_types, stack, context) do
744+
arity = length(args_types)
745+
mfac = mfac(expr, mod, fun, arity)
746+
{_type, domain, [{args, _} | _] = clauses} = signature(mod, fun, arity)
747+
domain = domain || args
748+
tuple = {:badremote, mfac, expr, args_types, domain, clauses, context}
749+
error(tuple, meta, stack, context)
724750
end
725751

726-
## Diagnosstics
752+
## Diagnostics
727753

728-
def format_diagnostic({:badindex, expr, type, index, context}) do
754+
def format_diagnostic({:badindex, mfac, expr, type, index, context}) do
729755
traces = collect_traces(expr, context)
756+
{mod, fun, arity, _converter} = mfac
757+
mfa = Exception.format_mfa(mod, fun, arity)
730758

731759
%{
732760
details: %{typing_traces: traces},
733761
message:
734762
IO.iodata_to_binary([
735763
"""
736-
expected a tuple with at least #{pluralize(index + 1, "element", "elements")} in #{format_mfa(expr)}:
764+
expected a tuple with at least #{pluralize(index + 1, "element", "elements")} in #{mfa}:
737765
738766
#{expr_to_string(expr) |> indent(4)}
739767
@@ -778,10 +806,9 @@ defmodule Module.Types.Apply do
778806
}
779807
end
780808

781-
def format_diagnostic({:badremote, expr, args_types, domain, clauses, context}) do
809+
def format_diagnostic({:badremote, mfac, expr, args_types, domain, clauses, context}) do
782810
traces = collect_traces(expr, context)
783-
{{:., _, [mod, fun]}, _, args} = expr
784-
{mod, fun, args, converter} = :elixir_rewrite.erl_to_ex(mod, fun, args)
811+
{mod, fun, arity, converter} = mfac
785812

786813
explanation =
787814
empty_arg_reason(converter.(args_types)) ||
@@ -790,12 +817,15 @@ defmodule Module.Types.Apply do
790817
#{clauses_args_to_quoted_string(clauses, converter)}
791818
"""
792819

820+
mfa_or_fa =
821+
if mod, do: Exception.format_mfa(mod, fun, arity), else: "#{fun}/#{arity}"
822+
793823
%{
794824
details: %{typing_traces: traces},
795825
message:
796826
IO.iodata_to_binary([
797827
"""
798-
incompatible types given to #{Exception.format_mfa(mod, fun, length(args))}:
828+
incompatible types given to #{mfa_or_fa}:
799829
800830
#{expr_to_string(expr) |> indent(4)}
801831
@@ -932,9 +962,19 @@ defmodule Module.Types.Apply do
932962
defp pluralize(1, singular, _), do: "1 #{singular}"
933963
defp pluralize(i, _, plural), do: "#{i} #{plural}"
934964

935-
defp format_mfa({{:., _, [mod, fun]}, _, args}) do
936-
{mod, fun, args, _} = :elixir_rewrite.erl_to_ex(mod, fun, args)
937-
Exception.format_mfa(mod, fun, length(args))
965+
defp mfac({_, [inline: {mod, fun}] ++ _, _}, _mod, _fun, arity) do
966+
{mod, fun, arity, & &1}
967+
end
968+
969+
defp mfac({{:., _, [mod, fun]}, _, args}, _mod, _fun, _arity)
970+
when is_atom(mod) and is_atom(fun) do
971+
{mod, fun, args, converter} = :elixir_rewrite.erl_to_ex(mod, fun, args)
972+
{mod, fun, length(args), converter}
973+
end
974+
975+
defp mfac(_, mod, fun, arity)
976+
when is_atom(mod) and is_atom(fun) and is_integer(arity) do
977+
{mod, fun, arity, & &1}
938978
end
939979

940980
## Algebra helpers

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

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,35 @@ defmodule Module.Types.ExprTest do
243243
"""
244244
end
245245

246+
test "calling a function with invalid arguments on variables" do
247+
assert typeerror!(
248+
(
249+
x = List
250+
x.to_tuple(123)
251+
)
252+
)
253+
|> strip_ansi() ==
254+
~l"""
255+
incompatible types given to List.to_tuple/1:
256+
257+
x.to_tuple(123)
258+
259+
given types:
260+
261+
integer()
262+
263+
but expected one of:
264+
265+
list(term())
266+
267+
where "x" was given the type:
268+
269+
# type: List
270+
# from: types_test.ex:LINE-5
271+
x = List
272+
"""
273+
end
274+
246275
test "capture a function with non atoms" do
247276
assert typeerror!([<<x::integer>>], &x.foo_bar/2) ==
248277
~l"""
@@ -1400,6 +1429,16 @@ defmodule Module.Types.ExprTest do
14001429

14011430
assert typewarn!(:string.__info__(:functions)) ==
14021431
{dynamic(), ":string.__info__/1 is undefined or private"}
1432+
1433+
assert typeerror!([x], x.__info__(:whatever)) |> strip_ansi() =~ """
1434+
incompatible types given to __info__/1:
1435+
1436+
x.__info__(:whatever)
1437+
1438+
given types:
1439+
1440+
:whatever
1441+
"""
14031442
end
14041443

14051444
test "behaviour_info/1" do

0 commit comments

Comments
 (0)