From f31e1685fd58d561681540e99fa3f3f766e18bce Mon Sep 17 00:00:00 2001 From: Richard Smith Date: Mon, 13 Apr 2026 12:43:44 -0700 Subject: [PATCH] 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. --- toolchain/check/cpp/export.cpp | 167 +++++++++++++++- toolchain/check/cpp/export.h | 26 ++- toolchain/check/cpp/generate_ast.cpp | 180 ++++++------------ toolchain/check/cpp/import.cpp | 15 +- .../basics/raw_sem_ir/cpp_interop.carbon | 19 +- .../interop/cpp/function/thunk_ast.carbon | 2 + .../interop/cpp/namespace/export.carbon | 30 +++ .../namespace/{basic.carbon => import.carbon} | 4 +- .../interop/cpp/namespace/roundtrip.carbon | 8 +- .../testdata/interop/cpp/reverse/class.carbon | 14 ++ toolchain/sem_ir/name_scope.h | 21 +- 11 files changed, 325 insertions(+), 161 deletions(-) create mode 100644 toolchain/check/testdata/interop/cpp/namespace/export.carbon rename toolchain/check/testdata/interop/cpp/namespace/{basic.carbon => import.carbon} (99%) diff --git a/toolchain/check/cpp/export.cpp b/toolchain/check/cpp/export.cpp index e28bddc105fb..f9e46a68f54b 100644 --- a/toolchain/check/cpp/export.cpp +++ b/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(decl); +} + +auto ExportNameScopeToCpp(Context& context, SemIR::LocId loc_id, + SemIR::NameScopeId name_scope_id) + -> clang::DeclContext* { + llvm::SmallVector 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()) { + // 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()) { + // 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(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(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(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(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(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); } diff --git a/toolchain/check/cpp/export.h b/toolchain/check/cpp/export.h index 47b35d2c7ec9..c1f1e6b18e7f 100644 --- a/toolchain/check/cpp/export.h +++ b/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 diff --git a/toolchain/check/cpp/generate_ast.cpp b/toolchain/check/cpp/generate_ast.cpp index 1ed0bf6a44bc..8427675e7734 100644 --- a/toolchain/check/cpp/generate_ast.cpp +++ b/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(); + BuildCarbonNamespace(); } -// 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( - context.clang_decls().Get(scope.clang_decl_context_id()).key.decl); -} - -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& 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"); + auto* decl_context = + ExportNameScopeToCpp(*context_, SemIR::LocId(target_inst_id), + namespace_info.name_scope_id); + if (!decl_context) { 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; + if (isa(decl_context)) { + context_->TODO(GetCurrentCppLocId(), + "interop with translation unit decl"); + return nullptr; + } + return cast(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(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( const_cast(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(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(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(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(&context, &ast)); + llvm::makeIntrusiveRefCnt(&context)); if (llvm::Error error = action.Execute()) { // `Execute` currently never fails, but its contract allows it to. diff --git a/toolchain/check/cpp/import.cpp b/toolchain/check/cpp/import.cpp index 28a03c080614..842bf075e8fc 100644 --- a/toolchain/check/cpp/import.cpp +++ b/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; } diff --git a/toolchain/check/testdata/basics/raw_sem_ir/cpp_interop.carbon b/toolchain/check/testdata/basics/raw_sem_ir/cpp_interop.carbon index f98d66329fe6..4871c9631bb2 100644 --- a/toolchain/check/testdata/basics/raw_sem_ir/cpp_interop.carbon +++ b/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: "", 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: "", 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, 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, index: -1, is_template: 0, is_unused: 0, form: constant} // CHECK:STDOUT: entity_name50000003: {name: name4, parent_scope: name_scope50000001, index: -1, is_template: 0, is_unused: 0, form: constant} // 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} diff --git a/toolchain/check/testdata/interop/cpp/function/thunk_ast.carbon b/toolchain/check/testdata/interop/cpp/function/thunk_ast.carbon index 7b810b3c6092..19bd32ad3a4f 100644 --- a/toolchain/check/testdata/interop/cpp/function/thunk_ast.carbon +++ b/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]+}} <> +// CHECK:STDOUT: |-NamespaceDecl {{0x[a-f0-9]+}} <> Carbon // --- thunk_required.h @@ -32,6 +33,7 @@ auto foo(short a) -> void; // CHECK:STDOUT: |-InternalLinkageAttr {{0x[a-f0-9]+}} <> Implicit // CHECK:STDOUT: `-AsmLabelAttr {{0x[a-f0-9]+}} Implicit "_Z3foos.carbon_thunk" // CHECK:STDOUT: TranslationUnitDecl {{0x[a-f0-9]+}} <> +// CHECK:STDOUT: |-NamespaceDecl {{0x[a-f0-9]+}} <> Carbon // --- import_thunk_required.carbon diff --git a/toolchain/check/testdata/interop/cpp/namespace/export.carbon b/toolchain/check/testdata/interop/cpp/namespace/export.carbon new file mode 100644 index 000000000000..20654e439307 --- /dev/null +++ b/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; +'''; diff --git a/toolchain/check/testdata/interop/cpp/namespace/basic.carbon b/toolchain/check/testdata/interop/cpp/namespace/import.carbon similarity index 99% rename from toolchain/check/testdata/interop/cpp/namespace/basic.carbon rename to toolchain/check/testdata/interop/cpp/namespace/import.carbon index f61816241a81..08f9a7d5f692 100644 --- a/toolchain/check/testdata/interop/cpp/namespace/basic.carbon +++ b/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 diff --git a/toolchain/check/testdata/interop/cpp/namespace/roundtrip.carbon b/toolchain/check/testdata/interop/cpp/namespace/roundtrip.carbon index 7ce1421d1e8b..1bd9dfab82e6 100644 --- a/toolchain/check/testdata/interop/cpp/namespace/roundtrip.carbon +++ b/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: | ~~~~~~~~^ diff --git a/toolchain/check/testdata/interop/cpp/reverse/class.carbon b/toolchain/check/testdata/interop/cpp/reverse/class.carbon index 45dc342ef4dc..0a6b0bd4fd8c 100644 --- a/toolchain/check/testdata/interop/cpp/reverse/class.carbon +++ b/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; +'''; diff --git a/toolchain/sem_ir/name_scope.h b/toolchain/sem_ir/name_scope.h index 958ed0e22ebe..b5de2c07b819 100644 --- a/toolchain/sem_ir/name_scope.h +++ b/toolchain/sem_ir/name_scope.h @@ -217,16 +217,16 @@ class NameScope : public Printable { 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 { // 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, 0> import_ir_scopes_;