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 a null operand is null, and null is not true;
  • = is true for two nulls, and false for a null and a value;
  • != is true for a null and a value (null != 5 is true) and false for two nulls (Table 50, D-04);
  • an operation with a null operand is null.

When Null stays

The conditionNull stays in then whenNull stays in else
an orderingneveralways
x = otherother can be null, or its type is not knownunless other is only null
x != otherother is not only null: it has a kind, or is not knownwhen 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).

Conditionx when truex when not true
x = nullNullnumber
x = 5number = 5number in [-max..5), (5..max] | Null
x = ynumber | Nullnumber | Null
x = vnumber | Nullnumber | Null
x != nullnumberNull
x != 5number in [-max..5), (5..max] | Nullnumber = 5
x != ynumber | Nullnumber | Null
x != vnumber | Nullnumber | Null
x > null—number | Null
x > 5number in (5..max]number in [-max..5] | Null
x > ynumber in (-max..max]number | Null
x > vnumbernumber | Null
  • x = null is the guard. When it is not true, x is not null.
  • x = 5: when true, x is 5; when not true, x can still be null, because null = 5 is false.
  • x != 5: true for null, so Null stays in then; when not true, x is 5.
  • x = y and x != y learn nothing: y can be null, and then both are decided by two nulls.
  • x > null is null for every x, so the then branch cannot be taken.
  • x > y: true means neither is null, and x is above the lowest possible y.
  • x > v: true means x is not null and v is 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.

Conditionx when truex when not true
null = xNullnumber
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.

ExpressionTypeWarnings
true < vNulltype_mismatch
v >= {age: 20}Nulltype_mismatch
[1, 2] <= vNulltype_mismatch
(function(a: number) a) < vNulltype_mismatch
v < nullNull—
x < vboolean | Null—
v < vboolean | Null—
Conditionv when truev 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.

ExpressionTypeWarnings
true + vNulltype_mismatch
v + bNulltype_mismatch
{a: 1} * vNulltype_mismatch
[1] - vNulltype_mismatch
v / bNulltype_mismatch
s - vNulltype_mismatch
d * vNulltype_mismatch
v ** ddNulltype_mismatch
s + vstring | Null—
d + vdate | Null—
x + vnumber | Null—
x * vnumber | years and months duration | days and time duration | Null—
ym + vdate | date and time | years and months duration | Null—
dd / vnumber | days and time duration | Null—
v + vnumber | string | years and months duration | days and time duration | Null—
v - vnumber = 0 | years and months duration = P0M | days and time duration = PT0S | Null—
v * vnumber in [0..max] | Null—
v / vnumber = 1 | Null—
v ** vnumber | Null—
-vnumber | years and months duration | days and time duration | Null—
-(-v)number | years and months duration | days and time duration | Null—
(v - v) + 1number = 1 | Null—
(v / v) * 2number = 2 | Null—
v + nullNull—
Conditionv when truev 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 > 1number in [-max..-1)Any | Null
-v = 5number = -5Any | Null
v - v = 0numberAny | Null
v + v = "aa"stringAny | Null
v * v = 4numberAny | Null
v / v = 1number | years and months duration | days and time durationAny | 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).

Conditionx when truex when not true
x instance of NullNullnumber
x instance of AnynumberNull
x instance of numbernumberNull
Conditionv when truev when not true
v instance of NullNullAny
v != nullAnyNull
v = nullNullAny

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:

Conditionx when truex when not true
x in (null, 5)number = 5 | Nullnumber in [-max..5), (5..max]
x in (null)Nullnumber
Conditions when trues when not true
s = null or s = "a"string = "a" | Nullstring 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.

ExpressionTypeWarnings
knumber—
k = nullboolean = false—
null instance of number!boolean = false—
if k > 5 then k else 0number in 0, (5..max]—

The guard in use

ExpressionTypeWarnings
if x = null then 0 else x + 1number—
if null = x then 0 else xnumber—
x != null and x > 5boolean—
x = null or x > 5boolean—
if x > null then x else 0number = 0decided_condition
  • x + 1 is a number, never null, once x is known not to be null: it does not overflow, since the run rounds max + 1 to max (D-39).
  • x != null and x > 5 is boolean, never null: the right operand is read knowing the left is true, so x is not null there. The same for x = null or x > 5, read knowing the left is not true.
  • x > null is null, never true, so the then branch cannot be taken and the warning says so.

See also: number, Any, boolean.