Просмотр исходного кода

Add parsing for 'fn destroy' (#5045)

Syntax is proposed in #5017, but has already been discussed with leads.
Semantics is left as a TODO.

---------

Co-authored-by: josh11b <15258583+josh11b@users.noreply.github.com>
Jon Ross-Perkins 1 год назад
Родитель
Сommit
c44e688e5d

+ 18 - 1
toolchain/check/handle_name.cpp

@@ -152,6 +152,16 @@ auto HandleParseNode(Context& context, Parse::IdentifierNameExprId node_id)
   return true;
 }
 
+auto HandleParseNode(Context& context,
+                     Parse::KeywordNameNotBeforeParamsId node_id) -> bool {
+  return context.TODO(node_id, "KeywordNameNotBeforeParamsId");
+}
+
+auto HandleParseNode(Context& context, Parse::KeywordNameBeforeParamsId node_id)
+    -> bool {
+  return context.TODO(node_id, "KeywordNameBeforeParamsId");
+}
+
 auto HandleParseNode(Context& context, Parse::BaseNameId node_id) -> bool {
   context.node_stack().Push(node_id, SemIR::NameId::Base);
   return true;
@@ -188,7 +198,8 @@ auto HandleParseNode(Context& context,
 }
 
 auto HandleParseNode(Context& context,
-                     Parse::NameQualifierWithoutParamsId /*node_id*/) -> bool {
+                     Parse::IdentifierNameQualifierWithoutParamsId /*node_id*/)
+    -> bool {
   context.decl_name_stack().ApplyNameQualifier(PopNameComponent(context));
   return true;
 }
@@ -229,6 +240,12 @@ auto HandleParseNode(Context& context, Parse::DesignatorExprId node_id)
   return true;
 }
 
+auto HandleParseNode(Context& context,
+                     Parse::KeywordNameQualifierWithoutParamsId node_id)
+    -> bool {
+  return context.TODO(node_id, "KeywordNameQualifierWithoutParamsId");
+}
+
 auto HandleParseNode(Context& context, Parse::PackageExprId node_id) -> bool {
   AddInstAndPush<SemIR::NameRef>(
       context, node_id,

+ 1 - 2
toolchain/check/name_component.cpp

@@ -57,8 +57,7 @@ auto PopNameComponent(Context& context, SemIR::InstId return_slot_pattern_id)
   }
 
   auto [name_loc_id, name_id] =
-      context.node_stack()
-          .PopWithNodeId<Parse::NodeCategory::NonExprIdentifierName>();
+      context.node_stack().PopWithNodeId<Parse::NodeCategory::NonExprName>();
 
   return {
       .name_loc_id = name_loc_id,

+ 5 - 4
toolchain/check/node_stack.h

@@ -377,9 +377,9 @@ class NodeStack {
                           Id::KindFor<SemIR::InstId>());
     set_id_if_category_is(Parse::NodeCategory::Expr,
                           Id::KindFor<SemIR::InstId>());
-    set_id_if_category_is(Parse::NodeCategory::MemberName |
-                              Parse::NodeCategory::NonExprIdentifierName,
-                          Id::KindFor<SemIR::NameId>());
+    set_id_if_category_is(
+        Parse::NodeCategory::MemberName | Parse::NodeCategory::NonExprName,
+        Id::KindFor<SemIR::NameId>());
     set_id_if_category_is(Parse::NodeCategory::ImplAs,
                           Id::KindFor<SemIR::InstId>());
     set_id_if_category_is(Parse::NodeCategory::Decl |
@@ -469,11 +469,13 @@ class NodeStack {
       case Parse::NodeKind::ForHeader:
       case Parse::NodeKind::ForHeaderStart:
       case Parse::NodeKind::ForIn:
+      case Parse::NodeKind::IdentifierNameQualifierWithoutParams:
       case Parse::NodeKind::IdentifierPackageName:
       case Parse::NodeKind::IfConditionStart:
       case Parse::NodeKind::ImportIntroducer:
       case Parse::NodeKind::IndexExprStart:
       case Parse::NodeKind::InvalidParseStart:
+      case Parse::NodeKind::KeywordNameQualifierWithoutParams:
       case Parse::NodeKind::LibraryIntroducer:
       case Parse::NodeKind::LibrarySpecifier:
       case Parse::NodeKind::MatchCase:
@@ -494,7 +496,6 @@ class NodeStack {
       case Parse::NodeKind::NamedConstraintDefinitionStart:
       case Parse::NodeKind::NamedConstraintIntroducer:
       case Parse::NodeKind::NameQualifierWithParams:
-      case Parse::NodeKind::NameQualifierWithoutParams:
       case Parse::NodeKind::NamespaceStart:
       case Parse::NodeKind::PackageIntroducer:
       case Parse::NodeKind::ParenExprStart:

+ 179 - 0
toolchain/check/testdata/class/no_prelude/destroy.carbon

@@ -0,0 +1,179 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+// AUTOUPDATE
+// TIP: To test this file alone, run:
+// TIP:   bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/check/testdata/class/no_prelude/destroy.carbon
+// TIP: To dump output, run:
+// TIP:   bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/class/no_prelude/destroy.carbon
+
+// --- fail_todo_basic.carbon
+
+library "[[@TEST_NAME]]";
+
+class C {
+  // CHECK:STDERR: fail_todo_basic.carbon:[[@LINE+4]]:6: error: semantics TODO: `KeywordNameBeforeParamsId` [SemanticsTodo]
+  // CHECK:STDERR:   fn destroy[self: Self]();
+  // CHECK:STDERR:      ^~~~~~~
+  // CHECK:STDERR:
+  fn destroy[self: Self]();
+}
+
+// --- fail_todo_addr.carbon
+
+library "[[@TEST_NAME]]";
+
+class C {
+  // CHECK:STDERR: fail_todo_addr.carbon:[[@LINE+4]]:6: error: semantics TODO: `KeywordNameBeforeParamsId` [SemanticsTodo]
+  // CHECK:STDERR:   fn destroy[addr self: Self*]();
+  // CHECK:STDERR:      ^~~~~~~
+  // CHECK:STDERR:
+  fn destroy[addr self: Self*]();
+}
+
+// --- fail_todo_class_function.carbon
+
+library "[[@TEST_NAME]]";
+
+class C {
+  // CHECK:STDERR: fail_todo_class_function.carbon:[[@LINE+4]]:6: error: semantics TODO: `KeywordNameBeforeParamsId` [SemanticsTodo]
+  // CHECK:STDERR:   fn destroy();
+  // CHECK:STDERR:      ^~~~~~~
+  // CHECK:STDERR:
+  fn destroy();
+}
+
+// --- fail_todo_destroy_in_namespace.carbon
+
+library "[[@TEST_NAME]]";
+
+namespace NS;
+
+// CHECK:STDERR: fail_todo_destroy_in_namespace.carbon:[[@LINE+4]]:7: error: semantics TODO: `KeywordNameBeforeParamsId` [SemanticsTodo]
+// CHECK:STDERR: fn NS.destroy();
+// CHECK:STDERR:       ^~~~~~~
+// CHECK:STDERR:
+fn NS.destroy();
+
+// --- fail_todo_missing_params.carbon
+
+library "[[@TEST_NAME]]";
+
+
+class C {
+  // CHECK:STDERR: fail_todo_missing_params.carbon:[[@LINE+4]]:6: error: semantics TODO: `KeywordNameNotBeforeParamsId` [SemanticsTodo]
+  // CHECK:STDERR:   fn destroy;
+  // CHECK:STDERR:      ^~~~~~~
+  // CHECK:STDERR:
+  fn destroy;
+}
+
+fn C.destroy {}
+
+// --- fail_todo_wrong_scope.carbon
+
+library "[[@TEST_NAME]]";
+
+
+// CHECK:STDERR: fail_todo_wrong_scope.carbon:[[@LINE+4]]:4: error: semantics TODO: `KeywordNameBeforeParamsId` [SemanticsTodo]
+// CHECK:STDERR: fn destroy();
+// CHECK:STDERR:    ^~~~~~~
+// CHECK:STDERR:
+fn destroy();
+
+// --- fail_invalid_qualifier.carbon
+
+class C {
+  // CHECK:STDERR: fail_invalid_qualifier.carbon:[[@LINE+4]]:6: error: semantics TODO: `KeywordNameBeforeParamsId` [SemanticsTodo]
+  // CHECK:STDERR:   fn destroy();
+  // CHECK:STDERR:      ^~~~~~~
+  // CHECK:STDERR:
+  fn destroy();
+}
+
+fn C.destroy.destroy() {}
+
+// CHECK:STDOUT: --- fail_todo_basic.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %C: type = class_type @C [concrete]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {}
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @C {
+// CHECK:STDOUT:   complete_type_witness = invalid
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = constants.%C
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- fail_todo_addr.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %C: type = class_type @C [concrete]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {}
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @C {
+// CHECK:STDOUT:   complete_type_witness = invalid
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = constants.%C
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- fail_todo_class_function.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %C: type = class_type @C [concrete]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {}
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @C {
+// CHECK:STDOUT:   complete_type_witness = invalid
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = constants.%C
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- fail_todo_destroy_in_namespace.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {}
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- fail_todo_missing_params.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %C: type = class_type @C [concrete]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {}
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @C {
+// CHECK:STDOUT:   complete_type_witness = invalid
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = constants.%C
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- fail_todo_wrong_scope.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {}
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- fail_invalid_qualifier.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %C: type = class_type @C [concrete]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {}
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @C {
+// CHECK:STDOUT:   complete_type_witness = invalid
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = constants.%C
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 1 - 1
toolchain/lex/token_kind.def

@@ -179,7 +179,7 @@ CARBON_KEYWORD_TOKEN(Const,               "const")
 CARBON_KEYWORD_TOKEN(Continue,            "continue")
 CARBON_KEYWORD_TOKEN(Core,                "Core")
 CARBON_KEYWORD_TOKEN(Default,             "default")
-CARBON_KEYWORD_TOKEN(Destructor,          "destructor")
+CARBON_KEYWORD_TOKEN(Destroy,             "destroy")
 CARBON_KEYWORD_TOKEN(Else,                "else")
 CARBON_KEYWORD_TOKEN(Extend,              "extend")
 CARBON_KEYWORD_TOKEN(Extern,              "extern")

+ 1 - 0
toolchain/parse/BUILD

@@ -110,6 +110,7 @@ cc_library(
         "//toolchain/base:shared_value_stores",
         "//toolchain/diagnostics:diagnostic_emitter",
         "//toolchain/diagnostics:format_providers",
+        "//toolchain/lex:token_index",
         "//toolchain/lex:token_kind",
         "//toolchain/lex:tokenized_buffer",
     ],

+ 52 - 32
toolchain/parse/handle_decl_name_and_params.cpp

@@ -2,67 +2,87 @@
 // Exceptions. See /LICENSE for license information.
 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
 
+#include "toolchain/lex/token_index.h"
 #include "toolchain/parse/context.h"
 #include "toolchain/parse/handle.h"
 
 namespace Carbon::Parse {
 
-auto HandleDeclNameAndParams(Context& context) -> void {
-  auto state = context.PopState();
-
-  auto identifier = context.ConsumeIf(Lex::TokenKind::Identifier);
-  if (!identifier) {
-    Lex::TokenIndex token = *context.position();
-    if (context.tokens().GetKind(token) == Lex::TokenKind::FileEnd) {
-      // The end of file is an unhelpful diagnostic location. Instead, use the
-      // introducer token.
-      token = state.token;
-    }
-    if (state.token == *context.position()) {
-      CARBON_DIAGNOSTIC(ExpectedDeclNameAfterPeriod, Error,
-                        "`.` should be followed by a name");
-      context.emitter().Emit(token, ExpectedDeclNameAfterPeriod);
-    } else {
-      CARBON_DIAGNOSTIC(ExpectedDeclName, Error,
-                        "`{0}` introducer should be followed by a name",
-                        Lex::TokenKind);
-      context.emitter().Emit(token, ExpectedDeclName,
-                             context.tokens().GetKind(state.token));
-    }
-    context.ReturnErrorOnState();
-    context.AddInvalidParse(*context.position());
-    return;
-  }
-
+// Adds a leaf node for the name, and updates the state stack for parameter
+// handling.
+static auto HandleName(Context& context, Context::StateStackEntry state,
+                       Lex::TokenIndex name_token,
+                       NodeKind not_before_params_kind,
+                       NodeKind not_before_params_qualifier_kind,
+                       NodeKind before_params_kind) -> void {
   switch (context.PositionKind()) {
     case Lex::TokenKind::Period:
-      context.AddLeafNode(NodeKind::IdentifierNameNotBeforeParams, *identifier);
-      context.AddNode(NodeKind::NameQualifierWithoutParams,
+      context.AddLeafNode(not_before_params_kind, name_token);
+      context.AddNode(not_before_params_qualifier_kind,
                       context.ConsumeChecked(Lex::TokenKind::Period),
                       state.has_error);
       context.PushState(State::DeclNameAndParams);
       break;
 
     case Lex::TokenKind::OpenSquareBracket:
-      context.AddLeafNode(NodeKind::IdentifierNameBeforeParams, *identifier);
+      context.AddLeafNode(before_params_kind, name_token);
       state.state = State::DeclNameAndParamsAfterImplicit;
       context.PushState(state);
       context.PushState(State::PatternListAsImplicit);
       break;
 
     case Lex::TokenKind::OpenParen:
-      context.AddLeafNode(NodeKind::IdentifierNameBeforeParams, *identifier);
+      context.AddLeafNode(before_params_kind, name_token);
       state.state = State::DeclNameAndParamsAfterParams;
       context.PushState(state);
       context.PushState(State::PatternListAsExplicit);
       break;
 
     default:
-      context.AddLeafNode(NodeKind::IdentifierNameNotBeforeParams, *identifier);
+      context.AddLeafNode(not_before_params_kind, name_token);
       break;
   }
 }
 
+auto HandleDeclNameAndParams(Context& context) -> void {
+  auto state = context.PopState();
+
+  if (auto identifier = context.ConsumeIf(Lex::TokenKind::Identifier)) {
+    HandleName(context, state, *identifier,
+               NodeKind::IdentifierNameNotBeforeParams,
+               NodeKind::IdentifierNameQualifierWithoutParams,
+               NodeKind::IdentifierNameBeforeParams);
+    return;
+  }
+
+  if (auto keyword = context.ConsumeIf(Lex::TokenKind::Destroy)) {
+    HandleName(context, state, *keyword, NodeKind::KeywordNameNotBeforeParams,
+               NodeKind::KeywordNameQualifierWithoutParams,
+               NodeKind::KeywordNameBeforeParams);
+    return;
+  }
+
+  Lex::TokenIndex token = *context.position();
+  if (context.tokens().GetKind(token) == Lex::TokenKind::FileEnd) {
+    // The end of file is an unhelpful diagnostic location. Instead, use the
+    // introducer token.
+    token = state.token;
+  }
+  if (state.token == *context.position()) {
+    CARBON_DIAGNOSTIC(ExpectedDeclNameAfterPeriod, Error,
+                      "`.` should be followed by a name");
+    context.emitter().Emit(token, ExpectedDeclNameAfterPeriod);
+  } else {
+    CARBON_DIAGNOSTIC(ExpectedDeclName, Error,
+                      "`{0}` introducer should be followed by a name",
+                      Lex::TokenKind);
+    context.emitter().Emit(token, ExpectedDeclName,
+                           context.tokens().GetKind(state.token));
+  }
+  context.ReturnErrorOnState();
+  context.AddInvalidParse(*context.position());
+}
+
 auto HandleDeclNameAndParamsAfterImplicit(Context& context) -> void {
   auto state = context.PopState();
 

+ 1 - 1
toolchain/parse/node_category.h

@@ -25,7 +25,7 @@ LLVM_ENABLE_BITMASK_ENUMS_IN_NAMESPACE();
   X(MemberExpr)                 \
   X(MemberName)                 \
   X(Modifier)                   \
-  X(NonExprIdentifierName)      \
+  X(NonExprName)                \
   X(PackageName)                \
   X(Pattern)                    \
   X(Requirement)                \

+ 1 - 2
toolchain/parse/node_ids.h

@@ -79,8 +79,7 @@ using AnyPatternId = NodeIdInCategory<NodeCategory::Pattern>;
 using AnyStatementId =
     NodeIdInCategory<NodeCategory::Statement | NodeCategory::Decl>;
 using AnyRequirementId = NodeIdInCategory<NodeCategory::Requirement>;
-using AnyNonExprIdentifierNameId =
-    NodeIdInCategory<NodeCategory::NonExprIdentifierName>;
+using AnyNonExprNameId = NodeIdInCategory<NodeCategory::NonExprName>;
 using AnyPackageNameId = NodeIdInCategory<NodeCategory::PackageName>;
 
 // NodeId with kind that matches one of the `T::Kind`s.

+ 5 - 1
toolchain/parse/node_kind.def

@@ -98,6 +98,9 @@ CARBON_PARSE_NODE_KIND(EmptyDecl)
 CARBON_PARSE_NODE_KIND(IdentifierNameNotBeforeParams)
 CARBON_PARSE_NODE_KIND(IdentifierNameBeforeParams)
 
+CARBON_PARSE_NODE_KIND(KeywordNameNotBeforeParams)
+CARBON_PARSE_NODE_KIND(KeywordNameBeforeParams)
+
 CARBON_PARSE_NODE_KIND(IdentifierNameExpr)
 
 CARBON_PARSE_NODE_KIND(SelfValueName)
@@ -124,7 +127,8 @@ CARBON_PARSE_NODE_KIND(LibraryDecl)
 CARBON_PARSE_NODE_KIND(LibrarySpecifier)
 
 CARBON_PARSE_NODE_KIND(NameQualifierWithParams)
-CARBON_PARSE_NODE_KIND(NameQualifierWithoutParams)
+CARBON_PARSE_NODE_KIND(IdentifierNameQualifierWithoutParams)
+CARBON_PARSE_NODE_KIND(KeywordNameQualifierWithoutParams)
 
 CARBON_PARSE_NODE_KIND(ExportIntroducer)
 CARBON_PARSE_NODE_KIND(ExportDecl)

+ 1 - 1
toolchain/parse/testdata/alias/basic.carbon

@@ -44,7 +44,7 @@ fn F() {
 // CHECK:STDOUT:     {kind: 'Alias', text: ';', subtree_size: 7},
 // CHECK:STDOUT:       {kind: 'AliasIntroducer', text: 'alias'},
 // CHECK:STDOUT:         {kind: 'IdentifierNameNotBeforeParams', text: 'NS'},
-// CHECK:STDOUT:       {kind: 'NameQualifierWithoutParams', text: '.', subtree_size: 2},
+// CHECK:STDOUT:       {kind: 'IdentifierNameQualifierWithoutParams', text: '.', subtree_size: 2},
 // CHECK:STDOUT:       {kind: 'IdentifierNameNotBeforeParams', text: 'ns'},
 // CHECK:STDOUT:       {kind: 'AliasInitializer', text: '='},
 // CHECK:STDOUT:       {kind: 'IdentifierNameExpr', text: 'foo'},

+ 1 - 1
toolchain/parse/testdata/class/local.carbon

@@ -41,7 +41,7 @@ fn F() {
 // CHECK:STDOUT:       {kind: 'ClassDefinition', text: '}', subtree_size: 15},
 // CHECK:STDOUT:           {kind: 'FunctionIntroducer', text: 'fn'},
 // CHECK:STDOUT:             {kind: 'IdentifierNameNotBeforeParams', text: 'C'},
-// CHECK:STDOUT:           {kind: 'NameQualifierWithoutParams', text: '.', subtree_size: 2},
+// CHECK:STDOUT:           {kind: 'IdentifierNameQualifierWithoutParams', text: '.', subtree_size: 2},
 // CHECK:STDOUT:           {kind: 'IdentifierNameBeforeParams', text: 'H'},
 // CHECK:STDOUT:             {kind: 'ExplicitParamListStart', text: '('},
 // CHECK:STDOUT:           {kind: 'ExplicitParamList', text: ')', subtree_size: 2},

+ 62 - 0
toolchain/parse/testdata/function/declaration.carbon

@@ -36,6 +36,22 @@ fn foo(a: i32, b: i32);
 
 fn foo() -> u32;
 
+// --- keyword.carbon
+
+fn destroy() {}
+
+// --- keyword_no_params.carbon
+
+fn destroy {}
+
+// --- keyword_in_qualified.carbon
+
+fn MyClass.destroy() {}
+
+// --- keyword_decl.carbon
+
+fn destroy.destroy() {}
+
 // --- impl_fn.carbon
 
 impl fn F();
@@ -256,6 +272,52 @@ 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:     {kind: 'FunctionDecl', text: ';', subtree_size: 7},
 // CHECK:STDOUT:     {kind: 'FileEnd', text: ''},
 // CHECK:STDOUT:   ]
+// CHECK:STDOUT: - filename: keyword.carbon
+// CHECK:STDOUT:   parse_tree: [
+// CHECK:STDOUT:     {kind: 'FileStart', text: ''},
+// CHECK:STDOUT:         {kind: 'FunctionIntroducer', text: 'fn'},
+// CHECK:STDOUT:         {kind: 'KeywordNameBeforeParams', text: 'destroy'},
+// CHECK:STDOUT:           {kind: 'ExplicitParamListStart', text: '('},
+// CHECK:STDOUT:         {kind: 'ExplicitParamList', text: ')', subtree_size: 2},
+// CHECK:STDOUT:       {kind: 'FunctionDefinitionStart', text: '{', subtree_size: 5},
+// CHECK:STDOUT:     {kind: 'FunctionDefinition', text: '}', subtree_size: 6},
+// CHECK:STDOUT:     {kind: 'FileEnd', text: ''},
+// CHECK:STDOUT:   ]
+// CHECK:STDOUT: - filename: keyword_no_params.carbon
+// CHECK:STDOUT:   parse_tree: [
+// CHECK:STDOUT:     {kind: 'FileStart', text: ''},
+// CHECK:STDOUT:         {kind: 'FunctionIntroducer', text: 'fn'},
+// CHECK:STDOUT:         {kind: 'KeywordNameNotBeforeParams', text: 'destroy'},
+// CHECK:STDOUT:       {kind: 'FunctionDefinitionStart', text: '{', subtree_size: 3},
+// CHECK:STDOUT:     {kind: 'FunctionDefinition', text: '}', subtree_size: 4},
+// CHECK:STDOUT:     {kind: 'FileEnd', text: ''},
+// CHECK:STDOUT:   ]
+// CHECK:STDOUT: - filename: keyword_in_qualified.carbon
+// CHECK:STDOUT:   parse_tree: [
+// CHECK:STDOUT:     {kind: 'FileStart', text: ''},
+// CHECK:STDOUT:         {kind: 'FunctionIntroducer', text: 'fn'},
+// CHECK:STDOUT:           {kind: 'IdentifierNameNotBeforeParams', text: 'MyClass'},
+// CHECK:STDOUT:         {kind: 'IdentifierNameQualifierWithoutParams', text: '.', subtree_size: 2},
+// CHECK:STDOUT:         {kind: 'KeywordNameBeforeParams', text: 'destroy'},
+// CHECK:STDOUT:           {kind: 'ExplicitParamListStart', text: '('},
+// CHECK:STDOUT:         {kind: 'ExplicitParamList', text: ')', subtree_size: 2},
+// CHECK:STDOUT:       {kind: 'FunctionDefinitionStart', text: '{', subtree_size: 7},
+// CHECK:STDOUT:     {kind: 'FunctionDefinition', text: '}', subtree_size: 8},
+// CHECK:STDOUT:     {kind: 'FileEnd', text: ''},
+// CHECK:STDOUT:   ]
+// CHECK:STDOUT: - filename: keyword_decl.carbon
+// CHECK:STDOUT:   parse_tree: [
+// CHECK:STDOUT:     {kind: 'FileStart', text: ''},
+// CHECK:STDOUT:         {kind: 'FunctionIntroducer', text: 'fn'},
+// CHECK:STDOUT:           {kind: 'KeywordNameNotBeforeParams', text: 'destroy'},
+// CHECK:STDOUT:         {kind: 'KeywordNameQualifierWithoutParams', text: '.', subtree_size: 2},
+// CHECK:STDOUT:         {kind: 'KeywordNameBeforeParams', text: 'destroy'},
+// CHECK:STDOUT:           {kind: 'ExplicitParamListStart', text: '('},
+// CHECK:STDOUT:         {kind: 'ExplicitParamList', text: ')', subtree_size: 2},
+// CHECK:STDOUT:       {kind: 'FunctionDefinitionStart', text: '{', subtree_size: 7},
+// CHECK:STDOUT:     {kind: 'FunctionDefinition', text: '}', subtree_size: 8},
+// CHECK:STDOUT:     {kind: 'FileEnd', text: ''},
+// CHECK:STDOUT:   ]
 // CHECK:STDOUT: - filename: impl_fn.carbon
 // CHECK:STDOUT:   parse_tree: [
 // CHECK:STDOUT:     {kind: 'FileStart', text: ''},

+ 1 - 1
toolchain/parse/testdata/generics/impl/fail_out_of_line_member.carbon

@@ -79,7 +79,7 @@ fn C.(Self as Interface).F() {}
 // CHECK:STDOUT:     {kind: 'ClassDefinition', text: '}', subtree_size: 15},
 // CHECK:STDOUT:       {kind: 'FunctionIntroducer', text: 'fn'},
 // CHECK:STDOUT:         {kind: 'IdentifierNameNotBeforeParams', text: 'C'},
-// CHECK:STDOUT:       {kind: 'NameQualifierWithoutParams', text: '.', subtree_size: 2},
+// CHECK:STDOUT:       {kind: 'IdentifierNameQualifierWithoutParams', text: '.', subtree_size: 2},
 // CHECK:STDOUT:       {kind: 'InvalidParse', text: '(', has_error: yes},
 // CHECK:STDOUT:     {kind: 'FunctionDecl', text: '}', has_error: yes, subtree_size: 5},
 // CHECK:STDOUT:     {kind: 'FileEnd', text: ''},

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

@@ -19,7 +19,7 @@ namespace Foo.;
 // CHECK:STDOUT:     {kind: 'FileStart', text: ''},
 // CHECK:STDOUT:       {kind: 'NamespaceStart', text: 'namespace'},
 // CHECK:STDOUT:         {kind: 'IdentifierNameNotBeforeParams', text: 'Foo'},
-// CHECK:STDOUT:       {kind: 'NameQualifierWithoutParams', text: '.', subtree_size: 2},
+// CHECK:STDOUT:       {kind: 'IdentifierNameQualifierWithoutParams', text: '.', subtree_size: 2},
 // CHECK:STDOUT:       {kind: 'InvalidParse', text: ';', has_error: yes},
 // CHECK:STDOUT:     {kind: 'Namespace', text: ';', has_error: yes, subtree_size: 5},
 // CHECK:STDOUT:     {kind: 'FileEnd', text: ''},

+ 3 - 3
toolchain/parse/testdata/namespace/nested.carbon

@@ -23,14 +23,14 @@ fn Foo.Bar.Baz() {
 // CHECK:STDOUT:     {kind: 'Namespace', text: ';', subtree_size: 3},
 // CHECK:STDOUT:       {kind: 'NamespaceStart', text: 'namespace'},
 // CHECK:STDOUT:         {kind: 'IdentifierNameNotBeforeParams', text: 'Foo'},
-// CHECK:STDOUT:       {kind: 'NameQualifierWithoutParams', text: '.', subtree_size: 2},
+// CHECK:STDOUT:       {kind: 'IdentifierNameQualifierWithoutParams', text: '.', subtree_size: 2},
 // CHECK:STDOUT:       {kind: 'IdentifierNameNotBeforeParams', text: 'Bar'},
 // CHECK:STDOUT:     {kind: 'Namespace', text: ';', subtree_size: 5},
 // CHECK:STDOUT:         {kind: 'FunctionIntroducer', text: 'fn'},
 // CHECK:STDOUT:           {kind: 'IdentifierNameNotBeforeParams', text: 'Foo'},
-// CHECK:STDOUT:         {kind: 'NameQualifierWithoutParams', text: '.', subtree_size: 2},
+// CHECK:STDOUT:         {kind: 'IdentifierNameQualifierWithoutParams', text: '.', subtree_size: 2},
 // CHECK:STDOUT:           {kind: 'IdentifierNameNotBeforeParams', text: 'Bar'},
-// CHECK:STDOUT:         {kind: 'NameQualifierWithoutParams', text: '.', subtree_size: 2},
+// CHECK:STDOUT:         {kind: 'IdentifierNameQualifierWithoutParams', text: '.', subtree_size: 2},
 // CHECK:STDOUT:         {kind: 'IdentifierNameBeforeParams', text: 'Baz'},
 // CHECK:STDOUT:           {kind: 'ExplicitParamListStart', text: '('},
 // CHECK:STDOUT:         {kind: 'ExplicitParamList', text: ')', subtree_size: 2},

+ 2 - 2
toolchain/parse/testdata/packages/export.carbon

@@ -162,7 +162,7 @@ export Foo;
 // CHECK:STDOUT:     {kind: 'PackageDecl', text: ';', subtree_size: 3},
 // CHECK:STDOUT:       {kind: 'ExportIntroducer', text: 'export'},
 // CHECK:STDOUT:         {kind: 'IdentifierNameNotBeforeParams', text: 'Foo'},
-// CHECK:STDOUT:       {kind: 'NameQualifierWithoutParams', text: '.', subtree_size: 2},
+// CHECK:STDOUT:       {kind: 'IdentifierNameQualifierWithoutParams', text: '.', subtree_size: 2},
 // CHECK:STDOUT:       {kind: 'IdentifierNameNotBeforeParams', text: 'Bar'},
 // CHECK:STDOUT:     {kind: 'ExportDecl', text: ';', subtree_size: 5},
 // CHECK:STDOUT:     {kind: 'FileEnd', text: ''},
@@ -230,7 +230,7 @@ export Foo;
 // CHECK:STDOUT:     {kind: 'PackageDecl', text: ';', subtree_size: 3},
 // CHECK:STDOUT:       {kind: 'ExportIntroducer', text: 'export'},
 // CHECK:STDOUT:         {kind: 'IdentifierNameNotBeforeParams', text: 'Foo'},
-// CHECK:STDOUT:       {kind: 'NameQualifierWithoutParams', text: '.', subtree_size: 2},
+// CHECK:STDOUT:       {kind: 'IdentifierNameQualifierWithoutParams', text: '.', subtree_size: 2},
 // CHECK:STDOUT:       {kind: 'InvalidParse', text: ';', has_error: yes},
 // CHECK:STDOUT:     {kind: 'ExportDecl', text: ';', has_error: yes, subtree_size: 5},
 // CHECK:STDOUT:     {kind: 'FileEnd', text: ''},

+ 24 - 8
toolchain/parse/typed_nodes.h

@@ -131,13 +131,19 @@ using EmptyDecl =
 // to be followed by parameters.
 using IdentifierNameBeforeParams =
     LeafNode<NodeKind::IdentifierNameBeforeParams, Lex::IdentifierTokenIndex,
-             NodeCategory::MemberName | NodeCategory::NonExprIdentifierName>;
+             NodeCategory::MemberName | NodeCategory::NonExprName>;
+using KeywordNameBeforeParams =
+    LeafNode<NodeKind::KeywordNameBeforeParams, Lex::TokenIndex,
+             NodeCategory::MemberName | NodeCategory::NonExprName>;
 
 // A name in a non-expression context, such as a declaration, that is known
 // to not be followed by parameters.
 using IdentifierNameNotBeforeParams =
     LeafNode<NodeKind::IdentifierNameNotBeforeParams, Lex::IdentifierTokenIndex,
-             NodeCategory::MemberName | NodeCategory::NonExprIdentifierName>;
+             NodeCategory::MemberName | NodeCategory::NonExprName>;
+using KeywordNameNotBeforeParams =
+    LeafNode<NodeKind::KeywordNameNotBeforeParams, Lex::TokenIndex,
+             NodeCategory::MemberName | NodeCategory::NonExprName>;
 
 // A name in an expression context.
 using IdentifierNameExpr =
@@ -174,21 +180,31 @@ struct NameQualifierWithParams {
 };
 
 // A name qualifier without parameters, such as `A.`.
-struct NameQualifierWithoutParams {
-  static constexpr auto Kind = NodeKind::NameQualifierWithoutParams.Define(
-      {.bracketed_by = IdentifierNameNotBeforeParams::Kind});
+struct IdentifierNameQualifierWithoutParams {
+  static constexpr auto Kind =
+      NodeKind::IdentifierNameQualifierWithoutParams.Define(
+          {.bracketed_by = IdentifierNameNotBeforeParams::Kind});
 
   IdentifierNameNotBeforeParamsId name;
   Lex::PeriodTokenIndex token;
 };
+struct KeywordNameQualifierWithoutParams {
+  static constexpr auto Kind =
+      NodeKind::KeywordNameQualifierWithoutParams.Define(
+          {.bracketed_by = KeywordNameNotBeforeParams::Kind});
+
+  KeywordNameNotBeforeParamsId name;
+  Lex::PeriodTokenIndex token;
+};
 
 // A complete name in a declaration: `A.C(T:! type).F(n: i32)`.
 // Note that this includes the parameters of the entity itself.
 struct DeclName {
   llvm::SmallVector<
-      NodeIdOneOf<NameQualifierWithParams, NameQualifierWithoutParams>>
+      NodeIdOneOf<NameQualifierWithParams, IdentifierNameQualifierWithoutParams,
+                  KeywordNameQualifierWithoutParams>>
       qualifiers;
-  AnyNonExprIdentifierNameId name;
+  AnyNonExprNameId name;
   std::optional<ImplicitParamListId> implicit_params;
   std::optional<ExplicitParamListId> params;
 };
@@ -1157,7 +1173,7 @@ struct ChoiceDefinition {
 
   ChoiceDefinitionStartId signature;
   struct Alternative {
-    AnyNonExprIdentifierNameId name;
+    AnyNonExprNameId name;
     std::optional<ExplicitParamListId> parameters;
   };
   CommaSeparatedList<Alternative, ChoiceAlternativeListCommaId> alternatives;

+ 3 - 3
toolchain/parse/typed_nodes_test.cpp

@@ -290,10 +290,10 @@ Optional [^:]*: found
 Optional [^:]*: begin
 NodeIdForKind error: wrong kind IdentifierNameBeforeParams, expected ImplicitParamList
 Optional [^:]*: missing
-NodeIdInCategory NonExprIdentifierName: kind IdentifierNameBeforeParams consumed
+NodeIdInCategory NonExprName: kind IdentifierNameBeforeParams consumed
 Vector: begin
-NodeIdOneOf NameQualifierWithParams or NameQualifierWithoutParams: NameQualifierWithoutParams consumed
-NodeIdOneOf error: wrong kind AbstractModifier, expected NameQualifierWithParams or NameQualifierWithoutParams
+NodeIdOneOf NameQualifierWithParams or IdentifierNameQualifierWithoutParams or KeywordNameQualifierWithoutParams: IdentifierNameQualifierWithoutParams consumed
+NodeIdOneOf error: wrong kind AbstractModifier, expected NameQualifierWithParams or IdentifierNameQualifierWithoutParams or KeywordNameQualifierWithoutParams
 Vector: end
 Aggregate [^:]*: success
 Vector: begin