From 6f36317f8c1c3d9e361f4d2aa7e59502a3059095 Mon Sep 17 00:00:00 2001 From: Lucian Popescu Date: Sat, 19 Sep 2026 21:51:00 +0100 Subject: [PATCH] Translate classes with mutable fields --- cpp2rust/converter/converter.cpp | 16 +++++- cpp2rust/converter/converter_lib.cpp | 9 +++ cpp2rust/converter/converter_lib.h | 2 + tests/unit/class.cpp | 25 +++++++++ tests/unit/out/refcount/class.rs | 82 ++++++++++++++++++++++++++++ tests/unit/out/unsafe/class.rs | 34 ++++++++++++ 6 files changed, 165 insertions(+), 3 deletions(-) diff --git a/cpp2rust/converter/converter.cpp b/cpp2rust/converter/converter.cpp index cf33029e6..1d664178b 100644 --- a/cpp2rust/converter/converter.cpp +++ b/cpp2rust/converter/converter.cpp @@ -1115,7 +1115,7 @@ bool Converter::ConvertCXXMethodDecl(clang::CXXMethodDecl *decl) { } std::string Converter::GetSelfMaybeWithMut(const clang::CXXMethodDecl *decl) { - return decl->isConst() ? "&self" : "&mut self"; + return MethodNeedsMutableReceiver(decl) ? "&mut self" : "&self"; } std::string Converter::GetCtorName(clang::CXXConstructorDecl *decl) { @@ -3151,12 +3151,22 @@ void Converter::SetUFCSReceiver(clang::Expr *base, bool is_arrow, } Buffer buf(*this); PushExprKind push(*this, ExprKind::LValue); - StrCat(method->isConst() ? "&" : "&mut"); + auto object_type = is_arrow ? base->getType()->getPointeeType() + : base->getType().getNonReferenceType(); + bool cast_mut = + MethodNeedsMutableReceiver(method) && object_type.isConstQualified(); + StrCat(MethodNeedsMutableReceiver(method) ? "&mut" : "&"); + if (cast_mut) { + StrCat("*(&raw const"); + } if (is_arrow) { ConvertArrow(base); } else { Convert(base); } + if (cast_mut) { + StrCat(").cast_mut()"); + } ufcs_receiver_ = std::move(buf).str(); } @@ -4383,7 +4393,7 @@ std::string Converter::GetComparisonCall(const clang::FunctionDecl *op, auto record = GetRecordName(decl); auto arg = std::format("{} as *const {}", rhs, record); if (const auto *method = clang::dyn_cast(op)) { - auto recv = method->isConst() + auto recv = !MethodNeedsMutableReceiver(method) ? std::string(lhs) : std::format("&mut *(&raw const *{}).cast_mut()", lhs); return std::format("{}::{}({}, {})", GetUFCSName(method), diff --git a/cpp2rust/converter/converter_lib.cpp b/cpp2rust/converter/converter_lib.cpp index 0dbb2fd11..ee739bf1d 100644 --- a/cpp2rust/converter/converter_lib.cpp +++ b/cpp2rust/converter/converter_lib.cpp @@ -388,6 +388,15 @@ bool IsRValueConvertingConstructor(const clang::CXXConstructorDecl *ctor) { ctor->getParamDecl(0)->getType()->isRValueReferenceType(); } +bool MethodNeedsMutableReceiver(const clang::CXXMethodDecl *method) { + if (!method->isConst()) { + return true; + } + return std::any_of(method->getParent()->field_begin(), + method->getParent()->field_end(), + [](const clang::FieldDecl *f) { return f->isMutable(); }); +} + bool IsPassThroughConstructor(const clang::CXXConstructorDecl *ctor) { return !IsConvertibleCopyOrMoveConstructor(ctor) && (ctor->isCopyOrMoveConstructor() || diff --git a/cpp2rust/converter/converter_lib.h b/cpp2rust/converter/converter_lib.h index 709940a70..b5c23b432 100644 --- a/cpp2rust/converter/converter_lib.h +++ b/cpp2rust/converter/converter_lib.h @@ -93,6 +93,8 @@ bool IsRValueConvertingConstructor(const clang::CXXConstructorDecl *ctor); bool IsPassThroughConstructor(const clang::CXXConstructorDecl *ctor); +bool MethodNeedsMutableReceiver(const clang::CXXMethodDecl *method); + bool IsConvertibleCXXRecordDecl(const clang::CXXRecordDecl *decl); bool IsConvertibleCXXMethodDecl(const clang::CXXMethodDecl *decl); diff --git a/tests/unit/class.cpp b/tests/unit/class.cpp index cdb862173..981d0c06c 100644 --- a/tests/unit/class.cpp +++ b/tests/unit/class.cpp @@ -36,6 +36,21 @@ struct Route { } }; +struct Counter { + int v; + mutable int calls; + + int Get() const { + ++calls; + return v; + } + + bool operator==(const Counter &o) const { + ++calls; + return v == o.v; + } +}; + int RandomRoute(Route &route) { if (route.path.first % 2) { return route.path.SetFirst(route.path.SetSecond(10)); @@ -49,5 +64,15 @@ int main() { Route route2 = {{1, 0}, 10}; double old_cost = route1.SetCost(route2.SetCost(15)); assert(RandomRoute(route1) + RandomRoute(route2) + old_cost == 9); + Counter c1{3, 0}; + const Counter c2{3, 0}; + const Counter *pc = &c1; + assert(c1.Get() == 3); + assert(c2.Get() == 3); + assert(pc->Get() == 3); + assert(c1 == c2); + assert(c2 == c1); + assert(c1.calls == 3); + assert(c2.calls == 2); return 0; } diff --git a/tests/unit/out/refcount/class.rs b/tests/unit/out/refcount/class.rs index fdc497eb7..68387df4f 100644 --- a/tests/unit/out/refcount/class.rs +++ b/tests/unit/out/refcount/class.rs @@ -66,6 +66,55 @@ impl ByteRepr for Route { } } } +#[derive(Default)] +pub struct Counter { + pub v: Value, + pub calls: Value, +} +impl std::cmp::PartialEq for Counter { + fn eq(&self, other: &Self) -> bool { + { + CounterImpl::operator_eq( + &Rc::new(RefCell::new(Counter { + v: self.v.clone(), + calls: self.calls.clone(), + })) + .as_pointer(), + Rc::new(RefCell::new(Counter { + v: other.v.clone(), + calls: other.calls.clone(), + })) + .as_pointer(), + ) + } + } +} +impl std::cmp::Eq for Counter {} +impl Clone for Counter { + fn clone(&self) -> Self { + let __this: Value = Rc::new(RefCell::new(Self { + v: Rc::new(RefCell::new((*self.v.borrow()))), + calls: Rc::new(RefCell::new((*self.calls.borrow()))), + })); + let this: Ptr = __this.as_pointer(); + Rc::try_unwrap(__this).ok().unwrap().into_inner() + } +} +impl ByteRepr for Counter { + fn byte_size() -> usize { + 8 + } + fn to_bytes(&self, buf: &mut [u8]) { + (*self.v.borrow()).to_bytes(&mut buf[0..4]); + (*self.calls.borrow()).to_bytes(&mut buf[4..8]); + } + fn from_bytes(buf: &[u8]) -> Self { + Self { + v: Rc::new(RefCell::new(::from_bytes(&buf[0..4]))), + calls: Rc::new(RefCell::new(::from_bytes(&buf[4..8]))), + } + } +} pub fn RandomRoute_0(route: Ptr) -> i32 { if (((*(*(*route.upgrade().deref()).path.borrow()).first.borrow()) % 2) != 0) { return ({ @@ -115,8 +164,41 @@ fn main_0() -> i32 { + (*old_cost.borrow())) == 9_f64) ); + let c1: Value = Rc::new(RefCell::new(Counter { + v: Rc::new(RefCell::new(3)), + calls: Rc::new(RefCell::new(0)), + })); + let c2: Value = Rc::new(RefCell::new(Counter { + v: Rc::new(RefCell::new(3)), + calls: Rc::new(RefCell::new(0)), + })); + let pc: Value> = Rc::new(RefCell::new((c1.as_pointer()))); + assert!((({ CounterImpl::Get(&c1.as_pointer(),) }) == 3)); + assert!((({ CounterImpl::Get(&c2.as_pointer(),) }) == 3)); + assert!((({ CounterImpl::Get(&(*pc.borrow()),) }) == 3)); + assert!(({ CounterImpl::operator_eq(&c1.as_pointer(), c2.as_pointer(),) })); + assert!(({ CounterImpl::operator_eq(&c2.as_pointer(), c1.as_pointer(),) })); + assert!(((*(*c1.borrow()).calls.borrow()) == 3)); + assert!(((*(*c2.borrow()).calls.borrow()) == 2)); return 0; } +pub trait CounterImpl { + fn Get(&self) -> i32; + fn operator_eq(&self, o: Ptr) -> bool; +} +impl CounterImpl for Ptr { + fn Get(&self) -> i32 { + (*(*(*self).upgrade().deref()).calls.borrow_mut()).prefix_inc(); + return (*(*(*self).upgrade().deref()).v.borrow()); + } + fn operator_eq(&self, o: Ptr) -> bool { + (*(*(*self).upgrade().deref()).calls.borrow_mut()).prefix_inc(); + return { + let _lhs = (*(*(*self).upgrade().deref()).v.borrow()); + _lhs == (*(*o.upgrade().deref()).v.borrow()) + }; + } +} pub trait PairImpl { fn NOP(&self); fn GetFirst(&self) -> i32; diff --git a/tests/unit/out/unsafe/class.rs b/tests/unit/out/unsafe/class.rs index 89b75608e..270727e1e 100644 --- a/tests/unit/out/unsafe/class.rs +++ b/tests/unit/out/unsafe/class.rs @@ -54,6 +54,30 @@ impl Route { return old_cost; } } +#[repr(C)] +#[derive(Copy, Clone, Default)] +pub struct Counter { + pub v: i32, + pub calls: i32, +} +impl Counter { + pub unsafe fn Get(&mut self) -> i32 { + self.calls.prefix_inc(); + return self.v; + } + pub unsafe fn operator_eq(&mut self, o: *const Counter) -> bool { + self.calls.prefix_inc(); + return ((self.v) == ((*o).v)); + } +} +impl std::cmp::PartialEq for Counter { + fn eq(&self, other: &Self) -> bool { + unsafe { + Counter::operator_eq(&mut *(&raw const *self).cast_mut(), other as *const Counter) + } + } +} +impl std::cmp::Eq for Counter {} pub unsafe fn RandomRoute_0(route: *mut Route) -> i32 { if ((((*route).path.first) % (2)) != 0) { return (unsafe { @@ -101,6 +125,16 @@ unsafe fn main_0() -> i32 { + (old_cost)) == (9_f64)) ); + let mut c1: Counter = Counter { v: 3, calls: 0 }; + let c2: Counter = Counter { v: 3, calls: 0 }; + let mut pc: *const Counter = (&mut c1 as *mut Counter).cast_const(); + assert!(((unsafe { Counter::Get(&mut *(&raw const c1).cast_mut(),) }) == (3))); + assert!(((unsafe { Counter::Get(&mut *(&raw const c2).cast_mut(),) }) == (3))); + assert!(((unsafe { Counter::Get(&mut *(&raw const (*pc)).cast_mut(),) }) == (3))); + assert!((unsafe { Counter::operator_eq(&mut *(&raw const c1).cast_mut(), &c2,) })); + assert!((unsafe { Counter::operator_eq(&mut *(&raw const c2).cast_mut(), &c1,) })); + assert!(((c1.calls) == (3))); + assert!(((c2.calls) == (2))); return 0; } pub unsafe fn __cpp2rust_init_globals() {}