Ver Fonte

Validate parse nodes correspond to expected tokens (#3295)

Can specify which tokens are allowed generally, and any additional
tokens that only occur when the parse node has an error.

---------

Co-authored-by: Richard Smith <richard@metafoo.co.uk>
josh11b há 2 anos atrás
pai
commit
a112f2e802

+ 1 - 0
toolchain/parse/BUILD

@@ -21,6 +21,7 @@ cc_library(
     deps = [
         "//common:check",
         "//common:enum_base",
+        "//toolchain/lex:token_kind",
     ],
 )
 

+ 2 - 0
toolchain/parse/context.cpp

@@ -58,6 +58,7 @@ Context::Context(Tree& tree, Lex::TokenizedBuffer& tokens,
 
 auto Context::AddLeafNode(NodeKind kind, Lex::Token token, bool has_error)
     -> void {
+  CheckNodeMatchesLexerToken(kind, tokens_->GetKind(token), has_error);
   tree_->node_impls_.push_back(
       Tree::NodeImpl(kind, has_error, token, /*subtree_size=*/1));
   if (has_error) {
@@ -67,6 +68,7 @@ auto Context::AddLeafNode(NodeKind kind, Lex::Token token, bool has_error)
 
 auto Context::AddNode(NodeKind kind, Lex::Token token, int subtree_start,
                       bool has_error) -> void {
+  CheckNodeMatchesLexerToken(kind, tokens_->GetKind(token), has_error);
   int subtree_size = tree_->size() - subtree_start + 1;
   tree_->node_impls_.push_back(
       Tree::NodeImpl(kind, has_error, token, subtree_size));

+ 2 - 1
toolchain/parse/handle_declaration_name_and_params.cpp

@@ -33,7 +33,8 @@ static auto HandleDeclarationNameAndParams(Context& context, State after_name)
     context.emitter().Emit(*context.position(), ExpectedDeclarationName,
                            context.tokens().GetKind(state.token));
     context.ReturnErrorOnState();
-    context.AddLeafNode(NodeKind::InvalidParse, *context.position());
+    context.AddLeafNode(NodeKind::InvalidParse, *context.position(),
+                        /*has_error=*/true);
   }
 }
 

+ 46 - 2
toolchain/parse/node_kind.cpp

@@ -26,7 +26,8 @@ auto NodeKind::bracket() const -> NodeKind {
   // Nodes are never self-bracketed, so we use that for nodes that instead set
   // child_count.
   static constexpr NodeKind Bracket[] = {
-#define CARBON_PARSE_NODE_KIND_BRACKET(Name, BracketName) NodeKind::BracketName,
+#define CARBON_PARSE_NODE_KIND_BRACKET(Name, BracketName, ...) \
+  NodeKind::BracketName,
 #define CARBON_PARSE_NODE_KIND_CHILD_COUNT(Name, ...) NodeKind::Name,
 #include "toolchain/parse/node_kind.def"
   };
@@ -38,7 +39,7 @@ auto NodeKind::bracket() const -> NodeKind {
 auto NodeKind::child_count() const -> int32_t {
   static constexpr int32_t ChildCount[] = {
 #define CARBON_PARSE_NODE_KIND_BRACKET(...) -1,
-#define CARBON_PARSE_NODE_KIND_CHILD_COUNT(Name, Size) Size,
+#define CARBON_PARSE_NODE_KIND_CHILD_COUNT(Name, Size, ...) Size,
 #include "toolchain/parse/node_kind.def"
   };
   auto child_count = ChildCount[AsInt()];
@@ -46,4 +47,47 @@ auto NodeKind::child_count() const -> int32_t {
   return child_count;
 }
 
+void CheckNodeMatchesLexerToken(NodeKind node_kind, Lex::TokenKind token_kind,
+                                bool has_error) {
+  switch (node_kind) {
+    // Use `CARBON_LOG CARBON_ANY_TOKEN` to discover which combinations happen
+    // in practice.
+#define CARBON_LOG                                                        \
+  llvm::errs() << "ZZZ: Created parse node with NodeKind " << node_kind   \
+               << " and has_error " << has_error << " for lexical token " \
+               << token_kind << "\n";
+
+#define CARBON_ANY_TOKEN return;
+
+#define CARBON_TOKEN(Expected)                  \
+  if (token_kind == Lex::TokenKind::Expected) { \
+    return;                                     \
+  }
+
+#define CARBON_IF_ERROR(MatchActions) \
+  if (has_error) {                    \
+    MatchActions                      \
+  }
+
+#define CARBON_CASE(Name, MatchActions) \
+  case NodeKind::Name:                  \
+    MatchActions;                       \
+    break;
+
+#define CARBON_PARSE_NODE_KIND_BRACKET(Name, BracketName, MatchActions) \
+  CARBON_CASE(Name, MatchActions)
+
+#define CARBON_PARSE_NODE_KIND_CHILD_COUNT(Name, Size, MatchActions) \
+  CARBON_CASE(Name, MatchActions)
+
+#include "toolchain/parse/node_kind.def"
+
+#undef CARBON_LOG
+#undef CARBON_CASE
+  }
+  CARBON_FATAL() << "Created parse node with NodeKind " << node_kind
+                 << " and has_error " << has_error
+                 << " for unexpected lexical token " << token_kind;
+}
+
 }  // namespace Carbon::Parse

+ 245 - 100
toolchain/parse/node_kind.def

@@ -11,13 +11,20 @@
 // - CARBON_PARSE_NODE_KIND(Name)
 //   Used as a fallback if other macros are missing. No kinds should use this
 //   directly.
-//   - CARBON_PARSE_NODE_KIND_BRACKET(Name, BracketName)
+//   - CARBON_PARSE_NODE_KIND_BRACKET(Name, BracketName, LexTokenKinds)
 //     Defines a bracketed node kind. BracketName should refer to the node
 //     kind that is the _start_ of the bracketed range.
-//   - CARBON_PARSE_NODE_KIND_CHILD_COUNT(Name, ChildCount)
+//   - CARBON_PARSE_NODE_KIND_CHILD_COUNT(Name, ChildCount, LexTokenKinds)
 //     Defines a parse node with a set number of children, often 0. This count
 //     must be correct even when the node contains errors.
 //
+//   In both cases, LexTokenKinds says which Lex::TokenKind values that this
+//   parse node can correspond to, and is a sequence of:
+//   - CARBON_TOKEN(kind): This node can correspond to this kind of token.
+//   - CARBON_ANY_TOKEN: This node can correspond to any token.
+//   - CARBON_IF_ERROR(LexTokenKinds): This node can additionally correspond
+//     to the given kinds of tokens if its `has_error` flag is set.
+//
 // This tree represents the subset relationship between these macros, where if a
 // specific x-macro isn't defined, it'll fall back to the parent macro.
 //
@@ -41,22 +48,27 @@
 #endif
 
 // The start of the file.
-CARBON_PARSE_NODE_KIND_CHILD_COUNT(FileStart, 0)
+CARBON_PARSE_NODE_KIND_CHILD_COUNT(FileStart, 0, CARBON_TOKEN(StartOfFile))
 
 // The end of the file.
-CARBON_PARSE_NODE_KIND_CHILD_COUNT(FileEnd, 0)
+CARBON_PARSE_NODE_KIND_CHILD_COUNT(FileEnd, 0, CARBON_TOKEN(EndOfFile))
 
 // An invalid parse. Used to balance the parse tree. Always has an error.
-CARBON_PARSE_NODE_KIND_CHILD_COUNT(InvalidParse, 0)
+CARBON_PARSE_NODE_KIND_CHILD_COUNT(InvalidParse, 0,
+                                   CARBON_IF_ERROR(CARBON_ANY_TOKEN))
 
 // An empty declaration, such as `;`.
-CARBON_PARSE_NODE_KIND_CHILD_COUNT(EmptyDeclaration, 0)
+CARBON_PARSE_NODE_KIND_CHILD_COUNT(EmptyDeclaration, 0,
+                                   CARBON_TOKEN(Semi)
+                                       CARBON_IF_ERROR(CARBON_ANY_TOKEN))
 
 // A name in a non-expression context, such as a declaration.
-CARBON_PARSE_NODE_KIND_CHILD_COUNT(Name, 0)
+CARBON_PARSE_NODE_KIND_CHILD_COUNT(Name, 0,
+                                   CARBON_TOKEN(Identifier)
+                                       CARBON_IF_ERROR(CARBON_ANY_TOKEN))
 
 // A name in an expression context.
-CARBON_PARSE_NODE_KIND_CHILD_COUNT(NameExpression, 0)
+CARBON_PARSE_NODE_KIND_CHILD_COUNT(NameExpression, 0, CARBON_TOKEN(Identifier))
 
 // `package`:
 //   PackageIntroducer
@@ -65,25 +77,31 @@ CARBON_PARSE_NODE_KIND_CHILD_COUNT(NameExpression, 0)
 //   PackageLibrary
 //   PackageApi or PackageImpl
 // PackageDirective
-CARBON_PARSE_NODE_KIND_CHILD_COUNT(PackageIntroducer, 0)
-CARBON_PARSE_NODE_KIND_CHILD_COUNT(PackageApi, 0)
-CARBON_PARSE_NODE_KIND_CHILD_COUNT(PackageImpl, 0)
-CARBON_PARSE_NODE_KIND_CHILD_COUNT(PackageLibrary, 1)
-CARBON_PARSE_NODE_KIND_BRACKET(PackageDirective, PackageIntroducer)
+CARBON_PARSE_NODE_KIND_CHILD_COUNT(PackageIntroducer, 0, CARBON_TOKEN(Package))
+CARBON_PARSE_NODE_KIND_CHILD_COUNT(PackageApi, 0, CARBON_TOKEN(Api))
+CARBON_PARSE_NODE_KIND_CHILD_COUNT(PackageImpl, 0, CARBON_TOKEN(Impl))
+CARBON_PARSE_NODE_KIND_CHILD_COUNT(PackageLibrary, 1, CARBON_TOKEN(Library))
+CARBON_PARSE_NODE_KIND_BRACKET(PackageDirective, PackageIntroducer,
+                               CARBON_TOKEN(Semi)
+                                   CARBON_IF_ERROR(CARBON_TOKEN(Package)))
 
 // `namespace`:
 //   NamespaceStart
 //   _external_: Name or QualifiedDeclaration
 // Namespace
-CARBON_PARSE_NODE_KIND_CHILD_COUNT(NamespaceStart, 0)
-CARBON_PARSE_NODE_KIND_CHILD_COUNT(Namespace, 2)
+CARBON_PARSE_NODE_KIND_CHILD_COUNT(NamespaceStart, 0, CARBON_TOKEN(Namespace))
+CARBON_PARSE_NODE_KIND_CHILD_COUNT(Namespace, 2, CARBON_TOKEN(Semi))
 
 // A code block:
 //   CodeBlockStart
 //   _external_: statements
 // CodeBlock
-CARBON_PARSE_NODE_KIND_CHILD_COUNT(CodeBlockStart, 0)
-CARBON_PARSE_NODE_KIND_BRACKET(CodeBlock, CodeBlockStart)
+CARBON_PARSE_NODE_KIND_CHILD_COUNT(CodeBlockStart, 0,
+                                   CARBON_TOKEN(OpenCurlyBrace)
+                                       CARBON_IF_ERROR(CARBON_ANY_TOKEN))
+CARBON_PARSE_NODE_KIND_BRACKET(CodeBlock, CodeBlockStart,
+                               CARBON_TOKEN(CloseCurlyBrace)
+                                   CARBON_IF_ERROR(CARBON_ANY_TOKEN))
 
 // `fn`:
 //     FunctionIntroducer
@@ -98,11 +116,15 @@ CARBON_PARSE_NODE_KIND_BRACKET(CodeBlock, CodeBlockStart)
 // The above is the structure for a definition; for a declaration,
 // FunctionDefinitionStart and later nodes are removed and replaced by
 // FunctionDeclaration.
-CARBON_PARSE_NODE_KIND_CHILD_COUNT(FunctionIntroducer, 0)
-CARBON_PARSE_NODE_KIND_CHILD_COUNT(ReturnType, 1)
-CARBON_PARSE_NODE_KIND_BRACKET(FunctionDefinitionStart, FunctionIntroducer)
-CARBON_PARSE_NODE_KIND_BRACKET(FunctionDefinition, FunctionDefinitionStart)
-CARBON_PARSE_NODE_KIND_BRACKET(FunctionDeclaration, FunctionIntroducer)
+CARBON_PARSE_NODE_KIND_CHILD_COUNT(FunctionIntroducer, 0, CARBON_TOKEN(Fn))
+CARBON_PARSE_NODE_KIND_CHILD_COUNT(ReturnType, 1, CARBON_TOKEN(MinusGreater))
+CARBON_PARSE_NODE_KIND_BRACKET(FunctionDefinitionStart, FunctionIntroducer,
+                               CARBON_TOKEN(OpenCurlyBrace))
+CARBON_PARSE_NODE_KIND_BRACKET(FunctionDefinition, FunctionDefinitionStart,
+                               CARBON_TOKEN(CloseCurlyBrace))
+CARBON_PARSE_NODE_KIND_BRACKET(FunctionDeclaration, FunctionIntroducer,
+                               CARBON_TOKEN(Semi)
+                                   CARBON_IF_ERROR(CARBON_TOKEN(Fn)))
 
 // A parameter list, possibly deduced:
 //   [Deduced]ParamertListStart
@@ -112,11 +134,15 @@ CARBON_PARSE_NODE_KIND_BRACKET(FunctionDeclaration, FunctionIntroducer)
 //
 // Expressions and ParameterListComma may repeat with ParameterListComma as a
 // separator.
-CARBON_PARSE_NODE_KIND_CHILD_COUNT(ParameterListStart, 0)
-CARBON_PARSE_NODE_KIND_CHILD_COUNT(DeducedParameterListStart, 0)
-CARBON_PARSE_NODE_KIND_CHILD_COUNT(ParameterListComma, 0)
-CARBON_PARSE_NODE_KIND_BRACKET(ParameterList, ParameterListStart)
-CARBON_PARSE_NODE_KIND_BRACKET(DeducedParameterList, DeducedParameterListStart)
+CARBON_PARSE_NODE_KIND_CHILD_COUNT(ParameterListStart, 0,
+                                   CARBON_TOKEN(OpenParen))
+CARBON_PARSE_NODE_KIND_CHILD_COUNT(DeducedParameterListStart, 0,
+                                   CARBON_TOKEN(OpenSquareBracket))
+CARBON_PARSE_NODE_KIND_CHILD_COUNT(ParameterListComma, 0, CARBON_TOKEN(Comma))
+CARBON_PARSE_NODE_KIND_BRACKET(ParameterList, ParameterListStart,
+                               CARBON_TOKEN(CloseParen))
+CARBON_PARSE_NODE_KIND_BRACKET(DeducedParameterList, DeducedParameterListStart,
+                               CARBON_TOKEN(CloseSquareBracket))
 
 // An array type, such as  `[i32; 3]` or `[i32;]`:
 //     ArrayExpressionStart
@@ -126,9 +152,14 @@ CARBON_PARSE_NODE_KIND_BRACKET(DeducedParameterList, DeducedParameterListStart)
 // ArrayExpression
 //
 // The bound expression is optional.
-CARBON_PARSE_NODE_KIND_CHILD_COUNT(ArrayExpressionStart, 0)
-CARBON_PARSE_NODE_KIND_CHILD_COUNT(ArrayExpressionSemi, 2)
-CARBON_PARSE_NODE_KIND_BRACKET(ArrayExpression, ArrayExpressionSemi)
+CARBON_PARSE_NODE_KIND_CHILD_COUNT(ArrayExpressionStart, 0,
+                                   CARBON_TOKEN(OpenSquareBracket))
+// May be associated with a `]` token on error.
+CARBON_PARSE_NODE_KIND_CHILD_COUNT(
+    ArrayExpressionSemi, 2,
+    CARBON_TOKEN(Semi) CARBON_IF_ERROR(CARBON_TOKEN(CloseSquareBracket)))
+CARBON_PARSE_NODE_KIND_BRACKET(ArrayExpression, ArrayExpressionSemi,
+                               CARBON_TOKEN(CloseSquareBracket))
 
 // A pattern binding, such as `name: Type`:
 //   Name
@@ -137,10 +168,13 @@ CARBON_PARSE_NODE_KIND_BRACKET(ArrayExpression, ArrayExpressionSemi)
 //
 // Address and Template may be parents to [Generic]PatternBinding, in that
 // order.
-CARBON_PARSE_NODE_KIND_CHILD_COUNT(PatternBinding, 2)
-CARBON_PARSE_NODE_KIND_CHILD_COUNT(GenericPatternBinding, 2)
-CARBON_PARSE_NODE_KIND_CHILD_COUNT(Address, 1)
-CARBON_PARSE_NODE_KIND_CHILD_COUNT(Template, 1)
+CARBON_PARSE_NODE_KIND_CHILD_COUNT(PatternBinding, 2,
+                                   CARBON_TOKEN(Colon)
+                                       CARBON_IF_ERROR(CARBON_ANY_TOKEN))
+CARBON_PARSE_NODE_KIND_CHILD_COUNT(GenericPatternBinding, 2,
+                                   CARBON_TOKEN(ColonExclaim))
+CARBON_PARSE_NODE_KIND_CHILD_COUNT(Address, 1, CARBON_TOKEN(Addr))
+CARBON_PARSE_NODE_KIND_CHILD_COUNT(Template, 1, CARBON_TOKEN(Template))
 
 // `let`:
 //   LetIntroducer
@@ -148,9 +182,10 @@ CARBON_PARSE_NODE_KIND_CHILD_COUNT(Template, 1)
 //   LetInitializer
 //   _external_: expression
 // LetDeclaration
-CARBON_PARSE_NODE_KIND_CHILD_COUNT(LetIntroducer, 0)
-CARBON_PARSE_NODE_KIND_CHILD_COUNT(LetInitializer, 0)
-CARBON_PARSE_NODE_KIND_BRACKET(LetDeclaration, LetIntroducer)
+CARBON_PARSE_NODE_KIND_CHILD_COUNT(LetIntroducer, 0, CARBON_TOKEN(Let))
+CARBON_PARSE_NODE_KIND_CHILD_COUNT(LetInitializer, 0, CARBON_TOKEN(Equal))
+CARBON_PARSE_NODE_KIND_BRACKET(LetDeclaration, LetIntroducer,
+                               CARBON_TOKEN(Semi))
 
 // `var`:
 //   VariableIntroducer
@@ -161,26 +196,33 @@ CARBON_PARSE_NODE_KIND_BRACKET(LetDeclaration, LetIntroducer)
 //
 // The VariableInitializer and following expression are paired: either both will
 // be present, or neither will.
-CARBON_PARSE_NODE_KIND_CHILD_COUNT(VariableIntroducer, 0)
-CARBON_PARSE_NODE_KIND_CHILD_COUNT(VariableInitializer, 0)
-CARBON_PARSE_NODE_KIND_BRACKET(VariableDeclaration, VariableIntroducer)
+CARBON_PARSE_NODE_KIND_CHILD_COUNT(VariableIntroducer, 0, CARBON_TOKEN(Var))
+CARBON_PARSE_NODE_KIND_CHILD_COUNT(VariableInitializer, 0, CARBON_TOKEN(Equal))
+CARBON_PARSE_NODE_KIND_BRACKET(VariableDeclaration, VariableIntroducer,
+                               CARBON_TOKEN(Semi)
+                                   CARBON_IF_ERROR(CARBON_TOKEN(Var)))
 
 // An expression statement:
 //   _external_: expression
 // ExpressionStatement
-CARBON_PARSE_NODE_KIND_CHILD_COUNT(ExpressionStatement, 1)
+CARBON_PARSE_NODE_KIND_CHILD_COUNT(ExpressionStatement, 1, CARBON_TOKEN(Semi))
 
 // `break`:
 //   BreakStatementStart
 // BreakStatement
-CARBON_PARSE_NODE_KIND_CHILD_COUNT(BreakStatementStart, 0)
-CARBON_PARSE_NODE_KIND_CHILD_COUNT(BreakStatement, 1)
+CARBON_PARSE_NODE_KIND_CHILD_COUNT(BreakStatementStart, 0, CARBON_TOKEN(Break))
+CARBON_PARSE_NODE_KIND_CHILD_COUNT(BreakStatement, 1,
+                                   CARBON_TOKEN(Semi)
+                                       CARBON_IF_ERROR(CARBON_TOKEN(Break)))
 
 // `continue`:
 //   ContinueStatementStart
 // ContinueStatement
-CARBON_PARSE_NODE_KIND_CHILD_COUNT(ContinueStatementStart, 0)
-CARBON_PARSE_NODE_KIND_CHILD_COUNT(ContinueStatement, 1)
+CARBON_PARSE_NODE_KIND_CHILD_COUNT(ContinueStatementStart, 0,
+                                   CARBON_TOKEN(Continue))
+CARBON_PARSE_NODE_KIND_CHILD_COUNT(ContinueStatement, 1,
+                                   CARBON_TOKEN(Semi)
+                                       CARBON_IF_ERROR(CARBON_TOKEN(Continue)))
 
 // `return`:
 //   ReturnStatementStart
@@ -188,8 +230,11 @@ CARBON_PARSE_NODE_KIND_CHILD_COUNT(ContinueStatement, 1)
 // ReturnStatement
 //
 // The child expression is optional.
-CARBON_PARSE_NODE_KIND_CHILD_COUNT(ReturnStatementStart, 0)
-CARBON_PARSE_NODE_KIND_BRACKET(ReturnStatement, ReturnStatementStart)
+CARBON_PARSE_NODE_KIND_CHILD_COUNT(ReturnStatementStart, 0,
+                                   CARBON_TOKEN(Return))
+CARBON_PARSE_NODE_KIND_BRACKET(ReturnStatement, ReturnStatementStart,
+                               CARBON_TOKEN(Semi)
+                                   CARBON_IF_ERROR(CARBON_TOKEN(Return)))
 
 // `for`:
 //     ForHeaderStart
@@ -203,10 +248,16 @@ CARBON_PARSE_NODE_KIND_BRACKET(ReturnStatement, ReturnStatementStart)
 // ForStatement
 //
 // Versus a normal `var`, ForIn replaces VariableDeclaration.
-CARBON_PARSE_NODE_KIND_CHILD_COUNT(ForHeaderStart, 0)
-CARBON_PARSE_NODE_KIND_BRACKET(ForIn, VariableIntroducer)
-CARBON_PARSE_NODE_KIND_BRACKET(ForHeader, ForHeaderStart)
-CARBON_PARSE_NODE_KIND_CHILD_COUNT(ForStatement, 2)
+CARBON_PARSE_NODE_KIND_CHILD_COUNT(ForHeaderStart, 0,
+                                   CARBON_TOKEN(OpenParen)
+                                       CARBON_IF_ERROR(CARBON_TOKEN(For)))
+CARBON_PARSE_NODE_KIND_BRACKET(ForIn, VariableIntroducer,
+                               CARBON_TOKEN(In)
+                                   CARBON_IF_ERROR(CARBON_ANY_TOKEN))
+CARBON_PARSE_NODE_KIND_BRACKET(ForHeader, ForHeaderStart,
+                               CARBON_TOKEN(CloseParen)
+                                   CARBON_IF_ERROR(CARBON_TOKEN(For)))
+CARBON_PARSE_NODE_KIND_CHILD_COUNT(ForStatement, 2, CARBON_TOKEN(For))
 
 // `if` statement + `else`:
 //     IfConditionStart
@@ -218,10 +269,14 @@ CARBON_PARSE_NODE_KIND_CHILD_COUNT(ForStatement, 2)
 // IfStatement
 //
 // IfStatementElse and the following node are optional based on `else` presence.
-CARBON_PARSE_NODE_KIND_CHILD_COUNT(IfConditionStart, 0)
-CARBON_PARSE_NODE_KIND_BRACKET(IfCondition, IfConditionStart)
-CARBON_PARSE_NODE_KIND_CHILD_COUNT(IfStatementElse, 0)
-CARBON_PARSE_NODE_KIND_BRACKET(IfStatement, IfCondition)
+CARBON_PARSE_NODE_KIND_CHILD_COUNT(IfConditionStart, 0,
+                                   CARBON_TOKEN(OpenParen)
+                                       CARBON_IF_ERROR(CARBON_TOKEN(If)))
+CARBON_PARSE_NODE_KIND_BRACKET(IfCondition, IfConditionStart,
+                               CARBON_TOKEN(CloseParen)
+                                   CARBON_IF_ERROR(CARBON_TOKEN(If)))
+CARBON_PARSE_NODE_KIND_CHILD_COUNT(IfStatementElse, 0, CARBON_TOKEN(Else))
+CARBON_PARSE_NODE_KIND_BRACKET(IfStatement, IfCondition, CARBON_TOKEN(If))
 
 // `while`:
 //     WhileConditionStart
@@ -229,17 +284,21 @@ CARBON_PARSE_NODE_KIND_BRACKET(IfStatement, IfCondition)
 //   WhileCondition
 //   _external_: CodeBlock
 // WhileStatement
-CARBON_PARSE_NODE_KIND_CHILD_COUNT(WhileConditionStart, 0)
-CARBON_PARSE_NODE_KIND_BRACKET(WhileCondition, WhileConditionStart)
-CARBON_PARSE_NODE_KIND_CHILD_COUNT(WhileStatement, 2)
+CARBON_PARSE_NODE_KIND_CHILD_COUNT(WhileConditionStart, 0,
+                                   CARBON_TOKEN(OpenParen))
+CARBON_PARSE_NODE_KIND_BRACKET(WhileCondition, WhileConditionStart,
+                               CARBON_TOKEN(CloseParen))
+CARBON_PARSE_NODE_KIND_CHILD_COUNT(WhileStatement, 2, CARBON_TOKEN(While))
 
 // Index expressions, such as `a[1]`:
 //     _external_: expression
 //   IndexExpressionStart
 //   _external_: expression
 // IndexExpression
-CARBON_PARSE_NODE_KIND_CHILD_COUNT(IndexExpressionStart, 1)
-CARBON_PARSE_NODE_KIND_BRACKET(IndexExpression, IndexExpressionStart)
+CARBON_PARSE_NODE_KIND_CHILD_COUNT(IndexExpressionStart, 1,
+                                   CARBON_TOKEN(OpenSquareBracket))
+CARBON_PARSE_NODE_KIND_BRACKET(IndexExpression, IndexExpressionStart,
+                               CARBON_TOKEN(CloseSquareBracket))
 
 // Parenthesized expressions, such as `(2)`:
 //   ParenExpressionOrTupleLiteralStart
@@ -254,11 +313,14 @@ CARBON_PARSE_NODE_KIND_BRACKET(IndexExpression, IndexExpressionStart)
 //
 // Expressions and TupleLiteralComma may repeat with TupleLiteralComma as a
 // separator.
-CARBON_PARSE_NODE_KIND_CHILD_COUNT(ParenExpressionOrTupleLiteralStart, 0)
+CARBON_PARSE_NODE_KIND_CHILD_COUNT(ParenExpressionOrTupleLiteralStart, 0,
+                                   CARBON_TOKEN(OpenParen))
 CARBON_PARSE_NODE_KIND_BRACKET(ParenExpression,
-                               ParenExpressionOrTupleLiteralStart)
-CARBON_PARSE_NODE_KIND_CHILD_COUNT(TupleLiteralComma, 0)
-CARBON_PARSE_NODE_KIND_BRACKET(TupleLiteral, ParenExpressionOrTupleLiteralStart)
+                               ParenExpressionOrTupleLiteralStart,
+                               CARBON_TOKEN(CloseParen))
+CARBON_PARSE_NODE_KIND_CHILD_COUNT(TupleLiteralComma, 0, CARBON_TOKEN(Comma))
+CARBON_PARSE_NODE_KIND_BRACKET(TupleLiteral, ParenExpressionOrTupleLiteralStart,
+                               CARBON_TOKEN(CloseParen))
 
 // Call expressions, such as `a()`:
 //     _external_: expression
@@ -269,9 +331,11 @@ CARBON_PARSE_NODE_KIND_BRACKET(TupleLiteral, ParenExpressionOrTupleLiteralStart)
 //
 // Expressions and CallExpressionComma may repeat with CallExpressionComma as a
 // separator.
-CARBON_PARSE_NODE_KIND_CHILD_COUNT(CallExpressionStart, 1)
-CARBON_PARSE_NODE_KIND_CHILD_COUNT(CallExpressionComma, 0)
-CARBON_PARSE_NODE_KIND_BRACKET(CallExpression, CallExpressionStart)
+CARBON_PARSE_NODE_KIND_CHILD_COUNT(CallExpressionStart, 1,
+                                   CARBON_TOKEN(OpenParen))
+CARBON_PARSE_NODE_KIND_CHILD_COUNT(CallExpressionComma, 0, CARBON_TOKEN(Comma))
+CARBON_PARSE_NODE_KIND_BRACKET(CallExpression, CallExpressionStart,
+                               CARBON_TOKEN(CloseParen))
 
 // A qualified declaration, such as `a.b`:
 //   _external_: NameExpression or QualifiedDeclaration
@@ -280,47 +344,104 @@ CARBON_PARSE_NODE_KIND_BRACKET(CallExpression, CallExpressionStart)
 //
 // TODO: This will eventually more general expressions, for example with
 // `GenericType(type_args).ChildType(child_type_args).Name`.
-CARBON_PARSE_NODE_KIND_CHILD_COUNT(QualifiedDeclaration, 2)
+CARBON_PARSE_NODE_KIND_CHILD_COUNT(QualifiedDeclaration, 2,
+                                   CARBON_TOKEN(Period))
 
 // A member access expression, such as `a.b` or
 // `GetObject().(Interface.member)`:
 //   _external_: lhs expression
 //   _external_: rhs expression
 // QualifiedExpression
-CARBON_PARSE_NODE_KIND_CHILD_COUNT(MemberAccessExpression, 2)
+CARBON_PARSE_NODE_KIND_CHILD_COUNT(MemberAccessExpression, 2,
+                                   CARBON_TOKEN(Period))
 
 // A pointer member access expression, such as `a->b` or
 // `GetObject()->(Interface.member)`:
 //   _external_: lhs expression
 //   _external_: rhs expression
 // QualifiedExpression
-CARBON_PARSE_NODE_KIND_CHILD_COUNT(PointerMemberAccessExpression, 2)
+CARBON_PARSE_NODE_KIND_CHILD_COUNT(PointerMemberAccessExpression, 2,
+                                   CARBON_TOKEN(MinusGreater))
+
+// clang-format off
 
 // A literal.
-CARBON_PARSE_NODE_KIND_CHILD_COUNT(Literal, 0)
+CARBON_PARSE_NODE_KIND_CHILD_COUNT(Literal, 0,
+                                   CARBON_TOKEN(False)
+                                   CARBON_TOKEN(True)
+                                   CARBON_TOKEN(IntegerLiteral)
+                                   CARBON_TOKEN(RealLiteral)
+                                   CARBON_TOKEN(StringLiteral)
+                                   CARBON_TOKEN(Bool)
+                                   CARBON_TOKEN(IntegerTypeLiteral)
+                                   CARBON_TOKEN(UnsignedIntegerTypeLiteral)
+                                   CARBON_TOKEN(FloatingPointTypeLiteral)
+                                   CARBON_TOKEN(StringTypeLiteral)
+                                   CARBON_TOKEN(Type))
 
 // A prefix operator:
 //   _external_: expression
 // PrefixOperator
-CARBON_PARSE_NODE_KIND_CHILD_COUNT(PrefixOperator, 1)
+CARBON_PARSE_NODE_KIND_CHILD_COUNT(PrefixOperator, 1,
+                                   CARBON_TOKEN(Star)
+                                   CARBON_TOKEN(Amp)
+                                   CARBON_TOKEN(Not)
+                                   CARBON_TOKEN(Minus)
+                                   CARBON_TOKEN(MinusMinus)
+                                   CARBON_TOKEN(PlusPlus)
+                                   CARBON_TOKEN(Caret)
+                                   CARBON_TOKEN(Const))
 
 // An infix operator:
 //   _external_: lhs expression
 //   _external_: rhs expression
 // InfixOperator
-CARBON_PARSE_NODE_KIND_CHILD_COUNT(InfixOperator, 2)
+CARBON_PARSE_NODE_KIND_CHILD_COUNT(InfixOperator, 2,
+                                   CARBON_TOKEN(Amp)
+                                   CARBON_TOKEN(AmpEqual)
+                                   CARBON_TOKEN(And)
+                                   CARBON_TOKEN(As)
+                                   CARBON_TOKEN(Caret)
+                                   CARBON_TOKEN(CaretEqual)
+                                   CARBON_TOKEN(Equal)
+                                   CARBON_TOKEN(EqualEqual)
+                                   CARBON_TOKEN(ExclaimEqual)
+                                   CARBON_TOKEN(Greater)
+                                   CARBON_TOKEN(GreaterEqual)
+                                   CARBON_TOKEN(GreaterGreater)
+                                   CARBON_TOKEN(GreaterGreaterEqual)
+                                   CARBON_TOKEN(Less)
+                                   CARBON_TOKEN(LessEqual)
+                                   CARBON_TOKEN(LessLess)
+                                   CARBON_TOKEN(LessLessEqual)
+                                   CARBON_TOKEN(Minus)
+                                   CARBON_TOKEN(MinusEqual)
+                                   CARBON_TOKEN(Or)
+                                   CARBON_TOKEN(Percent)
+                                   CARBON_TOKEN(PercentEqual)
+                                   CARBON_TOKEN(Pipe)
+                                   CARBON_TOKEN(PipeEqual)
+                                   CARBON_TOKEN(Plus)
+                                   CARBON_TOKEN(PlusEqual)
+                                   CARBON_TOKEN(Slash)
+                                   CARBON_TOKEN(SlashEqual)
+                                   CARBON_TOKEN(Star)
+                                   CARBON_TOKEN(StarEqual))
+
+// clang-format on
 
 // The first operand of a short-circuiting infix operator:
 //     _external_: expression
 //   ShortCircuitOperand
 //   _external_: expression
 // _external_: InfixOperator
-CARBON_PARSE_NODE_KIND_CHILD_COUNT(ShortCircuitOperand, 1)
+CARBON_PARSE_NODE_KIND_CHILD_COUNT(ShortCircuitOperand, 1,
+                                   CARBON_TOKEN(And) CARBON_TOKEN(Or))
 
 // A postfix operator:
 //   _external_: expression
 // PostfixOperator
-CARBON_PARSE_NODE_KIND_CHILD_COUNT(PostfixOperator, 1)
+CARBON_PARSE_NODE_KIND_CHILD_COUNT(PostfixOperator, 1, CARBON_TOKEN(Star))
 
 // `if` expression + `then` + `else`:
 //     _external_: expression
@@ -329,9 +450,11 @@ CARBON_PARSE_NODE_KIND_CHILD_COUNT(PostfixOperator, 1)
 //   IfExpressionThen
 //   _external_: expression
 // IfExpressionElse
-CARBON_PARSE_NODE_KIND_CHILD_COUNT(IfExpressionIf, 1)
-CARBON_PARSE_NODE_KIND_CHILD_COUNT(IfExpressionThen, 1)
-CARBON_PARSE_NODE_KIND_CHILD_COUNT(IfExpressionElse, 3)
+CARBON_PARSE_NODE_KIND_CHILD_COUNT(IfExpressionIf, 1, CARBON_TOKEN(If))
+CARBON_PARSE_NODE_KIND_CHILD_COUNT(IfExpressionThen, 1, CARBON_TOKEN(Then))
+CARBON_PARSE_NODE_KIND_CHILD_COUNT(IfExpressionElse, 3,
+                                   CARBON_TOKEN(Else)
+                                       CARBON_IF_ERROR(CARBON_TOKEN(If)))
 
 // Struct literals, such as `{.a = 0}`:
 //   StructLiteralOrStructTypeLiteralStart
@@ -356,16 +479,21 @@ CARBON_PARSE_NODE_KIND_CHILD_COUNT(IfExpressionElse, 3)
 // When a valid StructFieldType or StructFieldValue cannot be formed, elements
 // may be replaced by StructFieldUnknown, which may have a preceding sibling
 // StructFieldDesignator if one was successfully parsed.
-CARBON_PARSE_NODE_KIND_CHILD_COUNT(StructLiteralOrStructTypeLiteralStart, 0)
-CARBON_PARSE_NODE_KIND_CHILD_COUNT(StructFieldDesignator, 1)
-CARBON_PARSE_NODE_KIND_CHILD_COUNT(StructFieldValue, 2)
-CARBON_PARSE_NODE_KIND_CHILD_COUNT(StructFieldType, 2)
-CARBON_PARSE_NODE_KIND_CHILD_COUNT(StructFieldUnknown, 0)
-CARBON_PARSE_NODE_KIND_CHILD_COUNT(StructComma, 0)
+CARBON_PARSE_NODE_KIND_CHILD_COUNT(StructLiteralOrStructTypeLiteralStart, 0,
+                                   CARBON_TOKEN(OpenCurlyBrace))
+CARBON_PARSE_NODE_KIND_CHILD_COUNT(StructFieldDesignator, 1,
+                                   CARBON_TOKEN(Period))
+CARBON_PARSE_NODE_KIND_CHILD_COUNT(StructFieldValue, 2, CARBON_TOKEN(Equal))
+CARBON_PARSE_NODE_KIND_CHILD_COUNT(StructFieldType, 2, CARBON_TOKEN(Colon))
+CARBON_PARSE_NODE_KIND_CHILD_COUNT(StructFieldUnknown, 0,
+                                   CARBON_IF_ERROR(CARBON_ANY_TOKEN))
+CARBON_PARSE_NODE_KIND_CHILD_COUNT(StructComma, 0, CARBON_TOKEN(Comma))
 CARBON_PARSE_NODE_KIND_BRACKET(StructLiteral,
-                               StructLiteralOrStructTypeLiteralStart)
+                               StructLiteralOrStructTypeLiteralStart,
+                               CARBON_TOKEN(CloseCurlyBrace))
 CARBON_PARSE_NODE_KIND_BRACKET(StructTypeLiteral,
-                               StructLiteralOrStructTypeLiteralStart)
+                               StructLiteralOrStructTypeLiteralStart,
+                               CARBON_TOKEN(CloseCurlyBrace))
 
 // `class`:
 //     ClassIntroducer
@@ -377,10 +505,14 @@ CARBON_PARSE_NODE_KIND_BRACKET(StructTypeLiteral,
 // The above is the structure for a definition; for a declaration,
 // ClassDefinitionStart and later nodes are removed and replaced by
 // ClassDeclaration.
-CARBON_PARSE_NODE_KIND_CHILD_COUNT(ClassIntroducer, 0)
-CARBON_PARSE_NODE_KIND_BRACKET(ClassDefinitionStart, ClassIntroducer)
-CARBON_PARSE_NODE_KIND_BRACKET(ClassDefinition, ClassDefinitionStart)
-CARBON_PARSE_NODE_KIND_BRACKET(ClassDeclaration, ClassIntroducer)
+CARBON_PARSE_NODE_KIND_CHILD_COUNT(ClassIntroducer, 0, CARBON_TOKEN(Class))
+CARBON_PARSE_NODE_KIND_BRACKET(ClassDefinitionStart, ClassIntroducer,
+                               CARBON_TOKEN(OpenCurlyBrace))
+CARBON_PARSE_NODE_KIND_BRACKET(ClassDefinition, ClassDefinitionStart,
+                               CARBON_TOKEN(CloseCurlyBrace))
+CARBON_PARSE_NODE_KIND_BRACKET(ClassDeclaration, ClassIntroducer,
+                               CARBON_TOKEN(Semi)
+                                   CARBON_IF_ERROR(CARBON_TOKEN(Class)))
 
 // `interface`:
 //     InterfaceIntroducer
@@ -392,10 +524,15 @@ CARBON_PARSE_NODE_KIND_BRACKET(ClassDeclaration, ClassIntroducer)
 // The above is the structure for a definition; for a declaration,
 // InterfaceDefinitionStart and later nodes are removed and replaced by
 // InterfaceDeclaration.
-CARBON_PARSE_NODE_KIND_CHILD_COUNT(InterfaceIntroducer, 0)
-CARBON_PARSE_NODE_KIND_BRACKET(InterfaceDefinitionStart, InterfaceIntroducer)
-CARBON_PARSE_NODE_KIND_BRACKET(InterfaceDefinition, InterfaceDefinitionStart)
-CARBON_PARSE_NODE_KIND_BRACKET(InterfaceDeclaration, InterfaceIntroducer)
+CARBON_PARSE_NODE_KIND_CHILD_COUNT(InterfaceIntroducer, 0,
+                                   CARBON_TOKEN(Interface))
+CARBON_PARSE_NODE_KIND_BRACKET(InterfaceDefinitionStart, InterfaceIntroducer,
+                               CARBON_TOKEN(OpenCurlyBrace))
+CARBON_PARSE_NODE_KIND_BRACKET(InterfaceDefinition, InterfaceDefinitionStart,
+                               CARBON_TOKEN(CloseCurlyBrace))
+CARBON_PARSE_NODE_KIND_BRACKET(InterfaceDeclaration, InterfaceIntroducer,
+                               CARBON_TOKEN(Semi)
+                                   CARBON_IF_ERROR(CARBON_TOKEN(Interface)))
 
 // `constraint`:
 //     NamedConstraintIntroducer
@@ -407,22 +544,30 @@ CARBON_PARSE_NODE_KIND_BRACKET(InterfaceDeclaration, InterfaceIntroducer)
 // The above is the structure for a definition; for a declaration,
 // NamedConstraintDefinitionStart and later nodes are removed and replaced by
 // NamedConstraintDeclaration.
-CARBON_PARSE_NODE_KIND_CHILD_COUNT(NamedConstraintIntroducer, 0)
+CARBON_PARSE_NODE_KIND_CHILD_COUNT(NamedConstraintIntroducer, 0,
+                                   CARBON_TOKEN(Constraint))
 CARBON_PARSE_NODE_KIND_BRACKET(NamedConstraintDefinitionStart,
-                               NamedConstraintIntroducer)
+                               NamedConstraintIntroducer,
+                               CARBON_TOKEN(OpenCurlyBrace))
 CARBON_PARSE_NODE_KIND_BRACKET(NamedConstraintDefinition,
-                               NamedConstraintDefinitionStart)
+                               NamedConstraintDefinitionStart,
+                               CARBON_TOKEN(CloseCurlyBrace))
 CARBON_PARSE_NODE_KIND_BRACKET(NamedConstraintDeclaration,
-                               NamedConstraintIntroducer)
+                               NamedConstraintIntroducer, CARBON_TOKEN(Semi))
 
 // The `self` value and `Self` type identifier keywords. Typically of the form
 // `self: Self`:
 //   SelfValueName
 //   SelfTypeNameExpression
 // PatternBinding
-CARBON_PARSE_NODE_KIND_CHILD_COUNT(SelfValueName, 0)
-CARBON_PARSE_NODE_KIND_CHILD_COUNT(SelfTypeNameExpression, 0)
+CARBON_PARSE_NODE_KIND_CHILD_COUNT(SelfValueName, 0,
+                                   CARBON_TOKEN(SelfValueIdentifier))
+CARBON_PARSE_NODE_KIND_CHILD_COUNT(SelfTypeNameExpression, 0,
+                                   CARBON_TOKEN(SelfTypeIdentifier))
 
 #undef CARBON_PARSE_NODE_KIND
 #undef CARBON_PARSE_NODE_KIND_BRACKET
 #undef CARBON_PARSE_NODE_KIND_CHILD_COUNT
+#undef CARBON_TOKEN
+#undef CARBON_ANY_TOKEN
+#undef CARBON_IF_ERROR

+ 6 - 0
toolchain/parse/node_kind.h

@@ -8,6 +8,7 @@
 #include <cstdint>
 
 #include "common/enum_base.h"
+#include "toolchain/lex/token_kind.h"
 
 namespace Carbon::Parse {
 
@@ -44,6 +45,11 @@ class NodeKind : public CARBON_ENUM_BASE(NodeKind) {
 // We expect the parse node kind to fit compactly into 8 bits.
 static_assert(sizeof(NodeKind) == 1, "Kind objects include padding!");
 
+// Validates that a `parse_node_kind` parser node can be generated for a
+// `lex_token_kind` lexer token.
+void CheckNodeMatchesLexerToken(NodeKind parse_node_kind,
+                                Lex::TokenKind lex_token_kind, bool has_error);
+
 }  // namespace Carbon::Parse
 
 #endif  // CARBON_TOOLCHAIN_PARSE_NODE_KIND_H_

+ 1 - 1
toolchain/parse/testdata/function/declaration/fail_missing_name.carbon

@@ -13,7 +13,7 @@ fn ();
 // CHECK:STDOUT:   parse_tree: [
 // CHECK:STDOUT:     {kind: 'FileStart', text: ''},
 // CHECK:STDOUT:       {kind: 'FunctionIntroducer', text: 'fn'},
-// CHECK:STDOUT:       {kind: 'InvalidParse', text: '('},
+// CHECK:STDOUT:       {kind: 'InvalidParse', text: '(', has_error: yes},
 // CHECK:STDOUT:     {kind: 'FunctionDeclaration', text: ';', has_error: yes, subtree_size: 3},
 // CHECK:STDOUT:     {kind: 'FileEnd', text: ''},
 // CHECK:STDOUT:   ]

+ 1 - 1
toolchain/parse/testdata/function/declaration/fail_only_fn_and_semi.carbon

@@ -13,7 +13,7 @@ fn;
 // CHECK:STDOUT:   parse_tree: [
 // CHECK:STDOUT:     {kind: 'FileStart', text: ''},
 // CHECK:STDOUT:       {kind: 'FunctionIntroducer', text: 'fn'},
-// CHECK:STDOUT:       {kind: 'InvalidParse', text: ';'},
+// CHECK:STDOUT:       {kind: 'InvalidParse', text: ';', has_error: yes},
 // CHECK:STDOUT:     {kind: 'FunctionDeclaration', text: ';', has_error: yes, subtree_size: 3},
 // CHECK:STDOUT:     {kind: 'FileEnd', text: ''},
 // CHECK:STDOUT:   ]

+ 1 - 1
toolchain/parse/testdata/function/declaration/fail_repeated_fn_and_semi.carbon

@@ -13,7 +13,7 @@ fn fn;
 // CHECK:STDOUT:   parse_tree: [
 // CHECK:STDOUT:     {kind: 'FileStart', text: ''},
 // CHECK:STDOUT:       {kind: 'FunctionIntroducer', text: 'fn'},
-// CHECK:STDOUT:       {kind: 'InvalidParse', text: 'fn'},
+// CHECK:STDOUT:       {kind: 'InvalidParse', text: 'fn', has_error: yes},
 // CHECK:STDOUT:     {kind: 'FunctionDeclaration', text: ';', has_error: yes, subtree_size: 3},
 // CHECK:STDOUT:     {kind: 'FileEnd', text: ''},
 // CHECK:STDOUT:   ]

+ 1 - 1
toolchain/parse/testdata/function/declaration/fail_skip_indented_newline_until_outdent.carbon

@@ -16,7 +16,7 @@ fn F();
 // CHECK:STDOUT:   parse_tree: [
 // CHECK:STDOUT:     {kind: 'FileStart', text: ''},
 // CHECK:STDOUT:       {kind: 'FunctionIntroducer', text: 'fn'},
-// CHECK:STDOUT:       {kind: 'InvalidParse', text: '('},
+// CHECK:STDOUT:       {kind: 'InvalidParse', text: '(', has_error: yes},
 // CHECK:STDOUT:     {kind: 'FunctionDeclaration', text: 'fn', has_error: yes, subtree_size: 3},
 // CHECK:STDOUT:       {kind: 'FunctionIntroducer', text: 'fn'},
 // CHECK:STDOUT:       {kind: 'Name', text: 'F'},

+ 1 - 1
toolchain/parse/testdata/function/declaration/fail_skip_indented_newline_with_semi.carbon

@@ -16,7 +16,7 @@ fn F();
 // CHECK:STDOUT:   parse_tree: [
 // CHECK:STDOUT:     {kind: 'FileStart', text: ''},
 // CHECK:STDOUT:       {kind: 'FunctionIntroducer', text: 'fn'},
-// CHECK:STDOUT:       {kind: 'InvalidParse', text: '('},
+// CHECK:STDOUT:       {kind: 'InvalidParse', text: '(', has_error: yes},
 // CHECK:STDOUT:     {kind: 'FunctionDeclaration', text: ';', has_error: yes, subtree_size: 3},
 // CHECK:STDOUT:       {kind: 'FunctionIntroducer', text: 'fn'},
 // CHECK:STDOUT:       {kind: 'Name', text: 'F'},

+ 1 - 1
toolchain/parse/testdata/function/declaration/fail_skip_indented_newline_without_semi.carbon

@@ -16,7 +16,7 @@ fn F();
 // CHECK:STDOUT:   parse_tree: [
 // CHECK:STDOUT:     {kind: 'FileStart', text: ''},
 // CHECK:STDOUT:       {kind: 'FunctionIntroducer', text: 'fn'},
-// CHECK:STDOUT:       {kind: 'InvalidParse', text: '('},
+// CHECK:STDOUT:       {kind: 'InvalidParse', text: '(', has_error: yes},
 // CHECK:STDOUT:     {kind: 'FunctionDeclaration', text: 'fn', has_error: yes, subtree_size: 3},
 // CHECK:STDOUT:       {kind: 'FunctionIntroducer', text: 'fn'},
 // CHECK:STDOUT:       {kind: 'Name', text: 'F'},

+ 1 - 1
toolchain/parse/testdata/function/declaration/fail_skip_to_newline_without_semi.carbon

@@ -14,7 +14,7 @@ fn F();
 // CHECK:STDOUT:   parse_tree: [
 // CHECK:STDOUT:     {kind: 'FileStart', text: ''},
 // CHECK:STDOUT:       {kind: 'FunctionIntroducer', text: 'fn'},
-// CHECK:STDOUT:       {kind: 'InvalidParse', text: '('},
+// CHECK:STDOUT:       {kind: 'InvalidParse', text: '(', has_error: yes},
 // CHECK:STDOUT:     {kind: 'FunctionDeclaration', text: 'fn', has_error: yes, subtree_size: 3},
 // CHECK:STDOUT:       {kind: 'FunctionIntroducer', text: 'fn'},
 // CHECK:STDOUT:       {kind: 'Name', text: 'F'},

+ 1 - 1
toolchain/parse/testdata/function/declaration/fail_without_name_and_many_tokens_in_params.carbon

@@ -13,7 +13,7 @@ fn (a tokens c d e f g h i j k l m n o p q r s t u v w x y z);
 // CHECK:STDOUT:   parse_tree: [
 // CHECK:STDOUT:     {kind: 'FileStart', text: ''},
 // CHECK:STDOUT:       {kind: 'FunctionIntroducer', text: 'fn'},
-// CHECK:STDOUT:       {kind: 'InvalidParse', text: '('},
+// CHECK:STDOUT:       {kind: 'InvalidParse', text: '(', has_error: yes},
 // CHECK:STDOUT:     {kind: 'FunctionDeclaration', text: ';', has_error: yes, subtree_size: 3},
 // CHECK:STDOUT:     {kind: 'FileEnd', text: ''},
 // CHECK:STDOUT:   ]

+ 1 - 1
toolchain/parse/testdata/generics/interface/fail_missing_name.carbon

@@ -14,7 +14,7 @@ interface {
 // CHECK:STDOUT:   parse_tree: [
 // CHECK:STDOUT:     {kind: 'FileStart', text: ''},
 // CHECK:STDOUT:       {kind: 'InterfaceIntroducer', text: 'interface'},
-// CHECK:STDOUT:       {kind: 'InvalidParse', text: '{'},
+// CHECK:STDOUT:       {kind: 'InvalidParse', text: '{', has_error: yes},
 // CHECK:STDOUT:     {kind: 'InterfaceDeclaration', text: 'interface', has_error: yes, subtree_size: 3},
 // CHECK:STDOUT:     {kind: 'FileEnd', text: ''},
 // CHECK:STDOUT:   ]

+ 1 - 1
toolchain/parse/testdata/namespace/fail_no_name.carbon

@@ -13,7 +13,7 @@ namespace;
 // CHECK:STDOUT:   parse_tree: [
 // CHECK:STDOUT:     {kind: 'FileStart', text: ''},
 // CHECK:STDOUT:       {kind: 'NamespaceStart', text: 'namespace'},
-// CHECK:STDOUT:       {kind: 'InvalidParse', text: ';'},
+// CHECK:STDOUT:       {kind: 'InvalidParse', text: ';', has_error: yes},
 // CHECK:STDOUT:     {kind: 'Namespace', text: ';', has_error: yes, subtree_size: 3},
 // CHECK:STDOUT:     {kind: 'FileEnd', text: ''},
 // CHECK:STDOUT:   ]