Skip to content

Commit 41353c6

Browse files
committed
Infer precise types for default arguments
1 parent 7e49585 commit 41353c6

4 files changed

Lines changed: 302 additions & 188 deletions

File tree

lib/elixir/lib/module/types.ex

Lines changed: 85 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
defmodule Module.Types do
66
@moduledoc false
7-
alias Module.Types.{Descr, Expr, Pattern, Helpers}
7+
alias Module.Types.{Apply, Descr, Expr, Helpers, Pattern}
88

99
# The mode controls what happens on function application when
1010
# there are gradual arguments. Non-gradual arguments always
@@ -251,17 +251,27 @@ defmodule Module.Types do
251251
context ->
252252
{_kind, info, mapping} = Map.fetch!(context.local_sigs, fun_arity)
253253

254-
clauses_indexes =
255-
for type_index <- pending,
256-
not skip_unused_clause?(info, type_index),
257-
{clause_index, ^type_index} <- mapping,
258-
do: clause_index
254+
if pending != [] do
255+
{used_indexes, unused_indexes} =
256+
Enum.reduce(mapping, {[], []}, fn {clause_index, type_index},
257+
{used_indexes, unused_indexes} ->
258+
if type_index in pending and not skip_unused_clause?(info, type_index) do
259+
{used_indexes, [clause_index | unused_indexes]}
260+
else
261+
{[clause_index | used_indexes], unused_indexes}
262+
end
263+
end)
259264

260-
Enum.reduce(clauses_indexes, context, fn clause_index, context ->
261-
{meta, _args, _guards, _body} = Enum.fetch!(clauses, clause_index)
262-
stack = %{stack | function: fun_arity}
263-
Helpers.warn(__MODULE__, {:unused_clause, kind, fun_arity}, meta, stack, context)
264-
end)
265+
unused_indexes = Enum.uniq(unused_indexes) -- used_indexes
266+
267+
Enum.reduce(unused_indexes, context, fn clause_index, context ->
268+
{meta, _args, _guards, _body} = Enum.fetch!(clauses, clause_index)
269+
stack = %{stack | function: fun_arity}
270+
Helpers.warn(__MODULE__, {:unused_clause, kind, fun_arity}, meta, stack, context)
271+
end)
272+
else
273+
context
274+
end
265275
end
266276
end
267277

@@ -321,6 +331,70 @@ defmodule Module.Types do
321331
stack = stack |> fresh_stack(mode, fun_arity) |> with_file_meta(meta)
322332
base_info = {:def, kind, fun, expected}
323333

334+
case clauses do
335+
[{meta, args, [], {:super, _, [_ | _]} = body}] ->
336+
default_local_handler(meta, args, body, base_info, kind, fun, expected, stack, context)
337+
338+
_ ->
339+
infer_local_handler(clauses, base_info, kind, fun, expected, stack, context)
340+
end
341+
end
342+
343+
defp default_local_handler(meta, args, body, base_info, kind, fun, expected, stack, context) do
344+
guards = []
345+
previous = Pattern.init_previous()
346+
fresh_context = fresh_context(context)
347+
info = {base_info, args, guards}
348+
349+
try do
350+
{trees, _, _, _, head_context} =
351+
Pattern.of_head(args, guards, expected, previous, info, meta, stack, fresh_context)
352+
353+
# Compute the intersected arrows from the function call
354+
{:super, meta, call_args} = body
355+
{_kind, call_fun} = Keyword.fetch!(meta, :super)
356+
term = Descr.term()
357+
of_fun = &Expr.of_expr/5
358+
359+
{arrows, body_context} =
360+
Apply.local_arrows(call_fun, call_args, term, body, stack, head_context, of_fun)
361+
362+
# For each arrow, compute the default arrow
363+
{_, mapping, inferred} =
364+
Enum.reduce(arrows, {0, [], []}, fn
365+
{clause_domain, return_type}, {index, mapping, inferred} ->
366+
of_fun = &Expr.of_expr(&1, &2, body, stack, &3)
367+
368+
{_clause_args, clause_context} =
369+
Helpers.zip_map_reduce(call_args, clause_domain, head_context, of_fun)
370+
371+
clause_types = Pattern.of_domain(trees, stack, clause_context)
372+
373+
{_type_index, inferred} =
374+
add_inferred(inferred, clause_types, return_type, index - 1, [])
375+
376+
{index + 1, [{0, index} | mapping], inferred}
377+
end)
378+
379+
domain =
380+
case inferred do
381+
[_] ->
382+
nil
383+
384+
_ ->
385+
inferred
386+
|> Enum.map(fn {args, _} -> args end)
387+
|> Enum.zip_with(fn types -> Enum.reduce(types, &Descr.union/2) end)
388+
end
389+
390+
{{:infer, domain, Enum.reverse(inferred)}, mapping, restore_context(body_context, context)}
391+
rescue
392+
e ->
393+
internal_error!(e, __STACKTRACE__, kind, meta, fun, args, guards, body, stack)
394+
end
395+
end
396+
397+
defp infer_local_handler(clauses, base_info, kind, fun, expected, stack, context) do
324398
{_, _, _, domain, mapping, clauses_types, clauses_context} =
325399
Enum.reduce(clauses, {0, 0, Pattern.init_previous(), [], [], [], context}, fn
326400
{meta, args, guards, body},

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

Lines changed: 37 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1375,41 +1375,70 @@ defmodule Module.Types.Apply do
13751375
## Local
13761376

13771377
def local(fun, args, expected, {_, meta, _} = expr, stack, context, of_fun) do
1378-
{local_info, domain, context} = local_domain(fun, args, expected, meta, stack, context)
1378+
{updated_used?, info, domain, context} =
1379+
local_domain(fun, args, expected, meta, stack, context)
13791380

13801381
{args_types, context} =
13811382
zip_map_reduce(args, domain, context, &of_fun.(&1, &2, expr, stack, &3))
13821383

1383-
local_apply(local_info, fun, args_types, expr, stack, context)
1384+
case local_apply(updated_used?, info, fun, args_types, expr, stack, context) do
1385+
{_indexes, type, context} -> {type, context}
1386+
{type, context} -> {type, context}
1387+
end
13841388
end
13851389

1390+
def local_arrows(fun, args, expected, {_, meta, _} = expr, stack, context, of_fun) do
1391+
{updated_used?, info, domain, context} =
1392+
local_domain(fun, args, expected, meta, stack, context)
1393+
1394+
{args_types, context} =
1395+
zip_map_reduce(args, domain, context, &of_fun.(&1, &2, expr, stack, &3))
1396+
1397+
case local_apply(updated_used?, info, fun, args_types, expr, stack, context) do
1398+
{indexes, _type, context} -> {indexes_to_arrows(indexes, info), context}
1399+
{type, context} -> {[{Enum.map(args, fn _ -> type end), type}], context}
1400+
end
1401+
end
1402+
1403+
defp indexes_to_arrows(indexes, {_, _domain, clauses}),
1404+
do: indexes_to_arrows(Enum.reverse(indexes), clauses, 0)
1405+
1406+
defp indexes_to_arrows([index | indexes], [clause | clauses], index),
1407+
do: [clause | indexes_to_arrows(indexes, clauses, index + 1)]
1408+
1409+
defp indexes_to_arrows([_ | _] = indexes, [_clause | clauses], index),
1410+
do: indexes_to_arrows(indexes, clauses, index + 1)
1411+
1412+
defp indexes_to_arrows([], _clauses, _index),
1413+
do: []
1414+
13861415
defp local_domain(fun, args, expected, meta, stack, context) do
13871416
arity = length(args)
13881417

13891418
case stack.local_handler.(meta, {fun, arity}, stack, context) do
13901419
false ->
1391-
{{false, :none}, List.duplicate(term(), arity), context}
1420+
{false, :none, List.duplicate(term(), arity), context}
13921421

13931422
{kind, info, context} ->
13941423
update_used? = is_warning(stack) and kind == :defp
13951424

13961425
if info == :none do
1397-
{{update_used?, :none}, List.duplicate(term(), arity), context}
1426+
{update_used?, :none, List.duplicate(term(), arity), context}
13981427
else
1399-
{{update_used?, info}, filter_domain(info, expected, arity), context}
1428+
{update_used?, info, filter_domain(info, expected, arity), context}
14001429
end
14011430
end
14021431
end
14031432

1404-
defp local_apply({update_used?, :none}, fun, args_types, _expr, _stack, context) do
1433+
defp local_apply(update_used?, :none, fun, args_types, _expr, _stack, context) do
14051434
if update_used? do
14061435
{dynamic(), put_in(context.local_used[{fun, length(args_types)}], [])}
14071436
else
14081437
{dynamic(), context}
14091438
end
14101439
end
14111440

1412-
defp local_apply({update_used?, info}, fun, args_types, expr, stack, context) do
1441+
defp local_apply(update_used?, info, fun, args_types, expr, stack, context) do
14131442
case local_apply(info, args_types, stack) do
14141443
{indexes, type} ->
14151444
context =
@@ -1421,7 +1450,7 @@ defmodule Module.Types.Apply do
14211450
context
14221451
end
14231452

1424-
{type, context}
1453+
{indexes, type, context}
14251454

14261455
:error ->
14271456
error = {:badlocal, info, args_types, expr, context}

0 commit comments

Comments
 (0)