Просмотр исходного кода

Add support for importing C++ namespaces (#5103)

This adds support for having different C++ `NameScope`s (and not just
the main `Cpp` scope), and we keep a pointer to `clang::DeclContext`
these scopes so we can look up C++ names in the right part of the AST.

C++ Interop Demo with a namespace:

```c++
// hello_world.h

namespace some_namespace {

void hello_world();

}  // namespace some_namespace
```

```c++
// hello_world.cpp

#include <cstdio>

namespace some_namespace {

void hello_world() { printf("Hello World!\n"); }

}  // namespace some_namespace
```

```carbon
// main.carbon

library "Main";

import Cpp library "hello_world.h";

fn Run() -> i32 {
  Cpp.some_namespace.hello_world();
  return 0;
}
```

```shell
$ clang -c hello_world.cpp
$ bazel-bin/toolchain/carbon compile main.carbon
$ bazel-bin/toolchain/carbon link hello_world.o main.o --output=demo
$ ./demo
Hello World!
```

Closes #5102
Boaz Brickner 1 год назад
Родитель
Сommit
fcd38a4d7f

+ 76 - 51
toolchain/check/import.cpp

@@ -89,12 +89,53 @@ static auto CopyNameFromImportIR(Context& context,
   return import_name_id;
 }
 
+// Returns the LocIdandInst for the namespace.
+static auto MakeImportedNamespaceLocIdAndInst(Context& context,
+                                              SemIR::InstId import_id,
+                                              SemIR::Namespace namespace_inst)
+    -> SemIR::LocIdAndInst {
+  if (import_id.has_value()) {
+    SemIR::LocId import_loc_id = context.insts().GetLocId(import_id);
+    return import_loc_id.is_import_ir_inst_id()
+               ? MakeImportedLocIdAndInst(
+                     context, import_loc_id.import_ir_inst_id(), namespace_inst)
+               // TODO: Check that this actually is an `AnyNamespaceId`.
+               : SemIR::LocIdAndInst(
+                     Parse::AnyNamespaceId::UnsafeMake(import_loc_id.node_id()),
+                     namespace_inst);
+  }
+
+  // TODO: Associate the namespace with a proper location. This is related to:
+  // https://github.com/carbon-language/carbon-lang/issues/4666.
+  return SemIR::LocIdAndInst::NoLoc(namespace_inst);
+}
+
 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 {
+                        SemIR::InstId import_id) -> AddImportNamespaceResult {
+  auto namespace_inst =
+      SemIR::Namespace{.type_id = namespace_type_id,
+                       .name_scope_id = SemIR::NameScopeId::None,
+                       .import_id = import_id};
+  auto namespace_inst_and_loc =
+      MakeImportedNamespaceLocIdAndInst(context, import_id, namespace_inst);
+  AddImportNamespaceResult result = {
+      .name_scope_id = SemIR::NameScopeId::None,
+      .inst_id = AddPlaceholderInstInNoBlock(context, namespace_inst_and_loc)};
+  context.import_ref_ids().push_back(result.inst_id);
+  namespace_inst.name_scope_id =
+      context.name_scopes().Add(result.inst_id, name_id, parent_scope_id);
+  result.name_scope_id = namespace_inst.name_scope_id;
+  ReplaceInstBeforeConstantUse(context, result.inst_id, namespace_inst);
+  return result;
+}
+
+auto AddImportNamespaceToScope(
+    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)
+    -> AddImportNamespaceToScopeResult {
   auto* parent_scope = &context.name_scopes().Get(parent_scope_id);
   auto [inserted, entry_id] = parent_scope->LookupOrAdd(
       name_id,
@@ -113,8 +154,8 @@ auto AddImportNamespace(Context& context, SemIR::TypeId namespace_type_id,
           // id to get more accurate location.
           DiagnoseDuplicateName(context, name_id, import_id, prev_inst_id);
         }
-        return {.name_scope_id = namespace_inst->name_scope_id,
-                .inst_id = prev_inst_id,
+        return {.add_result = {.name_scope_id = namespace_inst->name_scope_id,
+                               .inst_id = prev_inst_id},
                 .is_duplicate_of_namespace_in_current_package = true};
       }
     }
@@ -122,47 +163,29 @@ auto AddImportNamespace(Context& context, SemIR::TypeId namespace_type_id,
 
   auto import_id = make_import_id();
   CARBON_CHECK(import_id.has_value());
-  auto import_loc_id = context.insts().GetLocId(import_id);
 
-  auto namespace_inst =
-      SemIR::Namespace{.type_id = namespace_type_id,
-                       .name_scope_id = SemIR::NameScopeId::None,
-                       .import_id = import_id};
-  auto namespace_inst_and_loc =
-      import_loc_id.is_import_ir_inst_id()
-          ? MakeImportedLocIdAndInst(context, import_loc_id.import_ir_inst_id(),
-                                     namespace_inst)
-          // TODO: Check that this actually is an `AnyNamespaceId`.
-          : SemIR::LocIdAndInst(
-                Parse::AnyNamespaceId::UnsafeMake(import_loc_id.node_id()),
-                namespace_inst);
-  auto namespace_id =
-      AddPlaceholderInstInNoBlock(context, namespace_inst_and_loc);
-  context.import_ref_ids().push_back(namespace_id);
-  namespace_inst.name_scope_id =
-      context.name_scopes().Add(namespace_id, name_id, parent_scope_id);
-  ReplaceInstBeforeConstantUse(context, namespace_id, namespace_inst);
+  AddImportNamespaceToScopeResult result = {
+      .add_result = AddImportNamespace(context, namespace_type_id, name_id,
+                                       parent_scope_id, import_id),
+      .is_duplicate_of_namespace_in_current_package = false};
 
   // Note we have to get the parent scope freshly, creating the imported
   // namespace may invalidate the pointer above.
   parent_scope = &context.name_scopes().Get(parent_scope_id);
-
   // Diagnose if there's a name conflict, but still produce the namespace to
-  // supersede the name conflict in order to avoid repeat diagnostics. Names are
-  // poisoned optimistically by name lookup before checking for imports, so we
-  // may be overwriting a poisoned entry here.
-  auto& result = parent_scope->GetEntry(entry_id).result;
-  if (!result.is_poisoned() && !inserted) {
-    // TODO: Pass the import namespace name location instead of the namespace id
-    // to get more accurate location.
-    DiagnoseDuplicateName(context, name_id, namespace_id,
-                          result.target_inst_id());
+  // supersede the name conflict in order to avoid repeat diagnostics. Names
+  // are poisoned optimistically by name lookup before checking for imports,
+  // so we may be overwriting a poisoned entry here.
+  auto& lookup_result = parent_scope->GetEntry(entry_id).result;
+  if (!lookup_result.is_poisoned() && !inserted) {
+    // TODO: Pass the import namespace name location instead of the namespace
+    // id to get more accurate location.
+    DiagnoseDuplicateName(context, name_id, result.add_result.inst_id,
+                          lookup_result.target_inst_id());
   }
-  result = SemIR::ScopeLookupResult::MakeFound(namespace_id,
-                                               SemIR::AccessKind::Public);
-  return {.name_scope_id = namespace_inst.name_scope_id,
-          .inst_id = namespace_id,
-          .is_duplicate_of_namespace_in_current_package = false};
+  lookup_result = SemIR::ScopeLookupResult::MakeFound(
+      result.add_result.inst_id, SemIR::AccessKind::Public);
+  return result;
 }
 
 // Adds a copied namespace to the cache.
@@ -185,7 +208,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) -> AddImportNamespaceResult {
+    SemIR::NameId name_id) -> AddImportNamespaceToScopeResult {
   // Produce the namespace for the entry.
   auto make_import_id = [&]() {
     auto entity_name_id = context.entity_names().Add(
@@ -201,17 +224,18 @@ static auto CopySingleNameScopeFromImportIR(
     context.import_ref_ids().push_back(inst_id);
     return inst_id;
   };
-  AddImportNamespaceResult result = AddImportNamespace(
+  AddImportNamespaceToScopeResult result = AddImportNamespaceToScope(
       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);
+  auto namespace_const_id =
+      context.constant_values().Get(result.add_result.inst_id);
   context.import_ir_constant_values()[ir_id.index].Set(import_inst_id,
                                                        namespace_const_id);
 
   if (copied_namespaces) {
     CacheCopiedNamespace(*copied_namespaces, import_scope_id,
-                         result.name_scope_id);
+                         result.add_result.name_scope_id);
   }
   return result;
 }
@@ -261,7 +285,7 @@ static auto CopyAncestorNameScopesFromImportIR(
         CopySingleNameScopeFromImportIR(
             context, namespace_type_id, &copied_namespaces, ir_id,
             import_scope.inst_id(), import_scope_id, scope_cursor, name_id)
-            .name_scope_id;
+            .add_result.name_scope_id;
   }
 
   return scope_cursor;
@@ -432,7 +456,7 @@ auto ImportApiFile(Context& context, SemIR::TypeId namespace_type_id,
             SemIR::ImportIRId::ApiForImpl, todo_scope.api_inst_id,
             todo_scope.api_scope_id, todo_scope.impl_parent_scope_id,
             todo_scope.impl_name_id)
-            .name_scope_id;
+            .add_result.name_scope_id;
     ImportScopeFromApiFile(context, api_sem_ir, todo_scope.api_scope_id,
                            impl_scope_id, todo_scopes);
   }
@@ -496,12 +520,13 @@ auto ImportLibrariesFromOtherPackage(Context& context,
 
   auto name_id = SemIR::NameId::ForPackageName(package_id);
 
-  AddImportNamespaceResult result = AddImportNamespace(
+  AddImportNamespaceToScopeResult result = AddImportNamespaceToScope(
       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);
+  auto namespace_const_id =
+      context.constant_values().Get(result.add_result.inst_id);
 
-  auto& scope = context.name_scopes().Get(result.name_scope_id);
+  auto& scope = context.name_scopes().Get(result.add_result.name_scope_id);
   scope.set_is_closed_import(
       !result.is_duplicate_of_namespace_in_current_package);
   for (auto import_ir : import_irs) {
@@ -563,14 +588,14 @@ static auto AddNamespaceFromOtherPackage(Context& context,
     -> SemIR::InstId {
   auto namespace_type_id =
       GetSingletonType(context, SemIR::NamespaceType::SingletonInstId);
-  AddImportNamespaceResult result = CopySingleNameScopeFromImportIR(
+  AddImportNamespaceToScopeResult 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);
+  auto& scope = context.name_scopes().Get(result.add_result.name_scope_id);
   scope.set_is_closed_import(
       !result.is_duplicate_of_namespace_in_current_package);
   scope.AddImportIRScope({import_ir_id, import_ns.name_scope_id});
-  return result.inst_id;
+  return result.add_result.inst_id;
 }
 
 auto ImportNameFromOtherPackage(

+ 29 - 8
toolchain/check/import.h

@@ -12,21 +12,42 @@
 namespace Carbon::Check {
 
 struct AddImportNamespaceResult {
+  // The namespace scope id.
   SemIR::NameScopeId name_scope_id;
+
+  // The namespace instruction 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.
+// Adds a namespace to the IR. Associates the namespace with the import referred
+// in `import_id`, if any. Do not try to add the namespace `name_id` to the
+// scope and don't check if it already exists. Used when the name is added
+// afterwards.
 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;
+                        SemIR::InstId import_id) -> AddImportNamespaceResult;
+
+struct AddImportNamespaceToScopeResult {
+  AddImportNamespaceResult add_result;
+
+  // When trying to add the namespace name, whether it already exists and refers
+  // to a namespace.
+  bool is_duplicate_of_namespace_in_current_package = false;
+};
+
+// Adds a namespace to the IR. Associates the namespace with the import returned
+// from `make_import_id`, which must return a value. Tries to add the namespace
+// `name_id` to the scope. If the name already exists, diagnose only if
+// `diagnose_duplicate_namespace` is true or if it doesn't refer to a namespace.
+// `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 AddImportNamespaceToScope(
+    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)
+    -> AddImportNamespaceToScopeResult;
 
 // Imports the API file's name lookup information into a corresponding
 // implementation file. Only information for the current package will be copied;

+ 32 - 10
toolchain/check/import_cpp.cpp

@@ -22,6 +22,7 @@
 #include "toolchain/check/type.h"
 #include "toolchain/diagnostics/diagnostic.h"
 #include "toolchain/diagnostics/format_providers.h"
+#include "toolchain/parse/node_ids.h"
 #include "toolchain/sem_ir/ids.h"
 #include "toolchain/sem_ir/name_scope.h"
 
@@ -102,7 +103,7 @@ static auto AddNamespace(Context& context, PackageNameId cpp_package_id,
         {.node_id = import.node_id, .library_id = import.library_id});
   }
 
-  return AddImportNamespace(
+  return AddImportNamespaceToScope(
              context,
              GetSingletonType(context, SemIR::NamespaceType::SingletonInstId),
              SemIR::NameId::ForPackageName(cpp_package_id),
@@ -112,7 +113,7 @@ static auto AddNamespace(Context& context, PackageNameId cpp_package_id,
                return AddInst<SemIR::ImportCppDecl>(
                    context, imports.front().node_id, {});
              })
-      .name_scope_id;
+      .add_result.name_scope_id;
 }
 
 auto ImportCppFiles(Context& context, llvm::StringRef importing_file_path,
@@ -136,7 +137,8 @@ auto ImportCppFiles(Context& context, llvm::StringRef importing_file_path,
   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);
-  name_scope.set_is_cpp_scope(true);
+  name_scope.set_cpp_decl_context(
+      generated_ast->getASTContext().getTranslationUnitDecl());
 
   context.sem_ir().set_cpp_ast(generated_ast.get());
 
@@ -147,10 +149,10 @@ auto ImportCppFiles(Context& context, llvm::StringRef importing_file_path,
   return std::move(generated_ast);
 }
 
-// Look ups the given name in the Clang AST. Returns the lookup result if lookup
-// was successful.
+// Look ups the given name in the Clang AST in a specific scope. Returns the
+// lookup result if lookup was successful.
 static auto ClangLookup(Context& context, SemIR::LocId loc_id,
-                        SemIR::NameId name_id)
+                        SemIR::NameScopeId scope_id, SemIR::NameId name_id)
     -> std::optional<clang::LookupResult> {
   std::optional<llvm::StringRef> name =
       context.names().GetAsStringIfIdentifier(name_id);
@@ -172,7 +174,7 @@ static auto ClangLookup(Context& context, SemIR::LocId loc_id,
       clang::Sema::LookupNameKind::LookupOrdinaryName);
 
   bool found = sema.LookupQualifiedName(
-      lookup, ast->getASTContext().getTranslationUnitDecl());
+      lookup, context.name_scopes().Get(scope_id).cpp_decl_context());
 
   if (lookup.isClassLookup()) {
     // TODO: To support class lookup, also return the AccessKind for storage.
@@ -250,17 +252,37 @@ static auto ImportFunctionDecl(Context& context, SemIR::LocId loc_id,
   return decl_id;
 }
 
+// Imports a namespace declaration from Clang to Carbon. If successful, returns
+// the new Carbon namespace declaration `InstId`.
+static auto ImportNamespaceDecl(Context& context,
+                                SemIR::NameScopeId parent_scope_id,
+                                SemIR::NameId name_id,
+                                clang::NamespaceDecl* clang_decl)
+    -> SemIR::InstId {
+  auto result = AddImportNamespace(
+      context, GetSingletonType(context, SemIR::NamespaceType::SingletonInstId),
+      name_id, parent_scope_id, /*import_id=*/SemIR::InstId::None);
+  context.name_scopes()
+      .Get(result.name_scope_id)
+      .set_cpp_decl_context(clang_decl);
+  return result.inst_id;
+}
+
 // Imports a declaration from Clang to Carbon. If successful, returns the
 // instruction for the new Carbon declaration.
 static auto ImportNameDecl(Context& context, SemIR::LocId loc_id,
                            SemIR::NameScopeId scope_id, SemIR::NameId name_id,
-                           const clang::NamedDecl* clang_decl)
-    -> SemIR::InstId {
+                           clang::NamedDecl* clang_decl) -> SemIR::InstId {
   if (const auto* clang_function_decl =
           clang::dyn_cast<clang::FunctionDecl>(clang_decl)) {
     return ImportFunctionDecl(context, loc_id, scope_id, name_id,
                               clang_function_decl);
   }
+  if (auto* clang_namespace_decl =
+          clang::dyn_cast<clang::NamespaceDecl>(clang_decl)) {
+    return ImportNamespaceDecl(context, scope_id, name_id,
+                               clang_namespace_decl);
+  }
 
   context.TODO(loc_id, llvm::formatv("Unsupported: Declaration type {0}",
                                      clang_decl->getDeclKindName())
@@ -271,7 +293,7 @@ static auto ImportNameDecl(Context& context, SemIR::LocId loc_id,
 auto ImportNameFromCpp(Context& context, SemIR::LocId loc_id,
                        SemIR::NameScopeId scope_id, SemIR::NameId name_id)
     -> SemIR::InstId {
-  auto lookup = ClangLookup(context, loc_id, name_id);
+  auto lookup = ClangLookup(context, loc_id, scope_id, name_id);
   if (!lookup) {
     return SemIR::InstId::None;
   }

+ 3 - 1
toolchain/check/name_lookup.cpp

@@ -181,7 +181,9 @@ auto LookupNameInExactScope(Context& context, SemIR::LocId loc_id,
     if (imported_inst_id.has_value()) {
       SemIR::ScopeLookupResult result = SemIR::ScopeLookupResult::MakeFound(
           imported_inst_id, SemIR::AccessKind::Public);
-      scope.AddRequired({.name_id = name_id, .result = result});
+      // `ImportNameFromCpp()` can invalidate `scope`, so we do a scope lookup.
+      context.name_scopes().Get(scope_id).AddRequired(
+          {.name_id = name_id, .result = result});
       return result;
     }
   }

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

@@ -58,6 +58,7 @@ import Cpp library "\"foo.h\"";
 // CHECK:STDOUT:
 // CHECK:STDOUT: imports {
 // CHECK:STDOUT:   %Cpp: <namespace> = namespace file.%Cpp.import_cpp, [concrete] {
+// CHECK:STDOUT:     import Cpp//...
 // CHECK:STDOUT:     has_error
 // CHECK:STDOUT:   }
 // CHECK:STDOUT: }

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

@@ -219,6 +219,7 @@ import Cpp library "multiple_errors_and_multiple_warnings.h";
 // CHECK:STDOUT:
 // CHECK:STDOUT: imports {
 // CHECK:STDOUT:   %Cpp: <namespace> = namespace file.%Cpp.import_cpp, [concrete] {
+// CHECK:STDOUT:     import Cpp//...
 // CHECK:STDOUT:     has_error
 // CHECK:STDOUT:   }
 // CHECK:STDOUT: }
@@ -236,6 +237,7 @@ import Cpp library "multiple_errors_and_multiple_warnings.h";
 // CHECK:STDOUT:
 // CHECK:STDOUT: imports {
 // CHECK:STDOUT:   %Cpp: <namespace> = namespace file.%Cpp.import_cpp, [concrete] {
+// CHECK:STDOUT:     import Cpp//...
 // CHECK:STDOUT:     has_error
 // CHECK:STDOUT:   }
 // CHECK:STDOUT: }
@@ -252,7 +254,9 @@ import Cpp library "multiple_errors_and_multiple_warnings.h";
 // CHECK:STDOUT: --- import_cpp_file_with_one_warning.carbon
 // CHECK:STDOUT:
 // CHECK:STDOUT: imports {
-// CHECK:STDOUT:   %Cpp: <namespace> = namespace file.%Cpp.import_cpp, [concrete] {}
+// CHECK:STDOUT:   %Cpp: <namespace> = namespace file.%Cpp.import_cpp, [concrete] {
+// CHECK:STDOUT:     import Cpp//...
+// CHECK:STDOUT:   }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: file {
@@ -267,7 +271,9 @@ import Cpp library "multiple_errors_and_multiple_warnings.h";
 // CHECK:STDOUT: --- import_cpp_file_with_multiple_warnings.carbon
 // CHECK:STDOUT:
 // CHECK:STDOUT: imports {
-// CHECK:STDOUT:   %Cpp: <namespace> = namespace file.%Cpp.import_cpp, [concrete] {}
+// CHECK:STDOUT:   %Cpp: <namespace> = namespace file.%Cpp.import_cpp, [concrete] {
+// CHECK:STDOUT:     import Cpp//...
+// CHECK:STDOUT:   }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: file {
@@ -283,6 +289,7 @@ import Cpp library "multiple_errors_and_multiple_warnings.h";
 // CHECK:STDOUT:
 // CHECK:STDOUT: imports {
 // CHECK:STDOUT:   %Cpp: <namespace> = namespace file.%Cpp.import_cpp, [concrete] {
+// CHECK:STDOUT:     import Cpp//...
 // CHECK:STDOUT:     has_error
 // CHECK:STDOUT:   }
 // CHECK:STDOUT: }
@@ -300,6 +307,7 @@ import Cpp library "multiple_errors_and_multiple_warnings.h";
 // CHECK:STDOUT:
 // CHECK:STDOUT: imports {
 // CHECK:STDOUT:   %Cpp: <namespace> = namespace file.%Cpp.import_cpp, [concrete] {
+// CHECK:STDOUT:     import Cpp//...
 // CHECK:STDOUT:     has_error
 // CHECK:STDOUT:   }
 // CHECK:STDOUT: }
@@ -316,7 +324,9 @@ import Cpp library "multiple_errors_and_multiple_warnings.h";
 // CHECK:STDOUT: --- import_multiple_cpp_files_with_warnings.carbon
 // CHECK:STDOUT:
 // CHECK:STDOUT: imports {
-// CHECK:STDOUT:   %Cpp: <namespace> = namespace file.%Cpp.import_cpp, [concrete] {}
+// CHECK:STDOUT:   %Cpp: <namespace> = namespace file.%Cpp.import_cpp, [concrete] {
+// CHECK:STDOUT:     import Cpp//...
+// CHECK:STDOUT:   }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: file {
@@ -333,6 +343,7 @@ import Cpp library "multiple_errors_and_multiple_warnings.h";
 // CHECK:STDOUT:
 // CHECK:STDOUT: imports {
 // CHECK:STDOUT:   %Cpp: <namespace> = namespace file.%Cpp.import_cpp, [concrete] {
+// CHECK:STDOUT:     import Cpp//...
 // CHECK:STDOUT:     has_error
 // CHECK:STDOUT:   }
 // CHECK:STDOUT: }

+ 19 - 6
toolchain/check/testdata/interop/cpp/no_prelude/cpp_namespace.carbon

@@ -72,7 +72,9 @@ 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, [concrete] {}
+// CHECK:STDOUT:   %Cpp: <namespace> = namespace file.%Cpp.import_cpp, [concrete] {
+// CHECK:STDOUT:     import Cpp//...
+// CHECK:STDOUT:   }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: file {
@@ -82,13 +84,17 @@ import Cpp library "header.h";
 // CHECK:STDOUT:   %Cpp.import_cpp = import_cpp {
 // CHECK:STDOUT:     import Cpp "header.h"
 // CHECK:STDOUT:   }
-// CHECK:STDOUT:   %Cpp: <namespace> = namespace [concrete] {}
+// CHECK:STDOUT:   %Cpp: <namespace> = namespace [concrete] {
+// CHECK:STDOUT:     import Cpp//...
+// CHECK:STDOUT:   }
 // 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, [concrete] {}
+// CHECK:STDOUT:   %Cpp: <namespace> = namespace file.%Cpp.import_cpp, [concrete] {
+// CHECK:STDOUT:     import Cpp//...
+// CHECK:STDOUT:   }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: file {
@@ -108,7 +114,9 @@ import Cpp library "header.h";
 // CHECK:STDOUT: --- alias.carbon
 // CHECK:STDOUT:
 // CHECK:STDOUT: imports {
-// CHECK:STDOUT:   %Cpp: <namespace> = namespace file.%Cpp.import_cpp, [concrete] {}
+// CHECK:STDOUT:   %Cpp: <namespace> = namespace file.%Cpp.import_cpp, [concrete] {
+// CHECK:STDOUT:     import Cpp//...
+// CHECK:STDOUT:   }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: file {
@@ -134,6 +142,7 @@ import Cpp library "header.h";
 // CHECK:STDOUT: imports {
 // CHECK:STDOUT:   %Cpp: <namespace> = namespace file.%Cpp.import_cpp, [concrete] {
 // CHECK:STDOUT:     .C = file.%C.decl
+// CHECK:STDOUT:     import Cpp//...
 // CHECK:STDOUT:   }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
@@ -158,7 +167,9 @@ import Cpp library "header.h";
 // CHECK:STDOUT: --- api_and_impl.carbon
 // CHECK:STDOUT:
 // CHECK:STDOUT: imports {
-// CHECK:STDOUT:   %Cpp: <namespace> = namespace file.%Cpp.import_cpp, [concrete] {}
+// CHECK:STDOUT:   %Cpp: <namespace> = namespace file.%Cpp.import_cpp, [concrete] {
+// CHECK:STDOUT:     import Cpp//...
+// CHECK:STDOUT:   }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: file {
@@ -174,7 +185,9 @@ import Cpp library "header.h";
 // 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, [concrete] {}
+// CHECK:STDOUT:   %Cpp: <namespace> = namespace %Main.Cpp, [concrete] {
+// CHECK:STDOUT:     import Cpp//...
+// CHECK:STDOUT:   }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: file {

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

@@ -26,6 +26,7 @@ import Cpp library "not_found.h";
 // CHECK:STDOUT:
 // CHECK:STDOUT: imports {
 // CHECK:STDOUT:   %Cpp: <namespace> = namespace file.%Cpp.import_cpp, [concrete] {
+// CHECK:STDOUT:     import Cpp//...
 // CHECK:STDOUT:     has_error
 // CHECK:STDOUT:   }
 // CHECK:STDOUT: }

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

@@ -186,6 +186,7 @@ fn F() {
 // CHECK:STDOUT: imports {
 // CHECK:STDOUT:   %Cpp: <namespace> = namespace file.%Cpp.import_cpp, [concrete] {
 // CHECK:STDOUT:     .foo = @MyF.%foo.decl
+// CHECK:STDOUT:     import Cpp//...
 // CHECK:STDOUT:   }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
@@ -221,6 +222,7 @@ fn F() {
 // CHECK:STDOUT: imports {
 // CHECK:STDOUT:   %Cpp: <namespace> = namespace file.%Cpp.import_cpp, [concrete] {
 // CHECK:STDOUT:     .bar = <poisoned>
+// CHECK:STDOUT:     import Cpp//...
 // CHECK:STDOUT:   }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
@@ -250,7 +252,9 @@ fn F() {
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: imports {
-// CHECK:STDOUT:   %Cpp: <namespace> = namespace file.%Cpp.import_cpp, [concrete] {}
+// CHECK:STDOUT:   %Cpp: <namespace> = namespace file.%Cpp.import_cpp, [concrete] {
+// CHECK:STDOUT:     import Cpp//...
+// CHECK:STDOUT:   }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: file {
@@ -284,6 +288,7 @@ fn F() {
 // CHECK:STDOUT: imports {
 // CHECK:STDOUT:   %Cpp: <namespace> = namespace file.%Cpp.import_cpp, [concrete] {
 // CHECK:STDOUT:     .r#base = @MyF.%base.decl
+// CHECK:STDOUT:     import Cpp//...
 // CHECK:STDOUT:   }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
@@ -319,6 +324,7 @@ fn F() {
 // CHECK:STDOUT: imports {
 // CHECK:STDOUT:   %Cpp: <namespace> = namespace file.%Cpp.import_cpp, [concrete] {
 // CHECK:STDOUT:     .foo = <error>
+// CHECK:STDOUT:     import Cpp//...
 // CHECK:STDOUT:   }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
@@ -350,6 +356,7 @@ fn F() {
 // CHECK:STDOUT: imports {
 // CHECK:STDOUT:   %Cpp: <namespace> = namespace file.%Cpp.import_cpp, [concrete] {
 // CHECK:STDOUT:     .foo = <error>
+// CHECK:STDOUT:     import Cpp//...
 // CHECK:STDOUT:   }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
@@ -381,6 +388,7 @@ fn F() {
 // CHECK:STDOUT: imports {
 // CHECK:STDOUT:   %Cpp: <namespace> = namespace file.%Cpp.import_cpp, [concrete] {
 // CHECK:STDOUT:     .foo = <error>
+// CHECK:STDOUT:     import Cpp//...
 // CHECK:STDOUT:   }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
@@ -412,6 +420,7 @@ fn F() {
 // CHECK:STDOUT: imports {
 // CHECK:STDOUT:   %Cpp: <namespace> = namespace file.%Cpp.import_cpp, [concrete] {
 // CHECK:STDOUT:     .foo = <error>
+// CHECK:STDOUT:     import Cpp//...
 // CHECK:STDOUT:   }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
@@ -443,6 +452,7 @@ fn F() {
 // CHECK:STDOUT: imports {
 // CHECK:STDOUT:   %Cpp: <namespace> = namespace file.%Cpp.import_cpp, [concrete] {
 // CHECK:STDOUT:     .foo = <error>
+// CHECK:STDOUT:     import Cpp//...
 // CHECK:STDOUT:   }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:

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

@@ -25,7 +25,9 @@ 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, [concrete] {}
+// CHECK:STDOUT:   %Cpp: <namespace> = namespace file.%Cpp.import_cpp, [concrete] {
+// CHECK:STDOUT:     import Cpp//...
+// CHECK:STDOUT:   }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: file {

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

@@ -26,7 +26,9 @@ import Cpp library "file2.h";
 // CHECK:STDOUT: --- multiple_imports.carbon
 // CHECK:STDOUT:
 // CHECK:STDOUT: imports {
-// CHECK:STDOUT:   %Cpp: <namespace> = namespace file.%Cpp.import_cpp, [concrete] {}
+// CHECK:STDOUT:   %Cpp: <namespace> = namespace file.%Cpp.import_cpp, [concrete] {
+// CHECK:STDOUT:     import Cpp//...
+// CHECK:STDOUT:   }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: file {

+ 369 - 0
toolchain/check/testdata/interop/cpp/no_prelude/namespace.carbon

@@ -0,0 +1,369 @@
+// 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/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/namespace.carbon
+
+// --- namespace.h
+
+namespace my_namespace { void foo(); }
+
+// --- import_namespace.carbon
+
+library "[[@TEST_NAME]]";
+
+import Cpp library "namespace.h";
+
+fn MyF() {
+  Cpp.my_namespace.foo();
+}
+
+// --- multiple_namespaces.h
+
+namespace my_namespace1 {
+  void foo1();
+  namespace my_namespace2 {
+    void foo2();
+    namespace my_namespace3 {
+      void foo3();
+    }
+  }
+}
+
+// --- import_multiple_namespaces.carbon
+
+library "[[@TEST_NAME]]";
+
+import Cpp library "multiple_namespaces.h";
+
+fn MyF() {
+  Cpp.my_namespace1.foo1();
+  Cpp.my_namespace1.my_namespace2.foo2();
+  Cpp.my_namespace1.my_namespace2.my_namespace3.foo3();
+}
+
+// --- fail_import_namespace_wrong_name_in_namespace.carbon
+
+library "[[@TEST_NAME]]";
+
+import Cpp library "namespace.h";
+
+fn MyF() {
+  // CHECK:STDERR: fail_import_namespace_wrong_name_in_namespace.carbon:[[@LINE+4]]:3: error: member name `not_foo` not found in `Cpp.my_namespace` [MemberNameNotFoundInScope]
+  // CHECK:STDERR:   Cpp.my_namespace.not_foo();
+  // CHECK:STDERR:   ^~~~~~~~~~~~~~~~~~~~~~~~
+  // CHECK:STDERR:
+  Cpp.my_namespace.not_foo();
+}
+
+// --- fail_import_namespace_use_different_name.carbon
+
+library "[[@TEST_NAME]]";
+
+import Cpp library "namespace.h";
+
+fn MyF() {
+  // CHECK:STDERR: fail_import_namespace_use_different_name.carbon:[[@LINE+4]]:3: error: member name `not_my_namespace` not found in `Cpp` [MemberNameNotFoundInScope]
+  // CHECK:STDERR:   Cpp.not_my_namespace.foo();
+  // CHECK:STDERR:   ^~~~~~~~~~~~~~~~~~~~
+  // CHECK:STDERR:
+  Cpp.not_my_namespace.foo();
+}
+
+// --- namespace_special_name_decl.h
+
+namespace base { void foo(); }
+
+// --- fail_import_namespace_special_name_decl.carbon
+
+library "[[@TEST_NAME]]";
+
+import Cpp library "namespace_special_name_decl.h";
+
+fn MyF() {
+  // CHECK:STDERR: fail_import_namespace_special_name_decl.carbon:[[@LINE+4]]:3: error: member name `base` not found in `Cpp` [MemberNameNotFoundInScope]
+  // CHECK:STDERR:   Cpp.base.foo();
+  // CHECK:STDERR:   ^~~~~~~~
+  // CHECK:STDERR:
+  Cpp.base.foo();
+}
+
+// --- import_namespace_escaped_special_name_decl.carbon
+
+library "[[@TEST_NAME]]";
+
+import Cpp library "namespace_special_name_decl.h";
+
+fn MyF() {
+  Cpp.r#base.foo();
+}
+
+// CHECK:STDOUT: --- import_namespace.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %MyF.type: type = fn_type @MyF [concrete]
+// CHECK:STDOUT:   %empty_tuple.type: type = tuple_type () [concrete]
+// CHECK:STDOUT:   %MyF: %MyF.type = struct_value () [concrete]
+// CHECK:STDOUT:   %foo.type: type = fn_type @foo [concrete]
+// CHECK:STDOUT:   %foo: %foo.type = struct_value () [concrete]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: imports {
+// CHECK:STDOUT:   %Cpp: <namespace> = namespace file.%Cpp.import_cpp, [concrete] {
+// CHECK:STDOUT:     .my_namespace = %my_namespace
+// CHECK:STDOUT:     import Cpp//...
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %my_namespace: <namespace> = namespace [concrete] {
+// CHECK:STDOUT:     .foo = @MyF.%foo.decl
+// CHECK:STDOUT:     import Cpp//...
+// CHECK:STDOUT:   }
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [concrete] {
+// CHECK:STDOUT:     .Cpp = imports.%Cpp
+// CHECK:STDOUT:     .MyF = %MyF.decl
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Cpp.import_cpp = import_cpp {
+// CHECK:STDOUT:     import Cpp "namespace.h"
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %MyF.decl: %MyF.type = fn_decl @MyF [concrete = constants.%MyF] {} {}
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @MyF() {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %Cpp.ref: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
+// CHECK:STDOUT:   %my_namespace.ref: <namespace> = name_ref my_namespace, imports.%my_namespace [concrete = imports.%my_namespace]
+// CHECK:STDOUT:   %foo.decl: %foo.type = fn_decl @foo [concrete = constants.%foo] {} {}
+// CHECK:STDOUT:   %foo.ref: %foo.type = name_ref foo, %foo.decl [concrete = constants.%foo]
+// CHECK:STDOUT:   %foo.call: init %empty_tuple.type = call %foo.ref()
+// CHECK:STDOUT:   return
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @foo[]();
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- import_multiple_namespaces.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %MyF.type: type = fn_type @MyF [concrete]
+// CHECK:STDOUT:   %empty_tuple.type: type = tuple_type () [concrete]
+// CHECK:STDOUT:   %MyF: %MyF.type = struct_value () [concrete]
+// CHECK:STDOUT:   %foo1.type: type = fn_type @foo1 [concrete]
+// CHECK:STDOUT:   %foo1: %foo1.type = struct_value () [concrete]
+// CHECK:STDOUT:   %foo2.type: type = fn_type @foo2 [concrete]
+// CHECK:STDOUT:   %foo2: %foo2.type = struct_value () [concrete]
+// CHECK:STDOUT:   %foo3.type: type = fn_type @foo3 [concrete]
+// CHECK:STDOUT:   %foo3: %foo3.type = struct_value () [concrete]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: imports {
+// CHECK:STDOUT:   %Cpp: <namespace> = namespace file.%Cpp.import_cpp, [concrete] {
+// CHECK:STDOUT:     .my_namespace1 = %my_namespace1
+// CHECK:STDOUT:     import Cpp//...
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %my_namespace1: <namespace> = namespace [concrete] {
+// CHECK:STDOUT:     .foo1 = @MyF.%foo1.decl
+// CHECK:STDOUT:     .my_namespace2 = %my_namespace2
+// CHECK:STDOUT:     import Cpp//...
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %my_namespace2: <namespace> = namespace [concrete] {
+// CHECK:STDOUT:     .foo2 = @MyF.%foo2.decl
+// CHECK:STDOUT:     .my_namespace3 = %my_namespace3
+// CHECK:STDOUT:     import Cpp//...
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %my_namespace3: <namespace> = namespace [concrete] {
+// CHECK:STDOUT:     .foo3 = @MyF.%foo3.decl
+// CHECK:STDOUT:     import Cpp//...
+// CHECK:STDOUT:   }
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [concrete] {
+// CHECK:STDOUT:     .Cpp = imports.%Cpp
+// CHECK:STDOUT:     .MyF = %MyF.decl
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Cpp.import_cpp = import_cpp {
+// CHECK:STDOUT:     import Cpp "multiple_namespaces.h"
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %MyF.decl: %MyF.type = fn_decl @MyF [concrete = constants.%MyF] {} {}
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @MyF() {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %Cpp.ref.loc7: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
+// CHECK:STDOUT:   %my_namespace1.ref.loc7: <namespace> = name_ref my_namespace1, imports.%my_namespace1 [concrete = imports.%my_namespace1]
+// CHECK:STDOUT:   %foo1.decl: %foo1.type = fn_decl @foo1 [concrete = constants.%foo1] {} {}
+// CHECK:STDOUT:   %foo1.ref: %foo1.type = name_ref foo1, %foo1.decl [concrete = constants.%foo1]
+// CHECK:STDOUT:   %foo1.call: init %empty_tuple.type = call %foo1.ref()
+// CHECK:STDOUT:   %Cpp.ref.loc8: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
+// CHECK:STDOUT:   %my_namespace1.ref.loc8: <namespace> = name_ref my_namespace1, imports.%my_namespace1 [concrete = imports.%my_namespace1]
+// CHECK:STDOUT:   %my_namespace2.ref.loc8: <namespace> = name_ref my_namespace2, imports.%my_namespace2 [concrete = imports.%my_namespace2]
+// CHECK:STDOUT:   %foo2.decl: %foo2.type = fn_decl @foo2 [concrete = constants.%foo2] {} {}
+// CHECK:STDOUT:   %foo2.ref: %foo2.type = name_ref foo2, %foo2.decl [concrete = constants.%foo2]
+// CHECK:STDOUT:   %foo2.call: init %empty_tuple.type = call %foo2.ref()
+// CHECK:STDOUT:   %Cpp.ref.loc9: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
+// CHECK:STDOUT:   %my_namespace1.ref.loc9: <namespace> = name_ref my_namespace1, imports.%my_namespace1 [concrete = imports.%my_namespace1]
+// CHECK:STDOUT:   %my_namespace2.ref.loc9: <namespace> = name_ref my_namespace2, imports.%my_namespace2 [concrete = imports.%my_namespace2]
+// CHECK:STDOUT:   %my_namespace3.ref: <namespace> = name_ref my_namespace3, imports.%my_namespace3 [concrete = imports.%my_namespace3]
+// CHECK:STDOUT:   %foo3.decl: %foo3.type = fn_decl @foo3 [concrete = constants.%foo3] {} {}
+// CHECK:STDOUT:   %foo3.ref: %foo3.type = name_ref foo3, %foo3.decl [concrete = constants.%foo3]
+// CHECK:STDOUT:   %foo3.call: init %empty_tuple.type = call %foo3.ref()
+// CHECK:STDOUT:   return
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @foo1[]();
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @foo2[]();
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @foo3[]();
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- fail_import_namespace_wrong_name_in_namespace.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %MyF.type: type = fn_type @MyF [concrete]
+// CHECK:STDOUT:   %MyF: %MyF.type = struct_value () [concrete]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: imports {
+// CHECK:STDOUT:   %Cpp: <namespace> = namespace file.%Cpp.import_cpp, [concrete] {
+// CHECK:STDOUT:     .my_namespace = %my_namespace
+// CHECK:STDOUT:     import Cpp//...
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %my_namespace: <namespace> = namespace [concrete] {
+// CHECK:STDOUT:     .not_foo = <poisoned>
+// CHECK:STDOUT:     import Cpp//...
+// CHECK:STDOUT:   }
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [concrete] {
+// CHECK:STDOUT:     .Cpp = imports.%Cpp
+// CHECK:STDOUT:     .MyF = %MyF.decl
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Cpp.import_cpp = import_cpp {
+// CHECK:STDOUT:     import Cpp "namespace.h"
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %MyF.decl: %MyF.type = fn_decl @MyF [concrete = constants.%MyF] {} {}
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @MyF() {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %Cpp.ref: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
+// CHECK:STDOUT:   %my_namespace.ref: <namespace> = name_ref my_namespace, imports.%my_namespace [concrete = imports.%my_namespace]
+// CHECK:STDOUT:   %not_foo.ref: <error> = name_ref not_foo, <error> [concrete = <error>]
+// CHECK:STDOUT:   return
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- fail_import_namespace_use_different_name.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %MyF.type: type = fn_type @MyF [concrete]
+// CHECK:STDOUT:   %MyF: %MyF.type = struct_value () [concrete]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: imports {
+// CHECK:STDOUT:   %Cpp: <namespace> = namespace file.%Cpp.import_cpp, [concrete] {
+// CHECK:STDOUT:     .not_my_namespace = <poisoned>
+// CHECK:STDOUT:     import Cpp//...
+// CHECK:STDOUT:   }
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [concrete] {
+// CHECK:STDOUT:     .Cpp = imports.%Cpp
+// CHECK:STDOUT:     .MyF = %MyF.decl
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Cpp.import_cpp = import_cpp {
+// CHECK:STDOUT:     import Cpp "namespace.h"
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %MyF.decl: %MyF.type = fn_decl @MyF [concrete = constants.%MyF] {} {}
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @MyF() {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %Cpp.ref: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
+// CHECK:STDOUT:   %not_my_namespace.ref: <error> = name_ref not_my_namespace, <error> [concrete = <error>]
+// CHECK:STDOUT:   %foo.ref: <error> = name_ref foo, <error> [concrete = <error>]
+// CHECK:STDOUT:   return
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- fail_import_namespace_special_name_decl.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %MyF.type: type = fn_type @MyF [concrete]
+// CHECK:STDOUT:   %MyF: %MyF.type = struct_value () [concrete]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: imports {
+// CHECK:STDOUT:   %Cpp: <namespace> = namespace file.%Cpp.import_cpp, [concrete] {
+// CHECK:STDOUT:     import Cpp//...
+// CHECK:STDOUT:   }
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [concrete] {
+// CHECK:STDOUT:     .Cpp = imports.%Cpp
+// CHECK:STDOUT:     .MyF = %MyF.decl
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Cpp.import_cpp = import_cpp {
+// CHECK:STDOUT:     import Cpp "namespace_special_name_decl.h"
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %MyF.decl: %MyF.type = fn_decl @MyF [concrete = constants.%MyF] {} {}
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @MyF() {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %Cpp.ref: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
+// CHECK:STDOUT:   %base.ref: <error> = name_ref base, <error> [concrete = <error>]
+// CHECK:STDOUT:   %foo.ref: <error> = name_ref foo, <error> [concrete = <error>]
+// CHECK:STDOUT:   return
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- import_namespace_escaped_special_name_decl.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %MyF.type: type = fn_type @MyF [concrete]
+// CHECK:STDOUT:   %empty_tuple.type: type = tuple_type () [concrete]
+// CHECK:STDOUT:   %MyF: %MyF.type = struct_value () [concrete]
+// CHECK:STDOUT:   %foo.type: type = fn_type @foo [concrete]
+// CHECK:STDOUT:   %foo: %foo.type = struct_value () [concrete]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: imports {
+// CHECK:STDOUT:   %Cpp: <namespace> = namespace file.%Cpp.import_cpp, [concrete] {
+// CHECK:STDOUT:     .r#base = %base
+// CHECK:STDOUT:     import Cpp//...
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %base: <namespace> = namespace [concrete] {
+// CHECK:STDOUT:     .foo = @MyF.%foo.decl
+// CHECK:STDOUT:     import Cpp//...
+// CHECK:STDOUT:   }
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [concrete] {
+// CHECK:STDOUT:     .Cpp = imports.%Cpp
+// CHECK:STDOUT:     .MyF = %MyF.decl
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Cpp.import_cpp = import_cpp {
+// CHECK:STDOUT:     import Cpp "namespace_special_name_decl.h"
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %MyF.decl: %MyF.type = fn_decl @MyF [concrete = constants.%MyF] {} {}
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @MyF() {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %Cpp.ref: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
+// CHECK:STDOUT:   %base.ref: <namespace> = name_ref r#base, imports.%base [concrete = imports.%base]
+// CHECK:STDOUT:   %foo.decl: %foo.type = fn_decl @foo [concrete = constants.%foo] {} {}
+// CHECK:STDOUT:   %foo.ref: %foo.type = name_ref foo, %foo.decl [concrete = constants.%foo]
+// CHECK:STDOUT:   %foo.call: init %empty_tuple.type = call %foo.ref()
+// CHECK:STDOUT:   return
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @foo[]();
+// CHECK:STDOUT:

+ 1 - 0
toolchain/check/testdata/interop/cpp/no_prelude/unsupported_decl_type.carbon

@@ -43,6 +43,7 @@ fn F() {
 // CHECK:STDOUT: imports {
 // CHECK:STDOUT:   %Cpp: <namespace> = namespace file.%Cpp.import_cpp, [concrete] {
 // CHECK:STDOUT:     .foo = <poisoned>
+// CHECK:STDOUT:     import Cpp//...
 // CHECK:STDOUT:   }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:

+ 7 - 1
toolchain/sem_ir/formatter.cpp

@@ -663,7 +663,8 @@ class FormatterImpl {
     const auto& scope = sem_ir_->name_scopes().Get(id);
 
     if (scope.entries().empty() && scope.extended_scopes().empty() &&
-        scope.import_ir_scopes().empty() && !scope.has_error()) {
+        scope.import_ir_scopes().empty() && !scope.is_cpp_scope() &&
+        !scope.has_error()) {
       // Name scope is empty.
       return;
     }
@@ -723,6 +724,11 @@ class FormatterImpl {
       out_ << "import " << label << "\n";
     }
 
+    if (scope.is_cpp_scope()) {
+      Indent();
+      out_ << "import Cpp//...\n";
+    }
+
     if (scope.has_error()) {
       Indent();
       out_ << "has_error\n";

+ 16 - 5
toolchain/sem_ir/name_scope.h

@@ -5,6 +5,7 @@
 #ifndef CARBON_TOOLCHAIN_SEM_IR_NAME_SCOPE_H_
 #define CARBON_TOOLCHAIN_SEM_IR_NAME_SCOPE_H_
 
+#include "clang/AST/DeclBase.h"
 #include "common/map.h"
 #include "toolchain/sem_ir/ids.h"
 #include "toolchain/sem_ir/inst.h"
@@ -212,10 +213,15 @@ class NameScope : public Printable<NameScope> {
     is_closed_import_ = is_closed_import;
   }
 
-  auto is_cpp_scope() const -> bool { return is_cpp_scope_; }
+  auto is_cpp_scope() const -> bool { return cpp_decl_context(); }
 
-  auto set_is_cpp_scope(bool is_cpp_scope) -> void {
-    is_cpp_scope_ = is_cpp_scope;
+  auto cpp_decl_context() const -> const clang::DeclContext* {
+    return cpp_decl_context_;
+  }
+  auto cpp_decl_context() -> clang::DeclContext* { return cpp_decl_context_; }
+
+  auto set_cpp_decl_context(clang::DeclContext* cpp_decl_context) -> void {
+    cpp_decl_context_ = cpp_decl_context;
   }
 
   // Returns true if this name scope describes an imported package.
@@ -288,8 +294,13 @@ class NameScope : public Printable<NameScope> {
   // True if this is a closed namespace created by importing a package.
   bool is_closed_import_ = false;
 
-  // True if this is the `Cpp` namescope used when importing C++ code.
-  bool is_cpp_scope_ = false;
+  // Set if this is the `Cpp` scope or a scope inside `Cpp`. Points to the
+  // matching Clang declaration context to look for names. This is mutable since
+  // `clang::Sema::LookupQualifiedName()` requires a mutable `DeclContext`.
+  // TODO: Ensure we can easily serialize/deserialize this. Consider decl ID to
+  // point into the AST. This is related to:
+  // https://github.com/carbon-language/carbon-lang/issues/4666.
+  clang::DeclContext* cpp_decl_context_ = nullptr;
 
   // True if this is the scope of an interface definition, where associated
   // entities will be bound to the interface's `Self` symbolic type.