Skip to content

Commit 6b8c2dd

Browse files
committed
Start merging fun_fetch into fun_apply
1 parent 81f885d commit 6b8c2dd

File tree

2 files changed

+273
-254
lines changed

2 files changed

+273
-254
lines changed

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

Lines changed: 115 additions & 132 deletions
Original file line numberDiff line numberDiff line change
@@ -715,6 +715,7 @@ defmodule Module.Types.Descr do
715715
- Either the static part is a non-empty function type of the given arity, or
716716
- The static part is empty and the dynamic part contains functions of the given arity
717717
"""
718+
# TODO: REMOVE ME
718719
def fun_fetch(:term, _arity), do: :error
719720

720721
def fun_fetch(%{} = descr, arity) when is_integer(arity) do
@@ -733,7 +734,6 @@ defmodule Module.Types.Descr do
733734
end
734735
end
735736

736-
defp fun_only?(descr), do: empty?(Map.delete(descr, :fun))
737737
defp fun_only?(descr, arity), do: empty?(difference(descr, fun(arity)))
738738

739739
## Atoms
@@ -916,7 +916,7 @@ defmodule Module.Types.Descr do
916916
# * Representation:
917917
# - fun(): Top function type (leaf 1)
918918
# - 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`
920920

921921
# * Examples:
922922
# - fun([integer()], atom()): A function from integer to atom
@@ -967,74 +967,6 @@ defmodule Module.Types.Descr do
967967
defp lower_bound(:term), do: :term
968968
defp lower_bound(type), do: Map.delete(type, :dynamic)
969969

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-
1038970
@doc """
1039971
Applies a function type to a list of argument types.
1040972
@@ -1056,46 +988,86 @@ defmodule Module.Types.Descr do
1056988
# For more details, see Definition 6.15 in https://vlanvin.fr/papers/thesis.pdf
1057989
1058990
## Examples
991+
1059992
iex> fun_apply(fun([integer()], atom()), [integer()])
1060-
atom()
993+
{:ok, atom()}
1061994
1062995
iex> fun_apply(fun([integer()], atom()), [float()])
1063996
:badarg
1064997
1065998
iex> fun_apply(fun([dynamic()], atom()), [dynamic()])
1066-
atom()
999+
{:ok, atom()}
10671000
"""
1001+
def fun_apply(:term, _arguments) do
1002+
:badfun
1003+
end
1004+
10681005
def fun_apply(fun, arguments) do
10691006
if empty?(domain_descr(arguments)) do
10701007
:badarg
10711008
else
10721009
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
10751023
end
10761024
end
10771025
end
10781026

1027+
defp fun_only?(descr), do: empty?(Map.delete(descr, :fun))
1028+
10791029
defp fun_apply_with_strategy(fun_static, fun_dynamic, arguments) do
10801030
args_dynamic? = are_arguments_dynamic?(arguments)
10811031

10821032
# For non-dynamic function and arguments, just return the static result
10831033
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)
10851035
else
10861036
# For dynamic cases, combine static and dynamic results
10871037
{static_args, dynamic_args} =
10881038
if args_dynamic?,
10891039
do: {materialize_arguments(arguments, :up), materialize_arguments(arguments, :down)},
10901040
else: {arguments, arguments}
10911041

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
10931047

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
10991071
end
11001072
end
11011073
end
@@ -1106,48 +1078,47 @@ defmodule Module.Types.Descr do
11061078

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

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

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

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)}
11351095
end)
1096+
|> case do
1097+
:term -> {:halt, :term}
1098+
inner -> {:cont, union(inner, acc)}
1099+
end
1100+
end)
11361101

1137-
{:ok, result}
1102+
{:ok, result}
11381103

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

1143-
{_, _, arity} ->
1144-
{:badarity, arity}
1110+
{:ok, result}
11451111

1146-
:badfun ->
1147-
:badfun
1112+
true ->
1113+
:badarg
1114+
end
11481115
end
11491116
end
11501117

1118+
defp fun_apply_static(%{}, _arguments, _mode) do
1119+
:badfun
1120+
end
1121+
11511122
# Helper function for function application that handles the application of
11521123
# function arrows to input types.
11531124

@@ -1223,30 +1194,42 @@ defmodule Module.Types.Descr do
12231194
#
12241195
# ## Internal Use
12251196
#
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
12271198
# 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)
12381204

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]}
12441208

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
12471221
end)
12481222

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
12501233
end
12511234

12521235
# Checks if a function type is empty.

0 commit comments

Comments
 (0)