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

Separate `ClassType` from `ClassDeclaration`. (#3329)

Retain the `ClassDeclaration` node to represent a syntactic declaration
of a class (including possibly a declaration of a generic class), but
use a separate SemIR node to represent the class type itself. This
allows us to give the two separate treatment.

The `ClassDeclaration` is still entered into the name lookup table for
its enclosing scope, but when it is named in an expression, the class
type is produced instead. When the class declaration is named in a
declaration name, it can be used to define members of the class, but an
expression that resolves to the class type cannot be used to define
members of the class.

In order to distinguish these cases, use `Name` rather than
`NameExpression` for the left-hand side of a `QualifiedName` parse node.
This removes the only use of the `Expression` form of a declaration
name, so that is also removed.

In the future, `ClassType` will also be used to describe types such as
`Vector(T)`, for which there is no corresponding `ClassDeclaration`.
Richard Smith 2 лет назад
Родитель
Сommit
7d9340880e
29 измененных файлов с 187 добавлено и 106 удалено
  1. 11 9
      toolchain/check/context.cpp
  2. 2 2
      toolchain/check/context.h
  3. 7 23
      toolchain/check/declaration_name_stack.cpp
  4. 3 9
      toolchain/check/declaration_name_stack.h
  5. 14 4
      toolchain/check/handle_class.cpp
  6. 35 26
      toolchain/check/handle_name.cpp
  7. 2 1
      toolchain/check/testdata/array/fail_incomplete_element.carbon
  8. 2 1
      toolchain/check/testdata/class/basic.carbon
  9. 2 1
      toolchain/check/testdata/class/fail_incomplete.carbon
  10. 44 0
      toolchain/check/testdata/class/fail_member_of_let.carbon
  11. 5 4
      toolchain/check/testdata/class/fail_redefinition.carbon
  12. 2 1
      toolchain/check/testdata/class/fail_reorder.carbon
  13. 2 1
      toolchain/check/testdata/class/fail_scope.carbon
  14. 2 1
      toolchain/check/testdata/class/forward_declared.carbon
  15. 3 2
      toolchain/check/testdata/class/redeclaration.carbon
  16. 2 1
      toolchain/check/testdata/class/scope.carbon
  17. 0 1
      toolchain/check/testdata/namespace/nested.carbon
  18. 2 1
      toolchain/check/testdata/struct/fail_nested_incomplete.carbon
  19. 2 1
      toolchain/check/testdata/tuples/fail_nested_incomplete.carbon
  20. 5 0
      toolchain/lower/handle_type.cpp
  21. 10 4
      toolchain/parse/handle_declaration_name_and_params.cpp
  22. 1 1
      toolchain/parse/node_kind.def
  23. 1 1
      toolchain/parse/testdata/namespace/fail_incomplete_name.carbon
  24. 2 2
      toolchain/parse/testdata/namespace/nested.carbon
  25. 6 3
      toolchain/sem_ir/file.cpp
  26. 4 3
      toolchain/sem_ir/file.h
  27. 2 2
      toolchain/sem_ir/formatter.cpp
  28. 1 0
      toolchain/sem_ir/node_kind.def
  29. 13 1
      toolchain/sem_ir/typed_nodes.h

+ 11 - 9
toolchain/check/context.cpp

@@ -89,11 +89,13 @@ auto Context::DiagnoseNameNotFound(Parse::Node parse_node, StringId name_id)
                  semantics_ir_->strings().Get(name_id));
 }
 
-auto Context::NoteIncompleteClass(SemIR::ClassDeclaration class_decl,
+auto Context::NoteIncompleteClass(SemIR::ClassId class_id,
                                   DiagnosticBuilder& builder) -> void {
   CARBON_DIAGNOSTIC(ClassForwardDeclaredHere, Note,
                     "Class was forward declared here.");
-  builder.Note(class_decl.parse_node, ClassForwardDeclaredHere);
+  const auto& class_info = semantics_ir().classes().Get(class_id);
+  builder.Note(semantics_ir().GetNode(class_info.declaration_id).parse_node(),
+               ClassForwardDeclaredHere);
 }
 
 auto Context::AddNameToLookup(Parse::Node name_node, StringId name_id,
@@ -410,12 +412,12 @@ class TypeCompleter {
         }
         break;
 
-      case SemIR::ClassDeclaration::Kind:
+      case SemIR::ClassType::Kind:
         // TODO: Support class definitions and complete class types.
         if (diagnoser_) {
           auto builder = (*diagnoser_)();
-          context_.NoteIncompleteClass(type_node.As<SemIR::ClassDeclaration>(),
-                                       builder);
+          context_.NoteIncompleteClass(
+              type_node.As<SemIR::ClassType>().class_id, builder);
           builder.Emit();
         }
         return false;
@@ -610,6 +612,7 @@ class TypeCompleter {
       case SemIR::BranchIf::Kind:
       case SemIR::BranchWithArg::Kind:
       case SemIR::Call::Kind:
+      case SemIR::ClassDeclaration::Kind:
       case SemIR::Dereference::Kind:
       case SemIR::FunctionDeclaration::Kind:
       case SemIR::InitializeFrom::Kind:
@@ -659,7 +662,7 @@ class TypeCompleter {
         return BuildTupleTypeValueRepresentation(type_id,
                                                  node.As<SemIR::TupleType>());
 
-      case SemIR::ClassDeclaration::Kind:
+      case SemIR::ClassType::Kind:
         // TODO: Support class definitions and complete class types.
         CARBON_FATAL() << "Class types are currently never complete";
 
@@ -760,9 +763,8 @@ static auto ProfileType(Context& semantics_context, SemIR::Node node,
     case SemIR::Builtin::Kind:
       canonical_id.AddInteger(node.As<SemIR::Builtin>().builtin_kind.AsInt());
       break;
-    case SemIR::ClassDeclaration::Kind:
-      canonical_id.AddInteger(
-          node.As<SemIR::ClassDeclaration>().class_id.index);
+    case SemIR::ClassType::Kind:
+      canonical_id.AddInteger(node.As<SemIR::ClassType>().class_id.index);
       break;
     case SemIR::CrossReference::Kind: {
       // TODO: Cross-references should be canonicalized by looking at their

+ 2 - 2
toolchain/check/context.h

@@ -66,8 +66,8 @@ class Context {
   auto DiagnoseNameNotFound(Parse::Node parse_node, StringId name_id) -> void;
 
   // Adds a note to a diagnostic explaining that a class is incomplete.
-  auto NoteIncompleteClass(SemIR::ClassDeclaration class_decl,
-                           DiagnosticBuilder& builder) -> void;
+  auto NoteIncompleteClass(SemIR::ClassId class_id, DiagnosticBuilder& builder)
+      -> void;
 
   // Pushes a new scope onto scope_stack_.
   auto PushScope(SemIR::NameScopeId scope_id = SemIR::NameScopeId::Invalid)

+ 7 - 23
toolchain/check/declaration_name_stack.cpp

@@ -85,26 +85,6 @@ auto DeclarationNameStack::AddNameToLookup(NameContext name_context,
   }
 }
 
-auto DeclarationNameStack::ApplyExpressionQualifier(Parse::Node parse_node,
-                                                    SemIR::NodeId node_id)
-    -> void {
-  auto& name_context = declaration_name_stack_.back();
-  if (CanResolveQualifier(name_context, parse_node)) {
-    if (node_id == SemIR::NodeId::BuiltinError) {
-      // The input node is an error, so error the context.
-      name_context.state = NameContext::State::Error;
-      return;
-    }
-
-    // For other nodes, we expect a regular resolved node, for example a
-    // namespace or generic type. Store it and continue for the target scope
-    // update.
-    name_context.resolved_node_id = node_id;
-
-    UpdateScopeIfNeeded(name_context);
-  }
-}
-
 auto DeclarationNameStack::ApplyNameQualifier(Parse::Node parse_node,
                                               StringId name_id) -> void {
   ApplyNameQualifierTo(declaration_name_stack_.back(), parse_node, name_id);
@@ -193,9 +173,13 @@ auto DeclarationNameStack::CanResolveQualifier(NameContext& name_context,
                           std::string);
         auto builder = context_->emitter().Build(
             name_context.parse_node, QualifiedDeclarationInIncompleteClassScope,
-            context_->semantics_ir().StringifyTypeExpression(
-                name_context.resolved_node_id, true));
-        context_->NoteIncompleteClass(*class_decl, builder);
+            context_->semantics_ir().StringifyType(
+                context_->semantics_ir()
+                    .classes()
+                    .Get(class_decl->class_id)
+                    .self_type_id,
+                true));
+        context_->NoteIncompleteClass(class_decl->class_id, builder);
         builder.Emit();
       } else {
         CARBON_DIAGNOSTIC(

+ 3 - 9
toolchain/check/declaration_name_stack.h

@@ -15,10 +15,9 @@ class Context;
 
 // Provides support and stacking for qualified declaration name handling.
 //
-// A qualified declaration name will consist of entries which are either
-// Identifiers or full expressions. Expressions are expected to resolve to
-// types, such as how `fn Vector(i32).Clear() { ... }` uses the expression
-// `Vector(i32)` to indicate the type whose member is being declared.
+// A qualified declaration name will consist of entries, which are `Name`s
+// optionally followed by generic parameter lists, such as `Vector(T:! type)`
+// in `fn Vector(T:! type).Clear();`, but parameter lists aren't supported yet.
 // Identifiers such as `Clear` will be resolved to a name if possible, for
 // example when declaring things that are in a non-generic type or namespace,
 // and are otherwise marked as an unresolved identifier.
@@ -111,11 +110,6 @@ class DeclarationNameStack {
   auto MakeUnqualifiedName(Parse::Node parse_node, StringId name_id)
       -> NameContext;
 
-  // Applies an expression from the node stack to the top of the declaration
-  // name stack.
-  auto ApplyExpressionQualifier(Parse::Node parse_node, SemIR::NodeId node_id)
-      -> void;
-
   // Applies a Name from the node stack to the top of the declaration name
   // stack.
   auto ApplyNameQualifier(Parse::Node parse_node, StringId name_id) -> void;

+ 14 - 4
toolchain/check/handle_class.cpp

@@ -26,9 +26,8 @@ static auto BuildClassDeclaration(Context& context)
   auto decl_block_id = context.node_block_stack().Pop();
 
   // Add the class declaration.
-  auto class_decl =
-      SemIR::ClassDeclaration{class_keyword, SemIR::TypeId::TypeType,
-                              SemIR::ClassId::Invalid, decl_block_id};
+  auto class_decl = SemIR::ClassDeclaration{
+      class_keyword, SemIR::ClassId::Invalid, decl_block_id};
   auto class_decl_id = context.AddNode(class_decl);
 
   // Check whether this is a redeclaration.
@@ -55,7 +54,18 @@ static auto BuildClassDeclaration(Context& context)
         {.name_id = name_context.state ==
                             DeclarationNameStack::NameContext::State::Unresolved
                         ? name_context.unresolved_name_id
-                        : StringId::Invalid});
+                        : StringId::Invalid,
+         // `.self_type_id` depends on `class_id`, so is set below.
+         .self_type_id = SemIR::TypeId::Invalid,
+         .declaration_id = class_decl_id});
+
+    // Build the `Self` type.
+    auto& class_info =
+        context.semantics_ir().classes().Get(class_decl.class_id);
+    class_info.self_type_id =
+        context.CanonicalizeType(context.AddNode(SemIR::ClassType{
+            class_keyword, context.GetBuiltinType(SemIR::BuiltinKind::TypeType),
+            class_decl.class_id}));
   }
 
   // Write the class ID into the ClassDeclaration.

+ 35 - 26
toolchain/check/handle_name.cpp

@@ -18,7 +18,7 @@ static auto GetAsNameScope(Context& context, SemIR::NodeId base_id)
   if (auto base_as_namespace = base.TryAs<SemIR::Namespace>()) {
     return base_as_namespace->name_scope_id;
   }
-  if (auto base_as_class = base.TryAs<SemIR::ClassDeclaration>()) {
+  if (auto base_as_class = base.TryAs<SemIR::ClassType>()) {
     auto& class_info =
         context.semantics_ir().classes().Get(base_as_class->class_id);
     if (!class_info.scope_id.is_valid()) {
@@ -29,7 +29,7 @@ static auto GetAsNameScope(Context& context, SemIR::NodeId base_id)
           context.semantics_ir().GetNode(base_id).parse_node(),
           QualifiedExpressionInIncompleteClassScope,
           context.semantics_ir().StringifyTypeExpression(base_id, true));
-      context.NoteIncompleteClass(*base_as_class, builder);
+      context.NoteIncompleteClass(base_as_class->class_id, builder);
       builder.Emit();
     }
     return class_info.scope_id;
@@ -125,7 +125,17 @@ auto HandleNameExpression(Context& context, Parse::Node parse_node) -> bool {
       context.LookupName(parse_node, name_id, SemIR::NameScopeId::Invalid,
                          /*print_diagnostics=*/true);
   auto value = context.semantics_ir().GetNode(value_id);
-  // This is a reference to a name binding that has a value and a type.
+
+  // If lookup finds a class declaration, the value is its `Self` type.
+  if (auto class_decl = value.TryAs<SemIR::ClassDeclaration>()) {
+    value_id = context.semantics_ir().GetTypeAllowBuiltinTypes(
+        context.semantics_ir()
+            .classes()
+            .Get(class_decl->class_id)
+            .self_type_id);
+    value = context.semantics_ir().GetNode(value_id);
+  }
+
   CARBON_CHECK(value.kind().value_kind() == SemIR::NodeValueKind::Typed);
   context.AddNodeAndPush(
       parse_node,
@@ -135,34 +145,33 @@ auto HandleNameExpression(Context& context, Parse::Node parse_node) -> bool {
 
 auto HandleQualifiedDeclaration(Context& context, Parse::Node parse_node)
     -> bool {
-  auto pop_and_apply_first_child = [&]() {
-    if (context.parse_tree().node_kind(context.node_stack().PeekParseNode()) !=
-        Parse::NodeKind::QualifiedDeclaration) {
-      // First QualifiedDeclaration in a chain.
-      auto [parse_node1, node_id1] =
-          context.node_stack().PopExpressionWithParseNode();
-      context.declaration_name_stack().ApplyExpressionQualifier(
-          parse_node1, context.FollowNameReferences(node_id1));
+  auto [parse_node2, name_id2] =
+      context.node_stack().PopWithParseNode<Parse::NodeKind::Name>();
+
+  Parse::Node parse_node1 = context.node_stack().PeekParseNode();
+  switch (context.parse_tree().node_kind(parse_node1)) {
+    case Parse::NodeKind::QualifiedDeclaration:
+      // This is the second or subsequent QualifiedDeclaration in a chain.
+      // Nothing to do: the first QualifiedDeclaration remains as a
+      // bracketing node for later QualifiedDeclarations.
+      break;
+
+    case Parse::NodeKind::Name: {
+      // This is the first QualifiedDeclaration in a chain, and starts with a
+      // name.
+      auto name_id = context.node_stack().Pop<Parse::NodeKind::Name>();
+      context.declaration_name_stack().ApplyNameQualifier(parse_node1, name_id);
       // Add the QualifiedDeclaration so that it can be used for bracketing.
       context.node_stack().Push(parse_node);
-    } else {
-      // Nothing to do: the QualifiedDeclaration remains as a bracketing node
-      // for later QualifiedDeclarations.
+      break;
     }
-  };
-
-  Parse::Node parse_node2 = context.node_stack().PeekParseNode();
-  if (context.parse_tree().node_kind(parse_node2) == Parse::NodeKind::Name) {
-    StringId name_id2 = context.node_stack().Pop<Parse::NodeKind::Name>();
-    pop_and_apply_first_child();
-    context.declaration_name_stack().ApplyNameQualifier(parse_node2, name_id2);
-  } else {
-    SemIR::NodeId node_id2 = context.node_stack().PopExpression();
-    pop_and_apply_first_child();
-    context.declaration_name_stack().ApplyExpressionQualifier(parse_node2,
-                                                              node_id2);
+
+    default:
+      CARBON_FATAL() << "Unexpected node kind on left side of qualified "
+                        "declaration name";
   }
 
+  context.declaration_name_stack().ApplyNameQualifier(parse_node2, name_id2);
   return true;
 }
 

+ 2 - 1
toolchain/check/testdata/array/fail_incomplete_element.carbon

@@ -20,7 +20,8 @@ var a: [Incomplete; 1];
 var p: Incomplete* = &a[0];
 
 // CHECK:STDOUT: file "fail_incomplete_element.carbon" {
-// CHECK:STDOUT:   %Incomplete: type = class_declaration @Incomplete, ()
+// CHECK:STDOUT:   class_declaration @Incomplete, ()
+// CHECK:STDOUT:   %Incomplete: type = class_type @Incomplete
 // CHECK:STDOUT:   %Incomplete.ref.loc15: type = name_reference "Incomplete", %Incomplete
 // CHECK:STDOUT:   %.loc15_21: i32 = int_literal 1
 // CHECK:STDOUT:   %.loc15_22: type = array_type %.loc15_21, Incomplete

+ 2 - 1
toolchain/check/testdata/class/basic.carbon

@@ -23,7 +23,8 @@ fn Run() -> i32 {
 }
 
 // CHECK:STDOUT: file "basic.carbon" {
-// CHECK:STDOUT:   %Class: type = class_declaration @Class, ()
+// CHECK:STDOUT:   class_declaration @Class, ()
+// CHECK:STDOUT:   %Class: type = class_type @Class
 // CHECK:STDOUT:   %G: <function> = fn_decl @G
 // CHECK:STDOUT:   %Run: <function> = fn_decl @Run
 // CHECK:STDOUT: }

+ 2 - 1
toolchain/check/testdata/class/fail_incomplete.carbon

@@ -119,7 +119,8 @@ fn CallReturnIncomplete() {
 }
 
 // CHECK:STDOUT: file "fail_incomplete.carbon" {
-// CHECK:STDOUT:   %Class: type = class_declaration @Class, ()
+// CHECK:STDOUT:   class_declaration @Class, ()
+// CHECK:STDOUT:   %Class: type = class_type @Class
 // CHECK:STDOUT:   %.loc15: <function> = fn_decl @.1
 // CHECK:STDOUT:   %CallClassFunction: <function> = fn_decl @CallClassFunction
 // CHECK:STDOUT:   %Class.ref: type = name_reference "Class", %Class

+ 44 - 0
toolchain/check/testdata/class/fail_member_of_let.carbon

@@ -0,0 +1,44 @@
+// 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
+
+class Class {
+  fn F() -> i32;
+}
+
+// TODO: Use `:!` here once it is available.
+let T: type = Class;
+
+// The class name is required to be written in the same way as in the class
+// declaration. An expression that evaluates to the class name is not accepted.
+// CHECK:STDERR: fail_member_of_let.carbon:[[@LINE+6]]:6: ERROR: Declaration qualifiers are only allowed for entities that provide a scope.
+// CHECK:STDERR: fn T.F() {}
+// CHECK:STDERR:      ^
+// CHECK:STDERR: fail_member_of_let.carbon:[[@LINE+3]]:4: Non-scope entity referenced here.
+// CHECK:STDERR: fn T.F() {}
+// CHECK:STDERR:    ^
+fn T.F() {}
+
+// CHECK:STDOUT: file "fail_member_of_let.carbon" {
+// CHECK:STDOUT:   class_declaration @Class, ()
+// CHECK:STDOUT:   %Class: type = class_type @Class
+// CHECK:STDOUT:   %Class.ref: type = name_reference "Class", %Class
+// CHECK:STDOUT:   %T: type = bind_name "T", %Class.ref
+// CHECK:STDOUT:   %.loc22: <function> = fn_decl @.1
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @Class {
+// CHECK:STDOUT:   %F: <function> = fn_decl @F
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .F = %F
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @F() -> i32;
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @.1() {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   return
+// CHECK:STDOUT: }

+ 5 - 4
toolchain/check/testdata/class/fail_redefinition.carbon

@@ -25,8 +25,9 @@ fn Class.G() {}
 fn Class.H() {}
 
 // CHECK:STDOUT: file "fail_redefinition.carbon" {
-// CHECK:STDOUT:   %Class.loc7: type = class_declaration @Class, ()
-// CHECK:STDOUT:   %Class.loc18: type = class_declaration @Class, ()
+// CHECK:STDOUT:   class_declaration @Class, ()
+// CHECK:STDOUT:   %Class: type = class_type @Class
+// CHECK:STDOUT:   class_declaration @Class, ()
 // CHECK:STDOUT:   %F: <function> = fn_decl @F
 // CHECK:STDOUT:   %G: <function> = fn_decl @G
 // CHECK:STDOUT:   %H: <function> = fn_decl @H
@@ -37,8 +38,8 @@ fn Class.H() {}
 // CHECK:STDOUT:   %H: <function> = fn_decl @H
 // CHECK:STDOUT:
 // CHECK:STDOUT: !members:
-// CHECK:STDOUT:   .F = <unexpected noderef 9>
-// CHECK:STDOUT:   .H = <unexpected noderef 10>
+// CHECK:STDOUT:   .F = <unexpected noderef 10>
+// CHECK:STDOUT:   .H = <unexpected noderef 11>
 // CHECK:STDOUT:   .G = %G
 // CHECK:STDOUT: }
 // CHECK:STDOUT:

+ 2 - 1
toolchain/check/testdata/class/fail_reorder.carbon

@@ -23,7 +23,8 @@ class Class {
 }
 
 // CHECK:STDOUT: file "fail_reorder.carbon" {
-// CHECK:STDOUT:   %Class: type = class_declaration @Class, ()
+// CHECK:STDOUT:   class_declaration @Class, ()
+// CHECK:STDOUT:   %Class: type = class_type @Class
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: class @Class {

+ 2 - 1
toolchain/check/testdata/class/fail_scope.carbon

@@ -22,7 +22,8 @@ class Class {
 }
 
 // CHECK:STDOUT: file "fail_scope.carbon" {
-// CHECK:STDOUT:   %Class: type = class_declaration @Class, ()
+// CHECK:STDOUT:   class_declaration @Class, ()
+// CHECK:STDOUT:   %Class: type = class_type @Class
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: class @Class {

+ 2 - 1
toolchain/check/testdata/class/forward_declared.carbon

@@ -9,7 +9,8 @@ class Class;
 fn F(p: Class*) -> Class* { return p; }
 
 // CHECK:STDOUT: file "forward_declared.carbon" {
-// CHECK:STDOUT:   %Class: type = class_declaration @Class, ()
+// CHECK:STDOUT:   class_declaration @Class, ()
+// CHECK:STDOUT:   %Class: type = class_type @Class
 // CHECK:STDOUT:   %F: <function> = fn_decl @F
 // CHECK:STDOUT: }
 // CHECK:STDOUT:

+ 3 - 2
toolchain/check/testdata/class/redeclaration.carbon

@@ -13,8 +13,9 @@ class Class {
 fn Class.F() {}
 
 // CHECK:STDOUT: file "redeclaration.carbon" {
-// CHECK:STDOUT:   %Class.loc7: type = class_declaration @Class, ()
-// CHECK:STDOUT:   %Class.loc9: type = class_declaration @Class, ()
+// CHECK:STDOUT:   class_declaration @Class, ()
+// CHECK:STDOUT:   %Class: type = class_type @Class
+// CHECK:STDOUT:   class_declaration @Class, ()
 // CHECK:STDOUT:   %F: <function> = fn_decl @F
 // CHECK:STDOUT: }
 // CHECK:STDOUT:

+ 2 - 1
toolchain/check/testdata/class/scope.carbon

@@ -19,7 +19,8 @@ fn Run() -> i32 {
 }
 
 // CHECK:STDOUT: file "scope.carbon" {
-// CHECK:STDOUT:   %Class: type = class_declaration @Class, ()
+// CHECK:STDOUT:   class_declaration @Class, ()
+// CHECK:STDOUT:   %Class: type = class_type @Class
 // CHECK:STDOUT:   %F: <function> = fn_decl @F.2
 // CHECK:STDOUT:   %Run: <function> = fn_decl @Run
 // CHECK:STDOUT: }

+ 0 - 1
toolchain/check/testdata/namespace/nested.carbon

@@ -16,7 +16,6 @@ fn Foo.Bar.Baz() {
 
 // CHECK:STDOUT: file "nested.carbon" {
 // CHECK:STDOUT:   %.loc7: <namespace> = namespace {.Bar = %.loc8}
-// CHECK:STDOUT:   %Foo.ref: <namespace> = name_reference "Foo", %.loc7
 // CHECK:STDOUT:   %.loc8: <namespace> = namespace {.Wiz = %Wiz, .Baz = %Baz}
 // CHECK:STDOUT:   %Wiz: <function> = fn_decl @Wiz
 // CHECK:STDOUT:   %Baz: <function> = fn_decl @Baz

+ 2 - 1
toolchain/check/testdata/struct/fail_nested_incomplete.carbon

@@ -20,7 +20,8 @@ var s: {.a: Incomplete};
 var p: Incomplete* = &s.a;
 
 // CHECK:STDOUT: file "fail_nested_incomplete.carbon" {
-// CHECK:STDOUT:   %Incomplete: type = class_declaration @Incomplete, ()
+// CHECK:STDOUT:   class_declaration @Incomplete, ()
+// CHECK:STDOUT:   %Incomplete: type = class_type @Incomplete
 // CHECK:STDOUT:   %Incomplete.ref.loc15: type = name_reference "Incomplete", %Incomplete
 // CHECK:STDOUT:   %.loc15: type = struct_type {.a: Incomplete}
 // CHECK:STDOUT:   %s: ref <error> = var "s"

+ 2 - 1
toolchain/check/testdata/tuples/fail_nested_incomplete.carbon

@@ -20,7 +20,8 @@ var t: (i32, Incomplete);
 var p: Incomplete* = &t[1];
 
 // CHECK:STDOUT: file "fail_nested_incomplete.carbon" {
-// CHECK:STDOUT:   %Incomplete: type = class_declaration @Incomplete, ()
+// CHECK:STDOUT:   class_declaration @Incomplete, ()
+// CHECK:STDOUT:   %Incomplete: type = class_type @Incomplete
 // CHECK:STDOUT:   %Incomplete.ref.loc15: type = name_reference "Incomplete", %Incomplete
 // CHECK:STDOUT:   %.loc15_24.1: type = tuple_type (type, type)
 // CHECK:STDOUT:   %.loc15_24.2: (type, type) = tuple_literal (i32, %Incomplete.ref.loc15)

+ 5 - 0
toolchain/lower/handle_type.cpp

@@ -11,6 +11,11 @@ auto HandleArrayType(FunctionContext& context, SemIR::NodeId node_id,
   context.SetLocal(node_id, context.GetTypeAsValue());
 }
 
+auto HandleClassType(FunctionContext& context, SemIR::NodeId node_id,
+                     SemIR::ClassType /*node*/) -> void {
+  context.SetLocal(node_id, context.GetTypeAsValue());
+}
+
 auto HandleConstType(FunctionContext& context, SemIR::NodeId node_id,
                      SemIR::ConstType /*node*/) -> void {
   context.SetLocal(node_id, context.GetTypeAsValue());

+ 10 - 4
toolchain/parse/handle_declaration_name_and_params.cpp

@@ -17,10 +17,7 @@ static auto HandleDeclarationNameAndParams(Context& context, State after_name)
     context.PushState(state);
 
     if (context.PositionIs(Lex::TokenKind::Period)) {
-      // Because there's a qualifier, we process the first segment as an
-      // expression for simplicity. This just means semantics has one less thing
-      // to handle here.
-      context.AddLeafNode(NodeKind::NameExpression, *identifier);
+      context.AddLeafNode(NodeKind::Name, *identifier);
       state.state = State::PeriodAsDeclaration;
       context.PushState(state);
     } else {
@@ -71,6 +68,15 @@ static auto HandleDeclarationNameAndParamsAfterName(Context& context,
     return;
   }
 
+  // TODO: We can have a parameter list after a name qualifier, regardless of
+  // whether the entity itself permits or requires parameters:
+  //
+  //   fn Class(T:! type).AnotherClass(U:! type).Function(v: T) {}
+  //
+  // We should retain a `DeclarationNameAndParams...` state on the stack in all
+  // cases below to check for a period after a parameter list, which indicates
+  // that we've not finished parsing the declaration name.
+
   if (params == Params::None) {
     return;
   }

+ 1 - 1
toolchain/parse/node_kind.def

@@ -354,7 +354,7 @@ CARBON_PARSE_NODE_KIND_BRACKET(CallExpression, CallExpressionStart,
                                CARBON_TOKEN(CloseParen))
 
 // A qualified declaration, such as `a.b`:
-//   _external_: NameExpression or QualifiedDeclaration
+//   _external_: Name or QualifiedDeclaration
 //   _external_: Name
 // QualifiedDeclaration
 //

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

@@ -13,7 +13,7 @@ namespace Foo.;
 // CHECK:STDOUT:   parse_tree: [
 // CHECK:STDOUT:     {kind: 'FileStart', text: ''},
 // CHECK:STDOUT:       {kind: 'NamespaceStart', text: 'namespace'},
-// CHECK:STDOUT:         {kind: 'NameExpression', text: 'Foo'},
+// CHECK:STDOUT:         {kind: 'Name', text: 'Foo'},
 // CHECK:STDOUT:         {kind: 'Name', text: ';', has_error: yes},
 // CHECK:STDOUT:       {kind: 'QualifiedDeclaration', text: '.', subtree_size: 3},
 // CHECK:STDOUT:     {kind: 'Namespace', text: ';', subtree_size: 5},

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

@@ -18,12 +18,12 @@ fn Foo.Bar.Baz() {
 // CHECK:STDOUT:       {kind: 'Name', text: 'Foo'},
 // CHECK:STDOUT:     {kind: 'Namespace', text: ';', subtree_size: 3},
 // CHECK:STDOUT:       {kind: 'NamespaceStart', text: 'namespace'},
-// CHECK:STDOUT:         {kind: 'NameExpression', text: 'Foo'},
+// CHECK:STDOUT:         {kind: 'Name', text: 'Foo'},
 // CHECK:STDOUT:         {kind: 'Name', text: 'Bar'},
 // CHECK:STDOUT:       {kind: 'QualifiedDeclaration', text: '.', subtree_size: 3},
 // CHECK:STDOUT:     {kind: 'Namespace', text: ';', subtree_size: 5},
 // CHECK:STDOUT:         {kind: 'FunctionIntroducer', text: 'fn'},
-// CHECK:STDOUT:             {kind: 'NameExpression', text: 'Foo'},
+// CHECK:STDOUT:             {kind: 'Name', text: 'Foo'},
 // CHECK:STDOUT:             {kind: 'Name', text: 'Bar'},
 // CHECK:STDOUT:           {kind: 'QualifiedDeclaration', text: '.', subtree_size: 3},
 // CHECK:STDOUT:           {kind: 'Name', text: 'Baz'},

+ 6 - 3
toolchain/sem_ir/file.cpp

@@ -196,6 +196,7 @@ static auto GetTypePrecedence(NodeKind kind) -> int {
     case NameReference::Kind:
     case StructType::Kind:
     case TupleType::Kind:
+    case ClassType::Kind:
       return 0;
     case ConstType::Kind:
       return -1;
@@ -304,9 +305,9 @@ auto File::StringifyTypeExpression(NodeId outer_node_id,
         }
         break;
       }
-      case ClassDeclaration::Kind: {
+      case ClassType::Kind: {
         auto class_name_id =
-            classes().Get(node.As<ClassDeclaration>().class_id).name_id;
+            classes().Get(node.As<ClassType>().class_id).name_id;
         out << strings().Get(class_name_id);
         break;
       }
@@ -405,6 +406,7 @@ auto File::StringifyTypeExpression(NodeId outer_node_id,
       case BranchWithArg::Kind:
       case Builtin::Kind:
       case Call::Kind:
+      case ClassDeclaration::Kind:
       case CrossReference::Kind:
       case Dereference::Kind:
       case FunctionDeclaration::Kind:
@@ -466,6 +468,7 @@ auto GetExpressionCategory(const File& file, NodeId node_id)
       case Branch::Kind:
       case BranchIf::Kind:
       case BranchWithArg::Kind:
+      case ClassDeclaration::Kind:
       case FunctionDeclaration::Kind:
       case Namespace::Kind:
       case NoOp::Kind:
@@ -492,7 +495,7 @@ auto GetExpressionCategory(const File& file, NodeId node_id)
       case BindValue::Kind:
       case BlockArg::Kind:
       case BoolLiteral::Kind:
-      case ClassDeclaration::Kind:
+      case ClassType::Kind:
       case ConstType::Kind:
       case IntegerLiteral::Kind:
       case Parameter::Kind:

+ 4 - 3
toolchain/sem_ir/file.h

@@ -64,14 +64,15 @@ struct Class : public Printable<Class> {
 
   // The class name.
   StringId name_id;
-
+  // The class type, which is the type of `Self` in the class definition.
+  TypeId self_type_id;
+  // The first declaration of the class. This is a ClassDeclaration.
+  NodeId declaration_id = NodeId::Invalid;
   // The definition, if the class has been defined or is currently being
   // defined. This is a ClassDeclaration.
   NodeId definition_id = NodeId::Invalid;
-
   // The class scope.
   NameScopeId scope_id = NameScopeId::Invalid;
-
   // The first block of the class body.
   // TODO: Handle control flow in the class body, such as if-expressions.
   NodeBlockId body_block_id = NodeBlockId::Invalid;

+ 2 - 2
toolchain/sem_ir/formatter.cpp

@@ -424,9 +424,9 @@ class NodeNamer {
                                .name_id);
           continue;
         }
-        case ClassDeclaration::Kind: {
+        case ClassType::Kind: {
           add_node_name_id(semantics_ir_.classes()
-                               .Get(node.As<ClassDeclaration>().class_id)
+                               .Get(node.As<ClassType>().class_id)
                                .name_id);
           continue;
         }

+ 1 - 0
toolchain/sem_ir/node_kind.def

@@ -33,6 +33,7 @@ CARBON_SEM_IR_NODE_KIND(BranchWithArg)
 CARBON_SEM_IR_NODE_KIND(Builtin)
 CARBON_SEM_IR_NODE_KIND(Call)
 CARBON_SEM_IR_NODE_KIND(ClassDeclaration)
+CARBON_SEM_IR_NODE_KIND(ClassType)
 CARBON_SEM_IR_NODE_KIND(ConstType)
 CARBON_SEM_IR_NODE_KIND(CrossReference)
 CARBON_SEM_IR_NODE_KIND(Dereference)

+ 13 - 1
toolchain/sem_ir/typed_nodes.h

@@ -180,13 +180,25 @@ struct ClassDeclaration {
       NodeKind::ClassDeclaration.Define("class_declaration");
 
   Parse::Node parse_node;
-  TypeId type_id;
+  // No type: a class declaration is not itself a value. The name of a class
+  // declaration becomes a class type value.
+  // TODO: For a generic class declaration, the name of the class declaration
+  // should become a parameterized entity name value.
   ClassId class_id;
   // The declaration block, containing the class name's qualifiers and the
   // class's generic parameters.
   NodeBlockId decl_block_id;
 };
 
+struct ClassType {
+  static constexpr auto Kind = NodeKind::ClassType.Define("class_type");
+
+  Parse::Node parse_node;
+  TypeId type_id;
+  ClassId class_id;
+  // TODO: Once we support generic classes, include the class's arguments here.
+};
+
 struct ConstType {
   static constexpr auto Kind = NodeKind::ConstType.Define("const_type");