diff --git a/toolchain/check/cpp/export.cpp b/toolchain/check/cpp/export.cpp index e854a4fb3dbf..bf8d85c013da 100644 --- a/toolchain/check/cpp/export.cpp +++ b/toolchain/check/cpp/export.cpp @@ -168,14 +168,101 @@ auto ExportNameScopeToCpp(Context& context, SemIR::LocId loc_id, return decl_context; } +static auto GetClassTypeInstId(Context& context, SemIR::ClassId class_id, + SemIR::SpecificId specific_id) + -> SemIR::TypeInstId { + auto type_id = GetClassType(context, class_id, specific_id); + return context.types().GetTypeInstId(type_id); +} + +// Creates a `clang::ClassTemplateSpecializationDecl`, and registers it +// with the `ClassTemplateDecl` and `clang_decls`. +static auto CreateClassTemplateSpecializationDecl( + Context& context, clang::ClassTemplateDecl* class_template_decl, + llvm::ArrayRef template_args, + SemIR::TypeInstId class_type_inst_id) + -> clang::ClassTemplateSpecializationDecl* { + auto* class_template_specialization_decl = + clang::ClassTemplateSpecializationDecl::Create( + context.ast_context(), + class_template_decl->getTemplatedDecl()->getTagKind(), + class_template_decl->getDeclContext(), + class_template_decl->getTemplatedDecl()->getBeginLoc(), + class_template_decl->getLocation(), class_template_decl, + template_args, + /*StrictPackMatch=*/false, + /*PrevDecl=*/nullptr); + class_template_decl->AddSpecialization(class_template_specialization_decl, + /*InsertPos=*/nullptr); + class_template_specialization_decl->setHasExternalLexicalStorage(); + class_template_specialization_decl->setHasExternalVisibleStorage(); + + // Create and store the `ClangDecl`. + auto key = SemIR::ClangDeclKey::ForNonFunctionDecl( + class_template_specialization_decl); + context.clang_decls().Add({.key = key, .inst_id = class_type_inst_id}); + + return class_template_specialization_decl; +} + +// Exports a specific Carbon class into C++ as a template class specialization. +// +// If the specific class has already been exported, returns the existing C++ +// decl. Otherwise, creates a new C++ class template specialization and +// returns it. Returns nullptr if the class could not be exported and an error +// was diagnosed. +static auto ExportClassSpecificToCpp(Context& context, SemIR::LocId loc_id, + SemIR::ClassType class_type) + -> clang::ClassTemplateSpecializationDecl* { + CARBON_CHECK(class_type.specific_id.has_value()); + + // Use existing export if possible. + auto class_type_inst_id = + GetClassTypeInstId(context, class_type.class_id, class_type.specific_id); + if (const auto* clang_decl = + context.clang_decls().Lookup(class_type_inst_id)) { + return cast(clang_decl->decl()); + } + + // Ensure the generic class is exported, and get its `ClassTemplateDecl`. + auto generic_class_type_id = GetGenericClassType(context, class_type.class_id, + SemIR::SpecificId::None); + auto generic_class_type = + context.types().GetAs(generic_class_type_id); + auto* class_template_decl = + ExportGenericClassToCpp(context, generic_class_type); + if (!class_template_decl) { + return nullptr; + } + + llvm::SmallVector template_args; + const auto specific = context.specifics().Get(class_type.specific_id); + auto specific_args = context.inst_blocks().Get(specific.args_id); + for (auto specific_arg_inst_id : specific_args) { + // TODO: also handle non-type args. Such args can't happen here yet, since + // the `ExportGenericClassToCpp` call above already checks for them. + + auto cpp_type = MapToCppType( + context, context.types().GetTypeIdForTypeInstId(specific_arg_inst_id)); + if (cpp_type.isNull()) { + context.TODO(loc_id, "failed to map specific type arg to C++"); + return nullptr; + } + + template_args.push_back(cpp_type); + } + + return CreateClassTemplateSpecializationDecl( + context, class_template_decl, template_args, class_type_inst_id); +} + auto ExportClassToCpp(Context& context, SemIR::ClassType class_type) -> clang::TagDecl* { const auto& class_info = context.classes().Get(class_type.class_id); SemIR::LocId loc_id(class_info.first_decl_id()); if (class_type.specific_id.has_value()) { - context.TODO(loc_id, "interop with specific class"); - return nullptr; + return ExportClassSpecificToCpp(context, loc_id, class_type); } // If this class was produced by importing a C++ declaration or has @@ -338,13 +425,6 @@ auto ExportGenericClassToCpp(Context& context, return class_template_decl; } -static auto GetClassTypeInstId(Context& context, SemIR::ClassId class_id, - SemIR::SpecificId specific_id) - -> SemIR::TypeInstId { - auto type_id = GetClassType(context, class_id, specific_id); - return context.types().GetTypeInstId(type_id); -} - auto ExportClassSpecializationToCpp( Context& context, clang::ClassTemplateDecl* class_template_decl, llvm::ArrayRef template_args) -> bool { @@ -369,27 +449,10 @@ auto ExportClassSpecializationToCpp( return false; } - auto* class_template_specialization_decl = - clang::ClassTemplateSpecializationDecl::Create( - context.ast_context(), - class_template_decl->getTemplatedDecl()->getTagKind(), - class_template_decl->getDeclContext(), - class_template_decl->getTemplatedDecl()->getBeginLoc(), - class_template_decl->getLocation(), class_template_decl, - template_args, - /*StrictPackMatch=*/false, - /*PrevDecl=*/nullptr); - class_template_decl->AddSpecialization(class_template_specialization_decl, - /*InsertPos=*/nullptr); - class_template_specialization_decl->setHasExternalLexicalStorage(); - class_template_specialization_decl->setHasExternalVisibleStorage(); - - // Create and store the `ClangDeclId`. auto class_type_inst_id = GetClassTypeInstId(context, class_decl.class_id, specific_id); - auto key = SemIR::ClangDeclKey::ForNonFunctionDecl( - class_template_specialization_decl); - context.clang_decls().Add({.key = key, .inst_id = class_type_inst_id}); + CreateClassTemplateSpecializationDecl(context, class_template_decl, + template_args, class_type_inst_id); return true; } diff --git a/toolchain/check/testdata/interop/cpp/class/export/class.carbon b/toolchain/check/testdata/interop/cpp/class/export/class.carbon index 22c2d95195aa..40ca6857c3e1 100644 --- a/toolchain/check/testdata/interop/cpp/class/export/class.carbon +++ b/toolchain/check/testdata/interop/cpp/class/export/class.carbon @@ -81,7 +81,7 @@ library "[[@TEST_NAME]]"; import Cpp; -// CHECK:STDERR: fail_todo_monomorphization_failure.carbon:[[@LINE+4]]:1: error: semantics TODO: `interop with specific class` [SemanticsTodo] +// CHECK:STDERR: fail_todo_monomorphization_failure.carbon:[[@LINE+4]]:1: error: semantics TODO: `binding maps to a non-type template parameter` [SemanticsTodo] // CHECK:STDERR: class B(N: i32) { // CHECK:STDERR: ^~~~~~~~~~~~~~~~~ // CHECK:STDERR: diff --git a/toolchain/check/testdata/interop/cpp/class/export/generic.carbon b/toolchain/check/testdata/interop/cpp/class/export/generic.carbon index d7e39e1721f5..68dff369b704 100644 --- a/toolchain/check/testdata/interop/cpp/class/export/generic.carbon +++ b/toolchain/check/testdata/interop/cpp/class/export/generic.carbon @@ -25,14 +25,10 @@ void F() { } '''; -// --- fail_todo_specific_alias.carbon +// --- specific_alias.carbon library "[[@TEST_NAME]]"; import Cpp; -// CHECK:STDERR: fail_todo_specific_alias.carbon:[[@LINE+4]]:1: error: semantics TODO: `interop with specific class` [SemanticsTodo] -// CHECK:STDERR: class C(T: type) { -// CHECK:STDERR: ^~~~~~~~~~~~~~~~~~ -// CHECK:STDERR: class C(T: type) { var t: T; } @@ -40,20 +36,36 @@ alias A = C(i32); inline Cpp ''' void F() { - // CHECK:STDERR: fail_todo_specific_alias.carbon:[[@LINE+8]]:11: error: semantics TODO: `interop with unsupported type` [SemanticsTodo] + Carbon::A a; + a.t = 123; +} +'''; + +// --- fail_unsupported_specific_arg.carbon +library "[[@TEST_NAME]]"; +import Cpp; + +// CHECK:STDERR: fail_unsupported_specific_arg.carbon:[[@LINE+4]]:1: error: semantics TODO: `failed to map specific type arg to C++` [SemanticsTodo] +// CHECK:STDERR: class C(T: type) { +// CHECK:STDERR: ^~~~~~~~~~~~~~~~~~ +// CHECK:STDERR: +class C(T: type) { + var t: T; +} +alias A = C(()); + +inline Cpp ''' +void F() { + // CHECK:STDERR: fail_unsupported_specific_arg.carbon:[[@LINE+8]]:11: error: semantics TODO: `interop with unsupported type` [SemanticsTodo] // CHECK:STDERR: Carbon::A a; // CHECK:STDERR: ^ // CHECK:STDERR: - // CHECK:STDERR: fail_todo_specific_alias.carbon:[[@LINE+4]]:11: error: no type named 'A' in namespace 'Carbon' [CppInteropParseError] + // CHECK:STDERR: fail_unsupported_specific_arg.carbon:[[@LINE+4]]:11: error: no type named 'A' in namespace 'Carbon' [CppInteropParseError] // CHECK:STDERR: 23 | Carbon::A a; // CHECK:STDERR: | ~~~~~~~~^ // CHECK:STDERR: Carbon::A a; - // CHECK:STDERR: fail_todo_specific_alias.carbon:[[@LINE+4]]:3: error: use of undeclared identifier 'c' [CppInteropParseError] - // CHECK:STDERR: 28 | c.t = 123; - // CHECK:STDERR: | ^ - // CHECK:STDERR: - c.t = 123; + a.t = 123; } '''; diff --git a/toolchain/check/testdata/interop/cpp/function/export/generic.carbon b/toolchain/check/testdata/interop/cpp/function/export/generic.carbon index 3bdad61ca90a..00b2e64ccf92 100644 --- a/toolchain/check/testdata/interop/cpp/function/export/generic.carbon +++ b/toolchain/check/testdata/interop/cpp/function/export/generic.carbon @@ -90,11 +90,11 @@ void G() { library "[[@TEST_NAME]]"; import Cpp; -// CHECK:STDERR: fail_todo_enclosing_generic.carbon:[[@LINE+4]]:1: error: semantics TODO: `interop with specific class` [SemanticsTodo] -// CHECK:STDERR: class A(T: type) { -// CHECK:STDERR: ^~~~~~~~~~~~~~~~~~ -// CHECK:STDERR: class A(T: type) { + // CHECK:STDERR: fail_todo_enclosing_generic.carbon:[[@LINE+4]]:3: error: semantics TODO: `non-class non-namespace name scope` [SemanticsTodo] + // CHECK:STDERR: fn F[U: type](x: T, y: U); + // CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~ + // CHECK:STDERR: fn F[U: type](x: T, y: U); } alias B = A(i32); @@ -104,15 +104,11 @@ void f() { // TODO: this currently fails because `B` can't be imported, but the intent // is to test a generic function within an enclosing generic scope. // - // CHECK:STDERR: fail_todo_enclosing_generic.carbon:[[@LINE+8]]:11: error: semantics TODO: `interop with unsupported type` [SemanticsTodo] - // CHECK:STDERR: Carbon::B b; - // CHECK:STDERR: ^ - // CHECK:STDERR: - // CHECK:STDERR: fail_todo_enclosing_generic.carbon:[[@LINE+4]]:11: error: no type named 'B' in namespace 'Carbon' [CppInteropParseError] - // CHECK:STDERR: 26 | Carbon::B b; - // CHECK:STDERR: | ~~~~~~~~^ - // CHECK:STDERR: Carbon::B b; + // CHECK:STDERR: fail_todo_enclosing_generic.carbon:[[@LINE+4]]:5: error: no member named 'F' in 'Carbon::A' [CppInteropParseError] + // CHECK:STDERR: 23 | b.F(1, 2); + // CHECK:STDERR: | ~ ^ + // CHECK:STDERR: b.F(1, 2); } '''; diff --git a/toolchain/check/testdata/interop/cpp/function/import/param_unsupported.carbon b/toolchain/check/testdata/interop/cpp/function/import/param_unsupported.carbon index 1b36196fedf2..e046a2e0ab98 100644 --- a/toolchain/check/testdata/interop/cpp/function/import/param_unsupported.carbon +++ b/toolchain/check/testdata/interop/cpp/function/import/param_unsupported.carbon @@ -78,7 +78,7 @@ fn F(x: i512) { // CHECK:STDERR: Cpp.foo(x); // CHECK:STDERR: ^ // CHECK:STDERR: - // CHECK:STDERR: min_prelude/parts/int.carbon:10:1: error: semantics TODO: `interop with specific class` [SemanticsTodo] + // CHECK:STDERR: min_prelude/parts/int.carbon:10:1: error: semantics TODO: `interop with non-identifier package name` [SemanticsTodo] // CHECK:STDERR: class Int(N: IntLiteral) { // CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~ // CHECK:STDERR: