@@ -2,13 +2,48 @@ defmodule Module.Types do
22 @ moduledoc false
33 alias Module.Types . { Descr , Expr , Pattern , Helpers }
44
5+ # The mode controls what happens on function application when
6+ # there are gradual arguments. Non-gradual arguments always
7+ # perform subtyping and return its output (OUT).
8+ #
9+ # * :strict - Requires types signatures (not implemented).
10+ # * Strong arrows with gradual performs subtyping and returns OUT
11+ # * Weak arrows with gradual performs subtyping and returns OUT
12+ #
13+ # * :static - Type signatures have been given.
14+ # * Strong arrows with gradual performs compatibility and returns OUT
15+ # * Weak arrows with gradual performs compatibility and returns dynamic()
16+ #
17+ # * :dynamic - Type signatures have not been given.
18+ # * Strong arrows with gradual performs compatibility and returns dynamic(OUT)
19+ # * Weak arrows with gradual performs compatibility and returns dynamic()
20+ #
21+ # * :infer - Same as :dynamic but skips remote calls.
22+ #
23+ # * :traversal - Focused mostly on traversing AST, skips most type system
24+ # operations. Used by macros and when skipping inference.
25+ #
26+ # The mode may also control exhaustiveness checks in the future (to be decided).
27+ # We may also want for applications with subtyping in dynamic mode to always
28+ # intersect with dynamic, but this mode may be too lax (to be decided based on
29+ # feedback).
30+ @ modes [ :static , :dynamic , :infer , :traversal ]
31+
532 # These functions are not inferred because they are added/managed by the compiler
633 @ no_infer [ __protocol__: 1 , behaviour_info: 1 ]
734
835 @ doc false
936 def infer ( module , file , defs , private , defmacrop , env ) do
37+ infer_signatures? = :elixir_config . get ( :infer_signatures )
1038 defmacrop = Map . from_keys ( defmacrop , [ ] )
11- finder = & :lists . keyfind ( & 1 , 1 , defs )
39+
40+ finder =
41+ fn fun_arity ->
42+ case :lists . keyfind ( fun_arity , 1 , defs ) do
43+ { _ , kind , _ , _ } = clause -> { infer_mode ( kind , infer_signatures? ) , clause }
44+ false -> false
45+ end
46+ end
1247
1348 handler = fn meta , fun_arity , stack , context ->
1449 case local_handler ( meta , fun_arity , stack , context , finder ) do
@@ -34,10 +69,10 @@ defmodule Module.Types do
3469 { types , context } ->
3570 cond do
3671 kind in [ :def , :defmacro ] ->
37- { _kind , inferred , context } =
38- local_handler ( meta , fun_arity , stack , context , fn _ -> def end )
72+ finder = fn _ -> { infer_mode ( kind , infer_signatures? ) , def } end
73+ { _kind , inferred , context } = local_handler ( meta , fun_arity , stack , context , finder )
3974
40- if kind == :def and fun_arity not in @ no_infer do
75+ if infer_signatures? and kind == :def and fun_arity not in @ no_infer do
4176 { [ { fun_arity , inferred } | types ] , context }
4277 else
4378 { types , context }
@@ -48,9 +83,10 @@ defmodule Module.Types do
4883 # we don't need them stored in the signatures when we perform
4984 # unreachable checks. This may cause defmacrop to be traversed
5085 # twice if it uses default arguments (which is the only way
51- # to refer to another defmacrop in definitions).
86+ # to refer to another defmacrop in definitions) but that should
87+ # be cheap anyway.
5288 { _kind , _inferred , context } =
53- local_handler ( fun_arity , kind , meta , clauses , stack , context )
89+ local_handler ( fun_arity , kind , meta , clauses , :traversal , stack , context )
5490
5591 { types , context }
5692
@@ -68,6 +104,10 @@ defmodule Module.Types do
68104 { Map . new ( types ) , unreachable }
69105 end
70106
107+ defp infer_mode ( kind , infer_signatures? ) do
108+ if infer_signatures? and kind in [ :def , :defp ] , do: :infer , else: :traversal
109+ end
110+
71111 defp undefined_function! ( reason , meta , { fun , arity } , stack , env ) do
72112 env = % { env | function: stack . function , file: stack . file }
73113 tuple = { reason , { fun , arity } , stack . module }
@@ -117,16 +157,21 @@ defmodule Module.Types do
117157
118158 @ doc false
119159 def warnings ( module , file , defs , no_warn_undefined , cache ) do
120- finder = & :lists . keyfind ( & 1 , 1 , defs )
160+ finder = fn fun_arity ->
161+ case :lists . keyfind ( fun_arity , 1 , defs ) do
162+ { _ , _ , _ , _ } = clause -> { :dynamic , clause }
163+ false -> false
164+ end
165+ end
166+
121167 handler = & local_handler ( & 1 , & 2 , & 3 , & 4 , finder )
122168 stack = stack ( :dynamic , file , module , { :__info__ , 1 } , no_warn_undefined , cache , handler )
123169 context = context ( % { } )
124170
125171 context =
126172 Enum . reduce ( defs , context , fn { fun_arity , _kind , meta , _clauses } = def , context ->
127- { _kind , _inferred , context } =
128- local_handler ( meta , fun_arity , stack , context , fn _ -> def end )
129-
173+ finder = fn _ -> { :dynamic , def } end
174+ { _kind , _inferred , context } = local_handler ( meta , fun_arity , stack , context , finder )
130175 context
131176 end )
132177
@@ -161,11 +206,11 @@ defmodule Module.Types do
161206
162207 local_sigs ->
163208 case finder . ( fun_arity ) do
164- { fun_arity , kind , meta , clauses } ->
209+ { mode , { fun_arity , kind , meta , clauses } } ->
165210 context = put_in ( context . local_sigs , Map . put ( local_sigs , fun_arity , kind ) )
166211
167212 { inferred , mapping , context } =
168- local_handler ( fun_arity , kind , meta , clauses , stack , context )
213+ local_handler ( fun_arity , kind , meta , clauses , mode , stack , context )
169214
170215 context =
171216 update_in ( context . local_sigs , & Map . put ( & 1 , fun_arity , { kind , inferred , mapping } ) )
@@ -178,9 +223,9 @@ defmodule Module.Types do
178223 end
179224 end
180225
181- defp local_handler ( { fun , arity } = fun_arity , kind , meta , clauses , stack , context ) do
226+ defp local_handler ( { fun , arity } = fun_arity , kind , meta , clauses , mode , stack , context ) do
182227 expected = List . duplicate ( Descr . dynamic ( ) , arity )
183- stack = stack |> fresh_stack ( kind , fun_arity ) |> with_file_meta ( meta )
228+ stack = stack |> fresh_stack ( mode , fun_arity ) |> with_file_meta ( meta )
184229
185230 { _ , _ , mapping , clauses_types , clauses_context } =
186231 Enum . reduce ( clauses , { 0 , 0 , [ ] , [ ] , context } , fn
@@ -259,7 +304,7 @@ defmodule Module.Types do
259304
260305 @ doc false
261306 def stack ( mode , file , module , function , no_warn_undefined , cache , handler )
262- when mode in [ :static , :dynamic , :infer , :traversal ] do
307+ when mode in @ modes do
263308 % {
264309 # The fallback meta used for literals in patterns and guards
265310 meta: [ ] ,
@@ -273,31 +318,7 @@ defmodule Module.Types do
273318 no_warn_undefined: no_warn_undefined ,
274319 # A tuple with cache information or a Macro.Env struct indicating no remote traversals
275320 cache: cache ,
276- # The mode controls what happens on function application when
277- # there are gradual arguments. Non-gradual arguments always
278- # perform subtyping and return its output (OUT).
279- #
280- # * :strict - Requires types signatures (not implemented).
281- # * Strong arrows with gradual performs subtyping and returns OUT
282- # * Weak arrows with gradual performs subtyping and returns OUT
283- #
284- # * :static - Type signatures have been given.
285- # * Strong arrows with gradual performs compatibility and returns OUT
286- # * Weak arrows with gradual performs compatibility and returns dynamic()
287- #
288- # * :dynamic - Type signatures have not been given.
289- # * Strong arrows with gradual performs compatibility and returns dynamic(OUT)
290- # * Weak arrows with gradual performs compatibility and returns dynamic()
291- #
292- # * :infer - Same as :dynamic but skips remote calls.
293- #
294- # * :traversal - Focused mostly on traversing AST, skips most type system
295- # operations. Used by macros and functions which already have signatures.
296- #
297- # The mode may also control exhaustiveness checks in the future (to be decided).
298- # We may also want for applications with subtyping in dynamic mode to always
299- # intersect with dynamic, but this mode may be too lax (to be decided based on
300- # feedback).
321+ # The mode to be used, see the @modes attribute
301322 mode: mode ,
302323 # The function for handling local calls
303324 local_handler: handler
@@ -322,15 +343,8 @@ defmodule Module.Types do
322343 }
323344 end
324345
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 }
346+ defp fresh_stack ( stack , mode , function ) when mode in @ modes do
347+ % { stack | mode: mode , function: function }
334348 end
335349
336350 defp fresh_context ( context ) do
0 commit comments