Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
81aff51
Update tests
lucic71 Oct 30, 2026
53fc2c4
Drop into/try_into conversions form pair rules
lucic71 Oct 30, 2026
da3ed45
Construct pair argument as the target type
lucic71 Oct 30, 2026
5b16533
Add rules for pair converting constructor
lucic71 Oct 30, 2026
89f30be
Add pair converting constructor tests
lucic71 Oct 30, 2026
6ed8c6b
Constraint rules
lucic71 Oct 30, 2026
8af8e36
Update tests
lucic71 Oct 30, 2026
fa46b03
Build Init expression for parameter conversion
lucic71 Oct 30, 2026
39127ce
Update tests
lucic71 Oct 30, 2026
8a369ac
Update tests
lucic71 Oct 30, 2026
5337c2b
Add rules for conversion assignment
lucic71 Oct 30, 2026
92e66c9
Use CppFrom trait in rules
lucic71 Oct 30, 2026
d5057f0
Update tests
lucic71 Oct 30, 2026
5f3962c
Add CppFrom macros
lucic71 Oct 30, 2026
3f96a34
Update rules
lucic71 Oct 30, 2026
72fbc4c
Update tests
lucic71 Oct 30, 2026
e414fd7
Add converter support for CppFrom
lucic71 Oct 30, 2026
96907e2
Add CppFrom for const ptr
lucic71 Oct 30, 2026
2ef06d4
Add implicit arithmetic covnersion
lucic71 Oct 30, 2026
a531119
format
lucic71 Oct 30, 2026
bab6025
Update tests
lucic71 Oct 30, 2026
180937b
Use cpp_from in rules
lucic71 Oct 30, 2026
702f9f6
Update tests
lucic71 Oct 30, 2026
c338f9c
Add conversion node to optimize the common case
lucic71 Oct 30, 2026
45515cf
Update tests
lucic71 Oct 30, 2026
1bb9c6f
format
lucic71 Oct 30, 2026
498f1f0
Add more supported patterns
lucic71 Oct 30, 2026
001bdfa
Fix the other implicit conversion rules
lucic71 Oct 30, 2026
0e5cda1
Update tests
lucic71 Oct 30, 2026
c3f34d8
Record the bounds on the generics
lucic71 Oct 30, 2026
d451bf7
Update tests
lucic71 Oct 30, 2026
710f98b
Merge conversion and default nodes in a single node
lucic71 Oct 30, 2026
9688e95
format
lucic71 Oct 30, 2026
d95410d
Fix elidable boxed types
lucic71 Oct 30, 2026
915d957
Use updated rules
lucic71 Oct 30, 2026
87e6cf3
Format
lucic71 Oct 30, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
136 changes: 126 additions & 10 deletions cpp2rust/converter/converter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#include <clang/Basic/Version.h>
#include <clang/Sema/Template.h>
#include <llvm/ADT/DenseMap.h>
#include <llvm/ADT/StringExtras.h>
#include <llvm/Support/ConvertUTF.h>
#include <llvm/Support/ErrorHandling.h>

Expand Down Expand Up @@ -879,6 +880,9 @@ void Converter::EmitRustStructOrUnion(clang::RecordDecl *decl) {
EmitReprC(decl);
}
EmitDerives(decl);
if (auto *cxx = clang::dyn_cast<clang::CXXRecordDecl>(decl)) {
EmitConversionAttrs(cxx);
}
EmitByteSizeAttr(decl);

// Fields
Expand Down Expand Up @@ -956,6 +960,41 @@ void Converter::EmitDerives(const clang::RecordDecl *decl) {
StrCat(")]");
}

void Converter::EmitConversionAttrs(clang::CXXRecordDecl *decl) {
std::unordered_set<std::string> from_types;
ForEachConvertingConstructor(decl, [&](clang::CXXConstructorDecl *ctor) {
auto *param = ctor->getParamDecl(0);
auto type = param->getType();
auto source =
GetUnboxedTypeAsString(type.getNonReferenceType().getUnqualifiedType());
bool is_move = type->isRValueReferenceType();
if (!from_types.insert(source + (is_move ? "&&" : "")).second) {
return;
}
const char *kind =
!type->isReferenceType() ? "value"
: is_move || type.getNonReferenceType().isConstQualified() ? "ptr"
: "mut_ptr";
StrCat(std::format(
"#[{}({}, {}, {}{})]", is_move ? "cpp_from_move" : "cpp_from", source,
GetCtorName(ctor), param->hasDefaultArg() ? "some_" : "", kind));
});
std::unordered_set<std::string> into_types;
ForEachMethod(decl, [&](clang::CXXMethodDecl *method) {
if (!IsImplicitConversionOperator(method)) {
return;
}
auto *conversion = clang::cast<clang::CXXConversionDecl>(method);
auto target = GetUnboxedTypeAsString(
conversion->getConversionType().getUnqualifiedType());
if (!into_types.insert(target).second) {
return;
}
StrCat(
std::format("#[cpp_into({}, {})]", target, GetMethodName(conversion)));
});
}

void Converter::EmitReprC(clang::RecordDecl *decl) {
if (decl->hasAttr<clang::AlignedAttr>()) {
StrCat(std::format("#[repr(C, align({}))]",
Expand Down Expand Up @@ -4208,6 +4247,10 @@ Converter::GetStructAttributes(const clang::RecordDecl *decl) {
struct_attrs.emplace_back("MoveCtorUnsafe");
}

if (HasImplicitConversions(decl)) {
struct_attrs.emplace_back("CppFromUnsafe");
}

if (HasCopyAssignOperator(decl)) {
struct_attrs.emplace_back("CopyAssignUnsafe");
}
Expand Down Expand Up @@ -4946,6 +4989,14 @@ std::string Converter::ConvertPlaceholder(clang::Expr *expr, clang::Expr *arg,
ToStringBase(ctx_.getPointerType(elem)));
}

if (auto *init =
RuleRegistry::BuildParamConversion(ctx_, expr, ph_ctx.arg_idx, arg)) {
Buffer buf(*this);
PushExprKind push(*this, ExprKind::RValue);
ConvertInit(init->getType(), init);
return std::move(buf).str();
}

if (ph_ctx.needs_materialization()) {
auto materialized = ph_ctx.materialize_ctx->GetOrMaterialize(
static_cast<unsigned>(ph_ctx.materialize_idx),
Expand Down Expand Up @@ -5093,11 +5144,6 @@ std::string Converter::ConvertIRFragment(
result += t->text;
} else if (auto *g = std::get_if<GenericFragment>(&frag)) {
result += GetTemplateArgumentAsString(bindings[g->n - 1]);
} else if (auto *d = std::get_if<DefaultFragment>(&frag)) {
auto &arg = bindings[d->n - 1];
assert(arg.getKind() == clang::TemplateArgument::Type &&
"default of a non-type generic");
result += GetUnboxedDefaultAsString(arg.getAsType());
} else if (auto *ph = std::get_if<PlaceholderFragment>(&frag)) {
assert(expr && "placeholder outside an expression rule");
auto arg_idx = ph->n;
Expand Down Expand Up @@ -5138,12 +5184,78 @@ std::string Converter::ConvertIRFragment(
result +=
(*re)->prefix + ' ' +
ConvertIRFragment((*re)->inner, bindings, expr, args, num_args, ctx);
} else if (auto *call =
std::get_if<std::unique_ptr<GenericCallFragment>>(&frag)) {
result += ConvertGenericCall(**call, bindings, expr, args, num_args, ctx);
}
}

return result;
}

std::string
Converter::ConvertGenericCall(const TranslationRule::GenericCallFragment &call,
llvm::ArrayRef<clang::TemplateArgument> bindings,
clang::Expr *expr, clang::Expr **args,
unsigned num_args, TempMaterializationCtx *ctx) {
std::vector<std::string> call_args;
for (const auto &arg : call.args) {
call_args.push_back(
ConvertIRFragment(arg, bindings, expr, args, num_args, ctx));
}
clang::QualType type;
if (call.generic <= bindings.size() &&
bindings[call.generic - 1].getKind() == clang::TemplateArgument::Type) {
type = bindings[call.generic - 1].getAsType();
} else {
assert(expr && "unbound generic outside an expression rule");
type = RuleRegistry::GetGenericType(ctx_, expr, call.generic);
}
if (call.method == "default") {
assert(call_args.empty() && "default takes no arguments");
return GetUnboxedDefaultAsString(type);
}
if (call.method == "cpp_from" || call.method == "cpp_from_move") {
assert(call_args.size() == 1 && "cpp_from takes one argument");
return ConvertConversion(
type, RuleRegistry::GetConversionSourceType(ctx_, expr, call.generic),
call.method == "cpp_from_move", call_args[0]);
}
return std::format("<{}>::{}({})", GetUnboxedTypeAsString(type), call.method,
llvm::join(call_args, ", "));
}

std::string Converter::ConvertConversion(clang::QualType to,
clang::QualType from, bool is_move,
const std::string &ptr) {
if (to->isArithmeticType() && from->isArithmeticType()) {
auto value = DerefPtrExpr(ptr, from);
if (ctx_.hasSameUnqualifiedType(to, from)) {
return value;
}
if (to->isBooleanType()) {
return std::format("({} != {})", value,
from->isFloatingType() ? "0.0" : "0");
}
if (from->isBooleanType() && to->isFloatingType()) {
return std::format("({} as u8 as {})", value, GetUnsafeTypeAsString(to));
}
return std::format("({} as {})", value, GetUnsafeTypeAsString(to));
}
return std::format("<{} as {}<{}>>::{}({})", GetUnboxedTypeAsString(to),
GetConversionTraitName(), GetUnboxedTypeAsString(from),
is_move ? "cpp_from_move" : "cpp_from", ptr);
}

const char *Converter::GetConversionTraitName() const {
return "CppFromUnsafe";
}

std::string Converter::DerefPtrExpr(std::string_view ptr_expr,
clang::QualType pointee_type) {
return std::format("({}{})", GetPointerDerefPrefix(pointee_type), ptr_expr);
}

std::string
Converter::ConvertVariadicTail(clang::Expr *expr,
const std::vector<clang::Expr *> &all_args) {
Expand Down Expand Up @@ -5180,14 +5292,18 @@ void Converter::ConvertConstructFromArgs(clang::QualType type,
clang::SourceLocation loc) {
auto *init = BuildInitExpr(GetSema(), type, args, loc);
assert(init && "type cannot be initialized from the arguments");
if (auto *ctor =
clang::dyn_cast<clang::CXXConstructExpr>(init->IgnoreImplicit())) {
ConvertConstructedValue(type, ctor);
if (args.empty() &&
!clang::isa<clang::CXXConstructExpr>(init->IgnoreImplicit())) {
StrCat(GetDefaultAsString(type));
return;
}
ConvertInit(type, init);
}

if (args.empty()) {
StrCat(GetDefaultAsString(type));
void Converter::ConvertInit(clang::QualType type, clang::Expr *init) {
if (auto *ctor =
clang::dyn_cast<clang::CXXConstructExpr>(init->IgnoreImplicit())) {
ConvertConstructedValue(type, ctor);
return;
}
Convert(init);
Expand Down
19 changes: 19 additions & 0 deletions cpp2rust/converter/converter.h
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,9 @@ class Converter : public clang::RecursiveASTVisitor<Converter> {

void EmitReprC(clang::RecordDecl *decl);
void EmitDerives(const clang::RecordDecl *decl);

void EmitConversionAttrs(clang::CXXRecordDecl *decl);

virtual void EmitRustUnion(clang::RecordDecl *decl);

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

std::string
ConvertGenericCall(const TranslationRule::GenericCallFragment &call,
llvm::ArrayRef<clang::TemplateArgument> bindings,
clang::Expr *expr, clang::Expr **args, unsigned num_args,
TempMaterializationCtx *ctx);

std::string ConvertConversion(clang::QualType to, clang::QualType from,
bool is_move, const std::string &ptr);

virtual const char *GetConversionTraitName() const;

virtual std::string DerefPtrExpr(std::string_view ptr_expr,
clang::QualType pointee_type);

struct PlaceholderCtx {
unsigned arg_idx;
std::optional<clang::QualType> implicit_convert_to;
Expand Down Expand Up @@ -866,6 +883,8 @@ class Converter : public clang::RecursiveASTVisitor<Converter> {
llvm::ArrayRef<clang::Expr *> args,
clang::SourceLocation loc);

void ConvertInit(clang::QualType type, clang::Expr *init);

virtual void ConvertConstructedValue(clang::QualType type,
clang::CXXConstructExpr *ctor);

Expand Down
44 changes: 44 additions & 0 deletions cpp2rust/converter/converter_lib.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -543,6 +543,50 @@ bool IsConvertibleMoveConstructor(const clang::CXXConstructorDecl *ctor) {
return ctor->isUserProvided() || NeedsDefaultedMoveConstructor(parent);
}

static bool
IsImplicitConvertingConstructor(const clang::CXXConstructorDecl *ctor) {
return !ctor->isCopyOrMoveConstructor() && !ctor->isImplicit() &&
!ctor->isDeleted() && ctor->hasBody() && ctor->getNumParams() == 1 &&
ctor->isConvertingConstructor(/*AllowExplicit=*/false);
}

void ForEachConvertingConstructor(
clang::CXXRecordDecl *decl,
llvm::function_ref<void(clang::CXXConstructorDecl *)> fn) {
for (auto *ctor : decl->ctors()) {
if (IsImplicitConvertingConstructor(ctor)) {
fn(ctor);
}
}
for (auto *ctor : GetTemplateInstantiatedCtors(decl)) {
if (IsImplicitConvertingConstructor(ctor)) {
fn(ctor);
}
}
}

bool IsImplicitConversionOperator(const clang::CXXMethodDecl *method) {
auto *conversion = clang::dyn_cast<clang::CXXConversionDecl>(method);
return conversion && !conversion->isExplicit() && !conversion->isDeleted() &&
conversion->hasBody() &&
!conversion->getConversionType()->isReferenceType();
}

bool HasImplicitConversions(const clang::RecordDecl *decl) {
auto *cxx = clang::dyn_cast<clang::CXXRecordDecl>(decl);
if (!cxx || !IsUserDefinedDecl(cxx)) {
return false;
}
bool found = false;
ForEachConvertingConstructor(
const_cast<clang::CXXRecordDecl *>(cxx),
[&](clang::CXXConstructorDecl *) { found = true; });
ForEachMethod(cxx, [&](clang::CXXMethodDecl *method) {
found = found || IsImplicitConversionOperator(method);
});
return found;
}

bool HasMoveFromConstructor(const clang::RecordDecl *decl) {
auto *cxx = clang::dyn_cast<clang::CXXRecordDecl>(decl);
return cxx && llvm::any_of(cxx->ctors(), [](auto *ctor) {
Expand Down
8 changes: 8 additions & 0 deletions cpp2rust/converter/converter_lib.h
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,14 @@ bool IsConvertibleMoveConstructor(const clang::CXXConstructorDecl *ctor);
// Whether decl has a move constructor converted to a move_from method.
bool HasMoveFromConstructor(const clang::RecordDecl *decl);

void ForEachConvertingConstructor(
clang::CXXRecordDecl *decl,
llvm::function_ref<void(clang::CXXConstructorDecl *)> fn);

bool IsImplicitConversionOperator(const clang::CXXMethodDecl *method);

bool HasImplicitConversions(const clang::RecordDecl *decl);

bool IsConvertibleMoveAssignment(const clang::CXXMethodDecl *method);

bool IsConvertibleCopyAssignment(const clang::CXXMethodDecl *method);
Expand Down
8 changes: 7 additions & 1 deletion cpp2rust/converter/models/boxed_vars.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,9 @@ class AddressTakenVisitor
if (auto *source = GetTrivialCopySource(expr)) {
AddValueUse(source);
}
AddRuleArgs(expr, expr->getArgs(), expr->getNumArgs());
if (!expr->isElidable()) {
AddRuleArgs(expr, expr->getArgs(), expr->getNumArgs());
}
return true;
}

Expand Down Expand Up @@ -237,6 +239,10 @@ class AddressTakenVisitor
auto all_args = BuildUnifiedArgs(expr, args, num_args);
for (unsigned i = 0; i < all_args.size() && i < rule->params.size(); ++i) {
auto *arg = IgnoreNoOpCasts(all_args[i]);
if (auto *init = RuleRegistry::BuildParamConversion(ctx_, expr, i, arg)) {
TraverseStmt(init);
continue;
}
if (!rule->params[i].is_pointer() || arg->getType()->isPointerType() ||
RuleRegistry::MapsToPointer(ctx_, arg->getType())) {
AddValueUse(arg);
Expand Down
8 changes: 8 additions & 0 deletions cpp2rust/converter/models/converter_refcount.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2257,6 +2257,10 @@ ConverterRefCount::GetStructAttributes(const clang::RecordDecl *decl) {
attrs.emplace_back("MoveCtor");
}

if (HasImplicitConversions(decl)) {
attrs.emplace_back("CppFrom");
}

if (HasCopyAssignOperator(decl)) {
attrs.emplace_back("CopyAssign");
}
Expand All @@ -2276,6 +2280,10 @@ ConverterRefCount::GetStructAttributes(const clang::RecordDecl *decl) {
return attrs;
}

const char *ConverterRefCount::GetConversionTraitName() const {
return "CppFrom";
}

bool ConverterRefCount::TypeDerivesDefault(clang::QualType qual_type) {
// Arrays are translated to Value<Box<[T]>>, whose Default is empty
return !qual_type->isArrayType() && Converter::TypeDerivesDefault(qual_type);
Expand Down
4 changes: 3 additions & 1 deletion cpp2rust/converter/models/converter_refcount.h
Original file line number Diff line number Diff line change
Expand Up @@ -418,7 +418,9 @@ class ConverterRefCount final : public Converter {
// Wraps a pointer expression with deref prefix/suffix: e.g.
// "(*ptr.upgrade().deref())" or "(ptr.read())"
std::string DerefPtrExpr(std::string_view ptr_expr,
clang::QualType pointee_type);
clang::QualType pointee_type) override;

const char *GetConversionTraitName() const override;

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

Expand Down
Loading
Loading