Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,7 @@ tests/test_files/namespaces.pyx
tests/test_files/number_conv.pyx
tests/test_files/enums.pyx
tests/test_files/wrapped_container_wrapper.pyx
tests/test_files/wrap_len_wrapper.pyx

# generated typestubs
tests/test_files/*.pyi
Expand Down
12 changes: 12 additions & 0 deletions autowrap/CodeGenerator.py
Original file line number Diff line number Diff line change
Expand Up @@ -684,6 +684,18 @@ def create_wrapper_for_class(self, r_class: ResolvedClass, out_codes: CodeDict)
locals(),
)

if len(r_class.wrap_len) != 0:
class_code.add(
"""
|
| def __len__(self):
| return deref(self.inst.get()).%s
|
"""
% r_class.wrap_len[0],
locals(),
)

if "wrap-buffer-protocol" in r_class.cpp_decl.annotations:
buffer_parts = r_class.cpp_decl.annotations["wrap-buffer-protocol"][0].split(",")
buffer_sourcer = buffer_parts[0]
Expand Down
2 changes: 2 additions & 0 deletions autowrap/DeclResolver.py
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,7 @@ class ResolvedClass(object):
no_pxd_import: bool
wrap_manual_memory: Union[bool, List[AnyStr]]
wrap_hash: List[AnyStr]
wrap_len: List[AnyStr]
local_map: Dict
instance_map: Dict
pxd_import_path: Optional[AnyStr]
Expand Down Expand Up @@ -206,6 +207,7 @@ def __init__(self, name, methods, attributes, decl, instance_map, local_map):
# elif empty list or list with actual content: pass
assert isinstance(self.wrap_manual_memory, list)
self.wrap_hash = decl.annotations.get("wrap-hash", [])
self.wrap_len = decl.annotations.get("wrap-len", [])
self.local_map = local_map
self.instance_map = instance_map
self.pxd_import_path = None
Expand Down
138 changes: 138 additions & 0 deletions tests/test_files/wrap_len_test.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
#ifndef WRAP_LEN_TEST_HPP
#define WRAP_LEN_TEST_HPP

#include <vector>
#include <string>
#include <cstddef>

// Basic container with size() - used with wrap-len
class BasicContainer {
private:
std::vector<int> data_;
public:
BasicContainer() {}
BasicContainer(int count) {
for (int i = 0; i < count; ++i) {
data_.push_back(i);
}
}

size_t size() const { return data_.size(); }
void add(int value) { data_.push_back(value); }
void clear() { data_.clear(); }
int get(size_t index) const { return data_[index]; }
};

// Container with length() method instead of size()
class LengthContainer {
private:
std::vector<std::string> items_;
public:
LengthContainer() {}

int length() const { return static_cast<int>(items_.size()); }
void append(const std::string& item) { items_.push_back(item); }
std::string getItem(int index) const { return items_[index]; }
};

// Container with count() method
class CountContainer {
private:
int count_;
public:
CountContainer() : count_(0) {}
CountContainer(int initial) : count_(initial) {}

unsigned int count() const { return static_cast<unsigned int>(count_); }
void increment() { count_++; }
void decrement() { if (count_ > 0) count_--; }
};

// Container with size() but NO wrap-len annotation - should NOT have __len__
class NoLenContainer {
private:
std::vector<double> values_;
public:
NoLenContainer() {}

size_t size() const { return values_.size(); }
void push(double v) { values_.push_back(v); }
double sum() const {
double s = 0;
for (auto v : values_) s += v;
return s;
}
};

// Container where size() is wrap-ignored
// The size method should not be wrapped, but wrap-len should still work
// since it calls the C++ method directly
class IgnoredSizeContainer {
private:
std::vector<int> data_;
public:
IgnoredSizeContainer() {}
IgnoredSizeContainer(int count) {
for (int i = 0; i < count; ++i) {
data_.push_back(i * 10);
}
}

// This method is wrap-ignored but wrap-len should still work
size_t size() const { return data_.size(); }
void add(int v) { data_.push_back(v); }
int get(size_t i) const { return data_[i]; }
};

// Container with getSize() - different naming convention
class GetSizeContainer {
private:
int size_;
public:
GetSizeContainer() : size_(0) {}
GetSizeContainer(int s) : size_(s) {}

size_t getSize() const { return static_cast<size_t>(size_); }
void setSize(int s) { size_ = s; }
};

// Empty container that always returns 0
class EmptyContainer {
public:
EmptyContainer() {}
size_t size() const { return 0; }
};

// Template container
template <typename T>
class TemplateContainer {
private:
std::vector<T> data_;
public:
TemplateContainer() {}

size_t size() const { return data_.size(); }
void add(const T& value) { data_.push_back(value); }
T get(size_t index) const { return data_[index]; }
};

// Container with both size() and length() - test that we can choose
class DualLenContainer {
private:
std::vector<int> data_;
public:
DualLenContainer() {}
DualLenContainer(int count) {
for (int i = 0; i < count; ++i) {
data_.push_back(i);
}
}

// Returns actual size
size_t size() const { return data_.size(); }
// Returns size * 2 (for testing that wrap-len picks the right method)
size_t length() const { return data_.size() * 2; }
void add(int v) { data_.push_back(v); }
};

#endif // WRAP_LEN_TEST_HPP
110 changes: 110 additions & 0 deletions tests/test_files/wrap_len_test.pxd
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
# cython: language_level=3
from libcpp.string cimport string as libcpp_string
from libcpp.vector cimport vector as libcpp_vector
from libcpp cimport bool

cdef extern from "wrap_len_test.hpp":

# Basic container with wrap-len using size()
cdef cppclass BasicContainer:
# wrap-len:
# size()

BasicContainer()
BasicContainer(int count)

size_t size()
void add(int value)
void clear()
int get(size_t index)

# Container with length() method - wrap-len using length()
cdef cppclass LengthContainer:
# wrap-len:
# length()

LengthContainer()

int length()
void append(libcpp_string item)
libcpp_string getItem(int index)

# Container with count() method
cdef cppclass CountContainer:
# wrap-len:
# count()

CountContainer()
CountContainer(int initial)

unsigned int count()
void increment()
void decrement()

# Container with size() but NO wrap-len annotation
# This should NOT have __len__ in Python
cdef cppclass NoLenContainer:
NoLenContainer()

size_t size()
void push(double v)
double sum()

# Container where size() is wrap-ignored
# wrap-len should still work since it directly calls C++ method
cdef cppclass IgnoredSizeContainer:
# wrap-len:
# size()

IgnoredSizeContainer()
IgnoredSizeContainer(int count)

size_t size() # wrap-ignore
void add(int v)
int get(size_t i)

# Container using getSize() method name
cdef cppclass GetSizeContainer:
# wrap-len:
# getSize()

GetSizeContainer()
GetSizeContainer(int s)

size_t getSize()
void setSize(int s)

# Empty container that always returns 0
cdef cppclass EmptyContainer:
# wrap-len:
# size()

EmptyContainer()
size_t size()

# Template container - test wrap-len with templates
cdef cppclass TemplateContainer[T]:
# wrap-instances:
# IntTemplateContainer := TemplateContainer[int]
# StringTemplateContainer := TemplateContainer[libcpp_string]
# wrap-len:
# size()

TemplateContainer()

size_t size()
void add(T value)
T get(size_t index)

# Container with both size() and length() - test that wrap-len picks correctly
# Here we choose length() which returns size * 2
cdef cppclass DualLenContainer:
# wrap-len:
# length()

DualLenContainer()
DualLenContainer(int count)

size_t size()
size_t length()
void add(int v)
Loading