Jelajahi Sumber

Introduce a Clang diagnostic instruction and use it to point to C++ source locations on Clang errors and warnings (#5262)

Introduce `ImportIRId::Cpp` and refer to clang source location in its
`ImportIRInst`.

Part of #5245.
Boaz Brickner 1 tahun lalu
induk
melakukan
609ccefd18

+ 17 - 13
toolchain/check/check_unit.cpp

@@ -34,7 +34,11 @@ static auto GetImportedIRCount(UnitAndImports* unit_and_imports) -> int {
     count += package_imports.imports.size();
   }
   if (!unit_and_imports->api_for_impl) {
-    // Leave an empty slot for ImportIRId::ApiForImpl.
+    // Leave an empty slot for `ImportIRId::ApiForImpl`.
+    ++count;
+  }
+  if (!unit_and_imports->cpp_import_names.empty()) {
+    // Leave an empty slot for `ImportIRId::Cpp`.
     ++count;
   }
   return count;
@@ -101,20 +105,20 @@ auto CheckUnit::InitPackageScopeAndImports() -> void {
                                  .import_id = SemIR::InstId::None});
   CARBON_CHECK(package_inst_id == SemIR::Namespace::PackageInstId);
 
-  // If there is an implicit `api` import, set it first so that it uses the
-  // ImportIRId::ApiForImpl when processed for imports.
+  // Call `SetSpecialImportIRs()` to set `ImportIRId::ApiForImpl` and
+  // `ImportIRId::Cpp` first, as required.
   if (unit_and_imports_->api_for_impl) {
     const auto& names = context_.parse_tree().packaging_decl()->names;
     auto import_decl_id = AddInst<SemIR::ImportDecl>(
         context_, names.node_id,
         {.package_id = SemIR::NameId::ForPackageName(names.package_id)});
-    SetApiImportIR(context_,
-                   {.decl_id = import_decl_id,
-                    .is_export = false,
-                    .sem_ir = unit_and_imports_->api_for_impl->unit->sem_ir});
+    SetSpecialImportIRs(
+        context_, {.decl_id = import_decl_id,
+                   .is_export = false,
+                   .sem_ir = unit_and_imports_->api_for_impl->unit->sem_ir});
   } else {
-    SetApiImportIR(context_,
-                   {.decl_id = SemIR::InstId::None, .sem_ir = nullptr});
+    SetSpecialImportIRs(context_,
+                        {.decl_id = SemIR::InstId::None, .sem_ir = nullptr});
   }
 
   // Add import instructions for everything directly imported. Implicit imports
@@ -327,9 +331,9 @@ auto CheckUnit::ImportOtherPackages(SemIR::TypeId namespace_type_id) -> void {
       if (local_imports) {
         CARBON_CHECK(package_id == api_imports_entry.first);
       } else {
-        auto import_ir_inst_id = context_.import_ir_insts().Add(
-            {.ir_id = SemIR::ImportIRId::ApiForImpl,
-             .inst_id = api_imports->import_decl_id});
+        auto import_ir_inst_id =
+            context_.import_ir_insts().Add(SemIR::ImportIRInst(
+                SemIR::ImportIRId::ApiForImpl, api_imports->import_decl_id));
         import_decl_id =
             AddInst(context_, MakeImportedLocIdAndInst<SemIR::ImportDecl>(
                                   context_, import_ir_inst_id,
@@ -411,7 +415,7 @@ auto CheckUnit::CheckRequiredDeclarations() -> void {
       auto import_ir_id = context_.sem_ir()
                               .import_ir_insts()
                               .Get(function_loc_id.import_ir_inst_id())
-                              .ir_id;
+                              .ir_id();
       auto& import_ir = context_.import_irs().Get(import_ir_id);
       if (import_ir.sem_ir->package_id().has_value() !=
           context_.sem_ir().package_id().has_value()) {

+ 36 - 0
toolchain/check/diagnostic_emitter.cpp

@@ -5,6 +5,7 @@
 #include "toolchain/check/diagnostic_emitter.h"
 
 #include <algorithm>
+#include <optional>
 #include <string>
 
 #include "common/raw_string_ostream.h"
@@ -16,6 +17,14 @@ namespace Carbon::Check {
 auto DiagnosticEmitter::ConvertLoc(SemIR::LocId loc_id,
                                    ContextFnT context_fn) const
     -> Diagnostics::ConvertedLoc {
+  // TODO: Instead of special casing Clang location here, support it within
+  // `GetAbsoluteNodeId()`. See discussion in
+  // https://github.com/carbon-language/carbon-lang/pull/5262/files#r2040308805.
+  auto converted_clang_loc = TryConvertClangDiagnosticLoc(loc_id);
+  if (converted_clang_loc) {
+    return *converted_clang_loc;
+  }
+
   auto converted = ConvertLocImpl(loc_id, context_fn);
 
   // Use the token when possible, but -1 is the default value.
@@ -60,6 +69,33 @@ auto DiagnosticEmitter::ConvertLocImpl(SemIR::LocId loc_id,
   return ConvertLocInFile(final_node_id, token_only, context_fn);
 }
 
+auto DiagnosticEmitter::TryConvertClangDiagnosticLoc(SemIR::LocId loc_id) const
+    -> std::optional<Diagnostics::ConvertedLoc> {
+  if (loc_id.kind() != SemIR::LocId::Kind::ImportIRInstId) {
+    return std::nullopt;
+  }
+
+  SemIR::ImportIRInst import_ir_inst =
+      sem_ir_->import_ir_insts().Get(loc_id.import_ir_inst_id());
+
+  if (import_ir_inst.ir_id() != SemIR::ImportIRId::Cpp) {
+    return std::nullopt;
+  }
+
+  clang::SourceLocation clang_loc =
+      sem_ir_->clang_source_locs().Get(import_ir_inst.clang_source_loc_id());
+
+  CARBON_CHECK(sem_ir_->cpp_ast());
+  clang::PresumedLoc presumed_loc =
+      sem_ir_->cpp_ast()->getSourceManager().getPresumedLoc(clang_loc);
+
+  return Diagnostics::ConvertedLoc{
+      .loc = {.filename = presumed_loc.getFilename(),
+              .line_number = static_cast<int32_t>(presumed_loc.getLine())},
+      // TODO: Set `last_byte_offset` based on the `import Cpp` location.
+      .last_byte_offset = 0};
+}
+
 auto DiagnosticEmitter::ConvertLocInFile(SemIR::AbsoluteNodeId absolute_node_id,
                                          bool token_only,
                                          ContextFnT /*context_fn*/) const

+ 4 - 0
toolchain/check/diagnostic_emitter.h

@@ -51,6 +51,10 @@ class DiagnosticEmitter : public DiagnosticEmitterBase {
   auto ConvertLocImpl(SemIR::LocId loc_id, ContextFnT context_fn) const
       -> Diagnostics::ConvertedLoc;
 
+  // Returns `ConvertedLoc` if `loc` points to a `ClangDiagnostic` instruction.
+  auto TryConvertClangDiagnosticLoc(SemIR::LocId loc_id) const
+      -> std::optional<Diagnostics::ConvertedLoc>;
+
   // Converts a node_id corresponding to a specific sem_ir to a diagnostic
   // location.
   auto ConvertLocInFile(SemIR::AbsoluteNodeId absolute_node_id, bool token_only,

+ 1 - 1
toolchain/check/dump.cpp

@@ -120,7 +120,7 @@ LLVM_DUMP_METHOD static auto Dump(const Context& context, SemIR::LocId loc_id)
       auto import_ir_id = context.sem_ir()
                               .import_ir_insts()
                               .Get(loc_id.import_ir_inst_id())
-                              .ir_id;
+                              .ir_id();
       const auto* import_file =
           context.sem_ir().import_irs().Get(import_ir_id).sem_ir;
       out << "LocId(import from \"" << FormatEscaped(import_file->filename())

+ 4 - 4
toolchain/check/handle_class.cpp

@@ -132,8 +132,8 @@ static auto MergeOrAddName(Context& context, Parse::AnyClassDeclId node_id,
 
       // Verify the decl so that things like aliases are name conflicts.
       const auto* import_ir =
-          context.import_irs().Get(import_ir_inst.ir_id).sem_ir;
-      if (!import_ir->insts().Is<SemIR::ClassDecl>(import_ir_inst.inst_id)) {
+          context.import_irs().Get(import_ir_inst.ir_id()).sem_ir;
+      if (!import_ir->insts().Is<SemIR::ClassDecl>(import_ir_inst.inst_id())) {
         break;
       }
 
@@ -142,12 +142,12 @@ static auto MergeOrAddName(Context& context, Parse::AnyClassDeclId node_id,
           context.constant_values().GetConstantInstId(prev_id));
       if (auto class_type = decl_value.TryAs<SemIR::ClassType>()) {
         prev_class_id = class_type->class_id;
-        prev_import_ir_id = import_ir_inst.ir_id;
+        prev_import_ir_id = import_ir_inst.ir_id();
       } else if (auto generic_class_type =
                      context.types().TryGetAs<SemIR::GenericClassType>(
                          decl_value.type_id())) {
         prev_class_id = generic_class_type->class_id;
-        prev_import_ir_id = import_ir_inst.ir_id;
+        prev_import_ir_id = import_ir_inst.ir_id();
       }
       break;
     }

+ 4 - 3
toolchain/check/handle_function.cpp

@@ -221,8 +221,9 @@ static auto TryMergeRedecl(Context& context, Parse::AnyFunctionDeclId node_id,
 
       // Verify the decl so that things like aliases are name conflicts.
       const auto* import_ir =
-          context.import_irs().Get(import_ir_inst.ir_id).sem_ir;
-      if (!import_ir->insts().Is<SemIR::FunctionDecl>(import_ir_inst.inst_id)) {
+          context.import_irs().Get(import_ir_inst.ir_id()).sem_ir;
+      if (!import_ir->insts().Is<SemIR::FunctionDecl>(
+              import_ir_inst.inst_id())) {
         break;
       }
 
@@ -233,7 +234,7 @@ static auto TryMergeRedecl(Context& context, Parse::AnyFunctionDeclId node_id,
                 struct_value->type_id)) {
           prev_function_id = function_type->function_id;
           prev_type_id = struct_value->type_id;
-          prev_import_ir_id = import_ir_inst.ir_id;
+          prev_import_ir_id = import_ir_inst.ir_id();
         }
       }
       break;

+ 1 - 1
toolchain/check/impl_lookup.cpp

@@ -42,7 +42,7 @@ static auto FindAssociatedImportIRs(Context& context,
     if (!decl_id.has_value()) {
       return;
     }
-    if (auto ir_id = GetCanonicalImportIRInst(context, decl_id).ir_id;
+    if (auto ir_id = GetCanonicalImportIRInst(context, decl_id).ir_id();
         ir_id.has_value()) {
       result.push_back(ir_id);
     }

+ 5 - 5
toolchain/check/import.cpp

@@ -226,7 +226,7 @@ static auto CopySingleNameScopeFromImportIR(
     auto entity_name_id = context.entity_names().Add(
         {.name_id = name_id, .parent_scope_id = parent_scope_id});
     auto import_ir_inst_id = context.import_ir_insts().Add(
-        {.ir_id = ir_id, .inst_id = import_inst_id});
+        SemIR::ImportIRInst(ir_id, import_inst_id));
     auto inst_id = AddInstInNoBlock(
         context, MakeImportedLocIdAndInst<SemIR::ImportRefLoaded>(
                      context, import_ir_inst_id,
@@ -354,7 +354,7 @@ static auto AddImportRefOrMerge(Context& context, SemIR::ImportIRId ir_id,
     auto entity_name_id = context.entity_names().Add(
         {.name_id = name_id, .parent_scope_id = parent_scope_id});
     auto import_ref = AddImportRef(
-        context, {.ir_id = ir_id, .inst_id = import_inst_id}, entity_name_id);
+        context, SemIR::ImportIRInst(ir_id, import_inst_id), entity_name_id);
     entry.result = SemIR::ScopeLookupResult::MakeFound(
         import_ref, SemIR::AccessKind::Public);
 
@@ -444,8 +444,8 @@ static auto ImportScopeFromApiFile(Context& context,
     } else {
       // Add an ImportRef for other instructions.
       AddScopedImportRef(context, impl_scope_id, impl_scope, impl_name_id,
-                         {.ir_id = SemIR::ImportIRId::ApiForImpl,
-                          .inst_id = api_entry.result.target_inst_id()},
+                         SemIR::ImportIRInst(SemIR::ImportIRId::ApiForImpl,
+                                             api_entry.result.target_inst_id()),
                          api_entry.result.access_kind());
     }
   }
@@ -668,7 +668,7 @@ auto ImportNameFromOtherPackage(
       } else {
         result_id = AddScopedImportRef(
             context, scope_id, context.name_scopes().Get(scope_id), name_id,
-            {.ir_id = import_ir_id, .inst_id = import_scope_inst_id},
+            SemIR::ImportIRInst(import_ir_id, import_scope_inst_id),
             SemIR::AccessKind::Public);
         LoadImportRef(context, result_id);
       }

+ 80 - 37
toolchain/check/import_cpp.cpp

@@ -52,6 +52,17 @@ static auto GenerateCppIncludesHeaderCode(
 
 namespace {
 
+// Adds the given source location and an `ImportIRInst` referring to it in
+// `ImportIRId::Cpp`.
+static auto AddImportIRInst(Context& context,
+                            clang::SourceLocation clang_source_loc)
+    -> SemIR::ImportIRInstId {
+  SemIR::ClangSourceLocId clang_source_loc_id =
+      context.sem_ir().clang_source_locs().Add(clang_source_loc);
+  return context.import_ir_insts().Add(
+      SemIR::ImportIRInst(clang_source_loc_id));
+}
+
 // Used to convert Clang diagnostics to Carbon diagnostics.
 class CarbonClangDiagnosticConsumer : public clang::DiagnosticConsumer {
  public:
@@ -66,6 +77,9 @@ class CarbonClangDiagnosticConsumer : public clang::DiagnosticConsumer {
                         const clang::Diagnostic& info) -> void override {
     DiagnosticConsumer::HandleDiagnostic(diag_level, info);
 
+    SemIR::ImportIRInstId clang_import_ir_inst_id =
+        AddImportIRInst(*context_, info.getLocation());
+
     llvm::SmallString<256> message;
     info.FormatDiagnostic(message);
 
@@ -84,40 +98,48 @@ class CarbonClangDiagnosticConsumer : public clang::DiagnosticConsumer {
 
     std::string diagnostics_str = diagnostics_stream.TakeStr();
 
-    switch (diag_level) {
-      case clang::DiagnosticsEngine::Ignored:
-      case clang::DiagnosticsEngine::Note:
-      case clang::DiagnosticsEngine::Remark: {
-        context_->TODO(
-            loc_id_,
-            llvm::formatv(
-                "Unsupported: C++ diagnostic level for diagnostic\n{0}",
-                diagnostics_str));
-        return;
-      }
-      case clang::DiagnosticsEngine::Warning:
-      case clang::DiagnosticsEngine::Error:
-      case clang::DiagnosticsEngine::Fatal: {
-        // TODO: Adjust diagnostics to drop the Carbon file here, and then
-        // remove the "C++:\n" prefix.
-        CARBON_DIAGNOSTIC(CppInteropParseWarning, Warning, "C++:\n{0}",
-                          std::string);
-        CARBON_DIAGNOSTIC(CppInteropParseError, Error, "C++:\n{0}",
-                          std::string);
-        // TODO: This should be part of the location, instead of added as a note
-        // here.
-        CARBON_DIAGNOSTIC(InCppImport, Note, "in `Cpp` import");
-
-        // TODO: Use a more specific location.
-        context_->emitter()
-            .Build(SemIR::LocId::None,
-                   diag_level == clang::DiagnosticsEngine::Warning
-                       ? CppInteropParseWarning
-                       : CppInteropParseError,
-                   diagnostics_str)
-            .Note(loc_id_, InCppImport)
-            .Emit();
-        return;
+    diagnostic_infos_.push_back({.level = diag_level,
+                                 .import_ir_inst_id = clang_import_ir_inst_id,
+                                 .message = diagnostics_str});
+  }
+
+  // Outputs Carbon diagnostics based on the collected Clang diagnostics. Must
+  // be called after the AST is set in the context.
+  auto EmitDiagnostics() -> void {
+    for (const ClangDiagnosticInfo& info : diagnostic_infos_) {
+      switch (info.level) {
+        case clang::DiagnosticsEngine::Ignored:
+        case clang::DiagnosticsEngine::Note:
+        case clang::DiagnosticsEngine::Remark: {
+          context_->TODO(
+              SemIR::LocId(info.import_ir_inst_id),
+              llvm::formatv(
+                  "Unsupported: C++ diagnostic level for diagnostic\n{0}",
+                  info.message));
+          break;
+        }
+        case clang::DiagnosticsEngine::Warning:
+        case clang::DiagnosticsEngine::Error:
+        case clang::DiagnosticsEngine::Fatal: {
+          // TODO: Adjust diagnostics to drop the Carbon file here, and then
+          // remove the "C++:\n" prefix.
+          CARBON_DIAGNOSTIC(CppInteropParseWarning, Warning, "C++:\n{0}",
+                            std::string);
+          CARBON_DIAGNOSTIC(CppInteropParseError, Error, "C++:\n{0}",
+                            std::string);
+          // TODO: This should be part of the location, instead of added as a
+          // note here.
+          CARBON_DIAGNOSTIC(InCppImport, Note, "in `Cpp` import");
+          context_->emitter()
+              .Build(SemIR::LocId(info.import_ir_inst_id),
+                     info.level == clang::DiagnosticsEngine::Warning
+                         ? CppInteropParseWarning
+                         : CppInteropParseError,
+                     info.message)
+              .Note(loc_id_, InCppImport)
+              .Emit();
+          break;
+        }
       }
     }
   }
@@ -128,13 +150,32 @@ class CarbonClangDiagnosticConsumer : public clang::DiagnosticConsumer {
 
   // The location that triggered calling Clang.
   SemIR::LocId loc_id_;
+
+  // Information on a Clang diagnostic that can be converted to a Carbon
+  // diagnostic.
+  struct ClangDiagnosticInfo {
+    // The Clang diagnostic level.
+    clang::DiagnosticsEngine::Level level;
+
+    // The ID of the ImportIR instruction referring to the Clang source
+    // location.
+    SemIR::ImportIRInstId import_ir_inst_id;
+
+    // The Clang diagnostic textual message.
+    std::string message;
+  };
+
+  // Collects the information for all Clang diagnostics to be converted to
+  // Carbon diagnostics after the context has been initialized with the Clang
+  // AST.
+  llvm::SmallVector<ClangDiagnosticInfo> diagnostic_infos_;
 };
 
 }  // namespace
 
 // Returns an AST for the C++ imports and a bool that represents whether
 // compilation errors where encountered or the generated AST is null due to an
-// error.
+// error. Sets the AST in the context's `sem_ir`.
 // TODO: Consider to always have a (non-null) AST.
 static auto GenerateAst(Context& context, llvm::StringRef importing_file_path,
                         llvm::ArrayRef<Parse::Tree::PackagingNames> imports,
@@ -157,6 +198,10 @@ static auto GenerateAst(Context& context, llvm::StringRef importing_file_path,
   // Remove link to the diagnostics consumer before its deletion.
   ast->getDiagnostics().setClient(nullptr);
 
+  // In order to emit diagnostics, we need the AST.
+  context.sem_ir().set_cpp_ast(ast.get());
+  diagnostics_consumer.EmitDiagnostics();
+
   return {std::move(ast), !ast || diagnostics_consumer.getNumErrors() > 0};
 }
 
@@ -212,8 +257,6 @@ auto ImportCppFiles(Context& context, llvm::StringRef importing_file_path,
   name_scope.set_cpp_decl_context(
       generated_ast->getASTContext().getTranslationUnitDecl());
 
-  context.sem_ir().set_cpp_ast(generated_ast.get());
-
   if (ast_has_error) {
     name_scope.set_has_error();
   }

+ 44 - 31
toolchain/check/import_ref.cpp

@@ -37,7 +37,10 @@ static auto InternalAddImportIR(Context& context, SemIR::ImportIR import_ir)
   return context.import_irs().Add(import_ir);
 }
 
-auto SetApiImportIR(Context& context, SemIR::ImportIR import_ir) -> void {
+// Adds a special-cased IR and verifies it received the correct ID.
+static auto SetSpecialImportIR(Context& context, SemIR::ImportIR import_ir,
+                               SemIR::ImportIRId expected_import_ir_id)
+    -> void {
   auto ir_id = SemIR::ImportIRId::None;
   if (import_ir.sem_ir != nullptr) {
     ir_id = AddImportIR(context, import_ir);
@@ -45,8 +48,16 @@ auto SetApiImportIR(Context& context, SemIR::ImportIR import_ir) -> void {
     // We don't have a check_ir_id, so add without touching check_ir_map.
     ir_id = InternalAddImportIR(context, import_ir);
   }
-  CARBON_CHECK(ir_id == SemIR::ImportIRId::ApiForImpl,
-               "ApiForImpl must be the first IR");
+  CARBON_CHECK(ir_id == expected_import_ir_id,
+               "Actual ImportIRId ($0) != Expected ImportIRId ({1})", ir_id,
+               expected_import_ir_id);
+}
+
+auto SetSpecialImportIRs(Context& context, SemIR::ImportIR import_ir) -> void {
+  SetSpecialImportIR(context, import_ir, SemIR::ImportIRId::ApiForImpl);
+  SetSpecialImportIR(context,
+                     {.decl_id = SemIR::InstId::None, .is_export = false},
+                     SemIR::ImportIRId::Cpp);
 }
 
 auto AddImportIR(Context& context, SemIR::ImportIR import_ir)
@@ -95,8 +106,8 @@ static auto AddLoadedImportRef(Context& context, SemIR::TypeId type_id,
   context.import_ref_ids().push_back(inst_id);
 
   context.constant_values().Set(inst_id, const_id);
-  context.import_ir_constant_values()[import_ir_inst.ir_id.index].Set(
-      import_ir_inst.inst_id, const_id);
+  context.import_ir_constant_values()[import_ir_inst.ir_id().index].Set(
+      import_ir_inst.inst_id(), const_id);
   return inst_id;
 }
 
@@ -111,8 +122,8 @@ static auto GetCanonicalImportIRInst(Context& context,
     if (loc_id.kind() == SemIR::LocId::Kind::ImportIRInstId) {
       auto import_ir_inst =
           cursor_ir->import_ir_insts().Get(loc_id.import_ir_inst_id());
-      cursor_ir = cursor_ir->import_irs().Get(import_ir_inst.ir_id).sem_ir;
-      cursor_inst_id = import_ir_inst.inst_id;
+      cursor_ir = cursor_ir->import_irs().Get(import_ir_inst.ir_id()).sem_ir;
+      cursor_inst_id = import_ir_inst.inst_id();
       continue;
     }
 
@@ -135,7 +146,7 @@ static auto GetCanonicalImportIRInst(Context& context,
                                   .is_export = false,
                                   .sem_ir = cursor_ir});
   }
-  return {.ir_id = ir_id, .inst_id = cursor_inst_id};
+  return SemIR::ImportIRInst(ir_id, cursor_inst_id);
 }
 
 auto GetCanonicalImportIRInst(Context& context, SemIR::InstId inst_id)
@@ -155,7 +166,7 @@ auto VerifySameCanonicalImportIRInst(Context& context, SemIR::NameId name_id,
     return;
   }
   auto conflict_id =
-      AddImportRef(context, {.ir_id = new_ir_id, .inst_id = new_inst_id});
+      AddImportRef(context, SemIR::ImportIRInst(new_ir_id, new_inst_id));
   // TODO: Pass the imported name location instead of the conflict id.
   DiagnoseDuplicateName(context, name_id, conflict_id, prev_id);
 }
@@ -609,7 +620,7 @@ class ImportRefResolver : public ImportContext {
       const auto* prev_ir = cursor_ir;
       auto prev_inst_id = cursor_inst_id;
 
-      cursor_ir = cursor_ir->import_irs().Get(ir_inst.ir_id).sem_ir;
+      cursor_ir = cursor_ir->import_irs().Get(ir_inst.ir_id()).sem_ir;
       cursor_ir_id =
           local_context().check_ir_map()[cursor_ir->check_ir_id().index];
       if (!cursor_ir_id.has_value()) {
@@ -619,7 +630,7 @@ class ImportRefResolver : public ImportContext {
                                           .is_export = false,
                                           .sem_ir = cursor_ir});
       }
-      cursor_inst_id = ir_inst.inst_id;
+      cursor_inst_id = ir_inst.inst_id();
 
       CARBON_CHECK(cursor_ir != prev_ir || cursor_inst_id != prev_inst_id,
                    "{0}", cursor_ir->insts().Get(cursor_inst_id));
@@ -634,7 +645,7 @@ class ImportRefResolver : public ImportContext {
         return result;
       } else {
         result.indirect_insts.push_back(
-            {.ir_id = cursor_ir_id, .inst_id = cursor_inst_id});
+            SemIR::ImportIRInst(cursor_ir_id, cursor_inst_id));
       }
     }
   }
@@ -646,8 +657,8 @@ class ImportRefResolver : public ImportContext {
     local_constant_values_for_import_insts().Set(inst_id, const_id);
     for (auto indirect_inst : indirect_insts) {
       local_context()
-          .import_ir_constant_values()[indirect_inst.ir_id.index]
-          .Set(indirect_inst.inst_id, const_id);
+          .import_ir_constant_values()[indirect_inst.ir_id().index]
+          .Set(indirect_inst.inst_id(), const_id);
     }
   }
 
@@ -663,7 +674,7 @@ static auto AddImportRef(ImportContext& context, SemIR::InstId inst_id,
                          SemIR::EntityNameId entity_name_id =
                              SemIR::EntityNameId::None) -> SemIR::InstId {
   return AddImportRef(context.local_context(),
-                      {.ir_id = context.import_ir_id(), .inst_id = inst_id},
+                      SemIR::ImportIRInst(context.import_ir_id(), inst_id),
                       entity_name_id);
 }
 
@@ -672,13 +683,13 @@ static auto AddLoadedImportRef(ImportContext& context, SemIR::TypeId type_id,
                                SemIR::ConstantId const_id) -> SemIR::InstId {
   return AddLoadedImportRef(
       context.local_context(), type_id,
-      {.ir_id = context.import_ir_id(), .inst_id = inst_id}, const_id);
+      SemIR::ImportIRInst(context.import_ir_id(), inst_id), const_id);
 }
 
 static auto AddImportIRInst(ImportContext& context, SemIR::InstId inst_id)
     -> SemIR::ImportIRInstId {
   return context.local_import_ir_insts().Add(
-      {.ir_id = context.import_ir_id(), .inst_id = inst_id});
+      SemIR::ImportIRInst(context.import_ir_id(), inst_id));
 }
 
 // Computes, sets, and returns the constant value for an instruction.
@@ -3267,10 +3278,11 @@ static auto GetInstForLoad(Context& context,
   // The first ImportIRInst is added directly because the IR doesn't need to be
   // localized.
   import_ir_insts.push_back(import_ir_inst);
-  const auto* cursor_ir = context.import_irs().Get(import_ir_inst.ir_id).sem_ir;
+  const auto* cursor_ir =
+      context.import_irs().Get(import_ir_inst.ir_id()).sem_ir;
 
   while (true) {
-    auto cursor_inst = cursor_ir->insts().Get(import_ir_inst.inst_id);
+    auto cursor_inst = cursor_ir->insts().Get(import_ir_inst.inst_id());
 
     auto import_ref = cursor_inst.TryAs<SemIR::ImportRefUnloaded>();
     if (!import_ref) {
@@ -3280,12 +3292,12 @@ static auto GetInstForLoad(Context& context,
 
     import_ir_inst =
         cursor_ir->import_ir_insts().Get(import_ref->import_ir_inst_id);
-    cursor_ir = cursor_ir->import_irs().Get(import_ir_inst.ir_id).sem_ir;
-    import_ir_insts.push_back(
-        {.ir_id = AddImportIR(context, {.decl_id = SemIR::InstId::None,
-                                        .is_export = false,
-                                        .sem_ir = cursor_ir}),
-         .inst_id = import_ir_inst.inst_id});
+    cursor_ir = cursor_ir->import_irs().Get(import_ir_inst.ir_id()).sem_ir;
+    import_ir_insts.push_back(SemIR::ImportIRInst(
+        AddImportIR(context, {.decl_id = SemIR::InstId::None,
+                              .is_export = false,
+                              .sem_ir = cursor_ir}),
+        import_ir_inst.inst_id()));
   }
 }
 
@@ -3302,9 +3314,9 @@ auto LoadImportRef(Context& context, SemIR::InstId inst_id) -> void {
   // The last indirect instruction is the one to resolve. Pop it here because
   // Resolve will assign the constant.
   auto load_ir_inst = indirect_insts.pop_back_val();
-  ImportRefResolver resolver(&context, load_ir_inst.ir_id);
+  ImportRefResolver resolver(&context, load_ir_inst.ir_id());
   auto type_id = resolver.ResolveType(load_type_id);
-  auto constant_id = resolver.Resolve(load_ir_inst.inst_id);
+  auto constant_id = resolver.Resolve(load_ir_inst.inst_id());
 
   // Replace the ImportRefUnloaded instruction with ImportRefLoaded. This
   // doesn't use ReplacePlaceholderImportedInst because it would trigger
@@ -3318,8 +3330,8 @@ auto LoadImportRef(Context& context, SemIR::InstId inst_id) -> void {
   // Store the constant for both the ImportRefLoaded and indirect instructions.
   context.constant_values().Set(inst_id, constant_id);
   for (const auto& import_ir_inst : indirect_insts) {
-    context.import_ir_constant_values()[import_ir_inst.ir_id.index].Set(
-        import_ir_inst.inst_id, constant_id);
+    context.import_ir_constant_values()[import_ir_inst.ir_id().index].Set(
+        import_ir_inst.inst_id(), constant_id);
   }
 }
 
@@ -3370,8 +3382,9 @@ auto CheckCompatibleImportedNodeKind(Context& context,
                                      SemIR::ImportIRInstId imported_loc_id,
                                      SemIR::InstKind kind) -> void {
   auto& import_ir_inst = context.import_ir_insts().Get(imported_loc_id);
-  const auto* import_ir = context.import_irs().Get(import_ir_inst.ir_id).sem_ir;
-  auto imported_kind = import_ir->insts().Get(import_ir_inst.inst_id).kind();
+  const auto* import_ir =
+      context.import_irs().Get(import_ir_inst.ir_id()).sem_ir;
+  auto imported_kind = import_ir->insts().Get(import_ir_inst.inst_id()).kind();
   CARBON_CHECK(
       HasCompatibleImportedNodeKind(imported_kind, kind),
       "Node of kind {0} created with location of imported node of kind {1}",

+ 4 - 3
toolchain/check/import_ref.h

@@ -11,9 +11,10 @@
 
 namespace Carbon::Check {
 
-// Sets the IR for ImportIRId::ApiForImpl. Should be called before AddImportIR
-// in order to ensure the correct ID is assigned.
-auto SetApiImportIR(Context& context, SemIR::ImportIR import_ir) -> void;
+// Sets the IRs for `ImportIRId::ApiForImpl` from `import_ir`, and
+// `ImportIRId::Cpp` from defaults. Should be called before `AddImportIR` in
+// order to ensure the correct IDs are assigned.
+auto SetSpecialImportIRs(Context& context, SemIR::ImportIR import_ir) -> void;
 
 // Adds an ImportIR, returning the ID. May use an existing ID if already added.
 auto AddImportIR(Context& context, SemIR::ImportIR import_ir)

+ 1 - 0
toolchain/check/testdata/basics/builtin_insts.carbon

@@ -15,6 +15,7 @@
 // CHECK:STDOUT: sem_ir:
 // CHECK:STDOUT:   import_irs:
 // CHECK:STDOUT:     ir0:             {decl_id: inst<none>, is_export: false}
+// CHECK:STDOUT:     ir1:             {decl_id: inst<none>, is_export: false}
 // CHECK:STDOUT:   import_ir_insts: {}
 // CHECK:STDOUT:   name_scopes:
 // CHECK:STDOUT:     name_scope0:     {inst: inst14, parent_scope: name_scope<none>, has_error: false, extended_scopes: [], names: {}}

+ 5 - 5
toolchain/check/testdata/interop/cpp/no_prelude/bad_import.carbon

@@ -22,18 +22,18 @@ import Cpp;
 
 library "[[@TEST_NAME]]";
 
-// CHECK:STDERR: fail_import_cpp_library_empty.carbon:[[@LINE+4]]:1: error: `Cpp` import missing library [CppInteropMissingLibrary]
+// CHECK:STDERR: fail_import_cpp_library_empty.carbon:[[@LINE+9]]:1: error: `Cpp` import missing library [CppInteropMissingLibrary]
 // CHECK:STDERR: import Cpp library "";
 // CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~
 // CHECK:STDERR:
-import Cpp library "";
-
-// --- fail_import_cpp_library_file_with_quotes.carbon
-// CHECK:STDERR: fail_import_cpp_library_file_with_quotes.carbon: error: C++:
+// CHECK:STDERR: fail_import_cpp_library_file_with_quotes.carbon.generated.cpp_imports.h:1: error: C++:
 // CHECK:STDERR: fail_import_cpp_library_file_with_quotes.carbon.generated.cpp_imports.h:1:10: fatal error: '\"foo.h\"' file not found
 // CHECK:STDERR:     1 | #include "\"foo.h\""
 // CHECK:STDERR:       |          ^~~~~~~~~~~
 // CHECK:STDERR:  [CppInteropParseError]
+import Cpp library "";
+
+// --- fail_import_cpp_library_file_with_quotes.carbon
 
 library "[[@TEST_NAME]]";
 

+ 169 - 131
toolchain/check/testdata/interop/cpp/no_prelude/cpp_diagnostics.carbon

@@ -10,15 +10,15 @@
 
 // --- one_error.h
 
-#error "error1"
-
-// --- fail_import_cpp_file_with_one_error.carbon
-// CHECK:STDERR: fail_import_cpp_file_with_one_error.carbon: error: C++:
+// CHECK:STDERR: ./one_error.h:[[@LINE+6]]: error: C++:
 // CHECK:STDERR: In file included from fail_import_cpp_file_with_one_error.carbon.generated.cpp_imports.h:1:
-// CHECK:STDERR: ./one_error.h:2:2: error: "error1"
-// CHECK:STDERR:     2 | #error "error1"
+// CHECK:STDERR: ./one_error.h:[[@LINE+4]]:2: error: "error1"
+// CHECK:STDERR:     8 | #error "error1"
 // CHECK:STDERR:       |  ^
 // CHECK:STDERR:  [CppInteropParseError]
+#error "error1"
+
+// --- fail_import_cpp_file_with_one_error.carbon
 
 library "[[@TEST_NAME]]";
 
@@ -30,16 +30,16 @@ import Cpp library "one_error.h";
 
 // --- multiple_errors.h
 
+// CHECK:STDERR: ./multiple_errors.h:[[@LINE+6]]: error: C++:
+// CHECK:STDERR: In file included from fail_import_cpp_file_with_multiple_errors.carbon.generated.cpp_imports.h:1:
+// CHECK:STDERR: ./multiple_errors.h:[[@LINE+4]]:2: error: "error1"
+// CHECK:STDERR:     8 | #error "error1"
+// CHECK:STDERR:       |  ^
+// CHECK:STDERR:  [CppInteropParseError]
 #error "error1"
 #error "error2"
 
 // --- fail_import_cpp_file_with_multiple_errors.carbon
-// CHECK:STDERR: fail_import_cpp_file_with_multiple_errors.carbon: error: C++:
-// CHECK:STDERR: In file included from fail_import_cpp_file_with_multiple_errors.carbon.generated.cpp_imports.h:1:
-// CHECK:STDERR: ./multiple_errors.h:2:2: error: "error1"
-// CHECK:STDERR:     2 | #error "error1"
-// CHECK:STDERR:       |  ^
-// CHECK:STDERR:  [CppInteropParseError]
 
 library "[[@TEST_NAME]]";
 
@@ -47,10 +47,10 @@ library "[[@TEST_NAME]]";
 // CHECK:STDERR: import Cpp library "multiple_errors.h";
 // CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 // CHECK:STDERR:
-// CHECK:STDERR: fail_import_cpp_file_with_multiple_errors.carbon: error: C++:
+// CHECK:STDERR: ./multiple_errors.h:9: error: C++:
 // CHECK:STDERR: In file included from fail_import_cpp_file_with_multiple_errors.carbon.generated.cpp_imports.h:1:
-// CHECK:STDERR: ./multiple_errors.h:3:2: error: "error2"
-// CHECK:STDERR:     3 | #error "error2"
+// CHECK:STDERR: ./multiple_errors.h:9:2: error: "error2"
+// CHECK:STDERR:     9 | #error "error2"
 // CHECK:STDERR:       |  ^
 // CHECK:STDERR:  [CppInteropParseError]
 // CHECK:STDERR: fail_import_cpp_file_with_multiple_errors.carbon:[[@LINE+4]]:1: note: in `Cpp` import [InCppImport]
@@ -61,15 +61,15 @@ import Cpp library "multiple_errors.h";
 
 // --- one_warning.h
 
-#warning "warning1"
-
-// --- import_cpp_file_with_one_warning.carbon
-// CHECK:STDERR: import_cpp_file_with_one_warning.carbon: warning: C++:
+// CHECK:STDERR: ./one_warning.h:[[@LINE+6]]: warning: C++:
 // CHECK:STDERR: In file included from import_cpp_file_with_one_warning.carbon.generated.cpp_imports.h:1:
-// CHECK:STDERR: ./one_warning.h:2:2: warning: "warning1"
-// CHECK:STDERR:     2 | #warning "warning1"
+// CHECK:STDERR: ./one_warning.h:[[@LINE+4]]:2: warning: "warning1"
+// CHECK:STDERR:     8 | #warning "warning1"
 // CHECK:STDERR:       |  ^
 // CHECK:STDERR:  [CppInteropParseWarning]
+#warning "warning1"
+
+// --- import_cpp_file_with_one_warning.carbon
 
 library "[[@TEST_NAME]]";
 
@@ -81,17 +81,17 @@ import Cpp library "one_warning.h";
 
 // --- multiple_warnings.h
 
+// CHECK:STDERR: ./multiple_warnings.h:[[@LINE+6]]: warning: C++:
+// CHECK:STDERR: In file included from import_cpp_file_with_multiple_warnings.carbon.generated.cpp_imports.h:1:
+// CHECK:STDERR: ./multiple_warnings.h:[[@LINE+4]]:2: warning: "warning1"
+// CHECK:STDERR:     8 | #warning "warning1"
+// CHECK:STDERR:       |  ^
+// CHECK:STDERR:  [CppInteropParseWarning]
 #warning "warning1"
 #warning "warning2"
 #warning "warning3"
 
 // --- import_cpp_file_with_multiple_warnings.carbon
-// CHECK:STDERR: import_cpp_file_with_multiple_warnings.carbon: warning: C++:
-// CHECK:STDERR: In file included from import_cpp_file_with_multiple_warnings.carbon.generated.cpp_imports.h:1:
-// CHECK:STDERR: ./multiple_warnings.h:2:2: warning: "warning1"
-// CHECK:STDERR:     2 | #warning "warning1"
-// CHECK:STDERR:       |  ^
-// CHECK:STDERR:  [CppInteropParseWarning]
 
 library "[[@TEST_NAME]]";
 
@@ -99,20 +99,20 @@ library "[[@TEST_NAME]]";
 // CHECK:STDERR: import Cpp library "multiple_warnings.h";
 // CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 // CHECK:STDERR:
-// CHECK:STDERR: import_cpp_file_with_multiple_warnings.carbon: warning: C++:
+// CHECK:STDERR: ./multiple_warnings.h:9: warning: C++:
 // CHECK:STDERR: In file included from import_cpp_file_with_multiple_warnings.carbon.generated.cpp_imports.h:1:
-// CHECK:STDERR: ./multiple_warnings.h:3:2: warning: "warning2"
-// CHECK:STDERR:     3 | #warning "warning2"
+// CHECK:STDERR: ./multiple_warnings.h:9:2: warning: "warning2"
+// CHECK:STDERR:     9 | #warning "warning2"
 // CHECK:STDERR:       |  ^
 // CHECK:STDERR:  [CppInteropParseWarning]
 // CHECK:STDERR: import_cpp_file_with_multiple_warnings.carbon:[[@LINE+14]]:1: note: in `Cpp` import [InCppImport]
 // CHECK:STDERR: import Cpp library "multiple_warnings.h";
 // CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 // CHECK:STDERR:
-// CHECK:STDERR: import_cpp_file_with_multiple_warnings.carbon: warning: C++:
+// CHECK:STDERR: ./multiple_warnings.h:10: warning: C++:
 // CHECK:STDERR: In file included from import_cpp_file_with_multiple_warnings.carbon.generated.cpp_imports.h:1:
-// CHECK:STDERR: ./multiple_warnings.h:4:2: warning: "warning3"
-// CHECK:STDERR:     4 | #warning "warning3"
+// CHECK:STDERR: ./multiple_warnings.h:10:2: warning: "warning3"
+// CHECK:STDERR:    10 | #warning "warning3"
 // CHECK:STDERR:       |  ^
 // CHECK:STDERR:  [CppInteropParseWarning]
 // CHECK:STDERR: import_cpp_file_with_multiple_warnings.carbon:[[@LINE+4]]:1: note: in `Cpp` import [InCppImport]
@@ -123,16 +123,16 @@ import Cpp library "multiple_warnings.h";
 
 // --- one_error_and_one_warning.h
 
+// CHECK:STDERR: ./one_error_and_one_warning.h:[[@LINE+6]]: error: C++:
+// CHECK:STDERR: In file included from fail_import_cpp_file_with_one_error_and_one_warning.carbon.generated.cpp_imports.h:1:
+// CHECK:STDERR: ./one_error_and_one_warning.h:[[@LINE+4]]:2: error: "error1"
+// CHECK:STDERR:     8 | #error "error1"
+// CHECK:STDERR:       |  ^
+// CHECK:STDERR:  [CppInteropParseError]
 #error "error1"
 #warning "warning1"
 
 // --- fail_import_cpp_file_with_one_error_and_one_warning.carbon
-// CHECK:STDERR: fail_import_cpp_file_with_one_error_and_one_warning.carbon: error: C++:
-// CHECK:STDERR: In file included from fail_import_cpp_file_with_one_error_and_one_warning.carbon.generated.cpp_imports.h:1:
-// CHECK:STDERR: ./one_error_and_one_warning.h:2:2: error: "error1"
-// CHECK:STDERR:     2 | #error "error1"
-// CHECK:STDERR:       |  ^
-// CHECK:STDERR:  [CppInteropParseError]
 
 library "[[@TEST_NAME]]";
 
@@ -140,10 +140,10 @@ library "[[@TEST_NAME]]";
 // CHECK:STDERR: import Cpp library "one_error_and_one_warning.h";
 // CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 // CHECK:STDERR:
-// CHECK:STDERR: fail_import_cpp_file_with_one_error_and_one_warning.carbon: warning: C++:
+// CHECK:STDERR: ./one_error_and_one_warning.h:9: warning: C++:
 // CHECK:STDERR: In file included from fail_import_cpp_file_with_one_error_and_one_warning.carbon.generated.cpp_imports.h:1:
-// CHECK:STDERR: ./one_error_and_one_warning.h:3:2: warning: "warning1"
-// CHECK:STDERR:     3 | #warning "warning1"
+// CHECK:STDERR: ./one_error_and_one_warning.h:9:2: warning: "warning1"
+// CHECK:STDERR:     9 | #warning "warning1"
 // CHECK:STDERR:       |  ^
 // CHECK:STDERR:  [CppInteropParseWarning]
 // CHECK:STDERR: fail_import_cpp_file_with_one_error_and_one_warning.carbon:[[@LINE+4]]:1: note: in `Cpp` import [InCppImport]
@@ -154,6 +154,12 @@ import Cpp library "one_error_and_one_warning.h";
 
 // --- multiple_errors_and_multiple_warnings.h
 
+// CHECK:STDERR: ./multiple_errors_and_multiple_warnings.h:[[@LINE+6]]: error: C++:
+// CHECK:STDERR: In file included from fail_import_cpp_file_with_multiple_errors_and_multiple_warnings.carbon.generated.cpp_imports.h:1:
+// CHECK:STDERR: ./multiple_errors_and_multiple_warnings.h:[[@LINE+4]]:2: error: "error1"
+// CHECK:STDERR:     8 | #error "error1"
+// CHECK:STDERR:       |  ^
+// CHECK:STDERR:  [CppInteropParseError]
 #error "error1"
 #error "error2"
 #warning "warning1"
@@ -161,227 +167,242 @@ import Cpp library "one_error_and_one_warning.h";
 #warning "warning3"
 
 // --- fail_import_cpp_file_with_multiple_errors_and_multiple_warnings.carbon
-// CHECK:STDERR: fail_import_cpp_file_with_multiple_errors_and_multiple_warnings.carbon: error: C++:
-// CHECK:STDERR: In file included from fail_import_cpp_file_with_multiple_errors_and_multiple_warnings.carbon.generated.cpp_imports.h:1:
-// CHECK:STDERR: ./multiple_errors_and_multiple_warnings.h:2:2: error: "error1"
-// CHECK:STDERR:     2 | #error "error1"
-// CHECK:STDERR:       |  ^
-// CHECK:STDERR:  [CppInteropParseError]
 
 library "[[@TEST_NAME]]";
 
-// CHECK:STDERR: fail_import_cpp_file_with_multiple_errors_and_multiple_warnings.carbon:[[@LINE+44]]:1: note: in `Cpp` import [InCppImport]
+// CHECK:STDERR: fail_import_cpp_file_with_multiple_errors_and_multiple_warnings.carbon:[[@LINE+50]]:1: note: in `Cpp` import [InCppImport]
 // CHECK:STDERR: import Cpp library "multiple_errors_and_multiple_warnings.h";
 // CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 // CHECK:STDERR:
-// CHECK:STDERR: fail_import_cpp_file_with_multiple_errors_and_multiple_warnings.carbon: error: C++:
+// CHECK:STDERR: ./multiple_errors_and_multiple_warnings.h:9: error: C++:
 // CHECK:STDERR: In file included from fail_import_cpp_file_with_multiple_errors_and_multiple_warnings.carbon.generated.cpp_imports.h:1:
-// CHECK:STDERR: ./multiple_errors_and_multiple_warnings.h:3:2: error: "error2"
-// CHECK:STDERR:     3 | #error "error2"
+// CHECK:STDERR: ./multiple_errors_and_multiple_warnings.h:9:2: error: "error2"
+// CHECK:STDERR:     9 | #error "error2"
 // CHECK:STDERR:       |  ^
 // CHECK:STDERR:  [CppInteropParseError]
-// CHECK:STDERR: fail_import_cpp_file_with_multiple_errors_and_multiple_warnings.carbon:[[@LINE+34]]:1: note: in `Cpp` import [InCppImport]
+// CHECK:STDERR: fail_import_cpp_file_with_multiple_errors_and_multiple_warnings.carbon:[[@LINE+40]]:1: note: in `Cpp` import [InCppImport]
 // CHECK:STDERR: import Cpp library "multiple_errors_and_multiple_warnings.h";
 // CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 // CHECK:STDERR:
-// CHECK:STDERR: fail_import_cpp_file_with_multiple_errors_and_multiple_warnings.carbon: warning: C++:
+// CHECK:STDERR: ./multiple_errors_and_multiple_warnings.h:10: warning: C++:
 // CHECK:STDERR: In file included from fail_import_cpp_file_with_multiple_errors_and_multiple_warnings.carbon.generated.cpp_imports.h:1:
-// CHECK:STDERR: ./multiple_errors_and_multiple_warnings.h:4:2: warning: "warning1"
-// CHECK:STDERR:     4 | #warning "warning1"
+// CHECK:STDERR: ./multiple_errors_and_multiple_warnings.h:10:2: warning: "warning1"
+// CHECK:STDERR:    10 | #warning "warning1"
 // CHECK:STDERR:       |  ^
 // CHECK:STDERR:  [CppInteropParseWarning]
-// CHECK:STDERR: fail_import_cpp_file_with_multiple_errors_and_multiple_warnings.carbon:[[@LINE+24]]:1: note: in `Cpp` import [InCppImport]
+// CHECK:STDERR: fail_import_cpp_file_with_multiple_errors_and_multiple_warnings.carbon:[[@LINE+30]]:1: note: in `Cpp` import [InCppImport]
 // CHECK:STDERR: import Cpp library "multiple_errors_and_multiple_warnings.h";
 // CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 // CHECK:STDERR:
-// CHECK:STDERR: fail_import_cpp_file_with_multiple_errors_and_multiple_warnings.carbon: warning: C++:
+// CHECK:STDERR: ./multiple_errors_and_multiple_warnings.h:11: warning: C++:
 // CHECK:STDERR: In file included from fail_import_cpp_file_with_multiple_errors_and_multiple_warnings.carbon.generated.cpp_imports.h:1:
-// CHECK:STDERR: ./multiple_errors_and_multiple_warnings.h:5:2: warning: "warning2"
-// CHECK:STDERR:     5 | #warning "warning2"
+// CHECK:STDERR: ./multiple_errors_and_multiple_warnings.h:11:2: warning: "warning2"
+// CHECK:STDERR:    11 | #warning "warning2"
 // CHECK:STDERR:       |  ^
 // CHECK:STDERR:  [CppInteropParseWarning]
-// CHECK:STDERR: fail_import_cpp_file_with_multiple_errors_and_multiple_warnings.carbon:[[@LINE+14]]:1: note: in `Cpp` import [InCppImport]
+// CHECK:STDERR: fail_import_cpp_file_with_multiple_errors_and_multiple_warnings.carbon:[[@LINE+20]]:1: note: in `Cpp` import [InCppImport]
 // CHECK:STDERR: import Cpp library "multiple_errors_and_multiple_warnings.h";
 // CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 // CHECK:STDERR:
-// CHECK:STDERR: fail_import_cpp_file_with_multiple_errors_and_multiple_warnings.carbon: warning: C++:
+// CHECK:STDERR: ./multiple_errors_and_multiple_warnings.h:12: warning: C++:
 // CHECK:STDERR: In file included from fail_import_cpp_file_with_multiple_errors_and_multiple_warnings.carbon.generated.cpp_imports.h:1:
-// CHECK:STDERR: ./multiple_errors_and_multiple_warnings.h:6:2: warning: "warning3"
-// CHECK:STDERR:     6 | #warning "warning3"
+// CHECK:STDERR: ./multiple_errors_and_multiple_warnings.h:12:2: warning: "warning3"
+// CHECK:STDERR:    12 | #warning "warning3"
 // CHECK:STDERR:       |  ^
 // CHECK:STDERR:  [CppInteropParseWarning]
-// CHECK:STDERR: fail_import_cpp_file_with_multiple_errors_and_multiple_warnings.carbon:[[@LINE+4]]:1: note: in `Cpp` import [InCppImport]
+// CHECK:STDERR: fail_import_cpp_file_with_multiple_errors_and_multiple_warnings.carbon:[[@LINE+10]]:1: note: in `Cpp` import [InCppImport]
 // CHECK:STDERR: import Cpp library "multiple_errors_and_multiple_warnings.h";
 // CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 // CHECK:STDERR:
-import Cpp library "multiple_errors_and_multiple_warnings.h";
-
-// --- import_multiple_cpp_files_with_warnings.carbon
-// CHECK:STDERR: import_multiple_cpp_files_with_warnings.carbon: warning: C++:
+// CHECK:STDERR: ./one_warning.h:8: warning: C++:
 // CHECK:STDERR: In file included from import_multiple_cpp_files_with_warnings.carbon.generated.cpp_imports.h:1:
-// CHECK:STDERR: ./one_warning.h:2:2: warning: "warning1"
-// CHECK:STDERR:     2 | #warning "warning1"
+// CHECK:STDERR: ./one_warning.h:8:2: warning: "warning1"
+// CHECK:STDERR:     8 | #warning "warning1"
 // CHECK:STDERR:       |  ^
 // CHECK:STDERR:  [CppInteropParseWarning]
+import Cpp library "multiple_errors_and_multiple_warnings.h";
+
+// --- import_multiple_cpp_files_with_warnings.carbon
 
 library "[[@TEST_NAME]]";
 
 import Cpp library "one_warning.h";
-// CHECK:STDERR: import_multiple_cpp_files_with_warnings.carbon:[[@LINE+34]]:1: note: in `Cpp` import [InCppImport]
+// CHECK:STDERR: import_multiple_cpp_files_with_warnings.carbon:[[@LINE+40]]:1: note: in `Cpp` import [InCppImport]
 // CHECK:STDERR: import Cpp library "multiple_warnings.h";
 // CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 // CHECK:STDERR:
-// CHECK:STDERR: import_multiple_cpp_files_with_warnings.carbon: warning: C++:
+// CHECK:STDERR: ./multiple_warnings.h:8: warning: C++:
 // CHECK:STDERR: In file included from import_multiple_cpp_files_with_warnings.carbon.generated.cpp_imports.h:2:
-// CHECK:STDERR: ./multiple_warnings.h:2:2: warning: "warning1"
-// CHECK:STDERR:     2 | #warning "warning1"
+// CHECK:STDERR: ./multiple_warnings.h:8:2: warning: "warning1"
+// CHECK:STDERR:     8 | #warning "warning1"
 // CHECK:STDERR:       |  ^
 // CHECK:STDERR:  [CppInteropParseWarning]
-// CHECK:STDERR: import_multiple_cpp_files_with_warnings.carbon:[[@LINE+24]]:1: note: in `Cpp` import [InCppImport]
+// CHECK:STDERR: import_multiple_cpp_files_with_warnings.carbon:[[@LINE+30]]:1: note: in `Cpp` import [InCppImport]
 // CHECK:STDERR: import Cpp library "multiple_warnings.h";
 // CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 // CHECK:STDERR:
-// CHECK:STDERR: import_multiple_cpp_files_with_warnings.carbon: warning: C++:
+// CHECK:STDERR: ./multiple_warnings.h:9: warning: C++:
 // CHECK:STDERR: In file included from import_multiple_cpp_files_with_warnings.carbon.generated.cpp_imports.h:2:
-// CHECK:STDERR: ./multiple_warnings.h:3:2: warning: "warning2"
-// CHECK:STDERR:     3 | #warning "warning2"
+// CHECK:STDERR: ./multiple_warnings.h:9:2: warning: "warning2"
+// CHECK:STDERR:     9 | #warning "warning2"
 // CHECK:STDERR:       |  ^
 // CHECK:STDERR:  [CppInteropParseWarning]
-// CHECK:STDERR: import_multiple_cpp_files_with_warnings.carbon:[[@LINE+14]]:1: note: in `Cpp` import [InCppImport]
+// CHECK:STDERR: import_multiple_cpp_files_with_warnings.carbon:[[@LINE+20]]:1: note: in `Cpp` import [InCppImport]
 // CHECK:STDERR: import Cpp library "multiple_warnings.h";
 // CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 // CHECK:STDERR:
-// CHECK:STDERR: import_multiple_cpp_files_with_warnings.carbon: warning: C++:
+// CHECK:STDERR: ./multiple_warnings.h:10: warning: C++:
 // CHECK:STDERR: In file included from import_multiple_cpp_files_with_warnings.carbon.generated.cpp_imports.h:2:
-// CHECK:STDERR: ./multiple_warnings.h:4:2: warning: "warning3"
-// CHECK:STDERR:     4 | #warning "warning3"
+// CHECK:STDERR: ./multiple_warnings.h:10:2: warning: "warning3"
+// CHECK:STDERR:    10 | #warning "warning3"
 // CHECK:STDERR:       |  ^
 // CHECK:STDERR:  [CppInteropParseWarning]
-// CHECK:STDERR: import_multiple_cpp_files_with_warnings.carbon:[[@LINE+4]]:1: note: in `Cpp` import [InCppImport]
+// CHECK:STDERR: import_multiple_cpp_files_with_warnings.carbon:[[@LINE+10]]:1: note: in `Cpp` import [InCppImport]
 // CHECK:STDERR: import Cpp library "multiple_warnings.h";
 // CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 // CHECK:STDERR:
-import Cpp library "multiple_warnings.h";
-
-// --- fail_import_multiple_cpp_files_with_warnings_and_errors.carbon
-// CHECK:STDERR: fail_import_multiple_cpp_files_with_warnings_and_errors.carbon: error: C++:
+// CHECK:STDERR: ./one_error_and_one_warning.h:8: error: C++:
 // CHECK:STDERR: In file included from fail_import_multiple_cpp_files_with_warnings_and_errors.carbon.generated.cpp_imports.h:1:
-// CHECK:STDERR: ./one_error_and_one_warning.h:2:2: error: "error1"
-// CHECK:STDERR:     2 | #error "error1"
+// CHECK:STDERR: ./one_error_and_one_warning.h:8:2: error: "error1"
+// CHECK:STDERR:     8 | #error "error1"
 // CHECK:STDERR:       |  ^
 // CHECK:STDERR:  [CppInteropParseError]
+import Cpp library "multiple_warnings.h";
+
+// --- fail_import_multiple_cpp_files_with_warnings_and_errors.carbon
 
 library "[[@TEST_NAME]]";
 
 import Cpp library "one_error_and_one_warning.h";
-// CHECK:STDERR: fail_import_multiple_cpp_files_with_warnings_and_errors.carbon:[[@LINE+64]]:1: note: in `Cpp` import [InCppImport]
+// CHECK:STDERR: fail_import_multiple_cpp_files_with_warnings_and_errors.carbon:[[@LINE+66]]:1: note: in `Cpp` import [InCppImport]
 // CHECK:STDERR: import Cpp library "multiple_errors_and_multiple_warnings.h";
 // CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 // CHECK:STDERR:
-// CHECK:STDERR: fail_import_multiple_cpp_files_with_warnings_and_errors.carbon: warning: C++:
+// CHECK:STDERR: ./one_error_and_one_warning.h:9: warning: C++:
 // CHECK:STDERR: In file included from fail_import_multiple_cpp_files_with_warnings_and_errors.carbon.generated.cpp_imports.h:1:
-// CHECK:STDERR: ./one_error_and_one_warning.h:3:2: warning: "warning1"
-// CHECK:STDERR:     3 | #warning "warning1"
+// CHECK:STDERR: ./one_error_and_one_warning.h:9:2: warning: "warning1"
+// CHECK:STDERR:     9 | #warning "warning1"
 // CHECK:STDERR:       |  ^
 // CHECK:STDERR:  [CppInteropParseWarning]
-// CHECK:STDERR: fail_import_multiple_cpp_files_with_warnings_and_errors.carbon:[[@LINE+54]]:1: note: in `Cpp` import [InCppImport]
+// CHECK:STDERR: fail_import_multiple_cpp_files_with_warnings_and_errors.carbon:[[@LINE+56]]:1: note: in `Cpp` import [InCppImport]
 // CHECK:STDERR: import Cpp library "multiple_errors_and_multiple_warnings.h";
 // CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 // CHECK:STDERR:
-// CHECK:STDERR: fail_import_multiple_cpp_files_with_warnings_and_errors.carbon: error: C++:
+// CHECK:STDERR: ./multiple_errors_and_multiple_warnings.h:8: error: C++:
 // CHECK:STDERR: In file included from fail_import_multiple_cpp_files_with_warnings_and_errors.carbon.generated.cpp_imports.h:2:
-// CHECK:STDERR: ./multiple_errors_and_multiple_warnings.h:2:2: error: "error1"
-// CHECK:STDERR:     2 | #error "error1"
+// CHECK:STDERR: ./multiple_errors_and_multiple_warnings.h:8:2: error: "error1"
+// CHECK:STDERR:     8 | #error "error1"
 // CHECK:STDERR:       |  ^
 // CHECK:STDERR:  [CppInteropParseError]
-// CHECK:STDERR: fail_import_multiple_cpp_files_with_warnings_and_errors.carbon:[[@LINE+44]]:1: note: in `Cpp` import [InCppImport]
+// CHECK:STDERR: fail_import_multiple_cpp_files_with_warnings_and_errors.carbon:[[@LINE+46]]:1: note: in `Cpp` import [InCppImport]
 // CHECK:STDERR: import Cpp library "multiple_errors_and_multiple_warnings.h";
 // CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 // CHECK:STDERR:
-// CHECK:STDERR: fail_import_multiple_cpp_files_with_warnings_and_errors.carbon: error: C++:
+// CHECK:STDERR: ./multiple_errors_and_multiple_warnings.h:9: error: C++:
 // CHECK:STDERR: In file included from fail_import_multiple_cpp_files_with_warnings_and_errors.carbon.generated.cpp_imports.h:2:
-// CHECK:STDERR: ./multiple_errors_and_multiple_warnings.h:3:2: error: "error2"
-// CHECK:STDERR:     3 | #error "error2"
+// CHECK:STDERR: ./multiple_errors_and_multiple_warnings.h:9:2: error: "error2"
+// CHECK:STDERR:     9 | #error "error2"
 // CHECK:STDERR:       |  ^
 // CHECK:STDERR:  [CppInteropParseError]
-// CHECK:STDERR: fail_import_multiple_cpp_files_with_warnings_and_errors.carbon:[[@LINE+34]]:1: note: in `Cpp` import [InCppImport]
+// CHECK:STDERR: fail_import_multiple_cpp_files_with_warnings_and_errors.carbon:[[@LINE+36]]:1: note: in `Cpp` import [InCppImport]
 // CHECK:STDERR: import Cpp library "multiple_errors_and_multiple_warnings.h";
 // CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 // CHECK:STDERR:
-// CHECK:STDERR: fail_import_multiple_cpp_files_with_warnings_and_errors.carbon: warning: C++:
+// CHECK:STDERR: ./multiple_errors_and_multiple_warnings.h:10: warning: C++:
 // CHECK:STDERR: In file included from fail_import_multiple_cpp_files_with_warnings_and_errors.carbon.generated.cpp_imports.h:2:
-// CHECK:STDERR: ./multiple_errors_and_multiple_warnings.h:4:2: warning: "warning1"
-// CHECK:STDERR:     4 | #warning "warning1"
+// CHECK:STDERR: ./multiple_errors_and_multiple_warnings.h:10:2: warning: "warning1"
+// CHECK:STDERR:    10 | #warning "warning1"
 // CHECK:STDERR:       |  ^
 // CHECK:STDERR:  [CppInteropParseWarning]
-// CHECK:STDERR: fail_import_multiple_cpp_files_with_warnings_and_errors.carbon:[[@LINE+24]]:1: note: in `Cpp` import [InCppImport]
+// CHECK:STDERR: fail_import_multiple_cpp_files_with_warnings_and_errors.carbon:[[@LINE+26]]:1: note: in `Cpp` import [InCppImport]
 // CHECK:STDERR: import Cpp library "multiple_errors_and_multiple_warnings.h";
 // CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 // CHECK:STDERR:
-// CHECK:STDERR: fail_import_multiple_cpp_files_with_warnings_and_errors.carbon: warning: C++:
+// CHECK:STDERR: ./multiple_errors_and_multiple_warnings.h:11: warning: C++:
 // CHECK:STDERR: In file included from fail_import_multiple_cpp_files_with_warnings_and_errors.carbon.generated.cpp_imports.h:2:
-// CHECK:STDERR: ./multiple_errors_and_multiple_warnings.h:5:2: warning: "warning2"
-// CHECK:STDERR:     5 | #warning "warning2"
+// CHECK:STDERR: ./multiple_errors_and_multiple_warnings.h:11:2: warning: "warning2"
+// CHECK:STDERR:    11 | #warning "warning2"
 // CHECK:STDERR:       |  ^
 // CHECK:STDERR:  [CppInteropParseWarning]
-// CHECK:STDERR: fail_import_multiple_cpp_files_with_warnings_and_errors.carbon:[[@LINE+14]]:1: note: in `Cpp` import [InCppImport]
+// CHECK:STDERR: fail_import_multiple_cpp_files_with_warnings_and_errors.carbon:[[@LINE+16]]:1: note: in `Cpp` import [InCppImport]
 // CHECK:STDERR: import Cpp library "multiple_errors_and_multiple_warnings.h";
 // CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 // CHECK:STDERR:
-// CHECK:STDERR: fail_import_multiple_cpp_files_with_warnings_and_errors.carbon: warning: C++:
+// CHECK:STDERR: ./multiple_errors_and_multiple_warnings.h:12: warning: C++:
 // CHECK:STDERR: In file included from fail_import_multiple_cpp_files_with_warnings_and_errors.carbon.generated.cpp_imports.h:2:
-// CHECK:STDERR: ./multiple_errors_and_multiple_warnings.h:6:2: warning: "warning3"
-// CHECK:STDERR:     6 | #warning "warning3"
+// CHECK:STDERR: ./multiple_errors_and_multiple_warnings.h:12:2: warning: "warning3"
+// CHECK:STDERR:    12 | #warning "warning3"
 // CHECK:STDERR:       |  ^
 // CHECK:STDERR:  [CppInteropParseWarning]
-// CHECK:STDERR: fail_import_multiple_cpp_files_with_warnings_and_errors.carbon:[[@LINE+4]]:1: note: in `Cpp` import [InCppImport]
+// CHECK:STDERR: fail_import_multiple_cpp_files_with_warnings_and_errors.carbon:[[@LINE+6]]:1: note: in `Cpp` import [InCppImport]
 // CHECK:STDERR: import Cpp library "multiple_errors_and_multiple_warnings.h";
 // CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 // CHECK:STDERR:
+// CHECK:STDERR: ./one_error.h:8: error: C++:
+// CHECK:STDERR: In file included from fail_import_indirect_error.carbon.generated.cpp_imports.h:1:
 import Cpp library "multiple_errors_and_multiple_warnings.h";
 
 // --- indirect_error.h
 
+// CHECK:STDERR: In file included from ./indirect_error.h:[[@LINE+5]]:
+// CHECK:STDERR: ./one_error.h:8:2: error: "error1"
+// CHECK:STDERR:     8 | #error "error1"
+// CHECK:STDERR:       |  ^
+// CHECK:STDERR:  [CppInteropParseError]
 #include "one_error.h"
 
 // --- fail_import_indirect_error.carbon
-// CHECK:STDERR: fail_import_indirect_error.carbon: error: C++:
-// CHECK:STDERR: In file included from fail_import_indirect_error.carbon.generated.cpp_imports.h:1:
-// CHECK:STDERR: In file included from ./indirect_error.h:2:
-// CHECK:STDERR: ./one_error.h:2:2: error: "error1"
-// CHECK:STDERR:     2 | #error "error1"
-// CHECK:STDERR:       |  ^
-// CHECK:STDERR:  [CppInteropParseError]
 
 library "[[@TEST_NAME]]";
 
-// CHECK:STDERR: fail_import_indirect_error.carbon:[[@LINE+4]]:1: note: in `Cpp` import [InCppImport]
+// CHECK:STDERR: fail_import_indirect_error.carbon:[[@LINE+6]]:1: note: in `Cpp` import [InCppImport]
 // CHECK:STDERR: import Cpp library "indirect_error.h";
 // CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 // CHECK:STDERR:
+// CHECK:STDERR: ./one_warning.h:8: warning: C++:
+// CHECK:STDERR: In file included from import_indirect_warning.carbon.generated.cpp_imports.h:1:
 import Cpp library "indirect_error.h";
 
 // --- indirect_warning.h
 
+// CHECK:STDERR: In file included from ./indirect_warning.h:[[@LINE+5]]:
+// CHECK:STDERR: ./one_warning.h:8:2: warning: "warning1"
+// CHECK:STDERR:     8 | #warning "warning1"
+// CHECK:STDERR:       |  ^
+// CHECK:STDERR:  [CppInteropParseWarning]
 #include "one_warning.h"
 
 // --- import_indirect_warning.carbon
-// CHECK:STDERR: import_indirect_warning.carbon: warning: C++:
-// CHECK:STDERR: In file included from import_indirect_warning.carbon.generated.cpp_imports.h:1:
-// CHECK:STDERR: In file included from ./indirect_warning.h:2:
-// CHECK:STDERR: ./one_warning.h:2:2: warning: "warning1"
-// CHECK:STDERR:     2 | #warning "warning1"
-// CHECK:STDERR:       |  ^
-// CHECK:STDERR:  [CppInteropParseWarning]
 
 library "[[@TEST_NAME]]";
 
-// CHECK:STDERR: import_indirect_warning.carbon:[[@LINE+4]]:1: note: in `Cpp` import [InCppImport]
+// CHECK:STDERR: import_indirect_warning.carbon:[[@LINE+10]]:1: note: in `Cpp` import [InCppImport]
 // CHECK:STDERR: import Cpp library "indirect_warning.h";
 // CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 // CHECK:STDERR:
+// CHECK:STDERR: ./one_warning.h:8: warning: C++:
+// CHECK:STDERR: In file included from fail_import_cpp_library_lexer_error.carbon.generated.cpp_imports.h:1:
+// CHECK:STDERR: ./one_warning.h:8:2: warning: "warning1"
+// CHECK:STDERR:     8 | #warning "warning1"
+// CHECK:STDERR:       |  ^
+// CHECK:STDERR:  [CppInteropParseWarning]
 import Cpp library "indirect_warning.h";
 
+// --- fail_import_cpp_library_lexer_error.carbon
+
+library "[[@TEST_NAME]]";  // Trailing comment
+
+// TODO: Move this warning to be after the lexer trailing comment error.
+// CHECK:STDERR: fail_import_cpp_library_lexer_error.carbon:[[@LINE+8]]:1: note: in `Cpp` import [InCppImport]
+// CHECK:STDERR: import Cpp library "one_warning.h";
+// CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+// CHECK:STDERR:
+// CHECK:STDERR: fail_import_cpp_library_lexer_error.carbon:[[@LINE-7]]:44: error: trailing comments are not permitted [TrailingComment]
+// CHECK:STDERR: library "import_cpp_library_lexer_error";  // Trailing comment
+// CHECK:STDERR:                                            ^
+// CHECK:STDERR:
+import Cpp library "one_warning.h";
+
 // CHECK:STDOUT: --- fail_import_cpp_file_with_one_error.carbon
 // CHECK:STDOUT:
 // CHECK:STDOUT: imports {
@@ -560,3 +581,20 @@ import Cpp library "indirect_warning.h";
 // CHECK:STDOUT:   }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
+// CHECK:STDOUT: --- fail_import_cpp_library_lexer_error.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: imports {
+// CHECK:STDOUT:   %Cpp: <namespace> = namespace file.%Cpp.import_cpp, [concrete] {
+// CHECK:STDOUT:     import Cpp//...
+// CHECK:STDOUT:   }
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [concrete] {
+// CHECK:STDOUT:     .Cpp = imports.%Cpp
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Cpp.import_cpp = import_cpp {
+// CHECK:STDOUT:     import Cpp "one_warning.h"
+// CHECK:STDOUT:   }
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 3 - 3
toolchain/check/testdata/interop/cpp/no_prelude/file_not_found.carbon

@@ -7,14 +7,14 @@
 // TIP:   bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/check/testdata/interop/cpp/no_prelude/file_not_found.carbon
 // TIP: To dump output, run:
 // TIP:   bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/interop/cpp/no_prelude/file_not_found.carbon
-
-// --- fail_cpp_file_not_found.carbon
-// CHECK:STDERR: fail_cpp_file_not_found.carbon: error: C++:
+// CHECK:STDERR: fail_cpp_file_not_found.carbon.generated.cpp_imports.h:1: error: C++:
 // CHECK:STDERR: fail_cpp_file_not_found.carbon.generated.cpp_imports.h:1:10: fatal error: 'not_found.h' file not found
 // CHECK:STDERR:     1 | #include "not_found.h"
 // CHECK:STDERR:       |          ^~~~~~~~~~~~~
 // CHECK:STDERR:  [CppInteropParseError]
 
+// --- fail_cpp_file_not_found.carbon
+
 library "[[@TEST_NAME]]";
 
 // CHECK:STDERR: fail_cpp_file_not_found.carbon:[[@LINE+4]]:1: note: in `Cpp` import [InCppImport]

+ 2 - 2
toolchain/driver/compile_subcommand.cpp

@@ -573,13 +573,13 @@ auto CompilationUnit::PostCheck() -> void {
         auto import_ir_inst =
             file->import_ir_insts().Get(loc_id.import_ir_inst_id());
         const auto* import_file =
-            file->import_irs().Get(import_ir_inst.ir_id).sem_ir;
+            file->import_irs().Get(import_ir_inst.ir_id()).sem_ir;
         CARBON_CHECK(import_file);
         if (!IncludeInDumps(import_file->filename())) {
           return false;
         }
         file = import_file;
-        entity_inst_id = import_ir_inst.inst_id;
+        entity_inst_id = import_ir_inst.inst_id();
       }
     };
 

+ 5 - 3
toolchain/driver/testdata/compile/multifile_raw_and_textual_ir.carbon

@@ -31,6 +31,7 @@ fn B() {
 // CHECK:STDOUT: sem_ir:
 // CHECK:STDOUT:   import_irs:
 // CHECK:STDOUT:     ir0:             {decl_id: inst<none>, is_export: false}
+// CHECK:STDOUT:     ir1:             {decl_id: inst<none>, is_export: false}
 // CHECK:STDOUT:   import_ir_insts: {}
 // CHECK:STDOUT:   name_scopes:
 // CHECK:STDOUT:     name_scope0:     {inst: inst14, parent_scope: name_scope<none>, has_error: false, extended_scopes: [], names: {name0: inst15}}
@@ -105,10 +106,11 @@ fn B() {
 // CHECK:STDOUT: sem_ir:
 // CHECK:STDOUT:   import_irs:
 // CHECK:STDOUT:     ir0:             {decl_id: inst<none>, is_export: false}
-// CHECK:STDOUT:     ir1:             {decl_id: inst15, is_export: false}
+// CHECK:STDOUT:     ir1:             {decl_id: inst<none>, is_export: false}
+// CHECK:STDOUT:     ir2:             {decl_id: inst15, is_export: false}
 // CHECK:STDOUT:   import_ir_insts:
-// CHECK:STDOUT:     import_ir_inst0: {ir_id: ir1, inst_id: inst15}
-// CHECK:STDOUT:     import_ir_inst1: {ir_id: ir1, inst_id: inst15}
+// CHECK:STDOUT:     import_ir_inst0: {ir_id: ir2, inst_id: inst15}
+// CHECK:STDOUT:     import_ir_inst1: {ir_id: ir2, inst_id: inst15}
 // CHECK:STDOUT:   name_scopes:
 // CHECK:STDOUT:     name_scope0:     {inst: inst14, parent_scope: name_scope<none>, has_error: false, extended_scopes: [], names: {name1: inst16, name0: inst17}}
 // CHECK:STDOUT:     name_scope1:     {inst: inst16, parent_scope: name_scope0, has_error: false, extended_scopes: [], names: {name1: inst22}}

+ 5 - 3
toolchain/driver/testdata/compile/multifile_raw_ir.carbon

@@ -31,6 +31,7 @@ fn B() {
 // CHECK:STDOUT: sem_ir:
 // CHECK:STDOUT:   import_irs:
 // CHECK:STDOUT:     ir0:             {decl_id: inst<none>, is_export: false}
+// CHECK:STDOUT:     ir1:             {decl_id: inst<none>, is_export: false}
 // CHECK:STDOUT:   import_ir_insts: {}
 // CHECK:STDOUT:   name_scopes:
 // CHECK:STDOUT:     name_scope0:     {inst: inst14, parent_scope: name_scope<none>, has_error: false, extended_scopes: [], names: {name0: inst15}}
@@ -85,10 +86,11 @@ fn B() {
 // CHECK:STDOUT: sem_ir:
 // CHECK:STDOUT:   import_irs:
 // CHECK:STDOUT:     ir0:             {decl_id: inst<none>, is_export: false}
-// CHECK:STDOUT:     ir1:             {decl_id: inst15, is_export: false}
+// CHECK:STDOUT:     ir1:             {decl_id: inst<none>, is_export: false}
+// CHECK:STDOUT:     ir2:             {decl_id: inst15, is_export: false}
 // CHECK:STDOUT:   import_ir_insts:
-// CHECK:STDOUT:     import_ir_inst0: {ir_id: ir1, inst_id: inst15}
-// CHECK:STDOUT:     import_ir_inst1: {ir_id: ir1, inst_id: inst15}
+// CHECK:STDOUT:     import_ir_inst0: {ir_id: ir2, inst_id: inst15}
+// CHECK:STDOUT:     import_ir_inst1: {ir_id: ir2, inst_id: inst15}
 // CHECK:STDOUT:   name_scopes:
 // CHECK:STDOUT:     name_scope0:     {inst: inst14, parent_scope: name_scope<none>, has_error: false, extended_scopes: [], names: {name1: inst16, name0: inst17}}
 // CHECK:STDOUT:     name_scope1:     {inst: inst16, parent_scope: name_scope0, has_error: false, extended_scopes: [], names: {name1: inst22}}

+ 1 - 0
toolchain/driver/testdata/compile/raw_and_textual_ir.carbon

@@ -21,6 +21,7 @@ fn Foo(n: ()) -> ((), ()) {
 // CHECK:STDOUT: sem_ir:
 // CHECK:STDOUT:   import_irs:
 // CHECK:STDOUT:     ir0:             {decl_id: inst<none>, is_export: false}
+// CHECK:STDOUT:     ir1:             {decl_id: inst<none>, is_export: false}
 // CHECK:STDOUT:   import_ir_insts: {}
 // CHECK:STDOUT:   name_scopes:
 // CHECK:STDOUT:     name_scope0:     {inst: inst14, parent_scope: name_scope<none>, has_error: false, extended_scopes: [], names: {name0: inst34}}

+ 1 - 0
toolchain/driver/testdata/compile/raw_ir.carbon

@@ -21,6 +21,7 @@ fn Foo[T:! type](n: T) -> (T, ()) {
 // CHECK:STDOUT: sem_ir:
 // CHECK:STDOUT:   import_irs:
 // CHECK:STDOUT:     ir0:             {decl_id: inst<none>, is_export: false}
+// CHECK:STDOUT:     ir1:             {decl_id: inst<none>, is_export: false}
 // CHECK:STDOUT:   import_ir_insts: {}
 // CHECK:STDOUT:   name_scopes:
 // CHECK:STDOUT:     name_scope0:     {inst: inst14, parent_scope: name_scope<none>, has_error: false, extended_scopes: [], names: {name0: inst36}}

+ 6 - 5
toolchain/sem_ir/absolute_node_id.cpp

@@ -15,7 +15,8 @@ static auto FollowImportRef(
     const File*& cursor_ir, InstId& cursor_inst_id,
     ImportIRInstId import_ir_inst_id) -> void {
   auto import_ir_inst = cursor_ir->import_ir_insts().Get(import_ir_inst_id);
-  const auto& import_ir = cursor_ir->import_irs().Get(import_ir_inst.ir_id);
+  CARBON_CHECK(import_ir_inst.ir_id() != ImportIRId::Cpp);
+  const auto& import_ir = cursor_ir->import_irs().Get(import_ir_inst.ir_id());
   CARBON_CHECK(import_ir.decl_id.has_value(),
                "If we get `None` locations here, we may need to more "
                "thoroughly track ImportDecls.");
@@ -31,9 +32,9 @@ static auto FollowImportRef(
       auto implicit_import_ir_inst =
           cursor_ir->import_ir_insts().Get(import_loc_id.import_ir_inst_id());
       const auto& implicit_ir =
-          cursor_ir->import_irs().Get(implicit_import_ir_inst.ir_id);
-      auto implicit_loc_id =
-          implicit_ir.sem_ir->insts().GetLocId(implicit_import_ir_inst.inst_id);
+          cursor_ir->import_irs().Get(implicit_import_ir_inst.ir_id());
+      auto implicit_loc_id = implicit_ir.sem_ir->insts().GetLocId(
+          implicit_import_ir_inst.inst_id());
       CARBON_CHECK(implicit_loc_id.kind() == SemIR::LocId::Kind::NodeId,
                    "Should only be one layer of implicit imports");
       absolute_node_ids.push_back(
@@ -54,7 +55,7 @@ static auto FollowImportRef(
   }
 
   cursor_ir = import_ir.sem_ir;
-  cursor_inst_id = import_ir_inst.inst_id;
+  cursor_inst_id = import_ir_inst.inst_id();
 }
 
 // Returns true if this is the final parse node location. If the location is is

+ 2 - 2
toolchain/sem_ir/expr_info.cpp

@@ -50,8 +50,8 @@ auto GetExprCategory(const File& file, InstId inst_id) -> ExprCategory {
       case ImportRefLoaded::Kind: {
         auto import_ir_inst = ir->import_ir_insts().Get(
             untyped_inst.As<SemIR::AnyImportRef>().import_ir_inst_id);
-        ir = ir->import_irs().Get(import_ir_inst.ir_id).sem_ir;
-        inst_id = import_ir_inst.inst_id;
+        ir = ir->import_irs().Get(import_ir_inst.ir_id()).sem_ir;
+        inst_id = import_ir_inst.inst_id();
         continue;
       }
 

+ 11 - 0
toolchain/sem_ir/file.h

@@ -198,6 +198,7 @@ class File : public Printable<File> {
     return import_cpps_;
   }
   auto cpp_ast() -> clang::ASTUnit* { return cpp_ast_; }
+  auto cpp_ast() const -> const clang::ASTUnit* { return cpp_ast_; }
   // TODO: When the AST can be created before creating `File`, initialize the
   // pointer in the constructor and remove this function. This is part of
   // https://github.com/carbon-language/carbon-lang/issues/4666
@@ -231,6 +232,13 @@ class File : public Printable<File> {
     return expr_regions_;
   }
 
+  auto clang_source_locs() -> ValueStore<ClangSourceLocId>& {
+    return clang_source_locs_;
+  }
+  auto clang_source_locs() const -> const ValueStore<ClangSourceLocId>& {
+    return clang_source_locs_;
+  }
+
   auto top_inst_block_id() const -> InstBlockId { return top_inst_block_id_; }
   auto set_top_inst_block_id(InstBlockId block_id) -> void {
     top_inst_block_id_ = block_id;
@@ -354,6 +362,9 @@ class File : public Printable<File> {
   // Single-entry/single-exit regions that are referenced as units, e.g. because
   // they represent expressions.
   ValueStore<ExprRegionId> expr_regions_;
+
+  // C++ source locations for C++ interop.
+  ValueStore<ClangSourceLocId> clang_source_locs_;
 };
 
 }  // namespace Carbon::SemIR

+ 7 - 6
toolchain/sem_ir/formatter.cpp

@@ -566,7 +566,7 @@ class FormatterImpl {
       auto loc_id = sem_ir_->insts().GetLocId(first_owning_decl_id);
       if (loc_id.kind() == SemIR::LocId::Kind::ImportIRInstId) {
         auto import_ir_id =
-            sem_ir_->import_ir_insts().Get(loc_id.import_ir_inst_id()).ir_id;
+            sem_ir_->import_ir_insts().Get(loc_id.import_ir_inst_id()).ir_id();
         const auto* import_file =
             sem_ir_->import_irs().Get(import_ir_id).sem_ir;
         pending_imported_from_ = import_file->filename();
@@ -1149,7 +1149,7 @@ class FormatterImpl {
                           llvm::StringLiteral loaded_label) -> void {
     out_ << " ";
     auto import_ir_inst = sem_ir_->import_ir_insts().Get(import_ir_inst_id);
-    FormatArg(import_ir_inst.ir_id);
+    FormatArg(import_ir_inst.ir_id());
     out_ << ", ";
     if (entity_name_id.has_value()) {
       // Prefer to show the entity name when possible.
@@ -1157,17 +1157,18 @@ class FormatterImpl {
     } else {
       // Show a name based on the location when possible, or the numeric
       // instruction as a last resort.
-      const auto& import_ir = sem_ir_->import_irs().Get(import_ir_inst.ir_id);
-      auto loc_id = import_ir.sem_ir->insts().GetLocId(import_ir_inst.inst_id);
+      const auto& import_ir = sem_ir_->import_irs().Get(import_ir_inst.ir_id());
+      auto loc_id =
+          import_ir.sem_ir->insts().GetLocId(import_ir_inst.inst_id());
       switch (loc_id.kind()) {
         case SemIR::LocId::Kind::None: {
-          out_ << import_ir_inst.inst_id << " [no loc]";
+          out_ << import_ir_inst.inst_id() << " [no loc]";
           break;
         }
         case SemIR::LocId::Kind::ImportIRInstId: {
           // TODO: Probably don't want to format each indirection, but maybe
           // reuse GetCanonicalImportIRInst?
-          out_ << import_ir_inst.inst_id << " [indirect]";
+          out_ << import_ir_inst.inst_id() << " [indirect]";
           break;
         }
         case SemIR::LocId::Kind::NodeId: {

+ 22 - 1
toolchain/sem_ir/ids.h

@@ -14,10 +14,19 @@
 #include "toolchain/diagnostics/diagnostic_emitter.h"
 #include "toolchain/parse/node_ids.h"
 
+// NOLINTNEXTLINE(readability-identifier-naming)
+namespace clang {
+
+// Forward declare indexed types, for integration with ValueStore.
+class SourceLocation;
+
+}  // namespace clang
+
 namespace Carbon::SemIR {
 
 // Forward declare indexed types, for integration with ValueStore.
 class File;
+class ImportIRInst;
 class Inst;
 class NameScope;
 struct AssociatedConstant;
@@ -32,7 +41,6 @@ struct Specific;
 struct SpecificInterface;
 struct ImportCpp;
 struct ImportIR;
-struct ImportIRInst;
 struct Impl;
 struct Interface;
 struct StructTypeField;
@@ -409,10 +417,15 @@ struct ImportIRId : public IdBase<ImportIRId> {
   // instructions.
   static const ImportIRId ApiForImpl;
 
+  // The `Cpp` import. A null entry is added if there is none, in which case
+  // this ID should not show up in instructions.
+  static const ImportIRId Cpp;
+
   using IdBase::IdBase;
 };
 
 constexpr ImportIRId ImportIRId::ApiForImpl = ImportIRId(0);
+constexpr ImportIRId ImportIRId::Cpp = ImportIRId(ApiForImpl.index + 1);
 
 // A boolean value.
 struct BoolValue : public IdBase<BoolValue> {
@@ -757,6 +770,14 @@ struct TypeId : public IdBase<TypeId> {
   auto Print(llvm::raw_ostream& out) const -> void;
 };
 
+// The ID of a Clang Source Location.
+struct ClangSourceLocId : public IdBase<ClangSourceLocId> {
+  static constexpr llvm::StringLiteral Label = "clang_source_loc";
+  using ValueType = clang::SourceLocation;
+
+  using IdBase::IdBase;
+};
+
 // An index for element access, for structs, tuples, and classes.
 struct ElementIndex : public IndexBase<ElementIndex> {
   static constexpr llvm::StringLiteral Label = "element";

+ 44 - 6
toolchain/sem_ir/import_ir.h

@@ -29,19 +29,57 @@ struct ImportIR : public Printable<ImportIR> {
 static_assert(sizeof(ImportIR) == 8 + sizeof(uintptr_t), "Unexpected size");
 
 // A reference to an instruction in an imported IR. Used for diagnostics with
-// LocId.
-struct ImportIRInst : public Printable<ImportIRInst> {
+// LocId. For a `Cpp` import, points to a Clang source location.
+class ImportIRInst : public Printable<ImportIRInst> {
+ public:
+  // Constructor for a non-`Cpp` import.
+  explicit ImportIRInst(ImportIRId ir_id, InstId inst_id)
+      : ir_id_(ir_id), inst_id_(inst_id) {
+    CARBON_CHECK(ir_id != ImportIRId::Cpp);
+  }
+
+  // Constructor for a `Cpp` import.
+  explicit ImportIRInst(ClangSourceLocId clang_source_loc_id)
+      : ir_id_(SemIR::ImportIRId::Cpp),
+        clang_source_loc_id_(clang_source_loc_id) {}
+
   auto Print(llvm::raw_ostream& out) const -> void {
-    out << "{ir_id: " << ir_id << ", inst_id: " << inst_id << "}";
+    out << "{ir_id: " << ir_id() << ", ";
+    if (ir_id() == ImportIRId::Cpp) {
+      out << "clang_source_loc_id: " << clang_source_loc_id();
+    } else {
+      out << "inst_id: " << inst_id();
+    }
+    out << "}";
   }
 
   friend auto operator==(const ImportIRInst& lhs, const ImportIRInst& rhs)
       -> bool {
-    return lhs.ir_id == rhs.ir_id && lhs.inst_id == rhs.inst_id;
+    return lhs.ir_id() == rhs.ir_id() &&
+           (lhs.ir_id() == ImportIRId::Cpp
+                ? lhs.clang_source_loc_id() == rhs.clang_source_loc_id()
+                : lhs.inst_id() == rhs.inst_id());
   }
 
-  ImportIRId ir_id;
-  InstId inst_id;
+  auto ir_id() const -> ImportIRId { return ir_id_; }
+  auto inst_id() const -> InstId {
+    CARBON_CHECK(ir_id() != ImportIRId::Cpp);
+    return inst_id_;
+  }
+  auto clang_source_loc_id() const -> ClangSourceLocId {
+    CARBON_CHECK(ir_id() == ImportIRId::Cpp);
+    return clang_source_loc_id_;
+  }
+
+ private:
+  ImportIRId ir_id_;
+  union {
+    // Set iff `ir_id != ImportIRId::Cpp`.
+    InstId inst_id_;
+
+    // Set iff `ir_id == ImportIRId::Cpp`.
+    ClangSourceLocId clang_source_loc_id_;
+  };
 };
 
 }  // namespace Carbon::SemIR

+ 2 - 1
toolchain/sem_ir/inst_fingerprinter.cpp

@@ -293,7 +293,8 @@ struct Worklist {
 
   auto Add(ImportIRInstId ir_inst_id) -> void {
     auto ir_inst = sem_ir->import_ir_insts().Get(ir_inst_id);
-    AddInFile(sem_ir->import_irs().Get(ir_inst.ir_id).sem_ir, ir_inst.inst_id);
+    AddInFile(sem_ir->import_irs().Get(ir_inst.ir_id()).sem_ir,
+              ir_inst.inst_id());
   }
 
   template <typename T>

+ 1 - 1
toolchain/sem_ir/inst_namer.cpp

@@ -737,7 +737,7 @@ auto InstNamer::CollectNamesInBlock(ScopeId top_scope_id,
         auto import_ir_inst =
             sem_ir_->import_ir_insts().Get(inst.import_ir_inst_id);
         const auto& import_ir =
-            *sem_ir_->import_irs().Get(import_ir_inst.ir_id).sem_ir;
+            *sem_ir_->import_irs().Get(import_ir_inst.ir_id()).sem_ir;
         auto package_id = import_ir.package_id();
         if (auto ident_id = package_id.AsIdentifierId(); ident_id.has_value()) {
           out << import_ir.identifiers().Get(ident_id);

+ 1 - 1
toolchain/sem_ir/yaml_test.cpp

@@ -56,7 +56,7 @@ TEST(SemIRTest, Yaml) {
   auto type_builtin = Pair(type_id, Yaml::Mapping(_));
 
   auto file = Yaml::Mapping(ElementsAre(
-      Pair("import_irs", Yaml::Mapping(SizeIs(1))),
+      Pair("import_irs", Yaml::Mapping(SizeIs(2))),
       Pair("import_ir_insts", Yaml::Mapping(SizeIs(0))),
       Pair("name_scopes", Yaml::Mapping(SizeIs(1))),
       Pair("entity_names", Yaml::Mapping(SizeIs(1))),