@@ -930,31 +930,19 @@ defmodule Module.Types.Descr do
930
930
# Creates a function type from a list of inputs and an output
931
931
# where the inputs and/or output may be dynamic.
932
932
#
933
- # For function (t → s) with dynamic components:
933
+ # One approach is, for function (t → s) with dynamic components:
934
934
# - Static part: (upper_bound(t) → lower_bound(s))
935
935
# - Dynamic part: dynamic(lower_bound(t) → upper_bound(s))
936
936
#
937
- # When handling dynamic types:
938
- # - `upper_bound(t)` extracts the upper bound (most general type) of a gradual type.
939
- # For `dynamic(integer())`, it is `integer( )`.
940
- # - `lower_bound(t)` extracts the lower bound (most specific type) of a gradual type .
937
+ # However, this comes with the downside that ` dynamic(integer()) -> binary()`
938
+ # cannot receive integers as arguments. So instead we surface the dynamic up,
939
+ # as we do for other data types, converting it to `dynamic(( integer() -> binary()) )`.
940
+ # One could obtain the other type if desired by explicitly defining it .
941
941
defp fun_descr ( args , output ) when is_list ( args ) do
942
- dynamic_arguments? = are_arguments_dynamic? ( args )
943
- dynamic_output? = match? ( % { dynamic: _ } , output )
944
-
945
- if dynamic_arguments? or dynamic_output? do
946
- input_static = if dynamic_arguments? , do: materialize_arguments ( args , :up ) , else: args
947
- input_dynamic = if dynamic_arguments? , do: materialize_arguments ( args , :down ) , else: args
948
-
949
- output_static = if dynamic_output? , do: lower_bound ( output ) , else: output
950
- output_dynamic = if dynamic_output? , do: upper_bound ( output ) , else: output
951
-
952
- % {
953
- fun: fun_new ( input_static , output_static ) ,
954
- dynamic: % { fun: fun_new ( input_dynamic , output_dynamic ) }
955
- }
942
+ if any_dynamic? ( [ output | args ] ) do
943
+ [ output | args ] = Enum . map ( [ output | args ] , & upper_bound / 1 )
944
+ % { dynamic: % { fun: fun_new ( args , output ) } }
956
945
else
957
- # No dynamic components, use standard function type
958
946
% { fun: fun_new ( args , output ) }
959
947
end
960
948
end
@@ -1027,7 +1015,7 @@ defmodule Module.Types.Descr do
1027
1015
defp fun_only? ( descr ) , do: empty? ( Map . delete ( descr , :fun ) )
1028
1016
1029
1017
defp fun_apply_with_strategy ( fun_static , fun_dynamic , arguments ) do
1030
- args_dynamic? = are_arguments_dynamic ?( arguments )
1018
+ args_dynamic? = any_dynamic ?( arguments )
1031
1019
arity = length ( arguments )
1032
1020
1033
1021
# For non-dynamic function and arguments, just return the static result
@@ -1053,8 +1041,7 @@ defmodule Module.Types.Descr do
1053
1041
# For dynamic cases, combine static and dynamic results
1054
1042
{ static_args , dynamic_args , maybe_empty? } =
1055
1043
if args_dynamic? do
1056
- { materialize_arguments ( arguments , :up ) , materialize_arguments ( arguments , :down ) ,
1057
- true }
1044
+ { Enum . map ( arguments , & upper_bound / 1 ) , Enum . map ( arguments , & lower_bound / 1 ) , true }
1058
1045
else
1059
1046
{ arguments , arguments , false }
1060
1047
end
@@ -1069,11 +1056,7 @@ defmodule Module.Types.Descr do
1069
1056
end
1070
1057
end
1071
1058
1072
- # Materializes arguments using the specified direction (up or down)
1073
- defp materialize_arguments ( arguments , :up ) , do: Enum . map ( arguments , & upper_bound / 1 )
1074
- defp materialize_arguments ( arguments , :down ) , do: Enum . map ( arguments , & lower_bound / 1 )
1075
-
1076
- defp are_arguments_dynamic? ( arguments ) , do: Enum . any? ( arguments , & match? ( % { dynamic: _ } , & 1 ) )
1059
+ defp any_dynamic? ( arguments ) , do: Enum . any? ( arguments , & match? ( % { dynamic: _ } , & 1 ) )
1077
1060
1078
1061
defp fun_normalize_both ( fun_static , fun_dynamic , arity ) do
1079
1062
case fun_normalize ( fun_static , arity , :static ) do
0 commit comments