Skip to content

Commit 3d7cf8e

Browse files
committed
Refactor function type handling in descr.ex to streamline function description and domain calculations. Update related tests for improved clarity and coverage.
1 parent 4763bac commit 3d7cf8e

File tree

2 files changed

+255
-541
lines changed

2 files changed

+255
-541
lines changed

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

Lines changed: 44 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ defmodule Module.Types.Descr do
9696
iex> fun([integer()], atom()) # Creates (integer) -> atom
9797
iex> fun([integer(), float()], boolean()) # Creates (integer, float) -> boolean
9898
"""
99-
def fun(args, return) when is_list(args), do: %{fun: fun_descr(args, return)}
99+
def fun(args, return) when is_list(args), do: fun_descr(args, return)
100100

101101
@doc """
102102
Creates the top function type for the given arity, where all arguments are none()
@@ -893,7 +893,9 @@ defmodule Module.Types.Descr do
893893

894894
# * Representation:
895895
# - fun(): Top function type (leaf 1)
896-
# - Function literals: {[t1, ..., tn], t} where [t1, ..., tn] are argument types and t is return type
896+
# - Function literals: {tag, [t1, ..., tn], t} where [t1, ..., tn] are argument types and t is return type
897+
# tag is either `:weak` or `:strong`
898+
# TODO: implement `:strong`
897899
# - Normalized form for function applications: {domain, arrows, arity} is produced by `fun_normalize/1`
898900

899901
# * Examples:
@@ -904,12 +906,7 @@ defmodule Module.Types.Descr do
904906
# unary functions with tuple domains to handle special cases like representing functions of a
905907
# specific arity (e.g., (none,none->term) for arity 2).
906908

907-
defp fun_descr(inputs, output), do: {{:weak, inputs, output}, 1, 0}
908-
909-
@doc "Utility function: fast build an intersection from a list of functions"
910-
def fun_from_intersection(intersection) do
911-
Enum.reduce(intersection, 1, fn {dom, ret}, acc -> {{:weak, dom, ret}, acc, 0} end)
912-
end
909+
defp fun_new(inputs, output), do: {{:weak, inputs, output}, 1, 0}
913910

914911
@doc """
915912
Creates a function type from a list of inputs and an output where the inputs and/or output may be dynamic.
@@ -919,34 +916,28 @@ defmodule Module.Types.Descr do
919916
- Dynamic part: dynamic(down(t) → up(s))
920917
921918
When handling dynamic types:
922-
- `up(t)` extracts the upper bound (most general type) of a gradual type
923-
- `down(t)` extracts the lower bound (most specific type) of a gradual type
919+
- `up(t)` extracts the upper bound (most general type) of a gradual type.
920+
For `dynamic(integer())`, it is `integer()`.
921+
- `down(t)` extracts the lower bound (most specific type) of a gradual type.
924922
"""
925-
def fun_from_annotation(inputs, output) do
926-
dynamic_arguments? = are_arguments_dynamic?(inputs)
923+
def fun_descr(args, output) when is_list(args) do
924+
dynamic_arguments? = are_arguments_dynamic?(args)
927925
dynamic_output? = match?(%{dynamic: _}, output)
928926

929-
cond do
930-
dynamic_arguments? and dynamic_output? ->
931-
static_part = fun(materialize_arguments(inputs, :up), down(output))
932-
dynamic_part = dynamic(fun(materialize_arguments(inputs, :down), up(output)))
933-
union(static_part, dynamic_part)
934-
935-
# Only arguments are dynamic
936-
dynamic_arguments? ->
937-
static_part = fun(materialize_arguments(inputs, :up), output)
938-
dynamic_part = dynamic(fun(materialize_arguments(inputs, :down), output))
939-
union(static_part, dynamic_part)
940-
941-
# Only return type is dynamic
942-
dynamic_output? ->
943-
static_part = fun(inputs, down(output))
944-
dynamic_part = dynamic(fun(inputs, up(output)))
945-
union(static_part, dynamic_part)
927+
if dynamic_arguments? or dynamic_output? do
928+
input_static = if dynamic_arguments?, do: materialize_arguments(args, :up), else: args
929+
input_dynamic = if dynamic_arguments?, do: materialize_arguments(args, :down), else: args
946930

947-
true ->
948-
# No dynamic components, use standard function type
949-
fun(inputs, output)
931+
output_static = if dynamic_output?, do: down(output), else: output
932+
output_dynamic = if dynamic_output?, do: up(output), else: output
933+
934+
%{
935+
fun: fun_new(input_static, output_static),
936+
dynamic: %{fun: fun_new(input_dynamic, output_dynamic)}
937+
}
938+
else
939+
# No dynamic components, use standard function type
940+
%{fun: fun_new(args, output)}
950941
end
951942
end
952943

@@ -962,7 +953,7 @@ defmodule Module.Types.Descr do
962953
# Example: for functions (integer,float)->:ok and (float,integer)->:error
963954
# domain isn't {integer|float,integer|float} as that would incorrectly accept {float,float}
964955
# Instead, it is {integer,float} or {float,integer}
965-
def domain_new(types) when is_list(types), do: tuple(types)
956+
def domain_descr(types) when is_list(types), do: tuple(types)
966957

967958
@doc """
968959
Calculates the domain of a function type.
@@ -1063,9 +1054,13 @@ defmodule Module.Types.Descr do
10631054
atom()
10641055
"""
10651056
def fun_apply(fun, arguments) do
1066-
case :maps.take(:dynamic, fun) do
1067-
:error -> fun_apply_with_strategy(fun, nil, arguments)
1068-
{fun_dynamic, fun_static} -> fun_apply_with_strategy(fun_static, fun_dynamic, arguments)
1057+
if empty?(domain_descr(arguments)) do
1058+
:badarguments
1059+
else
1060+
case :maps.take(:dynamic, fun) do
1061+
:error -> fun_apply_with_strategy(fun, nil, arguments)
1062+
{fun_dynamic, fun_static} -> fun_apply_with_strategy(fun_static, fun_dynamic, arguments)
1063+
end
10691064
end
10701065
end
10711066

@@ -1100,7 +1095,7 @@ defmodule Module.Types.Descr do
11001095
defp are_arguments_dynamic?(arguments), do: Enum.any?(arguments, &match?(%{dynamic: _}, &1))
11011096

11021097
defp fun_apply_static(%{fun: fun_bdd}, arguments) do
1103-
type_args = domain_new(arguments)
1098+
type_args = domain_descr(arguments)
11041099

11051100
if empty?(type_args) do
11061101
# At this stage we do not check that the function can be applied to the arguments (using domain)
@@ -1129,9 +1124,7 @@ defmodule Module.Types.Descr do
11291124

11301125
{:ok, result}
11311126
else
1132-
:emptyfunction -> :emptyfunction
1133-
:badarguments -> :badarguments
1134-
false -> :badarguments
1127+
_ -> :badarguments
11351128
end
11361129
end
11371130
end
@@ -1156,7 +1149,7 @@ defmodule Module.Types.Descr do
11561149

11571150
defp aux_apply(result, input, returns_reached, [{_tag, dom, ret} | arrow_intersections]) do
11581151
# Calculate the part of the input not covered by this arrow's domain
1159-
dom_subtract = difference(input, domain_new(dom))
1152+
dom_subtract = difference(input, domain_descr(dom))
11601153

11611154
# Refine the return type by intersecting with this arrow's return type
11621155
ret_refine = intersection(returns_reached, ret)
@@ -1229,7 +1222,9 @@ defmodule Module.Types.Descr do
12291222

12301223
# Calculate domain from all positive functions
12311224
path_domain =
1232-
Enum.reduce(pos_funs, none(), fn {_, args, _}, acc -> union(acc, domain_new(args)) end)
1225+
Enum.reduce(pos_funs, none(), fn {_, args, _}, acc ->
1226+
union(acc, domain_descr(args))
1227+
end)
12331228

12341229
{intersection(domain, path_domain), [pos_funs | arrows], new_arity}
12351230
end
@@ -1269,6 +1264,8 @@ defmodule Module.Types.Descr do
12691264
# - `{[fun(1), fun(2)], []}` is empty (different arities)
12701265
# - `{[fun(integer() -> atom())], [fun(none() -> term())]}` is empty
12711266
# - `{[], _}` (representing the top function type fun()) is never empty
1267+
#
1268+
# TODO: test performance
12721269
defp fun_empty?([], _), do: false
12731270

12741271
defp fun_empty?(positives, negatives) do
@@ -1287,7 +1284,7 @@ defmodule Module.Types.Descr do
12871284
# function's domain is a supertype of the positive domain and if the phi function
12881285
# determines emptiness.
12891286
length(neg_arguments) == positive_arity and
1290-
subtype?(domain_new(neg_arguments), positive_domain) and
1287+
subtype?(domain_descr(neg_arguments), positive_domain) and
12911288
phi_starter(neg_arguments, negation(neg_return), positives)
12921289
end)
12931290
end
@@ -1300,10 +1297,10 @@ defmodule Module.Types.Descr do
13001297
positives
13011298
|> Enum.reduce_while({:empty, none()}, fn
13021299
{_tag, args, _}, {:empty, _} ->
1303-
{:cont, {length(args), domain_new(args)}}
1300+
{:cont, {length(args), domain_descr(args)}}
13041301

13051302
{_tag, args, _}, {arity, dom} when length(args) == arity ->
1306-
{:cont, {arity, union(dom, domain_new(args))}}
1303+
{:cont, {arity, union(dom, domain_descr(args))}}
13071304

13081305
{_tag, _args, _}, {_arity, _} ->
13091306
{:halt, {:empty, none()}}
@@ -2986,10 +2983,10 @@ defmodule Module.Types.Descr do
29862983
29872984
## Examples
29882985
2989-
iex> tuple_fetch(domain_new([integer(), atom()]), 0)
2986+
iex> tuple_fetch(domain_descr([integer(), atom()]), 0)
29902987
{false, integer()}
29912988
2992-
iex> tuple_fetch(union(domain_new([integer()]), domain_new([integer(), atom()])), 1)
2989+
iex> tuple_fetch(union(domain_descr([integer()]), domain_descr([integer(), atom()])), 1)
29932990
{true, atom()}
29942991
29952992
iex> tuple_fetch(dynamic(), 0)

0 commit comments

Comments
 (0)