← board

Bug: case ... else <stmt1>; <stmt2>; ... end (multi-statement else, no begin/end) fails to parse

Repro (minimal, isolated)

program ReproFFGroup2;
function Foo(x: Integer; var text: AnsiString): Integer;
begin
  case x of
    0: text := 'a';
    1: begin text := 'b'; Result := 1; Exit; end;
  else
    text := 'c'; Result := 4; Exit;
  end;
  Result := 5;
end;
var t: AnsiString;
begin
  writeln(Foo(0, t), ' ', t);
end.

Fails: Expected: end, but got: Result — the parser reads text := 'c'; as the entire else-branch (treating it like a normal label: single-statement; case arm), then expects end immediately and chokes on the second statement (Result := 4;). Standard Pascal (and FPC) treats a case statement's else clause as taking an implicit statement list running up to end (the same convention as a try...except...end's exception-handler list, or a bare begin...end block) — this compiler's parser apparently only reads one statement after else before expecting end.

Fix once wrapped in begin...end:

  else
  begin
    text := 'c'; Result := 4; Exit;
  end;

compiles and runs correctly (Result=5, i.e. control fell through — wait, in the real repro the else branch's Exit fires and returns 4; confirmed via the isolated test in the finding session).

Impact

Silent nothing at first — it's a hard parse error, so any code hitting this shape simply fails to compile, which is at least loud (unlike the sibling open-array stack-copy bug filed alongside this one). The confusing part is diagnostic quality: the reported error line/token give essentially no hint that the real issue is an earlier unwrapped multi-statement else — a future person hitting this will likely burn real time bisecting, as this session did, unless they know to look for this specific pattern.

Suggested fix

case statement parsing (ParseStatementAST's tkCase handler or equivalent, compiler/parser.inc) needs its else-clause parsing to consume a statement list (repeatedly parse statements until it sees end), not a single statement — matching every other implicit-statement-list context in the language (try/except, try/finally, the top-level begin...end of a routine body, etc.). Also worth a better diagnostic: if the parser is at end-or-statement-list ambiguity inside a case, an error mentioning "case else" specifically (not just a generic "expected end") would have saved real debugging time here.

Workaround used

Wrapped the one production instance of this pattern in begin...end (compiler/asmdisasm_x64.inc's FF-opcode-group decoder, the case mrm.RegField and 7 of ... else ... end; block). Audited every other case statement in the same new file for the same shape (grep for bare else lines, checked each one's following statement count) — this was the only instance. Not otherwise blocking; case...else with a single statement (the overwhelmingly common case) is unaffected.

Also worth checking while in there

Fixed (2026-07-01, Track A)

ParseCaseStatementAST (compiler/parser.inc)'s tkElse branch called ParseStatementAST exactly ONCE then expected end — capturing only the first bare statement after else and leaving any following statements un-consumed, which the outer branch-parsing loop then choked on (reported far from the real cause, against the end token or whatever identifier followed). Fixed by replacing the single ParseStatementAST call with a loop that consumes statements until end/EOF, chaining them into an AN_SEQ list — the exact same node-chaining pattern ParseBlockAST already uses for a begin...end body (including its progress watchdog against a statement that consumes zero tokens hanging the loop). Confirmed this AN_SEQ-as-branch-body shape was already valid and already exercised: a case-LABEL arm wrapped in begin...end already produces exactly this same shape via ParseStatementAST's own tkBegin handling — so no IR/codegen changes were needed, only the parser's else-clause statement count.

Verified: the ticket's own repro (label 0 single-stmt, label 1 begin/end multi-stmt, else multi-stmt-no-wrap) all three paths now correct and matches FPC's output exactly. New test/test_case_else_multistmt.pas in make test-core. Full make test green, self-host bootstrap byte-identical (pinned v115, same stabilize pass as the sibling bug-const-array-of-ansistring-literal-too-many-elements fix).

Log