Chapter 9
Type Checking
Every rule has a type, and the checker verifies, when a grammar is compiled, that the values flowing through the grammar are of the types they are used as. This chapter defines the type of each Rule Expression, how the type of a rule without an explicit Type Expression is inferred, and the errors that the checker reports. The types themselves and the relation "assignable" are defined in the Type System.
Rule Expression Types
| Rule Expression | Type |
|---|---|
Literal, character class, ., a #atomic, _ |
node[string] |
a b |
node[(A, B)], a tuple node of the types of the elements; the elements of a group that is not captured are elements of the enclosing sequence |
a / b |
A | B, the join of the types of a and b |
a*, a+, a #repeat(...) |
node[[]A], a list node of the type of the element |
a? |
*A, where A is the type of a |
&a, !a |
node[()], a node of the empty tuple |
| Rule call | The declared type of the called rule, or its inferred type |
A node type also records the captures made in the node, which
x.$y reads; diagnostics show them after the
value, as in node[(node[string], num), {"k": 1}].
Types of Captures
The type of a capture name:a is the type of a. If the capture is made only
in some alternatives of a choice, or inside an optional expression, it may not
match, and its type is *T instead. A name captured more than once in a match
has the join of the types of its captures, since the last capture that matches
gives its value; when at least one of those captures is always made, the join is
taken without nil, and otherwise each capture that may not be made contributes nil,
so the type is optional, *T.
rule r = x:"a"? y:("b" / z:"c") // $x: *node[string], $y: node[string], $z: *node[string]
rule s = k:num "=" k:("x" "y") // $k: num | node[(node[string], node[string])]
Action Expression Types
Action Expressions in Value Actions and Predicate Actions have the following types.
| Action Expression | Type |
|---|---|
Integer literal, len(x), x.Start, x.End |
int |
x.Value |
T for a node of type node[T] or of a named node type over it: string for a string node, (A, B) for a tuple node and []A for a list node |
String literal, text(x) |
string |
true, false, a comparison, &&, ||, ! |
bool |
nil |
nil, assignable to any optional type |
$label |
The type of the capture (see above) |
T{Field: value, ...} |
Struct Type T |
[]T{a, b, ...} |
[]T; every element must have a known Type assignable to T |
x.Name |
The type of the field Name; optional (*T) if x has a join type and only some members declare the field |
x.$y |
The join of the types of capture y of the node types x may have |
x[i] |
The element type of x, which MUST be a list, a tuple, or a node whose value is a list or a tuple; i MUST be an int, and a constant index into a tuple selects that element's type |
x.(T) |
T; the type of x or one of its alternatives MUST be assignable to T or one of its alternatives, or the other way around |
foldl(init, l, f), foldr(init, l, f) |
The type of init, widened to the join of it and the type of f's result |
map(l, f) |
[]T, where T is the type of f's result |
list(a, b, ...) |
[]T, where T is the join of the types of the arguments |
concat(l1, l2, ...) |
[]T, where T is the join of the element types of the arguments |
The list functions treat nil as an empty list, which contributes no element
Type: concat(nil, l) has the Type of concat(l), and filter(nil, f) is
assignable to every list Type.
For []T{...}, the checker validates every element against the explicit element
Type T. It reports non-assignable elements and elements whose Type is unknown,
including unknown values nested in a typed list. An empty literal still has
the declared []T Type. Nil is allowed only if T permits nil.
A struct field value whose Type is unknown (_, or a list with elements of
such a Type) is reported when the field's Type is more specific:
cannot use _ as Ident in field Name of Decl: the type of the value is not known; convert it with a cast, as in value.(Ident). The same holds for a value
of Type _, which fits only _. A cast (see Casts) is the check
that runs; see Values and Constructors. A value
whose Type is known and assignable needs no check, and a constructor checks nothing when
it runs.
Likewise, a rule value whose Type is unknown, from an action, a called rule or
an Operators Rule line, is reported when the rule declares a Type it does not fit:
rule term produces _, which is not assignable to Expr: the type of the value is not known; convert it with a cast, as in value.(Expr) (see Types and
Errors). In an Operators Rule with a declared Type, the captures
lhs and rhs have that Type.
Diagnostics print a type as written in a grammar: node[string] for a string
node, node[(node[string], node[string])] for a tuple node of two string nodes,
(node[string], node[string]) for its value and []node[string] for a list
value. A reference to a rule without a declared type prints the rule's name,
also where a recursive type refers to itself, as in []*expr. Run-time errors
name a value the same way, writing _ for elements they do not examine:
node[(_, _)] for a tuple node of two elements, node[[]_] for a list node
and error for an error value.
A named node type is printed by its name, as Num, and cannot convert X to N names both types.
A cast tests a value as the value types describe it. node[T] takes
a node whose value T takes, so node[string] takes a string node,
node[(A, B)] a tuple node of two elements of types A and B, and
node[[]A] a list node of elements of type A; node[_] takes every node; a
tuple Type takes a tuple value of as many elements; a list Type takes a list
value, such as one an action builds, and not the node of a repetition, whose
list is its Value; []_ and []any take every list value; and an error
value from recovery fits only a Type that admits error. A cast in a predicate that fails
makes the predicate fail.
The argument of len and text MUST be a string or a node (possibly nil);
the list arguments of foldl, foldr, map and concat MUST be lists, and
an error value (from error recovery) counts as an empty list.
A function ((acc, item) => ...) can appear only as an argument of foldl,
foldr or map, and it has the number of parameters those functions require.
Type Inference
The type of a rule without a declared type is inferred from its body, or from
its action if it has one. Dependencies are inferred before their callers, so
finite acyclic chains do not depend on definition order or an iteration cap.
Recursive dependency groups are iterated until their types no longer change;
join-member ordering does not count as a type change. Operators Rule operator actions
also depend on their rule's type through the lhs and rhs values.
The implementation bounds changing recursive types conservatively. After an initial inferred result, further growth beyond 2,048 bytes in the written type or 50 increases in structural nesting depth widens that rule to a type that is not known for the rest of inference. Repeating non-stable type states widen only the rules that vary in the cycle. Stable peers, acyclic rules and stable oversized recursive types retain their types. These are resource limits, not proofs of infinite growth: a finite recursive type can also reach them. Declare recursive result types when precise checking is required.
rule num = [0-9]+ #atomic // node[string]
rule pair = k:num "=" v:num // node[(num, node[string], num), {"k": 0, "v": 2}]
The type of the accumulator of foldl and foldr is widened to the join of
the type of the initial value and the type of the result of the function. The checker widens
three times; if the result of the function still does not fit the accumulator, the call is a
compile-time error (the accumulator type does not converge), and the result of the function
must be cast.
Checked Errors
The type checker reports the following errors:
- an undefined type, a type alias that refers to itself, and a duplicate field;
- a field that the struct type does not declare, in a struct constructor or a field access, and a field value that is not assignable to the type of the field;
- a typed-list element that is unknown or not assignable to its explicit element Type;
- a rule whose value is not assignable to its declared type;
- a constructor
N{...}of a named node type with other than one positional argument, or a positional argument to a struct type; - a value that cannot be converted to a named node type where a conversion is required (
N{x}or a rule's declared type), including_,node[_],error,niland scalars; - a cast to a named node type that the static types rule out (see Casts).
- a field access on a value that may be
nil; - an operand of an operator or an argument of a built-in function with an invalid type;
- a variable that is read or bound without a declaration, a binding whose value is not assignable to the declared type of the variable, and a variable declaration with an unsupported type or an initial value of another type (see Variables).
A variable has the type its var declaration gives it, so a read of a variable
is checked like any other value of that type: x + 1 and V{N: x} are checked
when the grammar is compiled. A binding is checked only statically: it is a compile-time error
unless the Type of the value is known to be assignable to the declared type of the variable,
cannot use <value type> as <type> in the binding of variable <name>. A value whose Type the
checker does not know, such as a rule value of unknown Type, is rejected too, with a hint
to convert it explicitly: x := $d.(int). The cast is an ordinary cast (see
Casts); when it fails, that is an evaluation error of the binding, which
makes the predicate fail. Nothing is checked when a binding runs, so a variable always holds a value of its
declared type.