diff --git a/toolchain/check/cpp/export.cpp b/toolchain/check/cpp/export.cpp index 7a2204a72d7f..ed2cb3e67c9c 100644 --- a/toolchain/check/cpp/export.cpp +++ b/toolchain/check/cpp/export.cpp @@ -1327,6 +1327,30 @@ static auto BuildCppToCarbonThunkFunctionType(Context& context, ->getAs(); } +// Look up the `SemIR::Class` associated with a `clang::Decl`. The mapping is +// required to exist. +static auto GetClassInfoForClangDecl(Context& context, clang::Decl* decl) + -> const SemIR::Class& { + auto clang_decl_id = context.clang_decls().LookupId( + SemIR::ClangDeclKey::ForNonFunctionDecl(decl)); + const auto& clang_decl = context.clang_decls().Get(clang_decl_id); + auto class_id = SemIR::ClassId::None; + // Regular classes are mapped in `clang_decls` to a `ClassDecl` + // inst. Specific classes are mapped to a `ClassType` plus a specific. + if (auto class_decl = + context.insts().TryGetAs(clang_decl.inst_id)) { + class_id = class_decl->class_id; + + } else if (auto class_type = context.insts().TryGetAs( + clang_decl.inst_id)) { + class_id = class_type->class_id; + + } else { + CARBON_FATAL("failed to map clang decl to a Carbon class"); + } + return context.classes().Get(class_id); +} + // Create the declaration of the C++ thunk. static auto BuildCppToCarbonThunkDecl(Context& context, SemIR::LocId loc_id, const FunctionInfo& target, @@ -1385,8 +1409,13 @@ static auto BuildCppToCarbonThunkDecl(Context& context, SemIR::LocId loc_id, tinfo, target.GetStorageClass(), uses_fp_intrin, inline_specified, constexpr_kind, clang_loc, trailing_requires_clause); } - // TODO: Map Carbon access to C++ access. - thunk_function_decl->setAccess(clang::AS_public); + + // Map Carbon access to C++ access. + const auto& class_info = GetClassInfoForClangDecl(context, parent_class); + const auto& class_scope = context.name_scopes().Get(class_info.scope_id); + SetCppClassMemberAccess(class_scope, target.function.name_id, + thunk_function_decl); + // Carbon overriders are non-virtual in C++; only the corresponding thunk is // virtual. thunk_function_decl->setVirtualAsWritten( @@ -1725,9 +1754,9 @@ auto DefineExportedVirtualFunction(Context& context, SemIR::LocId loc_id, // name. // // Returns nullptr if an error occurs. -auto ExportNonGenericFunctionToCpp(Context& context, SemIR::LocId loc_id, - const FunctionInfo& target, - std::string_view extra_name = "") +static auto ExportNonGenericFunctionToCpp(Context& context, SemIR::LocId loc_id, + const FunctionInfo& target, + std::string_view extra_name = "") -> clang::FunctionDecl* { auto* thunk_function_decl = ExportNonGenericFunctionDeclToCpp(context, loc_id, target); diff --git a/toolchain/check/testdata/interop/cpp/class/export/access.carbon b/toolchain/check/testdata/interop/cpp/class/export/access.carbon new file mode 100644 index 000000000000..8188086504fc --- /dev/null +++ b/toolchain/check/testdata/interop/cpp/class/export/access.carbon @@ -0,0 +1,108 @@ +// 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/destroy.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/export/access.carbon +// TIP: To dump output, run: +// TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/interop/cpp/class/export/access.carbon + +// --- fail_access_exported_private_var.carbon +library "[[@TEST_NAME]]"; +import Cpp; + +class A {} + +class B { + fn Make() -> Self { return {}; } + private var a: A = {}; +} + +inline Cpp ''' +void F() { + auto b = Carbon::B::Make(); + // CHECK:STDERR: fail_access_exported_private_var.carbon:[[@LINE+5]]:5: error: 'a' is a private member of 'Carbon::Main::B' [CppInteropParseError] + // CHECK:STDERR: fail_access_exported_private_var.carbon:[[@LINE-7]]:16: note: implicitly declared private here [CppInteropParseNote] + // CHECK:STDERR: + // CHECK:STDERR: fail_access_exported_private_var.carbon:[[@LINE+2]]:5: warning: expression result unused [CppInteropParseWarning] + // CHECK:STDERR: + b.a; +} +'''; + +// --- fail_access_exported_private_method.carbon +library "[[@TEST_NAME]]"; +import Cpp; + +class A {} + +class B { + fn Make() -> Self { return {}; } + private fn F() {} +} + +inline Cpp ''' +void G() { + auto b = Carbon::B::Make(); + // CHECK:STDERR: fail_access_exported_private_method.carbon:[[@LINE+3]]:5: error: 'F' is a private member of 'Carbon::Main::B' [CppInteropParseError] + // CHECK:STDERR: fail_access_exported_private_method.carbon:[[@LINE-7]]:18: note: implicitly declared private here [CppInteropParseNote] + // CHECK:STDERR: + b.F(); +} +'''; + +// --- fail_access_exported_private_var_in_generic.carbon +library "[[@TEST_NAME]]"; +import Cpp; + +class A {} + +class B(T: type) { + private var a: A = {}; +} + +fn MakeB() -> B(A) { + return {}; +} + +inline Cpp ''' +void F() { + auto b = Carbon::MakeB(); + // CHECK:STDERR: fail_access_exported_private_var_in_generic.carbon:[[@LINE+5]]:5: error: 'a' is a private member of 'Carbon::Main::B' [CppInteropParseError] + // CHECK:STDERR: fail_access_exported_private_var_in_generic.carbon:[[@LINE-11]]:16: note: implicitly declared private here [CppInteropParseNote] + // CHECK:STDERR: + // CHECK:STDERR: fail_access_exported_private_var_in_generic.carbon:[[@LINE+2]]:5: warning: expression result unused [CppInteropParseWarning] + // CHECK:STDERR: + b.a; +} +'''; + +// --- fail_todo_access_exported_private_method_in_generic.carbon +library "[[@TEST_NAME]]"; +import Cpp; + +class A {} + +class B(T: type) { + // TODO: exporting of methods in generics is not yet supported. + // CHECK:STDERR: fail_todo_access_exported_private_method_in_generic.carbon:[[@LINE+2]]:3-18: error: semantics TODO: `non-class non-namespace name scope` [SemanticsTodo] + // CHECK:STDERR: + private fn F() {} +} + +fn MakeB() -> B(A) { + return {}; +} + +inline Cpp ''' +void F() { + auto b = Carbon::MakeB(); + // TODO: this error should be that `F` is a private member of `B`. + // CHECK:STDERR: fail_todo_access_exported_private_method_in_generic.carbon:[[@LINE+2]]:5: error: no member named 'F' in 'Carbon::Main::B' [CppInteropParseError] + // CHECK:STDERR: + b.F(); +} +''';