فهرست منبع

Compute specific constant values. (#4128)

When forming a specific (previously called a generic instance), evaluate
the eval block of the generic to determine the values of any constants
used in that specific. The majority of the work here is updating
eval.cpp so that it can use the results of prior evaluations in the same
block when computing later values.

Include the computed results in the formatted SemIR output.

---------

Co-authored-by: Jon Ross-Perkins <jperkins@google.com>
Richard Smith 1 سال پیش
والد
کامیت
efea072be3
73فایلهای تغییر یافته به همراه1429 افزوده شده و 272 حذف شده
  1. 319 146
      toolchain/check/eval.cpp
  2. 8 2
      toolchain/check/eval.h
  3. 23 7
      toolchain/check/generic.cpp
  4. 5 0
      toolchain/check/testdata/array/generic_empty.carbon
  5. 11 8
      toolchain/check/testdata/basics/no_prelude/raw_ir.carbon
  6. 6 0
      toolchain/check/testdata/builtins/int/make_type_signed.carbon
  7. 6 0
      toolchain/check/testdata/builtins/int/make_type_unsigned.carbon
  8. 18 2
      toolchain/check/testdata/class/fail_generic_method.carbon
  9. 22 3
      toolchain/check/testdata/class/generic/basic.carbon
  10. 42 6
      toolchain/check/testdata/class/generic/call.carbon
  11. 21 3
      toolchain/check/testdata/class/generic/fail_todo_use.carbon
  12. 36 4
      toolchain/check/testdata/class/generic/field.carbon
  13. 59 15
      toolchain/check/testdata/class/generic/import.carbon
  14. 11 1
      toolchain/check/testdata/class/generic/member_inline.carbon
  15. 78 8
      toolchain/check/testdata/class/generic/member_out_of_line.carbon
  16. 62 10
      toolchain/check/testdata/class/generic/redeclare.carbon
  17. 23 3
      toolchain/check/testdata/class/generic/self.carbon
  18. 14 3
      toolchain/check/testdata/class/generic_method.carbon
  19. 5 0
      toolchain/check/testdata/eval/fail_symbolic.carbon
  20. 5 0
      toolchain/check/testdata/eval/symbolic.carbon
  21. 5 0
      toolchain/check/testdata/function/builtin/method.carbon
  22. 5 0
      toolchain/check/testdata/function/builtin/no_prelude/call_from_operator.carbon
  23. 5 0
      toolchain/check/testdata/function/generic/fail_todo_param_in_type.carbon
  24. 6 0
      toolchain/check/testdata/function/generic/no_prelude/fail_type_param_mismatch.carbon
  25. 7 0
      toolchain/check/testdata/function/generic/no_prelude/indirect_generic_type.carbon
  26. 5 0
      toolchain/check/testdata/function/generic/no_prelude/type_param.carbon
  27. 5 0
      toolchain/check/testdata/function/generic/no_prelude/type_param_scope.carbon
  28. 48 0
      toolchain/check/testdata/function/generic/redeclare.carbon
  29. 9 0
      toolchain/check/testdata/impl/compound.carbon
  30. 4 0
      toolchain/check/testdata/impl/extend_impl.carbon
  31. 5 0
      toolchain/check/testdata/impl/fail_call_invalid.carbon
  32. 16 1
      toolchain/check/testdata/impl/fail_extend_impl_forall.carbon
  33. 4 0
      toolchain/check/testdata/impl/fail_extend_partially_defined_interface.carbon
  34. 4 0
      toolchain/check/testdata/impl/fail_impl_as_scope.carbon
  35. 18 0
      toolchain/check/testdata/impl/fail_impl_bad_assoc_fn.carbon
  36. 4 0
      toolchain/check/testdata/impl/impl_as.carbon
  37. 8 0
      toolchain/check/testdata/impl/impl_forall.carbon
  38. 4 0
      toolchain/check/testdata/impl/lookup/alias.carbon
  39. 4 0
      toolchain/check/testdata/impl/lookup/fail_alias_impl_not_found.carbon
  40. 4 0
      toolchain/check/testdata/impl/lookup/fail_todo_undefined_impl.carbon
  41. 4 0
      toolchain/check/testdata/impl/lookup/import.carbon
  42. 5 0
      toolchain/check/testdata/impl/lookup/instance_method.carbon
  43. 4 0
      toolchain/check/testdata/impl/lookup/no_prelude/import.carbon
  44. 4 0
      toolchain/check/testdata/impl/no_prelude/basic.carbon
  45. 5 0
      toolchain/check/testdata/impl/no_prelude/import_self.carbon
  46. 5 0
      toolchain/check/testdata/impl/no_prelude/self_in_class.carbon
  47. 14 0
      toolchain/check/testdata/impl/no_prelude/self_in_signature.carbon
  48. 8 0
      toolchain/check/testdata/interface/fail_todo_define_default_fn_inline.carbon
  49. 8 0
      toolchain/check/testdata/interface/fail_todo_define_default_fn_out_of_line.carbon
  50. 5 0
      toolchain/check/testdata/interface/no_prelude/as_type_of_type.carbon
  51. 4 0
      toolchain/check/testdata/interface/no_prelude/basic.carbon
  52. 4 0
      toolchain/check/testdata/interface/no_prelude/default_fn.carbon
  53. 12 0
      toolchain/check/testdata/interface/no_prelude/fail_add_member_outside_definition.carbon
  54. 22 2
      toolchain/check/testdata/interface/no_prelude/fail_generic_redeclaration.carbon
  55. 8 0
      toolchain/check/testdata/interface/no_prelude/fail_lookup_undefined.carbon
  56. 4 0
      toolchain/check/testdata/interface/no_prelude/fail_member_lookup.carbon
  57. 8 0
      toolchain/check/testdata/interface/no_prelude/fail_redeclare_member.carbon
  58. 14 0
      toolchain/check/testdata/interface/no_prelude/fail_todo_facet_lookup.carbon
  59. 27 5
      toolchain/check/testdata/interface/no_prelude/fail_todo_generic_default_fn.carbon
  60. 8 0
      toolchain/check/testdata/interface/no_prelude/fail_todo_modifiers.carbon
  61. 72 7
      toolchain/check/testdata/interface/no_prelude/generic.carbon
  62. 10 0
      toolchain/check/testdata/interface/no_prelude/generic_binding_after_assoc_const.carbon
  63. 16 3
      toolchain/check/testdata/interface/no_prelude/generic_import.carbon
  64. 8 0
      toolchain/check/testdata/interface/no_prelude/import.carbon
  65. 5 0
      toolchain/check/testdata/interface/no_prelude/self.carbon
  66. 8 0
      toolchain/check/testdata/interface/todo_define_not_default.carbon
  67. 5 0
      toolchain/check/testdata/namespace/fail_params.carbon
  68. 16 4
      toolchain/check/testdata/packages/no_prelude/fail_export_name_params.carbon
  69. 5 0
      toolchain/check/testdata/return/fail_let_in_type.carbon
  70. 33 9
      toolchain/check/testdata/struct/import.carbon
  71. 33 9
      toolchain/check/testdata/tuples/import.carbon
  72. 68 4
      toolchain/sem_ir/formatter.cpp
  73. 33 7
      toolchain/sem_ir/generic.h

+ 319 - 146
toolchain/check/eval.cpp

@@ -17,6 +17,104 @@
 
 namespace Carbon::Check {
 
+namespace {
+// Information about an eval block of a specific that we are currently building.
+struct SpecificEvalInfo {
+  // The region within the specific whose eval block we are building.
+  SemIR::GenericInstIndex::Region region;
+  // The work-in-progress contents of the eval block.
+  llvm::ArrayRef<SemIR::InstId> values;
+};
+
+// Information about the context within which we are performing evaluation.
+struct EvalContext {
+  // Given a constant value from the SemIR we're evaluating, finds the
+  // corresponding constant value to use in the context of this evaluation.
+  // This can be different if the original SemIR is for a generic and we are
+  // evaluating with specific arguments for the generic parameters.
+  auto GetInContext(SemIR::ConstantId const_id) -> SemIR::ConstantId {
+    if (!const_id.is_symbolic()) {
+      return const_id;
+    }
+
+    // While resolving a specific, map from previous instructions in the eval
+    // block into their evaluated values. These values won't be present on the
+    // instance itself yet, so `GetConstantInInstance` won't be able to find
+    // them.
+    if (specific_eval_info) {
+      const auto& symbolic_info =
+          context.constant_values().GetSymbolicConstant(const_id);
+      if (symbolic_info.index.is_valid() &&
+          symbolic_info.generic_id ==
+              context.generic_instances().Get(specific_id).generic_id &&
+          symbolic_info.index.region() == specific_eval_info->region) {
+        auto inst_id = specific_eval_info->values[symbolic_info.index.index()];
+        CARBON_CHECK(inst_id.is_valid())
+            << "Forward reference in eval block: index "
+            << symbolic_info.index.index() << " referenced before evaluation";
+        return context.constant_values().Get(inst_id);
+      }
+    }
+
+    // Map from a specific constant value to the canonical value.
+    return GetConstantInInstance(context.sem_ir(), specific_id, const_id);
+  }
+
+  // Gets the constant value of the specified instruction in this context.
+  auto GetConstantValue(SemIR::InstId inst_id) -> SemIR::ConstantId {
+    return GetInContext(context.constant_values().Get(inst_id));
+  }
+
+  // Gets the constant value of the specified type in this context.
+  auto GetConstantValue(SemIR::TypeId type_id) -> SemIR::ConstantId {
+    return GetInContext(context.types().GetConstantId(type_id));
+  }
+
+  // Gets the instruction describing the constant value of the specified type in
+  // this context.
+  auto GetConstantValueAsInst(SemIR::TypeId id) -> SemIR::Inst {
+    return insts().Get(
+        context.constant_values().GetInstId(GetConstantValue(id)));
+  }
+
+  auto ints() -> CanonicalValueStore<IntId>& { return sem_ir().ints(); }
+  auto floats() -> FloatValueStore& { return sem_ir().floats(); }
+  auto bind_names() -> SemIR::BindNameStore& { return sem_ir().bind_names(); }
+  auto functions() -> const ValueStore<SemIR::FunctionId>& {
+    return sem_ir().functions();
+  }
+  auto classes() -> const ValueStore<SemIR::ClassId>& {
+    return sem_ir().classes();
+  }
+  auto interfaces() -> const ValueStore<SemIR::InterfaceId>& {
+    return sem_ir().interfaces();
+  }
+  auto generic_instances() -> const SemIR::GenericInstanceStore& {
+    return sem_ir().generic_instances();
+  }
+  auto types() -> const SemIR::TypeStore& { return sem_ir().types(); }
+  auto type_blocks() -> SemIR::BlockValueStore<SemIR::TypeBlockId>& {
+    return sem_ir().type_blocks();
+  }
+  auto insts() -> const SemIR::InstStore& { return sem_ir().insts(); }
+  auto inst_blocks() -> SemIR::InstBlockStore& {
+    return sem_ir().inst_blocks();
+  }
+
+  auto sem_ir() -> SemIR::File& { return context.sem_ir(); }
+
+  auto emitter() -> Context::DiagnosticEmitter& { return context.emitter(); }
+
+  // The type-checking context in which we're performing evaluation.
+  Context& context;
+  // The specific that we are evaluating within.
+  SemIR::GenericInstanceId specific_id;
+  // If we are currently evaluating an eval block for `specific_id`, information
+  // about that evaluation.
+  std::optional<SpecificEvalInfo> specific_eval_info;
+};
+}  // namespace
+
 namespace {
 // The evaluation phase for an expression, computed by evaluation. These are
 // ordered so that the phase of an expression is the numerically highest phase
@@ -56,9 +154,10 @@ static auto GetPhase(SemIR::ConstantId constant_id) -> Phase {
 // type of a constant is effectively treated as an operand of that constant when
 // determining its phase. For example, an empty struct with a symbolic type is a
 // symbolic constant, not a template constant.
-static auto GetTypePhase(Context& context, SemIR::TypeId type_id) -> Phase {
+static auto GetTypePhase(EvalContext& eval_context, SemIR::TypeId type_id)
+    -> Phase {
   CARBON_CHECK(type_id.is_valid());
-  return GetPhase(context.types().GetConstantId(type_id));
+  return GetPhase(eval_context.GetConstantValue(type_id));
 }
 
 // Returns the later of two phases.
@@ -121,32 +220,33 @@ static auto MakeFloatResult(Context& context, SemIR::TypeId type_id,
 // Overloads are provided for different kinds of ID.
 
 // If the given instruction is constant, returns its constant value.
-static auto GetConstantValue(Context& context, SemIR::InstId inst_id,
+static auto GetConstantValue(EvalContext& eval_context, SemIR::InstId inst_id,
                              Phase* phase) -> SemIR::InstId {
-  auto const_id = context.constant_values().Get(inst_id);
+  auto const_id = eval_context.GetConstantValue(inst_id);
   *phase = LatestPhase(*phase, GetPhase(const_id));
-  return context.constant_values().GetInstId(const_id);
+  return eval_context.context.constant_values().GetInstId(const_id);
 }
 
 // A type is always constant, but we still need to extract its phase.
-static auto GetConstantValue(Context& context, SemIR::TypeId type_id,
+static auto GetConstantValue(EvalContext& eval_context, SemIR::TypeId type_id,
                              Phase* phase) -> SemIR::TypeId {
-  auto const_id = context.types().GetConstantId(type_id);
+  auto const_id = eval_context.GetConstantValue(type_id);
   *phase = LatestPhase(*phase, GetPhase(const_id));
   return type_id;
 }
 
 // If the given instruction block contains only constants, returns a
 // corresponding block of those values.
-static auto GetConstantValue(Context& context, SemIR::InstBlockId inst_block_id,
-                             Phase* phase) -> SemIR::InstBlockId {
+static auto GetConstantValue(EvalContext& eval_context,
+                             SemIR::InstBlockId inst_block_id, Phase* phase)
+    -> SemIR::InstBlockId {
   if (!inst_block_id.is_valid()) {
     return SemIR::InstBlockId::Invalid;
   }
-  auto insts = context.inst_blocks().Get(inst_block_id);
+  auto insts = eval_context.inst_blocks().Get(inst_block_id);
   llvm::SmallVector<SemIR::InstId> const_insts;
   for (auto inst_id : insts) {
-    auto const_inst_id = GetConstantValue(context, inst_id, phase);
+    auto const_inst_id = GetConstantValue(eval_context, inst_id, phase);
     if (!const_inst_id.is_valid()) {
       return SemIR::InstBlockId::Invalid;
     }
@@ -162,34 +262,35 @@ static auto GetConstantValue(Context& context, SemIR::InstBlockId inst_block_id,
   }
   // TODO: If the new block is identical to the original block, and we know the
   // old ID was canonical, return the original ID.
-  return context.inst_blocks().AddCanonical(const_insts);
+  return eval_context.inst_blocks().AddCanonical(const_insts);
 }
 
 // The constant value of a type block is that type block, but we still need to
 // extract its phase.
-static auto GetConstantValue(Context& context, SemIR::TypeBlockId type_block_id,
-                             Phase* phase) -> SemIR::TypeBlockId {
+static auto GetConstantValue(EvalContext& eval_context,
+                             SemIR::TypeBlockId type_block_id, Phase* phase)
+    -> SemIR::TypeBlockId {
   if (!type_block_id.is_valid()) {
     return SemIR::TypeBlockId::Invalid;
   }
-  auto types = context.type_blocks().Get(type_block_id);
+  auto types = eval_context.type_blocks().Get(type_block_id);
   for (auto type_id : types) {
-    GetConstantValue(context, type_id, phase);
+    GetConstantValue(eval_context, type_id, phase);
   }
   return type_block_id;
 }
 
 // The constant value of a generic instance is the generic instance with the
 // corresponding constant values for its arguments.
-static auto GetConstantValue(Context& context,
+static auto GetConstantValue(EvalContext& eval_context,
                              SemIR::GenericInstanceId instance_id, Phase* phase)
     -> SemIR::GenericInstanceId {
   if (!instance_id.is_valid()) {
     return SemIR::GenericInstanceId::Invalid;
   }
 
-  const auto& instance = context.generic_instances().Get(instance_id);
-  auto args_id = GetConstantValue(context, instance.args_id, phase);
+  const auto& instance = eval_context.generic_instances().Get(instance_id);
+  auto args_id = GetConstantValue(eval_context, instance.args_id, phase);
   if (!args_id.is_valid()) {
     return SemIR::GenericInstanceId::Invalid;
   }
@@ -197,17 +298,18 @@ static auto GetConstantValue(Context& context,
   if (args_id == instance.args_id) {
     return instance_id;
   }
-  return MakeGenericInstance(context, instance.generic_id, args_id);
+  return MakeGenericInstance(eval_context.context, instance.generic_id,
+                             args_id);
 }
 
 // Replaces the specified field of the given typed instruction with its constant
 // value, if it has constant phase. Returns true on success, false if the value
 // has runtime phase.
 template <typename InstT, typename FieldIdT>
-static auto ReplaceFieldWithConstantValue(Context& context, InstT* inst,
-                                          FieldIdT InstT::*field, Phase* phase)
-    -> bool {
-  auto unwrapped = GetConstantValue(context, inst->*field, phase);
+static auto ReplaceFieldWithConstantValue(EvalContext& eval_context,
+                                          InstT* inst, FieldIdT InstT::*field,
+                                          Phase* phase) -> bool {
+  auto unwrapped = GetConstantValue(eval_context, inst->*field, phase);
   if (!unwrapped.is_valid() && (inst->*field).is_valid()) {
     return false;
   }
@@ -227,7 +329,7 @@ static auto ReplaceFieldWithConstantValue(Context& context, InstT* inst,
 // `ConstantId::Error` is returned.
 template <typename InstT, typename ValidateFn, typename... EachFieldIdT>
 static auto RebuildAndValidateIfFieldsAreConstant(
-    Context& context, SemIR::Inst inst, ValidateFn validate_fn,
+    EvalContext& eval_context, SemIR::Inst inst, ValidateFn validate_fn,
     EachFieldIdT InstT::*... each_field_id) -> SemIR::ConstantId {
   // Build a constant instruction by replacing each non-constant operand with
   // its constant value.
@@ -235,38 +337,40 @@ static auto RebuildAndValidateIfFieldsAreConstant(
   // Some instruction kinds don't have a `type_id` field. For those that do, the
   // type contributes to the phase.
   Phase phase = inst.type_id().is_valid()
-                    ? GetTypePhase(context, inst.type_id())
+                    ? GetTypePhase(eval_context, inst.type_id())
                     : Phase::Template;
-  if ((ReplaceFieldWithConstantValue(context, &typed_inst, each_field_id,
+  if ((ReplaceFieldWithConstantValue(eval_context, &typed_inst, each_field_id,
                                      &phase) &&
        ...)) {
     if (phase == Phase::UnknownDueToError || !validate_fn(typed_inst)) {
       return SemIR::ConstantId::Error;
     }
-    return MakeConstantResult(context, typed_inst, phase);
+    return MakeConstantResult(eval_context.context, typed_inst, phase);
   }
   return MakeNonConstantResult(phase);
 }
 
 // Same as above but with no validation step.
 template <typename InstT, typename... EachFieldIdT>
-static auto RebuildIfFieldsAreConstant(Context& context, SemIR::Inst inst,
+static auto RebuildIfFieldsAreConstant(EvalContext& eval_context,
+                                       SemIR::Inst inst,
                                        EachFieldIdT InstT::*... each_field_id)
     -> SemIR::ConstantId {
   return RebuildAndValidateIfFieldsAreConstant(
-      context, inst, [](...) { return true; }, each_field_id...);
+      eval_context, inst, [](...) { return true; }, each_field_id...);
 }
 
 // Rebuilds the given aggregate initialization instruction as a corresponding
 // constant aggregate value, if its elements are all constants.
-static auto RebuildInitAsValue(Context& context, SemIR::Inst inst,
+static auto RebuildInitAsValue(EvalContext& eval_context, SemIR::Inst inst,
                                SemIR::InstKind value_kind)
     -> SemIR::ConstantId {
   auto init_inst = inst.As<SemIR::AnyAggregateInit>();
-  Phase phase = GetTypePhase(context, init_inst.type_id);
-  auto elements_id = GetConstantValue(context, init_inst.elements_id, &phase);
+  Phase phase = GetTypePhase(eval_context, init_inst.type_id);
+  auto elements_id =
+      GetConstantValue(eval_context, init_inst.elements_id, &phase);
   return MakeConstantResult(
-      context,
+      eval_context.context,
       SemIR::AnyAggregateValue{.kind = value_kind,
                                .type_id = init_inst.type_id,
                                .elements_id = elements_id},
@@ -274,22 +378,23 @@ static auto RebuildInitAsValue(Context& context, SemIR::Inst inst,
 }
 
 // Performs an access into an aggregate, retrieving the specified element.
-static auto PerformAggregateAccess(Context& context, SemIR::Inst inst)
+static auto PerformAggregateAccess(EvalContext& eval_context, SemIR::Inst inst)
     -> SemIR::ConstantId {
   auto access_inst = inst.As<SemIR::AnyAggregateAccess>();
   Phase phase = Phase::Template;
   if (auto aggregate_id =
-          GetConstantValue(context, access_inst.aggregate_id, &phase);
+          GetConstantValue(eval_context, access_inst.aggregate_id, &phase);
       aggregate_id.is_valid()) {
     if (auto aggregate =
-            context.insts().TryGetAs<SemIR::AnyAggregateValue>(aggregate_id)) {
-      auto elements = context.inst_blocks().Get(aggregate->elements_id);
+            eval_context.insts().TryGetAs<SemIR::AnyAggregateValue>(
+                aggregate_id)) {
+      auto elements = eval_context.inst_blocks().Get(aggregate->elements_id);
       auto index = static_cast<size_t>(access_inst.index.index);
       CARBON_CHECK(index < elements.size()) << "Access out of bounds.";
       // `Phase` is not used here. If this element is a template constant, then
       // so is the result of indexing, even if the aggregate also contains a
       // symbolic context.
-      return context.constant_values().Get(elements[index]);
+      return eval_context.GetConstantValue(elements[index]);
     } else {
       CARBON_CHECK(phase != Phase::Template)
           << "Failed to evaluate template constant " << inst;
@@ -300,18 +405,18 @@ static auto PerformAggregateAccess(Context& context, SemIR::Inst inst)
 
 // Performs an index into a homogeneous aggregate, retrieving the specified
 // element.
-static auto PerformAggregateIndex(Context& context, SemIR::Inst inst)
+static auto PerformAggregateIndex(EvalContext& eval_context, SemIR::Inst inst)
     -> SemIR::ConstantId {
   auto index_inst = inst.As<SemIR::AnyAggregateIndex>();
   Phase phase = Phase::Template;
   auto aggregate_id =
-      GetConstantValue(context, index_inst.aggregate_id, &phase);
-  auto index_id = GetConstantValue(context, index_inst.index_id, &phase);
+      GetConstantValue(eval_context, index_inst.aggregate_id, &phase);
+  auto index_id = GetConstantValue(eval_context, index_inst.index_id, &phase);
 
   if (!index_id.is_valid()) {
     return MakeNonConstantResult(phase);
   }
-  auto index = context.insts().TryGetAs<SemIR::IntLiteral>(index_id);
+  auto index = eval_context.insts().TryGetAs<SemIR::IntLiteral>(index_id);
   if (!index) {
     CARBON_CHECK(phase != Phase::Template)
         << "Template constant integer should be a literal";
@@ -320,22 +425,24 @@ static auto PerformAggregateIndex(Context& context, SemIR::Inst inst)
 
   // Array indexing is invalid if the index is constant and out of range.
   auto aggregate_type_id =
-      context.insts().Get(index_inst.aggregate_id).type_id();
-  const auto& index_val = context.ints().Get(index->int_id);
+      eval_context.insts().Get(index_inst.aggregate_id).type_id();
+  const auto& index_val = eval_context.ints().Get(index->int_id);
   if (auto array_type =
-          context.types().TryGetAs<SemIR::ArrayType>(aggregate_type_id)) {
-    if (auto bound =
-            context.insts().TryGetAs<SemIR::IntLiteral>(array_type->bound_id)) {
+          eval_context.types().TryGetAs<SemIR::ArrayType>(aggregate_type_id)) {
+    if (auto bound = eval_context.insts().TryGetAs<SemIR::IntLiteral>(
+            array_type->bound_id)) {
       // This awkward call to `getZExtValue` is a workaround for APInt not
       // supporting comparisons between integers of different bit widths.
       if (index_val.getActiveBits() > 64 ||
-          context.ints().Get(bound->int_id).ule(index_val.getZExtValue())) {
+          eval_context.ints()
+              .Get(bound->int_id)
+              .ule(index_val.getZExtValue())) {
         CARBON_DIAGNOSTIC(ArrayIndexOutOfBounds, Error,
                           "Array index `{0}` is past the end of type `{1}`.",
                           TypedInt, SemIR::TypeId);
-        context.emitter().Emit(index_inst.index_id, ArrayIndexOutOfBounds,
-                               {.type = index->type_id, .value = index_val},
-                               aggregate_type_id);
+        eval_context.emitter().Emit(
+            index_inst.index_id, ArrayIndexOutOfBounds,
+            {.type = index->type_id, .value = index_val}, aggregate_type_id);
         return SemIR::ConstantId::Error;
       }
     }
@@ -345,18 +452,18 @@ static auto PerformAggregateIndex(Context& context, SemIR::Inst inst)
     return MakeNonConstantResult(phase);
   }
   auto aggregate =
-      context.insts().TryGetAs<SemIR::AnyAggregateValue>(aggregate_id);
+      eval_context.insts().TryGetAs<SemIR::AnyAggregateValue>(aggregate_id);
   if (!aggregate) {
     CARBON_CHECK(phase != Phase::Template)
         << "Unexpected representation for template constant aggregate";
     return MakeNonConstantResult(phase);
   }
 
-  auto elements = context.inst_blocks().Get(aggregate->elements_id);
+  auto elements = eval_context.inst_blocks().Get(aggregate->elements_id);
   // We checked this for the array case above.
   CARBON_CHECK(index_val.ult(elements.size()))
       << "Index out of bounds in tuple indexing";
-  return context.constant_values().Get(elements[index_val.getZExtValue()]);
+  return eval_context.GetConstantValue(elements[index_val.getZExtValue()]);
 }
 
 // Enforces that an integer type has a valid bit width.
@@ -867,7 +974,7 @@ static auto MakeConstantForBuiltinCall(Context& context, SemIRLoc loc,
 }
 
 // Makes a constant for a call instruction.
-static auto MakeConstantForCall(Context& context, SemIRLoc loc,
+static auto MakeConstantForCall(EvalContext& eval_context, SemIRLoc loc,
                                 SemIR::Call call) -> SemIR::ConstantId {
   Phase phase = Phase::Template;
 
@@ -880,17 +987,17 @@ static auto MakeConstantForCall(Context& context, SemIRLoc loc,
   }
 
   // If the callee isn't constant, this is not a constant call.
-  if (!ReplaceFieldWithConstantValue(context, &call, &SemIR::Call::callee_id,
-                                     &phase)) {
+  if (!ReplaceFieldWithConstantValue(eval_context, &call,
+                                     &SemIR::Call::callee_id, &phase)) {
     return SemIR::ConstantId::NotConstant;
   }
 
   auto callee_function =
-      SemIR::GetCalleeFunction(context.sem_ir(), call.callee_id);
+      SemIR::GetCalleeFunction(eval_context.sem_ir(), call.callee_id);
   auto builtin_kind = SemIR::BuiltinFunctionKind::None;
   if (callee_function.function_id.is_valid()) {
     // Calls to builtins might be constant.
-    builtin_kind = context.functions()
+    builtin_kind = eval_context.functions()
                        .Get(callee_function.function_id)
                        .builtin_function_kind;
     if (builtin_kind == SemIR::BuiltinFunctionKind::None) {
@@ -904,7 +1011,7 @@ static auto MakeConstantForCall(Context& context, SemIRLoc loc,
   }
 
   // If the arguments aren't constant, this is not a constant call.
-  if (!ReplaceFieldWithConstantValue(context, &call, &SemIR::Call::args_id,
+  if (!ReplaceFieldWithConstantValue(eval_context, &call, &SemIR::Call::args_id,
                                      &phase)) {
     return SemIR::ConstantId::NotConstant;
   }
@@ -914,34 +1021,37 @@ static auto MakeConstantForCall(Context& context, SemIRLoc loc,
 
   // Handle calls to builtins.
   if (builtin_kind != SemIR::BuiltinFunctionKind::None) {
-    return MakeConstantForBuiltinCall(context, loc, call, builtin_kind,
-                                      context.inst_blocks().Get(call.args_id),
-                                      phase);
+    return MakeConstantForBuiltinCall(
+        eval_context.context, loc, call, builtin_kind,
+        eval_context.inst_blocks().Get(call.args_id), phase);
   }
 
   // Look at the type of the callee for special cases: calls to generic class
   // and generic interface types.
-  auto type_inst =
-      context.types().GetAsInst(context.insts().Get(call.callee_id).type_id());
+  auto type_inst = eval_context.GetConstantValueAsInst(
+      eval_context.insts().Get(call.callee_id).type_id());
   CARBON_KIND_SWITCH(type_inst) {
     case CARBON_KIND(SemIR::GenericClassType generic_class): {
       auto instance_id = MakeGenericInstance(
-          context, context.classes().Get(generic_class.class_id).generic_id,
+          eval_context.context,
+          eval_context.classes().Get(generic_class.class_id).generic_id,
           call.args_id);
       return MakeConstantResult(
-          context,
+          eval_context.context,
           SemIR::ClassType{.type_id = call.type_id,
                            .class_id = generic_class.class_id,
                            .instance_id = instance_id},
           phase);
     }
     case CARBON_KIND(SemIR::GenericInterfaceType generic_interface): {
-      auto instance_id = MakeGenericInstance(
-          context,
-          context.interfaces().Get(generic_interface.interface_id).generic_id,
-          call.args_id);
+      auto instance_id =
+          MakeGenericInstance(eval_context.context,
+                              eval_context.interfaces()
+                                  .Get(generic_interface.interface_id)
+                                  .generic_id,
+                              call.args_id);
       return MakeConstantResult(
-          context,
+          eval_context.context,
           SemIR::InterfaceType{.type_id = call.type_id,
                                .interface_id = generic_interface.interface_id,
                                .instance_id = instance_id},
@@ -953,37 +1063,37 @@ static auto MakeConstantForCall(Context& context, SemIRLoc loc,
   }
 }
 
-auto TryEvalInst(Context& context, SemIR::InstId inst_id, SemIR::Inst inst)
-    -> SemIR::ConstantId {
+auto TryEvalInstInContext(EvalContext& eval_context, SemIR::InstId inst_id,
+                          SemIR::Inst inst) -> SemIR::ConstantId {
   // TODO: Ensure we have test coverage for each of these cases that can result
   // in a constant, once those situations are all reachable.
   CARBON_KIND_SWITCH(inst) {
     // These cases are constants if their operands are.
     case SemIR::AddrOf::Kind:
-      return RebuildIfFieldsAreConstant(context, inst,
+      return RebuildIfFieldsAreConstant(eval_context, inst,
                                         &SemIR::AddrOf::lvalue_id);
     case CARBON_KIND(SemIR::ArrayType array_type): {
       return RebuildAndValidateIfFieldsAreConstant(
-          context, inst,
+          eval_context, inst,
           [&](SemIR::ArrayType result) {
             auto bound_id = array_type.bound_id;
-            auto int_bound =
-                context.insts().TryGetAs<SemIR::IntLiteral>(result.bound_id);
+            auto int_bound = eval_context.insts().TryGetAs<SemIR::IntLiteral>(
+                result.bound_id);
             if (!int_bound) {
               // TODO: Permit symbolic array bounds. This will require fixing
               // callers of `GetArrayBoundValue`.
-              context.TODO(bound_id, "symbolic array bound");
+              eval_context.context.TODO(bound_id, "symbolic array bound");
               return false;
             }
             // TODO: We should check that the size of the resulting array type
             // fits in 64 bits, not just that the bound does. Should we use a
             // 32-bit limit for 32-bit targets?
-            const auto& bound_val = context.ints().Get(int_bound->int_id);
-            if (context.types().IsSignedInt(int_bound->type_id) &&
+            const auto& bound_val = eval_context.ints().Get(int_bound->int_id);
+            if (eval_context.types().IsSignedInt(int_bound->type_id) &&
                 bound_val.isNegative()) {
               CARBON_DIAGNOSTIC(ArrayBoundNegative, Error,
                                 "Array bound of {0} is negative.", TypedInt);
-              context.emitter().Emit(
+              eval_context.emitter().Emit(
                   bound_id, ArrayBoundNegative,
                   {.type = int_bound->type_id, .value = bound_val});
               return false;
@@ -991,7 +1101,7 @@ auto TryEvalInst(Context& context, SemIR::InstId inst_id, SemIR::Inst inst)
             if (bound_val.getActiveBits() > 64) {
               CARBON_DIAGNOSTIC(ArrayBoundTooLarge, Error,
                                 "Array bound of {0} is too large.", TypedInt);
-              context.emitter().Emit(
+              eval_context.emitter().Emit(
                   bound_id, ArrayBoundTooLarge,
                   {.type = int_bound->type_id, .value = bound_val});
               return false;
@@ -1002,72 +1112,74 @@ auto TryEvalInst(Context& context, SemIR::InstId inst_id, SemIR::Inst inst)
     }
     case SemIR::AssociatedEntityType::Kind:
       return RebuildIfFieldsAreConstant(
-          context, inst, &SemIR::AssociatedEntityType::entity_type_id);
+          eval_context, inst, &SemIR::AssociatedEntityType::entity_type_id);
     case SemIR::BoundMethod::Kind:
-      return RebuildIfFieldsAreConstant(context, inst,
+      return RebuildIfFieldsAreConstant(eval_context, inst,
                                         &SemIR::BoundMethod::object_id,
                                         &SemIR::BoundMethod::function_id);
     case SemIR::ClassType::Kind:
-      return RebuildIfFieldsAreConstant(context, inst,
+      return RebuildIfFieldsAreConstant(eval_context, inst,
                                         &SemIR::ClassType::instance_id);
     case SemIR::InterfaceType::Kind:
-      return RebuildIfFieldsAreConstant(context, inst,
+      return RebuildIfFieldsAreConstant(eval_context, inst,
                                         &SemIR::InterfaceType::instance_id);
     case SemIR::InterfaceWitness::Kind:
-      return RebuildIfFieldsAreConstant(context, inst,
+      return RebuildIfFieldsAreConstant(eval_context, inst,
                                         &SemIR::InterfaceWitness::elements_id);
     case CARBON_KIND(SemIR::IntType int_type): {
       return RebuildAndValidateIfFieldsAreConstant(
-          context, inst,
+          eval_context, inst,
           [&](SemIR::IntType result) {
-            return ValidateIntType(context, int_type.bit_width_id, result);
+            return ValidateIntType(eval_context.context, int_type.bit_width_id,
+                                   result);
           },
           &SemIR::IntType::bit_width_id);
     }
     case SemIR::PointerType::Kind:
-      return RebuildIfFieldsAreConstant(context, inst,
+      return RebuildIfFieldsAreConstant(eval_context, inst,
                                         &SemIR::PointerType::pointee_id);
     case CARBON_KIND(SemIR::FloatType float_type): {
       return RebuildAndValidateIfFieldsAreConstant(
-          context, inst,
+          eval_context, inst,
           [&](SemIR::FloatType result) {
-            return ValidateFloatType(context, float_type.bit_width_id, result);
+            return ValidateFloatType(eval_context.context,
+                                     float_type.bit_width_id, result);
           },
           &SemIR::FloatType::bit_width_id);
     }
     case SemIR::StructType::Kind:
-      return RebuildIfFieldsAreConstant(context, inst,
+      return RebuildIfFieldsAreConstant(eval_context, inst,
                                         &SemIR::StructType::fields_id);
     case SemIR::StructTypeField::Kind:
-      return RebuildIfFieldsAreConstant(context, inst,
+      return RebuildIfFieldsAreConstant(eval_context, inst,
                                         &SemIR::StructTypeField::field_type_id);
     case SemIR::StructValue::Kind:
-      return RebuildIfFieldsAreConstant(context, inst,
+      return RebuildIfFieldsAreConstant(eval_context, inst,
                                         &SemIR::StructValue::elements_id);
     case SemIR::TupleType::Kind:
-      return RebuildIfFieldsAreConstant(context, inst,
+      return RebuildIfFieldsAreConstant(eval_context, inst,
                                         &SemIR::TupleType::elements_id);
     case SemIR::TupleValue::Kind:
-      return RebuildIfFieldsAreConstant(context, inst,
+      return RebuildIfFieldsAreConstant(eval_context, inst,
                                         &SemIR::TupleValue::elements_id);
     case SemIR::UnboundElementType::Kind:
       return RebuildIfFieldsAreConstant(
-          context, inst, &SemIR::UnboundElementType::class_type_id,
+          eval_context, inst, &SemIR::UnboundElementType::class_type_id,
           &SemIR::UnboundElementType::element_type_id);
 
     // Initializers evaluate to a value of the object representation.
     case SemIR::ArrayInit::Kind:
       // TODO: Add an `ArrayValue` to represent a constant array object
       // representation instead of using a `TupleValue`.
-      return RebuildInitAsValue(context, inst, SemIR::TupleValue::Kind);
+      return RebuildInitAsValue(eval_context, inst, SemIR::TupleValue::Kind);
     case SemIR::ClassInit::Kind:
       // TODO: Add a `ClassValue` to represent a constant class object
       // representation instead of using a `StructValue`.
-      return RebuildInitAsValue(context, inst, SemIR::StructValue::Kind);
+      return RebuildInitAsValue(eval_context, inst, SemIR::StructValue::Kind);
     case SemIR::StructInit::Kind:
-      return RebuildInitAsValue(context, inst, SemIR::StructValue::Kind);
+      return RebuildInitAsValue(eval_context, inst, SemIR::StructValue::Kind);
     case SemIR::TupleInit::Kind:
-      return RebuildInitAsValue(context, inst, SemIR::TupleValue::Kind);
+      return RebuildInitAsValue(eval_context, inst, SemIR::TupleValue::Kind);
 
     case SemIR::AssociatedEntity::Kind:
     case SemIR::BuiltinInst::Kind:
@@ -1075,29 +1187,29 @@ auto TryEvalInst(Context& context, SemIR::InstId inst_id, SemIR::Inst inst)
     case SemIR::GenericClassType::Kind:
     case SemIR::GenericInterfaceType::Kind:
       // Builtins are always template constants.
-      return MakeConstantResult(context, inst, Phase::Template);
+      return MakeConstantResult(eval_context.context, inst, Phase::Template);
 
     case CARBON_KIND(SemIR::FunctionDecl fn_decl): {
       return MakeConstantResult(
-          context,
+          eval_context.context,
           SemIR::StructValue{.type_id = fn_decl.type_id,
                              .elements_id = SemIR::InstBlockId::Empty},
-          GetTypePhase(context, fn_decl.type_id));
+          GetTypePhase(eval_context, fn_decl.type_id));
     }
 
     case CARBON_KIND(SemIR::ClassDecl class_decl): {
       // If the class has generic parameters, we don't produce a class type, but
       // a callable whose return value is a class type.
-      if (context.classes().Get(class_decl.class_id).is_generic()) {
+      if (eval_context.classes().Get(class_decl.class_id).is_generic()) {
         return MakeConstantResult(
-            context,
+            eval_context.context,
             SemIR::StructValue{.type_id = class_decl.type_id,
                                .elements_id = SemIR::InstBlockId::Empty},
-            GetTypePhase(context, class_decl.type_id));
+            GetTypePhase(eval_context, class_decl.type_id));
       }
       // A non-generic class declaration evaluates to the class type.
       return MakeConstantResult(
-          context,
+          eval_context.context,
           SemIR::ClassType{.type_id = SemIR::TypeId::TypeType,
                            .class_id = class_decl.class_id,
                            .instance_id = SemIR::GenericInstanceId::Invalid},
@@ -1106,16 +1218,18 @@ auto TryEvalInst(Context& context, SemIR::InstId inst_id, SemIR::Inst inst)
     case CARBON_KIND(SemIR::InterfaceDecl interface_decl): {
       // If the interface has generic parameters, we don't produce an interface
       // type, but a callable whose return value is an interface type.
-      if (context.interfaces().Get(interface_decl.interface_id).is_generic()) {
+      if (eval_context.interfaces()
+              .Get(interface_decl.interface_id)
+              .is_generic()) {
         return MakeConstantResult(
-            context,
+            eval_context.context,
             SemIR::StructValue{.type_id = interface_decl.type_id,
                                .elements_id = SemIR::InstBlockId::Empty},
-            GetTypePhase(context, interface_decl.type_id));
+            GetTypePhase(eval_context, interface_decl.type_id));
       }
       // A non-generic interface declaration evaluates to the interface type.
       return MakeConstantResult(
-          context,
+          eval_context.context,
           SemIR::InterfaceType{
               .type_id = SemIR::TypeId::TypeType,
               .interface_id = interface_decl.interface_id,
@@ -1124,9 +1238,9 @@ auto TryEvalInst(Context& context, SemIR::InstId inst_id, SemIR::Inst inst)
     }
 
     case CARBON_KIND(SemIR::SpecificConstant instance): {
-      // Pull the instance-specific constant value out of the generic instance.
+      // Pull the constant value out of the specific.
       return SemIR::GetConstantValueInInstance(
-          context.sem_ir(), instance.instance_id, instance.inst_id);
+          eval_context.sem_ir(), instance.instance_id, instance.inst_id);
     }
 
     // These cases are treated as being the unique canonical definition of the
@@ -1147,20 +1261,20 @@ auto TryEvalInst(Context& context, SemIR::InstId inst_id, SemIR::Inst inst)
       // TODO: Convert literals into a canonical form. Currently we can form two
       // different `i32` constants with the same value if they are represented
       // by `APInt`s with different bit widths.
-      return MakeConstantResult(context, inst, Phase::Template);
+      return MakeConstantResult(eval_context.context, inst, Phase::Template);
 
     // The elements of a constant aggregate can be accessed.
     case SemIR::ClassElementAccess::Kind:
     case SemIR::InterfaceWitnessAccess::Kind:
     case SemIR::StructAccess::Kind:
     case SemIR::TupleAccess::Kind:
-      return PerformAggregateAccess(context, inst);
+      return PerformAggregateAccess(eval_context, inst);
     case SemIR::ArrayIndex::Kind:
     case SemIR::TupleIndex::Kind:
-      return PerformAggregateIndex(context, inst);
+      return PerformAggregateIndex(eval_context, inst);
 
     case CARBON_KIND(SemIR::Call call): {
-      return MakeConstantForCall(context, inst_id, call);
+      return MakeConstantForCall(eval_context, inst_id, call);
     }
 
     // TODO: These need special handling.
@@ -1173,58 +1287,73 @@ auto TryEvalInst(Context& context, SemIR::InstId inst_id, SemIR::Inst inst)
       break;
 
     case CARBON_KIND(SemIR::BindSymbolicName bind): {
+      const auto& bind_name = eval_context.bind_names().Get(bind.bind_name_id);
+
+      // If we know which instance we're evaluating within and this is an
+      // argument of that instance, its constant value is the corresponding
+      // argument value.
+      if (bind_name.bind_index.is_valid() &&
+          eval_context.specific_id.is_valid()) {
+        const auto& specific =
+            eval_context.generic_instances().Get(eval_context.specific_id);
+        auto args = eval_context.inst_blocks().Get(specific.args_id);
+        CARBON_CHECK(static_cast<size_t>(bind_name.bind_index.index) <
+                     args.size())
+            << "Use of binding " << bind_name.bind_index
+            << " with no corresponding value.";
+        return eval_context.context.constant_values().Get(
+            args[bind_name.bind_index.index]);
+      }
+
       // The constant form of a symbolic binding is an idealized form of the
       // original, with no equivalent value.
-      bind.bind_name_id = context.bind_names().MakeCanonical(bind.bind_name_id);
+      bind.bind_name_id =
+          eval_context.bind_names().MakeCanonical(bind.bind_name_id);
       bind.value_id = SemIR::InstId::Invalid;
-      return MakeConstantResult(context, bind, Phase::Symbolic);
+      return MakeConstantResult(eval_context.context, bind, Phase::Symbolic);
     }
 
     // These semantic wrappers don't change the constant value.
     case CARBON_KIND(SemIR::AsCompatible inst): {
-      return context.constant_values().Get(inst.source_id);
+      return eval_context.GetConstantValue(inst.source_id);
     }
     case CARBON_KIND(SemIR::BindAlias typed_inst): {
-      return context.constant_values().Get(typed_inst.value_id);
+      return eval_context.GetConstantValue(typed_inst.value_id);
     }
     case CARBON_KIND(SemIR::ExportDecl typed_inst): {
-      return context.constant_values().Get(typed_inst.value_id);
+      return eval_context.GetConstantValue(typed_inst.value_id);
     }
     case CARBON_KIND(SemIR::NameRef typed_inst): {
-      // Map from an instance-specific constant value to the canonical value.
-      // TODO: Remove this once we properly model instructions with
-      // instance-dependent constant values.
-      return GetConstantValueInInstance(context.sem_ir(),
-                                        SemIR::GenericInstanceId::Invalid,
-                                        typed_inst.value_id);
+      return eval_context.GetConstantValue(typed_inst.value_id);
     }
     case CARBON_KIND(SemIR::Converted typed_inst): {
-      return context.constant_values().Get(typed_inst.result_id);
+      return eval_context.GetConstantValue(typed_inst.result_id);
     }
     case CARBON_KIND(SemIR::InitializeFrom typed_inst): {
-      return context.constant_values().Get(typed_inst.src_id);
+      return eval_context.GetConstantValue(typed_inst.src_id);
     }
     case CARBON_KIND(SemIR::SpliceBlock typed_inst): {
-      return context.constant_values().Get(typed_inst.result_id);
+      return eval_context.GetConstantValue(typed_inst.result_id);
     }
     case CARBON_KIND(SemIR::ValueOfInitializer typed_inst): {
-      return context.constant_values().Get(typed_inst.init_id);
+      return eval_context.GetConstantValue(typed_inst.init_id);
     }
     case CARBON_KIND(SemIR::FacetTypeAccess typed_inst): {
       // TODO: Once we start tracking the witness in the facet value, remove it
       // here. For now, we model a facet value as just a type.
-      return context.constant_values().Get(typed_inst.facet_id);
+      return eval_context.GetConstantValue(typed_inst.facet_id);
     }
 
     // `not true` -> `false`, `not false` -> `true`.
     // All other uses of unary `not` are non-constant.
     case CARBON_KIND(SemIR::UnaryOperatorNot typed_inst): {
-      auto const_id = context.constant_values().Get(typed_inst.operand_id);
+      auto const_id = eval_context.GetConstantValue(typed_inst.operand_id);
       auto phase = GetPhase(const_id);
       if (phase == Phase::Template) {
-        auto value = context.insts().GetAs<SemIR::BoolLiteral>(
-            context.constant_values().GetInstId(const_id));
-        return MakeBoolResult(context, value.type_id, !value.value.ToBool());
+        auto value = eval_context.insts().GetAs<SemIR::BoolLiteral>(
+            eval_context.context.constant_values().GetInstId(const_id));
+        return MakeBoolResult(eval_context.context, value.type_id,
+                              !value.value.ToBool());
       }
       if (phase == Phase::UnknownDueToError) {
         return SemIR::ConstantId::Error;
@@ -1235,15 +1364,15 @@ auto TryEvalInst(Context& context, SemIR::InstId inst_id, SemIR::Inst inst)
     // `const (const T)` evaluates to `const T`. Otherwise, `const T` evaluates
     // to itself.
     case CARBON_KIND(SemIR::ConstType typed_inst): {
-      auto inner_id = context.constant_values().Get(
-          context.types().GetInstId(typed_inst.inner_id));
+      auto inner_id = eval_context.GetConstantValue(
+          eval_context.types().GetInstId(typed_inst.inner_id));
       if (inner_id.is_constant() &&
-          context.insts()
-              .Get(context.constant_values().GetInstId(inner_id))
+          eval_context.insts()
+              .Get(eval_context.context.constant_values().GetInstId(inner_id))
               .Is<SemIR::ConstType>()) {
         return inner_id;
       }
-      return MakeConstantResult(context, inst, GetPhase(inner_id));
+      return MakeConstantResult(eval_context.context, inst, GetPhase(inner_id));
     }
 
     // These cases are either not expressions or not constant.
@@ -1272,4 +1401,48 @@ auto TryEvalInst(Context& context, SemIR::InstId inst_id, SemIR::Inst inst)
   return SemIR::ConstantId::NotConstant;
 }
 
+auto TryEvalInst(Context& context, SemIR::InstId inst_id, SemIR::Inst inst)
+    -> SemIR::ConstantId {
+  EvalContext eval_context = {
+      .context = context,
+      .specific_id = SemIR::GenericInstanceId::Invalid,
+      .specific_eval_info = std::nullopt,
+  };
+  return TryEvalInstInContext(eval_context, inst_id, inst);
+}
+
+auto TryEvalBlockForSpecific(Context& context,
+                             SemIR::GenericInstanceId specific_id,
+                             SemIR::GenericInstIndex::Region region)
+    -> SemIR::InstBlockId {
+  auto generic_id = context.generic_instances().Get(specific_id).generic_id;
+  auto eval_block_id = context.generics().Get(generic_id).GetEvalBlock(region);
+  auto eval_block = context.inst_blocks().Get(eval_block_id);
+
+  llvm::SmallVector<SemIR::InstId> result;
+  result.resize(eval_block.size(), SemIR::InstId::Invalid);
+
+  EvalContext eval_context = {
+      .context = context,
+      .specific_id = specific_id,
+      .specific_eval_info =
+          SpecificEvalInfo{
+              .region = region,
+              .values = result,
+          },
+  };
+
+  for (auto [i, inst_id] : llvm::enumerate(eval_block)) {
+    auto const_id = TryEvalInstInContext(eval_context, inst_id,
+                                         context.insts().Get(inst_id));
+    result[i] = context.constant_values().GetInstId(const_id);
+
+    // TODO: If this becomes possible through monomorphization failure, produce
+    // a diagnostic and put `SemIR::InstId::BuiltinError` in the table entry.
+    CARBON_CHECK(result[i].is_valid());
+  }
+
+  return context.inst_blocks().Add(result);
+}
+
 }  // namespace Carbon::Check

+ 8 - 2
toolchain/check/eval.h

@@ -14,11 +14,17 @@ namespace Carbon::Check {
 // Determines the phase of the instruction `inst`, and returns its constant
 // value if it has constant phase. If it has runtime phase, returns
 // `SemIR::ConstantId::NotConstant`.
-//
-// TODO: Support symbolic phase.
 auto TryEvalInst(Context& context, SemIR::InstId inst_id, SemIR::Inst inst)
     -> SemIR::ConstantId;
 
+// Evaluates the eval block for a region of a specific. Produces a block
+// containing the evaluated constant values of the instructions in the eval
+// block.
+auto TryEvalBlockForSpecific(Context& context,
+                             SemIR::GenericInstanceId specific_id,
+                             SemIR::GenericInstIndex::Region region)
+    -> SemIR::InstBlockId;
+
 }  // namespace Carbon::Check
 
 #endif  // CARBON_TOOLCHAIN_CHECK_EVAL_H_

+ 23 - 7
toolchain/check/generic.cpp

@@ -5,6 +5,7 @@
 #include "toolchain/check/generic.h"
 
 #include "common/map.h"
+#include "toolchain/check/eval.h"
 #include "toolchain/check/generic_region_stack.h"
 #include "toolchain/check/subst.h"
 #include "toolchain/sem_ir/ids.h"
@@ -130,8 +131,6 @@ static auto MakeGenericEvalBlock(Context& context, SemIR::GenericId generic_id,
 
   Map<SemIR::InstId, SemIR::InstId> constants_in_generic;
   // TODO: For the definition region, populate constants from the declaration.
-  // TODO: Add `BindSymbolicName` instructions for enclosing generics to the
-  // map.
 
   // The work done in this loop might invalidate iterators into the generic
   // region stack, but shouldn't add new dependent instructions to the current
@@ -194,6 +193,10 @@ auto FinishGenericDecl(Context& context, SemIR::InstId decl_id)
     return SemIR::GenericId::Invalid;
   }
 
+  // Build the new Generic object. Note that we intentionally do not hold a
+  // persistent reference to it throughout this function, because the `generics`
+  // collection can have items added to it by import resolution while we are
+  // building this generic.
   auto bindings_id = context.inst_blocks().Add(all_bindings);
   auto generic_id = context.generics().Add(
       SemIR::Generic{.decl_id = decl_id,
@@ -203,12 +206,10 @@ auto FinishGenericDecl(Context& context, SemIR::InstId decl_id)
   auto decl_block_id = MakeGenericEvalBlock(
       context, generic_id, SemIR::GenericInstIndex::Region::Declaration);
   context.generic_region_stack().Pop();
+  context.generics().Get(generic_id).decl_block_id = decl_block_id;
 
   auto self_instance_id = MakeGenericSelfInstance(context, generic_id);
-
-  auto& generic_info = context.generics().Get(generic_id);
-  generic_info.decl_block_id = decl_block_id;
-  generic_info.self_instance_id = self_instance_id;
+  context.generics().Get(generic_id).self_instance_id = self_instance_id;
   return generic_id;
 }
 
@@ -238,7 +239,22 @@ auto MakeGenericInstance(Context& context, SemIR::GenericId generic_id,
                          SemIR::InstBlockId args_id)
     -> SemIR::GenericInstanceId {
   auto instance_id = context.generic_instances().GetOrAdd(generic_id, args_id);
-  // TODO: Perform substitution into the generic declaration if needed.
+
+  // TODO: Remove this once we import generics properly.
+  if (!generic_id.is_valid()) {
+    return instance_id;
+  }
+
+  // If this is the first time we've formed this instance, evaluate its decl
+  // block to form information about the instance.
+  if (!context.generic_instances().Get(instance_id).decl_block_id.is_valid()) {
+    auto decl_block_id = TryEvalBlockForSpecific(
+        context, instance_id, SemIR::GenericInstIndex::Region::Declaration);
+    // Note that TryEvalBlockForSpecific may reallocate the list of generic
+    // instances, so re-lookup the instance here.
+    context.generic_instances().Get(instance_id).decl_block_id = decl_block_id;
+  }
+
   return instance_id;
 }
 

+ 5 - 0
toolchain/check/testdata/array/generic_empty.carbon

@@ -54,3 +54,8 @@ fn G(T:! type) {
 // CHECK:STDOUT:   return
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
+// CHECK:STDOUT: specific file.%G.decl(constants.%T) {
+// CHECK:STDOUT: declaration:
+// CHECK:STDOUT:   @G.%T => constants.%T
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 11 - 8
toolchain/check/testdata/basics/no_prelude/raw_ir.carbon

@@ -28,7 +28,7 @@ fn Foo[T:! type](n: T) -> (T, ()) {
 // CHECK:STDOUT:     bind_name0:      {name: name1, parent_scope: name_scope<invalid>, index: comp_time_bind0}
 // CHECK:STDOUT:     bind_name1:      {name: name2, parent_scope: name_scope<invalid>, index: comp_time_bind<invalid>}
 // CHECK:STDOUT:   functions:
-// CHECK:STDOUT:     function0:       {name: name0, parent_scope: name_scope0, param_refs: block5, return_storage: inst+15, return_slot: present, body: [block11]}
+// CHECK:STDOUT:     function0:       {name: name0, parent_scope: name_scope0, param_refs: block5, return_storage: inst+15, return_slot: present, body: [block12]}
 // CHECK:STDOUT:   classes:         {}
 // CHECK:STDOUT:   generics:
 // CHECK:STDOUT:     generic0:        {decl: inst+16, bindings: block8}
@@ -75,14 +75,14 @@ fn Foo[T:! type](n: T) -> (T, ()) {
 // CHECK:STDOUT:     'inst+19':         {kind: PointerType, arg0: type4, type: typeTypeType}
 // CHECK:STDOUT:     'inst+20':         {kind: NameRef, arg0: name2, arg1: inst+6, type: type1}
 // CHECK:STDOUT:     'inst+21':         {kind: TupleLiteral, arg0: empty, type: type2}
-// CHECK:STDOUT:     'inst+22':         {kind: TupleLiteral, arg0: block12, type: type4}
+// CHECK:STDOUT:     'inst+22':         {kind: TupleLiteral, arg0: block13, type: type4}
 // CHECK:STDOUT:     'inst+23':         {kind: TupleAccess, arg0: inst+15, arg1: element0, type: type1}
 // CHECK:STDOUT:     'inst+24':         {kind: InitializeFrom, arg0: inst+20, arg1: inst+23, type: type1}
 // CHECK:STDOUT:     'inst+25':         {kind: TupleAccess, arg0: inst+15, arg1: element1, type: type2}
 // CHECK:STDOUT:     'inst+26':         {kind: TupleInit, arg0: empty, arg1: inst+25, type: type2}
-// CHECK:STDOUT:     'inst+27':         {kind: TupleValue, arg0: block14, type: type2}
+// CHECK:STDOUT:     'inst+27':         {kind: TupleValue, arg0: block15, type: type2}
 // CHECK:STDOUT:     'inst+28':         {kind: Converted, arg0: inst+21, arg1: inst+26, type: type2}
-// CHECK:STDOUT:     'inst+29':         {kind: TupleInit, arg0: block13, arg1: inst+15, type: type4}
+// CHECK:STDOUT:     'inst+29':         {kind: TupleInit, arg0: block14, arg1: inst+15, type: type4}
 // CHECK:STDOUT:     'inst+30':         {kind: Converted, arg0: inst+22, arg1: inst+29, type: type4}
 // CHECK:STDOUT:     'inst+31':         {kind: ReturnExpr, arg0: inst+30, arg1: inst+15}
 // CHECK:STDOUT:   constant_values:
@@ -136,6 +136,9 @@ fn Foo[T:! type](n: T) -> (T, ()) {
 // CHECK:STDOUT:     block10:
 // CHECK:STDOUT:       0:               inst+3
 // CHECK:STDOUT:     block11:
+// CHECK:STDOUT:       0:               inst+3
+// CHECK:STDOUT:       1:               inst+13
+// CHECK:STDOUT:     block12:
 // CHECK:STDOUT:       0:               inst+20
 // CHECK:STDOUT:       1:               inst+21
 // CHECK:STDOUT:       2:               inst+22
@@ -147,14 +150,14 @@ fn Foo[T:! type](n: T) -> (T, ()) {
 // CHECK:STDOUT:       8:               inst+29
 // CHECK:STDOUT:       9:               inst+30
 // CHECK:STDOUT:       10:              inst+31
-// CHECK:STDOUT:     block12:
+// CHECK:STDOUT:     block13:
 // CHECK:STDOUT:       0:               inst+20
 // CHECK:STDOUT:       1:               inst+21
-// CHECK:STDOUT:     block13:
+// CHECK:STDOUT:     block14:
 // CHECK:STDOUT:       0:               inst+24
 // CHECK:STDOUT:       1:               inst+28
-// CHECK:STDOUT:     block14:         {}
-// CHECK:STDOUT:     block15:
+// CHECK:STDOUT:     block15:         {}
+// CHECK:STDOUT:     block16:
 // CHECK:STDOUT:       0:               inst+0
 // CHECK:STDOUT:       1:               inst+16
 // CHECK:STDOUT: ...

+ 6 - 0
toolchain/check/testdata/builtins/int/make_type_signed.carbon

@@ -217,6 +217,12 @@ var m: Int(1000000000);
 // CHECK:STDOUT:   return %x.ref
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
+// CHECK:STDOUT: specific file.%Symbolic.decl(constants.%N) {
+// CHECK:STDOUT: declaration:
+// CHECK:STDOUT:   @Symbolic.%N => constants.%N
+// CHECK:STDOUT:   file.%int.make_type_signed.loc14_28 => constants.%.6
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
 // CHECK:STDOUT: --- fail_zero_size.carbon
 // CHECK:STDOUT:
 // CHECK:STDOUT: constants {

+ 6 - 0
toolchain/check/testdata/builtins/int/make_type_unsigned.carbon

@@ -217,6 +217,12 @@ var m: UInt(1000000000);
 // CHECK:STDOUT:   return %x.ref
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
+// CHECK:STDOUT: specific file.%Symbolic.decl(constants.%N) {
+// CHECK:STDOUT: declaration:
+// CHECK:STDOUT:   @Symbolic.%N => constants.%N
+// CHECK:STDOUT:   file.%int.make_type_unsigned.loc14_29 => constants.%.6
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
 // CHECK:STDOUT: --- fail_zero_size.carbon
 // CHECK:STDOUT:
 // CHECK:STDOUT: constants {

+ 18 - 2
toolchain/check/testdata/class/fail_generic_method.carbon

@@ -38,7 +38,7 @@ fn Class(N:! i32).F[self: Self](n: T) {}
 // 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, (%T) [symbolic]
+// CHECK:STDOUT:   %Class.2: type = class_type @Class, file.%Class.decl(%T) [symbolic]
 // CHECK:STDOUT:   %.2: type = unbound_element_type %Class.2, %T [symbolic]
 // CHECK:STDOUT:   %F.type: type = fn_type @F [template]
 // CHECK:STDOUT:   %F: %F.type = struct_value () [template]
@@ -85,7 +85,7 @@ fn Class(N:! i32).F[self: Self](n: T) {}
 // CHECK:STDOUT:   %T.ref.loc12: type = name_ref T, file.%T.loc11_13.2 [symbolic = constants.%T]
 // CHECK:STDOUT:   %.loc12: %.2 = field_decl a, element0 [template]
 // CHECK:STDOUT:   %F.decl: %F.type = fn_decl @F [template = constants.%F] {
-// CHECK:STDOUT:     %.loc13: type = specific_constant constants.%Class.2, (constants.%T) [symbolic = %.loc13 (constants.%Class.2)]
+// CHECK:STDOUT:     %.loc13: type = specific_constant constants.%Class.2, file.%Class.decl(constants.%T) [symbolic = %.loc13 (constants.%Class.2)]
 // CHECK:STDOUT:     %Self.ref: type = name_ref Self, %.loc13 [symbolic = %.loc13 (constants.%Class.2)]
 // CHECK:STDOUT:     %self.loc13_8.1: @Class.%.loc13 (%Class.2) = param self
 // CHECK:STDOUT:     %self.loc13_8.2: @Class.%.loc13 (%Class.2) = bind_name self, %self.loc13_8.1
@@ -111,3 +111,19 @@ fn Class(N:! i32).F[self: Self](n: T) {}
 // CHECK:STDOUT:   return
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
+// CHECK:STDOUT: specific file.%Class.decl(constants.%T) {
+// CHECK:STDOUT: declaration:
+// CHECK:STDOUT:   file.%T.loc11_13.2 => constants.%T
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @Class.%F.decl(constants.%T) {
+// CHECK:STDOUT: declaration:
+// CHECK:STDOUT:   @Class.%.loc13 => constants.%Class.2
+// CHECK:STDOUT:   @Class.%T.ref.loc13 => constants.%T
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific file.%.decl(constants.%N) {
+// CHECK:STDOUT: declaration:
+// CHECK:STDOUT:   file.%N.loc32_10.2 => constants.%N
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 22 - 3
toolchain/check/testdata/class/generic/basic.carbon

@@ -29,7 +29,7 @@ class Class(T:! 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, (%T) [symbolic]
+// CHECK:STDOUT:   %Class.2: type = class_type @Class, file.%Class.decl(%T) [symbolic]
 // CHECK:STDOUT:   %.2: type = ptr_type %Class.2 [symbolic]
 // CHECK:STDOUT:   %.3: type = ptr_type %T [symbolic]
 // CHECK:STDOUT:   %GetAddr.type: type = fn_type @GetAddr [template]
@@ -57,7 +57,7 @@ class Class(T:! type) {
 // CHECK:STDOUT: class @Class
 // CHECK:STDOUT:     generic [file.%T.loc11_13.2: type] {
 // CHECK:STDOUT:   %GetAddr.decl: %GetAddr.type = fn_decl @GetAddr [template = constants.%GetAddr] {
-// CHECK:STDOUT:     %.loc12_25: type = specific_constant constants.%Class.2, (constants.%T) [symbolic = %.loc12_25 (constants.%Class.2)]
+// CHECK:STDOUT:     %.loc12_25: type = specific_constant constants.%Class.2, file.%Class.decl(constants.%T) [symbolic = %.loc12_25 (constants.%Class.2)]
 // CHECK:STDOUT:     %Self.ref.loc12: type = name_ref Self, %.loc12_25 [symbolic = %.loc12_25 (constants.%Class.2)]
 // CHECK:STDOUT:     %.loc12_29: type = ptr_type %Class.2 [symbolic = %.loc12_29 (constants.%.2)]
 // CHECK:STDOUT:     %self.loc12_19.1: @Class.%.loc12_29 (%.2) = param self
@@ -68,7 +68,7 @@ class Class(T:! type) {
 // CHECK:STDOUT:     %return.var.loc12: ref %.3 = var <return slot>
 // CHECK:STDOUT:   }
 // CHECK:STDOUT:   %GetValue.decl: %GetValue.type = fn_decl @GetValue [template = constants.%GetValue] {
-// CHECK:STDOUT:     %.loc17: type = specific_constant constants.%Class.2, (constants.%T) [symbolic = %.loc17 (constants.%Class.2)]
+// CHECK:STDOUT:     %.loc17: type = specific_constant constants.%Class.2, file.%Class.decl(constants.%T) [symbolic = %.loc17 (constants.%Class.2)]
 // CHECK:STDOUT:     %Self.ref.loc17: type = name_ref Self, %.loc17 [symbolic = %.loc17 (constants.%Class.2)]
 // CHECK:STDOUT:     %self.loc17_15.1: @Class.%.loc17 (%Class.2) = param self
 // CHECK:STDOUT:     %self.loc17_15.2: @Class.%.loc17 (%Class.2) = bind_name self, %self.loc17_15.1
@@ -106,3 +106,22 @@ class Class(T:! type) {
 // CHECK:STDOUT:   return %.loc18_16.2
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
+// CHECK:STDOUT: specific file.%Class.decl(constants.%T) {
+// CHECK:STDOUT: declaration:
+// CHECK:STDOUT:   file.%T.loc11_13.2 => constants.%T
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @Class.%GetAddr.decl(constants.%T) {
+// CHECK:STDOUT: declaration:
+// CHECK:STDOUT:   @Class.%.loc12_25 => constants.%Class.2
+// CHECK:STDOUT:   @Class.%.loc12_29 => constants.%.2
+// CHECK:STDOUT:   @Class.%T.ref.loc12 => constants.%T
+// CHECK:STDOUT:   @Class.%.loc12_38 => constants.%.3
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @Class.%GetValue.decl(constants.%T) {
+// CHECK:STDOUT: declaration:
+// CHECK:STDOUT:   @Class.%.loc17 => constants.%Class.2
+// CHECK:STDOUT:   @Class.%T.ref.loc17 => constants.%T
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 42 - 6
toolchain/check/testdata/class/generic/call.carbon

@@ -73,14 +73,14 @@ var a: Class(5, i32*);
 // CHECK:STDOUT:   %N: i32 = bind_symbolic_name N 1 [symbolic]
 // CHECK:STDOUT:   %Class.type: type = generic_class_type @Class [template]
 // CHECK:STDOUT:   %Class.1: %Class.type = struct_value () [template]
-// CHECK:STDOUT:   %Class.2: type = class_type @Class, (%T, %N) [symbolic]
+// CHECK:STDOUT:   %Class.2: type = class_type @Class, file.%Class.decl(%T, %N) [symbolic]
 // CHECK:STDOUT:   %.2: type = struct_type {} [template]
 // CHECK:STDOUT:   %.3: type = ptr_type i32 [template]
 // CHECK:STDOUT:   %.4: i32 = int_literal 5 [template]
-// CHECK:STDOUT:   %Class.3: type = class_type @Class, (%.3, %.4) [template]
+// CHECK:STDOUT:   %Class.3: type = class_type @Class, file.%Class.decl(%.3, %.4) [template]
 // CHECK:STDOUT:   %.5: type = ptr_type %.2 [template]
 // CHECK:STDOUT:   %.6: i32 = int_literal 0 [template]
-// CHECK:STDOUT:   %Class.4: type = class_type @Class, (%.1, %.6) [template]
+// CHECK:STDOUT:   %Class.4: type = class_type @Class, file.%Class.decl(%.1, %.6) [template]
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: imports {
@@ -136,6 +136,24 @@ var a: Class(5, i32*);
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @Int32() -> type = "int.make_type_32";
 // CHECK:STDOUT:
+// CHECK:STDOUT: specific file.%Class.decl(constants.%T, constants.%N) {
+// CHECK:STDOUT: declaration:
+// CHECK:STDOUT:   file.%T.loc4_13.2 => constants.%T
+// CHECK:STDOUT:   file.%N.loc4_23.2 => constants.%N
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific file.%Class.decl(constants.%.3, constants.%.4) {
+// CHECK:STDOUT: declaration:
+// CHECK:STDOUT:   file.%T.loc4_13.2 => constants.%.3
+// CHECK:STDOUT:   file.%N.loc4_23.2 => constants.%.4
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific file.%Class.decl(constants.%.1, constants.%.6) {
+// CHECK:STDOUT: declaration:
+// CHECK:STDOUT:   file.%T.loc4_13.2 => constants.%.1
+// CHECK:STDOUT:   file.%N.loc4_23.2 => constants.%.6
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
 // CHECK:STDOUT: --- fail_too_few.carbon
 // CHECK:STDOUT:
 // CHECK:STDOUT: constants {
@@ -146,7 +164,7 @@ var a: Class(5, i32*);
 // CHECK:STDOUT:   %N: i32 = bind_symbolic_name N 1 [symbolic]
 // CHECK:STDOUT:   %Class.type: type = generic_class_type @Class [template]
 // CHECK:STDOUT:   %Class.1: %Class.type = struct_value () [template]
-// CHECK:STDOUT:   %Class.2: type = class_type @Class, (%T, %N) [symbolic]
+// CHECK:STDOUT:   %Class.2: type = class_type @Class, file.%Class.decl(%T, %N) [symbolic]
 // CHECK:STDOUT:   %.2: type = struct_type {} [template]
 // CHECK:STDOUT:   %.3: type = ptr_type i32 [template]
 // CHECK:STDOUT: }
@@ -193,6 +211,12 @@ var a: Class(5, i32*);
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @Int32() -> type = "int.make_type_32";
 // CHECK:STDOUT:
+// CHECK:STDOUT: specific file.%Class.decl(constants.%T, constants.%N) {
+// CHECK:STDOUT: declaration:
+// CHECK:STDOUT:   file.%T.loc4_13.2 => constants.%T
+// CHECK:STDOUT:   file.%N.loc4_23.2 => constants.%N
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
 // CHECK:STDOUT: --- fail_too_many.carbon
 // CHECK:STDOUT:
 // CHECK:STDOUT: constants {
@@ -203,7 +227,7 @@ var a: Class(5, i32*);
 // CHECK:STDOUT:   %N: i32 = bind_symbolic_name N 1 [symbolic]
 // CHECK:STDOUT:   %Class.type: type = generic_class_type @Class [template]
 // CHECK:STDOUT:   %Class.1: %Class.type = struct_value () [template]
-// CHECK:STDOUT:   %Class.2: type = class_type @Class, (%T, %N) [symbolic]
+// CHECK:STDOUT:   %Class.2: type = class_type @Class, file.%Class.decl(%T, %N) [symbolic]
 // CHECK:STDOUT:   %.2: type = struct_type {} [template]
 // CHECK:STDOUT:   %.3: type = ptr_type i32 [template]
 // CHECK:STDOUT:   %.4: i32 = int_literal 1 [template]
@@ -254,6 +278,12 @@ var a: Class(5, i32*);
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @Int32() -> type = "int.make_type_32";
 // CHECK:STDOUT:
+// CHECK:STDOUT: specific file.%Class.decl(constants.%T, constants.%N) {
+// CHECK:STDOUT: declaration:
+// CHECK:STDOUT:   file.%T.loc4_13.2 => constants.%T
+// CHECK:STDOUT:   file.%N.loc4_23.2 => constants.%N
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
 // CHECK:STDOUT: --- fail_no_conversion.carbon
 // CHECK:STDOUT:
 // CHECK:STDOUT: constants {
@@ -264,7 +294,7 @@ var a: Class(5, i32*);
 // CHECK:STDOUT:   %N: i32 = bind_symbolic_name N 1 [symbolic]
 // CHECK:STDOUT:   %Class.type: type = generic_class_type @Class [template]
 // CHECK:STDOUT:   %Class.1: %Class.type = struct_value () [template]
-// CHECK:STDOUT:   %Class.2: type = class_type @Class, (%T, %N) [symbolic]
+// CHECK:STDOUT:   %Class.2: type = class_type @Class, file.%Class.decl(%T, %N) [symbolic]
 // CHECK:STDOUT:   %.2: type = struct_type {} [template]
 // CHECK:STDOUT:   %.3: i32 = int_literal 5 [template]
 // CHECK:STDOUT:   %.4: type = ptr_type i32 [template]
@@ -313,3 +343,9 @@ var a: Class(5, i32*);
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @Int32() -> type = "int.make_type_32";
 // CHECK:STDOUT:
+// CHECK:STDOUT: specific file.%Class.decl(constants.%T, constants.%N) {
+// CHECK:STDOUT: declaration:
+// CHECK:STDOUT:   file.%T.loc4_13.2 => constants.%T
+// CHECK:STDOUT:   file.%N.loc4_23.2 => constants.%N
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 21 - 3
toolchain/check/testdata/class/generic/fail_todo_use.carbon

@@ -43,7 +43,7 @@ fn Run() -> i32 {
 // 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, (%T) [symbolic]
+// CHECK:STDOUT:   %Class.2: type = class_type @Class, file.%Class.decl(%T) [symbolic]
 // CHECK:STDOUT:   %.2: type = ptr_type %Class.2 [symbolic]
 // CHECK:STDOUT:   %.3: type = ptr_type %T [symbolic]
 // CHECK:STDOUT:   %Get.type: type = fn_type @Get [template]
@@ -55,7 +55,7 @@ fn Run() -> i32 {
 // CHECK:STDOUT:   %Int32: %Int32.type = struct_value () [template]
 // CHECK:STDOUT:   %Run.type: type = fn_type @Run [template]
 // CHECK:STDOUT:   %Run: %Run.type = struct_value () [template]
-// CHECK:STDOUT:   %Class.3: type = class_type @Class, (i32) [template]
+// CHECK:STDOUT:   %Class.3: type = class_type @Class, file.%Class.decl(i32) [template]
 // CHECK:STDOUT:   %.7: i32 = int_literal 0 [template]
 // CHECK:STDOUT:   %.8: type = struct_type {.k: i32} [template]
 // CHECK:STDOUT:   %.9: type = ptr_type %Class.3 [template]
@@ -89,7 +89,7 @@ fn Run() -> i32 {
 // CHECK:STDOUT: class @Class
 // CHECK:STDOUT:     generic [file.%T.loc11_13.2: type] {
 // CHECK:STDOUT:   %Get.decl: %Get.type = fn_decl @Get [template = constants.%Get] {
-// CHECK:STDOUT:     %.loc12_21: type = specific_constant constants.%Class.2, (constants.%T) [symbolic = %.loc12_21 (constants.%Class.2)]
+// CHECK:STDOUT:     %.loc12_21: type = specific_constant constants.%Class.2, file.%Class.decl(constants.%T) [symbolic = %.loc12_21 (constants.%Class.2)]
 // CHECK:STDOUT:     %Self.ref: type = name_ref Self, %.loc12_21 [symbolic = %.loc12_21 (constants.%Class.2)]
 // CHECK:STDOUT:     %.loc12_25: type = ptr_type %Class.2 [symbolic = %.loc12_25 (constants.%.2)]
 // CHECK:STDOUT:     %self.loc12_15.1: @Class.%.loc12_25 (%.2) = param self
@@ -143,3 +143,21 @@ fn Run() -> i32 {
 // CHECK:STDOUT:   return <error>
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
+// CHECK:STDOUT: specific file.%Class.decl(constants.%T) {
+// CHECK:STDOUT: declaration:
+// CHECK:STDOUT:   file.%T.loc11_13.2 => constants.%T
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @Class.%Get.decl(constants.%T) {
+// CHECK:STDOUT: declaration:
+// CHECK:STDOUT:   @Class.%.loc12_21 => constants.%Class.2
+// CHECK:STDOUT:   @Class.%.loc12_25 => constants.%.2
+// CHECK:STDOUT:   @Class.%T.ref.loc12 => constants.%T
+// CHECK:STDOUT:   @Class.%.loc12_34 => constants.%.3
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific file.%Class.decl(i32) {
+// CHECK:STDOUT: declaration:
+// CHECK:STDOUT:   file.%T.loc11_13.2 => i32
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 36 - 4
toolchain/check/testdata/class/generic/field.carbon

@@ -58,7 +58,7 @@ fn H(U:! type, c: Class(U)) -> U {
 // 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, (%T) [symbolic]
+// CHECK:STDOUT:   %Class.2: type = class_type @Class, file.%Class.decl(%T) [symbolic]
 // CHECK:STDOUT:   %.2: type = unbound_element_type %Class.2, %T [symbolic]
 // CHECK:STDOUT:   %.3: type = struct_type {.x: %T} [symbolic]
 // CHECK:STDOUT:   %G.type: type = fn_type @G [template]
@@ -113,6 +113,17 @@ fn H(U:! type, c: Class(U)) -> U {
 // CHECK:STDOUT:   return %.loc9_11.2
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
+// CHECK:STDOUT: specific file.%Class.decl(constants.%T) {
+// CHECK:STDOUT: declaration:
+// CHECK:STDOUT:   file.%T.loc2_13.2 => constants.%T
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific file.%G.decl(constants.%T) {
+// CHECK:STDOUT: declaration:
+// CHECK:STDOUT:   @G.%T => constants.%T
+// CHECK:STDOUT:   file.%.loc8_24 => constants.%Class.2
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
 // CHECK:STDOUT: --- fail_todo_field.carbon
 // CHECK:STDOUT:
 // CHECK:STDOUT: constants {
@@ -120,17 +131,17 @@ fn H(U:! type, c: Class(U)) -> U {
 // 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, (%T) [symbolic]
+// CHECK:STDOUT:   %Class.2: type = class_type @Class, file.%Class.decl(%T) [symbolic]
 // CHECK:STDOUT:   %.2: type = unbound_element_type %Class.2, %T [symbolic]
 // CHECK:STDOUT:   %.3: type = struct_type {.x: %T} [symbolic]
 // CHECK:STDOUT:   %Int32.type: type = fn_type @Int32 [template]
 // CHECK:STDOUT:   %Int32: %Int32.type = struct_value () [template]
-// CHECK:STDOUT:   %Class.3: type = class_type @Class, (i32) [template]
+// CHECK:STDOUT:   %Class.3: type = class_type @Class, file.%Class.decl(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 %.3 [symbolic]
 // CHECK:STDOUT:   %U: type = bind_symbolic_name U 0 [symbolic]
-// CHECK:STDOUT:   %Class.4: type = class_type @Class, (%U) [symbolic]
+// CHECK:STDOUT:   %Class.4: type = class_type @Class, file.%Class.decl(%U) [symbolic]
 // CHECK:STDOUT:   %H.type: type = fn_type @H [template]
 // CHECK:STDOUT:   %H: %H.type = struct_value () [template]
 // CHECK:STDOUT: }
@@ -212,3 +223,24 @@ fn H(U:! type, c: Class(U)) -> U {
 // CHECK:STDOUT:   return <error>
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
+// CHECK:STDOUT: specific file.%Class.decl(constants.%T) {
+// CHECK:STDOUT: declaration:
+// CHECK:STDOUT:   file.%T.loc2_13.2 => constants.%T
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific file.%Class.decl(i32) {
+// CHECK:STDOUT: declaration:
+// CHECK:STDOUT:   file.%T.loc2_13.2 => i32
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific file.%Class.decl(constants.%U) {
+// CHECK:STDOUT: declaration:
+// CHECK:STDOUT:   file.%T.loc2_13.2 => constants.%U
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific file.%H.decl(constants.%U) {
+// CHECK:STDOUT: declaration:
+// CHECK:STDOUT:   @H.%U => constants.%U
+// CHECK:STDOUT:   file.%.loc18_24 => constants.%Class.4
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 59 - 15
toolchain/check/testdata/class/generic/import.carbon

@@ -95,10 +95,10 @@ 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, (%T) [symbolic]
+// CHECK:STDOUT:   %Class.2: type = class_type @Class, file.%Class.decl(%T) [symbolic]
 // CHECK:STDOUT:   %CompleteClass.type: type = generic_class_type @CompleteClass [template]
 // CHECK:STDOUT:   %CompleteClass.1: %CompleteClass.type = struct_value () [template]
-// CHECK:STDOUT:   %CompleteClass.2: type = class_type @CompleteClass, (%T) [symbolic]
+// CHECK:STDOUT:   %CompleteClass.2: type = class_type @CompleteClass, file.%CompleteClass.decl(%T) [symbolic]
 // CHECK:STDOUT:   %Int32.type: type = fn_type @Int32 [template]
 // CHECK:STDOUT:   %Int32: %Int32.type = struct_value () [template]
 // CHECK:STDOUT:   %.2: type = unbound_element_type %CompleteClass.2, i32 [symbolic]
@@ -106,7 +106,7 @@ class Class(U:! type) {
 // CHECK:STDOUT:   %F.1: %F.type.1 = struct_value () [template]
 // CHECK:STDOUT:   %.3: type = struct_type {.n: i32} [template]
 // CHECK:STDOUT:   %.4: i32 = int_literal 0 [template]
-// CHECK:STDOUT:   %CompleteClass.3: type = class_type @CompleteClass, (i32) [template]
+// CHECK:STDOUT:   %CompleteClass.3: type = class_type @CompleteClass, file.%CompleteClass.decl(i32) [template]
 // CHECK:STDOUT:   %F.type.2: type = fn_type @F.2 [template]
 // CHECK:STDOUT:   %F.2: %F.type.2 = struct_value () [template]
 // CHECK:STDOUT: }
@@ -179,6 +179,25 @@ class Class(U:! type) {
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @F.2() -> %CompleteClass.3;
 // CHECK:STDOUT:
+// CHECK:STDOUT: specific file.%Class.decl(constants.%T) {
+// CHECK:STDOUT: declaration:
+// CHECK:STDOUT:   file.%T.loc4_13.2 => constants.%T
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific file.%CompleteClass.decl(constants.%T) {
+// CHECK:STDOUT: declaration:
+// CHECK:STDOUT:   file.%T.loc6_21.2 => constants.%T
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @CompleteClass.%F.decl(constants.%T) {
+// CHECK:STDOUT: declaration:
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific file.%CompleteClass.decl(i32) {
+// CHECK:STDOUT: declaration:
+// CHECK:STDOUT:   file.%T.loc6_21.2 => i32
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
 // CHECK:STDOUT: --- foo.impl.carbon
 // CHECK:STDOUT:
 // CHECK:STDOUT: constants {
@@ -186,16 +205,16 @@ 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, (%T) [symbolic]
+// CHECK:STDOUT:   %Class.2: type = class_type @Class, <invalid>(%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, (%T) [symbolic]
+// CHECK:STDOUT:   %CompleteClass.2: type = class_type @CompleteClass, <invalid>(%T) [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, (i32) [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]
@@ -269,6 +288,10 @@ class Class(U:! type) {
 // CHECK:STDOUT:   return %.loc9_18 to %return
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
+// CHECK:STDOUT: specific <invalid>(constants.%T);
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific <invalid>(i32);
+// CHECK:STDOUT:
 // CHECK:STDOUT: --- use_foo.carbon
 // CHECK:STDOUT:
 // CHECK:STDOUT: constants {
@@ -281,8 +304,8 @@ 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, <unexpected instref inst+28> [symbolic]
-// CHECK:STDOUT:   %CompleteClass.2: type = class_type @CompleteClass, (%T) [symbolic]
-// CHECK:STDOUT:   %CompleteClass.3: type = class_type @CompleteClass, (i32) [template]
+// 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.1: type = fn_type @F.1 [template]
 // CHECK:STDOUT:   %F.1: %F.type.1 = struct_value () [template]
@@ -356,6 +379,10 @@ class Class(U:! type) {
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @F.2() -> i32;
 // CHECK:STDOUT:
+// CHECK:STDOUT: specific <invalid>(constants.%T);
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific <invalid>(i32);
+// CHECK:STDOUT:
 // CHECK:STDOUT: --- fail_todo_use_foo.carbon
 // CHECK:STDOUT:
 // CHECK:STDOUT: constants {
@@ -368,8 +395,8 @@ 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, <unexpected instref inst+28> [symbolic]
-// CHECK:STDOUT:   %CompleteClass.2: type = class_type @CompleteClass, (%T) [symbolic]
-// CHECK:STDOUT:   %CompleteClass.3: type = class_type @CompleteClass, (i32) [template]
+// 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]
@@ -438,6 +465,10 @@ class Class(U:! type) {
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @F() -> %CompleteClass.3;
 // CHECK:STDOUT:
+// CHECK:STDOUT: specific <invalid>(constants.%T);
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific <invalid>(i32);
+// CHECK:STDOUT:
 // CHECK:STDOUT: --- fail_generic_arg_mismatch.carbon
 // CHECK:STDOUT:
 // CHECK:STDOUT: constants {
@@ -448,13 +479,13 @@ 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, <unexpected instref inst+19> [symbolic]
-// CHECK:STDOUT:   %CompleteClass.2: type = class_type @CompleteClass, (%T) [symbolic]
+// CHECK:STDOUT:   %CompleteClass.2: type = class_type @CompleteClass, <invalid>(%T) [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, (%.3) [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, (i32) [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: }
@@ -513,6 +544,12 @@ class Class(U:! type) {
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @F() -> %CompleteClass.4;
 // CHECK:STDOUT:
+// CHECK:STDOUT: specific <invalid>(constants.%T);
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific <invalid>(constants.%.3);
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific <invalid>(i32);
+// CHECK:STDOUT:
 // CHECK:STDOUT: --- fail_bad_foo.impl.carbon
 // CHECK:STDOUT:
 // CHECK:STDOUT: constants {
@@ -521,10 +558,10 @@ 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, <unexpected instref inst+17> [symbolic]
-// CHECK:STDOUT:   %Class.2: type = class_type @Class, (%T) [symbolic]
+// CHECK:STDOUT:   %Class.2: type = class_type @Class, <invalid>(%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, (%U) [symbolic]
+// CHECK:STDOUT:   %.3: type = class_type @.1, file.%.decl(%U) [symbolic]
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: imports {
@@ -562,3 +599,10 @@ class Class(U:! type) {
 // CHECK:STDOUT:   .x = %.loc13
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
+// CHECK:STDOUT: specific <invalid>(constants.%T);
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific file.%.decl(constants.%U) {
+// CHECK:STDOUT: declaration:
+// CHECK:STDOUT:   file.%U.loc9_13.2 => constants.%U
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 11 - 1
toolchain/check/testdata/class/generic/member_inline.carbon

@@ -21,7 +21,7 @@ class Class(T:! 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, (%T) [symbolic]
+// CHECK:STDOUT:   %Class.2: type = class_type @Class, file.%Class.decl(%T) [symbolic]
 // CHECK:STDOUT:   %F.type: type = fn_type @F [template]
 // CHECK:STDOUT:   %F: %F.type = struct_value () [template]
 // CHECK:STDOUT:   %.2: type = struct_type {} [template]
@@ -62,3 +62,13 @@ class Class(T:! type) {
 // CHECK:STDOUT:   return %n.ref
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
+// CHECK:STDOUT: specific file.%Class.decl(constants.%T) {
+// CHECK:STDOUT: declaration:
+// CHECK:STDOUT:   file.%T.loc11_13.2 => constants.%T
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @Class.%F.decl(constants.%T) {
+// CHECK:STDOUT: declaration:
+// CHECK:STDOUT:   @Class.%T.ref.loc12_11 => constants.%T
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 78 - 8
toolchain/check/testdata/class/generic/member_out_of_line.carbon

@@ -106,7 +106,7 @@ fn Generic(T:! ()).WrongType() {}
 // 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, (%T) [symbolic]
+// CHECK:STDOUT:   %Class.2: type = class_type @Class, file.%Class.decl(%T) [symbolic]
 // CHECK:STDOUT:   %F.type: type = fn_type @F [template]
 // CHECK:STDOUT:   %F: %F.type = struct_value () [template]
 // CHECK:STDOUT:   %.2: type = struct_type {} [template]
@@ -156,6 +156,16 @@ fn Generic(T:! ()).WrongType() {}
 // CHECK:STDOUT:   return %n.ref
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
+// CHECK:STDOUT: specific file.%Class.decl(constants.%T) {
+// CHECK:STDOUT: declaration:
+// CHECK:STDOUT:   file.%T.loc4_13.2 => constants.%T
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @Class.%F.decl(constants.%T) {
+// CHECK:STDOUT: declaration:
+// CHECK:STDOUT:   @Class.%T.ref.loc5_11 => constants.%T
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
 // CHECK:STDOUT: --- nested.carbon
 // CHECK:STDOUT:
 // CHECK:STDOUT: constants {
@@ -163,11 +173,11 @@ fn Generic(T:! ()).WrongType() {}
 // CHECK:STDOUT:   %A.type: type = generic_class_type @A [template]
 // CHECK:STDOUT:   %.1: type = tuple_type () [template]
 // CHECK:STDOUT:   %A.1: %A.type = struct_value () [template]
-// CHECK:STDOUT:   %A.2: type = class_type @A, (%T) [symbolic]
+// CHECK:STDOUT:   %A.2: type = class_type @A, file.%A.decl(%T) [symbolic]
 // CHECK:STDOUT:   %N: %T = bind_symbolic_name N 1 [symbolic]
 // CHECK:STDOUT:   %B.type: type = generic_class_type @B [template]
 // CHECK:STDOUT:   %B.1: %B.type = struct_value () [template]
-// CHECK:STDOUT:   %B.2: type = class_type @B, (%T, %N) [symbolic]
+// CHECK:STDOUT:   %B.2: type = class_type @B, @A.%B.decl(%T, %N) [symbolic]
 // CHECK:STDOUT:   %F.type: type = fn_type @F [template]
 // CHECK:STDOUT:   %F: %F.type = struct_value () [template]
 // CHECK:STDOUT:   %.2: type = struct_type {} [template]
@@ -191,7 +201,7 @@ fn Generic(T:! ()).WrongType() {}
 // CHECK:STDOUT:     %T.ref.loc10_22: type = name_ref T, %T.loc10_6.2 [symbolic = constants.%T]
 // CHECK:STDOUT:     %N.loc10_18.1: %T = param N
 // CHECK:STDOUT:     %N.loc10_18.2: %T = bind_symbolic_name N 1, %N.loc10_18.1 [symbolic = constants.%N]
-// CHECK:STDOUT:     %.loc10: type = specific_constant constants.%B.2, (constants.%T, constants.%N) [symbolic = constants.%B.2]
+// CHECK:STDOUT:     %.loc10: type = specific_constant constants.%B.2, @A.%B.decl(constants.%T, constants.%N) [symbolic = constants.%B.2]
 // CHECK:STDOUT:     %Self.ref: type = name_ref Self, %.loc10 [symbolic = constants.%B.2]
 // CHECK:STDOUT:     %self.loc10_27.1: %B.2 = param self
 // CHECK:STDOUT:     @F.%self: %B.2 = bind_name self, %self.loc10_27.1
@@ -217,7 +227,7 @@ fn Generic(T:! ()).WrongType() {}
 // CHECK:STDOUT: class @B
 // CHECK:STDOUT:     generic [file.%T.loc4_9.2: type, @A.%N.loc5_11.2: @A.%T.ref (%T)] {
 // CHECK:STDOUT:   %F.decl: %F.type = fn_decl @F [template = constants.%F] {
-// CHECK:STDOUT:     %.loc6: type = specific_constant constants.%B.2, (constants.%T, constants.%N) [symbolic = %.loc6 (constants.%B.2)]
+// CHECK:STDOUT:     %.loc6: type = specific_constant constants.%B.2, @A.%B.decl(constants.%T, constants.%N) [symbolic = %.loc6 (constants.%B.2)]
 // CHECK:STDOUT:     %Self.ref: type = name_ref Self, %.loc6 [symbolic = %.loc6 (constants.%B.2)]
 // CHECK:STDOUT:     %self.loc6_10.1: @B.%.loc6 (%B.2) = param self
 // CHECK:STDOUT:     %self.loc6_10.2: @B.%.loc6 (%B.2) = bind_name self, %self.loc6_10.1
@@ -237,6 +247,23 @@ fn Generic(T:! ()).WrongType() {}
 // CHECK:STDOUT:   return
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
+// CHECK:STDOUT: specific file.%A.decl(constants.%T) {
+// CHECK:STDOUT: declaration:
+// CHECK:STDOUT:   file.%T.loc4_9.2 => constants.%T
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @A.%B.decl(constants.%T, constants.%N) {
+// CHECK:STDOUT: declaration:
+// CHECK:STDOUT:   @A.%T.ref => constants.%T
+// CHECK:STDOUT:   @A.%N.loc5_11.2 => constants.%N
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @B.%F.decl(constants.%T, constants.%N) {
+// CHECK:STDOUT: declaration:
+// CHECK:STDOUT:   @B.%.loc6 => constants.%B.2
+// CHECK:STDOUT:   @B.%T.ref => constants.%T
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
 // CHECK:STDOUT: --- fail_mismatched_not_generic_vs_generic.carbon
 // CHECK:STDOUT:
 // CHECK:STDOUT: constants {
@@ -280,6 +307,11 @@ fn Generic(T:! ()).WrongType() {}
 // CHECK:STDOUT:   return
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
+// CHECK:STDOUT: specific file.%.decl(constants.%T) {
+// CHECK:STDOUT: declaration:
+// CHECK:STDOUT:   file.%T.loc15_15.2 => constants.%T
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
 // CHECK:STDOUT: --- fail_mismatched_too_few_args.carbon
 // CHECK:STDOUT:
 // CHECK:STDOUT: constants {
@@ -287,7 +319,7 @@ fn Generic(T:! ()).WrongType() {}
 // CHECK:STDOUT:   %Generic.type: type = generic_class_type @Generic [template]
 // CHECK:STDOUT:   %.1: type = tuple_type () [template]
 // CHECK:STDOUT:   %Generic.1: %Generic.type = struct_value () [template]
-// CHECK:STDOUT:   %Generic.2: type = class_type @Generic, (%T) [symbolic]
+// CHECK:STDOUT:   %Generic.2: type = class_type @Generic, file.%Generic.decl(%T) [symbolic]
 // CHECK:STDOUT:   %TooFew.type: type = fn_type @TooFew [template]
 // CHECK:STDOUT:   %TooFew: %TooFew.type = struct_value () [template]
 // CHECK:STDOUT:   %.2: type = struct_type {} [template]
@@ -326,6 +358,15 @@ fn Generic(T:! ()).WrongType() {}
 // CHECK:STDOUT:   return
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
+// CHECK:STDOUT: specific file.%Generic.decl(constants.%T) {
+// CHECK:STDOUT: declaration:
+// CHECK:STDOUT:   file.%T.loc4_15.2 => constants.%T
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @Generic.%TooFew.decl(constants.%T) {
+// CHECK:STDOUT: declaration:
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
 // CHECK:STDOUT: --- fail_mismatched_too_many_args.carbon
 // CHECK:STDOUT:
 // CHECK:STDOUT: constants {
@@ -333,7 +374,7 @@ fn Generic(T:! ()).WrongType() {}
 // CHECK:STDOUT:   %Generic.type: type = generic_class_type @Generic [template]
 // CHECK:STDOUT:   %.1: type = tuple_type () [template]
 // CHECK:STDOUT:   %Generic.1: %Generic.type = struct_value () [template]
-// CHECK:STDOUT:   %Generic.2: type = class_type @Generic, (%T) [symbolic]
+// CHECK:STDOUT:   %Generic.2: type = class_type @Generic, file.%Generic.decl(%T) [symbolic]
 // CHECK:STDOUT:   %TooMany.type: type = fn_type @TooMany [template]
 // CHECK:STDOUT:   %TooMany: %TooMany.type = struct_value () [template]
 // CHECK:STDOUT:   %.2: type = struct_type {} [template]
@@ -379,6 +420,21 @@ fn Generic(T:! ()).WrongType() {}
 // CHECK:STDOUT:   return
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
+// CHECK:STDOUT: specific file.%Generic.decl(constants.%T) {
+// CHECK:STDOUT: declaration:
+// CHECK:STDOUT:   file.%T.loc4_15.2 => constants.%T
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @Generic.%TooMany.decl(constants.%T) {
+// CHECK:STDOUT: declaration:
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific file.%.decl(constants.%T, constants.%U) {
+// CHECK:STDOUT: declaration:
+// CHECK:STDOUT:   file.%T.loc15_12.2 => constants.%T
+// CHECK:STDOUT:   file.%U.loc15_22.2 => constants.%U
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
 // CHECK:STDOUT: --- fail_mismatched_wrong_arg_type.carbon
 // CHECK:STDOUT:
 // CHECK:STDOUT: constants {
@@ -386,7 +442,7 @@ fn Generic(T:! ()).WrongType() {}
 // CHECK:STDOUT:   %Generic.type: type = generic_class_type @Generic [template]
 // CHECK:STDOUT:   %.1: type = tuple_type () [template]
 // CHECK:STDOUT:   %Generic.1: %Generic.type = struct_value () [template]
-// CHECK:STDOUT:   %Generic.2: type = class_type @Generic, (%T.1) [symbolic]
+// CHECK:STDOUT:   %Generic.2: type = class_type @Generic, file.%Generic.decl(%T.1) [symbolic]
 // CHECK:STDOUT:   %WrongType.type: type = fn_type @WrongType [template]
 // CHECK:STDOUT:   %WrongType: %WrongType.type = struct_value () [template]
 // CHECK:STDOUT:   %.2: type = struct_type {} [template]
@@ -432,3 +488,17 @@ fn Generic(T:! ()).WrongType() {}
 // CHECK:STDOUT:   return
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
+// CHECK:STDOUT: specific file.%Generic.decl(constants.%T.1) {
+// CHECK:STDOUT: declaration:
+// CHECK:STDOUT:   file.%T.loc4_15.2 => constants.%T.1
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @Generic.%WrongType.decl(constants.%T.1) {
+// CHECK:STDOUT: declaration:
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific file.%.decl(constants.%T.2) {
+// CHECK:STDOUT: declaration:
+// CHECK:STDOUT:   file.%T.loc14_12.2 => constants.%T.2
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 62 - 10
toolchain/check/testdata/class/generic/redeclare.carbon

@@ -93,7 +93,7 @@ class E(U:! type) {}
 // CHECK:STDOUT:   %Generic.type: type = generic_class_type @Generic [template]
 // CHECK:STDOUT:   %.1: type = tuple_type () [template]
 // CHECK:STDOUT:   %Generic.1: %Generic.type = struct_value () [template]
-// CHECK:STDOUT:   %Generic.2: type = class_type @Generic, (%T) [symbolic]
+// CHECK:STDOUT:   %Generic.2: type = class_type @Generic, file.%Generic.decl.loc4(%T) [symbolic]
 // CHECK:STDOUT:   %.2: type = struct_type {} [template]
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
@@ -120,6 +120,11 @@ class E(U:! type) {}
 // CHECK:STDOUT:   .Self = constants.%Generic.2
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
+// CHECK:STDOUT: specific file.%Generic.decl.loc4(constants.%T) {
+// CHECK:STDOUT: declaration:
+// CHECK:STDOUT:   file.%T.loc4_15.2 => constants.%T
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
 // CHECK:STDOUT: --- fail_mismatch_param_list.carbon
 // CHECK:STDOUT:
 // CHECK:STDOUT: constants {
@@ -128,7 +133,7 @@ class E(U:! type) {}
 // CHECK:STDOUT:   %.type: type = generic_class_type @.1 [template]
 // CHECK:STDOUT:   %.1: type = tuple_type () [template]
 // CHECK:STDOUT:   %.2: %.type = struct_value () [template]
-// CHECK:STDOUT:   %.3: type = class_type @.1, (%T) [symbolic]
+// CHECK:STDOUT:   %.3: type = class_type @.1, file.%.decl(%T) [symbolic]
 // CHECK:STDOUT:   %.4: type = struct_type {} [template]
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
@@ -154,6 +159,11 @@ class E(U:! type) {}
 // CHECK:STDOUT:   .Self = constants.%.3
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
+// CHECK:STDOUT: specific file.%.decl(constants.%T) {
+// CHECK:STDOUT: declaration:
+// CHECK:STDOUT:   file.%T.loc12_9.2 => constants.%T
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
 // CHECK:STDOUT: --- fail_mismatch_implicit_param_list.carbon
 // CHECK:STDOUT:
 // CHECK:STDOUT: constants {
@@ -163,12 +173,12 @@ class E(U:! type) {}
 // CHECK:STDOUT:   %N.1: i32 = bind_symbolic_name N 0 [symbolic]
 // CHECK:STDOUT:   %B.type: type = generic_class_type @B [template]
 // CHECK:STDOUT:   %B.1: %B.type = struct_value () [template]
-// CHECK:STDOUT:   %B.2: type = class_type @B, (%N.1) [symbolic]
+// CHECK:STDOUT:   %B.2: type = class_type @B, file.%B.decl(%N.1) [symbolic]
 // CHECK:STDOUT:   %T: type = bind_symbolic_name T 0 [symbolic]
 // CHECK:STDOUT:   %N.2: %T = bind_symbolic_name N 1 [symbolic]
 // CHECK:STDOUT:   %.type: type = generic_class_type @.1 [template]
 // CHECK:STDOUT:   %.2: %.type = struct_value () [template]
-// CHECK:STDOUT:   %.3: type = class_type @.1, (%T, %N.2) [symbolic]
+// CHECK:STDOUT:   %.3: type = class_type @.1, file.%.decl(%T, %N.2) [symbolic]
 // CHECK:STDOUT:   %.4: type = struct_type {} [template]
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
@@ -210,6 +220,17 @@ class E(U:! type) {}
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @Int32() -> type = "int.make_type_32";
 // CHECK:STDOUT:
+// CHECK:STDOUT: specific file.%B.decl(constants.%N.1) {
+// CHECK:STDOUT: declaration:
+// CHECK:STDOUT:   file.%N.loc4_9.2 => constants.%N.1
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific file.%.decl(constants.%T, constants.%N.2) {
+// CHECK:STDOUT: declaration:
+// CHECK:STDOUT:   file.%T.loc12_9.2 => constants.%T
+// CHECK:STDOUT:   file.%N.loc12_19.2 => constants.%N.2
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
 // CHECK:STDOUT: --- fail_mismatch_param_count.carbon
 // CHECK:STDOUT:
 // CHECK:STDOUT: constants {
@@ -217,13 +238,13 @@ class E(U:! type) {}
 // CHECK:STDOUT:   %C.type: type = generic_class_type @C [template]
 // CHECK:STDOUT:   %.1: type = tuple_type () [template]
 // CHECK:STDOUT:   %C.1: %C.type = struct_value () [template]
-// CHECK:STDOUT:   %C.2: type = class_type @C, (%T) [symbolic]
+// CHECK:STDOUT:   %C.2: type = class_type @C, file.%C.decl(%T) [symbolic]
 // CHECK:STDOUT:   %Int32.type: type = fn_type @Int32 [template]
 // CHECK:STDOUT:   %Int32: %Int32.type = struct_value () [template]
 // CHECK:STDOUT:   %U: i32 = bind_symbolic_name U 1 [symbolic]
 // CHECK:STDOUT:   %.type: type = generic_class_type @.1 [template]
 // CHECK:STDOUT:   %.2: %.type = struct_value () [template]
-// CHECK:STDOUT:   %.3: type = class_type @.1, (%T, %U) [symbolic]
+// CHECK:STDOUT:   %.3: type = class_type @.1, file.%.decl(%T, %U) [symbolic]
 // CHECK:STDOUT:   %.4: type = struct_type {} [template]
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
@@ -264,6 +285,17 @@ class E(U:! type) {}
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @Int32() -> type = "int.make_type_32";
 // CHECK:STDOUT:
+// CHECK:STDOUT: specific file.%C.decl(constants.%T) {
+// CHECK:STDOUT: declaration:
+// CHECK:STDOUT:   file.%T.loc4_9.2 => constants.%T
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific file.%.decl(constants.%T, constants.%U) {
+// CHECK:STDOUT: declaration:
+// CHECK:STDOUT:   file.%T.loc12_9.2 => constants.%T
+// CHECK:STDOUT:   file.%U.loc12_19.2 => constants.%U
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
 // CHECK:STDOUT: --- fail_mismatch_param_type.carbon
 // CHECK:STDOUT:
 // CHECK:STDOUT: constants {
@@ -271,13 +303,13 @@ class E(U:! type) {}
 // CHECK:STDOUT:   %D.type: type = generic_class_type @D [template]
 // CHECK:STDOUT:   %.1: type = tuple_type () [template]
 // CHECK:STDOUT:   %D.1: %D.type = struct_value () [template]
-// CHECK:STDOUT:   %D.2: type = class_type @D, (%T.1) [symbolic]
+// CHECK:STDOUT:   %D.2: type = class_type @D, file.%D.decl(%T.1) [symbolic]
 // CHECK:STDOUT:   %Int32.type: type = fn_type @Int32 [template]
 // CHECK:STDOUT:   %Int32: %Int32.type = struct_value () [template]
 // CHECK:STDOUT:   %T.2: i32 = bind_symbolic_name T 0 [symbolic]
 // CHECK:STDOUT:   %.type: type = generic_class_type @.1 [template]
 // CHECK:STDOUT:   %.2: %.type = struct_value () [template]
-// CHECK:STDOUT:   %.3: type = class_type @.1, (%T.2) [symbolic]
+// CHECK:STDOUT:   %.3: type = class_type @.1, file.%.decl(%T.2) [symbolic]
 // CHECK:STDOUT:   %.4: type = struct_type {} [template]
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
@@ -316,6 +348,16 @@ class E(U:! type) {}
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @Int32() -> type = "int.make_type_32";
 // CHECK:STDOUT:
+// CHECK:STDOUT: specific file.%D.decl(constants.%T.1) {
+// CHECK:STDOUT: declaration:
+// CHECK:STDOUT:   file.%T.loc4_9.2 => constants.%T.1
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific file.%.decl(constants.%T.2) {
+// CHECK:STDOUT: declaration:
+// CHECK:STDOUT:   file.%T.loc12_9.2 => constants.%T.2
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
 // CHECK:STDOUT: --- fail_mismatch_param_name.carbon
 // CHECK:STDOUT:
 // CHECK:STDOUT: constants {
@@ -323,11 +365,11 @@ class E(U:! type) {}
 // CHECK:STDOUT:   %E.type: type = generic_class_type @E [template]
 // CHECK:STDOUT:   %.1: type = tuple_type () [template]
 // CHECK:STDOUT:   %E.1: %E.type = struct_value () [template]
-// CHECK:STDOUT:   %E.2: type = class_type @E, (%T) [symbolic]
+// CHECK:STDOUT:   %E.2: type = class_type @E, file.%E.decl(%T) [symbolic]
 // CHECK:STDOUT:   %U: type = bind_symbolic_name U 0 [symbolic]
 // CHECK:STDOUT:   %.type: type = generic_class_type @.1 [template]
 // CHECK:STDOUT:   %.2: %.type = struct_value () [template]
-// CHECK:STDOUT:   %.3: type = class_type @.1, (%U) [symbolic]
+// CHECK:STDOUT:   %.3: type = class_type @.1, file.%.decl(%U) [symbolic]
 // CHECK:STDOUT:   %.4: type = struct_type {} [template]
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
@@ -357,3 +399,13 @@ class E(U:! type) {}
 // CHECK:STDOUT:   .Self = constants.%.3
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
+// CHECK:STDOUT: specific file.%E.decl(constants.%T) {
+// CHECK:STDOUT: declaration:
+// CHECK:STDOUT:   file.%T.loc4_9.2 => constants.%T
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific file.%.decl(constants.%U) {
+// CHECK:STDOUT: declaration:
+// CHECK:STDOUT:   file.%U.loc11_9.2 => constants.%U
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 23 - 3
toolchain/check/testdata/class/generic/self.carbon

@@ -26,7 +26,7 @@ class Class(T:! 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, (%T) [symbolic]
+// CHECK:STDOUT:   %Class.2: type = class_type @Class, file.%Class.decl(%T) [symbolic]
 // CHECK:STDOUT:   %MakeSelf.type: type = fn_type @MakeSelf [template]
 // CHECK:STDOUT:   %MakeSelf: %MakeSelf.type = struct_value () [template]
 // CHECK:STDOUT:   %MakeClass.type: type = fn_type @MakeClass [template]
@@ -53,7 +53,7 @@ class Class(T:! type) {
 // CHECK:STDOUT: class @Class
 // CHECK:STDOUT:     generic [file.%T.loc11_13.2: type] {
 // CHECK:STDOUT:   %MakeSelf.decl: %MakeSelf.type = fn_decl @MakeSelf [template = constants.%MakeSelf] {
-// CHECK:STDOUT:     %.loc14: type = specific_constant constants.%Class.2, (constants.%T) [symbolic = %.loc14 (constants.%Class.2)]
+// CHECK:STDOUT:     %.loc14: type = specific_constant constants.%Class.2, file.%Class.decl(constants.%T) [symbolic = %.loc14 (constants.%Class.2)]
 // CHECK:STDOUT:     %Self.ref: type = name_ref Self, %.loc14 [symbolic = %.loc14 (constants.%Class.2)]
 // CHECK:STDOUT:     %return.var.loc14: ref %Class.2 = var <return slot>
 // CHECK:STDOUT:   }
@@ -94,7 +94,7 @@ class Class(T:! type) {
 // CHECK:STDOUT:   %.loc17_9: ref %Class.2 = splice_block %c.var {}
 // CHECK:STDOUT:   %MakeSelf.call: init %Class.2 = call %MakeSelf.ref() to %.loc17_9
 // CHECK:STDOUT:   assign %c.var, %MakeSelf.call
-// CHECK:STDOUT:   %.loc18_12: type = specific_constant constants.%Class.2, (constants.%T) [symbolic = constants.%Class.2]
+// CHECK:STDOUT:   %.loc18_12: type = specific_constant constants.%Class.2, file.%Class.decl(constants.%T) [symbolic = constants.%Class.2]
 // CHECK:STDOUT:   %Self.ref: type = name_ref Self, %.loc18_12 [symbolic = constants.%Class.2]
 // CHECK:STDOUT:   %s.var: ref %Class.2 = var s
 // CHECK:STDOUT:   %s: ref %Class.2 = bind_name s, %s.var
@@ -105,3 +105,23 @@ class Class(T:! type) {
 // CHECK:STDOUT:   return
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
+// CHECK:STDOUT: specific file.%Class.decl(constants.%T) {
+// CHECK:STDOUT: declaration:
+// CHECK:STDOUT:   file.%T.loc11_13.2 => constants.%T
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @Class.%MakeSelf.decl(constants.%T) {
+// CHECK:STDOUT: declaration:
+// CHECK:STDOUT:   @Class.%.loc14 => constants.%Class.2
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @Class.%MakeClass.decl(constants.%T) {
+// CHECK:STDOUT: declaration:
+// CHECK:STDOUT:   @Class.%T.ref => constants.%T
+// CHECK:STDOUT:   @Class.%.loc15_26 => constants.%Class.2
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @Class.%F.decl(constants.%T) {
+// CHECK:STDOUT: declaration:
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 14 - 3
toolchain/check/testdata/class/generic_method.carbon

@@ -22,7 +22,7 @@ fn Class(T:! type).F[self: Self](n: T) {}
 // 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, (%T) [symbolic]
+// CHECK:STDOUT:   %Class.2: type = class_type @Class, file.%Class.decl(%T) [symbolic]
 // CHECK:STDOUT:   %.2: type = unbound_element_type %Class.2, %T [symbolic]
 // CHECK:STDOUT:   %F.type: type = fn_type @F [template]
 // CHECK:STDOUT:   %F: %F.type = struct_value () [template]
@@ -44,7 +44,7 @@ fn Class(T:! type).F[self: Self](n: T) {}
 // CHECK:STDOUT:   %F.decl: %F.type = fn_decl @F [template = constants.%F] {
 // CHECK:STDOUT:     %T.loc16_10.1: type = param T
 // CHECK:STDOUT:     %T.loc16_10.2: type = bind_symbolic_name T 0, %T.loc16_10.1 [symbolic = constants.%T]
-// CHECK:STDOUT:     %.loc16: type = specific_constant constants.%Class.2, (constants.%T) [symbolic = constants.%Class.2]
+// CHECK:STDOUT:     %.loc16: type = specific_constant constants.%Class.2, %Class.decl(constants.%T) [symbolic = constants.%Class.2]
 // CHECK:STDOUT:     %Self.ref: type = name_ref Self, %.loc16 [symbolic = constants.%Class.2]
 // CHECK:STDOUT:     %self.loc16_22.1: %Class.2 = param self
 // CHECK:STDOUT:     @F.%self: %Class.2 = bind_name self, %self.loc16_22.1
@@ -59,7 +59,7 @@ fn Class(T:! type).F[self: Self](n: T) {}
 // CHECK:STDOUT:   %T.ref.loc12: type = name_ref T, file.%T.loc11_13.2 [symbolic = constants.%T]
 // CHECK:STDOUT:   %.loc12: %.2 = field_decl a, element0 [template]
 // CHECK:STDOUT:   %F.decl: %F.type = fn_decl @F [template = constants.%F] {
-// CHECK:STDOUT:     %.loc13: type = specific_constant constants.%Class.2, (constants.%T) [symbolic = %.loc13 (constants.%Class.2)]
+// CHECK:STDOUT:     %.loc13: type = specific_constant constants.%Class.2, file.%Class.decl(constants.%T) [symbolic = %.loc13 (constants.%Class.2)]
 // CHECK:STDOUT:     %Self.ref: type = name_ref Self, %.loc13 [symbolic = %.loc13 (constants.%Class.2)]
 // CHECK:STDOUT:     %self.loc13_8.1: @Class.%.loc13 (%Class.2) = param self
 // CHECK:STDOUT:     %self.loc13_8.2: @Class.%.loc13 (%Class.2) = bind_name self, %self.loc13_8.1
@@ -80,3 +80,14 @@ fn Class(T:! type).F[self: Self](n: T) {}
 // CHECK:STDOUT:   return
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
+// CHECK:STDOUT: specific file.%Class.decl(constants.%T) {
+// CHECK:STDOUT: declaration:
+// CHECK:STDOUT:   file.%T.loc11_13.2 => constants.%T
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @Class.%F.decl(constants.%T) {
+// CHECK:STDOUT: declaration:
+// CHECK:STDOUT:   @Class.%.loc13 => constants.%Class.2
+// CHECK:STDOUT:   @Class.%T.ref.loc13 => constants.%T
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 5 - 0
toolchain/check/testdata/eval/fail_symbolic.carbon

@@ -63,3 +63,8 @@ fn G(N:! i32) {
 // CHECK:STDOUT:   return
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
+// CHECK:STDOUT: specific file.%G.decl(constants.%N) {
+// CHECK:STDOUT: declaration:
+// CHECK:STDOUT:   @G.%N => constants.%N
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 5 - 0
toolchain/check/testdata/eval/symbolic.carbon

@@ -70,3 +70,8 @@ fn F(T:! type) {
 // CHECK:STDOUT:   return
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
+// CHECK:STDOUT: specific file.%F.decl(constants.%T) {
+// CHECK:STDOUT: declaration:
+// CHECK:STDOUT:   @F.%T => constants.%T
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 5 - 0
toolchain/check/testdata/function/builtin/method.carbon

@@ -136,3 +136,8 @@ var arr: [i32; 1.(I.F)(2)];
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @F.2[@impl.%self.loc16_8.2: i32](@impl.%other.loc16_19.2: i32) -> i32 = "int.sadd";
 // CHECK:STDOUT:
+// CHECK:STDOUT: specific @I.%F.decl(constants.%Self) {
+// CHECK:STDOUT: declaration:
+// CHECK:STDOUT:   @I.%Self.ref.loc12_14 => constants.%Self
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

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

@@ -86,6 +86,11 @@ var arr: [i32; 1 + 2] = (3, 4, 3 + 4);
 // CHECK:STDOUT: fn @Op[@Add.%self.loc7_9.2: @Add.%Self.ref.loc7_15 (%Self)](@Add.%other.loc7_21.2: @Add.%Self.ref.loc7_15 (%Self)) -> %Self
 // CHECK:STDOUT:     generic [@Add.%Self: %.2];
 // CHECK:STDOUT:
+// CHECK:STDOUT: specific @Add.%Op.decl(constants.%Self) {
+// CHECK:STDOUT: declaration:
+// CHECK:STDOUT:   @Add.%Self.ref.loc7_15 => constants.%Self
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
 // CHECK:STDOUT: --- user.carbon
 // CHECK:STDOUT:
 // CHECK:STDOUT: constants {

+ 5 - 0
toolchain/check/testdata/function/generic/fail_todo_param_in_type.carbon

@@ -58,3 +58,8 @@ fn F(N:! i32, a: [i32; N]*);
 // CHECK:STDOUT: fn @F(%N: i32, %a: <error>)
 // CHECK:STDOUT:     generic [%N: i32];
 // CHECK:STDOUT:
+// CHECK:STDOUT: specific file.%F.decl(constants.%N) {
+// CHECK:STDOUT: declaration:
+// CHECK:STDOUT:   @F.%N => constants.%N
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 6 - 0
toolchain/check/testdata/function/generic/no_prelude/fail_type_param_mismatch.carbon

@@ -54,3 +54,9 @@ fn F(T:! type, U:! type) {
 // CHECK:STDOUT:   return
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
+// CHECK:STDOUT: specific file.%F.decl(constants.%T, constants.%U) {
+// CHECK:STDOUT: declaration:
+// CHECK:STDOUT:   @F.%T => constants.%T
+// CHECK:STDOUT:   @F.%U => constants.%U
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 7 - 0
toolchain/check/testdata/function/generic/no_prelude/indirect_generic_type.carbon

@@ -50,3 +50,10 @@ fn F(T:! type, p: T**) -> T* {
 // CHECK:STDOUT:   return %.loc12_10.2
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
+// CHECK:STDOUT: specific file.%F.decl(constants.%T) {
+// CHECK:STDOUT: declaration:
+// CHECK:STDOUT:   @F.%T => constants.%T
+// CHECK:STDOUT:   file.%.loc11_20 => constants.%.1
+// CHECK:STDOUT:   file.%.loc11_21 => constants.%.2
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 5 - 0
toolchain/check/testdata/function/generic/no_prelude/type_param.carbon

@@ -49,3 +49,8 @@ fn F(T:! type) {
 // CHECK:STDOUT:   return
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
+// CHECK:STDOUT: specific file.%F.decl(constants.%T) {
+// CHECK:STDOUT: declaration:
+// CHECK:STDOUT:   @F.%T => constants.%T
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 5 - 0
toolchain/check/testdata/function/generic/no_prelude/type_param_scope.carbon

@@ -47,3 +47,8 @@ fn F(T:! type, n: T) -> T {
 // CHECK:STDOUT:   return %m.ref
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
+// CHECK:STDOUT: specific file.%F.decl(constants.%T) {
+// CHECK:STDOUT: declaration:
+// CHECK:STDOUT:   @F.%T => constants.%T
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 48 - 0
toolchain/check/testdata/function/generic/redeclare.carbon

@@ -145,6 +145,12 @@ fn F(U:! type, T:! type) -> U* {
 // CHECK:STDOUT:   return %.loc7_14.2
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
+// CHECK:STDOUT: specific file.%F.decl.loc4(constants.%T) {
+// CHECK:STDOUT: declaration:
+// CHECK:STDOUT:   file.%T.loc4_6.2 => constants.%T
+// CHECK:STDOUT:   file.%.loc4 => constants.%.1
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
 // CHECK:STDOUT: --- fail_different_return_type.carbon
 // CHECK:STDOUT:
 // CHECK:STDOUT: constants {
@@ -198,6 +204,20 @@ fn F(U:! type, T:! type) -> U* {
 // CHECK:STDOUT:   return <error>
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
+// CHECK:STDOUT: specific file.%F.decl(constants.%T, constants.%U) {
+// CHECK:STDOUT: declaration:
+// CHECK:STDOUT:   @F.%T => constants.%T
+// CHECK:STDOUT:   @F.%U => constants.%U
+// CHECK:STDOUT:   file.%.loc4 => constants.%.1
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific file.%.decl(constants.%T, constants.%U) {
+// CHECK:STDOUT: declaration:
+// CHECK:STDOUT:   @.1.%T => constants.%T
+// CHECK:STDOUT:   @.1.%U => constants.%U
+// CHECK:STDOUT:   file.%.loc13 => constants.%.3
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
 // CHECK:STDOUT: --- fail_reorder.carbon
 // CHECK:STDOUT:
 // CHECK:STDOUT: constants {
@@ -253,6 +273,20 @@ fn F(U:! type, T:! type) -> U* {
 // CHECK:STDOUT:   return <error>
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
+// CHECK:STDOUT: specific file.%F.decl(constants.%T.1, constants.%U.1) {
+// CHECK:STDOUT: declaration:
+// CHECK:STDOUT:   @F.%T => constants.%T.1
+// CHECK:STDOUT:   @F.%U => constants.%U.1
+// CHECK:STDOUT:   file.%.loc4 => constants.%.1
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific file.%.decl(constants.%U.2, constants.%T.2) {
+// CHECK:STDOUT: declaration:
+// CHECK:STDOUT:   @.1.%U => constants.%U.2
+// CHECK:STDOUT:   @.1.%T => constants.%T.2
+// CHECK:STDOUT:   file.%.loc13 => constants.%.3
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
 // CHECK:STDOUT: --- fail_rename.carbon
 // CHECK:STDOUT:
 // CHECK:STDOUT: constants {
@@ -308,3 +342,17 @@ fn F(U:! type, T:! type) -> U* {
 // CHECK:STDOUT:   return <error>
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
+// CHECK:STDOUT: specific file.%F.decl(constants.%T.1, constants.%U.1) {
+// CHECK:STDOUT: declaration:
+// CHECK:STDOUT:   @F.%T => constants.%T.1
+// CHECK:STDOUT:   @F.%U => constants.%U.1
+// CHECK:STDOUT:   file.%.loc4 => constants.%.1
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific file.%.decl(constants.%U.2, constants.%T.2) {
+// CHECK:STDOUT: declaration:
+// CHECK:STDOUT:   @.1.%U => constants.%U.2
+// CHECK:STDOUT:   @.1.%T => constants.%T.2
+// CHECK:STDOUT:   file.%.loc13 => constants.%.3
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 9 - 0
toolchain/check/testdata/impl/compound.carbon

@@ -219,3 +219,12 @@ fn InstanceCallIndirect(p: i32*) {
 // CHECK:STDOUT:   return
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
+// CHECK:STDOUT: specific @Simple.%F.decl(constants.%Self) {
+// CHECK:STDOUT: declaration:
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @Simple.%G.decl(constants.%Self) {
+// CHECK:STDOUT: declaration:
+// CHECK:STDOUT:   @Simple.%Self.ref => constants.%Self
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 4 - 0
toolchain/check/testdata/impl/extend_impl.carbon

@@ -112,3 +112,7 @@ fn G(c: C) {
 // CHECK:STDOUT:   return
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
+// CHECK:STDOUT: specific @HasF.%F.decl(constants.%Self) {
+// CHECK:STDOUT: declaration:
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 5 - 0
toolchain/check/testdata/impl/fail_call_invalid.carbon

@@ -117,3 +117,8 @@ fn InstanceCall(n: i32) {
 // CHECK:STDOUT:   return
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
+// CHECK:STDOUT: specific @Simple.%G.decl(constants.%Self) {
+// CHECK:STDOUT: declaration:
+// CHECK:STDOUT:   @Simple.%Self.ref => constants.%Self
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 16 - 1
toolchain/check/testdata/impl/fail_extend_impl_forall.carbon

@@ -28,7 +28,7 @@ class C {
 // CHECK:STDOUT:   %GenericInterface.type: type = generic_interface_type @GenericInterface [template]
 // CHECK:STDOUT:   %.1: type = tuple_type () [template]
 // CHECK:STDOUT:   %GenericInterface: %GenericInterface.type = struct_value () [template]
-// CHECK:STDOUT:   %.2: type = interface_type @GenericInterface, (%T) [symbolic]
+// CHECK:STDOUT:   %.2: type = interface_type @GenericInterface, file.%GenericInterface.decl(%T) [symbolic]
 // CHECK:STDOUT:   %Self: %.2 = bind_symbolic_name Self 1 [symbolic]
 // CHECK:STDOUT:   %F.type.1: type = fn_type @F.1 [template]
 // CHECK:STDOUT:   %F.1: %F.type.1 = struct_value () [template]
@@ -110,3 +110,18 @@ class C {
 // CHECK:STDOUT:   return
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
+// CHECK:STDOUT: specific file.%GenericInterface.decl(constants.%T) {
+// CHECK:STDOUT: declaration:
+// CHECK:STDOUT:   file.%T.loc11_28.2 => constants.%T
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @GenericInterface.%F.decl(constants.%T, constants.%Self) {
+// CHECK:STDOUT: declaration:
+// CHECK:STDOUT:   @GenericInterface.%T.ref => constants.%T
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @impl.%F.decl(constants.%T) {
+// CHECK:STDOUT: declaration:
+// CHECK:STDOUT:   @impl.%T.ref => constants.%T
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 4 - 0
toolchain/check/testdata/impl/fail_extend_partially_defined_interface.carbon

@@ -62,3 +62,7 @@ interface I {
 // CHECK:STDOUT:   has_error
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
+// CHECK:STDOUT: specific @I.%C.decl(constants.%Self) {
+// CHECK:STDOUT: declaration:
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 4 - 0
toolchain/check/testdata/impl/fail_impl_as_scope.carbon

@@ -75,3 +75,7 @@ impl as Simple {
 // CHECK:STDOUT:   return
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
+// CHECK:STDOUT: specific @Simple.%F.decl(constants.%Self) {
+// CHECK:STDOUT: declaration:
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 18 - 0
toolchain/check/testdata/impl/fail_impl_bad_assoc_fn.carbon

@@ -887,3 +887,21 @@ class SelfNestedBadReturnType {
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @F.15(@impl.15.%x.loc212_10.2: %.26) -> %.21;
 // CHECK:STDOUT:
+// CHECK:STDOUT: specific @I.%F.decl(constants.%Self.1) {
+// CHECK:STDOUT: declaration:
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @J.%F.decl(constants.%Self.2) {
+// CHECK:STDOUT: declaration:
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @SelfNested.%F.decl(constants.%Self.3) {
+// CHECK:STDOUT: declaration:
+// CHECK:STDOUT:   @SelfNested.%Self.ref.loc188_12 => constants.%Self.3
+// CHECK:STDOUT:   @SelfNested.%.loc188_16.3 => constants.%.10
+// CHECK:STDOUT:   <unexpected instref inst+267> => <unexpected instref inst+268>
+// CHECK:STDOUT:   @SelfNested.%.loc188_37 => constants.%.11
+// CHECK:STDOUT:   @SelfNested.%.loc188_38.2 => constants.%.13
+// CHECK:STDOUT:   @SelfNested.%.loc188_52 => constants.%.15
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 4 - 0
toolchain/check/testdata/impl/impl_as.carbon

@@ -96,3 +96,7 @@ class C {
 // CHECK:STDOUT:   return
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
+// CHECK:STDOUT: specific @Simple.%F.decl(constants.%Self) {
+// CHECK:STDOUT: declaration:
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 8 - 0
toolchain/check/testdata/impl/impl_forall.carbon

@@ -77,3 +77,11 @@ impl forall [T:! type] T as Simple {
 // CHECK:STDOUT:   return
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
+// CHECK:STDOUT: specific @Simple.%F.decl(constants.%Self) {
+// CHECK:STDOUT: declaration:
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @impl.%F.decl(constants.%T) {
+// CHECK:STDOUT: declaration:
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 4 - 0
toolchain/check/testdata/impl/lookup/alias.carbon

@@ -118,3 +118,7 @@ fn G(c: C) {
 // CHECK:STDOUT:   return
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
+// CHECK:STDOUT: specific @HasF.%F.decl(constants.%Self) {
+// CHECK:STDOUT: declaration:
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 4 - 0
toolchain/check/testdata/impl/lookup/fail_alias_impl_not_found.carbon

@@ -96,3 +96,7 @@ fn F(c: C) {
 // CHECK:STDOUT:   return
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
+// CHECK:STDOUT: specific @I.%F.decl(constants.%Self) {
+// CHECK:STDOUT: declaration:
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 4 - 0
toolchain/check/testdata/impl/lookup/fail_todo_undefined_impl.carbon

@@ -124,3 +124,7 @@ impl C as I {
 // CHECK:STDOUT:   return
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
+// CHECK:STDOUT: specific @I.%F.decl(constants.%Self) {
+// CHECK:STDOUT: declaration:
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

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

@@ -96,6 +96,10 @@ fn G(c: Impl.C) {
 // CHECK:STDOUT:   return
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
+// CHECK:STDOUT: specific @HasF.%F.decl(constants.%Self) {
+// CHECK:STDOUT: declaration:
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
 // CHECK:STDOUT: --- use.carbon
 // CHECK:STDOUT:
 // CHECK:STDOUT: constants {

+ 5 - 0
toolchain/check/testdata/impl/lookup/instance_method.carbon

@@ -142,3 +142,8 @@ fn F(c: C) -> i32 {
 // CHECK:STDOUT:   return %.loc24_15.2
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
+// CHECK:STDOUT: specific @I.%F.decl(constants.%Self) {
+// CHECK:STDOUT: declaration:
+// CHECK:STDOUT:   @I.%Self.ref => constants.%Self
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

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

@@ -93,6 +93,10 @@ fn G(c: Impl.C) {
 // CHECK:STDOUT:   return
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
+// CHECK:STDOUT: specific @HasF.%F.decl(constants.%Self) {
+// CHECK:STDOUT: declaration:
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
 // CHECK:STDOUT: --- use.carbon
 // CHECK:STDOUT:
 // CHECK:STDOUT: constants {

+ 4 - 0
toolchain/check/testdata/impl/no_prelude/basic.carbon

@@ -81,3 +81,7 @@ impl C as Simple {
 // CHECK:STDOUT:   return
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
+// CHECK:STDOUT: specific @Simple.%F.decl(constants.%Self) {
+// CHECK:STDOUT: declaration:
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

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

@@ -78,6 +78,11 @@ fn F(x: (), y: ()) -> () {
 // CHECK:STDOUT: fn @Op[@Add.%self.loc5_9.2: @Add.%Self.ref.loc5_15 (%Self)](@Add.%other.loc5_21.2: @Add.%Self.ref.loc5_15 (%Self)) -> %Self
 // CHECK:STDOUT:     generic [@Add.%Self: %.1];
 // CHECK:STDOUT:
+// CHECK:STDOUT: specific @Add.%Op.decl(constants.%Self) {
+// CHECK:STDOUT: declaration:
+// CHECK:STDOUT:   @Add.%Self.ref.loc5_15 => constants.%Self
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
 // CHECK:STDOUT: --- b.carbon
 // CHECK:STDOUT:
 // CHECK:STDOUT: constants {

+ 5 - 0
toolchain/check/testdata/impl/no_prelude/self_in_class.carbon

@@ -107,3 +107,8 @@ class A {
 // CHECK:STDOUT:   return %.loc21_34 to @impl.%return.var
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
+// CHECK:STDOUT: specific @DefaultConstructible.%Make.decl(constants.%Self) {
+// CHECK:STDOUT: declaration:
+// CHECK:STDOUT:   @DefaultConstructible.%Self.ref => constants.%Self
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 14 - 0
toolchain/check/testdata/impl/no_prelude/self_in_signature.carbon

@@ -275,3 +275,17 @@ impl D as SelfNested {
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @F.6(@impl.4.%x.loc36_8.2: %.22);
 // CHECK:STDOUT:
+// CHECK:STDOUT: specific @UseSelf.%F.decl(constants.%Self.1) {
+// CHECK:STDOUT: declaration:
+// CHECK:STDOUT:   @UseSelf.%Self.ref.loc12_14 => constants.%Self.1
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @SelfNested.%F.decl(constants.%Self.2) {
+// CHECK:STDOUT: declaration:
+// CHECK:STDOUT:   @SelfNested.%Self.ref.loc28_12 => constants.%Self.2
+// CHECK:STDOUT:   @SelfNested.%.loc28_16.3 => constants.%.10
+// CHECK:STDOUT:   <unexpected instref inst+86> => <unexpected instref inst+87>
+// CHECK:STDOUT:   @SelfNested.%.loc28_36 => constants.%.11
+// CHECK:STDOUT:   @SelfNested.%.loc28_37.2 => constants.%.13
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 8 - 0
toolchain/check/testdata/interface/fail_todo_define_default_fn_inline.carbon

@@ -95,3 +95,11 @@ interface Interface {
 // CHECK:STDOUT: fn @G(@Interface.%a.loc21_16.2: i32, @Interface.%b.loc21_24.2: i32) -> i32 = "int.sadd"
 // CHECK:STDOUT:     generic [@Interface.%Self: %.1];
 // CHECK:STDOUT:
+// CHECK:STDOUT: specific @Interface.%F.decl(constants.%Self) {
+// CHECK:STDOUT: declaration:
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @Interface.%G.decl(constants.%Self) {
+// CHECK:STDOUT: declaration:
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 8 - 0
toolchain/check/testdata/interface/fail_todo_define_default_fn_out_of_line.carbon

@@ -141,3 +141,11 @@ fn Interface.G(a: i32, b: i32) -> i32 = "int.sadd";
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @.2(%a: i32, %b: i32) -> i32 = "int.sadd";
 // CHECK:STDOUT:
+// CHECK:STDOUT: specific @Interface.%F.decl(constants.%Self) {
+// CHECK:STDOUT: declaration:
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @Interface.%G.decl(constants.%Self) {
+// CHECK:STDOUT: declaration:
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 5 - 0
toolchain/check/testdata/interface/no_prelude/as_type_of_type.carbon

@@ -57,3 +57,8 @@ fn F(T:! Empty) {
 // CHECK:STDOUT:   return
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
+// CHECK:STDOUT: specific file.%F.decl(constants.%T) {
+// CHECK:STDOUT: declaration:
+// CHECK:STDOUT:   @F.%T => constants.%T
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 4 - 0
toolchain/check/testdata/interface/no_prelude/basic.carbon

@@ -63,3 +63,7 @@ interface ForwardDeclared {
 // CHECK:STDOUT: fn @F()
 // CHECK:STDOUT:     generic [@ForwardDeclared.%Self: %.2];
 // CHECK:STDOUT:
+// CHECK:STDOUT: specific @ForwardDeclared.%F.decl(constants.%Self.2) {
+// CHECK:STDOUT: declaration:
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 4 - 0
toolchain/check/testdata/interface/no_prelude/default_fn.carbon

@@ -104,3 +104,7 @@ class C {
 // CHECK:STDOUT:   return
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
+// CHECK:STDOUT: specific @I.%F.decl(constants.%Self) {
+// CHECK:STDOUT: declaration:
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 12 - 0
toolchain/check/testdata/interface/no_prelude/fail_add_member_outside_definition.carbon

@@ -101,3 +101,15 @@ interface Outer {
 // CHECK:STDOUT: fn @F.2()
 // CHECK:STDOUT:     generic [@Outer.%Self: %.3];
 // CHECK:STDOUT:
+// CHECK:STDOUT: specific @Outer.%Inner.decl(constants.%Self.2) {
+// CHECK:STDOUT: declaration:
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @Inner.%.decl(constants.%Self.2, constants.%Self.3) {
+// CHECK:STDOUT: declaration:
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @Outer.%F.decl(constants.%Self.2) {
+// CHECK:STDOUT: declaration:
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 22 - 2
toolchain/check/testdata/interface/no_prelude/fail_generic_redeclaration.carbon

@@ -45,7 +45,7 @@ interface DifferentParams(T:! ()) {}
 // CHECK:STDOUT:   %.type.1: type = generic_interface_type @.1 [template]
 // CHECK:STDOUT:   %.2: type = tuple_type () [template]
 // CHECK:STDOUT:   %.3: %.type.1 = struct_value () [template]
-// CHECK:STDOUT:   %.4: type = interface_type @.1, (%T.1) [symbolic]
+// CHECK:STDOUT:   %.4: type = interface_type @.1, file.%.decl.loc19(%T.1) [symbolic]
 // CHECK:STDOUT:   %Self.1: %.4 = bind_symbolic_name Self 1 [symbolic]
 // CHECK:STDOUT:   %Generic.type: type = generic_interface_type @Generic [template]
 // CHECK:STDOUT:   %Generic: %Generic.type = struct_value () [template]
@@ -56,7 +56,7 @@ interface DifferentParams(T:! ()) {}
 // CHECK:STDOUT:   %T.2: %.2 = bind_symbolic_name T 0 [symbolic]
 // CHECK:STDOUT:   %.type.2: type = generic_interface_type @.3 [template]
 // CHECK:STDOUT:   %.6: %.type.2 = struct_value () [template]
-// CHECK:STDOUT:   %.7: type = interface_type @.3, (%T.2) [symbolic]
+// CHECK:STDOUT:   %.7: type = interface_type @.3, file.%.decl.loc38(%T.2) [symbolic]
 // CHECK:STDOUT:   %Self.3: %.7 = bind_symbolic_name Self 1 [symbolic]
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
@@ -122,3 +122,23 @@ interface DifferentParams(T:! ()) {}
 // CHECK:STDOUT:   witness = ()
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
+// CHECK:STDOUT: specific file.%.decl.loc19(constants.%T.1) {
+// CHECK:STDOUT: declaration:
+// CHECK:STDOUT:   file.%T.loc19_22.2 => constants.%T.1
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific file.%Generic.decl(constants.%T.1) {
+// CHECK:STDOUT: declaration:
+// CHECK:STDOUT:   file.%T.loc21_19.2 => constants.%T.1
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific file.%DifferentParams.decl(constants.%T.1) {
+// CHECK:STDOUT: declaration:
+// CHECK:STDOUT:   file.%T.loc31_27.2 => constants.%T.1
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific file.%.decl.loc38(constants.%T.2) {
+// CHECK:STDOUT: declaration:
+// CHECK:STDOUT:   file.%T.loc38_27.2 => constants.%T.2
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 8 - 0
toolchain/check/testdata/interface/no_prelude/fail_lookup_undefined.carbon

@@ -113,3 +113,11 @@ interface BeingDefined {
 // CHECK:STDOUT: fn @.2()
 // CHECK:STDOUT:     generic [@BeingDefined.%Self: %.4];
 // CHECK:STDOUT:
+// CHECK:STDOUT: specific @BeingDefined.%H.decl(constants.%Self) {
+// CHECK:STDOUT: declaration:
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @BeingDefined.%.decl(constants.%Self) {
+// CHECK:STDOUT: declaration:
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 4 - 0
toolchain/check/testdata/interface/no_prelude/fail_member_lookup.carbon

@@ -80,3 +80,7 @@ fn F() {
 // CHECK:STDOUT:   return
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
+// CHECK:STDOUT: specific @Interface.%F.decl(constants.%Self) {
+// CHECK:STDOUT: declaration:
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 8 - 0
toolchain/check/testdata/interface/no_prelude/fail_redeclare_member.carbon

@@ -58,3 +58,11 @@ interface Interface {
 // CHECK:STDOUT: fn @.1()
 // CHECK:STDOUT:     generic [@Interface.%Self: %.1];
 // CHECK:STDOUT:
+// CHECK:STDOUT: specific @Interface.%F.decl(constants.%Self) {
+// CHECK:STDOUT: declaration:
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @Interface.%.decl(constants.%Self) {
+// CHECK:STDOUT: declaration:
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 14 - 0
toolchain/check/testdata/interface/no_prelude/fail_todo_facet_lookup.carbon

@@ -95,3 +95,17 @@ fn CallFacet(T:! Interface, x: T) {
 // CHECK:STDOUT:   return
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
+// CHECK:STDOUT: specific @Interface.%F.decl(constants.%Self) {
+// CHECK:STDOUT: declaration:
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific file.%CallStatic.decl(constants.%T) {
+// CHECK:STDOUT: declaration:
+// CHECK:STDOUT:   @CallStatic.%T => constants.%T
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific file.%CallFacet.decl(constants.%T) {
+// CHECK:STDOUT: declaration:
+// CHECK:STDOUT:   @CallFacet.%T => constants.%T
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 27 - 5
toolchain/check/testdata/interface/no_prelude/fail_todo_generic_default_fn.carbon

@@ -28,7 +28,7 @@ fn I(T:! type).F[self: Self]() -> Self { return self; }
 // CHECK:STDOUT:   %I.type: type = generic_interface_type @I [template]
 // CHECK:STDOUT:   %.1: type = tuple_type () [template]
 // CHECK:STDOUT:   %I: %I.type = struct_value () [template]
-// CHECK:STDOUT:   %.2: type = interface_type @I, (%T) [symbolic]
+// CHECK:STDOUT:   %.2: type = interface_type @I, file.%I.decl(%T) [symbolic]
 // CHECK:STDOUT:   %Self: %.2 = bind_symbolic_name Self 1 [symbolic]
 // CHECK:STDOUT:   %F.type: type = fn_type @F [template]
 // CHECK:STDOUT:   %F: %F.type = struct_value () [template]
@@ -49,13 +49,13 @@ fn I(T:! type).F[self: Self]() -> Self { return self; }
 // CHECK:STDOUT:   %.decl: %.type = fn_decl @.1 [template = constants.%.5] {
 // CHECK:STDOUT:     %T.loc22_6.1: type = param T
 // CHECK:STDOUT:     %T.loc22_6.2: type = bind_symbolic_name T 0, %T.loc22_6.1 [symbolic = %T.loc22_6.2 (constants.%T)]
-// CHECK:STDOUT:     %.loc22_24.1: <unexpected instref inst+42> (%.2) = specific_constant @I.%Self, (constants.%T) [symbolic = %.loc22_24.1 (constants.%Self)]
+// CHECK:STDOUT:     %.loc22_24.1: <unexpected instref inst+42> (%.2) = specific_constant @I.%Self, %I.decl(constants.%T) [symbolic = %.loc22_24.1 (constants.%Self)]
 // CHECK:STDOUT:     %Self.ref.loc22_24: <unexpected instref inst+42> (%.2) = name_ref Self, %.loc22_24.1 [symbolic = %.loc22_24.1 (constants.%Self)]
 // CHECK:STDOUT:     %.loc22_24.2: type = facet_type_access %Self.ref.loc22_24 [symbolic = %.loc22_24.1 (constants.%Self)]
 // CHECK:STDOUT:     %.loc22_24.3: type = converted %Self.ref.loc22_24, %.loc22_24.2 [symbolic = %.loc22_24.1 (constants.%Self)]
 // CHECK:STDOUT:     %self.loc22_18.1: file.%.loc22_24.1 (%Self) = param self
 // CHECK:STDOUT:     @.1.%self: file.%.loc22_24.1 (%Self) = bind_name self, %self.loc22_18.1
-// CHECK:STDOUT:     %.loc22_35.1: <unexpected instref inst+42> (%.2) = specific_constant @I.%Self, (constants.%T) [symbolic = %.loc22_24.1 (constants.%Self)]
+// CHECK:STDOUT:     %.loc22_35.1: <unexpected instref inst+42> (%.2) = specific_constant @I.%Self, %I.decl(constants.%T) [symbolic = %.loc22_24.1 (constants.%Self)]
 // CHECK:STDOUT:     %Self.ref.loc22_35: <unexpected instref inst+42> (%.2) = name_ref Self, %.loc22_35.1 [symbolic = %.loc22_24.1 (constants.%Self)]
 // CHECK:STDOUT:     %.loc22_35.2: type = facet_type_access %Self.ref.loc22_35 [symbolic = %.loc22_24.1 (constants.%Self)]
 // CHECK:STDOUT:     %.loc22_35.3: type = converted %Self.ref.loc22_35, %.loc22_35.2 [symbolic = %.loc22_24.1 (constants.%Self)]
@@ -67,13 +67,13 @@ fn I(T:! type).F[self: Self]() -> Self { return self; }
 // CHECK:STDOUT:     generic [file.%T.loc11_13.2: type] {
 // CHECK:STDOUT:   %Self: %.2 = bind_symbolic_name Self 1 [symbolic = constants.%Self]
 // CHECK:STDOUT:   %F.decl: %F.type = fn_decl @F [template = constants.%F] {
-// CHECK:STDOUT:     %.loc13_14.1: %.2 = specific_constant %Self, (constants.%T) [symbolic = %.loc13_14.1 (constants.%Self)]
+// CHECK:STDOUT:     %.loc13_14.1: %.2 = specific_constant %Self, file.%I.decl(constants.%T) [symbolic = %.loc13_14.1 (constants.%Self)]
 // CHECK:STDOUT:     %Self.ref.loc13_14: %.2 = name_ref Self, %.loc13_14.1 [symbolic = %.loc13_14.1 (constants.%Self)]
 // CHECK:STDOUT:     %.loc13_14.2: type = facet_type_access %Self.ref.loc13_14 [symbolic = %.loc13_14.1 (constants.%Self)]
 // CHECK:STDOUT:     %.loc13_14.3: type = converted %Self.ref.loc13_14, %.loc13_14.2 [symbolic = %.loc13_14.1 (constants.%Self)]
 // CHECK:STDOUT:     %self.loc13_8.1: @I.%.loc13_14.1 (%Self) = param self
 // CHECK:STDOUT:     %self.loc13_8.2: @I.%.loc13_14.1 (%Self) = bind_name self, %self.loc13_8.1
-// CHECK:STDOUT:     %.loc13_25.1: %.2 = specific_constant %Self, (constants.%T) [symbolic = %.loc13_14.1 (constants.%Self)]
+// CHECK:STDOUT:     %.loc13_25.1: %.2 = specific_constant %Self, file.%I.decl(constants.%T) [symbolic = %.loc13_14.1 (constants.%Self)]
 // CHECK:STDOUT:     %Self.ref.loc13_25: %.2 = name_ref Self, %.loc13_25.1 [symbolic = %.loc13_14.1 (constants.%Self)]
 // CHECK:STDOUT:     %.loc13_25.2: type = facet_type_access %Self.ref.loc13_25 [symbolic = %.loc13_14.1 (constants.%Self)]
 // CHECK:STDOUT:     %.loc13_25.3: type = converted %Self.ref.loc13_25, %.loc13_25.2 [symbolic = %.loc13_14.1 (constants.%Self)]
@@ -97,3 +97,25 @@ fn I(T:! type).F[self: Self]() -> Self { return self; }
 // CHECK:STDOUT:   return %self.ref
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
+// CHECK:STDOUT: specific file.%I.decl(constants.%T) {
+// CHECK:STDOUT: declaration:
+// CHECK:STDOUT:   file.%T.loc11_13.2 => constants.%T
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @I.%F.decl(constants.%T, constants.%Self) {
+// CHECK:STDOUT: declaration:
+// CHECK:STDOUT:   @I.%.loc13_14.1 => constants.%Self
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific file.%I.decl(file.%T.loc22_6.2) {
+// CHECK:STDOUT: declaration:
+// CHECK:STDOUT:   file.%T.loc11_13.2 => constants.%T
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific file.%.decl(constants.%T) {
+// CHECK:STDOUT: declaration:
+// CHECK:STDOUT:   file.%T.loc22_6.2 => constants.%T
+// CHECK:STDOUT:   <unexpected instref inst+42> => constants.%.2
+// CHECK:STDOUT:   file.%.loc22_24.1 => constants.%Self
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 8 - 0
toolchain/check/testdata/interface/no_prelude/fail_todo_modifiers.carbon

@@ -69,3 +69,11 @@ interface Modifiers {
 // CHECK:STDOUT:   return
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
+// CHECK:STDOUT: specific @Modifiers.%Final.decl(constants.%Self) {
+// CHECK:STDOUT: declaration:
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @Modifiers.%Default.decl(constants.%Self) {
+// CHECK:STDOUT: declaration:
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 72 - 7
toolchain/check/testdata/interface/no_prelude/generic.carbon

@@ -65,22 +65,22 @@ fn G(T:! Generic(B)) {
 // CHECK:STDOUT:   %Simple.type: type = generic_interface_type @Simple [template]
 // CHECK:STDOUT:   %.1: type = tuple_type () [template]
 // CHECK:STDOUT:   %Simple: %Simple.type = struct_value () [template]
-// CHECK:STDOUT:   %.2: type = interface_type @Simple, (%T.1) [symbolic]
+// CHECK:STDOUT:   %.2: type = interface_type @Simple, file.%Simple.decl(%T.1) [symbolic]
 // CHECK:STDOUT:   %Self.1: %.2 = bind_symbolic_name Self 1 [symbolic]
 // CHECK:STDOUT:   %X: type = class_type @X [template]
 // CHECK:STDOUT:   %.3: type = struct_type {} [template]
 // CHECK:STDOUT:   %WithAssocFn.type: type = generic_interface_type @WithAssocFn [template]
 // CHECK:STDOUT:   %WithAssocFn: %WithAssocFn.type = struct_value () [template]
-// CHECK:STDOUT:   %.4: type = interface_type @WithAssocFn, (%T.1) [symbolic]
+// CHECK:STDOUT:   %.4: type = interface_type @WithAssocFn, file.%WithAssocFn.decl(%T.1) [symbolic]
 // CHECK:STDOUT:   %Self.2: %.4 = bind_symbolic_name Self 1 [symbolic]
 // CHECK:STDOUT:   %F.type.1: type = fn_type @F.1 [template]
 // CHECK:STDOUT:   %F.1: %F.type.1 = struct_value () [template]
 // CHECK:STDOUT:   %.5: type = assoc_entity_type @WithAssocFn, %F.type.1 [template]
 // CHECK:STDOUT:   %.6: %.5 = assoc_entity element0, @WithAssocFn.%F.decl [template]
 // CHECK:STDOUT:   %C: type = class_type @C [template]
-// CHECK:STDOUT:   %.7: type = interface_type @Simple, (%C) [template]
+// CHECK:STDOUT:   %.7: type = interface_type @Simple, file.%Simple.decl(%C) [template]
 // CHECK:STDOUT:   %.8: <witness> = interface_witness () [template]
-// CHECK:STDOUT:   %.9: type = interface_type @WithAssocFn, (%C) [template]
+// CHECK:STDOUT:   %.9: type = interface_type @WithAssocFn, file.%WithAssocFn.decl(%C) [template]
 // CHECK:STDOUT:   %F.type.2: type = fn_type @F.2 [template]
 // CHECK:STDOUT:   %F.2: %F.type.2 = struct_value () [template]
 // CHECK:STDOUT:   %.10: <witness> = interface_witness (%F.2) [template]
@@ -237,6 +237,46 @@ fn G(T:! Generic(B)) {
 // CHECK:STDOUT:   return
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
+// CHECK:STDOUT: specific file.%Simple.decl(constants.%T.1) {
+// CHECK:STDOUT: declaration:
+// CHECK:STDOUT:   file.%T.loc4_18.2 => constants.%T.1
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific file.%WithAssocFn.decl(constants.%T.1) {
+// CHECK:STDOUT: declaration:
+// CHECK:STDOUT:   file.%T.loc8_23.2 => constants.%T.1
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @WithAssocFn.%F.decl(constants.%T.1, constants.%Self.2) {
+// CHECK:STDOUT: declaration:
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific file.%Simple.decl(constants.%C) {
+// CHECK:STDOUT: declaration:
+// CHECK:STDOUT:   file.%T.loc4_18.2 => constants.%C
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific file.%WithAssocFn.decl(constants.%C) {
+// CHECK:STDOUT: declaration:
+// CHECK:STDOUT:   file.%T.loc8_23.2 => constants.%C
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific file.%WithImplicitArgs.decl(constants.%T.1, constants.%N) {
+// CHECK:STDOUT: declaration:
+// CHECK:STDOUT:   file.%T.loc22_28.2 => constants.%T.1
+// CHECK:STDOUT:   file.%N.loc22_38.2 => constants.%N
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific file.%Receive.decl(constants.%T.2) {
+// CHECK:STDOUT: declaration:
+// CHECK:STDOUT:   @Receive.%T => constants.%T.2
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific file.%Pass.decl(constants.%T.2) {
+// CHECK:STDOUT: declaration:
+// CHECK:STDOUT:   @Pass.%T => constants.%T.2
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
 // CHECK:STDOUT: --- fail_mismatched_args.carbon
 // CHECK:STDOUT:
 // CHECK:STDOUT: constants {
@@ -244,16 +284,16 @@ fn G(T:! Generic(B)) {
 // CHECK:STDOUT:   %Generic.type: type = generic_interface_type @Generic [template]
 // CHECK:STDOUT:   %.1: type = tuple_type () [template]
 // CHECK:STDOUT:   %Generic: %Generic.type = struct_value () [template]
-// CHECK:STDOUT:   %.2: type = interface_type @Generic, (%T.1) [symbolic]
+// CHECK:STDOUT:   %.2: type = interface_type @Generic, file.%Generic.decl(%T.1) [symbolic]
 // CHECK:STDOUT:   %Self: %.2 = bind_symbolic_name Self 1 [symbolic]
 // CHECK:STDOUT:   %A: type = class_type @A [template]
 // CHECK:STDOUT:   %.3: type = struct_type {} [template]
 // CHECK:STDOUT:   %B: type = class_type @B [template]
-// CHECK:STDOUT:   %.4: type = interface_type @Generic, (%A) [template]
+// CHECK:STDOUT:   %.4: type = interface_type @Generic, file.%Generic.decl(%A) [template]
 // CHECK:STDOUT:   %T.2: %.4 = bind_symbolic_name T 0 [symbolic]
 // CHECK:STDOUT:   %F.type: type = fn_type @F [template]
 // CHECK:STDOUT:   %F: %F.type = struct_value () [template]
-// CHECK:STDOUT:   %.5: type = interface_type @Generic, (%B) [template]
+// CHECK:STDOUT:   %.5: type = interface_type @Generic, file.%Generic.decl(%B) [template]
 // CHECK:STDOUT:   %T.3: %.5 = bind_symbolic_name T 0 [symbolic]
 // CHECK:STDOUT:   %G.type: type = fn_type @G [template]
 // CHECK:STDOUT:   %G: %G.type = struct_value () [template]
@@ -324,3 +364,28 @@ fn G(T:! Generic(B)) {
 // CHECK:STDOUT:   return
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
+// CHECK:STDOUT: specific file.%Generic.decl(constants.%T.1) {
+// CHECK:STDOUT: declaration:
+// CHECK:STDOUT:   file.%T.loc4_19.2 => constants.%T.1
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific file.%Generic.decl(constants.%A) {
+// CHECK:STDOUT: declaration:
+// CHECK:STDOUT:   file.%T.loc4_19.2 => constants.%A
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific file.%F.decl(constants.%T.2) {
+// CHECK:STDOUT: declaration:
+// CHECK:STDOUT:   @F.%T => constants.%T.2
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific file.%Generic.decl(constants.%B) {
+// CHECK:STDOUT: declaration:
+// CHECK:STDOUT:   file.%T.loc4_19.2 => constants.%B
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific file.%G.decl(constants.%T.3) {
+// CHECK:STDOUT: declaration:
+// CHECK:STDOUT:   @G.%T => constants.%T.3
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 10 - 0
toolchain/check/testdata/interface/no_prelude/generic_binding_after_assoc_const.carbon

@@ -71,3 +71,13 @@ interface I {
 // CHECK:STDOUT: fn @G(@I.%T.loc16_8.2: type)
 // CHECK:STDOUT:     generic [@I.%Self: %.1, @I.%T.loc16_8.2: type];
 // CHECK:STDOUT:
+// CHECK:STDOUT: specific @I.%F.decl(constants.%Self, constants.%T) {
+// CHECK:STDOUT: declaration:
+// CHECK:STDOUT:   @I.%T.loc12_8.2 => constants.%T
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @I.%G.decl(constants.%Self, constants.%T) {
+// CHECK:STDOUT: declaration:
+// CHECK:STDOUT:   @I.%T.loc16_8.2 => constants.%T
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 16 - 3
toolchain/check/testdata/interface/no_prelude/generic_import.carbon

@@ -34,7 +34,7 @@ impl C as AddWith(C) {
 // CHECK:STDOUT:   %AddWith.type: type = generic_interface_type @AddWith [template]
 // CHECK:STDOUT:   %.1: type = tuple_type () [template]
 // CHECK:STDOUT:   %AddWith: %AddWith.type = struct_value () [template]
-// CHECK:STDOUT:   %.2: type = interface_type @AddWith, (%T) [symbolic]
+// CHECK:STDOUT:   %.2: type = interface_type @AddWith, file.%AddWith.decl(%T) [symbolic]
 // CHECK:STDOUT:   %Self: %.2 = bind_symbolic_name Self 1 [symbolic]
 // CHECK:STDOUT:   %F.type: type = fn_type @F [template]
 // CHECK:STDOUT:   %F: %F.type = struct_value () [template]
@@ -67,6 +67,15 @@ impl C as AddWith(C) {
 // CHECK:STDOUT: fn @F()
 // CHECK:STDOUT:     generic [file.%T.loc4_19.2: type, @AddWith.%Self: %.2];
 // CHECK:STDOUT:
+// CHECK:STDOUT: specific file.%AddWith.decl(constants.%T) {
+// CHECK:STDOUT: declaration:
+// CHECK:STDOUT:   file.%T.loc4_19.2 => constants.%T
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @AddWith.%F.decl(constants.%T, constants.%Self) {
+// CHECK:STDOUT: declaration:
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
 // CHECK:STDOUT: --- b.carbon
 // CHECK:STDOUT:
 // CHECK:STDOUT: constants {
@@ -76,9 +85,9 @@ 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, <unexpected instref inst+14> [symbolic]
-// CHECK:STDOUT:   %.3: type = interface_type @AddWith, (%T) [symbolic]
+// CHECK:STDOUT:   %.3: type = interface_type @AddWith, <invalid>(%T) [symbolic]
 // CHECK:STDOUT:   %Self: %.3 = bind_symbolic_name Self 1 [symbolic]
-// CHECK:STDOUT:   %.4: type = interface_type @AddWith, (%C) [template]
+// 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]
@@ -138,3 +147,7 @@ impl C as AddWith(C) {
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @F.2();
 // CHECK:STDOUT:
+// CHECK:STDOUT: specific <invalid>(constants.%T);
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific <invalid>(constants.%C);
+// CHECK:STDOUT:

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

@@ -132,6 +132,14 @@ var f: ForwardDeclared* = &f_ref.f;
 // CHECK:STDOUT: fn @F.2()
 // CHECK:STDOUT:     generic [@ForwardDeclared.%Self: %.8];
 // CHECK:STDOUT:
+// CHECK:STDOUT: specific @Basic.%F.decl(constants.%Self.2) {
+// CHECK:STDOUT: declaration:
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @ForwardDeclared.%F.decl(constants.%Self.3) {
+// CHECK:STDOUT: declaration:
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
 // CHECK:STDOUT: --- b.carbon
 // CHECK:STDOUT:
 // CHECK:STDOUT: constants {

+ 5 - 0
toolchain/check/testdata/interface/no_prelude/self.carbon

@@ -55,3 +55,8 @@ interface UseSelf {
 // CHECK:STDOUT: fn @F[@UseSelf.%self.loc12_8.2: @UseSelf.%Self.ref.loc12_14 (%Self)]() -> %Self
 // CHECK:STDOUT:     generic [@UseSelf.%Self: %.1];
 // CHECK:STDOUT:
+// CHECK:STDOUT: specific @UseSelf.%F.decl(constants.%Self) {
+// CHECK:STDOUT: declaration:
+// CHECK:STDOUT:   @UseSelf.%Self.ref.loc12_14 => constants.%Self
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 8 - 0
toolchain/check/testdata/interface/todo_define_not_default.carbon

@@ -121,3 +121,11 @@ interface I {
 // CHECK:STDOUT: fn @G(@I.%a.loc14_8.2: i32, @I.%b.loc14_16.2: i32) -> i32 = "int.sadd"
 // CHECK:STDOUT:     generic [@I.%Self: %.1];
 // CHECK:STDOUT:
+// CHECK:STDOUT: specific @I.%F.decl(constants.%Self) {
+// CHECK:STDOUT: declaration:
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @I.%G.decl(constants.%Self) {
+// CHECK:STDOUT: declaration:
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 5 - 0
toolchain/check/testdata/namespace/fail_params.carbon

@@ -101,3 +101,8 @@ fn D(T:! type).F() {}
 // CHECK:STDOUT:   return
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
+// CHECK:STDOUT: specific file.%.decl(constants.%T) {
+// CHECK:STDOUT: declaration:
+// CHECK:STDOUT:   file.%T.loc39_6.2 => constants.%T
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 16 - 4
toolchain/check/testdata/packages/no_prelude/fail_export_name_params.carbon

@@ -35,10 +35,10 @@ export C2(T:! type);
 // CHECK:STDOUT:   %C1.type: type = generic_class_type @C1 [template]
 // CHECK:STDOUT:   %.1: type = tuple_type () [template]
 // CHECK:STDOUT:   %C1.1: %C1.type = struct_value () [template]
-// CHECK:STDOUT:   %C1.2: type = class_type @C1, (%T) [symbolic]
+// CHECK:STDOUT:   %C1.2: type = class_type @C1, file.%C1.decl(%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, (%T) [symbolic]
+// CHECK:STDOUT:   %C2.2: type = class_type @C2, file.%C2.decl(%T) [symbolic]
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: file {
@@ -62,6 +62,16 @@ export C2(T:! type);
 // CHECK:STDOUT: class @C2
 // CHECK:STDOUT:     generic [file.%T.loc5_10.2: type];
 // CHECK:STDOUT:
+// CHECK:STDOUT: specific file.%C1.decl(constants.%T) {
+// CHECK:STDOUT: declaration:
+// CHECK:STDOUT:   file.%T.loc4_10.2 => constants.%T
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific file.%C2.decl(constants.%T) {
+// CHECK:STDOUT: declaration:
+// CHECK:STDOUT:   file.%T.loc5_10.2 => constants.%T
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
 // CHECK:STDOUT: --- fail_b.carbon
 // CHECK:STDOUT:
 // CHECK:STDOUT: constants {
@@ -69,10 +79,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, <unexpected instref inst+18> [symbolic]
-// CHECK:STDOUT:   %C1.2: type = class_type @C1, (%T) [symbolic]
+// CHECK:STDOUT:   %C1.2: type = class_type @C1, <invalid>(%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, (%T) [symbolic]
+// CHECK:STDOUT:   %C2.2: type = class_type @C2, <invalid>(%T) [symbolic]
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: imports {
@@ -96,3 +106,5 @@ export C2(T:! type);
 // CHECK:STDOUT:
 // CHECK:STDOUT: class @C2;
 // CHECK:STDOUT:
+// CHECK:STDOUT: specific <invalid>(constants.%T);
+// CHECK:STDOUT:

+ 5 - 0
toolchain/check/testdata/return/fail_let_in_type.carbon

@@ -61,3 +61,8 @@ fn FirstPerfectNumber() -> z { return 6; }
 // CHECK:STDOUT:   return <error>
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
+// CHECK:STDOUT: specific <unexpected instref inst+29>(constants.%y) {
+// CHECK:STDOUT: declaration:
+// CHECK:STDOUT:   <unexpected instref inst+27> => constants.%y
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 33 - 9
toolchain/check/testdata/struct/import.carbon

@@ -73,13 +73,13 @@ var c_bad: C({.a = 3, .b = 4}) = F();
 // CHECK:STDOUT:   %S: %.11 = bind_symbolic_name S 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, (%S) [symbolic]
+// CHECK:STDOUT:   %C.2: type = class_type @C, file.%C.decl(%S) [symbolic]
 // CHECK:STDOUT:   %.12: type = struct_type {} [template]
 // CHECK:STDOUT:   %.13: i32 = int_literal 1 [template]
 // CHECK:STDOUT:   %.14: i32 = int_literal 2 [template]
 // CHECK:STDOUT:   %.15: type = ptr_type %.11 [template]
 // CHECK:STDOUT:   %struct.4: %.11 = struct_value (%.13, %.14) [template]
-// CHECK:STDOUT:   %C.3: type = class_type @C, (%struct.4) [template]
+// CHECK:STDOUT:   %C.3: type = class_type @C, file.%C.decl(%struct.4) [template]
 // CHECK:STDOUT:   %F.type: type = fn_type @F [template]
 // CHECK:STDOUT:   %F: %F.type = struct_value () [template]
 // CHECK:STDOUT: }
@@ -189,6 +189,16 @@ var c_bad: C({.a = 3, .b = 4}) = F();
 // CHECK:STDOUT:   return
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
+// CHECK:STDOUT: specific file.%C.decl(constants.%S) {
+// CHECK:STDOUT: declaration:
+// CHECK:STDOUT:   file.%S.loc8_9.2 => constants.%S
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific file.%C.decl(constants.%struct.4) {
+// CHECK:STDOUT: declaration:
+// CHECK:STDOUT:   file.%S.loc8_9.2 => constants.%struct.4
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
 // CHECK:STDOUT: --- implicit.impl.carbon
 // CHECK:STDOUT:
 // CHECK:STDOUT: constants {
@@ -208,12 +218,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, <unexpected instref inst+103> [symbolic]
-// CHECK:STDOUT:   %C.2: type = class_type @C, (%S) [symbolic]
+// CHECK:STDOUT:   %C.2: type = class_type @C, <invalid>(%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, (%struct) [template]
+// CHECK:STDOUT:   %C.3: type = class_type @C, <invalid>(%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]
@@ -327,6 +337,10 @@ var c_bad: C({.a = 3, .b = 4}) = F();
 // CHECK:STDOUT:   return
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
+// CHECK:STDOUT: specific <invalid>(constants.%S);
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific <invalid>(constants.%struct);
+// CHECK:STDOUT:
 // CHECK:STDOUT: --- fail_bad_type.impl.carbon
 // CHECK:STDOUT:
 // CHECK:STDOUT: constants {
@@ -336,13 +350,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, <unexpected instref inst+21> [symbolic]
-// CHECK:STDOUT:   %C.2: type = class_type @C, (%S) [symbolic]
+// CHECK:STDOUT:   %C.2: type = class_type @C, <invalid>(%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, (%struct) [template]
+// CHECK:STDOUT:   %C.3: type = class_type @C, <invalid>(%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]
@@ -396,6 +410,10 @@ var c_bad: C({.a = 3, .b = 4}) = F();
 // CHECK:STDOUT:   return
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
+// CHECK:STDOUT: specific <invalid>(constants.%S);
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific <invalid>(constants.%struct);
+// CHECK:STDOUT:
 // CHECK:STDOUT: --- fail_bad_value.impl.carbon
 // CHECK:STDOUT:
 // CHECK:STDOUT: constants {
@@ -405,17 +423,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, <unexpected instref inst+21> [symbolic]
-// CHECK:STDOUT:   %C.2: type = class_type @C, (%S) [symbolic]
+// CHECK:STDOUT:   %C.2: type = class_type @C, <invalid>(%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, (%struct.1) [template]
+// CHECK:STDOUT:   %C.3: type = class_type @C, <invalid>(%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, (%struct.2) [template]
+// CHECK:STDOUT:   %C.4: type = class_type @C, <invalid>(%struct.2) [template]
 // CHECK:STDOUT:   %F.type: type = fn_type @F [template]
 // CHECK:STDOUT:   %F: %F.type = struct_value () [template]
 // CHECK:STDOUT: }
@@ -470,3 +488,9 @@ var c_bad: C({.a = 3, .b = 4}) = F();
 // CHECK:STDOUT:   return
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
+// CHECK:STDOUT: specific <invalid>(constants.%S);
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific <invalid>(constants.%struct.1);
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific <invalid>(constants.%struct.2);
+// CHECK:STDOUT:

+ 33 - 9
toolchain/check/testdata/tuples/import.carbon

@@ -81,10 +81,10 @@ var c_bad: C((3, 4)) = F();
 // CHECK:STDOUT:   %X: %.9 = bind_symbolic_name X 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, (%X) [symbolic]
+// CHECK:STDOUT:   %C.2: type = class_type @C, file.%C.decl(%X) [symbolic]
 // CHECK:STDOUT:   %.18: type = struct_type {} [template]
 // CHECK:STDOUT:   %tuple.5: %.9 = tuple_value (%.15, %.16) [template]
-// CHECK:STDOUT:   %C.3: type = class_type @C, (%tuple.5) [template]
+// CHECK:STDOUT:   %C.3: type = class_type @C, file.%C.decl(%tuple.5) [template]
 // CHECK:STDOUT:   %F.type: type = fn_type @F [template]
 // CHECK:STDOUT:   %F: %F.type = struct_value () [template]
 // CHECK:STDOUT: }
@@ -211,6 +211,16 @@ var c_bad: C((3, 4)) = F();
 // CHECK:STDOUT:   return
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
+// CHECK:STDOUT: specific file.%C.decl(constants.%X) {
+// CHECK:STDOUT: declaration:
+// CHECK:STDOUT:   file.%X.loc7_9.2 => constants.%X
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific file.%C.decl(constants.%tuple.5) {
+// CHECK:STDOUT: declaration:
+// CHECK:STDOUT:   file.%X.loc7_9.2 => constants.%tuple.5
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
 // CHECK:STDOUT: --- implicit.impl.carbon
 // CHECK:STDOUT:
 // CHECK:STDOUT: constants {
@@ -233,11 +243,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, <unexpected instref inst+105> [symbolic]
-// CHECK:STDOUT:   %C.2: type = class_type @C, (%X) [symbolic]
+// CHECK:STDOUT:   %C.2: type = class_type @C, <invalid>(%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, (%tuple) [template]
+// CHECK:STDOUT:   %C.3: type = class_type @C, <invalid>(%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]
@@ -368,6 +378,10 @@ var c_bad: C((3, 4)) = F();
 // CHECK:STDOUT:   return
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
+// CHECK:STDOUT: specific <invalid>(constants.%X);
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific <invalid>(constants.%tuple);
+// CHECK:STDOUT:
 // CHECK:STDOUT: --- fail_bad_type.impl.carbon
 // CHECK:STDOUT:
 // CHECK:STDOUT: constants {
@@ -377,14 +391,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, <unexpected instref inst+17> [symbolic]
-// CHECK:STDOUT:   %C.2: type = class_type @C, (%X) [symbolic]
+// CHECK:STDOUT:   %C.2: type = class_type @C, <invalid>(%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, (%tuple) [template]
+// CHECK:STDOUT:   %C.3: type = class_type @C, <invalid>(%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]
@@ -439,6 +453,10 @@ var c_bad: C((3, 4)) = F();
 // CHECK:STDOUT:   return
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
+// CHECK:STDOUT: specific <invalid>(constants.%X);
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific <invalid>(constants.%tuple);
+// CHECK:STDOUT:
 // CHECK:STDOUT: --- fail_bad_value.impl.carbon
 // CHECK:STDOUT:
 // CHECK:STDOUT: constants {
@@ -448,17 +466,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, <unexpected instref inst+17> [symbolic]
-// CHECK:STDOUT:   %C.2: type = class_type @C, (%X) [symbolic]
+// CHECK:STDOUT:   %C.2: type = class_type @C, <invalid>(%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, (%tuple.1) [template]
+// CHECK:STDOUT:   %C.3: type = class_type @C, <invalid>(%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, (%tuple.2) [template]
+// CHECK:STDOUT:   %C.4: type = class_type @C, <invalid>(%tuple.2) [template]
 // CHECK:STDOUT:   %F.type: type = fn_type @F [template]
 // CHECK:STDOUT:   %F: %F.type = struct_value () [template]
 // CHECK:STDOUT: }
@@ -513,3 +531,9 @@ var c_bad: C((3, 4)) = F();
 // CHECK:STDOUT:   return
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
+// CHECK:STDOUT: specific <invalid>(constants.%X);
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific <invalid>(constants.%tuple.1);
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific <invalid>(constants.%tuple.2);
+// CHECK:STDOUT:

+ 68 - 4
toolchain/sem_ir/formatter.cpp

@@ -74,6 +74,10 @@ class Formatter {
       FormatFunction(FunctionId(i));
     }
 
+    for (int i : llvm::seq(sem_ir_.generic_instances().size())) {
+      FormatSpecific(GenericInstanceId(i));
+    }
+
     // End-of-file newline.
     out_ << "\n";
   }
@@ -327,6 +331,55 @@ class Formatter {
     // blocks that don't duplicate portions of the generic body.
   }
 
+  auto FormatSpecificRegion(const Generic& generic,
+                            const GenericInstance& specific,
+                            GenericInstIndex::Region region,
+                            llvm::StringRef region_name) -> void {
+    if (!specific.GetValueBlock(region).is_valid()) {
+      return;
+    }
+
+    IndentLabel();
+    out_ << region_name << ":\n";
+    for (auto [generic_inst_id, specific_inst_id] :
+         llvm::zip(sem_ir_.inst_blocks().Get(generic.GetEvalBlock(region)),
+                   sem_ir_.inst_blocks().Get(specific.GetValueBlock(region)))) {
+      Indent();
+      FormatInstName(generic_inst_id);
+      out_ << " => ";
+      FormatInstName(specific_inst_id);
+      out_ << "\n";
+    }
+  }
+
+  auto FormatSpecific(GenericInstanceId id) -> void {
+    const auto& specific = sem_ir_.generic_instances().Get(id);
+
+    out_ << "\n";
+
+    out_ << "specific ";
+    FormatSpecificName(id);
+
+    // TODO: Remove once we stop forming generic specifics with no generic
+    // during import.
+    if (!specific.generic_id.is_valid()) {
+      out_ << ";\n";
+      return;
+    }
+    out_ << " ";
+
+    const auto& generic = sem_ir_.generics().Get(specific.generic_id);
+
+    OpenBrace();
+    FormatSpecificRegion(generic, specific,
+                         GenericInstIndex::Region::Declaration, "declaration");
+    FormatSpecificRegion(generic, specific,
+                         GenericInstIndex::Region::Definition, "definition");
+    CloseBrace();
+
+    out_ << "\n";
+  }
+
   auto FormatParamList(InstBlockId param_refs_id) -> void {
     llvm::ListSeparator sep;
     for (InstId param_id : sem_ir_.inst_blocks().Get(param_refs_id)) {
@@ -816,10 +869,7 @@ class Formatter {
     out_ << ')';
   }
 
-  auto FormatArg(GenericInstanceId id) -> void {
-    const auto& instance = sem_ir_.generic_instances().Get(id);
-    FormatArg(instance.args_id);
-  }
+  auto FormatArg(GenericInstanceId id) -> void { FormatSpecificName(id); }
 
   auto FormatArg(RealId id) -> void {
     // TODO: Format with a `.` when the exponent is near zero.
@@ -880,6 +930,20 @@ class Formatter {
 
   auto FormatImplName(ImplId id) -> void { out_ << inst_namer_.GetNameFor(id); }
 
+  auto FormatSpecificName(GenericInstanceId id) -> void {
+    const auto& specific = sem_ir_.generic_instances().Get(id);
+    // TODO: We don't yet import generics properly, and instead form specifics
+    // with an invalid generic ID. In this case, just print a placeholder for
+    // now. Once import works, we can remove this code.
+    if (!specific.generic_id.is_valid()) {
+      out_ << "<invalid>";
+    } else {
+      const Generic& generic = sem_ir_.generics().Get(specific.generic_id);
+      FormatInstName(generic.decl_id);
+    }
+    FormatArg(specific.args_id);
+  }
+
   auto FormatConstant(ConstantId id) -> void {
     if (!id.is_valid()) {
       out_ << "<not constant>";

+ 33 - 7
toolchain/sem_ir/generic.h

@@ -19,6 +19,15 @@ struct Generic : public Printable<Generic> {
     out << "{decl: " << decl_id << ", bindings: " << bindings_id << "}";
   }
 
+  // Returns the eval block for the specified region of the generic. This is a
+  // block of instructions that should be evaluated to compute the values and
+  // instructions needed by that region of the generic.
+  auto GetEvalBlock(GenericInstIndex::Region region) const -> InstBlockId {
+    return region == GenericInstIndex::Region::Declaration
+               ? decl_block_id
+               : definition_block_id;
+  }
+
   // The following members always have values, and do not change throughout the
   // lifetime of the generic.
 
@@ -36,9 +45,10 @@ struct Generic : public Printable<Generic> {
   // The following members are set at the end of the corresponding region of the
   // generic.
 
-  // A block of instructions that should be evaluated to compute the values and
-  // instructions needed by the declaration of the generic.
+  // The eval block for the declaration region of the generic.
   InstBlockId decl_block_id = InstBlockId::Invalid;
+  // The eval block for the definition region of the generic.
+  InstBlockId definition_block_id = InstBlockId::Invalid;
 };
 
 // Provides storage for generics.
@@ -61,18 +71,29 @@ struct GenericInstance : Printable<GenericInstance> {
     out << "{generic: " << generic_id << ", args: " << args_id << "}";
   }
 
+  // Returns the value block for this region of the specific. This is a block
+  // containing values and instructions produced by evaluating the corresponding
+  // eval block of the generic within the context of this specific. These are
+  // the constant values and types and the instantiated template-dependent
+  // instructions that are used in this region of the specific.
+  auto GetValueBlock(GenericInstIndex::Region region) const -> InstBlockId {
+    return region == GenericInstIndex::Region::Declaration
+               ? decl_block_id
+               : definition_block_id;
+  }
+
   // The generic that this is an instance of.
   GenericId generic_id;
   // Argument values, corresponding to the bindings in `Generic::bindings_id`.
   InstBlockId args_id;
 
-  // The following members are set when the corresponding region of the generic
-  // instance is resolved.
+  // The following members are set when the corresponding region of the specific
+  // is resolved.
 
-  // The values and instructions produced by evaluating the decl block of the
-  // generic. These are the constant values and types and the instantiated
-  // template-dependent instructions needed by the declaration of this instance.
+  // The value block for the declaration region of the specific.
   InstBlockId decl_block_id = InstBlockId::Invalid;
+  // The value block for the definition region of the specific.
+  InstBlockId definition_block_id = InstBlockId::Invalid;
 };
 
 // Provides storage for deduplicated instances of generics.
@@ -99,6 +120,11 @@ class GenericInstanceStore : public Yaml::Printable<GenericInstanceStore> {
     return generic_instances_.OutputYaml();
   }
 
+  auto array_ref() const -> llvm::ArrayRef<GenericInstance> {
+    return generic_instances_.array_ref();
+  }
+  auto size() const -> size_t { return generic_instances_.size(); }
+
  private:
   // Context for hashing keys.
   class KeyContext;