Skip to content

Commit 4bc16f1

Browse files
committed
[Add] Pyright stubs for quadrants types and NDArray subscript syntax
Add .pyi stub files for primitive_types, ndarray_type, utils, annotations, and compound_types so that Pyright/mypy can type-check code using quadrants types. Update .gitignore to only ignore generated stubs in stubs/quadrants/_lib/. Add pyright test for primitive dtype annotations and NDArray subscript syntax.
1 parent 5b02e64 commit 4bc16f1

8 files changed

Lines changed: 215 additions & 6 deletions

File tree

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,6 @@ imgui.ini
9393
!pyrightconfig.json
9494
*.whl
9595
*.so
96-
stubs/
96+
stubs/quadrants/_lib/
9797
CHANGELOG.md
9898
python/quadrants/_version.py
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
from typing import Any, Generic, TypeVar
2+
3+
T = TypeVar("T")
4+
5+
class Template(Generic[T]):
6+
element_type: type[T]
7+
ndim: int | None
8+
def __init__(self, element_type: type[T] = ..., ndim: int | None = ...) -> None: ...
9+
def __getitem__(self, i: Any) -> T: ...
10+
11+
template = Template
12+
13+
class sparse_matrix_builder: ...
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
from typing import Any
2+
3+
class CompoundType:
4+
def from_kernel_struct_ret(self, launch_ctx: Any, index: tuple[Any, ...]) -> Any: ...
5+
def check_matched(self, other: Any) -> bool: ...
6+
def to_string(self) -> str: ...
7+
8+
def matrix(n: int | None = ..., m: int | None = ..., dtype: Any = ...) -> Any: ...
9+
def vector(n: int | None = ..., dtype: Any = ...) -> Any: ...
10+
def struct(**kwargs: Any) -> Any: ...
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
from typing import Any
2+
3+
class NdarrayType:
4+
dtype: Any
5+
ndim: int | None
6+
needs_grad: bool | None
7+
boundary: int
8+
9+
def __init__(
10+
self,
11+
dtype: Any = ...,
12+
ndim: int | None = ...,
13+
element_dim: int | None = ...,
14+
element_shape: tuple[int, ...] | None = ...,
15+
field_dim: int | None = ...,
16+
needs_grad: bool | None = ...,
17+
boundary: str = ...,
18+
) -> None: ...
19+
@classmethod
20+
def __class_getitem__(cls, args: Any) -> type[NdarrayType]: ...
21+
def __getitem__(self, i: Any) -> Any: ...
22+
def __setitem__(self, i: Any, v: Any) -> None: ...
23+
def __repr__(self) -> str: ...
24+
def __str__(self) -> str: ...
25+
26+
ndarray = NdarrayType
27+
NDArray = NdarrayType
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
from typing import Any, ClassVar, Union
2+
3+
from quadrants._lib.core.quadrants_python import DataTypeCxx
4+
5+
class PrimitiveMeta(type):
6+
cxx: DataTypeCxx
7+
def __eq__(cls, other: object) -> bool: ...
8+
def __ne__(cls, other: object) -> bool: ...
9+
def __hash__(cls) -> int: ...
10+
def __repr__(cls) -> str: ...
11+
def __getattr__(cls, name: str) -> Any: ...
12+
13+
class PrimitiveBase(metaclass=PrimitiveMeta):
14+
cxx: ClassVar[DataTypeCxx]
15+
16+
class f16(PrimitiveBase): ...
17+
class f32(PrimitiveBase): ...
18+
class f64(PrimitiveBase): ...
19+
class i8(PrimitiveBase): ...
20+
class i16(PrimitiveBase): ...
21+
class i32(PrimitiveBase): ...
22+
class i64(PrimitiveBase): ...
23+
class u1(PrimitiveBase): ...
24+
class u8(PrimitiveBase): ...
25+
class u16(PrimitiveBase): ...
26+
class u32(PrimitiveBase): ...
27+
class u64(PrimitiveBase): ...
28+
29+
float16 = f16
30+
float32 = f32
31+
float64 = f64
32+
int8 = i8
33+
int16 = i16
34+
int32 = i32
35+
int64 = i64
36+
uint1 = u1
37+
uint8 = u8
38+
uint16 = u16
39+
uint32 = u32
40+
uint64 = u64
41+
42+
# Raw C++ DataType instances (internal use)
43+
f16_cxx: DataTypeCxx
44+
f32_cxx: DataTypeCxx
45+
f64_cxx: DataTypeCxx
46+
i8_cxx: DataTypeCxx
47+
i16_cxx: DataTypeCxx
48+
i32_cxx: DataTypeCxx
49+
i64_cxx: DataTypeCxx
50+
u1_cxx: DataTypeCxx
51+
u8_cxx: DataTypeCxx
52+
u16_cxx: DataTypeCxx
53+
u32_cxx: DataTypeCxx
54+
u64_cxx: DataTypeCxx
55+
56+
class RefType:
57+
tp: Any
58+
def __init__(self, tp: Any) -> None: ...
59+
60+
def ref(tp: Any) -> RefType: ...
61+
62+
real_types: set[type[PrimitiveBase] | type]
63+
real_type_ids: set[int]
64+
integer_types: set[type[PrimitiveBase] | type]
65+
integer_type_ids: set[int]
66+
all_types: set[type[PrimitiveBase] | type]
67+
cxx_type_ids: set[int]
68+
type_ids: set[int]
69+
_python_primitive_types = Union[int, float, bool, str, None]

stubs/quadrants/types/utils.pyi

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
from typing import Any
2+
3+
def is_signed(dt: Any) -> bool: ...
4+
def is_integral(dt: Any) -> bool: ...
5+
def is_real(dt: Any) -> bool: ...
6+
def is_tensor(dt: Any) -> bool: ...
Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# This is a test file. It just has to exist, to check that pyright works with it.
1+
# Pyright test: NDArray annotations accepted by the type checker and functional at runtime.
22

33
import quadrants as qd
44

@@ -7,29 +7,40 @@
77
qd.init(arch=qd.cpu)
88

99

10+
# Legacy call syntax (still works, pyright warns about call expressions in type positions)
1011
@qd.kernel
11-
def k1(a: qd.types.ndarray(), b: qd.types.NDArray, c: qd.types.NDArray[qd.i32, 1]) -> None: ...
12+
def k1(a: qd.types.ndarray(), b: qd.types.NDArray, c: qd.types.NDArray[qd.i32, 1]) -> None: ... # type: ignore[reportInvalidTypeForm]
1213

1314

1415
@qd.kernel()
15-
def k2(a: qd.types.ndarray(), b: qd.types.NDArray, c: qd.types.NDArray[qd.i32, 1]) -> None: ...
16+
def k2(a: qd.types.ndarray(), b: qd.types.NDArray, c: qd.types.NDArray[qd.i32, 1]) -> None: ... # type: ignore[reportInvalidTypeForm]
17+
18+
19+
# New subscript syntax (preferred, no pyright warnings)
20+
@qd.kernel
21+
def k3(a: qd.types.NDArray[qd.i32, 1], b: qd.types.NDArray[qd.i32], c: qd.types.NDArray) -> None: ...
1622

1723

1824
@qd.data_oriented
1925
class SomeClass:
2026
@qd.kernel
21-
def k1(self, a: qd.types.ndarray(), b: qd.types.NDArray, c: qd.types.NDArray[qd.i32, 1]) -> None: ...
27+
def k1(self, a: qd.types.ndarray(), b: qd.types.NDArray, c: qd.types.NDArray[qd.i32, 1]) -> None: ... # type: ignore[reportInvalidTypeForm]
2228

2329
@qd.kernel()
24-
def k2(self, a: qd.types.ndarray(), b: qd.types.NDArray, c: qd.types.NDArray[qd.i32, 1]) -> None: ...
30+
def k2(self, a: qd.types.ndarray(), b: qd.types.NDArray, c: qd.types.NDArray[qd.i32, 1]) -> None: ... # type: ignore[reportInvalidTypeForm]
31+
32+
@qd.kernel
33+
def k3(self, a: qd.types.NDArray[qd.i32, 1], b: qd.types.NDArray[qd.i32], c: qd.types.NDArray) -> None: ...
2534

2635

2736
@test_utils.test()
2837
def test_ndarray_type():
2938
a = qd.ndarray(qd.i32, (10,))
3039
k1(a, a, a)
3140
k2(a, a, a)
41+
k3(a, a, a)
3242

3343
some_class = SomeClass()
3444
some_class.k1(a, a, a)
3545
some_class.k2(a, a, a)
46+
some_class.k3(a, a, a)
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
"""Pyright test: primitive dtype classes and NDArray subscript syntax.
2+
3+
This file must produce zero pyright errors. It validates that:
4+
- Primitive dtypes (f32, i32, etc.) work as type annotations
5+
- NDArray[dtype, ndim] subscript syntax is accepted
6+
- from __future__ import annotations (stringified) works
7+
- NDArray works via qd.types and top-level qd.NDArray
8+
- Return types and Optional wrappers work
9+
"""
10+
11+
from __future__ import annotations
12+
13+
from typing import Optional
14+
15+
import quadrants as qd
16+
17+
18+
# Primitive types as annotations
19+
def accept_f32(x: qd.f32) -> None: ...
20+
21+
22+
def accept_i32(x: qd.i32) -> None: ...
23+
24+
25+
def accept_any_dtype(x: qd.f32 | qd.i32 | qd.u8) -> None: ...
26+
27+
28+
# NDArray subscript: dtype + ndim
29+
def kernel_2d(a: qd.types.NDArray[qd.f32, 2]) -> None: ...
30+
31+
32+
# NDArray subscript: dtype only
33+
def kernel_dtype(a: qd.types.NDArray[qd.i32]) -> None: ...
34+
35+
36+
# NDArray bare (no subscript)
37+
def kernel_bare(a: qd.types.NDArray) -> None: ...
38+
39+
40+
# Multiple NDArray args with different types
41+
def multi_args(
42+
a: qd.types.NDArray[qd.f32, 2],
43+
b: qd.types.NDArray[qd.i32, 1],
44+
c: qd.types.NDArray,
45+
) -> None: ...
46+
47+
48+
# Top-level NDArray alias (accessible via qd.types)
49+
def top_level(a: qd.types.NDArray[qd.f32, 2]) -> None: ...
50+
51+
52+
# Return types
53+
def make_arr() -> qd.types.NDArray[qd.f32, 2]: ...
54+
55+
56+
# Optional wrapping
57+
def maybe_arr(x: Optional[qd.types.NDArray[qd.f32, 2]]) -> None: ...
58+
59+
60+
# Variable annotations
61+
field1: qd.types.NDArray[qd.f32, 2]
62+
field2: qd.types.NDArray
63+
64+
65+
# In class body
66+
class MyModel:
67+
buf: qd.types.NDArray[qd.f32, 3]
68+
69+
def forward(self, x: qd.types.NDArray[qd.f32, 2]) -> qd.types.NDArray[qd.f32, 2]: ...
70+
71+
72+
# Access via qd.types submodule
73+
def via_types(x: qd.types.NDArray[qd.types.f32, 2]) -> None: ...

0 commit comments

Comments
 (0)