Recover base declarations missing a colon (#7341)

Malformed `base` declarations with an omitted colon need two different
recovery paths. For `extend base`, the consumed `extend` modifier
requires the parse tree to retain its `BaseColon` and base expression
children, so this synthesizes an errored `BaseColon` and continues
parsing the expression.

Other malformed forms, such as `base calss X {}`, now use the standard
declaration-error recovery: emit `ExpectedAfterBase`, skip past the
likely declaration end, and form an errored `BaseDecl` without inventing
a colon or cascading diagnostics.

The regression covers `extend base Foo;`, bare `base;`, and the reviewer
counterexample `base calss X {}`.

Tests:
- `prek run --files toolchain/parse/handle_base.cpp
toolchain/parse/testdata/class/fail_base.carbon`
- `./scripts/run_bazelisk.py test -c dbg //toolchain/parse/...`
- `./scripts/run_bazelisk.py test -c dbg //toolchain/testing:file_test`

AI assistance: OpenAI Codex helped inspect the parser recovery path,
implement the change, and run verification. The operator reviewed and
authorized the contribution.

Assisted-by: OpenAI Codex
This commit is contained in:
mstr-six
2026-06-11 22:33:29 +00:00
committed by GitHub
parent 103fa5dadf
commit f3b8e231ca
2 changed files with 45 additions and 7 deletions
+12 -6
View File
@@ -13,15 +13,21 @@ auto HandleBaseAfterIntroducer(Context& context) -> void {
if (!context.ConsumeAndAddLeafNodeIf(Lex::TokenKind::Colon,
NodeKind::BaseColon)) {
// TODO: If the next token isn't a colon or `class`, try to recover
// based on whether we're in a class, whether we have an `extend`
// modifier, and the following tokens.
CARBON_DIAGNOSTIC(ExpectedAfterBase, Error,
"`class` or `:` expected after `base`");
context.emitter().Emit(*context.position(), ExpectedAfterBase);
context.RecoverFromDeclError(state, NodeKind::BaseDecl,
/*skip_past_likely_end=*/true);
return;
auto base_token = *(context.position() - 1);
auto previous_token = Lex::TokenIndex(base_token.index - 1);
if (context.tokens().GetKind(previous_token) != Lex::TokenKind::Extend) {
context.RecoverFromDeclError(state, NodeKind::BaseDecl,
/*skip_past_likely_end=*/true);
return;
}
// Preserve the `extend base` tree shape using an errored placeholder.
context.AddLeafNode(NodeKind::BaseColon, *context.position(),
/*has_error=*/true);
state.has_error = true;
}
state.kind = StateKind::BaseDecl;