Skip to content

Commit 665b520

Browse files
authored
Translate defaulted and implicit comparison operators (#356)
This PR translates defaulted comparison operator such as `==` or `<=>` and the implicit comparison operators derived from `<=>`. It asks Sema to define the defaulted/implicit operators and then cpp2rust translates them as normal methods.
1 parent 1814bb5 commit 665b520

29 files changed

Lines changed: 1253 additions & 56 deletions

‎cpp2rust/converter/converter.cpp‎

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -966,6 +966,14 @@ bool Converter::VisitCXXRecordDecl(clang::CXXRecordDecl *decl) {
966966
sema_->DefineImplicitCopyConstructor(decl->getLocation(), ctor);
967967
}
968968
}
969+
for (auto *method : decl->methods()) {
970+
if (IsComparisonOperator(method) && method->isDefaulted() &&
971+
!method->doesThisDeclarationHaveABody()) {
972+
sema_->DefineDefaultedComparison(
973+
decl->getLocation(), method,
974+
sema_->getDefaultedComparisonKind(method));
975+
}
976+
}
969977

970978
EmitRustStructOrUnion(decl);
971979
} else if (decl->isUnion()) {
@@ -1268,6 +1276,14 @@ void Converter::ConvertCondition(clang::Expr *cond) {
12681276
}
12691277

12701278
bool Converter::VisitIfStmt(clang::IfStmt *stmt) {
1279+
if (auto *init = stmt->getInit()) {
1280+
PushBrace scope(*this);
1281+
Convert(init);
1282+
stmt->setInit(nullptr);
1283+
Convert(stmt);
1284+
stmt->setInit(init);
1285+
return false;
1286+
}
12711287
StrCat(keyword::kIf);
12721288
ConvertCondition(stmt->getCond());
12731289
ConvertBody(stmt->getThen());
@@ -1832,7 +1848,7 @@ Converter::CallInfo Converter::CollectCallInfo(clang::CallExpr *expr) {
18321848
for (unsigned i = 0; i < num_named_params && i < num_args; ++i) {
18331849
auto *arg = expr->getArg(i + arg_begin);
18341850
CallArg ca{
1835-
.param_name = function
1851+
.param_name = function && !function->getParamDecl(i)->getName().empty()
18361852
? ("_" + function->getParamDecl(i)->getNameAsString())
18371853
: ("_arg" + std::to_string(i)),
18381854
.param_type = function ? function->getParamDecl(i)->getType()
@@ -2729,7 +2745,10 @@ bool Converter::VisitUnaryOperator(clang::UnaryOperator *expr) {
27292745
expr->getType()->isIntegerType() && !expr->getType()->isBooleanType();
27302746
PushParen paren_cast(*this, needs_int_cast);
27312747
StrCat(token::kNot);
2732-
ConvertCondition(sub_expr);
2748+
{
2749+
PushParen paren_operand(*this);
2750+
ConvertCondition(sub_expr);
2751+
}
27332752
if (needs_int_cast) {
27342753
ConvertCast(expr->getType());
27352754
}

‎cpp2rust/converter/converter_lib.cpp‎

Lines changed: 45 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -375,7 +375,7 @@ bool IsConvertibleCXXMethodDecl(const clang::CXXMethodDecl *decl) {
375375
if (llvm::isa<clang::CXXDestructorDecl>(decl)) {
376376
return GetUserDefinedDestructor(decl->getParent()) != nullptr;
377377
}
378-
return !decl->isImplicit();
378+
return !decl->isImplicit() || IsComparisonOperator(decl);
379379
}
380380

381381
bool IsConvertibleFunctionDecl(const clang::FunctionDecl *decl) {
@@ -648,12 +648,17 @@ std::string GetNamedDeclAsString(const clang::NamedDecl *decl) {
648648
auto *pdecl = llvm::dyn_cast<clang::ParmVarDecl>(decl);
649649
assert(pdecl && "Unexpected unnamed construct");
650650

651-
const auto *ctor =
652-
llvm::dyn_cast<clang::CXXConstructorDecl>(pdecl->getDeclContext());
653-
name = (pdecl->isExplicitObjectParameter() ||
654-
(ctor && ctor->isCopyOrMoveConstructor()))
655-
? "self"
656-
: "_";
651+
const auto *fn =
652+
llvm::dyn_cast<clang::FunctionDecl>(pdecl->getDeclContext());
653+
const auto *ctor = llvm::dyn_cast_or_null<clang::CXXConstructorDecl>(fn);
654+
if (pdecl->isExplicitObjectParameter() ||
655+
(ctor && ctor->isCopyOrMoveConstructor())) {
656+
name = "self";
657+
} else if (fn && fn->isDefaulted() && IsComparisonOperator(fn)) {
658+
name = std::format("_arg{}", pdecl->getFunctionScopeIndex());
659+
} else {
660+
name = "_";
661+
}
657662
} else if (auto *pdecl = llvm::dyn_cast<clang::ParmVarDecl>(decl)) {
658663
// Expanded parameter packs share one name across the expansion
659664
if (auto *fn = llvm::dyn_cast_or_null<clang::FunctionDecl>(
@@ -804,7 +809,14 @@ bool IsSameTypeComparison(const clang::FunctionDecl *fn,
804809

805810
bool IsUserOperatorCall(const clang::CXXOperatorCallExpr *expr) {
806811
const auto *callee = expr->getDirectCallee();
807-
if (!callee || !callee->isUserProvided() || !IsUserDefinedDecl(callee)) {
812+
if (!callee) {
813+
return false;
814+
}
815+
if (const auto *method = clang::dyn_cast<clang::CXXMethodDecl>(callee);
816+
method && method->isDefaulted() && IsComparisonOperator(method)) {
817+
return IsUserDefinedDecl(method->getParent());
818+
}
819+
if (!callee->isUserProvided() || !IsUserDefinedDecl(callee)) {
808820
return false;
809821
}
810822
if (const auto *method = clang::dyn_cast<clang::CXXMethodDecl>(callee)) {
@@ -864,6 +876,21 @@ bool RecordNeedsDestruction(const clang::CXXRecordDecl *decl) {
864876
return GetUserDefinedDestructor(decl) || HasFieldsNeedingDestruction(decl);
865877
}
866878

879+
bool IsComparisonOperator(const clang::FunctionDecl *fn) {
880+
switch (fn->getOverloadedOperator()) {
881+
case clang::OO_EqualEqual:
882+
case clang::OO_ExclaimEqual:
883+
case clang::OO_Less:
884+
case clang::OO_LessEqual:
885+
case clang::OO_Greater:
886+
case clang::OO_GreaterEqual:
887+
case clang::OO_Spaceship:
888+
return true;
889+
default:
890+
return false;
891+
}
892+
}
893+
867894
bool IsEmittableMethod(clang::CXXMethodDecl *method) {
868895
if (method->isDeleted()) {
869896
return false;
@@ -876,6 +903,9 @@ bool IsEmittableMethod(clang::CXXMethodDecl *method) {
876903
if (method->isVirtual()) {
877904
return false;
878905
}
906+
if (IsComparisonOperator(method)) {
907+
return method->hasBody();
908+
}
879909
// Compiler-generated members are covered by derived traits
880910
if (method->isImplicit()) {
881911
return false;
@@ -889,16 +919,20 @@ bool IsEmittableMethod(clang::CXXMethodDecl *method) {
889919
}
890920

891921
bool IsMethodOnPtr(const clang::CXXMethodDecl *method) {
892-
if (method->isImplicit() || method->isDeleted() || method->isStatic() ||
893-
method->isVirtual() || clang::isa<clang::CXXConstructorDecl>(method)) {
922+
if (method->isDeleted() || method->isStatic() || method->isVirtual() ||
923+
clang::isa<clang::CXXConstructorDecl>(method)) {
924+
return false;
925+
}
926+
if (method->isImplicit() && !IsComparisonOperator(method)) {
894927
return false;
895928
}
896929
if (!IsUserDefinedDecl(method->getParent()) ||
897930
method->getParent()->isLambda()) {
898931
return false;
899932
}
900933
if (auto *definition = method->getDefinition();
901-
definition && definition->isDefaulted()) {
934+
definition && definition->isDefaulted() &&
935+
!IsComparisonOperator(method)) {
902936
return false;
903937
}
904938
if (clang::isa<clang::CXXDestructorDecl>(method)) {

‎cpp2rust/converter/converter_lib.h‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ bool IsConvertibleCXXRecordDecl(const clang::CXXRecordDecl *decl);
9191

9292
bool IsConvertibleCXXMethodDecl(const clang::CXXMethodDecl *decl);
9393

94+
bool IsComparisonOperator(const clang::FunctionDecl *fn);
9495
bool IsEmittableMethod(clang::CXXMethodDecl *method);
9596

9697
bool IsMethodOnPtr(const clang::CXXMethodDecl *method);
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
// ADDITIONAL_COMPILE_FLAGS: -std=c++20
2+
#include <cassert>
3+
#include <compare>
4+
5+
struct Eq {
6+
int a;
7+
int b;
8+
bool operator==(const Eq &) const = default;
9+
};
10+
11+
struct Cmp {
12+
int a;
13+
int b;
14+
auto operator<=>(const Cmp &) const = default;
15+
};
16+
17+
struct Both {
18+
int a;
19+
bool operator==(const Both &) const = default;
20+
std::strong_ordering operator<=>(const Both &) const = default;
21+
};
22+
23+
struct OrdOnly {
24+
int a;
25+
auto operator<=>(const OrdOnly &) const = default;
26+
bool operator==(const OrdOnly &) const = delete;
27+
};
28+
29+
struct Inner {
30+
int x;
31+
auto operator<=>(const Inner &) const = default;
32+
};
33+
34+
struct Outer {
35+
Inner i;
36+
int y;
37+
auto operator<=>(const Outer &) const = default;
38+
};
39+
40+
// Secondary comparison operators can be defaulted too; they are defined as
41+
// rewrites of the primary ones: != as !(a == b), < and >= via (a <=> b).
42+
struct Secondary {
43+
int a;
44+
bool operator==(const Secondary &) const = default;
45+
bool operator!=(const Secondary &) const = default;
46+
auto operator<=>(const Secondary &) const = default;
47+
bool operator<(const Secondary &) const = default;
48+
bool operator>=(const Secondary &) const = default;
49+
};
50+
51+
int main() {
52+
Eq e1{1, 2}, e2{1, 2}, e3{1, 3};
53+
assert(e1 == e2);
54+
assert(e1 != e3);
55+
Cmp c1{1, 2}, c2{1, 3}, c3{2, 0}, c4{1, 9};
56+
assert(c1 < c2);
57+
assert(c3 > c4);
58+
assert(c1 == c1);
59+
assert((c1 <=> c2) == std::strong_ordering::less);
60+
Both b1{1}, b2{2};
61+
assert(b1 < b2);
62+
assert(b2 == b2);
63+
OrdOnly o1{1}, o2{2};
64+
assert(o1 < o2);
65+
assert((o2 <=> o1) == std::strong_ordering::greater);
66+
Outer x1{{1}, 9}, x2{{2}, 0}, x3{{1}, 9};
67+
assert(x1 < x2);
68+
assert(x1 == x3);
69+
assert((x2 <=> x1) == std::strong_ordering::greater);
70+
Secondary s1{1}, s2{2};
71+
assert(s1 != s2);
72+
assert(s1 < s2);
73+
assert(s2 >= s1);
74+
assert(!(s2 < s1));
75+
return 0;
76+
}

‎tests/unit/out/refcount/bool_condition_ptr.rs‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,13 @@ fn main_0() -> i32 {
1616
if !(*p.borrow()).is_null() {
1717
assert!(true);
1818
}
19-
if !!(*p.borrow()).is_null() {
19+
if !(!(*p.borrow()).is_null()) {
2020
assert!(false);
2121
}
2222
if !(*np.borrow()).is_null() {
2323
assert!(false);
2424
}
25-
if !!(*np.borrow()).is_null() {
25+
if !(!(*np.borrow()).is_null()) {
2626
assert!(true);
2727
}
2828
let iter: Value<Ptr<i32>> = Rc::new(RefCell::new((*p.borrow()).clone()));
@@ -36,9 +36,9 @@ fn main_0() -> i32 {
3636
assert!(((*t3.borrow()) == 1));
3737
let t4: Value<i32> = Rc::new(RefCell::new(if !(*np.borrow()).is_null() { 1 } else { 0 }));
3838
assert!(((*t4.borrow()) == 0));
39-
let t5: Value<i32> = Rc::new(RefCell::new((!!(*p.borrow()).is_null() as i32)));
39+
let t5: Value<i32> = Rc::new(RefCell::new((!(!(*p.borrow()).is_null()) as i32)));
4040
assert!(((*t5.borrow()) == 0));
41-
let t6: Value<i32> = Rc::new(RefCell::new((!!(*np.borrow()).is_null() as i32)));
41+
let t6: Value<i32> = Rc::new(RefCell::new((!(!(*np.borrow()).is_null()) as i32)));
4242
assert!(((*t6.borrow()) == 1));
4343
let b2: Value<bool> = Rc::new(RefCell::new(!(*p.borrow()).is_null()));
4444
let b3: Value<bool> = Rc::new(RefCell::new(!(*np.borrow()).is_null()));

‎tests/unit/out/refcount/bool_condition_ptr_c.rs‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,13 @@ fn main_0() -> i32 {
1616
if !(*p.borrow()).is_null() {
1717
assert!((1 != 0));
1818
}
19-
if !!(*p.borrow()).is_null() {
19+
if !(!(*p.borrow()).is_null()) {
2020
assert!((0 != 0));
2121
}
2222
if !(*np.borrow()).is_null() {
2323
assert!((0 != 0));
2424
}
25-
if !!(*np.borrow()).is_null() {
25+
if !(!(*np.borrow()).is_null()) {
2626
assert!((1 != 0));
2727
}
2828
let iter: Value<Ptr<i32>> = Rc::new(RefCell::new((*p.borrow()).clone()));
@@ -36,9 +36,9 @@ fn main_0() -> i32 {
3636
assert!(((((*t3.borrow()) == 1) as i32) != 0));
3737
let t4: Value<i32> = Rc::new(RefCell::new(if !(*np.borrow()).is_null() { 1 } else { 0 }));
3838
assert!(((((*t4.borrow()) == 0) as i32) != 0));
39-
let t5: Value<i32> = Rc::new(RefCell::new((!!(*p.borrow()).is_null() as i32)));
39+
let t5: Value<i32> = Rc::new(RefCell::new((!(!(*p.borrow()).is_null()) as i32)));
4040
assert!(((((*t5.borrow()) == 0) as i32) != 0));
41-
let t6: Value<i32> = Rc::new(RefCell::new((!!(*np.borrow()).is_null() as i32)));
41+
let t6: Value<i32> = Rc::new(RefCell::new((!(!(*np.borrow()).is_null()) as i32)));
4242
assert!(((((*t6.borrow()) == 1) as i32) != 0));
4343
let b2: Value<bool> = Rc::new(RefCell::new(!(*p.borrow()).is_null()));
4444
let b3: Value<bool> = Rc::new(RefCell::new(!(*np.borrow()).is_null()));

‎tests/unit/out/refcount/builtin.rs‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -38,11 +38,11 @@ pub fn test_popcountl_7() {
3838
pub fn test_mul_overflow_long_8() {
3939
let r: Value<i64> = Rc::new(RefCell::new(0_i64));
4040
assert!(
41-
((!{
41+
((!({
4242
let (val, ovf) = 3_i64.overflowing_mul(7_i64);
4343
(r.as_pointer()).write(val);
4444
ovf
45-
} as i32)
45+
}) as i32)
4646
!= 0)
4747
);
4848
assert!(((((*r.borrow()) == 21_i64) as i32) != 0));
@@ -55,11 +55,11 @@ pub fn test_mul_overflow_long_8() {
5555
pub fn test_mul_overflow_long_long_9() {
5656
let r: Value<i64> = Rc::new(RefCell::new(0_i64));
5757
assert!(
58-
((!{
58+
((!({
5959
let (val, ovf) = 1000_i64.overflowing_mul(1000_i64);
6060
(r.as_pointer()).write(val);
6161
ovf
62-
} as i32)
62+
}) as i32)
6363
!= 0)
6464
);
6565
assert!(((((*r.borrow()) == 1000000_i64) as i32) != 0));

‎tests/unit/out/refcount/fn_ptr_as_condition.rs‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ fn main_0() -> i32 {
3131
({ maybe_call_1(FnPtr::<fn(Ptr<i32>)>::null(), (b.as_pointer())) });
3232
assert!(((*b.borrow()) == 5));
3333
let fn_: Value<FnPtr<fn(Ptr<i32>)>> = Rc::new(RefCell::new(FnPtr::<fn(Ptr<i32>)>::null()));
34-
if !!(*fn_.borrow()).is_null() {
34+
if !(!(*fn_.borrow()).is_null()) {
3535
(*fn_.borrow_mut()) = FnPtr::<fn(Ptr<i32>)>::new(double_it_0);
3636
}
3737
let c: Value<i32> = Rc::new(RefCell::new(3));

‎tests/unit/out/refcount/libc_char_ptr_field.rs‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ fn main_0() -> i32 {
2222
}
2323
},
2424
));
25-
if !!(*pw.borrow()).is_null() {
25+
if !(!(*pw.borrow()).is_null()) {
2626
return 0;
2727
}
2828
let home: Value<Ptr<u8>> = Rc::new(RefCell::new(

0 commit comments

Comments
 (0)