diff --git a/toolchain/check/cpp/export.cpp b/toolchain/check/cpp/export.cpp index 3c1bdfef5ea2..7d54ac666f3e 100644 --- a/toolchain/check/cpp/export.cpp +++ b/toolchain/check/cpp/export.cpp @@ -716,8 +716,15 @@ static auto BuildCppToCarbonThunkFunctionType(Context& context, } auto ext_proto_info = clang::FunctionProtoType::ExtProtoInfo(); - if (target.self_param && target.self_param->kind == ParamPatternKind::Ref) { - ext_proto_info.RefQualifier = clang::RQ_LValue; + if (target.self_param) { + if (target.self_param->kind == ParamPatternKind::Ref) { + ext_proto_info.RefQualifier = clang::RQ_LValue; + } else { + // A method with `self` doesn't modify the object, so export it as + // `const`. Unlike `ref self`, `self` doesn't require a reference + // expression, so no ref-qualifier is added. + ext_proto_info.TypeQuals.addConst(); + } } return context.ast_context() .getFunctionType(cpp_return_type, thunk_param_types, ext_proto_info) @@ -821,11 +828,11 @@ static auto BuildCppToCarbonThunkDecl(Context& context, SemIR::LocId loc_id, // Get an expr for accessing `this` in a method. static auto GetThisArg(clang::Sema& sema, clang::SourceLocation clang_loc, - clang::CXXRecordDecl* record_decl) -> clang::Expr* { - clang::QualType class_type = - sema.getASTContext().getCanonicalTagType(record_decl); - auto class_ptr_type = sema.getASTContext().getPointerType(class_type); - auto* this_expr = sema.BuildCXXThisExpr(clang_loc, class_ptr_type, + const clang::CXXMethodDecl* method_decl) + -> clang::Expr* { + // These pick up the method's `const` qualifier, if any. + clang::QualType class_type = method_decl->getFunctionObjectParameterType(); + auto* this_expr = sema.BuildCXXThisExpr(clang_loc, method_decl->getThisType(), /*IsImplicit=*/true); return clang::UnaryOperator::Create( sema.getASTContext(), this_expr, clang::UO_Deref, class_type, @@ -874,8 +881,8 @@ static auto BuildCppToCarbonThunkBody(Context& context, llvm::SmallVector call_args; // For methods, pass the `this` pointer as the first argument to the callee. if (target.self_param) { - auto* parent_class = cast(target.decl_context); - call_args.push_back(GetThisArg(sema, clang_loc, parent_class)); + call_args.push_back( + GetThisArg(sema, clang_loc, cast(function_decl))); } for (auto* param : function_decl->parameters()) { clang::Expr* call_arg = @@ -1354,7 +1361,7 @@ auto ExportDestructorToCpp(Context& context, const SemIR::Class& class_info, sema.BuildDeclRefExpr(cpp_function_decl, cpp_function_decl->getType(), clang::VK_PRValue, clang_loc); llvm::SmallVector call_args; - call_args.push_back(GetThisArg(sema, clang_loc, record_decl)); + call_args.push_back(GetThisArg(sema, clang_loc, cpp_destructor_decl)); clang::ExprResult call = sema.BuildCallExpr(nullptr, callee.get(), clang_loc, call_args, clang_loc); diff --git a/toolchain/check/testdata/interop/cpp/class/export/method.carbon b/toolchain/check/testdata/interop/cpp/class/export/method.carbon index a634c3b36616..b07bb4f9282d 100644 --- a/toolchain/check/testdata/interop/cpp/class/export/method.carbon +++ b/toolchain/check/testdata/interop/cpp/class/export/method.carbon @@ -53,6 +53,20 @@ void F() { } '''; +// --- method_const_lvalue.carbon +library "[[@TEST_NAME]]"; +import Cpp; + +class C { + fn M(self); +} + +inline Cpp ''' +void F(const Carbon::C& c) { + c.M(); +} +'''; + // --- ref_method_lvalue.carbon library "[[@TEST_NAME]]"; import Cpp; @@ -89,6 +103,27 @@ void F() { } '''; +// --- fail_ref_method_const_lvalue.carbon +library "[[@TEST_NAME]]"; +import Cpp; + +class C { + fn M(ref self); +} + +inline Cpp ''' +void F(const Carbon::C& c) { + // CHECK:STDERR: fail_ref_method_const_lvalue.carbon:[[@LINE+7]]:3: error: 'this' argument to member function 'M' has type 'const Carbon::C', but function is not marked const [CppInteropParseError] + // CHECK:STDERR: 17 | c.M(); + // CHECK:STDERR: | ^ + // CHECK:STDERR: fail_ref_method_const_lvalue.carbon:[[@LINE-8]]:17: note: 'M' declared here [CppInteropParseNote] + // CHECK:STDERR: 5 | fn M(ref self); + // CHECK:STDERR: | ^ + // CHECK:STDERR: + c.M(); +} +'''; + // --- method_alias.carbon library "[[@TEST_NAME]]"; import Cpp; diff --git a/toolchain/lower/testdata/interop/cpp/class/export/method.carbon b/toolchain/lower/testdata/interop/cpp/class/export/method.carbon index 6d5c7d6178fe..73a87ea14a8f 100644 --- a/toolchain/lower/testdata/interop/cpp/class/export/method.carbon +++ b/toolchain/lower/testdata/interop/cpp/class/export/method.carbon @@ -78,7 +78,7 @@ fn CallCallF() { Cpp.CallF(); } // CHECK:STDOUT: entry: // CHECK:STDOUT: %ref.tmp = alloca %"class.Carbon::A", align 1 // CHECK:STDOUT: call void @llvm.lifetime.start.p0(ptr %ref.tmp) #4 -// CHECK:STDOUT: call void @_ZN6Carbon1A1FEv(ptr noundef nonnull align 1 %ref.tmp) +// CHECK:STDOUT: call void @_ZNK6Carbon1A1FEv(ptr noundef nonnull align 1 %ref.tmp) // CHECK:STDOUT: call void @_ZN6Carbon1AD2Ev(ptr noundef nonnull align 1 %ref.tmp) #4 // CHECK:STDOUT: call void @llvm.lifetime.end.p0(ptr %ref.tmp) #4 // CHECK:STDOUT: ret void @@ -88,7 +88,7 @@ fn CallCallF() { Cpp.CallF(); } // CHECK:STDOUT: declare void @llvm.lifetime.start.p0(ptr captures(none)) #1 // CHECK:STDOUT: // CHECK:STDOUT: ; Function Attrs: alwaysinline mustprogress nounwind uwtable -// CHECK:STDOUT: define internal void @_ZN6Carbon1A1FEv(ptr noundef nonnull align 1 %this) #2 align 2 { +// CHECK:STDOUT: define internal void @_ZNK6Carbon1A1FEv(ptr noundef nonnull align 1 %this) #2 align 2 { // CHECK:STDOUT: entry: // CHECK:STDOUT: %this.addr = alloca ptr, align 8 // CHECK:STDOUT: store ptr %this, ptr %this.addr, align 8, !tbaa !12 @@ -197,7 +197,7 @@ fn CallCallF() { Cpp.CallF(); } // CHECK:STDOUT: entry: // CHECK:STDOUT: %ref.tmp = alloca %"class.Carbon::A", align 1 // CHECK:STDOUT: call void @llvm.lifetime.start.p0(ptr %ref.tmp) #4 -// CHECK:STDOUT: %call = call noundef i32 @_ZN6Carbon1A1FEi(ptr noundef nonnull align 1 %ref.tmp, i32 noundef 123) +// CHECK:STDOUT: %call = call noundef i32 @_ZNK6Carbon1A1FEi(ptr noundef nonnull align 1 %ref.tmp, i32 noundef 123) // CHECK:STDOUT: call void @_ZN6Carbon1AD2Ev(ptr noundef nonnull align 1 %ref.tmp) #4 // CHECK:STDOUT: call void @llvm.lifetime.end.p0(ptr %ref.tmp) #4 // CHECK:STDOUT: ret i32 %call @@ -207,7 +207,7 @@ fn CallCallF() { Cpp.CallF(); } // CHECK:STDOUT: declare void @llvm.lifetime.start.p0(ptr captures(none)) #1 // CHECK:STDOUT: // CHECK:STDOUT: ; Function Attrs: alwaysinline mustprogress nounwind uwtable -// CHECK:STDOUT: define internal noundef i32 @_ZN6Carbon1A1FEi(ptr noundef nonnull align 1 %this, i32 noundef %0) #2 align 2 { +// CHECK:STDOUT: define internal noundef i32 @_ZNK6Carbon1A1FEi(ptr noundef nonnull align 1 %this, i32 noundef %0) #2 align 2 { // CHECK:STDOUT: entry: // CHECK:STDOUT: %retval = alloca i32, align 4 // CHECK:STDOUT: %this.addr = alloca ptr, align 8 diff --git a/toolchain/lower/testdata/interop/cpp/class/import/dynamic.carbon b/toolchain/lower/testdata/interop/cpp/class/import/dynamic.carbon index ef4f591e470f..22773424f338 100644 --- a/toolchain/lower/testdata/interop/cpp/class/import/dynamic.carbon +++ b/toolchain/lower/testdata/interop/cpp/class/import/dynamic.carbon @@ -63,7 +63,7 @@ fn DoThing() { // CHECK:STDOUT: // CHECK:STDOUT: @_ZTV4Base = available_externally constant { [4 x ptr] } { [4 x ptr] [ptr null, ptr @_ZTI4Base, ptr @_ZN11FurtherBase17further_base_funcEv, ptr @_ZNR4Base4funcEv] }, align 8 // CHECK:STDOUT: @_ZTV11FurtherBase = available_externally constant { [3 x ptr] } { [3 x ptr] [ptr null, ptr @_ZTI11FurtherBase, ptr @_ZN11FurtherBase17further_base_funcEv] }, align 8 -// CHECK:STDOUT: @_ZTVN6Carbon7DerivedE = linkonce_odr dso_local constant { [5 x ptr] } { [5 x ptr] [ptr null, ptr @_ZTIN6Carbon7DerivedE, ptr @_ZN11FurtherBase17further_base_funcEv, ptr @_ZNR6Carbon7Derived4funcEv, ptr @_ZN6Carbon7Derived10other_funcEv] }, comdat, align 8 +// CHECK:STDOUT: @_ZTVN6Carbon7DerivedE = linkonce_odr dso_local constant { [5 x ptr] } { [5 x ptr] [ptr null, ptr @_ZTIN6Carbon7DerivedE, ptr @_ZN11FurtherBase17further_base_funcEv, ptr @_ZNR6Carbon7Derived4funcEv, ptr @_ZNK6Carbon7Derived10other_funcEv] }, comdat, align 8 // CHECK:STDOUT: @_ZTI4Base = external constant ptr // CHECK:STDOUT: @_ZTI11FurtherBase = external constant ptr // CHECK:STDOUT: @_ZTIN6Carbon7DerivedE = linkonce_odr dso_local constant { ptr, ptr, ptr } { ptr getelementptr inbounds (ptr, ptr @_ZTVN10__cxxabiv120__si_class_type_infoE, i64 2), ptr @_ZTSN6Carbon7DerivedE, ptr @_ZTI4Base }, comdat, align 8 @@ -180,7 +180,7 @@ fn DoThing() { // CHECK:STDOUT: } // CHECK:STDOUT: // CHECK:STDOUT: ; Function Attrs: alwaysinline mustprogress nounwind uwtable -// CHECK:STDOUT: define internal void @_ZN6Carbon7Derived10other_funcEv(ptr noundef nonnull align 8 dereferenceable(12) %this) unnamed_addr #4 align 2 { +// CHECK:STDOUT: define internal void @_ZNK6Carbon7Derived10other_funcEv(ptr noundef nonnull align 8 dereferenceable(12) %this) unnamed_addr #4 align 2 { // CHECK:STDOUT: entry: // CHECK:STDOUT: %this.addr = alloca ptr, align 8 // CHECK:STDOUT: store ptr %this, ptr %this.addr, align 8, !tbaa !59