From 49024c8d8346c3bf7e31d28f91cfd09cf6bec2b7 Mon Sep 17 00:00:00 2001 From: Richard Smith Date: Fri, 22 May 2026 16:53:19 -0700 Subject: [PATCH] Improve diagnostics and error recovery for invalid identifiers. (#7249) If a keyword or a sized type literal (eg, `f2`) is used in a context where we are confident that we are expecting an identifier -- either before a `:` in a binding pattern or after a `.` in a member access or designator -- then recover as if a raw identifier was used. This appears to be a particular stumbling block for coding agents, so seems worth paying special attention to. Add a mechanism to the tokenized buffer to track additional tokens synthesized for error recovery so that we can keep the lexed token sequence immutable and still satisfy the invariants throughout the rest of the toolchain for recovery tokens. Thanks to chandlerc for suggesting this approach! --- .../class/self/fail_self_type_member.carbon | 6 ++-- .../testdata/struct/fail_keyword_name.carbon | 25 ++++++++--------- toolchain/lex/token_info.h | 9 ++++++ toolchain/lex/token_kind.h | 12 ++++++++ toolchain/lex/tokenized_buffer.cpp | 26 +++++++++++++++++ toolchain/lex/tokenized_buffer.h | 23 +++++++++++++-- toolchain/parse/context.h | 2 +- toolchain/parse/handle_binding_pattern.cpp | 28 +++++++++++++------ toolchain/parse/handle_pattern.cpp | 20 ++++--------- toolchain/parse/handle_period.cpp | 23 +++++++++------ .../basics/fail_invalid_designators.carbon | 4 +-- .../member_access/fail_keyword.carbon | 24 ++++++++-------- .../testdata/package_expr/fail_in_name.carbon | 4 +-- .../parse/testdata/var/fail_bad_name.carbon | 24 ++++++++++++++++ 14 files changed, 163 insertions(+), 67 deletions(-) diff --git a/toolchain/check/testdata/class/self/fail_self_type_member.carbon b/toolchain/check/testdata/class/self/fail_self_type_member.carbon index b2242bfac09e..da7f2686bc24 100644 --- a/toolchain/check/testdata/class/self/fail_self_type_member.carbon +++ b/toolchain/check/testdata/class/self/fail_self_type_member.carbon @@ -16,13 +16,13 @@ class Class { fn F() -> bool { var c1: Class = {.b = true}; - // CHECK:STDERR: fail_self_type_member.carbon:[[@LINE+8]]:17: error: expected identifier after `.` [ExpectedIdentifierAfterPeriodOrArrow] + // CHECK:STDERR: fail_self_type_member.carbon:[[@LINE+8]]:17: error: expected identifier after `.`; prefix reserved word with `r#` to form a valid identifier [ExpectedIdentifierAfterPeriodOrArrow] // CHECK:STDERR: var c2: Class.Self = c1; // CHECK:STDERR: ^~~~ // CHECK:STDERR: - // CHECK:STDERR: fail_self_type_member.carbon:[[@LINE+4]]:17: error: semantics TODO: `handle invalid parse trees in `check`` [SemanticsTodo] + // CHECK:STDERR: fail_self_type_member.carbon:[[@LINE+4]]:11: error: member name `r#Self` not found in `Class` [MemberNameNotFoundInInstScope] // CHECK:STDERR: var c2: Class.Self = c1; - // CHECK:STDERR: ^~~~ + // CHECK:STDERR: ^~~~~~~~~~ // CHECK:STDERR: var c2: Class.Self = c1; return c2.b; diff --git a/toolchain/check/testdata/struct/fail_keyword_name.carbon b/toolchain/check/testdata/struct/fail_keyword_name.carbon index 741601e9a6f3..185b0bc1f261 100644 --- a/toolchain/check/testdata/struct/fail_keyword_name.carbon +++ b/toolchain/check/testdata/struct/fail_keyword_name.carbon @@ -3,8 +3,6 @@ // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // // INCLUDE-FILE: toolchain/testing/testdata/min_prelude/int.carbon -// TODO: Add ranges and switch to "--dump-sem-ir-ranges=only". -// EXTRA-ARGS: --dump-sem-ir-ranges=if-present // // AUTOUPDATE // TIP: To test this file alone, run: @@ -12,21 +10,20 @@ // TIP: To dump output, run: // TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/struct/fail_keyword_name.carbon -// CHECK:STDERR: fail_keyword_name.carbon:[[@LINE+8]]:13: error: expected identifier after `.` [ExpectedIdentifierAfterPeriodOrArrow] -// CHECK:STDERR: fn F() -> {.class: i32}; -// CHECK:STDERR: ^~~~~ -// CHECK:STDERR: -// CHECK:STDERR: fail_keyword_name.carbon:[[@LINE+4]]:13: error: semantics TODO: `handle invalid parse trees in `check`` [SemanticsTodo] +// CHECK:STDERR: fail_keyword_name.carbon:[[@LINE+4]]:13: error: expected identifier after `.`; prefix reserved word with `r#` to form a valid identifier [ExpectedIdentifierAfterPeriodOrArrow] // CHECK:STDERR: fn F() -> {.class: i32}; // CHECK:STDERR: ^~~~~ // CHECK:STDERR: fn F() -> {.class: i32}; -// CHECK:STDERR: fail_keyword_name.carbon:[[@LINE+4]]:19: error: expected identifier after `.` [ExpectedIdentifierAfterPeriodOrArrow] -// CHECK:STDERR: fn G() { return {.return = 5}; }; -// CHECK:STDERR: ^~~~~~ +// CHECK:STDERR: fail_keyword_name.carbon:[[@LINE+4]]:13: error: expected identifier after `.`; prefix reserved word with `r#` to form a valid identifier [ExpectedIdentifierAfterPeriodOrArrow] +// CHECK:STDERR: fn G() -> {.return: i32} { +// CHECK:STDERR: ^~~~~~ // CHECK:STDERR: -fn G() { return {.return = 5}; }; - -// CHECK:STDOUT: --- fail_keyword_name.carbon -// CHECK:STDOUT: +fn G() -> {.return: i32} { + // CHECK:STDERR: fail_keyword_name.carbon:[[@LINE+4]]:12: error: expected identifier after `.`; prefix reserved word with `r#` to form a valid identifier [ExpectedIdentifierAfterPeriodOrArrow] + // CHECK:STDERR: return {.return = 5}; + // CHECK:STDERR: ^~~~~~ + // CHECK:STDERR: + return {.return = 5}; +}; diff --git a/toolchain/lex/token_info.h b/toolchain/lex/token_info.h index fcbf22715be4..2841718ca36e 100644 --- a/toolchain/lex/token_info.h +++ b/toolchain/lex/token_info.h @@ -133,6 +133,15 @@ class TokenInfo { *this = error; } + // Resets the token to be an identifier with the given identifier id at its + // original position and with the same whitespace adjacency. + auto ResetAsErrorRecoveryIdentifier(IdentifierId id) -> void { + CARBON_CHECK(kind().is_word()); + TokenInfo error(TokenKind::Identifier, has_leading_space(), id.index, + byte_offset()); + *this = error; + } + private: friend class Lexer; diff --git a/toolchain/lex/token_kind.h b/toolchain/lex/token_kind.h index 352a5ae3bf0e..38befb2eb308 100644 --- a/toolchain/lex/token_kind.h +++ b/toolchain/lex/token_kind.h @@ -86,6 +86,18 @@ class TokenKind : public CARBON_ENUM_BASE(TokenKind) { *this == TokenKind::FloatTypeLiteral; } + // Test whether this kind of token is a word. + auto is_word() const -> bool { + return *this == TokenKind::Identifier || *this == TokenKind::Underscore || + is_keyword() || is_sized_type_literal(); + } + + // Test whether this kind of token is a binding pattern operator. + auto is_binding_pattern_operator() const -> bool { + return *this == TokenKind::Colon || *this == TokenKind::ColonExclaim || + *this == TokenKind::ColonQuestion; + } + // If this token kind has a fixed spelling when in source code, returns it. // Otherwise returns an empty string. auto fixed_spelling() const -> llvm::StringLiteral { diff --git a/toolchain/lex/tokenized_buffer.cpp b/toolchain/lex/tokenized_buffer.cpp index f23edde0e047..5738ffffe10b 100644 --- a/toolchain/lex/tokenized_buffer.cpp +++ b/toolchain/lex/tokenized_buffer.cpp @@ -204,6 +204,32 @@ auto TokenizedBuffer::IsRecoveryToken(TokenIndex token) const -> bool { return recovery_tokens_[token.index]; } +auto TokenizedBuffer::AddPostLexingRecoveryTokenAsIdentifier(TokenIndex token) + -> TokenIndex { + auto kind = GetKind(token); + CARBON_CHECK(kind.is_word(), + "Invalid token kind {0} for recovery as identifier", kind); + CARBON_CHECK(kind != TokenKind::Identifier, "Recovery not required"); + + auto identifier_id = value_stores_->identifiers().Add(GetTokenText(token)); + auto info = token_infos_.Get(token); + info.ResetAsErrorRecoveryIdentifier(identifier_id); + return AddPostLexingRecoveryToken(info); +} + +auto TokenizedBuffer::AddPostLexingRecoveryToken(TokenInfo info) -> TokenIndex { + // Only resize once to avoid quadratic behavior if lots of recovery tokens are + // added. + if (recovery_tokens_.empty()) { + recovery_tokens_.resize(token_infos_.size()); + } + + auto token = token_infos_.Add(info); + recovery_tokens_.push_back(true); + ++post_lexing_recovery_tokens_; + return token; +} + auto TokenizedBuffer::GetIndentColumnNumber(LineIndex line) const -> int { return line_infos_.Get(line).indent + 1; } diff --git a/toolchain/lex/tokenized_buffer.h b/toolchain/lex/tokenized_buffer.h index 1d964b2088d4..780ce9f3b586 100644 --- a/toolchain/lex/tokenized_buffer.h +++ b/toolchain/lex/tokenized_buffer.h @@ -176,6 +176,12 @@ class TokenizedBuffer : public Printable { // For example, a closing paren inserted to match an unmatched paren. auto IsRecoveryToken(TokenIndex token) const -> bool; + // Adds and returns the index of an identifier token formed from the spelling + // of the given word token. This should only be used for error recovery + // purposes, when a phase after the lexer determines that a word token was + // intended to represent an identifier rather than its special meaning. + auto AddPostLexingRecoveryTokenAsIdentifier(TokenIndex token) -> TokenIndex; + // Returns the 1-based indentation column number. auto GetIndentColumnNumber(LineIndex line) const -> int; @@ -221,9 +227,14 @@ class TokenizedBuffer : public Printable { // Returns true if the buffer has errors that were detected at lexing time. auto has_errors() const -> bool { return has_errors_; } + // Returns the tokens produced by lexing the source file. This includes any + // recovery tokens inserted inline by lexing, such as matching brackets, but + // excludes recovery tokens added after lexing finished. auto tokens() const -> llvm::iterator_range { - return llvm::make_range(TokenIterator(TokenIndex(0)), - TokenIterator(TokenIndex(token_infos_.size()))); + return llvm::make_range( + TokenIterator(TokenIndex(0)), + TokenIterator( + TokenIndex(token_infos_.size() - post_lexing_recovery_tokens_))); } auto size() const -> int { return token_infos_.size(); } @@ -317,6 +328,9 @@ class TokenizedBuffer : public Printable { // Adds the token and adjusts the expected tree size. auto AddToken(TokenInfo info) -> TokenIndex; + // Adds a post-lexing recovery token and adjusts the buffer state. + auto AddPostLexingRecoveryToken(TokenInfo info) -> TokenIndex; + auto GetTokenPrintWidths(TokenIndex token) const -> PrintWidths; auto PrintToken(llvm::raw_ostream& output_stream, TokenIndex token, PrintWidths widths) const -> void; @@ -357,6 +371,11 @@ class TokenizedBuffer : public Printable { bool has_errors_ = false; + // The number of recovery tokens created after lexing finished. These tokens + // are included in `token_infos_`, but are excluded from the token sequence + // produced by `tokens()`. + int32_t post_lexing_recovery_tokens_ = 0; + // A vector of flags for recovery tokens. If empty, there are none. When doing // token recovery, this will be extended to be indexable by token indices and // contain true for the tokens that were synthesized for recovery. diff --git a/toolchain/parse/context.h b/toolchain/parse/context.h index be6a238b2cec..8b980025fc19 100644 --- a/toolchain/parse/context.h +++ b/toolchain/parse/context.h @@ -421,7 +421,7 @@ class Context { auto tree() const -> const Tree& { return *tree_; } - auto tokens() const -> const Lex::TokenizedBuffer& { return *tokens_; } + auto tokens() const -> Lex::TokenizedBuffer& { return *tokens_; } auto has_errors() const -> bool { return err_tracker_.seen_error(); } diff --git a/toolchain/parse/handle_binding_pattern.cpp b/toolchain/parse/handle_binding_pattern.cpp index 4777c34c985f..a0800716a794 100644 --- a/toolchain/parse/handle_binding_pattern.cpp +++ b/toolchain/parse/handle_binding_pattern.cpp @@ -12,15 +12,16 @@ auto HandleBindingPattern(Context& context) -> void { auto state = context.PopState(); // Handle an invalid pattern introducer for parameters and variables. - auto on_error = [&](bool expected_name) { + auto on_error = [&](bool expected_name, bool recover_as_raw = false) { if (!state.has_error) { CARBON_DIAGNOSTIC( ExpectedBindingPattern, Error, - "expected {0:name|`:`, `:!`, or `:?`} in binding pattern", - Diagnostics::BoolAsSelect); + "expected {0:name|`:`, `:!`, or `:?`} in binding pattern" + "{1:; prefix reserved word with `r#` to form a valid identifier|}", + Diagnostics::BoolAsSelect, Diagnostics::BoolAsSelect); context.emitter().Emit(*context.position(), ExpectedBindingPattern, - expected_name); - state.has_error = true; + expected_name, recover_as_raw); + state.has_error = !recover_as_raw; } }; @@ -44,6 +45,19 @@ auto HandleBindingPattern(Context& context) -> void { context.AddLeafNode(NodeKind::SelfValueName, *self); } else if (auto underscore = context.ConsumeIf(Lex::TokenKind::Underscore)) { context.AddLeafNode(NodeKind::UnderscoreName, *underscore); + } else if (context.PositionKind().is_word() && + context.PositionKind(Lookahead::NextToken) + .is_binding_pattern_operator()) { + // A word token that is not a valid binding name appeared before the `:`, + // such as a numeric type literal or a keyword. For error recovery, convert + // the token to an identifier, as we can be confident that a word in this + // position was intended to be a declared name. + auto word_as_identifier = + context.tokens().AddPostLexingRecoveryTokenAsIdentifier( + context.Consume()); + context.AddLeafNode(NodeKind::IdentifierNameNotBeforeSignature, + word_as_identifier); + on_error(/*expected_name=*/true, /*recover_as_raw*/ true); } else { // Add a placeholder for the name. context.AddLeafNode(NodeKind::IdentifierNameNotBeforeSignature, @@ -52,9 +66,7 @@ auto HandleBindingPattern(Context& context) -> void { } if (auto token_kind = context.PositionKind(); - token_kind == Lex::TokenKind::Colon || - token_kind == Lex::TokenKind::ColonExclaim || - token_kind == Lex::TokenKind::ColonQuestion) { + token_kind.is_binding_pattern_operator()) { // Add the wrapper node for the `template` keyword if present. if (template_token) { if (token_kind != Lex::TokenKind::ColonExclaim && !state.has_error) { diff --git a/toolchain/parse/handle_pattern.cpp b/toolchain/parse/handle_pattern.cpp index 8740e06d01ad..d3f54cdd27be 100644 --- a/toolchain/parse/handle_pattern.cpp +++ b/toolchain/parse/handle_pattern.cpp @@ -7,12 +7,6 @@ namespace Carbon::Parse { -static auto IsBindingPatternOperator(Lex::TokenKind kind) -> bool { - return kind == Lex::TokenKind::Colon || - kind == Lex::TokenKind::ColonExclaim || - kind == Lex::TokenKind::ColonQuestion; -} - auto HandlePattern(Context& context) -> void { auto state = context.PopState(); switch (context.PositionKind()) { @@ -37,19 +31,15 @@ auto HandlePattern(Context& context) -> void { state.in_var_pattern, state.in_unused_pattern, state.ambient_precedence); break; - case Lex::TokenKind::Identifier: - case Lex::TokenKind::SelfValueIdentifier: - case Lex::TokenKind::Underscore: { - if (IsBindingPatternOperator( - context.PositionKind(Lookahead::NextToken))) { + default: + if (context.PositionKind().is_word() && + context.PositionKind(Lookahead::NextToken) + .is_binding_pattern_operator()) { context.PushStateForPattern( StateKind::BindingPattern, state.in_var_pattern, state.in_unused_pattern, state.ambient_precedence); break; } - [[fallthrough]]; - } - default: context.PushState(StateKind::ExprPattern); context.PushStateForExpr(state.ambient_precedence); break; @@ -63,7 +53,7 @@ auto HandleExprPattern(Context& context) -> void { // have a malformed attempt to introduce a binding pattern that we interpreted // as an expression pattern, so diagnose that here rather than diagnosing a // missing `;` at an outer level. - if (IsBindingPatternOperator(context.PositionKind())) { + if (context.PositionKind().is_binding_pattern_operator()) { if (!state.has_error) { CARBON_DIAGNOSTIC(ExpectedBindingName, Error, "unexpected expression before {0} in binding pattern", diff --git a/toolchain/parse/handle_period.cpp b/toolchain/parse/handle_period.cpp index 881d5e3f9946..939573329ca5 100644 --- a/toolchain/parse/handle_period.cpp +++ b/toolchain/parse/handle_period.cpp @@ -38,16 +38,23 @@ static auto HandlePeriodOrArrow(Context& context, NodeKind node_kind, context.PushState(StateKind::OnlyParenExpr); return; } else { - CARBON_DIAGNOSTIC(ExpectedIdentifierAfterPeriodOrArrow, Error, - "expected identifier after `{0:->|.}`", - Diagnostics::BoolAsSelect); + bool recover_as_raw = context.PositionKind().is_word(); + CARBON_DIAGNOSTIC( + ExpectedIdentifierAfterPeriodOrArrow, Error, + "expected identifier after `{0:->|.}`" + "{1:; prefix reserved word with `r#` to form a valid identifier|}", + Diagnostics::BoolAsSelect, Diagnostics::BoolAsSelect); context.emitter().Emit(*context.position(), - ExpectedIdentifierAfterPeriodOrArrow, is_arrow); - // If we see a keyword, assume it was intended to be a name. - // TODO: Should keywords be valid here? - if (context.PositionKind().is_keyword()) { + ExpectedIdentifierAfterPeriodOrArrow, is_arrow, + recover_as_raw); + // If we see a word, assume it was intended to be a name. + // TODO: Should word tokens be valid here? + if (recover_as_raw) { + auto word_as_identifier = + context.tokens().AddPostLexingRecoveryTokenAsIdentifier( + context.Consume()); context.AddLeafNode(NodeKind::IdentifierNameNotBeforeSignature, - context.Consume(), /*has_error=*/true); + word_as_identifier); } else { context.AddLeafNode(NodeKind::IdentifierNameNotBeforeSignature, *context.position(), /*has_error=*/true); diff --git a/toolchain/parse/testdata/basics/fail_invalid_designators.carbon b/toolchain/parse/testdata/basics/fail_invalid_designators.carbon index 7f6a3039cf17..cdd460b7b39d 100644 --- a/toolchain/parse/testdata/basics/fail_invalid_designators.carbon +++ b/toolchain/parse/testdata/basics/fail_invalid_designators.carbon @@ -15,7 +15,7 @@ fn F() { // CHECK:STDERR: ^ // CHECK:STDERR: a.; - // CHECK:STDERR: fail_invalid_designators.carbon:[[@LINE+4]]:5: error: expected identifier after `.` [ExpectedIdentifierAfterPeriodOrArrow] + // CHECK:STDERR: fail_invalid_designators.carbon:[[@LINE+4]]:5: error: expected identifier after `.`; prefix reserved word with `r#` to form a valid identifier [ExpectedIdentifierAfterPeriodOrArrow] // CHECK:STDERR: a.fn; // CHECK:STDERR: ^~ // CHECK:STDERR: @@ -35,7 +35,7 @@ fn F() { // CHECK:STDOUT: {kind: 'MemberAccessExpr', text: '.', subtree_size: 3}, // CHECK:STDOUT: {kind: 'ExprStatement', text: ';', has_error: yes, subtree_size: 4}, // CHECK:STDOUT: {kind: 'IdentifierNameExpr', text: 'a'}, -// CHECK:STDOUT: {kind: 'IdentifierNameNotBeforeSignature', text: 'fn', has_error: yes}, +// CHECK:STDOUT: {kind: 'IdentifierNameNotBeforeSignature', text: 'fn'}, // CHECK:STDOUT: {kind: 'MemberAccessExpr', text: '.', subtree_size: 3}, // CHECK:STDOUT: {kind: 'ExprStatement', text: ';', subtree_size: 4}, // CHECK:STDOUT: {kind: 'FunctionDefinition', text: '}', subtree_size: 14}, diff --git a/toolchain/parse/testdata/member_access/fail_keyword.carbon b/toolchain/parse/testdata/member_access/fail_keyword.carbon index 6b0760f84453..f4de7870c932 100644 --- a/toolchain/parse/testdata/member_access/fail_keyword.carbon +++ b/toolchain/parse/testdata/member_access/fail_keyword.carbon @@ -9,32 +9,32 @@ // TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/parse/testdata/member_access/fail_keyword.carbon fn F() { - // CHECK:STDERR: fail_keyword.carbon:[[@LINE+4]]:5: error: expected identifier after `.` [ExpectedIdentifierAfterPeriodOrArrow] + // CHECK:STDERR: fail_keyword.carbon:[[@LINE+4]]:5: error: expected identifier after `.`; prefix reserved word with `r#` to form a valid identifier [ExpectedIdentifierAfterPeriodOrArrow] // CHECK:STDERR: a.self; // CHECK:STDERR: ^~~~ // CHECK:STDERR: a.self; - // CHECK:STDERR: fail_keyword.carbon:[[@LINE+4]]:5: error: expected identifier after `.` [ExpectedIdentifierAfterPeriodOrArrow] + // CHECK:STDERR: fail_keyword.carbon:[[@LINE+4]]:5: error: expected identifier after `.`; prefix reserved word with `r#` to form a valid identifier [ExpectedIdentifierAfterPeriodOrArrow] // CHECK:STDERR: a.Self; // CHECK:STDERR: ^~~~ // CHECK:STDERR: a.Self; - // CHECK:STDERR: fail_keyword.carbon:[[@LINE+4]]:5: error: expected identifier after `.` [ExpectedIdentifierAfterPeriodOrArrow] + // CHECK:STDERR: fail_keyword.carbon:[[@LINE+4]]:5: error: expected identifier after `.`; prefix reserved word with `r#` to form a valid identifier [ExpectedIdentifierAfterPeriodOrArrow] // CHECK:STDERR: a.for; // CHECK:STDERR: ^~~ // CHECK:STDERR: a.for; - // CHECK:STDERR: fail_keyword.carbon:[[@LINE+4]]:6: error: expected identifier after `->` [ExpectedIdentifierAfterPeriodOrArrow] + // CHECK:STDERR: fail_keyword.carbon:[[@LINE+4]]:6: error: expected identifier after `->`; prefix reserved word with `r#` to form a valid identifier [ExpectedIdentifierAfterPeriodOrArrow] // CHECK:STDERR: p->self; // CHECK:STDERR: ^~~~ // CHECK:STDERR: p->self; - // CHECK:STDERR: fail_keyword.carbon:[[@LINE+4]]:6: error: expected identifier after `->` [ExpectedIdentifierAfterPeriodOrArrow] + // CHECK:STDERR: fail_keyword.carbon:[[@LINE+4]]:6: error: expected identifier after `->`; prefix reserved word with `r#` to form a valid identifier [ExpectedIdentifierAfterPeriodOrArrow] // CHECK:STDERR: p->Self; // CHECK:STDERR: ^~~~ // CHECK:STDERR: p->Self; - // CHECK:STDERR: fail_keyword.carbon:[[@LINE+4]]:6: error: expected identifier after `->` [ExpectedIdentifierAfterPeriodOrArrow] + // CHECK:STDERR: fail_keyword.carbon:[[@LINE+4]]:6: error: expected identifier after `->`; prefix reserved word with `r#` to form a valid identifier [ExpectedIdentifierAfterPeriodOrArrow] // CHECK:STDERR: p->while; // CHECK:STDERR: ^~~~~ // CHECK:STDERR: @@ -50,27 +50,27 @@ fn F() { // CHECK:STDOUT: {kind: 'ExplicitParamList', text: ')', subtree_size: 2}, // CHECK:STDOUT: {kind: 'FunctionDefinitionStart', text: '{', subtree_size: 5}, // CHECK:STDOUT: {kind: 'IdentifierNameExpr', text: 'a'}, -// CHECK:STDOUT: {kind: 'IdentifierNameNotBeforeSignature', text: 'self', has_error: yes}, +// CHECK:STDOUT: {kind: 'IdentifierNameNotBeforeSignature', text: 'self'}, // CHECK:STDOUT: {kind: 'MemberAccessExpr', text: '.', subtree_size: 3}, // CHECK:STDOUT: {kind: 'ExprStatement', text: ';', subtree_size: 4}, // CHECK:STDOUT: {kind: 'IdentifierNameExpr', text: 'a'}, -// CHECK:STDOUT: {kind: 'IdentifierNameNotBeforeSignature', text: 'Self', has_error: yes}, +// CHECK:STDOUT: {kind: 'IdentifierNameNotBeforeSignature', text: 'Self'}, // CHECK:STDOUT: {kind: 'MemberAccessExpr', text: '.', subtree_size: 3}, // CHECK:STDOUT: {kind: 'ExprStatement', text: ';', subtree_size: 4}, // CHECK:STDOUT: {kind: 'IdentifierNameExpr', text: 'a'}, -// CHECK:STDOUT: {kind: 'IdentifierNameNotBeforeSignature', text: 'for', has_error: yes}, +// CHECK:STDOUT: {kind: 'IdentifierNameNotBeforeSignature', text: 'for'}, // CHECK:STDOUT: {kind: 'MemberAccessExpr', text: '.', subtree_size: 3}, // CHECK:STDOUT: {kind: 'ExprStatement', text: ';', subtree_size: 4}, // CHECK:STDOUT: {kind: 'IdentifierNameExpr', text: 'p'}, -// CHECK:STDOUT: {kind: 'IdentifierNameNotBeforeSignature', text: 'self', has_error: yes}, +// CHECK:STDOUT: {kind: 'IdentifierNameNotBeforeSignature', text: 'self'}, // CHECK:STDOUT: {kind: 'PointerMemberAccessExpr', text: '->', subtree_size: 3}, // CHECK:STDOUT: {kind: 'ExprStatement', text: ';', subtree_size: 4}, // CHECK:STDOUT: {kind: 'IdentifierNameExpr', text: 'p'}, -// CHECK:STDOUT: {kind: 'IdentifierNameNotBeforeSignature', text: 'Self', has_error: yes}, +// CHECK:STDOUT: {kind: 'IdentifierNameNotBeforeSignature', text: 'Self'}, // CHECK:STDOUT: {kind: 'PointerMemberAccessExpr', text: '->', subtree_size: 3}, // CHECK:STDOUT: {kind: 'ExprStatement', text: ';', subtree_size: 4}, // CHECK:STDOUT: {kind: 'IdentifierNameExpr', text: 'p'}, -// CHECK:STDOUT: {kind: 'IdentifierNameNotBeforeSignature', text: 'while', has_error: yes}, +// CHECK:STDOUT: {kind: 'IdentifierNameNotBeforeSignature', text: 'while'}, // CHECK:STDOUT: {kind: 'PointerMemberAccessExpr', text: '->', subtree_size: 3}, // CHECK:STDOUT: {kind: 'ExprStatement', text: ';', subtree_size: 4}, // CHECK:STDOUT: {kind: 'FunctionDefinition', text: '}', subtree_size: 30}, diff --git a/toolchain/parse/testdata/package_expr/fail_in_name.carbon b/toolchain/parse/testdata/package_expr/fail_in_name.carbon index a70a87fac06b..e8efdcaac3e9 100644 --- a/toolchain/parse/testdata/package_expr/fail_in_name.carbon +++ b/toolchain/parse/testdata/package_expr/fail_in_name.carbon @@ -15,7 +15,7 @@ var package.n: i32; // `val` is a keyword, but during error recovery we treat it as an identifier. -// CHECK:STDERR: fail_in_name.carbon:[[@LINE+8]]:13: error: expected identifier after `.` [ExpectedIdentifierAfterPeriodOrArrow] +// CHECK:STDERR: fail_in_name.carbon:[[@LINE+8]]:13: error: expected identifier after `.`; prefix reserved word with `r#` to form a valid identifier [ExpectedIdentifierAfterPeriodOrArrow] // CHECK:STDERR: var package.val: i32; // CHECK:STDERR: ^~~ // CHECK:STDERR: @@ -49,7 +49,7 @@ class package.C { // CHECK:STDOUT: {kind: 'VariableDecl', text: ';', subtree_size: 6}, // CHECK:STDOUT: {kind: 'VariableIntroducer', text: 'var'}, // CHECK:STDOUT: {kind: 'PackageExpr', text: 'package'}, -// CHECK:STDOUT: {kind: 'IdentifierNameNotBeforeSignature', text: 'val', has_error: yes}, +// CHECK:STDOUT: {kind: 'IdentifierNameNotBeforeSignature', text: 'val'}, // CHECK:STDOUT: {kind: 'MemberAccessExpr', text: '.', subtree_size: 3}, // CHECK:STDOUT: {kind: 'VariablePattern', text: 'var', has_error: yes, subtree_size: 4}, // CHECK:STDOUT: {kind: 'VariableDecl', text: ';', subtree_size: 6}, diff --git a/toolchain/parse/testdata/var/fail_bad_name.carbon b/toolchain/parse/testdata/var/fail_bad_name.carbon index 9acc2a081a6e..1833824cc131 100644 --- a/toolchain/parse/testdata/var/fail_bad_name.carbon +++ b/toolchain/parse/testdata/var/fail_bad_name.carbon @@ -14,6 +14,18 @@ // CHECK:STDERR: var *; +// CHECK:STDERR: fail_bad_name.carbon:[[@LINE+4]]:11: error: expected name in binding pattern; prefix reserved word with `r#` to form a valid identifier [ExpectedBindingPattern] +// CHECK:STDERR: var choice: i32; +// CHECK:STDERR: ^ +// CHECK:STDERR: +var choice: i32; + +// CHECK:STDERR: fail_bad_name.carbon:[[@LINE+4]]:7: error: expected name in binding pattern; prefix reserved word with `r#` to form a valid identifier [ExpectedBindingPattern] +// CHECK:STDERR: var f2: f32; +// CHECK:STDERR: ^ +// CHECK:STDERR: +var f2: f32; + // CHECK:STDOUT: - filename: fail_bad_name.carbon // CHECK:STDOUT: parse_tree: [ // CHECK:STDOUT: {kind: 'FileStart', text: ''}, @@ -22,5 +34,17 @@ var *; // CHECK:STDOUT: {kind: 'PrefixOperatorStar', text: '*', has_error: yes, subtree_size: 2}, // CHECK:STDOUT: {kind: 'VariablePattern', text: 'var', subtree_size: 3}, // CHECK:STDOUT: {kind: 'VariableDecl', text: ';', subtree_size: 5}, +// CHECK:STDOUT: {kind: 'VariableIntroducer', text: 'var'}, +// CHECK:STDOUT: {kind: 'IdentifierNameNotBeforeSignature', text: 'choice'}, +// CHECK:STDOUT: {kind: 'IntTypeLiteral', text: 'i32'}, +// CHECK:STDOUT: {kind: 'VarBindingPattern', text: ':', subtree_size: 3}, +// CHECK:STDOUT: {kind: 'VariablePattern', text: 'var', subtree_size: 4}, +// CHECK:STDOUT: {kind: 'VariableDecl', text: ';', subtree_size: 6}, +// CHECK:STDOUT: {kind: 'VariableIntroducer', text: 'var'}, +// CHECK:STDOUT: {kind: 'IdentifierNameNotBeforeSignature', text: 'f2'}, +// CHECK:STDOUT: {kind: 'FloatTypeLiteral', text: 'f32'}, +// CHECK:STDOUT: {kind: 'VarBindingPattern', text: ':', subtree_size: 3}, +// CHECK:STDOUT: {kind: 'VariablePattern', text: 'var', subtree_size: 4}, +// CHECK:STDOUT: {kind: 'VariableDecl', text: ';', subtree_size: 6}, // CHECK:STDOUT: {kind: 'FileEnd', text: ''}, // CHECK:STDOUT: ]