From 6d484b82da007921057be65f5068739ea4ae17c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Gouveia?= Date: Sat, 13 Jun 2026 17:46:22 +0000 Subject: [PATCH 1/3] Add pre-commit tests --- tests/unit/class_templates.cpp | 43 +++++++++++ tests/unit/out/refcount/class_templates.rs | 86 ++++++++++++++++++++++ tests/unit/out/unsafe/class_templates.rs | 78 ++++++++++++++++++++ 3 files changed, 207 insertions(+) create mode 100644 tests/unit/class_templates.cpp create mode 100644 tests/unit/out/refcount/class_templates.rs create mode 100644 tests/unit/out/unsafe/class_templates.rs diff --git a/tests/unit/class_templates.cpp b/tests/unit/class_templates.cpp new file mode 100644 index 00000000..d4c0d8c6 --- /dev/null +++ b/tests/unit/class_templates.cpp @@ -0,0 +1,43 @@ +#include +#include + +template class MyContainer { + std::vector vec; + +public: + using value_type = T; + using reference = T &; + using const_reference = const T &; + using size_type = std::size_t; + + bool empty() const { return vec.empty(); } + size_type size() const { return vec.size(); } + const_reference back() const { return vec.back(); } + reference back() { return vec.back(); } + void pop_back() { return vec.pop_back(); } + void push_back(const_reference item) { vec.push_back(item); } +}; + +int main() { + MyContainer imc; + assert(imc.empty()); + imc.push_back(1); + assert(imc.size() == 1 && imc.back() == 1); + imc.pop_back(); + assert(imc.empty()); + + MyContainer cmc; + assert(cmc.empty()); + cmc.push_back('a'); + assert(cmc.size() == 1 && cmc.back() == 'a'); + cmc.pop_back(); + assert(cmc.empty()); + + MyContainer fmc; + assert(fmc.empty()); + fmc.push_back('a'); + assert(fmc.size() == 1 && fmc.back() == 'a'); + fmc.pop_back(); + assert(fmc.empty()); + return 0; +} diff --git a/tests/unit/out/refcount/class_templates.rs b/tests/unit/out/refcount/class_templates.rs new file mode 100644 index 00000000..7d0059ee --- /dev/null +++ b/tests/unit/out/refcount/class_templates.rs @@ -0,0 +1,86 @@ +extern crate libcc2rs; +use libcc2rs::*; +use std::cell::RefCell; +use std::collections::BTreeMap; +use std::io::prelude::*; +use std::io::{Read, Seek, Write}; +use std::os::fd::AsFd; +use std::rc::{Rc, Weak}; +#[derive(Default)] +pub struct MyContainer { + vec_: Value>, +} +impl MyContainer { + pub fn empty(&self) -> bool { + return (*self.vec_.borrow()).is_empty(); + } + pub fn size(&self) -> usize { + return (*self.vec_.borrow()).len(); + } + pub fn back_const(&self) -> Ptr { + return (self.vec_.as_pointer() as Ptr).to_last(); + } + pub fn back(&self) -> Ptr { + return (self.vec_.as_pointer() as Ptr).to_last(); + } + pub fn pop_back(&self) { + (*self.vec_.borrow_mut()).pop(); + return; + } + pub fn push_back(&self, item: Ptr) { + { + let a0_clone = (item.read()).clone(); + (*self.vec_.borrow_mut()).push(a0_clone) + }; + } +} +impl Clone for MyContainer { + fn clone(&self) -> Self { + let mut this = Self { + vec_: Rc::new(RefCell::new((*self.vec_.borrow()).clone())), + }; + this + } +} +impl ByteRepr for MyContainer {} +pub fn main() { + std::process::exit(main_0()); +} +fn main_0() -> i32 { + let imc: Value = Rc::new(RefCell::new(::default())); + assert!(({ (*imc.borrow()).empty() })); + ({ + let _item: Value = Rc::new(RefCell::new(1)); + (*imc.borrow()).push_back(_item.as_pointer()) + }); + assert!( + (({ (*imc.borrow()).size() }) == 1_usize) && ((({ (*imc.borrow()).back() }).read()) == 1) + ); + ({ (*imc.borrow()).pop_back() }); + assert!(({ (*imc.borrow()).empty() })); + let cmc: Value = Rc::new(RefCell::new(::default())); + assert!(({ (*cmc.borrow()).empty() })); + ({ + let _item: Value = Rc::new(RefCell::new(('a' as u8))); + (*cmc.borrow()).push_back(_item.as_pointer()) + }); + assert!( + (({ (*cmc.borrow()).size() }) == 1_usize) + && (((({ (*cmc.borrow()).back() }).read()) as i32) == (('a' as u8) as i32)) + ); + ({ (*cmc.borrow()).pop_back() }); + assert!(({ (*cmc.borrow()).empty() })); + let fmc: Value = Rc::new(RefCell::new(::default())); + assert!(({ (*fmc.borrow()).empty() })); + ({ + let _item: Value = Rc::new(RefCell::new((('a' as u8) as f32))); + (*fmc.borrow()).push_back(_item.as_pointer()) + }); + assert!( + (({ (*fmc.borrow()).size() }) == 1_usize) + && ((({ (*fmc.borrow()).back() }).read()) == ((('a' as u8) as i32) as f32)) + ); + ({ (*fmc.borrow()).pop_back() }); + assert!(({ (*fmc.borrow()).empty() })); + return 0; +} diff --git a/tests/unit/out/unsafe/class_templates.rs b/tests/unit/out/unsafe/class_templates.rs new file mode 100644 index 00000000..0b1c9263 --- /dev/null +++ b/tests/unit/out/unsafe/class_templates.rs @@ -0,0 +1,78 @@ +extern crate libc; +use libc::*; +extern crate libcc2rs; +use libcc2rs::*; +use std::collections::BTreeMap; +use std::io::{Read, Seek, Write}; +use std::os::fd::{AsFd, FromRawFd, IntoRawFd}; +use std::rc::Rc; +#[repr(C)] +#[derive(Clone, Default)] +pub struct MyContainer { + vec_: Vec, +} +impl MyContainer { + pub unsafe fn empty(&self) -> bool { + return self.vec_.is_empty(); + } + pub unsafe fn size(&self) -> usize { + return self.vec_.len(); + } + pub unsafe fn back_const(&self) -> *const i32 { + return ((self.vec_).last().unwrap()); + } + pub unsafe fn back(&mut self) -> *mut i32 { + return ((self.vec_).last_mut().unwrap()); + } + pub unsafe fn pop_back(&mut self) { + self.vec_.pop(); + return; + } + pub unsafe fn push_back(&mut self, item: *const i32) { + { + let a0_clone = (*item).clone(); + self.vec_.push(a0_clone) + }; + } +} +pub fn main() { + unsafe { + std::process::exit(main_0() as i32); + } +} +unsafe fn main_0() -> i32 { + let mut imc: MyContainer = ::default(); + assert!((unsafe { imc.empty() })); + (unsafe { + let mut _item = 1; + imc.push_back(&mut _item) + }); + assert!(((unsafe { imc.size() }) == (1_usize)) && ((*(unsafe { imc.back() })) == (1))); + (unsafe { imc.pop_back() }); + assert!((unsafe { imc.empty() })); + let mut cmc: MyContainer = ::default(); + assert!((unsafe { cmc.empty() })); + (unsafe { + let mut _item = ('a' as u8); + cmc.push_back(&mut _item) + }); + assert!( + ((unsafe { cmc.size() }) == (1_usize)) + && (((*(unsafe { cmc.back() })) as i32) == (('a' as u8) as i32)) + ); + (unsafe { cmc.pop_back() }); + assert!((unsafe { cmc.empty() })); + let mut fmc: MyContainer = ::default(); + assert!((unsafe { fmc.empty() })); + (unsafe { + let mut _item = (('a' as u8) as f32); + fmc.push_back(&mut _item) + }); + assert!( + ((unsafe { fmc.size() }) == (1_usize)) + && ((*(unsafe { fmc.back() })) == ((('a' as u8) as i32) as f32)) + ); + (unsafe { fmc.pop_back() }); + assert!((unsafe { fmc.empty() })); + return 0; +} From 2944b7dcee892f6ab57760ae53d9f5bad14c1a70 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Gouveia?= Date: Sat, 13 Jun 2026 22:07:24 +0000 Subject: [PATCH 2/3] Fix class template translations --- cpp2rust/converter/converter.cpp | 2 +- cpp2rust/converter/mapper.cpp | 35 ++++++++- cpp2rust/converter/mapper.h | 2 + tests/unit/out/refcount/class_templates.rs | 88 ++++++++++++++++++++-- tests/unit/out/unsafe/class_templates.rs | 68 +++++++++++++++-- 5 files changed, 180 insertions(+), 15 deletions(-) diff --git a/cpp2rust/converter/converter.cpp b/cpp2rust/converter/converter.cpp index 0e06acec..cf221193 100644 --- a/cpp2rust/converter/converter.cpp +++ b/cpp2rust/converter/converter.cpp @@ -3476,7 +3476,7 @@ std::string Converter::GetRecordName(const clang::NamedDecl *decl) const { if (auto it = inner_structs_.find(ID); it != inner_structs_.end()) { return it->second; } - return ReplaceAll(Mapper::ToString(decl), "::", "_"); + return Mapper::ToRustName(Mapper::ToString(Mapper::GetTypeForDecl(decl))); } std::vector diff --git a/cpp2rust/converter/mapper.cpp b/cpp2rust/converter/mapper.cpp index cd664f1b..1b755eb6 100644 --- a/cpp2rust/converter/mapper.cpp +++ b/cpp2rust/converter/mapper.cpp @@ -723,9 +723,30 @@ bool ParamIsPointer(const clang::Expr *expr, unsigned index) { return GetParamInfo(expr, index).is_pointer(); } +clang::QualType GetTypeForDecl(const clang::NamedDecl *decl) { + if (const auto *spec = + llvm::dyn_cast(decl)) { + llvm::ArrayRef args = + spec->getTemplateArgs().asArray(); + llvm::SmallVector canon(args.begin(), + args.end()); + ctx_->canonicalizeTemplateArguments(canon); + + return ctx_->getTemplateSpecializationType( + clang::ElaboratedTypeKeyword::None, + clang::TemplateName(spec->getSpecializedTemplate()), args, canon); + } + + const auto *rdecl = llvm::dyn_cast(decl); + assert(rdecl && "Unsupported decl type"); + + return ctx_->getTagType(clang::ElaboratedTypeKeyword::None, + rdecl->getQualifier(), rdecl, /*OwnsTag*/ false); +} + void AddRuleForUserDefinedType(clang::NamedDecl *decl) { - auto cpp_name = ToString(decl); - auto rs_name = ReplaceAll(cpp_name, "::", "_"); + auto cpp_name = ToString(GetTypeForDecl(decl)); + auto rs_name = ToRustName(cpp_name); AddTypeRule(cpp_name, TranslationRule::TypeRule::Plain(rs_name)); @@ -767,6 +788,16 @@ void AddRuleForUserDefinedType(clang::NamedDecl *decl) { } } +std::string ToRustName(std::string name) { + size_t pos = 0; + std::string chars = "<>, "; + while ((pos = name.find_first_of(chars, pos)) != std::string::npos) { + name.replace(pos, 1, 1, '_'); + ++pos; + } + return ReplaceAll(name, "::", "_"); +} + std::string ToString(clang::QualType qual_type, ScalarSugar sugar) { assert(ctx_); diff --git a/cpp2rust/converter/mapper.h b/cpp2rust/converter/mapper.h index a967870e..ae6a4145 100644 --- a/cpp2rust/converter/mapper.h +++ b/cpp2rust/converter/mapper.h @@ -43,10 +43,12 @@ enum class ScalarSugar { kPreserve, }; +clang::QualType GetTypeForDecl(const clang::NamedDecl *decl); std::string ToString(clang::QualType qual_type, ScalarSugar sugar = ScalarSugar::kDesugar); std::string ToString(const clang::Expr *expr); std::string ToString(const clang::NamedDecl *decl); +std::string ToRustName(std::string name); void LoadTranslationRules(Model model, clang::ASTContext &ctx, const std::string &rules_dir); diff --git a/tests/unit/out/refcount/class_templates.rs b/tests/unit/out/refcount/class_templates.rs index 7d0059ee..7cb21d66 100644 --- a/tests/unit/out/refcount/class_templates.rs +++ b/tests/unit/out/refcount/class_templates.rs @@ -7,10 +7,10 @@ use std::io::{Read, Seek, Write}; use std::os::fd::AsFd; use std::rc::{Rc, Weak}; #[derive(Default)] -pub struct MyContainer { +pub struct MyContainer_int_ { vec_: Value>, } -impl MyContainer { +impl MyContainer_int_ { pub fn empty(&self) -> bool { return (*self.vec_.borrow()).is_empty(); } @@ -34,7 +34,7 @@ impl MyContainer { }; } } -impl Clone for MyContainer { +impl Clone for MyContainer_int_ { fn clone(&self) -> Self { let mut this = Self { vec_: Rc::new(RefCell::new((*self.vec_.borrow()).clone())), @@ -42,12 +42,86 @@ impl Clone for MyContainer { this } } -impl ByteRepr for MyContainer {} +impl ByteRepr for MyContainer_int_ {} +#[derive(Default)] +pub struct MyContainer_char_ { + vec_: Value>, +} +impl MyContainer_char_ { + pub fn empty(&self) -> bool { + return (*self.vec_.borrow()).is_empty(); + } + pub fn size(&self) -> usize { + return (*self.vec_.borrow()).len(); + } + pub fn back_const(&self) -> Ptr { + return (self.vec_.as_pointer() as Ptr).to_last(); + } + pub fn back(&self) -> Ptr { + return (self.vec_.as_pointer() as Ptr).to_last(); + } + pub fn pop_back(&self) { + (*self.vec_.borrow_mut()).pop(); + return; + } + pub fn push_back(&self, item: Ptr) { + { + let a0_clone = (item.read()).clone(); + (*self.vec_.borrow_mut()).push(a0_clone) + }; + } +} +impl Clone for MyContainer_char_ { + fn clone(&self) -> Self { + let mut this = Self { + vec_: Rc::new(RefCell::new((*self.vec_.borrow()).clone())), + }; + this + } +} +impl ByteRepr for MyContainer_char_ {} +#[derive(Default)] +pub struct MyContainer_float_ { + vec_: Value>, +} +impl MyContainer_float_ { + pub fn empty(&self) -> bool { + return (*self.vec_.borrow()).is_empty(); + } + pub fn size(&self) -> usize { + return (*self.vec_.borrow()).len(); + } + pub fn back_const(&self) -> Ptr { + return (self.vec_.as_pointer() as Ptr).to_last(); + } + pub fn back(&self) -> Ptr { + return (self.vec_.as_pointer() as Ptr).to_last(); + } + pub fn pop_back(&self) { + (*self.vec_.borrow_mut()).pop(); + return; + } + pub fn push_back(&self, item: Ptr) { + { + let a0_clone = (item.read()).clone(); + (*self.vec_.borrow_mut()).push(a0_clone) + }; + } +} +impl Clone for MyContainer_float_ { + fn clone(&self) -> Self { + let mut this = Self { + vec_: Rc::new(RefCell::new((*self.vec_.borrow()).clone())), + }; + this + } +} +impl ByteRepr for MyContainer_float_ {} pub fn main() { std::process::exit(main_0()); } fn main_0() -> i32 { - let imc: Value = Rc::new(RefCell::new(::default())); + let imc: Value = Rc::new(RefCell::new(::default())); assert!(({ (*imc.borrow()).empty() })); ({ let _item: Value = Rc::new(RefCell::new(1)); @@ -58,7 +132,7 @@ fn main_0() -> i32 { ); ({ (*imc.borrow()).pop_back() }); assert!(({ (*imc.borrow()).empty() })); - let cmc: Value = Rc::new(RefCell::new(::default())); + let cmc: Value = Rc::new(RefCell::new(::default())); assert!(({ (*cmc.borrow()).empty() })); ({ let _item: Value = Rc::new(RefCell::new(('a' as u8))); @@ -70,7 +144,7 @@ fn main_0() -> i32 { ); ({ (*cmc.borrow()).pop_back() }); assert!(({ (*cmc.borrow()).empty() })); - let fmc: Value = Rc::new(RefCell::new(::default())); + let fmc: Value = Rc::new(RefCell::new(::default())); assert!(({ (*fmc.borrow()).empty() })); ({ let _item: Value = Rc::new(RefCell::new((('a' as u8) as f32))); diff --git a/tests/unit/out/unsafe/class_templates.rs b/tests/unit/out/unsafe/class_templates.rs index 0b1c9263..752c8f21 100644 --- a/tests/unit/out/unsafe/class_templates.rs +++ b/tests/unit/out/unsafe/class_templates.rs @@ -8,10 +8,10 @@ use std::os::fd::{AsFd, FromRawFd, IntoRawFd}; use std::rc::Rc; #[repr(C)] #[derive(Clone, Default)] -pub struct MyContainer { +pub struct MyContainer_int_ { vec_: Vec, } -impl MyContainer { +impl MyContainer_int_ { pub unsafe fn empty(&self) -> bool { return self.vec_.is_empty(); } @@ -35,13 +35,71 @@ impl MyContainer { }; } } +#[repr(C)] +#[derive(Clone, Default)] +pub struct MyContainer_char_ { + vec_: Vec, +} +impl MyContainer_char_ { + pub unsafe fn empty(&self) -> bool { + return self.vec_.is_empty(); + } + pub unsafe fn size(&self) -> usize { + return self.vec_.len(); + } + pub unsafe fn back_const(&self) -> *const u8 { + return ((self.vec_).last().unwrap()); + } + pub unsafe fn back(&mut self) -> *mut u8 { + return ((self.vec_).last_mut().unwrap()); + } + pub unsafe fn pop_back(&mut self) { + self.vec_.pop(); + return; + } + pub unsafe fn push_back(&mut self, item: *const u8) { + { + let a0_clone = (*item).clone(); + self.vec_.push(a0_clone) + }; + } +} +#[repr(C)] +#[derive(Clone, Default)] +pub struct MyContainer_float_ { + vec_: Vec, +} +impl MyContainer_float_ { + pub unsafe fn empty(&self) -> bool { + return self.vec_.is_empty(); + } + pub unsafe fn size(&self) -> usize { + return self.vec_.len(); + } + pub unsafe fn back_const(&self) -> *const f32 { + return ((self.vec_).last().unwrap()); + } + pub unsafe fn back(&mut self) -> *mut f32 { + return ((self.vec_).last_mut().unwrap()); + } + pub unsafe fn pop_back(&mut self) { + self.vec_.pop(); + return; + } + pub unsafe fn push_back(&mut self, item: *const f32) { + { + let a0_clone = (*item).clone(); + self.vec_.push(a0_clone) + }; + } +} pub fn main() { unsafe { std::process::exit(main_0() as i32); } } unsafe fn main_0() -> i32 { - let mut imc: MyContainer = ::default(); + let mut imc: MyContainer_int_ = ::default(); assert!((unsafe { imc.empty() })); (unsafe { let mut _item = 1; @@ -50,7 +108,7 @@ unsafe fn main_0() -> i32 { assert!(((unsafe { imc.size() }) == (1_usize)) && ((*(unsafe { imc.back() })) == (1))); (unsafe { imc.pop_back() }); assert!((unsafe { imc.empty() })); - let mut cmc: MyContainer = ::default(); + let mut cmc: MyContainer_char_ = ::default(); assert!((unsafe { cmc.empty() })); (unsafe { let mut _item = ('a' as u8); @@ -62,7 +120,7 @@ unsafe fn main_0() -> i32 { ); (unsafe { cmc.pop_back() }); assert!((unsafe { cmc.empty() })); - let mut fmc: MyContainer = ::default(); + let mut fmc: MyContainer_float_ = ::default(); assert!((unsafe { fmc.empty() })); (unsafe { let mut _item = (('a' as u8) as f32); From ba6bb9906856bcd128b83c4c20c4f63080cc8866 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Gouveia?= Date: Mon, 15 Jun 2026 11:17:59 +0000 Subject: [PATCH 3/3] Address reviews --- cpp2rust/converter/mapper.cpp | 5 ++--- tests/unit/class_templates.cpp | 4 ++-- tests/unit/out/refcount/class_templates.rs | 4 ++-- tests/unit/out/unsafe/class_templates.rs | 5 ++--- 4 files changed, 8 insertions(+), 10 deletions(-) diff --git a/cpp2rust/converter/mapper.cpp b/cpp2rust/converter/mapper.cpp index 1b755eb6..26803783 100644 --- a/cpp2rust/converter/mapper.cpp +++ b/cpp2rust/converter/mapper.cpp @@ -790,9 +790,8 @@ void AddRuleForUserDefinedType(clang::NamedDecl *decl) { std::string ToRustName(std::string name) { size_t pos = 0; - std::string chars = "<>, "; - while ((pos = name.find_first_of(chars, pos)) != std::string::npos) { - name.replace(pos, 1, 1, '_'); + while ((pos = name.find_first_of("<>, ", pos)) != std::string::npos) { + name[pos] = '_'; ++pos; } return ReplaceAll(name, "::", "_"); diff --git a/tests/unit/class_templates.cpp b/tests/unit/class_templates.cpp index d4c0d8c6..3588f5d1 100644 --- a/tests/unit/class_templates.cpp +++ b/tests/unit/class_templates.cpp @@ -35,8 +35,8 @@ int main() { MyContainer fmc; assert(fmc.empty()); - fmc.push_back('a'); - assert(fmc.size() == 1 && fmc.back() == 'a'); + fmc.push_back(1.0); + assert(fmc.size() == 1 && fmc.back() == 1.0); fmc.pop_back(); assert(fmc.empty()); return 0; diff --git a/tests/unit/out/refcount/class_templates.rs b/tests/unit/out/refcount/class_templates.rs index 7cb21d66..3cbce86e 100644 --- a/tests/unit/out/refcount/class_templates.rs +++ b/tests/unit/out/refcount/class_templates.rs @@ -147,12 +147,12 @@ fn main_0() -> i32 { let fmc: Value = Rc::new(RefCell::new(::default())); assert!(({ (*fmc.borrow()).empty() })); ({ - let _item: Value = Rc::new(RefCell::new((('a' as u8) as f32))); + let _item: Value = Rc::new(RefCell::new((1.0E+0 as f32))); (*fmc.borrow()).push_back(_item.as_pointer()) }); assert!( (({ (*fmc.borrow()).size() }) == 1_usize) - && ((({ (*fmc.borrow()).back() }).read()) == ((('a' as u8) as i32) as f32)) + && (((({ (*fmc.borrow()).back() }).read()) as f64) == 1.0E+0) ); ({ (*fmc.borrow()).pop_back() }); assert!(({ (*fmc.borrow()).empty() })); diff --git a/tests/unit/out/unsafe/class_templates.rs b/tests/unit/out/unsafe/class_templates.rs index 752c8f21..151ccbf1 100644 --- a/tests/unit/out/unsafe/class_templates.rs +++ b/tests/unit/out/unsafe/class_templates.rs @@ -123,12 +123,11 @@ unsafe fn main_0() -> i32 { let mut fmc: MyContainer_float_ = ::default(); assert!((unsafe { fmc.empty() })); (unsafe { - let mut _item = (('a' as u8) as f32); + let mut _item = (1.0E+0 as f32); fmc.push_back(&mut _item) }); assert!( - ((unsafe { fmc.size() }) == (1_usize)) - && ((*(unsafe { fmc.back() })) == ((('a' as u8) as i32) as f32)) + ((unsafe { fmc.size() }) == (1_usize)) && (((*(unsafe { fmc.back() })) as f64) == (1.0E+0)) ); (unsafe { fmc.pop_back() }); assert!((unsafe { fmc.empty() }));