Declared types that go past the specification's lattice: {:not, type} and {:union, [type, ...]} (D-178).
The lattice has no union and no negation, and the internal type (Feel.Type.Union) has both: Any except number is what is
left of a value of v instance of number in the else branch, number | string is a join the analysis makes at every if.
A host that knows more of a name than the lattice can say, that it is anything but a number or that it is a number or a string, says
it with these two, as it says "never null" with {:non_null, type} (D-48):
Feel.Type.t/0 | Written | The values |
|---|---|---|
{:not, :number} | Any except number | every value that is not a number |
{:union, [:number, :string]} | number | string | a number or a string |
{:not, {:union, [:number, {:list, :any}]}} | Any except number | list<Any> | neither a number nor a list |
What they mean
The union translation (to_union/1) supplies the analysis representation. Runtime membership
evaluates the union or negation directly with the original component declarations. Recursion may
occur in a value's retained function signature even when the target declaration has none, so an
analysis projection cannot replace the runtime check. In particular, negating a recursive
declaration projects to Any | Null without making runtime negation accept every value.
nullis in the type as it is in every other type of a declaration: the run exemptsnullfrom a declared type (D-138), so an analysis that left it out would be wrong. Only a mark on the whole type,{:non_null, type}or(Any except number)!, takes it out. A mark on a member of a union, or on the type afterexcept, is ignored byto_union/1and refused byFeel.Type.parse/2:Any except number!would read as "not a number, and not null", and it is not.{:not, type}isAnyless the values oftypethat are notnull.Nullis not a value that can be taken out (nullis under every type), so{:not, :null}isAny, and{:not, :any}isNull.{:union, types}is the join of the types, and has at least one. A member that is a composite type is flattened by the union's normal form, sonumber | (string | boolean)isnumber | string | boolean.- Conversion is the one of the specification, asked of the union: a singleton list is unwrapped when its element is in the
type and the list is not (
Feel.Type.Conversion). A value is not wrapped in a list, and a date is not made a date and time, to reach a member of a union (3is notlist<number> | string, and is not made[3]), since the specification's conversions are for one type that is expected, and a union is not one. A list whose element type is a composite type is wrapped as any list is. - A function type with a composite type in its parameters or its result is compared by the algebra of arrows
(
Feel.Type.Arrows), not by the lattice. Composite conformance involving recursive descriptors conservatively accepts identical declarations; it does not prove recursive relations from projections.
Feel.type/1 is a type of the specification, so for a name declared with one it is wider than the declaration: Any for Any except number and for number | string, list<Any> for list<number> | list<string>. Feel.IR.type/1 retains
the internal representation, including conservative upper approximations where required.
Summary
Functions
Whether a type is one of the two, at its outermost level. A composite type inside a list, a context or a function type is reached by the code that walks the type.
Whether a type is proved within another when either is composite. Non-recursive declarations compare their analysis unions. If either contains guarded recursive descriptors, only identical declarations are accepted here; a negative answer need not establish semantic non-inclusion.
The analysis union of a composite declaration, null included. Recursive declarations may give
an upper approximation rather than exact runtime membership. Raises ArgumentError for a union of no type.
Whether a value is in a composite type. null is exempt; each component is checked directly
against the original declaration, including retained function signatures.
Functions
@spec composite?(Feel.Type.t()) :: boolean()
Whether a type is one of the two, at its outermost level. A composite type inside a list, a context or a function type is reached by the code that walks the type.
iex> Feel.Type.Composite.composite?({:not, :number})
true
iex> Feel.Type.Composite.composite?({:list, {:not, :number}})
false
@spec conforms?(Feel.Type.t(), Feel.Type.t()) :: boolean()
Whether a type is proved within another when either is composite. Non-recursive declarations compare their analysis unions. If either contains guarded recursive descriptors, only identical declarations are accepted here; a negative answer need not establish semantic non-inclusion.
iex> Feel.Type.Composite.conforms?(:number, {:union, [:number, :string]})
true
iex> Feel.Type.Composite.conforms?({:not, :number}, :any)
true
iex> Feel.Type.Composite.conforms?({:union, [:number, :string]}, :number)
false
@spec to_union(Feel.Type.t()) :: Feel.Type.Union.t()
The analysis union of a composite declaration, null included. Recursive declarations may give
an upper approximation rather than exact runtime membership. Raises ArgumentError for a union of no type.
iex> Feel.Type.Composite.to_union({:not, :number}) |> Feel.Type.Union.format()
"Any except number | Null"
iex> Feel.Type.Composite.to_union({:union, [:number, :string]}) |> Feel.Type.Union.format()
"number | string | Null"
iex> Feel.Type.Composite.to_union({:not, {:union, [:number, :string]}}) |> Feel.Type.Union.format()
"Any except number, string | Null"
iex> Feel.Type.Composite.to_union({:not, {:list, :number}}) |> Feel.Type.Union.format()
"Any except list<number | Null> | Null"
iex> Feel.Type.Composite.to_union({:union, []})
** (ArgumentError) a union type needs a member
@spec value_conforms?(Feel.Value.t(), Feel.Type.t()) :: boolean()
Whether a value is in a composite type. null is exempt; each component is checked directly
against the original declaration, including retained function signatures.
iex> Feel.Type.Composite.value_conforms?(Decimal.new(1), {:not, :number})
false
iex> Feel.Type.Composite.value_conforms?("a", {:not, :number})
true
iex> Feel.Type.Composite.value_conforms?(nil, {:not, :number})
true