mirror of
https://github.com/carbon-language/carbon-lang.git
synced 2026-09-30 06:04:54 +01:00
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:
@@ -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);
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user