Skip to content

Add __len__ wrapping for C++ container classes#223

Merged
timosachsenberg merged 1 commit intomasterfrom
claude/wrap-len-cpp-containers-SK9El
Dec 18, 2025
Merged

Add __len__ wrapping for C++ container classes#223
timosachsenberg merged 1 commit intomasterfrom
claude/wrap-len-cpp-containers-SK9El

Conversation

@timosachsenberg
Copy link
Collaborator

@timosachsenberg timosachsenberg commented Dec 17, 2025

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()

Summary by CodeRabbit

  • New Features

    • Wrapped classes now support the wrap-len annotation, enabling automatic __len__() method generation for Python compatibility. Supports multiple length-reporting method names (size(), length(), count(), getSize()).
  • Tests

    • Comprehensive test coverage added for wrap-len functionality, including template containers, wrap-ignore interactions, and Python iteration compatibility.

✏️ Tip: You can customize this high-level summary in your review settings.

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()
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 17, 2025

📝 Walkthrough

Walkthrough

This PR implements a new wrap-len annotation feature for autowrap. The changes add infrastructure to parse wrap-len annotations from class declarations in DeclResolver.py, generate corresponding __len__() methods in wrapped Python classes via CodeGenerator.py, and provide comprehensive test coverage with C++ headers, Cython interfaces, and Python validation tests.

Changes

Cohort / File(s) Summary
Feature Implementation
autowrap/DeclResolver.py, autowrap/CodeGenerator.py
Introduced wrap_len: List[AnyStr] attribute to ResolvedClass to store wrap-len annotation data; modified CodeGenerator to generate __len__(self) methods for wrapped classes that dereference the configured wrap-len method.
Test Infrastructure
tests/test_files/wrap_len_test.hpp, tests/test_files/wrap_len_test.pxd
Added new C++ header and Cython interface files defining nine container test classes (BasicContainer, LengthContainer, CountContainer, NoLenContainer, IgnoredSizeContainer, GetSizeContainer, EmptyContainer, TemplateContainer<T>, DualLenContainer) with various size-reporting methods to validate wrap-len behavior across different naming conventions and container types.
Test Validation
tests/test_wrap_len.py
Added comprehensive test module covering wrap-len functionality: basic size methods (size, length, count, getSize), containers without wrap-len, wrap-ignored methods, template specializations, dual-method containers, edge cases, iteration compatibility, and consistency across multiple instances.
Build Configuration
.gitignore
Added tests/test_files/wrap_len_wrapper.pyx to ignore sections.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

  • autowrap/DeclResolver.py & autowrap/CodeGenerator.py: Core logic changes require understanding wrap-len annotation semantics and method-binding patterns; straightforward but critical to validate correct method dereferencing.
  • Test file set (wrap_len_test.hpp, wrap_len_test.pxd, test_wrap_len.py): Validates feature coverage across diverse container patterns; review should confirm all test scenarios exercise intended behavior correctly and edge cases are covered.
  • Heterogeneous changes: Mix of parsing logic, code generation, test infrastructure, and validation tests across multiple files with different concerns.

Possibly related PRs

  • PR #191: Modifies PXDParser to recognize multiline annotations starting with "wrap-", which directly enables the wrap-len annotation parsing that this PR depends on.

Suggested reviewers

  • jpfeuffer

Poem

🐰 A wrap-len tale so fine,
Where containers declare their line,
No more magic, truth is clear—
__len__ comes when wrap-len's near!
Templates, duals, all in sight,
The rabbit coded through the night!

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 47.89% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely summarizes the main change: adding len wrapping capability for C++ container classes, which aligns with the comprehensive implementation across code generation, resolver, and test files.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch claude/wrap-len-cpp-containers-SK9El

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (2)
tests/test_wrap_len.py (1)

116-121: Consider renaming unused loop variable.

The loop variable i is unused since the same item is appended repeatedly. While functionally correct, renaming to _ would clarify intent and silence the static analysis warning.

         # Add multiple items
-        for i in range(100):
+        for _ in range(100):
             container.append(b"item")
tests/test_files/wrap_len_test.hpp (1)

23-23: Consider bounds checking for robustness (optional).

The get() method accesses data_[index] without bounds checking, which could cause undefined behavior if called with an out-of-bounds index. While acceptable for test code where inputs are controlled, adding .at(index) would provide safer behavior with automatic bounds checking.

-    int get(size_t index) const { return data_[index]; }
+    int get(size_t index) const { return data_.at(index); }
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 798e8c2 and 966d0e5.

📒 Files selected for processing (6)
  • .gitignore (1 hunks)
  • autowrap/CodeGenerator.py (1 hunks)
  • autowrap/DeclResolver.py (2 hunks)
  • tests/test_files/wrap_len_test.hpp (1 hunks)
  • tests/test_files/wrap_len_test.pxd (1 hunks)
  • tests/test_wrap_len.py (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
tests/test_wrap_len.py (2)
autowrap/__init__.py (1)
  • parse_and_generate_code (106-119)
tests/test_files/wrap_len_test.hpp (14)
  • BasicContainer (13-13)
  • BasicContainer (14-18)
  • LengthContainer (31-31)
  • i (84-84)
  • i (84-84)
  • CountContainer (43-43)
  • CountContainer (44-44)
  • IgnoredSizeContainer (74-74)
  • IgnoredSizeContainer (75-79)
  • GetSizeContainer (92-92)
  • GetSizeContainer (93-93)
  • EmptyContainer (102-102)
  • DualLenContainer (124-124)
  • DualLenContainer (125-129)
autowrap/CodeGenerator.py (1)
autowrap/Code.py (1)
  • add (56-75)
🪛 Ruff (0.14.8)
tests/test_wrap_len.py

118-118: Loop control variable i not used within loop body

Rename unused i to _i

(B007)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (8)
  • GitHub Check: test (==3.1.0, 3.11)
  • GitHub Check: test (==3.2.0, 3.13)
  • GitHub Check: test (==3.1.0, 3.10)
  • GitHub Check: test (==3.2.0, 3.10)
  • GitHub Check: test (==3.2.0, 3.11)
  • GitHub Check: test (==3.1.0, 3.12)
  • GitHub Check: test (==3.2.0, 3.12)
  • GitHub Check: test (==3.1.0, 3.13)
🔇 Additional comments (9)
.gitignore (1)

176-176: LGTM!

The generated wrapper file is correctly added to the gitignore, consistent with other test wrapper files in this section.

autowrap/DeclResolver.py (1)

179-179: LGTM!

The wrap_len attribute is correctly added to ResolvedClass, following the same pattern as the existing wrap_hash attribute. The type annotation and initialization are consistent with the codebase conventions.

Also applies to: 210-210

tests/test_wrap_len.py (3)

31-45: LGTM!

The module fixture correctly uses autowrap's parse_and_generate_code and compile_and_import utilities to build the test wrapper. The fixture scope is appropriately set to "module" for efficiency.


146-154: Verify unsigned underflow behavior.

The test assumes decrement() clamps at 0 for the unsigned return type. This relies on the C++ implementation checking if (count_ > 0) before decrementing (as seen in wrap_len_test.hpp line 48). The test correctly validates this safety behavior.


276-305: LGTM - Good test for method selection.

This test validates that wrap-len correctly uses the specified method (length() which returns size * 2) rather than defaulting to size(). This is important for verifying the annotation system works as intended.

tests/test_files/wrap_len_test.hpp (1)

1-138: LGTM - Well-designed test fixtures.

The C++ container classes provide good coverage of the wrap-len scenarios:

  • Various method naming conventions (size(), length(), count(), getSize())
  • Template containers
  • Method selection between multiple candidates (DualLenContainer)
  • Edge cases (always-empty, wrap-ignored methods)

The classes are simple and focused on testing the annotation feature.

autowrap/CodeGenerator.py (1)

687-697: No changes needed. The generated __len__ method is syntactically correct. The wrap_len[0] value contains the full method call syntax including parentheses (e.g., size()), as specified in the wrap-len annotation format. The code at lines 687-697 generates correct Cython syntax: return deref(self.inst.get()).size().

Likely an incorrect or invalid review comment.

tests/test_files/wrap_len_test.pxd (2)

1-6: LGTM: File header and imports are well-structured.

The Cython language level directive and standard library imports are appropriate for this test file.


8-110: Excellent test coverage for wrap-len feature.

The test file provides comprehensive coverage of wrap-len scenarios:

  • Basic usage with size() method
  • Alternative method names (length(), count(), getSize())
  • Absence of wrap-len annotation
  • Interaction with wrap-ignore
  • Template classes
  • Multiple candidate methods

The class declarations are well-structured and the comments clearly explain the intent of each test case. The corresponding C++ header file contains proper implementations for all test classes.

@jpfeuffer
Copy link
Contributor

I thought more about wrap-vector-member.

Then Len is Automatically member.size() etc.

@jpfeuffer
Copy link
Contributor

And when you do wrap-vector-interface, it will call size on the class itself. And all the other functions, too.

@jpfeuffer
Copy link
Contributor

Annotation on class level.

Didn't think it 100% through though

@timosachsenberg
Copy link
Collaborator Author

Ok I leave this for the next autowrap update

@timosachsenberg timosachsenberg merged commit 3179c51 into master Dec 18, 2025
11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants