Skip to content

Conversation

@Ghelfi
Copy link

@Ghelfi Ghelfi commented Jan 7, 2026

Description

Add support for @property in warp.struct.
Allows user to define property getters in the warp.struct. The properties are converted into warp functions at the codegen step.

Closes #1152

Before your PR is "Ready for review"

  • All commits are signed-off to indicate that your contribution adheres to the Developer Certificate of Origin requirements
  • Necessary tests have been added
  • Documentation is up-to-date
  • Auto-generated files modified by compiling Warp and building the documentation have been updated (e.g. __init__.pyi, docs/api_reference/, docs/language_reference/)
  • Code passes formatting and linting checks with pre-commit run -a

Summary by CodeRabbit

  • New Features

    • Read-only properties on structs can be defined and accessed as first-class members.
  • Bug Fixes / Behavior

    • Struct attribute reads now dispatch to property implementations instead of direct field access.
    • Setters/deleters for struct properties are disallowed and will raise an error.
  • Tests

    • Added tests validating property reads on structs and rejecting property setters.

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

@copy-pr-bot
Copy link

copy-pr-bot bot commented Jan 7, 2026

This pull request requires additional validation before any workflows can run on NVIDIA's runners.

Pull request vetters can view their responsibilities here.

Contributors can view more details about this message here.

@coderabbitai
Copy link

coderabbitai bot commented Jan 7, 2026

📝 Walkthrough

Walkthrough

Collects Python @property definitions on Warp structs, defers creating per-property Warp Functions until after native_name is assigned, includes property names in struct hashing, and routes struct attribute reads to the generated property Functions. Adds tests for read-only properties and rejects setters.

Changes

Cohort / File(s) Summary
Struct codegen & type changes
warp/_src/codegen.py
Add properties: dict[str, warp._src.context.Function] to Struct and StructType. Collect @property metadata during construction, include property names in struct hashing, postpone instantiation of per-property Warp Function objects until after native_name resolution, populate properties, and make attribute read codegen dispatch property accesses to the corresponding Function (emit an add_call) instead of direct field access.
Tests for struct properties
warp/tests/test_struct.py
Add StructWithProperty with a read-only neg_value property; add kernel kernel_struct_property reading the property; add test_struct_property and test_struct_property_with_setter (the latter asserts defining a setter raises TypeError); register both tests.

Sequence Diagram(s)

sequenceDiagram
    autonumber
    actor Kernel as "Kernel (source)"
    participant Codegen as "Codegen / emit_Attribute"
    participant StructType as "StructType / Struct"
    participant PropFunc as "Generated Property Function"
    participant Runtime as "Runtime / Native Function"

    Kernel->>Codegen: emit attribute read for `s.neg_value`
    Note right of Codegen: detect aggregate is Struct\nlookup `neg_value` in StructType.properties
    Codegen->>PropFunc: emit add_call, bind self to struct instance
    PropFunc->>Runtime: invoke native property getter
    Runtime-->>PropFunc: return value
    PropFunc-->>Codegen: provide value expression
    Codegen-->>Kernel: inline call result into kernel code
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: adding support for @Property getters in warp.struct, which is the primary objective of the pull request.
Linked Issues check ✅ Passed The changes implement all coding requirements from issue #1152: property getter support, lowering to warp.Functions with struct instance as self, and dot-notation access in kernels.
Out of Scope Changes check ✅ Passed All changes in codegen.py and test_struct.py are directly scoped to implementing @Property support for warp.struct as specified in the linked issue.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

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.

for name, item in property_members:
# We currently support only getters
if item.fset is not None:
raise TypeError("Struct properties with setters are not supported")
Copy link
Author

Choose a reason for hiding this comment

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

For now, only getters are supported. Setters should be doable too.

Copy link

@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 (1)
warp/tests/test_struct.py (1)

912-921: Add backward pass test for property differentiation.

The test validates forward pass execution but doesn't verify that properties work correctly in the backward pass. Since properties are converted to Warp functions, they should be differentiable. Consider adding a gradient test to ensure d(neg_value)/d(value) = -1:

Suggested additional test
def test_struct_property_grad(test, device):
    s = StructWithProperty()
    s.value = wp.array([42.0], dtype=float, device=device, requires_grad=True)
    
    out = wp.zeros(1, dtype=float, device=device, requires_grad=True)
    
    tape = wp.Tape()
    with tape:
        @wp.kernel
        def grad_test_kernel(s: StructWithProperty, out: wp.array(dtype=float)):
            out[0] = s.neg_value
        
        wp.launch(grad_test_kernel, dim=1, inputs=[s, out], device=device)
    
    tape.backward(loss=out)
    
    # d(neg_value)/d(value) should be -1
    assert_np_equal(s.value.grad.numpy(), np.array([-1.0]))

Note: This assumes the property can access array fields. If that's not supported yet, use a simpler struct with scalar fields, or defer this test if array field access in properties is out of scope.

📜 Review details

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 776dd2e and 5381b3d.

📒 Files selected for processing (2)
  • warp/_src/codegen.py
  • warp/tests/test_struct.py
🧰 Additional context used
🧬 Code graph analysis (2)
warp/_src/codegen.py (1)
warp/_src/context.py (3)
  • context (3470-3485)
  • Function (132-528)
  • func (862-905)
warp/tests/test_struct.py (1)
warp/_src/context.py (4)
  • struct (1320-1342)
  • kernel (1174-1316)
  • launch (6594-6635)
  • launch (6638-6877)
🪛 Ruff (0.14.10)
warp/_src/codegen.py

526-526: Avoid specifying long messages outside the exception class

(TRY003)


528-528: Avoid specifying long messages outside the exception class

(TRY003)

warp/tests/test_struct.py

912-912: Unused function argument: test

(ARG001)


920-920: assert_np_equal may be undefined, or defined from star imports

(F405)


923-923: add_function_test may be undefined, or defined from star imports

(F405)

🔇 Additional comments (8)
warp/tests/test_struct.py (3)

898-905: LGTM! Clean property definition.

The struct definition is straightforward and serves as a good test case for the property feature. The property correctly uses the @property decorator and returns a derived value from the struct field.


907-910: LGTM! Property access test.

The kernel correctly demonstrates property access via dot notation (s.neg_value), which is the key feature being tested.


923-923: LGTM! Standard test registration.

The test registration follows the established pattern in this file. The static analysis warning about add_function_test being undefined is a false positive due to the star import from warp.tests.unittest_utils on line 24.

warp/_src/codegen.py (5)

463-463: LGTM! Properties dictionary added.

The initialization of self.properties follows the same pattern as self.vars and includes a helpful type hint.


486-490: LGTM! Property metadata collection.

The early collection of property members with deferred Function creation is a sound design. The comment clearly explains why Function creation is postponed until after native_name is computed.


512-515: Property names added to hash.

Including property names in the struct hash ensures module rebuilds when property names change. The property implementations will be hashed separately via the Function class mechanism, so implementation changes should also trigger rebuilds.


521-555: Property Function creation logic is sound.

The implementation correctly:

  • Validates that only getters are supported (no setters/deleters)
  • Adds type annotations for the self argument to enable overload resolution
  • Creates unique Function objects with predictable C++ names ({struct_native_name}_{property_name})
  • Handles edge cases like missing __annotations__ attribute

The static analysis warnings about long error messages (lines 526, 528) are minor style suggestions that don't affect functionality.


2309-2313: LGTM! Property access code generation.

The property access is cleanly integrated into the existing attribute resolution logic. Calling the property Function via add_call ensures that both forward and adjoint code are generated correctly, enabling automatic differentiation through properties.

Copy link

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

Greptile Overview

Greptile Summary

Added support for @property getters in warp.struct classes, allowing computed properties to be accessed within Warp kernels by converting property getters into Warp functions at codegen time.

Key changes:

  • Added properties dict to Struct class to store property-to-Function mappings
  • Property collection using inspect.getmembers() during struct initialization
  • Property names included in struct hash for identity tracking
  • Property getters converted to Warp Functions with struct instance as first argument
  • Property access handled in Adjoint.emit_Attribute() before regular field access
  • Added validation to reject properties with setters or deleters
  • Test coverage for basic property getter functionality

Minor issues:

  • Misleading comment about hash stability at line 512
  • Missing validation for edge case where property getter has no arguments

Confidence Score: 4/5

  • This PR is safe to merge with minor documentation improvements recommended
  • The implementation is solid and well-structured, with proper validation for setters/deleters and correct integration into the attribute resolution pipeline. Test coverage demonstrates basic functionality. Minor issues include a misleading comment and a missing edge case validation, neither of which would cause runtime errors in normal usage.
  • No files require special attention - the implementation is straightforward and well-integrated

Important Files Changed

File Analysis

Filename Score Overview
warp/_src/codegen.py 4/5 Adds @property support for warp.struct by collecting properties during struct initialization, converting them to Warp Functions, and handling property access in attribute resolution
warp/tests/test_struct.py 5/5 Adds basic test for property getter functionality with a simple struct that has a neg_value property returning the negated value

Sequence Diagram

sequenceDiagram
    participant User
    participant Decorator as @wp.struct
    participant Struct as Struct.__init__
    participant InspectModule as inspect.getmembers
    participant FunctionClass as Function
    participant Adjoint as Adjoint.emit_Attribute

    User->>Decorator: Define struct with @property
    Decorator->>Struct: Create Struct instance
    Struct->>Struct: Collect field annotations
    Struct->>InspectModule: getmembers(cls)
    InspectModule-->>Struct: Return all members including properties
    Struct->>Struct: Filter for property instances
    Struct->>Struct: Compute hash (includes property names)
    Struct->>Struct: Set native_name
    loop For each property
        Struct->>Struct: Validate no setter/deleter
        Struct->>Struct: Add self annotation to getter
        Struct->>FunctionClass: Create Function from getter
        FunctionClass-->>Struct: Return Function instance
        Struct->>Struct: Set native_func name
        Struct->>Struct: Store in properties dict
    end
    
    Note over User,Adjoint: Later: During kernel compilation
    User->>Adjoint: Access struct.property in kernel
    Adjoint->>Adjoint: Check if attr is property
    alt Is property
        Adjoint->>Adjoint: add_call(prop, (aggregate,), {}, {})
        Adjoint-->>User: Return property value
    else Is regular field
        Adjoint->>Adjoint: Access field normally
        Adjoint-->>User: Return field value
    end
Loading

@greptile-apps
Copy link

greptile-apps bot commented Jan 7, 2026

Additional Comments (2)

warp/_src/codegen.py
The comment says "to ensure layout/identity stability if names change", but including property names in the hash means the hash WILL change if property names change. Consider clarifying this comment to say "to ensure hash changes when property names change" or similar.


warp/_src/codegen.py
Check that the property getter has at least one argument. If len(argspec.args) == 0, the self annotation won't be added, which could cause issues during function resolution.

Copy link

@antoinebrl antoinebrl left a comment

Choose a reason for hiding this comment

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

Would it make sense to augment the documentation to illustrate this new feature?

@greptile-apps
Copy link

greptile-apps bot commented Jan 7, 2026

Greptile Overview

Greptile Summary

This PR adds support for @property getters in warp.struct definitions, converting them to warp functions at codegen time. The implementation collects properties via inspect.getmembers, creates corresponding Function objects with the struct instance as the first argument, and dispatches property access through the existing attribute handling mechanism.

Key Implementation Details:

  • Properties are collected during Struct initialization (line 486-490)
  • Property names are included in the struct hash computation (line 512-514)
  • Each property getter is wrapped in a Function with self annotation added (line 536-539)
  • Attribute access checks properties before fields (line 2309-2312)
  • Properties with setters or deleters are explicitly rejected (line 525-528)
  • Properties are only accessible within kernels, not in regular Python code outside kernels

Issues Found:

  1. Missing validation for property getters without arguments - If a property has no self parameter, the code silently skips annotation, leading to function resolution failures
  2. No validation for property-field name collisions - Properties shadow fields with the same name, making fields inaccessible
  3. Direct mutation of getter annotations - Modifying getter.__annotations__ directly can cause issues if the struct is instantiated multiple times
  4. Incomplete test coverage - Tests don't cover edge cases like property-field collisions, assignment attempts, or getters without arguments

Architecture Considerations:
The implementation follows the existing pattern of converting Python constructs to warp functions, which is consistent with the codebase design. Properties are read-only by design (no setters), which aligns with typical GPU programming constraints.

Confidence Score: 3/5

  • This PR has several logic issues that could cause runtime errors or confusing behavior, but the core functionality is sound
  • Score of 3 reflects three significant logic issues found: (1) missing validation for property getters without arguments leads to unclear errors, (2) no check for property-field name collisions causes field shadowing, and (3) direct mutation of getter annotations could cause issues with multiple instantiations. While the core implementation is architecturally sound and follows existing patterns, these validation gaps and the incomplete test coverage prevent a higher score. The issues are not critical security vulnerabilities but will cause confusing runtime errors for users in edge cases.
  • Pay close attention to warp/_src/codegen.py, specifically the property collection and validation logic (lines 486-555) where the three logic issues exist

Important Files Changed

File Analysis

Filename Score Overview
warp/_src/codegen.py 3/5 Added property support to Struct class with property collection, Function creation, and attribute dispatch. Three logic issues found: missing validation for getters without arguments, no check for property-field name collisions, and direct mutation of getter annotations.
warp/tests/test_struct.py 4/5 Added basic tests for property getter and setter rejection. Tests are functional but missing coverage for edge cases like property-field collisions, getters without arguments, and property assignment in kernels.

Sequence Diagram

sequenceDiagram
    participant User as User Code
    participant Struct as @wp.struct Decorator
    participant Codegen as Struct.__init__
    participant Kernel as @wp.kernel
    participant Adjoint as emit_Attribute

    User->>Struct: Define struct with @property
    Struct->>Codegen: Create Struct instance
    
    Codegen->>Codegen: Collect fields from annotations
    Codegen->>Codegen: inspect.getmembers(cls)
    Codegen->>Codegen: Filter for property objects
    
    Note over Codegen: Validate property (no setter/deleter)
    
    Codegen->>Codegen: get_full_arg_spec(getter)
    Codegen->>Codegen: Add self annotation to getter
    Codegen->>Codegen: Create Function from getter
    Codegen->>Codegen: Store in self.properties[name]
    
    User->>Kernel: Launch kernel with struct
    Kernel->>Adjoint: Access s.property_name
    
    Adjoint->>Adjoint: Check if attr in aggregate_type.properties
    Adjoint->>Adjoint: add_call(prop, (aggregate,), {}, {})
    
    Note over Adjoint: Property getter invoked as function<br/>with struct instance as argument
    
    Adjoint-->>Kernel: Return property value
    Kernel-->>User: Kernel execution complete
Loading

Copy link

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

Additional Comments (3)

  1. warp/_src/codegen.py, line 532-539 (link)

    logic: Mutating getter.__annotations__ directly modifies the original function's annotations, which could cause issues if the same property getter is used in multiple structs or accessed outside the struct context. Should create a copy of annotations before modifying.

  2. warp/_src/codegen.py, line 536-539 (link)

    logic: Property getters without a self parameter will silently fail to get proper type annotations, leading to confusing errors during Function creation. Should validate that property getters have at least one parameter.

  3. warp/tests/test_struct.py, line 899-924 (link)

    style: Test coverage is minimal - only validates basic property getter. Missing tests for:

    • Multiple properties in same struct
    • Properties with complex types (structs, arrays)
    • Error cases (setter/deleter rejection)
    • Property calling other properties
    • Properties with no return type annotation

    Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

2 files reviewed, 3 comments

Edit Code Review Agent Settings | Greptile

Copy link

@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 (1)
warp/_src/codegen.py (1)

521-555: Property Function creation is well-structured.

The implementation correctly:

  1. Validates that only getters are supported (no setters/deleters)
  2. Injects the struct type as the self parameter annotation
  3. Creates a unique native function name based on struct and property names

One consideration: modifying getter.__annotations__ directly at line 539 mutates the original function's annotations. If the same getter function is reused elsewhere, this could cause unexpected behavior. Consider working with a copy:

♻️ Optional: Avoid mutating original function annotations
             # Find the name of the first argument (conventionally 'self')
             argspec = inspect.getfullargspec(getter)
             if len(argspec.args) > 0:
                 self_arg = argspec.args[0]
-                getter.__annotations__[self_arg] = self
+                # Use a copy to avoid mutating the original function's annotations
+                if not hasattr(getter, "__annotations__"):
+                    getter.__annotations__ = {}
+                else:
+                    getter.__annotations__ = getter.__annotations__.copy()
+                getter.__annotations__[self_arg] = self
📜 Review details

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5381b3d and c9f80cc.

📒 Files selected for processing (2)
  • warp/_src/codegen.py
  • warp/tests/test_struct.py
🧰 Additional context used
🧬 Code graph analysis (1)
warp/tests/test_struct.py (1)
warp/_src/context.py (4)
  • struct (1320-1342)
  • kernel (1174-1316)
  • launch (6594-6635)
  • launch (6638-6877)
🪛 Ruff (0.14.10)
warp/_src/codegen.py

526-526: Avoid specifying long messages outside the exception class

(TRY003)


528-528: Avoid specifying long messages outside the exception class

(TRY003)

warp/tests/test_struct.py

913-913: Unused function argument: test

(ARG001)


921-921: assert_np_equal may be undefined, or defined from star imports

(F405)


924-924: Unused function argument: device

(ARG001)


940-940: add_function_test may be undefined, or defined from star imports

(F405)


941-941: add_function_test may be undefined, or defined from star imports

(F405)

⏰ 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). (1)
  • GitHub Check: Greptile Review
🔇 Additional comments (8)
warp/tests/test_struct.py (4)

899-910: Well-structured test struct and kernel for property access.

The struct definition with the @property decorator and the corresponding kernel that reads s.neg_value correctly exercise the new property getter functionality.


913-921: Test correctly validates property getter in kernel.

The test instantiates the struct, sets a value, launches the kernel that reads the property, and validates the output. This provides good coverage for the basic property getter flow.


924-937: Good addition of setter rejection test.

This test validates that attempting to define a property with a setter raises an appropriate TypeError with a clear message. This addresses the previous review feedback about testing setter rejection.


940-941: Test registrations look correct.

Both new tests are properly registered with add_function_test and include the devices parameter for cross-device testing.

warp/_src/codegen.py (4)

463-463: Correct initialization of properties dictionary.

The properties dict is properly initialized as empty and will be populated after native_name is computed, following the deferred initialization pattern described in the AI summary.


486-490: Property collection correctly defers Function creation.

Collecting property metadata early (before hash computation) while deferring Function creation until after native_name is set is the right approach. This ensures property names are included in the hash while avoiding issues with forward references.


512-515: Property names correctly included in struct hash.

Hashing property names ensures the struct identity changes if properties are renamed. Note that changes to property getter implementation won't affect the hash, which is consistent with how struct field changes are handled.


2309-2312: Property dispatch in emit_Attribute correctly handles property access.

The implementation properly checks if the accessed attribute is a property on the struct and dispatches to the corresponding Function via add_call. This integrates cleanly with the existing attribute access logic.

Copy link

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

Additional Comments (1)

  1. warp/tests/test_struct.py, line 925 (link)

    syntax: error message regex doesn't match actual error in codegen.py:526

2 files reviewed, 1 comment

Edit Code Review Agent Settings | Greptile

Copy link

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

Greptile Overview

Greptile Summary

Adds @property getter support for warp.struct, converting properties into warp functions at codegen. Properties are collected via inspect.getmembers(), validated (rejecting setters/deleters), and stored as Function objects. During attribute access, properties are dispatched through add_call() instead of direct field access.

Key Changes:

  • Added Struct.properties dict to track property Functions
  • Property names hashed into struct identity (native_name)
  • Property access handled in emit_Attribute() via function call dispatch
  • Validation rejects setters and deleters at struct definition time

Issues Found:

  • Test error message regex doesn't match implementation ("do not support" vs "with setters are not supported")
  • Uses inspect.getfullargspec() instead of get_full_arg_spec() helper, risking stringized annotation issues
  • Only property names hashed, not return types (inconsistent with field hashing)
  • No test coverage for property assignment attempts inside kernels

Confidence Score: 3/5

  • This PR has implementation issues that need fixing before merge
  • Score reflects one critical test failure (regex mismatch will cause test to fail), plus several code quality and consistency issues. The core feature implementation is sound, but the test bug must be fixed.
  • Pay close attention to the test regex fix in warp/tests/test_struct.py and consider the annotation handling improvement in warp/_src/codegen.py

Important Files Changed

File Analysis

Filename Score Overview
warp/_src/codegen.py 3/5 Added property support for warp structs; found issues with error message mismatch, potential annotation handling bug, and missing assignment validation
warp/tests/test_struct.py 3/5 Added basic property tests; error message regex doesn't match actual error message in implementation

Sequence Diagram

sequenceDiagram
    participant User
    participant Decorator as @wp.struct
    participant Struct as Struct.__init__
    participant inspect
    participant Function
    participant Kernel as @wp.kernel
    participant Adjoint as emit_Attribute

    User->>Decorator: Define struct with @property
    Decorator->>Struct: Create Struct instance
    Struct->>inspect: getmembers(cls)
    inspect-->>Struct: property_members list
    Struct->>Struct: Hash fields and property names
    Struct->>Struct: Set native_name
    loop For each property
        Struct->>inspect: getfullargspec(getter)
        Struct->>Struct: Add self annotation
        Struct->>Function: Create Function(getter)
        Function-->>Struct: Property function
        Struct->>Struct: Store in self.properties
    end
    
    User->>Kernel: Access struct.property
    Kernel->>Adjoint: emit_Attribute(node)
    Adjoint->>Adjoint: Check if attr in properties
    Adjoint->>Adjoint: add_call(prop_func, args)
    Adjoint-->>Kernel: Return property value
Loading

@greptile-apps
Copy link

greptile-apps bot commented Jan 7, 2026

Additional Comments (4)

warp/tests/test_struct.py
error message regex doesn't match actual implementation. Code raises "Struct properties with setters are not supported" (line 526 in codegen.py) but test expects "Struct properties do not support setters"

    with test.assertRaisesRegex(TypeError, "Struct properties with setters are not supported"):

warp/_src/codegen.py
use get_full_arg_spec() helper instead of inspect.getfullargspec() to handle stringized annotations properly (consistent with rest of codebase, e.g. line 1084)

            argspec = get_full_arg_spec(getter)

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!


warp/tests/test_struct.py
consider adding test for property assignment inside kernel (e.g., s.neg_value = 10 in @wp.kernel) to verify proper error handling when users try to assign to read-only properties


warp/_src/codegen.py
consider hashing property return types in addition to names for consistency with field hashing (line 505) and to ensure struct identity changes when property signatures change

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Copy link

@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)
warp/tests/test_struct.py (2)

925-939: LGTM! Test correctly validates setter rejection.

The test properly verifies that defining a property with a setter raises a TypeError with an appropriate message. This addresses the past review feedback requesting this validation.

Optional: Consider removing unused device parameter.

The device parameter on line 925 is not used in this test since it only validates struct definition, not kernel execution. While keeping it maintains signature consistency with other test functions, removing it would eliminate the static analysis warning.

♻️ Optional refactor to remove unused parameter
-def test_struct_property_with_setter(test, device):
+def test_struct_property_with_setter(test, _device):
     """Tests that structs with properties (setters) are not supported."""

or update the test registration to pass devices=None:

-add_function_test(TestStruct, "test_struct_property_with_setter", test_struct_property_with_setter, devices=devices)
+add_function_test(TestStruct, "test_struct_property_with_setter", test_struct_property_with_setter, devices=None)

899-943: Optional: Consider expanding test coverage for edge cases.

While the current tests adequately cover the basic property functionality, you might consider adding tests for:

  • Multiple properties on a single struct
  • Properties that reference multiple fields
  • Properties on nested structs
  • Property with a deleter (to verify it's handled correctly or rejected)

These are nice-to-have improvements rather than blockers.

📜 Review details

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3f297db and 1a37b6e.

📒 Files selected for processing (1)
  • warp/tests/test_struct.py
🧰 Additional context used
🧬 Code graph analysis (1)
warp/tests/test_struct.py (1)
warp/_src/context.py (1)
  • struct (1320-1342)
🪛 Ruff (0.14.10)
warp/tests/test_struct.py

913-913: Unused function argument: test

(ARG001)


922-922: assert_np_equal may be undefined, or defined from star imports

(F405)


925-925: Unused function argument: device

(ARG001)


942-942: add_function_test may be undefined, or defined from star imports

(F405)


943-943: add_function_test may be undefined, or defined from star imports

(F405)

🔇 Additional comments (4)
warp/tests/test_struct.py (4)

899-906: LGTM! Clean test struct with read-only property.

The StructWithProperty definition is well-structured with a simple property getter that returns the negation of the value field. This provides a clear test case for the property feature.


908-910: LGTM! Kernel correctly demonstrates property access.

The kernel properly accesses the struct property using dot-notation (s.neg_value), which aligns with the PR objectives for enabling property access inside kernels.


913-922: LGTM! Test correctly verifies property getter functionality.

The test properly validates that property getters work end-to-end: it sets a value, launches a kernel that reads the property, and asserts the correct negated result.


942-943: LGTM! Test registrations are correct.

Both tests are properly registered following the standard pattern used throughout the file.

@greptile-apps
Copy link

greptile-apps bot commented Jan 7, 2026

Greptile found no issues!

From now on, if a review finishes and we haven't found any issues, we will not post anything, but you can confirm that we reviewed your changes in the status check section.

This feature can be toggled off in your Code Review Settings by deselecting "Create a status check for each PR".

Copy link

@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 (1)
warp/_src/codegen.py (1)

532-539: Consider copying annotations before mutation.

The code modifies getter.__annotations__ directly, which mutates the original function's annotations. While property getters on Warp structs are unlikely to be reused elsewhere, defensively copying the annotations dict before modification would be safer.

♻️ Suggested fix
             # We need to add 'self' as the first argument, with the type of the struct itself.
             # This allows overload resolution to match the struct instance to the 'self' argument.
-            if not hasattr(getter, "__annotations__"):
-                getter.__annotations__ = {}
+            # Copy annotations to avoid mutating the original function
+            annotations = dict(getattr(getter, "__annotations__", {}))

             # Find the name of the first argument (conventionally 'self')
             argspec = get_full_arg_spec(getter)
             if len(argspec.args) > 0:
                 self_arg = argspec.args[0]
-                getter.__annotations__[self_arg] = self
+                annotations[self_arg] = self
+            getter.__annotations__ = annotations
📜 Review details

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1a37b6e and 6168508.

📒 Files selected for processing (2)
  • warp/_src/codegen.py
  • warp/tests/test_struct.py
🧰 Additional context used
🧬 Code graph analysis (1)
warp/tests/test_struct.py (2)
warp/_src/context.py (4)
  • struct (1320-1342)
  • kernel (1174-1316)
  • launch (6594-6635)
  • launch (6638-6877)
warp/tests/unittest_utils.py (1)
  • add_function_test (282-301)
🪛 Ruff (0.14.10)
warp/tests/test_struct.py

913-913: Unused function argument: test

(ARG001)


922-922: assert_np_equal may be undefined, or defined from star imports

(F405)


925-925: Unused function argument: device

(ARG001)


942-942: add_function_test may be undefined, or defined from star imports

(F405)


943-943: add_function_test may be undefined, or defined from star imports

(F405)

warp/_src/codegen.py

526-526: Avoid specifying long messages outside the exception class

(TRY003)


528-528: Avoid specifying long messages outside the exception class

(TRY003)

⏰ 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). (1)
  • GitHub Check: Greptile Review
🔇 Additional comments (8)
warp/tests/test_struct.py (3)

899-910: LGTM!

The struct definition with a read-only property and the corresponding kernel that accesses s.neg_value are correctly implemented for testing the new property feature.


913-922: LGTM!

The test correctly validates that struct properties (getters) work as expected by verifying the computed negative value.


925-943: LGTM!

The test correctly validates that struct properties with setters raise a TypeError, addressing the previous review feedback. The unused device parameter is consistent with similar test patterns in this file (e.g., test_struct_inheritance_error).

warp/_src/codegen.py (5)

463-463: LGTM!

Proper initialization of the properties dictionary to store generated Warp Functions for struct property getters.


486-490: LGTM!

Using inspect.getmembers to collect properties is appropriate. Deferring Function creation until after native_name is computed is the correct approach since the Function's native name depends on it.


512-515: LGTM!

Including property names in the hash ensures that adding, removing, or renaming properties invalidates the module hash, triggering recompilation as expected.


545-555: LGTM!

The Function creation correctly:

  • Uses the property getter as the source function for type inference.
  • Sets a unique key {struct.key}.{property_name}.
  • Uses an empty namespace to generate a free function.
  • Assigns a predictable native function name {native_name}_{property_name}.

2309-2312: LGTM!

The attribute dispatch logic correctly intercepts property access on struct types and routes to the generated property Function via add_call, seamlessly integrating with existing codegen infrastructure.

Copy link

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

2 files reviewed, 2 comments

Edit Code Review Agent Settings | Greptile

Comment on lines +537 to +539
if len(argspec.args) > 0:
self_arg = argspec.args[0]
getter.__annotations__[self_arg] = self
Copy link

Choose a reason for hiding this comment

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

logic: If property getter has no arguments, self annotation won't be added but Function will still be created, potentially causing issues during code generation

Suggested change
if len(argspec.args) > 0:
self_arg = argspec.args[0]
getter.__annotations__[self_arg] = self
if len(argspec.args) == 0:
raise TypeError(f"Property getter '{name}' must have at least one argument (self)")
self_arg = argspec.args[0]
getter.__annotations__[self_arg] = self

Comment on lines +488 to +490
for name, item in inspect.getmembers(self.cls):
if isinstance(item, property):
property_members.append((name, item))
Copy link

Choose a reason for hiding this comment

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

logic: No validation prevents properties from having the same name as struct fields, which could cause ambiguity since properties are checked before vars in attribute access (line 2309)

Suggested change
for name, item in inspect.getmembers(self.cls):
if isinstance(item, property):
property_members.append((name, item))
for name, item in inspect.getmembers(self.cls):
if isinstance(item, property):
if name in self.vars:
raise TypeError(f"Property '{name}' conflicts with struct field of the same name")
property_members.append((name, item))

@shi-eric shi-eric requested a review from nvlukasz January 7, 2026 19:50
Copy link

@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)
warp/tests/test_struct.py (1)

913-923: Minor: Unused test parameter.

The test logic is correct and properly verifies that property getters work in kernels. However, the test parameter is unused in this function (though it may be required by the add_function_test framework signature).

If the framework allows it, consider removing the parameter:

-def test_struct_property(test, device):
+def test_struct_property(device):

Otherwise, you can prefix it with an underscore to indicate it's intentionally unused: _test.

warp/_src/codegen.py (1)

521-556: Property Function creation is correct with minor style suggestion.

The implementation properly:

  • Defers Function creation until after native_name is available (needed for unique native_func naming)
  • Correctly rejects setters and deleters with appropriate error messages
  • Annotates the getter's first argument with the Struct type for proper overload resolution
  • Creates Functions with unique native_func names based on the struct's native_name

The error messages on lines 526 and 528 could be extracted to constants to follow best practices, but this is a minor style issue.

♻️ Optional: Extract error messages to constants

To follow TRY003 guidelines, consider extracting the error messages:

+# At module level
+_STRUCT_PROPERTY_SETTER_ERROR = "Struct properties with setters are not supported"
+_STRUCT_PROPERTY_DELETER_ERROR = "Struct properties with deleters are not supported"
+
 # In Struct.__init__:
             if item.fset is not None:
-                raise TypeError("Struct properties with setters are not supported")
+                raise TypeError(_STRUCT_PROPERTY_SETTER_ERROR)
             if item.fdel is not None:
-                raise TypeError("Struct properties with deleters are not supported")
+                raise TypeError(_STRUCT_PROPERTY_DELETER_ERROR)
📜 Review details

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6168508 and 6c226e1.

📒 Files selected for processing (2)
  • warp/_src/codegen.py
  • warp/tests/test_struct.py
🧰 Additional context used
🧬 Code graph analysis (2)
warp/tests/test_struct.py (1)
warp/_src/context.py (4)
  • struct (1320-1342)
  • kernel (1174-1316)
  • launch (6594-6635)
  • launch (6638-6877)
warp/_src/codegen.py (1)
warp/_src/context.py (3)
  • context (3470-3485)
  • Function (132-528)
  • func (862-905)
🪛 Ruff (0.14.10)
warp/tests/test_struct.py

913-913: Unused function argument: test

(ARG001)


922-922: assert_np_equal may be undefined, or defined from star imports

(F405)


925-925: Unused function argument: device

(ARG001)


942-942: add_function_test may be undefined, or defined from star imports

(F405)


943-943: add_function_test may be undefined, or defined from star imports

(F405)

warp/_src/codegen.py

526-526: Avoid specifying long messages outside the exception class

(TRY003)


528-528: Avoid specifying long messages outside the exception class

(TRY003)

🔇 Additional comments (6)
warp/tests/test_struct.py (3)

899-906: LGTM! Clean test struct definition.

The StructWithProperty provides a clear test case for read-only property getters on structs.


908-911: LGTM! Correct kernel implementation.

The kernel properly accesses the struct property and demonstrates the expected usage pattern.


925-943: Test logic is correct.

The test properly verifies that struct properties with setters are rejected with the appropriate error message. The unused device parameter (line 925) is likely required by the add_function_test framework signature, since the test is registered with devices=devices (line 943).

warp/_src/codegen.py (3)

463-463: LGTM! Property storage correctly initialized.

The properties dictionary is properly initialized to store the mapping from property names to their corresponding Warp Functions.


486-516: LGTM! Property collection and hashing correctly implemented.

The implementation properly:

  • Collects property metadata early using inspect.getmembers
  • Includes property names in the struct hash to ensure identity stability
  • Computes the hash before native_name, which depends on it

This ensures that adding, removing, or renaming properties will result in a different struct hash, which is the correct behavior.


2309-2313: LGTM! Property attribute dispatch correctly implemented.

The code properly routes struct property reads to the corresponding Warp Functions:

  • Checks that the aggregate is a Struct and the attribute is in its properties
  • Uses add_call to invoke the property Function with the struct instance as the first argument
  • This integrates seamlessly with the existing attribute resolution flow

Copy link

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

4 files reviewed, 4 comments

Edit Code Review Agent Settings | Greptile

Comment on lines +536 to +539
argspec = get_full_arg_spec(getter)
if len(argspec.args) > 0:
self_arg = argspec.args[0]
getter.__annotations__[self_arg] = self
Copy link

Choose a reason for hiding this comment

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

Missing validation for property getters without arguments. If a property getter has no arguments (e.g., @property\ndef prop() -> float: return 1.0), the code silently skips adding the self annotation, which will cause issues during function resolution when the property is accessed.

Consider adding validation:

Suggested change
argspec = get_full_arg_spec(getter)
if len(argspec.args) > 0:
self_arg = argspec.args[0]
getter.__annotations__[self_arg] = self
# Find the name of the first argument (conventionally 'self')
argspec = get_full_arg_spec(getter)
if len(argspec.args) == 0:
raise TypeError(f"Struct property '{name}' must have at least one argument (self)")
if len(argspec.args) > 0:
self_arg = argspec.args[0]
getter.__annotations__[self_arg] = self

Comment on lines +486 to +490
# Collect properties, but postpone Function creation until after native_name is set
property_members = []
for name, item in inspect.getmembers(self.cls):
if isinstance(item, property):
property_members.append((name, item))
Copy link

Choose a reason for hiding this comment

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

No validation prevents property names from colliding with field names. Since property access is checked before field access (line 2309-2312), a property with the same name as a field will shadow the field, making the field inaccessible. This could lead to confusing behavior.

Consider adding a validation check:

Suggested change
# Collect properties, but postpone Function creation until after native_name is set
property_members = []
for name, item in inspect.getmembers(self.cls):
if isinstance(item, property):
property_members.append((name, item))
# Collect properties, but postpone Function creation until after native_name is set
property_members = []
for name, item in inspect.getmembers(self.cls):
if isinstance(item, property):
if name in self.vars:
raise TypeError(f"Property '{name}' conflicts with field name in struct '{self.key}'")
property_members.append((name, item))

Comment on lines +532 to +539
if not hasattr(getter, "__annotations__"):
getter.__annotations__ = {}

# Find the name of the first argument (conventionally 'self')
argspec = get_full_arg_spec(getter)
if len(argspec.args) > 0:
self_arg = argspec.args[0]
getter.__annotations__[self_arg] = self
Copy link

Choose a reason for hiding this comment

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

Directly mutating the original getter function's __annotations__ dictionary is problematic. If the struct class is instantiated multiple times or the getter function is used elsewhere, this mutation will affect all instances and could cause unexpected behavior or conflicts.

Consider copying the annotations dictionary instead:

Suggested change
if not hasattr(getter, "__annotations__"):
getter.__annotations__ = {}
# Find the name of the first argument (conventionally 'self')
argspec = get_full_arg_spec(getter)
if len(argspec.args) > 0:
self_arg = argspec.args[0]
getter.__annotations__[self_arg] = self
# We need to add 'self' as the first argument, with the type of the struct itself.
# This allows overload resolution to match the struct instance to the 'self' argument.
# Copy annotations to avoid mutating the original function
annotations = dict(getattr(getter, "__annotations__", {}))
# Find the name of the first argument (conventionally 'self')
argspec = get_full_arg_spec(getter)
if len(argspec.args) > 0:
self_arg = argspec.args[0]
annotations[self_arg] = self
getter.__annotations__ = annotations

Comment on lines +942 to +943
add_function_test(TestStruct, "test_struct_property", test_struct_property, devices=devices)
add_function_test(TestStruct, "test_struct_property_with_setter", test_struct_property_with_setter, devices=devices)
Copy link

Choose a reason for hiding this comment

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

Test coverage could be improved. Consider adding tests for:

  1. Property with deleter (should raise TypeError)
  2. Property name that conflicts with a field name (currently silently shadows the field)
  3. Property getter with no arguments (currently fails with unclear error)
  4. Attempting to assign to a property inside a kernel (e.g., s.neg_value = 10)
  5. Property without return type annotation
  6. Multiple properties on the same struct
  7. Property that accesses another property

These edge cases would help ensure robust error handling and prevent confusing runtime errors.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

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.

[REQ] Add support for python @property to warp.struct

2 participants