Skip to content

Commit 691378b

Browse files
authored
Fix implicit construction in rules (#569)
Closes #567 and #568 C++ has 3 ways of implicitly constructing a value: * conversion constructor, `struct Foo { Foo(int i) {} }` * conversion operator, `structure Foo { operator int() () {} }` * integer conversions, `vector(InputIt first, InputIt last)` a range of longs can initialize a vector of ints A rule that does implicit conversion cannot know which of the three versions is used. We need a common interface for all three that rules can use. This PR adds CppFrom. Structs that have one of the two: conversion constructor or conversion operator, implement CppFrom using a derived trait. We cannot use Rust's From because From has value semantics while C++ can have reference semantics, for example: `struct Foo { Foo(const OtherStructThatHasCloneSideEffects&) {} }`. ```cpp struct S { int x; S(int i) : x(i) {} }; ``` ```rust #[derive(CppFrom)] // Derive CppFrom for the method called `new` that receives // the argument `i32` using `value` semantics #[cpp_from(i32, new, value)] pub struct S { pub x: i32, } impl S { pub fn new(i: i32) -> Self { Self { x: i } } } // expanded by the derive impl CppFrom<i32> for S { fn cpp_from(src: Ptr<i32>) -> Self { S::new(src.read()) } } ``` --- Rules that need implicit construction are written like this: ```rs // Conversion constructor of pair: pair<T3, T4> -> pair<T1, T2> fn f24<T1: CppFrom<T3>, T2: CppFrom<T4>, T3, T4>( a0: (Value<T3>, Value<T4>), ) -> (Value<T1>, Value<T2>) { let __a0 = a0; ( Rc::new(RefCell::new(T1::cpp_from(__a0.0.as_pointer()))), Rc::new(RefCell::new(T2::cpp_from(__a0.1.as_pointer()))), ) } ``` --- Some notes: * to optimize the generated code. I added a new Rules IR node called GenericCall. If the GenericCall is cpp_form and the source and destination are integers, don't use the fully qualified call `T1::call_from(<T2 expr>)`, use `T2 as T1` * I replaced the Default node with GeneriCall, T1::default() is also a generic call
1 parent bf51851 commit 691378b

137 files changed

Lines changed: 2446 additions & 504 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎cpp2rust/converter/converter.cpp‎

Lines changed: 126 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
#include <clang/Basic/Version.h>
1111
#include <clang/Sema/Template.h>
1212
#include <llvm/ADT/DenseMap.h>
13+
#include <llvm/ADT/StringExtras.h>
1314
#include <llvm/Support/ConvertUTF.h>
1415
#include <llvm/Support/ErrorHandling.h>
1516

@@ -879,6 +880,9 @@ void Converter::EmitRustStructOrUnion(clang::RecordDecl *decl) {
879880
EmitReprC(decl);
880881
}
881882
EmitDerives(decl);
883+
if (auto *cxx = clang::dyn_cast<clang::CXXRecordDecl>(decl)) {
884+
EmitConversionAttrs(cxx);
885+
}
882886
EmitByteSizeAttr(decl);
883887

884888
// Fields
@@ -956,6 +960,41 @@ void Converter::EmitDerives(const clang::RecordDecl *decl) {
956960
StrCat(")]");
957961
}
958962

963+
void Converter::EmitConversionAttrs(clang::CXXRecordDecl *decl) {
964+
std::unordered_set<std::string> from_types;
965+
ForEachConvertingConstructor(decl, [&](clang::CXXConstructorDecl *ctor) {
966+
auto *param = ctor->getParamDecl(0);
967+
auto type = param->getType();
968+
auto source =
969+
GetUnboxedTypeAsString(type.getNonReferenceType().getUnqualifiedType());
970+
bool is_move = type->isRValueReferenceType();
971+
if (!from_types.insert(source + (is_move ? "&&" : "")).second) {
972+
return;
973+
}
974+
const char *kind =
975+
!type->isReferenceType() ? "value"
976+
: is_move || type.getNonReferenceType().isConstQualified() ? "ptr"
977+
: "mut_ptr";
978+
StrCat(std::format(
979+
"#[{}({}, {}, {}{})]", is_move ? "cpp_from_move" : "cpp_from", source,
980+
GetCtorName(ctor), param->hasDefaultArg() ? "some_" : "", kind));
981+
});
982+
std::unordered_set<std::string> into_types;
983+
ForEachMethod(decl, [&](clang::CXXMethodDecl *method) {
984+
if (!IsImplicitConversionOperator(method)) {
985+
return;
986+
}
987+
auto *conversion = clang::cast<clang::CXXConversionDecl>(method);
988+
auto target = GetUnboxedTypeAsString(
989+
conversion->getConversionType().getUnqualifiedType());
990+
if (!into_types.insert(target).second) {
991+
return;
992+
}
993+
StrCat(
994+
std::format("#[cpp_into({}, {})]", target, GetMethodName(conversion)));
995+
});
996+
}
997+
959998
void Converter::EmitReprC(clang::RecordDecl *decl) {
960999
if (decl->hasAttr<clang::AlignedAttr>()) {
9611000
StrCat(std::format("#[repr(C, align({}))]",
@@ -4208,6 +4247,10 @@ Converter::GetStructAttributes(const clang::RecordDecl *decl) {
42084247
struct_attrs.emplace_back("MoveCtorUnsafe");
42094248
}
42104249

4250+
if (HasImplicitConversions(decl)) {
4251+
struct_attrs.emplace_back("CppFromUnsafe");
4252+
}
4253+
42114254
if (HasCopyAssignOperator(decl)) {
42124255
struct_attrs.emplace_back("CopyAssignUnsafe");
42134256
}
@@ -4946,6 +4989,14 @@ std::string Converter::ConvertPlaceholder(clang::Expr *expr, clang::Expr *arg,
49464989
ToStringBase(ctx_.getPointerType(elem)));
49474990
}
49484991

4992+
if (auto *init =
4993+
RuleRegistry::BuildParamConversion(ctx_, expr, ph_ctx.arg_idx, arg)) {
4994+
Buffer buf(*this);
4995+
PushExprKind push(*this, ExprKind::RValue);
4996+
ConvertInit(init->getType(), init);
4997+
return std::move(buf).str();
4998+
}
4999+
49495000
if (ph_ctx.needs_materialization()) {
49505001
auto materialized = ph_ctx.materialize_ctx->GetOrMaterialize(
49515002
static_cast<unsigned>(ph_ctx.materialize_idx),
@@ -5093,11 +5144,6 @@ std::string Converter::ConvertIRFragment(
50935144
result += t->text;
50945145
} else if (auto *g = std::get_if<GenericFragment>(&frag)) {
50955146
result += GetTemplateArgumentAsString(bindings[g->n - 1]);
5096-
} else if (auto *d = std::get_if<DefaultFragment>(&frag)) {
5097-
auto &arg = bindings[d->n - 1];
5098-
assert(arg.getKind() == clang::TemplateArgument::Type &&
5099-
"default of a non-type generic");
5100-
result += GetUnboxedDefaultAsString(arg.getAsType());
51015147
} else if (auto *ph = std::get_if<PlaceholderFragment>(&frag)) {
51025148
assert(expr && "placeholder outside an expression rule");
51035149
auto arg_idx = ph->n;
@@ -5138,12 +5184,78 @@ std::string Converter::ConvertIRFragment(
51385184
result +=
51395185
(*re)->prefix + ' ' +
51405186
ConvertIRFragment((*re)->inner, bindings, expr, args, num_args, ctx);
5187+
} else if (auto *call =
5188+
std::get_if<std::unique_ptr<GenericCallFragment>>(&frag)) {
5189+
result += ConvertGenericCall(**call, bindings, expr, args, num_args, ctx);
51415190
}
51425191
}
51435192

51445193
return result;
51455194
}
51465195

5196+
std::string
5197+
Converter::ConvertGenericCall(const TranslationRule::GenericCallFragment &call,
5198+
llvm::ArrayRef<clang::TemplateArgument> bindings,
5199+
clang::Expr *expr, clang::Expr **args,
5200+
unsigned num_args, TempMaterializationCtx *ctx) {
5201+
std::vector<std::string> call_args;
5202+
for (const auto &arg : call.args) {
5203+
call_args.push_back(
5204+
ConvertIRFragment(arg, bindings, expr, args, num_args, ctx));
5205+
}
5206+
clang::QualType type;
5207+
if (call.generic <= bindings.size() &&
5208+
bindings[call.generic - 1].getKind() == clang::TemplateArgument::Type) {
5209+
type = bindings[call.generic - 1].getAsType();
5210+
} else {
5211+
assert(expr && "unbound generic outside an expression rule");
5212+
type = RuleRegistry::GetGenericType(ctx_, expr, call.generic);
5213+
}
5214+
if (call.method == "default") {
5215+
assert(call_args.empty() && "default takes no arguments");
5216+
return GetUnboxedDefaultAsString(type);
5217+
}
5218+
if (call.method == "cpp_from" || call.method == "cpp_from_move") {
5219+
assert(call_args.size() == 1 && "cpp_from takes one argument");
5220+
return ConvertConversion(
5221+
type, RuleRegistry::GetConversionSourceType(ctx_, expr, call.generic),
5222+
call.method == "cpp_from_move", call_args[0]);
5223+
}
5224+
return std::format("<{}>::{}({})", GetUnboxedTypeAsString(type), call.method,
5225+
llvm::join(call_args, ", "));
5226+
}
5227+
5228+
std::string Converter::ConvertConversion(clang::QualType to,
5229+
clang::QualType from, bool is_move,
5230+
const std::string &ptr) {
5231+
if (to->isArithmeticType() && from->isArithmeticType()) {
5232+
auto value = DerefPtrExpr(ptr, from);
5233+
if (ctx_.hasSameUnqualifiedType(to, from)) {
5234+
return value;
5235+
}
5236+
if (to->isBooleanType()) {
5237+
return std::format("({} != {})", value,
5238+
from->isFloatingType() ? "0.0" : "0");
5239+
}
5240+
if (from->isBooleanType() && to->isFloatingType()) {
5241+
return std::format("({} as u8 as {})", value, GetUnsafeTypeAsString(to));
5242+
}
5243+
return std::format("({} as {})", value, GetUnsafeTypeAsString(to));
5244+
}
5245+
return std::format("<{} as {}<{}>>::{}({})", GetUnboxedTypeAsString(to),
5246+
GetConversionTraitName(), GetUnboxedTypeAsString(from),
5247+
is_move ? "cpp_from_move" : "cpp_from", ptr);
5248+
}
5249+
5250+
const char *Converter::GetConversionTraitName() const {
5251+
return "CppFromUnsafe";
5252+
}
5253+
5254+
std::string Converter::DerefPtrExpr(std::string_view ptr_expr,
5255+
clang::QualType pointee_type) {
5256+
return std::format("({}{})", GetPointerDerefPrefix(pointee_type), ptr_expr);
5257+
}
5258+
51475259
std::string
51485260
Converter::ConvertVariadicTail(clang::Expr *expr,
51495261
const std::vector<clang::Expr *> &all_args) {
@@ -5180,14 +5292,18 @@ void Converter::ConvertConstructFromArgs(clang::QualType type,
51805292
clang::SourceLocation loc) {
51815293
auto *init = BuildInitExpr(GetSema(), type, args, loc);
51825294
assert(init && "type cannot be initialized from the arguments");
5183-
if (auto *ctor =
5184-
clang::dyn_cast<clang::CXXConstructExpr>(init->IgnoreImplicit())) {
5185-
ConvertConstructedValue(type, ctor);
5295+
if (args.empty() &&
5296+
!clang::isa<clang::CXXConstructExpr>(init->IgnoreImplicit())) {
5297+
StrCat(GetDefaultAsString(type));
51865298
return;
51875299
}
5300+
ConvertInit(type, init);
5301+
}
51885302

5189-
if (args.empty()) {
5190-
StrCat(GetDefaultAsString(type));
5303+
void Converter::ConvertInit(clang::QualType type, clang::Expr *init) {
5304+
if (auto *ctor =
5305+
clang::dyn_cast<clang::CXXConstructExpr>(init->IgnoreImplicit())) {
5306+
ConvertConstructedValue(type, ctor);
51915307
return;
51925308
}
51935309
Convert(init);

‎cpp2rust/converter/converter.h‎

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,9 @@ class Converter : public clang::RecursiveASTVisitor<Converter> {
178178

179179
void EmitReprC(clang::RecordDecl *decl);
180180
void EmitDerives(const clang::RecordDecl *decl);
181+
182+
void EmitConversionAttrs(clang::CXXRecordDecl *decl);
183+
181184
virtual void EmitRustUnion(clang::RecordDecl *decl);
182185

183186
virtual bool EmitsReprCForRecords() const { return true; }
@@ -304,6 +307,20 @@ class Converter : public clang::RecursiveASTVisitor<Converter> {
304307
clang::Expr *expr, clang::Expr **args, unsigned num_args,
305308
TempMaterializationCtx *ctx);
306309

310+
std::string
311+
ConvertGenericCall(const TranslationRule::GenericCallFragment &call,
312+
llvm::ArrayRef<clang::TemplateArgument> bindings,
313+
clang::Expr *expr, clang::Expr **args, unsigned num_args,
314+
TempMaterializationCtx *ctx);
315+
316+
std::string ConvertConversion(clang::QualType to, clang::QualType from,
317+
bool is_move, const std::string &ptr);
318+
319+
virtual const char *GetConversionTraitName() const;
320+
321+
virtual std::string DerefPtrExpr(std::string_view ptr_expr,
322+
clang::QualType pointee_type);
323+
307324
struct PlaceholderCtx {
308325
unsigned arg_idx;
309326
std::optional<clang::QualType> implicit_convert_to;
@@ -866,6 +883,8 @@ class Converter : public clang::RecursiveASTVisitor<Converter> {
866883
llvm::ArrayRef<clang::Expr *> args,
867884
clang::SourceLocation loc);
868885

886+
void ConvertInit(clang::QualType type, clang::Expr *init);
887+
869888
virtual void ConvertConstructedValue(clang::QualType type,
870889
clang::CXXConstructExpr *ctor);
871890

‎cpp2rust/converter/converter_lib.cpp‎

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -543,6 +543,50 @@ bool IsConvertibleMoveConstructor(const clang::CXXConstructorDecl *ctor) {
543543
return ctor->isUserProvided() || NeedsDefaultedMoveConstructor(parent);
544544
}
545545

546+
static bool
547+
IsImplicitConvertingConstructor(const clang::CXXConstructorDecl *ctor) {
548+
return !ctor->isCopyOrMoveConstructor() && !ctor->isImplicit() &&
549+
!ctor->isDeleted() && ctor->hasBody() && ctor->getNumParams() == 1 &&
550+
ctor->isConvertingConstructor(/*AllowExplicit=*/false);
551+
}
552+
553+
void ForEachConvertingConstructor(
554+
clang::CXXRecordDecl *decl,
555+
llvm::function_ref<void(clang::CXXConstructorDecl *)> fn) {
556+
for (auto *ctor : decl->ctors()) {
557+
if (IsImplicitConvertingConstructor(ctor)) {
558+
fn(ctor);
559+
}
560+
}
561+
for (auto *ctor : GetTemplateInstantiatedCtors(decl)) {
562+
if (IsImplicitConvertingConstructor(ctor)) {
563+
fn(ctor);
564+
}
565+
}
566+
}
567+
568+
bool IsImplicitConversionOperator(const clang::CXXMethodDecl *method) {
569+
auto *conversion = clang::dyn_cast<clang::CXXConversionDecl>(method);
570+
return conversion && !conversion->isExplicit() && !conversion->isDeleted() &&
571+
conversion->hasBody() &&
572+
!conversion->getConversionType()->isReferenceType();
573+
}
574+
575+
bool HasImplicitConversions(const clang::RecordDecl *decl) {
576+
auto *cxx = clang::dyn_cast<clang::CXXRecordDecl>(decl);
577+
if (!cxx || !IsUserDefinedDecl(cxx)) {
578+
return false;
579+
}
580+
bool found = false;
581+
ForEachConvertingConstructor(
582+
const_cast<clang::CXXRecordDecl *>(cxx),
583+
[&](clang::CXXConstructorDecl *) { found = true; });
584+
ForEachMethod(cxx, [&](clang::CXXMethodDecl *method) {
585+
found = found || IsImplicitConversionOperator(method);
586+
});
587+
return found;
588+
}
589+
546590
bool HasMoveFromConstructor(const clang::RecordDecl *decl) {
547591
auto *cxx = clang::dyn_cast<clang::CXXRecordDecl>(decl);
548592
return cxx && llvm::any_of(cxx->ctors(), [](auto *ctor) {

‎cpp2rust/converter/converter_lib.h‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,14 @@ bool IsConvertibleMoveConstructor(const clang::CXXConstructorDecl *ctor);
105105
// Whether decl has a move constructor converted to a move_from method.
106106
bool HasMoveFromConstructor(const clang::RecordDecl *decl);
107107

108+
void ForEachConvertingConstructor(
109+
clang::CXXRecordDecl *decl,
110+
llvm::function_ref<void(clang::CXXConstructorDecl *)> fn);
111+
112+
bool IsImplicitConversionOperator(const clang::CXXMethodDecl *method);
113+
114+
bool HasImplicitConversions(const clang::RecordDecl *decl);
115+
108116
bool IsConvertibleMoveAssignment(const clang::CXXMethodDecl *method);
109117

110118
bool IsConvertibleCopyAssignment(const clang::CXXMethodDecl *method);

‎cpp2rust/converter/models/boxed_vars.cpp‎

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,9 @@ class AddressTakenVisitor
177177
if (auto *source = GetTrivialCopySource(expr)) {
178178
AddValueUse(source);
179179
}
180-
AddRuleArgs(expr, expr->getArgs(), expr->getNumArgs());
180+
if (!expr->isElidable()) {
181+
AddRuleArgs(expr, expr->getArgs(), expr->getNumArgs());
182+
}
181183
return true;
182184
}
183185

@@ -237,6 +239,10 @@ class AddressTakenVisitor
237239
auto all_args = BuildUnifiedArgs(expr, args, num_args);
238240
for (unsigned i = 0; i < all_args.size() && i < rule->params.size(); ++i) {
239241
auto *arg = IgnoreNoOpCasts(all_args[i]);
242+
if (auto *init = RuleRegistry::BuildParamConversion(ctx_, expr, i, arg)) {
243+
TraverseStmt(init);
244+
continue;
245+
}
240246
if (!rule->params[i].is_pointer() || arg->getType()->isPointerType() ||
241247
RuleRegistry::MapsToPointer(ctx_, arg->getType())) {
242248
AddValueUse(arg);

‎cpp2rust/converter/models/converter_refcount.cpp‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2257,6 +2257,10 @@ ConverterRefCount::GetStructAttributes(const clang::RecordDecl *decl) {
22572257
attrs.emplace_back("MoveCtor");
22582258
}
22592259

2260+
if (HasImplicitConversions(decl)) {
2261+
attrs.emplace_back("CppFrom");
2262+
}
2263+
22602264
if (HasCopyAssignOperator(decl)) {
22612265
attrs.emplace_back("CopyAssign");
22622266
}
@@ -2276,6 +2280,10 @@ ConverterRefCount::GetStructAttributes(const clang::RecordDecl *decl) {
22762280
return attrs;
22772281
}
22782282

2283+
const char *ConverterRefCount::GetConversionTraitName() const {
2284+
return "CppFrom";
2285+
}
2286+
22792287
bool ConverterRefCount::TypeDerivesDefault(clang::QualType qual_type) {
22802288
// Arrays are translated to Value<Box<[T]>>, whose Default is empty
22812289
return !qual_type->isArrayType() && Converter::TypeDerivesDefault(qual_type);

‎cpp2rust/converter/models/converter_refcount.h‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -418,7 +418,9 @@ class ConverterRefCount final : public Converter {
418418
// Wraps a pointer expression with deref prefix/suffix: e.g.
419419
// "(*ptr.upgrade().deref())" or "(ptr.read())"
420420
std::string DerefPtrExpr(std::string_view ptr_expr,
421-
clang::QualType pointee_type);
421+
clang::QualType pointee_type) override;
422+
423+
const char *GetConversionTraitName() const override;
422424

423425
std::string GetInnerType(clang::QualType type);
424426

0 commit comments

Comments
 (0)