Skip to content

Commit f253a73

Browse files
committed
Surface dynamic up in function creation
1 parent bae8bf4 commit f253a73

File tree

2 files changed

+19
-53
lines changed

2 files changed

+19
-53
lines changed

lib/elixir/lib/module/types/descr.ex

Lines changed: 11 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -930,31 +930,19 @@ defmodule Module.Types.Descr do
930930
# Creates a function type from a list of inputs and an output
931931
# where the inputs and/or output may be dynamic.
932932
#
933-
# For function (t → s) with dynamic components:
933+
# One approach is, for function (t → s) with dynamic components:
934934
# - Static part: (upper_bound(t) → lower_bound(s))
935935
# - Dynamic part: dynamic(lower_bound(t) → upper_bound(s))
936936
#
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.
941941
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)}}
956945
else
957-
# No dynamic components, use standard function type
958946
%{fun: fun_new(args, output)}
959947
end
960948
end
@@ -1027,7 +1015,7 @@ defmodule Module.Types.Descr do
10271015
defp fun_only?(descr), do: empty?(Map.delete(descr, :fun))
10281016

10291017
defp fun_apply_with_strategy(fun_static, fun_dynamic, arguments) do
1030-
args_dynamic? = are_arguments_dynamic?(arguments)
1018+
args_dynamic? = any_dynamic?(arguments)
10311019
arity = length(arguments)
10321020

10331021
# For non-dynamic function and arguments, just return the static result
@@ -1053,8 +1041,7 @@ defmodule Module.Types.Descr do
10531041
# For dynamic cases, combine static and dynamic results
10541042
{static_args, dynamic_args, maybe_empty?} =
10551043
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}
10581045
else
10591046
{arguments, arguments, false}
10601047
end
@@ -1069,11 +1056,7 @@ defmodule Module.Types.Descr do
10691056
end
10701057
end
10711058

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))
10771060

10781061
defp fun_normalize_both(fun_static, fun_dynamic, arity) do
10791062
case fun_normalize(fun_static, arity, :static) do

lib/elixir/test/elixir/module/types/descr_test.exs

Lines changed: 8 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -765,8 +765,7 @@ defmodule Module.Types.DescrTest do
765765
assert fun_apply(fun([integer()], none()), [integer()]) == {:ok, none()}
766766
assert fun_apply(fun([integer()], term()), [integer()]) == {:ok, term()}
767767

768-
# Dynamic return and dynamic args
769-
assert fun_apply(fun([integer()], dynamic()), [integer()]) == {:ok, dynamic()}
768+
# Dynamic args
770769
assert fun_apply(fun([term()], term()), [dynamic()]) == {:ok, term()}
771770

772771
# Arity mismatches
@@ -781,13 +780,13 @@ defmodule Module.Types.DescrTest do
781780
# Function intersection with unions and dynamic return
782781
fun2 =
783782
intersection(
784-
fun([union(integer(), atom())], dynamic()),
783+
fun([union(integer(), atom())], term()),
785784
fun([union(integer(), pid())], atom())
786785
)
787786

788-
assert fun_apply(fun2, [integer()]) == {:ok, dynamic(atom())}
789-
assert fun_apply(fun2, [atom()]) == {:ok, dynamic()}
790-
assert fun_apply(fun2, [pid()]) |> elem(1) |> equal?(atom())
787+
assert fun_apply(fun2, [integer()]) == {:ok, atom()}
788+
assert fun_apply(fun2, [atom()]) == {:ok, term()}
789+
assert fun_apply(fun2, [pid()]) == {:ok, atom()}
791790

792791
# Function intersection with same domain, different codomains
793792
assert fun([integer()], term())
@@ -799,25 +798,6 @@ defmodule Module.Types.DescrTest do
799798
assert fun_apply(fun3, [atom([:ok])]) == {:ok, none()}
800799
end
801800

802-
test "static with dynamic signature" do
803-
assert fun_apply(fun([dynamic()], term()), [dynamic()]) == {:ok, term()}
804-
805-
assert fun_apply(fun([dynamic()], integer()), [dynamic()])
806-
|> elem(1)
807-
|> equal?(integer())
808-
809-
assert fun_apply(fun([dynamic(), atom()], float()), [dynamic(), atom()])
810-
|> elem(1)
811-
|> equal?(float())
812-
813-
fun = fun([dynamic(integer())], atom())
814-
assert fun_apply(fun, [dynamic(integer())]) |> elem(1) |> equal?(atom())
815-
# TODO: This should work
816-
assert fun_apply(fun, [dynamic(number())]) == :badarg
817-
assert fun_apply(fun, [integer()]) == :badarg
818-
assert fun_apply(fun, [float()]) == :badarg
819-
end
820-
821801
defp dynamic_fun(args, return), do: dynamic(fun(args, return))
822802

823803
test "dynamic" do
@@ -1712,6 +1692,9 @@ defmodule Module.Types.DescrTest do
17121692
assert fun([integer(), float()], boolean()) |> to_quoted_string() ==
17131693
"(integer(), float() -> boolean())"
17141694

1695+
assert fun([integer(), float()], dynamic()) |> to_quoted_string() ==
1696+
"dynamic((integer(), float() -> term()))"
1697+
17151698
assert fun([integer()], boolean())
17161699
|> union(fun([float()], boolean()))
17171700
|> to_quoted_string() ==

0 commit comments

Comments
 (0)