From 43867a678b156aa093d3cee3bf997b38c1dc6f04 Mon Sep 17 00:00:00 2001 From: Richard Smith Date: Mon, 13 Apr 2026 17:18:33 -0700 Subject: [PATCH] Reverse interop support for type aliases. (#7043) Allow any type that has a mapping from Carbon to C++ to be exposed to C++ via name lookup. This also exposes the logic to export Carbon classes to C++ to apply during type mapping, which gives very slight support for passing Carbon types to C++ functions from Carbon, but not really enough to sensibly test yet. Depends on #7042. --- toolchain/check/cpp/access.cpp | 11 +++ toolchain/check/cpp/access.h | 4 ++ toolchain/check/cpp/export.cpp | 8 +-- toolchain/check/cpp/generate_ast.cpp | 68 ++++++++++++++----- toolchain/check/cpp/type_mapping.cpp | 35 +++++----- .../testdata/interop/cpp/class/access.carbon | 10 ++- .../interop/cpp/type_alias/export.carbon | 36 ++++++++++ .../import.carbon} | 4 +- toolchain/sem_ir/constant.h | 5 ++ 9 files changed, 135 insertions(+), 46 deletions(-) create mode 100644 toolchain/check/testdata/interop/cpp/type_alias/export.carbon rename toolchain/check/testdata/interop/cpp/{typedef.carbon => type_alias/import.carbon} (99%) diff --git a/toolchain/check/cpp/access.cpp b/toolchain/check/cpp/access.cpp index 291577dffa49..f641b402b33b 100644 --- a/toolchain/check/cpp/access.cpp +++ b/toolchain/check/cpp/access.cpp @@ -44,4 +44,15 @@ auto MapCppAccess(clang::DeclAccessPair access_pair) -> SemIR::AccessKind { } } +auto MapToCppAccess(SemIR::AccessKind access) -> clang::AccessSpecifier { + switch (access) { + case SemIR::AccessKind::Public: + return clang::AS_public; + case SemIR::AccessKind::Protected: + return clang::AS_protected; + case SemIR::AccessKind::Private: + return clang::AS_private; + } +} + } // namespace Carbon::Check diff --git a/toolchain/check/cpp/access.h b/toolchain/check/cpp/access.h index 557defe74fa6..04b7d070dd85 100644 --- a/toolchain/check/cpp/access.h +++ b/toolchain/check/cpp/access.h @@ -13,6 +13,10 @@ namespace Carbon::Check { // access) pair. auto MapCppAccess(clang::DeclAccessPair access_pair) -> SemIR::AccessKind; +// Maps a Carbon access kind to a C++ access specifier, suitable for use when +// declaring a C++ class member with the same access. Never returns AS_none. +auto MapToCppAccess(SemIR::AccessKind access) -> clang::AccessSpecifier; + } // namespace Carbon::Check #endif // CARBON_TOOLCHAIN_CHECK_CPP_ACCESS_H_ diff --git a/toolchain/check/cpp/export.cpp b/toolchain/check/cpp/export.cpp index f9e46a68f54b..eaa8a11a0d2a 100644 --- a/toolchain/check/cpp/export.cpp +++ b/toolchain/check/cpp/export.cpp @@ -131,12 +131,8 @@ auto ExportClassToCpp(Context& context, SemIR::LocId loc_id, } 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; - } + CARBON_CHECK(identifier_info, "non-identifier class name {0}", + class_info.name_id); auto* decl_context = ExportNameScopeToCpp(context, loc_id, class_info.parent_scope_id); diff --git a/toolchain/check/cpp/generate_ast.cpp b/toolchain/check/cpp/generate_ast.cpp index 8427675e7734..c0a66b3055ea 100644 --- a/toolchain/check/cpp/generate_ast.cpp +++ b/toolchain/check/cpp/generate_ast.cpp @@ -28,8 +28,11 @@ #include "llvm/Support/raw_ostream.h" #include "toolchain/base/kind_switch.h" #include "toolchain/check/context.h" +#include "toolchain/check/cpp/access.h" #include "toolchain/check/cpp/export.h" #include "toolchain/check/cpp/import.h" +#include "toolchain/check/cpp/location.h" +#include "toolchain/check/cpp/type_mapping.h" #include "toolchain/check/import_ref.h" #include "toolchain/check/name_lookup.h" #include "toolchain/check/type_completion.h" @@ -38,6 +41,7 @@ #include "toolchain/diagnostics/format_providers.h" #include "toolchain/parse/node_ids.h" #include "toolchain/sem_ir/cpp_file.h" +#include "toolchain/sem_ir/typed_insts.h" namespace Carbon::Check { @@ -340,7 +344,8 @@ class CarbonExternalASTSource : public clang::ExternalASTSource { // Map a Carbon entity to a Clang NamedDecl. Returns null if the entity cannot // currently be represented in C++. - auto MapInstIdToClangDecl(LookupResult lookup) -> clang::NamedDecl*; + auto MapInstIdToClangDeclOrType(LookupResult lookup) + -> std::variant; // Get a current best-effort location for the current position within C++ // processing. @@ -374,12 +379,23 @@ void CarbonExternalASTSource::StartTranslationUnit( BuildCarbonNamespace(); } -auto CarbonExternalASTSource::MapInstIdToClangDecl(LookupResult lookup) - -> clang::NamedDecl* { +auto CarbonExternalASTSource::MapInstIdToClangDeclOrType(LookupResult lookup) + -> std::variant { auto target_inst_id = lookup.scope_result.target_inst_id(); - auto target_constant = - context_->constant_values().GetConstantInstId(target_inst_id); - auto target_inst = context_->insts().Get(target_constant); + auto target_const_id = context_->constant_values().Get(target_inst_id); + auto target_inst = context_->constant_values().GetInst(target_const_id); + + if (target_inst.type_id() == SemIR::TypeType::TypeId) { + auto type_id = + context_->types().GetTypeIdForTypeConstantId(target_const_id); + auto type = MapToCppType(*context_, type_id); + if (type.isNull()) { + context_->TODO(GetCurrentCppLocId(), "interop with unsupported type"); + return nullptr; + } + return type; + } + CARBON_KIND_SWITCH(target_inst) { case CARBON_KIND(SemIR::Namespace namespace_info): { auto* decl_context = @@ -395,12 +411,8 @@ auto CarbonExternalASTSource::MapInstIdToClangDecl(LookupResult lookup) } return cast(decl_context); } - case CARBON_KIND(SemIR::ClassType class_type): { - 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); + auto callee = GetCallee(context_->sem_ir(), target_inst_id); auto* callee_function = std::get_if(&callee); if (!callee_function) { return nullptr; @@ -490,13 +502,35 @@ auto CarbonExternalASTSource::FindExternalVisibleDeclsByName( } // Map the found Carbon entity to a Clang NamedDecl. - auto* clang_decl = MapInstIdToClangDecl(result); - if (!clang_decl) { - return false; - } + CARBON_KIND_SWITCH(MapInstIdToClangDeclOrType(result)) { + case CARBON_KIND(clang::NamedDecl* clang_decl): { + if (clang_decl) { + SetExternalVisibleDeclsForName(decl_context, decl_name, {clang_decl}); + return true; + } else { + SetNoExternalVisibleDeclsForName(decl_context, decl_name); + return false; + } + } - SetExternalVisibleDeclsForName(decl_context, decl_name, {clang_decl}); - return true; + case CARBON_KIND(clang::QualType type): { + // Create a typedef declaration to model the type result. + // TODO: If the type is a tag type that was declared with this name in + // this context, use the tag decl directly. + auto& ast_context = context_->ast_context(); + auto loc = GetCppLocation( + *context_, SemIR::LocId(result.scope_result.target_inst_id())); + auto* typedef_decl = clang::TypedefDecl::Create( + ast_context, const_cast(decl_context), loc, loc, + identifier, ast_context.getTrivialTypeSourceInfo(type, loc)); + if (isa(decl_context)) { + typedef_decl->setAccess( + MapToCppAccess(result.scope_result.access_kind())); + } + SetExternalVisibleDeclsForName(decl_context, decl_name, {typedef_decl}); + return true; + } + } } auto CarbonExternalASTSource::CompleteType(clang::TagDecl* tag_decl) -> void { diff --git a/toolchain/check/cpp/type_mapping.cpp b/toolchain/check/cpp/type_mapping.cpp index be57cf5dc887..2e6db1a6390b 100644 --- a/toolchain/check/cpp/type_mapping.cpp +++ b/toolchain/check/cpp/type_mapping.cpp @@ -17,6 +17,7 @@ #include "toolchain/base/value_ids.h" #include "toolchain/check/context.h" #include "toolchain/check/convert.h" +#include "toolchain/check/cpp/export.h" #include "toolchain/check/cpp/import.h" #include "toolchain/check/cpp/location.h" #include "toolchain/check/literal.h" @@ -137,21 +138,10 @@ static auto VerifyIntegerTypeWidth(Context& context, clang::QualType type, // Maps a Carbon class type to a C++ type. Returns a null `QualType` if the // type is not supported. -static auto TryMapClassType(Context& context, SemIR::ClassType class_type) - -> TryMapTypeResult { +static auto TryMapClassType(Context& context, SemIR::TypeInstId class_inst_id, + SemIR::ClassType class_type) -> TryMapTypeResult { clang::ASTContext& ast_context = context.ast_context(); - // If the class was imported from C++, return the original C++ type. - auto clang_decl_id = - context.name_scopes() - .Get(context.classes().Get(class_type.class_id).scope_id) - .clang_decl_context_id(); - if (clang_decl_id.has_value()) { - clang::Decl* clang_decl = context.clang_decls().Get(clang_decl_id).key.decl; - auto* tag_type_decl = clang::cast(clang_decl); - return ast_context.getCanonicalTagType(tag_type_decl); - } - // If the class represents a Carbon type literal, map it to the corresponding // C++ builtin type. auto type_info = @@ -230,10 +220,18 @@ static auto TryMapClassType(Context& context, SemIR::ClassType class_type) } } - // Otherwise we don't have a mapping for this Carbon class type. - // TODO: If the class type wasn't imported from C++, create a corresponding - // C++ class type. - return clang::QualType(); + // TODO: We cannot yet map specific classes. + if (class_type.specific_id.has_value()) { + return clang::QualType(); + } + + // Otherwise, find the existing C++ declaration or create a new one. + auto* tag_decl = ExportClassToCpp(context, SemIR::LocId(class_inst_id), + class_inst_id, class_type); + if (!tag_decl) { + return clang::QualType(); + } + return ast_context.getCanonicalTagType(tag_decl); } // Maps a Carbon type to a C++ type. Either returns the mapped type, a null type @@ -253,7 +251,8 @@ static auto TryMapType(Context& context, SemIR::TypeId type_id) return context.ast_context().CharTy; } case CARBON_KIND(SemIR::ClassType class_type): { - return TryMapClassType(context, class_type); + return TryMapClassType(context, context.types().GetTypeInstId(type_id), + class_type); } case CARBON_KIND(SemIR::ConstType const_type): { return WrappedType{ diff --git a/toolchain/check/testdata/interop/cpp/class/access.carbon b/toolchain/check/testdata/interop/cpp/class/access.carbon index 4ffac43a3b8b..43c350a5be73 100644 --- a/toolchain/check/testdata/interop/cpp/class/access.carbon +++ b/toolchain/check/testdata/interop/cpp/class/access.carbon @@ -316,9 +316,13 @@ class Derived { fn F[self: Self]() { //@dump-sem-ir-begin // TODO: It should be possible to call protected methods in the base class. - // CHECK:STDERR: fail_todo_import_function_member_protected_extend_call_instance_in_member.carbon:[[@LINE+4]]:5: error: call argument of type `Derived` is not supported [CppCallArgTypeNotSupported] - // CHECK:STDERR: self.instance_fn(); - // CHECK:STDERR: ^~~~ + // CHECK:STDERR: fail_todo_import_function_member_protected_extend_call_instance_in_member.carbon:[[@LINE+8]]:22: error: no matching function for call to 'instance_fn' [CppInteropParseError] + // CHECK:STDERR: 19 | self.instance_fn(); + // CHECK:STDERR: | ^ + // CHECK:STDERR: fail_todo_import_function_member_protected_extend_call_instance_in_member.carbon:[[@LINE-10]]:10: in file included here [InCppInclude] + // CHECK:STDERR: ./function_member_protected.h:4:8: note: candidate function not viable: cannot convert argument of incomplete type 'const Carbon::Derived' to 'C' for object argument [CppInteropParseNote] + // CHECK:STDERR: 4 | auto instance_fn() -> void; + // CHECK:STDERR: | ^ // CHECK:STDERR: self.instance_fn(); //@dump-sem-ir-end diff --git a/toolchain/check/testdata/interop/cpp/type_alias/export.carbon b/toolchain/check/testdata/interop/cpp/type_alias/export.carbon new file mode 100644 index 000000000000..3ee4bced61fe --- /dev/null +++ b/toolchain/check/testdata/interop/cpp/type_alias/export.carbon @@ -0,0 +1,36 @@ +// 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/int.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/type_alias/export.carbon +// TIP: To dump output, run: +// TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/interop/cpp/type_alias/export.carbon + +// --- aliases.carbon +library "[[@TEST_NAME]]"; + +import Cpp; + +alias A = i32; + +class C {} +alias CA = C; + +inline Cpp ''' +// Valid redeclarations -- same type. +using T = Carbon::A; +using T = int; + +// Valid redeclarations -- same type. +using U = Carbon::C; +using U = Carbon::CA; +'''; + +// Valid: Cpp.U is the same type as CA. +fn F(p: CA*) -> Cpp.U* { + return p; +} diff --git a/toolchain/check/testdata/interop/cpp/typedef.carbon b/toolchain/check/testdata/interop/cpp/type_alias/import.carbon similarity index 99% rename from toolchain/check/testdata/interop/cpp/typedef.carbon rename to toolchain/check/testdata/interop/cpp/type_alias/import.carbon index 63d8d6b8ef31..0f14006f8d4c 100644 --- a/toolchain/check/testdata/interop/cpp/typedef.carbon +++ b/toolchain/check/testdata/interop/cpp/type_alias/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/typedef.carbon +// TIP: bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/check/testdata/interop/cpp/type_alias/import.carbon // TIP: To dump output, run: -// TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/interop/cpp/typedef.carbon +// TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/interop/cpp/type_alias/import.carbon // --- i32_typedef.h diff --git a/toolchain/sem_ir/constant.h b/toolchain/sem_ir/constant.h index 2a294c52d26d..d520209fb479 100644 --- a/toolchain/sem_ir/constant.h +++ b/toolchain/sem_ir/constant.h @@ -188,6 +188,11 @@ class ConstantValueStore { return insts_->Is(GetInstId(const_id)); } + // Returns the requested instruction from the underlying constant inst. + auto GetInst(ConstantId const_id) const -> Inst { + return insts_->Get(GetInstId(const_id)); + } + // Returns the requested instruction from the underlying constant inst, which // is known to have the specified type. template