diff --git a/toolchain/check/cpp/generate_ast.cpp b/toolchain/check/cpp/generate_ast.cpp index 0f59ecdc3f7e..3fc7d87acbbb 100644 --- a/toolchain/check/cpp/generate_ast.cpp +++ b/toolchain/check/cpp/generate_ast.cpp @@ -8,6 +8,7 @@ #include #include "clang/AST/ASTContext.h" +#include "clang/AST/Decl.h" #include "clang/Basic/FileManager.h" #include "clang/CodeGen/ModuleBuilder.h" #include "clang/Frontend/CompilerInstance.h" @@ -340,12 +341,6 @@ class CarbonExternalASTSource : public clang::ExternalASTSource { Check::Context* context_; clang::ASTContext* ast_context_; - // The association between clang DeclContexts and the corresponding - // SemIR::Namespaces in Carbon. - // TODO: reuse the SemIR::File::ClangDeclStore to avoid duplicates, and to - // enable roundtripping through forward and reverse interop (once we have - // syntax/support for that). - Map scope_map_; // Has the "Carbon" C++ namespace been created yet // (this could be replaced with `!scope_map_.empty()` if Carbon::Map supported @@ -363,6 +358,21 @@ void CarbonExternalASTSource::StartTranslationUnit( 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( + context.clang_decls().Get(scope.clang_decl_context_id()).key.decl); +} + auto CarbonExternalASTSource::MapInstIdToClangDecl( clang::DeclContext& decl_context, LookupResult lookup) -> clang::NamedDecl* { @@ -372,10 +382,21 @@ 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"); + 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. @@ -384,19 +405,26 @@ auto CarbonExternalASTSource::MapInstIdToClangDecl( auto* namespace_decl = clang::NamespaceDecl::Create( *ast_context_, &decl_context, false, clang::SourceLocation(), clang::SourceLocation(), identifier_info, nullptr, false); - auto result = scope_map_.Insert(namespace_decl->getPrimaryContext(), - target_inst_id); - CARBON_CHECK(result.is_inserted(), "Inserting over an existing entry."); + auto key = SemIR::ClangDeclKey::ForNonFunctionDecl(namespace_decl); + context_->clang_decls().Add({.key = key, .inst_id = target_inst_id}); namespace_decl->setHasExternalVisibleStorage(); return namespace_decl; } 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); - return clang::CXXRecordDecl::Create( + CARBON_CHECK(identifier_info, "class with non-identifier name {0}", + class_info.name_id); + 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}); + return record_decl; } case SemIR::StructValue::Kind: { auto callee = GetCallee(context_->sem_ir(), target_constant); @@ -406,8 +434,14 @@ auto CarbonExternalASTSource::MapInstIdToClangDecl( } const SemIR::Function& function = context_->functions().Get(callee_function->function_id); + if (function.clang_decl_id.has_value()) { + return cast( + context_->clang_decls().Get(function.clang_decl_id).key.decl); + } auto* identifier_info = GetClangIdentifierInfo(*context_, function.name_id); + CARBON_CHECK(identifier_info, "function with non-identifier name {0}", + function.name_id); if (function.call_param_ranges.explicit_size() != 0) { context_->TODO(target_inst_id, @@ -480,20 +514,24 @@ auto CarbonExternalASTSource::FindExternalVisibleDeclsByName( clang::SourceLocation(), &ast_context.Idents.get(carbon_namespace_name), nullptr, false); carbon_cpp_namespace->setHasExternalVisibleStorage(); - auto result = scope_map_.Insert(carbon_cpp_namespace->getPrimaryContext(), - SemIR::Namespace::PackageInstId); - CARBON_CHECK(result.is_inserted(), "Inserting over an existing entry."); + 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; } - auto decl_context_inst_id = - scope_map_.Lookup(decl_context->getPrimaryContext()); + // Find the Carbon declaration corresponding to this Clang declaration. + auto* decl = cast( + const_cast(decl_context->getPrimaryContext())); + auto key = SemIR::ClangDeclKey::ForNonFunctionDecl(decl); + auto decl_id = context_->clang_decls().Lookup(key); CARBON_CHECK( - decl_context_inst_id, + decl_id.has_value(), "The DeclContext should already be associated with a Carbon InstId."); + auto decl_context_inst_id = context_->clang_decls().Get(decl_id).inst_id; llvm::SmallVector lookup_scopes; @@ -501,7 +539,7 @@ auto CarbonExternalASTSource::FindExternalVisibleDeclsByName( // here - completeness should've been checked by clang before this point. if (!AppendLookupScopesForConstant( *context_, SemIR::LocId::None, - context_->constant_values().Get(decl_context_inst_id.value()), + context_->constant_values().Get(decl_context_inst_id), SemIR::ConstantId::None, &lookup_scopes)) { return false; } @@ -524,8 +562,11 @@ auto CarbonExternalASTSource::FindExternalVisibleDeclsByName( } // Map the found Carbon entity to a Clang NamedDecl. - // Use the key to reach the owned, mutable copy of decl_context. - auto* clang_decl = MapInstIdToClangDecl(*decl_context_inst_id.key(), result); + // 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); if (!clang_decl) { return false; } diff --git a/toolchain/check/testdata/interop/cpp/class/roundtrip.carbon b/toolchain/check/testdata/interop/cpp/class/roundtrip.carbon new file mode 100644 index 000000000000..db9166c3b794 --- /dev/null +++ b/toolchain/check/testdata/interop/cpp/class/roundtrip.carbon @@ -0,0 +1,43 @@ +// 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/class/roundtrip.carbon +// TIP: To dump output, run: +// TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/interop/cpp/class/roundtrip.carbon + +// --- carbon_to_cpp_to_carbon.carbon +library "[[@TEST_NAME]]"; + +import Cpp; + +class C {} + +inline Cpp ''' +using CAlias = Carbon::C; +'''; + +fn F(p: C*) { + let unused q: Cpp.CAlias* = p; +} + +// --- cpp_to_carbon_to_cpp.carbon +library "[[@TEST_NAME]]"; + +import Cpp; + +inline Cpp ''' +class C {}; +'''; + +alias CAlias = Cpp.C; + +inline Cpp ''' +auto F(C *p) -> Carbon::CAlias* { + return p; +} +'''; diff --git a/toolchain/check/testdata/interop/cpp/namespace.carbon b/toolchain/check/testdata/interop/cpp/namespace/basic.carbon similarity index 99% rename from toolchain/check/testdata/interop/cpp/namespace.carbon rename to toolchain/check/testdata/interop/cpp/namespace/basic.carbon index c7283d8c8844..f61816241a81 100644 --- a/toolchain/check/testdata/interop/cpp/namespace.carbon +++ b/toolchain/check/testdata/interop/cpp/namespace/basic.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.carbon +// TIP: bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/check/testdata/interop/cpp/namespace/basic.carbon // TIP: To dump output, run: -// TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/interop/cpp/namespace.carbon +// TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/interop/cpp/namespace/basic.carbon // ============================================================================ // Single diff --git a/toolchain/check/testdata/interop/cpp/namespace/roundtrip.carbon b/toolchain/check/testdata/interop/cpp/namespace/roundtrip.carbon new file mode 100644 index 000000000000..7ce1421d1e8b --- /dev/null +++ b/toolchain/check/testdata/interop/cpp/namespace/roundtrip.carbon @@ -0,0 +1,100 @@ +// 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/roundtrip.carbon +// TIP: To dump output, run: +// TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/interop/cpp/namespace/roundtrip.carbon + +// --- carbon_to_cpp_to_carbon.carbon +library "[[@TEST_NAME]]"; + +import Cpp; + +namespace N; + +inline Cpp ''' +namespace M = Carbon::N; +'''; + +class C {} + +fn N.F(c: C); + +fn G(c: C) { + Cpp.M.F(c); +} + +// --- cpp_to_carbon_to_cpp.carbon +library "[[@TEST_NAME]]"; + +import Cpp; + +inline Cpp ''' +namespace N {} +'''; + +alias M = Cpp.N; + +inline Cpp ''' +namespace N { void F(); } + +static_assert(&N::F == &Carbon::M::F); +'''; + +// --- cpp_carbon.carbon +library "[[@TEST_NAME]]"; + +import Cpp; + +fn F(); + +fn G() { + // TODO: Should we disallow this? + Cpp.Carbon.F(); +} + +// --- fail_carbon_cpp.carbon +library "[[@TEST_NAME]]"; + +import Cpp; + +inline Cpp ''' +void F(); + +void G() { + // This is not found because it looks for the identifier `Cpp`, not the keyword `Cpp`. + // CHECK:STDERR: fail_carbon_cpp.carbon:[[@LINE+4]]:11: error: no member named 'Cpp' in namespace 'Carbon' [CppInteropParseError] + // CHECK:STDERR: 14 | Carbon::Cpp::F(); + // CHECK:STDERR: | ~~~~~~~~^ + // CHECK:STDERR: + Carbon::Cpp::F(); +} +'''; + +// --- fail_carbon_raw_cpp.carbon +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+4]]:11: error: no member named 'Cpp' in namespace 'Carbon' [CppInteropParseError] + // CHECK:STDERR: 19 | Carbon::Cpp::F(); + // CHECK:STDERR: | ~~~~~~~~^ + // CHECK:STDERR: + Carbon::Cpp::F(); +} +''';