@@ -93,23 +93,42 @@ defmodule Module.Types.Descr do
9393 Creates a function type with the given arguments and return type.
9494
9595 ## Examples
96- iex> fun([integer()], atom()) # Creates (integer) -> atom
97- iex> fun([integer(), float()], boolean()) # Creates (integer, float) -> boolean
96+
97+ fun([integer()], atom())
98+ #=> (integer -> atom)
99+
100+ fun([integer(), float()], boolean())
101+ #=> (integer, float -> boolean)
98102 """
99103 def fun ( args , return ) when is_list ( args ) , do: fun_descr ( args , return )
100104
101105 @ doc """
102- Creates the top function type for the given arity, where all arguments are none()
103- and return is term().
106+ Creates the top function type for the given arity,
107+ where all arguments are none() and return is term().
104108
105109 ## Examples
106- iex> fun(1) # Creates (none) -> term
107- iex> fun(2) # Creates (none, none) -> term
110+
111+ fun(1)
112+ #=> (none -> term)
113+
114+ fun(2)
115+ #=> Creates (none, none) -> term
108116 """
109117 def fun ( arity ) when is_integer ( arity ) and arity >= 0 do
110118 fun ( List . duplicate ( none ( ) , arity ) , term ( ) )
111119 end
112120
121+ @ doc """
122+ Tuples represent function domains, using unions to combine parameters.
123+
124+ Example: for functions (integer, float ->:ok) and (float, integer -> :error)
125+ domain isn't {integer|float,integer|float} as that would incorrectly accept {float,float}
126+ Instead, it is {integer,float} or {float,integer}
127+
128+ Made public for testing.
129+ """
130+ def domain_descr ( types ) when is_list ( types ) , do: tuple ( types )
131+
113132 ## Optional
114133
115134 # `not_set()` is a special base type that represents an not_set field in a map.
@@ -704,11 +723,11 @@ defmodule Module.Types.Descr do
704723 if not empty? ( descr ) and fun_only? ( descr , arity ) , do: :ok , else: :error
705724
706725 { dynamic , static } ->
707- empty_static = empty? ( static )
726+ empty_static? = empty? ( static )
708727
709728 cond do
710- not empty_static -> if fun_only? ( static , arity ) , do: :ok , else: :error
711- empty_static and not empty? ( intersection ( dynamic , fun ( arity ) ) ) -> :ok
729+ not empty_static? -> if fun_only? ( static , arity ) , do: :ok , else: :error
730+ empty_static? and not empty? ( intersection ( dynamic , fun ( arity ) ) ) -> :ok
712731 true -> :error
713732 end
714733 end
@@ -880,47 +899,46 @@ defmodule Module.Types.Descr do
880899
881900 # Function types are represented using Binary Decision Diagrams (BDDs) for efficient
882901 # handling of unions, intersections, and negations.
902+ #
903+ # Currently, they only represent weak types. It is yet to be decided how all of the
904+ # types will be encoded in the descr.
883905
884906 ### Key concepts:
885907
886- # * BDD structure: A tree with function nodes and :fun_top/:fun_bottom leaves. Paths to :fun_top
887- # represent valid function types. Nodes are positive when following a left
888- # branch (e.g. (int, float) -> bool) and negative otherwise.
908+ # * BDD structure: A tree with function nodes and :fun_top/:fun_bottom leaves.
909+ # Paths to :fun_top represent valid function types. Nodes are positive when
910+ # following a left branch (e.g. (int, float -> bool) and negative otherwise.
889911
890912 # * Function variance:
891913 # - Contravariance in arguments: If s <: t, then (t → r) <: (s → r)
892914 # - Covariance in returns: If s <: t, then (u → s) <: (u → t)
893915
894916 # * Representation:
895917 # - fun(): Top function type (leaf 1)
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`
918+ # - Function literals: {[t1, ..., tn], t} where [t1, ..., tn] are argument types and t is return type
899919 # - Normalized form for function applications: {domain, arrows, arity} is produced by `fun_normalize/1`
900920
901921 # * Examples:
902922 # - fun([integer()], atom()): A function from integer to atom
903923 # - intersection(fun([integer()], atom()), fun([float()], boolean())): A function handling both signatures
904924
905- # Note: Function domains are expressed as tuple types. We use separate representations rather than
906- # unary functions with tuple domains to handle special cases like representing functions of a
907- # specific arity (e.g., (none,none->term) for arity 2).
908-
925+ # Note: Function domains are expressed as tuple types. We use separate representations
926+ # rather than unary functions with tuple domains to handle special cases like representing
927+ # functions of a specific arity (e.g., (none,none->term) for arity 2).
909928 defp fun_new ( inputs , output ) , do: { { inputs , output } , :fun_top , :fun_bottom }
910929
911- @ doc """
912- Creates a function type from a list of inputs and an output where the inputs and/or output may be dynamic.
913-
914- For function (t → s) with dynamic components:
915- - Static part: (upper_bound(t) → lower_bound(s))
916- - Dynamic part: dynamic(lower_bound(t) → upper_bound(s))
917-
918- When handling dynamic types:
919- - `upper_bound(t)` extracts the upper bound (most general type) of a gradual type.
920- For `dynamic(integer())`, it is `integer()`.
921- - `lower_bound(t)` extracts the lower bound (most specific type) of a gradual type.
922- """
923- def fun_descr ( args , output ) when is_list ( args ) do
930+ # Creates a function type from a list of inputs and an output
931+ # where the inputs and/or output may be dynamic.
932+ #
933+ # For function (t → s) with dynamic components:
934+ # - Static part: (upper_bound(t) → lower_bound(s))
935+ # - Dynamic part: dynamic(lower_bound(t) → upper_bound(s))
936+ #
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.
941+ defp fun_descr ( args , output ) when is_list ( args ) do
924942 dynamic_arguments? = are_arguments_dynamic? ( args )
925943 dynamic_output? = match? ( % { dynamic: _ } , output )
926944
@@ -949,16 +967,11 @@ defmodule Module.Types.Descr do
949967 defp lower_bound ( :term ) , do: :term
950968 defp lower_bound ( type ) , do: Map . delete ( type , :dynamic )
951969
952- # Tuples represent function domains, using unions to combine parameters.
953- # Example: for functions (integer,float)->:ok and (float,integer)->:error
954- # domain isn't {integer|float,integer|float} as that would incorrectly accept {float,float}
955- # Instead, it is {integer,float} or {float,integer}
956- def domain_descr ( types ) when is_list ( types ) , do: tuple ( types )
957-
958970 @ doc """
959971 Calculates the domain of a function type.
960972
961973 For a function type, the domain is the set of valid input types.
974+
962975 Returns:
963976 - `:badfunction` if the type is not a function type
964977 - A tuple type representing the domain for valid function types
@@ -1021,7 +1034,7 @@ defmodule Module.Types.Descr do
10211034
10221035 defp fun_domain_static ( :term ) , do: :badfunction
10231036 defp fun_domain_static ( % { } ) , do: { :ok , none ( ) }
1024- defp fun_domain_static ( :emptyfunction ) , do: { :ok , none ( ) }
1037+ defp fun_domain_static ( :empty_function ) , do: { :ok , none ( ) }
10251038
10261039 @ doc """
10271040 Applies a function type to a list of argument types.
@@ -1205,7 +1218,7 @@ defmodule Module.Types.Descr do
12051218 ## Return Values
12061219 #
12071220 # - `{domain, arrows, arity}` for valid function BDDs
1208- # - `:emptyfunction ` if the BDD represents an empty function type
1221+ # - `:empty_function ` if the BDD represents an empty function type
12091222 #
12101223 # ## Internal Use
12111224 #
@@ -1232,7 +1245,7 @@ defmodule Module.Types.Descr do
12321245 end
12331246 end )
12341247
1235- if arrows == [ ] , do: :emptyfunction , else: { domain , arrows , arity }
1248+ if arrows == [ ] , do: :empty_function , else: { domain , arrows , arity }
12361249 end
12371250
12381251 # Checks if a function type is empty.
@@ -1262,12 +1275,12 @@ defmodule Module.Types.Descr do
12621275 # 2. There exists a negative function that negates the whole positive intersection
12631276
12641277 ## Examples
1278+ #
12651279 # - `{[fun(1)], []}` is not empty
12661280 # - `{[fun(1), fun(2)], []}` is empty (different arities)
12671281 # - `{[fun(integer() -> atom())], [fun(none() -> term())]}` is empty
12681282 # - `{[], _}` (representing the top function type fun()) is never empty
12691283 #
1270- # TODO: test performance
12711284 defp fun_empty? ( [ ] , _ ) , do: false
12721285
12731286 defp fun_empty? ( positives , negatives ) do
@@ -1278,9 +1291,12 @@ defmodule Module.Types.Descr do
12781291
12791292 { positive_arity , positive_domain } ->
12801293 # Check if any negative function negates the whole positive intersection
1281- # e.g. (integer()->atom()) is negated by
1282- # i) (none()->term()) ii) (none()->atom())
1283- # ii) (integer()->term()) iv) (integer()->atom())
1294+ # e.g. (integer() -> atom()) is negated by:
1295+ #
1296+ # * (none() -> term())
1297+ # * (none() -> atom())
1298+ # * (integer() -> term())
1299+ # * (integer() -> atom())
12841300 Enum . any? ( negatives , fn { neg_arguments , neg_return } ->
12851301 # Filter positives to only those with matching arity, then check if the negative
12861302 # function's domain is a supertype of the positive domain and if the phi function
@@ -1311,7 +1327,7 @@ defmodule Module.Types.Descr do
13111327
13121328 # Implements the Φ (phi) function for determining function subtyping relationships.
13131329 #
1314- ## Algorithm
1330+ # # # Algorithm
13151331 #
13161332 # For inputs t₁...tₙ, booleans b₁...bₙ, negated return type t, and set of arrow types P:
13171333 #
@@ -1324,7 +1340,6 @@ defmodule Module.Types.Descr do
13241340 # Returns true if the intersection of the positives is a subtype of (t1,...,tn)->(not t).
13251341 #
13261342 # See [Castagna and Lanvin (2024)](https://arxiv.org/abs/2408.14345), Theorem 4.2.
1327-
13281343 defp phi_starter ( arguments , return , positives ) do
13291344 n = length ( arguments )
13301345 # Arity mismatch: if there is one positive function with a different arity,
@@ -1399,27 +1414,25 @@ defmodule Module.Types.Descr do
13991414 defp fun_to_quoted ( :fun , _opts ) , do: [ { :fun , [ ] , [ ] } ]
14001415
14011416 defp fun_to_quoted ( bdd , opts ) do
1402- arrows = bdd |> fun_get ( )
1417+ arrows = fun_get ( bdd )
14031418
1404- for { positives , negatives } <- arrows ,
1405- not fun_empty? ( positives , negatives ) do
1419+ for { positives , negatives } <- arrows , not fun_empty? ( positives , negatives ) do
14061420 fun_intersection_to_quoted ( positives , opts )
14071421 end
14081422 |> case do
14091423 [ ] -> [ ]
1410- [ single ] -> [ single ]
14111424 multiple -> [ Enum . reduce ( multiple , & { :or , [ ] , [ & 2 , & 1 ] } ) ]
14121425 end
14131426 end
14141427
14151428 defp fun_intersection_to_quoted ( intersection , opts ) do
14161429 intersection
14171430 |> Enum . map ( fn { args , ret } ->
1418- { :-> , [ ] , [ [ to_quoted ( tuple_descr ( :closed , args ) , opts ) ] , to_quoted ( ret , opts ) ] }
1431+ { :__block__ , [ ] ,
1432+ [ [ { :-> , [ ] , [ Enum . map ( args , & to_quoted ( & 1 , opts ) ) , to_quoted ( ret , opts ) ] } ] ] }
14191433 end )
14201434 |> case do
14211435 [ ] -> { :fun , [ ] , [ ] }
1422- [ single ] -> single
14231436 multiple -> Enum . reduce ( multiple , & { :and , [ ] , [ & 2 , & 1 ] } )
14241437 end
14251438 end
0 commit comments