Skip to content

Commit 7f403b2

Browse files
Add wrap-view annotation for in-place member modification
Adds a new `wrap-view` annotation that generates companion View classes alongside regular wrapper classes. View classes provide direct access to nested C++ objects without creating copies. Key features: - `obj.view()` returns a view for in-place member modification - View properties return nested views (not copies) for wrapped classes - Methods returning `T&` (mutable reference) return views on view class - Views keep parent objects alive via Python reference chain Implementation details: - View classes use raw pointers + Python parent references (avoids Cython issues with shared_ptr aliasing across types) - Methods marked wrap-ignore that return T& are still available on view classes (T& returns don't work on main classes due to Cython limitations, but work on view classes via pointer access) - Forward references handled by pre-computing classes_with_views Includes unit tests for code generation and integration tests that compile actual Cython code and verify runtime behavior. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 6236c3d commit 7f403b2

9 files changed

Lines changed: 1401 additions & 4 deletions

File tree

autowrap/CodeGenerator.py

Lines changed: 506 additions & 3 deletions
Large diffs are not rendered by default.

autowrap/DeclResolver.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,10 @@ class is the same as the name of the C++ class.
7676
There are a few additional hints you can give to the wrapper, for classes these are:
7777
- wrap-ignore: will not create a wrapper for this class (e.g. abstract
7878
base class that needs to be known to Cython but cannot be wrapped)
79+
- wrap-view: generate a companion ${ClassName}View class that provides
80+
in-place access to members. The view class allows direct
81+
modification of nested objects without copies. Methods
82+
returning T& (mutable reference) return views instead of copies.
7983
- wrap-manual-memory: will allow the user to provide manual memory
8084
management of self.inst, therefore the class will
8185
not provide the automated __dealloc__ and inst
@@ -173,6 +177,7 @@ class ResolvedClass(object):
173177
cpp_decl: PXDParser.CppClassDecl
174178
ns: AnyStr
175179
wrap_ignore: bool
180+
wrap_view: bool
176181
no_pxd_import: bool
177182
wrap_manual_memory: Union[bool, List[AnyStr]]
178183
wrap_hash: List[AnyStr]
@@ -193,6 +198,7 @@ def __init__(self, name, methods, attributes, decl, instance_map, local_map):
193198
self.ns = get_namespace(decl.pxd_path, DEFAULT_NAMESPACE)
194199

195200
self.wrap_ignore = decl.annotations.get("wrap-ignore", False)
201+
self.wrap_view = decl.annotations.get("wrap-view", False)
196202
self.no_pxd_import = decl.annotations.get("no-pxd-import", False)
197203
self.wrap_manual_memory = decl.annotations.get("wrap-manual-memory", [])
198204
# fix previous code where we had a bool ...
@@ -560,8 +566,14 @@ def _resolve_class_decl(class_decl, typedef_mapping, i_mapping):
560566
for mname, mdcls in class_decl.methods.items():
561567
for mdcl in mdcls:
562568
ignore = mdcl.annotations.get("wrap-ignore", False)
569+
# Keep wrap-ignore methods that return mutable references (T&)
570+
# since these are useful for wrap-view generation even when
571+
# the main class method is ignored. Filter out other wrap-ignore methods.
563572
if ignore:
564-
continue
573+
result_type = mdcl.result_type
574+
if not (result_type.is_ref and not result_type.is_const):
575+
# Not a mutable reference return - skip as before
576+
continue
565577
if mdcl.name == class_decl.name:
566578
r_method = _resolve_constructor(cinst_name, mdcl, i_mapping, local_mapping)
567579
else:

docs/README.md

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,11 @@ and for methods by putting them on the same line. The currently supported
159159
directives are:
160160

161161
- `wrap-ignore`: Will not create a wrapper for this function or class (e.g. abstract base class that needs to be known to Cython but cannot be wrapped)
162+
- `wrap-view`: Generate a companion `${ClassName}View` class that provides in-place access to members. The main wrapper class returns copies (safe, but modifications don't affect the original). The view class holds a reference to the parent and allows direct modification:
163+
- Public attributes become view properties (getters return views of nested objects, setters modify in-place)
164+
- Methods returning `T&` (mutable reference) return views instead of copies
165+
- Views keep the parent object alive via shared_ptr
166+
- Use `obj.view()` on the main class to get a view instance
162167
- `wrap-iter-begin`: For begin iterators
163168
- `wrap-iter-end`: For end iterators
164169
- `wrap-attach`: Attach to a specific class (can be used for static functions or nested classes)
@@ -280,6 +285,34 @@ namespace std {
280285
}
281286
```
282287
288+
#### wrap-view Example
289+
290+
The `wrap-view` directive generates a companion view class for in-place member modification:
291+
292+
```cython
293+
cdef cppclass Inner:
294+
# wrap-view
295+
int value
296+
297+
cdef cppclass Outer:
298+
# wrap-view
299+
Inner & getInner() # Mutable reference getter
300+
Inner inner_member # Public attribute
301+
```
302+
303+
This generates `Inner`, `InnerView`, `Outer`, and `OuterView` classes. Usage:
304+
305+
```python
306+
>>> outer = Outer()
307+
>>> outer.inner_member.value = 42 # Modifies a COPY - original unchanged!
308+
>>> view = outer.view()
309+
>>> view.inner_member.value = 42 # Modifies IN-PLACE - original changed!
310+
>>> inner_view = view.getInner() # Returns InnerView, not copy
311+
>>> inner_view.value = 100 # In-place modification
312+
```
313+
314+
The view keeps the parent alive - you can safely store and use views as long as needed.
315+
283316
### Docstrings
284317

285318
Docstrings can be added to classes and methods using the `wrap-doc` statement. Multi line docs are supported with

0 commit comments

Comments
 (0)