فهرست منبع

Only export each class or namespace to C++ once. (#7042)

Instead of exporting a class or namespace each time a new C++ name
lookup discovers it, track that we have exported the entity on its name
scope, and if a new name lookup finds the same entity, produce the same
clang declaration.
Richard Smith 3 هفته پیش
والد
کامیت
f31e1685fd

+ 163 - 4
toolchain/check/cpp/export.cpp

@@ -14,6 +14,159 @@
 
 namespace Carbon::Check {
 
+// If the given name scope was produced by importing a C++ declaration or has
+// already been exported to C++, return the corresponding Clang decl context.
+static auto GetClangDeclContextForScope(Context& context,
+                                        SemIR::NameScopeId scope_id)
+    -> clang::DeclContext* {
+  if (!scope_id.has_value()) {
+    return nullptr;
+  }
+  auto& scope = context.name_scopes().Get(scope_id);
+  auto clang_decl_context_id = scope.clang_decl_context_id();
+  if (!clang_decl_context_id.has_value()) {
+    return nullptr;
+  }
+  auto* decl = context.clang_decls().Get(clang_decl_context_id).key.decl;
+  return cast<clang::DeclContext>(decl);
+}
+
+auto ExportNameScopeToCpp(Context& context, SemIR::LocId loc_id,
+                          SemIR::NameScopeId name_scope_id)
+    -> clang::DeclContext* {
+  llvm::SmallVector<SemIR::NameScopeId> name_scope_ids_to_create;
+
+  // Walk through the parent scopes, looking for one that's already mapped into
+  // C++. We already mapped the package scope to ::Carbon, so we must find one.
+  clang::DeclContext* decl_context = nullptr;
+  while (true) {
+    // If this name scope was produced by importing a C++ declaration or has
+    // already been exported to C++, return the corresponding Clang declaration.
+    if (auto* existing_decl_context =
+            GetClangDeclContextForScope(context, name_scope_id)) {
+      decl_context = existing_decl_context;
+      break;
+    }
+
+    // Otherwise, continue to the parent and create a scope for it first.
+    name_scope_ids_to_create.push_back(name_scope_id);
+    name_scope_id = context.name_scopes().Get(name_scope_id).parent_scope_id();
+
+    // TODO: What should happen if there's an intervening function scope?
+    CARBON_CHECK(
+        name_scope_id.has_value(),
+        "Reached the top level without finding a scope mapped into C++");
+  }
+
+  // Create the name scopes in order, starting from the outermost one.
+  while (!name_scope_ids_to_create.empty()) {
+    name_scope_id = name_scope_ids_to_create.pop_back_val();
+
+    auto& name_scope = context.name_scopes().Get(name_scope_id);
+    auto* identifier_info =
+        GetClangIdentifierInfo(context, name_scope.name_id());
+    if (!identifier_info) {
+      // TODO: Handle keyword package names like `Cpp` and `Core`. These can
+      // be named from C++ via an alias.
+      context.TODO(loc_id, "interop with non-identifier package name");
+      return nullptr;
+    }
+
+    auto inst = context.insts().Get(name_scope.inst_id());
+    if (inst.Is<SemIR::Namespace>()) {
+      // TODO: Provide a source location.
+      auto* namespace_decl = clang::NamespaceDecl::Create(
+          context.ast_context(), decl_context, false, clang::SourceLocation(),
+          clang::SourceLocation(), identifier_info, nullptr, false);
+      decl_context = namespace_decl;
+    } else if (inst.Is<SemIR::ClassDecl>()) {
+      // TODO: Provide a source location.
+      auto* record_decl = clang::CXXRecordDecl::Create(
+          context.ast_context(), clang::TagTypeKind::Class, decl_context,
+          clang::SourceLocation(), clang::SourceLocation(), identifier_info);
+      // If this is a member class, set its access.
+      if (isa<clang::CXXRecordDecl>(decl_context)) {
+        // TODO: Map Carbon access to C++ access.
+        record_decl->setAccess(clang::AS_public);
+      }
+
+      decl_context = record_decl;
+      decl_context->setHasExternalLexicalStorage();
+    } else {
+      context.TODO(loc_id, "non-class non-namespace name scope");
+      return nullptr;
+    }
+
+    decl_context->setHasExternalVisibleStorage();
+
+    auto key = SemIR::ClangDeclKey::ForNonFunctionDecl(
+        cast<clang::Decl>(decl_context));
+    auto clang_decl_id = context.clang_decls().Add(
+        {.key = key, .inst_id = name_scope.inst_id()});
+    name_scope.set_clang_decl_context_id(clang_decl_id, /*is_cpp_scope=*/false);
+  }
+
+  return decl_context;
+}
+
+auto ExportClassToCpp(Context& context, SemIR::LocId loc_id,
+                      SemIR::InstId class_inst_id, SemIR::ClassType class_type)
+    -> clang::TagDecl* {
+  // TODO: A lot of logic in this function is shared with ExportNameScopeToCpp.
+  // This should be refactored.
+
+  if (class_type.specific_id.has_value()) {
+    context.TODO(loc_id, "interop with specific class");
+    return nullptr;
+  }
+
+  const auto& class_info = context.classes().Get(class_type.class_id);
+
+  // If this class was produced by importing a C++ declaration or has
+  // already been exported to C++, return the corresponding Clang declaration.
+  // That could either be a CXXRecordDecl or an EnumDecl.
+  if (auto* decl_context =
+          GetClangDeclContextForScope(context, class_info.scope_id)) {
+    return cast<clang::TagDecl>(decl_context);
+  }
+
+  auto* identifier_info = GetClangIdentifierInfo(context, class_info.name_id);
+  if (!identifier_info) {
+    // TODO: Handle keyword package names like `Cpp` and `Core`. These can
+    // be named from C++ via an alias.
+    context.TODO(loc_id, "interop with non-identifier package name");
+    return nullptr;
+  }
+
+  auto* decl_context =
+      ExportNameScopeToCpp(context, loc_id, class_info.parent_scope_id);
+  // TODO: Provide a source location.
+  auto* record_decl = clang::CXXRecordDecl::Create(
+      context.ast_context(), clang::TagTypeKind::Class, decl_context,
+      clang::SourceLocation(), clang::SourceLocation(), identifier_info);
+  // If this is a member class, set its access.
+  if (isa<clang::CXXRecordDecl>(decl_context)) {
+    // TODO: Map Carbon access to C++ access.
+    record_decl->setAccess(clang::AS_public);
+  }
+
+  record_decl->setHasExternalLexicalStorage();
+  record_decl->setHasExternalVisibleStorage();
+
+  auto key =
+      SemIR::ClangDeclKey::ForNonFunctionDecl(cast<clang::Decl>(record_decl));
+  auto clang_decl_id =
+      context.clang_decls().Add({.key = key, .inst_id = class_inst_id});
+  if (class_info.scope_id.has_value()) {
+    // TODO: Record the Carbon class -> clang declaration mapping for incomplete
+    // classes too.
+    context.name_scopes()
+        .Get(class_info.scope_id)
+        .set_clang_decl_context_id(clang_decl_id, /*is_cpp_scope=*/false);
+  }
+  return record_decl;
+}
+
 // Create a `clang::FunctionDecl` for the given Carbon function. This
 // can be used to call the Carbon function from C++. The Carbon
 // function's ABI must be compatible with C++.
@@ -233,9 +386,8 @@ static auto BuildCarbonToCarbonThunk(
   return carbon_thunk_function_id;
 }
 
-auto GetReverseInteropFunctionDecl(Context& context, SemIR::LocId loc_id,
-                                   clang::DeclContext& decl_context,
-                                   SemIR::FunctionId callee_function_id)
+auto ExportFunctionToCpp(Context& context, SemIR::LocId loc_id,
+                         SemIR::FunctionId callee_function_id)
     -> clang::FunctionDecl* {
   const SemIR::Function& callee = context.functions().Get(callee_function_id);
 
@@ -260,6 +412,13 @@ auto GetReverseInteropFunctionDecl(Context& context, SemIR::LocId loc_id,
     return nullptr;
   }
 
+  // Map the parent scope into the C++ AST.
+  auto* decl_context =
+      ExportNameScopeToCpp(context, loc_id, callee.parent_scope_id);
+  if (!decl_context) {
+    return nullptr;
+  }
+
   // Get the parameter types of the Carbon function being called.
   auto callee_function_params =
       context.inst_blocks().Get(callee.call_param_patterns_id);
@@ -283,7 +442,7 @@ auto GetReverseInteropFunctionDecl(Context& context, SemIR::LocId loc_id,
   }
 
   // Create a C++ thunk that calls the Carbon thunk.
-  return BuildCppToCarbonThunk(context, loc_id, &decl_context,
+  return BuildCppToCarbonThunk(context, loc_id, decl_context,
                                context.names().GetFormatted(callee.name_id),
                                carbon_function_decl, callee_param_type_ids);
 }

+ 22 - 4
toolchain/check/cpp/export.h

@@ -11,11 +11,29 @@
 
 namespace Carbon::Check {
 
+// Exports a Carbon name scope into C++ as a namespace or class, or returns the
+// C++ namespace or class declaration that it was imported from.
+//
+// If the name scope has already been exported, returns the existing context.
+// Otherwise, creates a new C++ declaration context and returns it. Returns
+// nullptr if the name scope could not be exported and an error was diagnosed.
+auto ExportNameScopeToCpp(Context& context, SemIR::LocId loc_id,
+                          SemIR::NameScopeId name_scope_id)
+    -> clang::DeclContext*;
+
+// Exports a Carbon class into C++ as a class, or returns the C++ tag type that
+// the class was imported from.
+//
+// If the class has already been exported, returns the existing C++ class.
+// Otherwise, creates a new C++ class and returns it. Returns nullptr if the
+// class could not be exported and an error was diagnosed.
+auto ExportClassToCpp(Context& context, SemIR::LocId loc_id,
+                      SemIR::InstId class_inst_id, SemIR::ClassType class_type)
+    -> clang::TagDecl*;
+
 // Get a `clang::FunctionDecl` that can be used to call a Carbon function.
-auto GetReverseInteropFunctionDecl(Context& context, SemIR::LocId loc_id,
-                                   clang::DeclContext& decl_context,
-                                   SemIR::FunctionId function_id)
-    -> clang::FunctionDecl*;
+auto ExportFunctionToCpp(Context& context, SemIR::LocId loc_id,
+                         SemIR::FunctionId function_id) -> clang::FunctionDecl*;
 
 }  // namespace Carbon::Check
 

+ 55 - 123
toolchain/check/cpp/generate_ast.cpp

@@ -321,9 +321,7 @@ class ShallowCopyCompilerInvocation : public clang::CompilerInvocation {
 // Provides clang AST nodes representing Carbon SemIR entities.
 class CarbonExternalASTSource : public clang::ExternalASTSource {
  public:
-  explicit CarbonExternalASTSource(Context* context,
-                                   clang::ASTContext* ast_context)
-      : context_(context), ast_context_(ast_context) {}
+  explicit CarbonExternalASTSource(Context* context) : context_(context) {}
 
   auto StartTranslationUnit(clang::ASTConsumer* consumer) -> void override;
 
@@ -336,10 +334,13 @@ class CarbonExternalASTSource : public clang::ExternalASTSource {
   auto CompleteType(clang::TagDecl* tag_decl) -> void override;
 
  private:
+  // Builds the top-level C++ namespace `Carbon` and adds it to the translation
+  // unit.
+  auto BuildCarbonNamespace() -> void;
+
   // Map a Carbon entity to a Clang NamedDecl. Returns null if the entity cannot
   // currently be represented in C++.
-  auto MapInstIdToClangDecl(clang::DeclContext& decl_context,
-                            LookupResult lookup) -> clang::NamedDecl*;
+  auto MapInstIdToClangDecl(LookupResult lookup) -> clang::NamedDecl*;
 
   // Get a current best-effort location for the current position within C++
   // processing.
@@ -364,41 +365,16 @@ class CarbonExternalASTSource : public clang::ExternalASTSource {
   }
 
   Check::Context* context_;
-  clang::ASTContext* ast_context_;
-
-  // Has the "Carbon" C++ namespace been created yet
-  // (this could be replaced with `!scope_map_.empty()` if Carbon::Map supported
-  // `empty()`)
-  bool root_scope_initialized_ = false;
 };
 
 }  // namespace
 
 void CarbonExternalASTSource::StartTranslationUnit(
     clang::ASTConsumer* /*Consumer*/) {
-  auto& translation_unit = *ast_context_->getTranslationUnitDecl();
-  // Mark the translation unit as having external storage so we get a query for
-  // the `Carbon` namespace in the top level/translation unit scope.
-  translation_unit.setHasExternalVisibleStorage();
-}
-
-// If the given name scope was produced by importing a C++ declaration, return
-// the corresponding Clang declaration.
-static auto GetClangDeclForScope(Context& context, SemIR::NameScopeId scope_id)
-    -> clang::NamedDecl* {
-  if (!scope_id.has_value()) {
-    return nullptr;
-  }
-  auto& scope = context.name_scopes().Get(scope_id);
-  if (!scope.is_cpp_scope()) {
-    return nullptr;
-  }
-  return dyn_cast<clang::NamedDecl>(
-      context.clang_decls().Get(scope.clang_decl_context_id()).key.decl);
+  BuildCarbonNamespace();
 }
 
-auto CarbonExternalASTSource::MapInstIdToClangDecl(
-    clang::DeclContext& decl_context, LookupResult lookup)
+auto CarbonExternalASTSource::MapInstIdToClangDecl(LookupResult lookup)
     -> clang::NamedDecl* {
   auto target_inst_id = lookup.scope_result.target_inst_id();
   auto target_constant =
@@ -406,57 +382,22 @@ auto CarbonExternalASTSource::MapInstIdToClangDecl(
   auto target_inst = context_->insts().Get(target_constant);
   CARBON_KIND_SWITCH(target_inst) {
     case CARBON_KIND(SemIR::Namespace namespace_info): {
-      if (auto* decl =
-              GetClangDeclForScope(*context_, namespace_info.name_scope_id)) {
-        return decl;
+      auto* decl_context =
+          ExportNameScopeToCpp(*context_, SemIR::LocId(target_inst_id),
+                               namespace_info.name_scope_id);
+      if (!decl_context) {
+        return nullptr;
       }
-      auto& name_scope =
-          context_->name_scopes().Get(namespace_info.name_scope_id);
-      auto* identifier_info =
-          GetClangIdentifierInfo(*context_, name_scope.name_id());
-      if (!identifier_info) {
-        // TODO: Handle keyword package names like `Cpp` and `Core`. These can
-        // be named from C++ via an alias.
-        context_->TODO(SemIR::LocId(target_inst_id),
-                       "interop with non-identifier package name");
+      if (isa<clang::TranslationUnitDecl>(decl_context)) {
+        context_->TODO(GetCurrentCppLocId(),
+                       "interop with translation unit decl");
         return nullptr;
       }
-      // TODO: Don't immediately use the decl_context - build any intermediate
-      // namespaces iteratively.
-      // Eventually add a mapping and use that/populate it/keep it up to date.
-      // decl_context could be prepopulated in that mapping and not passed
-      // explicitly to MapInstIdToClangDecl.
-      auto* namespace_decl = clang::NamespaceDecl::Create(
-          *ast_context_, &decl_context, false, clang::SourceLocation(),
-          clang::SourceLocation(), identifier_info, nullptr, false);
-      auto key = SemIR::ClangDeclKey::ForNonFunctionDecl(namespace_decl);
-      context_->clang_decls().Add({.key = key, .inst_id = target_inst_id});
-      namespace_decl->setHasExternalVisibleStorage();
-      return namespace_decl;
+      return cast<clang::NamedDecl>(decl_context);
     }
     case CARBON_KIND(SemIR::ClassType class_type): {
-      const auto& class_info = context_->classes().Get(class_type.class_id);
-      if (auto* decl = GetClangDeclForScope(*context_, class_info.scope_id)) {
-        return decl;
-      }
-      auto* identifier_info =
-          GetClangIdentifierInfo(*context_, class_info.name_id);
-      CARBON_CHECK(identifier_info, "class with non-identifier name {0}",
-                   class_info.name_id);
-      // TODO: Check whether we've already mapped this class and if so, return
-      // the prior mapping.
-      auto* record_decl = clang::CXXRecordDecl::Create(
-          *ast_context_, clang::TagTypeKind::Class, &decl_context,
-          clang::SourceLocation(), clang::SourceLocation(), identifier_info);
-      auto key = SemIR::ClangDeclKey::ForNonFunctionDecl(record_decl);
-      context_->clang_decls().Add({.key = key, .inst_id = target_inst_id});
-      record_decl->setHasExternalLexicalStorage();
-      record_decl->setHasExternalVisibleStorage();
-      if (isa<clang::CXXRecordDecl>(decl_context)) {
-        // TODO: Map Carbon access to C++ access.
-        record_decl->setAccess(clang::AS_public);
-      }
-      return record_decl;
+      return ExportClassToCpp(*context_, SemIR::LocId(target_inst_id),
+                              target_inst_id, class_type);
     }
     case SemIR::StructValue::Kind: {
       auto callee = GetCallee(context_->sem_ir(), target_constant);
@@ -472,50 +413,44 @@ auto CarbonExternalASTSource::MapInstIdToClangDecl(
             context_->clang_decls().Get(function.clang_decl_id).key.decl);
       }
 
-      return GetReverseInteropFunctionDecl(
-          *context_, SemIR::LocId(target_inst_id), decl_context,
-          callee_function->function_id);
+      return ExportFunctionToCpp(*context_, SemIR::LocId(target_inst_id),
+                                 callee_function->function_id);
     }
     default:
       return nullptr;
   }
 }
 
+auto CarbonExternalASTSource::BuildCarbonNamespace() -> void {
+  static const llvm::StringLiteral carbon_namespace_name = "Carbon";
+  auto& ast_context = context_->ast_context();
+  auto* identifier = &ast_context.Idents.get(carbon_namespace_name);
+
+  // Create the namespace and add it to the translation unit scope.
+  auto* decl_context = ast_context.getTranslationUnitDecl();
+  auto* carbon_cpp_namespace = clang::NamespaceDecl::Create(
+      ast_context, decl_context, /*Inline=*/false, clang::SourceLocation(),
+      clang::SourceLocation(), identifier, /*PrevDecl=*/nullptr,
+      /*Nested=*/false);
+  decl_context->addDecl(carbon_cpp_namespace);
+
+  // We provide custom lookup results within this namespace.
+  carbon_cpp_namespace->setHasExternalVisibleStorage();
+
+  // Register this file's package scope as corresponding to the `Carbon`
+  // namespace in C++.
+  // TODO: For mangling purposes, include the package as a sub-namespace.
+  auto key = SemIR::ClangDeclKey::ForNonFunctionDecl(carbon_cpp_namespace);
+  auto clang_decl_id = context_->clang_decls().Add(
+      {.key = key, .inst_id = SemIR::Namespace::PackageInstId});
+  context_->name_scopes()
+      .Get(SemIR::NameScopeId::Package)
+      .set_clang_decl_context_id(clang_decl_id, /*is_cpp_scope=*/false);
+}
+
 auto CarbonExternalASTSource::FindExternalVisibleDeclsByName(
     const clang::DeclContext* decl_context, clang::DeclarationName decl_name,
     const clang::DeclContext* /*OriginalDC*/) -> bool {
-  if (decl_context->getDeclKind() == clang::Decl::Kind::TranslationUnit) {
-    // If the context doesn't already have a mapping between C++ and Carbon,
-    // check if this is the root mapping (for the "Carbon" namespace in the
-    // translation unit scope) and if so, create that mapping.
-
-    if (root_scope_initialized_) {
-      return false;
-    }
-
-    static const llvm::StringLiteral carbon_namespace_name = "Carbon";
-    if (auto* identifier = decl_name.getAsIdentifierInfo();
-        !identifier || !identifier->isStr(carbon_namespace_name)) {
-      return false;
-    }
-
-    // Build the top level 'Carbon' namespace
-    auto& ast_context = decl_context->getParentASTContext();
-    auto& mutable_tu_decl_context = *ast_context.getTranslationUnitDecl();
-    auto* carbon_cpp_namespace = clang::NamespaceDecl::Create(
-        ast_context, &mutable_tu_decl_context, false, clang::SourceLocation(),
-        clang::SourceLocation(), &ast_context.Idents.get(carbon_namespace_name),
-        nullptr, false);
-    carbon_cpp_namespace->setHasExternalVisibleStorage();
-    auto key = SemIR::ClangDeclKey::ForNonFunctionDecl(carbon_cpp_namespace);
-    context_->clang_decls().Add(
-        {.key = key, .inst_id = SemIR::Namespace::PackageInstId});
-    SetExternalVisibleDeclsForName(decl_context, decl_name,
-                                   {carbon_cpp_namespace});
-    root_scope_initialized_ = true;
-    return true;
-  }
-
   // Find the Carbon declaration corresponding to this Clang declaration.
   auto* decl = cast<clang::Decl>(
       const_cast<clang::DeclContext*>(decl_context->getPrimaryContext()));
@@ -555,11 +490,7 @@ auto CarbonExternalASTSource::FindExternalVisibleDeclsByName(
   }
 
   // Map the found Carbon entity to a Clang NamedDecl.
-  // TODO: Stop passing in the `DeclContext` here; the context in which we
-  // performed the lookup that first found the Carbon declaration should not
-  // affect the Clang declaration we produce.
-  auto* clang_decl = MapInstIdToClangDecl(
-      *const_cast<clang::DeclContext*>(decl_context), result);
+  auto* clang_decl = MapInstIdToClangDecl(result);
   if (!clang_decl) {
     return false;
   }
@@ -681,16 +612,17 @@ class GenerateASTAction : public clang::ASTFrontendAction {
     auto& parser = *parser_ptr;
 
     clang_instance.getPreprocessor().EnterMainSourceFile();
+    parser.Initialize();
+
+    context_->set_cpp_context(
+        std::make_unique<CppContext>(clang_instance, std::move(parser_ptr)));
+
     if (auto* source = clang_instance.getASTContext().getExternalSource()) {
       source->StartTranslationUnit(&clang_instance.getASTConsumer());
     }
 
-    parser.Initialize();
     clang_instance.getSema().ActOnStartOfTranslationUnit();
 
-    context_->set_cpp_context(
-        std::make_unique<CppContext>(clang_instance, std::move(parser_ptr)));
-
     ParseTopLevelDecls(parser, clang_instance.getASTConsumer());
   }
 
@@ -765,7 +697,7 @@ auto GenerateAst(Context& context,
   // support. Implement multiplexing support (possibly in Clang) to restore
   // modules functionality.
   ast.setExternalSource(
-      llvm::makeIntrusiveRefCnt<CarbonExternalASTSource>(&context, &ast));
+      llvm::makeIntrusiveRefCnt<CarbonExternalASTSource>(&context));
 
   if (llvm::Error error = action.Execute()) {
     // `Execute` currently never fails, but its contract allows it to.

+ 9 - 6
toolchain/check/cpp/import.cpp

@@ -137,10 +137,12 @@ auto ImportCpp(Context& context,
   name_scope.set_is_closed_import(true);
 
   if (GenerateAst(context, imports, fs, llvm_context, std::move(invocation))) {
-    name_scope.set_clang_decl_context_id(context.clang_decls().Add(
-        {.key = SemIR::ClangDeclKey(
-             context.ast_context().getTranslationUnitDecl()),
-         .inst_id = name_scope.inst_id()}));
+    name_scope.set_clang_decl_context_id(
+        context.clang_decls().Add(
+            {.key = SemIR::ClangDeclKey(
+                 context.ast_context().getTranslationUnitDecl()),
+             .inst_id = name_scope.inst_id()}),
+        /*is_cpp_scope=*/true);
   } else {
     name_scope.set_has_error();
   }
@@ -302,7 +304,8 @@ static auto ImportNamespaceDecl(Context& context,
   context.name_scopes()
       .Get(result.name_scope_id)
       .set_clang_decl_context_id(
-          context.clang_decls().Add({.key = key, .inst_id = result.inst_id}));
+          context.clang_decls().Add({.key = key, .inst_id = result.inst_id}),
+          /*is_cpp_scope=*/true);
   return result.inst_id;
 }
 
@@ -373,7 +376,7 @@ static auto ImportTagDecl(Context& context, clang::TagDecl* clang_decl)
       class_inst_id, SemIR::NameId::None, class_info.parent_scope_id);
   context.name_scopes()
       .Get(class_info.scope_id)
-      .set_clang_decl_context_id(clang_decl_id);
+      .set_clang_decl_context_id(clang_decl_id, /*is_cpp_scope=*/true);
 
   return class_inst_id;
 }

+ 10 - 9
toolchain/check/testdata/basics/raw_sem_ir/cpp_interop.carbon

@@ -53,14 +53,15 @@ fn G(x: Cpp.X) {
 // CHECK:STDOUT:     import_ir_inst3: {ir_id: import_ir(Cpp), clang_source_loc_id: clang_source_loc50000003}
 // CHECK:STDOUT:     import_ir_inst4: {ir_id: import_ir(Cpp), clang_source_loc_id: clang_source_loc50000004}
 // CHECK:STDOUT:   clang_decls:
-// CHECK:STDOUT:     clang_decl_id50000000: {key: "<translation unit>", inst_id: inst50000011}
-// CHECK:STDOUT:     clang_decl_id50000001: {key: "struct X {}", inst_id: inst50000014}
-// CHECK:STDOUT:     clang_decl_id50000002: {key: "X * _Nonnull p", inst_id: inst50000022}
-// CHECK:STDOUT:     clang_decl_id50000003: {key: {decl: "void f(X x = {})", kind: normal, num_params: 0}, inst_id: inst5000002D}
-// CHECK:STDOUT:     clang_decl_id50000004: {key: {decl: "inline void f__carbon_thunk()", kind: normal, num_params: 0}, inst_id: inst50000030}
-// CHECK:STDOUT:     clang_decl_id50000005: {key: {decl: "void f(X x = {})", kind: normal, num_params: 1}, inst_id: inst5000003B}
-// CHECK:STDOUT:     clang_decl_id50000006: {key: {decl: "inline void f__carbon_thunk(X * _Nonnull x)", kind: normal, num_params: 1}, inst_id: inst50000043}
-// CHECK:STDOUT:     clang_decl_id50000007: {key: "X * _Nonnull global", inst_id: inst5000004E}
+// CHECK:STDOUT:     clang_decl_id50000000: {key: "namespace Carbon {\n}", inst_id: instF}
+// CHECK:STDOUT:     clang_decl_id50000001: {key: "<translation unit>", inst_id: inst50000011}
+// CHECK:STDOUT:     clang_decl_id50000002: {key: "struct X {}", inst_id: inst50000014}
+// CHECK:STDOUT:     clang_decl_id50000003: {key: "X * _Nonnull p", inst_id: inst50000022}
+// CHECK:STDOUT:     clang_decl_id50000004: {key: {decl: "void f(X x = {})", kind: normal, num_params: 0}, inst_id: inst5000002D}
+// CHECK:STDOUT:     clang_decl_id50000005: {key: {decl: "inline void f__carbon_thunk()", kind: normal, num_params: 0}, inst_id: inst50000030}
+// CHECK:STDOUT:     clang_decl_id50000006: {key: {decl: "void f(X x = {})", kind: normal, num_params: 1}, inst_id: inst5000003B}
+// CHECK:STDOUT:     clang_decl_id50000007: {key: {decl: "inline void f__carbon_thunk(X * _Nonnull x)", kind: normal, num_params: 1}, inst_id: inst50000043}
+// CHECK:STDOUT:     clang_decl_id50000008: {key: "X * _Nonnull global", inst_id: inst5000004E}
 // CHECK:STDOUT:   name_scopes:
 // CHECK:STDOUT:     name_scope0:     {inst: instF, parent_scope: name_scope<none>, has_error: false, extended_scopes: [], names: {name(Cpp): inst50000011, name0: inst5000001D}}
 // CHECK:STDOUT:     name_scope50000001: {inst: inst50000011, parent_scope: name_scope0, has_error: false, extended_scopes: [], names: {name2: inst50000014, name3: inst5000002A, name4: inst5000004E}}
@@ -71,7 +72,7 @@ fn G(x: Cpp.X) {
 // CHECK:STDOUT:     entity_name50000002: {name: name1, parent_scope: name_scope<none>, index: -1, is_template: 0, is_unused: 0, form: constant<none>}
 // CHECK:STDOUT:     entity_name50000003: {name: name4, parent_scope: name_scope50000001, index: -1, is_template: 0, is_unused: 0, form: constant<none>}
 // CHECK:STDOUT:   cpp_global_vars:
-// CHECK:STDOUT:     cpp_global_var50000000: {key: {entity_name_id: entity_name50000003}, clang_decl_id: clang_decl_id50000007}
+// CHECK:STDOUT:     cpp_global_var50000000: {key: {entity_name_id: entity_name50000003}, clang_decl_id: clang_decl_id50000008}
 // CHECK:STDOUT:   functions:
 // CHECK:STDOUT:     function50000000: {name: name0, parent_scope: name_scope0, call_param_patterns_id: inst_block50000007, call_params_id: inst_block50000008, body: [inst_block5000000B]}
 // CHECK:STDOUT:     function50000001: {name: name3, parent_scope: name_scope50000001, call_param_patterns_id: inst_block_empty, call_params_id: inst_block_empty}

+ 2 - 0
toolchain/check/testdata/interop/cpp/function/thunk_ast.carbon

@@ -12,6 +12,7 @@
 // TIP: To dump output, run:
 // TIP:   bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/interop/cpp/function/thunk_ast.carbon
 // CHECK:STDOUT: TranslationUnitDecl {{0x[a-f0-9]+}} <<invalid sloc>> <invalid sloc>
+// CHECK:STDOUT: |-NamespaceDecl {{0x[a-f0-9]+}} <<invalid sloc>> <invalid sloc> Carbon
 
 // --- thunk_required.h
 
@@ -32,6 +33,7 @@ auto foo(short a) -> void;
 // CHECK:STDOUT:   |-InternalLinkageAttr {{0x[a-f0-9]+}} <<invalid sloc>> Implicit
 // CHECK:STDOUT:   `-AsmLabelAttr {{0x[a-f0-9]+}} <col:6> Implicit "_Z3foos.carbon_thunk"
 // CHECK:STDOUT: TranslationUnitDecl {{0x[a-f0-9]+}} <<invalid sloc>> <invalid sloc>
+// CHECK:STDOUT: |-NamespaceDecl {{0x[a-f0-9]+}} <<invalid sloc>> <invalid sloc> Carbon
 
 // --- import_thunk_required.carbon
 

+ 30 - 0
toolchain/check/testdata/interop/cpp/namespace/export.carbon

@@ -0,0 +1,30 @@
+// 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
+//
+// INCLUDE-FILE: toolchain/testing/testdata/min_prelude/none.carbon
+//
+// AUTOUPDATE
+// TIP: To test this file alone, run:
+// TIP:   bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/check/testdata/interop/cpp/namespace/export.carbon
+// TIP: To dump output, run:
+// TIP:   bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/interop/cpp/namespace/export.carbon
+
+// --- alias_identity.carbon
+
+import Cpp;
+
+namespace M;
+alias N = M;
+
+class M.C {}
+
+inline Cpp '''
+// OK, valid redeclaration: M and N are the same namespace.
+namespace A = Carbon::M;
+namespace A = Carbon::N;
+
+// Valid: M::C and N::C are the same class.
+Carbon::M::C *pmc;
+Carbon::N::C *pnc = pmc;
+''';

+ 2 - 2
toolchain/check/testdata/interop/cpp/namespace/basic.carbon → toolchain/check/testdata/interop/cpp/namespace/import.carbon

@@ -6,9 +6,9 @@
 //
 // AUTOUPDATE
 // TIP: To test this file alone, run:
-// TIP:   bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/check/testdata/interop/cpp/namespace/basic.carbon
+// TIP:   bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/check/testdata/interop/cpp/namespace/import.carbon
 // TIP: To dump output, run:
-// TIP:   bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/interop/cpp/namespace/basic.carbon
+// TIP:   bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/interop/cpp/namespace/import.carbon
 
 // ============================================================================
 // Single

+ 4 - 4
toolchain/check/testdata/interop/cpp/namespace/roundtrip.carbon

@@ -81,16 +81,16 @@ library "[[@TEST_NAME]]";
 
 import Cpp;
 
-// CHECK:STDERR: fail_carbon_raw_cpp.carbon:[[@LINE+4]]:7: error: semantics TODO: `interop with non-identifier package name` [SemanticsTodo]
-// CHECK:STDERR: alias r#Cpp = Cpp;
-// CHECK:STDERR:       ^~~
-// CHECK:STDERR:
 alias r#Cpp = Cpp;
 
 inline Cpp '''
 void F();
 
 void G() {
+  // CHECK:STDERR: fail_carbon_raw_cpp.carbon:[[@LINE+8]]:16: error: semantics TODO: `interop with translation unit decl` [SemanticsTodo]
+  // CHECK:STDERR:   Carbon::Cpp::F();
+  // CHECK:STDERR:                ^
+  // CHECK:STDERR:
   // CHECK:STDERR: fail_carbon_raw_cpp.carbon:[[@LINE+4]]:11: error: no member named 'Cpp' in namespace 'Carbon' [CppInteropParseError]
   // CHECK:STDERR:    19 |   Carbon::Cpp::F();
   // CHECK:STDERR:       |   ~~~~~~~~^

+ 14 - 0
toolchain/check/testdata/interop/cpp/reverse/class.carbon

@@ -107,3 +107,17 @@ inline Cpp '''
 // CHECK:STDERR:
 static_assert(sizeof(Carbon::A) == 2 * sizeof(int));
 ''';
+
+// --- alias_identity.carbon
+library "[[@TEST_NAME]]";
+
+import Cpp;
+
+class A {}
+alias B = A;
+
+inline Cpp '''
+// OK, Carbon::A and Carbon::B are the same type.
+Carbon::A *pa;
+Carbon::B *pb = pa;
+''';

+ 13 - 8
toolchain/sem_ir/name_scope.h

@@ -217,16 +217,16 @@ class NameScope : public Printable<NameScope> {
     is_closed_import_ = is_closed_import;
   }
 
-  auto is_cpp_scope() const -> bool {
-    return clang_decl_context_id().has_value();
-  }
+  auto is_cpp_scope() const -> bool { return is_cpp_scope_; }
 
   auto clang_decl_context_id() const -> ClangDeclId {
     return clang_decl_context_id_;
   }
 
-  auto set_clang_decl_context_id(ClangDeclId clang_decl_context_id) -> void {
+  auto set_clang_decl_context_id(ClangDeclId clang_decl_context_id,
+                                 bool is_cpp_scope) -> void {
     clang_decl_context_id_ = clang_decl_context_id;
+    is_cpp_scope_ = is_cpp_scope;
   }
 
   // Returns true if this name scope describes an imported package.
@@ -296,14 +296,19 @@ class NameScope : public Printable<NameScope> {
   // True if this is a closed namespace created by importing a package.
   bool is_closed_import_ = false;
 
-  // Set if this is the `Cpp` scope or a scope inside `Cpp`. Points to the
-  // matching Clang declaration context to look for names.
-  ClangDeclId clang_decl_context_id_ = ClangDeclId::None;
-
   // True if this is the scope of an interface definition, where associated
   // entities will be bound to the interface's `Self` symbolic type.
   bool is_interface_definition_ = false;
 
+  // True if this scope was imported from C++. Set if this is the `Cpp` scope or
+  // a scope inside `Cpp`.
+  bool is_cpp_scope_ = false;
+
+  // The C++ declaration context corresponding to this scope. If `is_cpp_scope_`
+  // is true, this is the C++ scope from which this name scope was imported.
+  // Otherwise, this is the scope to which this name scope is exported.
+  ClangDeclId clang_decl_context_id_ = ClangDeclId::None;
+
   // Imported IR scopes that compose this namespace. This will be empty for
   // scopes that correspond to the current package.
   llvm::SmallVector<std::pair<ImportIRId, NameScopeId>, 0> import_ir_scopes_;