Handle missing curlies after match_first without crashing in parse (#7480)

This commit is contained in:
Dana Jansens
2026-07-11 00:03:31 +00:00
committed by GitHub
parent 783f1601fd
commit bf106c3b4b
5 changed files with 67 additions and 4 deletions
+1
View File
@@ -114,6 +114,7 @@ CARBON_DIAGNOSTIC_KIND(ExpectedIdentifierAfterPeriodOrArrow)
CARBON_DIAGNOSTIC_KIND(ExpectedIdentifierOrSelfAfterPeriod)
CARBON_DIAGNOSTIC_KIND(ExpectedBindingName)
CARBON_DIAGNOSTIC_KIND(ExpectedBindingPattern)
CARBON_DIAGNOSTIC_KIND(ExpectedCurlyBraceAfter)
CARBON_DIAGNOSTIC_KIND(ExpectedGenericBindingPatternAfterTemplate)
CARBON_DIAGNOSTIC_KIND(ExpectedParenAfter)
CARBON_DIAGNOSTIC_KIND(ExpectedRuntimeBindingPatternAfterRef)
+16
View File
@@ -52,6 +52,22 @@ auto Context::ReplacePlaceholderNode(int32_t position, NodeKind kind,
*node_impl = Tree::NodeImpl(kind, has_error, token);
}
auto Context::ConsumeAndAddOpenCurlyBrace(Lex::TokenIndex default_token,
NodeKind start_kind)
-> std::optional<Lex::TokenIndex> {
if (auto open_curly = ConsumeIf(Lex::TokenKind::OpenCurlyBrace)) {
AddLeafNode(start_kind, *open_curly, /*has_error=*/false);
return open_curly;
} else {
CARBON_DIAGNOSTIC(ExpectedCurlyBraceAfter, Error,
"expected `{` after `{0}`", Lex::TokenKind);
emitter_.Emit(*position_, ExpectedCurlyBraceAfter,
tokens().GetKind(default_token));
AddLeafNode(start_kind, default_token, /*has_error=*/true);
return std::nullopt;
}
}
auto Context::ConsumeAndAddOpenParen(Lex::TokenIndex default_token,
NodeKind start_kind)
-> std::optional<Lex::TokenIndex> {
+8
View File
@@ -189,6 +189,14 @@ class Context {
// Consumes the current token. Does not return it.
auto ConsumeAndDiscard() -> void { ++position_; }
// Parses an open curly brace token, possibly diagnosing if necessary. Creates
// a leaf parse node of the specified start kind. The default_token is used
// when there's no open curly brace. Returns the open curly brace token if it
// was found.
auto ConsumeAndAddOpenCurlyBrace(Lex::TokenIndex default_token,
NodeKind start_kind)
-> std::optional<Lex::TokenIndex>;
// Parses an open paren token, possibly diagnosing if necessary. Creates a
// leaf parse node of the specified start kind. The default_token is used when
// there's no open paren. Returns the open paren token if it was found.
+11 -3
View File
@@ -2,6 +2,7 @@
// Exceptions. See /LICENSE for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
#include "toolchain/lex/token_kind.h"
#include "toolchain/parse/context.h"
#include "toolchain/parse/handle.h"
@@ -10,10 +11,17 @@ namespace Carbon::Parse {
auto HandleMatchFirst(Context& context) -> void {
auto state = context.PopState();
// MatchFirstIntroducer node is automatically added for the MatchFirst state.
context.AddNode(NodeKind::MatchFirstDefinitionStart, context.Consume(),
state.has_error);
if (auto open_curly = context.ConsumeAndAddOpenCurlyBrace(
state.token, NodeKind::MatchFirstDefinitionStart)) {
state.token = *open_curly;
} else {
state.has_error = true;
}
context.PushState(state, StateKind::MatchFirstFinish);
context.PushState(StateKind::DeclScopeLoopAsRegular);
if (!state.has_error) {
context.PushState(StateKind::DeclScopeLoopAsRegular);
}
}
auto HandleMatchFirstFinish(Context& context) -> void {
+31 -1
View File
@@ -8,6 +8,20 @@
// TIP: To dump output, run:
// TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/parse/testdata/generics/impl/match_first.carbon
// --- empty.carbon
match_first {}
// --- fail_missing_braces.carbon
// CHECK:STDERR: fail_missing_braces.carbon:[[@LINE+4]]:12: error: expected `{` after `match_first` [ExpectedCurlyBraceAfter]
// CHECK:STDERR: match_first;
// CHECK:STDERR: ^
// CHECK:STDERR:
match_first;
// --- with_impls.carbon
match_first {
impl forall [T:! I] T as Interface {
@@ -20,7 +34,23 @@ impl forall [T:! J] T as Interface {
}
// CHECK:STDOUT: - filename: match_first.carbon
// CHECK:STDOUT: - filename: empty.carbon
// CHECK:STDOUT: parse_tree: [
// CHECK:STDOUT: {kind: 'FileStart', text: ''},
// CHECK:STDOUT: {kind: 'MatchFirstIntroducer', text: 'match_first'},
// CHECK:STDOUT: {kind: 'MatchFirstDefinitionStart', text: '{', subtree_size: 2},
// CHECK:STDOUT: {kind: 'MatchFirst', text: '}', subtree_size: 3},
// CHECK:STDOUT: {kind: 'FileEnd', text: ''},
// CHECK:STDOUT: ]
// CHECK:STDOUT: - filename: fail_missing_braces.carbon
// CHECK:STDOUT: parse_tree: [
// CHECK:STDOUT: {kind: 'FileStart', text: ''},
// CHECK:STDOUT: {kind: 'MatchFirstIntroducer', text: 'match_first'},
// CHECK:STDOUT: {kind: 'MatchFirstDefinitionStart', text: 'match_first', has_error: yes, subtree_size: 2},
// CHECK:STDOUT: {kind: 'MatchFirst', text: ';', has_error: yes, subtree_size: 3},
// CHECK:STDOUT: {kind: 'FileEnd', text: ''},
// CHECK:STDOUT: ]
// CHECK:STDOUT: - filename: with_impls.carbon
// CHECK:STDOUT: parse_tree: [
// CHECK:STDOUT: {kind: 'FileStart', text: ''},
// CHECK:STDOUT: {kind: 'MatchFirstIntroducer', text: 'match_first'},