@@ -715,6 +715,7 @@ defmodule Module.Types.Descr do
715
715
- Either the static part is a non-empty function type of the given arity, or
716
716
- The static part is empty and the dynamic part contains functions of the given arity
717
717
"""
718
+ # TODO: REMOVE ME
718
719
def fun_fetch ( :term , _arity ) , do: :error
719
720
720
721
def fun_fetch ( % { } = descr , arity ) when is_integer ( arity ) do
@@ -733,7 +734,6 @@ defmodule Module.Types.Descr do
733
734
end
734
735
end
735
736
736
- defp fun_only? ( descr ) , do: empty? ( Map . delete ( descr , :fun ) )
737
737
defp fun_only? ( descr , arity ) , do: empty? ( difference ( descr , fun ( arity ) ) )
738
738
739
739
## Atoms
@@ -916,7 +916,7 @@ defmodule Module.Types.Descr do
916
916
# * Representation:
917
917
# - fun(): Top function type (leaf 1)
918
918
# - Function literals: {[t1, ..., tn], t} where [t1, ..., tn] are argument types and t is return type
919
- # - Normalized form for function applications: {domain, arrows, arity } is produced by `fun_normalize/1 `
919
+ # - Normalized form for function applications: {domain, arrows} is produced by `fun_normalize/3 `
920
920
921
921
# * Examples:
922
922
# - fun([integer()], atom()): A function from integer to atom
@@ -967,74 +967,6 @@ defmodule Module.Types.Descr do
967
967
defp lower_bound ( :term ) , do: :term
968
968
defp lower_bound ( type ) , do: Map . delete ( type , :dynamic )
969
969
970
- @ doc """
971
- Calculates the domain of a function type.
972
-
973
- For a function type, the domain is the set of valid input types.
974
-
975
- Returns:
976
- - `:badfun` if the type is not a function type
977
- - A tuple type representing the domain for valid function types
978
-
979
- Handles both static and dynamic function types:
980
- 1. For static functions, returns their exact domain
981
- 2. For dynamic functions, computes domain based on both static and dynamic parts
982
-
983
- Formula is dom(t) = dom(upper_bound(t)) ∪ dynamic(dom(lower_bound(t))).
984
- See Definition 6.15 in https://vlanvin.fr/papers/thesis.pdf.
985
-
986
- ## Examples
987
- iex> fun_domain(fun([integer()], atom()))
988
- domain_repr([integer()])
989
-
990
- iex> fun_domain(fun([integer(), float()], boolean()))
991
- domain_repr([integer(), float()])
992
- """
993
- def fun_domain ( :term ) , do: :badfun
994
-
995
- def fun_domain ( type ) do
996
- result =
997
- case :maps . take ( :dynamic , type ) do
998
- :error ->
999
- # Static function type
1000
- with true <- fun_only? ( type ) , { :ok , domain } <- fun_domain_static ( type ) do
1001
- domain
1002
- else
1003
- _ -> :badfun
1004
- end
1005
-
1006
- { dynamic , static } when static == @ none ->
1007
- with { :ok , domain } <- fun_domain_static ( dynamic ) , do: domain
1008
-
1009
- { dynamic , static } ->
1010
- with true <- fun_only? ( static ) ,
1011
- { :ok , static_domain } <- fun_domain_static ( static ) ,
1012
- { :ok , dynamic_domain } <- fun_domain_static ( dynamic ) do
1013
- union ( dynamic_domain , dynamic ( static_domain ) )
1014
- else
1015
- _ -> :badfun
1016
- end
1017
- end
1018
-
1019
- case result do
1020
- :badfun -> :badfun
1021
- result -> if empty? ( result ) , do: :badfun , else: result
1022
- end
1023
- end
1024
-
1025
- # Returns {:ok, domain} if the domain of the static type is well-defined.
1026
- # For that, it has to contain a non-empty function type.
1027
- # Otherwise, returns :badfun.
1028
- defp fun_domain_static ( % { fun: bdd } ) do
1029
- case fun_normalize ( bdd ) do
1030
- { domain , _ , _ } -> { :ok , domain }
1031
- _ -> { :ok , none ( ) }
1032
- end
1033
- end
1034
-
1035
- defp fun_domain_static ( :term ) , do: :badfun
1036
- defp fun_domain_static ( % { } ) , do: { :ok , none ( ) }
1037
-
1038
970
@ doc """
1039
971
Applies a function type to a list of argument types.
1040
972
@@ -1056,46 +988,86 @@ defmodule Module.Types.Descr do
1056
988
# For more details, see Definition 6.15 in https://vlanvin.fr/papers/thesis.pdf
1057
989
1058
990
## Examples
991
+
1059
992
iex> fun_apply(fun([integer()], atom()), [integer()])
1060
- atom()
993
+ {:ok, atom()}
1061
994
1062
995
iex> fun_apply(fun([integer()], atom()), [float()])
1063
996
:badarg
1064
997
1065
998
iex> fun_apply(fun([dynamic()], atom()), [dynamic()])
1066
- atom()
999
+ {:ok, atom()}
1067
1000
"""
1001
+ def fun_apply ( :term , _arguments ) do
1002
+ :badfun
1003
+ end
1004
+
1068
1005
def fun_apply ( fun , arguments ) do
1069
1006
if empty? ( domain_descr ( arguments ) ) do
1070
1007
:badarg
1071
1008
else
1072
1009
case :maps . take ( :dynamic , fun ) do
1073
- :error -> fun_apply_with_strategy ( fun , nil , arguments )
1074
- { fun_dynamic , fun_static } -> fun_apply_with_strategy ( fun_static , fun_dynamic , arguments )
1010
+ :error ->
1011
+ if fun_only? ( fun ) do
1012
+ fun_apply_with_strategy ( fun , nil , arguments )
1013
+ else
1014
+ :badfun
1015
+ end
1016
+
1017
+ { fun_dynamic , fun_static } ->
1018
+ if fun_only? ( fun_static ) do
1019
+ fun_apply_with_strategy ( fun_static , fun_dynamic , arguments )
1020
+ else
1021
+ :badfun
1022
+ end
1075
1023
end
1076
1024
end
1077
1025
end
1078
1026
1027
+ defp fun_only? ( descr ) , do: empty? ( Map . delete ( descr , :fun ) )
1028
+
1079
1029
defp fun_apply_with_strategy ( fun_static , fun_dynamic , arguments ) do
1080
1030
args_dynamic? = are_arguments_dynamic? ( arguments )
1081
1031
1082
1032
# For non-dynamic function and arguments, just return the static result
1083
1033
if fun_dynamic == nil and not args_dynamic? do
1084
- with { :ok , type } <- fun_apply_static ( fun_static , arguments ) , do: type
1034
+ fun_apply_static ( fun_static , arguments , :static )
1085
1035
else
1086
1036
# For dynamic cases, combine static and dynamic results
1087
1037
{ static_args , dynamic_args } =
1088
1038
if args_dynamic? ,
1089
1039
do: { materialize_arguments ( arguments , :up ) , materialize_arguments ( arguments , :down ) } ,
1090
1040
else: { arguments , arguments }
1091
1041
1092
- dynamic_fun = fun_dynamic || fun_static
1042
+ case fun_apply_static ( fun_static , static_args , :static ) do
1043
+ { :ok , res1 } when fun_dynamic == nil ->
1044
+ with { :ok , res2 } <- fun_apply_static ( fun_static , dynamic_args , :static ) do
1045
+ { :ok , union ( res1 , dynamic ( res2 ) ) }
1046
+ end
1093
1047
1094
- with { :ok , res1 } <- fun_apply_static ( fun_static , static_args ) ,
1095
- { :ok , res2 } <- fun_apply_static ( dynamic_fun , dynamic_args ) do
1096
- union ( res1 , dynamic ( res2 ) )
1097
- else
1098
- _ -> :badarg
1048
+ { :ok , res1 } when fun_dynamic != nil ->
1049
+ # If static succeeded, the dynamic part can fail, we don't care
1050
+ case fun_apply_static ( fun_dynamic , dynamic_args , :dynamic ) do
1051
+ { :ok , res2 } -> { :ok , union ( res1 , dynamic ( res2 ) ) }
1052
+ _ -> { :ok , res1 }
1053
+ end
1054
+
1055
+ :badfun ->
1056
+ # Then the dynamic call has to succeed
1057
+ result =
1058
+ if fun_dynamic do
1059
+ fun_apply_static ( fun_dynamic , dynamic_args , :dynamic )
1060
+ else
1061
+ fun_apply_static ( fun_static , dynamic_args , :static )
1062
+ end
1063
+
1064
+ with { :ok , descr } <- result do
1065
+ { :ok , dynamic ( descr ) }
1066
+ end
1067
+
1068
+ # badarg/badarity
1069
+ error ->
1070
+ error
1099
1071
end
1100
1072
end
1101
1073
end
@@ -1106,48 +1078,47 @@ defmodule Module.Types.Descr do
1106
1078
1107
1079
defp are_arguments_dynamic? ( arguments ) , do: Enum . any? ( arguments , & match? ( % { dynamic: _ } , & 1 ) )
1108
1080
1109
- defp fun_apply_static ( % { fun: fun_bdd } , arguments ) do
1110
- type_args = domain_descr ( arguments )
1081
+ defp fun_apply_static ( % { fun: fun_bdd } , arguments , mode ) do
1082
+ arity = length ( arguments )
1111
1083
1112
- case fun_normalize ( fun_bdd ) do
1113
- { domain , arrows , arity } when arity == length ( arguments ) ->
1114
- cond do
1115
- empty? ( type_args ) ->
1116
- # Opti: short-circuits when inner loop is none() or outer loop is term()
1117
- result =
1118
- Enum . reduce_while ( arrows , none ( ) , fn intersection_of_arrows , acc ->
1119
- Enum . reduce_while ( intersection_of_arrows , term ( ) , fn
1120
- { _dom , _ret } , acc when acc == @ none -> { :halt , acc }
1121
- { _dom , ret } , acc -> { :cont , intersection ( acc , ret ) }
1122
- end )
1123
- |> case do
1124
- :term -> { :halt , :term }
1125
- inner -> { :cont , union ( inner , acc ) }
1126
- end
1127
- end )
1128
-
1129
- { :ok , result }
1084
+ with { :ok , domain , arrows } <- fun_normalize ( fun_bdd , arity , mode ) do
1085
+ type_args = domain_descr ( arguments )
1130
1086
1131
- subtype? ( type_args , domain ) ->
1132
- result =
1133
- Enum . reduce ( arrows , none ( ) , fn intersection_of_arrows , acc ->
1134
- aux_apply ( acc , type_args , term ( ) , intersection_of_arrows )
1087
+ cond do
1088
+ empty? ( type_args ) ->
1089
+ # Opti: short-circuits when inner loop is none() or outer loop is term()
1090
+ result =
1091
+ Enum . reduce_while ( arrows , none ( ) , fn intersection_of_arrows , acc ->
1092
+ Enum . reduce_while ( intersection_of_arrows , term ( ) , fn
1093
+ { _dom , _ret } , acc when acc == @ none -> { :halt , acc }
1094
+ { _dom , ret } , acc -> { :cont , intersection ( acc , ret ) }
1135
1095
end )
1096
+ |> case do
1097
+ :term -> { :halt , :term }
1098
+ inner -> { :cont , union ( inner , acc ) }
1099
+ end
1100
+ end )
1136
1101
1137
- { :ok , result }
1102
+ { :ok , result }
1138
1103
1139
- true ->
1140
- :badarg
1141
- end
1104
+ subtype? ( type_args , domain ) ->
1105
+ result =
1106
+ Enum . reduce ( arrows , none ( ) , fn intersection_of_arrows , acc ->
1107
+ aux_apply ( acc , type_args , term ( ) , intersection_of_arrows )
1108
+ end )
1142
1109
1143
- { _ , _ , arity } ->
1144
- { :badarity , arity }
1110
+ { :ok , result }
1145
1111
1146
- :badfun ->
1147
- :badfun
1112
+ true ->
1113
+ :badarg
1114
+ end
1148
1115
end
1149
1116
end
1150
1117
1118
+ defp fun_apply_static ( % { } , _arguments , _mode ) do
1119
+ :badfun
1120
+ end
1121
+
1151
1122
# Helper function for function application that handles the application of
1152
1123
# function arrows to input types.
1153
1124
@@ -1223,30 +1194,42 @@ defmodule Module.Types.Descr do
1223
1194
#
1224
1195
# ## Internal Use
1225
1196
#
1226
- # This function is used internally by `fun_apply`, `fun_domain`, and others to
1197
+ # This function is used internally by `fun_apply`, and others to
1227
1198
# ensure consistent handling of function types in all operations.
1228
- defp fun_normalize ( bdd ) do
1229
- { domain , arrows , arity } =
1230
- fun_get ( bdd )
1231
- |> Enum . reduce ( { term ( ) , [ ] , nil } , fn { pos_funs , neg_funs } , { domain , arrows , arity } ->
1232
- # Skip empty function intersections
1233
- if fun_empty? ( pos_funs , neg_funs ) do
1234
- { domain , arrows , arity }
1235
- else
1236
- # Determine arity from first positive function or keep existing
1237
- new_arity = arity || pos_funs |> List . first ( ) |> elem ( 0 ) |> length ( )
1199
+ defp fun_normalize ( bdd , arity , mode ) do
1200
+ { domain , arrows , bad_arities } =
1201
+ Enum . reduce ( fun_get ( bdd ) , { term ( ) , [ ] , [ ] } , fn
1202
+ { [ { args , _ } | _ ] = pos_funs , neg_funs } , { domain , arrows , bad_arities } ->
1203
+ arrow_arity = length ( args )
1238
1204
1239
- # Calculate domain from all positive functions
1240
- path_domain =
1241
- Enum . reduce ( pos_funs , none ( ) , fn { args , _ } , acc ->
1242
- union ( acc , domain_descr ( args ) )
1243
- end )
1205
+ cond do
1206
+ arrow_arity != arity ->
1207
+ { domain , arrows , [ arrow_arity | bad_arities ] }
1244
1208
1245
- { intersection ( domain , path_domain ) , [ pos_funs | arrows ] , new_arity }
1246
- end
1209
+ fun_empty? ( pos_funs , neg_funs ) ->
1210
+ { domain , arrows , bad_arities }
1211
+
1212
+ true ->
1213
+ # Calculate domain from all positive functions
1214
+ path_domain =
1215
+ Enum . reduce ( pos_funs , none ( ) , fn { args , _ } , acc ->
1216
+ union ( acc , domain_descr ( args ) )
1217
+ end )
1218
+
1219
+ { intersection ( domain , path_domain ) , [ pos_funs | arrows ] , bad_arities }
1220
+ end
1247
1221
end )
1248
1222
1249
- if arrows == [ ] , do: :badfun , else: { domain , arrows , arity }
1223
+ case { arrows , bad_arities } do
1224
+ { [ ] , [ ] } ->
1225
+ :badfun
1226
+
1227
+ { arrows , [ _ | _ ] = bad_arities } when mode == :static or arrows == [ ] ->
1228
+ { :badarity , Enum . uniq ( bad_arities ) }
1229
+
1230
+ { _ , _ } ->
1231
+ { :ok , domain , arrows }
1232
+ end
1250
1233
end
1251
1234
1252
1235
# Checks if a function type is empty.
0 commit comments