Skip to content

Commit 2d6388a

Browse files
authored
Only translate used methods of a template class (#329)
cpp2rust materializes template specializations in order to have access to defaulted copy or move constructors. However, materializing all the methods of a class is wrong. Some methods might not satisfy the constraints of the type that specializes the class, for example: ```cpp struct Point { int x; }; template <typename T> struct Box { T val; T get() { return val; } // This only works for T's that have operator+. // int is fine, Point is not. T twice() { return val + val; } }; ``` Specializing `Box` with `int` satisfies both get and twice, however `Point` only satisfies get. This PR only materializes methods that are used by the specialization.
1 parent ba10c52 commit 2d6388a

9 files changed

Lines changed: 230 additions & 52 deletions

File tree

‎cpp2rust/converter/converter.cpp‎

Lines changed: 35 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -626,22 +626,6 @@ static bool hasUserDefinedNonDefaultCopyOrMoveCtor(clang::CXXRecordDecl *decl) {
626626
return false;
627627
}
628628

629-
void Converter::materializeTemplateSpecialization(clang::CXXRecordDecl *decl) {
630-
for (auto method : decl->methods()) {
631-
const clang::FunctionDecl *definition = nullptr;
632-
if (method->isDefined(definition)) {
633-
continue;
634-
}
635-
636-
if (auto pattern = method->getTemplateInstantiationPattern()) {
637-
if (pattern->doesThisDeclarationHaveABody()) {
638-
sema_->InstantiateFunctionDefinition(method->getLocation(), method,
639-
/*Recursive=*/true);
640-
}
641-
}
642-
}
643-
}
644-
645629
bool IsPointerType(clang::QualType qual_type) {
646630
return qual_type->isPointerType() ||
647631
(qual_type->isArrayType() &&
@@ -750,6 +734,23 @@ bool Converter::VisitRecordDecl(clang::RecordDecl *decl) {
750734
return false;
751735
}
752736

737+
static bool IsEmittableMethod(clang::CXXMethodDecl *method) {
738+
// Virtual methods go into the base trait impl, destructors into Drop
739+
if (method->isVirtual() || clang::isa<clang::CXXDestructorDecl>(method)) {
740+
return false;
741+
}
742+
// Compiler-generated members are covered by derived traits
743+
if (method->isImplicit()) {
744+
return false;
745+
}
746+
if (auto *definition = method->getDefinition();
747+
definition && definition->isDefaulted()) {
748+
return false;
749+
}
750+
return method->isThisDeclarationADefinition() ||
751+
clang::isa<clang::CXXConstructorDecl>(method);
752+
}
753+
753754
void Converter::EmitRustStructOrUnion(clang::RecordDecl *decl) {
754755
// Enums and static variables. In rust they live outside the record
755756
for (auto *d : decl->decls()) {
@@ -809,17 +810,9 @@ void Converter::EmitRustStructOrUnion(clang::RecordDecl *decl) {
809810
if (auto *cxx = clang::dyn_cast<clang::CXXRecordDecl>(decl)) {
810811
auto struct_name = GetRecordName(cxx);
811812

812-
ConvertCXXMethodDecls(
813-
cxx, std::format("{} {}", keyword::kImpl, struct_name),
814-
[](auto *method) {
815-
return !method->isImplicit() &&
816-
!(method->getDefinition() &&
817-
method->getDefinition()->isDefaulted()) &&
818-
(method->isThisDeclarationADefinition() ||
819-
clang::isa<clang::CXXConstructorDecl>(method)) &&
820-
!method->isVirtual() &&
821-
!clang::isa<clang::CXXDestructorDecl>(method);
822-
});
813+
ConvertCXXMethodDecls(cxx,
814+
std::format("{} {}", keyword::kImpl, struct_name),
815+
IsEmittableMethod);
823816

824817
if (cxx->bases_begin() != cxx->bases_end()) {
825818
ConvertCXXMethodDecls(
@@ -867,10 +860,6 @@ void Converter::EmitRustUnion(clang::RecordDecl *decl) {
867860
}
868861

869862
bool Converter::VisitCXXRecordDecl(clang::CXXRecordDecl *decl) {
870-
if (clang::isa<clang::ClassTemplateSpecializationDecl>(decl)) {
871-
materializeTemplateSpecialization(decl);
872-
}
873-
874863
decl->dump(log());
875864

876865
Mapper::AddRuleForUserDefinedType(decl);
@@ -888,6 +877,15 @@ bool Converter::VisitCXXRecordDecl(clang::CXXRecordDecl *decl) {
888877
}
889878

890879
if (!record_decls_.MarkDefined(GetRecordName(decl))) {
880+
// Other translation units may instantiate members this one did not.
881+
if (clang::isa<clang::ClassTemplateSpecializationDecl>(decl)) {
882+
ConvertCXXMethodDecls(
883+
decl, std::format("{} {}", keyword::kImpl, GetRecordName(decl)),
884+
[](auto *method) {
885+
return IsEmittableMethod(method) && method->hasBody() &&
886+
!decl_ids_.contains(GetMethodID(method));
887+
});
888+
}
891889
return false;
892890
}
893891

@@ -927,6 +925,12 @@ bool Converter::VisitCXXMethodDecl(clang::CXXMethodDecl *decl) {
927925
if (!IsConvertibleCXXMethodDecl(decl)) {
928926
return false;
929927
}
928+
if (!decl->isPureVirtual() && !decl->hasBody()) {
929+
return false;
930+
}
931+
if (!decl_ids_.insert(GetMethodID(decl)).second) {
932+
return false;
933+
}
930934
curr_function_ = decl;
931935

932936
if (decl->isOutOfLine() && !decl->overridden_methods().empty()) {

‎cpp2rust/converter/converter.h‎

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -931,8 +931,6 @@ class Converter : public clang::RecursiveASTVisitor<Converter> {
931931
bool IsCastRedundantInRust(clang::Expr *expr, clang::QualType target_type);
932932

933933
private:
934-
void materializeTemplateSpecialization(clang::CXXRecordDecl *decl);
935-
936934
std::string getIntegerLiteral(clang::IntegerLiteral *expr, bool incl_type,
937935
const clang::QualType *type = nullptr);
938936
const char *keyword_unsafe_;

‎cpp2rust/converter/converter_lib.cpp‎

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -262,7 +262,9 @@ bool IsConvertibleCXXRecordDecl(const clang::CXXRecordDecl *decl) {
262262
return decl->isThisDeclarationADefinition() &&
263263
std::all_of(
264264
decl->method_begin(), decl->method_end(), [](auto *method) {
265-
return method->getDefinition() || method->isPureVirtual();
265+
return method->getDefinition() || method->isPureVirtual() ||
266+
method->getTemplateInstantiationPattern() ||
267+
method->getDescribedFunctionTemplate();
266268
});
267269
}
268270

@@ -427,6 +429,10 @@ std::string GetID(const clang::Decl *decl) {
427429
return GetLocationID(decl) + GetParamSignature(decl);
428430
}
429431

432+
std::string GetMethodID(const clang::CXXMethodDecl *decl) {
433+
return decl->getQualifiedNameAsString() + GetID(decl);
434+
}
435+
430436
std::string DisambiguateAnonymousTag(const clang::TagDecl *tag) {
431437
if (!tag) {
432438
return "";

‎cpp2rust/converter/converter_lib.h‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ unsigned GetColumnNumber(const clang::Decl *decl);
9999
unsigned GetArraySize(clang::QualType array_type);
100100

101101
std::string GetID(const clang::Decl *decl);
102+
std::string GetMethodID(const clang::CXXMethodDecl *decl);
102103

103104
std::string GetNamedDeclAsString(const clang::NamedDecl *decl);
104105

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
#include <cassert>
2+
3+
// This tests that cpp2rust respects the lazy instantiation of templates. On
4+
// Box<int> and Box<Point> declarations, only the class template and data
5+
// memebers are instantiated.
6+
//
7+
// Then, each used method is lazyly instantiated when it's used, i.e. twice()
8+
// is not instantiated for Box<Point>.
9+
10+
struct Point {
11+
int x;
12+
};
13+
14+
template <typename T> struct Box {
15+
T val;
16+
T get() { return val; }
17+
18+
// This only works for T's that have operator+.
19+
// int is fine, Point is not.
20+
T twice() { return val + val; }
21+
};
22+
23+
int main() {
24+
Box<int> i = {3};
25+
assert(i.twice() == 6);
26+
27+
Box<Point> p = {{4}};
28+
assert(p.get().x == 4);
29+
30+
return 0;
31+
}
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
extern crate libcc2rs;
2+
use libcc2rs::*;
3+
use std::cell::RefCell;
4+
use std::collections::BTreeMap;
5+
use std::io::prelude::*;
6+
use std::io::{Read, Seek, Write};
7+
use std::os::fd::AsFd;
8+
use std::rc::{Rc, Weak};
9+
#[derive(Default)]
10+
pub struct Point {
11+
pub x: Value<i32>,
12+
}
13+
impl Clone for Point {
14+
fn clone(&self) -> Self {
15+
let mut this = Self {
16+
x: Rc::new(RefCell::new((*self.x.borrow()))),
17+
};
18+
this
19+
}
20+
}
21+
impl ByteRepr for Point {
22+
fn byte_size() -> usize {
23+
4
24+
}
25+
fn to_bytes(&self, buf: &mut [u8]) {
26+
(*self.x.borrow()).to_bytes(&mut buf[0..4]);
27+
}
28+
fn from_bytes(buf: &[u8]) -> Self {
29+
Self {
30+
x: Rc::new(RefCell::new(<i32>::from_bytes(&buf[0..4]))),
31+
}
32+
}
33+
}
34+
#[derive(Default)]
35+
pub struct Box_int_ {
36+
pub val: Value<i32>,
37+
}
38+
impl Box_int_ {
39+
pub fn twice(&self) -> i32 {
40+
return ((*self.val.borrow()) + (*self.val.borrow()));
41+
}
42+
}
43+
impl Clone for Box_int_ {
44+
fn clone(&self) -> Self {
45+
let mut this = Self {
46+
val: Rc::new(RefCell::new((*self.val.borrow()))),
47+
};
48+
this
49+
}
50+
}
51+
impl ByteRepr for Box_int_ {
52+
fn byte_size() -> usize {
53+
4
54+
}
55+
fn to_bytes(&self, buf: &mut [u8]) {
56+
(*self.val.borrow()).to_bytes(&mut buf[0..4]);
57+
}
58+
fn from_bytes(buf: &[u8]) -> Self {
59+
Self {
60+
val: Rc::new(RefCell::new(<i32>::from_bytes(&buf[0..4]))),
61+
}
62+
}
63+
}
64+
#[derive(Default)]
65+
pub struct Box_Point_ {
66+
pub val: Value<Point>,
67+
}
68+
impl Box_Point_ {
69+
pub fn get(&self) -> Point {
70+
return (*self.val.borrow()).clone();
71+
}
72+
}
73+
impl Clone for Box_Point_ {
74+
fn clone(&self) -> Self {
75+
let mut this = Self {
76+
val: Rc::new(RefCell::new((*self.val.borrow()).clone())),
77+
};
78+
this
79+
}
80+
}
81+
impl ByteRepr for Box_Point_ {
82+
fn byte_size() -> usize {
83+
4
84+
}
85+
fn to_bytes(&self, buf: &mut [u8]) {
86+
(*self.val.borrow()).to_bytes(&mut buf[0..4]);
87+
}
88+
fn from_bytes(buf: &[u8]) -> Self {
89+
Self {
90+
val: Rc::new(RefCell::new(<Point>::from_bytes(&buf[0..4]))),
91+
}
92+
}
93+
}
94+
pub fn main() {
95+
std::process::exit(main_0());
96+
}
97+
fn main_0() -> i32 {
98+
let i: Value<Box_int_> = Rc::new(RefCell::new(Box_int_ {
99+
val: Rc::new(RefCell::new(3)),
100+
}));
101+
assert!((({ (*i.borrow()).twice() }) == 6));
102+
let p: Value<Box_Point_> = Rc::new(RefCell::new(Box_Point_ {
103+
val: Rc::new(RefCell::new(Point {
104+
x: Rc::new(RefCell::new(4)),
105+
})),
106+
}));
107+
assert!(((*({ (*p.borrow()).get() }).x.borrow()) == 4));
108+
return 0;
109+
}

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

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,6 @@ impl MyContainer_int_ {
1717
pub fn size(&self) -> usize {
1818
return (*self.vec_.borrow()).len();
1919
}
20-
pub fn back_const(&self) -> Ptr<i32> {
21-
return (self.vec_.as_pointer() as Ptr<i32>).to_last();
22-
}
2320
pub fn back(&self) -> Ptr<i32> {
2421
return (self.vec_.as_pointer() as Ptr<i32>).to_last();
2522
}
@@ -66,9 +63,6 @@ impl MyContainer_char_ {
6663
pub fn size(&self) -> usize {
6764
return (*self.vec_.borrow()).len();
6865
}
69-
pub fn back_const(&self) -> Ptr<u8> {
70-
return (self.vec_.as_pointer() as Ptr<u8>).to_last();
71-
}
7266
pub fn back(&self) -> Ptr<u8> {
7367
return (self.vec_.as_pointer() as Ptr<u8>).to_last();
7468
}
@@ -115,9 +109,6 @@ impl MyContainer_float_ {
115109
pub fn size(&self) -> usize {
116110
return (*self.vec_.borrow()).len();
117111
}
118-
pub fn back_const(&self) -> Ptr<f32> {
119-
return (self.vec_.as_pointer() as Ptr<f32>).to_last();
120-
}
121112
pub fn back(&self) -> Ptr<f32> {
122113
return (self.vec_.as_pointer() as Ptr<f32>).to_last();
123114
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
extern crate libc;
2+
use libc::*;
3+
extern crate libcc2rs;
4+
use libcc2rs::*;
5+
use std::collections::BTreeMap;
6+
use std::io::{Read, Seek, Write};
7+
use std::os::fd::{AsFd, FromRawFd, IntoRawFd};
8+
use std::rc::Rc;
9+
#[repr(C)]
10+
#[derive(Copy, Clone, Default)]
11+
pub struct Point {
12+
pub x: i32,
13+
}
14+
#[repr(C)]
15+
#[derive(Copy, Clone, Default)]
16+
pub struct Box_int_ {
17+
pub val: i32,
18+
}
19+
impl Box_int_ {
20+
pub unsafe fn twice(&mut self) -> i32 {
21+
return ((self.val) + (self.val));
22+
}
23+
}
24+
#[repr(C)]
25+
#[derive(Copy, Clone, Default)]
26+
pub struct Box_Point_ {
27+
pub val: Point,
28+
}
29+
impl Box_Point_ {
30+
pub unsafe fn get(&mut self) -> Point {
31+
return self.val.clone();
32+
}
33+
}
34+
pub fn main() {
35+
unsafe {
36+
std::process::exit(main_0() as i32);
37+
}
38+
}
39+
unsafe fn main_0() -> i32 {
40+
let mut i: Box_int_ = Box_int_ { val: 3 };
41+
assert!(((unsafe { i.twice() }) == (6)));
42+
let mut p: Box_Point_ = Box_Point_ {
43+
val: Point { x: 4 },
44+
};
45+
assert!((((unsafe { p.get() }).x) == (4)));
46+
return 0;
47+
}

‎tests/unit/out/unsafe/class_templates.rs‎

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,6 @@ impl MyContainer_int_ {
1818
pub unsafe fn size(&self) -> usize {
1919
return self.vec_.len();
2020
}
21-
pub unsafe fn back_const(&self) -> *const i32 {
22-
return ((self.vec_).last().unwrap());
23-
}
2421
pub unsafe fn back(&mut self) -> *mut i32 {
2522
return ((self.vec_).last_mut().unwrap());
2623
}
@@ -47,9 +44,6 @@ impl MyContainer_char_ {
4744
pub unsafe fn size(&self) -> usize {
4845
return self.vec_.len();
4946
}
50-
pub unsafe fn back_const(&self) -> *const libc::c_char {
51-
return ((self.vec_).last().unwrap());
52-
}
5347
pub unsafe fn back(&mut self) -> *mut libc::c_char {
5448
return ((self.vec_).last_mut().unwrap());
5549
}
@@ -76,9 +70,6 @@ impl MyContainer_float_ {
7670
pub unsafe fn size(&self) -> usize {
7771
return self.vec_.len();
7872
}
79-
pub unsafe fn back_const(&self) -> *const f32 {
80-
return ((self.vec_).last().unwrap());
81-
}
8273
pub unsafe fn back(&mut self) -> *mut f32 {
8374
return ((self.vec_).last_mut().unwrap());
8475
}

0 commit comments

Comments
 (0)