Преглед изворни кода

Produce a note indicating where the specific was used from if monomorphization fails. (#4662)

Also fix a bug in `Context::GetClassType` that previously tried to
complete the class type before returning it. That's not correct --
`GetCompleteTypeImpl` is only appropriate for cases where the type can
trivially be completed and completing it can't fail -- and led to
infinite recursion with this change because we would call `GetClassType`
when producing a diagnostic if completing that class type failed.
Richard Smith пре 1 година
родитељ
комит
758b6c42ba

+ 4 - 1
toolchain/check/check_unit.cpp

@@ -402,7 +402,10 @@ auto CheckUnit::CheckRequiredDefinitions() -> void {
         CARBON_FATAL("TODO: Support interfaces in DiagnoseMissingDefinitions");
       }
       case CARBON_KIND(SemIR::SpecificFunction specific_function): {
-        if (!ResolveSpecificDefinition(context_,
+        // TODO: Track a location for the use. In general we may want to track a
+        // list of enclosing locations if this was used from a generic.
+        SemIRLoc use_loc = decl_inst_id;
+        if (!ResolveSpecificDefinition(context_, use_loc,
                                        specific_function.specific_id)) {
           CARBON_DIAGNOSTIC(MissingGenericFunctionDefinition, Error,
                             "use of undefined generic function");

+ 20 - 43
toolchain/check/context.cpp

@@ -228,47 +228,21 @@ auto Context::DiagnoseNameNotFound(SemIRLoc loc, SemIR::NameId name_id)
   emitter_->Emit(loc, NameNotFound, name_id);
 }
 
-// Given an instruction associated with a scope and a `SpecificId` for that
-// scope, returns an instruction that describes the specific scope.
-static auto GetInstForSpecificScope(Context& context, SemIR::InstId inst_id,
-                                    SemIR::SpecificId specific_id)
-    -> SemIR::InstId {
-  if (!specific_id.is_valid()) {
-    return inst_id;
-  }
-  auto inst = context.insts().Get(inst_id);
-  CARBON_KIND_SWITCH(inst) {
-    case CARBON_KIND(SemIR::ClassDecl class_decl): {
-      return context.types().GetInstId(
-          context.GetClassType(class_decl.class_id, specific_id));
-    }
-    case CARBON_KIND(SemIR::InterfaceDecl interface_decl): {
-      return context.types().GetInstId(
-          context.GetInterfaceType(interface_decl.interface_id, specific_id));
-    }
-    default: {
-      // Don't know how to form a specific for this generic scope.
-      // TODO: Handle more cases.
-      return SemIR::InstId::Invalid;
-    }
-  }
-}
-
 auto Context::DiagnoseMemberNameNotFound(
     SemIRLoc loc, SemIR::NameId name_id,
     llvm::ArrayRef<LookupScope> lookup_scopes) -> void {
   if (lookup_scopes.size() == 1 &&
       lookup_scopes.front().name_scope_id.is_valid()) {
-    const auto& scope = name_scopes().Get(lookup_scopes.front().name_scope_id);
-    if (auto specific_inst_id = GetInstForSpecificScope(
-            *this, scope.inst_id(), lookup_scopes.front().specific_id);
-        specific_inst_id.is_valid()) {
-      CARBON_DIAGNOSTIC(MemberNameNotFoundInScope, Error,
-                        "member name `{0}` not found in {1}", SemIR::NameId,
-                        InstIdAsType);
-      emitter_->Emit(loc, MemberNameNotFoundInScope, name_id, specific_inst_id);
-      return;
-    }
+    auto specific_id = lookup_scopes.front().specific_id;
+    auto scope_inst_id =
+        specific_id.is_valid()
+            ? GetInstForSpecific(*this, specific_id)
+            : name_scopes().Get(lookup_scopes.front().name_scope_id).inst_id();
+    CARBON_DIAGNOSTIC(MemberNameNotFoundInScope, Error,
+                      "member name `{0}` not found in {1}", SemIR::NameId,
+                      InstIdAsType);
+    emitter_->Emit(loc, MemberNameNotFoundInScope, name_id, scope_inst_id);
+    return;
   }
 
   CARBON_DIAGNOSTIC(MemberNameNotFound, Error, "member name `{0}` not found",
@@ -895,8 +869,9 @@ namespace {
 //   complete.
 class TypeCompleter {
  public:
-  TypeCompleter(Context& context, Context::BuildDiagnosticFn diagnoser)
-      : context_(context), diagnoser_(diagnoser) {}
+  TypeCompleter(Context& context, SemIRLoc loc,
+                Context::BuildDiagnosticFn diagnoser)
+      : context_(context), loc_(loc), diagnoser_(diagnoser) {}
 
   // Attempts to complete the given type. Returns true if it is now complete,
   // false if it could not be completed.
@@ -1009,7 +984,7 @@ class TypeCompleter {
           return false;
         }
         if (inst.specific_id.is_valid()) {
-          ResolveSpecificDefinition(context_, inst.specific_id);
+          ResolveSpecificDefinition(context_, loc_, inst.specific_id);
         }
         if (auto adapted_type_id =
                 class_info.GetAdaptedType(context_.sem_ir(), inst.specific_id);
@@ -1281,19 +1256,21 @@ class TypeCompleter {
 
   Context& context_;
   llvm::SmallVector<WorkItem> work_list_;
+  SemIRLoc loc_;
   Context::BuildDiagnosticFn diagnoser_;
 };
 }  // namespace
 
 auto Context::TryToCompleteType(SemIR::TypeId type_id) -> bool {
-  return TypeCompleter(*this, nullptr).Complete(type_id);
+  // TODO: We need a location here in case we need to instantiate a class type.
+  return TypeCompleter(*this, SemIR::LocId::Invalid, nullptr).Complete(type_id);
 }
 
 auto Context::RequireCompleteType(SemIR::TypeId type_id, SemIR::LocId loc_id,
                                   BuildDiagnosticFn diagnoser) -> bool {
   CARBON_CHECK(diagnoser);
 
-  if (!TypeCompleter(*this, diagnoser).Complete(type_id)) {
+  if (!TypeCompleter(*this, loc_id, diagnoser).Complete(type_id)) {
     return false;
   }
 
@@ -1358,7 +1335,7 @@ auto Context::RequireDefinedType(SemIR::TypeId type_id, SemIR::LocId loc_id,
       }
 
       if (interface.specific_id.is_valid()) {
-        ResolveSpecificDefinition(*this, interface.specific_id);
+        ResolveSpecificDefinition(*this, loc_id, interface.specific_id);
       }
     }
     // TODO: Finish facet type resolution.
@@ -1443,7 +1420,7 @@ auto Context::GetSingletonType(SemIR::InstId singleton_id) -> SemIR::TypeId {
 
 auto Context::GetClassType(SemIR::ClassId class_id,
                            SemIR::SpecificId specific_id) -> SemIR::TypeId {
-  return GetCompleteTypeImpl<SemIR::ClassType>(*this, class_id, specific_id);
+  return GetTypeImpl<SemIR::ClassType>(*this, class_id, specific_id);
 }
 
 auto Context::GetFunctionType(SemIR::FunctionId fn_id,

+ 6 - 5
toolchain/check/decl_name_stack.cpp

@@ -115,7 +115,8 @@ auto DeclNameStack::Restore(SuspendedName sus) -> void {
     // Reattempt to resolve the definition of the specific. The generic might
     // have been defined after we suspended this scope.
     if (suspended_scope.entry.specific_id.is_valid()) {
-      ResolveSpecificDefinition(*context_, suspended_scope.entry.specific_id);
+      ResolveSpecificDefinition(*context_, sus.name_context.loc_id,
+                                suspended_scope.entry.specific_id);
     }
 
     context_->scope_stack().Restore(std::move(suspended_scope));
@@ -192,7 +193,7 @@ auto DeclNameStack::LookupOrAddName(NameContext name_context,
 // `fn Class(T:! type).F(n: i32)` we will push the scope for `Class(T:! type)`
 // between the scope containing the declaration of `T` and the scope
 // containing the declaration of `n`.
-static auto PushNameQualifierScope(Context& context,
+static auto PushNameQualifierScope(Context& context, SemIRLoc loc,
                                    SemIR::InstId scope_inst_id,
                                    SemIR::NameScopeId scope_id,
                                    SemIR::SpecificId specific_id,
@@ -203,7 +204,7 @@ static auto PushNameQualifierScope(Context& context,
 
   // When declaring a member of a generic, resolve the self specific.
   if (specific_id.is_valid()) {
-    ResolveSpecificDefinition(context, specific_id);
+    ResolveSpecificDefinition(context, loc, specific_id);
   }
 
   context.scope_stack().Push(scope_inst_id, scope_id, specific_id, has_error);
@@ -229,8 +230,8 @@ auto DeclNameStack::ApplyNameQualifier(const NameComponent& name) -> void {
   // Resolve the qualifier as a scope and enter the new scope.
   auto [scope_id, specific_id] = ResolveAsScope(name_context, name);
   if (scope_id.is_valid()) {
-    PushNameQualifierScope(*context_, name_context.resolved_inst_id, scope_id,
-                           specific_id,
+    PushNameQualifierScope(*context_, name_context.loc_id,
+                           name_context.resolved_inst_id, scope_id, specific_id,
                            context_->name_scopes().Get(scope_id).has_error());
     name_context.parent_scope_id = scope_id;
   } else {

+ 1 - 1
toolchain/check/deduce.cpp

@@ -505,7 +505,7 @@ auto DeductionContext::MakeSpecific() -> SemIR::SpecificId {
   // TODO: Convert the deduced values to the types of the bindings.
 
   return Check::MakeSpecific(
-      context(), generic_id_,
+      context(), loc_id_, generic_id_,
       context().inst_blocks().AddCanonical(result_arg_ids_));
 }
 

+ 51 - 16
toolchain/check/eval.cpp

@@ -31,13 +31,32 @@ struct SpecificEvalInfo {
 class EvalContext {
  public:
   explicit EvalContext(
-      Context& context,
+      Context& context, SemIRLoc fallback_loc,
       SemIR::SpecificId specific_id = SemIR::SpecificId::Invalid,
       std::optional<SpecificEvalInfo> specific_eval_info = std::nullopt)
       : context_(context),
+        fallback_loc_(fallback_loc),
         specific_id_(specific_id),
         specific_eval_info_(specific_eval_info) {}
 
+  // Gets the location to use for diagnostics if a better location is
+  // unavailable.
+  // TODO: This is also sometimes unavailable.
+  auto fallback_loc() const -> SemIRLoc { return fallback_loc_; }
+
+  // Returns a location to use to point at an instruction in a diagnostic, given
+  // a list of instructions that might have an attached location. This is the
+  // location of the first instruction in the list that has a location if there
+  // is one, and otherwise the fallback location.
+  auto GetDiagnosticLoc(llvm::ArrayRef<SemIR::InstId> inst_ids) -> SemIRLoc {
+    for (auto inst_id : inst_ids) {
+      if (inst_id.is_valid() && context_.insts().GetLocId(inst_id).is_valid()) {
+        return inst_id;
+      }
+    }
+    return fallback_loc_;
+  }
+
   // Gets the value of the specified compile-time binding in this context.
   // Returns `Invalid` if the value is not fixed in this context.
   auto GetCompileTimeBindValue(SemIR::CompileTimeBindIndex bind_index)
@@ -161,6 +180,8 @@ class EvalContext {
  private:
   // The type-checking context in which we're performing evaluation.
   Context& context_;
+  // The location to use for diagnostics when a better location isn't available.
+  SemIRLoc fallback_loc_;
   // The specific that we are evaluating within.
   SemIR::SpecificId specific_id_;
   // If we are currently evaluating an eval block for `specific_id_`,
@@ -419,7 +440,8 @@ static auto GetConstantValue(EvalContext& eval_context,
   if (args_id == specific.args_id) {
     return specific_id;
   }
-  return MakeSpecific(eval_context.context(), specific.generic_id, args_id);
+  return MakeSpecific(eval_context.context(), eval_context.fallback_loc(),
+                      specific.generic_id, args_id);
 }
 
 // Like `GetConstantValue` but does a `FacetTypeId` -> `FacetTypeInfo`
@@ -602,7 +624,7 @@ static auto PerformArrayIndex(EvalContext& eval_context, SemIR::ArrayIndex inst)
                           "array index `{0}` is past the end of type {1}",
                           TypedInt, SemIR::TypeId);
         eval_context.emitter().Emit(
-            inst.index_id, ArrayIndexOutOfBounds,
+            eval_context.GetDiagnosticLoc(inst.index_id), ArrayIndexOutOfBounds,
             {.type = index->type_id, .value = index_val}, aggregate_type_id);
         return SemIR::ErrorInst::SingletonConstantId;
       }
@@ -1336,7 +1358,7 @@ static auto TryEvalInstInContext(EvalContext& eval_context,
               CARBON_DIAGNOSTIC(ArrayBoundNegative, Error,
                                 "array bound of {0} is negative", TypedInt);
               eval_context.emitter().Emit(
-                  bound_id, ArrayBoundNegative,
+                  eval_context.GetDiagnosticLoc(bound_id), ArrayBoundNegative,
                   {.type = int_bound->type_id, .value = bound_val});
               return false;
             }
@@ -1344,7 +1366,7 @@ static auto TryEvalInstInContext(EvalContext& eval_context,
               CARBON_DIAGNOSTIC(ArrayBoundTooLarge, Error,
                                 "array bound of {0} is too large", TypedInt);
               eval_context.emitter().Emit(
-                  bound_id, ArrayBoundTooLarge,
+                  eval_context.GetDiagnosticLoc(bound_id), ArrayBoundTooLarge,
                   {.type = int_bound->type_id, .value = bound_val});
               return false;
             }
@@ -1393,7 +1415,8 @@ static auto TryEvalInstInContext(EvalContext& eval_context,
           [&](SemIR::IntType result) {
             return ValidateIntType(
                 eval_context.context(),
-                inst_id.is_valid() ? inst_id : int_type.bit_width_id, result);
+                eval_context.GetDiagnosticLoc({inst_id, int_type.bit_width_id}),
+                result);
           },
           &SemIR::IntType::bit_width_id);
     }
@@ -1405,7 +1428,9 @@ static auto TryEvalInstInContext(EvalContext& eval_context,
           eval_context, inst,
           [&](SemIR::FloatType result) {
             return ValidateFloatType(eval_context.context(),
-                                     float_type.bit_width_id, result);
+                                     eval_context.GetDiagnosticLoc(
+                                         {inst_id, float_type.bit_width_id}),
+                                     result);
           },
           &SemIR::FloatType::bit_width_id);
     }
@@ -1568,7 +1593,8 @@ static auto TryEvalInstInContext(EvalContext& eval_context,
     }
 
     case CARBON_KIND(SemIR::Call call): {
-      return MakeConstantForCall(eval_context, inst_id, call);
+      return MakeConstantForCall(eval_context,
+                                 eval_context.GetDiagnosticLoc(inst_id), call);
     }
 
     // TODO: These need special handling.
@@ -1785,7 +1811,8 @@ static auto TryEvalInstInContext(EvalContext& eval_context,
                                 "{0} evaluates to incomplete type {1}",
                                 SemIR::TypeId, SemIR::TypeId);
               return eval_context.emitter().Build(
-                  inst_id, IncompleteTypeInMonomorphization,
+                  eval_context.GetDiagnosticLoc(inst_id),
+                  IncompleteTypeInMonomorphization,
                   require_complete.complete_type_id, complete_type_id);
             });
         if (complete_type_id == SemIR::ErrorInst::SingletonTypeId) {
@@ -1842,11 +1869,12 @@ static auto TryEvalInstInContext(EvalContext& eval_context,
 
 auto TryEvalInst(Context& context, SemIR::InstId inst_id, SemIR::Inst inst)
     -> SemIR::ConstantId {
-  EvalContext eval_context(context);
+  EvalContext eval_context(context, inst_id);
   return TryEvalInstInContext(eval_context, inst_id, inst);
 }
 
-auto TryEvalBlockForSpecific(Context& context, SemIR::SpecificId specific_id,
+auto TryEvalBlockForSpecific(Context& context, SemIRLoc loc,
+                             SemIR::SpecificId specific_id,
                              SemIR::GenericInstIndex::Region region)
     -> SemIR::InstBlockId {
   auto generic_id = context.specifics().Get(specific_id).generic_id;
@@ -1856,20 +1884,27 @@ auto TryEvalBlockForSpecific(Context& context, SemIR::SpecificId specific_id,
   llvm::SmallVector<SemIR::InstId> result;
   result.resize(eval_block.size(), SemIR::InstId::Invalid);
 
-  EvalContext eval_context(context, specific_id,
+  EvalContext eval_context(context, loc, specific_id,
                            SpecificEvalInfo{
                                .region = region,
                                .values = result,
                            });
 
+  DiagnosticAnnotationScope annotate_diagnostics(
+      &context.emitter(), [&](auto& builder) {
+        CARBON_DIAGNOSTIC(ResolvingSpecificHere, Note, "in {0} used here",
+                          InstIdAsType);
+        if (loc.is_inst_id && !loc.inst_id.is_valid()) {
+          return;
+        }
+        builder.Note(loc, ResolvingSpecificHere,
+                     GetInstForSpecific(context, specific_id));
+      });
+
   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::ErrorInst::SingletonInstId` in the table
-    // entry.
     CARBON_CHECK(result[i].is_valid());
   }
 

+ 2 - 1
toolchain/check/eval.h

@@ -20,7 +20,8 @@ auto TryEvalInst(Context& context, SemIR::InstId inst_id, SemIR::Inst inst)
 // 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::SpecificId specific_id,
+auto TryEvalBlockForSpecific(Context& context, SemIRLoc loc,
+                             SemIR::SpecificId specific_id,
                              SemIR::GenericInstIndex::Region region)
     -> SemIR::InstBlockId;
 

+ 43 - 10
toolchain/check/generic.cpp

@@ -5,11 +5,13 @@
 #include "toolchain/check/generic.h"
 
 #include "common/map.h"
+#include "toolchain/base/kind_switch.h"
 #include "toolchain/check/eval.h"
 #include "toolchain/check/generic_region_stack.h"
 #include "toolchain/check/subst.h"
 #include "toolchain/sem_ir/ids.h"
 #include "toolchain/sem_ir/inst.h"
+#include "toolchain/sem_ir/typed_insts.h"
 
 namespace Carbon::Check {
 
@@ -341,7 +343,7 @@ auto FinishGenericDecl(Context& context, SemIR::InstId decl_id)
   context.generic_region_stack().Pop();
   context.generics().Get(generic_id).decl_block_id = decl_block_id;
 
-  auto self_specific_id = MakeSelfSpecific(context, generic_id);
+  auto self_specific_id = MakeSelfSpecific(context, decl_id, generic_id);
   context.generics().Get(generic_id).self_specific_id = self_specific_id;
   return generic_id;
 }
@@ -371,15 +373,16 @@ auto FinishGenericDefinition(Context& context, SemIR::GenericId generic_id)
   context.generic_region_stack().Pop();
 }
 
-auto MakeSpecific(Context& context, SemIR::GenericId generic_id,
+auto MakeSpecific(Context& context, SemIRLoc loc, SemIR::GenericId generic_id,
                   SemIR::InstBlockId args_id) -> SemIR::SpecificId {
   auto specific_id = context.specifics().GetOrAdd(generic_id, args_id);
 
   // If this is the first time we've formed this specific, evaluate its decl
   // block to form information about the specific.
   if (!context.specifics().Get(specific_id).decl_block_id.is_valid()) {
-    auto decl_block_id = TryEvalBlockForSpecific(
-        context, specific_id, SemIR::GenericInstIndex::Region::Declaration);
+    auto decl_block_id =
+        TryEvalBlockForSpecific(context, loc, specific_id,
+                                SemIR::GenericInstIndex::Region::Declaration);
     // Note that TryEvalBlockForSpecific may reallocate the list of specifics,
     // so re-lookup the specific here.
     context.specifics().Get(specific_id).decl_block_id = decl_block_id;
@@ -388,8 +391,8 @@ auto MakeSpecific(Context& context, SemIR::GenericId generic_id,
   return specific_id;
 }
 
-auto MakeSelfSpecific(Context& context, SemIR::GenericId generic_id)
-    -> SemIR::SpecificId {
+auto MakeSelfSpecific(Context& context, SemIRLoc loc,
+                      SemIR::GenericId generic_id) -> SemIR::SpecificId {
   if (!generic_id.is_valid()) {
     return SemIR::SpecificId::Invalid;
   }
@@ -409,11 +412,11 @@ auto MakeSelfSpecific(Context& context, SemIR::GenericId generic_id)
   // TODO: This could be made more efficient. We don't need to perform
   // substitution here; we know we want identity mappings for all constants and
   // types. We could also consider not storing the mapping at all in this case.
-  return MakeSpecific(context, generic_id, args_id);
+  return MakeSpecific(context, loc, generic_id, args_id);
 }
 
-auto ResolveSpecificDefinition(Context& context, SemIR::SpecificId specific_id)
-    -> bool {
+auto ResolveSpecificDefinition(Context& context, SemIRLoc loc,
+                               SemIR::SpecificId specific_id) -> bool {
   auto& specific = context.specifics().Get(specific_id);
   auto generic_id = specific.generic_id;
   CARBON_CHECK(generic_id.is_valid(), "Specific with no generic ID");
@@ -426,7 +429,7 @@ auto ResolveSpecificDefinition(Context& context, SemIR::SpecificId specific_id)
       return false;
     }
     auto definition_block_id = TryEvalBlockForSpecific(
-        context, specific_id, SemIR::GenericInstIndex::Region::Definition);
+        context, loc, specific_id, SemIR::GenericInstIndex::Region::Definition);
     // Note that TryEvalBlockForSpecific may reallocate the list of specifics,
     // so re-lookup the specific here.
     context.specifics().Get(specific_id).definition_block_id =
@@ -435,4 +438,34 @@ auto ResolveSpecificDefinition(Context& context, SemIR::SpecificId specific_id)
   return true;
 }
 
+auto GetInstForSpecific(Context& context, SemIR::SpecificId specific_id)
+    -> SemIR::InstId {
+  CARBON_CHECK(specific_id.is_valid());
+  const auto& specific = context.specifics().Get(specific_id);
+  const auto& generic = context.generics().Get(specific.generic_id);
+  auto decl = context.insts().Get(generic.decl_id);
+  CARBON_KIND_SWITCH(decl) {
+    case CARBON_KIND(SemIR::ClassDecl class_decl): {
+      return context.types().GetInstId(
+          context.GetClassType(class_decl.class_id, specific_id));
+    }
+    case CARBON_KIND(SemIR::InterfaceDecl interface_decl): {
+      return context.types().GetInstId(
+          context.GetInterfaceType(interface_decl.interface_id, specific_id));
+    }
+    case SemIR::FunctionDecl::Kind: {
+      return context.constant_values().GetInstId(
+          TryEvalInst(context, SemIR::InstId::Invalid,
+                      SemIR::SpecificFunction{
+                          .type_id = context.GetSingletonType(
+                              SemIR::SpecificFunctionType::SingletonInstId),
+                          .callee_id = generic.decl_id,
+                          .specific_id = specific_id}));
+    }
+    default: {
+      CARBON_FATAL("Unknown kind for generic declaration {0}", decl);
+    }
+  }
+}
+
 }  // namespace Carbon::Check

+ 12 - 7
toolchain/check/generic.h

@@ -49,29 +49,34 @@ auto RebuildGenericEvalBlock(Context& context, SemIR::GenericId generic_id,
 // declaration, but not the definition, of the generic.
 //
 // `args_id` should be a canonical instruction block referring to constants.
-auto MakeSpecific(Context& context, SemIR::GenericId generic_id,
+auto MakeSpecific(Context& context, SemIRLoc loc, SemIR::GenericId generic_id,
                   SemIR::InstBlockId args_id) -> SemIR::SpecificId;
 
 // Builds a new specific if the given generic is valid. Otherwise returns an
 // invalid specific.
-inline auto MakeSpecificIfGeneric(Context& context, SemIR::GenericId generic_id,
+inline auto MakeSpecificIfGeneric(Context& context, SemIRLoc loc,
+                                  SemIR::GenericId generic_id,
                                   SemIR::InstBlockId args_id)
     -> SemIR::SpecificId {
-  return generic_id.is_valid() ? MakeSpecific(context, generic_id, args_id)
+  return generic_id.is_valid() ? MakeSpecific(context, loc, generic_id, args_id)
                                : SemIR::SpecificId::Invalid;
 }
 
 // Builds the specific that describes how the generic should refer to itself.
 // For example, for a generic `G(T:! type)`, this is the specific `G(T)`. For an
 // invalid `generic_id`, returns an invalid specific ID.
-auto MakeSelfSpecific(Context& context, SemIR::GenericId generic_id)
-    -> SemIR::SpecificId;
+auto MakeSelfSpecific(Context& context, SemIRLoc loc,
+                      SemIR::GenericId generic_id) -> SemIR::SpecificId;
 
 // Attempts to resolve the definition of the given specific, by evaluating the
 // eval block of the corresponding generic and storing a corresponding value
 // block in the specific. Returns false if a definition is not available.
-auto ResolveSpecificDefinition(Context& context, SemIR::SpecificId specific_id)
-    -> bool;
+auto ResolveSpecificDefinition(Context& context, SemIRLoc loc,
+                               SemIR::SpecificId specific_id) -> bool;
+
+// Returns an instruction describing the entity named by the given specific.
+auto GetInstForSpecific(Context& context, SemIR::SpecificId specific_id)
+    -> SemIR::InstId;
 
 }  // namespace Carbon::Check
 

+ 3 - 3
toolchain/check/impl.cpp

@@ -33,7 +33,7 @@ static auto NoteAssociatedFunction(Context& context,
 // Gets the self specific of a generic declaration that is an interface member,
 // given a specific for an enclosing generic, plus a type to use as `Self`.
 static auto GetSelfSpecificForInterfaceMemberWithSelfType(
-    Context& context, SemIR::SpecificId enclosing_specific_id,
+    Context& context, SemIRLoc loc, SemIR::SpecificId enclosing_specific_id,
     SemIR::GenericId generic_id, SemIR::TypeId self_type_id)
     -> SemIR::SpecificId {
   const auto& generic = context.generics().Get(generic_id);
@@ -84,7 +84,7 @@ static auto GetSelfSpecificForInterfaceMemberWithSelfType(
   }
 
   auto args_id = context.inst_blocks().AddCanonical(arg_ids);
-  return MakeSpecific(context, generic_id, args_id);
+  return MakeSpecific(context, loc, generic_id, args_id);
 }
 
 // Checks that `impl_function_id` is a valid implementation of the function
@@ -115,7 +115,7 @@ static auto CheckAssociatedFunctionImplementation(
   // parameters.
   auto interface_function_specific_id =
       GetSelfSpecificForInterfaceMemberWithSelfType(
-          context, interface_function_type.specific_id,
+          context, impl_decl_id, interface_function_type.specific_id,
           context.functions()
               .Get(interface_function_type.function_id)
               .generic_id,

+ 1 - 1
toolchain/check/impl_lookup.cpp

@@ -160,7 +160,7 @@ auto LookupInterfaceWitness(Context& context, SemIR::LocId loc_id,
     if (specific_id.is_valid()) {
       // We need a definition of the specific `impl` so we can access its
       // witness.
-      ResolveSpecificDefinition(context, specific_id);
+      ResolveSpecificDefinition(context, loc_id, specific_id);
     }
     return context.constant_values().GetInstId(
         SemIR::GetConstantValueInSpecific(context.sem_ir(), specific_id,

+ 3 - 2
toolchain/check/import_ref.cpp

@@ -2714,8 +2714,9 @@ static auto FinishPendingGeneric(ImportRefResolver& resolver,
                             SemIR::GenericInstIndex::Region::Declaration);
   resolver.local_generics().Get(pending.local_id).decl_block_id = decl_block_id;
 
-  auto self_specific_id =
-      MakeSelfSpecific(resolver.local_context(), pending.local_id);
+  auto local_decl_id = resolver.local_generics().Get(pending.local_id).decl_id;
+  auto self_specific_id = MakeSelfSpecific(resolver.local_context(),
+                                           local_decl_id, pending.local_id);
   resolver.local_generics().Get(pending.local_id).self_specific_id =
       self_specific_id;
   resolver.AddPendingSpecific({.import_id = import_generic.self_specific_id,

+ 3 - 1
toolchain/check/subst.cpp

@@ -190,7 +190,9 @@ static auto PopOperand(Context& context, Worklist& worklist, SemIR::IdKind kind,
       auto args_id =
           PopOperand(context, worklist, SemIR::IdKind::For<SemIR::InstBlockId>,
                      specific.args_id.index);
-      return MakeSpecific(context, specific.generic_id,
+      // TODO: Provide a location here.
+      SemIRLoc loc = SemIR::InstId::Invalid;
+      return MakeSpecific(context, loc, specific.generic_id,
                           SemIR::InstBlockId(args_id))
           .index;
     }

+ 3 - 0
toolchain/check/testdata/function/generic/resolve_used.carbon

@@ -24,6 +24,9 @@ fn ErrorIfNIsZero(N:! Core.IntLiteral()) {
 }
 
 fn CallNegative() {
+  // CHECK:STDERR: fail_todo_call_monomorphization_error.carbon:[[@LINE+3]]:3: note: in `ErrorIfNIsZero(0)` used here [ResolvingSpecificHere]
+  // CHECK:STDERR:   ErrorIfNIsZero(0);
+  // CHECK:STDERR:   ^~~~~~~~~~~~~~
   ErrorIfNIsZero(0);
 }
 

+ 16 - 10
toolchain/check/testdata/generic/complete_type.carbon

@@ -15,20 +15,23 @@ library "[[@TEST_NAME]]";
 class B;
 
 class A(T:! type) {
-  // CHECK:STDERR: fail_incomplete_in_class.carbon:[[@LINE+7]]:10: error: `T` evaluates to incomplete type `B` [IncompleteTypeInMonomorphization]
+  // CHECK:STDERR: fail_incomplete_in_class.carbon:[[@LINE+6]]:10: error: `T` evaluates to incomplete type `B` [IncompleteTypeInMonomorphization]
   // CHECK:STDERR:   var v: T;
   // CHECK:STDERR:          ^
   // CHECK:STDERR: fail_incomplete_in_class.carbon:[[@LINE-6]]:1: note: class was forward declared here [ClassForwardDeclaredHere]
   // CHECK:STDERR: class B;
   // CHECK:STDERR: ^~~~~~~~
-  // CHECK:STDERR:
   var v: T;
 }
 
+// CHECK:STDERR: fail_incomplete_in_class.carbon:[[@LINE+11]]:6: note: in `A(B)` used here [ResolvingSpecificHere]
+// CHECK:STDERR: fn F(x: A(B)) {}
+// CHECK:STDERR:      ^~~~~~~
+// CHECK:STDERR:
 // CHECK:STDERR: fail_incomplete_in_class.carbon:[[@LINE+7]]:6: error: parameter has incomplete type `A(B)` in function definition [IncompleteTypeInFunctionParam]
 // CHECK:STDERR: fn F(x: A(B)) {}
 // CHECK:STDERR:      ^~~~~~~
-// CHECK:STDERR: fail_incomplete_in_class.carbon:[[@LINE-16]]:1: note: class was forward declared here [ClassForwardDeclaredHere]
+// CHECK:STDERR: fail_incomplete_in_class.carbon:[[@LINE-19]]:1: note: class was forward declared here [ClassForwardDeclaredHere]
 // CHECK:STDERR: class B;
 // CHECK:STDERR: ^~~~~~~~
 // CHECK:STDERR:
@@ -67,6 +70,9 @@ fn F(T:! type) {
   var v: T;
 }
 
+// CHECK:STDERR: fail_incomplete_in_function_at_eof.carbon:[[@LINE+3]]:10: note: in `F(B)` used here [ResolvingSpecificHere]
+// CHECK:STDERR: fn G() { F(B); }
+// CHECK:STDERR:          ^
 fn G() { F(B); }
 
 // CHECK:STDOUT: --- fail_incomplete_in_class.carbon
@@ -125,7 +131,7 @@ fn G() { F(B); }
 // CHECK:STDOUT:     %x.param: %A.2 = value_param runtime_param0
 // CHECK:STDOUT:     %x: %A.2 = bind_name x, %x.param
 // CHECK:STDOUT:   }
-// CHECK:STDOUT:   %B.decl.loc26: type = class_decl @B [template = constants.%B] {} {}
+// CHECK:STDOUT:   %B.decl.loc29: type = class_decl @B [template = constants.%B] {} {}
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: class @B {
@@ -145,17 +151,17 @@ fn G() { F(B); }
 // CHECK:STDOUT:   %A: type = class_type @A, @A(%T.loc6_9.2) [symbolic = %A (constants.%A.1)]
 // CHECK:STDOUT:   %A.elem: type = unbound_element_type @A.%A (%A.1), @A.%T.loc6_9.2 (%T) [symbolic = %A.elem (constants.%A.elem.1)]
 // CHECK:STDOUT:   %struct_type.v: type = struct_type {.v: @A.%T.loc6_9.2 (%T)} [symbolic = %struct_type.v (constants.%struct_type.v.1)]
-// CHECK:STDOUT:   %complete_type.loc15_1.2: <witness> = complete_type_witness @A.%struct_type.v (%struct_type.v.1) [symbolic = %complete_type.loc15_1.2 (constants.%complete_type.1)]
+// CHECK:STDOUT:   %complete_type.loc14_1.2: <witness> = complete_type_witness @A.%struct_type.v (%struct_type.v.1) [symbolic = %complete_type.loc14_1.2 (constants.%complete_type.1)]
 // CHECK:STDOUT:
 // CHECK:STDOUT:   class {
 // CHECK:STDOUT:     %T.ref: type = name_ref T, %T.loc6_9.1 [symbolic = %T.loc6_9.2 (constants.%T)]
-// CHECK:STDOUT:     %.loc14: @A.%A.elem (%A.elem.1) = field_decl v, element0 [template]
-// CHECK:STDOUT:     %complete_type.loc15_1.1: <witness> = complete_type_witness %struct_type.v.1 [symbolic = %complete_type.loc15_1.2 (constants.%complete_type.1)]
+// CHECK:STDOUT:     %.loc13: @A.%A.elem (%A.elem.1) = field_decl v, element0 [template]
+// CHECK:STDOUT:     %complete_type.loc14_1.1: <witness> = complete_type_witness %struct_type.v.1 [symbolic = %complete_type.loc14_1.2 (constants.%complete_type.1)]
 // CHECK:STDOUT:
 // CHECK:STDOUT:   !members:
 // CHECK:STDOUT:     .Self = constants.%A.1
-// CHECK:STDOUT:     .v = %.loc14
-// CHECK:STDOUT:     complete_type_witness = %complete_type.loc15_1.1
+// CHECK:STDOUT:     .v = %.loc13
+// CHECK:STDOUT:     complete_type_witness = %complete_type.loc14_1.1
 // CHECK:STDOUT:   }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
@@ -183,7 +189,7 @@ fn G() { F(B); }
 // CHECK:STDOUT:   %A => constants.%A.2
 // CHECK:STDOUT:   %A.elem => constants.%A.elem.2
 // CHECK:STDOUT:   %struct_type.v => constants.%struct_type.v.2
-// CHECK:STDOUT:   %complete_type.loc15_1.2 => constants.%complete_type.2
+// CHECK:STDOUT:   %complete_type.loc14_1.2 => constants.%complete_type.2
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: --- incomplete_in_function.carbon

+ 1 - 0
toolchain/diagnostics/diagnostic_kind.def

@@ -141,6 +141,7 @@ CARBON_DIAGNOSTIC_KIND(SemanticsTodo)
 
 // Location context.
 CARBON_DIAGNOSTIC_KIND(InImport)
+CARBON_DIAGNOSTIC_KIND(ResolvingSpecificHere)
 
 // Package/import checking diagnostics.
 CARBON_DIAGNOSTIC_KIND(IncorrectExtension)

+ 10 - 1
toolchain/sem_ir/stringify_type.cpp

@@ -403,6 +403,16 @@ auto StringifyTypeExpr(const SemIR::File& sem_ir, InstId outer_inst_id)
         step_stack.PushTypeId(inst.pointee_id);
         break;
       }
+      case CARBON_KIND(SpecificFunction inst): {
+        auto callee = SemIR::GetCalleeFunction(sem_ir, inst.callee_id);
+        if (callee.function_id.is_valid()) {
+          step_stack.PushEntityName(sem_ir.functions().Get(callee.function_id),
+                                    inst.specific_id);
+        } else {
+          step_stack.PushString("<invalid specific function>");
+        }
+        break;
+      }
       case CARBON_KIND(StructType inst): {
         auto fields = sem_ir.struct_type_fields().Get(inst.fields_id);
         if (fields.empty()) {
@@ -500,7 +510,6 @@ auto StringifyTypeExpr(const SemIR::File& sem_ir, InstId outer_inst_id)
       case ReturnSlot::Kind:
       case ReturnSlotPattern::Kind:
       case SpecificConstant::Kind:
-      case SpecificFunction::Kind:
       case SpliceBlock::Kind:
       case StringLiteral::Kind:
       case StructAccess::Kind: