Ver Fonte

Replace NodeId with a hybrid LocationId in SemIR diagnostics. (#3810)

The purpose of this change is to allow something such as a FunctionDecl
instruction to note an imported instruction as the "loc_id". Note that
doesn't occur here: this change is already very sweeping in edits. There
is no testdata affected, intended to show equivalent behavior.

We might want to consolidate NodeId references towards LocationId, but
if that's preferred, I'd still like to split it out. A lot of this just
piping through LocationId where it's a build error otherwise, enough
that imports should be able to start using it for diagnostics.
ValueStores are added but still unused -- just flushing out structure
for review.

Restructuring SemIRLocation is necessary to use LocationId this way. For
TokenOnly, it's not getting used in Parse, so I migrated it to Check and
it's now specific to SemIRLocation.

I also considered making LocationId reference an InstId (which would
need to be an ImportRef) instead of an ImportIRInstId. However, that
would've required import.cpp to add instructions for decls which are
reached during resolution -- we typically don't have an inst ready for
use. An extra inst is essentially 16 bytes in InstId's ValueStore + 4
bytes in LocationId's ValueStore, whereas this is 8 bytes per.
Jon Ross-Perkins há 2 anos atrás
pai
commit
b079acd86f

+ 16 - 11
toolchain/check/check.cpp

@@ -40,16 +40,21 @@ class SemIRDiagnosticConverter : public DiagnosticConverter<SemIRLocation> {
       -> DiagnosticLocation override {
     // Parse nodes always refer to the current IR.
     if (!loc.is_inst_id) {
-      return ConvertLocationInFile(sem_ir_, loc.node_location, context_fn);
+      CARBON_CHECK(loc.loc_id.is_node_id() || !loc.loc_id.is_valid())
+          << "TODO: Handle non-NodeId locs";
+      return ConvertLocationInFile(sem_ir_, loc.loc_id.node_id(),
+                                   loc.token_only, context_fn);
     }
 
     const auto* cursor_ir = sem_ir_;
     auto cursor_inst_id = loc.inst_id;
     while (true) {
       // If the parse node is valid, use it for the location.
-      if (auto node_id = cursor_ir->insts().GetNodeId(cursor_inst_id);
-          node_id.is_valid()) {
-        return ConvertLocationInFile(cursor_ir, node_id, context_fn);
+      if (auto loc_id = cursor_ir->insts().GetLocationId(cursor_inst_id);
+          loc_id.is_valid()) {
+        CARBON_CHECK(loc_id.is_node_id()) << "TODO: Handle non-NodeId locs";
+        return ConvertLocationInFile(cursor_ir, loc_id.node_id(),
+                                     loc.token_only, context_fn);
       }
 
       // If the parse node was invalid, recurse through import references when
@@ -57,8 +62,8 @@ class SemIRDiagnosticConverter : public DiagnosticConverter<SemIRLocation> {
       if (auto import_ref = cursor_ir->insts().TryGetAs<SemIR::AnyImportRef>(
               cursor_inst_id)) {
         const auto& import_ir = cursor_ir->import_irs().Get(import_ref->ir_id);
-        auto context_loc =
-            ConvertLocationInFile(cursor_ir, import_ir.node_id, context_fn);
+        auto context_loc = ConvertLocationInFile(cursor_ir, import_ir.node_id,
+                                                 loc.token_only, context_fn);
         CARBON_DIAGNOSTIC(InImport, Note, "In import.");
         context_fn(context_loc, InImport);
         cursor_ir = import_ir.sem_ir;
@@ -78,7 +83,7 @@ class SemIRDiagnosticConverter : public DiagnosticConverter<SemIRLocation> {
 
       // Invalid parse node but not an import; just nothing to point at.
       return ConvertLocationInFile(cursor_ir, Parse::NodeId::Invalid,
-                                   context_fn);
+                                   loc.token_only, context_fn);
     }
   }
 
@@ -97,13 +102,13 @@ class SemIRDiagnosticConverter : public DiagnosticConverter<SemIRLocation> {
   }
 
  private:
-  auto ConvertLocationInFile(const SemIR::File* sem_ir,
-                             Parse::NodeLocation node_location,
-                             ContextFnT context_fn) const
+  auto ConvertLocationInFile(const SemIR::File* sem_ir, Parse::NodeId node_id,
+                             bool token_only, ContextFnT context_fn) const
       -> DiagnosticLocation {
     auto it = node_converters_->find(sem_ir);
     CARBON_CHECK(it != node_converters_->end());
-    return it->second->ConvertLocation(node_location, context_fn);
+    return it->second->ConvertLocation(Parse::NodeLocation(node_id, token_only),
+                                       context_fn);
   }
 
   const llvm::DenseMap<const SemIR::File*, Parse::NodeLocationConverter*>*

+ 27 - 27
toolchain/check/context.cpp

@@ -68,14 +68,14 @@ auto Context::VerifyOnFinish() -> void {
   param_and_arg_refs_stack_.VerifyOnFinish();
 }
 
-auto Context::AddInstInNoBlock(SemIR::NodeIdAndInst node_id_and_inst)
+auto Context::AddInstInNoBlock(SemIR::LocationIdAndInst loc_id_and_inst)
     -> SemIR::InstId {
-  auto inst_id = sem_ir().insts().AddInNoBlock(node_id_and_inst);
-  CARBON_VLOG() << "AddInst: " << node_id_and_inst.inst << "\n";
+  auto inst_id = sem_ir().insts().AddInNoBlock(loc_id_and_inst);
+  CARBON_VLOG() << "AddInst: " << loc_id_and_inst.inst << "\n";
 
-  auto const_id = TryEvalInst(*this, inst_id, node_id_and_inst.inst);
+  auto const_id = TryEvalInst(*this, inst_id, loc_id_and_inst.inst);
   if (const_id.is_constant()) {
-    CARBON_VLOG() << "Constant: " << node_id_and_inst.inst << " -> "
+    CARBON_VLOG() << "Constant: " << loc_id_and_inst.inst << " -> "
                   << const_id.inst_id() << "\n";
     constant_values().Set(inst_id, const_id);
   }
@@ -83,23 +83,24 @@ auto Context::AddInstInNoBlock(SemIR::NodeIdAndInst node_id_and_inst)
   return inst_id;
 }
 
-auto Context::AddInst(SemIR::NodeIdAndInst node_id_and_inst) -> SemIR::InstId {
-  auto inst_id = AddInstInNoBlock(node_id_and_inst);
+auto Context::AddInst(SemIR::LocationIdAndInst loc_id_and_inst)
+    -> SemIR::InstId {
+  auto inst_id = AddInstInNoBlock(loc_id_and_inst);
   inst_block_stack_.AddInstId(inst_id);
   return inst_id;
 }
 
-auto Context::AddPlaceholderInstInNoBlock(SemIR::NodeIdAndInst node_id_and_inst)
-    -> SemIR::InstId {
-  auto inst_id = sem_ir().insts().AddInNoBlock(node_id_and_inst);
-  CARBON_VLOG() << "AddPlaceholderInst: " << node_id_and_inst.inst << "\n";
+auto Context::AddPlaceholderInstInNoBlock(
+    SemIR::LocationIdAndInst loc_id_and_inst) -> SemIR::InstId {
+  auto inst_id = sem_ir().insts().AddInNoBlock(loc_id_and_inst);
+  CARBON_VLOG() << "AddPlaceholderInst: " << loc_id_and_inst.inst << "\n";
   constant_values().Set(inst_id, SemIR::ConstantId::Invalid);
   return inst_id;
 }
 
-auto Context::AddPlaceholderInst(SemIR::NodeIdAndInst node_id_and_inst)
+auto Context::AddPlaceholderInst(SemIR::LocationIdAndInst loc_id_and_inst)
     -> SemIR::InstId {
-  auto inst_id = AddPlaceholderInstInNoBlock(node_id_and_inst);
+  auto inst_id = AddPlaceholderInstInNoBlock(loc_id_and_inst);
   inst_block_stack_.AddInstId(inst_id);
   return inst_id;
 }
@@ -111,24 +112,24 @@ auto Context::AddConstant(SemIR::Inst inst, bool is_symbolic)
   return const_id;
 }
 
-auto Context::AddInstAndPush(SemIR::NodeIdAndInst node_id_and_inst) -> void {
-  auto inst_id = AddInst(node_id_and_inst);
-  node_stack_.Push(node_id_and_inst.node_id, inst_id);
+auto Context::AddInstAndPush(SemIR::LocationIdAndInst loc_id_and_inst) -> void {
+  auto inst_id = AddInst(loc_id_and_inst);
+  node_stack_.Push(loc_id_and_inst.loc_id.node_id(), inst_id);
 }
 
 auto Context::ReplaceInstBeforeConstantUse(
-    SemIR::InstId inst_id, SemIR::NodeIdAndInst node_id_and_inst) -> void {
-  sem_ir().insts().Set(inst_id, node_id_and_inst);
+    SemIR::InstId inst_id, SemIR::LocationIdAndInst loc_id_and_inst) -> void {
+  sem_ir().insts().Set(inst_id, loc_id_and_inst);
 
-  CARBON_VLOG() << "ReplaceInst: " << inst_id << " -> " << node_id_and_inst.inst
+  CARBON_VLOG() << "ReplaceInst: " << inst_id << " -> " << loc_id_and_inst.inst
                 << "\n";
 
   // Redo evaluation. This is only safe to do if this instruction has not
   // already been used as a constant, which is the caller's responsibility to
   // ensure.
-  auto const_id = TryEvalInst(*this, inst_id, node_id_and_inst.inst);
+  auto const_id = TryEvalInst(*this, inst_id, loc_id_and_inst.inst);
   if (const_id.is_constant()) {
-    CARBON_VLOG() << "Constant: " << node_id_and_inst.inst << " -> "
+    CARBON_VLOG() << "Constant: " << loc_id_and_inst.inst << " -> "
                   << const_id.inst_id() << "\n";
   }
   constant_values().Set(inst_id, const_id);
@@ -162,11 +163,11 @@ auto Context::DiagnoseDuplicateName(SemIRLocation dup_def,
       .Emit();
 }
 
-auto Context::DiagnoseNameNotFound(Parse::NodeId node_id, SemIR::NameId name_id)
-    -> void {
+auto Context::DiagnoseNameNotFound(SemIR::LocationId loc_id,
+                                   SemIR::NameId name_id) -> void {
   CARBON_DIAGNOSTIC(NameNotFound, Error, "Name `{0}` not found.",
                     SemIR::NameId);
-  emitter_->Emit(node_id, NameNotFound, name_id);
+  emitter_->Emit(loc_id, NameNotFound, name_id);
 }
 
 auto Context::NoteIncompleteClass(SemIR::ClassId class_id,
@@ -208,7 +209,7 @@ auto Context::AddNameToLookup(SemIR::NameId name_id, SemIR::InstId target_id)
   }
 }
 
-auto Context::LookupNameInDecl(Parse::NodeId node_id, SemIR::NameId name_id,
+auto Context::LookupNameInDecl(SemIR::LocationId loc_id, SemIR::NameId name_id,
                                SemIR::NameScopeId scope_id) -> SemIR::InstId {
   if (!scope_id.is_valid()) {
     // Look for a name in the current scope only. There are two cases where the
@@ -245,8 +246,7 @@ auto Context::LookupNameInDecl(Parse::NodeId node_id, SemIR::NameId name_id,
     //
     //    // Error, no `F` in `B`.
     //    fn B.F() {}
-    return LookupNameInExactScope(node_id, name_id,
-                                  name_scopes().Get(scope_id));
+    return LookupNameInExactScope(loc_id, name_id, name_scopes().Get(scope_id));
   }
 }
 

+ 15 - 11
toolchain/check/context.h

@@ -41,38 +41,39 @@ class Context {
   auto VerifyOnFinish() -> void;
 
   // Adds an instruction to the current block, returning the produced ID.
-  auto AddInst(SemIR::NodeIdAndInst node_id_and_inst) -> SemIR::InstId;
+  auto AddInst(SemIR::LocationIdAndInst loc_id_and_inst) -> SemIR::InstId;
 
   // Adds an instruction in no block, returning the produced ID. Should be used
   // rarely.
-  auto AddInstInNoBlock(SemIR::NodeIdAndInst node_id_and_inst) -> SemIR::InstId;
+  auto AddInstInNoBlock(SemIR::LocationIdAndInst loc_id_and_inst)
+      -> SemIR::InstId;
 
   // Adds an instruction to the current block, returning the produced ID. The
   // instruction is a placeholder that is expected to be replaced by
   // `ReplaceInstBeforeConstantUse`.
-  auto AddPlaceholderInst(SemIR::NodeIdAndInst node_id_and_inst)
+  auto AddPlaceholderInst(SemIR::LocationIdAndInst loc_id_and_inst)
       -> SemIR::InstId;
 
   // Adds an instruction in no block, returning the produced ID. Should be used
   // rarely. The instruction is a placeholder that is expected to be replaced by
   // `ReplaceInstBeforeConstantUse`.
-  auto AddPlaceholderInstInNoBlock(SemIR::NodeIdAndInst node_id_and_inst)
+  auto AddPlaceholderInstInNoBlock(SemIR::LocationIdAndInst loc_id_and_inst)
       -> SemIR::InstId;
 
   // Adds an instruction to the constants block, returning the produced ID.
   auto AddConstant(SemIR::Inst inst, bool is_symbolic) -> SemIR::ConstantId;
 
   // Pushes a parse tree node onto the stack, storing the SemIR::Inst as the
-  // result.
-  auto AddInstAndPush(SemIR::NodeIdAndInst node_id_and_inst) -> void;
+  // result. Only valid if the LocationId is for a NodeId.
+  auto AddInstAndPush(SemIR::LocationIdAndInst loc_id_and_inst) -> void;
 
-  // Replaces the value of the instruction `inst_id` with `node_id_and_inst`.
+  // Replaces the value of the instruction `inst_id` with `loc_id_and_inst`.
   // The instruction is required to not have been used in any constant
   // evaluation, either because it's newly created and entirely unused, or
   // because it's only used in a position that constant evaluation ignores, such
   // as a return slot.
   auto ReplaceInstBeforeConstantUse(SemIR::InstId inst_id,
-                                    SemIR::NodeIdAndInst node_id_and_inst)
+                                    SemIR::LocationIdAndInst loc_id_and_inst)
       -> void;
 
   // Adds an import_ref instruction for the specified instruction in the
@@ -87,7 +88,7 @@ class Context {
   // remain const.
   auto SetNamespaceNodeId(SemIR::InstId inst_id, Parse::NodeId node_id)
       -> void {
-    sem_ir().insts().SetNodeId(inst_id, node_id);
+    sem_ir().insts().SetLocationId(inst_id, SemIR::LocationId(node_id));
   }
 
   // Adds a name to name lookup. Prints a diagnostic for name conflicts.
@@ -96,7 +97,7 @@ class Context {
   // Performs name lookup in a specified scope for a name appearing in a
   // declaration, returning the referenced instruction. If scope_id is invalid,
   // uses the current contextual scope.
-  auto LookupNameInDecl(Parse::NodeId node_id, SemIR::NameId name_id,
+  auto LookupNameInDecl(SemIR::LocationId loc_id, SemIR::NameId name_id,
                         SemIR::NameScopeId scope_id) -> SemIR::InstId;
 
   // Performs an unqualified name lookup, returning the referenced instruction.
@@ -120,7 +121,7 @@ class Context {
       -> void;
 
   // Prints a diagnostic for a missing name.
-  auto DiagnoseNameNotFound(Parse::NodeId node_id, SemIR::NameId name_id)
+  auto DiagnoseNameNotFound(SemIR::LocationId loc_id, SemIR::NameId name_id)
       -> void;
 
   // Adds a note to a diagnostic explaining that a class is incomplete.
@@ -323,6 +324,9 @@ class Context {
   auto import_irs() -> ValueStore<SemIR::ImportIRId>& {
     return sem_ir().import_irs();
   }
+  auto import_ir_insts() -> ValueStore<SemIR::ImportIRInstId>& {
+    return sem_ir().import_ir_insts();
+  }
   auto names() -> SemIR::NameStoreWrapper { return sem_ir().names(); }
   auto name_scopes() -> SemIR::NameScopeStore& {
     return sem_ir().name_scopes();

+ 81 - 81
toolchain/check/convert.cpp

@@ -96,7 +96,7 @@ static auto FinalizeTemporary(Context& context, SemIR::InstId init_id,
         << sem_ir.insts().Get(return_slot_id);
     auto init = sem_ir.insts().Get(init_id);
     return context.AddInst(
-        {sem_ir.insts().GetNodeId(init_id),
+        {sem_ir.insts().GetLocationId(init_id),
          SemIR::Temporary{init.type_id(), return_slot_id, init_id}});
   }
 
@@ -111,11 +111,11 @@ static auto FinalizeTemporary(Context& context, SemIR::InstId init_id,
   // materialize and initialize a temporary, rather than two separate
   // instructions.
   auto init = sem_ir.insts().Get(init_id);
-  auto node_id = sem_ir.insts().GetNodeId(init_id);
+  auto loc_id = sem_ir.insts().GetLocationId(init_id);
   auto temporary_id =
-      context.AddInst({node_id, SemIR::TemporaryStorage{init.type_id()}});
+      context.AddInst({loc_id, SemIR::TemporaryStorage{init.type_id()}});
   return context.AddInst(
-      {node_id, SemIR::Temporary{init.type_id(), temporary_id, init_id}});
+      {loc_id, SemIR::Temporary{init.type_id(), temporary_id, init_id}});
 }
 
 // Materialize a temporary to hold the result of the given expression if it is
@@ -131,7 +131,7 @@ static auto MaterializeIfInitializing(Context& context, SemIR::InstId expr_id)
 
 // Creates and adds an instruction to perform element access into an aggregate.
 template <typename AccessInstT, typename InstBlockT>
-static auto MakeElementAccessInst(Context& context, Parse::NodeId node_id,
+static auto MakeElementAccessInst(Context& context, SemIR::LocationId loc_id,
                                   SemIR::InstId aggregate_id,
                                   SemIR::TypeId elem_type_id, InstBlockT& block,
                                   std::size_t i) {
@@ -140,14 +140,14 @@ static auto MakeElementAccessInst(Context& context, Parse::NodeId node_id,
     // index so that we don't need an integer literal instruction here, and
     // remove this special case.
     auto index_id = block.AddInst(
-        {node_id,
+        {loc_id,
          SemIR::IntLiteral{context.GetBuiltinType(SemIR::BuiltinKind::IntType),
                            context.ints().Add(llvm::APInt(32, i))}});
     return block.AddInst(
-        {node_id, AccessInstT{elem_type_id, aggregate_id, index_id}});
+        {loc_id, AccessInstT{elem_type_id, aggregate_id, index_id}});
   } else {
-    return block.AddInst({node_id, AccessInstT{elem_type_id, aggregate_id,
-                                               SemIR::ElementIndex(i)}});
+    return block.AddInst({loc_id, AccessInstT{elem_type_id, aggregate_id,
+                                              SemIR::ElementIndex(i)}});
   }
 }
 
@@ -166,7 +166,7 @@ static auto MakeElementAccessInst(Context& context, Parse::NodeId node_id,
 // instruction used to access the destination element.
 template <typename SourceAccessInstT, typename TargetAccessInstT>
 static auto ConvertAggregateElement(
-    Context& context, Parse::NodeId node_id, SemIR::InstId src_id,
+    Context& context, SemIR::LocationId loc_id, SemIR::InstId src_id,
     SemIR::TypeId src_elem_type,
     llvm::ArrayRef<SemIR::InstId> src_literal_elems,
     ConversionTarget::Kind kind, SemIR::InstId target_id,
@@ -177,22 +177,22 @@ static auto ConvertAggregateElement(
   auto src_elem_id =
       !src_literal_elems.empty()
           ? src_literal_elems[i]
-          : MakeElementAccessInst<SourceAccessInstT>(context, node_id, src_id,
+          : MakeElementAccessInst<SourceAccessInstT>(context, loc_id, src_id,
                                                      src_elem_type, context, i);
 
   // If we're performing a conversion rather than an initialization, we won't
   // have or need a target.
   ConversionTarget target = {.kind = kind, .type_id = target_elem_type};
   if (!target.is_initializer()) {
-    return Convert(context, node_id, src_elem_id, target);
+    return Convert(context, loc_id, src_elem_id, target);
   }
 
   // Compute the location of the target element and initialize it.
   PendingBlock::DiscardUnusedInstsScope scope(target_block);
   target.init_block = target_block;
   target.init_id = MakeElementAccessInst<TargetAccessInstT>(
-      context, node_id, target_id, target_elem_type, *target_block, i);
-  return Convert(context, node_id, src_elem_id, target);
+      context, loc_id, target_id, target_elem_type, *target_block, i);
+  return Convert(context, loc_id, src_elem_id, target);
 }
 
 // Performs a conversion from a tuple to an array type. This function only
@@ -206,7 +206,7 @@ static auto ConvertTupleToArray(Context& context, SemIR::TupleType tuple_type,
   auto tuple_elem_types = sem_ir.type_blocks().Get(tuple_type.elements_id);
 
   auto value = sem_ir.insts().Get(value_id);
-  auto value_node_id = sem_ir.insts().GetNodeId(value_id);
+  auto value_loc_id = sem_ir.insts().GetLocationId(value_id);
 
   // If we're initializing from a tuple literal, we will use its elements
   // directly. Otherwise, materialize a temporary if needed and index into the
@@ -229,7 +229,7 @@ static auto ConvertTupleToArray(Context& context, SemIR::TupleType tuple_type,
                       "Cannot initialize array of {0} element(s) from tuple "
                       "with {1} element(s).",
                       uint64_t, size_t);
-    context.emitter().Emit(value_node_id,
+    context.emitter().Emit(value_loc_id,
                            literal_elems.empty()
                                ? ArrayInitFromExprArgCountMismatch
                                : ArrayInitFromLiteralArgCountMismatch,
@@ -246,7 +246,7 @@ static auto ConvertTupleToArray(Context& context, SemIR::TupleType tuple_type,
   SemIR::InstId return_slot_id = target.init_id;
   if (!target.init_id.is_valid()) {
     return_slot_id = target_block->AddInst(
-        {value_node_id, SemIR::TemporaryStorage{target.type_id}});
+        {value_loc_id, SemIR::TemporaryStorage{target.type_id}});
   }
 
   // Initialize each element of the array from the corresponding element of the
@@ -260,7 +260,7 @@ static auto ConvertTupleToArray(Context& context, SemIR::TupleType tuple_type,
     // approach.
     auto init_id =
         ConvertAggregateElement<SemIR::TupleAccess, SemIR::ArrayIndex>(
-            context, value_node_id, value_id, src_type_id, literal_elems,
+            context, value_loc_id, value_id, src_type_id, literal_elems,
             ConversionTarget::FullInitializer, return_slot_id,
             array_type.element_type_id, target_block, i);
     if (init_id == SemIR::InstId::BuiltinError) {
@@ -273,7 +273,7 @@ static auto ConvertTupleToArray(Context& context, SemIR::TupleType tuple_type,
   // reference to the return slot.
   target_block->InsertHere();
   return context.AddInst(
-      {value_node_id,
+      {value_loc_id,
        SemIR::ArrayInit{target.type_id, sem_ir.inst_blocks().Add(inits),
                         return_slot_id}});
 }
@@ -290,7 +290,7 @@ static auto ConvertTupleToTuple(Context& context, SemIR::TupleType src_type,
   auto dest_elem_types = sem_ir.type_blocks().Get(dest_type.elements_id);
 
   auto value = sem_ir.insts().Get(value_id);
-  auto value_node_id = sem_ir.insts().GetNodeId(value_id);
+  auto value_loc_id = sem_ir.insts().GetLocationId(value_id);
 
   // If we're initializing from a tuple literal, we will use its elements
   // directly. Otherwise, materialize a temporary if needed and index into the
@@ -310,7 +310,7 @@ static auto ConvertTupleToTuple(Context& context, SemIR::TupleType src_type,
                       "Cannot initialize tuple of {0} element(s) from tuple "
                       "with {1} element(s).",
                       size_t, size_t);
-    context.emitter().Emit(value_node_id, TupleInitElementCountMismatch,
+    context.emitter().Emit(value_loc_id, TupleInitElementCountMismatch,
                            dest_elem_types.size(), src_elem_types.size());
     return SemIR::InstId::BuiltinError;
   }
@@ -342,7 +342,7 @@ static auto ConvertTupleToTuple(Context& context, SemIR::TupleType src_type,
     // approach.
     auto init_id =
         ConvertAggregateElement<SemIR::TupleAccess, SemIR::TupleAccess>(
-            context, value_node_id, value_id, src_type_id, literal_elems,
+            context, value_loc_id, value_id, src_type_id, literal_elems,
             inner_kind, target.init_id, dest_type_id, target.init_block, i);
     if (init_id == SemIR::InstId::BuiltinError) {
       return SemIR::InstId::BuiltinError;
@@ -353,11 +353,11 @@ static auto ConvertTupleToTuple(Context& context, SemIR::TupleType src_type,
   if (is_init) {
     target.init_block->InsertHere();
     return context.AddInst(
-        {value_node_id,
+        {value_loc_id,
          SemIR::TupleInit{target.type_id, new_block.id(), target.init_id}});
   } else {
     return context.AddInst(
-        {value_node_id, SemIR::TupleValue{target.type_id, new_block.id()}});
+        {value_loc_id, SemIR::TupleValue{target.type_id, new_block.id()}});
   }
 }
 
@@ -374,7 +374,7 @@ static auto ConvertStructToStructOrClass(Context& context,
   auto dest_elem_fields = sem_ir.inst_blocks().Get(dest_type.fields_id);
 
   auto value = sem_ir.insts().Get(value_id);
-  auto value_node_id = sem_ir.insts().GetNodeId(value_id);
+  auto value_loc_id = sem_ir.insts().GetLocationId(value_id);
 
   // If we're initializing from a struct literal, we will use its elements
   // directly. Otherwise, materialize a temporary if needed and index into the
@@ -397,7 +397,7 @@ static auto ConvertStructToStructOrClass(Context& context,
                       "with {2} field(s).",
                       llvm::StringLiteral, size_t, size_t);
     context.emitter().Emit(
-        value_node_id, StructInitElementCountMismatch,
+        value_loc_id, StructInitElementCountMismatch,
         is_class ? llvm::StringLiteral("class") : llvm::StringLiteral("struct"),
         dest_elem_fields.size(), src_elem_fields.size());
     return SemIR::InstId::BuiltinError;
@@ -448,7 +448,7 @@ static auto ConvertStructToStructOrClass(Context& context,
               StructInitMissingFieldInLiteral, Error,
               "Missing value for field `{0}` in struct initialization.",
               SemIR::NameId);
-          context.emitter().Emit(value_node_id, StructInitMissingFieldInLiteral,
+          context.emitter().Emit(value_loc_id, StructInitMissingFieldInLiteral,
                                  dest_field.name_id);
         } else {
           CARBON_DIAGNOSTIC(StructInitMissingFieldInConversion, Error,
@@ -456,8 +456,8 @@ static auto ConvertStructToStructOrClass(Context& context,
                             "missing field `{2}` in source type.",
                             SemIR::TypeId, SemIR::TypeId, SemIR::NameId);
           context.emitter().Emit(
-              value_node_id, StructInitMissingFieldInConversion,
-              value.type_id(), target.type_id, dest_field.name_id);
+              value_loc_id, StructInitMissingFieldInConversion, value.type_id(),
+              target.type_id, dest_field.name_id);
         }
         return SemIR::InstId::BuiltinError;
       }
@@ -470,7 +470,7 @@ static auto ConvertStructToStructOrClass(Context& context,
     // approach.
     auto init_id =
         ConvertAggregateElement<SemIR::StructAccess, TargetAccessInstT>(
-            context, value_node_id, value_id, src_field.field_type_id,
+            context, value_loc_id, value_id, src_field.field_type_id,
             literal_elems, inner_kind, target.init_id, dest_field.field_type_id,
             target.init_block, src_field_index);
     if (init_id == SemIR::InstId::BuiltinError) {
@@ -484,16 +484,16 @@ static auto ConvertStructToStructOrClass(Context& context,
     CARBON_CHECK(is_init)
         << "Converting directly to a class value is not supported";
     return context.AddInst(
-        {value_node_id,
+        {value_loc_id,
          SemIR::ClassInit{target.type_id, new_block.id(), target.init_id}});
   } else if (is_init) {
     target.init_block->InsertHere();
     return context.AddInst(
-        {value_node_id,
+        {value_loc_id,
          SemIR::StructInit{target.type_id, new_block.id(), target.init_id}});
   } else {
     return context.AddInst(
-        {value_node_id, SemIR::StructValue{target.type_id, new_block.id()}});
+        {value_loc_id, SemIR::StructValue{target.type_id, new_block.id()}});
   }
 }
 
@@ -539,7 +539,7 @@ static auto ConvertStructToClass(Context& context, SemIR::StructType src_type,
     target.kind = ConversionTarget::Initializer;
     target.init_block = &target_block;
     target.init_id =
-        target_block.AddInst({context.insts().GetNodeId(value_id),
+        target_block.AddInst({context.insts().GetLocationId(value_id),
                               SemIR::TemporaryStorage{target.type_id}});
   }
 
@@ -549,7 +549,7 @@ static auto ConvertStructToClass(Context& context, SemIR::StructType src_type,
   if (need_temporary) {
     target_block.InsertHere();
     result_id = context.AddInst(
-        {context.insts().GetNodeId(value_id),
+        {context.insts().GetLocationId(value_id),
          SemIR::Temporary{target.type_id, target.init_id, result_id}});
   }
   return result_id;
@@ -596,7 +596,7 @@ static auto ComputeInheritancePath(Context& context, SemIR::TypeId derived_id,
 
 // Performs a conversion from a derived class value or reference to a base class
 // value or reference.
-static auto ConvertDerivedToBase(Context& context, Parse::NodeId node_id,
+static auto ConvertDerivedToBase(Context& context, SemIR::LocationId loc_id,
                                  SemIR::InstId value_id,
                                  const InheritancePath& path) -> SemIR::InstId {
   // Materialize a temporary if necessary.
@@ -606,27 +606,27 @@ static auto ConvertDerivedToBase(Context& context, Parse::NodeId node_id,
   for (auto base_id : path) {
     auto base_decl = context.insts().GetAs<SemIR::BaseDecl>(base_id);
     value_id = context.AddInst(
-        {node_id, SemIR::ClassElementAccess{base_decl.base_type_id, value_id,
-                                            base_decl.index}});
+        {loc_id, SemIR::ClassElementAccess{base_decl.base_type_id, value_id,
+                                           base_decl.index}});
   }
   return value_id;
 }
 
 // Performs a conversion from a derived class pointer to a base class pointer.
 static auto ConvertDerivedPointerToBasePointer(
-    Context& context, Parse::NodeId node_id, SemIR::PointerType src_ptr_type,
+    Context& context, SemIR::LocationId loc_id, SemIR::PointerType src_ptr_type,
     SemIR::TypeId dest_ptr_type_id, SemIR::InstId ptr_id,
     const InheritancePath& path) -> SemIR::InstId {
   // Form `*p`.
   ptr_id = ConvertToValueExpr(context, ptr_id);
   auto ref_id =
-      context.AddInst({node_id, SemIR::Deref{src_ptr_type.pointee_id, ptr_id}});
+      context.AddInst({loc_id, SemIR::Deref{src_ptr_type.pointee_id, ptr_id}});
 
   // Convert as a reference expression.
-  ref_id = ConvertDerivedToBase(context, node_id, ref_id, path);
+  ref_id = ConvertDerivedToBase(context, loc_id, ref_id, path);
 
   // Take the address.
-  return context.AddInst({node_id, SemIR::AddrOf{dest_ptr_type_id, ref_id}});
+  return context.AddInst({loc_id, SemIR::AddrOf{dest_ptr_type_id, ref_id}});
 }
 
 // Returns whether `category` is a valid expression category to produce as a
@@ -651,7 +651,7 @@ static auto IsValidExprCategoryForConversionTarget(
   }
 }
 
-static auto PerformBuiltinConversion(Context& context, Parse::NodeId node_id,
+static auto PerformBuiltinConversion(Context& context, SemIR::LocationId loc_id,
                                      SemIR::InstId value_id,
                                      ConversionTarget target) -> SemIR::InstId {
   auto& sem_ir = context.sem_ir();
@@ -709,7 +709,7 @@ static auto PerformBuiltinConversion(Context& context, Parse::NodeId node_id,
         // value representation is a copy of the object representation, so we
         // already have a value of the right form.
         return context.AddInst(
-            {node_id, SemIR::ValueOfInitializer{value_type_id, value_id}});
+            {loc_id, SemIR::ValueOfInitializer{value_type_id, value_id}});
       }
     }
   }
@@ -760,7 +760,7 @@ static auto PerformBuiltinConversion(Context& context, Parse::NodeId node_id,
     if (auto path =
             ComputeInheritancePath(context, value_type_id, target.type_id);
         path && !path->empty()) {
-      return ConvertDerivedToBase(context, node_id, value_id, *path);
+      return ConvertDerivedToBase(context, loc_id, value_id, *path);
     }
   }
 
@@ -773,7 +773,7 @@ static auto PerformBuiltinConversion(Context& context, Parse::NodeId node_id,
                                      target_pointer_type->pointee_id);
           path && !path->empty()) {
         return ConvertDerivedPointerToBasePointer(
-            context, node_id, *src_pointer_type, target.type_id, value_id,
+            context, loc_id, *src_pointer_type, target.type_id, value_id,
             *path);
       }
     }
@@ -788,7 +788,7 @@ static auto PerformBuiltinConversion(Context& context, Parse::NodeId node_id,
            sem_ir.inst_blocks().Get(tuple_literal->elements_id)) {
         // TODO: This call recurses back into conversion. Switch to an
         // iterative approach.
-        type_ids.push_back(ExprAsType(context, node_id, tuple_inst_id));
+        type_ids.push_back(ExprAsType(context, loc_id, tuple_inst_id));
       }
       auto tuple_type_id = context.GetTupleType(type_ids);
       return sem_ir.types().GetInstId(tuple_type_id);
@@ -812,7 +812,7 @@ static auto PerformBuiltinConversion(Context& context, Parse::NodeId node_id,
     // combining the above conversions and this one in a single conversion.
     if (sem_ir.types().Is<SemIR::InterfaceType>(value_type_id)) {
       return context.AddInst(
-          {node_id, SemIR::FacetTypeAccess{target.type_id, value_id}});
+          {loc_id, SemIR::FacetTypeAccess{target.type_id, value_id}});
     }
   }
 
@@ -849,7 +849,7 @@ static auto PerformCopy(Context& context, SemIR::InstId expr_id)
   return SemIR::InstId::BuiltinError;
 }
 
-auto Convert(Context& context, Parse::NodeId node_id, SemIR::InstId expr_id,
+auto Convert(Context& context, SemIR::LocationId loc_id, SemIR::InstId expr_id,
              ConversionTarget target) -> SemIR::InstId {
   auto& sem_ir = context.sem_ir();
   auto orig_expr_id = expr_id;
@@ -882,7 +882,7 @@ auto Convert(Context& context, Parse::NodeId node_id, SemIR::InstId expr_id,
         CARBON_DIAGNOSTIC(IncompleteTypeInConversion, Error,
                           "Invalid use of incomplete type `{0}`.",
                           SemIR::TypeId);
-        return context.emitter().Build(node_id,
+        return context.emitter().Build(loc_id,
                                        target.is_initializer()
                                            ? IncompleteTypeInInit
                                        : target.kind == ConversionTarget::Value
@@ -894,7 +894,7 @@ auto Convert(Context& context, Parse::NodeId node_id, SemIR::InstId expr_id,
   }
 
   // Check whether any builtin conversion applies.
-  expr_id = PerformBuiltinConversion(context, node_id, expr_id, target);
+  expr_id = PerformBuiltinConversion(context, loc_id, expr_id, target);
   if (expr_id == SemIR::InstId::BuiltinError) {
     return expr_id;
   }
@@ -911,7 +911,7 @@ auto Convert(Context& context, Parse::NodeId node_id, SemIR::InstId expr_id,
                       "Cannot convert from `{0}` to `{1}` with `as`.",
                       SemIR::TypeId, SemIR::TypeId);
     context.emitter()
-        .Build(node_id,
+        .Build(loc_id,
                target.kind == ConversionTarget::ExplicitAs
                    ? ExplicitAsConversionFailure
                    : ImplicitAsConversionFailure,
@@ -923,7 +923,7 @@ auto Convert(Context& context, Parse::NodeId node_id, SemIR::InstId expr_id,
   // Track that we performed a type conversion, if we did so.
   if (orig_expr_id != expr_id) {
     expr_id = context.AddInst(
-        {context.insts().GetNodeId(orig_expr_id),
+        {context.insts().GetLocationId(orig_expr_id),
          SemIR::Converted{target.type_id, orig_expr_id, expr_id}});
   }
 
@@ -974,7 +974,7 @@ auto Convert(Context& context, Parse::NodeId node_id, SemIR::InstId expr_id,
 
       // If we have a reference and don't want one, form a value binding.
       // TODO: Support types with custom value representations.
-      expr_id = context.AddInst({context.insts().GetNodeId(expr_id),
+      expr_id = context.AddInst({context.insts().GetLocationId(expr_id),
                                  SemIR::BindValue{expr.type_id(), expr_id}});
       // We now have a value expression.
       [[fallthrough]];
@@ -993,7 +993,7 @@ auto Convert(Context& context, Parse::NodeId node_id, SemIR::InstId expr_id,
         init_rep.kind == SemIR::InitRepr::ByCopy) {
       target.init_block->InsertHere();
       expr_id = context.AddInst(
-          {node_id,
+          {loc_id,
            SemIR::InitializeFrom{target.type_id, expr_id, target.init_id}});
     }
   }
@@ -1001,11 +1001,11 @@ auto Convert(Context& context, Parse::NodeId node_id, SemIR::InstId expr_id,
   return expr_id;
 }
 
-auto Initialize(Context& context, Parse::NodeId node_id,
+auto Initialize(Context& context, SemIR::LocationId loc_id,
                 SemIR::InstId target_id, SemIR::InstId value_id)
     -> SemIR::InstId {
   PendingBlock target_block(context);
-  return Convert(context, node_id, value_id,
+  return Convert(context, loc_id, value_id,
                  {.kind = ConversionTarget::Initializer,
                   .type_id = context.insts().Get(target_id).type_id(),
                   .init_id = target_id,
@@ -1014,36 +1014,36 @@ auto Initialize(Context& context, Parse::NodeId node_id,
 
 auto ConvertToValueExpr(Context& context, SemIR::InstId expr_id)
     -> SemIR::InstId {
-  return Convert(context, context.insts().GetNodeId(expr_id), expr_id,
+  return Convert(context, context.insts().GetLocationId(expr_id), expr_id,
                  {.kind = ConversionTarget::Value,
                   .type_id = context.insts().Get(expr_id).type_id()});
 }
 
 auto ConvertToValueOrRefExpr(Context& context, SemIR::InstId expr_id)
     -> SemIR::InstId {
-  return Convert(context, context.insts().GetNodeId(expr_id), expr_id,
+  return Convert(context, context.insts().GetLocationId(expr_id), expr_id,
                  {.kind = ConversionTarget::ValueOrRef,
                   .type_id = context.insts().Get(expr_id).type_id()});
 }
 
-auto ConvertToValueOfType(Context& context, Parse::NodeId node_id,
+auto ConvertToValueOfType(Context& context, SemIR::LocationId loc_id,
                           SemIR::InstId expr_id, SemIR::TypeId type_id)
     -> SemIR::InstId {
-  return Convert(context, node_id, expr_id,
+  return Convert(context, loc_id, expr_id,
                  {.kind = ConversionTarget::Value, .type_id = type_id});
 }
 
-auto ConvertToValueOrRefOfType(Context& context, Parse::NodeId node_id,
+auto ConvertToValueOrRefOfType(Context& context, SemIR::LocationId loc_id,
                                SemIR::InstId expr_id, SemIR::TypeId type_id)
     -> SemIR::InstId {
-  return Convert(context, node_id, expr_id,
+  return Convert(context, loc_id, expr_id,
                  {.kind = ConversionTarget::ValueOrRef, .type_id = type_id});
 }
 
-auto ConvertToBoolValue(Context& context, Parse::NodeId node_id,
+auto ConvertToBoolValue(Context& context, SemIR::LocationId loc_id,
                         SemIR::InstId value_id) -> SemIR::InstId {
   return ConvertToValueOfType(
-      context, node_id, value_id,
+      context, loc_id, value_id,
       context.GetBuiltinType(SemIR::BuiltinKind::BoolType));
 }
 
@@ -1057,7 +1057,7 @@ auto ConvertForExplicitAs(Context& context, Parse::NodeId as_node,
 CARBON_DIAGNOSTIC(InCallToFunction, Note, "Calling function declared here.");
 
 // Convert the object argument in a method call to match the `self` parameter.
-static auto ConvertSelf(Context& context, Parse::NodeId call_node_id,
+static auto ConvertSelf(Context& context, SemIR::LocationId call_loc_id,
                         SemIR::InstId callee_id,
                         std::optional<SemIR::AddrPattern> addr_pattern,
                         SemIR::InstId self_param_id, SemIR::Param self_param,
@@ -1066,7 +1066,7 @@ static auto ConvertSelf(Context& context, Parse::NodeId call_node_id,
     CARBON_DIAGNOSTIC(MissingObjectInMethodCall, Error,
                       "Missing object argument in method call.");
     context.emitter()
-        .Build(call_node_id, MissingObjectInMethodCall)
+        .Build(call_loc_id, MissingObjectInMethodCall)
         .Note(callee_id, InCallToFunction)
         .Emit();
     return SemIR::InstId::BuiltinError;
@@ -1096,20 +1096,20 @@ static auto ConvertSelf(Context& context, Parse::NodeId call_node_id,
       default:
         CARBON_DIAGNOSTIC(AddrSelfIsNonRef, Error,
                           "`addr self` method cannot be invoked on a value.");
-        context.emitter().Emit(TokenOnly(call_node_id), AddrSelfIsNonRef);
+        context.emitter().Emit(TokenOnly(call_loc_id), AddrSelfIsNonRef);
         return SemIR::InstId::BuiltinError;
     }
-    auto node_id = context.insts().GetNodeId(self_or_addr_id);
+    auto loc_id = context.insts().GetLocationId(self_or_addr_id);
     self_or_addr_id = context.AddInst(
-        {node_id, SemIR::AddrOf{context.GetPointerType(self.type_id()),
-                                self_or_addr_id}});
+        {loc_id, SemIR::AddrOf{context.GetPointerType(self.type_id()),
+                               self_or_addr_id}});
   }
 
-  return ConvertToValueOfType(context, call_node_id, self_or_addr_id,
+  return ConvertToValueOfType(context, call_loc_id, self_or_addr_id,
                               self_param.type_id);
 }
 
-auto ConvertCallArgs(Context& context, Parse::NodeId call_node_id,
+auto ConvertCallArgs(Context& context, SemIR::LocationId call_loc_id,
                      SemIR::InstId self_id,
                      llvm::ArrayRef<SemIR::InstId> arg_refs,
                      SemIR::InstId return_storage_id, SemIR::InstId callee_id,
@@ -1125,7 +1125,7 @@ auto ConvertCallArgs(Context& context, Parse::NodeId call_node_id,
                       "{1} argument(s).",
                       int, int);
     context.emitter()
-        .Build(call_node_id, CallArgCountMismatch, arg_refs.size(),
+        .Build(call_loc_id, CallArgCountMismatch, arg_refs.size(),
                param_refs.size())
         .Note(callee_id, InCallToFunction)
         .Emit();
@@ -1145,7 +1145,7 @@ auto ConvertCallArgs(Context& context, Parse::NodeId call_node_id,
         context.sem_ir(), implicit_param_id);
     if (param.name_id == SemIR::NameId::SelfValue) {
       auto converted_self_id =
-          ConvertSelf(context, call_node_id, callee_id, addr_pattern, param_id,
+          ConvertSelf(context, call_loc_id, callee_id, addr_pattern, param_id,
                       param, self_id);
       if (converted_self_id == SemIR::InstId::BuiltinError) {
         return SemIR::InstBlockId::Invalid;
@@ -1153,7 +1153,7 @@ auto ConvertCallArgs(Context& context, Parse::NodeId call_node_id,
       args.push_back(converted_self_id);
     } else {
       // TODO: Form argument values for implicit parameters.
-      context.TODO(call_node_id, "Call with implicit parameters");
+      context.TODO(call_loc_id, "Call with implicit parameters");
       return SemIR::InstBlockId::Invalid;
     }
   }
@@ -1175,7 +1175,7 @@ auto ConvertCallArgs(Context& context, Parse::NodeId call_node_id,
     // TODO: Convert to the proper expression category. For now, we assume
     // parameters are all `let` bindings.
     auto converted_arg_id =
-        ConvertToValueOfType(context, call_node_id, arg_id, param_type_id);
+        ConvertToValueOfType(context, call_loc_id, arg_id, param_type_id);
     if (converted_arg_id == SemIR::InstId::BuiltinError) {
       return SemIR::InstBlockId::Invalid;
     }
@@ -1191,10 +1191,10 @@ auto ConvertCallArgs(Context& context, Parse::NodeId call_node_id,
   return context.inst_blocks().Add(args);
 }
 
-auto ExprAsType(Context& context, Parse::NodeId node_id, SemIR::InstId value_id)
-    -> SemIR::TypeId {
+auto ExprAsType(Context& context, SemIR::LocationId loc_id,
+                SemIR::InstId value_id) -> SemIR::TypeId {
   auto type_inst_id =
-      ConvertToValueOfType(context, node_id, value_id, SemIR::TypeId::TypeType);
+      ConvertToValueOfType(context, loc_id, value_id, SemIR::TypeId::TypeType);
   if (type_inst_id == SemIR::InstId::BuiltinError) {
     return SemIR::TypeId::Error;
   }
@@ -1203,7 +1203,7 @@ auto ExprAsType(Context& context, Parse::NodeId node_id, SemIR::InstId value_id)
   if (!type_const_id.is_constant()) {
     CARBON_DIAGNOSTIC(TypeExprEvaluationFailure, Error,
                       "Cannot evaluate type expression.");
-    context.emitter().Emit(node_id, TypeExprEvaluationFailure);
+    context.emitter().Emit(loc_id, TypeExprEvaluationFailure);
     return SemIR::TypeId::Error;
   }
 

+ 8 - 8
toolchain/check/convert.h

@@ -50,13 +50,13 @@ struct ConversionTarget {
 };
 
 // Convert a value to another type and expression category.
-auto Convert(Context& context, Parse::NodeId node_id, SemIR::InstId expr_id,
+auto Convert(Context& context, SemIR::LocationId loc_id, SemIR::InstId expr_id,
              ConversionTarget target) -> SemIR::InstId;
 
 // Performs initialization of `target_id` from `value_id`. Returns the
 // possibly-converted initializing expression, which should be assigned to the
 // target using a suitable node for the kind of initialization.
-auto Initialize(Context& context, Parse::NodeId node_id,
+auto Initialize(Context& context, SemIR::LocationId loc_id,
                 SemIR::InstId target_id, SemIR::InstId value_id)
     -> SemIR::InstId;
 
@@ -70,18 +70,18 @@ auto ConvertToValueOrRefExpr(Context& context, SemIR::InstId expr_id)
     -> SemIR::InstId;
 
 // Converts `expr_id` to a value expression of type `type_id`.
-auto ConvertToValueOfType(Context& context, Parse::NodeId node_id,
+auto ConvertToValueOfType(Context& context, SemIR::LocationId loc_id,
                           SemIR::InstId expr_id, SemIR::TypeId type_id)
     -> SemIR::InstId;
 
 // Convert the given expression to a value or reference expression of the given
 // type.
-auto ConvertToValueOrRefOfType(Context& context, Parse::NodeId node_id,
+auto ConvertToValueOrRefOfType(Context& context, SemIR::LocationId loc_id,
                                SemIR::InstId expr_id, SemIR::TypeId type_id)
     -> SemIR::InstId;
 
 // Converts `value_id` to a value expression of type `bool`.
-auto ConvertToBoolValue(Context& context, Parse::NodeId node_id,
+auto ConvertToBoolValue(Context& context, SemIR::LocationId loc_id,
                         SemIR::InstId value_id) -> SemIR::InstId;
 
 // Converts `value_id` to type `type_id` for an `as` expression.
@@ -92,7 +92,7 @@ auto ConvertForExplicitAs(Context& context, Parse::NodeId as_node,
 // Implicitly converts a set of arguments to match the parameter types in a
 // function call. Returns a block containing the converted implicit and explicit
 // argument values.
-auto ConvertCallArgs(Context& context, Parse::NodeId call_node_id,
+auto ConvertCallArgs(Context& context, SemIR::LocationId call_loc_id,
                      SemIR::InstId self_id,
                      llvm::ArrayRef<SemIR::InstId> arg_refs,
                      SemIR::InstId return_storage_id, SemIR::InstId callee_id,
@@ -100,8 +100,8 @@ auto ConvertCallArgs(Context& context, Parse::NodeId call_node_id,
                      SemIR::InstBlockId param_refs_id) -> SemIR::InstBlockId;
 
 // Converts an expression for use as a type.
-auto ExprAsType(Context& context, Parse::NodeId node_id, SemIR::InstId value_id)
-    -> SemIR::TypeId;
+auto ExprAsType(Context& context, SemIR::LocationId loc_id,
+                SemIR::InstId value_id) -> SemIR::TypeId;
 
 }  // namespace Carbon::Check
 

+ 18 - 18
toolchain/check/decl_name_stack.cpp

@@ -15,10 +15,10 @@ auto DeclNameStack::MakeEmptyNameContext() -> NameContext {
       .target_scope_id = context_->scope_stack().PeekNameScopeId()};
 }
 
-auto DeclNameStack::MakeUnqualifiedName(Parse::NodeId node_id,
+auto DeclNameStack::MakeUnqualifiedName(SemIR::LocationId loc_id,
                                         SemIR::NameId name_id) -> NameContext {
   NameContext context = MakeEmptyNameContext();
-  ApplyNameQualifierTo(context, node_id, name_id, /*is_unqualified=*/true);
+  ApplyNameQualifierTo(context, loc_id, name_id, /*is_unqualified=*/true);
   return context;
 }
 
@@ -38,8 +38,8 @@ auto DeclNameStack::FinishName() -> NameContext {
     // into the name.
   } else {
     // The name had no qualifiers, so we need to process the node now.
-    auto [node_id, name_id] = context_->node_stack().PopNameWithNodeId();
-    ApplyNameQualifier(node_id, name_id);
+    auto [loc_id, name_id] = context_->node_stack().PopNameWithNodeId();
+    ApplyNameQualifier(loc_id, name_id);
   }
 
   NameContext result = decl_name_stack_.back();
@@ -92,7 +92,7 @@ auto DeclNameStack::LookupOrAddName(NameContext name_context,
                 QualifiedDeclOutsideScopeEntity, Error,
                 "Out-of-line declaration requires a declaration in "
                 "scoped entity.");
-            context_->emitter().Emit(name_context.node_id,
+            context_->emitter().Emit(name_context.loc_id,
                                      QualifiedDeclOutsideScopeEntity);
           }
         }
@@ -125,20 +125,20 @@ auto DeclNameStack::AddNameToLookup(NameContext name_context,
   }
 }
 
-auto DeclNameStack::ApplyNameQualifier(Parse::NodeId node_id,
+auto DeclNameStack::ApplyNameQualifier(SemIR::LocationId loc_id,
                                        SemIR::NameId name_id) -> void {
-  ApplyNameQualifierTo(decl_name_stack_.back(), node_id, name_id,
+  ApplyNameQualifierTo(decl_name_stack_.back(), loc_id, name_id,
                        /*is_unqualified=*/false);
 }
 
 auto DeclNameStack::ApplyNameQualifierTo(NameContext& name_context,
-                                         Parse::NodeId node_id,
+                                         SemIR::LocationId loc_id,
                                          SemIR::NameId name_id,
                                          bool is_unqualified) -> void {
-  if (TryResolveQualifier(name_context, node_id)) {
+  if (TryResolveQualifier(name_context, loc_id)) {
     // For identifier nodes, we need to perform a lookup on the identifier.
     auto resolved_inst_id = context_->LookupNameInDecl(
-        name_context.node_id, name_id, name_context.target_scope_id);
+        name_context.loc_id, name_id, name_context.target_scope_id);
     if (!resolved_inst_id.is_valid()) {
       // Invalid indicates an unresolved name. Store it and return.
       name_context.state = NameContext::State::Unresolved;
@@ -221,7 +221,7 @@ auto DeclNameStack::UpdateScopeIfNeeded(NameContext& name_context,
         CARBON_DIAGNOSTIC(QualifiedDeclOutsidePackageSource, Note,
                           "Package imported here.");
         context_->emitter()
-            .Build(name_context.node_id, QualifiedDeclOutsidePackage)
+            .Build(name_context.loc_id, QualifiedDeclOutsidePackage)
             .Note(scope.inst_id, QualifiedDeclOutsidePackageSource)
             .Emit();
         // Only error once per package.
@@ -241,7 +241,7 @@ auto DeclNameStack::UpdateScopeIfNeeded(NameContext& name_context,
 }
 
 auto DeclNameStack::TryResolveQualifier(NameContext& name_context,
-                                        Parse::NodeId node_id) -> bool {
+                                        SemIR::LocationId loc_id) -> bool {
   // Update has_qualifiers based on the state before any possible changes. If
   // this is the first qualifier, it may just be the name.
   name_context.has_qualifiers = name_context.state != NameContext::State::Empty;
@@ -255,7 +255,7 @@ auto DeclNameStack::TryResolveQualifier(NameContext& name_context,
       // Because more qualifiers were found, we diagnose that the earlier
       // qualifier failed to resolve.
       name_context.state = NameContext::State::Error;
-      context_->DiagnoseNameNotFound(name_context.node_id,
+      context_->DiagnoseNameNotFound(name_context.loc_id,
                                      name_context.unresolved_name_id);
       return false;
 
@@ -268,7 +268,7 @@ auto DeclNameStack::TryResolveQualifier(NameContext& name_context,
                           "Cannot declare a member of incomplete class `{0}`.",
                           SemIR::TypeId);
         auto builder = context_->emitter().Build(
-            name_context.node_id, QualifiedDeclInIncompleteClassScope,
+            name_context.loc_id, QualifiedDeclInIncompleteClassScope,
             context_->classes().Get(class_decl->class_id).self_type_id);
         context_->NoteIncompleteClass(class_decl->class_id, builder);
         builder.Emit();
@@ -280,7 +280,7 @@ auto DeclNameStack::TryResolveQualifier(NameContext& name_context,
             "Cannot declare a member of undefined interface `{0}`.",
             std::string);
         auto builder = context_->emitter().Build(
-            name_context.node_id, QualifiedDeclInUndefinedInterfaceScope,
+            name_context.loc_id, QualifiedDeclInUndefinedInterfaceScope,
             context_->sem_ir().StringifyTypeExpr(
                 context_->sem_ir()
                     .constant_values()
@@ -295,8 +295,8 @@ auto DeclNameStack::TryResolveQualifier(NameContext& name_context,
         CARBON_DIAGNOSTIC(QualifiedNameNonScopeEntity, Note,
                           "Non-scope entity referenced here.");
         context_->emitter()
-            .Build(node_id, QualifiedNameInNonScope)
-            .Note(name_context.node_id, QualifiedNameNonScopeEntity)
+            .Build(loc_id, QualifiedNameInNonScope)
+            .Note(name_context.loc_id, QualifiedNameNonScopeEntity)
             .Emit();
       }
       name_context.state = NameContext::State::Error;
@@ -305,7 +305,7 @@ auto DeclNameStack::TryResolveQualifier(NameContext& name_context,
 
     case NameContext::State::Empty:
     case NameContext::State::Resolved: {
-      name_context.node_id = node_id;
+      name_context.loc_id = loc_id;
       return true;
     }
 

+ 7 - 7
toolchain/check/decl_name_stack.h

@@ -7,7 +7,6 @@
 
 #include "llvm/ADT/SmallVector.h"
 #include "toolchain/check/scope_index.h"
-#include "toolchain/parse/node_ids.h"
 #include "toolchain/sem_ir/ids.h"
 
 namespace Carbon::Check {
@@ -124,8 +123,8 @@ class DeclNameStack {
     // should be used.
     SemIR::NameScopeId target_scope_id;
 
-    // The last parse node used.
-    Parse::NodeId node_id = Parse::NodeId::Invalid;
+    // The last location ID used.
+    SemIR::LocationId loc_id = SemIR::LocationId::Invalid;
 
     union {
       // The ID of a resolved qualifier, including both identifiers and
@@ -177,13 +176,14 @@ class DeclNameStack {
   // unqualified name in the current context. This is suitable for adding to
   // name lookup in situations where a qualified name is not permitted, such as
   // a pattern binding.
-  auto MakeUnqualifiedName(Parse::NodeId node_id, SemIR::NameId name_id)
+  auto MakeUnqualifiedName(SemIR::LocationId loc_id, SemIR::NameId name_id)
       -> NameContext;
 
   // Applies a Name from the name stack to the top of the declaration name
   // stack. This will enter the scope corresponding to the name if the name
   // describes an existing scope, such as a namespace or a defined class.
-  auto ApplyNameQualifier(Parse::NodeId node_id, SemIR::NameId name_id) -> void;
+  auto ApplyNameQualifier(SemIR::LocationId loc_id, SemIR::NameId name_id)
+      -> void;
 
   // Adds a name to name lookup. Prints a diagnostic for name conflicts.
   auto AddNameToLookup(NameContext name_context, SemIR::InstId target_id)
@@ -199,12 +199,12 @@ class DeclNameStack {
   auto MakeEmptyNameContext() -> NameContext;
 
   // Applies a Name from the name stack to given name context.
-  auto ApplyNameQualifierTo(NameContext& name_context, Parse::NodeId node_id,
+  auto ApplyNameQualifierTo(NameContext& name_context, SemIR::LocationId loc_id,
                             SemIR::NameId name_id, bool is_unqualified) -> void;
 
   // Returns true if the context is in a state where it can resolve qualifiers.
   // Updates name_context as needed.
-  auto TryResolveQualifier(NameContext& name_context, Parse::NodeId node_id)
+  auto TryResolveQualifier(NameContext& name_context, SemIR::LocationId loc_id)
       -> bool;
 
   // Updates the scope on name_context as needed. This is called after

+ 14 - 6
toolchain/check/diagnostic_helpers.h

@@ -16,23 +16,31 @@ namespace Carbon::Check {
 // directly, or an inst ID which is later translated to a parse node.
 struct SemIRLocation {
   // NOLINTNEXTLINE(google-explicit-constructor)
-  SemIRLocation(SemIR::InstId inst_id) : inst_id(inst_id), is_inst_id(true) {}
+  SemIRLocation(SemIR::InstId inst_id)
+      : inst_id(inst_id), is_inst_id(true), token_only(false) {}
 
   // NOLINTNEXTLINE(google-explicit-constructor)
-  SemIRLocation(Parse::NodeLocation node_location)
-      : node_location(node_location), is_inst_id(false) {}
+  SemIRLocation(Parse::NodeId node_id) : SemIRLocation(node_id, false) {}
+
   // NOLINTNEXTLINE(google-explicit-constructor)
-  SemIRLocation(Parse::NodeId node_id)
-      : SemIRLocation(Parse::NodeLocation(node_id)) {}
+  SemIRLocation(SemIR::LocationId loc_id) : SemIRLocation(loc_id, false) {}
+
+  explicit SemIRLocation(SemIR::LocationId loc_id, bool token_only)
+      : loc_id(loc_id), is_inst_id(false), token_only(token_only) {}
 
   union {
     SemIR::InstId inst_id;
-    Parse::NodeLocation node_location;
+    SemIR::LocationId loc_id;
   };
 
   bool is_inst_id;
+  bool token_only;
 };
 
+inline auto TokenOnly(SemIR::LocationId loc_id) -> SemIRLocation {
+  return SemIRLocation(loc_id, true);
+}
+
 // An integer value together with its type. The type is used to determine how to
 // format the value in diagnostics.
 struct TypedInt {

+ 3 - 3
toolchain/check/handle_alias.cpp

@@ -48,20 +48,20 @@ auto HandleAlias(Context& context, Parse::AliasId /*node_id*/) -> bool {
     // TODO: Look into handling `false`, this doesn't do it right now because it
     // sees a value instruction instead of a builtin.
     alias_id = context.AddInst(
-        {name_context.node_id,
+        {name_context.loc_id,
          SemIR::BindAlias{context.insts().Get(expr_id).type_id(), bind_name_id,
                           expr_id}});
   } else if (auto inst = context.insts().TryGetAs<SemIR::NameRef>(expr_id)) {
     // Pass through name references, albeit changing the name in use.
     alias_id = context.AddInst(
-        {name_context.node_id,
+        {name_context.loc_id,
          SemIR::BindAlias{inst->type_id, bind_name_id, inst->value_id}});
   } else {
     CARBON_DIAGNOSTIC(AliasRequiresNameRef, Error,
                       "Alias initializer must be a name reference.");
     context.emitter().Emit(expr_node, AliasRequiresNameRef);
     alias_id =
-        context.AddInst({name_context.node_id,
+        context.AddInst({name_context.loc_id,
                          SemIR::BindAlias{SemIR::TypeId::Error, bind_name_id,
                                           SemIR::InstId::BuiltinError}});
   }

+ 3 - 2
toolchain/check/handle_binding_pattern.cpp

@@ -19,8 +19,9 @@ auto HandleAnyBindingPattern(Context& context, Parse::NodeId node_id,
   auto [name_node, name_id] = context.node_stack().PopNameWithNodeId();
 
   // Create the appropriate kind of binding for this pattern.
-  auto make_bind_name = [&](SemIR::TypeId type_id,
-                            SemIR::InstId value_id) -> SemIR::NodeIdAndInst {
+  auto make_bind_name =
+      [&](SemIR::TypeId type_id,
+          SemIR::InstId value_id) -> SemIR::LocationIdAndInst {
     // TODO: Eventually the name will need to support associations with other
     // scopes, but right now we don't support qualified names here.
     auto bind_name_id = context.bind_names().Add(

+ 1 - 1
toolchain/check/handle_expr_statement.cpp

@@ -15,7 +15,7 @@ static auto HandleDiscardedExpr(Context& context, SemIR::InstId expr_id)
   // If we discard an initializing expression, convert it to a value or
   // reference so that it has something to initialize.
   auto expr = context.insts().Get(expr_id);
-  Convert(context, context.insts().GetNodeId(expr_id), expr_id,
+  Convert(context, context.insts().GetLocationId(expr_id), expr_id,
           {.kind = ConversionTarget::Discarded, .type_id = expr.type_id()});
 
   // TODO: This will eventually need to do some "do not discard" analysis.

+ 2 - 2
toolchain/check/handle_index.cpp

@@ -45,7 +45,7 @@ auto HandleIndexExpr(Context& context, Parse::IndexExprId node_id) -> bool {
   switch (operand_type_inst.kind()) {
     case SemIR::ArrayType::Kind: {
       auto array_type = operand_type_inst.As<SemIR::ArrayType>();
-      auto index_node_id = context.insts().GetNodeId(index_inst_id);
+      auto index_node_id = context.insts().GetLocationId(index_inst_id);
       auto cast_index_id = ConvertToValueOfType(
           context, index_node_id, index_inst_id,
           context.GetBuiltinType(SemIR::BuiltinKind::IntType));
@@ -74,7 +74,7 @@ auto HandleIndexExpr(Context& context, Parse::IndexExprId node_id) -> bool {
     }
     case SemIR::TupleType::Kind: {
       SemIR::TypeId element_type_id = SemIR::TypeId::Error;
-      auto index_node_id = context.insts().GetNodeId(index_inst_id);
+      auto index_node_id = context.insts().GetLocationId(index_inst_id);
       index_inst_id = ConvertToValueOfType(
           context, index_node_id, index_inst_id,
           context.GetBuiltinType(SemIR::BuiltinKind::IntType));

+ 5 - 6
toolchain/check/handle_let.cpp

@@ -28,7 +28,7 @@ auto HandleLetInitializer(Context& context, Parse::LetInitializerId node_id)
 
 static auto BuildAssociatedConstantDecl(
     Context& context, Parse::LetDeclId node_id, SemIR::InstId pattern_id,
-    SemIR::NodeIdAndInst pattern, SemIR::InterfaceId interface_id) -> void {
+    SemIR::LocationIdAndInst pattern, SemIR::InterfaceId interface_id) -> void {
   auto& interface_info = context.interfaces().Get(interface_id);
 
   auto binding_pattern = pattern.inst.TryAs<SemIR::BindSymbolicName>();
@@ -36,7 +36,7 @@ static auto BuildAssociatedConstantDecl(
     CARBON_DIAGNOSTIC(ExpectedSymbolicBindingInAssociatedConstant, Error,
                       "Pattern in associated constant declaration must be a "
                       "single `:!` binding.");
-    context.emitter().Emit(pattern.node_id,
+    context.emitter().Emit(pattern.loc_id,
                            ExpectedSymbolicBindingInAssociatedConstant);
     context.name_scopes().Get(interface_info.scope_id).has_error = true;
     return;
@@ -55,7 +55,7 @@ static auto BuildAssociatedConstantDecl(
   // Add an associated entity name to the interface scope.
   auto assoc_id = BuildAssociatedEntity(context, interface_id, decl_id);
   auto name_context =
-      context.decl_name_stack().MakeUnqualifiedName(pattern.node_id, name_id);
+      context.decl_name_stack().MakeUnqualifiedName(pattern.loc_id, name_id);
   context.decl_name_stack().AddNameToLookup(name_context, assoc_id);
 }
 
@@ -98,7 +98,7 @@ auto HandleLetDecl(Context& context, Parse::LetDeclId node_id) -> bool {
   }
   context.decl_state_stack().Pop(DeclState::Let);
 
-  auto pattern = context.insts().GetWithNodeId(pattern_id);
+  auto pattern = context.insts().GetWithLocationId(pattern_id);
   auto interface_scope = context.GetCurrentScopeAs<SemIR::InterfaceDecl>();
 
   if (value_id) {
@@ -119,8 +119,7 @@ auto HandleLetDecl(Context& context, Parse::LetDeclId node_id) -> bool {
     CARBON_DIAGNOSTIC(
         ExpectedInitializerAfterLet, Error,
         "Expected `=`; `let` declaration must have an initializer.");
-    context.emitter().Emit(Parse::TokenOnly(node_id),
-                           ExpectedInitializerAfterLet);
+    context.emitter().Emit(TokenOnly(node_id), ExpectedInitializerAfterLet);
     value_id = SemIR::InstId::BuiltinError;
   }
 

+ 2 - 2
toolchain/check/handle_namespace.cpp

@@ -37,10 +37,10 @@ auto HandleNamespace(Context& context, Parse::NamespaceId node_id) -> bool {
     // previous declaration. Otherwise, diagnose the issue.
     if (auto existing =
             context.insts().TryGetAs<SemIR::Namespace>(existing_inst_id)) {
-      // When the name conflict is an imported namespace, fill the parse node
+      // When the name conflict is an imported namespace, fill the location ID
       // so that future diagnostics point at this declaration.
       if (existing->import_id.is_valid() &&
-          !context.insts().GetNodeId(existing_inst_id).is_valid()) {
+          !context.insts().GetLocationId(existing_inst_id).is_valid()) {
         context.SetNamespaceNodeId(existing_inst_id, node_id);
       }
     } else {

+ 2 - 2
toolchain/check/handle_variable.cpp

@@ -55,7 +55,7 @@ auto HandleVariableDecl(Context& context, Parse::VariableDeclId node_id)
     // Form a corresponding name in the current context, and bind the name to
     // the variable.
     auto name_context = context.decl_name_stack().MakeUnqualifiedName(
-        context.insts().GetNodeId(value_id),
+        context.insts().GetLocationId(value_id),
         context.bind_names().Get(bind_name->bind_name_id).name_id);
     context.decl_name_stack().AddNameToLookup(name_context, value_id);
     value_id = bind_name->value_id;
@@ -63,7 +63,7 @@ auto HandleVariableDecl(Context& context, Parse::VariableDeclId node_id)
                  context.insts().TryGetAs<SemIR::FieldDecl>(value_id)) {
     // Introduce the field name into the class.
     auto name_context = context.decl_name_stack().MakeUnqualifiedName(
-        context.insts().GetNodeId(value_id), field_decl->name_id);
+        context.insts().GetLocationId(value_id), field_decl->name_id);
     context.decl_name_stack().AddNameToLookup(name_context, value_id);
   }
   // TODO: Handle other kinds of pattern.

+ 1 - 1
toolchain/check/interface.cpp

@@ -29,7 +29,7 @@ auto BuildAssociatedEntity(Context& context, SemIR::InterfaceId interface_id,
   // not the declaration itself.
   auto type_id = context.GetAssociatedEntityType(
       interface_id, context.insts().Get(decl_id).type_id());
-  return context.AddInst({context.insts().GetNodeId(decl_id),
+  return context.AddInst({context.insts().GetLocationId(decl_id),
                           SemIR::AssociatedEntity{type_id, index, decl_id}});
 }
 

+ 7 - 7
toolchain/check/modifiers.cpp

@@ -11,17 +11,17 @@ namespace Carbon::Check {
 static auto ReportNotAllowed(Context& context, Parse::NodeId modifier_node,
                              Lex::TokenKind decl_kind,
                              llvm::StringRef context_string,
-                             Parse::NodeId context_node) -> void {
+                             SemIR::LocationId context_loc_id) -> void {
   CARBON_DIAGNOSTIC(ModifierNotAllowedOn, Error,
                     "`{0}` not allowed on `{1}` declaration{2}.",
                     Lex::TokenKind, Lex::TokenKind, std::string);
   auto diag = context.emitter().Build(modifier_node, ModifierNotAllowedOn,
                                       context.token_kind(modifier_node),
                                       decl_kind, context_string.str());
-  if (context_node.is_valid()) {
+  if (context_loc_id.is_valid()) {
     CARBON_DIAGNOSTIC(ModifierNotInContext, Note,
                       "Containing definition here.");
-    diag.Note(context_node, ModifierNotInContext);
+    diag.Note(context_loc_id, ModifierNotInContext);
   }
   diag.Emit();
 }
@@ -41,7 +41,7 @@ static auto ModifierOrderAsSet(ModifierOrder order) -> KeywordModifierSet {
 auto ForbidModifiersOnDecl(Context& context, KeywordModifierSet forbidden,
                            Lex::TokenKind decl_kind,
                            llvm::StringRef context_string,
-                           Parse::NodeId context_node) -> void {
+                           SemIR::LocationId context_loc_id) -> void {
   auto& s = context.decl_state_stack().innermost();
   auto not_allowed = s.modifier_set & forbidden;
   if (!not_allowed) {
@@ -53,7 +53,7 @@ auto ForbidModifiersOnDecl(Context& context, KeywordModifierSet forbidden,
     auto order = static_cast<ModifierOrder>(order_index);
     if (!!(not_allowed & ModifierOrderAsSet(order))) {
       ReportNotAllowed(context, s.modifier_node_id(order), decl_kind,
-                       context_string, context_node);
+                       context_string, context_loc_id);
       s.set_modifier_node_id(order, Parse::NodeId::Invalid);
     }
   }
@@ -121,12 +121,12 @@ auto CheckMethodModifiersOnFunction(Context& context,
       if (inheritance_kind == SemIR::Class::Final) {
         ForbidModifiersOnDecl(context, KeywordModifierSet::Virtual, decl_kind,
                               " in a non-abstract non-base `class` definition",
-                              context.insts().GetNodeId(target_id));
+                              context.insts().GetLocationId(target_id));
       }
       if (inheritance_kind != SemIR::Class::Abstract) {
         ForbidModifiersOnDecl(context, KeywordModifierSet::Abstract, decl_kind,
                               " in a non-abstract `class` definition",
-                              context.insts().GetNodeId(target_id));
+                              context.insts().GetLocationId(target_id));
       }
       return;
     }

+ 5 - 6
toolchain/check/modifiers.h

@@ -26,14 +26,13 @@ auto CheckMethodModifiersOnFunction(Context& context,
                                     SemIR::NameScopeId target_scope_id) -> void;
 
 // Like `LimitModifiersOnDecl`, except says which modifiers are forbidden, and a
-// `context_string` (and optional `context_node`) specifying the context in
+// `context_string` (and optional `context_loc_id`) specifying the context in
 // which those modifiers are forbidden.
 // TODO: Take another look at diagnostic phrasing for callers.
-auto ForbidModifiersOnDecl(Context& context, KeywordModifierSet forbidden,
-                           Lex::TokenKind decl_kind,
-                           llvm::StringRef context_string,
-                           Parse::NodeId context_node = Parse::NodeId::Invalid)
-    -> void;
+auto ForbidModifiersOnDecl(
+    Context& context, KeywordModifierSet forbidden, Lex::TokenKind decl_kind,
+    llvm::StringRef context_string,
+    SemIR::LocationId context_loc_id = SemIR::LocationId::Invalid) -> void;
 
 // Reports a diagnostic (using `decl_kind`) if modifiers on this declaration are
 // not in `allowed`. Updates the declaration state in

+ 5 - 5
toolchain/check/pending_block.h

@@ -39,8 +39,8 @@ class PendingBlock {
     size_t size_;
   };
 
-  auto AddInst(SemIR::NodeIdAndInst node_id_and_inst) -> SemIR::InstId {
-    auto inst_id = context_.AddInstInNoBlock(node_id_and_inst);
+  auto AddInst(SemIR::LocationIdAndInst loc_id_and_inst) -> SemIR::InstId {
+    auto inst_id = context_.AddInstInNoBlock(loc_id_and_inst);
     insts_.push_back(inst_id);
     return inst_id;
   }
@@ -56,7 +56,7 @@ class PendingBlock {
   // Replace the instruction at target_id with the instructions in this block.
   // The new value for target_id should be value_id.
   auto MergeReplacing(SemIR::InstId target_id, SemIR::InstId value_id) -> void {
-    auto value = context_.insts().GetWithNodeId(value_id);
+    auto value = context_.insts().GetWithLocationId(value_id);
 
     // There are three cases here:
 
@@ -64,7 +64,7 @@ class PendingBlock {
       // 1) The block is empty. Replace `target_id` with an empty splice
       // pointing at `value_id`.
       context_.ReplaceInstBeforeConstantUse(
-          target_id, {value.node_id,
+          target_id, {value.loc_id,
                       SemIR::SpliceBlock{value.inst.type_id(),
                                          SemIR::InstBlockId::Empty, value_id}});
     } else if (insts_.size() == 1 && insts_[0] == value_id) {
@@ -75,7 +75,7 @@ class PendingBlock {
       // 3) Anything else: splice it into the IR, replacing `target_id`.
       context_.ReplaceInstBeforeConstantUse(
           target_id,
-          {value.node_id,
+          {value.loc_id,
            SemIR::SpliceBlock{value.inst.type_id(),
                               context_.inst_blocks().Add(insts_), value_id}});
     }

+ 0 - 4
toolchain/parse/tree_node_diagnostic_converter.h

@@ -30,10 +30,6 @@ class NodeLocation {
   bool token_only_;
 };
 
-inline auto TokenOnly(NodeId node_id) -> NodeLocation {
-  return NodeLocation(node_id, true);
-}
-
 class NodeLocationConverter : public DiagnosticConverter<NodeLocation> {
  public:
   explicit NodeLocationConverter(const Lex::TokenizedBuffer* tokens,

+ 2 - 0
toolchain/sem_ir/BUILD

@@ -36,6 +36,7 @@ cc_library(
         "//toolchain/base:index_base",
         "//toolchain/base:value_store",
         "//toolchain/diagnostics:diagnostic_emitter",
+        "//toolchain/parse:node_kind",
         "//toolchain/sem_ir:builtin_kind",
     ],
 )
@@ -92,6 +93,7 @@ cc_library(
         "file.h",
         "function.h",
         "impl.h",
+        "import_ir.h",
         "interface.h",
         "name.h",
         "name_scope.h",

+ 1 - 1
toolchain/sem_ir/constant.cpp

@@ -26,7 +26,7 @@ auto ConstantStore::GetOrAdd(Inst inst, bool is_symbolic) -> ConstantId {
 
   // Create the new inst and insert the new node.
   auto inst_id = constants_.getContext()->insts().AddInNoBlock(
-      NodeIdAndInst::Untyped(Parse::NodeId::Invalid, inst));
+      LocationIdAndInst::Untyped(Parse::NodeId::Invalid, inst));
   auto constant_id = is_symbolic
                          ? SemIR::ConstantId::ForSymbolicConstant(inst_id)
                          : SemIR::ConstantId::ForTemplateConstant(inst_id);

+ 11 - 9
toolchain/sem_ir/file.h

@@ -17,6 +17,7 @@
 #include "toolchain/sem_ir/function.h"
 #include "toolchain/sem_ir/ids.h"
 #include "toolchain/sem_ir/impl.h"
+#include "toolchain/sem_ir/import_ir.h"
 #include "toolchain/sem_ir/inst.h"
 #include "toolchain/sem_ir/interface.h"
 #include "toolchain/sem_ir/name.h"
@@ -40,15 +41,6 @@ struct BindNameInfo : public Printable<BindNameInfo> {
 
 class File;
 
-struct ImportIR : public Printable<ImportIR> {
-  auto Print(llvm::raw_ostream& out) const -> void { out << node_id; }
-
-  // The node ID for the import.
-  Parse::ImportDirectiveId node_id;
-  // The imported IR.
-  const File* sem_ir;
-};
-
 // Provides semantic analysis on a Parse::Tree.
 class File : public Printable<File> {
  public:
@@ -149,6 +141,12 @@ class File : public Printable<File> {
   auto import_irs() const -> const ValueStore<ImportIRId>& {
     return import_irs_;
   }
+  auto import_ir_insts() -> ValueStore<ImportIRInstId>& {
+    return import_ir_insts_;
+  }
+  auto import_ir_insts() const -> const ValueStore<ImportIRInstId>& {
+    return import_ir_insts_;
+  }
   auto names() const -> NameStoreWrapper {
     return NameStoreWrapper(&identifiers());
   }
@@ -225,6 +223,10 @@ class File : public Printable<File> {
   // for references of builtins).
   ValueStore<ImportIRId> import_irs_;
 
+  // Related IR instructions. These are created for LocationIds for instructions
+  // that are import-related.
+  ValueStore<ImportIRInstId> import_ir_insts_;
+
   // Storage for name scopes.
   NameScopeStore name_scopes_;
 

+ 18 - 14
toolchain/sem_ir/formatter.cpp

@@ -83,7 +83,8 @@ class InstNamer {
         insts[fn.return_slot_id.index] = {
             fn_scope,
             GetScopeInfo(fn_scope).insts.AllocateName(
-                *this, sem_ir.insts().GetNodeId(fn.return_slot_id), "return")};
+                *this, sem_ir.insts().GetLocationId(fn.return_slot_id),
+                "return")};
       }
       if (!fn.body_block_ids.empty()) {
         AddBlockLabel(fn_scope, fn.body_block_ids.front(), "entry", fn_loc);
@@ -268,7 +269,7 @@ class InstNamer {
       return Name(allocated.insert({name, NameResult()}).first);
     }
 
-    auto AllocateName(const InstNamer& namer, Parse::NodeId node,
+    auto AllocateName(const InstNamer& namer, SemIR::LocationId loc_id,
                       std::string name) -> Name {
       // The best (shortest) name for this instruction so far, and the current
       // name for it.
@@ -307,8 +308,9 @@ class InstNamer {
       }
 
       // Append location information to try to disambiguate.
-      if (node.is_valid()) {
-        auto token = namer.parse_tree_.node_token(node);
+      // TODO: Consider handling inst_id cases.
+      if (loc_id.is_node_id()) {
+        auto token = namer.parse_tree_.node_token(loc_id.node_id());
         llvm::raw_string_ostream(name)
             << ".loc" << namer.tokenized_buffer_.GetLineNumber(token);
         add_name();
@@ -344,29 +346,30 @@ class InstNamer {
 
   auto AddBlockLabel(ScopeId scope_id, InstBlockId block_id,
                      std::string name = "",
-                     Parse::NodeId node_id = Parse::NodeId::Invalid) -> void {
+                     SemIR::LocationId loc_id = SemIR::LocationId::Invalid)
+      -> void {
     if (!block_id.is_valid() || labels[block_id.index].second) {
       return;
     }
 
-    if (!node_id.is_valid()) {
+    if (!loc_id.is_valid()) {
       if (const auto& block = sem_ir_.inst_blocks().Get(block_id);
           !block.empty()) {
-        node_id = sem_ir_.insts().GetNodeId(block.front());
+        loc_id = sem_ir_.insts().GetLocationId(block.front());
       }
     }
 
     labels[block_id.index] = {
-        scope_id, GetScopeInfo(scope_id).labels.AllocateName(*this, node_id,
+        scope_id, GetScopeInfo(scope_id).labels.AllocateName(*this, loc_id,
                                                              std::move(name))};
   }
 
   // Finds and adds a suitable block label for the given SemIR instruction that
   // represents some kind of branch.
-  auto AddBlockLabel(ScopeId scope_id, Parse::NodeId node_id, AnyBranch branch)
-      -> void {
+  auto AddBlockLabel(ScopeId scope_id, SemIR::LocationId loc_id,
+                     AnyBranch branch) -> void {
     llvm::StringRef name;
-    switch (parse_tree_.node_kind(node_id)) {
+    switch (parse_tree_.node_kind(loc_id.node_id())) {
       case Parse::NodeKind::IfExprIf:
         switch (branch.kind) {
           case BranchIf::Kind:
@@ -428,7 +431,7 @@ class InstNamer {
         break;
     }
 
-    AddBlockLabel(scope_id, branch.target_id, name.str(), node_id);
+    AddBlockLabel(scope_id, branch.target_id, name.str(), loc_id);
   }
 
   auto CollectNamesInBlock(ScopeId scope_id, InstBlockId block_id) -> void {
@@ -451,7 +454,7 @@ class InstNamer {
       auto add_inst_name = [&](std::string name) {
         insts[inst_id.index] = {
             scope_id, scope.insts.AllocateName(
-                          *this, sem_ir_.insts().GetNodeId(inst_id), name)};
+                          *this, sem_ir_.insts().GetLocationId(inst_id), name)};
       };
       auto add_inst_name_id = [&](NameId name_id, llvm::StringRef suffix = "") {
         add_inst_name(
@@ -459,7 +462,8 @@ class InstNamer {
       };
 
       if (auto branch = untyped_inst.TryAs<AnyBranch>()) {
-        AddBlockLabel(scope_id, sem_ir_.insts().GetNodeId(inst_id), *branch);
+        AddBlockLabel(scope_id, sem_ir_.insts().GetLocationId(inst_id),
+                      *branch);
       }
 
       CARBON_KIND_SWITCH(untyped_inst) {

+ 63 - 0
toolchain/sem_ir/ids.h

@@ -10,6 +10,7 @@
 #include "toolchain/base/index_base.h"
 #include "toolchain/base/value_store.h"
 #include "toolchain/diagnostics/diagnostic_emitter.h"
+#include "toolchain/parse/node_ids.h"
 #include "toolchain/sem_ir/builtin_kind.h"
 
 namespace Carbon::SemIR {
@@ -21,6 +22,7 @@ struct BindNameInfo;
 struct Class;
 struct Function;
 struct ImportIR;
+struct ImportIRInst;
 struct Interface;
 struct Impl;
 struct NameScope;
@@ -481,6 +483,67 @@ struct ElementIndex : public IndexBase, public Printable<ElementIndex> {
   }
 };
 
+// The ID of an ImportIRInst.
+struct ImportIRInstId : public IdBase, public Printable<InstId> {
+  using ValueType = ImportIRInst;
+
+  using IdBase::IdBase;
+};
+
+// A SemIR location used exclusively for diagnostic locations.
+//
+// Contents:
+// - index > Invalid: A Parse::NodeId in the current IR.
+// - index < Invalid: An ImportIRInstId.
+// - index == Invalid: Can be used for either.
+struct LocationId : public IdBase, public Printable<FunctionId> {
+  // An explicitly invalid function ID.
+  static const LocationId Invalid;
+
+  // NOLINTNEXTLINE(google-explicit-constructor)
+  constexpr LocationId(Parse::InvalidNodeId /*invalid*/)
+      : IdBase(InvalidIndex) {}
+
+  // NOLINTNEXTLINE(google-explicit-constructor)
+  constexpr LocationId(Parse::NodeId node_id) : IdBase(node_id.index) {
+    CARBON_CHECK(node_id.is_valid() == is_valid());
+  }
+
+  // NOLINTNEXTLINE(google-explicit-constructor)
+  constexpr LocationId(ImportIRInstId inst_id)
+      : IdBase(InvalidIndex + ImportIRInstId::InvalidIndex - inst_id.index) {
+    CARBON_CHECK(inst_id.is_valid() == is_valid());
+  }
+
+  auto is_node_id() const -> bool { return index > InvalidIndex; }
+  auto is_import_ir_inst_id() const -> bool { return index < InvalidIndex; }
+
+  // This is allowed to return an invalid NodeId, but should never be used for a
+  // valid InstId.
+  auto node_id() const -> Parse::NodeId {
+    CARBON_CHECK(is_node_id() || !is_valid());
+    return Parse::NodeId(index);
+  }
+
+  // This is allowed to return an invalid InstId, but should never be used for a
+  // valid NodeId.
+  auto import_ir_inst_id() const -> ImportIRInstId {
+    CARBON_CHECK(is_import_ir_inst_id() || !is_valid());
+    return ImportIRInstId(InvalidIndex + ImportIRInstId::InvalidIndex - index);
+  }
+
+  auto Print(llvm::raw_ostream& out) const -> void {
+    out << "loc_";
+    if (is_node_id() || !is_valid()) {
+      out << node_id();
+    } else {
+      out << import_ir_inst_id();
+    }
+  }
+};
+
+constexpr LocationId LocationId::Invalid = LocationId(Parse::NodeId::Invalid);
+
 }  // namespace Carbon::SemIR
 
 // Support use of Id types as DenseMap/DenseSet keys.

+ 37 - 0
toolchain/sem_ir/import_ir.h

@@ -0,0 +1,37 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+#ifndef CARBON_TOOLCHAIN_SEM_IR_IMPORT_IR_H_
+#define CARBON_TOOLCHAIN_SEM_IR_IMPORT_IR_H_
+
+#include "llvm/ADT/FoldingSet.h"
+#include "toolchain/sem_ir/ids.h"
+#include "toolchain/sem_ir/inst.h"
+
+namespace Carbon::SemIR {
+
+// A reference to an imported IR.
+struct ImportIR : public Printable<ImportIR> {
+  auto Print(llvm::raw_ostream& out) const -> void { out << node_id; }
+
+  // The node ID for the import.
+  Parse::ImportDirectiveId node_id;
+  // The imported IR.
+  const File* sem_ir;
+};
+
+// A reference to an instruction in an imported IR. Used for diagnostics with
+// LocationId.
+struct ImportIRInst : public Printable<ImportIRInst> {
+  auto Print(llvm::raw_ostream& out) const -> void {
+    out << ir_id << ":" << inst_id;
+  }
+
+  ImportIRId ir_id;
+  InstId inst_id;
+};
+
+}  // namespace Carbon::SemIR
+
+#endif  // CARBON_TOOLCHAIN_SEM_IR_IMPORT_IR_H_

+ 41 - 28
toolchain/sem_ir/inst.h

@@ -288,15 +288,15 @@ inline auto operator<<(llvm::raw_ostream& out, TypedInst inst)
   return out;
 }
 
-// Associates a NodeId and Inst in order to provide type-checking that the
+// Associates a LocationId and Inst in order to provide type-checking that the
 // TypedNodeId corresponds to the InstT.
-struct NodeIdAndInst {
-  // In cases where the NodeId is untyped or the InstT is unknown, the check
-  // can't be done at compile time.
+struct LocationIdAndInst {
+  // In cases where the NodeId is untyped, an inst_id, or the InstT is unknown,
+  // the NodeId check can't be done at compile time.
   // TODO: Consider runtime validation that InstT::Kind::TypedNodeId
   // corresponds.
-  static auto Untyped(Parse::NodeId node_id, Inst inst) -> NodeIdAndInst {
-    return NodeIdAndInst(node_id, inst, /*is_untyped=*/true);
+  static auto Untyped(LocationId loc_id, Inst inst) -> LocationIdAndInst {
+    return LocationIdAndInst(loc_id, inst, /*is_untyped=*/true);
   }
 
   // For the common case, support construction as:
@@ -304,22 +304,32 @@ struct NodeIdAndInst {
   template <typename InstT>
     requires(Internal::HasNodeId<InstT>)
   // NOLINTNEXTLINE(google-explicit-constructor)
-  NodeIdAndInst(decltype(InstT::Kind)::TypedNodeId node_id, InstT inst)
-      : node_id(node_id), inst(inst) {}
+  LocationIdAndInst(decltype(InstT::Kind)::TypedNodeId node_id, InstT inst)
+      : loc_id(node_id), inst(inst) {}
 
   // For cases with no parse node, support construction as:
   //   context.AddInst({SemIR::MyInst{...}});
   template <typename InstT>
     requires(!Internal::HasNodeId<InstT>)
   // NOLINTNEXTLINE(google-explicit-constructor)
-  NodeIdAndInst(InstT inst) : node_id(Parse::NodeId::Invalid), inst(inst) {}
+  LocationIdAndInst(InstT inst) : loc_id(Parse::NodeId::Invalid), inst(inst) {}
 
-  Parse::NodeId node_id;
+  // If TypedNodeId is Parse::NodeId, allow construction with a LocationId
+  // rather than requiring Untyped.
+  // TODO: This is somewhat historical due to fetching the NodeId from insts()
+  // for things like Temporary; should we require Untyped in these cases?
+  template <typename InstT>
+    requires(std::same_as<typename decltype(InstT::Kind)::TypedNodeId,
+                          Parse::NodeId>)
+  LocationIdAndInst(LocationId loc_id, InstT inst)
+      : loc_id(loc_id), inst(inst) {}
+
+  LocationId loc_id;
   Inst inst;
 
  private:
-  explicit NodeIdAndInst(Parse::NodeId node_id, Inst inst, bool /*is_untyped*/)
-      : node_id(node_id), inst(inst) {}
+  explicit LocationIdAndInst(LocationId loc_id, Inst inst, bool /*is_untyped*/)
+      : loc_id(loc_id), inst(inst) {}
 };
 
 // Provides a ValueStore wrapper for an API specific to instructions.
@@ -330,17 +340,17 @@ class InstStore {
   // instruction block. Check::Context::AddInst or InstBlockStack::AddInst
   // should usually be used instead, to add the instruction to the current
   // block.
-  auto AddInNoBlock(NodeIdAndInst node_id_and_inst) -> InstId {
-    node_ids_.push_back(node_id_and_inst.node_id);
-    return values_.Add(node_id_and_inst.inst);
+  auto AddInNoBlock(LocationIdAndInst loc_id_and_inst) -> InstId {
+    loc_ids_.push_back(loc_id_and_inst.loc_id);
+    return values_.Add(loc_id_and_inst.inst);
   }
 
   // Returns the requested instruction.
   auto Get(InstId inst_id) const -> Inst { return values_.Get(inst_id); }
 
-  // Returns the requested instruction and its parse node.
-  auto GetWithNodeId(InstId inst_id) const -> NodeIdAndInst {
-    return NodeIdAndInst::Untyped(GetNodeId(inst_id), Get(inst_id));
+  // Returns the requested instruction and its location ID.
+  auto GetWithLocationId(InstId inst_id) const -> LocationIdAndInst {
+    return LocationIdAndInst::Untyped(GetLocationId(inst_id), Get(inst_id));
   }
 
   // Returns whether the requested instruction is the specified type.
@@ -373,23 +383,26 @@ class InstStore {
     return TryGetAs<InstT>(inst_id);
   }
 
-  auto GetNodeId(InstId inst_id) const -> Parse::NodeId {
-    return node_ids_[inst_id.index];
+  auto GetLocationId(InstId inst_id) const -> LocationId {
+    CARBON_CHECK(inst_id.index >= 0) << inst_id.index;
+    CARBON_CHECK(inst_id.index < (int)loc_ids_.size())
+        << inst_id.index << " " << loc_ids_.size();
+    return loc_ids_[inst_id.index];
   }
 
-  // Overwrites a given instruction and parse node with a new value.
-  auto Set(InstId inst_id, NodeIdAndInst node_id_and_inst) -> void {
-    values_.Get(inst_id) = node_id_and_inst.inst;
-    node_ids_[inst_id.index] = node_id_and_inst.node_id;
+  // Overwrites a given instruction and location ID with a new value.
+  auto Set(InstId inst_id, LocationIdAndInst loc_id_and_inst) -> void {
+    values_.Get(inst_id) = loc_id_and_inst.inst;
+    loc_ids_[inst_id.index] = loc_id_and_inst.loc_id;
   }
 
-  auto SetNodeId(InstId inst_id, Parse::NodeId node_id) -> void {
-    node_ids_[inst_id.index] = node_id;
+  auto SetLocationId(InstId inst_id, LocationId loc_id) -> void {
+    loc_ids_[inst_id.index] = loc_id;
   }
 
   // Reserves space.
   auto Reserve(size_t size) -> void {
-    node_ids_.reserve(size);
+    loc_ids_.reserve(size);
     values_.Reserve(size);
   }
 
@@ -397,7 +410,7 @@ class InstStore {
   auto size() const -> int { return values_.size(); }
 
  private:
-  llvm::SmallVector<Parse::NodeId> node_ids_;
+  llvm::SmallVector<LocationId> loc_ids_;
   ValueStore<InstId> values_;
 };