mirror of
https://github.com/carbon-language/carbon-lang.git
synced 2026-09-24 17:00:25 +01:00
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:
@@ -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;
|
||||
|
||||
+33
-1
@@ -15,6 +15,12 @@ class A {
|
||||
// CHECK:STDERR:
|
||||
base;
|
||||
|
||||
// CHECK:STDERR: fail_base.carbon:[[@LINE+4]]:8: error: `class` or `:` expected after `base` [ExpectedAfterBase]
|
||||
// CHECK:STDERR: base calss X {}
|
||||
// CHECK:STDERR: ^~~~~
|
||||
// CHECK:STDERR:
|
||||
base calss X {}
|
||||
|
||||
// CHECK:STDERR: fail_base.carbon:[[@LINE+4]]:9: error: expected expression [ExpectedExpr]
|
||||
// CHECK:STDERR: base: ;
|
||||
// CHECK:STDERR: ^
|
||||
@@ -34,6 +40,16 @@ class A {
|
||||
var n: i32;
|
||||
}
|
||||
|
||||
base class Foo {}
|
||||
|
||||
class B {
|
||||
// CHECK:STDERR: fail_base.carbon:[[@LINE+4]]:15: error: `class` or `:` expected after `base` [ExpectedAfterBase]
|
||||
// CHECK:STDERR: extend base Foo;
|
||||
// CHECK:STDERR: ^~~
|
||||
// CHECK:STDERR:
|
||||
extend base Foo;
|
||||
}
|
||||
|
||||
// CHECK:STDOUT: - filename: fail_base.carbon
|
||||
// CHECK:STDOUT: parse_tree: [
|
||||
// CHECK:STDOUT: {kind: 'FileStart', text: ''},
|
||||
@@ -43,6 +59,8 @@ class A {
|
||||
// CHECK:STDOUT: {kind: 'BaseIntroducer', text: 'base'},
|
||||
// CHECK:STDOUT: {kind: 'BaseDecl', text: ';', has_error: yes, subtree_size: 2},
|
||||
// CHECK:STDOUT: {kind: 'BaseIntroducer', text: 'base'},
|
||||
// CHECK:STDOUT: {kind: 'BaseDecl', text: '}', has_error: yes, subtree_size: 2},
|
||||
// CHECK:STDOUT: {kind: 'BaseIntroducer', text: 'base'},
|
||||
// CHECK:STDOUT: {kind: 'BaseColon', text: ':'},
|
||||
// CHECK:STDOUT: {kind: 'InvalidParse', text: ';', has_error: yes},
|
||||
// CHECK:STDOUT: {kind: 'BaseDecl', text: ';', has_error: yes, subtree_size: 4},
|
||||
@@ -53,6 +71,20 @@ class A {
|
||||
// CHECK:STDOUT: {kind: 'BaseDecl', text: 'var', has_error: yes, subtree_size: 5},
|
||||
// CHECK:STDOUT: {kind: 'InvalidParseStart', text: 'n', has_error: yes},
|
||||
// CHECK:STDOUT: {kind: 'InvalidParseSubtree', text: ';', has_error: yes, subtree_size: 2},
|
||||
// CHECK:STDOUT: {kind: 'ClassDefinition', text: '}', subtree_size: 17},
|
||||
// CHECK:STDOUT: {kind: 'ClassDefinition', text: '}', subtree_size: 19},
|
||||
// CHECK:STDOUT: {kind: 'ClassIntroducer', text: 'class'},
|
||||
// CHECK:STDOUT: {kind: 'BaseModifier', text: 'base'},
|
||||
// CHECK:STDOUT: {kind: 'IdentifierNameNotBeforeSignature', text: 'Foo'},
|
||||
// CHECK:STDOUT: {kind: 'ClassDefinitionStart', text: '{', subtree_size: 4},
|
||||
// CHECK:STDOUT: {kind: 'ClassDefinition', text: '}', subtree_size: 5},
|
||||
// CHECK:STDOUT: {kind: 'ClassIntroducer', text: 'class'},
|
||||
// CHECK:STDOUT: {kind: 'IdentifierNameNotBeforeSignature', text: 'B'},
|
||||
// CHECK:STDOUT: {kind: 'ClassDefinitionStart', text: '{', subtree_size: 3},
|
||||
// CHECK:STDOUT: {kind: 'BaseIntroducer', text: 'base'},
|
||||
// CHECK:STDOUT: {kind: 'ExtendModifier', text: 'extend'},
|
||||
// CHECK:STDOUT: {kind: 'BaseColon', text: 'Foo', has_error: yes},
|
||||
// CHECK:STDOUT: {kind: 'IdentifierNameExpr', text: 'Foo'},
|
||||
// CHECK:STDOUT: {kind: 'BaseDecl', text: ';', has_error: yes, subtree_size: 5},
|
||||
// CHECK:STDOUT: {kind: 'ClassDefinition', text: '}', subtree_size: 9},
|
||||
// CHECK:STDOUT: {kind: 'FileEnd', text: ''},
|
||||
// CHECK:STDOUT: ]
|
||||
|
||||
Reference in New Issue
Block a user