Export methods taking self as const member functions (#7577)

A method declared with `self` does not modify the object, but it was
exported to C++ as a non-const member function, so calling it on a const
reference would fail.

```carbon
class C {
  fn Get(self);
}

inline Cpp '''
void F(const Carbon::C& c) {
  c.Get();
}
''';
```
```
error: 'this' argument to member function 'Get' has type 'const Carbon::C', but function is not marked const
```

Import already maps `f() const` to `fn f(self)`, and this PR implements
the same behavior for exporting. No ref-qualifier is added, since that
maps to `ref self`, so that is unchanged.

`GetThisArg()` now builds `this` from the method instead of the parent
record, so that it picks up the method's const-qualifier.
This commit is contained in:
arhwx
2026-07-29 17:28:54 +00:00
committed by GitHub
parent f51c075f8b
commit 6ea2087f0a
4 changed files with 58 additions and 16 deletions
+17 -10
View File
@@ -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<clang::Expr*> call_args;
// For methods, pass the `this` pointer as the first argument to the callee.
if (target.self_param) {
auto* parent_class = cast<clang::CXXRecordDecl>(target.decl_context);
call_args.push_back(GetThisArg(sema, clang_loc, parent_class));
call_args.push_back(
GetThisArg(sema, clang_loc, cast<clang::CXXMethodDecl>(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<clang::Expr*> 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);