Skip to content

Commit 63df487

Browse files
committed
New version
1 parent fd2600a commit 63df487

File tree

2 files changed

+190
-93
lines changed

2 files changed

+190
-93
lines changed

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

Lines changed: 179 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -1028,50 +1028,126 @@ defmodule Module.Types.Descr do
10281028

10291029
defp fun_apply_with_strategy(fun_static, fun_dynamic, arguments) do
10301030
args_dynamic? = are_arguments_dynamic?(arguments)
1031+
arity = length(arguments)
10311032

10321033
# For non-dynamic function and arguments, just return the static result
10331034
if fun_dynamic == nil and not args_dynamic? do
1034-
fun_apply_static(fun_static, arguments, :static, args_dynamic?)
1035-
else
1036-
# For dynamic cases, combine static and dynamic results
1037-
{static_args, dynamic_args, maybe_empty?} =
1038-
if args_dynamic? do
1039-
{materialize_arguments(arguments, :up), materialize_arguments(arguments, :down), true}
1035+
with {:ok, static_domain, static_arrows} <- fun_normalize(fun_static, arity, :static) do
1036+
if subtype?(domain_descr(arguments), static_domain) do
1037+
{:ok, fun_apply_static(arguments, static_arrows, false)}
10401038
else
1041-
{arguments, arguments, false}
1039+
:badarg
10421040
end
1041+
end
1042+
else
1043+
normalized =
1044+
case fun_normalize(fun_static, arity, :static) do
1045+
{:ok, static_domain, static_arrows} when fun_dynamic == nil ->
1046+
# TODO: Should dynamic_arrows be [] or static_arrows
1047+
{:ok, static_domain, static_arrows, static_arrows}
1048+
1049+
{:ok, static_domain, static_arrows} when fun_dynamic != nil ->
1050+
case fun_normalize(fun_dynamic, arity, :dynamic) do
1051+
{:ok, dynamic_domain, dynamic_arrows} ->
1052+
domain = union(dynamic_domain, dynamic(union(static_domain, dynamic_domain)))
1053+
{:ok, domain, static_arrows, dynamic_arrows}
10431054

1044-
case fun_apply_static(fun_static, static_args, :static, false) do
1045-
{:ok, res1} when fun_dynamic == nil ->
1046-
# We need to compute which parts are dynamic
1047-
with {:ok, res2} <- fun_apply_static(fun_static, dynamic_args, :static, maybe_empty?) do
1048-
{:ok, union(res1, dynamic(res2))}
1049-
end
1055+
_ ->
1056+
# TODO: Should dynamic_arrows be [] or static_arrows
1057+
{:ok, static_domain, static_arrows, static_arrows}
1058+
end
10501059

1051-
{:ok, res1} when fun_dynamic != nil ->
1052-
# If static succeeded, the dynamic part can fail, we don't care
1053-
case fun_apply_static(fun_dynamic, dynamic_args, :dynamic, maybe_empty?) do
1054-
{:ok, res2} -> {:ok, union(res1, dynamic(res2))}
1055-
_ -> {:ok, res1}
1056-
end
1060+
:badfun ->
1061+
case fun_normalize(fun_dynamic, arity, :dynamic) do
1062+
{:ok, dynamic_domain, dynamic_arrows} ->
1063+
# TODO: Should static_arrows be [] or dynamic_arrows
1064+
{:ok, dynamic_domain, [], dynamic_arrows}
10571065

1058-
:badfun ->
1059-
# Then the dynamic call has to succeed
1060-
result =
1061-
if fun_dynamic do
1062-
fun_apply_static(fun_dynamic, dynamic_args, :dynamic, maybe_empty?)
1063-
else
1064-
fun_apply_static(fun_static, dynamic_args, :static, maybe_empty?)
1066+
error ->
1067+
error
10651068
end
10661069

1067-
with {:ok, descr} <- result do
1068-
{:ok, dynamic(descr)}
1069-
end
1070+
error ->
1071+
error
1072+
end
10701073

1071-
# badarg/badarity
1072-
error ->
1073-
error
1074+
with {:ok, domain, static_arrows, dynamic_arrows} <- normalized do
1075+
if subtype?(domain_descr(arguments), domain) do
1076+
# For dynamic cases, combine static and dynamic results
1077+
{static_args, dynamic_args, maybe_empty?} =
1078+
if args_dynamic? do
1079+
{materialize_arguments(arguments, :up), materialize_arguments(arguments, :down),
1080+
true}
1081+
else
1082+
{arguments, arguments, false}
1083+
end
1084+
1085+
{:ok,
1086+
union(
1087+
fun_apply_static(static_args, static_arrows, false),
1088+
dynamic(fun_apply_static(dynamic_args, dynamic_arrows, maybe_empty?))
1089+
)}
1090+
else
1091+
:badarg
1092+
end
10741093
end
1094+
1095+
# case fun_normalize(fun_static, arity, :static) do
1096+
# {:ok, static_domain, static_arrow} when fun_dynamic == nil ->
1097+
# with {:ok, res1} <-
1098+
# new_fun_apply_static(static_domain, static_arrows, static_args, false),
1099+
# {:ok, res2} <-
1100+
# new_fun_apply_static(static_domain, static_arrows, dynamic_args, maybe_empty?) do
1101+
# {:ok, union(res1, dynamic(res2))}
1102+
# end
1103+
1104+
# {:ok, static_domain, static_arrows} when fun_dynamic != nil ->
1105+
# case fun_normalize(fun_dynamic, arity, :dynamic) do
1106+
# {:ok, dynamic_domain, dynamic_arrows} ->
1107+
# with {:ok, res1} <-
1108+
# checked_fun_apply_static(
1109+
# union(dynamic_domain, static_domain),
1110+
# static_domain,
1111+
# static_arrows,
1112+
# static_args,
1113+
# false
1114+
# ) do
1115+
# case checked_fun_apply_static(
1116+
# dynamic_domain,
1117+
# dynamic_domain,
1118+
# dynamic_arrows,
1119+
# dynamic_args,
1120+
# maybe_empty?
1121+
# ) do
1122+
# {:ok, res2} ->
1123+
# {:ok, union(res1, dynamic(res2))}
1124+
1125+
# _ ->
1126+
# {:ok, res1}
1127+
# end
1128+
# end
1129+
1130+
# _ ->
1131+
# new_fun_apply_static(static_domain, static_arrows, static_args, false)
1132+
# end
1133+
1134+
# :badfun ->
1135+
# # Then the dynamic call has to succeed
1136+
# result =
1137+
# if fun_dynamic do
1138+
# fun_apply_static(fun_dynamic, dynamic_args, :dynamic, maybe_empty?)
1139+
# else
1140+
# fun_apply_static(fun_static, dynamic_args, :static, maybe_empty?)
1141+
# end
1142+
1143+
# with {:ok, descr} <- result do
1144+
# {:ok, dynamic(descr)}
1145+
# end
1146+
1147+
# # badarg/badarity
1148+
# error ->
1149+
# error
1150+
# end
10751151
end
10761152
end
10771153

@@ -1081,46 +1157,76 @@ defmodule Module.Types.Descr do
10811157

10821158
defp are_arguments_dynamic?(arguments), do: Enum.any?(arguments, &match?(%{dynamic: _}, &1))
10831159

1084-
defp fun_apply_static(%{fun: fun_bdd}, arguments, mode, maybe_empty?) do
1085-
arity = length(arguments)
1086-
1087-
with {:ok, domain, arrows} <- fun_normalize(fun_bdd, arity, mode) do
1088-
type_args = domain_descr(arguments)
1160+
defp fun_apply_static(arguments, arrows, maybe_empty?) do
1161+
type_args = domain_descr(arguments)
10891162

1090-
cond do
1091-
# Optization: short-circuits when inner loop is none() or outer loop is term()
1092-
maybe_empty? and empty?(type_args) ->
1093-
result =
1094-
Enum.reduce_while(arrows, none(), fn intersection_of_arrows, acc ->
1095-
Enum.reduce_while(intersection_of_arrows, term(), fn
1096-
{_dom, _ret}, acc when acc == @none -> {:halt, acc}
1097-
{_dom, ret}, acc -> {:cont, intersection(acc, ret)}
1098-
end)
1099-
|> case do
1100-
:term -> {:halt, :term}
1101-
inner -> {:cont, union(inner, acc)}
1102-
end
1103-
end)
1104-
1105-
{:ok, result}
1106-
1107-
subtype?(type_args, domain) ->
1108-
result =
1109-
Enum.reduce(arrows, none(), fn intersection_of_arrows, acc ->
1110-
aux_apply(acc, type_args, term(), intersection_of_arrows)
1111-
end)
1112-
1113-
{:ok, result}
1114-
1115-
true ->
1116-
:badarg
1117-
end
1163+
# Optimization: short-circuits when inner loop is none() or outer loop is term()
1164+
if maybe_empty? and empty?(type_args) do
1165+
Enum.reduce_while(arrows, none(), fn intersection_of_arrows, acc ->
1166+
Enum.reduce_while(intersection_of_arrows, term(), fn
1167+
{_dom, _ret}, acc when acc == @none -> {:halt, acc}
1168+
{_dom, ret}, acc -> {:cont, intersection(acc, ret)}
1169+
end)
1170+
|> case do
1171+
:term -> {:halt, :term}
1172+
inner -> {:cont, union(inner, acc)}
1173+
end
1174+
end)
1175+
else
1176+
Enum.reduce(arrows, none(), fn intersection_of_arrows, acc ->
1177+
aux_apply(acc, type_args, term(), intersection_of_arrows)
1178+
end)
11181179
end
11191180
end
11201181

1121-
defp fun_apply_static(%{}, _arguments, _mode, _maybe_empty?) do
1122-
:badfun
1123-
end
1182+
# defp fun_apply_static(descr, arguments, mode, maybe_empty?) do
1183+
# arity = length(arguments)
1184+
1185+
# with {:ok, domain, arrows} <- fun_normalize(descr, arity, mode) do
1186+
# new_fun_apply_static(domain, arrows, arguments, maybe_empty?)
1187+
# end
1188+
# end
1189+
1190+
# defp checked_fun_apply_static(check_domain, domain, arrows, arguments, maybe_empty?) do
1191+
# if subtype?(domain_descr(arguments), check_domain) do
1192+
# new_fun_apply_static(domain, arrows, arguments, maybe_empty?)
1193+
# else
1194+
# :badarg
1195+
# end
1196+
# end
1197+
1198+
# defp new_fun_apply_static(domain, arrows, arguments, maybe_empty?) do
1199+
# type_args = domain_descr(arguments)
1200+
1201+
# cond do
1202+
# # Optimization: short-circuits when inner loop is none() or outer loop is term()
1203+
# maybe_empty? and empty?(type_args) ->
1204+
# result =
1205+
# Enum.reduce_while(arrows, none(), fn intersection_of_arrows, acc ->
1206+
# Enum.reduce_while(intersection_of_arrows, term(), fn
1207+
# {_dom, _ret}, acc when acc == @none -> {:halt, acc}
1208+
# {_dom, ret}, acc -> {:cont, intersection(acc, ret)}
1209+
# end)
1210+
# |> case do
1211+
# :term -> {:halt, :term}
1212+
# inner -> {:cont, union(inner, acc)}
1213+
# end
1214+
# end)
1215+
1216+
# {:ok, result}
1217+
1218+
# subtype?(type_args, domain) ->
1219+
# result =
1220+
# Enum.reduce(arrows, none(), fn intersection_of_arrows, acc ->
1221+
# aux_apply(acc, type_args, term(), intersection_of_arrows)
1222+
# end)
1223+
1224+
# {:ok, result}
1225+
1226+
# true ->
1227+
# :badarg
1228+
# end
1229+
# end
11241230

11251231
# Helper function for function application that handles the application of
11261232
# function arrows to input types.
@@ -1199,7 +1305,7 @@ defmodule Module.Types.Descr do
11991305
#
12001306
# This function is used internally by `fun_apply`, and others to
12011307
# ensure consistent handling of function types in all operations.
1202-
defp fun_normalize(bdd, arity, mode) do
1308+
defp fun_normalize(%{fun: bdd}, arity, mode) do
12031309
{domain, arrows, bad_arities} =
12041310
Enum.reduce(fun_get(bdd), {term(), [], []}, fn
12051311
{[{args, _} | _] = pos_funs, neg_funs}, {domain, arrows, bad_arities} ->
@@ -1235,6 +1341,10 @@ defmodule Module.Types.Descr do
12351341
end
12361342
end
12371343

1344+
defp fun_normalize(%{}, _arity, _mode) do
1345+
:badfun
1346+
end
1347+
12381348
# Checks if a function type is empty.
12391349
#
12401350
# A function type is empty if:

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

Lines changed: 11 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -814,7 +814,7 @@ defmodule Module.Types.DescrTest do
814814
assert fun_apply(fun, [dynamic(integer())]) |> elem(1) |> equal?(atom())
815815
# TODO: This should work
816816
assert fun_apply(fun, [dynamic(number())]) == :badarg
817-
assert fun_apply(fun, [integer()]) == {:ok, atom()}
817+
assert fun_apply(fun, [integer()]) == :badarg
818818
assert fun_apply(fun, [float()]) == :badarg
819819
end
820820

@@ -829,9 +829,13 @@ defmodule Module.Types.DescrTest do
829829
assert fun_apply(dynamic_fun([integer()], term()), [integer()]) == {:ok, dynamic()}
830830

831831
# Dynamic return and dynamic args
832-
assert fun_apply(dynamic_fun([integer()], dynamic()), [integer()]) == {:ok, dynamic()}
833832
assert fun_apply(dynamic_fun([term()], term()), [dynamic()]) == {:ok, dynamic()}
834833

834+
fun = dynamic_fun([integer()], binary())
835+
assert fun_apply(fun, [integer()]) == {:ok, dynamic(binary())}
836+
assert fun_apply(fun, [dynamic(integer())]) == {:ok, dynamic(binary())}
837+
assert fun_apply(fun, [dynamic(atom())]) == :badarg
838+
835839
# Arity mismatches
836840
assert fun_apply(dynamic_fun([integer()], integer()), [term(), term()]) == {:badarity, [1]}
837841

@@ -859,6 +863,10 @@ defmodule Module.Types.DescrTest do
859863
assert fun_apply(fun2, [atom()]) == {:ok, dynamic()}
860864
assert fun_apply(fun2, [pid()]) |> elem(1) |> equal?(dynamic(atom()))
861865

866+
assert fun_apply(fun2, [dynamic(integer())]) == {:ok, dynamic(atom())}
867+
assert fun_apply(fun2, [dynamic(atom())]) == {:ok, dynamic(atom())}
868+
assert fun_apply(fun2, [dynamic(pid())]) |> elem(1) |> equal?(dynamic(atom()))
869+
862870
# Function intersection with singleton atoms
863871
fun3 =
864872
intersection(
@@ -870,23 +878,6 @@ defmodule Module.Types.DescrTest do
870878
end
871879

872880
test "static and dynamic" do
873-
fun_match =
874-
union(
875-
fun([atom()], integer()),
876-
dynamic_fun([integer()], binary())
877-
)
878-
879-
assert fun_match |> fun_apply([atom()]) ==
880-
{:ok, integer()}
881-
882-
assert fun_match |> fun_apply([dynamic(atom())]) ==
883-
{:ok, dynamic(union(integer(), binary())) |> union(integer())}
884-
885-
assert fun_match |> fun_apply([integer()]) == :badarg
886-
assert fun_match |> fun_apply([dynamic(integer())]) == :badarg
887-
assert fun_match |> fun_apply([union(atom(), integer())]) == :badarg
888-
assert fun_match |> fun_apply([dynamic(union(atom(), integer()))]) == :badarg
889-
890881
# Bad arity
891882
fun_arities =
892883
union(
@@ -908,11 +899,7 @@ defmodule Module.Types.DescrTest do
908899
dynamic_fun([integer()], binary())
909900
)
910901

911-
assert fun_args
912-
|> fun_apply([atom()])
913-
|> elem(1)
914-
|> equal?(integer())
915-
902+
assert fun_args |> fun_apply([atom()]) == :badarg
916903
assert fun_args |> fun_apply([integer()]) == :badarg
917904

918905
# Badfun

0 commit comments

Comments
 (0)