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

Add a GenericClassType as the type of the name of a generic class. (#3935)

This is modeled analogously to FunctionType. So far, GenericClassType
has no operations, but eventually values of that type will be callable
like values of FunctionType.

---------

Co-authored-by: Jon Ross-Perkins <jperkins@google.com>
Richard Smith 2 лет назад
Родитель
Сommit
9783d44fed

+ 19 - 5
toolchain/check/context.cpp

@@ -949,6 +949,7 @@ class TypeCompleter {
       }
       case SemIR::AssociatedEntityType::Kind:
       case SemIR::FunctionType::Kind:
+      case SemIR::GenericClassType::Kind:
       case SemIR::InterfaceType::Kind:
       case SemIR::UnboundElementType::Kind: {
         // These types have no runtime operations, so we use an empty value
@@ -1021,6 +1022,7 @@ auto Context::GetTypeIdForTypeConstant(SemIR::ConstantId constant_id)
   return it->second;
 }
 
+// Gets or forms a type_id for a type, given the instruction kind and arguments.
 template <typename InstT, typename... EachArgT>
 static auto GetTypeImpl(Context& context, EachArgT... each_arg)
     -> SemIR::TypeId {
@@ -1030,6 +1032,18 @@ static auto GetTypeImpl(Context& context, EachArgT... each_arg)
                   InstT{SemIR::TypeId::TypeType, each_arg...}));
 }
 
+// Gets or forms a type_id for a type, given the instruction kind and arguments,
+// and completes the type. This should only be used when type completion cannot
+// fail.
+template <typename InstT, typename... EachArgT>
+static auto GetCompleteTypeImpl(Context& context, EachArgT... each_arg)
+    -> SemIR::TypeId {
+  auto type_id = GetTypeImpl<InstT>(context, each_arg...);
+  bool complete = context.TryToCompleteType(type_id);
+  CARBON_CHECK(complete) << "Type completion should not fail";
+  return type_id;
+}
+
 auto Context::GetStructType(SemIR::InstBlockId refs_id) -> SemIR::TypeId {
   return GetTypeImpl<SemIR::StructType>(*this, refs_id);
 }
@@ -1059,11 +1073,11 @@ auto Context::GetBuiltinType(SemIR::BuiltinKind kind) -> SemIR::TypeId {
 }
 
 auto Context::GetFunctionType(SemIR::FunctionId fn_id) -> SemIR::TypeId {
-  auto type_id = GetTypeImpl<SemIR::FunctionType>(*this, fn_id);
-  // To keep client code simpler, complete function types before returning them.
-  bool complete = TryToCompleteType(type_id);
-  CARBON_CHECK(complete) << "Failed to complete function type";
-  return type_id;
+  return GetCompleteTypeImpl<SemIR::FunctionType>(*this, fn_id);
+}
+
+auto Context::GetGenericClassType(SemIR::ClassId class_id) -> SemIR::TypeId {
+  return GetCompleteTypeImpl<SemIR::GenericClassType>(*this, class_id);
 }
 
 auto Context::GetPointerType(SemIR::TypeId pointee_type_id) -> SemIR::TypeId {

+ 5 - 0
toolchain/check/context.h

@@ -241,6 +241,11 @@ class Context {
   // Gets a function type. The returned type will be complete.
   auto GetFunctionType(SemIR::FunctionId fn_id) -> SemIR::TypeId;
 
+  // Gets a generic class type, which is the type of a name of a generic class,
+  // such as the type of `Vector` given `class Vector(T:! type)`. The returned
+  // type will be complete.
+  auto GetGenericClassType(SemIR::ClassId class_id) -> SemIR::TypeId;
+
   // Returns a pointer type whose pointee type is `pointee_type_id`.
   auto GetPointerType(SemIR::TypeId pointee_type_id) -> SemIR::TypeId;
 

+ 16 - 6
toolchain/check/eval.cpp

@@ -907,6 +907,12 @@ auto TryEvalInst(Context& context, SemIR::InstId inst_id, SemIR::Inst inst)
       return RebuildIfFieldsAreConstant(context, inst,
                                         &SemIR::BoundMethod::object_id,
                                         &SemIR::BoundMethod::function_id);
+    case SemIR::ClassType::Kind:
+      // TODO: Look at generic arguments once they're modeled.
+      return MakeConstantResult(context, inst, Phase::Template);
+    case SemIR::InterfaceType::Kind:
+      // TODO: Look at generic arguments once they're modeled.
+      return MakeConstantResult(context, inst, Phase::Template);
     case SemIR::InterfaceWitness::Kind:
       return RebuildIfFieldsAreConstant(context, inst,
                                         &SemIR::InterfaceWitness::elements_id);
@@ -966,6 +972,7 @@ auto TryEvalInst(Context& context, SemIR::InstId inst_id, SemIR::Inst inst)
     case SemIR::AssociatedEntity::Kind:
     case SemIR::Builtin::Kind:
     case SemIR::FunctionType::Kind:
+    case SemIR::GenericClassType::Kind:
       // Builtins are always template constants.
       return MakeConstantResult(context, inst, Phase::Template);
 
@@ -977,7 +984,15 @@ auto TryEvalInst(Context& context, SemIR::InstId inst_id, SemIR::Inst inst)
     }
 
     case CARBON_KIND(SemIR::ClassDecl class_decl): {
-      // TODO: Once classes have generic arguments, handle them.
+      // If the class has generic arguments, we don't produce a class type, but
+      // a callable whose return value is a class type.
+      if (context.classes().Get(class_decl.class_id).is_generic()) {
+        return MakeConstantResult(
+            context,
+            SemIR::StructValue{class_decl.type_id, SemIR::InstBlockId::Empty},
+            Phase::Template);
+      }
+      // A non-generic class declaration evaluates to the class type.
       return MakeConstantResult(
           context,
           SemIR::ClassType{SemIR::TypeId::TypeType, class_decl.class_id},
@@ -992,11 +1007,6 @@ auto TryEvalInst(Context& context, SemIR::InstId inst_id, SemIR::Inst inst)
           Phase::Template);
     }
 
-    case SemIR::ClassType::Kind:
-    case SemIR::InterfaceType::Kind:
-      CARBON_FATAL() << inst.kind()
-                     << " is only created during corresponding Decl handling.";
-
     // These cases are treated as being the unique canonical definition of the
     // corresponding constant value.
     // TODO: This doesn't properly handle redeclarations. Consider adding a

+ 23 - 4
toolchain/check/handle_class.cpp

@@ -7,6 +7,7 @@
 #include "toolchain/check/context.h"
 #include "toolchain/check/convert.h"
 #include "toolchain/check/decl_name_stack.h"
+#include "toolchain/check/eval.h"
 #include "toolchain/check/merge.h"
 #include "toolchain/check/modifiers.h"
 #include "toolchain/sem_ir/ids.h"
@@ -70,11 +71,17 @@ static auto MergeOrAddName(Context& context, Parse::AnyClassDeclId node_id,
         break;
       }
 
-      // Use the type to get the ID.
-      if (auto class_type = context.insts().TryGetAs<SemIR::ClassType>(
-              context.constant_values().Get(prev_id).inst_id())) {
+      // Use the constant value to get the ID.
+      auto decl_value =
+          context.insts().Get(context.constant_values().Get(prev_id).inst_id());
+      if (auto class_type = decl_value.TryAs<SemIR::ClassType>()) {
         prev_class_id = class_type->class_id;
         prev_import_ir_id = import_ir_inst.ir_id;
+      } else if (auto generic_class_type =
+                     context.types().TryGetAs<SemIR::GenericClassType>(
+                         decl_value.type_id())) {
+        prev_class_id = generic_class_type->class_id;
+        prev_import_ir_id = import_ir_inst.ir_id;
       }
       break;
     }
@@ -164,6 +171,9 @@ static auto BuildClassDecl(Context& context, Parse::AnyClassDeclId node_id,
     // was an error in the qualifier, we will have lost track of the class name
     // here. We should keep track of it even if the name is invalid.
     class_decl.class_id = context.classes().Add(class_info);
+    if (class_info.is_generic()) {
+      class_decl.type_id = context.GetGenericClassType(class_decl.class_id);
+    }
   }
 
   // Write the class ID into the ClassDecl.
@@ -171,8 +181,17 @@ static auto BuildClassDecl(Context& context, Parse::AnyClassDeclId node_id,
 
   if (is_new_class) {
     // Build the `Self` type using the resulting type constant.
+    // TODO: Form this as part of building the definition, not as part of the
+    // declaration.
     auto& class_info = context.classes().Get(class_decl.class_id);
-    class_info.self_type_id = context.GetTypeIdForTypeInst(class_decl_id);
+    if (class_info.is_generic()) {
+      // TODO: Pass in the generic arguments once we can represent them.
+      class_info.self_type_id = context.GetTypeIdForTypeConstant(TryEvalInst(
+          context, SemIR::InstId::Invalid,
+          SemIR::ClassType{SemIR::TypeId::TypeType, class_decl.class_id}));
+    } else {
+      class_info.self_type_id = context.GetTypeIdForTypeInst(class_decl_id);
+    }
   }
 
   return {class_decl.class_id, class_decl_id};

+ 54 - 17
toolchain/check/import_ref.cpp

@@ -558,6 +558,9 @@ class ImportRefResolver {
       case CARBON_KIND(SemIR::FunctionType inst): {
         return TryResolveTypedInst(inst);
       }
+      case CARBON_KIND(SemIR::GenericClassType inst): {
+        return TryResolveTypedInst(inst);
+      }
       case CARBON_KIND(SemIR::ImportRefLoaded inst): {
         return TryResolveTypedInst(inst, inst_id);
       }
@@ -684,7 +687,7 @@ class ImportRefResolver {
   // complete declaration, because things such as `Self` may refer back to the
   // type.
   auto MakeIncompleteClass(const SemIR::Class& import_class)
-      -> SemIR::ConstantId {
+      -> std::pair<SemIR::ClassId, SemIR::ConstantId> {
     auto class_decl =
         SemIR::ClassDecl{SemIR::TypeId::TypeType, SemIR::ClassId::Invalid,
                          SemIR::InstBlockId::Empty};
@@ -694,24 +697,32 @@ class ImportRefResolver {
     // incomplete type so that any references have something to point at.
     class_decl.class_id = context_.classes().Add({
         .name_id = GetLocalNameId(import_class.name_id),
-        // Set in the second pass once we've imported them.
+        // These are set in the second pass once we've imported them. Import
+        // enough of the parameter lists that we know whether this class is a
+        // generic class and can build the right constant value for it.
+        // TODO: Add a better way to represent a generic `Class` prior to
+        // importing the parameters.
         .enclosing_scope_id = SemIR::NameScopeId::Invalid,
-        .implicit_param_refs_id = SemIR::InstBlockId::Invalid,
-        .param_refs_id = SemIR::InstBlockId::Invalid,
-        // `.self_type_id` depends on the ClassType, so is set below.
+        .implicit_param_refs_id = import_class.implicit_param_refs_id.is_valid()
+                                      ? SemIR::InstBlockId::Empty
+                                      : SemIR::InstBlockId::Invalid,
+        .param_refs_id = import_class.param_refs_id.is_valid()
+                             ? SemIR::InstBlockId::Empty
+                             : SemIR::InstBlockId::Invalid,
         .self_type_id = SemIR::TypeId::Invalid,
+        // These fields can be set immediately.
         .decl_id = class_decl_id,
         .inheritance_kind = import_class.inheritance_kind,
     });
 
+    if (import_class.is_generic()) {
+      class_decl.type_id = context_.GetGenericClassType(class_decl.class_id);
+    }
+
     // Write the class ID into the ClassDecl.
     context_.ReplaceInstBeforeConstantUse(class_decl_id, class_decl);
     auto self_const_id = context_.constant_values().Get(class_decl_id);
-
-    // Build the `Self` type using the resulting type constant.
-    auto& class_info = context_.classes().Get(class_decl.class_id);
-    class_info.self_type_id = context_.GetTypeIdForTypeConstant(self_const_id);
-    return self_const_id;
+    return {class_decl.class_id, self_const_id};
   }
 
   // Fills out the class definition for an incomplete class.
@@ -755,10 +766,23 @@ class ImportRefResolver {
                            SemIR::ConstantId class_const_id) -> ResolveResult {
     const auto& import_class = import_ir_.classes().Get(inst.class_id);
 
-    // On the first pass, create a forward declaration of the class for any
-    // recursive references.
+    SemIR::ClassId class_id = SemIR::ClassId::Invalid;
     if (!class_const_id.is_valid()) {
-      class_const_id = MakeIncompleteClass(import_class);
+      // On the first pass, create a forward declaration of the class for any
+      // recursive references.
+      std::tie(class_id, class_const_id) = MakeIncompleteClass(import_class);
+    } else {
+      // On the second pass, compute the class ID from the constant value of the
+      // declaration.
+      auto class_const_inst = context_.insts().Get(class_const_id.inst_id());
+      if (auto class_type = class_const_inst.TryAs<SemIR::ClassType>()) {
+        class_id = class_type->class_id;
+      } else {
+        auto generic_class_type =
+            context_.types().GetAs<SemIR::GenericClassType>(
+                class_const_inst.type_id());
+        class_id = generic_class_type.class_id;
+      }
     }
 
     // Load constants for the definition.
@@ -770,6 +794,7 @@ class ImportRefResolver {
         GetLocalParamConstantIds(import_class.implicit_param_refs_id);
     llvm::SmallVector<SemIR::ConstantId> param_const_ids =
         GetLocalParamConstantIds(import_class.param_refs_id);
+    auto self_const_id = GetLocalConstantId(import_class.self_type_id);
     auto object_repr_const_id =
         import_class.object_repr_id.is_valid()
             ? GetLocalConstantId(import_class.object_repr_id)
@@ -782,15 +807,13 @@ class ImportRefResolver {
       return ResolveResult::Retry(class_const_id);
     }
 
-    auto& new_class = context_.classes().Get(
-        context_.insts()
-            .GetAs<SemIR::ClassType>(class_const_id.inst_id())
-            .class_id);
+    auto& new_class = context_.classes().Get(class_id);
     new_class.enclosing_scope_id = enclosing_scope_id;
     new_class.implicit_param_refs_id = GetLocalParamRefsId(
         import_class.implicit_param_refs_id, implicit_param_const_ids);
     new_class.param_refs_id =
         GetLocalParamRefsId(import_class.param_refs_id, param_const_ids);
+    new_class.self_type_id = context_.GetTypeIdForTypeConstant(self_const_id);
 
     if (import_class.is_defined()) {
       AddClassDefinition(import_class, new_class, object_repr_const_id,
@@ -916,6 +939,20 @@ class ImportRefResolver {
     return {context_.types().GetConstantId(fn_val.type_id())};
   }
 
+  auto TryResolveTypedInst(SemIR::GenericClassType inst) -> ResolveResult {
+    auto initial_work = work_stack_.size();
+    CARBON_CHECK(inst.type_id == SemIR::TypeId::TypeType);
+    auto class_const_id =
+        GetLocalConstantId(import_ir_.classes().Get(inst.class_id).decl_id);
+    if (HasNewWork(initial_work)) {
+      return ResolveResult::Retry();
+    }
+    auto class_val = context_.insts().Get(class_const_id.inst_id());
+    CARBON_CHECK(
+        context_.types().Is<SemIR::GenericClassType>(class_val.type_id()));
+    return {context_.types().GetConstantId(class_val.type_id())};
+  }
+
   auto TryResolveTypedInst(SemIR::ImportRefLoaded /*inst*/,
                            SemIR::InstId inst_id) -> ResolveResult {
     auto initial_work = work_stack_.size();

+ 12 - 10
toolchain/check/testdata/class/fail_todo_generic_method.carbon

@@ -25,14 +25,16 @@ fn Class(T:! type).F[self: Self](n: T) {}
 // CHECK:STDOUT:
 // CHECK:STDOUT: constants {
 // CHECK:STDOUT:   %T: type = bind_symbolic_name T 0 [symbolic]
-// CHECK:STDOUT:   %Class: type = class_type @Class [template]
-// CHECK:STDOUT:   %.1: type = unbound_element_type Class, T [symbolic]
+// CHECK:STDOUT:   %Class.1: type = generic_class_type @Class [template]
+// CHECK:STDOUT:   %.1: type = tuple_type () [template]
+// CHECK:STDOUT:   %struct.1: Class = struct_value () [template]
+// CHECK:STDOUT:   %Class.2: type = class_type @Class [template]
+// CHECK:STDOUT:   %.2: type = unbound_element_type Class, T [symbolic]
 // CHECK:STDOUT:   %F: type = fn_type @F [template]
-// CHECK:STDOUT:   %.2: type = tuple_type () [template]
-// CHECK:STDOUT:   %struct.1: F = struct_value () [template]
+// CHECK:STDOUT:   %struct.2: F = struct_value () [template]
 // CHECK:STDOUT:   %.3: type = struct_type {.a: T} [symbolic]
 // CHECK:STDOUT:   %.4: type = fn_type @.1 [template]
-// CHECK:STDOUT:   %struct.2: <invalid> = struct_value () [template]
+// CHECK:STDOUT:   %struct.3: <invalid> = struct_value () [template]
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: file {
@@ -41,11 +43,11 @@ fn Class(T:! type).F[self: Self](n: T) {}
 // CHECK:STDOUT:     .Class = %Class.decl
 // CHECK:STDOUT:   }
 // CHECK:STDOUT:   %Core: <namespace> = namespace [template] {}
-// CHECK:STDOUT:   %Class.decl: type = class_decl @Class [template = constants.%Class] {
+// CHECK:STDOUT:   %Class.decl: Class = class_decl @Class [template = constants.%struct.1] {
 // CHECK:STDOUT:     %T.loc7_13.1: type = param T
 // CHECK:STDOUT:     %T.loc7_13.2: type = bind_symbolic_name T 0, %T.loc7_13.1 [symbolic = constants.%T]
 // CHECK:STDOUT:   }
-// CHECK:STDOUT:   %.decl: <invalid> = fn_decl @.1 [template = constants.%struct.2] {
+// CHECK:STDOUT:   %.decl: <invalid> = fn_decl @.1 [template = constants.%struct.3] {
 // CHECK:STDOUT:     %T.loc22_10.1: type = param T
 // CHECK:STDOUT:     @.1.%T: type = bind_symbolic_name T 0, %T.loc22_10.1 [symbolic = constants.%T]
 // CHECK:STDOUT:   }
@@ -54,8 +56,8 @@ fn Class(T:! type).F[self: Self](n: T) {}
 // CHECK:STDOUT: class @Class {
 // CHECK:STDOUT:   %T.ref.loc8: type = name_ref T, file.%T.loc7_13.2 [symbolic = constants.%T]
 // CHECK:STDOUT:   %.loc8: <unbound element of class Class> = field_decl a, element0 [template]
-// CHECK:STDOUT:   %F.decl: F = fn_decl @F [template = constants.%struct.1] {
-// CHECK:STDOUT:     %Self.ref: type = name_ref Self, constants.%Class [template = constants.%Class]
+// CHECK:STDOUT:   %F.decl: F = fn_decl @F [template = constants.%struct.2] {
+// CHECK:STDOUT:     %Self.ref: type = name_ref Self, constants.%Class.2 [template = constants.%Class.2]
 // CHECK:STDOUT:     %self.loc9_8.1: Class = param self
 // CHECK:STDOUT:     %self.loc9_8.2: Class = bind_name self, %self.loc9_8.1
 // CHECK:STDOUT:     %T.ref.loc9: type = name_ref T, file.%T.loc7_13.2 [symbolic = constants.%T]
@@ -64,7 +66,7 @@ fn Class(T:! type).F[self: Self](n: T) {}
 // CHECK:STDOUT:   }
 // CHECK:STDOUT:
 // CHECK:STDOUT: !members:
-// CHECK:STDOUT:   .Self = constants.%Class
+// CHECK:STDOUT:   .Self = constants.%Class.2
 // CHECK:STDOUT:   .a = %.loc8
 // CHECK:STDOUT:   .F = %F.decl
 // CHECK:STDOUT: }

+ 5 - 2
toolchain/check/testdata/class/generic.carbon

@@ -9,7 +9,10 @@ class C[]();
 // CHECK:STDOUT: --- generic.carbon
 // CHECK:STDOUT:
 // CHECK:STDOUT: constants {
-// CHECK:STDOUT:   %C: type = class_type @C [template]
+// CHECK:STDOUT:   %C.1: type = generic_class_type @C [template]
+// CHECK:STDOUT:   %.1: type = tuple_type () [template]
+// CHECK:STDOUT:   %struct: C = struct_value () [template]
+// CHECK:STDOUT:   %C.2: type = class_type @C [template]
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: file {
@@ -18,7 +21,7 @@ class C[]();
 // CHECK:STDOUT:     .C = %C.decl
 // CHECK:STDOUT:   }
 // CHECK:STDOUT:   %Core: <namespace> = namespace [template] {}
-// CHECK:STDOUT:   %C.decl: type = class_decl @C [template = constants.%C] {}
+// CHECK:STDOUT:   %C.decl: C = class_decl @C [template = constants.%struct] {}
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: class @C;

+ 16 - 14
toolchain/check/testdata/class/generic/basic.carbon

@@ -22,14 +22,16 @@ class Class(T:! type) {
 // CHECK:STDOUT:
 // CHECK:STDOUT: constants {
 // CHECK:STDOUT:   %T: type = bind_symbolic_name T 0 [symbolic]
-// CHECK:STDOUT:   %Class: type = class_type @Class [template]
-// CHECK:STDOUT:   %.1: type = ptr_type Class [template]
-// CHECK:STDOUT:   %.2: type = ptr_type T [symbolic]
+// CHECK:STDOUT:   %Class.1: type = generic_class_type @Class [template]
+// CHECK:STDOUT:   %.1: type = tuple_type () [template]
+// CHECK:STDOUT:   %struct.1: Class = struct_value () [template]
+// CHECK:STDOUT:   %Class.2: type = class_type @Class [template]
+// CHECK:STDOUT:   %.2: type = ptr_type Class [template]
+// CHECK:STDOUT:   %.3: type = ptr_type T [symbolic]
 // CHECK:STDOUT:   %GetAddr: type = fn_type @GetAddr [template]
-// CHECK:STDOUT:   %.3: type = tuple_type () [template]
-// CHECK:STDOUT:   %struct.1: GetAddr = struct_value () [template]
+// CHECK:STDOUT:   %struct.2: GetAddr = struct_value () [template]
 // CHECK:STDOUT:   %GetValue: type = fn_type @GetValue [template]
-// CHECK:STDOUT:   %struct.2: GetValue = struct_value () [template]
+// CHECK:STDOUT:   %struct.3: GetValue = struct_value () [template]
 // CHECK:STDOUT:   %.4: type = unbound_element_type Class, T [symbolic]
 // CHECK:STDOUT:   %.5: type = struct_type {.k: T} [symbolic]
 // CHECK:STDOUT:   %.6: type = ptr_type {.k: T} [symbolic]
@@ -41,25 +43,25 @@ class Class(T:! type) {
 // CHECK:STDOUT:     .Class = %Class.decl
 // CHECK:STDOUT:   }
 // CHECK:STDOUT:   %Core: <namespace> = namespace [template] {}
-// CHECK:STDOUT:   %Class.decl: type = class_decl @Class [template = constants.%Class] {
+// CHECK:STDOUT:   %Class.decl: Class = class_decl @Class [template = constants.%struct.1] {
 // CHECK:STDOUT:     %T.loc7_13.1: type = param T
 // CHECK:STDOUT:     %T.loc7_13.2: type = bind_symbolic_name T 0, %T.loc7_13.1 [symbolic = constants.%T]
 // CHECK:STDOUT:   }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: class @Class {
-// CHECK:STDOUT:   %GetAddr.decl: GetAddr = fn_decl @GetAddr [template = constants.%struct.1] {
-// CHECK:STDOUT:     %Self.ref.loc8: type = name_ref Self, constants.%Class [template = constants.%Class]
-// CHECK:STDOUT:     %.loc8_29: type = ptr_type Class [template = constants.%.1]
+// CHECK:STDOUT:   %GetAddr.decl: GetAddr = fn_decl @GetAddr [template = constants.%struct.2] {
+// CHECK:STDOUT:     %Self.ref.loc8: type = name_ref Self, constants.%Class.2 [template = constants.%Class.2]
+// CHECK:STDOUT:     %.loc8_29: type = ptr_type Class [template = constants.%.2]
 // CHECK:STDOUT:     %self.loc8_19.1: Class* = param self
 // CHECK:STDOUT:     %self.loc8_19.3: Class* = bind_name self, %self.loc8_19.1
 // CHECK:STDOUT:     %.loc8_14: Class* = addr_pattern %self.loc8_19.3
 // CHECK:STDOUT:     %T.ref.loc8: type = name_ref T, file.%T.loc7_13.2 [symbolic = constants.%T]
-// CHECK:STDOUT:     %.loc8_38: type = ptr_type T [symbolic = constants.%.2]
+// CHECK:STDOUT:     %.loc8_38: type = ptr_type T [symbolic = constants.%.3]
 // CHECK:STDOUT:     %return.var.loc8: ref T* = var <return slot>
 // CHECK:STDOUT:   }
-// CHECK:STDOUT:   %GetValue.decl: GetValue = fn_decl @GetValue [template = constants.%struct.2] {
-// CHECK:STDOUT:     %Self.ref.loc13: type = name_ref Self, constants.%Class [template = constants.%Class]
+// CHECK:STDOUT:   %GetValue.decl: GetValue = fn_decl @GetValue [template = constants.%struct.3] {
+// CHECK:STDOUT:     %Self.ref.loc13: type = name_ref Self, constants.%Class.2 [template = constants.%Class.2]
 // CHECK:STDOUT:     %self.loc13_15.1: Class = param self
 // CHECK:STDOUT:     %self.loc13_15.2: Class = bind_name self, %self.loc13_15.1
 // CHECK:STDOUT:     %T.ref.loc13: type = name_ref T, file.%T.loc7_13.2 [symbolic = constants.%T]
@@ -69,7 +71,7 @@ class Class(T:! type) {
 // CHECK:STDOUT:   %.loc17: <unbound element of class Class> = field_decl k, element0 [template]
 // CHECK:STDOUT:
 // CHECK:STDOUT: !members:
-// CHECK:STDOUT:   .Self = constants.%Class
+// CHECK:STDOUT:   .Self = constants.%Class.2
 // CHECK:STDOUT:   .GetAddr = %GetAddr.decl
 // CHECK:STDOUT:   .GetValue = %GetValue.decl
 // CHECK:STDOUT:   .k = %.loc17

+ 10 - 8
toolchain/check/testdata/class/generic/fail_todo_member_out_of_line.carbon

@@ -26,13 +26,15 @@ fn Class(T:! type).F(n: T) -> T {
 // CHECK:STDOUT:
 // CHECK:STDOUT: constants {
 // CHECK:STDOUT:   %T: type = bind_symbolic_name T 0 [symbolic]
-// CHECK:STDOUT:   %Class: type = class_type @Class [template]
-// CHECK:STDOUT:   %F: type = fn_type @F [template]
+// CHECK:STDOUT:   %Class.1: type = generic_class_type @Class [template]
 // CHECK:STDOUT:   %.1: type = tuple_type () [template]
-// CHECK:STDOUT:   %struct.1: F = struct_value () [template]
+// CHECK:STDOUT:   %struct.1: Class = struct_value () [template]
+// CHECK:STDOUT:   %Class.2: type = class_type @Class [template]
+// CHECK:STDOUT:   %F: type = fn_type @F [template]
+// CHECK:STDOUT:   %struct.2: F = struct_value () [template]
 // CHECK:STDOUT:   %.2: type = struct_type {} [template]
 // CHECK:STDOUT:   %.3: type = fn_type @.1 [template]
-// CHECK:STDOUT:   %struct.2: <invalid> = struct_value () [template]
+// CHECK:STDOUT:   %struct.3: <invalid> = struct_value () [template]
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: file {
@@ -41,18 +43,18 @@ fn Class(T:! type).F(n: T) -> T {
 // CHECK:STDOUT:     .Class = %Class.decl
 // CHECK:STDOUT:   }
 // CHECK:STDOUT:   %Core: <namespace> = namespace [template] {}
-// CHECK:STDOUT:   %Class.decl: type = class_decl @Class [template = constants.%Class] {
+// CHECK:STDOUT:   %Class.decl: Class = class_decl @Class [template = constants.%struct.1] {
 // CHECK:STDOUT:     %T.loc7_13.1: type = param T
 // CHECK:STDOUT:     %T.loc7_13.2: type = bind_symbolic_name T 0, %T.loc7_13.1 [symbolic = constants.%T]
 // CHECK:STDOUT:   }
-// CHECK:STDOUT:   %.decl: <invalid> = fn_decl @.1 [template = constants.%struct.2] {
+// CHECK:STDOUT:   %.decl: <invalid> = fn_decl @.1 [template = constants.%struct.3] {
 // CHECK:STDOUT:     %T.loc21_10.1: type = param T
 // CHECK:STDOUT:     @.1.%T: type = bind_symbolic_name T 0, %T.loc21_10.1 [symbolic = constants.%T]
 // CHECK:STDOUT:   }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: class @Class {
-// CHECK:STDOUT:   %F.decl: F = fn_decl @F [template = constants.%struct.1] {
+// CHECK:STDOUT:   %F.decl: F = fn_decl @F [template = constants.%struct.2] {
 // CHECK:STDOUT:     %T.ref.loc8_11: type = name_ref T, file.%T.loc7_13.2 [symbolic = constants.%T]
 // CHECK:STDOUT:     %n.loc8_8.1: T = param n
 // CHECK:STDOUT:     %n.loc8_8.2: T = bind_name n, %n.loc8_8.1
@@ -61,7 +63,7 @@ fn Class(T:! type).F(n: T) -> T {
 // CHECK:STDOUT:   }
 // CHECK:STDOUT:
 // CHECK:STDOUT: !members:
-// CHECK:STDOUT:   .Self = constants.%Class
+// CHECK:STDOUT:   .Self = constants.%Class.2
 // CHECK:STDOUT:   .F = %F.decl
 // CHECK:STDOUT: }
 // CHECK:STDOUT:

+ 17 - 15
toolchain/check/testdata/class/generic/fail_todo_use.carbon

@@ -14,7 +14,7 @@ class Class(T:! type) {
 
 // TODO: The following should work.
 fn Run() -> i32 {
-  // CHECK:STDERR: fail_todo_use.carbon:[[@LINE+3]]:10: ERROR: Value of type `type` is not callable.
+  // CHECK:STDERR: fail_todo_use.carbon:[[@LINE+3]]:10: ERROR: Value of type `Class` is not callable.
   // CHECK:STDERR:   var v: Class(i32) = {.k = 0};
   // CHECK:STDERR:          ^~~~~~
   var v: Class(i32) = {.k = 0};
@@ -25,17 +25,19 @@ fn Run() -> i32 {
 // CHECK:STDOUT:
 // CHECK:STDOUT: constants {
 // CHECK:STDOUT:   %T: type = bind_symbolic_name T 0 [symbolic]
-// CHECK:STDOUT:   %Class: type = class_type @Class [template]
-// CHECK:STDOUT:   %.1: type = ptr_type Class [template]
-// CHECK:STDOUT:   %.2: type = ptr_type T [symbolic]
+// CHECK:STDOUT:   %Class.1: type = generic_class_type @Class [template]
+// CHECK:STDOUT:   %.1: type = tuple_type () [template]
+// CHECK:STDOUT:   %struct.1: Class = struct_value () [template]
+// CHECK:STDOUT:   %Class.2: type = class_type @Class [template]
+// CHECK:STDOUT:   %.2: type = ptr_type Class [template]
+// CHECK:STDOUT:   %.3: type = ptr_type T [symbolic]
 // CHECK:STDOUT:   %Get: type = fn_type @Get [template]
-// CHECK:STDOUT:   %.3: type = tuple_type () [template]
-// CHECK:STDOUT:   %struct.1: Get = struct_value () [template]
+// CHECK:STDOUT:   %struct.2: Get = struct_value () [template]
 // CHECK:STDOUT:   %.4: type = unbound_element_type Class, T [symbolic]
 // CHECK:STDOUT:   %.5: type = struct_type {.k: T} [symbolic]
 // CHECK:STDOUT:   %.6: type = ptr_type {.k: T} [symbolic]
 // CHECK:STDOUT:   %Run: type = fn_type @Run [template]
-// CHECK:STDOUT:   %struct.2: Run = struct_value () [template]
+// CHECK:STDOUT:   %struct.3: Run = struct_value () [template]
 // CHECK:STDOUT:   %.7: i32 = int_literal 0 [template]
 // CHECK:STDOUT:   %.8: type = struct_type {.k: i32} [template]
 // CHECK:STDOUT: }
@@ -47,31 +49,31 @@ fn Run() -> i32 {
 // CHECK:STDOUT:     .Run = %Run.decl
 // CHECK:STDOUT:   }
 // CHECK:STDOUT:   %Core: <namespace> = namespace [template] {}
-// CHECK:STDOUT:   %Class.decl: type = class_decl @Class [template = constants.%Class] {
+// CHECK:STDOUT:   %Class.decl: Class = class_decl @Class [template = constants.%struct.1] {
 // CHECK:STDOUT:     %T.loc7_13.1: type = param T
 // CHECK:STDOUT:     %T.loc7_13.2: type = bind_symbolic_name T 0, %T.loc7_13.1 [symbolic = constants.%T]
 // CHECK:STDOUT:   }
-// CHECK:STDOUT:   %Run.decl: Run = fn_decl @Run [template = constants.%struct.2] {
+// CHECK:STDOUT:   %Run.decl: Run = fn_decl @Run [template = constants.%struct.3] {
 // CHECK:STDOUT:     @Run.%return: ref i32 = var <return slot>
 // CHECK:STDOUT:   }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: class @Class {
-// CHECK:STDOUT:   %Get.decl: Get = fn_decl @Get [template = constants.%struct.1] {
-// CHECK:STDOUT:     %Self.ref: type = name_ref Self, constants.%Class [template = constants.%Class]
-// CHECK:STDOUT:     %.loc8_25: type = ptr_type Class [template = constants.%.1]
+// CHECK:STDOUT:   %Get.decl: Get = fn_decl @Get [template = constants.%struct.2] {
+// CHECK:STDOUT:     %Self.ref: type = name_ref Self, constants.%Class.2 [template = constants.%Class.2]
+// CHECK:STDOUT:     %.loc8_25: type = ptr_type Class [template = constants.%.2]
 // CHECK:STDOUT:     %self.loc8_15.1: Class* = param self
 // CHECK:STDOUT:     %self.loc8_15.3: Class* = bind_name self, %self.loc8_15.1
 // CHECK:STDOUT:     %.loc8_10: Class* = addr_pattern %self.loc8_15.3
 // CHECK:STDOUT:     %T.ref.loc8: type = name_ref T, file.%T.loc7_13.2 [symbolic = constants.%T]
-// CHECK:STDOUT:     %.loc8_34: type = ptr_type T [symbolic = constants.%.2]
+// CHECK:STDOUT:     %.loc8_34: type = ptr_type T [symbolic = constants.%.3]
 // CHECK:STDOUT:     %return.var: ref T* = var <return slot>
 // CHECK:STDOUT:   }
 // CHECK:STDOUT:   %T.ref.loc12: type = name_ref T, file.%T.loc7_13.2 [symbolic = constants.%T]
 // CHECK:STDOUT:   %.loc12: <unbound element of class Class> = field_decl k, element0 [template]
 // CHECK:STDOUT:
 // CHECK:STDOUT: !members:
-// CHECK:STDOUT:   .Self = constants.%Class
+// CHECK:STDOUT:   .Self = constants.%Class.2
 // CHECK:STDOUT:   .Get = %Get.decl
 // CHECK:STDOUT:   .k = %.loc12
 // CHECK:STDOUT: }
@@ -88,7 +90,7 @@ fn Run() -> i32 {
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @Run() -> i32 {
 // CHECK:STDOUT: !entry:
-// CHECK:STDOUT:   %Class.ref: type = name_ref Class, file.%Class.decl [template = constants.%Class]
+// CHECK:STDOUT:   %Class.ref: Class = name_ref Class, file.%Class.decl [template = constants.%struct.1]
 // CHECK:STDOUT:   %v.var: ref <error> = var v
 // CHECK:STDOUT:   %v: ref <error> = bind_name v, %v.var
 // CHECK:STDOUT:   %.loc20_29: i32 = int_literal 0 [template = constants.%.7]

+ 26 - 16
toolchain/check/testdata/class/generic/import.carbon

@@ -38,7 +38,10 @@ class Class(U:! type) {
 // CHECK:STDOUT:
 // CHECK:STDOUT: constants {
 // CHECK:STDOUT:   %T: type = bind_symbolic_name T 0 [symbolic]
-// CHECK:STDOUT:   %Class: type = class_type @Class [template]
+// CHECK:STDOUT:   %Class.1: type = generic_class_type @Class [template]
+// CHECK:STDOUT:   %.1: type = tuple_type () [template]
+// CHECK:STDOUT:   %struct: Class = struct_value () [template]
+// CHECK:STDOUT:   %Class.2: type = class_type @Class [template]
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: file {
@@ -47,7 +50,7 @@ class Class(U:! type) {
 // CHECK:STDOUT:     .Class = %Class.decl
 // CHECK:STDOUT:   }
 // CHECK:STDOUT:   %Core: <namespace> = namespace [template] {}
-// CHECK:STDOUT:   %Class.decl: type = class_decl @Class [template = constants.%Class] {
+// CHECK:STDOUT:   %Class.decl: Class = class_decl @Class [template = constants.%struct] {
 // CHECK:STDOUT:     %T.loc4_13.1: type = param T
 // CHECK:STDOUT:     %T.loc4_13.2: type = bind_symbolic_name T 0, %T.loc4_13.1 [symbolic = constants.%T]
 // CHECK:STDOUT:   }
@@ -58,10 +61,13 @@ class Class(U:! type) {
 // CHECK:STDOUT: --- foo.impl.carbon
 // CHECK:STDOUT:
 // CHECK:STDOUT: constants {
-// CHECK:STDOUT:   %T: type = bind_symbolic_name T 0, <unexpected instref inst+9> [symbolic]
-// CHECK:STDOUT:   %Class: type = class_type @Class [template]
-// CHECK:STDOUT:   %.1: type = unbound_element_type Class, T [symbolic]
-// CHECK:STDOUT:   %.2: type = struct_type {.x: T} [symbolic]
+// CHECK:STDOUT:   %T: type = bind_symbolic_name T 0, <unexpected instref inst+11> [symbolic]
+// CHECK:STDOUT:   %Class: type = generic_class_type @Class [template]
+// CHECK:STDOUT:   %.1: type = tuple_type () [template]
+// CHECK:STDOUT:   %struct.1: Class = struct_value () [template]
+// CHECK:STDOUT:   %struct.2: type = struct_value () [template]
+// CHECK:STDOUT:   %.2: type = unbound_element_type <cannot stringify inst+9>, T [symbolic]
+// CHECK:STDOUT:   %.3: type = struct_type {.x: T} [symbolic]
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: file {
@@ -69,9 +75,9 @@ class Class(U:! type) {
 // CHECK:STDOUT:     .Core = %Core
 // CHECK:STDOUT:     .Class = %Class.decl
 // CHECK:STDOUT:   }
-// CHECK:STDOUT:   %import_ref: type = import_ref ir0, inst+5, loaded [template = constants.%Class]
+// CHECK:STDOUT:   %import_ref: Class = import_ref ir0, inst+5, loaded [template = constants.%struct.1]
 // CHECK:STDOUT:   %Core: <namespace> = namespace [template] {}
-// CHECK:STDOUT:   %Class.decl: type = class_decl @Class [template = constants.%Class] {
+// CHECK:STDOUT:   %Class.decl: type = class_decl @Class [template = constants.%struct.2] {
 // CHECK:STDOUT:     %T.loc4_13.1: type = param T
 // CHECK:STDOUT:     %T.loc4_13.2: type = bind_symbolic_name T 0, %T.loc4_13.1 [symbolic = constants.%T]
 // CHECK:STDOUT:   }
@@ -79,10 +85,10 @@ class Class(U:! type) {
 // CHECK:STDOUT:
 // CHECK:STDOUT: class @Class {
 // CHECK:STDOUT:   %T.ref: type = name_ref T, file.%T.loc4_13.2 [symbolic = constants.%T]
-// CHECK:STDOUT:   %.loc5: <unbound element of class Class> = field_decl x, element0 [template]
+// CHECK:STDOUT:   %.loc5: <unbound element of class <cannot stringify inst+9>> = field_decl x, element0 [template]
 // CHECK:STDOUT:
 // CHECK:STDOUT: !members:
-// CHECK:STDOUT:   .Self = constants.%Class
+// CHECK:STDOUT:   .Self = constants.%struct.1
 // CHECK:STDOUT:   .x = %.loc5
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
@@ -90,9 +96,13 @@ class Class(U:! type) {
 // CHECK:STDOUT:
 // CHECK:STDOUT: constants {
 // CHECK:STDOUT:   %U: type = bind_symbolic_name U 0 [symbolic]
-// CHECK:STDOUT:   %Class: type = class_type @Class [template]
-// CHECK:STDOUT:   %T: type = bind_symbolic_name T 0, <unexpected instref inst+10> [symbolic]
-// CHECK:STDOUT:   %.1: type = class_type @.1 [template]
+// CHECK:STDOUT:   %Class: type = generic_class_type @Class [template]
+// CHECK:STDOUT:   %.1: type = tuple_type () [template]
+// CHECK:STDOUT:   %struct.1: Class = struct_value () [template]
+// CHECK:STDOUT:   %T: type = bind_symbolic_name T 0, <unexpected instref inst+12> [symbolic]
+// CHECK:STDOUT:   %.2: type = generic_class_type @.1 [template]
+// CHECK:STDOUT:   %struct.2: <invalid> = struct_value () [template]
+// CHECK:STDOUT:   %.3: type = class_type @.1 [template]
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: file {
@@ -100,9 +110,9 @@ class Class(U:! type) {
 // CHECK:STDOUT:     .Class = %import_ref
 // CHECK:STDOUT:     .Core = %Core
 // CHECK:STDOUT:   }
-// CHECK:STDOUT:   %import_ref: type = import_ref ir0, inst+5, loaded [template = constants.%Class]
+// CHECK:STDOUT:   %import_ref: Class = import_ref ir0, inst+5, loaded [template = constants.%struct.1]
 // CHECK:STDOUT:   %Core: <namespace> = namespace [template] {}
-// CHECK:STDOUT:   %.decl: type = class_decl @.1 [template = constants.%.1] {
+// CHECK:STDOUT:   %.decl: <invalid> = class_decl @.1 [template = constants.%struct.2] {
 // CHECK:STDOUT:     %U.loc9_13.1: type = param U
 // CHECK:STDOUT:     %U.loc9_13.2: type = bind_symbolic_name U 0, %U.loc9_13.1 [symbolic = constants.%U]
 // CHECK:STDOUT:   }
@@ -115,7 +125,7 @@ class Class(U:! type) {
 // CHECK:STDOUT:   %.loc13: <error> = field_decl x, element0 [template]
 // CHECK:STDOUT:
 // CHECK:STDOUT: !members:
-// CHECK:STDOUT:   .Self = constants.%.1
+// CHECK:STDOUT:   .Self = constants.%.3
 // CHECK:STDOUT:   .x = %.loc13
 // CHECK:STDOUT: }
 // CHECK:STDOUT:

+ 8 - 6
toolchain/check/testdata/class/generic/member_inline.carbon

@@ -14,10 +14,12 @@ class Class(T:! type) {
 // CHECK:STDOUT:
 // CHECK:STDOUT: constants {
 // CHECK:STDOUT:   %T: type = bind_symbolic_name T 0 [symbolic]
-// CHECK:STDOUT:   %Class: type = class_type @Class [template]
-// CHECK:STDOUT:   %F: type = fn_type @F [template]
+// CHECK:STDOUT:   %Class.1: type = generic_class_type @Class [template]
 // CHECK:STDOUT:   %.1: type = tuple_type () [template]
-// CHECK:STDOUT:   %struct: F = struct_value () [template]
+// CHECK:STDOUT:   %struct.1: Class = struct_value () [template]
+// CHECK:STDOUT:   %Class.2: type = class_type @Class [template]
+// CHECK:STDOUT:   %F: type = fn_type @F [template]
+// CHECK:STDOUT:   %struct.2: F = struct_value () [template]
 // CHECK:STDOUT:   %.2: type = struct_type {} [template]
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
@@ -27,14 +29,14 @@ class Class(T:! type) {
 // CHECK:STDOUT:     .Class = %Class.decl
 // CHECK:STDOUT:   }
 // CHECK:STDOUT:   %Core: <namespace> = namespace [template] {}
-// CHECK:STDOUT:   %Class.decl: type = class_decl @Class [template = constants.%Class] {
+// CHECK:STDOUT:   %Class.decl: Class = class_decl @Class [template = constants.%struct.1] {
 // CHECK:STDOUT:     %T.loc7_13.1: type = param T
 // CHECK:STDOUT:     %T.loc7_13.2: type = bind_symbolic_name T 0, %T.loc7_13.1 [symbolic = constants.%T]
 // CHECK:STDOUT:   }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: class @Class {
-// CHECK:STDOUT:   %F.decl: F = fn_decl @F [template = constants.%struct] {
+// CHECK:STDOUT:   %F.decl: F = fn_decl @F [template = constants.%struct.2] {
 // CHECK:STDOUT:     %T.ref.loc8_11: type = name_ref T, file.%T.loc7_13.2 [symbolic = constants.%T]
 // CHECK:STDOUT:     %n.loc8_8.1: T = param n
 // CHECK:STDOUT:     %n.loc8_8.2: T = bind_name n, %n.loc8_8.1
@@ -43,7 +45,7 @@ class Class(T:! type) {
 // CHECK:STDOUT:   }
 // CHECK:STDOUT:
 // CHECK:STDOUT: !members:
-// CHECK:STDOUT:   .Self = constants.%Class
+// CHECK:STDOUT:   .Self = constants.%Class.2
 // CHECK:STDOUT:   .F = %F.decl
 // CHECK:STDOUT: }
 // CHECK:STDOUT:

+ 60 - 33
toolchain/check/testdata/class/generic/redeclare.carbon

@@ -86,8 +86,12 @@ class E(U:! type) {}
 // CHECK:STDOUT:
 // CHECK:STDOUT: constants {
 // CHECK:STDOUT:   %T: type = bind_symbolic_name T 0 [symbolic]
-// CHECK:STDOUT:   %Generic: type = class_type @Generic [template]
-// CHECK:STDOUT:   %.1: type = struct_type {} [template]
+// CHECK:STDOUT:   %Generic.1: type = generic_class_type @Generic [template]
+// CHECK:STDOUT:   %.1: type = tuple_type () [template]
+// CHECK:STDOUT:   %struct.1: Generic = struct_value () [template]
+// CHECK:STDOUT:   %Generic.2: type = class_type @Generic [template]
+// CHECK:STDOUT:   %struct.2: type = struct_value () [template]
+// CHECK:STDOUT:   %.2: type = struct_type {} [template]
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: file {
@@ -96,11 +100,11 @@ class E(U:! type) {}
 // CHECK:STDOUT:     .Generic = %Generic.decl.loc4
 // CHECK:STDOUT:   }
 // CHECK:STDOUT:   %Core: <namespace> = namespace [template] {}
-// CHECK:STDOUT:   %Generic.decl.loc4: type = class_decl @Generic [template = constants.%Generic] {
+// CHECK:STDOUT:   %Generic.decl.loc4: Generic = class_decl @Generic [template = constants.%struct.1] {
 // CHECK:STDOUT:     %T.loc4_15.1: type = param T
 // CHECK:STDOUT:     %T.loc4_15.2: type = bind_symbolic_name T 0, %T.loc4_15.1 [symbolic = constants.%T]
 // CHECK:STDOUT:   }
-// CHECK:STDOUT:   %Generic.decl.loc6: type = class_decl @Generic [template = constants.%Generic] {
+// CHECK:STDOUT:   %Generic.decl.loc6: type = class_decl @Generic [template = constants.%struct.2] {
 // CHECK:STDOUT:     %T.loc6_15.1: type = param T
 // CHECK:STDOUT:     %T.loc6_15.2: type = bind_symbolic_name T 0, %T.loc6_15.1 [symbolic = constants.%T]
 // CHECK:STDOUT:   }
@@ -108,7 +112,7 @@ class E(U:! type) {}
 // CHECK:STDOUT:
 // CHECK:STDOUT: class @Generic {
 // CHECK:STDOUT: !members:
-// CHECK:STDOUT:   .Self = constants.%Generic
+// CHECK:STDOUT:   .Self = constants.%Generic.2
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: --- fail_mismatch_param_list.carbon
@@ -116,8 +120,11 @@ class E(U:! type) {}
 // CHECK:STDOUT: constants {
 // CHECK:STDOUT:   %A: type = class_type @A [template]
 // CHECK:STDOUT:   %T: type = bind_symbolic_name T 0 [symbolic]
-// CHECK:STDOUT:   %.1: type = class_type @.1 [template]
-// CHECK:STDOUT:   %.2: type = struct_type {} [template]
+// CHECK:STDOUT:   %.1: type = generic_class_type @.1 [template]
+// CHECK:STDOUT:   %.2: type = tuple_type () [template]
+// CHECK:STDOUT:   %struct: <invalid> = struct_value () [template]
+// CHECK:STDOUT:   %.3: type = class_type @.1 [template]
+// CHECK:STDOUT:   %.4: type = struct_type {} [template]
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: file {
@@ -127,7 +134,7 @@ class E(U:! type) {}
 // CHECK:STDOUT:   }
 // CHECK:STDOUT:   %Core: <namespace> = namespace [template] {}
 // CHECK:STDOUT:   %A.decl: type = class_decl @A [template = constants.%A] {}
-// CHECK:STDOUT:   %.decl: type = class_decl @.1 [template = constants.%.1] {
+// CHECK:STDOUT:   %.decl: <invalid> = class_decl @.1 [template = constants.%struct] {
 // CHECK:STDOUT:     %T.loc12_9.1: type = param T
 // CHECK:STDOUT:     %T.loc12_9.2: type = bind_symbolic_name T 0, %T.loc12_9.1 [symbolic = constants.%T]
 // CHECK:STDOUT:   }
@@ -137,18 +144,23 @@ class E(U:! type) {}
 // CHECK:STDOUT:
 // CHECK:STDOUT: class @.1 {
 // CHECK:STDOUT: !members:
-// CHECK:STDOUT:   .Self = constants.%.1
+// CHECK:STDOUT:   .Self = constants.%.3
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: --- fail_mismatch_implicit_param_list.carbon
 // CHECK:STDOUT:
 // CHECK:STDOUT: constants {
 // CHECK:STDOUT:   %N.1: i32 = bind_symbolic_name N 0 [symbolic]
-// CHECK:STDOUT:   %B: type = class_type @B [template]
+// CHECK:STDOUT:   %B.1: type = generic_class_type @B [template]
+// CHECK:STDOUT:   %.1: type = tuple_type () [template]
+// CHECK:STDOUT:   %struct.1: B = struct_value () [template]
+// CHECK:STDOUT:   %B.2: type = class_type @B [template]
 // CHECK:STDOUT:   %T: type = bind_symbolic_name T 0 [symbolic]
 // CHECK:STDOUT:   %N.2: T = bind_symbolic_name N 1 [symbolic]
-// CHECK:STDOUT:   %.1: type = class_type @.1 [template]
-// CHECK:STDOUT:   %.2: type = struct_type {} [template]
+// CHECK:STDOUT:   %.2: type = generic_class_type @.1 [template]
+// CHECK:STDOUT:   %struct.2: <invalid> = struct_value () [template]
+// CHECK:STDOUT:   %.3: type = class_type @.1 [template]
+// CHECK:STDOUT:   %.4: type = struct_type {} [template]
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: file {
@@ -157,11 +169,11 @@ class E(U:! type) {}
 // CHECK:STDOUT:     .B = %B.decl
 // CHECK:STDOUT:   }
 // CHECK:STDOUT:   %Core: <namespace> = namespace [template] {}
-// CHECK:STDOUT:   %B.decl: type = class_decl @B [template = constants.%B] {
+// CHECK:STDOUT:   %B.decl: B = class_decl @B [template = constants.%struct.1] {
 // CHECK:STDOUT:     %N.loc4_9.1: i32 = param N
 // CHECK:STDOUT:     %N.loc4_9.2: i32 = bind_symbolic_name N 0, %N.loc4_9.1 [symbolic = constants.%N.1]
 // CHECK:STDOUT:   }
-// CHECK:STDOUT:   %.decl: type = class_decl @.1 [template = constants.%.1] {
+// CHECK:STDOUT:   %.decl: <invalid> = class_decl @.1 [template = constants.%struct.2] {
 // CHECK:STDOUT:     %T.loc12_9.1: type = param T
 // CHECK:STDOUT:     %T.loc12_9.2: type = bind_symbolic_name T 0, %T.loc12_9.1 [symbolic = constants.%T]
 // CHECK:STDOUT:     %T.ref: type = name_ref T, %T.loc12_9.2 [symbolic = constants.%T]
@@ -174,17 +186,22 @@ class E(U:! type) {}
 // CHECK:STDOUT:
 // CHECK:STDOUT: class @.1 {
 // CHECK:STDOUT: !members:
-// CHECK:STDOUT:   .Self = constants.%.1
+// CHECK:STDOUT:   .Self = constants.%.3
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: --- fail_mismatch_param_count.carbon
 // CHECK:STDOUT:
 // CHECK:STDOUT: constants {
 // CHECK:STDOUT:   %T: type = bind_symbolic_name T 0 [symbolic]
-// CHECK:STDOUT:   %C: type = class_type @C [template]
+// CHECK:STDOUT:   %C.1: type = generic_class_type @C [template]
+// CHECK:STDOUT:   %.1: type = tuple_type () [template]
+// CHECK:STDOUT:   %struct.1: C = struct_value () [template]
+// CHECK:STDOUT:   %C.2: type = class_type @C [template]
 // CHECK:STDOUT:   %U: i32 = bind_symbolic_name U 1 [symbolic]
-// CHECK:STDOUT:   %.1: type = class_type @.1 [template]
-// CHECK:STDOUT:   %.2: type = struct_type {} [template]
+// CHECK:STDOUT:   %.2: type = generic_class_type @.1 [template]
+// CHECK:STDOUT:   %struct.2: <invalid> = struct_value () [template]
+// CHECK:STDOUT:   %.3: type = class_type @.1 [template]
+// CHECK:STDOUT:   %.4: type = struct_type {} [template]
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: file {
@@ -193,11 +210,11 @@ class E(U:! type) {}
 // CHECK:STDOUT:     .C = %C.decl
 // CHECK:STDOUT:   }
 // CHECK:STDOUT:   %Core: <namespace> = namespace [template] {}
-// CHECK:STDOUT:   %C.decl: type = class_decl @C [template = constants.%C] {
+// CHECK:STDOUT:   %C.decl: C = class_decl @C [template = constants.%struct.1] {
 // CHECK:STDOUT:     %T.loc4_9.1: type = param T
 // CHECK:STDOUT:     %T.loc4_9.2: type = bind_symbolic_name T 0, %T.loc4_9.1 [symbolic = constants.%T]
 // CHECK:STDOUT:   }
-// CHECK:STDOUT:   %.decl: type = class_decl @.1 [template = constants.%.1] {
+// CHECK:STDOUT:   %.decl: <invalid> = class_decl @.1 [template = constants.%struct.2] {
 // CHECK:STDOUT:     %T.loc12_9.1: type = param T
 // CHECK:STDOUT:     %T.loc12_9.2: type = bind_symbolic_name T 0, %T.loc12_9.1 [symbolic = constants.%T]
 // CHECK:STDOUT:     %U.loc12_19.1: i32 = param U
@@ -209,17 +226,22 @@ class E(U:! type) {}
 // CHECK:STDOUT:
 // CHECK:STDOUT: class @.1 {
 // CHECK:STDOUT: !members:
-// CHECK:STDOUT:   .Self = constants.%.1
+// CHECK:STDOUT:   .Self = constants.%.3
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: --- fail_mismatch_param_type.carbon
 // CHECK:STDOUT:
 // CHECK:STDOUT: constants {
 // CHECK:STDOUT:   %T.1: type = bind_symbolic_name T 0 [symbolic]
-// CHECK:STDOUT:   %D: type = class_type @D [template]
+// CHECK:STDOUT:   %D.1: type = generic_class_type @D [template]
+// CHECK:STDOUT:   %.1: type = tuple_type () [template]
+// CHECK:STDOUT:   %struct.1: D = struct_value () [template]
+// CHECK:STDOUT:   %D.2: type = class_type @D [template]
 // CHECK:STDOUT:   %T.2: i32 = bind_symbolic_name T 0 [symbolic]
-// CHECK:STDOUT:   %.1: type = class_type @.1 [template]
-// CHECK:STDOUT:   %.2: type = struct_type {} [template]
+// CHECK:STDOUT:   %.2: type = generic_class_type @.1 [template]
+// CHECK:STDOUT:   %struct.2: <invalid> = struct_value () [template]
+// CHECK:STDOUT:   %.3: type = class_type @.1 [template]
+// CHECK:STDOUT:   %.4: type = struct_type {} [template]
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: file {
@@ -228,11 +250,11 @@ class E(U:! type) {}
 // CHECK:STDOUT:     .D = %D.decl
 // CHECK:STDOUT:   }
 // CHECK:STDOUT:   %Core: <namespace> = namespace [template] {}
-// CHECK:STDOUT:   %D.decl: type = class_decl @D [template = constants.%D] {
+// CHECK:STDOUT:   %D.decl: D = class_decl @D [template = constants.%struct.1] {
 // CHECK:STDOUT:     %T.loc4_9.1: type = param T
 // CHECK:STDOUT:     %T.loc4_9.2: type = bind_symbolic_name T 0, %T.loc4_9.1 [symbolic = constants.%T.1]
 // CHECK:STDOUT:   }
-// CHECK:STDOUT:   %.decl: type = class_decl @.1 [template = constants.%.1] {
+// CHECK:STDOUT:   %.decl: <invalid> = class_decl @.1 [template = constants.%struct.2] {
 // CHECK:STDOUT:     %T.loc12_9.1: i32 = param T
 // CHECK:STDOUT:     %T.loc12_9.2: i32 = bind_symbolic_name T 0, %T.loc12_9.1 [symbolic = constants.%T.2]
 // CHECK:STDOUT:   }
@@ -242,17 +264,22 @@ class E(U:! type) {}
 // CHECK:STDOUT:
 // CHECK:STDOUT: class @.1 {
 // CHECK:STDOUT: !members:
-// CHECK:STDOUT:   .Self = constants.%.1
+// CHECK:STDOUT:   .Self = constants.%.3
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: --- fail_mismatch_param_name.carbon
 // CHECK:STDOUT:
 // CHECK:STDOUT: constants {
 // CHECK:STDOUT:   %T: type = bind_symbolic_name T 0 [symbolic]
-// CHECK:STDOUT:   %E: type = class_type @E [template]
+// CHECK:STDOUT:   %E.1: type = generic_class_type @E [template]
+// CHECK:STDOUT:   %.1: type = tuple_type () [template]
+// CHECK:STDOUT:   %struct.1: E = struct_value () [template]
+// CHECK:STDOUT:   %E.2: type = class_type @E [template]
 // CHECK:STDOUT:   %U: type = bind_symbolic_name U 0 [symbolic]
-// CHECK:STDOUT:   %.1: type = class_type @.1 [template]
-// CHECK:STDOUT:   %.2: type = struct_type {} [template]
+// CHECK:STDOUT:   %.2: type = generic_class_type @.1 [template]
+// CHECK:STDOUT:   %struct.2: <invalid> = struct_value () [template]
+// CHECK:STDOUT:   %.3: type = class_type @.1 [template]
+// CHECK:STDOUT:   %.4: type = struct_type {} [template]
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: file {
@@ -261,11 +288,11 @@ class E(U:! type) {}
 // CHECK:STDOUT:     .E = %E.decl
 // CHECK:STDOUT:   }
 // CHECK:STDOUT:   %Core: <namespace> = namespace [template] {}
-// CHECK:STDOUT:   %E.decl: type = class_decl @E [template = constants.%E] {
+// CHECK:STDOUT:   %E.decl: E = class_decl @E [template = constants.%struct.1] {
 // CHECK:STDOUT:     %T.loc4_9.1: type = param T
 // CHECK:STDOUT:     %T.loc4_9.2: type = bind_symbolic_name T 0, %T.loc4_9.1 [symbolic = constants.%T]
 // CHECK:STDOUT:   }
-// CHECK:STDOUT:   %.decl: type = class_decl @.1 [template = constants.%.1] {
+// CHECK:STDOUT:   %.decl: <invalid> = class_decl @.1 [template = constants.%struct.2] {
 // CHECK:STDOUT:     %U.loc11_9.1: type = param U
 // CHECK:STDOUT:     %U.loc11_9.2: type = bind_symbolic_name U 0, %U.loc11_9.1 [symbolic = constants.%U]
 // CHECK:STDOUT:   }
@@ -275,6 +302,6 @@ class E(U:! type) {}
 // CHECK:STDOUT:
 // CHECK:STDOUT: class @.1 {
 // CHECK:STDOUT: !members:
-// CHECK:STDOUT:   .Self = constants.%.1
+// CHECK:STDOUT:   .Self = constants.%.3
 // CHECK:STDOUT: }
 // CHECK:STDOUT:

+ 1 - 0
toolchain/lower/file_context.cpp

@@ -385,6 +385,7 @@ auto FileContext::BuildType(SemIR::InstId inst_id) -> llvm::Type* {
     case SemIR::AssociatedEntityType::Kind:
     case SemIR::InterfaceType::Kind:
     case SemIR::FunctionType::Kind:
+    case SemIR::GenericClassType::Kind:
     case SemIR::UnboundElementType::Kind: {
       // Return an empty struct as a placeholder.
       // TODO: Should we model an interface as a witness table, or an associated

+ 5 - 0
toolchain/sem_ir/class.h

@@ -29,6 +29,11 @@ struct Class : public Printable<Class> {
   // we reach the `}` of the class definition.
   auto is_defined() const -> bool { return object_repr_id.is_valid(); }
 
+  // Determines whether this is a generic class.
+  auto is_generic() const -> bool {
+    return implicit_param_refs_id.is_valid() || param_refs_id.is_valid();
+  }
+
   // The following members always have values, and do not change throughout the
   // lifetime of the class.
 

+ 9 - 0
toolchain/sem_ir/file.cpp

@@ -178,6 +178,7 @@ static auto GetTypePrecedence(InstKind kind) -> int {
     case ClassType::Kind:
     case FloatType::Kind:
     case FunctionType::Kind:
+    case GenericClassType::Kind:
     case InterfaceType::Kind:
     case InterfaceWitnessAccess::Kind:
     case IntType::Kind:
@@ -316,9 +317,16 @@ static auto StringifyTypeExprImpl(const SemIR::File& outer_sem_ir,
       }
       case CARBON_KIND(FunctionType inst): {
         auto fn_name_id = sem_ir.functions().Get(inst.function_id).name_id;
+        // TODO: Consider formatting as `typeof(F)` instead.
         out << sem_ir.names().GetFormatted(fn_name_id);
         break;
       }
+      case CARBON_KIND(GenericClassType inst): {
+        auto class_name_id = sem_ir.classes().Get(inst.class_id).name_id;
+        // TODO: Consider formatting as `typeof(C)` instead.
+        out << sem_ir.names().GetFormatted(class_name_id);
+        break;
+      }
       case CARBON_KIND(InterfaceType inst): {
         auto interface_name_id =
             sem_ir.interfaces().Get(inst.interface_id).name_id;
@@ -554,6 +562,7 @@ auto GetExprCategory(const File& file, InstId inst_id) -> ExprCategory {
       case FloatLiteral::Kind:
       case FloatType::Kind:
       case FunctionType::Kind:
+      case GenericClassType::Kind:
       case InterfaceDecl::Kind:
       case InterfaceType::Kind:
       case InterfaceWitness::Kind:

+ 2 - 0
toolchain/sem_ir/inst_kind.def

@@ -110,6 +110,8 @@ CARBON_SEM_IR_INST_KIND_IMPL(FloatLiteral, TYPE_NEVER, CONSTANT_ALWAYS)
 CARBON_SEM_IR_INST_KIND_IMPL(FloatType, TYPE_ALWAYS, CONSTANT_CONDITIONAL)
 CARBON_SEM_IR_INST_KIND_IMPL(FunctionDecl, TYPE_NEVER, CONSTANT_NEVER)
 CARBON_SEM_IR_INST_KIND_IMPL(FunctionType, TYPE_ALWAYS, CONSTANT_CONDITIONAL)
+CARBON_SEM_IR_INST_KIND_IMPL(GenericClassType, TYPE_ALWAYS,
+                             CONSTANT_CONDITIONAL)
 CARBON_SEM_IR_INST_KIND_IMPL(ImplDecl, TYPE_NEVER, CONSTANT_NEVER)
 CARBON_SEM_IR_INST_KIND_IMPL(ImportRefUnloaded, TYPE_NEVER, CONSTANT_NEVER)
 CARBON_SEM_IR_INST_KIND_IMPL(ImportRefLoaded, TYPE_NEVER, CONSTANT_NEVER)

+ 4 - 0
toolchain/sem_ir/inst_namer.cpp

@@ -435,6 +435,10 @@ auto InstNamer::CollectNamesInBlock(ScopeId scope_id,
         add_inst_name_id(sem_ir_.functions().Get(inst.function_id).name_id);
         continue;
       }
+      case CARBON_KIND(GenericClassType inst): {
+        add_inst_name_id(sem_ir_.classes().Get(inst.class_id).name_id);
+        continue;
+      }
       case CARBON_KIND(ImplDecl inst): {
         CollectNamesInBlock(scope_id, inst.decl_block_id);
         break;

+ 12 - 0
toolchain/sem_ir/typed_insts.h

@@ -492,6 +492,18 @@ struct FunctionType {
   FunctionId function_id;
 };
 
+// The type of the name of a generic class. The corresponding value is an empty
+// `StructValue`.
+struct GenericClassType {
+  // This is only ever created as a constant, so doesn't have a location.
+  static constexpr auto Kind =
+      InstKind::GenericClassType.Define<Parse::InvalidNodeId>(
+          "generic_class_type");
+
+  TypeId type_id;
+  ClassId class_id;
+};
+
 struct ImplDecl {
   static constexpr auto Kind =
       InstKind::ImplDecl.Define<Parse::AnyImplDeclId>("impl_decl");