@@ -6,7 +6,8 @@ defmodule Module.Types do
66 @ no_infer [ __protocol__: 1 , behaviour_info: 1 ]
77
88 @ doc false
9- def infer ( module , file , defs , private , used , env ) do
9+ def infer ( module , file , defs , private , defmacrop , env ) do
10+ defmacrop = Map . from_keys ( defmacrop , [ ] )
1011 finder = & :lists . keyfind ( & 1 , 1 , defs )
1112
1213 handler = fn meta , fun_arity , stack , context ->
@@ -29,23 +30,38 @@ defmodule Module.Types do
2930 context = context ( % { } )
3031
3132 { types , % { local_sigs: local_sigs } } =
32- for { fun_arity , kind , meta , _clauses } = def <- defs ,
33- kind == :def or kind == :defmacro ,
34- reduce: { [ ] , context } do
33+ for { fun_arity , kind , meta , clauses } = def <- defs , reduce: { [ ] , context } do
3534 { types , context } ->
36- { _kind , inferred , context } =
37- local_handler ( meta , fun_arity , stack , context , fn _ -> def end )
38-
39- if kind == :def and fun_arity not in @ no_infer do
40- { [ { fun_arity , inferred } | types ] , context }
41- else
42- { types , context }
35+ cond do
36+ kind in [ :def , :defmacro ] ->
37+ { _kind , inferred , context } =
38+ local_handler ( meta , fun_arity , stack , context , fn _ -> def end )
39+
40+ if kind == :def and fun_arity not in @ no_infer do
41+ { [ { fun_arity , inferred } | types ] , context }
42+ else
43+ { types , context }
44+ end
45+
46+ kind == :defmacrop and is_map_key ( defmacrop , fun_arity ) ->
47+ # Bypass the caching structure for defmacrop, that's because
48+ # we don't need them stored in the signatures when we perform
49+ # unreachable checks. This may cause defmacrop to be traversed
50+ # twice if it uses default arguments (which is the only way
51+ # to refer to another defmacrop in definitions).
52+ { _kind , _inferred , context } =
53+ local_handler ( fun_arity , kind , meta , clauses , stack , context )
54+
55+ { types , context }
56+
57+ true ->
58+ { types , context }
4359 end
4460 end
4561
4662 unreachable =
4763 for { fun_arity , _kind , _meta , _defaults } = info <- private ,
48- warn_unused_def ( info , local_sigs , used , env ) ,
64+ warn_unused_def ( info , local_sigs , defmacrop , env ) ,
4965 not is_map_key ( local_sigs , fun_arity ) ,
5066 do: fun_arity
5167
@@ -63,7 +79,7 @@ defmodule Module.Types do
6379 end
6480
6581 defp warn_unused_def ( { fun_arity , kind , meta , 0 } , reachable , used , env ) do
66- case meta == false or Map . has_key? ( reachable , fun_arity ) or fun_arity in used do
82+ case is_map_key ( reachable , fun_arity ) or is_map_key ( used , fun_arity ) do
6783 true -> :ok
6884 false -> :elixir_errors . file_warn ( meta , env , __MODULE__ , { :unused_def , fun_arity , kind } )
6985 end
@@ -89,7 +105,7 @@ defmodule Module.Types do
89105 defp min_reachable_default ( max , min , last , name , reachable , used ) when max >= min do
90106 fun_arity = { name , max }
91107
92- case Map . has_key? ( reachable , fun_arity ) or fun_arity in used do
108+ case is_map_key ( reachable , fun_arity ) or is_map_key ( used , fun_arity ) do
93109 true -> min_reachable_default ( max - 1 , min , max , name , reachable , used )
94110 false -> min_reachable_default ( max - 1 , min , last , name , reachable , used )
95111 end
@@ -164,7 +180,7 @@ defmodule Module.Types do
164180
165181 defp local_handler ( { fun , arity } = fun_arity , kind , meta , clauses , stack , context ) do
166182 expected = List . duplicate ( Descr . dynamic ( ) , arity )
167- stack = stack |> fresh_stack ( fun_arity ) |> with_file_meta ( meta )
183+ stack = stack |> fresh_stack ( kind , fun_arity ) |> with_file_meta ( meta )
168184
169185 { _ , _ , mapping , clauses_types , clauses_context } =
170186 Enum . reduce ( clauses , { 0 , 0 , [ ] , [ ] , context } , fn
@@ -243,7 +259,7 @@ defmodule Module.Types do
243259
244260 @ doc false
245261 def stack ( mode , file , module , function , no_warn_undefined , cache , handler )
246- when mode in [ :static , :dynamic , :infer ] do
262+ when mode in [ :static , :dynamic , :infer , :traversal ] do
247263 % {
248264 # The fallback meta used for literals in patterns and guards
249265 meta: [ ] ,
@@ -275,6 +291,9 @@ defmodule Module.Types do
275291 #
276292 # * :infer - Same as :dynamic but skips remote calls.
277293 #
294+ # * :traversal - Focused mostly on traversing AST, skips most type system
295+ # operations. Used by macros and functions which already have signatures.
296+ #
278297 # The mode may also control exhaustiveness checks in the future (to be decided).
279298 # We may also want for applications with subtyping in dynamic mode to always
280299 # intersect with dynamic, but this mode may be too lax (to be decided based on
@@ -303,8 +322,15 @@ defmodule Module.Types do
303322 }
304323 end
305324
306- defp fresh_stack ( stack , function ) do
307- % { stack | function: function }
325+ defp fresh_stack ( % { mode: mode } = stack , kind , function ) do
326+ mode =
327+ cond do
328+ kind in [ :defmacro , :defmacrop ] and mode == :infer -> :traversal
329+ kind in [ :def , :defp ] and mode == :traversal -> :infer
330+ true -> mode
331+ end
332+
333+ % { stack | function: function , mode: mode }
308334 end
309335
310336 defp fresh_context ( context ) do
0 commit comments