Skip to content

Commit c39286e

Browse files
authored
Improve error report and checks on function application (#14541)
1 parent 7f1fe47 commit c39286e

File tree

8 files changed

+482
-240
lines changed

8 files changed

+482
-240
lines changed

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

Lines changed: 107 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -472,8 +472,6 @@ defmodule Module.Types.Apply do
472472
Returns the type of a remote capture.
473473
"""
474474
def remote_capture(modules, fun, arity, meta, stack, context) do
475-
# TODO: Do we check when the union of functions is invalid?
476-
# TODO: Deal with :infer types
477475
if stack.mode == :traversal or modules == [] do
478476
{dynamic(fun(arity)), context}
479477
else
@@ -483,6 +481,9 @@ defmodule Module.Types.Apply do
483481
{{:strong, _, clauses}, context} ->
484482
{union(type, fun_from_non_overlapping_clauses(clauses)), fallback?, context}
485483

484+
{{:infer, _, clauses}, context} when length(clauses) <= @max_clauses ->
485+
{union(type, fun_from_overlapping_clauses(clauses)), fallback?, context}
486+
486487
{_, context} ->
487488
{type, true, context}
488489
end
@@ -610,6 +611,19 @@ defmodule Module.Types.Apply do
610611
not Enum.any?(stack.no_warn_undefined, &(&1 == module or &1 == {module, fun, arity}))
611612
end
612613

614+
## Funs
615+
616+
def fun_apply(fun_type, args_types, call, stack, context) do
617+
case fun_apply(fun_type, args_types) do
618+
{:ok, res} ->
619+
{res, context}
620+
621+
reason ->
622+
error = {{:badapply, reason}, args_types, fun_type, call, context}
623+
{error_type(), error(__MODULE__, error, elem(call, 1), stack, context)}
624+
end
625+
end
626+
613627
## Local
614628

615629
def local_domain(fun, args, expected, meta, stack, context) do
@@ -682,13 +696,25 @@ defmodule Module.Types.Apply do
682696
{_kind, _info, context} when stack.mode == :traversal ->
683697
{dynamic(fun(arity)), context}
684698

685-
{kind, _info, context} ->
686-
if stack.mode != :infer and kind == :defp do
687-
# Mark all clauses as used, as the function is being exported.
688-
{dynamic(fun(arity)), put_in(context.local_used[fun_arity], [])}
689-
else
690-
{dynamic(fun(arity)), context}
691-
end
699+
{kind, info, context} ->
700+
result =
701+
case info do
702+
{:infer, _, clauses} when length(clauses) <= @max_clauses ->
703+
fun_from_overlapping_clauses(clauses)
704+
705+
_ ->
706+
dynamic(fun(arity))
707+
end
708+
709+
context =
710+
if stack.mode != :infer and kind == :defp do
711+
# Mark all clauses as used, as the function is being exported.
712+
put_in(context.local_used[fun_arity], [])
713+
else
714+
context
715+
end
716+
717+
{result, context}
692718
end
693719
end
694720

@@ -805,6 +831,78 @@ defmodule Module.Types.Apply do
805831

806832
## Diagnostics
807833

834+
def format_diagnostic({{:badapply, reason}, args_types, fun_type, expr, context}) do
835+
traces =
836+
case reason do
837+
# Include arguments in traces in case of badarg
838+
{:badarg, _} -> collect_traces(expr, context)
839+
# Otherwise just the fun
840+
_ -> collect_traces(elem(expr, 0), context)
841+
end
842+
843+
message =
844+
case reason do
845+
{:badarg, domain} ->
846+
"""
847+
incompatible types given on function application:
848+
849+
#{expr_to_string(expr) |> indent(4)}
850+
851+
given types:
852+
853+
#{args_to_quoted_string(args_types, domain, &Function.identity/1) |> indent(4)}
854+
855+
but function has type:
856+
857+
#{to_quoted_string(fun_type) |> indent(4)}
858+
"""
859+
860+
:badarg ->
861+
"""
862+
expected a #{length(args_types)}-arity function on call:
863+
864+
#{expr_to_string(expr) |> indent(4)}
865+
866+
but got type:
867+
868+
#{to_quoted_string(fun_type) |> indent(4)}
869+
"""
870+
871+
{:badarity, arities} ->
872+
info =
873+
case arities do
874+
[arity] -> "function with arity #{arity}"
875+
_ -> "function with arities #{Enum.join(arities, ",")}"
876+
end
877+
878+
"""
879+
expected a #{length(args_types)}-arity function on call:
880+
881+
#{expr_to_string(expr) |> indent(4)}
882+
883+
but got #{info}:
884+
885+
#{to_quoted_string(fun_type) |> indent(4)}
886+
"""
887+
888+
:badfun ->
889+
"""
890+
expected a #{length(args_types)}-arity function on call:
891+
892+
#{expr_to_string(expr) |> indent(4)}
893+
894+
but got type:
895+
896+
#{to_quoted_string(fun_type) |> indent(4)}
897+
"""
898+
end
899+
900+
%{
901+
details: %{typing_traces: traces},
902+
message: IO.iodata_to_binary([message, format_traces(traces)])
903+
}
904+
end
905+
808906
def format_diagnostic({:badlocal, {_, domain, clauses}, args_types, expr, context}) do
809907
domain = domain(domain, clauses)
810908
traces = collect_traces(expr, context)

0 commit comments

Comments
 (0)