Skip to content

Commit bae8bf4

Browse files
committed
More tests and refactor
1 parent 63df487 commit bae8bf4

File tree

2 files changed

+118
-210
lines changed

2 files changed

+118
-210
lines changed

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

Lines changed: 104 additions & 206 deletions
Original file line numberDiff line numberDiff line change
@@ -1040,122 +1040,124 @@ defmodule Module.Types.Descr do
10401040
end
10411041
end
10421042
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}
1043+
with {:ok, domain, static_arrows, dynamic_arrows} <-
1044+
fun_normalize_both(fun_static, fun_dynamic, arity) do
1045+
cond do
1046+
not subtype?(domain_descr(arguments), domain) ->
1047+
:badarg
10481048

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}
1049+
static_arrows == [] ->
1050+
{:ok, dynamic(fun_apply_static(arguments, dynamic_arrows, false))}
10541051

1055-
_ ->
1056-
# TODO: Should dynamic_arrows be [] or static_arrows
1057-
{:ok, static_domain, static_arrows, static_arrows}
1058-
end
1052+
true ->
1053+
# For dynamic cases, combine static and dynamic results
1054+
{static_args, dynamic_args, maybe_empty?} =
1055+
if args_dynamic? do
1056+
{materialize_arguments(arguments, :up), materialize_arguments(arguments, :down),
1057+
true}
1058+
else
1059+
{arguments, arguments, false}
1060+
end
1061+
1062+
{:ok,
1063+
union(
1064+
fun_apply_static(static_args, static_arrows, false),
1065+
dynamic(fun_apply_static(dynamic_args, dynamic_arrows, maybe_empty?))
1066+
)}
1067+
end
1068+
end
1069+
end
1070+
end
10591071

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}
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)
10651075

1066-
error ->
1067-
error
1068-
end
1076+
defp are_arguments_dynamic?(arguments), do: Enum.any?(arguments, &match?(%{dynamic: _}, &1))
10691077

1070-
error ->
1071-
error
1078+
defp fun_normalize_both(fun_static, fun_dynamic, arity) do
1079+
case fun_normalize(fun_static, arity, :static) do
1080+
{:ok, static_domain, static_arrows} when fun_dynamic == nil ->
1081+
{:ok, static_domain, static_arrows, static_arrows}
1082+
1083+
{:ok, static_domain, static_arrows} when fun_dynamic != nil ->
1084+
case fun_normalize(fun_dynamic, arity, :dynamic) do
1085+
{:ok, dynamic_domain, dynamic_arrows} ->
1086+
domain = union(dynamic_domain, dynamic(static_domain))
1087+
{:ok, domain, static_arrows, dynamic_arrows}
1088+
1089+
_ ->
1090+
{:ok, static_domain, static_arrows, static_arrows}
10721091
end
10731092

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
1093+
:badfun ->
1094+
case fun_normalize(fun_dynamic, arity, :dynamic) do
1095+
{:ok, dynamic_domain, dynamic_arrows} ->
1096+
{:ok, dynamic_domain, [], dynamic_arrows}
10841097

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
1098+
error ->
1099+
error
10921100
end
1093-
end
10941101

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
1102+
error ->
1103+
error
11511104
end
11521105
end
11531106

1154-
# Materializes arguments using the specified direction (up or down)
1155-
defp materialize_arguments(arguments, :up), do: Enum.map(arguments, &upper_bound/1)
1156-
defp materialize_arguments(arguments, :down), do: Enum.map(arguments, &lower_bound/1)
1107+
# Transforms a binary decision diagram (BDD) into the canonical form {domain, arrows, arity}:
1108+
#
1109+
# 1. **domain**: The union of all domains from positive functions in the BDD
1110+
# 2. **arrows**: List of lists, where each inner list contains an intersection of function arrows
1111+
# 3. **arity**: Function arity (number of parameters)
1112+
#
1113+
## Return Values
1114+
#
1115+
# - `{domain, arrows, arity}` for valid function BDDs
1116+
# - `:badfun` if the BDD represents an empty function type
1117+
#
1118+
# ## Internal Use
1119+
#
1120+
# This function is used internally by `fun_apply`, and others to
1121+
# ensure consistent handling of function types in all operations.
1122+
defp fun_normalize(%{fun: bdd}, arity, mode) do
1123+
{domain, arrows, bad_arities} =
1124+
Enum.reduce(fun_get(bdd), {term(), [], []}, fn
1125+
{[{args, _} | _] = pos_funs, neg_funs}, {domain, arrows, bad_arities} ->
1126+
arrow_arity = length(args)
11571127

1158-
defp are_arguments_dynamic?(arguments), do: Enum.any?(arguments, &match?(%{dynamic: _}, &1))
1128+
cond do
1129+
arrow_arity != arity ->
1130+
{domain, arrows, [arrow_arity | bad_arities]}
1131+
1132+
fun_empty?(pos_funs, neg_funs) ->
1133+
{domain, arrows, bad_arities}
1134+
1135+
true ->
1136+
# Calculate domain from all positive functions
1137+
path_domain =
1138+
Enum.reduce(pos_funs, none(), fn {args, _}, acc ->
1139+
union(acc, domain_descr(args))
1140+
end)
1141+
1142+
{intersection(domain, path_domain), [pos_funs | arrows], bad_arities}
1143+
end
1144+
end)
1145+
1146+
case {arrows, bad_arities} do
1147+
{[], []} ->
1148+
:badfun
1149+
1150+
{arrows, [_ | _] = bad_arities} when mode == :static or arrows == [] ->
1151+
{:badarity, Enum.uniq(bad_arities)}
1152+
1153+
{_, _} ->
1154+
{:ok, domain, arrows}
1155+
end
1156+
end
1157+
1158+
defp fun_normalize(%{}, _arity, _mode) do
1159+
:badfun
1160+
end
11591161

11601162
defp fun_apply_static(arguments, arrows, maybe_empty?) do
11611163
type_args = domain_descr(arguments)
@@ -1179,55 +1181,6 @@ defmodule Module.Types.Descr do
11791181
end
11801182
end
11811183

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
1230-
12311184
# Helper function for function application that handles the application of
12321185
# function arrows to input types.
12331186

@@ -1290,61 +1243,6 @@ defmodule Module.Types.Descr do
12901243
end
12911244
end
12921245

1293-
# Transforms a binary decision diagram (BDD) into the canonical form {domain, arrows, arity}:
1294-
#
1295-
# 1. **domain**: The union of all domains from positive functions in the BDD
1296-
# 2. **arrows**: List of lists, where each inner list contains an intersection of function arrows
1297-
# 3. **arity**: Function arity (number of parameters)
1298-
#
1299-
## Return Values
1300-
#
1301-
# - `{domain, arrows, arity}` for valid function BDDs
1302-
# - `:badfun` if the BDD represents an empty function type
1303-
#
1304-
# ## Internal Use
1305-
#
1306-
# This function is used internally by `fun_apply`, and others to
1307-
# ensure consistent handling of function types in all operations.
1308-
defp fun_normalize(%{fun: bdd}, arity, mode) do
1309-
{domain, arrows, bad_arities} =
1310-
Enum.reduce(fun_get(bdd), {term(), [], []}, fn
1311-
{[{args, _} | _] = pos_funs, neg_funs}, {domain, arrows, bad_arities} ->
1312-
arrow_arity = length(args)
1313-
1314-
cond do
1315-
arrow_arity != arity ->
1316-
{domain, arrows, [arrow_arity | bad_arities]}
1317-
1318-
fun_empty?(pos_funs, neg_funs) ->
1319-
{domain, arrows, bad_arities}
1320-
1321-
true ->
1322-
# Calculate domain from all positive functions
1323-
path_domain =
1324-
Enum.reduce(pos_funs, none(), fn {args, _}, acc ->
1325-
union(acc, domain_descr(args))
1326-
end)
1327-
1328-
{intersection(domain, path_domain), [pos_funs | arrows], bad_arities}
1329-
end
1330-
end)
1331-
1332-
case {arrows, bad_arities} do
1333-
{[], []} ->
1334-
:badfun
1335-
1336-
{arrows, [_ | _] = bad_arities} when mode == :static or arrows == [] ->
1337-
{:badarity, Enum.uniq(bad_arities)}
1338-
1339-
{_, _} ->
1340-
{:ok, domain, arrows}
1341-
end
1342-
end
1343-
1344-
defp fun_normalize(%{}, _arity, _mode) do
1345-
:badfun
1346-
end
1347-
13481246
# Checks if a function type is empty.
13491247
#
13501248
# A function type is empty if:

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

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -842,20 +842,30 @@ defmodule Module.Types.DescrTest do
842842
assert fun_apply(dynamic_fun([integer(), atom()], boolean()), [integer()]) ==
843843
{:badarity, [2]}
844844

845-
# Function intersection tests - basic
845+
# Function intersection tests
846+
fun0 = intersection(dynamic_fun([integer()], atom()), dynamic_fun([float()], binary()))
847+
assert fun_apply(fun0, [integer()]) == {:ok, dynamic(atom())}
848+
assert fun_apply(fun0, [float()]) == {:ok, dynamic(binary())}
849+
assert fun_apply(fun0, [dynamic(integer())]) == {:ok, dynamic(atom())}
850+
assert fun_apply(fun0, [dynamic(float())]) == {:ok, dynamic(binary())}
851+
assert fun_apply(fun0, [dynamic(number())]) == {:ok, dynamic(union(binary(), atom()))}
852+
853+
# Function intersection with subset domain
846854
fun1 = intersection(dynamic_fun([integer()], atom()), dynamic_fun([number()], term()))
847855
assert fun_apply(fun1, [integer()]) == {:ok, dynamic(atom())}
848856
assert fun_apply(fun1, [float()]) == {:ok, dynamic()}
857+
assert fun_apply(fun1, [dynamic(integer())]) == {:ok, dynamic(atom())}
858+
assert fun_apply(fun1, [dynamic(float())]) == {:ok, dynamic()}
849859

850860
# Function intersection with same domain, different codomains
851861
assert dynamic_fun([integer()], term())
852862
|> intersection(dynamic_fun([integer()], atom()))
853863
|> fun_apply([integer()]) == {:ok, dynamic(atom())}
854864

855-
# Function intersection with unions and dynamic return
865+
# Function intersection with overlapping domains
856866
fun2 =
857867
intersection(
858-
dynamic_fun([union(integer(), atom())], dynamic()),
868+
dynamic_fun([union(integer(), atom())], term()),
859869
dynamic_fun([union(integer(), pid())], atom())
860870
)
861871

@@ -864,7 +874,7 @@ defmodule Module.Types.DescrTest do
864874
assert fun_apply(fun2, [pid()]) |> elem(1) |> equal?(dynamic(atom()))
865875

866876
assert fun_apply(fun2, [dynamic(integer())]) == {:ok, dynamic(atom())}
867-
assert fun_apply(fun2, [dynamic(atom())]) == {:ok, dynamic(atom())}
877+
assert fun_apply(fun2, [dynamic(atom())]) == {:ok, dynamic()}
868878
assert fun_apply(fun2, [dynamic(pid())]) |> elem(1) |> equal?(dynamic(atom()))
869879

870880
# Function intersection with singleton atoms

0 commit comments

Comments
 (0)