Why can a shorthand that expands to two statements leave only its first statement inside a conditional branch?
answer
- the paste is statements, not a block
- a branch holds one statement
- the tail escapes the branch
- loud with an alternative, silent without
- group the body, or emit a block node
basics
~20 sBecause substitution inserts statements, not a block. In a grammar where a branch takes one statement unless delimiters group several, only the first expanded statement belongs to the branch and the rest becomes ordinary code after the conditional.
solid answer
~50 sThe paste is characters, and the grouping of those characters is decided afterwards by the grammar. If a branch takes a single statement unless a block groups several, then a body expanding to `log(x); hits = hits + 1` contributes only `log(x)` to the branch; the counter update lands after the conditional and runs on every pass. The failure shows up in two shapes. With a trailing `else`, the text usually stops parsing, and the reported position is inside characters the author never typed. Without one, it compiles cleanly and is silently wrong — a count that grows faster than the condition allows. Parentheses do not help here; they group expressions, not statements. The fix at text level is to make the body one grouped statement. A tree rewrite returns a sequence as a single block node, so the branch takes all of it.
code
pseudocode · 9 linesdefine RECORD_HIT(x) as log(x); hits = hits + 1
// source as written
if (overBudget) RECORD_HIT(item); else ignore(item);
// after substitution, before grouping is decided
if (overBudget) log(item); hits = hits + 1; else ignore(item);
// branch = log(item) only; hits climbs on every pass, and the
// alternative branch now has no conditional to attach togo deeper
Recall that a paste contributes statements one after another, and that a conditional branch may take only the first of them unless something groups the rest with it.
Explain both outcomes — a parse failure when an alternative branch follows, and silent unconditional execution when none does — and give the block-wrapping fix.
Diagnose the quiet version: name the behavioural symptom you would look for, and say why deleting the alternative branch to clear a build error makes the defect worse.
Decide whether a codebase permits multi-statement text-level bodies at all, and what the reviewers must be able to check without expanding each call by hand.
## Substitution inserts statements, not a block A text-level shorthand contributes characters. It has no way to say "and these belong together", because saying that is a grammatical act and the grammar has not run yet. When the body holds two statements, what arrives at the call site is two statements standing side by side in the middle of somebody else's code. What happens next is entirely up to the surrounding construct. A conditional branch that accepts exactly one statement takes the first one and stops. Everything after it is no longer part of the branch; it is the next statement in the enclosing block, and it runs unconditionally. ## The two shapes of the failure 1. **There is a trailing alternative branch.** The conditional is already complete after the first expanded statement, so the alternative has nothing left to attach to and the text stops parsing. The message is usually accurate and almost useless: it points at a position inside the expansion, which is code nobody wrote and nobody can search for. 2. **There is no alternative branch.** Nothing is ill formed, so nothing is reported. The program compiles, the first statement is guarded and the tail is not. The only symptom is behavioural: an effect that happens more often than the condition should allow — a counter climbing on paths that were supposed to skip it. The second shape is the dangerous one, and it is the one a review misses, because the call site reads exactly like the intent. ``` define RECORD_HIT(x) as log(x); hits = hits + 1 // source as written if (overBudget) RECORD_HIT(item); else ignore(item); // what the parser sees if (overBudget) log(item); hits = hits + 1; else ignore(item); // the branch ended at log(item); the alternative has no conditional left ``` ## Grammars group branches differently, so the symptom differs Languages do not agree on how a branch is delimited, and the same paste produces different damage depending on the rule: - Where a branch is **one statement unless explicit delimiters group several**, the tail simply escapes the branch, as traced above. - Where a branch is grouped by **layout**, the pasted tail carries whatever indentation the substitution produced, which is frequently not the indentation of the branch — so it either escapes in the same way or fails as an inconsistent block. - Where a branch must **always** be a delimited block, the hazard largely disappears for this construct, because there is no single-statement form for the tail to fall out of. The lesson is not about any one of those rules. It is that the shorthand's author cannot know which rule will apply at a call site they have never seen, because the shorthand does not describe structure at all. ## Fixes, and what each is worth - **Wrap the body in a block.** Now the body is one statement that happens to contain a sequence, and the branch takes it whole. Cheap and effective; it depends on the toolchain's rules for what may follow such a block at a call site. - **Make the body a single statement by construction.** Some codebases keep a house rule that a text-level shorthand is either one expression or one statement, never a sequence — enforced by review rather than by the toolchain. - **Do not reach for parentheses.** They group an expression. They cannot turn a sequence of statements into a statement, and a candidate who offers them here has not separated the two hazards. - **Move to a tree rewrite.** A transformer returns a node; a sequence is returned as a single block node. The branch takes the node it was given, so the grouping is decided by the structure the transformer built rather than by where delimiters happened to land in re-parsed text. ## How to recognise it in the wild | Signal | What it points at | |---|---| | A parse error at a position with no matching source text | a multi-statement paste that ended a construct early | | An effect that fires on paths the condition excludes | the tail of an expansion sitting outside the branch | | The same shorthand behaving differently at two call sites | one call site is inside a delimited block, the other is not | | Removing an alternative branch "fixes" the build | the error was the alternative losing its conditional, and the wrongness is now silent | That last row is worth dwelling on: making the message go away by deleting the alternative branch converts a loud failure into a quiet one. The question to ask of any expansion is not "does it compile" but "how many statements did it contribute, and which construct owns each of them".
- What does the same two-statement body do at a call site with no alternative branch?It compiles. The first statement stays inside the branch and the rest becomes an ordinary statement after the conditional, so the tail runs on every pass. Nothing is reported, and the only symptom is an effect or a count larger than the condition permits — which is why this shape survives review.
- Why is a transformer that rewrites a parsed tree not exposed to this?It returns a node, and a sequence of statements is returned as one block node. The caller's branch takes that node whole, so grouping is fixed by the structure the transformer built rather than by re-parsing characters whose delimiters may land anywhere.
saying these in an interview costs you the question
- Thinks the expanded statements stay together because they came from one shorthand
- Expects the toolchain to treat the whole expansion as a single unit
- Offers parentheses around the body as the fix for a statement sequence
- Says the problem cannot compile, so it is always caught at build time
- Assumes every grammar groups a branch the same way