Narrowing › null. The names in the examples are those of section 2.
In the specification's lattice Null is below every type, so number means "a number or null". In the analyzer's union (Feel.Type.Union) Null is an alternative of its own, present only when the value may be null (type-system.md, 3.2). A fact therefore has two jobs for every subject: to cut the alternatives of its kind, which the pages of the kinds describe, and to keep or remove Null. This page covers the second.
null determines how the two branches differ (section 4):
- an ordering (
< <= > >=) with anulloperand isnull, andnullis nottrue; =istruefor twonulls, andfalsefor anulland a value;!=istruefor anulland a value (null != 5istrue) andfalsefor twonulls (Table 50, D-04);- an operation with a
nulloperand isnull.
When Null stays
| The condition | Null stays in then when | Null stays in else |
|---|---|---|
| an ordering | never | always |
x = other | other can be null, or its type is not known | unless other is only null |
x != other | other is not only null: it has a kind, or is not known | when other can be null, or is not known |
And else can use the values for which the comparison is false only when the comparison cannot be null, that is, when the other side is of one kind and never null. That is the only difference between the rows below.
Every operator against every other side
The four sides: null alone, a constant of the same kind (never null), a name of the same kind that can be null (y), and a name of type Any (v).
| Condition | x when true | x when not true |
|---|---|---|
x = null | Null | number |
x = 5 | number = 5 | number in [-max..5), (5..max] | Null |
x = y | number | Null | number | Null |
x = v | number | Null | number | Null |
x != null | number | Null |
x != 5 | number in [-max..5), (5..max] | Null | number = 5 |
x != y | number | Null | number | Null |
x != v | number | Null | number | Null |
x > null | — | number | Null |
x > 5 | number in (5..max] | number in [-max..5] | Null |
x > y | number in (-max..max] | number | Null |
x > v | number | number | Null |
x = nullis the guard. When it is not true,xis notnull.x = 5: when true,xis5; when not true,xcan still benull, becausenull = 5isfalse.x != 5: true fornull, soNullstays inthen; when not true,xis5.x = yandx != ylearn nothing:ycan benull, and then both are decided by twonulls.x > nullisnullfor everyx, so thethenbranch cannot be taken.x > y: true means neither isnull, andxis above the lowest possibley.x > v: true meansxis notnullandvis a number;x's set learns nothing.
A condition with null on the left is the same: null = x is x = null, and null < x is x > null.
| Condition | x when true | x when not true |
|---|---|---|
null = x | Null | number |
null < x | — | number | Null |
An operand that has no order, against Any
A boolean, a list, a context, a range and a function have no order (Table 55 lists none of them), so an ordering with one of them is null whatever the other operand is: at run time the result is null for a value of the same kind, for a value of another kind (a type mismatch) and for a null, and the type of x > v above is open only because a number has an order. The type says so for a name of type Any as for a known kind (D-193, D-196), and for <= and >= as for < and >: true < v and true <= v are Null with a type_mismatch warning for every v, and a condition made of them is never true, so the then branch cannot be taken. A union is typed pair by pair: it is Null only when every pair of its alternatives is, so Any minus all the kinds that have an order is Null against Any, and Any minus just one of those kinds is not.
| Expression | Type | Warnings |
|---|---|---|
true < v | Null | type_mismatch |
v >= {age: 20} | Null | type_mismatch |
[1, 2] <= v | Null | type_mismatch |
(function(a: number) a) < v | Null | type_mismatch |
v < null | Null | — |
x < v | boolean | Null | — |
v < v | boolean | Null | — |
| Condition | v when true | v when not true |
|---|---|---|
true < v | — | Any | Null |
v > {age: 20} | — | Any | Null |
v <= true | — | Any | Null |
v >= [] | — | Any | Null |
<= and >= follow the ordered-kind domain table, like < and > (D-196). The specification's alternative formula a < b or a = b would make true <= true true; the supported interpretation instead gives null. The runtime and inference use that same restriction, including against Any.
An operand that has no operation, against Any
The same holds for + - * / **: a boolean, a list, a context, a range and a function have no arithmetic (Tables 57 to 61 and 63 list none of them), a string has + and no other operation, a date, a time and a date and time have + and -, a duration has all but **, and only a number has **. An operation with an operand of a kind that its operator does not support is null whatever the other operand is; at run time this holds for a value of any kind (a type mismatch) and for a null, so the type says so for a name of type Any as for a known kind (D-203): true + v is Null with a type_mismatch warning for every v, and a condition that contains it is never true. "a" + v, d + v and x + v are not Null, since for each of them there is a v that gives a value, and their types are the kinds that the pairs give, plus Null (D-203): the run adds a string to a string only, so s + v is string | Null; d + v is date | Null (a date and a duration); x + v is number | Null; x * v is a number, or a duration that a number multiplies, or null; and v + w is one of the seven kinds that have a +, or null. A negation has only one operand, so there is no other operand that could make it Null, and at run time only a number or a duration can be negated (D-207): -v is number | years and months duration | days and time duration | Null, as it is for a v that is a number, a years and months duration, a days and time duration or null (-b and -s are Null, as always). One v read twice is not two values (D-170, D-207): for a value combined with itself, the run gives a number, a string or a duration for v + v (two dates are not added), 0 of a number, a years and months duration or a days and time duration for v - v (a date, a time and a date and time minus themselves are a days and time duration of 0), a number for v * v, v / v and v ** v (a duration times itself is not defined, and a duration over itself is a number), and null for a v that is null; that is, the kinds of v + w (a second name of type Any) without the pairs of two different kinds (a date and a duration). A union is typed pair by pair, so it is Null only when every pair of its alternatives is.
| Expression | Type | Warnings |
|---|---|---|
true + v | Null | type_mismatch |
v + b | Null | type_mismatch |
{a: 1} * v | Null | type_mismatch |
[1] - v | Null | type_mismatch |
v / b | Null | type_mismatch |
s - v | Null | type_mismatch |
d * v | Null | type_mismatch |
v ** dd | Null | type_mismatch |
s + v | string | Null | — |
d + v | date | Null | — |
x + v | number | Null | — |
x * v | number | years and months duration | days and time duration | Null | — |
ym + v | date | date and time | years and months duration | Null | — |
dd / v | number | days and time duration | Null | — |
v + v | number | string | years and months duration | days and time duration | Null | — |
v - v | number = 0 | years and months duration = P0M | days and time duration = PT0S | Null | — |
v * v | number in [0..max] | Null | — |
v / v | number = 1 | Null | — |
v ** v | number | Null | — |
-v | number | years and months duration | days and time duration | Null | — |
-(-v) | number | years and months duration | days and time duration | Null | — |
(v - v) + 1 | number = 1 | Null | — |
(v / v) * 2 | number = 2 | Null | — |
v + null | Null | — |
| Condition | v when true | v when not true |
|---|---|---|
true + v > 1 | — | Any | Null |
s - v > 1 | — | Any | Null |
s + v > 1 | — | Any | Null |
d + v = 5 | — | Any | Null |
-v = "a" | — | Any | Null |
-v > 1 | number in [-max..-1) | Any | Null |
-v = 5 | number = -5 | Any | Null |
v - v = 0 | number | Any | Null |
v + v = "aa" | string | Any | Null |
v * v = 4 | number | Any | Null |
v / v = 1 | number | years and months duration | days and time duration | Any | Null |
A condition that contains an operation that is null for every v has the type Null, so it is never true and the then branch cannot be taken (if true + v > 1 then 1 else 2 is number = 2 with a decided_condition). The same holds for s + v > 1: s + v is string | Null (D-203), which is never above a number, so the comparison is a mismatch and Null; it used to be Any | Null, with the comparison typed boolean | Null, where the run gives null.
A negation and one value read twice work the same way: the kinds of -v, v - v, v + v and the rest are the kinds that the run gives, so a comparison with a value of another kind is never true (-v = "a" leaves no v), and the kinds of v for which the comparison can be true are those in the rows above (v / v = 1 can be true for a number and for a duration, since a duration divided by itself gives 1, but not for a date).
instance of
null is an instance of Null only, so x instance of T is never true for a null unless T is Null, and is false for it, never null itself (D-23, D-194).
| Condition | x when true | x when not true |
|---|---|---|
x instance of Null | Null | number |
x instance of Any | number | Null |
x instance of number | number | Null |
| Condition | v when true | v when not true |
|---|---|---|
v instance of Null | Null | Any |
v != null | Any | Null |
v = null | Null | Any |
Any inside a union means any value that is not null; the specification's Any is Any | Null. That is why v = null leaves non-null Any where it is not true. Other comparisons and type tests can also keep more specific exclusions, such as Any except number = 5 | Null (Any).
Tests with null in them
In an in, a null among the tests is the comparison with null. Joined in then, met in else:
| Condition | x when true | x when not true |
|---|---|---|
x in (null, 5) | number = 5 | Null | number in [-max..5), (5..max] |
x in (null) | Null | number |
| Condition | s when true | s when not true |
|---|---|---|
s = null or s = "a" | string = "a" | Null | string except "a" |
x in (null, 5) is true for null and for 5; when it is not true, x is neither, which removes Null from the other branch as well.
A declared non-null name (T!)
Under :enforce or :enforce_wide, a name declared number! (D-48) has no Null to keep or remove: a bound null ends the run with an input error. Other stances keep null in the inferred input type. The expression type grammar also accepts this extension in parameters and instance of targets; this is not the same as marking the overall result type, which is not supported.
| Expression | Type | Warnings |
|---|---|---|
k | number | — |
k = null | boolean = false | — |
null instance of number! | boolean = false | — |
if k > 5 then k else 0 | number in 0, (5..max] | — |
The guard in use
| Expression | Type | Warnings |
|---|---|---|
if x = null then 0 else x + 1 | number | — |
if null = x then 0 else x | number | — |
x != null and x > 5 | boolean | — |
x = null or x > 5 | boolean | — |
if x > null then x else 0 | number = 0 | decided_condition |
x + 1is a number, nevernull, oncexis known not to benull: it does not overflow, since the run roundsmax + 1tomax(D-39).x != null and x > 5isboolean, nevernull: the right operand is read knowing the left is true, soxis notnullthere. The same forx = null or x > 5, read knowing the left is not true.x > nullisnull, nevertrue, so thethenbranch cannot be taken and the warning says so.