Răsfoiți Sursa

Improve mapping of Clang diagnostics into Carbon diagnostics (#5894)

Instead of taking the complete text of the Clang diagnostic and using it
as the message portion of a Carbon diagnostic, generate the individual
pieces separately and pass them into the Carbon diagnostic
infrastructure.

* Clang's context lines are generated by running a custom "diagnostic
renderer" and tracking which lines it wants to print as context for a
given source location. When mapping from a C++ source location back to a
Carbon location, the Carbon `Loc` structure is now fully populated,
including filling in the context line and the column number.
* Clang's snippet is generated by running a custom diagnostic renderer
that is a cut-down version of the full text diagnostic renderer that
only prints a snippet. This is then attached to the Carbon diagnostic
manually as an override for the snippet we'd usually create.

We no longer repeat the file location twice on each diagnostic, and no
longer produce a bogus "in import" line for all locations coming from
clang that point arbitrarily to the first C++ import in the Carbon file.
The `[diagnostic kind]` marker is now displayed at the end of the
diagnostic message, not on a line of its own after the snippet.
Richard Smith 9 luni în urmă
părinte
comite
25681901bd

+ 23 - 1
toolchain/check/diagnostic_emitter.cpp

@@ -11,6 +11,7 @@
 #include "common/raw_string_ostream.h"
 #include "toolchain/check/diagnostic_helpers.h"
 #include "toolchain/sem_ir/absolute_node_id.h"
+#include "toolchain/sem_ir/diagnostic_loc_converter.h"
 #include "toolchain/sem_ir/ids.h"
 #include "toolchain/sem_ir/stringify.h"
 
@@ -23,7 +24,28 @@ auto DiagnosticEmitter::ConvertLoc(LocIdForDiagnostics loc_id,
       loc_id.loc_id(), loc_id.is_token_only());
   for (const auto& import : imports) {
     CARBON_DIAGNOSTIC(InImport, LocationInfo, "in import");
-    context_fn(import.loc, InImport);
+    CARBON_DIAGNOSTIC(InCppInclude, LocationInfo, "in file included here");
+    CARBON_DIAGNOSTIC(InCppModule, LocationInfo, "in module imported here");
+    CARBON_DIAGNOSTIC(InCppMacroExpansion, LocationInfo,
+                      "in expansion of macro defined here");
+    switch (import.kind) {
+      case Carbon::SemIR::DiagnosticLocConverter::ImportLoc::Import:
+        // TODO: Include the library name in the note.
+        context_fn(import.loc, InImport);
+        break;
+      case Carbon::SemIR::DiagnosticLocConverter::ImportLoc::CppInclude:
+        // TODO: Include the file name in the note.
+        context_fn(import.loc, InCppInclude);
+        break;
+      case Carbon::SemIR::DiagnosticLocConverter::ImportLoc::CppModuleImport:
+        // TODO: Include the module name in the note.
+        context_fn(import.loc, InCppModule);
+        break;
+      case Carbon::SemIR::DiagnosticLocConverter::ImportLoc::CppMacroExpansion:
+        // TODO: Include the macro name in the note.
+        context_fn(import.loc, InCppMacroExpansion);
+        break;
+    }
   }
 
   // Use the token when possible, but -1 is the default value.

+ 94 - 36
toolchain/check/import_cpp.cpp

@@ -141,32 +141,26 @@ class CarbonClangDiagnosticConsumer : public clang::DiagnosticConsumer {
     llvm::SmallString<256> message;
     info.FormatDiagnostic(message);
 
+    // Render a code snippet including any highlighted ranges and fixit hints.
+    // TODO: Also include the #include stack and macro expansion stack in the
+    // diagnostic output in some way.
+    RawStringOstream snippet_stream;
     if (!info.hasSourceManager()) {
-      // If we don't have a source manager, we haven't actually started
-      // compiling yet, and this is an error from the driver or early in the
-      // frontend. Pass it on directly.
+      // If we don't have a source manager, this is an error from early in the
+      // frontend. Don't produce a snippet.
       CARBON_CHECK(info.getLocation().isInvalid());
-      diagnostic_infos_.push_back({.level = diag_level,
-                                   .import_ir_inst_id = clang_import_ir_inst_id,
-                                   .message = message.str().str()});
-      return;
+    } else {
+      CodeContextRenderer(snippet_stream, invocation_->getLangOpts(),
+                          invocation_->getDiagnosticOpts())
+          .emitDiagnostic(
+              clang::FullSourceLoc(info.getLocation(), info.getSourceManager()),
+              diag_level, message, info.getRanges(), info.getFixItHints());
     }
 
-    // TODO: This includes the full clang diagnostic, including the source
-    // location, resulting in the location appearing twice in the output.
-    RawStringOstream diagnostics_stream;
-    clang::TextDiagnostic text_diagnostic(diagnostics_stream,
-                                          invocation_->getLangOpts(),
-                                          invocation_->getDiagnosticOpts());
-    text_diagnostic.emitDiagnostic(
-        clang::FullSourceLoc(info.getLocation(), info.getSourceManager()),
-        diag_level, message, info.getRanges(), info.getFixItHints());
-
-    std::string diagnostics_str = diagnostics_stream.TakeStr();
-
     diagnostic_infos_.push_back({.level = diag_level,
                                  .import_ir_inst_id = clang_import_ir_inst_id,
-                                 .message = diagnostics_str});
+                                 .message = message.str().str(),
+                                 .snippet = snippet_stream.TakeStr()});
   }
 
   // Outputs Carbon diagnostics based on the collected Clang diagnostics. Must
@@ -200,15 +194,22 @@ class CarbonClangDiagnosticConsumer : public clang::DiagnosticConsumer {
                   ? CppInteropParseWarning
                   : CppInteropParseError,
               info.message);
+          builder.OverrideSnippet(info.snippet);
           for (;
                i + 1 < diagnostic_infos_.size() &&
                diagnostic_infos_[i + 1].level == clang::DiagnosticsEngine::Note;
                ++i) {
             const ClangDiagnosticInfo& note_info = diagnostic_infos_[i + 1];
             CARBON_DIAGNOSTIC(CppInteropParseNote, Note, "{0}", std::string);
-            builder.Note(SemIR::LocId(note_info.import_ir_inst_id),
-                         CppInteropParseNote, note_info.message);
+            builder
+                .Note(SemIR::LocId(note_info.import_ir_inst_id),
+                      CppInteropParseNote, note_info.message)
+                .OverrideSnippet(note_info.snippet);
           }
+          // TODO: This will apply all current Carbon annotation functions. We
+          // should instead track how Clang's context notes and Carbon's
+          // annotation functions are interleaved, and interleave the notes in
+          // the same order.
           builder.Emit();
           break;
         }
@@ -218,11 +219,36 @@ class CarbonClangDiagnosticConsumer : public clang::DiagnosticConsumer {
   }
 
  private:
-  // The type-checking context in which we're running Clang.
-  Context* context_;
-
-  // The compiler invocation that is producing the diagnostics.
-  std::shared_ptr<clang::CompilerInvocation> invocation_;
+  // A diagnostics renderer based on clang's TextDiagnostic that captures just
+  // the code context (the snippet).
+  class CodeContextRenderer : public clang::TextDiagnostic {
+   public:
+    using TextDiagnostic::TextDiagnostic;
+
+    void emitDiagnosticMessage(
+        clang::FullSourceLoc /*loc*/, clang::PresumedLoc /*ploc*/,
+        clang::DiagnosticsEngine::Level /*level*/, llvm::StringRef /*message*/,
+        llvm::ArrayRef<clang::CharSourceRange> /*ranges*/,
+        clang::DiagOrStoredDiag /*info*/) override {}
+    void emitDiagnosticLoc(
+        clang::FullSourceLoc /*loc*/, clang::PresumedLoc /*ploc*/,
+        clang::DiagnosticsEngine::Level /*level*/,
+        llvm::ArrayRef<clang::CharSourceRange> /*ranges*/) override {}
+
+    // emitCodeContext is inherited from clang::TextDiagnostic.
+
+    void emitIncludeLocation(clang::FullSourceLoc /*loc*/,
+                             clang::PresumedLoc /*ploc*/) override {}
+    void emitImportLocation(clang::FullSourceLoc /*loc*/,
+                            clang::PresumedLoc /*ploc*/,
+                            llvm::StringRef /*module_name*/) override {}
+    void emitBuildingModuleLocation(clang::FullSourceLoc /*loc*/,
+                                    clang::PresumedLoc /*ploc*/,
+                                    llvm::StringRef /*module_name*/) override {}
+
+    // beginDiagnostic and endDiagnostic are inherited from
+    // clang::TextDiagnostic in case it wants to do any setup / teardown work.
+  };
 
   // Information on a Clang diagnostic that can be converted to a Carbon
   // diagnostic.
@@ -236,25 +262,56 @@ class CarbonClangDiagnosticConsumer : public clang::DiagnosticConsumer {
 
     // The Clang diagnostic textual message.
     std::string message;
+
+    // The code snippet produced by clang.
+    std::string snippet;
   };
 
+  // The type-checking context in which we're running Clang.
+  Context* context_;
+
+  // The compiler invocation that is producing the diagnostics.
+  std::shared_ptr<clang::CompilerInvocation> invocation_;
+
   // 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_;
 };
 
+// A wrapper around a clang::CompilerInvocation that allows us to make a shallow
+// copy of most of the invocation and only make a deep copy of the parts that we
+// want to change.
+//
+// clang::CowCompilerInvocation almost allows this, but doesn't derive from
+// CompilerInvocation or support shallow copies from a CompilerInvocation, so is
+// not useful to us as we can't build an ASTUnit from it.
+class ShallowCopyCompilerInvocation : public clang::CompilerInvocation {
+ public:
+  explicit ShallowCopyCompilerInvocation(
+      const clang::CompilerInvocation& invocation) {
+    shallow_copy_assign(invocation);
+
+    // The preprocessor options are modified to hold a replacement includes
+    // buffer, so make our own version of those options.
+    PPOpts = std::make_shared<clang::PreprocessorOptions>(*PPOpts);
+  }
+};
+
 }  // 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. Sets the AST in the context's `sem_ir`.
 // TODO: Consider to always have a (non-null) AST.
-static auto GenerateAst(Context& context,
-                        llvm::ArrayRef<Parse::Tree::PackagingNames> imports,
-                        llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> fs,
-                        std::shared_ptr<clang::CompilerInvocation> invocation)
+static auto GenerateAst(
+    Context& context, llvm::ArrayRef<Parse::Tree::PackagingNames> imports,
+    llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> fs,
+    std::shared_ptr<clang::CompilerInvocation> base_invocation)
     -> std::pair<std::unique_ptr<clang::ASTUnit>, bool> {
+  auto invocation =
+      std::make_shared<ShallowCopyCompilerInvocation>(*base_invocation);
+
   // Build a diagnostics engine.
   llvm::IntrusiveRefCntPtr<clang::DiagnosticsEngine> diags(
       clang::CompilerInstance::createDiagnostics(
@@ -274,9 +331,10 @@ static auto GenerateAst(Context& context,
   // TODO: Modify the frontend options to specify this memory buffer as input
   // instead of remapping the file.
   std::string includes = GenerateCppIncludesHeaderCode(context, imports);
-  auto includes_buffer = llvm::MemoryBuffer::getMemBuffer(includes, file_name);
+  auto includes_buffer =
+      llvm::MemoryBuffer::getMemBufferCopy(includes, file_name);
   invocation->getPreprocessorOpts().addRemappedFile(file_name,
-                                                    includes_buffer.get());
+                                                    includes_buffer.release());
 
   clang::DiagnosticErrorTrap trap(*diags);
 
@@ -285,9 +343,6 @@ static auto GenerateAst(Context& context,
       invocation, std::make_shared<clang::PCHContainerOperations>(), nullptr,
       diags, new clang::FileManager(invocation->getFileSystemOpts(), fs));
 
-  // Remove remapped file before its underlying storage is destroyed.
-  invocation->getPreprocessorOpts().clearRemappedFiles();
-
   // Attach the AST to SemIR. This needs to be done before we can emit any
   // diagnostics, so their locations can be properly interpreted by our
   // diagnostics machinery.
@@ -377,6 +432,9 @@ static auto ClangLookup(Context& context, SemIR::NameScopeId scope_id,
   CARBON_CHECK(ast);
   clang::Sema& sema = ast->getSema();
 
+  // TODO: Map the LocId of the lookup to a clang SourceLocation and provide it
+  // here so that clang's diagnostics can point into the carbon code that uses
+  // the name.
   clang::LookupResult lookup(
       sema,
       clang::DeclarationNameInfo(

+ 2 - 4
toolchain/check/testdata/interop/cpp/bad_import.carbon

@@ -34,10 +34,8 @@ import Cpp library "";
 
 library "[[@TEST_NAME]]";
 
-// CHECK:STDERR: fail_import_cpp_library_file_with_quotes.carbon:[[@LINE+6]]:1: in import [InImport]
-// CHECK:STDERR: fail_import_cpp_library_file_with_quotes.carbon:[[@LINE+5]]: error: fail_import_cpp_library_file_with_quotes.carbon:[[@LINE+5]]:10: fatal error: '\"foo.h\"' file not found
-// CHECK:STDERR:    10 | #include "\"foo.h\""
+// CHECK:STDERR: fail_import_cpp_library_file_with_quotes.carbon:[[@LINE+4]]:10: error: '\"foo.h\"' file not found [CppInteropParseError]
+// CHECK:STDERR:     8 | #include "\"foo.h\""
 // CHECK:STDERR:       |          ^~~~~~~~~~~
-// CHECK:STDERR:  [CppInteropParseError]
 // CHECK:STDERR:
 import Cpp library "\"foo.h\"";

+ 10 - 6
toolchain/check/testdata/interop/cpp/class/access.carbon

@@ -50,11 +50,13 @@ library "[[@TEST_NAME]]";
 import Cpp library "non_function_member_protected.h";
 
 fn F(c: Cpp.C) {
-  // CHECK:STDERR: fail_import_non_function_member_protected.carbon:[[@LINE+6]]:16: error: cannot access protected member `x` of type `Cpp.C` [ClassInvalidMemberAccess]
+  // CHECK:STDERR: fail_import_non_function_member_protected.carbon:[[@LINE+8]]:16: error: cannot access protected member `x` of type `Cpp.C` [ClassInvalidMemberAccess]
   // CHECK:STDERR:   let x: i32 = c.x;
   // CHECK:STDERR:                ^~~
-  // CHECK:STDERR: fail_import_non_function_member_protected.carbon:[[@LINE-6]]:1: in import [InImport]
-  // CHECK:STDERR: ./non_function_member_protected.h:2: note: declared here [ClassMemberDeclaration]
+  // CHECK:STDERR: fail_import_non_function_member_protected.carbon:[[@LINE-6]]:10: in file included here [InCppInclude]
+  // CHECK:STDERR: ./non_function_member_protected.h:2:7: note: declared here [ClassMemberDeclaration]
+  // CHECK:STDERR: class C {
+  // CHECK:STDERR:       ^
   // CHECK:STDERR:
   let x: i32 = c.x;
 }
@@ -76,11 +78,13 @@ library "[[@TEST_NAME]]";
 import Cpp library "non_function_member_private.h";
 
 fn F(c: Cpp.C) {
-  // CHECK:STDERR: fail_import_non_function_member_private.carbon:[[@LINE+6]]:16: error: cannot access private member `x` of type `Cpp.C` [ClassInvalidMemberAccess]
+  // CHECK:STDERR: fail_import_non_function_member_private.carbon:[[@LINE+8]]:16: error: cannot access private member `x` of type `Cpp.C` [ClassInvalidMemberAccess]
   // CHECK:STDERR:   let x: i32 = c.x;
   // CHECK:STDERR:                ^~~
-  // CHECK:STDERR: fail_import_non_function_member_private.carbon:[[@LINE-6]]:1: in import [InImport]
-  // CHECK:STDERR: ./non_function_member_private.h:2: note: declared here [ClassMemberDeclaration]
+  // CHECK:STDERR: fail_import_non_function_member_private.carbon:[[@LINE-6]]:10: in file included here [InCppInclude]
+  // CHECK:STDERR: ./non_function_member_private.h:2:7: note: declared here [ClassMemberDeclaration]
+  // CHECK:STDERR: class C {
+  // CHECK:STDERR:       ^
   // CHECK:STDERR:
   let x: i32 = c.x;
 }

+ 5 - 3
toolchain/check/testdata/interop/cpp/class/class.carbon

@@ -35,11 +35,13 @@ library "[[@TEST_NAME]]";
 import Cpp library "declaration.h";
 
 fn MyF() {
-  // CHECK:STDERR: fail_use_declaration_as_definition.carbon:[[@LINE+6]]:12: error: binding pattern has incomplete type `Bar` in name binding declaration [IncompleteTypeInBindingDecl]
+  // CHECK:STDERR: fail_use_declaration_as_definition.carbon:[[@LINE+8]]:12: error: binding pattern has incomplete type `Bar` in name binding declaration [IncompleteTypeInBindingDecl]
   // CHECK:STDERR:   var bar: Cpp.Bar;
   // CHECK:STDERR:            ^~~~~~~
-  // CHECK:STDERR: fail_use_declaration_as_definition.carbon:[[@LINE-6]]:1: in import [InImport]
-  // CHECK:STDERR: ./declaration.h:2: note: class was forward declared here [ClassForwardDeclaredHere]
+  // CHECK:STDERR: fail_use_declaration_as_definition.carbon:[[@LINE-6]]:10: in file included here [InCppInclude]
+  // CHECK:STDERR: ./declaration.h:2:7: note: class was forward declared here [ClassForwardDeclaredHere]
+  // CHECK:STDERR: class Bar;
+  // CHECK:STDERR:       ^
   // CHECK:STDERR:
   var bar: Cpp.Bar;
 }

+ 5 - 3
toolchain/check/testdata/interop/cpp/class/struct.carbon

@@ -35,11 +35,13 @@ library "[[@TEST_NAME]]";
 import Cpp library "declaration.h";
 
 fn MyF() {
-  // CHECK:STDERR: fail_use_declaration_as_definition.carbon:[[@LINE+6]]:12: error: binding pattern has incomplete type `Bar` in name binding declaration [IncompleteTypeInBindingDecl]
+  // CHECK:STDERR: fail_use_declaration_as_definition.carbon:[[@LINE+8]]:12: error: binding pattern has incomplete type `Bar` in name binding declaration [IncompleteTypeInBindingDecl]
   // CHECK:STDERR:   var bar: Cpp.Bar;
   // CHECK:STDERR:            ^~~~~~~
-  // CHECK:STDERR: fail_use_declaration_as_definition.carbon:[[@LINE-6]]:1: in import [InImport]
-  // CHECK:STDERR: ./declaration.h:2: note: class was forward declared here [ClassForwardDeclaredHere]
+  // CHECK:STDERR: fail_use_declaration_as_definition.carbon:[[@LINE-6]]:10: in file included here [InCppInclude]
+  // CHECK:STDERR: ./declaration.h:2:8: note: class was forward declared here [ClassForwardDeclaredHere]
+  // CHECK:STDERR: struct Bar;
+  // CHECK:STDERR:        ^
   // CHECK:STDERR:
   var bar: Cpp.Bar;
 }

+ 26 - 29
toolchain/check/testdata/interop/cpp/class/template.carbon

@@ -60,17 +60,14 @@ using Xint = X<int>;
 
 library "[[@TEST_NAME]]";
 
-// CHECK:STDERR: fail_use_instantiation_error.carbon:[[@LINE+11]]:1: in import [InImport]
-// CHECK:STDERR: ./instantiation_error.h:3: error: In file included from fail_use_instantiation_error.carbon:[[@LINE+10]]:
-// CHECK:STDERR: ./instantiation_error.h:3:25: error: type 'int' cannot be used prior to '::' because it has no members
+// CHECK:STDERR: fail_use_instantiation_error.carbon:[[@LINE+8]]:10: in file included here [InCppInclude]
+// CHECK:STDERR: ./instantiation_error.h:3:25: error: type 'int' cannot be used prior to '::' because it has no members [CppInteropParseError]
 // CHECK:STDERR:     3 |   using type = typename T::member;
 // CHECK:STDERR:       |                         ^
-// CHECK:STDERR:  [CppInteropParseError]
-// CHECK:STDERR: fail_use_instantiation_error.carbon:[[@LINE+5]]:1: in import [InImport]
-// CHECK:STDERR: ./instantiation_error.h:2: note: ./instantiation_error.h:2:29: note: in instantiation of template class 'X<int>' requested here
+// CHECK:STDERR: fail_use_instantiation_error.carbon:[[@LINE+4]]:10: in file included here [InCppInclude]
+// CHECK:STDERR: ./instantiation_error.h:2:29: note: in instantiation of template class 'X<int>' requested here [CppInteropParseNote]
 // CHECK:STDERR:     2 | template<typename T> struct X {
 // CHECK:STDERR:       |                             ^
-// CHECK:STDERR:  [CppInteropParseNote]
 import Cpp library "instantiation_error.h";
 
 //@dump-sem-ir-begin
@@ -173,11 +170,11 @@ var y: Cpp.Xint.r#type = 0;
 // CHECK:STDOUT:     %x.var_patt: %pattern_type.7ce = var_pattern %x.patt [concrete]
 // CHECK:STDOUT:   }
 // CHECK:STDOUT:   %x.var: ref %i32 = var %x.var_patt [concrete]
-// CHECK:STDOUT:   %.loc18: type = splice_block %type.ref.loc18 [concrete = constants.%i32] {
-// CHECK:STDOUT:     %Cpp.ref.loc18: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
+// CHECK:STDOUT:   %.loc15: type = splice_block %type.ref.loc15 [concrete = constants.%i32] {
+// CHECK:STDOUT:     %Cpp.ref.loc15: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
 // CHECK:STDOUT:     %XY.ref: type = name_ref XY, imports.%X.decl.dec4cb.1 [concrete = constants.%X.32d59e.1]
 // CHECK:STDOUT:     <elided>
-// CHECK:STDOUT:     %type.ref.loc18: type = name_ref r#type, %i32.2 [concrete = constants.%i32]
+// CHECK:STDOUT:     %type.ref.loc15: type = name_ref r#type, %i32.2 [concrete = constants.%i32]
 // CHECK:STDOUT:   }
 // CHECK:STDOUT:   %x: ref %i32 = bind_name x, %x.var [concrete = %x.var]
 // CHECK:STDOUT:   name_binding_decl {
@@ -185,33 +182,33 @@ var y: Cpp.Xint.r#type = 0;
 // CHECK:STDOUT:     %y.var_patt: %pattern_type.7ce = var_pattern %y.patt [concrete]
 // CHECK:STDOUT:   }
 // CHECK:STDOUT:   %y.var: ref %i32 = var %y.var_patt [concrete]
-// CHECK:STDOUT:   %.loc24: type = splice_block %type.ref.loc24 [concrete = constants.%i32] {
-// CHECK:STDOUT:     %Cpp.ref.loc24: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
+// CHECK:STDOUT:   %.loc21: type = splice_block %type.ref.loc21 [concrete = constants.%i32] {
+// CHECK:STDOUT:     %Cpp.ref.loc21: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
 // CHECK:STDOUT:     %Xint.ref: type = name_ref Xint, imports.%X.decl.dec4cb.2 [concrete = constants.%X.32d59e.2]
 // CHECK:STDOUT:     <elided>
-// CHECK:STDOUT:     %type.ref.loc24: type = name_ref r#type, %i32.1 [concrete = constants.%i32]
+// CHECK:STDOUT:     %type.ref.loc21: type = name_ref r#type, %i32.1 [concrete = constants.%i32]
 // CHECK:STDOUT:   }
 // CHECK:STDOUT:   %y: ref %i32 = bind_name y, %y.var [concrete = %y.var]
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @__global_init() {
 // CHECK:STDOUT: !entry:
-// CHECK:STDOUT:   %int_0.loc18: Core.IntLiteral = int_value 0 [concrete = constants.%int_0.5c6]
-// CHECK:STDOUT:   %impl.elem0.loc18: %.9c3 = impl_witness_access constants.%ImplicitAs.impl_witness.c75, element0 [concrete = constants.%Core.IntLiteral.as.ImplicitAs.impl.Convert.956]
-// CHECK:STDOUT:   %bound_method.loc18_1.1: <bound method> = bound_method %int_0.loc18, %impl.elem0.loc18 [concrete = constants.%Core.IntLiteral.as.ImplicitAs.impl.Convert.bound]
-// CHECK:STDOUT:   %specific_fn.loc18: <specific function> = specific_function %impl.elem0.loc18, @Core.IntLiteral.as.ImplicitAs.impl.Convert(constants.%int_32) [concrete = constants.%Core.IntLiteral.as.ImplicitAs.impl.Convert.specific_fn]
-// CHECK:STDOUT:   %bound_method.loc18_1.2: <bound method> = bound_method %int_0.loc18, %specific_fn.loc18 [concrete = constants.%bound_method]
-// CHECK:STDOUT:   %Core.IntLiteral.as.ImplicitAs.impl.Convert.call.loc18: init %i32 = call %bound_method.loc18_1.2(%int_0.loc18) [concrete = constants.%int_0.6a9]
-// CHECK:STDOUT:   %.loc18: init %i32 = converted %int_0.loc18, %Core.IntLiteral.as.ImplicitAs.impl.Convert.call.loc18 [concrete = constants.%int_0.6a9]
-// CHECK:STDOUT:   assign file.%x.var, %.loc18
-// CHECK:STDOUT:   %int_0.loc24: Core.IntLiteral = int_value 0 [concrete = constants.%int_0.5c6]
-// CHECK:STDOUT:   %impl.elem0.loc24: %.9c3 = impl_witness_access constants.%ImplicitAs.impl_witness.c75, element0 [concrete = constants.%Core.IntLiteral.as.ImplicitAs.impl.Convert.956]
-// CHECK:STDOUT:   %bound_method.loc24_1.1: <bound method> = bound_method %int_0.loc24, %impl.elem0.loc24 [concrete = constants.%Core.IntLiteral.as.ImplicitAs.impl.Convert.bound]
-// CHECK:STDOUT:   %specific_fn.loc24: <specific function> = specific_function %impl.elem0.loc24, @Core.IntLiteral.as.ImplicitAs.impl.Convert(constants.%int_32) [concrete = constants.%Core.IntLiteral.as.ImplicitAs.impl.Convert.specific_fn]
-// CHECK:STDOUT:   %bound_method.loc24_1.2: <bound method> = bound_method %int_0.loc24, %specific_fn.loc24 [concrete = constants.%bound_method]
-// CHECK:STDOUT:   %Core.IntLiteral.as.ImplicitAs.impl.Convert.call.loc24: init %i32 = call %bound_method.loc24_1.2(%int_0.loc24) [concrete = constants.%int_0.6a9]
-// CHECK:STDOUT:   %.loc24: init %i32 = converted %int_0.loc24, %Core.IntLiteral.as.ImplicitAs.impl.Convert.call.loc24 [concrete = constants.%int_0.6a9]
-// CHECK:STDOUT:   assign file.%y.var, %.loc24
+// CHECK:STDOUT:   %int_0.loc15: Core.IntLiteral = int_value 0 [concrete = constants.%int_0.5c6]
+// CHECK:STDOUT:   %impl.elem0.loc15: %.9c3 = impl_witness_access constants.%ImplicitAs.impl_witness.c75, element0 [concrete = constants.%Core.IntLiteral.as.ImplicitAs.impl.Convert.956]
+// CHECK:STDOUT:   %bound_method.loc15_1.1: <bound method> = bound_method %int_0.loc15, %impl.elem0.loc15 [concrete = constants.%Core.IntLiteral.as.ImplicitAs.impl.Convert.bound]
+// CHECK:STDOUT:   %specific_fn.loc15: <specific function> = specific_function %impl.elem0.loc15, @Core.IntLiteral.as.ImplicitAs.impl.Convert(constants.%int_32) [concrete = constants.%Core.IntLiteral.as.ImplicitAs.impl.Convert.specific_fn]
+// CHECK:STDOUT:   %bound_method.loc15_1.2: <bound method> = bound_method %int_0.loc15, %specific_fn.loc15 [concrete = constants.%bound_method]
+// CHECK:STDOUT:   %Core.IntLiteral.as.ImplicitAs.impl.Convert.call.loc15: init %i32 = call %bound_method.loc15_1.2(%int_0.loc15) [concrete = constants.%int_0.6a9]
+// CHECK:STDOUT:   %.loc15: init %i32 = converted %int_0.loc15, %Core.IntLiteral.as.ImplicitAs.impl.Convert.call.loc15 [concrete = constants.%int_0.6a9]
+// CHECK:STDOUT:   assign file.%x.var, %.loc15
+// CHECK:STDOUT:   %int_0.loc21: Core.IntLiteral = int_value 0 [concrete = constants.%int_0.5c6]
+// CHECK:STDOUT:   %impl.elem0.loc21: %.9c3 = impl_witness_access constants.%ImplicitAs.impl_witness.c75, element0 [concrete = constants.%Core.IntLiteral.as.ImplicitAs.impl.Convert.956]
+// CHECK:STDOUT:   %bound_method.loc21_1.1: <bound method> = bound_method %int_0.loc21, %impl.elem0.loc21 [concrete = constants.%Core.IntLiteral.as.ImplicitAs.impl.Convert.bound]
+// CHECK:STDOUT:   %specific_fn.loc21: <specific function> = specific_function %impl.elem0.loc21, @Core.IntLiteral.as.ImplicitAs.impl.Convert(constants.%int_32) [concrete = constants.%Core.IntLiteral.as.ImplicitAs.impl.Convert.specific_fn]
+// CHECK:STDOUT:   %bound_method.loc21_1.2: <bound method> = bound_method %int_0.loc21, %specific_fn.loc21 [concrete = constants.%bound_method]
+// CHECK:STDOUT:   %Core.IntLiteral.as.ImplicitAs.impl.Convert.call.loc21: init %i32 = call %bound_method.loc21_1.2(%int_0.loc21) [concrete = constants.%int_0.6a9]
+// CHECK:STDOUT:   %.loc21: init %i32 = converted %int_0.loc21, %Core.IntLiteral.as.ImplicitAs.impl.Convert.call.loc21 [concrete = constants.%int_0.6a9]
+// CHECK:STDOUT:   assign file.%y.var, %.loc21
 // CHECK:STDOUT:   <elided>
 // CHECK:STDOUT: }
 // CHECK:STDOUT:

+ 5 - 3
toolchain/check/testdata/interop/cpp/class/union.carbon

@@ -35,11 +35,13 @@ library "[[@TEST_NAME]]";
 import Cpp library "declaration.h";
 
 fn MyF() {
-  // CHECK:STDERR: fail_use_declaration_as_definition.carbon:[[@LINE+6]]:12: error: binding pattern has incomplete type `Bar` in name binding declaration [IncompleteTypeInBindingDecl]
+  // CHECK:STDERR: fail_use_declaration_as_definition.carbon:[[@LINE+8]]:12: error: binding pattern has incomplete type `Bar` in name binding declaration [IncompleteTypeInBindingDecl]
   // CHECK:STDERR:   var bar: Cpp.Bar;
   // CHECK:STDERR:            ^~~~~~~
-  // CHECK:STDERR: fail_use_declaration_as_definition.carbon:[[@LINE-6]]:1: in import [InImport]
-  // CHECK:STDERR: ./declaration.h:2: note: class was forward declared here [ClassForwardDeclaredHere]
+  // CHECK:STDERR: fail_use_declaration_as_definition.carbon:[[@LINE-6]]:10: in file included here [InCppInclude]
+  // CHECK:STDERR: ./declaration.h:2:7: note: class was forward declared here [ClassForwardDeclaredHere]
+  // CHECK:STDERR: union Bar;
+  // CHECK:STDERR:       ^
   // CHECK:STDERR:
   var bar: Cpp.Bar;
 }

+ 108 - 135
toolchain/check/testdata/interop/cpp/cpp_diagnostics.carbon

@@ -22,12 +22,10 @@
 
 library "[[@TEST_NAME]]";
 
-// CHECK:STDERR: fail_import_cpp_file_with_one_error.carbon:[[@LINE+7]]:1: in import [InImport]
-// CHECK:STDERR: ./one_error.h:2: error: In file included from fail_import_cpp_file_with_one_error.carbon:[[@LINE+6]]:
-// CHECK:STDERR: ./one_error.h:2:2: error: "error1"
+// CHECK:STDERR: fail_import_cpp_file_with_one_error.carbon:[[@LINE+5]]:10: in file included here [InCppInclude]
+// CHECK:STDERR: ./one_error.h:2:2: error: "error1" [CppInteropParseError]
 // CHECK:STDERR:     2 | #error "error1"
 // CHECK:STDERR:       |  ^
-// CHECK:STDERR:  [CppInteropParseError]
 // CHECK:STDERR:
 import Cpp library "one_error.h";
 
@@ -44,19 +42,15 @@ import Cpp library "one_error.h";
 
 library "[[@TEST_NAME]]";
 
-// CHECK:STDERR: fail_import_cpp_file_with_multiple_errors.carbon:[[@LINE+14]]:1: in import [InImport]
-// CHECK:STDERR: ./multiple_errors.h:2: error: In file included from fail_import_cpp_file_with_multiple_errors.carbon:[[@LINE+13]]:
-// CHECK:STDERR: ./multiple_errors.h:2:2: error: "error1"
+// CHECK:STDERR: fail_import_cpp_file_with_multiple_errors.carbon:[[@LINE+10]]:10: in file included here [InCppInclude]
+// CHECK:STDERR: ./multiple_errors.h:2:2: error: "error1" [CppInteropParseError]
 // CHECK:STDERR:     2 | #error "error1"
 // CHECK:STDERR:       |  ^
-// CHECK:STDERR:  [CppInteropParseError]
 // CHECK:STDERR:
-// CHECK:STDERR: fail_import_cpp_file_with_multiple_errors.carbon:[[@LINE+7]]:1: in import [InImport]
-// CHECK:STDERR: ./multiple_errors.h:3: error: In file included from fail_import_cpp_file_with_multiple_errors.carbon:[[@LINE+6]]:
-// CHECK:STDERR: ./multiple_errors.h:3:2: error: "error2"
+// CHECK:STDERR: fail_import_cpp_file_with_multiple_errors.carbon:[[@LINE+5]]:10: in file included here [InCppInclude]
+// CHECK:STDERR: ./multiple_errors.h:3:2: error: "error2" [CppInteropParseError]
 // CHECK:STDERR:     3 | #error "error2"
 // CHECK:STDERR:       |  ^
-// CHECK:STDERR:  [CppInteropParseError]
 // CHECK:STDERR:
 import Cpp library "multiple_errors.h";
 
@@ -73,12 +67,10 @@ import Cpp library "multiple_errors.h";
 library "[[@TEST_NAME]]";
 
 //@dump-sem-ir-begin
-// CHECK:STDERR: import_cpp_file_with_one_warning.carbon:[[@LINE+7]]:1: in import [InImport]
-// CHECK:STDERR: ./one_warning.h:2: warning: In file included from import_cpp_file_with_one_warning.carbon:[[@LINE+6]]:
-// CHECK:STDERR: ./one_warning.h:2:2: warning: "warning1"
+// CHECK:STDERR: import_cpp_file_with_one_warning.carbon:[[@LINE+5]]:10: in file included here [InCppInclude]
+// CHECK:STDERR: ./one_warning.h:2:2: warning: "warning1" [CppInteropParseWarning]
 // CHECK:STDERR:     2 | #warning "warning1"
 // CHECK:STDERR:       |  ^
-// CHECK:STDERR:  [CppInteropParseWarning]
 // CHECK:STDERR:
 import Cpp library "one_warning.h";
 //@dump-sem-ir-end
@@ -98,26 +90,20 @@ import Cpp library "one_warning.h";
 library "[[@TEST_NAME]]";
 
 //@dump-sem-ir-begin
-// CHECK:STDERR: import_cpp_file_with_multiple_warnings.carbon:[[@LINE+21]]:1: in import [InImport]
-// CHECK:STDERR: ./multiple_warnings.h:2: warning: In file included from import_cpp_file_with_multiple_warnings.carbon:[[@LINE+20]]:
-// CHECK:STDERR: ./multiple_warnings.h:2:2: warning: "warning1"
+// CHECK:STDERR: import_cpp_file_with_multiple_warnings.carbon:[[@LINE+15]]:10: in file included here [InCppInclude]
+// CHECK:STDERR: ./multiple_warnings.h:2:2: warning: "warning1" [CppInteropParseWarning]
 // CHECK:STDERR:     2 | #warning "warning1"
 // CHECK:STDERR:       |  ^
-// CHECK:STDERR:  [CppInteropParseWarning]
 // CHECK:STDERR:
-// CHECK:STDERR: import_cpp_file_with_multiple_warnings.carbon:[[@LINE+14]]:1: in import [InImport]
-// CHECK:STDERR: ./multiple_warnings.h:3: warning: In file included from import_cpp_file_with_multiple_warnings.carbon:[[@LINE+13]]:
-// CHECK:STDERR: ./multiple_warnings.h:3:2: warning: "warning2"
+// CHECK:STDERR: import_cpp_file_with_multiple_warnings.carbon:[[@LINE+10]]:10: in file included here [InCppInclude]
+// CHECK:STDERR: ./multiple_warnings.h:3:2: warning: "warning2" [CppInteropParseWarning]
 // CHECK:STDERR:     3 | #warning "warning2"
 // CHECK:STDERR:       |  ^
-// CHECK:STDERR:  [CppInteropParseWarning]
 // CHECK:STDERR:
-// CHECK:STDERR: import_cpp_file_with_multiple_warnings.carbon:[[@LINE+7]]:1: in import [InImport]
-// CHECK:STDERR: ./multiple_warnings.h:4: warning: In file included from import_cpp_file_with_multiple_warnings.carbon:[[@LINE+6]]:
-// CHECK:STDERR: ./multiple_warnings.h:4:2: warning: "warning3"
+// CHECK:STDERR: import_cpp_file_with_multiple_warnings.carbon:[[@LINE+5]]:10: in file included here [InCppInclude]
+// CHECK:STDERR: ./multiple_warnings.h:4:2: warning: "warning3" [CppInteropParseWarning]
 // CHECK:STDERR:     4 | #warning "warning3"
 // CHECK:STDERR:       |  ^
-// CHECK:STDERR:  [CppInteropParseWarning]
 // CHECK:STDERR:
 import Cpp library "multiple_warnings.h";
 //@dump-sem-ir-end
@@ -135,19 +121,15 @@ import Cpp library "multiple_warnings.h";
 
 library "[[@TEST_NAME]]";
 
-// CHECK:STDERR: fail_import_cpp_file_with_one_error_and_one_warning.carbon:[[@LINE+14]]:1: in import [InImport]
-// CHECK:STDERR: ./one_error_and_one_warning.h:2: error: In file included from fail_import_cpp_file_with_one_error_and_one_warning.carbon:[[@LINE+13]]:
-// CHECK:STDERR: ./one_error_and_one_warning.h:2:2: error: "error1"
+// CHECK:STDERR: fail_import_cpp_file_with_one_error_and_one_warning.carbon:[[@LINE+10]]:10: in file included here [InCppInclude]
+// CHECK:STDERR: ./one_error_and_one_warning.h:2:2: error: "error1" [CppInteropParseError]
 // CHECK:STDERR:     2 | #error "error1"
 // CHECK:STDERR:       |  ^
-// CHECK:STDERR:  [CppInteropParseError]
 // CHECK:STDERR:
-// CHECK:STDERR: fail_import_cpp_file_with_one_error_and_one_warning.carbon:[[@LINE+7]]:1: in import [InImport]
-// CHECK:STDERR: ./one_error_and_one_warning.h:3: warning: In file included from fail_import_cpp_file_with_one_error_and_one_warning.carbon:[[@LINE+6]]:
-// CHECK:STDERR: ./one_error_and_one_warning.h:3:2: warning: "warning1"
+// CHECK:STDERR: fail_import_cpp_file_with_one_error_and_one_warning.carbon:[[@LINE+5]]:10: in file included here [InCppInclude]
+// CHECK:STDERR: ./one_error_and_one_warning.h:3:2: warning: "warning1" [CppInteropParseWarning]
 // CHECK:STDERR:     3 | #warning "warning1"
 // CHECK:STDERR:       |  ^
-// CHECK:STDERR:  [CppInteropParseWarning]
 // CHECK:STDERR:
 import Cpp library "one_error_and_one_warning.h";
 
@@ -167,40 +149,30 @@ import Cpp library "one_error_and_one_warning.h";
 
 library "[[@TEST_NAME]]";
 
-// CHECK:STDERR: fail_import_cpp_file_with_multiple_errors_and_multiple_warnings.carbon:[[@LINE+35]]:1: in import [InImport]
-// CHECK:STDERR: ./multiple_errors_and_multiple_warnings.h:2: error: In file included from fail_import_cpp_file_with_multiple_errors_and_multiple_warnings.carbon:[[@LINE+34]]:
-// CHECK:STDERR: ./multiple_errors_and_multiple_warnings.h:2:2: error: "error1"
+// CHECK:STDERR: fail_import_cpp_file_with_multiple_errors_and_multiple_warnings.carbon:[[@LINE+25]]:10: in file included here [InCppInclude]
+// CHECK:STDERR: ./multiple_errors_and_multiple_warnings.h:2:2: error: "error1" [CppInteropParseError]
 // CHECK:STDERR:     2 | #error "error1"
 // CHECK:STDERR:       |  ^
-// CHECK:STDERR:  [CppInteropParseError]
 // CHECK:STDERR:
-// CHECK:STDERR: fail_import_cpp_file_with_multiple_errors_and_multiple_warnings.carbon:[[@LINE+28]]:1: in import [InImport]
-// CHECK:STDERR: ./multiple_errors_and_multiple_warnings.h:3: error: In file included from fail_import_cpp_file_with_multiple_errors_and_multiple_warnings.carbon:[[@LINE+27]]:
-// CHECK:STDERR: ./multiple_errors_and_multiple_warnings.h:3:2: error: "error2"
+// CHECK:STDERR: fail_import_cpp_file_with_multiple_errors_and_multiple_warnings.carbon:[[@LINE+20]]:10: in file included here [InCppInclude]
+// CHECK:STDERR: ./multiple_errors_and_multiple_warnings.h:3:2: error: "error2" [CppInteropParseError]
 // CHECK:STDERR:     3 | #error "error2"
 // CHECK:STDERR:       |  ^
-// CHECK:STDERR:  [CppInteropParseError]
 // CHECK:STDERR:
-// CHECK:STDERR: fail_import_cpp_file_with_multiple_errors_and_multiple_warnings.carbon:[[@LINE+21]]:1: in import [InImport]
-// CHECK:STDERR: ./multiple_errors_and_multiple_warnings.h:4: warning: In file included from fail_import_cpp_file_with_multiple_errors_and_multiple_warnings.carbon:[[@LINE+20]]:
-// CHECK:STDERR: ./multiple_errors_and_multiple_warnings.h:4:2: warning: "warning1"
+// CHECK:STDERR: fail_import_cpp_file_with_multiple_errors_and_multiple_warnings.carbon:[[@LINE+15]]:10: in file included here [InCppInclude]
+// CHECK:STDERR: ./multiple_errors_and_multiple_warnings.h:4:2: warning: "warning1" [CppInteropParseWarning]
 // CHECK:STDERR:     4 | #warning "warning1"
 // CHECK:STDERR:       |  ^
-// CHECK:STDERR:  [CppInteropParseWarning]
 // CHECK:STDERR:
-// CHECK:STDERR: fail_import_cpp_file_with_multiple_errors_and_multiple_warnings.carbon:[[@LINE+14]]:1: in import [InImport]
-// CHECK:STDERR: ./multiple_errors_and_multiple_warnings.h:5: warning: In file included from fail_import_cpp_file_with_multiple_errors_and_multiple_warnings.carbon:[[@LINE+13]]:
-// CHECK:STDERR: ./multiple_errors_and_multiple_warnings.h:5:2: warning: "warning2"
+// CHECK:STDERR: fail_import_cpp_file_with_multiple_errors_and_multiple_warnings.carbon:[[@LINE+10]]:10: in file included here [InCppInclude]
+// CHECK:STDERR: ./multiple_errors_and_multiple_warnings.h:5:2: warning: "warning2" [CppInteropParseWarning]
 // CHECK:STDERR:     5 | #warning "warning2"
 // CHECK:STDERR:       |  ^
-// CHECK:STDERR:  [CppInteropParseWarning]
 // CHECK:STDERR:
-// CHECK:STDERR: fail_import_cpp_file_with_multiple_errors_and_multiple_warnings.carbon:[[@LINE+7]]:1: in import [InImport]
-// CHECK:STDERR: ./multiple_errors_and_multiple_warnings.h:6: warning: In file included from fail_import_cpp_file_with_multiple_errors_and_multiple_warnings.carbon:[[@LINE+6]]:
-// CHECK:STDERR: ./multiple_errors_and_multiple_warnings.h:6:2: warning: "warning3"
+// CHECK:STDERR: fail_import_cpp_file_with_multiple_errors_and_multiple_warnings.carbon:[[@LINE+5]]:10: in file included here [InCppInclude]
+// CHECK:STDERR: ./multiple_errors_and_multiple_warnings.h:6:2: warning: "warning3" [CppInteropParseWarning]
 // CHECK:STDERR:     6 | #warning "warning3"
 // CHECK:STDERR:       |  ^
-// CHECK:STDERR:  [CppInteropParseWarning]
 // CHECK:STDERR:
 import Cpp library "multiple_errors_and_multiple_warnings.h";
 
@@ -213,35 +185,27 @@ import Cpp library "multiple_errors_and_multiple_warnings.h";
 library "[[@TEST_NAME]]";
 
 //@dump-sem-ir-begin
-// CHECK:STDERR: import_multiple_cpp_files_with_warnings.carbon:[[@LINE+28]]:1: in import [InImport]
-// CHECK:STDERR: ./one_warning.h:2: warning: In file included from import_multiple_cpp_files_with_warnings.carbon:[[@LINE+27]]:
-// CHECK:STDERR: ./one_warning.h:2:2: warning: "warning1"
+// CHECK:STDERR: import_multiple_cpp_files_with_warnings.carbon:[[@LINE+5]]:10: in file included here [InCppInclude]
+// CHECK:STDERR: ./one_warning.h:2:2: warning: "warning1" [CppInteropParseWarning]
 // CHECK:STDERR:     2 | #warning "warning1"
 // CHECK:STDERR:       |  ^
-// CHECK:STDERR:  [CppInteropParseWarning]
 // CHECK:STDERR:
-// CHECK:STDERR: import_multiple_cpp_files_with_warnings.carbon:[[@LINE+21]]:1: in import [InImport]
-// CHECK:STDERR: ./multiple_warnings.h:2: warning: In file included from import_multiple_cpp_files_with_warnings.carbon:[[@LINE+21]]:
-// CHECK:STDERR: ./multiple_warnings.h:2:2: warning: "warning1"
+import Cpp library "one_warning.h";
+// CHECK:STDERR: import_multiple_cpp_files_with_warnings.carbon:[[@LINE+15]]:10: in file included here [InCppInclude]
+// CHECK:STDERR: ./multiple_warnings.h:2:2: warning: "warning1" [CppInteropParseWarning]
 // CHECK:STDERR:     2 | #warning "warning1"
 // CHECK:STDERR:       |  ^
-// CHECK:STDERR:  [CppInteropParseWarning]
 // CHECK:STDERR:
-// CHECK:STDERR: import_multiple_cpp_files_with_warnings.carbon:[[@LINE+14]]:1: in import [InImport]
-// CHECK:STDERR: ./multiple_warnings.h:3: warning: In file included from import_multiple_cpp_files_with_warnings.carbon:[[@LINE+14]]:
-// CHECK:STDERR: ./multiple_warnings.h:3:2: warning: "warning2"
+// CHECK:STDERR: import_multiple_cpp_files_with_warnings.carbon:[[@LINE+10]]:10: in file included here [InCppInclude]
+// CHECK:STDERR: ./multiple_warnings.h:3:2: warning: "warning2" [CppInteropParseWarning]
 // CHECK:STDERR:     3 | #warning "warning2"
 // CHECK:STDERR:       |  ^
-// CHECK:STDERR:  [CppInteropParseWarning]
 // CHECK:STDERR:
-// CHECK:STDERR: import_multiple_cpp_files_with_warnings.carbon:[[@LINE+7]]:1: in import [InImport]
-// CHECK:STDERR: ./multiple_warnings.h:4: warning: In file included from import_multiple_cpp_files_with_warnings.carbon:[[@LINE+7]]:
-// CHECK:STDERR: ./multiple_warnings.h:4:2: warning: "warning3"
+// CHECK:STDERR: import_multiple_cpp_files_with_warnings.carbon:[[@LINE+5]]:10: in file included here [InCppInclude]
+// CHECK:STDERR: ./multiple_warnings.h:4:2: warning: "warning3" [CppInteropParseWarning]
 // CHECK:STDERR:     4 | #warning "warning3"
 // CHECK:STDERR:       |  ^
-// CHECK:STDERR:  [CppInteropParseWarning]
 // CHECK:STDERR:
-import Cpp library "one_warning.h";
 import Cpp library "multiple_warnings.h";
 //@dump-sem-ir-end
 
@@ -253,56 +217,42 @@ import Cpp library "multiple_warnings.h";
 
 library "[[@TEST_NAME]]";
 
-// CHECK:STDERR: fail_import_multiple_cpp_files_with_errors_and_warnings.carbon:[[@LINE+49]]:1: in import [InImport]
-// CHECK:STDERR: ./one_error_and_one_warning.h:2: error: In file included from fail_import_multiple_cpp_files_with_errors_and_warnings.carbon:[[@LINE+48]]:
-// CHECK:STDERR: ./one_error_and_one_warning.h:2:2: error: "error1"
+// CHECK:STDERR: fail_import_multiple_cpp_files_with_errors_and_warnings.carbon:[[@LINE+10]]:10: in file included here [InCppInclude]
+// CHECK:STDERR: ./one_error_and_one_warning.h:2:2: error: "error1" [CppInteropParseError]
 // CHECK:STDERR:     2 | #error "error1"
 // CHECK:STDERR:       |  ^
-// CHECK:STDERR:  [CppInteropParseError]
 // CHECK:STDERR:
-// CHECK:STDERR: fail_import_multiple_cpp_files_with_errors_and_warnings.carbon:[[@LINE+42]]:1: in import [InImport]
-// CHECK:STDERR: ./one_error_and_one_warning.h:3: warning: In file included from fail_import_multiple_cpp_files_with_errors_and_warnings.carbon:[[@LINE+41]]:
-// CHECK:STDERR: ./one_error_and_one_warning.h:3:2: warning: "warning1"
+// CHECK:STDERR: fail_import_multiple_cpp_files_with_errors_and_warnings.carbon:[[@LINE+5]]:10: in file included here [InCppInclude]
+// CHECK:STDERR: ./one_error_and_one_warning.h:3:2: warning: "warning1" [CppInteropParseWarning]
 // CHECK:STDERR:     3 | #warning "warning1"
 // CHECK:STDERR:       |  ^
-// CHECK:STDERR:  [CppInteropParseWarning]
 // CHECK:STDERR:
-// CHECK:STDERR: fail_import_multiple_cpp_files_with_errors_and_warnings.carbon:[[@LINE+35]]:1: in import [InImport]
-// CHECK:STDERR: ./multiple_errors_and_multiple_warnings.h:2: error: In file included from fail_import_multiple_cpp_files_with_errors_and_warnings.carbon:[[@LINE+35]]:
-// CHECK:STDERR: ./multiple_errors_and_multiple_warnings.h:2:2: error: "error1"
+import Cpp library "one_error_and_one_warning.h";
+// CHECK:STDERR: fail_import_multiple_cpp_files_with_errors_and_warnings.carbon:[[@LINE+25]]:10: in file included here [InCppInclude]
+// CHECK:STDERR: ./multiple_errors_and_multiple_warnings.h:2:2: error: "error1" [CppInteropParseError]
 // CHECK:STDERR:     2 | #error "error1"
 // CHECK:STDERR:       |  ^
-// CHECK:STDERR:  [CppInteropParseError]
 // CHECK:STDERR:
-// CHECK:STDERR: fail_import_multiple_cpp_files_with_errors_and_warnings.carbon:[[@LINE+28]]:1: in import [InImport]
-// CHECK:STDERR: ./multiple_errors_and_multiple_warnings.h:3: error: In file included from fail_import_multiple_cpp_files_with_errors_and_warnings.carbon:[[@LINE+28]]:
-// CHECK:STDERR: ./multiple_errors_and_multiple_warnings.h:3:2: error: "error2"
+// CHECK:STDERR: fail_import_multiple_cpp_files_with_errors_and_warnings.carbon:[[@LINE+20]]:10: in file included here [InCppInclude]
+// CHECK:STDERR: ./multiple_errors_and_multiple_warnings.h:3:2: error: "error2" [CppInteropParseError]
 // CHECK:STDERR:     3 | #error "error2"
 // CHECK:STDERR:       |  ^
-// CHECK:STDERR:  [CppInteropParseError]
 // CHECK:STDERR:
-// CHECK:STDERR: fail_import_multiple_cpp_files_with_errors_and_warnings.carbon:[[@LINE+21]]:1: in import [InImport]
-// CHECK:STDERR: ./multiple_errors_and_multiple_warnings.h:4: warning: In file included from fail_import_multiple_cpp_files_with_errors_and_warnings.carbon:[[@LINE+21]]:
-// CHECK:STDERR: ./multiple_errors_and_multiple_warnings.h:4:2: warning: "warning1"
+// CHECK:STDERR: fail_import_multiple_cpp_files_with_errors_and_warnings.carbon:[[@LINE+15]]:10: in file included here [InCppInclude]
+// CHECK:STDERR: ./multiple_errors_and_multiple_warnings.h:4:2: warning: "warning1" [CppInteropParseWarning]
 // CHECK:STDERR:     4 | #warning "warning1"
 // CHECK:STDERR:       |  ^
-// CHECK:STDERR:  [CppInteropParseWarning]
 // CHECK:STDERR:
-// CHECK:STDERR: fail_import_multiple_cpp_files_with_errors_and_warnings.carbon:[[@LINE+14]]:1: in import [InImport]
-// CHECK:STDERR: ./multiple_errors_and_multiple_warnings.h:5: warning: In file included from fail_import_multiple_cpp_files_with_errors_and_warnings.carbon:[[@LINE+14]]:
-// CHECK:STDERR: ./multiple_errors_and_multiple_warnings.h:5:2: warning: "warning2"
+// CHECK:STDERR: fail_import_multiple_cpp_files_with_errors_and_warnings.carbon:[[@LINE+10]]:10: in file included here [InCppInclude]
+// CHECK:STDERR: ./multiple_errors_and_multiple_warnings.h:5:2: warning: "warning2" [CppInteropParseWarning]
 // CHECK:STDERR:     5 | #warning "warning2"
 // CHECK:STDERR:       |  ^
-// CHECK:STDERR:  [CppInteropParseWarning]
 // CHECK:STDERR:
-// CHECK:STDERR: fail_import_multiple_cpp_files_with_errors_and_warnings.carbon:[[@LINE+7]]:1: in import [InImport]
-// CHECK:STDERR: ./multiple_errors_and_multiple_warnings.h:6: warning: In file included from fail_import_multiple_cpp_files_with_errors_and_warnings.carbon:[[@LINE+7]]:
-// CHECK:STDERR: ./multiple_errors_and_multiple_warnings.h:6:2: warning: "warning3"
+// CHECK:STDERR: fail_import_multiple_cpp_files_with_errors_and_warnings.carbon:[[@LINE+5]]:10: in file included here [InCppInclude]
+// CHECK:STDERR: ./multiple_errors_and_multiple_warnings.h:6:2: warning: "warning3" [CppInteropParseWarning]
 // CHECK:STDERR:     6 | #warning "warning3"
 // CHECK:STDERR:       |  ^
-// CHECK:STDERR:  [CppInteropParseWarning]
 // CHECK:STDERR:
-import Cpp library "one_error_and_one_warning.h";
 import Cpp library "multiple_errors_and_multiple_warnings.h";
 
 // ============================================================================
@@ -313,19 +263,15 @@ import Cpp library "multiple_errors_and_multiple_warnings.h";
 
 #include "one_error.h"
 
-// --- fail_todo_import_indirect_error.carbon
+// --- fail_import_indirect_error.carbon
 
 library "[[@TEST_NAME]]";
 
-// TODO: The `In file included from` line should not be prefixed. See
-// https://github.com/carbon-language/carbon-lang/pull/5614#pullrequestreview-2900939411
-// CHECK:STDERR: fail_todo_import_indirect_error.carbon:[[@LINE+8]]:1: in import [InImport]
-// CHECK:STDERR: ./one_error.h:2: error: In file included from fail_todo_import_indirect_error.carbon:[[@LINE+7]]:
-// CHECK:STDERR: In file included from ./indirect_error.h:2:
-// CHECK:STDERR: ./one_error.h:2:2: error: "error1"
+// CHECK:STDERR: fail_import_indirect_error.carbon:[[@LINE+6]]:10: in file included here [InCppInclude]
+// CHECK:STDERR: ./indirect_error.h:2:10: in file included here [InCppInclude]
+// CHECK:STDERR: ./one_error.h:2:2: error: "error1" [CppInteropParseError]
 // CHECK:STDERR:     2 | #error "error1"
 // CHECK:STDERR:       |  ^
-// CHECK:STDERR:  [CppInteropParseError]
 // CHECK:STDERR:
 import Cpp library "indirect_error.h";
 
@@ -337,20 +283,16 @@ import Cpp library "indirect_error.h";
 
 #include "one_warning.h"
 
-// --- todo_import_indirect_warning.carbon
+// --- import_indirect_warning.carbon
 
 library "[[@TEST_NAME]]";
 
-// TODO: The `In file included from` line should not be prefixed. See
-// https://github.com/carbon-language/carbon-lang/pull/5614#pullrequestreview-2900939411
 //@dump-sem-ir-begin
-// CHECK:STDERR: todo_import_indirect_warning.carbon:[[@LINE+8]]:1: in import [InImport]
-// CHECK:STDERR: ./one_warning.h:2: warning: In file included from todo_import_indirect_warning.carbon:[[@LINE+7]]:
-// CHECK:STDERR: In file included from ./indirect_warning.h:2:
-// CHECK:STDERR: ./one_warning.h:2:2: warning: "warning1"
+// CHECK:STDERR: import_indirect_warning.carbon:[[@LINE+6]]:10: in file included here [InCppInclude]
+// CHECK:STDERR: ./indirect_warning.h:2:10: in file included here [InCppInclude]
+// CHECK:STDERR: ./one_warning.h:2:2: warning: "warning1" [CppInteropParseWarning]
 // CHECK:STDERR:     2 | #warning "warning1"
 // CHECK:STDERR:       |  ^
-// CHECK:STDERR:  [CppInteropParseWarning]
 // CHECK:STDERR:
 import Cpp library "indirect_warning.h";
 //@dump-sem-ir-end
@@ -364,14 +306,12 @@ import Cpp library "indirect_warning.h";
 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+11]]:1: in import [InImport]
-// CHECK:STDERR: ./one_warning.h:2: warning: In file included from fail_import_cpp_library_lexer_error.carbon:[[@LINE+10]]:
-// CHECK:STDERR: ./one_warning.h:2:2: warning: "warning1"
+// CHECK:STDERR: fail_import_cpp_library_lexer_error.carbon:[[@LINE+9]]:10: in file included here [InCppInclude]
+// CHECK:STDERR: ./one_warning.h:2:2: warning: "warning1" [CppInteropParseWarning]
 // CHECK:STDERR:     2 | #warning "warning1"
 // CHECK:STDERR:       |  ^
-// CHECK:STDERR:  [CppInteropParseWarning]
 // CHECK:STDERR:
-// CHECK:STDERR: fail_import_cpp_library_lexer_error.carbon:[[@LINE-10]]:44: error: trailing comments are not permitted [TrailingComment]
+// CHECK:STDERR: fail_import_cpp_library_lexer_error.carbon:[[@LINE-8]]:44: error: trailing comments are not permitted [TrailingComment]
 // CHECK:STDERR: library "import_cpp_library_lexer_error";  // Trailing comment
 // CHECK:STDERR:                                            ^
 // CHECK:STDERR:
@@ -389,13 +329,11 @@ double score = 0.1
 
 library "[[@TEST_NAME]]";
 
-// CHECK:STDERR: fail_import_fix_it_hints.carbon:[[@LINE+8]]:1: in import [InImport]
-// CHECK:STDERR: ./fix_it_hints.h:2: error: In file included from fail_import_fix_it_hints.carbon:[[@LINE+7]]:
-// CHECK:STDERR: ./fix_it_hints.h:2:19: error: expected ';' after top level declarator
+// CHECK:STDERR: fail_import_fix_it_hints.carbon:[[@LINE+6]]:10: in file included here [InCppInclude]
+// CHECK:STDERR: ./fix_it_hints.h:2:19: error: expected ';' after top level declarator [CppInteropParseError]
 // CHECK:STDERR:     2 | double score = 0.1
 // CHECK:STDERR:       |                   ^
 // CHECK:STDERR:       |                   ;
-// CHECK:STDERR:  [CppInteropParseError]
 // CHECK:STDERR:
 import Cpp library "fix_it_hints.h";
 
@@ -419,17 +357,14 @@ inline void call_foobar() {
 
 library "[[@TEST_NAME]]";
 
-// CHECK:STDERR: fail_with_notes.carbon:[[@LINE+12]]:1: in import [InImport]
-// CHECK:STDERR: ./with_notes.h:5: error: In file included from fail_with_notes.carbon:[[@LINE+11]]:
-// CHECK:STDERR: ./with_notes.h:5:3: error: no matching function for call to 'foobar'
+// CHECK:STDERR: fail_with_notes.carbon:[[@LINE+9]]:10: in file included here [InCppInclude]
+// CHECK:STDERR: ./with_notes.h:5:3: error: no matching function for call to 'foobar' [CppInteropParseError]
 // CHECK:STDERR:     5 |   foobar(1, 2);
 // CHECK:STDERR:       |   ^~~~~~
-// CHECK:STDERR:  [CppInteropParseError]
-// CHECK:STDERR: fail_with_notes.carbon:[[@LINE+6]]:1: in import [InImport]
-// CHECK:STDERR: ./with_notes.h:2: note: ./with_notes.h:2:6: note: candidate function not viable: requires 1 argument, but 2 were provided
+// CHECK:STDERR: fail_with_notes.carbon:[[@LINE+5]]:10: in file included here [InCppInclude]
+// CHECK:STDERR: ./with_notes.h:2:6: note: candidate function not viable: requires 1 argument, but 2 were provided [CppInteropParseNote]
 // CHECK:STDERR:     2 | void foobar(int);
 // CHECK:STDERR:       |      ^      ~~~
-// CHECK:STDERR:  [CppInteropParseNote]
 // CHECK:STDERR:
 import Cpp library "with_notes.h";
 
@@ -437,6 +372,44 @@ fn F() {
   Cpp.call_foobar();
 }
 
+// ============================================================================
+// Context stacks
+// ============================================================================
+
+// --- indirect_include.h
+
+#define FOO BAR
+#define BAZ void f(error);
+
+FOO
+
+// --- direct_include.h
+
+#define BAR BAZ
+
+#include "indirect_include.h"
+
+// --- fail_use_context_stack.carbon
+
+library "[[@TEST_NAME]]";
+
+// CHECK:STDERR: fail_use_context_stack.carbon:[[@LINE+15]]:10: in file included here [InCppInclude]
+// CHECK:STDERR: ./direct_include.h:4:10: in file included here [InCppInclude]
+// CHECK:STDERR: ./indirect_include.h:2:13: in expansion of macro defined here [InCppMacroExpansion]
+// CHECK:STDERR: ./direct_include.h:2:13: in expansion of macro defined here [InCppMacroExpansion]
+// CHECK:STDERR: ./indirect_include.h:3:20: in expansion of macro defined here [InCppMacroExpansion]
+// CHECK:STDERR: ./indirect_include.h:5:1: error: unknown type name 'error' [CppInteropParseError]
+// CHECK:STDERR:     5 | FOO
+// CHECK:STDERR:       | ^
+// CHECK:STDERR:     2 | #define FOO BAR
+// CHECK:STDERR:       |             ^
+// CHECK:STDERR:     2 | #define BAR BAZ
+// CHECK:STDERR:       |             ^
+// CHECK:STDERR:     3 | #define BAZ void f(error);
+// CHECK:STDERR:       |                    ^
+// CHECK:STDERR:
+import Cpp library "direct_include.h";
+
 // CHECK:STDOUT: --- import_cpp_file_with_one_warning.carbon
 // CHECK:STDOUT:
 // CHECK:STDOUT: imports {
@@ -471,7 +444,7 @@ fn F() {
 // CHECK:STDOUT:   }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: --- todo_import_indirect_warning.carbon
+// CHECK:STDOUT: --- import_indirect_warning.carbon
 // CHECK:STDOUT:
 // CHECK:STDOUT: imports {
 // CHECK:STDOUT: }

+ 2 - 4
toolchain/check/testdata/interop/cpp/file_not_found.carbon

@@ -14,10 +14,8 @@
 
 library "[[@TEST_NAME]]";
 
-// CHECK:STDERR: fail_cpp_file_not_found.carbon:[[@LINE+6]]:1: in import [InImport]
-// CHECK:STDERR: fail_cpp_file_not_found.carbon:[[@LINE+5]]: error: fail_cpp_file_not_found.carbon:[[@LINE+5]]:10: fatal error: 'not_found.h' file not found
-// CHECK:STDERR:    10 | #include "not_found.h"
+// CHECK:STDERR: fail_cpp_file_not_found.carbon:[[@LINE+4]]:10: error: 'not_found.h' file not found [CppInteropParseError]
+// CHECK:STDERR:     8 | #include "not_found.h"
 // CHECK:STDERR:       |          ^~~~~~~~~~~~~
-// CHECK:STDERR:  [CppInteropParseError]
 // CHECK:STDERR:
 import Cpp library "not_found.h";

+ 25 - 15
toolchain/check/testdata/interop/cpp/function/class.carbon

@@ -27,11 +27,13 @@ library "[[@TEST_NAME]]";
 import Cpp library "decl_value_param_type.h";
 
 fn F() {
-  // CHECK:STDERR: fail_todo_import_decl_value_param_type.carbon:[[@LINE+7]]:11: error: forming value of incomplete type `Cpp.C` [IncompleteTypeInValueConversion]
+  // CHECK:STDERR: fail_todo_import_decl_value_param_type.carbon:[[@LINE+9]]:11: error: forming value of incomplete type `Cpp.C` [IncompleteTypeInValueConversion]
   // CHECK:STDERR:   Cpp.foo({});
   // CHECK:STDERR:           ^~
-  // CHECK:STDERR: fail_todo_import_decl_value_param_type.carbon:[[@LINE-6]]:1: in import [InImport]
-  // CHECK:STDERR: ./decl_value_param_type.h:2: note: class was forward declared here [ClassForwardDeclaredHere]
+  // CHECK:STDERR: fail_todo_import_decl_value_param_type.carbon:[[@LINE-6]]:10: in file included here [InCppInclude]
+  // CHECK:STDERR: ./decl_value_param_type.h:2:7: note: class was forward declared here [ClassForwardDeclaredHere]
+  // CHECK:STDERR: class C;
+  // CHECK:STDERR:       ^
   // CHECK:STDERR: fail_todo_import_decl_value_param_type.carbon: note: initializing function parameter [InCallToFunctionParam]
   // CHECK:STDERR:
   Cpp.foo({});
@@ -44,11 +46,13 @@ library "[[@TEST_NAME]]";
 import Cpp library "decl_value_param_type.h";
 
 fn F() {
-  // CHECK:STDERR: fail_todo_import_decl_value_param_type_previously_imported.carbon:[[@LINE+10]]:10: error: binding pattern has incomplete type `C` in name binding declaration [IncompleteTypeInBindingDecl]
+  // CHECK:STDERR: fail_todo_import_decl_value_param_type_previously_imported.carbon:[[@LINE+12]]:10: error: binding pattern has incomplete type `C` in name binding declaration [IncompleteTypeInBindingDecl]
   // CHECK:STDERR:   let c: Cpp.C;
   // CHECK:STDERR:          ^~~~~
-  // CHECK:STDERR: fail_todo_import_decl_value_param_type_previously_imported.carbon:[[@LINE-6]]:1: in import [InImport]
-  // CHECK:STDERR: ./decl_value_param_type.h:2: note: class was forward declared here [ClassForwardDeclaredHere]
+  // CHECK:STDERR: fail_todo_import_decl_value_param_type_previously_imported.carbon:[[@LINE-6]]:10: in file included here [InCppInclude]
+  // CHECK:STDERR: ./decl_value_param_type.h:2:7: note: class was forward declared here [ClassForwardDeclaredHere]
+  // CHECK:STDERR: class C;
+  // CHECK:STDERR:       ^
   // CHECK:STDERR:
   // CHECK:STDERR: fail_todo_import_decl_value_param_type_previously_imported.carbon:[[@LINE+4]]:15: error: expected `=`; `let` declaration must have an initializer [ExpectedInitializerAfterLet]
   // CHECK:STDERR:   let c: Cpp.C;
@@ -76,19 +80,23 @@ library "[[@TEST_NAME]]";
 import Cpp library "double_decl_value_param_type.h";
 
 fn F() {
-  // CHECK:STDERR: fail_todo_import_double_decl_value_param_type.carbon:[[@LINE+7]]:12: error: forming value of incomplete type `Cpp.C` [IncompleteTypeInValueConversion]
+  // CHECK:STDERR: fail_todo_import_double_decl_value_param_type.carbon:[[@LINE+9]]:12: error: forming value of incomplete type `Cpp.C` [IncompleteTypeInValueConversion]
   // CHECK:STDERR:   Cpp.foo1({});
   // CHECK:STDERR:            ^~
-  // CHECK:STDERR: fail_todo_import_double_decl_value_param_type.carbon:[[@LINE-6]]:1: in import [InImport]
-  // CHECK:STDERR: ./double_decl_value_param_type.h:2: note: class was forward declared here [ClassForwardDeclaredHere]
+  // CHECK:STDERR: fail_todo_import_double_decl_value_param_type.carbon:[[@LINE-6]]:10: in file included here [InCppInclude]
+  // CHECK:STDERR: ./double_decl_value_param_type.h:2:7: note: class was forward declared here [ClassForwardDeclaredHere]
+  // CHECK:STDERR: class C;
+  // CHECK:STDERR:       ^
   // CHECK:STDERR: fail_todo_import_double_decl_value_param_type.carbon: note: initializing function parameter [InCallToFunctionParam]
   // CHECK:STDERR:
   Cpp.foo1({});
-  // CHECK:STDERR: fail_todo_import_double_decl_value_param_type.carbon:[[@LINE+7]]:12: error: forming value of incomplete type `Cpp.C` [IncompleteTypeInValueConversion]
+  // CHECK:STDERR: fail_todo_import_double_decl_value_param_type.carbon:[[@LINE+9]]:12: error: forming value of incomplete type `Cpp.C` [IncompleteTypeInValueConversion]
   // CHECK:STDERR:   Cpp.foo2({});
   // CHECK:STDERR:            ^~
-  // CHECK:STDERR: fail_todo_import_double_decl_value_param_type.carbon:[[@LINE-14]]:1: in import [InImport]
-  // CHECK:STDERR: ./double_decl_value_param_type.h:2: note: class was forward declared here [ClassForwardDeclaredHere]
+  // CHECK:STDERR: fail_todo_import_double_decl_value_param_type.carbon:[[@LINE-16]]:10: in file included here [InCppInclude]
+  // CHECK:STDERR: ./double_decl_value_param_type.h:2:7: note: class was forward declared here [ClassForwardDeclaredHere]
+  // CHECK:STDERR: class C;
+  // CHECK:STDERR:       ^
   // CHECK:STDERR: fail_todo_import_double_decl_value_param_type.carbon: note: initializing function parameter [InCallToFunctionParam]
   // CHECK:STDERR:
   Cpp.foo2({});
@@ -359,11 +367,13 @@ library "[[@TEST_NAME]]";
 import Cpp library "decl_value_return_type.h";
 
 fn F() {
-  // CHECK:STDERR: fail_todo_import_decl_value_return_type.carbon:[[@LINE+7]]:3: error: function returns incomplete type `Cpp.C` [IncompleteTypeInFunctionReturnType]
+  // CHECK:STDERR: fail_todo_import_decl_value_return_type.carbon:[[@LINE+9]]:3: error: function returns incomplete type `Cpp.C` [IncompleteTypeInFunctionReturnType]
   // CHECK:STDERR:   Cpp.foo();
   // CHECK:STDERR:   ^~~~~~~~~
-  // CHECK:STDERR: fail_todo_import_decl_value_return_type.carbon:[[@LINE-6]]:1: in import [InImport]
-  // CHECK:STDERR: ./decl_value_return_type.h:2: note: class was forward declared here [ClassForwardDeclaredHere]
+  // CHECK:STDERR: fail_todo_import_decl_value_return_type.carbon:[[@LINE-6]]:10: in file included here [InCppInclude]
+  // CHECK:STDERR: ./decl_value_return_type.h:2:7: note: class was forward declared here [ClassForwardDeclaredHere]
+  // CHECK:STDERR: class C;
+  // CHECK:STDERR:       ^
   // CHECK:STDERR: fail_todo_import_decl_value_return_type.carbon: note: return type declared here [IncompleteReturnTypeHere]
   // CHECK:STDERR:
   Cpp.foo();

+ 25 - 15
toolchain/check/testdata/interop/cpp/function/struct.carbon

@@ -27,11 +27,13 @@ library "[[@TEST_NAME]]";
 import Cpp library "decl_value_param_type.h";
 
 fn F() {
-  // CHECK:STDERR: fail_todo_import_decl_value_param_type.carbon:[[@LINE+7]]:11: error: forming value of incomplete type `Cpp.S` [IncompleteTypeInValueConversion]
+  // CHECK:STDERR: fail_todo_import_decl_value_param_type.carbon:[[@LINE+9]]:11: error: forming value of incomplete type `Cpp.S` [IncompleteTypeInValueConversion]
   // CHECK:STDERR:   Cpp.foo({});
   // CHECK:STDERR:           ^~
-  // CHECK:STDERR: fail_todo_import_decl_value_param_type.carbon:[[@LINE-6]]:1: in import [InImport]
-  // CHECK:STDERR: ./decl_value_param_type.h:2: note: class was forward declared here [ClassForwardDeclaredHere]
+  // CHECK:STDERR: fail_todo_import_decl_value_param_type.carbon:[[@LINE-6]]:10: in file included here [InCppInclude]
+  // CHECK:STDERR: ./decl_value_param_type.h:2:8: note: class was forward declared here [ClassForwardDeclaredHere]
+  // CHECK:STDERR: struct S;
+  // CHECK:STDERR:        ^
   // CHECK:STDERR: fail_todo_import_decl_value_param_type.carbon: note: initializing function parameter [InCallToFunctionParam]
   // CHECK:STDERR:
   Cpp.foo({});
@@ -44,11 +46,13 @@ library "[[@TEST_NAME]]";
 import Cpp library "decl_value_param_type.h";
 
 fn F() {
-  // CHECK:STDERR: fail_todo_import_decl_value_param_type_previously_imported.carbon:[[@LINE+10]]:10: error: binding pattern has incomplete type `S` in name binding declaration [IncompleteTypeInBindingDecl]
+  // CHECK:STDERR: fail_todo_import_decl_value_param_type_previously_imported.carbon:[[@LINE+12]]:10: error: binding pattern has incomplete type `S` in name binding declaration [IncompleteTypeInBindingDecl]
   // CHECK:STDERR:   let s: Cpp.S;
   // CHECK:STDERR:          ^~~~~
-  // CHECK:STDERR: fail_todo_import_decl_value_param_type_previously_imported.carbon:[[@LINE-6]]:1: in import [InImport]
-  // CHECK:STDERR: ./decl_value_param_type.h:2: note: class was forward declared here [ClassForwardDeclaredHere]
+  // CHECK:STDERR: fail_todo_import_decl_value_param_type_previously_imported.carbon:[[@LINE-6]]:10: in file included here [InCppInclude]
+  // CHECK:STDERR: ./decl_value_param_type.h:2:8: note: class was forward declared here [ClassForwardDeclaredHere]
+  // CHECK:STDERR: struct S;
+  // CHECK:STDERR:        ^
   // CHECK:STDERR:
   // CHECK:STDERR: fail_todo_import_decl_value_param_type_previously_imported.carbon:[[@LINE+4]]:15: error: expected `=`; `let` declaration must have an initializer [ExpectedInitializerAfterLet]
   // CHECK:STDERR:   let s: Cpp.S;
@@ -76,19 +80,23 @@ library "[[@TEST_NAME]]";
 import Cpp library "double_decl_value_param_type.h";
 
 fn F() {
-  // CHECK:STDERR: fail_todo_import_double_decl_value_param_type.carbon:[[@LINE+7]]:12: error: forming value of incomplete type `Cpp.S` [IncompleteTypeInValueConversion]
+  // CHECK:STDERR: fail_todo_import_double_decl_value_param_type.carbon:[[@LINE+9]]:12: error: forming value of incomplete type `Cpp.S` [IncompleteTypeInValueConversion]
   // CHECK:STDERR:   Cpp.foo1({});
   // CHECK:STDERR:            ^~
-  // CHECK:STDERR: fail_todo_import_double_decl_value_param_type.carbon:[[@LINE-6]]:1: in import [InImport]
-  // CHECK:STDERR: ./double_decl_value_param_type.h:2: note: class was forward declared here [ClassForwardDeclaredHere]
+  // CHECK:STDERR: fail_todo_import_double_decl_value_param_type.carbon:[[@LINE-6]]:10: in file included here [InCppInclude]
+  // CHECK:STDERR: ./double_decl_value_param_type.h:2:8: note: class was forward declared here [ClassForwardDeclaredHere]
+  // CHECK:STDERR: struct S;
+  // CHECK:STDERR:        ^
   // CHECK:STDERR: fail_todo_import_double_decl_value_param_type.carbon: note: initializing function parameter [InCallToFunctionParam]
   // CHECK:STDERR:
   Cpp.foo1({});
-  // CHECK:STDERR: fail_todo_import_double_decl_value_param_type.carbon:[[@LINE+7]]:12: error: forming value of incomplete type `Cpp.S` [IncompleteTypeInValueConversion]
+  // CHECK:STDERR: fail_todo_import_double_decl_value_param_type.carbon:[[@LINE+9]]:12: error: forming value of incomplete type `Cpp.S` [IncompleteTypeInValueConversion]
   // CHECK:STDERR:   Cpp.foo2({});
   // CHECK:STDERR:            ^~
-  // CHECK:STDERR: fail_todo_import_double_decl_value_param_type.carbon:[[@LINE-14]]:1: in import [InImport]
-  // CHECK:STDERR: ./double_decl_value_param_type.h:2: note: class was forward declared here [ClassForwardDeclaredHere]
+  // CHECK:STDERR: fail_todo_import_double_decl_value_param_type.carbon:[[@LINE-16]]:10: in file included here [InCppInclude]
+  // CHECK:STDERR: ./double_decl_value_param_type.h:2:8: note: class was forward declared here [ClassForwardDeclaredHere]
+  // CHECK:STDERR: struct S;
+  // CHECK:STDERR:        ^
   // CHECK:STDERR: fail_todo_import_double_decl_value_param_type.carbon: note: initializing function parameter [InCallToFunctionParam]
   // CHECK:STDERR:
   Cpp.foo2({});
@@ -351,11 +359,13 @@ library "[[@TEST_NAME]]";
 import Cpp library "decl_value_return_type.h";
 
 fn F() {
-  // CHECK:STDERR: fail_todo_import_decl_value_return_type.carbon:[[@LINE+7]]:3: error: function returns incomplete type `Cpp.S` [IncompleteTypeInFunctionReturnType]
+  // CHECK:STDERR: fail_todo_import_decl_value_return_type.carbon:[[@LINE+9]]:3: error: function returns incomplete type `Cpp.S` [IncompleteTypeInFunctionReturnType]
   // CHECK:STDERR:   Cpp.foo();
   // CHECK:STDERR:   ^~~~~~~~~
-  // CHECK:STDERR: fail_todo_import_decl_value_return_type.carbon:[[@LINE-6]]:1: in import [InImport]
-  // CHECK:STDERR: ./decl_value_return_type.h:2: note: class was forward declared here [ClassForwardDeclaredHere]
+  // CHECK:STDERR: fail_todo_import_decl_value_return_type.carbon:[[@LINE-6]]:10: in file included here [InCppInclude]
+  // CHECK:STDERR: ./decl_value_return_type.h:2:8: note: class was forward declared here [ClassForwardDeclaredHere]
+  // CHECK:STDERR: struct S;
+  // CHECK:STDERR:        ^
   // CHECK:STDERR: fail_todo_import_decl_value_return_type.carbon: note: return type declared here [IncompleteReturnTypeHere]
   // CHECK:STDERR:
   Cpp.foo();

+ 26 - 16
toolchain/check/testdata/interop/cpp/function/union.carbon

@@ -28,11 +28,13 @@ import Cpp library "decl_value_param_type.h";
 
 fn F() {
   //@dump-sem-ir-begin
-  // CHECK:STDERR: fail_todo_import_decl_value_param_type.carbon:[[@LINE+7]]:11: error: forming value of incomplete type `Cpp.U` [IncompleteTypeInValueConversion]
+  // CHECK:STDERR: fail_todo_import_decl_value_param_type.carbon:[[@LINE+9]]:11: error: forming value of incomplete type `Cpp.U` [IncompleteTypeInValueConversion]
   // CHECK:STDERR:   Cpp.foo({});
   // CHECK:STDERR:           ^~
-  // CHECK:STDERR: fail_todo_import_decl_value_param_type.carbon:[[@LINE-7]]:1: in import [InImport]
-  // CHECK:STDERR: ./decl_value_param_type.h:2: note: class was forward declared here [ClassForwardDeclaredHere]
+  // CHECK:STDERR: fail_todo_import_decl_value_param_type.carbon:[[@LINE-7]]:10: in file included here [InCppInclude]
+  // CHECK:STDERR: ./decl_value_param_type.h:2:7: note: class was forward declared here [ClassForwardDeclaredHere]
+  // CHECK:STDERR: union U;
+  // CHECK:STDERR:       ^
   // CHECK:STDERR: fail_todo_import_decl_value_param_type.carbon: note: initializing function parameter [InCallToFunctionParam]
   // CHECK:STDERR:
   Cpp.foo({});
@@ -46,11 +48,13 @@ library "[[@TEST_NAME]]";
 import Cpp library "decl_value_param_type.h";
 
 fn F() {
-  // CHECK:STDERR: fail_todo_import_decl_value_param_type_previously_imported.carbon:[[@LINE+10]]:10: error: binding pattern has incomplete type `U` in name binding declaration [IncompleteTypeInBindingDecl]
+  // CHECK:STDERR: fail_todo_import_decl_value_param_type_previously_imported.carbon:[[@LINE+12]]:10: error: binding pattern has incomplete type `U` in name binding declaration [IncompleteTypeInBindingDecl]
   // CHECK:STDERR:   let u: Cpp.U;
   // CHECK:STDERR:          ^~~~~
-  // CHECK:STDERR: fail_todo_import_decl_value_param_type_previously_imported.carbon:[[@LINE-6]]:1: in import [InImport]
-  // CHECK:STDERR: ./decl_value_param_type.h:2: note: class was forward declared here [ClassForwardDeclaredHere]
+  // CHECK:STDERR: fail_todo_import_decl_value_param_type_previously_imported.carbon:[[@LINE-6]]:10: in file included here [InCppInclude]
+  // CHECK:STDERR: ./decl_value_param_type.h:2:7: note: class was forward declared here [ClassForwardDeclaredHere]
+  // CHECK:STDERR: union U;
+  // CHECK:STDERR:       ^
   // CHECK:STDERR:
   // CHECK:STDERR: fail_todo_import_decl_value_param_type_previously_imported.carbon:[[@LINE+4]]:15: error: expected `=`; `let` declaration must have an initializer [ExpectedInitializerAfterLet]
   // CHECK:STDERR:   let u: Cpp.U;
@@ -78,19 +82,23 @@ library "[[@TEST_NAME]]";
 import Cpp library "double_decl_value_param_type.h";
 
 fn F() {
-  // CHECK:STDERR: fail_todo_import_double_decl_value_param_type.carbon:[[@LINE+7]]:12: error: forming value of incomplete type `Cpp.U` [IncompleteTypeInValueConversion]
+  // CHECK:STDERR: fail_todo_import_double_decl_value_param_type.carbon:[[@LINE+9]]:12: error: forming value of incomplete type `Cpp.U` [IncompleteTypeInValueConversion]
   // CHECK:STDERR:   Cpp.foo1({});
   // CHECK:STDERR:            ^~
-  // CHECK:STDERR: fail_todo_import_double_decl_value_param_type.carbon:[[@LINE-6]]:1: in import [InImport]
-  // CHECK:STDERR: ./double_decl_value_param_type.h:2: note: class was forward declared here [ClassForwardDeclaredHere]
+  // CHECK:STDERR: fail_todo_import_double_decl_value_param_type.carbon:[[@LINE-6]]:10: in file included here [InCppInclude]
+  // CHECK:STDERR: ./double_decl_value_param_type.h:2:7: note: class was forward declared here [ClassForwardDeclaredHere]
+  // CHECK:STDERR: union U;
+  // CHECK:STDERR:       ^
   // CHECK:STDERR: fail_todo_import_double_decl_value_param_type.carbon: note: initializing function parameter [InCallToFunctionParam]
   // CHECK:STDERR:
   Cpp.foo1({});
-  // CHECK:STDERR: fail_todo_import_double_decl_value_param_type.carbon:[[@LINE+7]]:12: error: forming value of incomplete type `Cpp.U` [IncompleteTypeInValueConversion]
+  // CHECK:STDERR: fail_todo_import_double_decl_value_param_type.carbon:[[@LINE+9]]:12: error: forming value of incomplete type `Cpp.U` [IncompleteTypeInValueConversion]
   // CHECK:STDERR:   Cpp.foo2({});
   // CHECK:STDERR:            ^~
-  // CHECK:STDERR: fail_todo_import_double_decl_value_param_type.carbon:[[@LINE-14]]:1: in import [InImport]
-  // CHECK:STDERR: ./double_decl_value_param_type.h:2: note: class was forward declared here [ClassForwardDeclaredHere]
+  // CHECK:STDERR: fail_todo_import_double_decl_value_param_type.carbon:[[@LINE-16]]:10: in file included here [InCppInclude]
+  // CHECK:STDERR: ./double_decl_value_param_type.h:2:7: note: class was forward declared here [ClassForwardDeclaredHere]
+  // CHECK:STDERR: union U;
+  // CHECK:STDERR:       ^
   // CHECK:STDERR: fail_todo_import_double_decl_value_param_type.carbon: note: initializing function parameter [InCallToFunctionParam]
   // CHECK:STDERR:
   Cpp.foo2({});
@@ -354,11 +362,13 @@ library "[[@TEST_NAME]]";
 import Cpp library "decl_value_return_type.h";
 
 fn F() {
-  // CHECK:STDERR: fail_todo_import_decl_value_return_type.carbon:[[@LINE+7]]:3: error: function returns incomplete type `Cpp.U` [IncompleteTypeInFunctionReturnType]
+  // CHECK:STDERR: fail_todo_import_decl_value_return_type.carbon:[[@LINE+9]]:3: error: function returns incomplete type `Cpp.U` [IncompleteTypeInFunctionReturnType]
   // CHECK:STDERR:   Cpp.foo();
   // CHECK:STDERR:   ^~~~~~~~~
-  // CHECK:STDERR: fail_todo_import_decl_value_return_type.carbon:[[@LINE-6]]:1: in import [InImport]
-  // CHECK:STDERR: ./decl_value_return_type.h:2: note: class was forward declared here [ClassForwardDeclaredHere]
+  // CHECK:STDERR: fail_todo_import_decl_value_return_type.carbon:[[@LINE-6]]:10: in file included here [InCppInclude]
+  // CHECK:STDERR: ./decl_value_return_type.h:2:7: note: class was forward declared here [ClassForwardDeclaredHere]
+  // CHECK:STDERR: union U;
+  // CHECK:STDERR:       ^
   // CHECK:STDERR: fail_todo_import_decl_value_return_type.carbon: note: return type declared here [IncompleteReturnTypeHere]
   // CHECK:STDERR:
   Cpp.foo();
@@ -455,7 +465,7 @@ fn F() {
 // CHECK:STDOUT: !entry:
 // CHECK:STDOUT:   %Cpp.ref: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
 // CHECK:STDOUT:   %foo.ref: %foo.type = name_ref foo, imports.%foo.decl [concrete = constants.%foo]
-// CHECK:STDOUT:   %.loc15: %empty_struct_type = struct_literal ()
+// CHECK:STDOUT:   %.loc17: %empty_struct_type = struct_literal ()
 // CHECK:STDOUT:   %foo.call: init %empty_tuple.type = call %foo.ref(<error>)
 // CHECK:STDOUT:   <elided>
 // CHECK:STDOUT: }

+ 20 - 13
toolchain/check/testdata/interop/cpp/namespace.carbon

@@ -121,22 +121,29 @@ inline namespace { namespace N {} }
 
 library "[[@TEST_NAME]]";
 
-// CHECK:STDERR: fail_import_inline_ambiguity.carbon:[[@LINE+13]]:1: in import [InImport]
-// CHECK:STDERR: error: error: reference to 'N' is ambiguous
-// CHECK:STDERR:  [CppInteropParseError]
-// CHECK:STDERR: fail_import_inline_ambiguity.carbon:[[@LINE+10]]:1: in import [InImport]
-// CHECK:STDERR: ./inline_ambiguity.h:2: note: ./inline_ambiguity.h:2:11: note: candidate found by name lookup is 'N'
-// CHECK:STDERR:     2 | namespace N { void foo(); }
-// CHECK:STDERR:       |           ^
-// CHECK:STDERR:  [CppInteropParseNote]
-// CHECK:STDERR: fail_import_inline_ambiguity.carbon:[[@LINE+5]]:1: in import [InImport]
-// CHECK:STDERR: ./inline_ambiguity.h:3: note: ./inline_ambiguity.h:3:30: note: candidate found by name lookup is '(anonymous namespace)::N'
-// CHECK:STDERR:     3 | inline namespace { namespace N {} }
-// CHECK:STDERR:       |                              ^
-// CHECK:STDERR:  [CppInteropParseNote]
 import Cpp library "inline_ambiguity.h";
 
 fn MyF() {
+  // Artificially create an error message here so that the first error line for
+  // this file (which otherwise has no source location) is associated with this
+  // file split.
+  // TODO: Pass a location into Clang when performing C++ name lookup so that
+  // its error has a location attached.
+  // CHECK:STDERR: fail_import_inline_ambiguity.carbon:[[@LINE+13]]:3: error: name `error` not found [NameNotFound]
+  // CHECK:STDERR:   error;
+  // CHECK:STDERR:   ^~~~~
+  // CHECK:STDERR:
+  // CHECK:STDERR: error: reference to 'N' is ambiguous [CppInteropParseError]
+  // CHECK:STDERR: fail_import_inline_ambiguity.carbon:[[@LINE-13]]:10: in file included here [InCppInclude]
+  // CHECK:STDERR: ./inline_ambiguity.h:2:11: note: candidate found by name lookup is 'N' [CppInteropParseNote]
+  // CHECK:STDERR:     2 | namespace N { void foo(); }
+  // CHECK:STDERR:       |           ^
+  // CHECK:STDERR: fail_import_inline_ambiguity.carbon:[[@LINE-17]]:10: in file included here [InCppInclude]
+  // CHECK:STDERR: ./inline_ambiguity.h:3:30: note: candidate found by name lookup is '(anonymous namespace)::N' [CppInteropParseNote]
+  // CHECK:STDERR:     3 | inline namespace { namespace N {} }
+  // CHECK:STDERR:       |                              ^
+  error;
+
   // CHECK:STDERR: fail_import_inline_ambiguity.carbon:[[@LINE+4]]:3: note: in `Cpp` name lookup for `N` [InCppNameLookup]
   // CHECK:STDERR:   Cpp.N.foo();
   // CHECK:STDERR:   ^~~~~

+ 5 - 0
toolchain/diagnostics/coverage_test.cpp

@@ -46,6 +46,11 @@ constexpr Kind UntestedKinds[] = {
     // Producing an emit failure may be infeasible.
     Kind::CodeGenUnableToEmit,
 
+    // TODO: This is currently hard to test because it requires building and
+    // importing a module, which attempts to create additional files with
+    // unpredictable names in the module cache, which bazel doesn't permit.
+    Kind::InCppModule,
+
     // TODO: This can only fire if the first message in a diagnostic is rooted
     // in a file other than the file being compiled. The language server
     // currently only supports compiling one file at a time. Do one of:

+ 12 - 0
toolchain/diagnostics/diagnostic.cpp

@@ -24,6 +24,17 @@ auto Loc::FormatLocation(llvm::raw_ostream& out) const -> void {
 }
 
 auto Loc::FormatSnippet(llvm::raw_ostream& out, int indent) const -> void {
+  if (!snippet.empty()) {
+    llvm::StringRef snippet_ref = snippet;
+    do {
+      auto [snippet_line, rest] = snippet_ref.split('\n');
+      out.indent(indent);
+      out << snippet_line << "\n";
+      snippet_ref = rest;
+    } while (!snippet_ref.empty());
+    return;
+  }
+
   if (column_number == -1) {
     return;
   }
@@ -33,6 +44,7 @@ auto Loc::FormatSnippet(llvm::raw_ostream& out, int indent) const -> void {
 
   out.indent(indent);
   out << line << "\n";
+
   out.indent(indent + column);
   out << "^";
   // We want to ensure that we don't underline past the end of the line in

+ 4 - 0
toolchain/diagnostics/diagnostic.h

@@ -65,6 +65,10 @@ struct Loc {
   // A reference to the line of the error.
   llvm::StringRef line;
 
+  // A full snippet to print. If non-empty, this is used instead of `line` when
+  // printing a snippet. Should contain both the quoted text and the caret line.
+  std::string snippet;
+
   // 1-based line number. -1 indicates unknown; other values are unused.
   int32_t line_number = -1;
 

+ 16 - 0
toolchain/diagnostics/diagnostic_emitter.h

@@ -75,6 +75,12 @@ class Emitter {
     Builder(Builder&&) noexcept = default;
     auto operator=(Builder&&) noexcept -> Builder& = default;
 
+    // Overrides the snippet for the most recently added diagnostic or note with
+    // the given text. The provided override should include the caret text as
+    // well as the source snippet. An empty snippet restores the default
+    // behavior of printing the original source line.
+    auto OverrideSnippet(llvm::StringRef snippet) -> Builder&;
+
     // Adds a note diagnostic attached to the main diagnostic being built.
     // The API mirrors the main emission API: `Emitter::Emit`.
     // For the expected usage see the builder API: `Emitter::Build`.
@@ -302,6 +308,16 @@ struct DiagnosticTypeForArg<Arg> : public Arg::DiagnosticType {};
 
 }  // namespace Internal
 
+template <typename LocT>
+auto Emitter<LocT>::Builder::OverrideSnippet(llvm::StringRef snippet)
+    -> Builder& {
+  if (!emitter_) {
+    return *this;
+  }
+  diagnostic_.messages.back().loc.snippet = snippet;
+  return *this;
+}
+
 template <typename LocT>
 template <typename... Args>
 auto Emitter<LocT>::Builder::Note(

+ 3 - 0
toolchain/diagnostics/diagnostic_kind.def

@@ -162,6 +162,9 @@ CARBON_DIAGNOSTIC_KIND(SemanticsTodo)
 
 // Location context.
 CARBON_DIAGNOSTIC_KIND(InImport)
+CARBON_DIAGNOSTIC_KIND(InCppInclude)
+CARBON_DIAGNOSTIC_KIND(InCppModule)
+CARBON_DIAGNOSTIC_KIND(InCppMacroExpansion)
 CARBON_DIAGNOSTIC_KIND(ResolvingSpecificHere)
 CARBON_DIAGNOSTIC_KIND(InCppTypeCompletion)
 

+ 4 - 4
toolchain/language_server/testdata/text_document/open_with_cpp_nonexistent.carbon

@@ -33,7 +33,7 @@
 // CHECK:STDOUT:       "textDocumentSync": 2
 // CHECK:STDOUT:     }
 // CHECK:STDOUT:   }
-// CHECK:STDOUT: }Content-Length: 443{{\r}}
+// CHECK:STDOUT: }Content-Length: 464{{\r}}
 // CHECK:STDOUT: {{\r}}
 // CHECK:STDOUT: {
 // CHECK:STDOUT:   "jsonrpc": "2.0",
@@ -41,14 +41,14 @@
 // CHECK:STDOUT:   "params": {
 // CHECK:STDOUT:     "diagnostics": [
 // CHECK:STDOUT:       {
-// CHECK:STDOUT:         "message": "in import",
+// CHECK:STDOUT:         "message": "'nonexistent.h' file not found",
 // CHECK:STDOUT:         "range": {
 // CHECK:STDOUT:           "end": {
-// CHECK:STDOUT:             "character": 36,
+// CHECK:STDOUT:             "character": 11,
 // CHECK:STDOUT:             "line": 1
 // CHECK:STDOUT:           },
 // CHECK:STDOUT:           "start": {
-// CHECK:STDOUT:             "character": 0,
+// CHECK:STDOUT:             "character": 9,
 // CHECK:STDOUT:             "line": 0
 // CHECK:STDOUT:           }
 // CHECK:STDOUT:         },

+ 1 - 0
toolchain/sem_ir/BUILD

@@ -245,6 +245,7 @@ cc_library(
         ":typed_insts",
         "//toolchain/diagnostics:diagnostic_emitter",
         "//toolchain/parse:tree",
+        "@llvm-project//clang:frontend",
         "@llvm-project//llvm:Support",
     ],
 )

+ 0 - 6
toolchain/sem_ir/absolute_node_id.cpp

@@ -17,12 +17,6 @@ static auto FollowImportRef(
   auto import_ir_inst = cursor_ir->import_ir_insts().Get(import_ir_inst_id);
   if (import_ir_inst.ir_id() == ImportIRId::Cpp) {
     CARBON_CHECK(cursor_ir->import_cpps().size() > 0);
-    // TODO: Decompose the Clang source location to determine which C++ import
-    // made this location available, and use the location of that import instead
-    // of arbitrarily using the first C++ import.
-    absolute_node_ids.push_back(
-        AbsoluteNodeId(cursor_ir->check_ir_id(),
-                       cursor_ir->import_cpps().values().begin()->node_id));
     absolute_node_ids.push_back(
         AbsoluteNodeId(import_ir_inst.clang_source_loc_id()));
     return true;

+ 117 - 6
toolchain/sem_ir/diagnostic_loc_converter.cpp

@@ -4,8 +4,106 @@
 
 #include "toolchain/sem_ir/diagnostic_loc_converter.h"
 
+#include "clang/Frontend/DiagnosticRenderer.h"
+
 namespace Carbon::SemIR {
 
+static auto ConvertPresumedLocToDiagnosticsLoc(clang::FullSourceLoc loc,
+                                               clang::PresumedLoc presumed_loc)
+    -> Diagnostics::Loc {
+  llvm::StringRef line;
+
+  // Ask the Clang SourceManager for the contents of the line containing this
+  // location.
+  // TODO: If this location is in our generated header, use the source text from
+  // the presumed location (the Carbon source file) as the snippet instead.
+  bool loc_invalid = false;
+  const auto& src_mgr = loc.getManager();
+  auto [file_id, offset] = src_mgr.getDecomposedSpellingLoc(loc);
+  auto loc_line = src_mgr.getLineNumber(file_id, offset, &loc_invalid);
+  if (!loc_invalid) {
+    auto start_of_line = src_mgr.translateLineCol(file_id, loc_line, 1);
+    line = src_mgr.getCharacterData(start_of_line, &loc_invalid);
+    line = line.take_until([](char c) { return c == '\n'; });
+  }
+
+  return {.filename = presumed_loc.getFilename(),
+          .line = loc_invalid ? "" : line,
+          .line_number = static_cast<int32_t>(presumed_loc.getLine()),
+          .column_number = static_cast<int32_t>(presumed_loc.getColumn()),
+          .length = loc_invalid ? -1 : 1};
+}
+
+namespace {
+// A diagnostics "renderer" that renders the diagnostic into an array of
+// importing contexts based on the C++ include stack.
+class ClangImportCollector : public clang::DiagnosticRenderer {
+ public:
+  explicit ClangImportCollector(
+      const clang::LangOptions& lang_opts, clang::DiagnosticOptions& diag_opts,
+      llvm::SmallVectorImpl<DiagnosticLocConverter::ImportLoc>* imports)
+      : DiagnosticRenderer(lang_opts, diag_opts), imports_(imports) {}
+
+  void emitDiagnosticMessage(clang::FullSourceLoc loc, clang::PresumedLoc ploc,
+                             clang::DiagnosticsEngine::Level /*level*/,
+                             llvm::StringRef message,
+                             llvm::ArrayRef<clang::CharSourceRange> /*ranges*/,
+                             clang::DiagOrStoredDiag /*info*/) override {
+    if (!emitted_message_) {
+      emitted_message_ = true;
+      return;
+    }
+    // This is an "in macro expanded here" diagnostic that Clang emits after the
+    // emitted diagnostic. We treat that as another form of context location.
+    imports_->push_back(
+        {.loc = ConvertPresumedLocToDiagnosticsLoc(loc, ploc),
+         .kind = DiagnosticLocConverter::ImportLoc::CppMacroExpansion,
+         .imported_name = message});
+  }
+
+  void emitDiagnosticLoc(
+      clang::FullSourceLoc /*loc*/, clang::PresumedLoc /*ploc*/,
+      clang::DiagnosticsEngine::Level /*level*/,
+      llvm::ArrayRef<clang::CharSourceRange> /*ranges*/) override {}
+  void emitCodeContext(
+      clang::FullSourceLoc /*loc*/, clang::DiagnosticsEngine::Level /*level*/,
+      llvm::SmallVectorImpl<clang::CharSourceRange>& /*ranges*/,
+      llvm::ArrayRef<clang::FixItHint> /*hints*/) override {}
+
+  void emitIncludeLocation(clang::FullSourceLoc loc,
+                           clang::PresumedLoc ploc) override {
+    // TODO: If this location is for a `#include` in the generated C++ includes
+    // buffer that corresponds to a carbon import, report it as being an Import
+    // instead of a CppInclude.
+    imports_->push_back(
+        {.loc = ConvertPresumedLocToDiagnosticsLoc(loc, ploc),
+         .kind = DiagnosticLocConverter::ImportLoc::CppInclude});
+  }
+  void emitImportLocation(clang::FullSourceLoc loc, clang::PresumedLoc ploc,
+                          llvm::StringRef module_name) override {
+    imports_->push_back(
+        {.loc = ConvertPresumedLocToDiagnosticsLoc(loc, ploc),
+         .kind = DiagnosticLocConverter::ImportLoc::CppModuleImport,
+         .imported_name = module_name});
+  }
+  void emitBuildingModuleLocation(clang::FullSourceLoc loc,
+                                  clang::PresumedLoc ploc,
+                                  llvm::StringRef module_name) override {
+    imports_->push_back(
+        {.loc = ConvertPresumedLocToDiagnosticsLoc(loc, ploc),
+         .kind = DiagnosticLocConverter::ImportLoc::CppModuleImport,
+         .imported_name = module_name});
+  }
+
+ private:
+  llvm::SmallVectorImpl<DiagnosticLocConverter::ImportLoc>* imports_;
+  // Whether we've emitted the primary diagnostic message or not. Any diagnostic
+  // emitted after this is an "in macro expansion" note that we want to capture
+  // as context.
+  bool emitted_message_ = false;
+};
+}  // namespace
+
 auto DiagnosticLocConverter::ConvertWithImports(LocId loc_id,
                                                 bool token_only) const
     -> LocAndImports {
@@ -26,6 +124,20 @@ auto DiagnosticLocConverter::ConvertWithImports(LocId loc_id,
     result.imports.push_back({.loc = ConvertImpl(absolute_node_id, false).loc});
   }
 
+  // Convert the C++ import locations.
+  if (final_node_id.check_ir_id() == SemIR::CheckIRId::Cpp) {
+    const clang::ASTUnit* ast = sem_ir_->cpp_ast();
+    // Collect the location backtrace that Clang would use for an error here.
+    ClangImportCollector(ast->getLangOpts(),
+                         ast->getDiagnostics().getDiagnosticOptions(),
+                         &result.imports)
+        .emitDiagnostic(
+            clang::FullSourceLoc(sem_ir_->clang_source_locs().Get(
+                                     final_node_id.clang_source_loc_id()),
+                                 ast->getSourceManager()),
+            clang::DiagnosticsEngine::Error, "", {}, {});
+  }
+
   return result;
 }
 
@@ -63,17 +175,16 @@ auto DiagnosticLocConverter::ConvertImpl(
       sem_ir_->clang_source_locs().Get(clang_source_loc_id);
 
   CARBON_CHECK(sem_ir_->cpp_ast());
-  clang::PresumedLoc presumed_loc =
-      sem_ir_->cpp_ast()->getSourceManager().getPresumedLoc(clang_loc);
+  const auto& src_mgr = sem_ir_->cpp_ast()->getSourceManager();
+  clang::PresumedLoc presumed_loc = src_mgr.getPresumedLoc(clang_loc);
   if (presumed_loc.isInvalid()) {
     return Diagnostics::ConvertedLoc();
   }
-  unsigned offset =
-      sem_ir_->cpp_ast()->getSourceManager().getDecomposedLoc(clang_loc).second;
+  unsigned offset = src_mgr.getDecomposedLoc(clang_loc).second;
 
   return Diagnostics::ConvertedLoc{
-      .loc = {.filename = presumed_loc.getFilename(),
-              .line_number = static_cast<int32_t>(presumed_loc.getLine())},
+      .loc = ConvertPresumedLocToDiagnosticsLoc(
+          clang::FullSourceLoc(clang_loc, src_mgr), presumed_loc),
       .last_byte_offset = static_cast<int32_t>(offset)};
 }
 

+ 22 - 2
toolchain/sem_ir/diagnostic_loc_converter.h

@@ -23,9 +23,29 @@ class DiagnosticLocConverter {
  public:
   // Information about an import within which the location was found.
   struct ImportLoc {
+    enum Kind {
+      // A regular Carbon `import`.
+      Import,
+      // A C++ `#include`.
+      CppInclude,
+      // A C++ module import.
+      CppModuleImport,
+      // A C++ macro expansion.
+      CppMacroExpansion,
+    };
+
+    // The location of the import.
     Diagnostics::Loc loc;
-    // TODO: Include the name of the imported library in this information so it
-    // can be included in the diagnostic.
+
+    // The kind of import.
+    Kind kind;
+
+    // The name of the imported module for CppInclude, or the complete macro
+    // expansion diagnostic message for CppMacroExpansion.
+    // TODO: In the latter case, provide just the macro name.
+    // TODO: Include the name of the imported library in this information for
+    // Import so it can be mentioned in the diagnostic.
+    llvm::StringRef imported_name;
   };
 
   // Information about a location that has been converted from a LocId to a