Skip to content

Commit bf1a207

Browse files
committed
Type checking of apply/2 and apply/3
1 parent c5cbe7c commit bf1a207

File tree

4 files changed

+71
-1
lines changed

4 files changed

+71
-1
lines changed

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,8 @@ defmodule Module.Types.Apply do
145145
{:erlang, :>, [{[term(), term()], boolean()}]},
146146
{:erlang, :>=, [{[term(), term()], boolean()}]},
147147
{:erlang, :abs, [{[integer()], integer()}, {[float()], float()}]},
148+
{:erlang, :apply, [{[fun(), list(term())], dynamic()}]},
149+
{:erlang, :apply, [{[atom(), atom(), list(term())], dynamic()}]},
148150
{:erlang, :and, and_signature},
149151
{:erlang, :atom_to_binary, [{[atom()], binary()}]},
150152
{:erlang, :atom_to_list, [{[atom()], list(integer())}]},

lib/elixir/lib/module/types/expr.ex

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -404,6 +404,36 @@ defmodule Module.Types.Expr do
404404
end
405405
end
406406

407+
def of_expr({{:., _, [remote, :apply]}, _meta, [mod, fun, args]} = expr, stack, context)
408+
when remote in [Kernel, :erlang] and is_list(args) do
409+
{mod_type, context} = of_expr(mod, stack, context)
410+
{fun_type, context} = of_expr(fun, stack, context)
411+
improper_list? = Enum.any?(args, &match?({:|, _, [_, _]}, &1))
412+
413+
case atom_fetch(fun_type) do
414+
{_, [_ | _] = funs} when not improper_list? ->
415+
mods =
416+
case atom_fetch(mod_type) do
417+
{_, mods} -> mods
418+
_ -> []
419+
end
420+
421+
{args_types, context} = Enum.map_reduce(args, context, &of_expr(&1, stack, &2))
422+
423+
{types, context} =
424+
Enum.map_reduce(funs, context, fn fun, context ->
425+
apply_many(mods, fun, args, args_types, expr, stack, context)
426+
end)
427+
428+
{Enum.reduce(types, &union/2), context}
429+
430+
_ ->
431+
{args_type, context} = of_expr(args, stack, context)
432+
args_types = [mod_type, fun_type, args_type]
433+
Apply.remote(:erlang, :apply, [mod, fun, args], args_types, expr, stack, context)
434+
end
435+
end
436+
407437
def of_expr({{:., _, [remote, name]}, meta, args} = expr, stack, context) do
408438
{remote_type, context} = of_expr(remote, stack, context)
409439
{args_types, context} = Enum.map_reduce(args, context, &of_expr(&1, stack, &2))

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

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1472,6 +1472,44 @@ defmodule Module.Types.ExprTest do
14721472
end
14731473
end
14741474

1475+
describe "apply" do
1476+
test "handles conditional modules and functions" do
1477+
assert typecheck!([fun], apply(String, fun, ["foo", "bar", "baz"])) == dynamic()
1478+
1479+
assert typecheck!(
1480+
[condition, string],
1481+
(
1482+
fun = if condition, do: :to_integer, else: :to_float
1483+
apply(String, fun, [string])
1484+
)
1485+
) == union(integer(), float())
1486+
1487+
assert typecheck!(
1488+
[condition, string],
1489+
(
1490+
mod = if condition, do: String, else: List
1491+
fun = if condition, do: :to_integer, else: :to_float
1492+
apply(mod, fun, [string])
1493+
)
1494+
) == union(integer(), float())
1495+
1496+
assert typeerror!(
1497+
[condition, string],
1498+
(
1499+
mod = if condition, do: String, else: List
1500+
fun = if condition, do: :to_integer, else: :to_float
1501+
:erlang.apply(mod, fun, [string | "tail"])
1502+
)
1503+
) =~
1504+
"""
1505+
incompatible types given to Kernel.apply/3:
1506+
1507+
apply(mod, fun, [string | "tail"])
1508+
1509+
"""
1510+
end
1511+
end
1512+
14751513
describe "info" do
14761514
test "__info__/1" do
14771515
assert typecheck!(GenServer.__info__(:functions)) == list(tuple([atom(), integer()]))

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ defmodule TypeHelper do
138138

139139
defp new_stack(mode) do
140140
cache = if mode == :infer, do: :none, else: Module.ParallelChecker.test_cache()
141-
handler = fn _, _, _, _ -> raise "no local lookup" end
141+
handler = fn _, fun_arity, _, _ -> raise "no local lookup for: #{inspect(fun_arity)}" end
142142
Types.stack(mode, "types_test.ex", TypesTest, {:test, 0}, [], cache, handler)
143143
end
144144

0 commit comments

Comments
 (0)