Skip to content

Commit d25a780

Browse files
committed
Move traversal to a separate module for clarity and simplicity
1 parent dbfc3fa commit d25a780

8 files changed

Lines changed: 228 additions & 147 deletions

File tree

lib/elixir/lib/module/types.ex

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -24,14 +24,11 @@ defmodule Module.Types do
2424
#
2525
# * :infer - Same as :dynamic but skips remote calls.
2626
#
27-
# * :traversal - Focused mostly on traversing AST, skips most type system
28-
# operations. Used by macros and when skipping inference.
29-
#
3027
# The mode may also control exhaustiveness checks in the future (to be decided).
3128
# We may also want for applications with subtyping in dynamic mode to always
3229
# intersect with dynamic, but this mode may be too lax (to be decided based on
3330
# feedback).
34-
@modes [:static, :dynamic, :infer, :traversal]
31+
@modes [:static, :dynamic, :infer]
3532

3633
# These functions are not inferred because they are added/managed by the compiler
3734
@no_infer [behaviour_info: 1]
@@ -125,7 +122,7 @@ defmodule Module.Types do
125122
end
126123

127124
defp infer_mode(kind, infer_signatures?) do
128-
if infer_signatures? and kind in [:def, :defp], do: :infer, else: :traversal
125+
if infer_signatures? and kind in [:def, :defp], do: :infer, else: :traverse
129126
end
130127

131128
defp protocol?(attrs) do
@@ -154,7 +151,7 @@ defmodule Module.Types do
154151
| List.duplicate(Descr.dynamic(), arity - 1)
155152
]
156153

157-
{fun_arity, kind, meta, clauses} = def
154+
{_fun_arity, kind, meta, clauses} = def
158155

159156
clauses =
160157
for {meta, args, guards, body} <- clauses do
@@ -291,7 +288,7 @@ defmodule Module.Types do
291288
context = put_in(context.local_sigs, Map.put(local_sigs, fun_arity, kind))
292289

293290
{inferred, mapping, context} =
294-
local_handler(fun_arity, kind, meta, clauses, expected, mode, stack, context)
291+
local_handler(mode, fun_arity, kind, meta, clauses, expected, stack, context)
295292

296293
context =
297294
update_in(context.local_sigs, &Map.put(&1, fun_arity, {kind, inferred, mapping}))
@@ -304,7 +301,17 @@ defmodule Module.Types do
304301
end
305302
end
306303

307-
defp local_handler(fun_arity, kind, meta, clauses, expected, mode, stack, context) do
304+
defp local_handler(:traverse, {_, arity}, _kind, _meta, clauses, _expected, stack, context) do
305+
context =
306+
Enum.reduce(clauses, context, fn {_meta, _args, _guards, body}, context ->
307+
Module.Types.Traverse.of_expr(body, stack, context)
308+
end)
309+
310+
inferred = {:infer, nil, [{List.duplicate(Descr.term(), arity), Descr.dynamic()}]}
311+
{inferred, [{0, 0}], context}
312+
end
313+
314+
defp local_handler(mode, fun_arity, kind, meta, clauses, expected, stack, context) do
308315
{fun, _arity} = fun_arity
309316
stack = stack |> fresh_stack(mode, fun_arity) |> with_file_meta(meta)
310317

@@ -320,12 +327,7 @@ defmodule Module.Types do
320327
{return_type, context} =
321328
Expr.of_expr(body, Descr.term(), body, stack, context)
322329

323-
args_types =
324-
if stack.mode == :traversal do
325-
expected
326-
else
327-
Pattern.of_domain(trees, context)
328-
end
330+
args_types = Pattern.of_domain(trees, context)
329331

330332
{type_index, inferred} =
331333
add_inferred(inferred, args_types, return_type, total - 1, [])

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

Lines changed: 5 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -320,10 +320,6 @@ defmodule Module.Types.Apply do
320320
321321
Used only by info functions.
322322
"""
323-
def remote_domain(_fun, args, _expected, %{mode: :traversal}) do
324-
{:none, Enum.map(args, fn _ -> term() end)}
325-
end
326-
327323
def remote_domain(fun, args, expected, _stack) do
328324
arity = length(args)
329325
info = signature(fun, arity)
@@ -333,10 +329,6 @@ defmodule Module.Types.Apply do
333329
@doc """
334330
Returns the domain of a remote function with info to apply it.
335331
"""
336-
def remote_domain(_module, _fun, args, _expected, _meta, %{mode: :traversal}, context) do
337-
{:none, Enum.map(args, fn _ -> term() end), context}
338-
end
339-
340332
def remote_domain(:erlang, :is_function, [_, arity], expected, _meta, _stack, context)
341333
when is_integer(arity) and arity >= 0 do
342334
type = fun(arity)
@@ -834,14 +826,11 @@ defmodule Module.Types.Apply do
834826
Returns the type of a remote capture.
835827
"""
836828
def remote_capture(modules, fun, arity, meta, stack, context) do
837-
cond do
838-
stack.mode == :traversal ->
839-
{dynamic(), context}
840-
841-
modules == [] ->
829+
case modules do
830+
[] ->
842831
{dynamic(fun(arity)), context}
843832

844-
true ->
833+
[_ | _] ->
845834
{type, fallback?, context} =
846835
Enum.reduce(modules, {none(), false, context}, fn module, {type, fallback?, context} ->
847836
case signature(module, fun, arity, meta, stack, context) do
@@ -898,7 +887,7 @@ defmodule Module.Types.Apply do
898887

899888
defp export(module, fun, arity, meta, %{cache: cache} = stack, context) do
900889
cond do
901-
cache == nil or stack.mode == :traversal ->
890+
cache == nil ->
902891
{:none, context}
903892

904893
stack.mode == :infer ->
@@ -1003,7 +992,7 @@ defmodule Module.Types.Apply do
1003992
{kind, info, context} ->
1004993
update_used? = is_warning(stack) and kind == :defp
1005994

1006-
if stack.mode == :traversal or info == :none do
995+
if info == :none do
1007996
{{update_used?, :none}, List.duplicate(term(), arity), context}
1008997
else
1009998
{{update_used?, info}, filter_domain(info, expected, arity), context}
@@ -1060,9 +1049,6 @@ defmodule Module.Types.Apply do
10601049
false ->
10611050
{dynamic(fun(arity)), context}
10621051

1063-
{_kind, _info, context} when stack.mode == :traversal ->
1064-
{dynamic(fun(arity)), context}
1065-
10661052
{kind, info, context} ->
10671053
result =
10681054
case info do

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

Lines changed: 43 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -87,34 +87,28 @@ defmodule Module.Types.Expr do
8787
def of_expr(list, expected, expr, stack, context) when is_list(list) do
8888
{prefix, suffix} = unpack_list(list, [])
8989

90-
if stack.mode == :traversal do
91-
{_, context} = Enum.map_reduce(prefix, context, &of_expr(&1, term(), expr, stack, &2))
92-
{_, context} = of_expr(suffix, term(), expr, stack, context)
93-
{dynamic(), context}
94-
else
95-
hd_type =
96-
case list_hd(expected) do
97-
{:ok, type} -> type
98-
_ -> term()
99-
end
90+
hd_type =
91+
case list_hd(expected) do
92+
{:ok, type} -> type
93+
_ -> term()
94+
end
10095

101-
{prefix, context} = Enum.map_reduce(prefix, context, &of_expr(&1, hd_type, expr, stack, &2))
96+
{prefix, context} = Enum.map_reduce(prefix, context, &of_expr(&1, hd_type, expr, stack, &2))
10297

103-
{suffix, context} =
104-
if suffix == [] do
105-
{empty_list(), context}
106-
else
107-
tl_type =
108-
case list_tl(expected) do
109-
{:ok, type} -> type
110-
:badnonemptylist -> term()
111-
end
98+
{suffix, context} =
99+
if suffix == [] do
100+
{empty_list(), context}
101+
else
102+
tl_type =
103+
case list_tl(expected) do
104+
{:ok, type} -> type
105+
:badnonemptylist -> term()
106+
end
112107

113-
of_expr(suffix, tl_type, expr, stack, context)
114-
end
108+
of_expr(suffix, tl_type, expr, stack, context)
109+
end
115110

116-
{non_empty_list(Enum.reduce(prefix, &union/2), suffix), context}
117-
end
111+
{non_empty_list(Enum.reduce(prefix, &union/2), suffix), context}
118112
end
119113

120114
# {left, right}
@@ -178,23 +172,17 @@ defmodule Module.Types.Expr do
178172
{{key_type, value_type}, context}
179173
end)
180174

181-
expected =
182-
if stack.mode == :traversal do
183-
expected
184-
else
185-
# The only information we can attach to the expected types is that
186-
# certain keys are expected.
187-
expected_pairs =
188-
Enum.flat_map(pairs_types, fn {key_type, _value_type} ->
189-
case atom_fetch(key_type) do
190-
{:finite, [key]} -> [{key, term()}]
191-
_ -> []
192-
end
193-
end)
194-
195-
intersection(expected, open_map(expected_pairs))
196-
end
175+
# The only information we can attach to the expected types is that
176+
# certain keys are expected.
177+
expected_pairs =
178+
Enum.flat_map(pairs_types, fn {key_type, _value_type} ->
179+
case atom_fetch(key_type) do
180+
{:finite, [key]} -> [{key, term()}]
181+
_ -> []
182+
end
183+
end)
197184

185+
expected = intersection(expected, open_map(expected_pairs))
198186
{map_type, context} = of_expr(map, expected, expr, stack, context)
199187

200188
try do
@@ -226,16 +214,12 @@ defmodule Module.Types.Expr do
226214
{map_type, context} = of_expr(map, term(), struct, stack, context)
227215

228216
context =
229-
if stack.mode == :traversal do
217+
with {false, struct_key_type} <- map_fetch_key(map_type, :__struct__),
218+
{:finite, [^module]} <- atom_fetch(struct_key_type) do
230219
context
231220
else
232-
with {false, struct_key_type} <- map_fetch_key(map_type, :__struct__),
233-
{:finite, [^module]} <- atom_fetch(struct_key_type) do
234-
context
235-
else
236-
_ ->
237-
error(__MODULE__, {:badupdate, map_type, struct, context}, meta, stack, context)
238-
end
221+
_ ->
222+
error(__MODULE__, {:badupdate, map_type, struct, context}, meta, stack, context)
239223
end
240224

241225
Enum.reduce(pairs, {map_type, context}, fn {key, value}, {acc, context} ->
@@ -337,19 +321,14 @@ defmodule Module.Types.Expr do
337321
{patterns, _guards} = extract_head(head)
338322
domain = Enum.map(patterns, fn _ -> dynamic() end)
339323

340-
if stack.mode == :traversal do
341-
{_acc, context} = of_clauses(clauses, domain, @pending, nil, :fn, stack, context, none())
342-
{dynamic(), context}
343-
else
344-
{acc, context} =
345-
of_clauses_fun(clauses, domain, @pending, nil, :fn, stack, context, [], fn
346-
trees, body, context, acc ->
347-
args = Pattern.of_domain(trees, context)
348-
add_inferred(acc, args, body)
349-
end)
350-
351-
{fun_from_inferred_clauses(acc), context}
352-
end
324+
{acc, context} =
325+
of_clauses_fun(clauses, domain, @pending, nil, :fn, stack, context, [], fn
326+
trees, body, context, acc ->
327+
args = Pattern.of_domain(trees, context)
328+
add_inferred(acc, args, body)
329+
end)
330+
331+
{fun_from_inferred_clauses(acc), context}
353332
end
354333

355334
def of_expr({:try, _meta, [[do: body] ++ blocks]}, expected, expr, stack, original) do
@@ -469,11 +448,7 @@ defmodule Module.Types.Expr do
469448
{args_types, context} =
470449
Enum.map_reduce(args, context, &of_expr(&1, @pending, &1, stack, &2))
471450

472-
if stack.mode == :traversal do
473-
{dynamic(), context}
474-
else
475-
Apply.fun_apply(fun_type, args_types, call, stack, context)
476-
end
451+
Apply.fun_apply(fun_type, args_types, call, stack, context)
477452
end
478453

479454
def of_expr({{:., _, [callee, key_or_fun]}, meta, []} = call, expected, expr, stack, context)
@@ -528,19 +503,13 @@ defmodule Module.Types.Expr do
528503
# var
529504
def of_expr(var, expected, expr, stack, context) when is_var(var) do
530505
case stack do
531-
%{mode: :traversal} -> {dynamic(), context}
532506
%{refine_vars: false} -> {Of.var(var, context), context}
533507
%{} -> Of.refine_body_var(var, expected, expr, stack, context)
534508
end
535509
end
536510

537511
## Tuples
538512

539-
defp of_tuple(elems, _expected, expr, %{mode: :traversal} = stack, context) do
540-
{_types, context} = Enum.map_reduce(elems, context, &of_expr(&1, term(), expr, stack, &2))
541-
{dynamic(), context}
542-
end
543-
544513
defp of_tuple(elems, expected, expr, stack, context) do
545514
of_tuple(elems, 0, [], expected, expr, stack, context)
546515
end
@@ -741,14 +710,8 @@ defmodule Module.Types.Expr do
741710
defp dynamic_unless_static({_, _} = output, %{mode: :static}), do: output
742711
defp dynamic_unless_static({type, context}, %{mode: _}), do: {dynamic(type), context}
743712

744-
defp of_clauses(clauses, domain, expected, expr, info, %{mode: mode} = stack, context, acc) do
745-
fun =
746-
if mode == :traversal do
747-
fn _, _, _, _ -> dynamic() end
748-
else
749-
fn _trees, result, _context, acc -> union(result, acc) end
750-
end
751-
713+
defp of_clauses(clauses, domain, expected, expr, info, stack, context, acc) do
714+
fun = fn _trees, result, _context, acc -> union(result, acc) end
752715
of_clauses_fun(clauses, domain, expected, expr, info, stack, context, acc, fun)
753716
end
754717

lib/elixir/lib/module/types/helpers.ex

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ defmodule Module.Types.Helpers do
1111
@doc """
1212
Returns true if the mode cares about warnings.
1313
"""
14-
defguard is_warning(stack) when stack.mode not in [:traversal, :infer]
14+
defguard is_warning(stack) when stack.mode != :infer
1515

1616
@doc """
1717
Guard function to check if an AST node is a variable.

lib/elixir/lib/module/types/of.ex

Lines changed: 3 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -196,17 +196,6 @@ defmodule Module.Types.Of do
196196
@doc """
197197
Builds a closed map.
198198
"""
199-
def closed_map(pairs, _expected, %{mode: :traversal} = stack, context, of_fun) do
200-
context =
201-
Enum.reduce(pairs, context, fn {key, value}, context ->
202-
{_key_type, context} = of_fun.(key, term(), stack, context)
203-
{_, context} = of_fun.(value, term(), stack, context)
204-
context
205-
end)
206-
207-
{dynamic(), context}
208-
end
209-
210199
def closed_map(pairs, expected, stack, context, of_fun) do
211200
{pairs_types, context} = pairs(pairs, expected, stack, context, of_fun)
212201

@@ -354,18 +343,16 @@ defmodule Module.Types.Of do
354343
Handles instantiation of a new struct.
355344
"""
356345
# TODO: Type check the fields match the struct
357-
def struct_instance(struct, args, expected, meta, %{mode: mode} = stack, context, of_fun)
346+
def struct_instance(struct, args, expected, meta, stack, context, of_fun)
358347
when is_atom(struct) do
359348
{_info, context} = struct_info(struct, meta, stack, context)
360349

361350
# The compiler has already checked the keys are atoms and which ones are required.
362351
{args_types, context} =
363352
Enum.map_reduce(args, context, fn {key, value}, context when is_atom(key) ->
364353
value_type =
365-
with true <- mode != :traversal,
366-
{_, expected_value_type} <- map_fetch_key(expected, key) do
367-
expected_value_type
368-
else
354+
case map_fetch_key(expected, key) do
355+
{_, expected_value_type} -> expected_value_type
369356
_ -> term()
370357
end
371358

0 commit comments

Comments
 (0)