The static result types of operations, for Feel.Analysis.Analyzer.
A type here is a Feel.Type.Union, a list of alternatives with :null among them
when the value may be null (D-39). Given the types of the operands, each function
returns {:ok, type} or {:mismatch, type}; the second means that the operation is a
type mismatch, so null with an error, whenever no operand is null.
The mirror of this module at run time is Feel.Runtime; a test runs both on the same
operands and checks that the value of the one is among the alternatives of the other.
How a union is typed
Each pair of alternatives, one of each operand, is typed alone, and the result is the
union of what the pairs give. [:number, :null] + [:number] is the pairs
number + number, which is number | null, and null + number, which is null.
A pair that is a type mismatch gives null.
The result is {:mismatch, type} when every pair without a null is a type mismatch, and
there is one: the operation is an error unless an operand is null. The type is the same
union as for {:ok, type}; it is [:null] except for = and !=, which are false and
true for a null operand. A union with some pairs that are mismatches is a possible
mismatch, which is not reported.
Value sets
An alternative can be a set of values (D-39): {:number, set}, {:string, strings}, or
{:boolean, true} or {:boolean, false}; the bare :number, :string and :boolean are every
value of the kind. For numbers the operation works on the set (Feel.Analysis.Intervals): the
result is the set of what the operation can give, and it is null for lack of range only where
the intervals say it can be, so [0..150] + 1 is [1..151] and never null in itself, and a
divisor that does not have zero in it is never zero. A comparison of two sets that settles it is
the boolean it settles to: [0..150] < 200 is {:boolean, true}. The strings of sets are
joined for + and compared for = and !=; a boolean that is known decides and and or.
This module works out the kind of a result and whether it can be null, and puts the sets into the alternatives. What
the sets of the operands come to, and whether they decide a comparison, is asked of
Feel.Analysis.Refinement.Operations (D-133), which calls the modules named below; this module calls none of them.
A date and the two kinds of duration have sets too (D-109), which are sets of the numbers that stand
for the values (Feel.Type.Image: the days of a date, the months or the seconds of a duration), so the
same intervals work on them. The seconds of PT1H + PT2H are 3600 + 7200, and PT1H * 2 is the seconds
times 2: the result is the set of the durations, and null where the numbers can overflow or divide by
zero, as for a number. The months of a years and months duration do not overflow at run time, so a sum of
them that the numbers say could pass the range is any years and months duration, and not null. Two dates,
or two durations of one kind, are compared by their numbers: @"PT1H" < @"P1D" is {:boolean, true}. What
the numbers do not carry is worked out from the sets of both: a years and months duration times a number,
which is rounded (D-54) and so is not a product of images, has the months rounded as the run does
(Feel.Analysis.MonthScaling, D-109), and a date plus or minus a duration, which is not a sum of images
either, since the day of the month is clamped (2024-01-31 + P1M), has the dates of Feel.Analysis.DateArithmetic
(D-109). A time and a date and time have sets of the second they fall in, on three lines, for the clock and for the
instant, since their order depends on a zone (Feel.Type.Moment, D-109): a comparison of two of them is decided by
Feel.Analysis.MomentTests, a time or a date and time plus or minus a duration has the second that the sets come
to (Feel.Analysis.MomentArithmetic, D-109), and so does the difference of two of them (D-109).
The types it takes
The types are plain (Feel.IR.plain_type/1, D-152, D-152): a union whose members are the kinds whole. A kind less some of
it and the empty list (Feel.Type.Union.positive/1 makes the list of nothing of it) are for what passes a type on and for
the narrowing, and a function here that is given one takes it for a kind it does not know.
What a pair gives
null as an operand gives null, except for = and != (Table 50), which never give
null because of an operand, and for and and or, where a false or a true decides.
An alternative of the type :any or a named type is not known, so the result is
[:any, :null] or [:boolean, :null], unless the other operand decides it alone: a kind with no order for < and
> (D-196), a kind with no operation for + - * / ** (D-203); or unless the kinds say more: an operation on a kind that
has an operation and one that is not known is the union of what the pairs give (D-203). The rest is below.
Arithmetic
A number operation can give null: it overflows past decimal128, it can be too small to write, / divides
by zero, and ** has no value for a zero to a negative power or a negative number to a power that is not
whole. Whether it does is decided by the sets of the operands (see above), so on any number it can, and on
numbers that stay away from the ends of the range and from zero it cannot: 0 ** x for an x in [5..10]
is the number 0, and x ** 2 is [25..100] (D-161).
| Operator | Operands | Result |
|---|---|---|
+ | two strings | string |
+ - | two years and months durations | years and months duration (never null in itself) |
+ - | two days and time durations | days and time duration, null |
+ - * / ** | two numbers | number, null |
* | a days and time duration and a number, in either order | days and time duration, null |
/ | a days and time duration and a number | days and time duration, null |
/ | two durations of one kind | number, null |
* / | a years and months duration and a number | years and months duration, a set of the months rounded as the run does, and null (D-109) |
+ - | a date and a duration | date, a set of the days the run can give, and null where the year can leave the range (D-109) |
+ - | a date and time and a duration | date and time, a set of the seconds the run can give, and null where the clock can leave the calendar (D-109) |
+ - | a time and a days and time duration | time, a set of the seconds of the day round the day (D-109) |
- | two dates | days and time duration (a set, from the days between) |
- | two dates and times, two times, a date and a date and time | days and time duration, a set of the seconds between them to a second either way, and null (one has a zone, one has not, D-109) |
| negation | a number or a duration | the same type |
Every other pair of kinds is a mismatch, since the run does the operations of the temporal tables (D-54) and gives a type mismatch for the rest.
An operand of a kind that an operator has no operation for is a mismatch against any other operand, Any and a named
type included (D-203): true + v is null for every v, an error for one that is not null. A boolean, a list, a
range, a context, a function and a term that is no value of FEEL have no operation at all; a string has + and no
other; a date, a time and a date and time have + and -; a duration has all but **; only a number has **. So
true + v, [1] - v, "a" - v and d * v are Null. A kind that has an operation against one that is not known (Any,
a named type) is a value of the kinds that its pairs give with a kind of the unknown one, or null (D-203): the unknown
one is a value of every kind, a pair that no table names is null, and a null is null. x + v is number | Null (a
number is added to a number only), "a" + v is string | Null, d + v is date | Null, x * v is number | years and months duration | days and time duration | Null, and v + w is the seven kinds that have a +, and Null. The pairs are of
the kinds whole, so the set of values of the known operand does not cut the result (it is as narrow as the kinds say, and
it is a function of the operator and the kinds, which is kept). -v is a number, a years and months duration, a days
and time duration, or Null (D-207): the kinds that a negation has. One unknown value read twice (v - v, D-170, see
arithmetic_of_one/3) is the kinds of the pair of a kind with itself, and Null. A union is null only if every pair
of its alternatives is.
Negation cannot overflow, and a result that is not null is assumed for a number that
is well formed: at most 34 digits and in the exponent range. A bound number that is not
is null at run time and the type does not say so.
Comparison
= and != give boolean and never null for operands of one primitive kind, or
when an operand is null. Lists, contexts and ranges can hold elements that cannot be
compared, so they give boolean and null. <, <=, > and >= give null for a
null operand, and boolean for two numbers, two strings, two dates, two times, two dates
and times, or two durations of one kind (a time or a date and time with a zone that the time
zone database does not have would be null, and a value of FEEL has none). Operands of one
kind that has no order, such as two booleans, are a mismatch (null, with a type_mismatch
warning, D-193), for <= and >= as for < and >: true <= true is null (the domain reading of D-196, D-196).
Operands of different kinds are a mismatch, a date and a date and time, or the two kinds of duration,
included (notes 02, A-38; D-54).
An operand that has no order (a boolean, a list, a range, a context, a function, a term that is no value of FEEL) is
a mismatch against any other operand, Any and a named type included (D-196): true < v and true <= v are null
for every v, an error for one that is not null.
Logic
and and or give boolean for two booleans, and boolean and null when an operand
can be null or is not a boolean but the other is, since a false decides a conjunction
and a true a disjunction whatever the other operand is. An operand that is never a boolean (null, a number, a
string) leaves one outcome out: b and null is false or null and never true, b or null is true or null
and never false (Tables 51 and 52, Feel.Runtime.Logic; D-170). When no operand can be a boolean
the type is null, and not a mismatch: the run gives a plain null for an operand that is not a boolean (D-103).
Conditional
if is the one construct here that cannot be a mismatch, and conditional/3 returns a
type and not a result. The then branch is taken for a condition that is true and the
else branch for anything else, null and a value that is not a boolean included
(Table 50), so the condition adds nothing to the type. The type is the union of the types
of the branches that can be taken (reachable/1): a condition that is true for sure
leaves the then branch, and one that cannot be true leaves the else branch.
Summary
Types
A comparison operator, as in Feel.IR.comparison_operator/0.
How fine the numbers of each operand are (Feel.Analysis.Grain, D-161): a multiple of 10^-d, or nil. It lets a product or a
quotient of numbers that are not near zero be one that is never too small to write (D-161).
An arithmetic operator, as in Feel.IR.arithmetic_operator/0.
What a function of this module returns.
What the analyzer knows of a node besides its type (D-193): its type, and its structure. A list literal, a constant list,
and a context bound to one have a structure: the shapes of the elements of a list of up to 32, or only the number of them;
the shapes of the entries of a closed context of up to 32. Anything else has :none.
Functions
Returns the type of left <operator> right.
Returns the type of x <operator> x for one value x that is read twice (D-170), given the type of x: the two operands
are not two values of the type, so the pairs of two different alternatives are not there, and x - x is 0 (a date and
a time give the zero duration), x / x is 1 unless x is 0, x * x is not below 0, and x + x is 2x. An
alternative that is not a number, or a duration or a date for -, or a duration for /, is typed as it is for two
values. null is an alternative of the result for an x that is, as it is for two. An x of an unknown type (Any, a named
type) is one value of one kind, the same at both ends (D-207): the union, over the kinds that the operator has an operation
for, of the type for that kind, and null. x - x is 0 of a number, a years and months duration or a days and time
duration, x + x is a number, a string or a duration, x * x, x / x and x ** x are a number; the pairs of two kinds
(a date and a duration) that x - y has are not there.
Returns the type of value between low and high (D-69), which is value >= low and value <= high: the
types of the two comparisons, and then of the three-valued and. So it is true when the sets of the
value and of the bounds put it between them, false when they put it outside, null when an operand is
null (but false if the other bound already puts it outside), and a type mismatch only when both
comparisons are one: 1 between "a" and "b".
Returns the type of if … then then_type else else_type for the branches that can be taken, {then?, else?}: the
union of their types. At least one is taken. reachable/1 has them for the type of the condition; the analyzer also
leaves out a branch whose facts leave a name no value (D-152).
Returns the type of left <operator> right for a comparison.
Returns the type of if condition then then_type else else_type: the union of the types of
the branches that reachable/1 says can be taken.
Returns the type of left and right when right was typed knowing that left is true (D-194): the
names that left says something about have, in right, the types that it leaves them
(Feel.Analysis.Narrowing).
Returns the type of left and right when right was typed twice (D-194): as right_when_true for the case that
left is true, and as right_otherwise for the case that it is not. Where conjunction_after/2 can only say
that a left that is null gives false or null, this knows what the right is for the names as they are
then: x >= 1 and x <= 10 for an x of [1..3] that may be null is true, and null where x is.
Returns the type of left or right when right was typed knowing that left is not true (D-194): the
names that left says something about when it is false or null, or not a boolean, have, in right,
the types that it leaves them (Feel.Analysis.Narrowing).
Returns the type of value instance of type (Table 62), given the type of the value and the
type, resolved.
Returns the type of a test of in that is an expression, x in [1, 2, 3] (D-61), given the type of the expression and
whether it reads the input ?.
Returns the type of left and right or left or right.
Returns the type of subject in tests (D-61), given the types of the tests.
Returns the type of -operand. An operand of an unknown type (Any, a named type) is a number, a years and months
duration, a days and time duration, or null (D-207): the kinds that a negation has.
Which bounds on the operands of left <operator> right would rule null out (D-161), when neither
operand can be null: :near_zero for operands at least 0.001 away from zero, :too_large for
operands of at most 1E+30, :range where the result needs both, and nil where it is not null,
where it is certain (two single numbers, which is a warning of its own, D-161), or where these bounds
do not rule it out. It is what lets the analyzer say, in an info, that tighter allowed values would
rule null out, and the numbers say so: the operands are restricted as the hint says, and the
result has no null left.
Returns the type of the range literal [from..to] (D-61), given the types of its endpoints.
Returns which branches of if condition then … else … a run can take, as
{then?, else?}.
Returns the type of left = right and left != right for two nodes whose structure is known (D-193, D-193, D-161).
Types
@type comparison_operator() :: Feel.IR.comparison_operator()
A comparison operator, as in Feel.IR.comparison_operator/0.
How fine the numbers of each operand are (Feel.Analysis.Grain, D-161): a multiple of 10^-d, or nil. It lets a product or a
quotient of numbers that are not near zero be one that is never too small to write (D-161).
@type operator() :: Feel.IR.arithmetic_operator()
An arithmetic operator, as in Feel.IR.arithmetic_operator/0.
@type result() :: {:ok, Feel.Type.Union.t()} | {:mismatch, Feel.Type.Union.t()}
What a function of this module returns.
@type shape() :: %{type: Feel.Type.Union.t(), structure: structure()}
What the analyzer knows of a node besides its type (D-193): its type, and its structure. A list literal, a constant list,
and a context bound to one have a structure: the shapes of the elements of a list of up to 32, or only the number of them;
the shapes of the entries of a closed context of up to 32. Anything else has :none.
@type structure() :: :none | {:list, [shape()]} | {:size, non_neg_integer()} | {:context, %{optional(Feel.Name.t()) => shape()}}
Functions
@spec arithmetic(operator(), Feel.Type.Union.t(), Feel.Type.Union.t(), grains()) :: result()
Returns the type of left <operator> right.
iex> Feel.Analysis.Inference.arithmetic(:add, [:string], [:string])
{:ok, [:string]}
iex> Feel.Analysis.Inference.arithmetic(:add, [:number], [:number])
{:ok, [:number, :null]}
iex> Feel.Analysis.Inference.arithmetic(:add, [:number, :null], [:null])
{:ok, [:null]}
iex> Feel.Analysis.Inference.arithmetic(:add, [:number], [:string])
{:mismatch, [:null]}
iex> Feel.Analysis.Inference.arithmetic(:add, [:number], [:number, :string])
{:ok, [:number, :null]}
@spec arithmetic_of_one(operator(), Feel.Type.Union.t(), grains()) :: result()
Returns the type of x <operator> x for one value x that is read twice (D-170), given the type of x: the two operands
are not two values of the type, so the pairs of two different alternatives are not there, and x - x is 0 (a date and
a time give the zero duration), x / x is 1 unless x is 0, x * x is not below 0, and x + x is 2x. An
alternative that is not a number, or a duration or a date for -, or a duration for /, is typed as it is for two
values. null is an alternative of the result for an x that is, as it is for two. An x of an unknown type (Any, a named
type) is one value of one kind, the same at both ends (D-207): the union, over the kinds that the operator has an operation
for, of the type for that kind, and null. x - x is 0 of a number, a years and months duration or a days and time
duration, x + x is a number, a string or a duration, x * x, x / x and x ** x are a number; the pairs of two kinds
(a date and a duration) that x - y has are not there.
iex> Feel.Analysis.Inference.arithmetic_of_one(:subtract, [:number]) |> elem(1) |> Feel.Type.Union.format()
"number = 0"
iex> Feel.Analysis.Inference.arithmetic_of_one(:subtract, [:number, :null]) |> elem(1) |> Feel.Type.Union.format()
"number = 0 | Null"
iex> Feel.Analysis.Inference.arithmetic_of_one(:divide, [:number]) |> elem(1) |> Feel.Type.Union.format()
"number = 1 | Null"
iex> Feel.Analysis.Inference.arithmetic_of_one(:multiply, [:string])
{:mismatch, [:null]}
iex> Feel.Analysis.Inference.arithmetic_of_one(:subtract, [:any]) |> elem(1) |> Feel.Type.Union.format()
"number = 0 | years and months duration = P0M | days and time duration = PT0S | Null"
iex> Feel.Analysis.Inference.arithmetic_of_one(:multiply, [:any]) |> elem(1) |> Feel.Type.Union.format()
"number in [0..max] | Null"
@spec between( Feel.Type.Union.t(), Feel.Type.Union.t(), Feel.Type.Union.t(), {Feel.Type.Union.t(), Feel.Type.Union.t()} | {Feel.Type.Union.t(), Feel.Type.Union.t(), {Feel.Type.Union.t(), Feel.Type.Union.t()}} | nil ) :: result()
Returns the type of value between low and high (D-69), which is value >= low and value <= high: the
types of the two comparisons, and then of the three-valued and. So it is true when the sets of the
value and of the bounds put it between them, false when they put it outside, null when an operand is
null (but false if the other bound already puts it outside), and a type mismatch only when both
comparisons are one: 1 between "a" and "b".
after_lower is {value, high}, the types of the value and of the upper bound when they were typed
knowing that the lower comparison is true (D-194), as the right operand of an and is
(conjunction_after/2): x between 5 and 3 for an x in [0..10] is then false, since for an x of
[5..10] the upper comparison is. Without it the two comparisons are typed apart.
{value, high, {value_otherwise, high_otherwise}} adds the types of the value and of the upper bound where the lower
comparison is not true. The upper comparison is typed for each case of the lower one, as the range of an in is
(conjunction_cases/3, D-194): n between 0 and 10 for an n in [0..10] that may be null is true where the lower
comparison is, and null where it is not, the n being null there. An upper bound that reads a name that the lower
comparison narrows has another type there (n between 0 and n).
iex> Feel.Analysis.Inference.between([:number], [:number], [:number])
{:ok, [:boolean]}
iex> Feel.Analysis.Inference.between([:number, :null], [:number], [:number])
{:ok, [:boolean, :null]}
iex> Feel.Analysis.Inference.between([:number], [:string], [:string])
{:mismatch, [:null]}
iex> Feel.Analysis.Inference.between([:number], [:string], [:number])
{:ok, [{:boolean, false}, :null]}
iex> Feel.Analysis.Inference.between([:null], [:number], [:number])
{:ok, [:null]}
@spec branches({boolean(), boolean()}, Feel.Type.Union.t(), Feel.Type.Union.t()) :: Feel.Type.Union.t()
Returns the type of if … then then_type else else_type for the branches that can be taken, {then?, else?}: the
union of their types. At least one is taken. reachable/1 has them for the type of the condition; the analyzer also
leaves out a branch whose facts leave a name no value (D-152).
iex> Feel.Analysis.Inference.branches({true, true}, [:number], [:string])
[:number, :string]
iex> Feel.Analysis.Inference.branches({false, true}, [:number], [:string])
[:string]
@spec compare(comparison_operator(), Feel.Type.Union.t(), Feel.Type.Union.t()) :: result()
Returns the type of left <operator> right for a comparison.
iex> Feel.Analysis.Inference.compare(:lt, [:number], [:number])
{:ok, [:boolean]}
iex> Feel.Analysis.Inference.compare(:eq, [:number, :null], [:number])
{:ok, [:boolean]}
iex> Feel.Analysis.Inference.compare(:lt, [:number, :null], [:number])
{:ok, [:boolean, :null]}
iex> Feel.Analysis.Inference.compare(:lt, [:number], [:null])
{:ok, [:null]}
iex> Feel.Analysis.Inference.compare(:eq, [:number], [:string])
{:mismatch, [:null]}
iex> Feel.Analysis.Inference.compare(:lt, [{:string, ["a", "b"]}], [{:string, ["c"]}])
{:ok, [{:boolean, true}]}
iex> Feel.Analysis.Inference.compare(:lt, [{:string, ["a", "b"]}], [{:string, ["b"]}])
{:ok, [:boolean]}
iex> Feel.Analysis.Inference.compare(:gte, [{:string, ["a", "b"]}], [{:string, ["c"]}])
{:ok, [{:boolean, false}]}
iex> Feel.Analysis.Inference.compare(:eq, [{:list, [:any, :null]}], [{:list, [:null]}])
{:ok, [:boolean]}
iex> Feel.Analysis.Inference.compare(:eq, [{:list, [:number, :null]}, :null], [{:list, [:null]}])
{:ok, [:boolean]}
iex> Feel.Analysis.Inference.compare(:neq, [{:list, []}], [{:list, []}])
{:ok, [{:boolean, false}]}
iex> Feel.Analysis.Inference.compare(:eq, [{:list, [:number]}], [{:list, [:string]}])
{:ok, [:boolean, :null]}
@spec conditional(Feel.Type.Union.t(), Feel.Type.Union.t(), Feel.Type.Union.t()) :: Feel.Type.Union.t()
Returns the type of if condition then then_type else else_type: the union of the types of
the branches that reachable/1 says can be taken.
iex> Feel.Analysis.Inference.conditional([:boolean], [:number], [:string])
[:number, :string]
iex> Feel.Analysis.Inference.conditional([:boolean, :null], [:number], [:null])
[:number, :null]
iex> Feel.Analysis.Inference.conditional([{:boolean, true}], [:number], [:string])
[:number]
iex> Feel.Analysis.Inference.conditional([:null], [:number], [:string])
[:string]
@spec conjunction_after(Feel.Type.Union.t(), Feel.Type.Union.t()) :: Feel.Type.Union.t()
Returns the type of left and right when right was typed knowing that left is true (D-194): the
names that left says something about have, in right, the types that it leaves them
(Feel.Analysis.Narrowing).
Both operands are evaluated (D-53), but the result is right only where left is true. Where left is
false it is false. Where left is null, or not a boolean, it is false or null, and nothing more
is known, since right was typed for the case that left is true and is evaluated with the names as
they are: x > 5 and x = null for an x that is null has x > 5 null and x = null true, though
x = null is false for the x that x > 5 leaves. So the result is taken for each case of left.
iex> Feel.Analysis.Inference.conjunction_after([:boolean], [{:boolean, false}])
[{:boolean, false}]
iex> Feel.Analysis.Inference.conjunction_after([:boolean, :null], [{:boolean, false}])
[{:boolean, false}, :null]
iex> Feel.Analysis.Inference.conjunction_after([{:boolean, true}], [:boolean, :null])
[:boolean, :null]
iex> Feel.Analysis.Inference.conjunction_after([{:boolean, false}], [:boolean, :null])
[{:boolean, false}]
@spec conjunction_cases(Feel.Type.Union.t(), Feel.Type.Union.t(), Feel.Type.Union.t()) :: Feel.Type.Union.t()
Returns the type of left and right when right was typed twice (D-194): as right_when_true for the case that
left is true, and as right_otherwise for the case that it is not. Where conjunction_after/2 can only say
that a left that is null gives false or null, this knows what the right is for the names as they are
then: x >= 1 and x <= 10 for an x of [1..3] that may be null is true, and null where x is.
It is for a right operand that is cheap to type again, the upper comparison of a range.
iex> Feel.Analysis.Inference.conjunction_cases([{:boolean, true}, :null], [{:boolean, true}], [:null])
[{:boolean, true}, :null]
iex> Feel.Analysis.Inference.conjunction_cases([{:boolean, true}, :null], [{:boolean, true}], [:boolean, :null])
[:boolean, :null]
iex> Feel.Analysis.Inference.conjunction_cases([:boolean], [{:boolean, false}], [:null])
[{:boolean, false}]
@spec disjunction_after(Feel.Type.Union.t(), Feel.Type.Union.t()) :: Feel.Type.Union.t()
Returns the type of left or right when right was typed knowing that left is not true (D-194): the
names that left says something about when it is false or null, or not a boolean, have, in right,
the types that it leaves them (Feel.Analysis.Narrowing).
It is the other way round from conjunction_after/2. Both operands are evaluated (D-53), but right matters
only where left is not true, since true or right is true whatever right is. And everything but
true is what the facts of an else say, null and the values that are not booleans included, so the
narrowed right holds for every case of left that it can matter for, and the result is left or right
for each of them.
iex> Feel.Analysis.Inference.disjunction_after([{:boolean, true}], [:null])
[{:boolean, true}]
iex> Feel.Analysis.Inference.disjunction_after([:boolean], [{:boolean, false}])
[:boolean]
iex> Feel.Analysis.Inference.disjunction_after([{:boolean, false}], [:boolean])
[:boolean]
iex> Feel.Analysis.Inference.disjunction_after([:boolean], [:boolean])
[:boolean]
iex> Feel.Analysis.Inference.disjunction_after([{:boolean, false}, :null], [{:boolean, false}])
[{:boolean, false}, :null]
@spec instance_of(Feel.Type.Union.t(), Feel.Type.t()) :: Feel.Type.Union.t()
Returns the type of value instance of type (Table 62), given the type of the value and the
type, resolved.
It is a boolean and never null, whatever the value is: null instance of number is false,
where null < 1 is null. It is true for sure when the value is never null and the union is
within the type, and false for sure when the value is null and the type is not Null, or
the type is Null and the value is never null, or when what the value can be, null aside, has no value
in common with the type (Feel.Type.Union.intersect/2, D-194): a number is not a string. A list<string>
is still boolean against list<number>, for the empty list is both. Anything else is any boolean.
iex> Feel.Analysis.Inference.instance_of([:number, :null], :string)
[{:boolean, false}]
iex> Feel.Analysis.Inference.instance_of([{:list, [:string]}], {:list, :number})
[:boolean]
iex> Feel.Analysis.Inference.instance_of([{:list, [:string]}], :number)
[{:boolean, false}]
iex> Feel.Analysis.Inference.instance_of([:number], :number)
[{:boolean, true}]
iex> Feel.Analysis.Inference.instance_of([:number, :null], :number)
[:boolean]
iex> Feel.Analysis.Inference.instance_of([:null], :number)
[{:boolean, false}]
iex> Feel.Analysis.Inference.instance_of([:null], :null)
[{:boolean, true}]
iex> Feel.Analysis.Inference.instance_of([:number], :null)
[{:boolean, false}]
iex> Feel.Analysis.Inference.instance_of([:number], :any)
[{:boolean, true}]
iex> Feel.Analysis.Inference.instance_of([:null, :any], :any)
[:boolean]
@spec list_contains(Feel.Type.Union.t(), boolean()) :: Feel.Type.Union.t()
Returns the type of a test of in that is an expression, x in [1, 2, 3] (D-61), given the type of the expression and
whether it reads the input ?.
A list is list contains: the input is in it when it equals an element, or lies within an element that is a range,
and an element that it cannot be compared with is not equal to it. That is true or false for every input, null
included (Feel.Runtime.UnaryTests.member/3). A null is the value null, which the input equals or does not; but a
test that reads ? and is null is the answer itself, so it is null. Anything else is equality with the input, or a
range's containment, which are null for an input they cannot be compared with. The type is the exact one of the node
(Feel.IR.type/1), so that the empty list, which a plain reader takes for a list of null, is told from the rest: nothing
is in it, so the answer is false for every input.
iex> Feel.Analysis.Inference.list_contains([{:list, []}], false)
[{:boolean, false}]
iex> Feel.Analysis.Inference.list_contains([{:list, []}, :null], false)
[:boolean]
iex> Feel.Analysis.Inference.list_contains([{:list, [:number]}], false)
[:boolean]
iex> Feel.Analysis.Inference.list_contains([{:list, [:number]}, :null], false)
[:boolean]
iex> Feel.Analysis.Inference.list_contains([{:list, [:number]}, :null], true)
[:boolean, :null]
iex> Feel.Analysis.Inference.list_contains([{:list, [:number]}, :number], false)
[:boolean, :null]
iex> Feel.Analysis.Inference.list_contains([:any, :null], false)
[:boolean, :null]
@spec logic(:and | :or, Feel.Type.Union.t(), Feel.Type.Union.t()) :: result()
Returns the type of left and right or left or right.
iex> Feel.Analysis.Inference.logic(:and, [:boolean], [:boolean])
{:ok, [:boolean]}
iex> Feel.Analysis.Inference.logic(:or, [:boolean, :null], [:boolean])
{:ok, [:boolean, :null]}
iex> Feel.Analysis.Inference.logic(:and, [:null], [:null])
{:ok, [:null]}
iex> Feel.Analysis.Inference.logic(:and, [:number], [:string])
{:ok, [:null]}
iex> Feel.Analysis.Inference.logic(:and, [:boolean], [:null])
{:ok, [{:boolean, false}, :null]}
iex> Feel.Analysis.Inference.logic(:or, [:boolean], [:number])
{:ok, [{:boolean, true}, :null]}
iex> Feel.Analysis.Inference.logic(:and, [{:boolean, true}], [:null])
{:ok, [:null]}
iex> Feel.Analysis.Inference.logic(:or, [:any], [:null])
{:ok, [{:boolean, true}, :null]}
iex> Feel.Analysis.Inference.logic(:and, [:any], [{:boolean, false}])
{:ok, [{:boolean, false}]}
iex> Feel.Analysis.Inference.logic(:and, [:any], [:boolean])
{:ok, [:boolean, :null]}
@spec membership([Feel.Type.Union.t(), ...]) :: Feel.Type.Union.t()
Returns the type of subject in tests (D-61), given the types of the tests.
The value is the three-valued OR of the tests, taken alternative by alternative (D-194): it can be true
when a test can be, false when every test can be false, and null when no test is true and a test is
not false either. So a test that is false or null makes the whole false or null, never true,
and Null is among the alternatives only when a test can be null.
iex> Feel.Analysis.Inference.membership([[{:boolean, false}, :null]])
[{:boolean, false}, :null]
iex> Feel.Analysis.Inference.membership([[:boolean], [:null]])
[{:boolean, true}, :null]
iex> Feel.Analysis.Inference.membership([[{:boolean, true}], [:boolean, :null]])
[{:boolean, true}]
iex> Feel.Analysis.Inference.membership([[{:boolean, false}], [{:boolean, false}]])
[{:boolean, false}]
iex> Feel.Analysis.Inference.membership([[:boolean], [{:boolean, false}]])
[:boolean]
iex> Feel.Analysis.Inference.membership([[:boolean], [:boolean, :null]])
[:boolean, :null]
iex> Feel.Analysis.Inference.membership([[:null], [:null]])
[:null]
@spec negate(Feel.Type.Union.t()) :: result()
Returns the type of -operand. An operand of an unknown type (Any, a named type) is a number, a years and months
duration, a days and time duration, or null (D-207): the kinds that a negation has.
iex> Feel.Analysis.Inference.negate([:number])
{:ok, [:number]}
iex> Feel.Analysis.Inference.negate([:any])
{:ok, [:number, :years_and_months_duration, :days_and_time_duration, :null]}
iex> Feel.Analysis.Inference.negate([:number, :null])
{:ok, [:number, :null]}
iex> Feel.Analysis.Inference.negate([:string])
{:mismatch, [:null]}
@spec null_bounds( operator(), Feel.Type.Union.t(), Feel.Type.Union.t(), grains(), boolean() ) :: :near_zero | :too_large | :range | nil
Which bounds on the operands of left <operator> right would rule null out (D-161), when neither
operand can be null: :near_zero for operands at least 0.001 away from zero, :too_large for
operands of at most 1E+30, :range where the result needs both, and nil where it is not null,
where it is certain (two single numbers, which is a warning of its own, D-161), or where these bounds
do not rule it out. It is what lets the analyzer say, in an info, that tighter allowed values would
rule null out, and the numbers say so: the operands are restricted as the hint says, and the
result has no null left.
iex> alias Feel.Type.NumberSet
iex> to_150 = [{:number, NumberSet.interval(Decimal.new(0), Decimal.new(150))}]
iex> to_max = [{:number, NumberSet.interval(Decimal.new(0), NumberSet.max_value())}]
iex> two = [{:number, NumberSet.point(Decimal.new(2))}]
iex> fraction = [{:number, NumberSet.point(Decimal.new("1.19"))}]
iex> Feel.Analysis.Inference.null_bounds(:divide, to_150, two)
:near_zero
iex> Feel.Analysis.Inference.null_bounds(:multiply, to_max, two)
:too_large
iex> Feel.Analysis.Inference.null_bounds(:multiply, to_max, fraction)
:range
iex> Feel.Analysis.Inference.null_bounds(:multiply, to_150, two)
nil
iex> Feel.Analysis.Inference.null_bounds(:divide, to_150 ++ [:null], two)
nil
@spec range(Feel.Type.Union.t(), Feel.Type.Union.t()) :: {:ok, Feel.Type.Union.t()} | {:mismatch, Feel.Type.Union.t()}
Returns the type of the range literal [from..to] (D-61), given the types of its endpoints.
A range is made of two endpoints of one kind that has an order, and a null endpoint is no
endpoint, so the range is range<T> for the union T of what the endpoints can be, and never null,
when every pair of kinds that they can be makes one. It is {:mismatch, [:null]} when no pair does
([1.."a"]), and it has Null among its alternatives when only some do. A kind that is not known
(Any) can be any.
iex> Feel.Analysis.Inference.range([:number], [:number])
{:ok, [{:range, [:number]}]}
iex> Feel.Analysis.Inference.range([:number], [:null])
{:ok, [{:range, [:number]}]}
iex> Feel.Analysis.Inference.range([:number], [:string])
{:mismatch, [:null]}
iex> Feel.Analysis.Inference.range([:number, :string], [:number])
{:ok, [{:range, [:number, :string]}, :null]}
iex> Feel.Analysis.Inference.range([:any], [:number])
{:ok, [{:range, [:any]}, :null]}
@spec reachable(Feel.Type.Union.t()) :: {boolean(), boolean()}
Returns which branches of if condition then … else … a run can take, as
{then?, else?}.
The then branch is taken when the condition is true, so only if its type has an
alternative that can be true. The else branch is taken for any other value, so unless
the condition is true for sure. One of them is always taken.
iex> Feel.Analysis.Inference.reachable([:boolean, :null])
{true, true}
iex> Feel.Analysis.Inference.reachable([{:boolean, true}])
{true, false}
iex> Feel.Analysis.Inference.reachable([{:boolean, false}])
{false, true}
iex> Feel.Analysis.Inference.reachable([:number, :null])
{false, true}
@spec shapes_equal(:eq | :neq, shape(), shape()) :: Feel.Type.Union.t()
Returns the type of left = right and left != right for two nodes whose structure is known (D-193, D-193, D-161).
The structure is what the type does not have: a FEEL list type has no length, so a list in the entry of a context or in a
list is list<number>. Here it is followed to every depth:
- lists of other lengths are unequal for sure, whatever the elements (
Feel.Runtime.Compare), and the result is nevernull; - lists of one length, and contexts with the same keys, are equal by the conjunction of the equality of what they hold, each
pair of elements and each entry by the same rule (so
{a: [x]} = {a: [x, y]}isfalse); - contexts that cannot have the same keys are unequal for sure;
- anything else is by the types, which is what
compare/3makes of them.
The conjunction is that of the run (all_equal/1): the result can be false if a pair can, true if all can, and null
if a pair can and no pair is false for sure.
iex> leaf = fn type -> %{type: type, structure: :none} end
iex> list = fn items -> %{type: [{:list, [:number, :null]}], structure: {:list, items}} end
iex> Feel.Analysis.Inference.shapes_equal(:eq, list.([leaf.([:number])]), list.([leaf.([:number]), leaf.([:number])]))
[{:boolean, false}]
iex> Feel.Analysis.Inference.shapes_equal(:eq, list.([leaf.([:number])]), list.([leaf.([:number])]))
[:boolean]
iex> Feel.Analysis.Inference.shapes_equal(:eq, list.([leaf.([:number])]), list.([leaf.([:string])]))
[:null]
iex> Feel.Analysis.Inference.shapes_equal(:neq, list.([]), list.([]))
[{:boolean, false}]
iex> inner = fn count -> list.(List.duplicate(leaf.([:number]), count)) end
iex> Feel.Analysis.Inference.shapes_equal(:eq, list.([inner.(1)]), list.([inner.(2)]))
[{:boolean, false}]