@@ -133,15 +133,50 @@ defmodule Module.Types.Descr do
133133 end
134134
135135 @ doc """
136+ Converts a list of arguments into a domain.
137+
136138 Tuples represent function domains, using unions to combine parameters.
137139
138- Example: for functions (integer, float ->:ok) and (float, integer -> :error)
139- domain isn't {integer|float,integer|float} as that would incorrectly accept {float,float}
140- Instead, it is {integer,float} or {float,integer}
140+ Example: for functions (integer(), float() -> :ok) and (float(), integer() -> :error)
141+ domain isn't `{integer() or float(), integer() or float()}` as that would incorrectly
142+ accept `{float(), float()}`, instead it is `{integer(), float()} or {float(), integer()}`.
143+ """
144+ def args_to_domain ( types ) when is_list ( types ) , do: tuple ( types )
145+
146+ @ doc """
147+ Converts the domain to arguments.
148+
149+ The domain is expected to be closed tuples. They may have complex negations
150+ which are then simplified to a union of positive tuple literals only.
141151
142- Made public for testing.
152+ * For static tuple types: eliminates all negations from the DNF representation.
153+
154+ * For gradual tuple types: processes both dynamic and static components separately,
155+ then combines them.
156+
157+ Internally it uses `tuple_reduce/4` with concatenation as the join function
158+ and a transform that is simply the identity.
143159 """
144- def domain_descr ( types ) when is_list ( types ) , do: tuple ( types )
160+ def domain_to_args ( descr ) do
161+ case :maps . take ( :dynamic , descr ) do
162+ :error ->
163+ { tuple_elim_negations_static ( descr ) , [ ] }
164+
165+ { dynamic , static } ->
166+ { tuple_elim_negations_static ( static ) , tuple_elim_negations_static ( dynamic ) }
167+ end
168+ end
169+
170+ # Call tuple_reduce to build the simple union of tuples that come from each map literal.
171+ # Thus, initial is `[]`, join is concatenation, and the transform of a map literal
172+ # with no negations is just to keep the map literal as is.
173+ defp tuple_elim_negations_static ( % { tuple: dnf } = descr ) when map_size ( descr ) == 1 do
174+ tuple_reduce ( dnf , [ ] , & Kernel . ++ / 2 , fn :closed , elements ->
175+ [ elements ]
176+ end )
177+ end
178+
179+ defp tuple_elim_negations_static ( descr ) when descr == % { } , do: [ ]
145180
146181 ## Optional
147182
@@ -988,80 +1023,71 @@ defmodule Module.Types.Descr do
9881023 end
9891024
9901025 def fun_apply ( fun , arguments ) do
991- if empty? ( domain_descr ( arguments ) ) do
992- :badarg
993- else
994- case :maps . take ( :dynamic , fun ) do
995- :error ->
996- if fun_only? ( fun ) do
997- fun_apply_with_strategy ( fun , nil , arguments )
998- else
999- :badfun
1000- end
1026+ case :maps . take ( :dynamic , fun ) do
1027+ :error ->
1028+ if fun_only? ( fun ) do
1029+ fun_apply_with_strategy ( fun , fun , nil , arguments )
1030+ else
1031+ :badfun
1032+ end
10011033
1002- # Optimize the cases where dynamic closes over all function types
1003- { :term , fun_static } when fun_static == % { } ->
1004- { :ok , dynamic ( ) }
1005-
1006- { % { fun: @ fun_top } , fun_static } when fun_static == % { } ->
1007- { :ok , dynamic ( ) }
1008-
1009- { fun_dynamic , fun_static } ->
1010- if fun_only? ( fun_static ) do
1011- with :badarg <- fun_apply_with_strategy ( fun_static , fun_dynamic , arguments ) do
1012- if compatible? ( fun , fun ( arguments , term ( ) ) ) do
1013- { :ok , dynamic ( ) }
1014- else
1015- :badarg
1016- end
1017- end
1018- else
1019- :badfun
1020- end
1021- end
1034+ # Optimize the cases where dynamic closes over all function types
1035+ { :term , fun_static } when fun_static == % { } ->
1036+ { :ok , dynamic ( ) }
1037+
1038+ { % { fun: @ fun_top } , fun_static } when fun_static == % { } ->
1039+ { :ok , dynamic ( ) }
1040+
1041+ { fun_dynamic , fun_static } ->
1042+ if fun_only? ( fun_static ) do
1043+ fun_apply_with_strategy ( fun , fun_static , fun_dynamic , arguments )
1044+ else
1045+ :badfun
1046+ end
10221047 end
10231048 end
10241049
10251050 defp fun_only? ( descr ) , do: empty? ( Map . delete ( descr , :fun ) )
10261051
1027- defp fun_apply_with_strategy ( fun_static , fun_dynamic , arguments ) do
1052+ defp fun_apply_with_strategy ( fun , fun_static , fun_dynamic , arguments ) do
10281053 args_dynamic? = any_dynamic? ( arguments )
1054+ args_domain = args_to_domain ( arguments )
1055+ static? = fun_dynamic == nil and not args_dynamic?
10291056 arity = length ( arguments )
10301057
1031- # For non-dynamic function and arguments, just return the static result
1032- if fun_dynamic == nil and not args_dynamic? do
1033- with { :ok , static_domain , static_arrows } <- fun_normalize ( fun_static , arity , :static ) do
1034- if subtype? ( domain_descr ( arguments ) , static_domain ) do
1058+ with { :ok , domain , static_arrows , dynamic_arrows } <-
1059+ fun_normalize_both ( fun_static , fun_dynamic , arity ) do
1060+ cond do
1061+ empty? ( args_domain ) ->
1062+ { :badarg , domain }
1063+
1064+ not subtype? ( args_domain , domain ) ->
1065+ if static? or not compatible? ( fun , fun ( arguments , term ( ) ) ) do
1066+ { :badarg , domain }
1067+ else
1068+ { :ok , dynamic ( ) }
1069+ end
1070+
1071+ static? ->
10351072 { :ok , fun_apply_static ( arguments , static_arrows , false ) }
1036- else
1037- :badarg
1038- end
1039- end
1040- else
1041- with { :ok , domain , static_arrows , dynamic_arrows } <-
1042- fun_normalize_both ( fun_static , fun_dynamic , arity ) do
1043- cond do
1044- not subtype? ( domain_descr ( arguments ) , domain ) ->
1045- :badarg
10461073
1047- static_arrows == [ ] ->
1048- { :ok , dynamic ( fun_apply_static ( arguments , dynamic_arrows , false ) ) }
1074+ static_arrows == [ ] ->
1075+ { :ok , dynamic ( fun_apply_static ( arguments , dynamic_arrows , false ) ) }
10491076
1050- true ->
1051- # For dynamic cases, combine static and dynamic results
1052- { static_args , dynamic_args , maybe_empty? } =
1053- if args_dynamic? do
1054- { Enum . map ( arguments , & upper_bound / 1 ) , Enum . map ( arguments , & lower_bound / 1 ) , true }
1055- else
1056- { arguments , arguments , false }
1057- end
1058-
1059- { :ok ,
1060- union (
1061- fun_apply_static ( static_args , static_arrows , false ) ,
1062- dynamic ( fun_apply_static ( dynamic_args , dynamic_arrows , maybe_empty? ) )
1063- ) }
1064- end
1077+ true ->
1078+ # For dynamic cases, combine static and dynamic results
1079+ { static_args , dynamic_args , maybe_empty? } =
1080+ if args_dynamic? do
1081+ { Enum . map ( arguments , & upper_bound / 1 ) , Enum . map ( arguments , & lower_bound / 1 ) , true }
1082+ else
1083+ { arguments , arguments , false }
1084+ end
1085+
1086+ { :ok ,
1087+ union (
1088+ fun_apply_static ( static_args , static_arrows , false ) ,
1089+ dynamic ( fun_apply_static ( dynamic_args , dynamic_arrows , maybe_empty? ) )
1090+ ) }
10651091 end
10661092 end
10671093 end
@@ -1137,7 +1163,7 @@ defmodule Module.Types.Descr do
11371163 # Calculate domain from all positive functions
11381164 path_domain =
11391165 Enum . reduce ( pos_funs , none ( ) , fn { args , _ } , acc ->
1140- union ( acc , domain_descr ( args ) )
1166+ union ( acc , args_to_domain ( args ) )
11411167 end )
11421168
11431169 { intersection ( domain , path_domain ) , [ pos_funs | arrows ] , bad_arities }
@@ -1161,7 +1187,7 @@ defmodule Module.Types.Descr do
11611187 end
11621188
11631189 defp fun_apply_static ( arguments , arrows , maybe_empty? ) do
1164- type_args = domain_descr ( arguments )
1190+ type_args = args_to_domain ( arguments )
11651191
11661192 # Optimization: short-circuits when inner loop is none() or outer loop is term()
11671193 if maybe_empty? and empty? ( type_args ) do
@@ -1202,7 +1228,7 @@ defmodule Module.Types.Descr do
12021228
12031229 defp aux_apply ( result , input , returns_reached , [ { dom , ret } | arrow_intersections ] ) do
12041230 # Calculate the part of the input not covered by this arrow's domain
1205- dom_subtract = difference ( input , domain_descr ( dom ) )
1231+ dom_subtract = difference ( input , args_to_domain ( dom ) )
12061232
12071233 # Refine the return type by intersecting with this arrow's return type
12081234 ret_refine = intersection ( returns_reached , ret )
@@ -1298,7 +1324,7 @@ defmodule Module.Types.Descr do
12981324 # function's domain is a supertype of the positive domain and if the phi function
12991325 # determines emptiness.
13001326 length ( neg_arguments ) == positive_arity and
1301- subtype? ( domain_descr ( neg_arguments ) , positive_domain ) and
1327+ subtype? ( args_to_domain ( neg_arguments ) , positive_domain ) and
13021328 phi_starter ( neg_arguments , negation ( neg_return ) , positives )
13031329 end )
13041330 end
@@ -1311,10 +1337,10 @@ defmodule Module.Types.Descr do
13111337 positives
13121338 |> Enum . reduce_while ( { :empty , none ( ) } , fn
13131339 { args , _ } , { :empty , _ } ->
1314- { :cont , { length ( args ) , domain_descr ( args ) } }
1340+ { :cont , { length ( args ) , args_to_domain ( args ) } }
13151341
13161342 { args , _ } , { arity , dom } when length ( args ) == arity ->
1317- { :cont , { arity , union ( dom , domain_descr ( args ) ) } }
1343+ { :cont , { arity , union ( dom , args_to_domain ( args ) ) } }
13181344
13191345 { _args , _ } , { _arity , _ } ->
13201346 { :halt , { :empty , none ( ) } }
@@ -3091,6 +3117,9 @@ defmodule Module.Types.Descr do
30913117 end )
30923118 end
30933119
3120+ @ doc """
3121+ Returns all of the values that are part of a tuple.
3122+ """
30943123 def tuple_values ( descr ) do
30953124 case :maps . take ( :dynamic , descr ) do
30963125 :error ->
@@ -3182,46 +3211,6 @@ defmodule Module.Types.Descr do
31823211 )
31833212 end
31843213
3185- @ doc """
3186- Converts a tuple type to a simple union by eliminating negations.
3187-
3188- Takes a tuple type with complex negations and simplifies it to a union of
3189- positive tuple literals only.
3190-
3191- For static tuple types: eliminates all negations from the DNF representation.
3192- For gradual tuple types: processes both dynamic and static components separately,
3193- then combines them.
3194-
3195- Uses `tuple_reduce/4` with concatenation as the join function and a transform
3196- that is simply the identity.
3197-
3198- Returns the descriptor unchanged for non-tuple types, or a descriptor with
3199- simplified tuple DNF containing only positive literals. If simplification
3200- results in an empty tuple list, removes the `:tuple` key entirely.
3201- """
3202- def tuple_elim_negations ( descr ) do
3203- case :maps . take ( :dynamic , descr ) do
3204- :error ->
3205- tuple_elim_negations_static ( descr )
3206-
3207- { dynamic , static } ->
3208- tuple_elim_negations_static ( static )
3209- |> union ( dynamic ( tuple_elim_negations_static ( dynamic ) ) )
3210- end
3211- end
3212-
3213- # Call tuple_reduce to build the simple union of tuples that come from each map literal.
3214- # Thus, initial is `[]`, join is concatenation, and the transform of a map literal
3215- # with no negations is just to keep the map literal as is.
3216- defp tuple_elim_negations_static ( % { tuple: dnf } = descr ) do
3217- case tuple_reduce ( dnf , [ ] , & Kernel . ++ / 2 , fn tag , elements -> [ { tag , elements , [ ] } ] end ) do
3218- [ ] -> Map . delete ( descr , :tuple )
3219- new_dnf -> % { descr | tuple: new_dnf }
3220- end
3221- end
3222-
3223- defp tuple_elim_negations_static ( descr ) , do: descr
3224-
32253214 defp tuple_pop_index ( tag , elements , index ) do
32263215 case List . pop_at ( elements , index ) do
32273216 { nil , _ } -> { tag_to_type ( tag ) , % { tuple: [ { tag , elements , [ ] } ] } }
0 commit comments