Skip to content

Commit 966d0e5

Browse files
committed
Add wrap-len annotation for __len__ special method support
Implement the wrap-len annotation which allows C++ classes with container interfaces to expose Python's __len__ special method. This enables len() calls on wrapped objects. Changes: - Add wrap_len attribute to ResolvedClass in DeclResolver.py - Add __len__ code generation in CodeGenerator.py - Add comprehensive tests covering: - Basic wrap-len with size() method - Different method names (length(), count(), getSize()) - Container without wrap-len (no __len__ generated) - wrap-len with wrap-ignored size() method (still works) - Template classes with wrap-len - Choosing between multiple methods (size vs length) - Boolean context (empty containers are falsy) - Edge cases (empty, large containers) Usage in pxd files: cdef cppclass Container: # wrap-len: # size() size_t size()
1 parent 798e8c2 commit 966d0e5

File tree

6 files changed

+637
-0
lines changed

6 files changed

+637
-0
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,7 @@ tests/test_files/namespaces.pyx
173173
tests/test_files/number_conv.pyx
174174
tests/test_files/enums.pyx
175175
tests/test_files/wrapped_container_wrapper.pyx
176+
tests/test_files/wrap_len_wrapper.pyx
176177

177178
# generated typestubs
178179
tests/test_files/*.pyi

autowrap/CodeGenerator.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -684,6 +684,18 @@ def create_wrapper_for_class(self, r_class: ResolvedClass, out_codes: CodeDict)
684684
locals(),
685685
)
686686

687+
if len(r_class.wrap_len) != 0:
688+
class_code.add(
689+
"""
690+
|
691+
| def __len__(self):
692+
| return deref(self.inst.get()).%s
693+
|
694+
"""
695+
% r_class.wrap_len[0],
696+
locals(),
697+
)
698+
687699
if "wrap-buffer-protocol" in r_class.cpp_decl.annotations:
688700
buffer_parts = r_class.cpp_decl.annotations["wrap-buffer-protocol"][0].split(",")
689701
buffer_sourcer = buffer_parts[0]

autowrap/DeclResolver.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,7 @@ class ResolvedClass(object):
176176
no_pxd_import: bool
177177
wrap_manual_memory: Union[bool, List[AnyStr]]
178178
wrap_hash: List[AnyStr]
179+
wrap_len: List[AnyStr]
179180
local_map: Dict
180181
instance_map: Dict
181182
pxd_import_path: Optional[AnyStr]
@@ -206,6 +207,7 @@ def __init__(self, name, methods, attributes, decl, instance_map, local_map):
206207
# elif empty list or list with actual content: pass
207208
assert isinstance(self.wrap_manual_memory, list)
208209
self.wrap_hash = decl.annotations.get("wrap-hash", [])
210+
self.wrap_len = decl.annotations.get("wrap-len", [])
209211
self.local_map = local_map
210212
self.instance_map = instance_map
211213
self.pxd_import_path = None

tests/test_files/wrap_len_test.hpp

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
#ifndef WRAP_LEN_TEST_HPP
2+
#define WRAP_LEN_TEST_HPP
3+
4+
#include <vector>
5+
#include <string>
6+
#include <cstddef>
7+
8+
// Basic container with size() - used with wrap-len
9+
class BasicContainer {
10+
private:
11+
std::vector<int> data_;
12+
public:
13+
BasicContainer() {}
14+
BasicContainer(int count) {
15+
for (int i = 0; i < count; ++i) {
16+
data_.push_back(i);
17+
}
18+
}
19+
20+
size_t size() const { return data_.size(); }
21+
void add(int value) { data_.push_back(value); }
22+
void clear() { data_.clear(); }
23+
int get(size_t index) const { return data_[index]; }
24+
};
25+
26+
// Container with length() method instead of size()
27+
class LengthContainer {
28+
private:
29+
std::vector<std::string> items_;
30+
public:
31+
LengthContainer() {}
32+
33+
int length() const { return static_cast<int>(items_.size()); }
34+
void append(const std::string& item) { items_.push_back(item); }
35+
std::string getItem(int index) const { return items_[index]; }
36+
};
37+
38+
// Container with count() method
39+
class CountContainer {
40+
private:
41+
int count_;
42+
public:
43+
CountContainer() : count_(0) {}
44+
CountContainer(int initial) : count_(initial) {}
45+
46+
unsigned int count() const { return static_cast<unsigned int>(count_); }
47+
void increment() { count_++; }
48+
void decrement() { if (count_ > 0) count_--; }
49+
};
50+
51+
// Container with size() but NO wrap-len annotation - should NOT have __len__
52+
class NoLenContainer {
53+
private:
54+
std::vector<double> values_;
55+
public:
56+
NoLenContainer() {}
57+
58+
size_t size() const { return values_.size(); }
59+
void push(double v) { values_.push_back(v); }
60+
double sum() const {
61+
double s = 0;
62+
for (auto v : values_) s += v;
63+
return s;
64+
}
65+
};
66+
67+
// Container where size() is wrap-ignored
68+
// The size method should not be wrapped, but wrap-len should still work
69+
// since it calls the C++ method directly
70+
class IgnoredSizeContainer {
71+
private:
72+
std::vector<int> data_;
73+
public:
74+
IgnoredSizeContainer() {}
75+
IgnoredSizeContainer(int count) {
76+
for (int i = 0; i < count; ++i) {
77+
data_.push_back(i * 10);
78+
}
79+
}
80+
81+
// This method is wrap-ignored but wrap-len should still work
82+
size_t size() const { return data_.size(); }
83+
void add(int v) { data_.push_back(v); }
84+
int get(size_t i) const { return data_[i]; }
85+
};
86+
87+
// Container with getSize() - different naming convention
88+
class GetSizeContainer {
89+
private:
90+
int size_;
91+
public:
92+
GetSizeContainer() : size_(0) {}
93+
GetSizeContainer(int s) : size_(s) {}
94+
95+
size_t getSize() const { return static_cast<size_t>(size_); }
96+
void setSize(int s) { size_ = s; }
97+
};
98+
99+
// Empty container that always returns 0
100+
class EmptyContainer {
101+
public:
102+
EmptyContainer() {}
103+
size_t size() const { return 0; }
104+
};
105+
106+
// Template container
107+
template <typename T>
108+
class TemplateContainer {
109+
private:
110+
std::vector<T> data_;
111+
public:
112+
TemplateContainer() {}
113+
114+
size_t size() const { return data_.size(); }
115+
void add(const T& value) { data_.push_back(value); }
116+
T get(size_t index) const { return data_[index]; }
117+
};
118+
119+
// Container with both size() and length() - test that we can choose
120+
class DualLenContainer {
121+
private:
122+
std::vector<int> data_;
123+
public:
124+
DualLenContainer() {}
125+
DualLenContainer(int count) {
126+
for (int i = 0; i < count; ++i) {
127+
data_.push_back(i);
128+
}
129+
}
130+
131+
// Returns actual size
132+
size_t size() const { return data_.size(); }
133+
// Returns size * 2 (for testing that wrap-len picks the right method)
134+
size_t length() const { return data_.size() * 2; }
135+
void add(int v) { data_.push_back(v); }
136+
};
137+
138+
#endif // WRAP_LEN_TEST_HPP

tests/test_files/wrap_len_test.pxd

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
# cython: language_level=3
2+
from libcpp.string cimport string as libcpp_string
3+
from libcpp.vector cimport vector as libcpp_vector
4+
from libcpp cimport bool
5+
6+
cdef extern from "wrap_len_test.hpp":
7+
8+
# Basic container with wrap-len using size()
9+
cdef cppclass BasicContainer:
10+
# wrap-len:
11+
# size()
12+
13+
BasicContainer()
14+
BasicContainer(int count)
15+
16+
size_t size()
17+
void add(int value)
18+
void clear()
19+
int get(size_t index)
20+
21+
# Container with length() method - wrap-len using length()
22+
cdef cppclass LengthContainer:
23+
# wrap-len:
24+
# length()
25+
26+
LengthContainer()
27+
28+
int length()
29+
void append(libcpp_string item)
30+
libcpp_string getItem(int index)
31+
32+
# Container with count() method
33+
cdef cppclass CountContainer:
34+
# wrap-len:
35+
# count()
36+
37+
CountContainer()
38+
CountContainer(int initial)
39+
40+
unsigned int count()
41+
void increment()
42+
void decrement()
43+
44+
# Container with size() but NO wrap-len annotation
45+
# This should NOT have __len__ in Python
46+
cdef cppclass NoLenContainer:
47+
NoLenContainer()
48+
49+
size_t size()
50+
void push(double v)
51+
double sum()
52+
53+
# Container where size() is wrap-ignored
54+
# wrap-len should still work since it directly calls C++ method
55+
cdef cppclass IgnoredSizeContainer:
56+
# wrap-len:
57+
# size()
58+
59+
IgnoredSizeContainer()
60+
IgnoredSizeContainer(int count)
61+
62+
size_t size() # wrap-ignore
63+
void add(int v)
64+
int get(size_t i)
65+
66+
# Container using getSize() method name
67+
cdef cppclass GetSizeContainer:
68+
# wrap-len:
69+
# getSize()
70+
71+
GetSizeContainer()
72+
GetSizeContainer(int s)
73+
74+
size_t getSize()
75+
void setSize(int s)
76+
77+
# Empty container that always returns 0
78+
cdef cppclass EmptyContainer:
79+
# wrap-len:
80+
# size()
81+
82+
EmptyContainer()
83+
size_t size()
84+
85+
# Template container - test wrap-len with templates
86+
cdef cppclass TemplateContainer[T]:
87+
# wrap-instances:
88+
# IntTemplateContainer := TemplateContainer[int]
89+
# StringTemplateContainer := TemplateContainer[libcpp_string]
90+
# wrap-len:
91+
# size()
92+
93+
TemplateContainer()
94+
95+
size_t size()
96+
void add(T value)
97+
T get(size_t index)
98+
99+
# Container with both size() and length() - test that wrap-len picks correctly
100+
# Here we choose length() which returns size * 2
101+
cdef cppclass DualLenContainer:
102+
# wrap-len:
103+
# length()
104+
105+
DualLenContainer()
106+
DualLenContainer(int count)
107+
108+
size_t size()
109+
size_t length()
110+
void add(int v)

0 commit comments

Comments
 (0)