ADR-301: A Condition Is an Ordinary Expression — the Head of an if, a while and a for Parses the Body’s Language Minus the Forms a { Begins, and Those Take Parentheses

Status: Accepted Date: October 3, 2026 History: the records this one replaces are archived in docs/history/adr; read them only for how the decision was reached Answers: #206’s entry on the connectives missing from a head (if a && b { … } was a parse error), and what else the head refused that the body takes: ??, as, null and a tuple Related: ADR-276 (loops and jumps, whose benchmark found this; D19 measures an alternation’s order as D7 does), ADR-296 D17, ADR-278 D15 (?? is right-associative) and ADR-279 D1 (??’s fallback is one value, in a head as in a body), ADR-285 (as names a type), ADR-278 (null), ADR-132 (else if, which inherits this rule), ADR-135 (a list literal, a primary the head also takes), Part I 3.2 and 3.3

1. Question

What may stand in the head of an if, a while and a for?

The head has a grammar of its own for one reason: in for d in whole { … } the { opens the body, so a head may not contain a form that begins with a brace — a struct literal above all. That chain was built by copying the ordinary one and stopping early, and more stopped than the brace-led forms:

if a && b { … }          expected `{`; found unexpected token `&`
if a ?? b { … }          expected `{`; found unexpected token `?`
if a as i64 > 3 { … }    expected `{`; found unexpected token `as`
if a == null { … }       expected one of: `!`, `-`, `false`, `true`; found `null`
if (a, b) == pair { … }  expected `{`; found unexpected token `,`

Part I 3.2 wrote a condition as a bool with no restriction on what may produce one. A head that takes false and refuses null is not a restriction anybody could deduce.

2. How others do it

Nothing outside this language is compared: the question is one of consistency with its own body grammar, and the comparison that settles it is the one in §1: the same expression in a body and in a head.

3. Options, weighed

Option Lost because See
Add only what a program has run into (&&, ||), leave ?? for a reproduction a reproduction is evidence for adding a construct, not for an inconsistency in where an existing one may stand; waiting only means the next person meets it, and the gap turned out to be four forms, not one D6
Keep a list of what the head takes (&&, || and !) a list of the same kind produced the defect; the page now states the rule D3
Make brace-led forms unavailable in a condition a language restriction needing a justification; the actual constraint is only on the token that opens a body D5
Order head_primary as primary_expr orders it a PEG alternation is ordered, so a rare alternative in front costs every common one behind it (D7) D7

4. Decision

D1 — && and || are in the head, mirroring the ordinary chain exactly.

|| binds looser than &&, which binds looser than a comparison, and a range looser than all of them, as everywhere else: if a || b && c is a || (b && c).

D2 — What a head must keep out is what stands immediately before the body’s {.

An operator that cannot begin a block and does not end an expression — after it comes another operand, which descends to the same brace-free primary — leaves the token before the body’s { drawn from the same set as before. That is the argument for every level the head takes.

D3 — A head parses the same language as a body, minus exactly the forms a { begins.

A head that differs from the ordinary grammar in any other way is a trap rather than a restriction: the program reads as valid Nikaia and is refused for a reason no rule states, or accepted with another meaning. So the head chain is a copy with one difference, in head_primary, and a level missing for any other reason is a defect.

D4 — The head chain holds every level and every primary that is not brace-led. There is no exception.

Changed by ADR-279 D1: a ?? fallback is one value, so a ?? 0 > 3 is refused in a head exactly as in a body; the level itself stays where it is.

Changed by ADR-296 D24: dsl … from … is not a form of the language; its alternative stays in head_primary only to refuse it with the call to write instead.

Top to bottom: ??, range, ||, &&, comparison, +, *, as, unary, postfix, primary — the ordinary chain, level for level, with the same associativity and precedence. head_primary is primary_expr minus a list that can be written out: struct_lit, block_expr, if_expr, match_expr, overlap_expr, a dsl … { … } eod, and spawn, whose lambda is a brace. null, a tuple and dsl … from … are in it.

D5 — A brace-led form is excluded from a spelling, not from the position: parentheses reach it.

if p == (P { x: 1 }) { … }
if (match n { 1 => 10, else => 20 }) > 15 { … }

paren_expr takes a whole expr, so a head may hold anything the language has, and a form that starts with a brace is parenthesised so that the body’s { is still the body’s. The restriction is the token that opens a body is the body’s, with a one-character way round it.

D6 — What belongs in the head is decided by D3’s rule, not by a reproduction.

A reproduction is the right evidence for adding a construct to the language and the wrong evidence for an inconsistency in where an existing construct may stand. The question does this already exist everywhere it should is answered by the rule.

D7 — Where an alternative goes in head_primary is measured.

dsl … from … goes last: at the front it would be a failed keyword match before every primary in every head, and if { } would answer with dsl first in its expectation list. null goes after the literals, not beside false: third in the list it costs 0.45 % of a compile on a head-dense file. Neither word can be taken by another alternative, since both are reserved. A PEG alternation is an ordered list, so where a rare alternative sits is a cost paid by every common one behind it.

D8 — What it costs, and it is accepted.

Every level is descended for every head, whether used or not: 5,016 instructions per head, +0.5 % to +1.2 % of a compile on examples/; as and the primaries are paid per operand, not per head.

What is bought is not a few constructs but a position that can be described in one sentence.

5. Consequences

6. What this does not decide

This site is open source. Improve this page.