Skip to content

Return types for array subscript annotations (GH-1548)#1549

Open
FabienPean-Virtonomy wants to merge 4 commits into
NVIDIA:mainfrom
Virtonomy:FabienPean/union-type-hint
Open

Return types for array subscript annotations (GH-1548)#1549
FabienPean-Virtonomy wants to merge 4 commits into
NVIDIA:mainfrom
Virtonomy:FabienPean/union-type-hint

Conversation

@FabienPean-Virtonomy

@FabienPean-Virtonomy FabienPean-Virtonomy commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

Description

wp.array[...]-style subscripts previously produced lightweight instances, which cannot participate in PEP 604 unions (e.g. wp.array[float] | float) and forces per-use allocations. Return a cached lightweight annotation type instead, while preserving the codegen-facing metadata (dtype, ndim, vars, __ctype__) and updating the subscript-type tests to match.

Closes #1548

Checklist

  • I am familiar with the Contributing Guidelines.
  • New or existing tests cover these changes.
  • The documentation is up to date with these changes.
  • CHANGELOG.md is updated for any user-facing changes under the Unreleased section.

Validation summary

Tests updated and added to test_subscript_types.py test suite

Bug fix

import warp as wp

wp.init()

# PEP 604 union fails
try:
    T = wp.array[float] | float
except TypeError as e:
    print(f"union failed: {e}")

Summary by CodeRabbit

  • Bug Fixes

    • wp.array[...] subscript annotations now behave as lightweight annotation types and correctly support PEP 604 union expressions (e.g., wp.array[float] | float) at runtime.
    • View construction for non-fabric/non-fixed arrays now uses the intended concrete array type when producing views.
    • Adjoint initialization guarded to avoid follow-on errors from missing internal state after failed builds.
  • Documentation

    • Changelog updated with the above fix.

`wp.array[...]`-style subscripts previously produced lightweight *instances*, which can’t participate in PEP 604 unions (e.g. `wp.array[float] | float`) and forces per-use allocations.
Return a cached lightweight annotation *type* instead, while preserving the codegen-facing metadata (`dtype`, `ndim`, `vars`, `__ctype__`) and updating the subscript-type tests to match.

Signed-off-by: Fabien Péan <pean@virtonomy.io>
@copy-pr-bot

copy-pr-bot Bot commented Jun 12, 2026

Copy link
Copy Markdown

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

coderabbitai Bot commented Jun 12, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Enterprise

Run ID: c298ee4c-72d8-44fe-b0ef-95471d23b122

📥 Commits

Reviewing files that changed from the base of the PR and between 2b76761 and 3d8266f.

📒 Files selected for processing (4)
  • warp/_src/builtins.py
  • warp/_src/codegen.py
  • warp/_src/types.py
  • warp/tests/test_subscript_types.py
✅ Files skipped from review due to trivial changes (1)
  • warp/_src/codegen.py
🚧 Files skipped from review as they are similar to previous changes (2)
  • warp/tests/test_subscript_types.py
  • warp/_src/types.py

📝 Walkthrough

Walkthrough

This PR refactors Warp's array annotation system to return parameterized type objects (via a metaclass-based factory) instead of lightweight instances, updates detection and signature encoding to operate on those types, adjusts a couple runtime call sites, updates tests, and documents the change in the changelog.

Changes

Array Annotation Type System

Layer / File(s) Summary
Annotation type metaclass and marker bases
warp/_src/types.py
Introduce _ArrayAnnotationTypeMeta and marker base classes to represent parameterized array annotation types.
Subscript parsing and class getitem integration
warp/_src/types.py
_parse_array_subscript() and array1d/array2d/array3d/array4d __class_getitem__ now use _make_array_annotation_type() to return cached annotation types; Literal ndim handling adjusted.
Array detection predicates and type utilities
warp/_src/types.py
Add is_array_annotation() and extend is_array(), matches_array_class(), concrete_array_type(), and type_repr() to recognize and use the new annotation types.
Array type signature encoding refactor
warp/_src/types.py
get_type_code() moves array handling earlier, computes dtype/ndim from annotation types, and uses concrete_array_type() for prefix selection; old duplicate branch removed.
Runtime integration tweaks
warp/_src/builtins.py, warp/_src/codegen.py
view_value_func() now uses concrete_array_type(arr_type)(dtype=dtype, ndim=ndim) when constructing views; Adjoint.__init__ initializes return_var = None.
Test updates for annotation type behavior
warp/tests/test_subscript_types.py
Tests now assert cached annotation-type identity, verify annotations are type objects and support PEP 604 unions, and use public wp.array[...] subscript API in repr tests.
Changelog documentation
CHANGELOG.md
Adds an "Unreleased" Fixed entry documenting that wp.array[...] subscript annotations now return lightweight annotation types so PEP 604 union expressions evaluate correctly at runtime.

🎯 4 (Complex) | ⏱️ ~45 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning The PR includes one minor out-of-scope change: modifications to warp/_src/codegen.py initializing adj.return_var, which appears unrelated to the primary objective of array annotation type handling. Consider moving the adj.return_var initialization fix to a separate PR or justify its inclusion as a necessary prerequisite to prevent cascading failures during testing.
Docstring Coverage ⚠️ Warning Docstring coverage is 65.38% 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 'Return types for array subscript annotations (GH-1548)' directly summarizes the main change: converting array subscript annotations from instances to types to enable PEP 604 union syntax.
Linked Issues check ✅ Passed The PR successfully addresses issue #1548 by refactoring array subscript annotations to return cached lightweight annotation types instead of instances, enabling PEP 604 union syntax compliance across all targeted array classes.
Description check ✅ Passed The PR objectives and summary clearly explain the motivation, design changes, and testing approach, providing adequate context for reviewers.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
warp/_src/types.py (1)

5132-5153: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Returning classes here breaks the existing None-argument inference path.

After this change, wp.array[...] is a class object, but infer_argument_types() still reconstructs array templates with type(t)(dtype=t.dtype, ndim=t.ndim) on Line 7163. For these annotations, type(t) is now _ArrayAnnotationTypeMeta, so passing None for an array-typed parameter will raise instead of preserving the template type.

At minimum, the downstream reconstruction needs to special-case is_array_annotation(t) and reuse t (or rebuild it via _make_array_annotation_type(...)) instead of calling type(t)(...).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@warp/_src/types.py` around lines 5132 - 5153, The change made in
_parse_array_subscript returns class-like objects which breaks
infer_argument_types' reconstruction that calls type(t)(dtype=..., ndim=...)
(type(t) is now _ArrayAnnotationTypeMeta); update infer_argument_types to
special-case array annotations by using is_array_annotation(t) and either reuse
the existing annotation object t directly when filling in None arguments or
rebuild the annotation via
_make_array_annotation_type(_ARRAY_ANNOTATION_MAP.get(base), dtype=t.dtype,
ndim=t.ndim) instead of calling type(t)(...), ensuring array templates preserve
their original semantics.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@warp/_src/types.py`:
- Around line 5112-5129: _make_array_annotation_type currently constructs a new
_ArrayAnnotationTypeMeta on every call causing identity instability; add a cache
(e.g., a module-level dict or an attribute on ann_base) keyed by the normalized
dtype and ndim (use the same dtype = dtype if dtype is Any else
type_to_warp(dtype) logic and the resolved ndim) and return the cached class
when present instead of allocating a new one; update the function to compute the
cache key (ann_base, dtype, ndim), check and return from cache, and only create
and store a new _ArrayAnnotationTypeMeta if the key is missing so repeated
subscripts (e.g. wp.array[float]) yield identical types.
- Around line 5064-5070: The __ctype__ implementation for array-like classes
currently returns a bare array_t() which discards annotated rank/shape/strides;
change __ctype__ (using cls._concrete_cls, array_t(), indexedarray_t,
ARRAY_MAX_DIMS) to preserve the annotated ndim/shape/strides the same way the
old annotation path did—read cls.ndim (falling back to Any), cls.shape and
cls.strides if present (or default placeholders) and construct array_t(...) with
those values instead of returning array_t() so codegen/native consumers retain
the array rank information.

---

Outside diff comments:
In `@warp/_src/types.py`:
- Around line 5132-5153: The change made in _parse_array_subscript returns
class-like objects which breaks infer_argument_types' reconstruction that calls
type(t)(dtype=..., ndim=...) (type(t) is now _ArrayAnnotationTypeMeta); update
infer_argument_types to special-case array annotations by using
is_array_annotation(t) and either reuse the existing annotation object t
directly when filling in None arguments or rebuild the annotation via
_make_array_annotation_type(_ARRAY_ANNOTATION_MAP.get(base), dtype=t.dtype,
ndim=t.ndim) instead of calling type(t)(...), ensuring array templates preserve
their original semantics.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Enterprise

Run ID: 25e54390-be3e-49bd-94f0-acb5c563f40b

📥 Commits

Reviewing files that changed from the base of the PR and between be5e930 and 2b76761.

📒 Files selected for processing (3)
  • CHANGELOG.md
  • warp/_src/types.py
  • warp/tests/test_subscript_types.py

Comment thread warp/_src/types.py
Comment thread warp/_src/types.py
@greptile-apps

greptile-apps Bot commented Jun 12, 2026

Copy link
Copy Markdown

Greptile Summary

This PR changes wp.array[...]-style subscript annotations from lightweight instances of _ArrayAnnotationBase to types (classes) created dynamically by _make_array_annotation_type and cached in a module-level dict. Because the result is now a real Python type, it can participate in PEP 604 union expressions (wp.array[float] | float) without raising TypeError.

  • Core change (types.py): introduces _ArrayAnnotationTypeMeta (metaclass carrying vars, __ctype__, __repr__, __eq__, __hash__), replaces per-use instance allocation with a cached type factory, and adds is_array_annotation() to distinguish parameterized annotation types from both bare instances and unparameterized bases.
  • Consequential fixes: builtins.py corrects type(arr_type)concrete_array_type(arr_type) (the former would have returned the metaclass after this change), codegen.py defensively initialises adj.return_var = None, and get_type_code reorders the is_array branch before isinstance(arg_type, type) so annotation types don't fall into the wrong branch.

Confidence Score: 5/5

Safe to merge — the change is well-scoped, the cache correctly ensures identity equality, and all call sites that previously relied on instance checks have been updated to the new type-based helpers.

The implementation is mechanically sound: the metaclass correctly intercepts all class-level operations, _make_array_annotation_type caches by (ann_base, dtype, ndim) so repeated subscriptions are free, and is_array_annotation correctly gates on the presence of dtype/ndim/_concrete_cls to exclude unparameterized base types. The builtins.py fix (concrete_array_type instead of type()) is necessary and correct. The only findings are a stale docstring phrase and a redundant line in ctype.

No files require special attention.

Important Files Changed

Filename Overview
warp/_src/types.py Core change: replaces _ArrayAnnotationBase instances with types via metaclass _ArrayAnnotationTypeMeta; adds _make_array_annotation_type with a module-level cache; updates is_array, is_array_annotation, concrete_array_type, and matches_array_class to handle class-vs-instance distinction; reorders is_array branch in get_type_code before isinstance(type) to avoid annotation types falling into the wrong branch.
warp/_src/builtins.py Fixes view_value_func: replaces type(arr_type) (which returned the metaclass for annotation types) with concrete_array_type(arr_type) to correctly get the concrete array class before constructing a new instance.
warp/_src/codegen.py Defensive fix: initializes adj.return_var = None in Adjoint.init to prevent AttributeError on builds that are skipped after an earlier failure.
warp/tests/test_subscript_types.py Updates assertions from isinstance/assertIsInstance (instance checks) to issubclass checks to match the new type-based annotations; adds identity cache tests for all array annotation types; adds PEP 604 union smoke tests.
CHANGELOG.md Adds changelog entry under Unreleased for the wp.array[...] annotation type fix.

Reviews (2): Last reviewed commit: "Fix array annotation type handling to co..." | Re-trigger Greptile

Comment thread warp/_src/types.py
Comment thread warp/_src/types.py
Comment on lines +5028 to +5032
def __eq__(cls, other):
if not isinstance(other, type):
return NotImplemented
if not issubclass(other, _ArrayAnnotationBase):
return False

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 The __eq__ implementation returns False (not NotImplemented) when other is a type but is not an _ArrayAnnotationBase subclass. Python convention is to return NotImplemented when a comparison is not meaningful for the operand types, so the other side can still be tried. Returning False suppresses that fallback. In practice this rarely matters here (built-in type.__eq__ also falls back correctly), but it diverges from the standard contract and could silently short-circuit user-defined __eq__ on custom type objects compared against annotation types.

Suggested change
def __eq__(cls, other):
if not isinstance(other, type):
return NotImplemented
if not issubclass(other, _ArrayAnnotationBase):
return False
def __eq__(cls, other):
if not isinstance(other, type):
return NotImplemented
if not issubclass(other, _ArrayAnnotationBase):
return NotImplemented

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!

@shi-eric

Copy link
Copy Markdown
Contributor

/ok to test 2b76761

Signed-off-by: Fabien Péan <pean@virtonomy.io>
Signed-off-by: Fabien Péan <pean@virtonomy.io>
… method

Signed-off-by: Fabien Péan <pean@virtonomy.io>
@shi-eric

Copy link
Copy Markdown
Contributor

Thanks for the fix. It works and the tests are thorough. The root cause is just that wp.array[float] is an instance, so | has nothing to dispatch to. Why not give the annotation base __or__/__ror__ instead of promoting the annotations to types? Since these unions are only valid as annotations on plain Python functions (codegen doesn't support union types anyway), that would keep the annotation a lightweight instance and avoid the metaclass, identity cache, and call-site churn here. Was there something that needs the annotation to actually be a type?

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.

Union syntax raises TypeError

2 participants