Эх сурвалжийг харах

Generate Cpp namespace when import Cpp is used (#4873)

Also defined a dedicated `ImportCppDecl` `InstKind`.
Part of #4666
Boaz Brickner 1 жил өмнө
parent
commit
3f599c250b

+ 1 - 1
toolchain/check/check.cpp

@@ -89,7 +89,7 @@ static auto TrackImport(Map<ImportKey, UnitAndImports*>& api_map,
       unit_info.emitter.Emit(import.node_id, CppInteropFuzzing);
       return;
     }
-    unit_info.cpp_imports.push_back(import);
+    unit_info.cpp_import_names.push_back(import);
     return;
   }
 

+ 2 - 21
toolchain/check/check_unit.cpp

@@ -131,7 +131,6 @@ auto CheckUnit::InitPackageScopeAndImports() -> void {
         package_imports.node_id, {.package_id = SemIR::NameId::ForPackageName(
                                       package_imports.package_id)});
   }
-
   // Process the imports.
   if (unit_and_imports_->api_for_impl) {
     ImportApiFile(context_, namespace_type_id,
@@ -140,7 +139,8 @@ auto CheckUnit::InitPackageScopeAndImports() -> void {
   ImportCurrentPackage(package_inst_id, namespace_type_id);
   CARBON_CHECK(context_.scope_stack().PeekIndex() == ScopeIndex::Package);
   ImportOtherPackages(namespace_type_id);
-  ImportCppPackages();
+  ImportCppFiles(context_, unit_and_imports_->unit->sem_ir->filename(),
+                 unit_and_imports_->cpp_import_names, fs_);
 }
 
 auto CheckUnit::CollectDirectImports(
@@ -342,25 +342,6 @@ auto CheckUnit::ImportOtherPackages(SemIR::TypeId namespace_type_id) -> void {
   }
 }
 
-auto CheckUnit::ImportCppPackages() -> void {
-  const auto& imports = unit_and_imports_->cpp_imports;
-  if (imports.empty()) {
-    return;
-  }
-
-  llvm::SmallVector<std::pair<llvm::StringRef, SemIRLoc>> import_pairs;
-  import_pairs.reserve(imports.size());
-  for (const auto& import : imports) {
-    import_pairs.push_back(
-        {unit_and_imports_->unit->value_stores->string_literal_values().Get(
-             import.library_id),
-         import.node_id});
-  }
-
-  ImportCppFiles(context_, unit_and_imports_->unit->sem_ir->filename(),
-                 import_pairs, fs_);
-}
-
 // Loops over all nodes in the tree. On some errors, this may return early,
 // for example if an unrecoverable state is encountered.
 // NOLINTNEXTLINE(readability-function-size)

+ 1 - 4
toolchain/check/check_unit.h

@@ -94,7 +94,7 @@ struct UnitAndImports {
   Map<PackageNameId, int32_t> package_imports_map;
 
   // List of the `import Cpp` imports.
-  llvm::SmallVector<Parse::Tree::PackagingNames> cpp_imports;
+  llvm::SmallVector<Parse::Tree::PackagingNames> cpp_import_names;
 
   // The remaining number of imports which must be checked before this unit can
   // be processed.
@@ -153,9 +153,6 @@ class CheckUnit {
   // Imports all other Carbon packages (excluding the current package).
   auto ImportOtherPackages(SemIR::TypeId namespace_type_id) -> void;
 
-  // Imports all C++ packages.
-  auto ImportCppPackages() -> void;
-
   // Checks that each required definition is available. If the definition can be
   // generated by resolving a specific, does so, otherwise emits a diagnostic
   // for each declaration in context.definitions_required() that doesn't have a

+ 1 - 0
toolchain/check/eval.cpp

@@ -2119,6 +2119,7 @@ static auto TryEvalInstInContext(EvalContext& eval_context,
     case SemIR::Branch::Kind:
     case SemIR::BranchIf::Kind:
     case SemIR::BranchWithArg::Kind:
+    case SemIR::ImportCppDecl::Kind:
     case SemIR::ImportDecl::Kind:
     case SemIR::NameBindingDecl::Kind:
     case SemIR::OutParam::Kind:

+ 12 - 23
toolchain/check/import.cpp

@@ -85,23 +85,12 @@ static auto CopyNameFromImportIR(Context& context,
   return import_name_id;
 }
 
-namespace {
-struct NamespaceResult {
-  SemIR::NameScopeId name_scope_id;
-  SemIR::InstId inst_id;
-  bool is_duplicate_of_namespace_in_current_package;
-};
-}  // namespace
-
-// Adds a namespace to the IR. The bool on return is true if there was a name
-// conflict. diagnose_duplicate_namespace is used when handling a cross-package
-// import, where an existing namespace is in the current package and the new
-// namespace is a different package.
-static auto AddNamespace(
-    Context& context, SemIR::TypeId namespace_type_id, SemIR::NameId name_id,
-    SemIR::NameScopeId parent_scope_id, bool diagnose_duplicate_namespace,
-    llvm::function_ref<auto()->SemIR::InstId> make_import_id)
-    -> NamespaceResult {
+auto AddImportNamespace(Context& context, SemIR::TypeId namespace_type_id,
+                        SemIR::NameId name_id,
+                        SemIR::NameScopeId parent_scope_id,
+                        bool diagnose_duplicate_namespace,
+                        llvm::function_ref<SemIR::InstId()> make_import_id)
+    -> AddImportNamespaceResult {
   auto* parent_scope = &context.name_scopes().Get(parent_scope_id);
   auto [inserted, entry_id] = parent_scope->LookupOrAdd(
       name_id,
@@ -186,7 +175,7 @@ static auto CopySingleNameScopeFromImportIR(
     Map<SemIR::NameScopeId, SemIR::NameScopeId>* copied_namespaces,
     SemIR::ImportIRId ir_id, SemIR::InstId import_inst_id,
     SemIR::NameScopeId import_scope_id, SemIR::NameScopeId parent_scope_id,
-    SemIR::NameId name_id) -> NamespaceResult {
+    SemIR::NameId name_id) -> AddImportNamespaceResult {
   // Produce the namespace for the entry.
   auto make_import_id = [&]() {
     auto entity_name_id = context.entity_names().Add(
@@ -203,9 +192,9 @@ static auto CopySingleNameScopeFromImportIR(
     context.import_ref_ids().push_back(inst_id);
     return inst_id;
   };
-  NamespaceResult result =
-      AddNamespace(context, namespace_type_id, name_id, parent_scope_id,
-                   /*diagnose_duplicate_namespace=*/false, make_import_id);
+  AddImportNamespaceResult result = AddImportNamespace(
+      context, namespace_type_id, name_id, parent_scope_id,
+      /*diagnose_duplicate_namespace=*/false, make_import_id);
 
   auto namespace_const_id = context.constant_values().Get(result.inst_id);
   context.import_ir_constant_values()[ir_id.index].Set(import_inst_id,
@@ -458,7 +447,7 @@ auto ImportLibrariesFromOtherPackage(Context& context,
 
   auto name_id = SemIR::NameId::ForPackageName(package_id);
 
-  NamespaceResult result = AddNamespace(
+  AddImportNamespaceResult result = AddImportNamespace(
       context, namespace_type_id, name_id, SemIR::NameScopeId::Package,
       /*diagnose_duplicate_namespace=*/true, [&] { return import_decl_id; });
   auto namespace_const_id = context.constant_values().Get(result.inst_id);
@@ -525,7 +514,7 @@ static auto AddNamespaceFromOtherPackage(Context& context,
     -> SemIR::InstId {
   auto namespace_type_id =
       context.GetSingletonType(SemIR::NamespaceType::SingletonInstId);
-  NamespaceResult result = CopySingleNameScopeFromImportIR(
+  AddImportNamespaceResult result = CopySingleNameScopeFromImportIR(
       context, namespace_type_id, /*copied_namespaces=*/nullptr, import_ir_id,
       import_inst_id, import_ns.name_scope_id, parent_scope_id, name_id);
   auto& scope = context.name_scopes().Get(result.name_scope_id);

+ 17 - 0
toolchain/check/import.h

@@ -11,6 +11,23 @@
 
 namespace Carbon::Check {
 
+struct AddImportNamespaceResult {
+  SemIR::NameScopeId name_scope_id;
+  SemIR::InstId inst_id;
+  bool is_duplicate_of_namespace_in_current_package;
+};
+
+// Adds a namespace to the IR. The bool on return is true if there was a name
+// conflict. diagnose_duplicate_namespace is used when handling a cross-package
+// import, where an existing namespace is in the current package and the new
+// namespace is a different package.
+auto AddImportNamespace(Context& context, SemIR::TypeId namespace_type_id,
+                        SemIR::NameId name_id,
+                        SemIR::NameScopeId parent_scope_id,
+                        bool diagnose_duplicate_namespace,
+                        llvm::function_ref<SemIR::InstId()> make_import_id)
+    -> AddImportNamespaceResult;
+
 // Imports the API file's name lookup information into a corresponding
 // implementation file. Only information for the current package will be copied;
 // information for other packages should be handled through

+ 70 - 14
toolchain/check/import_cpp.cpp

@@ -5,6 +5,7 @@
 #include "toolchain/check/import_cpp.h"
 
 #include <memory>
+#include <optional>
 #include <string>
 
 #include "clang/Frontend/TextDiagnosticPrinter.h"
@@ -15,35 +16,39 @@
 #include "llvm/Support/raw_ostream.h"
 #include "toolchain/check/context.h"
 #include "toolchain/check/diagnostic_helpers.h"
+#include "toolchain/check/import.h"
 #include "toolchain/diagnostics/diagnostic.h"
 #include "toolchain/diagnostics/format_providers.h"
+#include "toolchain/sem_ir/name_scope.h"
 
 namespace Carbon::Check {
 
 // Generates C++ file contents to #include all requested imports.
 static auto GenerateCppIncludesHeaderCode(
-    llvm::ArrayRef<std::pair<llvm::StringRef, SemIRLoc>> imports)
+    Context& context, llvm::ArrayRef<Parse::Tree::PackagingNames> imports)
     -> std::string {
   std::string code;
   llvm::raw_string_ostream code_stream(code);
-  for (const auto& [path, _] : imports) {
-    code_stream << "#include \"" << FormatEscaped(path) << "\"\n";
+  for (const Parse::Tree::PackagingNames& import : imports) {
+    code_stream << "#include \""
+                << FormatEscaped(
+                       context.string_literal_values().Get(import.library_id))
+                << "\"\n";
   }
   return code;
 }
 
-auto ImportCppFiles(
-    Context& context, llvm::StringRef importing_file_path,
-    llvm::ArrayRef<std::pair<llvm::StringRef, SemIRLoc>> imports,
-    llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> fs) -> void {
-  size_t num_imports = imports.size();
-  if (num_imports == 0) {
-    return;
-  }
-
+// 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.
+// 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,
+                        llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> fs)
+    -> std::pair<std::unique_ptr<clang::ASTUnit>, bool> {
   // TODO: Use all import locations by referring each Clang diagnostic to the
   // relevant import.
-  SemIRLoc loc = imports.back().second;
+  SemIRLoc loc = imports.back().node_id;
 
   std::string diagnostics_str;
   llvm::raw_string_ostream diagnostics_stream(diagnostics_str);
@@ -54,7 +59,7 @@ auto ImportCppFiles(
                                                     diagnostic_options.get());
   // TODO: Share compilation flags with ClangRunner.
   auto ast = clang::tooling::buildASTFromCodeWithArgs(
-      GenerateCppIncludesHeaderCode(imports), {},
+      GenerateCppIncludesHeaderCode(context, imports), {},
       (importing_file_path + ".generated.cpp_imports.h").str(), "clang-tool",
       std::make_shared<clang::PCHContainerOperations>(),
       clang::tooling::getClangStripDependencyFileAdjuster(),
@@ -62,6 +67,7 @@ auto ImportCppFiles(
   // TODO: Implement and use a DynamicRecursiveASTVisitor to traverse the AST.
   int num_errors = diagnostics_consumer.getNumErrors();
   int num_warnings = diagnostics_consumer.getNumWarnings();
+  int num_imports = imports.size();
   if (num_errors > 0) {
     // TODO: Remove the warnings part when there are no warnings.
     CARBON_DIAGNOSTIC(
@@ -77,6 +83,56 @@ auto ImportCppFiles(
     context.emitter().Emit(loc, CppInteropParseWarning, num_warnings,
                            num_imports, diagnostics_str);
   }
+  return {std::move(ast), !ast || num_errors > 0};
+}
+
+// Adds a namespace for the `Cpp` import and returns its `NameScopeId`.
+static auto AddNamespace(Context& context, PackageNameId cpp_package_id,
+                         llvm::ArrayRef<Parse::Tree::PackagingNames> imports)
+    -> SemIR::NameScopeId {
+  auto& import_cpps = context.sem_ir().import_cpps();
+  import_cpps.Reserve(imports.size());
+  for (const Parse::Tree::PackagingNames& import : imports) {
+    import_cpps.Add(
+        {.node_id = import.node_id, .library_id = import.library_id});
+  }
+
+  return AddImportNamespace(
+             context,
+             context.GetSingletonType(SemIR::NamespaceType::SingletonInstId),
+             SemIR::NameId::ForPackageName(cpp_package_id),
+             SemIR::NameScopeId::Package,
+             /*diagnose_duplicate_namespace=*/false,
+             [&]() {
+               return context.AddInst<SemIR::ImportCppDecl>(
+                   imports.front().node_id, {});
+             })
+      .name_scope_id;
+}
+
+auto ImportCppFiles(Context& context, llvm::StringRef importing_file_path,
+                    llvm::ArrayRef<Parse::Tree::PackagingNames> imports,
+                    llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> fs)
+    -> void {
+  if (imports.empty()) {
+    return;
+  }
+
+  auto [ast, ast_has_error] =
+      GenerateAst(context, importing_file_path, imports, fs);
+
+  PackageNameId package_id = imports.front().package_id;
+  CARBON_CHECK(
+      llvm::all_of(imports, [&](const Parse::Tree::PackagingNames& import) {
+        return import.package_id == package_id;
+      }));
+  auto name_scope_id = AddNamespace(context, package_id, imports);
+  SemIR::NameScope& name_scope = context.name_scopes().Get(name_scope_id);
+  name_scope.set_is_closed_import(true);
+
+  if (ast_has_error) {
+    name_scope.set_has_error();
+  }
 }
 
 }  // namespace Carbon::Check

+ 4 - 5
toolchain/check/import_cpp.h

@@ -12,11 +12,10 @@
 namespace Carbon::Check {
 
 // Generates a C++ header that includes the imported cpp files, parses it and
-// report errors and warnings.
-auto ImportCppFiles(
-    Context& context, llvm::StringRef importing_file_path,
-    llvm::ArrayRef<std::pair<llvm::StringRef, SemIRLoc>> imports,
-    llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> fs) -> void;
+// report errors and warnings. If successful, adds a `Cpp` namespace.
+auto ImportCppFiles(Context& context, llvm::StringRef importing_file_path,
+                    llvm::ArrayRef<Parse::Tree::PackagingNames> imports,
+                    llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> fs) -> void;
 
 }  // namespace Carbon::Check
 

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

@@ -56,7 +56,18 @@ import Cpp library "\"foo.h\"";
 // CHECK:STDOUT:
 // CHECK:STDOUT: --- fail_import_cpp_library_file_with_quotes.carbon
 // CHECK:STDOUT:
+// CHECK:STDOUT: imports {
+// CHECK:STDOUT:   %Cpp: <namespace> = namespace file.%Cpp.import_cpp, [template] {
+// CHECK:STDOUT:     has_error
+// CHECK:STDOUT:   }
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
 // CHECK:STDOUT: file {
-// CHECK:STDOUT:   package: <namespace> = namespace [template] {}
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .Cpp = imports.%Cpp
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Cpp.import_cpp = import_cpp {
+// CHECK:STDOUT:     import Cpp "\"foo.h\""
+// CHECK:STDOUT:   }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:

+ 92 - 8
toolchain/check/testdata/interop/cpp/no_prelude/cpp_diagnostics.carbon

@@ -217,49 +217,133 @@ import Cpp library "multiple_errors_and_multiple_warnings.h";
 
 // CHECK:STDOUT: --- fail_import_cpp_file_with_one_error.carbon
 // CHECK:STDOUT:
+// CHECK:STDOUT: imports {
+// CHECK:STDOUT:   %Cpp: <namespace> = namespace file.%Cpp.import_cpp, [template] {
+// CHECK:STDOUT:     has_error
+// CHECK:STDOUT:   }
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
 // CHECK:STDOUT: file {
-// CHECK:STDOUT:   package: <namespace> = namespace [template] {}
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .Cpp = imports.%Cpp
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Cpp.import_cpp = import_cpp {
+// CHECK:STDOUT:     import Cpp "one_error.h"
+// CHECK:STDOUT:   }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: --- fail_import_cpp_file_with_multiple_errors.carbon
 // CHECK:STDOUT:
+// CHECK:STDOUT: imports {
+// CHECK:STDOUT:   %Cpp: <namespace> = namespace file.%Cpp.import_cpp, [template] {
+// CHECK:STDOUT:     has_error
+// CHECK:STDOUT:   }
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
 // CHECK:STDOUT: file {
-// CHECK:STDOUT:   package: <namespace> = namespace [template] {}
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .Cpp = imports.%Cpp
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Cpp.import_cpp = import_cpp {
+// CHECK:STDOUT:     import Cpp "multiple_errors.h"
+// CHECK:STDOUT:   }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: --- import_cpp_file_with_one_warning.carbon
 // CHECK:STDOUT:
+// CHECK:STDOUT: imports {
+// CHECK:STDOUT:   %Cpp: <namespace> = namespace file.%Cpp.import_cpp, [template] {}
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
 // CHECK:STDOUT: file {
-// CHECK:STDOUT:   package: <namespace> = namespace [template] {}
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// 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:
 // CHECK:STDOUT: --- import_cpp_file_with_multiple_warnings.carbon
 // CHECK:STDOUT:
+// CHECK:STDOUT: imports {
+// CHECK:STDOUT:   %Cpp: <namespace> = namespace file.%Cpp.import_cpp, [template] {}
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
 // CHECK:STDOUT: file {
-// CHECK:STDOUT:   package: <namespace> = namespace [template] {}
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .Cpp = imports.%Cpp
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Cpp.import_cpp = import_cpp {
+// CHECK:STDOUT:     import Cpp "multiple_warnings.h"
+// CHECK:STDOUT:   }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: --- fail_import_cpp_file_with_one_error_and_one_warning.carbon
 // CHECK:STDOUT:
+// CHECK:STDOUT: imports {
+// CHECK:STDOUT:   %Cpp: <namespace> = namespace file.%Cpp.import_cpp, [template] {
+// CHECK:STDOUT:     has_error
+// CHECK:STDOUT:   }
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
 // CHECK:STDOUT: file {
-// CHECK:STDOUT:   package: <namespace> = namespace [template] {}
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .Cpp = imports.%Cpp
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Cpp.import_cpp = import_cpp {
+// CHECK:STDOUT:     import Cpp "one_error_and_one_warning.h"
+// CHECK:STDOUT:   }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: --- fail_import_cpp_file_with_multiple_errors_and_multiple_warnings.carbon
 // CHECK:STDOUT:
+// CHECK:STDOUT: imports {
+// CHECK:STDOUT:   %Cpp: <namespace> = namespace file.%Cpp.import_cpp, [template] {
+// CHECK:STDOUT:     has_error
+// CHECK:STDOUT:   }
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
 // CHECK:STDOUT: file {
-// CHECK:STDOUT:   package: <namespace> = namespace [template] {}
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .Cpp = imports.%Cpp
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Cpp.import_cpp = import_cpp {
+// CHECK:STDOUT:     import Cpp "multiple_errors_and_multiple_warnings.h"
+// CHECK:STDOUT:   }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: --- import_multiple_cpp_files_with_warnings.carbon
 // CHECK:STDOUT:
+// CHECK:STDOUT: imports {
+// CHECK:STDOUT:   %Cpp: <namespace> = namespace file.%Cpp.import_cpp, [template] {}
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
 // CHECK:STDOUT: file {
-// CHECK:STDOUT:   package: <namespace> = namespace [template] {}
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .Cpp = imports.%Cpp
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Cpp.import_cpp = import_cpp {
+// CHECK:STDOUT:     import Cpp "one_warning.h"
+// CHECK:STDOUT:     import Cpp "multiple_warnings.h"
+// CHECK:STDOUT:   }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: --- fail_import_multiple_cpp_files_with_warnings_and_errors.carbon
 // CHECK:STDOUT:
+// CHECK:STDOUT: imports {
+// CHECK:STDOUT:   %Cpp: <namespace> = namespace file.%Cpp.import_cpp, [template] {
+// CHECK:STDOUT:     has_error
+// CHECK:STDOUT:   }
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
 // CHECK:STDOUT: file {
-// CHECK:STDOUT:   package: <namespace> = namespace [template] {}
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .Cpp = imports.%Cpp
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Cpp.import_cpp = import_cpp {
+// CHECK:STDOUT:     import Cpp "one_error_and_one_warning.h"
+// CHECK:STDOUT:     import Cpp "multiple_errors_and_multiple_warnings.h"
+// CHECK:STDOUT:   }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:

+ 187 - 0
toolchain/check/testdata/interop/cpp/no_prelude/cpp_namespace.carbon

@@ -0,0 +1,187 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+// AUTOUPDATE
+// TIP: To test this file alone, run:
+// TIP:   bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/check/testdata/interop/cpp/no_prelude/cpp_namespace.carbon
+// TIP: To dump output, run:
+// TIP:   bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/interop/cpp/no_prelude/cpp_namespace.carbon
+
+// --- header.h
+
+// --- fail_duplicate_cpp_name.carbon
+
+library "[[@TEST_NAME]]";
+
+import Cpp library "header.h";
+
+// CHECK:STDERR: fail_duplicate_cpp_name.carbon:[[@LINE+7]]:1: error: duplicate name being declared in the same scope [NameDeclDuplicate]
+// CHECK:STDERR: namespace Cpp;
+// CHECK:STDERR: ^~~~~~~~~~~~~~
+// CHECK:STDERR: fail_duplicate_cpp_name.carbon:[[@LINE-5]]:1: note: name is previously declared here [NameDeclPrevious]
+// CHECK:STDERR: import Cpp library "header.h";
+// CHECK:STDERR: ^~~~~~
+// CHECK:STDERR:
+namespace Cpp;
+
+// --- cpp_in_inner_namespace.carbon
+
+library "[[@TEST_NAME]]";
+
+import Cpp library "header.h";
+
+namespace N;
+namespace N.Cpp;
+
+// --- alias.carbon
+
+library "[[@TEST_NAME]]";
+
+import Cpp library "header.h";
+
+alias MyCpp = Cpp;
+
+// --- fail_add_name_to_cpp_namespace.carbon
+
+library "[[@TEST_NAME]]";
+
+import Cpp library "header.h";
+
+// CHECK:STDERR: fail_add_name_to_cpp_namespace.carbon:[[@LINE+7]]:7: error: imported packages cannot be used for declarations [QualifiedDeclOutsidePackage]
+// CHECK:STDERR: class Cpp.C {};
+// CHECK:STDERR:       ^~~
+// CHECK:STDERR: fail_add_name_to_cpp_namespace.carbon:[[@LINE-5]]:1: note: package imported here [QualifiedDeclOutsidePackageSource]
+// CHECK:STDERR: import Cpp library "header.h";
+// CHECK:STDERR: ^~~~~~
+// CHECK:STDERR:
+class Cpp.C {};
+
+// --- api_and_impl.carbon
+
+library "[[@TEST_NAME]]";
+
+import Cpp library "header.h";
+
+// --- api_and_impl.impl.carbon
+
+impl library "[[@TEST_NAME]]";
+
+import Cpp library "header.h";
+
+// CHECK:STDOUT: --- fail_duplicate_cpp_name.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: imports {
+// CHECK:STDOUT:   %Cpp: <namespace> = namespace file.%Cpp.import_cpp, [template] {}
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .Cpp = imports.%Cpp
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Cpp.import_cpp = import_cpp {
+// CHECK:STDOUT:     import Cpp "header.h"
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Cpp: <namespace> = namespace [template] {}
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- cpp_in_inner_namespace.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: imports {
+// CHECK:STDOUT:   %Cpp: <namespace> = namespace file.%Cpp.import_cpp, [template] {}
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .Cpp = imports.%Cpp
+// CHECK:STDOUT:     .N = %N
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Cpp.import_cpp = import_cpp {
+// CHECK:STDOUT:     import Cpp "header.h"
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %N: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .Cpp = %Cpp
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Cpp: <namespace> = namespace [template] {}
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- alias.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: imports {
+// CHECK:STDOUT:   %Cpp: <namespace> = namespace file.%Cpp.import_cpp, [template] {}
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .Cpp = imports.%Cpp
+// CHECK:STDOUT:     .MyCpp = %MyCpp
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Cpp.import_cpp = import_cpp {
+// CHECK:STDOUT:     import Cpp "header.h"
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Cpp.ref: <namespace> = name_ref Cpp, imports.%Cpp [template = imports.%Cpp]
+// CHECK:STDOUT:   %MyCpp: <namespace> = bind_alias MyCpp, imports.%Cpp [template = imports.%Cpp]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- fail_add_name_to_cpp_namespace.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %C: type = class_type @C [template]
+// CHECK:STDOUT:   %empty_struct_type: type = struct_type {} [template]
+// CHECK:STDOUT:   %complete_type: <witness> = complete_type_witness %empty_struct_type [template]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: imports {
+// CHECK:STDOUT:   %Cpp: <namespace> = namespace file.%Cpp.import_cpp, [template] {
+// CHECK:STDOUT:     .C = file.%C.decl
+// CHECK:STDOUT:   }
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .Cpp = imports.%Cpp
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Cpp.import_cpp = import_cpp {
+// CHECK:STDOUT:     import Cpp "header.h"
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %C.decl: type = class_decl @C [template = constants.%C] {} {}
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @C {
+// CHECK:STDOUT:   %complete_type: <witness> = complete_type_witness %empty_struct_type [template = constants.%complete_type]
+// CHECK:STDOUT:   complete_type_witness = %complete_type
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = constants.%C
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- api_and_impl.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: imports {
+// CHECK:STDOUT:   %Cpp: <namespace> = namespace file.%Cpp.import_cpp, [template] {}
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .Cpp = imports.%Cpp
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Cpp.import_cpp = import_cpp {
+// CHECK:STDOUT:     import Cpp "header.h"
+// CHECK:STDOUT:   }
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- api_and_impl.impl.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: imports {
+// CHECK:STDOUT:   %Main.Cpp: <namespace> = import_ref Main//api_and_impl, Cpp, loaded
+// CHECK:STDOUT:   %Cpp: <namespace> = namespace %Main.Cpp, [template] {}
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .Cpp = imports.%Cpp
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %default.import.loc2_6.1 = import <none>
+// CHECK:STDOUT:   %default.import.loc2_6.2 = import <none>
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

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

@@ -24,7 +24,18 @@ import Cpp library "not_found.h";
 
 // CHECK:STDOUT: --- fail_cpp_file_not_found.carbon
 // CHECK:STDOUT:
+// CHECK:STDOUT: imports {
+// CHECK:STDOUT:   %Cpp: <namespace> = namespace file.%Cpp.import_cpp, [template] {
+// CHECK:STDOUT:     has_error
+// CHECK:STDOUT:   }
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
 // CHECK:STDOUT: file {
-// CHECK:STDOUT:   package: <namespace> = namespace [template] {}
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .Cpp = imports.%Cpp
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Cpp.import_cpp = import_cpp {
+// CHECK:STDOUT:     import Cpp "not_found.h"
+// CHECK:STDOUT:   }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:

+ 10 - 1
toolchain/check/testdata/interop/cpp/no_prelude/function_decl.carbon

@@ -20,7 +20,16 @@ import Cpp library "function_decl.h";
 
 // CHECK:STDOUT: --- import_function_decl.carbon
 // CHECK:STDOUT:
+// CHECK:STDOUT: imports {
+// CHECK:STDOUT:   %Cpp: <namespace> = namespace file.%Cpp.import_cpp, [template] {}
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
 // CHECK:STDOUT: file {
-// CHECK:STDOUT:   package: <namespace> = namespace [template] {}
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .Cpp = imports.%Cpp
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Cpp.import_cpp = import_cpp {
+// CHECK:STDOUT:     import Cpp "function_decl.h"
+// CHECK:STDOUT:   }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:

+ 10 - 1
toolchain/check/testdata/interop/cpp/no_prelude/include.carbon

@@ -24,7 +24,16 @@ import Cpp library "including_file.h";
 
 // CHECK:STDOUT: --- import_function_decl.carbon
 // CHECK:STDOUT:
+// CHECK:STDOUT: imports {
+// CHECK:STDOUT:   %Cpp: <namespace> = namespace file.%Cpp.import_cpp, [template] {}
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
 // CHECK:STDOUT: file {
-// CHECK:STDOUT:   package: <namespace> = namespace [template] {}
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .Cpp = imports.%Cpp
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Cpp.import_cpp = import_cpp {
+// CHECK:STDOUT:     import Cpp "including_file.h"
+// CHECK:STDOUT:   }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:

+ 11 - 1
toolchain/check/testdata/interop/cpp/no_prelude/multiple_imports.carbon

@@ -25,7 +25,17 @@ import Cpp library "file2.h";
 
 // CHECK:STDOUT: --- multiple_imports.carbon
 // CHECK:STDOUT:
+// CHECK:STDOUT: imports {
+// CHECK:STDOUT:   %Cpp: <namespace> = namespace file.%Cpp.import_cpp, [template] {}
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
 // CHECK:STDOUT: file {
-// CHECK:STDOUT:   package: <namespace> = namespace [template] {}
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .Cpp = imports.%Cpp
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Cpp.import_cpp = import_cpp {
+// CHECK:STDOUT:     import Cpp "file1.h"
+// CHECK:STDOUT:     import Cpp "file2.h"
+// CHECK:STDOUT:   }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:

+ 1 - 0
toolchain/sem_ir/BUILD

@@ -93,6 +93,7 @@ cc_library(
         "function.h",
         "generic.h",
         "impl.h",
+        "import_cpp.h",
         "import_ir.h",
         "interface.h",
         "name.h",

+ 1 - 0
toolchain/sem_ir/file.cpp

@@ -268,6 +268,7 @@ auto GetExprCategory(const File& file, InstId inst_id) -> ExprCategory {
       case GenericInterfaceType::Kind:
       case ImplWitness::Kind:
       case ImplWitnessAccess::Kind:
+      case ImportCppDecl::Kind:
       case ImportDecl::Kind:
       case IntLiteralType::Kind:
       case IntType::Kind:

+ 8 - 0
toolchain/sem_ir/file.h

@@ -24,6 +24,7 @@
 #include "toolchain/sem_ir/generic.h"
 #include "toolchain/sem_ir/ids.h"
 #include "toolchain/sem_ir/impl.h"
+#include "toolchain/sem_ir/import_cpp.h"
 #include "toolchain/sem_ir/import_ir.h"
 #include "toolchain/sem_ir/inst.h"
 #include "toolchain/sem_ir/interface.h"
@@ -166,6 +167,10 @@ class File : public Printable<File> {
   auto import_ir_insts() const -> const ValueStore<ImportIRInstId>& {
     return import_ir_insts_;
   }
+  auto import_cpps() -> ValueStore<ImportCppId>& { return import_cpps_; }
+  auto import_cpps() const -> const ValueStore<ImportCppId>& {
+    return import_cpps_;
+  }
   auto names() const -> NameStoreWrapper {
     return NameStoreWrapper(&identifiers());
   }
@@ -275,6 +280,9 @@ class File : public Printable<File> {
   // that are import-related.
   ValueStore<ImportIRInstId> import_ir_insts_;
 
+  // List of Cpp imports.
+  ValueStore<ImportCppId> import_cpps_;
+
   // Type blocks within the IR. These reference entries in types_. Storage for
   // the data is provided by allocator_.
   BlockValueStore<TypeBlockId> type_blocks_;

+ 20 - 0
toolchain/sem_ir/formatter.cpp

@@ -846,6 +846,12 @@ class FormatterImpl {
     }
   }
 
+  // Format ImportCppDecl name.
+  auto FormatInstLHS(InstId inst_id, ImportCppDecl /*inst*/) -> void {
+    FormatName(inst_id);
+    out_ << " = ";
+  }
+
   // Format ImportDecl with its name.
   auto FormatInstLHS(InstId inst_id, ImportDecl /*inst*/) -> void {
     FormatName(inst_id);
@@ -1078,6 +1084,20 @@ class FormatterImpl {
     out_ << " " << buffer;
   }
 
+  // Format the metadata in File for `import Cpp`.
+  auto FormatInstRHS(ImportCppDecl /*inst*/) -> void {
+    out_ << " ";
+    OpenBrace();
+    for (ImportCpp import_cpp : sem_ir_->import_cpps().array_ref()) {
+      Indent();
+      out_ << "import Cpp \""
+           << FormatEscaped(
+                  sem_ir_->string_literal_values().Get(import_cpp.library_id))
+           << "\"\n";
+    }
+    CloseBrace();
+  }
+
   auto FormatImportRefRHS(ImportIRInstId import_ir_inst_id,
                           EntityNameId entity_name_id,
                           llvm::StringLiteral loaded_label) -> void {

+ 13 - 0
toolchain/sem_ir/ids.h

@@ -26,6 +26,7 @@ struct FacetTypeInfo;
 struct Function;
 struct Generic;
 struct Specific;
+struct ImportCpp;
 struct ImportIR;
 struct ImportIRInst;
 struct Impl;
@@ -377,6 +378,18 @@ struct GenericInstIndex : public IndexBase<GenericInstIndex> {
 constexpr GenericInstIndex GenericInstIndex::None =
     GenericInstIndex::MakeNone();
 
+struct ImportCppId : public IdBase<ImportCppId> {
+  static constexpr llvm::StringLiteral Label = "import_cpp";
+  using ValueType = ImportCpp;
+
+  // An ID with no value.
+  static const ImportCppId None;
+
+  using IdBase::IdBase;
+};
+
+constexpr ImportCppId ImportCppId::None = ImportCppId(NoneIndex);
+
 // The ID of an IR within the set of imported IRs, both direct and indirect.
 struct ImportIRId : public IdBase<ImportIRId> {
   static constexpr llvm::StringLiteral Label = "ir";

+ 25 - 0
toolchain/sem_ir/import_cpp.h

@@ -0,0 +1,25 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+#ifndef CARBON_TOOLCHAIN_SEM_IR_IMPORT_CPP_H_
+#define CARBON_TOOLCHAIN_SEM_IR_IMPORT_CPP_H_
+
+#include "common/raw_string_ostream.h"
+#include "toolchain/sem_ir/typed_insts.h"
+
+namespace Carbon::SemIR {
+
+// Per `import Cpp` data.
+struct ImportCpp : Printable<ImportCpp> {
+  auto Print(llvm::raw_ostream& out) const -> void {
+    out << "{node_id: " << node_id << ", library_id: " << library_id << "}";
+  }
+
+  Parse::ImportDeclId node_id;
+  StringLiteralValueId library_id;
+};
+
+}  // namespace Carbon::SemIR
+
+#endif  // CARBON_TOOLCHAIN_SEM_IR_IMPORT_CPP_H_

+ 1 - 0
toolchain/sem_ir/inst_kind.def

@@ -69,6 +69,7 @@ CARBON_SEM_IR_INST_KIND(GenericInterfaceType)
 CARBON_SEM_IR_INST_KIND(ImplDecl)
 CARBON_SEM_IR_INST_KIND(ImplWitness)
 CARBON_SEM_IR_INST_KIND(ImplWitnessAccess)
+CARBON_SEM_IR_INST_KIND(ImportCppDecl)
 CARBON_SEM_IR_INST_KIND(ImportDecl)
 CARBON_SEM_IR_INST_KIND(ImportRefLoaded)
 CARBON_SEM_IR_INST_KIND(ImportRefUnloaded)

+ 4 - 0
toolchain/sem_ir/inst_namer.cpp

@@ -705,6 +705,10 @@ auto InstNamer::CollectNamesInBlock(ScopeId top_scope_id,
         add_inst_name(out.TakeStr());
         continue;
       }
+      case ImportCppDecl::Kind: {
+        add_inst_name("Cpp.import_cpp");
+        continue;
+      }
       case CARBON_KIND(ImportDecl inst): {
         if (inst.package_id.has_value()) {
           add_inst_name_id(inst.package_id, ".import");

+ 1 - 0
toolchain/sem_ir/stringify_type.cpp

@@ -591,6 +591,7 @@ auto StringifyTypeExpr(const SemIR::File& sem_ir, InstId outer_inst_id)
       case FunctionDecl::Kind:
       case ImplDecl::Kind:
       case ImplWitness::Kind:
+      case ImportCppDecl::Kind:
       case ImportDecl::Kind:
       case ImportRefLoaded::Kind:
       case InitializeFrom::Kind:

+ 7 - 0
toolchain/sem_ir/typed_insts.h

@@ -840,6 +840,13 @@ struct ImplWitnessAccess {
   ElementIndex index;
 };
 
+// An `import Cpp` declaration.
+struct ImportCppDecl {
+  static constexpr auto Kind =
+      InstKind::ImportCppDecl.Define<Parse::ImportDeclId>(
+          {.ir_name = "import_cpp", .is_lowered = false});
+};
+
 // An `import` declaration. This is mainly for `import` diagnostics, and a 1:1
 // correspondence with actual `import`s isn't guaranteed.
 struct ImportDecl {