Skip to content

Commit 7862ac4

Browse files
committed
Initial recursion for local calls
1 parent 792e604 commit 7862ac4

File tree

7 files changed

+977
-884
lines changed

7 files changed

+977
-884
lines changed

lib/elixir/lib/module/types.ex

Lines changed: 119 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -3,33 +3,117 @@ defmodule Module.Types do
33

44
alias Module.Types.{Descr, Expr, Pattern}
55

6+
# TODO: Inference of tail recursion
7+
# TODO: Checking of unused private functions/clauses
8+
69
# These functions are not inferred because they are added/managed by the compiler
710
@no_infer [__protocol__: 1, behaviour_info: 1]
811

912
@doc false
1013
def infer(module, file, defs, env) do
11-
context = context()
12-
13-
for {{fun, arity}, :def, _meta, clauses} <- defs,
14-
{fun, arity} not in @no_infer,
15-
into: %{} do
16-
stack = stack(:infer, file, module, {fun, arity}, :all, env)
17-
expected = List.duplicate(Descr.dynamic(), arity)
18-
19-
pair_types =
20-
Enum.reduce(clauses, [], fn {meta, args, guards, body}, inferred ->
21-
try do
22-
{args, context} =
23-
Pattern.of_head(args, guards, expected, :default, meta, stack, context)
24-
25-
{return, _context} = Expr.of_expr(body, stack, context)
26-
add_inferred(inferred, args, return, [])
27-
rescue
28-
e -> internal_error!(e, __STACKTRACE__, :def, meta, module, fun, args, guards, body)
29-
end
30-
end)
31-
32-
{{fun, arity}, {:infer, Enum.reverse(pair_types)}}
14+
finder = &List.keyfind(defs, &1, 0)
15+
handler = &infer_signature_for(&1, &2, module, file, finder, env)
16+
context = context({handler, %{}})
17+
18+
{types, _context} =
19+
for {fun_arity, kind, _meta, _clauses} = def <- defs,
20+
kind == :def and fun_arity not in @no_infer,
21+
reduce: {[], context} do
22+
{types, context} ->
23+
{_kind, inferred, context} =
24+
infer_signature_for(fun_arity, context, module, file, fn _ -> def end, env)
25+
26+
{[{fun_arity, inferred} | types], context}
27+
end
28+
29+
Map.new(types)
30+
end
31+
32+
defp infer_signature_for(fun_arity, context, module, file, finder, env) do
33+
case context.local_handler do
34+
{_, %{^fun_arity => {kind, inferred}}} ->
35+
{kind, inferred, context}
36+
37+
{_, _} ->
38+
{{fun, arity}, kind, _meta, clauses} = finder.(fun_arity)
39+
expected = List.duplicate(Descr.dynamic(), arity)
40+
41+
stack = stack(:infer, file, module, fun_arity, :all, env)
42+
context = update_local_state(context, &Map.put(&1, fun_arity, {kind, :none}))
43+
44+
{pair_types, context} =
45+
Enum.reduce(clauses, {[], context}, fn
46+
{meta, args, guards, body}, {inferred, context} ->
47+
context = context(context.local_handler)
48+
49+
try do
50+
{args_types, context} =
51+
Pattern.of_head(args, guards, expected, :default, meta, stack, context)
52+
53+
{return_type, context} = Expr.of_expr(body, stack, context)
54+
{add_inferred(inferred, args_types, return_type, []), context}
55+
rescue
56+
e ->
57+
internal_error!(e, __STACKTRACE__, kind, meta, module, fun, args, guards, body)
58+
end
59+
end)
60+
61+
inferred = {:infer, Enum.reverse(pair_types)}
62+
{kind, inferred, update_local_state(context, &Map.put(&1, fun_arity, {kind, inferred}))}
63+
end
64+
end
65+
66+
@doc false
67+
def warnings(module, file, defs, no_warn_undefined, cache) do
68+
finder = &List.keyfind(defs, &1, 0)
69+
handler = &warnings_for(&1, &2, module, file, finder, no_warn_undefined, cache)
70+
context = context({handler, %{}})
71+
72+
context =
73+
Enum.reduce(defs, context, fn {fun_arity, _kind, _meta, _clauses} = def, context ->
74+
finder = fn _ -> def end
75+
76+
{_kind, _inferred, context} =
77+
warnings_for(fun_arity, context, module, file, finder, no_warn_undefined, cache)
78+
79+
context
80+
end)
81+
82+
context.warnings
83+
end
84+
85+
defp warnings_for(fun_arity, context, module, file, finder, no_warn_undefined, cache) do
86+
case context.local_handler do
87+
{_, %{^fun_arity => {kind, inferred}}} ->
88+
{kind, inferred, context}
89+
90+
{_, _} ->
91+
{{fun, arity}, kind, meta, clauses} = finder.(fun_arity)
92+
expected = List.duplicate(Descr.dynamic(), arity)
93+
94+
file = with_file_meta(meta, file)
95+
stack = stack(:dynamic, file, module, fun_arity, no_warn_undefined, cache)
96+
context = update_local_state(context, &Map.put(&1, fun_arity, {kind, :none}))
97+
98+
{pair_types, context} =
99+
Enum.reduce(clauses, {[], context}, fn
100+
{meta, args, guards, body}, {inferred, context} ->
101+
context = fresh_context(context)
102+
103+
try do
104+
{args_types, context} =
105+
Pattern.of_head(args, guards, expected, :default, meta, stack, context)
106+
107+
{return_type, context} = Expr.of_expr(body, stack, context)
108+
{add_inferred(inferred, args_types, return_type, []), context}
109+
rescue
110+
e ->
111+
internal_error!(e, __STACKTRACE__, kind, meta, module, fun, args, guards, body)
112+
end
113+
end)
114+
115+
inferred = {:infer, Enum.reverse(pair_types)}
116+
{kind, inferred, update_local_state(context, &Map.put(&1, fun_arity, {kind, inferred}))}
33117
end
34118
end
35119

@@ -44,30 +128,6 @@ defmodule Module.Types do
44128
defp add_inferred([], args, return, acc),
45129
do: [{args, return} | Enum.reverse(acc)]
46130

47-
@doc false
48-
def warnings(module, file, defs, no_warn_undefined, cache) do
49-
context = context()
50-
51-
Enum.flat_map(defs, fn {{fun, arity}, kind, meta, clauses} ->
52-
file = with_file_meta(meta, file)
53-
stack = stack(:dynamic, file, module, {fun, arity}, no_warn_undefined, cache)
54-
expected = List.duplicate(Descr.dynamic(), arity)
55-
56-
Enum.flat_map(clauses, fn {meta, args, guards, body} ->
57-
try do
58-
{_types, context} =
59-
Pattern.of_head(args, guards, expected, :default, meta, stack, context)
60-
61-
{_type, context} = Expr.of_expr(body, stack, context)
62-
context.warnings
63-
rescue
64-
e ->
65-
internal_error!(e, __STACKTRACE__, kind, meta, module, fun, args, guards, body)
66-
end
67-
end)
68-
end)
69-
end
70-
71131
defp with_file_meta(meta, file) do
72132
case Keyword.fetch(meta, :file) do
73133
{:ok, {meta_file, _}} -> meta_file
@@ -145,16 +205,26 @@ defmodule Module.Types do
145205
end
146206

147207
@doc false
148-
def context() do
208+
def context(local_handler, warnings \\ []) do
149209
%{
150210
# A list of all warnings found so far
151-
warnings: [],
211+
warnings: warnings,
152212
# All vars and their types
153213
vars: %{},
154214
# Variables and arguments from patterns
155215
pattern_info: nil,
156216
# If type checking has found an error/failure
157-
failed: false
217+
failed: false,
218+
# Local handler
219+
local_handler: local_handler
158220
}
159221
end
222+
223+
defp fresh_context(%{local_handler: local_handler, warnings: warnings}) do
224+
context(local_handler, warnings)
225+
end
226+
227+
defp update_local_state(%{local_handler: {handler, state}} = context, fun) do
228+
%{context | local_handler: {handler, fun.(state)}}
229+
end
160230
end

0 commit comments

Comments
 (0)