Explorar o código

Import support for generics and specifics (#4179)

Import generics and specifics when they are referenced by imported
entities.

When importing a generic, we import the symbolic constants required by
its eval block, and then rebuild the eval block itself given the list of
constants it needs to compute. This is likely a bit less efficient than
directly importing the contents of the eval block, but avoids needing to
either extend the importer code to be able to import the instructions
that can appear in the eval block or extend the evaluator to cope with
instructions from a different `SemIR::File`.

Importing a symbolic constant is unaffected, and does not yet preserve
the associated generic and index within that generic, so uses of a
generic from an imported IR still don't pick up values from the
specific, but the improved functionality can be seen in the changes to
the SemIR in the testcases.
Richard Smith hai 1 ano
pai
achega
3c8fc714a8
Modificáronse 37 ficheiros con 1156 adicións e 336 borrados
  1. 76 23
      toolchain/check/generic.cpp
  2. 8 0
      toolchain/check/generic.h
  3. 200 39
      toolchain/check/import_ref.cpp
  4. 271 85
      toolchain/check/testdata/class/generic/import.carbon
  5. 116 60
      toolchain/check/testdata/class/syntactic_merge_literal.carbon
  6. 5 1
      toolchain/check/testdata/function/builtin/no_prelude/call_from_operator.carbon
  7. 4 1
      toolchain/check/testdata/impl/lookup/import.carbon
  8. 4 1
      toolchain/check/testdata/impl/lookup/no_prelude/import.carbon
  9. 5 1
      toolchain/check/testdata/impl/no_prelude/import_self.carbon
  10. 5 1
      toolchain/check/testdata/index/fail_negative_indexing.carbon
  11. 51 23
      toolchain/check/testdata/interface/no_prelude/generic_import.carbon
  12. 8 2
      toolchain/check/testdata/interface/no_prelude/import.carbon
  13. 5 1
      toolchain/check/testdata/operators/builtin/fail_type_mismatch_once.carbon
  14. 5 1
      toolchain/check/testdata/operators/builtin/fail_unimplemented_op.carbon
  15. 11 2
      toolchain/check/testdata/operators/overloaded/add.carbon
  16. 11 2
      toolchain/check/testdata/operators/overloaded/bit_and.carbon
  17. 5 1
      toolchain/check/testdata/operators/overloaded/bit_complement.carbon
  18. 11 2
      toolchain/check/testdata/operators/overloaded/bit_or.carbon
  19. 11 2
      toolchain/check/testdata/operators/overloaded/bit_xor.carbon
  20. 6 1
      toolchain/check/testdata/operators/overloaded/dec.carbon
  21. 11 2
      toolchain/check/testdata/operators/overloaded/div.carbon
  22. 30 6
      toolchain/check/testdata/operators/overloaded/eq.carbon
  23. 12 2
      toolchain/check/testdata/operators/overloaded/fail_assign_non_ref.carbon
  24. 22 4
      toolchain/check/testdata/operators/overloaded/fail_no_impl.carbon
  25. 11 2
      toolchain/check/testdata/operators/overloaded/fail_no_impl_for_arg.carbon
  26. 6 1
      toolchain/check/testdata/operators/overloaded/inc.carbon
  27. 11 2
      toolchain/check/testdata/operators/overloaded/left_shift.carbon
  28. 11 2
      toolchain/check/testdata/operators/overloaded/mod.carbon
  29. 11 2
      toolchain/check/testdata/operators/overloaded/mul.carbon
  30. 5 1
      toolchain/check/testdata/operators/overloaded/negate.carbon
  31. 40 8
      toolchain/check/testdata/operators/overloaded/ordered.carbon
  32. 11 2
      toolchain/check/testdata/operators/overloaded/right_shift.carbon
  33. 11 2
      toolchain/check/testdata/operators/overloaded/sub.carbon
  34. 19 5
      toolchain/check/testdata/packages/no_prelude/fail_export_name_params.carbon
  35. 63 23
      toolchain/check/testdata/struct/import.carbon
  36. 63 23
      toolchain/check/testdata/tuples/import.carbon
  37. 1 0
      toolchain/sem_ir/generic.cpp

+ 76 - 23
toolchain/check/generic.cpp

@@ -44,6 +44,14 @@ static auto AddGenericConstantInstToEvalBlock(
 }
 
 namespace {
+// A map from an instruction ID representing a canonical symbolic constant to an
+// instruction within an eval block of the generic that computes the specific
+// value for that constant.
+//
+// We arbitrarily use a small size of 256 bytes for the map.
+// TODO: Determine a better number based on measurements.
+using ConstantsInGenericMap = Map<SemIR::InstId, SemIR::InstId, 256>;
+
 // Substitution callbacks to rebuild a generic constant in the eval block for a
 // generic region.
 class RebuildGenericConstantInEvalBlockCallbacks final
@@ -52,7 +60,7 @@ class RebuildGenericConstantInEvalBlockCallbacks final
   RebuildGenericConstantInEvalBlockCallbacks(
       Context& context, SemIR::GenericId generic_id,
       SemIR::GenericInstIndex::Region region,
-      Map<SemIR::InstId, SemIR::InstId>& constants_in_generic)
+      ConstantsInGenericMap& constants_in_generic)
       : context_(context),
         generic_id_(generic_id),
         region_(region),
@@ -62,6 +70,14 @@ class RebuildGenericConstantInEvalBlockCallbacks final
   // block, and substitute them for the instructions in the eval block.
   auto Subst(SemIR::InstId& inst_id) const -> bool override {
     auto const_id = context_.constant_values().Get(inst_id);
+    if (!const_id.is_valid()) {
+      // An unloaded import ref should never contain anything we need to
+      // substitute into. Don't trigger loading it here.
+      CARBON_CHECK(context_.insts().Is<SemIR::ImportRefUnloaded>(inst_id))
+          << "Substituting into instruction with invalid constant ID: "
+          << context_.insts().Get(inst_id);
+      return true;
+    }
     if (!const_id.is_symbolic()) {
       // This instruction doesn't have a symbolic constant value, so can't
       // contain any bindings that need to be substituted.
@@ -104,6 +120,8 @@ class RebuildGenericConstantInEvalBlockCallbacks final
       // TODO: Add a function on `Context` to add the instruction without
       // inserting it into the dependent instructions list or computing a
       // constant value for it.
+      // TODO: Provide a location based on the location of the instruction
+      // that uses the constant.
       auto inst_id = context_.sem_ir().insts().AddInNoBlock(
           SemIR::LocIdAndInst::NoLoc(new_inst));
       auto const_id = AddGenericConstantInstToEvalBlock(
@@ -118,7 +136,7 @@ class RebuildGenericConstantInEvalBlockCallbacks final
   Context& context_;
   SemIR::GenericId generic_id_;
   SemIR::GenericInstIndex::Region region_;
-  Map<SemIR::InstId, SemIR::InstId>& constants_in_generic_;
+  ConstantsInGenericMap& constants_in_generic_;
 };
 }  // namespace
 
@@ -129,8 +147,8 @@ class RebuildGenericConstantInEvalBlockCallbacks final
 static auto AddGenericTypeToEvalBlock(
     Context& context, SemIR::GenericId generic_id,
     SemIR::GenericInstIndex::Region region,
-    Map<SemIR::InstId, SemIR::InstId>& constants_in_generic,
-    SemIR::TypeId type_id) -> SemIR::TypeId {
+    ConstantsInGenericMap& constants_in_generic, SemIR::TypeId type_id)
+    -> SemIR::TypeId {
   // Substitute into the type's constant instruction and rebuild it in the eval
   // block.
   auto type_inst_id =
@@ -147,8 +165,8 @@ static auto AddGenericTypeToEvalBlock(
 static auto AddGenericConstantToEvalBlock(
     Context& context, SemIR::GenericId generic_id,
     SemIR::GenericInstIndex::Region region,
-    Map<SemIR::InstId, SemIR::InstId>& constants_in_generic,
-    SemIR::InstId inst_id) -> SemIR::ConstantId {
+    ConstantsInGenericMap& constants_in_generic, SemIR::InstId inst_id)
+    -> SemIR::ConstantId {
   // Substitute into the constant value and rebuild it in the eval block if
   // we've not encountered it before.
   auto const_inst_id = context.constant_values().GetConstantInstId(inst_id);
@@ -162,6 +180,24 @@ static auto AddGenericConstantToEvalBlock(
   return context.constant_values().Get(new_inst_id);
 }
 
+// Populates a map of constants in a generic from the constants in the
+// declaration region, in preparation for building the definition region.
+static auto PopulateConstantsFromDeclaration(
+    Context& context, SemIR::GenericId generic_id,
+    ConstantsInGenericMap& constants_in_generic) {
+  // For the definition region, populate constants from the declaration.
+  auto decl_eval_block = context.inst_blocks().Get(
+      context.generics().Get(generic_id).decl_block_id);
+  constants_in_generic.GrowForInsertCount(decl_eval_block.size());
+  for (auto inst_id : decl_eval_block) {
+    auto const_inst_id = context.constant_values().GetConstantInstId(inst_id);
+    auto result = constants_in_generic.Insert(const_inst_id, inst_id);
+    CARBON_CHECK(result.is_inserted())
+        << "Duplicate constant in generic decl eval block: "
+        << context.insts().Get(const_inst_id);
+  }
+}
+
 // Builds and returns a block of instructions whose constant values need to be
 // evaluated in order to resolve a generic to a specific.
 static auto MakeGenericEvalBlock(Context& context, SemIR::GenericId generic_id,
@@ -169,16 +205,11 @@ static auto MakeGenericEvalBlock(Context& context, SemIR::GenericId generic_id,
     -> SemIR::InstBlockId {
   context.inst_block_stack().Push();
 
-  Map<SemIR::InstId, SemIR::InstId> constants_in_generic;
+  ConstantsInGenericMap constants_in_generic;
 
   // For the definition region, populate constants from the declaration.
   if (region == SemIR::GenericInstIndex::Region::Definition) {
-    auto decl_eval_block = context.inst_blocks().Get(
-        context.generics().Get(generic_id).decl_block_id);
-    for (auto inst_id : decl_eval_block) {
-      constants_in_generic.Insert(
-          context.constant_values().GetConstantInstId(inst_id), inst_id);
-    }
+    PopulateConstantsFromDeclaration(context, generic_id, constants_in_generic);
   }
 
   // The work done in this loop might invalidate iterators into the generic
@@ -231,6 +262,37 @@ static auto MakeGenericEvalBlock(Context& context, SemIR::GenericId generic_id,
   return context.inst_block_stack().Pop();
 }
 
+// Builds and returns an eval block, given the list of canonical symbolic
+// constants that the instructions in the eval block should produce. This is
+// used when importing a generic.
+auto RebuildGenericEvalBlock(Context& context, SemIR::GenericId generic_id,
+                             SemIR::GenericInstIndex::Region region,
+                             llvm::ArrayRef<SemIR::InstId> const_ids)
+    -> SemIR::InstBlockId {
+  context.inst_block_stack().Push();
+
+  ConstantsInGenericMap constants_in_generic;
+
+  // For the definition region, populate constants from the declaration.
+  if (region == SemIR::GenericInstIndex::Region::Definition) {
+    PopulateConstantsFromDeclaration(context, generic_id, constants_in_generic);
+  }
+
+  constants_in_generic.GrowForInsertCount(const_ids.size());
+  for (auto [i, inst_id] : llvm::enumerate(const_ids)) {
+    // Build a constant in the inst block.
+    AddGenericConstantToEvalBlock(context, generic_id, region,
+                                  constants_in_generic, inst_id);
+    CARBON_CHECK(context.inst_block_stack().PeekCurrentBlockContents().size() ==
+                 i + 1)
+        << "Produced "
+        << (context.inst_block_stack().PeekCurrentBlockContents().size() - i)
+        << " instructions when importing " << context.insts().Get(inst_id);
+  }
+
+  return context.inst_block_stack().Pop();
+}
+
 auto FinishGenericDecl(Context& context, SemIR::InstId decl_id)
     -> SemIR::GenericId {
   auto all_bindings =
@@ -293,11 +355,6 @@ auto MakeSpecific(Context& context, SemIR::GenericId generic_id,
                   SemIR::InstBlockId args_id) -> SemIR::SpecificId {
   auto specific_id = context.specifics().GetOrAdd(generic_id, args_id);
 
-  // TODO: Remove this once we import generics properly.
-  if (!generic_id.is_valid()) {
-    return specific_id;
-  }
-
   // If this is the first time we've formed this specific, evaluate its decl
   // block to form information about the specific.
   if (!context.specifics().Get(specific_id).decl_block_id.is_valid()) {
@@ -339,11 +396,7 @@ auto ResolveSpecificDefinition(Context& context, SemIR::SpecificId specific_id)
     -> bool {
   auto& specific = context.specifics().Get(specific_id);
   auto generic_id = specific.generic_id;
-
-  // TODO: Remove this once we import generics properly.
-  if (!generic_id.is_valid()) {
-    return true;
-  }
+  CARBON_CHECK(generic_id.is_valid()) << "Specific with no generic ID";
 
   if (!specific.definition_block_id.is_valid()) {
     // Evaluate the eval block for the definition of the generic.

+ 8 - 0
toolchain/check/generic.h

@@ -31,6 +31,14 @@ auto FinishGenericRedecl(Context& context, SemIR::InstId decl_id,
 auto FinishGenericDefinition(Context& context, SemIR::GenericId generic_id)
     -> void;
 
+// Builds and returns an eval block, given the list of canonical symbolic
+// constants that the instructions in the eval block should produce. This is
+// used when importing a generic.
+auto RebuildGenericEvalBlock(Context& context, SemIR::GenericId generic_id,
+                             SemIR::GenericInstIndex::Region region,
+                             llvm::ArrayRef<SemIR::InstId> const_ids)
+    -> SemIR::InstBlockId;
+
 // Builds a new specific, or finds an existing one if this generic has already
 // been referenced with these arguments. Performs substitution into the
 // declaration, but not the definition, of the generic.

+ 200 - 39
toolchain/check/import_ref.cpp

@@ -267,6 +267,22 @@ class ImportRefResolver {
     llvm::SmallVector<SemIR::ImportIRInst> indirect_insts = {};
   };
 
+  // Local information associated with an imported generic.
+  struct GenericData {
+    llvm::SmallVector<SemIR::InstId> bindings;
+    // TODO: Add data for the self specific.
+    llvm::SmallVector<SemIR::InstId> decl_block;
+    llvm::SmallVector<SemIR::InstId> definition_block;
+  };
+
+  // Local information associated with an imported specific.
+  struct SpecificData {
+    SemIR::ConstantId generic_const_id;
+    llvm::SmallVector<SemIR::InstId> args;
+    llvm::SmallVector<SemIR::InstId> decl_block;
+    llvm::SmallVector<SemIR::InstId> definition_block;
+  };
+
   // Looks to see if an instruction has been resolved. If a constant is only
   // found indirectly, sets the constant for any indirect steps that don't
   // already have the constant. If a constant isn't found, returns the indirect
@@ -397,6 +413,18 @@ class ImportRefResolver {
     return inst_ids;
   }
 
+  // Gets a local instruction block ID corresponding to an imported inst block
+  // whose contents were already imported, for example by
+  // GetLocalInstBlockContents.
+  auto GetLocalInstBlockId(SemIR::InstBlockId import_block_id,
+                           llvm::ArrayRef<SemIR::InstId> contents)
+      -> SemIR::InstBlockId {
+    if (!import_block_id.is_valid()) {
+      return SemIR::InstBlockId::Invalid;
+    }
+    return context_.inst_blocks().Add(contents);
+  }
+
   // Gets a local canonical instruction block ID corresponding to an imported
   // inst block whose contents were already imported, for example by
   // GetLocalInstBlockContents.
@@ -409,43 +437,153 @@ class ImportRefResolver {
     return context_.inst_blocks().AddCanonical(contents);
   }
 
-  // Gets a local version of an imported generic.
-  auto GetLocalGeneric(SemIR::GenericId generic_id) -> SemIR::GenericId {
+  // Gets an incomplete local version of an imported generic. Most fields are
+  // set in the second pass.
+  auto MakeIncompleteGeneric(SemIR::InstId decl_id, SemIR::GenericId generic_id)
+      -> SemIR::GenericId {
     if (!generic_id.is_valid()) {
       return SemIR::GenericId::Invalid;
     }
 
-    // TODO: Support importing generics. Note that this comes up in the prelude,
-    // so for now we fall back to producing `Invalid` and treating imported
-    // generics as non-generic.
-    return SemIR::GenericId::Invalid;
+    return context_.generics().Add(
+        {.decl_id = decl_id,
+         .bindings_id = SemIR::InstBlockId::Invalid,
+         .self_specific_id = SemIR::SpecificId::Invalid});
   }
 
-  // Gets a local argument list corresponding to the arguments of an imported
-  // specific.
-  auto GetLocalSpecificArgs(SemIR::SpecificId specific_id)
-      -> llvm::SmallVector<SemIR::InstId> {
-    if (!specific_id.is_valid()) {
-      return {};
+  // Gets a local version of the data associated with a generic.
+  auto GetLocalGenericData(SemIR::GenericId generic_id) -> GenericData {
+    if (!generic_id.is_valid()) {
+      return GenericData();
+    }
+
+    const auto& generic = import_ir_.generics().Get(generic_id);
+    return {
+        .bindings = GetLocalInstBlockContents(generic.bindings_id),
+        .decl_block = GetLocalInstBlockContents(generic.decl_block_id),
+        .definition_block =
+            GetLocalInstBlockContents(generic.definition_block_id),
+    };
+  }
+
+  // Given the local constant values for the elements of the eval block, builds
+  // and returns the eval block for a region of a generic.
+  auto GetLocalEvalBlock(const SemIR::Generic& import_generic,
+                         SemIR::GenericId generic_id,
+                         SemIR::GenericInstIndex::Region region,
+                         llvm::ArrayRef<SemIR::InstId> inst_ids)
+      -> SemIR::InstBlockId {
+    auto import_block_id = import_generic.GetEvalBlock(region);
+    if (!import_block_id.is_valid()) {
+      return SemIR::InstBlockId::Invalid;
+    }
+    return RebuildGenericEvalBlock(context_, generic_id, region, inst_ids);
+  }
+
+  // Adds the given local generic data to the given generic.
+  auto SetGenericData(SemIR::GenericId import_generic_id,
+                      SemIR::GenericId new_generic_id,
+                      const GenericData& generic_data) -> void {
+    if (!import_generic_id.is_valid()) {
+      return;
+    }
+
+    const auto& import_generic = import_ir_.generics().Get(import_generic_id);
+    auto& new_generic = context_.generics().Get(new_generic_id);
+    new_generic.bindings_id = GetLocalCanonicalInstBlockId(
+        import_generic.bindings_id, generic_data.bindings);
+    // TODO: Import or rebuild the self specific.
+    new_generic.decl_block_id = GetLocalEvalBlock(
+        import_generic, new_generic_id,
+        SemIR::GenericInstIndex::Region::Declaration, generic_data.decl_block);
+    new_generic.definition_block_id =
+        GetLocalEvalBlock(import_generic, new_generic_id,
+                          SemIR::GenericInstIndex::Region::Definition,
+                          generic_data.definition_block);
+  }
+
+  // Gets a local constant value corresponding to an imported generic ID. May
+  // add work to the work stack and return `Invalid`.
+  auto GetLocalConstantId(SemIR::GenericId generic_id) -> SemIR::ConstantId {
+    if (!generic_id.is_valid()) {
+      return SemIR::ConstantId::Invalid;
+    }
+    return GetLocalConstantId(
+        import_ir_.insts()
+            .Get(import_ir_.generics().Get(generic_id).decl_id)
+            .type_id());
+  }
+
+  // Gets a local generic ID given the corresponding local constant ID returned
+  // by GetLocalConstantId for the imported generic. Does not add any new work.
+  auto GetLocalGenericId(SemIR::ConstantId local_const_id) -> SemIR::GenericId {
+    if (!local_const_id.is_valid()) {
+      return SemIR::GenericId::Invalid;
+    }
+    auto type = context_.insts().Get(
+        context_.constant_values().GetInstId(local_const_id));
+    CARBON_KIND_SWITCH(type) {
+      case CARBON_KIND(SemIR::FunctionType fn_type): {
+        return context_.functions().Get(fn_type.function_id).generic_id;
+      }
+      case CARBON_KIND(SemIR::GenericClassType class_type): {
+        return context_.classes().Get(class_type.class_id).generic_id;
+      }
+      case CARBON_KIND(SemIR::GenericInterfaceType interface_type): {
+        return context_.interfaces()
+            .Get(interface_type.interface_id)
+            .generic_id;
+      }
+      default: {
+        CARBON_FATAL() << "Unexpected type for generic declaration: " << type;
+      }
     }
-    return GetLocalInstBlockContents(
-        import_ir_.specifics().Get(specific_id).args_id);
   }
 
-  // Gets a local specific whose arguments were already imported by
-  // GetLocalSpecificArgs. Does not add any new work.
-  auto GetLocalSpecific(SemIR::SpecificId specific_id,
-                        llvm::ArrayRef<SemIR::InstId> args)
-      -> SemIR::SpecificId {
+  // Gets local information about an imported specific.
+  auto GetLocalSpecificData(SemIR::SpecificId specific_id) -> SpecificData {
     if (!specific_id.is_valid()) {
-      return SemIR::SpecificId::Invalid;
+      return {.generic_const_id = SemIR::ConstantId::Invalid, .args = {}};
     }
+
     const auto& specific = import_ir_.specifics().Get(specific_id);
-    // TODO: Import the generic.
-    auto generic_id = SemIR::GenericId::Invalid;
-    auto args_id = GetLocalCanonicalInstBlockId(specific.args_id, args);
-    // TODO: Also import the specific.
-    return context_.specifics().GetOrAdd(generic_id, args_id);
+    return {
+        .generic_const_id = GetLocalConstantId(specific.generic_id),
+        .args = GetLocalInstBlockContents(specific.args_id),
+        .decl_block = GetLocalInstBlockContents(specific.decl_block_id),
+        .definition_block =
+            GetLocalInstBlockContents(specific.definition_block_id),
+    };
+  }
+
+  // Gets a local specific whose data was already imported by
+  // GetLocalSpecificData. Does not add any new work.
+  auto GetOrAddLocalSpecific(SemIR::SpecificId import_specific_id,
+                             const SpecificData& data) -> SemIR::SpecificId {
+    if (!import_specific_id.is_valid()) {
+      return SemIR::SpecificId::Invalid;
+    }
+
+    // Form a corresponding local specific ID.
+    const auto& import_specific =
+        import_ir_.specifics().Get(import_specific_id);
+    auto generic_id = GetLocalGenericId(data.generic_const_id);
+    auto args_id =
+        GetLocalCanonicalInstBlockId(import_specific.args_id, data.args);
+
+    // Populate the specific. Note that we might get data from multiple
+    // different import IRs, so only import data we don't already have.
+    auto specific_id = context_.specifics().GetOrAdd(generic_id, args_id);
+    auto& specific = context_.specifics().Get(specific_id);
+    if (!specific.decl_block_id.is_valid()) {
+      specific.decl_block_id =
+          GetLocalInstBlockId(import_specific.decl_block_id, data.decl_block);
+    }
+    if (!specific.definition_block_id.is_valid()) {
+      specific.definition_block_id = GetLocalInstBlockId(
+          import_specific.definition_block_id, data.definition_block);
+    }
+    return specific_id;
   }
 
   // Returns the ConstantId for each parameter's type. Adds unresolved constants
@@ -653,7 +791,7 @@ class ImportRefResolver {
     return {
         .name_id = GetLocalNameId(import_base.name_id),
         .parent_scope_id = SemIR::NameScopeId::Invalid,
-        .generic_id = GetLocalGeneric(import_base.generic_id),
+        .generic_id = MakeIncompleteGeneric(decl_id, import_base.generic_id),
         .first_param_node_id = Parse::NodeId::Invalid,
         .last_param_node_id = Parse::NodeId::Invalid,
         .implicit_param_refs_id = import_base.implicit_param_refs_id.is_valid()
@@ -854,11 +992,22 @@ class ImportRefResolver {
       return ResolveResult::Retry();
     }
 
+    // TODO: Track an interface type, not an interface ID, on
+    // AssociatedEntityType.
+    auto interface_inst = context_.insts().Get(interface_inst_id);
+    SemIR::InterfaceId interface_id = SemIR::InterfaceId::Invalid;
+    if (interface_inst.Is<SemIR::InterfaceType>()) {
+      interface_id = interface_inst.As<SemIR::InterfaceType>().interface_id;
+    } else {
+      interface_id =
+          context_.types()
+              .GetAs<SemIR::GenericInterfaceType>(interface_inst.type_id())
+              .interface_id;
+    }
+
     return ResolveAs<SemIR::AssociatedEntityType>(
         {.type_id = SemIR::TypeId::TypeType,
-         .interface_id = context_.insts()
-                             .GetAs<SemIR::InterfaceType>(interface_inst_id)
-                             .interface_id,
+         .interface_id = interface_id,
          .entity_type_id =
              context_.GetTypeIdForTypeConstant(entity_type_const_id)});
   }
@@ -1009,6 +1158,7 @@ class ImportRefResolver {
         GetLocalParamConstantIds(import_class.implicit_param_refs_id);
     llvm::SmallVector<SemIR::ConstantId> param_const_ids =
         GetLocalParamConstantIds(import_class.param_refs_id);
+    auto generic_data = GetLocalGenericData(import_class.generic_id);
     auto self_const_id = GetLocalConstantId(import_class.self_type_id);
     auto object_repr_const_id =
         import_class.object_repr_id.is_valid()
@@ -1028,6 +1178,7 @@ class ImportRefResolver {
         import_class.implicit_param_refs_id, implicit_param_const_ids);
     new_class.param_refs_id =
         GetLocalParamRefsId(import_class.param_refs_id, param_const_ids);
+    SetGenericData(import_class.generic_id, new_class.generic_id, generic_data);
     new_class.self_type_id = context_.GetTypeIdForTypeConstant(self_const_id);
 
     if (import_class.is_defined()) {
@@ -1043,7 +1194,7 @@ class ImportRefResolver {
     CARBON_CHECK(inst.type_id == SemIR::TypeId::TypeType);
     auto class_const_id =
         GetLocalConstantId(import_ir_.classes().Get(inst.class_id).decl_id);
-    auto args = GetLocalSpecificArgs(inst.specific_id);
+    auto specific_data = GetLocalSpecificData(inst.specific_id);
     if (HasNewWork(initial_work)) {
       return ResolveResult::Retry();
     }
@@ -1058,7 +1209,7 @@ class ImportRefResolver {
     } else {
       auto generic_class_type = context_.types().GetAs<SemIR::GenericClassType>(
           class_const_inst.type_id());
-      auto specific_id = GetLocalSpecific(inst.specific_id, args);
+      auto specific_id = GetOrAddLocalSpecific(inst.specific_id, specific_data);
       return ResolveAs<SemIR::ClassType>(
           {.type_id = SemIR::TypeId::TypeType,
            .class_id = generic_class_type.class_id,
@@ -1116,6 +1267,7 @@ class ImportRefResolver {
         GetLocalParamConstantIds(function.implicit_param_refs_id);
     llvm::SmallVector<SemIR::ConstantId> param_const_ids =
         GetLocalParamConstantIds(function.param_refs_id);
+    auto generic_data = GetLocalGenericData(function.generic_id);
 
     if (HasNewWork(initial_work)) {
       return ResolveResult::Retry();
@@ -1130,7 +1282,9 @@ class ImportRefResolver {
     auto function_decl_id = context_.AddPlaceholderInstInNoBlock(
         SemIR::LocIdAndInst(import_ir_inst_id, function_decl));
     // TODO: Implement import for generics.
-    auto generic_id = GetLocalGeneric(function.generic_id);
+    auto generic_id =
+        MakeIncompleteGeneric(function_decl_id, function.generic_id);
+    SetGenericData(function.generic_id, generic_id, generic_data);
 
     auto new_return_storage = SemIR::InstId::Invalid;
     if (function.return_storage_id.is_valid()) {
@@ -1173,14 +1327,18 @@ class ImportRefResolver {
     CARBON_CHECK(inst.type_id == SemIR::TypeId::TypeType);
     auto fn_val_id = GetLocalConstantInstId(
         import_ir_.functions().Get(inst.function_id).decl_id);
+    auto specific_data = GetLocalSpecificData(inst.specific_id);
     if (HasNewWork(initial_work)) {
       return ResolveResult::Retry();
     }
-    auto fn_val = context_.insts().Get(fn_val_id);
-    CARBON_CHECK(context_.types().Is<SemIR::FunctionType>(fn_val.type_id()));
-    // TODO: Import the correct specific and build a function type constant
-    // using it.
-    return {.const_id = context_.types().GetConstantId(fn_val.type_id())};
+    auto fn_type_id = context_.insts().Get(fn_val_id).type_id();
+    return ResolveAs<SemIR::FunctionType>(
+        {.type_id = SemIR::TypeId::TypeType,
+         .function_id = context_.types()
+                            .GetAs<SemIR::FunctionType>(fn_type_id)
+                            .function_id,
+         .specific_id =
+             GetOrAddLocalSpecific(inst.specific_id, specific_data)});
   }
 
   auto TryResolveTypedInst(SemIR::GenericClassType inst) -> ResolveResult {
@@ -1322,6 +1480,7 @@ class ImportRefResolver {
         GetLocalParamConstantIds(import_interface.implicit_param_refs_id);
     llvm::SmallVector<SemIR::ConstantId> param_const_ids =
         GetLocalParamConstantIds(import_interface.param_refs_id);
+    auto generic_data = GetLocalGenericData(import_interface.generic_id);
 
     std::optional<SemIR::InstId> self_param_id;
     if (import_interface.is_defined()) {
@@ -1338,6 +1497,8 @@ class ImportRefResolver {
         import_interface.implicit_param_refs_id, implicit_param_const_ids);
     new_interface.param_refs_id =
         GetLocalParamRefsId(import_interface.param_refs_id, param_const_ids);
+    SetGenericData(import_interface.generic_id, new_interface.generic_id,
+                   generic_data);
 
     if (import_interface.is_defined()) {
       CARBON_CHECK(self_param_id);
@@ -1351,7 +1512,7 @@ class ImportRefResolver {
     CARBON_CHECK(inst.type_id == SemIR::TypeId::TypeType);
     auto interface_const_id = GetLocalConstantId(
         import_ir_.interfaces().Get(inst.interface_id).decl_id);
-    auto args = GetLocalSpecificArgs(inst.specific_id);
+    auto specific_data = GetLocalSpecificData(inst.specific_id);
     if (HasNewWork(initial_work)) {
       return ResolveResult::Retry();
     }
@@ -1368,7 +1529,7 @@ class ImportRefResolver {
       auto generic_interface_type =
           context_.types().GetAs<SemIR::GenericInterfaceType>(
               interface_const_inst.type_id());
-      auto specific_id = GetLocalSpecific(inst.specific_id, args);
+      auto specific_id = GetOrAddLocalSpecific(inst.specific_id, specific_data);
       return ResolveAs<SemIR::InterfaceType>(
           {.type_id = SemIR::TypeId::TypeType,
            .interface_id = generic_interface_type.interface_id,

+ 271 - 85
toolchain/check/testdata/class/generic/import.carbon

@@ -231,27 +231,35 @@ class Class(U:! type) {
 // CHECK:STDOUT:   %Class.type: type = generic_class_type @Class [template]
 // CHECK:STDOUT:   %.1: type = tuple_type () [template]
 // CHECK:STDOUT:   %Class.1: %Class.type = struct_value () [template]
-// CHECK:STDOUT:   %Class.2: type = class_type @Class, invalid(%T) [symbolic]
+// CHECK:STDOUT:   %Class.2: type = class_type @Class, @Class(%T) [symbolic]
 // CHECK:STDOUT:   %.2: type = unbound_element_type %Class.2, %T [symbolic]
 // CHECK:STDOUT:   %.3: type = struct_type {.x: %T} [symbolic]
 // CHECK:STDOUT:   %CompleteClass.type: type = generic_class_type @CompleteClass [template]
 // CHECK:STDOUT:   %CompleteClass.1: %CompleteClass.type = struct_value () [template]
 // CHECK:STDOUT:   %.4: type = struct_type {.n: i32} [template]
-// CHECK:STDOUT:   %CompleteClass.2: type = class_type @CompleteClass, invalid(%T) [symbolic]
+// CHECK:STDOUT:   %CompleteClass.2: type = class_type @CompleteClass, @CompleteClass(%T) [symbolic]
+// CHECK:STDOUT:   %F.type.1: type = fn_type @F.1 [template]
+// CHECK:STDOUT:   %F.1: %F.type.1 = struct_value () [template]
+// CHECK:STDOUT:   %F.type.2: type = fn_type @F.1, @CompleteClass(%T) [symbolic]
+// CHECK:STDOUT:   %F.2: %F.type.2 = struct_value () [symbolic]
+// CHECK:STDOUT:   %.5: type = unbound_element_type %CompleteClass.2, i32 [symbolic]
 // CHECK:STDOUT:   %Int32.type: type = fn_type @Int32 [template]
 // CHECK:STDOUT:   %Int32: %Int32.type = struct_value () [template]
-// CHECK:STDOUT:   %CompleteClass.3: type = class_type @CompleteClass, invalid(i32) [template]
-// CHECK:STDOUT:   %F.type: type = fn_type @F [template]
-// CHECK:STDOUT:   %F: %F.type = struct_value () [template]
-// CHECK:STDOUT:   %.5: type = ptr_type %.4 [template]
-// CHECK:STDOUT:   %.6: i32 = int_literal 1 [template]
-// CHECK:STDOUT:   %struct: %CompleteClass.3 = struct_value (%.6) [template]
+// CHECK:STDOUT:   %CompleteClass.3: type = class_type @CompleteClass, @CompleteClass(i32) [template]
+// CHECK:STDOUT:   %F.type.3: type = fn_type @F.2 [template]
+// CHECK:STDOUT:   %F.3: %F.type.3 = struct_value () [template]
+// CHECK:STDOUT:   %.6: type = unbound_element_type %CompleteClass.3, i32 [template]
+// CHECK:STDOUT:   %F.type.4: type = fn_type @F.1, @CompleteClass(i32) [template]
+// CHECK:STDOUT:   %F.4: %F.type.4 = struct_value () [template]
+// CHECK:STDOUT:   %.7: type = ptr_type %.4 [template]
+// CHECK:STDOUT:   %.8: i32 = int_literal 1 [template]
+// CHECK:STDOUT:   %struct: %CompleteClass.3 = struct_value (%.8) [template]
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: imports {
 // CHECK:STDOUT:   %import_ref.1: %Class.type = import_ref Main//foo, inst+6, loaded [template = constants.%Class.1]
 // CHECK:STDOUT:   %import_ref.2: %CompleteClass.type = import_ref Main//foo, inst+14, loaded [template = constants.%CompleteClass.1]
-// CHECK:STDOUT:   %import_ref.3: %F.type = import_ref Main//foo, inst+55, loaded [template = constants.%F]
+// CHECK:STDOUT:   %import_ref.3: %F.type.3 = import_ref Main//foo, inst+55, loaded [template = constants.%F.3]
 // CHECK:STDOUT:   %Core: <namespace> = namespace file.%Core.import, [template] {
 // CHECK:STDOUT:     .Int32 = %import_ref.7
 // CHECK:STDOUT:     import Core//prelude
@@ -282,7 +290,7 @@ class Class(U:! type) {
 // 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:   }
-// CHECK:STDOUT:   %F.decl: %F.type = fn_decl @F [template = constants.%F] {
+// CHECK:STDOUT:   %F.decl: %F.type.3 = fn_decl @F.2 [template = constants.%F.3] {
 // CHECK:STDOUT:     %CompleteClass.ref: %CompleteClass.type = name_ref CompleteClass, imports.%import_ref.2 [template = constants.%CompleteClass.1]
 // CHECK:STDOUT:     %int.make_type_32: init type = call constants.%Int32() [template = i32]
 // CHECK:STDOUT:     %.loc8_24.1: type = value_of_initializer %int.make_type_32 [template = i32]
@@ -290,42 +298,88 @@ class Class(U:! type) {
 // CHECK:STDOUT:     %.loc8_24.3: init type = call %CompleteClass.ref(%.loc8_24.2) [template = constants.%CompleteClass.3]
 // CHECK:STDOUT:     %.loc8_28.1: type = value_of_initializer %.loc8_24.3 [template = constants.%CompleteClass.3]
 // CHECK:STDOUT:     %.loc8_28.2: type = converted %.loc8_24.3, %.loc8_28.1 [template = constants.%CompleteClass.3]
-// CHECK:STDOUT:     @F.%return: ref %CompleteClass.3 = var <return slot>
+// CHECK:STDOUT:     @F.2.%return: ref %CompleteClass.3 = var <return slot>
+// CHECK:STDOUT:   }
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: generic class @Class(constants.%T: type) {
+// CHECK:STDOUT:   %T: type = bind_symbolic_name T 0 [symbolic = %T (constants.%T)]
+// CHECK:STDOUT:
+// CHECK:STDOUT: !definition:
+// CHECK:STDOUT:   %Class: type = class_type @Class, @Class(%T) [symbolic = %Class (constants.%Class.2)]
+// CHECK:STDOUT:   %.1: type = unbound_element_type @Class.%Class (%Class.2), @Class.%T (%T) [symbolic = %.1 (constants.%.2)]
+// CHECK:STDOUT:
+// CHECK:STDOUT:   class {
+// CHECK:STDOUT:     %T.ref: type = name_ref T, file.%T.loc4_13.2 [symbolic = %T (constants.%T)]
+// CHECK:STDOUT:     %.loc5: @Class.%.1 (%.2) = field_decl x, element0 [template]
+// CHECK:STDOUT:
+// CHECK:STDOUT:   !members:
+// CHECK:STDOUT:     .Self = constants.%Class.2
+// CHECK:STDOUT:     .x = %.loc5
 // CHECK:STDOUT:   }
 // CHECK:STDOUT: }
 // 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: %.2 = field_decl x, element0 [template]
+// CHECK:STDOUT: generic class @CompleteClass(constants.%T: type) {
+// CHECK:STDOUT:   %T: type = bind_symbolic_name T 0 [symbolic = %T (constants.%T)]
+// CHECK:STDOUT:
+// CHECK:STDOUT: !definition:
+// CHECK:STDOUT:   %CompleteClass: type = class_type @CompleteClass, @CompleteClass(%T) [symbolic = %CompleteClass (constants.%CompleteClass.2)]
+// CHECK:STDOUT:   %.1: type = unbound_element_type @CompleteClass.%CompleteClass (%CompleteClass.2), i32 [symbolic = %.1 (constants.%.5)]
+// CHECK:STDOUT:   %F.type: type = fn_type @F.1, @CompleteClass(%T) [symbolic = %F.type (constants.%F.type.2)]
+// CHECK:STDOUT:   %F: @CompleteClass.%F.type (%F.type.2) = struct_value () [symbolic = %F (constants.%F.2)]
 // CHECK:STDOUT:
-// CHECK:STDOUT: !members:
-// CHECK:STDOUT:   .Self = constants.%Class.2
-// CHECK:STDOUT:   .x = %.loc5
+// CHECK:STDOUT:   class {
+// CHECK:STDOUT:   !members:
+// CHECK:STDOUT:     .Self = imports.%import_ref.4
+// CHECK:STDOUT:     .n = imports.%import_ref.5
+// CHECK:STDOUT:     .F = imports.%import_ref.6
+// CHECK:STDOUT:   }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: class @CompleteClass {
-// CHECK:STDOUT: !members:
-// CHECK:STDOUT:   .Self = imports.%import_ref.4
-// CHECK:STDOUT:   .n = imports.%import_ref.5
-// CHECK:STDOUT:   .F = imports.%import_ref.6
+// CHECK:STDOUT: generic fn @F.1(constants.%T: type) {
+// CHECK:STDOUT: !definition:
+// CHECK:STDOUT:
+// CHECK:STDOUT:   fn() -> i32;
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @Int32() -> type = "int.make_type_32";
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @F() -> %return: %CompleteClass.3 {
+// CHECK:STDOUT: fn @F.2() -> %return: %CompleteClass.3 {
 // CHECK:STDOUT: !entry:
-// CHECK:STDOUT:   %.loc9_16: i32 = int_literal 1 [template = constants.%.6]
+// CHECK:STDOUT:   %.loc9_16: i32 = int_literal 1 [template = constants.%.8]
 // CHECK:STDOUT:   %.loc9_17.1: %.4 = struct_literal (%.loc9_16)
 // CHECK:STDOUT:   %.loc9_17.2: ref i32 = class_element_access %return, element0
-// CHECK:STDOUT:   %.loc9_17.3: init i32 = initialize_from %.loc9_16 to %.loc9_17.2 [template = constants.%.6]
+// CHECK:STDOUT:   %.loc9_17.3: init i32 = initialize_from %.loc9_16 to %.loc9_17.2 [template = constants.%.8]
 // CHECK:STDOUT:   %.loc9_17.4: init %CompleteClass.3 = class_init (%.loc9_17.3), %return [template = constants.%struct]
 // CHECK:STDOUT:   %.loc9_18: init %CompleteClass.3 = converted %.loc9_17.1, %.loc9_17.4 [template = constants.%struct]
 // CHECK:STDOUT:   return %.loc9_18 to %return
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: specific invalid(constants.%T);
+// CHECK:STDOUT: specific @Class(constants.%T) {
+// CHECK:STDOUT:   %T => constants.%T
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @Class(@Class.%T) {
+// CHECK:STDOUT:   %T => constants.%T
+// CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: specific invalid(i32);
+// CHECK:STDOUT: specific @CompleteClass(constants.%T) {
+// CHECK:STDOUT:   %T => constants.%T
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @CompleteClass(@CompleteClass.%T) {
+// CHECK:STDOUT:   %T => constants.%T
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @CompleteClass(i32) {
+// CHECK:STDOUT:   %T => i32
+// CHECK:STDOUT:
+// CHECK:STDOUT: !definition:
+// CHECK:STDOUT:   %CompleteClass => constants.%CompleteClass.3
+// CHECK:STDOUT:   %.1 => constants.%.6
+// CHECK:STDOUT:   %F.type => constants.%F.type.4
+// CHECK:STDOUT:   %F => constants.%F.4
+// CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: --- use_foo.carbon
 // CHECK:STDOUT:
@@ -339,19 +393,25 @@ class Class(U:! type) {
 // CHECK:STDOUT:   %CompleteClass.1: %CompleteClass.type = struct_value () [template]
 // CHECK:STDOUT:   %.2: type = struct_type {.n: i32} [template]
 // CHECK:STDOUT:   %T: type = bind_symbolic_name T 0 [symbolic]
-// CHECK:STDOUT:   %CompleteClass.2: type = class_type @CompleteClass, invalid(%T) [symbolic]
-// CHECK:STDOUT:   %CompleteClass.3: type = class_type @CompleteClass, invalid(i32) [template]
-// CHECK:STDOUT:   %.3: type = ptr_type %.2 [template]
+// CHECK:STDOUT:   %CompleteClass.2: type = class_type @CompleteClass, @CompleteClass(%T) [symbolic]
 // CHECK:STDOUT:   %F.type.1: type = fn_type @F.1 [template]
 // CHECK:STDOUT:   %F.1: %F.type.1 = struct_value () [template]
-// CHECK:STDOUT:   %F.type.2: type = fn_type @F.2 [template]
-// CHECK:STDOUT:   %F.2: %F.type.2 = struct_value () [template]
+// CHECK:STDOUT:   %F.type.2: type = fn_type @F.1, @CompleteClass(%T) [symbolic]
+// CHECK:STDOUT:   %F.2: %F.type.2 = struct_value () [symbolic]
+// CHECK:STDOUT:   %.3: type = unbound_element_type %CompleteClass.2, i32 [symbolic]
+// CHECK:STDOUT:   %CompleteClass.3: type = class_type @CompleteClass, @CompleteClass(i32) [template]
+// CHECK:STDOUT:   %.4: type = unbound_element_type %CompleteClass.3, i32 [template]
+// CHECK:STDOUT:   %F.type.3: type = fn_type @F.1, @CompleteClass(i32) [template]
+// CHECK:STDOUT:   %F.3: %F.type.3 = struct_value () [template]
+// CHECK:STDOUT:   %.5: type = ptr_type %.2 [template]
+// CHECK:STDOUT:   %F.type.4: type = fn_type @F.2 [template]
+// CHECK:STDOUT:   %F.4: %F.type.4 = struct_value () [template]
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: imports {
 // CHECK:STDOUT:   %import_ref.1 = import_ref Main//foo, inst+6, unloaded
 // CHECK:STDOUT:   %import_ref.2: %CompleteClass.type = import_ref Main//foo, inst+14, loaded [template = constants.%CompleteClass.1]
-// CHECK:STDOUT:   %import_ref.3: %F.type.1 = import_ref Main//foo, inst+55, loaded [template = constants.%F.1]
+// CHECK:STDOUT:   %import_ref.3: %F.type.4 = import_ref Main//foo, inst+55, loaded [template = constants.%F.4]
 // CHECK:STDOUT:   %Core: <namespace> = namespace file.%Core.import, [template] {
 // CHECK:STDOUT:     .Int32 = %import_ref.4
 // CHECK:STDOUT:     import Core//prelude
@@ -365,7 +425,7 @@ class Class(U:! type) {
 // CHECK:STDOUT:   %import_ref.4: %Int32.type = import_ref Core//prelude/types, inst+4, loaded [template = constants.%Int32]
 // CHECK:STDOUT:   %import_ref.5 = import_ref Main//foo, inst+18, unloaded
 // CHECK:STDOUT:   %import_ref.6 = import_ref Main//foo, inst+28, unloaded
-// CHECK:STDOUT:   %import_ref.7: %F.type.2 = import_ref Main//foo, inst+35, loaded [template = constants.%F.2]
+// CHECK:STDOUT:   %import_ref.7: %F.type.2 = import_ref Main//foo, inst+35, loaded [template = constants.%F.1]
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: file {
@@ -386,11 +446,21 @@ class Class(U:! type) {
 // CHECK:STDOUT:   }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: class @CompleteClass {
-// CHECK:STDOUT: !members:
-// CHECK:STDOUT:   .Self = imports.%import_ref.5
-// CHECK:STDOUT:   .n = imports.%import_ref.6
-// CHECK:STDOUT:   .F = imports.%import_ref.7
+// CHECK:STDOUT: generic class @CompleteClass(constants.%T: type) {
+// CHECK:STDOUT:   %T: type = bind_symbolic_name T 0 [symbolic = %T (constants.%T)]
+// CHECK:STDOUT:
+// CHECK:STDOUT: !definition:
+// CHECK:STDOUT:   %CompleteClass: type = class_type @CompleteClass, @CompleteClass(%T) [symbolic = %CompleteClass (constants.%CompleteClass.2)]
+// CHECK:STDOUT:   %.1: type = unbound_element_type @CompleteClass.%CompleteClass (%CompleteClass.2), i32 [symbolic = %.1 (constants.%.3)]
+// CHECK:STDOUT:   %F.type: type = fn_type @F.1, @CompleteClass(%T) [symbolic = %F.type (constants.%F.type.2)]
+// CHECK:STDOUT:   %F: @CompleteClass.%F.type (%F.type.2) = struct_value () [symbolic = %F (constants.%F.2)]
+// CHECK:STDOUT:
+// CHECK:STDOUT:   class {
+// CHECK:STDOUT:   !members:
+// CHECK:STDOUT:     .Self = imports.%import_ref.5
+// CHECK:STDOUT:     .n = imports.%import_ref.6
+// CHECK:STDOUT:     .F = imports.%import_ref.7
+// CHECK:STDOUT:   }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @Int32() -> type = "int.make_type_32";
@@ -406,25 +476,43 @@ class Class(U:! type) {
 // CHECK:STDOUT:   %.loc6_27.2: type = converted %.loc6_23.3, %.loc6_27.1 [template = constants.%CompleteClass.3]
 // CHECK:STDOUT:   %v.var: ref %CompleteClass.3 = var v
 // CHECK:STDOUT:   %v: ref %CompleteClass.3 = bind_name v, %v.var
-// CHECK:STDOUT:   %F.ref.loc6: %F.type.1 = name_ref F, imports.%import_ref.3 [template = constants.%F.1]
+// CHECK:STDOUT:   %F.ref.loc6: %F.type.4 = name_ref F, imports.%import_ref.3 [template = constants.%F.4]
 // CHECK:STDOUT:   %.loc6_7: ref %CompleteClass.3 = splice_block %v.var {}
 // CHECK:STDOUT:   %F.call.loc6: init %CompleteClass.3 = call %F.ref.loc6() to %.loc6_7
 // CHECK:STDOUT:   assign %v.var, %F.call.loc6
 // CHECK:STDOUT:   %v.ref: ref %CompleteClass.3 = name_ref v, %v
-// CHECK:STDOUT:   %F.ref.loc7: %F.type.2 = name_ref F, imports.%import_ref.7 [template = constants.%F.2]
+// CHECK:STDOUT:   %F.ref.loc7: %F.type.2 = name_ref F, imports.%import_ref.7 [template = constants.%F.1]
 // CHECK:STDOUT:   %F.call.loc7: init i32 = call %F.ref.loc7()
 // CHECK:STDOUT:   %.loc7_15.1: i32 = value_of_initializer %F.call.loc7
 // CHECK:STDOUT:   %.loc7_15.2: i32 = converted %F.call.loc7, %.loc7_15.1
 // CHECK:STDOUT:   return %.loc7_15.2
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @F.1() -> %CompleteClass.3;
+// CHECK:STDOUT: generic fn @F.1(constants.%T: type) {
+// CHECK:STDOUT: !definition:
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @F.2() -> i32;
+// CHECK:STDOUT:   fn() -> i32;
+// CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: specific invalid(constants.%T);
+// CHECK:STDOUT: fn @F.2() -> %CompleteClass.3;
 // CHECK:STDOUT:
-// CHECK:STDOUT: specific invalid(i32);
+// CHECK:STDOUT: specific @CompleteClass(constants.%T) {
+// CHECK:STDOUT:   %T => constants.%T
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @CompleteClass(@CompleteClass.%T) {
+// CHECK:STDOUT:   %T => constants.%T
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @CompleteClass(i32) {
+// CHECK:STDOUT:   %T => i32
+// CHECK:STDOUT:
+// CHECK:STDOUT: !definition:
+// CHECK:STDOUT:   %CompleteClass => constants.%CompleteClass.3
+// CHECK:STDOUT:   %.1 => constants.%.4
+// CHECK:STDOUT:   %F.type => constants.%F.type.3
+// CHECK:STDOUT:   %F => constants.%F.3
+// CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: --- fail_todo_use_foo.carbon
 // CHECK:STDOUT:
@@ -438,18 +526,25 @@ class Class(U:! type) {
 // CHECK:STDOUT:   %CompleteClass.1: %CompleteClass.type = struct_value () [template]
 // CHECK:STDOUT:   %.2: type = struct_type {.n: i32} [template]
 // CHECK:STDOUT:   %T: type = bind_symbolic_name T 0 [symbolic]
-// CHECK:STDOUT:   %CompleteClass.2: type = class_type @CompleteClass, invalid(%T) [symbolic]
-// CHECK:STDOUT:   %CompleteClass.3: type = class_type @CompleteClass, invalid(i32) [template]
-// CHECK:STDOUT:   %.3: type = ptr_type %.2 [template]
-// CHECK:STDOUT:   %F.type: type = fn_type @F [template]
-// CHECK:STDOUT:   %F: %F.type = struct_value () [template]
-// CHECK:STDOUT:   %.4: type = unbound_element_type %CompleteClass.2, i32 [symbolic]
+// CHECK:STDOUT:   %CompleteClass.2: type = class_type @CompleteClass, @CompleteClass(%T) [symbolic]
+// CHECK:STDOUT:   %F.type.1: type = fn_type @F.1 [template]
+// CHECK:STDOUT:   %F.1: %F.type.1 = struct_value () [template]
+// CHECK:STDOUT:   %F.type.2: type = fn_type @F.1, @CompleteClass(%T) [symbolic]
+// CHECK:STDOUT:   %F.2: %F.type.2 = struct_value () [symbolic]
+// CHECK:STDOUT:   %.3: type = unbound_element_type %CompleteClass.2, i32 [symbolic]
+// CHECK:STDOUT:   %CompleteClass.3: type = class_type @CompleteClass, @CompleteClass(i32) [template]
+// CHECK:STDOUT:   %.4: type = unbound_element_type %CompleteClass.3, i32 [template]
+// CHECK:STDOUT:   %F.type.3: type = fn_type @F.1, @CompleteClass(i32) [template]
+// CHECK:STDOUT:   %F.3: %F.type.3 = struct_value () [template]
+// CHECK:STDOUT:   %.5: type = ptr_type %.2 [template]
+// CHECK:STDOUT:   %F.type.4: type = fn_type @F.2 [template]
+// CHECK:STDOUT:   %F.4: %F.type.4 = struct_value () [template]
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: imports {
 // CHECK:STDOUT:   %import_ref.1 = import_ref Main//foo, inst+6, unloaded
 // CHECK:STDOUT:   %import_ref.2: %CompleteClass.type = import_ref Main//foo, inst+14, loaded [template = constants.%CompleteClass.1]
-// CHECK:STDOUT:   %import_ref.3: %F.type = import_ref Main//foo, inst+55, loaded [template = constants.%F]
+// CHECK:STDOUT:   %import_ref.3: %F.type.4 = import_ref Main//foo, inst+55, loaded [template = constants.%F.4]
 // CHECK:STDOUT:   %Core: <namespace> = namespace file.%Core.import, [template] {
 // CHECK:STDOUT:     .Int32 = %import_ref.4
 // CHECK:STDOUT:     import Core//prelude
@@ -462,7 +557,7 @@ class Class(U:! type) {
 // CHECK:STDOUT:   }
 // CHECK:STDOUT:   %import_ref.4: %Int32.type = import_ref Core//prelude/types, inst+4, loaded [template = constants.%Int32]
 // CHECK:STDOUT:   %import_ref.5 = import_ref Main//foo, inst+18, unloaded
-// CHECK:STDOUT:   %import_ref.6: %.4 = import_ref Main//foo, inst+28, loaded [template = %.1]
+// CHECK:STDOUT:   %import_ref.6: %.3 = import_ref Main//foo, inst+28, loaded [template = %.1]
 // CHECK:STDOUT:   %import_ref.7 = import_ref Main//foo, inst+35, unloaded
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
@@ -484,11 +579,21 @@ class Class(U:! type) {
 // CHECK:STDOUT:   }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: class @CompleteClass {
-// CHECK:STDOUT: !members:
-// CHECK:STDOUT:   .Self = imports.%import_ref.5
-// CHECK:STDOUT:   .n = imports.%import_ref.6
-// CHECK:STDOUT:   .F = imports.%import_ref.7
+// CHECK:STDOUT: generic class @CompleteClass(constants.%T: type) {
+// CHECK:STDOUT:   %T: type = bind_symbolic_name T 0 [symbolic = %T (constants.%T)]
+// CHECK:STDOUT:
+// CHECK:STDOUT: !definition:
+// CHECK:STDOUT:   %CompleteClass: type = class_type @CompleteClass, @CompleteClass(%T) [symbolic = %CompleteClass (constants.%CompleteClass.2)]
+// CHECK:STDOUT:   %.1: type = unbound_element_type @CompleteClass.%CompleteClass (%CompleteClass.2), i32 [symbolic = %.1 (constants.%.3)]
+// CHECK:STDOUT:   %F.type: type = fn_type @F.1, @CompleteClass(%T) [symbolic = %F.type (constants.%F.type.2)]
+// CHECK:STDOUT:   %F: @CompleteClass.%F.type (%F.type.2) = struct_value () [symbolic = %F (constants.%F.2)]
+// CHECK:STDOUT:
+// CHECK:STDOUT:   class {
+// CHECK:STDOUT:   !members:
+// CHECK:STDOUT:     .Self = imports.%import_ref.5
+// CHECK:STDOUT:     .n = imports.%import_ref.6
+// CHECK:STDOUT:     .F = imports.%import_ref.7
+// CHECK:STDOUT:   }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @Int32() -> type = "int.make_type_32";
@@ -504,21 +609,47 @@ class Class(U:! type) {
 // CHECK:STDOUT:   %.loc6_27.2: type = converted %.loc6_23.3, %.loc6_27.1 [template = constants.%CompleteClass.3]
 // CHECK:STDOUT:   %v.var: ref %CompleteClass.3 = var v
 // CHECK:STDOUT:   %v: ref %CompleteClass.3 = bind_name v, %v.var
-// CHECK:STDOUT:   %F.ref: %F.type = name_ref F, imports.%import_ref.3 [template = constants.%F]
+// CHECK:STDOUT:   %F.ref: %F.type.4 = name_ref F, imports.%import_ref.3 [template = constants.%F.4]
 // CHECK:STDOUT:   %.loc6_7: ref %CompleteClass.3 = splice_block %v.var {}
 // CHECK:STDOUT:   %F.call: init %CompleteClass.3 = call %F.ref() to %.loc6_7
 // CHECK:STDOUT:   assign %v.var, %F.call
 // CHECK:STDOUT:   %v.ref: ref %CompleteClass.3 = name_ref v, %v
-// CHECK:STDOUT:   %n.ref: %.4 = name_ref n, imports.%import_ref.6 [template = imports.%.1]
+// CHECK:STDOUT:   %n.ref: %.3 = name_ref n, imports.%import_ref.6 [template = imports.%.1]
 // CHECK:STDOUT:   %.loc12: i32 = class_element_access <error>, element0 [template = <error>]
 // CHECK:STDOUT:   return <error>
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @F() -> %CompleteClass.3;
+// CHECK:STDOUT: generic fn @F.1(constants.%T: type) {
+// CHECK:STDOUT: !definition:
 // CHECK:STDOUT:
-// CHECK:STDOUT: specific invalid(constants.%T);
+// CHECK:STDOUT:   fn() -> i32;
+// CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: specific invalid(i32);
+// CHECK:STDOUT: fn @F.2() -> %CompleteClass.3;
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @CompleteClass(constants.%T) {
+// CHECK:STDOUT:   %T => constants.%T
+// CHECK:STDOUT:
+// CHECK:STDOUT: !definition:
+// CHECK:STDOUT:   %CompleteClass => constants.%CompleteClass.2
+// CHECK:STDOUT:   %.1 => constants.%.3
+// CHECK:STDOUT:   %F.type => constants.%F.type.2
+// CHECK:STDOUT:   %F => constants.%F.2
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @CompleteClass(@CompleteClass.%T) {
+// CHECK:STDOUT:   %T => constants.%T
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @CompleteClass(i32) {
+// CHECK:STDOUT:   %T => i32
+// CHECK:STDOUT:
+// CHECK:STDOUT: !definition:
+// CHECK:STDOUT:   %CompleteClass => constants.%CompleteClass.3
+// CHECK:STDOUT:   %.1 => constants.%.4
+// CHECK:STDOUT:   %F.type => constants.%F.type.3
+// CHECK:STDOUT:   %F => constants.%F.3
+// CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: --- fail_generic_arg_mismatch.carbon
 // CHECK:STDOUT:
@@ -530,21 +661,32 @@ class Class(U:! type) {
 // CHECK:STDOUT:   %CompleteClass.1: %CompleteClass.type = struct_value () [template]
 // CHECK:STDOUT:   %.2: type = struct_type {.n: i32} [template]
 // CHECK:STDOUT:   %T: type = bind_symbolic_name T 0 [symbolic]
-// CHECK:STDOUT:   %CompleteClass.2: type = class_type @CompleteClass, invalid(%T) [symbolic]
+// CHECK:STDOUT:   %CompleteClass.2: type = class_type @CompleteClass, @CompleteClass(%T) [symbolic]
+// CHECK:STDOUT:   %F.type.1: type = fn_type @F.1 [template]
+// CHECK:STDOUT:   %F.1: %F.type.1 = struct_value () [template]
+// CHECK:STDOUT:   %F.type.2: type = fn_type @F.1, @CompleteClass(%T) [symbolic]
+// CHECK:STDOUT:   %F.2: %F.type.2 = struct_value () [symbolic]
+// CHECK:STDOUT:   %.3: type = unbound_element_type %CompleteClass.2, i32 [symbolic]
 // CHECK:STDOUT:   %Int32.type: type = fn_type @Int32 [template]
 // CHECK:STDOUT:   %Int32: %Int32.type = struct_value () [template]
-// CHECK:STDOUT:   %.3: type = ptr_type i32 [template]
-// CHECK:STDOUT:   %CompleteClass.3: type = class_type @CompleteClass, invalid(%.3) [template]
-// CHECK:STDOUT:   %.4: type = ptr_type %.2 [template]
-// CHECK:STDOUT:   %CompleteClass.4: type = class_type @CompleteClass, invalid(i32) [template]
-// CHECK:STDOUT:   %F.type: type = fn_type @F [template]
-// CHECK:STDOUT:   %F: %F.type = struct_value () [template]
+// CHECK:STDOUT:   %.4: type = ptr_type i32 [template]
+// CHECK:STDOUT:   %CompleteClass.3: type = class_type @CompleteClass, @CompleteClass(%.4) [template]
+// CHECK:STDOUT:   %.5: type = unbound_element_type %CompleteClass.3, i32 [template]
+// CHECK:STDOUT:   %F.type.3: type = fn_type @F.1, @CompleteClass(%.4) [template]
+// CHECK:STDOUT:   %F.3: %F.type.3 = struct_value () [template]
+// CHECK:STDOUT:   %.6: type = ptr_type %.2 [template]
+// CHECK:STDOUT:   %CompleteClass.4: type = class_type @CompleteClass, @CompleteClass(i32) [template]
+// CHECK:STDOUT:   %F.type.4: type = fn_type @F.2 [template]
+// CHECK:STDOUT:   %F.4: %F.type.4 = struct_value () [template]
+// CHECK:STDOUT:   %.7: type = unbound_element_type %CompleteClass.4, i32 [template]
+// CHECK:STDOUT:   %F.type.5: type = fn_type @F.1, @CompleteClass(i32) [template]
+// CHECK:STDOUT:   %F.5: %F.type.5 = struct_value () [template]
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: imports {
 // CHECK:STDOUT:   %import_ref.1 = import_ref Main//foo, inst+6, unloaded
 // CHECK:STDOUT:   %import_ref.2: %CompleteClass.type = import_ref Main//foo, inst+14, loaded [template = constants.%CompleteClass.1]
-// CHECK:STDOUT:   %import_ref.3: %F.type = import_ref Main//foo, inst+55, loaded [template = constants.%F]
+// CHECK:STDOUT:   %import_ref.3: %F.type.4 = import_ref Main//foo, inst+55, loaded [template = constants.%F.4]
 // CHECK:STDOUT:   %Core: <namespace> = namespace file.%Core.import, [template] {
 // CHECK:STDOUT:     .Int32 = %import_ref.7
 // CHECK:STDOUT:     import Core//prelude
@@ -574,11 +716,21 @@ class Class(U:! type) {
 // CHECK:STDOUT:   %Use.decl: %Use.type = fn_decl @Use [template = constants.%Use] {}
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: class @CompleteClass {
-// CHECK:STDOUT: !members:
-// CHECK:STDOUT:   .Self = imports.%import_ref.4
-// CHECK:STDOUT:   .n = imports.%import_ref.5
-// CHECK:STDOUT:   .F = imports.%import_ref.6
+// CHECK:STDOUT: generic class @CompleteClass(constants.%T: type) {
+// CHECK:STDOUT:   %T: type = bind_symbolic_name T 0 [symbolic = %T (constants.%T)]
+// CHECK:STDOUT:
+// CHECK:STDOUT: !definition:
+// CHECK:STDOUT:   %CompleteClass: type = class_type @CompleteClass, @CompleteClass(%T) [symbolic = %CompleteClass (constants.%CompleteClass.2)]
+// CHECK:STDOUT:   %.1: type = unbound_element_type @CompleteClass.%CompleteClass (%CompleteClass.2), i32 [symbolic = %.1 (constants.%.3)]
+// CHECK:STDOUT:   %F.type: type = fn_type @F.1, @CompleteClass(%T) [symbolic = %F.type (constants.%F.type.2)]
+// CHECK:STDOUT:   %F: @CompleteClass.%F.type (%F.type.2) = struct_value () [symbolic = %F (constants.%F.2)]
+// CHECK:STDOUT:
+// CHECK:STDOUT:   class {
+// CHECK:STDOUT:   !members:
+// CHECK:STDOUT:     .Self = imports.%import_ref.4
+// CHECK:STDOUT:     .n = imports.%import_ref.5
+// CHECK:STDOUT:     .F = imports.%import_ref.6
+// CHECK:STDOUT:   }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @Use() {
@@ -587,28 +739,56 @@ class Class(U:! type) {
 // CHECK:STDOUT:   %int.make_type_32: init type = call constants.%Int32() [template = i32]
 // CHECK:STDOUT:   %.loc11_27.1: type = value_of_initializer %int.make_type_32 [template = i32]
 // CHECK:STDOUT:   %.loc11_27.2: type = converted %int.make_type_32, %.loc11_27.1 [template = i32]
-// CHECK:STDOUT:   %.loc11_27.3: type = ptr_type i32 [template = constants.%.3]
+// CHECK:STDOUT:   %.loc11_27.3: type = ptr_type i32 [template = constants.%.4]
 // CHECK:STDOUT:   %.loc11_23: init type = call %CompleteClass.ref(%.loc11_27.3) [template = constants.%CompleteClass.3]
 // CHECK:STDOUT:   %.loc11_28.1: type = value_of_initializer %.loc11_23 [template = constants.%CompleteClass.3]
 // CHECK:STDOUT:   %.loc11_28.2: type = converted %.loc11_23, %.loc11_28.1 [template = constants.%CompleteClass.3]
 // CHECK:STDOUT:   %v.var: ref %CompleteClass.3 = var v
 // CHECK:STDOUT:   %v: ref %CompleteClass.3 = bind_name v, %v.var
-// CHECK:STDOUT:   %F.ref: %F.type = name_ref F, imports.%import_ref.3 [template = constants.%F]
+// CHECK:STDOUT:   %F.ref: %F.type.4 = name_ref F, imports.%import_ref.3 [template = constants.%F.4]
 // CHECK:STDOUT:   %.loc11_33: ref %CompleteClass.4 = temporary_storage
 // CHECK:STDOUT:   %F.call: init %CompleteClass.4 = call %F.ref() to %.loc11_33
 // CHECK:STDOUT:   assign %v.var, <error>
 // CHECK:STDOUT:   return
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
+// CHECK:STDOUT: generic fn @F.1(constants.%T: type) {
+// CHECK:STDOUT: !definition:
+// CHECK:STDOUT:
+// CHECK:STDOUT:   fn() -> i32;
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
 // CHECK:STDOUT: fn @Int32() -> type = "int.make_type_32";
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @F() -> %CompleteClass.4;
+// CHECK:STDOUT: fn @F.2() -> %CompleteClass.4;
 // CHECK:STDOUT:
-// CHECK:STDOUT: specific invalid(constants.%T);
+// CHECK:STDOUT: specific @CompleteClass(constants.%T) {
+// CHECK:STDOUT:   %T => constants.%T
+// CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: specific invalid(constants.%.3);
+// CHECK:STDOUT: specific @CompleteClass(@CompleteClass.%T) {
+// CHECK:STDOUT:   %T => constants.%T
+// CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: specific invalid(i32);
+// CHECK:STDOUT: specific @CompleteClass(constants.%.4) {
+// CHECK:STDOUT:   %T => constants.%.4
+// CHECK:STDOUT:
+// CHECK:STDOUT: !definition:
+// CHECK:STDOUT:   %CompleteClass => constants.%CompleteClass.3
+// CHECK:STDOUT:   %.1 => constants.%.5
+// CHECK:STDOUT:   %F.type => constants.%F.type.3
+// CHECK:STDOUT:   %F => constants.%F.3
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @CompleteClass(i32) {
+// CHECK:STDOUT:   %T => i32
+// CHECK:STDOUT:
+// CHECK:STDOUT: !definition:
+// CHECK:STDOUT:   %CompleteClass => constants.%CompleteClass.4
+// CHECK:STDOUT:   %.1 => constants.%.7
+// CHECK:STDOUT:   %F.type => constants.%F.type.5
+// CHECK:STDOUT:   %F => constants.%F.5
+// CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: --- fail_bad_foo.impl.carbon
 // CHECK:STDOUT:
@@ -618,7 +798,7 @@ class Class(U:! type) {
 // CHECK:STDOUT:   %.1: type = tuple_type () [template]
 // CHECK:STDOUT:   %Class.1: %Class.type = struct_value () [template]
 // CHECK:STDOUT:   %T: type = bind_symbolic_name T 0 [symbolic]
-// CHECK:STDOUT:   %Class.2: type = class_type @Class, invalid(%T) [symbolic]
+// CHECK:STDOUT:   %Class.2: type = class_type @Class, @Class(%T) [symbolic]
 // CHECK:STDOUT:   %.type: type = generic_class_type @.1 [template]
 // CHECK:STDOUT:   %.2: %.type = struct_value () [template]
 // CHECK:STDOUT:   %.3: type = class_type @.1, @.1(%U) [symbolic]
@@ -655,7 +835,11 @@ class Class(U:! type) {
 // CHECK:STDOUT:   }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: class @Class;
+// CHECK:STDOUT: generic class @Class(constants.%T: type) {
+// CHECK:STDOUT:   %T: type = bind_symbolic_name T 0 [symbolic = %T (constants.%T)]
+// CHECK:STDOUT:
+// CHECK:STDOUT:   class;
+// CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: generic class @.1(file.%U.loc14_13.2: type) {
 // CHECK:STDOUT:   %U: type = bind_symbolic_name U 0 [symbolic = %U (constants.%U)]
@@ -672,7 +856,9 @@ class Class(U:! type) {
 // CHECK:STDOUT:   }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: specific invalid(constants.%T);
+// CHECK:STDOUT: specific @Class(constants.%T) {
+// CHECK:STDOUT:   %T => constants.%T
+// CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: specific @.1(constants.%U) {
 // CHECK:STDOUT:   %U => constants.%U

+ 116 - 60
toolchain/check/testdata/class/syntactic_merge_literal.carbon

@@ -12,21 +12,21 @@
 
 // --- int_match.carbon
 
-class C(a: i32) {}
-class D(b: C(1_000));
-class D(b: C(1_000)) {}
+class C(a:! i32) {}
+class D(b:! C(1_000));
+class D(b:! C(1_000)) {}
 
 // --- fail_int_mismatch.carbon
 
-class C(a: i32) {}
-class D(b: C(1000));
-// CHECK:STDERR: fail_int_mismatch.carbon:[[@LINE+6]]:14: ERROR: Redeclaration syntax differs here.
-// CHECK:STDERR: class D(b: C(1_000)) {}
-// CHECK:STDERR:              ^~~~~
-// CHECK:STDERR: fail_int_mismatch.carbon:[[@LINE-4]]:14: Comparing with previous declaration here.
-// CHECK:STDERR: class D(b: C(1000));
-// CHECK:STDERR:              ^~~~
-class D(b: C(1_000)) {}
+class C(a:! i32) {}
+class D(b:! C(1000));
+// CHECK:STDERR: fail_int_mismatch.carbon:[[@LINE+6]]:15: ERROR: Redeclaration syntax differs here.
+// CHECK:STDERR: class D(b:! C(1_000)) {}
+// CHECK:STDERR:               ^~~~~
+// CHECK:STDERR: fail_int_mismatch.carbon:[[@LINE-4]]:15: Comparing with previous declaration here.
+// CHECK:STDERR: class D(b:! C(1000));
+// CHECK:STDERR:               ^~~~
+class D(b:! C(1_000)) {}
 
 // CHECK:STDOUT: --- int_match.carbon
 // CHECK:STDOUT:
@@ -34,15 +34,17 @@ class D(b: C(1_000)) {}
 // CHECK:STDOUT:   %Int32.type: type = fn_type @Int32 [template]
 // CHECK:STDOUT:   %.1: type = tuple_type () [template]
 // CHECK:STDOUT:   %Int32: %Int32.type = struct_value () [template]
+// CHECK:STDOUT:   %a: i32 = bind_symbolic_name a 0 [symbolic]
 // CHECK:STDOUT:   %C.type: type = generic_class_type @C [template]
 // CHECK:STDOUT:   %C.1: %C.type = struct_value () [template]
-// CHECK:STDOUT:   %C.2: type = class_type @C [template]
+// CHECK:STDOUT:   %C.2: type = class_type @C, @C(%a) [symbolic]
 // CHECK:STDOUT:   %.2: type = struct_type {} [template]
 // CHECK:STDOUT:   %.3: i32 = int_literal 1000 [template]
-// CHECK:STDOUT:   %C.3: type = class_type @C, invalid(%.3) [template]
+// CHECK:STDOUT:   %C.3: type = class_type @C, @C(%.3) [template]
+// CHECK:STDOUT:   %b: %C.3 = bind_symbolic_name b 0 [symbolic]
 // CHECK:STDOUT:   %D.type: type = generic_class_type @D [template]
 // CHECK:STDOUT:   %D.1: %D.type = struct_value () [template]
-// CHECK:STDOUT:   %D.2: type = class_type @D [template]
+// CHECK:STDOUT:   %D.2: type = class_type @D, @D(%b) [symbolic]
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: imports {
@@ -68,44 +70,66 @@ class D(b: C(1_000)) {}
 // CHECK:STDOUT:   %Core.import = import Core
 // CHECK:STDOUT:   %C.decl: %C.type = class_decl @C [template = constants.%C.1] {
 // CHECK:STDOUT:     %int.make_type_32: init type = call constants.%Int32() [template = i32]
-// CHECK:STDOUT:     %.loc2_12.1: type = value_of_initializer %int.make_type_32 [template = i32]
-// CHECK:STDOUT:     %.loc2_12.2: type = converted %int.make_type_32, %.loc2_12.1 [template = i32]
+// CHECK:STDOUT:     %.loc2_13.1: type = value_of_initializer %int.make_type_32 [template = i32]
+// CHECK:STDOUT:     %.loc2_13.2: type = converted %int.make_type_32, %.loc2_13.1 [template = i32]
 // CHECK:STDOUT:     %a.loc2_9.1: i32 = param a
-// CHECK:STDOUT:     %a.loc2_9.2: i32 = bind_name a, %a.loc2_9.1
+// CHECK:STDOUT:     %a.loc2_9.2: i32 = bind_symbolic_name a 0, %a.loc2_9.1 [symbolic = @C.%a (constants.%a)]
 // CHECK:STDOUT:   }
 // CHECK:STDOUT:   %D.decl.loc3: %D.type = class_decl @D [template = constants.%D.1] {
 // CHECK:STDOUT:     %C.ref.loc3: %C.type = name_ref C, %C.decl [template = constants.%C.1]
-// CHECK:STDOUT:     %.loc3_14: i32 = int_literal 1000 [template = constants.%.3]
-// CHECK:STDOUT:     %.loc3_13: init type = call %C.ref.loc3(%.loc3_14) [template = constants.%C.3]
-// CHECK:STDOUT:     %.loc3_19.1: type = value_of_initializer %.loc3_13 [template = constants.%C.3]
-// CHECK:STDOUT:     %.loc3_19.2: type = converted %.loc3_13, %.loc3_19.1 [template = constants.%C.3]
+// CHECK:STDOUT:     %.loc3_15: i32 = int_literal 1000 [template = constants.%.3]
+// CHECK:STDOUT:     %.loc3_14: init type = call %C.ref.loc3(%.loc3_15) [template = constants.%C.3]
+// CHECK:STDOUT:     %.loc3_20.1: type = value_of_initializer %.loc3_14 [template = constants.%C.3]
+// CHECK:STDOUT:     %.loc3_20.2: type = converted %.loc3_14, %.loc3_20.1 [template = constants.%C.3]
 // CHECK:STDOUT:     %b.loc3_9.1: %C.3 = param b
-// CHECK:STDOUT:     %b.loc3_9.2: %C.3 = bind_name b, %b.loc3_9.1
+// CHECK:STDOUT:     %b.loc3_9.2: %C.3 = bind_symbolic_name b 0, %b.loc3_9.1 [symbolic = @D.%b (constants.%b)]
 // CHECK:STDOUT:   }
 // CHECK:STDOUT:   %D.decl.loc4: %D.type = class_decl @D [template = constants.%D.1] {
 // CHECK:STDOUT:     %C.ref.loc4: %C.type = name_ref C, %C.decl [template = constants.%C.1]
-// CHECK:STDOUT:     %.loc4_14: i32 = int_literal 1000 [template = constants.%.3]
-// CHECK:STDOUT:     %.loc4_13: init type = call %C.ref.loc4(%.loc4_14) [template = constants.%C.3]
-// CHECK:STDOUT:     %.loc4_19.1: type = value_of_initializer %.loc4_13 [template = constants.%C.3]
-// CHECK:STDOUT:     %.loc4_19.2: type = converted %.loc4_13, %.loc4_19.1 [template = constants.%C.3]
+// CHECK:STDOUT:     %.loc4_15: i32 = int_literal 1000 [template = constants.%.3]
+// CHECK:STDOUT:     %.loc4_14: init type = call %C.ref.loc4(%.loc4_15) [template = constants.%C.3]
+// CHECK:STDOUT:     %.loc4_20.1: type = value_of_initializer %.loc4_14 [template = constants.%C.3]
+// CHECK:STDOUT:     %.loc4_20.2: type = converted %.loc4_14, %.loc4_20.1 [template = constants.%C.3]
 // CHECK:STDOUT:     %b.loc4_9.1: %C.3 = param b
-// CHECK:STDOUT:     %b.loc4_9.2: %C.3 = bind_name b, %b.loc4_9.1
+// CHECK:STDOUT:     %b.loc4_9.2: %C.3 = bind_symbolic_name b 0, %b.loc4_9.1 [symbolic = constants.%b]
 // CHECK:STDOUT:   }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: class @C {
-// CHECK:STDOUT: !members:
-// CHECK:STDOUT:   .Self = constants.%C.2
+// CHECK:STDOUT: generic class @C(file.%a.loc2_9.2: i32) {
+// CHECK:STDOUT:   %a: i32 = bind_symbolic_name a 0 [symbolic = %a (constants.%a)]
+// CHECK:STDOUT:
+// CHECK:STDOUT: !definition:
+// CHECK:STDOUT:
+// CHECK:STDOUT:   class {
+// CHECK:STDOUT:   !members:
+// CHECK:STDOUT:     .Self = constants.%C.2
+// CHECK:STDOUT:   }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: class @D {
-// CHECK:STDOUT: !members:
-// CHECK:STDOUT:   .Self = constants.%D.2
+// CHECK:STDOUT: generic class @D(file.%b.loc3_9.2: %C.3) {
+// CHECK:STDOUT:   %b: %C.3 = bind_symbolic_name b 0 [symbolic = %b (constants.%b)]
+// CHECK:STDOUT:
+// CHECK:STDOUT: !definition:
+// CHECK:STDOUT:
+// CHECK:STDOUT:   class {
+// CHECK:STDOUT:   !members:
+// CHECK:STDOUT:     .Self = constants.%D.2
+// CHECK:STDOUT:   }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @Int32() -> type = "int.make_type_32";
 // CHECK:STDOUT:
-// CHECK:STDOUT: specific invalid(constants.%.3);
+// CHECK:STDOUT: specific @C(constants.%a) {
+// CHECK:STDOUT:   %a => constants.%a
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @C(constants.%.3) {
+// CHECK:STDOUT:   %a => constants.%.3
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @D(constants.%b) {
+// CHECK:STDOUT:   %b => constants.%b
+// CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: --- fail_int_mismatch.carbon
 // CHECK:STDOUT:
@@ -113,18 +137,20 @@ class D(b: C(1_000)) {}
 // CHECK:STDOUT:   %Int32.type: type = fn_type @Int32 [template]
 // CHECK:STDOUT:   %.1: type = tuple_type () [template]
 // CHECK:STDOUT:   %Int32: %Int32.type = struct_value () [template]
+// CHECK:STDOUT:   %a: i32 = bind_symbolic_name a 0 [symbolic]
 // CHECK:STDOUT:   %C.type: type = generic_class_type @C [template]
 // CHECK:STDOUT:   %C.1: %C.type = struct_value () [template]
-// CHECK:STDOUT:   %C.2: type = class_type @C [template]
+// CHECK:STDOUT:   %C.2: type = class_type @C, @C(%a) [symbolic]
 // CHECK:STDOUT:   %.2: type = struct_type {} [template]
 // CHECK:STDOUT:   %.3: i32 = int_literal 1000 [template]
-// CHECK:STDOUT:   %C.3: type = class_type @C, invalid(%.3) [template]
+// CHECK:STDOUT:   %C.3: type = class_type @C, @C(%.3) [template]
+// CHECK:STDOUT:   %b: %C.3 = bind_symbolic_name b 0 [symbolic]
 // CHECK:STDOUT:   %D.type: type = generic_class_type @D [template]
 // CHECK:STDOUT:   %D.1: %D.type = struct_value () [template]
-// CHECK:STDOUT:   %D.2: type = class_type @D [template]
+// CHECK:STDOUT:   %D.2: type = class_type @D, @D(%b) [symbolic]
 // CHECK:STDOUT:   %.type: type = generic_class_type @.1 [template]
 // CHECK:STDOUT:   %.4: %.type = struct_value () [template]
-// CHECK:STDOUT:   %.5: type = class_type @.1 [template]
+// CHECK:STDOUT:   %.5: type = class_type @.1, @.1(%b) [symbolic]
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: imports {
@@ -150,44 +176,74 @@ class D(b: C(1_000)) {}
 // CHECK:STDOUT:   %Core.import = import Core
 // CHECK:STDOUT:   %C.decl: %C.type = class_decl @C [template = constants.%C.1] {
 // CHECK:STDOUT:     %int.make_type_32: init type = call constants.%Int32() [template = i32]
-// CHECK:STDOUT:     %.loc2_12.1: type = value_of_initializer %int.make_type_32 [template = i32]
-// CHECK:STDOUT:     %.loc2_12.2: type = converted %int.make_type_32, %.loc2_12.1 [template = i32]
+// CHECK:STDOUT:     %.loc2_13.1: type = value_of_initializer %int.make_type_32 [template = i32]
+// CHECK:STDOUT:     %.loc2_13.2: type = converted %int.make_type_32, %.loc2_13.1 [template = i32]
 // CHECK:STDOUT:     %a.loc2_9.1: i32 = param a
-// CHECK:STDOUT:     %a.loc2_9.2: i32 = bind_name a, %a.loc2_9.1
+// CHECK:STDOUT:     %a.loc2_9.2: i32 = bind_symbolic_name a 0, %a.loc2_9.1 [symbolic = @C.%a (constants.%a)]
 // CHECK:STDOUT:   }
 // CHECK:STDOUT:   %D.decl: %D.type = class_decl @D [template = constants.%D.1] {
 // CHECK:STDOUT:     %C.ref.loc3: %C.type = name_ref C, %C.decl [template = constants.%C.1]
-// CHECK:STDOUT:     %.loc3_14: i32 = int_literal 1000 [template = constants.%.3]
-// CHECK:STDOUT:     %.loc3_13: init type = call %C.ref.loc3(%.loc3_14) [template = constants.%C.3]
-// CHECK:STDOUT:     %.loc3_18.1: type = value_of_initializer %.loc3_13 [template = constants.%C.3]
-// CHECK:STDOUT:     %.loc3_18.2: type = converted %.loc3_13, %.loc3_18.1 [template = constants.%C.3]
+// CHECK:STDOUT:     %.loc3_15: i32 = int_literal 1000 [template = constants.%.3]
+// CHECK:STDOUT:     %.loc3_14: init type = call %C.ref.loc3(%.loc3_15) [template = constants.%C.3]
+// CHECK:STDOUT:     %.loc3_19.1: type = value_of_initializer %.loc3_14 [template = constants.%C.3]
+// CHECK:STDOUT:     %.loc3_19.2: type = converted %.loc3_14, %.loc3_19.1 [template = constants.%C.3]
 // CHECK:STDOUT:     %b.loc3_9.1: %C.3 = param b
-// CHECK:STDOUT:     %b.loc3_9.2: %C.3 = bind_name b, %b.loc3_9.1
+// CHECK:STDOUT:     %b.loc3_9.2: %C.3 = bind_symbolic_name b 0, %b.loc3_9.1 [symbolic = @D.%b (constants.%b)]
 // CHECK:STDOUT:   }
 // CHECK:STDOUT:   %.decl: %.type = class_decl @.1 [template = constants.%.4] {
 // CHECK:STDOUT:     %C.ref.loc10: %C.type = name_ref C, %C.decl [template = constants.%C.1]
-// CHECK:STDOUT:     %.loc10_14: i32 = int_literal 1000 [template = constants.%.3]
-// CHECK:STDOUT:     %.loc10_13: init type = call %C.ref.loc10(%.loc10_14) [template = constants.%C.3]
-// CHECK:STDOUT:     %.loc10_19.1: type = value_of_initializer %.loc10_13 [template = constants.%C.3]
-// CHECK:STDOUT:     %.loc10_19.2: type = converted %.loc10_13, %.loc10_19.1 [template = constants.%C.3]
+// CHECK:STDOUT:     %.loc10_15: i32 = int_literal 1000 [template = constants.%.3]
+// CHECK:STDOUT:     %.loc10_14: init type = call %C.ref.loc10(%.loc10_15) [template = constants.%C.3]
+// CHECK:STDOUT:     %.loc10_20.1: type = value_of_initializer %.loc10_14 [template = constants.%C.3]
+// CHECK:STDOUT:     %.loc10_20.2: type = converted %.loc10_14, %.loc10_20.1 [template = constants.%C.3]
 // CHECK:STDOUT:     %b.loc10_9.1: %C.3 = param b
-// CHECK:STDOUT:     %b.loc10_9.2: %C.3 = bind_name b, %b.loc10_9.1
+// CHECK:STDOUT:     %b.loc10_9.2: %C.3 = bind_symbolic_name b 0, %b.loc10_9.1 [symbolic = @.1.%b (constants.%b)]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: generic class @C(file.%a.loc2_9.2: i32) {
+// CHECK:STDOUT:   %a: i32 = bind_symbolic_name a 0 [symbolic = %a (constants.%a)]
+// CHECK:STDOUT:
+// CHECK:STDOUT: !definition:
+// CHECK:STDOUT:
+// CHECK:STDOUT:   class {
+// CHECK:STDOUT:   !members:
+// CHECK:STDOUT:     .Self = constants.%C.2
 // CHECK:STDOUT:   }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: class @C {
-// CHECK:STDOUT: !members:
-// CHECK:STDOUT:   .Self = constants.%C.2
+// CHECK:STDOUT: generic class @D(file.%b.loc3_9.2: %C.3) {
+// CHECK:STDOUT:   %b: %C.3 = bind_symbolic_name b 0 [symbolic = %b (constants.%b)]
+// CHECK:STDOUT:
+// CHECK:STDOUT:   class;
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: class @D;
+// CHECK:STDOUT: generic class @.1(file.%b.loc10_9.2: %C.3) {
+// CHECK:STDOUT:   %b: %C.3 = bind_symbolic_name b 0 [symbolic = %b (constants.%b)]
 // CHECK:STDOUT:
-// CHECK:STDOUT: class @.1 {
-// CHECK:STDOUT: !members:
-// CHECK:STDOUT:   .Self = constants.%.5
+// CHECK:STDOUT: !definition:
+// CHECK:STDOUT:
+// CHECK:STDOUT:   class {
+// CHECK:STDOUT:   !members:
+// CHECK:STDOUT:     .Self = constants.%.5
+// CHECK:STDOUT:   }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @Int32() -> type = "int.make_type_32";
 // CHECK:STDOUT:
-// CHECK:STDOUT: specific invalid(constants.%.3);
+// CHECK:STDOUT: specific @C(constants.%a) {
+// CHECK:STDOUT:   %a => constants.%a
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @C(constants.%.3) {
+// CHECK:STDOUT:   %a => constants.%.3
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @D(constants.%b) {
+// CHECK:STDOUT:   %b => constants.%b
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @.1(constants.%b) {
+// CHECK:STDOUT:   %b => constants.%b
+// CHECK:STDOUT: }
 // CHECK:STDOUT:

+ 5 - 1
toolchain/check/testdata/function/builtin/no_prelude/call_from_operator.carbon

@@ -191,7 +191,11 @@ var arr: [i32; 1 + 2] = (3, 4, 3 + 4);
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @Op.1[@impl.%self.loc7_9.2: i32](@impl.%other.loc7_21.2: i32) -> i32 = "int.sadd";
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @Op.2[%self: %Self](%other: %Self) -> %Self;
+// CHECK:STDOUT: generic fn @Op.2(constants.%Self: %.2) {
+// CHECK:STDOUT:   %Self: %.2 = bind_symbolic_name Self 0 [symbolic = %Self (constants.%Self)]
+// CHECK:STDOUT:
+// CHECK:STDOUT:   fn[%self: %Self](%other: %Self) -> %Self;
+// CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @__global_init() {
 // CHECK:STDOUT: !entry:

+ 4 - 1
toolchain/check/testdata/impl/lookup/import.carbon

@@ -197,7 +197,10 @@ fn G(c: Impl.C) {
 // CHECK:STDOUT:   return
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @F.1();
+// CHECK:STDOUT: generic fn @F.1(constants.%Self: %.2) {
+// CHECK:STDOUT:
+// CHECK:STDOUT:   fn();
+// CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @F.2();
 // CHECK:STDOUT:

+ 4 - 1
toolchain/check/testdata/impl/lookup/no_prelude/import.carbon

@@ -172,7 +172,10 @@ fn G(c: Impl.C) {
 // CHECK:STDOUT:   return
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @F.1();
+// CHECK:STDOUT: generic fn @F.1(constants.%Self: %.2) {
+// CHECK:STDOUT:
+// CHECK:STDOUT:   fn();
+// CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @F.2();
 // CHECK:STDOUT:

+ 5 - 1
toolchain/check/testdata/impl/no_prelude/import_self.carbon

@@ -170,7 +170,11 @@ fn F(x: (), y: ()) -> () {
 // CHECK:STDOUT:   return %.loc7_53
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @Op.2[%self: %Self](%other: %Self) -> %Self;
+// CHECK:STDOUT: generic fn @Op.2(constants.%Self: %.2) {
+// CHECK:STDOUT:   %Self: %.2 = bind_symbolic_name Self 0 [symbolic = %Self (constants.%Self)]
+// CHECK:STDOUT:
+// CHECK:STDOUT:   fn[%self: %Self](%other: %Self) -> %Self;
+// CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @F(%x: %.1, %y: %.1) -> %.1 {
 // CHECK:STDOUT: !entry:

+ 5 - 1
toolchain/check/testdata/index/fail_negative_indexing.carbon

@@ -88,7 +88,11 @@ var b: i32 = a[-10];
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @Int32() -> type = "int.make_type_32";
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @Op[%self: %Self]() -> %Self;
+// CHECK:STDOUT: generic fn @Op(constants.%Self: %.8) {
+// CHECK:STDOUT:   %Self: %.8 = bind_symbolic_name Self 0 [symbolic = %Self (constants.%Self)]
+// CHECK:STDOUT:
+// CHECK:STDOUT:   fn[%self: %Self]() -> %Self;
+// CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @__global_init() {
 // CHECK:STDOUT: !entry:

+ 51 - 23
toolchain/check/testdata/interface/no_prelude/generic_import.carbon

@@ -99,21 +99,26 @@ impl C as AddWith(C) {
 // CHECK:STDOUT:   %.2: type = tuple_type () [template]
 // CHECK:STDOUT:   %AddWith: %AddWith.type = struct_value () [template]
 // CHECK:STDOUT:   %T: type = bind_symbolic_name T 0 [symbolic]
-// CHECK:STDOUT:   %.3: type = interface_type @AddWith, invalid(%T) [symbolic]
+// CHECK:STDOUT:   %.3: type = interface_type @AddWith, @AddWith(%T) [symbolic]
 // CHECK:STDOUT:   %Self: %.3 = bind_symbolic_name Self 1 [symbolic]
-// CHECK:STDOUT:   %.4: type = interface_type @AddWith, invalid(%C) [template]
 // CHECK:STDOUT:   %F.type.1: type = fn_type @F.1 [template]
 // CHECK:STDOUT:   %F.1: %F.type.1 = struct_value () [template]
-// CHECK:STDOUT:   %F.type.2: type = fn_type @F.2 [template]
-// CHECK:STDOUT:   %F.2: %F.type.2 = struct_value () [template]
-// CHECK:STDOUT:   %.5: <witness> = interface_witness (%F.1) [template]
+// CHECK:STDOUT:   %F.type.2: type = fn_type @F.1, @AddWith(%T) [symbolic]
+// CHECK:STDOUT:   %.4: type = assoc_entity_type @AddWith, %F.type.2 [symbolic]
+// CHECK:STDOUT:   %.5: %.4 = assoc_entity element0, imports.%import_ref.2 [symbolic]
+// CHECK:STDOUT:   %F.2: %F.type.2 = struct_value () [symbolic]
+// CHECK:STDOUT:   %.6: type = interface_type @AddWith, @AddWith(%C) [template]
+// CHECK:STDOUT:   %F.type.3: type = fn_type @F.2 [template]
+// CHECK:STDOUT:   %F.3: %F.type.3 = struct_value () [template]
+// CHECK:STDOUT:   %.7: <witness> = interface_witness (%F.3) [template]
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: imports {
 // CHECK:STDOUT:   %import_ref.1: %AddWith.type = import_ref Main//a, inst+4, loaded [template = constants.%AddWith]
-// CHECK:STDOUT:   %import_ref.2 = import_ref Main//a, inst+10, unloaded
-// CHECK:STDOUT:   %import_ref.3 = import_ref Main//a, inst+16, unloaded
-// CHECK:STDOUT:   %import_ref.4: %F.type.2 = import_ref Main//a, inst+12, loaded [template = constants.%F.2]
+// CHECK:STDOUT:   %import_ref.2 = import_ref Main//a, inst+12, unloaded
+// CHECK:STDOUT:   %import_ref.3 = import_ref Main//a, inst+10, unloaded
+// CHECK:STDOUT:   %import_ref.4 = import_ref Main//a, inst+16, unloaded
+// CHECK:STDOUT:   %import_ref.5: %F.type.2 = import_ref Main//a, inst+12, loaded [template = constants.%F.1]
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: file {
@@ -123,26 +128,38 @@ impl C as AddWith(C) {
 // CHECK:STDOUT:   }
 // CHECK:STDOUT:   %default.import = import <invalid>
 // CHECK:STDOUT:   %C.decl: type = class_decl @C [template = constants.%C] {}
-// CHECK:STDOUT:   %.loc7_20.1: type = value_of_initializer %.loc7_18 [template = constants.%.4]
-// CHECK:STDOUT:   %.loc7_20.2: type = converted %.loc7_18, %.loc7_20.1 [template = constants.%.4]
+// CHECK:STDOUT:   %.loc7_20.1: type = value_of_initializer %.loc7_18 [template = constants.%.6]
+// CHECK:STDOUT:   %.loc7_20.2: type = converted %.loc7_18, %.loc7_20.1 [template = constants.%.6]
 // CHECK:STDOUT:   impl_decl @impl {
 // CHECK:STDOUT:     %C.ref.loc7_6: type = name_ref C, %C.decl [template = constants.%C]
 // CHECK:STDOUT:     %AddWith.ref: %AddWith.type = name_ref AddWith, imports.%import_ref.1 [template = constants.%AddWith]
 // CHECK:STDOUT:     %C.ref.loc7_19: type = name_ref C, %C.decl [template = constants.%C]
-// CHECK:STDOUT:     %.loc7_18: init type = call %AddWith.ref(%C.ref.loc7_19) [template = constants.%.4]
+// CHECK:STDOUT:     %.loc7_18: init type = call %AddWith.ref(%C.ref.loc7_19) [template = constants.%.6]
 // CHECK:STDOUT:   }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: interface @AddWith {
-// CHECK:STDOUT: !members:
-// CHECK:STDOUT:   .Self = imports.%import_ref.2
-// CHECK:STDOUT:   .F = imports.%import_ref.3
-// CHECK:STDOUT:   witness = (imports.%import_ref.4)
+// CHECK:STDOUT: generic interface @AddWith(constants.%T: type) {
+// CHECK:STDOUT:   %T: type = bind_symbolic_name T 0 [symbolic = %T (constants.%T)]
+// CHECK:STDOUT:
+// CHECK:STDOUT: !definition:
+// CHECK:STDOUT:   %.1: type = interface_type @AddWith, @AddWith(%T) [symbolic = %.1 (constants.%.3)]
+// CHECK:STDOUT:   %Self: %.3 = bind_symbolic_name Self 1 [symbolic = %Self (constants.%Self)]
+// CHECK:STDOUT:   %F.type: type = fn_type @F.1, @AddWith(%T) [symbolic = %F.type (constants.%F.type.2)]
+// CHECK:STDOUT:   %F: @AddWith.%F.type (%F.type.2) = struct_value () [symbolic = %F (constants.%F.2)]
+// CHECK:STDOUT:   %.2: type = assoc_entity_type @AddWith, @AddWith.%F.type (%F.type.2) [symbolic = %.2 (constants.%.4)]
+// CHECK:STDOUT:   %.3: @AddWith.%.2 (%.4) = assoc_entity element0, imports.%import_ref.2 [symbolic = %.3 (constants.%.5)]
+// CHECK:STDOUT:
+// CHECK:STDOUT:   interface {
+// CHECK:STDOUT:   !members:
+// CHECK:STDOUT:     .Self = imports.%import_ref.3
+// CHECK:STDOUT:     .F = imports.%import_ref.4
+// CHECK:STDOUT:     witness = (imports.%import_ref.5)
+// CHECK:STDOUT:   }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: impl @impl: %C as %.4 {
-// CHECK:STDOUT:   %F.decl: %F.type.1 = fn_decl @F.1 [template = constants.%F.1] {}
-// CHECK:STDOUT:   %.1: <witness> = interface_witness (%F.decl) [template = constants.%.5]
+// CHECK:STDOUT: impl @impl: %C as %.6 {
+// CHECK:STDOUT:   %F.decl: %F.type.3 = fn_decl @F.2 [template = constants.%F.3] {}
+// CHECK:STDOUT:   %.1: <witness> = interface_witness (%F.decl) [template = constants.%.7]
 // CHECK:STDOUT:
 // CHECK:STDOUT: !members:
 // CHECK:STDOUT:   .F = %F.decl
@@ -154,14 +171,25 @@ impl C as AddWith(C) {
 // CHECK:STDOUT:   .Self = constants.%C
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @F.1() {
+// CHECK:STDOUT: generic fn @F.1(constants.%T: type, constants.%Self: %.3) {
+// CHECK:STDOUT:
+// CHECK:STDOUT:   fn();
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @F.2() {
 // CHECK:STDOUT: !entry:
 // CHECK:STDOUT:   return
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @F.2();
+// CHECK:STDOUT: specific @AddWith(constants.%T) {
+// CHECK:STDOUT:   %T => constants.%T
+// CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: specific invalid(constants.%T);
+// CHECK:STDOUT: specific @AddWith(@AddWith.%T) {
+// CHECK:STDOUT:   %T => constants.%T
+// CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: specific invalid(constants.%C);
+// CHECK:STDOUT: specific @AddWith(constants.%C) {
+// CHECK:STDOUT:   %T => constants.%C
+// CHECK:STDOUT: }
 // CHECK:STDOUT:

+ 8 - 2
toolchain/check/testdata/interface/no_prelude/import.carbon

@@ -281,9 +281,15 @@ var f: ForwardDeclared* = &f_ref.f;
 // CHECK:STDOUT:   return
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @F.1();
+// CHECK:STDOUT: generic fn @F.1(constants.%Self.2: %.3) {
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @F.2();
+// CHECK:STDOUT:   fn();
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: generic fn @F.2(constants.%Self.3: %.4) {
+// CHECK:STDOUT:
+// CHECK:STDOUT:   fn();
+// CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @__global_init() {
 // CHECK:STDOUT: !entry:

+ 5 - 1
toolchain/check/testdata/operators/builtin/fail_type_mismatch_once.carbon

@@ -90,5 +90,9 @@ fn Main() -> i32 {
 // CHECK:STDOUT:   return <error>
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @Op[%self: %Self](%other: %Self) -> %Self;
+// CHECK:STDOUT: generic fn @Op(constants.%Self: %.4) {
+// CHECK:STDOUT:   %Self: %.4 = bind_symbolic_name Self 0 [symbolic = %Self (constants.%Self)]
+// CHECK:STDOUT:
+// CHECK:STDOUT:   fn[%self: %Self](%other: %Self) -> %Self;
+// CHECK:STDOUT: }
 // CHECK:STDOUT:

+ 5 - 1
toolchain/check/testdata/operators/builtin/fail_unimplemented_op.carbon

@@ -83,5 +83,9 @@ fn Main() -> i32 {
 // CHECK:STDOUT:   return <error>
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @Op[%self: %Self](%other: %Self) -> %Self;
+// CHECK:STDOUT: generic fn @Op(constants.%Self: %.4) {
+// CHECK:STDOUT:   %Self: %.4 = bind_symbolic_name Self 0 [symbolic = %Self (constants.%Self)]
+// CHECK:STDOUT:
+// CHECK:STDOUT:   fn[%self: %Self](%other: %Self) -> %Self;
+// CHECK:STDOUT: }
 // CHECK:STDOUT:

+ 11 - 2
toolchain/check/testdata/operators/overloaded/add.carbon

@@ -192,14 +192,23 @@ fn TestAssign(a: C*, b: C) {
 // CHECK:STDOUT:   return %.loc19_14 to @impl.1.%return.var
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @Op.2[%self: %Self.1](%other: %Self.1) -> %Self.1;
+// CHECK:STDOUT: generic fn @Op.2(constants.%Self.1: %.2) {
+// CHECK:STDOUT:   %Self: %.2 = bind_symbolic_name Self 0 [symbolic = %Self (constants.%Self.1)]
+// CHECK:STDOUT:
+// CHECK:STDOUT:   fn[%self: %Self.1](%other: %Self.1) -> %Self.1;
+// CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @Op.3[addr @impl.2.%self.loc23_14.3: %.7](@impl.2.%other.loc23_24.2: %C) {
 // CHECK:STDOUT: !entry:
 // CHECK:STDOUT:   return
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @Op.4[addr %self: %.8](%other: %Self.2);
+// CHECK:STDOUT: generic fn @Op.4(constants.%Self.2: %.6) {
+// CHECK:STDOUT:   %Self: %.6 = bind_symbolic_name Self 0 [symbolic = %Self (constants.%Self.2)]
+// CHECK:STDOUT:   %.2: type = ptr_type @Op.4.%Self (%Self.2) [symbolic = %.2 (constants.%.8)]
+// CHECK:STDOUT:
+// CHECK:STDOUT:   fn[addr %self: %.8](%other: %Self.2);
+// CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @TestOp(%a: %C, %b: %C) -> %return: %C {
 // CHECK:STDOUT: !entry:

+ 11 - 2
toolchain/check/testdata/operators/overloaded/bit_and.carbon

@@ -192,14 +192,23 @@ fn TestAssign(a: C*, b: C) {
 // CHECK:STDOUT:   return %.loc19_14 to @impl.1.%return.var
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @Op.2[%self: %Self.1](%other: %Self.1) -> %Self.1;
+// CHECK:STDOUT: generic fn @Op.2(constants.%Self.1: %.2) {
+// CHECK:STDOUT:   %Self: %.2 = bind_symbolic_name Self 0 [symbolic = %Self (constants.%Self.1)]
+// CHECK:STDOUT:
+// CHECK:STDOUT:   fn[%self: %Self.1](%other: %Self.1) -> %Self.1;
+// CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @Op.3[addr @impl.2.%self.loc23_14.3: %.7](@impl.2.%other.loc23_24.2: %C) {
 // CHECK:STDOUT: !entry:
 // CHECK:STDOUT:   return
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @Op.4[addr %self: %.8](%other: %Self.2);
+// CHECK:STDOUT: generic fn @Op.4(constants.%Self.2: %.6) {
+// CHECK:STDOUT:   %Self: %.6 = bind_symbolic_name Self 0 [symbolic = %Self (constants.%Self.2)]
+// CHECK:STDOUT:   %.2: type = ptr_type @Op.4.%Self (%Self.2) [symbolic = %.2 (constants.%.8)]
+// CHECK:STDOUT:
+// CHECK:STDOUT:   fn[addr %self: %.8](%other: %Self.2);
+// CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @TestOp(%a: %C, %b: %C) -> %return: %C {
 // CHECK:STDOUT: !entry:

+ 5 - 1
toolchain/check/testdata/operators/overloaded/bit_complement.carbon

@@ -120,7 +120,11 @@ fn TestOp(a: C) -> C {
 // CHECK:STDOUT:   return %.loc19_14 to @impl.%return.var
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @Op.2[%self: %Self]() -> %Self;
+// CHECK:STDOUT: generic fn @Op.2(constants.%Self: %.2) {
+// CHECK:STDOUT:   %Self: %.2 = bind_symbolic_name Self 0 [symbolic = %Self (constants.%Self)]
+// CHECK:STDOUT:
+// CHECK:STDOUT:   fn[%self: %Self]() -> %Self;
+// CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @TestOp(%a: %C) -> %return: %C {
 // CHECK:STDOUT: !entry:

+ 11 - 2
toolchain/check/testdata/operators/overloaded/bit_or.carbon

@@ -192,14 +192,23 @@ fn TestAssign(a: C*, b: C) {
 // CHECK:STDOUT:   return %.loc19_14 to @impl.1.%return.var
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @Op.2[%self: %Self.1](%other: %Self.1) -> %Self.1;
+// CHECK:STDOUT: generic fn @Op.2(constants.%Self.1: %.2) {
+// CHECK:STDOUT:   %Self: %.2 = bind_symbolic_name Self 0 [symbolic = %Self (constants.%Self.1)]
+// CHECK:STDOUT:
+// CHECK:STDOUT:   fn[%self: %Self.1](%other: %Self.1) -> %Self.1;
+// CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @Op.3[addr @impl.2.%self.loc23_14.3: %.7](@impl.2.%other.loc23_24.2: %C) {
 // CHECK:STDOUT: !entry:
 // CHECK:STDOUT:   return
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @Op.4[addr %self: %.8](%other: %Self.2);
+// CHECK:STDOUT: generic fn @Op.4(constants.%Self.2: %.6) {
+// CHECK:STDOUT:   %Self: %.6 = bind_symbolic_name Self 0 [symbolic = %Self (constants.%Self.2)]
+// CHECK:STDOUT:   %.2: type = ptr_type @Op.4.%Self (%Self.2) [symbolic = %.2 (constants.%.8)]
+// CHECK:STDOUT:
+// CHECK:STDOUT:   fn[addr %self: %.8](%other: %Self.2);
+// CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @TestOp(%a: %C, %b: %C) -> %return: %C {
 // CHECK:STDOUT: !entry:

+ 11 - 2
toolchain/check/testdata/operators/overloaded/bit_xor.carbon

@@ -192,14 +192,23 @@ fn TestAssign(a: C*, b: C) {
 // CHECK:STDOUT:   return %.loc19_14 to @impl.1.%return.var
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @Op.2[%self: %Self.1](%other: %Self.1) -> %Self.1;
+// CHECK:STDOUT: generic fn @Op.2(constants.%Self.1: %.2) {
+// CHECK:STDOUT:   %Self: %.2 = bind_symbolic_name Self 0 [symbolic = %Self (constants.%Self.1)]
+// CHECK:STDOUT:
+// CHECK:STDOUT:   fn[%self: %Self.1](%other: %Self.1) -> %Self.1;
+// CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @Op.3[addr @impl.2.%self.loc23_14.3: %.7](@impl.2.%other.loc23_24.2: %C) {
 // CHECK:STDOUT: !entry:
 // CHECK:STDOUT:   return
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @Op.4[addr %self: %.8](%other: %Self.2);
+// CHECK:STDOUT: generic fn @Op.4(constants.%Self.2: %.6) {
+// CHECK:STDOUT:   %Self: %.6 = bind_symbolic_name Self 0 [symbolic = %Self (constants.%Self.2)]
+// CHECK:STDOUT:   %.2: type = ptr_type @Op.4.%Self (%Self.2) [symbolic = %.2 (constants.%.8)]
+// CHECK:STDOUT:
+// CHECK:STDOUT:   fn[addr %self: %.8](%other: %Self.2);
+// CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @TestOp(%a: %C, %b: %C) -> %return: %C {
 // CHECK:STDOUT: !entry:

+ 6 - 1
toolchain/check/testdata/operators/overloaded/dec.carbon

@@ -109,7 +109,12 @@ fn TestOp() {
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @Op.1[addr @impl.%self.loc18_14.3: %.3]();
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @Op.2[addr %self: %.5]();
+// CHECK:STDOUT: generic fn @Op.2(constants.%Self: %.2) {
+// CHECK:STDOUT:   %Self: %.2 = bind_symbolic_name Self 0 [symbolic = %Self (constants.%Self)]
+// CHECK:STDOUT:   %.2: type = ptr_type @Op.2.%Self (%Self) [symbolic = %.2 (constants.%.5)]
+// CHECK:STDOUT:
+// CHECK:STDOUT:   fn[addr %self: %.5]();
+// CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @TestOp() {
 // CHECK:STDOUT: !entry:

+ 11 - 2
toolchain/check/testdata/operators/overloaded/div.carbon

@@ -192,14 +192,23 @@ fn TestAssign(a: C*, b: C) {
 // CHECK:STDOUT:   return %.loc19_14 to @impl.1.%return.var
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @Op.2[%self: %Self.1](%other: %Self.1) -> %Self.1;
+// CHECK:STDOUT: generic fn @Op.2(constants.%Self.1: %.2) {
+// CHECK:STDOUT:   %Self: %.2 = bind_symbolic_name Self 0 [symbolic = %Self (constants.%Self.1)]
+// CHECK:STDOUT:
+// CHECK:STDOUT:   fn[%self: %Self.1](%other: %Self.1) -> %Self.1;
+// CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @Op.3[addr @impl.2.%self.loc23_14.3: %.7](@impl.2.%other.loc23_24.2: %C) {
 // CHECK:STDOUT: !entry:
 // CHECK:STDOUT:   return
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @Op.4[addr %self: %.8](%other: %Self.2);
+// CHECK:STDOUT: generic fn @Op.4(constants.%Self.2: %.6) {
+// CHECK:STDOUT:   %Self: %.6 = bind_symbolic_name Self 0 [symbolic = %Self (constants.%Self.2)]
+// CHECK:STDOUT:   %.2: type = ptr_type @Op.4.%Self (%Self.2) [symbolic = %.2 (constants.%.8)]
+// CHECK:STDOUT:
+// CHECK:STDOUT:   fn[addr %self: %.8](%other: %Self.2);
+// CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @TestOp(%a: %C, %b: %C) -> %return: %C {
 // CHECK:STDOUT: !entry:

+ 30 - 6
toolchain/check/testdata/operators/overloaded/eq.carbon

@@ -224,9 +224,17 @@ fn TestLhsBad(a: D, b: C) -> bool {
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @NotEqual.1[@impl.%self.loc8_15.2: %C](@impl.%other.loc8_24.2: %C) -> bool;
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @Equal.2[%self: %Self](%other: %Self) -> bool;
+// CHECK:STDOUT: generic fn @Equal.2(constants.%Self: %.2) {
+// CHECK:STDOUT:   %Self: %.2 = bind_symbolic_name Self 0 [symbolic = %Self (constants.%Self)]
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @NotEqual.2[%self: %Self](%other: %Self) -> bool;
+// CHECK:STDOUT:   fn[%self: %Self](%other: %Self) -> bool;
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: generic fn @NotEqual.2(constants.%Self: %.2) {
+// CHECK:STDOUT:   %Self: %.2 = bind_symbolic_name Self 0 [symbolic = %Self (constants.%Self)]
+// CHECK:STDOUT:
+// CHECK:STDOUT:   fn[%self: %Self](%other: %Self) -> bool;
+// CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @TestEqual(%a: %C, %b: %C) -> bool {
 // CHECK:STDOUT: !entry:
@@ -357,7 +365,11 @@ fn TestLhsBad(a: D, b: C) -> bool {
 // CHECK:STDOUT:   return <error>
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @Equal[%self: %Self](%other: %Self) -> bool;
+// CHECK:STDOUT: generic fn @Equal(constants.%Self: %.4) {
+// CHECK:STDOUT:   %Self: %.4 = bind_symbolic_name Self 0 [symbolic = %Self (constants.%Self)]
+// CHECK:STDOUT:
+// CHECK:STDOUT:   fn[%self: %Self](%other: %Self) -> bool;
+// CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @TestNotEqual(%a: %D, %b: %D) -> bool {
 // CHECK:STDOUT: !entry:
@@ -366,7 +378,11 @@ fn TestLhsBad(a: D, b: C) -> bool {
 // CHECK:STDOUT:   return <error>
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @NotEqual[%self: %Self](%other: %Self) -> bool;
+// CHECK:STDOUT: generic fn @NotEqual(constants.%Self: %.4) {
+// CHECK:STDOUT:   %Self: %.4 = bind_symbolic_name Self 0 [symbolic = %Self (constants.%Self)]
+// CHECK:STDOUT:
+// CHECK:STDOUT:   fn[%self: %Self](%other: %Self) -> bool;
+// CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: --- fail_no_impl_for_args.carbon
 // CHECK:STDOUT:
@@ -521,9 +537,17 @@ fn TestLhsBad(a: D, b: C) -> bool {
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @NotEqual.1[@impl.%self.loc9_15.2: %C](@impl.%other.loc9_24.2: %C) -> bool;
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @Equal.2[%self: %Self](%other: %Self) -> bool;
+// CHECK:STDOUT: generic fn @Equal.2(constants.%Self: %.2) {
+// CHECK:STDOUT:   %Self: %.2 = bind_symbolic_name Self 0 [symbolic = %Self (constants.%Self)]
+// CHECK:STDOUT:
+// CHECK:STDOUT:   fn[%self: %Self](%other: %Self) -> bool;
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: generic fn @NotEqual.2(constants.%Self: %.2) {
+// CHECK:STDOUT:   %Self: %.2 = bind_symbolic_name Self 0 [symbolic = %Self (constants.%Self)]
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @NotEqual.2[%self: %Self](%other: %Self) -> bool;
+// CHECK:STDOUT:   fn[%self: %Self](%other: %Self) -> bool;
+// CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @TestRhsBad(%a: %C, %b: %D) -> bool {
 // CHECK:STDOUT: !entry:

+ 12 - 2
toolchain/check/testdata/operators/overloaded/fail_assign_non_ref.carbon

@@ -186,11 +186,21 @@ fn TestAddAssignNonRef(a: C, b: C) {
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @Op.1[addr @impl.1.%self.loc16_14.3: %.3]();
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @Op.2[addr %self: %.5]();
+// CHECK:STDOUT: generic fn @Op.2(constants.%Self.1: %.2) {
+// CHECK:STDOUT:   %Self: %.2 = bind_symbolic_name Self 0 [symbolic = %Self (constants.%Self.1)]
+// CHECK:STDOUT:   %.2: type = ptr_type @Op.2.%Self (%Self.1) [symbolic = %.2 (constants.%.5)]
+// CHECK:STDOUT:
+// CHECK:STDOUT:   fn[addr %self: %.5]();
+// CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @Op.3[addr @impl.2.%self.loc19_14.3: %.3](@impl.2.%other.loc19_24.2: %C);
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @Op.4[addr %self: %.8](%other: %Self.2);
+// CHECK:STDOUT: generic fn @Op.4(constants.%Self.2: %.7) {
+// CHECK:STDOUT:   %Self: %.7 = bind_symbolic_name Self 0 [symbolic = %Self (constants.%Self.2)]
+// CHECK:STDOUT:   %.2: type = ptr_type @Op.4.%Self (%Self.2) [symbolic = %.2 (constants.%.8)]
+// CHECK:STDOUT:
+// CHECK:STDOUT:   fn[addr %self: %.8](%other: %Self.2);
+// CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @TestIncNonRef(%a: %C) {
 // CHECK:STDOUT: !entry:

+ 22 - 4
toolchain/check/testdata/operators/overloaded/fail_no_impl.carbon

@@ -192,7 +192,11 @@ fn TestRef(b: C) {
 // CHECK:STDOUT:   return <error> to %return
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @Op.1[%self: %Self.1]() -> %Self.1;
+// CHECK:STDOUT: generic fn @Op.1(constants.%Self.1: %.4) {
+// CHECK:STDOUT:   %Self: %.4 = bind_symbolic_name Self 0 [symbolic = %Self (constants.%Self.1)]
+// CHECK:STDOUT:
+// CHECK:STDOUT:   fn[%self: %Self.1]() -> %Self.1;
+// CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @TestBinary(%a: %C, %b: %C) -> %return: %C {
 // CHECK:STDOUT: !entry:
@@ -201,7 +205,11 @@ fn TestRef(b: C) {
 // CHECK:STDOUT:   return <error> to %return
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @Op.2[%self: %Self.2](%other: %Self.2) -> %Self.2;
+// CHECK:STDOUT: generic fn @Op.2(constants.%Self.2: %.7) {
+// CHECK:STDOUT:   %Self: %.7 = bind_symbolic_name Self 0 [symbolic = %Self (constants.%Self.2)]
+// CHECK:STDOUT:
+// CHECK:STDOUT:   fn[%self: %Self.2](%other: %Self.2) -> %Self.2;
+// CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @TestRef(%b: %C) {
 // CHECK:STDOUT: !entry:
@@ -218,7 +226,17 @@ fn TestRef(b: C) {
 // CHECK:STDOUT:   return
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @Op.3[addr %self: %.11](%other: %Self.3);
+// CHECK:STDOUT: generic fn @Op.3(constants.%Self.3: %.10) {
+// CHECK:STDOUT:   %Self: %.10 = bind_symbolic_name Self 0 [symbolic = %Self (constants.%Self.3)]
+// CHECK:STDOUT:   %.2: type = ptr_type @Op.3.%Self (%Self.3) [symbolic = %.2 (constants.%.11)]
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @Op.4[addr %self: %.15]();
+// CHECK:STDOUT:   fn[addr %self: %.11](%other: %Self.3);
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: generic fn @Op.4(constants.%Self.4: %.14) {
+// CHECK:STDOUT:   %Self: %.14 = bind_symbolic_name Self 0 [symbolic = %Self (constants.%Self.4)]
+// CHECK:STDOUT:   %.2: type = ptr_type @Op.4.%Self (%Self.4) [symbolic = %.2 (constants.%.15)]
+// CHECK:STDOUT:
+// CHECK:STDOUT:   fn[addr %self: %.15]();
+// CHECK:STDOUT: }
 // CHECK:STDOUT:

+ 11 - 2
toolchain/check/testdata/operators/overloaded/fail_no_impl_for_arg.carbon

@@ -201,11 +201,20 @@ fn TestAssign(b: D) {
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @Op.1[@impl.1.%self.loc17_9.2: %C](@impl.1.%other.loc17_18.2: %C) -> %C;
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @Op.2[%self: %Self.1](%other: %Self.1) -> %Self.1;
+// CHECK:STDOUT: generic fn @Op.2(constants.%Self.1: %.2) {
+// CHECK:STDOUT:   %Self: %.2 = bind_symbolic_name Self 0 [symbolic = %Self (constants.%Self.1)]
+// CHECK:STDOUT:
+// CHECK:STDOUT:   fn[%self: %Self.1](%other: %Self.1) -> %Self.1;
+// CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @Op.3[addr @impl.2.%self.loc20_14.3: %.6](@impl.2.%other.loc20_24.2: %C);
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @Op.4[addr %self: %.7](%other: %Self.2);
+// CHECK:STDOUT: generic fn @Op.4(constants.%Self.2: %.5) {
+// CHECK:STDOUT:   %Self: %.5 = bind_symbolic_name Self 0 [symbolic = %Self (constants.%Self.2)]
+// CHECK:STDOUT:   %.2: type = ptr_type @Op.4.%Self (%Self.2) [symbolic = %.2 (constants.%.7)]
+// CHECK:STDOUT:
+// CHECK:STDOUT:   fn[addr %self: %.7](%other: %Self.2);
+// CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @Test(%a: %C, %b: %D) -> %return: %C {
 // CHECK:STDOUT: !entry:

+ 6 - 1
toolchain/check/testdata/operators/overloaded/inc.carbon

@@ -109,7 +109,12 @@ fn TestOp() {
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @Op.1[addr @impl.%self.loc18_14.3: %.3]();
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @Op.2[addr %self: %.5]();
+// CHECK:STDOUT: generic fn @Op.2(constants.%Self: %.2) {
+// CHECK:STDOUT:   %Self: %.2 = bind_symbolic_name Self 0 [symbolic = %Self (constants.%Self)]
+// CHECK:STDOUT:   %.2: type = ptr_type @Op.2.%Self (%Self) [symbolic = %.2 (constants.%.5)]
+// CHECK:STDOUT:
+// CHECK:STDOUT:   fn[addr %self: %.5]();
+// CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @TestOp() {
 // CHECK:STDOUT: !entry:

+ 11 - 2
toolchain/check/testdata/operators/overloaded/left_shift.carbon

@@ -192,14 +192,23 @@ fn TestAssign(a: C*, b: C) {
 // CHECK:STDOUT:   return %.loc19_14 to @impl.1.%return.var
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @Op.2[%self: %Self.1](%other: %Self.1) -> %Self.1;
+// CHECK:STDOUT: generic fn @Op.2(constants.%Self.1: %.2) {
+// CHECK:STDOUT:   %Self: %.2 = bind_symbolic_name Self 0 [symbolic = %Self (constants.%Self.1)]
+// CHECK:STDOUT:
+// CHECK:STDOUT:   fn[%self: %Self.1](%other: %Self.1) -> %Self.1;
+// CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @Op.3[addr @impl.2.%self.loc23_14.3: %.7](@impl.2.%other.loc23_24.2: %C) {
 // CHECK:STDOUT: !entry:
 // CHECK:STDOUT:   return
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @Op.4[addr %self: %.8](%other: %Self.2);
+// CHECK:STDOUT: generic fn @Op.4(constants.%Self.2: %.6) {
+// CHECK:STDOUT:   %Self: %.6 = bind_symbolic_name Self 0 [symbolic = %Self (constants.%Self.2)]
+// CHECK:STDOUT:   %.2: type = ptr_type @Op.4.%Self (%Self.2) [symbolic = %.2 (constants.%.8)]
+// CHECK:STDOUT:
+// CHECK:STDOUT:   fn[addr %self: %.8](%other: %Self.2);
+// CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @TestOp(%a: %C, %b: %C) -> %return: %C {
 // CHECK:STDOUT: !entry:

+ 11 - 2
toolchain/check/testdata/operators/overloaded/mod.carbon

@@ -192,14 +192,23 @@ fn TestAssign(a: C*, b: C) {
 // CHECK:STDOUT:   return %.loc19_14 to @impl.1.%return.var
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @Op.2[%self: %Self.1](%other: %Self.1) -> %Self.1;
+// CHECK:STDOUT: generic fn @Op.2(constants.%Self.1: %.2) {
+// CHECK:STDOUT:   %Self: %.2 = bind_symbolic_name Self 0 [symbolic = %Self (constants.%Self.1)]
+// CHECK:STDOUT:
+// CHECK:STDOUT:   fn[%self: %Self.1](%other: %Self.1) -> %Self.1;
+// CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @Op.3[addr @impl.2.%self.loc23_14.3: %.7](@impl.2.%other.loc23_24.2: %C) {
 // CHECK:STDOUT: !entry:
 // CHECK:STDOUT:   return
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @Op.4[addr %self: %.8](%other: %Self.2);
+// CHECK:STDOUT: generic fn @Op.4(constants.%Self.2: %.6) {
+// CHECK:STDOUT:   %Self: %.6 = bind_symbolic_name Self 0 [symbolic = %Self (constants.%Self.2)]
+// CHECK:STDOUT:   %.2: type = ptr_type @Op.4.%Self (%Self.2) [symbolic = %.2 (constants.%.8)]
+// CHECK:STDOUT:
+// CHECK:STDOUT:   fn[addr %self: %.8](%other: %Self.2);
+// CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @TestOp(%a: %C, %b: %C) -> %return: %C {
 // CHECK:STDOUT: !entry:

+ 11 - 2
toolchain/check/testdata/operators/overloaded/mul.carbon

@@ -192,14 +192,23 @@ fn TestAssign(a: C*, b: C) {
 // CHECK:STDOUT:   return %.loc19_14 to @impl.1.%return.var
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @Op.2[%self: %Self.1](%other: %Self.1) -> %Self.1;
+// CHECK:STDOUT: generic fn @Op.2(constants.%Self.1: %.2) {
+// CHECK:STDOUT:   %Self: %.2 = bind_symbolic_name Self 0 [symbolic = %Self (constants.%Self.1)]
+// CHECK:STDOUT:
+// CHECK:STDOUT:   fn[%self: %Self.1](%other: %Self.1) -> %Self.1;
+// CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @Op.3[addr @impl.2.%self.loc23_14.3: %.7](@impl.2.%other.loc23_24.2: %C) {
 // CHECK:STDOUT: !entry:
 // CHECK:STDOUT:   return
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @Op.4[addr %self: %.8](%other: %Self.2);
+// CHECK:STDOUT: generic fn @Op.4(constants.%Self.2: %.6) {
+// CHECK:STDOUT:   %Self: %.6 = bind_symbolic_name Self 0 [symbolic = %Self (constants.%Self.2)]
+// CHECK:STDOUT:   %.2: type = ptr_type @Op.4.%Self (%Self.2) [symbolic = %.2 (constants.%.8)]
+// CHECK:STDOUT:
+// CHECK:STDOUT:   fn[addr %self: %.8](%other: %Self.2);
+// CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @TestOp(%a: %C, %b: %C) -> %return: %C {
 // CHECK:STDOUT: !entry:

+ 5 - 1
toolchain/check/testdata/operators/overloaded/negate.carbon

@@ -120,7 +120,11 @@ fn TestOp(a: C) -> C {
 // CHECK:STDOUT:   return %.loc19_14 to @impl.%return.var
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @Op.2[%self: %Self]() -> %Self;
+// CHECK:STDOUT: generic fn @Op.2(constants.%Self: %.2) {
+// CHECK:STDOUT:   %Self: %.2 = bind_symbolic_name Self 0 [symbolic = %Self (constants.%Self)]
+// CHECK:STDOUT:
+// CHECK:STDOUT:   fn[%self: %Self]() -> %Self;
+// CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @TestOp(%a: %C) -> %return: %C {
 // CHECK:STDOUT: !entry:

+ 40 - 8
toolchain/check/testdata/operators/overloaded/ordered.carbon

@@ -299,13 +299,29 @@ fn TestGreaterEqual(a: D, b: D) -> bool {
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @GreaterOrEquivalent.1[@impl.%self.loc10_26.2: %C](@impl.%other.loc10_35.2: %C) -> bool;
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @Less.2[%self: %Self](%other: %Self) -> bool;
+// CHECK:STDOUT: generic fn @Less.2(constants.%Self: %.2) {
+// CHECK:STDOUT:   %Self: %.2 = bind_symbolic_name Self 0 [symbolic = %Self (constants.%Self)]
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @LessOrEquivalent.2[%self: %Self](%other: %Self) -> bool;
+// CHECK:STDOUT:   fn[%self: %Self](%other: %Self) -> bool;
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: generic fn @LessOrEquivalent.2(constants.%Self: %.2) {
+// CHECK:STDOUT:   %Self: %.2 = bind_symbolic_name Self 0 [symbolic = %Self (constants.%Self)]
+// CHECK:STDOUT:
+// CHECK:STDOUT:   fn[%self: %Self](%other: %Self) -> bool;
+// CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @Greater.2[%self: %Self](%other: %Self) -> bool;
+// CHECK:STDOUT: generic fn @Greater.2(constants.%Self: %.2) {
+// CHECK:STDOUT:   %Self: %.2 = bind_symbolic_name Self 0 [symbolic = %Self (constants.%Self)]
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @GreaterOrEquivalent.2[%self: %Self](%other: %Self) -> bool;
+// CHECK:STDOUT:   fn[%self: %Self](%other: %Self) -> bool;
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: generic fn @GreaterOrEquivalent.2(constants.%Self: %.2) {
+// CHECK:STDOUT:   %Self: %.2 = bind_symbolic_name Self 0 [symbolic = %Self (constants.%Self)]
+// CHECK:STDOUT:
+// CHECK:STDOUT:   fn[%self: %Self](%other: %Self) -> bool;
+// CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @TestLess(%a: %C, %b: %C) -> bool {
 // CHECK:STDOUT: !entry:
@@ -506,7 +522,11 @@ fn TestGreaterEqual(a: D, b: D) -> bool {
 // CHECK:STDOUT:   return <error>
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @Less[%self: %Self](%other: %Self) -> bool;
+// CHECK:STDOUT: generic fn @Less(constants.%Self: %.4) {
+// CHECK:STDOUT:   %Self: %.4 = bind_symbolic_name Self 0 [symbolic = %Self (constants.%Self)]
+// CHECK:STDOUT:
+// CHECK:STDOUT:   fn[%self: %Self](%other: %Self) -> bool;
+// CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @TestLessEqual(%a: %D, %b: %D) -> bool {
 // CHECK:STDOUT: !entry:
@@ -515,7 +535,11 @@ fn TestGreaterEqual(a: D, b: D) -> bool {
 // CHECK:STDOUT:   return <error>
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @LessOrEquivalent[%self: %Self](%other: %Self) -> bool;
+// CHECK:STDOUT: generic fn @LessOrEquivalent(constants.%Self: %.4) {
+// CHECK:STDOUT:   %Self: %.4 = bind_symbolic_name Self 0 [symbolic = %Self (constants.%Self)]
+// CHECK:STDOUT:
+// CHECK:STDOUT:   fn[%self: %Self](%other: %Self) -> bool;
+// CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @TestGreater(%a: %D, %b: %D) -> bool {
 // CHECK:STDOUT: !entry:
@@ -524,7 +548,11 @@ fn TestGreaterEqual(a: D, b: D) -> bool {
 // CHECK:STDOUT:   return <error>
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @Greater[%self: %Self](%other: %Self) -> bool;
+// CHECK:STDOUT: generic fn @Greater(constants.%Self: %.4) {
+// CHECK:STDOUT:   %Self: %.4 = bind_symbolic_name Self 0 [symbolic = %Self (constants.%Self)]
+// CHECK:STDOUT:
+// CHECK:STDOUT:   fn[%self: %Self](%other: %Self) -> bool;
+// CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @TestGreaterEqual(%a: %D, %b: %D) -> bool {
 // CHECK:STDOUT: !entry:
@@ -533,5 +561,9 @@ fn TestGreaterEqual(a: D, b: D) -> bool {
 // CHECK:STDOUT:   return <error>
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @GreaterOrEquivalent[%self: %Self](%other: %Self) -> bool;
+// CHECK:STDOUT: generic fn @GreaterOrEquivalent(constants.%Self: %.4) {
+// CHECK:STDOUT:   %Self: %.4 = bind_symbolic_name Self 0 [symbolic = %Self (constants.%Self)]
+// CHECK:STDOUT:
+// CHECK:STDOUT:   fn[%self: %Self](%other: %Self) -> bool;
+// CHECK:STDOUT: }
 // CHECK:STDOUT:

+ 11 - 2
toolchain/check/testdata/operators/overloaded/right_shift.carbon

@@ -192,14 +192,23 @@ fn TestAssign(a: C*, b: C) {
 // CHECK:STDOUT:   return %.loc19_14 to @impl.1.%return.var
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @Op.2[%self: %Self.1](%other: %Self.1) -> %Self.1;
+// CHECK:STDOUT: generic fn @Op.2(constants.%Self.1: %.2) {
+// CHECK:STDOUT:   %Self: %.2 = bind_symbolic_name Self 0 [symbolic = %Self (constants.%Self.1)]
+// CHECK:STDOUT:
+// CHECK:STDOUT:   fn[%self: %Self.1](%other: %Self.1) -> %Self.1;
+// CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @Op.3[addr @impl.2.%self.loc23_14.3: %.7](@impl.2.%other.loc23_24.2: %C) {
 // CHECK:STDOUT: !entry:
 // CHECK:STDOUT:   return
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @Op.4[addr %self: %.8](%other: %Self.2);
+// CHECK:STDOUT: generic fn @Op.4(constants.%Self.2: %.6) {
+// CHECK:STDOUT:   %Self: %.6 = bind_symbolic_name Self 0 [symbolic = %Self (constants.%Self.2)]
+// CHECK:STDOUT:   %.2: type = ptr_type @Op.4.%Self (%Self.2) [symbolic = %.2 (constants.%.8)]
+// CHECK:STDOUT:
+// CHECK:STDOUT:   fn[addr %self: %.8](%other: %Self.2);
+// CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @TestOp(%a: %C, %b: %C) -> %return: %C {
 // CHECK:STDOUT: !entry:

+ 11 - 2
toolchain/check/testdata/operators/overloaded/sub.carbon

@@ -192,14 +192,23 @@ fn TestAssign(a: C*, b: C) {
 // CHECK:STDOUT:   return %.loc19_14 to @impl.1.%return.var
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @Op.2[%self: %Self.1](%other: %Self.1) -> %Self.1;
+// CHECK:STDOUT: generic fn @Op.2(constants.%Self.1: %.2) {
+// CHECK:STDOUT:   %Self: %.2 = bind_symbolic_name Self 0 [symbolic = %Self (constants.%Self.1)]
+// CHECK:STDOUT:
+// CHECK:STDOUT:   fn[%self: %Self.1](%other: %Self.1) -> %Self.1;
+// CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @Op.3[addr @impl.2.%self.loc23_14.3: %.7](@impl.2.%other.loc23_24.2: %C) {
 // CHECK:STDOUT: !entry:
 // CHECK:STDOUT:   return
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @Op.4[addr %self: %.8](%other: %Self.2);
+// CHECK:STDOUT: generic fn @Op.4(constants.%Self.2: %.6) {
+// CHECK:STDOUT:   %Self: %.6 = bind_symbolic_name Self 0 [symbolic = %Self (constants.%Self.2)]
+// CHECK:STDOUT:   %.2: type = ptr_type @Op.4.%Self (%Self.2) [symbolic = %.2 (constants.%.8)]
+// CHECK:STDOUT:
+// CHECK:STDOUT:   fn[addr %self: %.8](%other: %Self.2);
+// CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @TestOp(%a: %C, %b: %C) -> %return: %C {
 // CHECK:STDOUT: !entry:

+ 19 - 5
toolchain/check/testdata/packages/no_prelude/fail_export_name_params.carbon

@@ -83,10 +83,10 @@ export C2(T:! type);
 // CHECK:STDOUT:   %.1: type = tuple_type () [template]
 // CHECK:STDOUT:   %C1.1: %C1.type = struct_value () [template]
 // CHECK:STDOUT:   %T: type = bind_symbolic_name T 0 [symbolic]
-// CHECK:STDOUT:   %C1.2: type = class_type @C1, invalid(%T) [symbolic]
+// CHECK:STDOUT:   %C1.2: type = class_type @C1, @C1(%T) [symbolic]
 // CHECK:STDOUT:   %C2.type: type = generic_class_type @C2 [template]
 // CHECK:STDOUT:   %C2.1: %C2.type = struct_value () [template]
-// CHECK:STDOUT:   %C2.2: type = class_type @C2, invalid(%T) [symbolic]
+// CHECK:STDOUT:   %C2.2: type = class_type @C2, @C2(%T) [symbolic]
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: imports {
@@ -106,9 +106,23 @@ export C2(T:! type);
 // CHECK:STDOUT:   %C2: %C2.type = export C2, imports.%import_ref.2 [template = constants.%C2.1]
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: class @C1;
+// CHECK:STDOUT: generic class @C1(constants.%T: type) {
+// CHECK:STDOUT:   %T: type = bind_symbolic_name T 0 [symbolic = %T (constants.%T)]
 // CHECK:STDOUT:
-// CHECK:STDOUT: class @C2;
+// CHECK:STDOUT:   class;
+// CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: specific invalid(constants.%T);
+// CHECK:STDOUT: generic class @C2(constants.%T: type) {
+// CHECK:STDOUT:   %T: type = bind_symbolic_name T 0 [symbolic = %T (constants.%T)]
+// CHECK:STDOUT:
+// CHECK:STDOUT:   class;
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @C1(constants.%T) {
+// CHECK:STDOUT:   %T => constants.%T
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @C2(constants.%T) {
+// CHECK:STDOUT:   %T => constants.%T
+// CHECK:STDOUT: }
 // CHECK:STDOUT:

+ 63 - 23
toolchain/check/testdata/struct/import.carbon

@@ -225,12 +225,12 @@ var c_bad: C({.a = 3, .b = 4}) = F();
 // CHECK:STDOUT:   %.10: type = struct_type {} [template]
 // CHECK:STDOUT:   %.11: type = struct_type {.a: i32, .b: i32} [template]
 // CHECK:STDOUT:   %S: %.11 = bind_symbolic_name S 0 [symbolic]
-// CHECK:STDOUT:   %C.2: type = class_type @C, invalid(%S) [symbolic]
+// CHECK:STDOUT:   %C.2: type = class_type @C, @C(%S) [symbolic]
 // CHECK:STDOUT:   %.12: i32 = int_literal 1 [template]
 // CHECK:STDOUT:   %.13: i32 = int_literal 2 [template]
 // CHECK:STDOUT:   %.14: type = ptr_type %.11 [template]
 // CHECK:STDOUT:   %struct: %.11 = struct_value (%.12, %.13) [template]
-// CHECK:STDOUT:   %C.3: type = class_type @C, invalid(%struct) [template]
+// CHECK:STDOUT:   %C.3: type = class_type @C, @C(%struct) [template]
 // CHECK:STDOUT:   %.15: type = ptr_type %.10 [template]
 // CHECK:STDOUT:   %F.type: type = fn_type @F [template]
 // CHECK:STDOUT:   %F: %F.type = struct_value () [template]
@@ -303,9 +303,15 @@ var c_bad: C({.a = 3, .b = 4}) = F();
 // CHECK:STDOUT:   %c: ref %C.3 = bind_name c, %c.var
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: class @C {
-// CHECK:STDOUT: !members:
-// CHECK:STDOUT:   .Self = imports.%import_ref.6
+// CHECK:STDOUT: generic class @C(constants.%S: %.11) {
+// CHECK:STDOUT:   %S: %.11 = bind_symbolic_name S 0 [symbolic = %S (constants.%S)]
+// CHECK:STDOUT:
+// CHECK:STDOUT: !definition:
+// CHECK:STDOUT:
+// CHECK:STDOUT:   class {
+// CHECK:STDOUT:   !members:
+// CHECK:STDOUT:     .Self = imports.%import_ref.6
+// CHECK:STDOUT:   }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @Int32() -> type = "int.make_type_32";
@@ -350,9 +356,15 @@ var c_bad: C({.a = 3, .b = 4}) = F();
 // CHECK:STDOUT:   return
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: specific invalid(constants.%S);
+// CHECK:STDOUT: specific @C(constants.%S) {
+// CHECK:STDOUT:   %S => constants.%S
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @C(constants.%struct) {
+// CHECK:STDOUT:   %S => constants.%struct
 // CHECK:STDOUT:
-// CHECK:STDOUT: specific invalid(constants.%struct);
+// CHECK:STDOUT: !definition:
+// CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: --- fail_bad_type.impl.carbon
 // CHECK:STDOUT:
@@ -363,13 +375,13 @@ var c_bad: C({.a = 3, .b = 4}) = F();
 // CHECK:STDOUT:   %.2: type = struct_type {} [template]
 // CHECK:STDOUT:   %.3: type = struct_type {.a: i32, .b: i32} [template]
 // CHECK:STDOUT:   %S: %.3 = bind_symbolic_name S 0 [symbolic]
-// CHECK:STDOUT:   %C.2: type = class_type @C, invalid(%S) [symbolic]
+// CHECK:STDOUT:   %C.2: type = class_type @C, @C(%S) [symbolic]
 // CHECK:STDOUT:   %.4: i32 = int_literal 1 [template]
 // CHECK:STDOUT:   %.5: i32 = int_literal 2 [template]
 // CHECK:STDOUT:   %.6: type = struct_type {.c: i32, .d: i32} [template]
 // CHECK:STDOUT:   %.7: type = ptr_type %.3 [template]
 // CHECK:STDOUT:   %struct: %.3 = struct_value (%.4, %.5) [template]
-// CHECK:STDOUT:   %C.3: type = class_type @C, invalid(%struct) [template]
+// CHECK:STDOUT:   %C.3: type = class_type @C, @C(%struct) [template]
 // CHECK:STDOUT:   %F.type: type = fn_type @F [template]
 // CHECK:STDOUT:   %F: %F.type = struct_value () [template]
 // CHECK:STDOUT:   %.8: type = ptr_type %.2 [template]
@@ -415,9 +427,15 @@ var c_bad: C({.a = 3, .b = 4}) = F();
 // CHECK:STDOUT:   %c_bad: ref <error> = bind_name c_bad, %c_bad.var
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: class @C {
-// CHECK:STDOUT: !members:
-// CHECK:STDOUT:   .Self = imports.%import_ref.5
+// CHECK:STDOUT: generic class @C(constants.%S: %.3) {
+// CHECK:STDOUT:   %S: %.3 = bind_symbolic_name S 0 [symbolic = %S (constants.%S)]
+// CHECK:STDOUT:
+// CHECK:STDOUT: !definition:
+// CHECK:STDOUT:
+// CHECK:STDOUT:   class {
+// CHECK:STDOUT:   !members:
+// CHECK:STDOUT:     .Self = imports.%import_ref.5
+// CHECK:STDOUT:   }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @F() -> %C.3;
@@ -431,9 +449,15 @@ var c_bad: C({.a = 3, .b = 4}) = F();
 // CHECK:STDOUT:   return
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: specific invalid(constants.%S);
+// CHECK:STDOUT: specific @C(constants.%S) {
+// CHECK:STDOUT:   %S => constants.%S
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @C(constants.%struct) {
+// CHECK:STDOUT:   %S => constants.%struct
 // CHECK:STDOUT:
-// CHECK:STDOUT: specific invalid(constants.%struct);
+// CHECK:STDOUT: !definition:
+// CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: --- fail_bad_value.impl.carbon
 // CHECK:STDOUT:
@@ -444,17 +468,17 @@ var c_bad: C({.a = 3, .b = 4}) = F();
 // CHECK:STDOUT:   %.2: type = struct_type {} [template]
 // CHECK:STDOUT:   %.3: type = struct_type {.a: i32, .b: i32} [template]
 // CHECK:STDOUT:   %S: %.3 = bind_symbolic_name S 0 [symbolic]
-// CHECK:STDOUT:   %C.2: type = class_type @C, invalid(%S) [symbolic]
+// CHECK:STDOUT:   %C.2: type = class_type @C, @C(%S) [symbolic]
 // CHECK:STDOUT:   %.4: i32 = int_literal 3 [template]
 // CHECK:STDOUT:   %.5: i32 = int_literal 4 [template]
 // CHECK:STDOUT:   %.6: type = ptr_type %.3 [template]
 // CHECK:STDOUT:   %struct.1: %.3 = struct_value (%.4, %.5) [template]
-// CHECK:STDOUT:   %C.3: type = class_type @C, invalid(%struct.1) [template]
+// CHECK:STDOUT:   %C.3: type = class_type @C, @C(%struct.1) [template]
 // CHECK:STDOUT:   %.7: type = ptr_type %.2 [template]
 // CHECK:STDOUT:   %.8: i32 = int_literal 2 [template]
 // CHECK:STDOUT:   %.9: i32 = int_literal 1 [template]
 // CHECK:STDOUT:   %struct.2: %.3 = struct_value (%.9, %.8) [template]
-// CHECK:STDOUT:   %C.4: type = class_type @C, invalid(%struct.2) [template]
+// CHECK:STDOUT:   %C.4: type = class_type @C, @C(%struct.2) [template]
 // CHECK:STDOUT:   %F.type: type = fn_type @F [template]
 // CHECK:STDOUT:   %F: %F.type = struct_value () [template]
 // CHECK:STDOUT: }
@@ -501,9 +525,15 @@ var c_bad: C({.a = 3, .b = 4}) = F();
 // CHECK:STDOUT:   %c_bad: ref %C.3 = bind_name c_bad, %c_bad.var
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: class @C {
-// CHECK:STDOUT: !members:
-// CHECK:STDOUT:   .Self = imports.%import_ref.5
+// CHECK:STDOUT: generic class @C(constants.%S: %.3) {
+// CHECK:STDOUT:   %S: %.3 = bind_symbolic_name S 0 [symbolic = %S (constants.%S)]
+// CHECK:STDOUT:
+// CHECK:STDOUT: !definition:
+// CHECK:STDOUT:
+// CHECK:STDOUT:   class {
+// CHECK:STDOUT:   !members:
+// CHECK:STDOUT:     .Self = imports.%import_ref.5
+// CHECK:STDOUT:   }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @F() -> %C.4;
@@ -517,9 +547,19 @@ var c_bad: C({.a = 3, .b = 4}) = F();
 // CHECK:STDOUT:   return
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: specific invalid(constants.%S);
+// CHECK:STDOUT: specific @C(constants.%S) {
+// CHECK:STDOUT:   %S => constants.%S
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @C(constants.%struct.1) {
+// CHECK:STDOUT:   %S => constants.%struct.1
 // CHECK:STDOUT:
-// CHECK:STDOUT: specific invalid(constants.%struct.1);
+// CHECK:STDOUT: !definition:
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @C(constants.%struct.2) {
+// CHECK:STDOUT:   %S => constants.%struct.2
 // CHECK:STDOUT:
-// CHECK:STDOUT: specific invalid(constants.%struct.2);
+// CHECK:STDOUT: !definition:
+// CHECK:STDOUT: }
 // CHECK:STDOUT:

+ 63 - 23
toolchain/check/testdata/tuples/import.carbon

@@ -249,11 +249,11 @@ var c_bad: C((3, 4)) = F();
 // CHECK:STDOUT:   %C.1: %C.type = struct_value () [template]
 // CHECK:STDOUT:   %.14: type = struct_type {} [template]
 // CHECK:STDOUT:   %X: %.8 = bind_symbolic_name X 0 [symbolic]
-// CHECK:STDOUT:   %C.2: type = class_type @C, invalid(%X) [symbolic]
+// CHECK:STDOUT:   %C.2: type = class_type @C, @C(%X) [symbolic]
 // CHECK:STDOUT:   %.15: i32 = int_literal 1 [template]
 // CHECK:STDOUT:   %.16: i32 = int_literal 2 [template]
 // CHECK:STDOUT:   %tuple: %.8 = tuple_value (%.15, %.16) [template]
-// CHECK:STDOUT:   %C.3: type = class_type @C, invalid(%tuple) [template]
+// CHECK:STDOUT:   %C.3: type = class_type @C, @C(%tuple) [template]
 // CHECK:STDOUT:   %.17: type = ptr_type %.14 [template]
 // CHECK:STDOUT:   %F.type: type = fn_type @F [template]
 // CHECK:STDOUT:   %F: %F.type = struct_value () [template]
@@ -334,9 +334,15 @@ var c_bad: C((3, 4)) = F();
 // CHECK:STDOUT:   %c: ref %C.3 = bind_name c, %c.var
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: class @C {
-// CHECK:STDOUT: !members:
-// CHECK:STDOUT:   .Self = imports.%import_ref.6
+// CHECK:STDOUT: generic class @C(constants.%X: %.8) {
+// CHECK:STDOUT:   %X: %.8 = bind_symbolic_name X 0 [symbolic = %X (constants.%X)]
+// CHECK:STDOUT:
+// CHECK:STDOUT: !definition:
+// CHECK:STDOUT:
+// CHECK:STDOUT:   class {
+// CHECK:STDOUT:   !members:
+// CHECK:STDOUT:     .Self = imports.%import_ref.6
+// CHECK:STDOUT:   }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @Int32() -> type = "int.make_type_32";
@@ -389,9 +395,15 @@ var c_bad: C((3, 4)) = F();
 // CHECK:STDOUT:   return
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: specific invalid(constants.%X);
+// CHECK:STDOUT: specific @C(constants.%X) {
+// CHECK:STDOUT:   %X => constants.%X
+// CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: specific invalid(constants.%tuple);
+// CHECK:STDOUT: specific @C(constants.%tuple) {
+// CHECK:STDOUT:   %X => constants.%tuple
+// CHECK:STDOUT:
+// CHECK:STDOUT: !definition:
+// CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: --- fail_bad_type.impl.carbon
 // CHECK:STDOUT:
@@ -402,14 +414,14 @@ var c_bad: C((3, 4)) = F();
 // CHECK:STDOUT:   %.2: type = struct_type {} [template]
 // CHECK:STDOUT:   %.3: type = tuple_type (i32, i32) [template]
 // CHECK:STDOUT:   %X: %.3 = bind_symbolic_name X 0 [symbolic]
-// CHECK:STDOUT:   %C.2: type = class_type @C, invalid(%X) [symbolic]
+// CHECK:STDOUT:   %C.2: type = class_type @C, @C(%X) [symbolic]
 // CHECK:STDOUT:   %.4: i32 = int_literal 1 [template]
 // CHECK:STDOUT:   %.5: i32 = int_literal 2 [template]
 // CHECK:STDOUT:   %.6: i32 = int_literal 3 [template]
 // CHECK:STDOUT:   %.7: type = tuple_type (i32, i32, i32) [template]
 // CHECK:STDOUT:   %.8: type = ptr_type %.3 [template]
 // CHECK:STDOUT:   %tuple: %.3 = tuple_value (%.4, %.5) [template]
-// CHECK:STDOUT:   %C.3: type = class_type @C, invalid(%tuple) [template]
+// CHECK:STDOUT:   %C.3: type = class_type @C, @C(%tuple) [template]
 // CHECK:STDOUT:   %F.type: type = fn_type @F [template]
 // CHECK:STDOUT:   %F: %F.type = struct_value () [template]
 // CHECK:STDOUT:   %.9: type = ptr_type %.2 [template]
@@ -456,9 +468,15 @@ var c_bad: C((3, 4)) = F();
 // CHECK:STDOUT:   %c_bad: ref <error> = bind_name c_bad, %c_bad.var
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: class @C {
-// CHECK:STDOUT: !members:
-// CHECK:STDOUT:   .Self = imports.%import_ref.5
+// CHECK:STDOUT: generic class @C(constants.%X: %.3) {
+// CHECK:STDOUT:   %X: %.3 = bind_symbolic_name X 0 [symbolic = %X (constants.%X)]
+// CHECK:STDOUT:
+// CHECK:STDOUT: !definition:
+// CHECK:STDOUT:
+// CHECK:STDOUT:   class {
+// CHECK:STDOUT:   !members:
+// CHECK:STDOUT:     .Self = imports.%import_ref.5
+// CHECK:STDOUT:   }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @F() -> %C.3;
@@ -472,9 +490,15 @@ var c_bad: C((3, 4)) = F();
 // CHECK:STDOUT:   return
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: specific invalid(constants.%X);
+// CHECK:STDOUT: specific @C(constants.%X) {
+// CHECK:STDOUT:   %X => constants.%X
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @C(constants.%tuple) {
+// CHECK:STDOUT:   %X => constants.%tuple
 // CHECK:STDOUT:
-// CHECK:STDOUT: specific invalid(constants.%tuple);
+// CHECK:STDOUT: !definition:
+// CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: --- fail_bad_value.impl.carbon
 // CHECK:STDOUT:
@@ -485,17 +509,17 @@ var c_bad: C((3, 4)) = F();
 // CHECK:STDOUT:   %.2: type = struct_type {} [template]
 // CHECK:STDOUT:   %.3: type = tuple_type (i32, i32) [template]
 // CHECK:STDOUT:   %X: %.3 = bind_symbolic_name X 0 [symbolic]
-// CHECK:STDOUT:   %C.2: type = class_type @C, invalid(%X) [symbolic]
+// CHECK:STDOUT:   %C.2: type = class_type @C, @C(%X) [symbolic]
 // CHECK:STDOUT:   %.4: i32 = int_literal 3 [template]
 // CHECK:STDOUT:   %.5: i32 = int_literal 4 [template]
 // CHECK:STDOUT:   %.6: type = ptr_type %.3 [template]
 // CHECK:STDOUT:   %tuple.1: %.3 = tuple_value (%.4, %.5) [template]
-// CHECK:STDOUT:   %C.3: type = class_type @C, invalid(%tuple.1) [template]
+// CHECK:STDOUT:   %C.3: type = class_type @C, @C(%tuple.1) [template]
 // CHECK:STDOUT:   %.7: type = ptr_type %.2 [template]
 // CHECK:STDOUT:   %.8: i32 = int_literal 2 [template]
 // CHECK:STDOUT:   %.9: i32 = int_literal 1 [template]
 // CHECK:STDOUT:   %tuple.2: %.3 = tuple_value (%.9, %.8) [template]
-// CHECK:STDOUT:   %C.4: type = class_type @C, invalid(%tuple.2) [template]
+// CHECK:STDOUT:   %C.4: type = class_type @C, @C(%tuple.2) [template]
 // CHECK:STDOUT:   %F.type: type = fn_type @F [template]
 // CHECK:STDOUT:   %F: %F.type = struct_value () [template]
 // CHECK:STDOUT: }
@@ -542,9 +566,15 @@ var c_bad: C((3, 4)) = F();
 // CHECK:STDOUT:   %c_bad: ref %C.3 = bind_name c_bad, %c_bad.var
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: class @C {
-// CHECK:STDOUT: !members:
-// CHECK:STDOUT:   .Self = imports.%import_ref.5
+// CHECK:STDOUT: generic class @C(constants.%X: %.3) {
+// CHECK:STDOUT:   %X: %.3 = bind_symbolic_name X 0 [symbolic = %X (constants.%X)]
+// CHECK:STDOUT:
+// CHECK:STDOUT: !definition:
+// CHECK:STDOUT:
+// CHECK:STDOUT:   class {
+// CHECK:STDOUT:   !members:
+// CHECK:STDOUT:     .Self = imports.%import_ref.5
+// CHECK:STDOUT:   }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @F() -> %C.4;
@@ -558,9 +588,19 @@ var c_bad: C((3, 4)) = F();
 // CHECK:STDOUT:   return
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: specific invalid(constants.%X);
+// CHECK:STDOUT: specific @C(constants.%X) {
+// CHECK:STDOUT:   %X => constants.%X
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @C(constants.%tuple.1) {
+// CHECK:STDOUT:   %X => constants.%tuple.1
 // CHECK:STDOUT:
-// CHECK:STDOUT: specific invalid(constants.%tuple.1);
+// CHECK:STDOUT: !definition:
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @C(constants.%tuple.2) {
+// CHECK:STDOUT:   %X => constants.%tuple.2
 // CHECK:STDOUT:
-// CHECK:STDOUT: specific invalid(constants.%tuple.2);
+// CHECK:STDOUT: !definition:
+// CHECK:STDOUT: }
 // CHECK:STDOUT:

+ 1 - 0
toolchain/sem_ir/generic.cpp

@@ -32,6 +32,7 @@ class SpecificStore::KeyContext : public TranslatingKeyContext<KeyContext> {
 
 auto SpecificStore::GetOrAdd(GenericId generic_id, InstBlockId args_id)
     -> SpecificId {
+  CARBON_CHECK(generic_id.is_valid());
   return lookup_table_
       .Insert(
           KeyContext::Key{.generic_id = generic_id, .args_id = args_id},