mirror of
https://github.com/carbon-language/carbon-lang.git
synced 2026-09-24 20:10:13 +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();
|
auto ext_proto_info = clang::FunctionProtoType::ExtProtoInfo();
|
||||||
if (target.self_param && target.self_param->kind == ParamPatternKind::Ref) {
|
if (target.self_param) {
|
||||||
ext_proto_info.RefQualifier = clang::RQ_LValue;
|
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()
|
return context.ast_context()
|
||||||
.getFunctionType(cpp_return_type, thunk_param_types, ext_proto_info)
|
.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.
|
// Get an expr for accessing `this` in a method.
|
||||||
static auto GetThisArg(clang::Sema& sema, clang::SourceLocation clang_loc,
|
static auto GetThisArg(clang::Sema& sema, clang::SourceLocation clang_loc,
|
||||||
clang::CXXRecordDecl* record_decl) -> clang::Expr* {
|
const clang::CXXMethodDecl* method_decl)
|
||||||
clang::QualType class_type =
|
-> clang::Expr* {
|
||||||
sema.getASTContext().getCanonicalTagType(record_decl);
|
// These pick up the method's `const` qualifier, if any.
|
||||||
auto class_ptr_type = sema.getASTContext().getPointerType(class_type);
|
clang::QualType class_type = method_decl->getFunctionObjectParameterType();
|
||||||
auto* this_expr = sema.BuildCXXThisExpr(clang_loc, class_ptr_type,
|
auto* this_expr = sema.BuildCXXThisExpr(clang_loc, method_decl->getThisType(),
|
||||||
/*IsImplicit=*/true);
|
/*IsImplicit=*/true);
|
||||||
return clang::UnaryOperator::Create(
|
return clang::UnaryOperator::Create(
|
||||||
sema.getASTContext(), this_expr, clang::UO_Deref, class_type,
|
sema.getASTContext(), this_expr, clang::UO_Deref, class_type,
|
||||||
@@ -874,8 +881,8 @@ static auto BuildCppToCarbonThunkBody(Context& context,
|
|||||||
llvm::SmallVector<clang::Expr*> call_args;
|
llvm::SmallVector<clang::Expr*> call_args;
|
||||||
// For methods, pass the `this` pointer as the first argument to the callee.
|
// For methods, pass the `this` pointer as the first argument to the callee.
|
||||||
if (target.self_param) {
|
if (target.self_param) {
|
||||||
auto* parent_class = cast<clang::CXXRecordDecl>(target.decl_context);
|
call_args.push_back(
|
||||||
call_args.push_back(GetThisArg(sema, clang_loc, parent_class));
|
GetThisArg(sema, clang_loc, cast<clang::CXXMethodDecl>(function_decl)));
|
||||||
}
|
}
|
||||||
for (auto* param : function_decl->parameters()) {
|
for (auto* param : function_decl->parameters()) {
|
||||||
clang::Expr* call_arg =
|
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(),
|
sema.BuildDeclRefExpr(cpp_function_decl, cpp_function_decl->getType(),
|
||||||
clang::VK_PRValue, clang_loc);
|
clang::VK_PRValue, clang_loc);
|
||||||
llvm::SmallVector<clang::Expr*> call_args;
|
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,
|
clang::ExprResult call = sema.BuildCallExpr(nullptr, callee.get(), clang_loc,
|
||||||
call_args, 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
|
// --- ref_method_lvalue.carbon
|
||||||
library "[[@TEST_NAME]]";
|
library "[[@TEST_NAME]]";
|
||||||
import Cpp;
|
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
|
// --- method_alias.carbon
|
||||||
library "[[@TEST_NAME]]";
|
library "[[@TEST_NAME]]";
|
||||||
import Cpp;
|
import Cpp;
|
||||||
|
|||||||
@@ -78,7 +78,7 @@ fn CallCallF() { Cpp.CallF(); }
|
|||||||
// CHECK:STDOUT: entry:
|
// CHECK:STDOUT: entry:
|
||||||
// CHECK:STDOUT: %ref.tmp = alloca %"class.Carbon::A", align 1
|
// 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 @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 @_ZN6Carbon1AD2Ev(ptr noundef nonnull align 1 %ref.tmp) #4
|
||||||
// CHECK:STDOUT: call void @llvm.lifetime.end.p0(ptr %ref.tmp) #4
|
// CHECK:STDOUT: call void @llvm.lifetime.end.p0(ptr %ref.tmp) #4
|
||||||
// CHECK:STDOUT: ret void
|
// 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: declare void @llvm.lifetime.start.p0(ptr captures(none)) #1
|
||||||
// CHECK:STDOUT:
|
// CHECK:STDOUT:
|
||||||
// CHECK:STDOUT: ; Function Attrs: alwaysinline mustprogress nounwind uwtable
|
// 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: entry:
|
||||||
// CHECK:STDOUT: %this.addr = alloca ptr, align 8
|
// CHECK:STDOUT: %this.addr = alloca ptr, align 8
|
||||||
// CHECK:STDOUT: store ptr %this, ptr %this.addr, align 8, !tbaa !12
|
// 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: entry:
|
||||||
// CHECK:STDOUT: %ref.tmp = alloca %"class.Carbon::A", align 1
|
// 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 @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 @_ZN6Carbon1AD2Ev(ptr noundef nonnull align 1 %ref.tmp) #4
|
||||||
// CHECK:STDOUT: call void @llvm.lifetime.end.p0(ptr %ref.tmp) #4
|
// CHECK:STDOUT: call void @llvm.lifetime.end.p0(ptr %ref.tmp) #4
|
||||||
// CHECK:STDOUT: ret i32 %call
|
// 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: declare void @llvm.lifetime.start.p0(ptr captures(none)) #1
|
||||||
// CHECK:STDOUT:
|
// CHECK:STDOUT:
|
||||||
// CHECK:STDOUT: ; Function Attrs: alwaysinline mustprogress nounwind uwtable
|
// 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: entry:
|
||||||
// CHECK:STDOUT: %retval = alloca i32, align 4
|
// CHECK:STDOUT: %retval = alloca i32, align 4
|
||||||
// CHECK:STDOUT: %this.addr = alloca ptr, align 8
|
// CHECK:STDOUT: %this.addr = alloca ptr, align 8
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ fn DoThing() {
|
|||||||
// CHECK:STDOUT:
|
// 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: @_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: @_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: @_ZTI4Base = external constant ptr
|
||||||
// CHECK:STDOUT: @_ZTI11FurtherBase = 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
|
// 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:
|
// CHECK:STDOUT:
|
||||||
// CHECK:STDOUT: ; Function Attrs: alwaysinline mustprogress nounwind uwtable
|
// 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: entry:
|
||||||
// CHECK:STDOUT: %this.addr = alloca ptr, align 8
|
// CHECK:STDOUT: %this.addr = alloca ptr, align 8
|
||||||
// CHECK:STDOUT: store ptr %this, ptr %this.addr, align 8, !tbaa !59
|
// CHECK:STDOUT: store ptr %this, ptr %this.addr, align 8, !tbaa !59
|
||||||
|
|||||||
Reference in New Issue
Block a user