This is an automated email from the ASF dual-hosted git repository.
junrushao pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/tvm-ffi.git
The following commit(s) were added to refs/heads/main by this push:
new b8d8104 feat(dataclass): Introduce more utility methods in
`tvm_ffi.dataclasses.*` (#555)
b8d8104 is described below
commit b8d810459a983f9c822a31b313348ea94c9af44c
Author: Junru Shao <[email protected]>
AuthorDate: Fri Apr 17 12:21:25 2026 -0700
feat(dataclass): Introduce more utility methods in `tvm_ffi.dataclasses.*`
(#555)
## Summary
This PR introduces:
- `tvm_ffi.dataclasses.fields` similar to
[dataclasses.fields](https://docs.python.org/3/library/dataclasses.html#dataclasses.fields)
- `tvm_ffi.dataclasses.replace` similar to
[dataclasses.replace](https://docs.python.org/3/library/dataclasses.html#dataclasses.replace)
- `tvm_ffi.dataclasses.is_dataclass` similar to
[dataclasses.is_dataclass](https://docs.python.org/3/library/dataclasses.html#dataclasses.is_dataclass)
- `tvm_ffi.Object.id_` similar to
[id](https://docs.python.org/3/library/functions.html#id)
- `tvm_ffi.Object.is_` similar to Python's `is` keyword
## Test plan
- [x] `uv run pytest tests/python` — 2148 passed, 38 skipped, 3 xfailed
- [x] `pre-commit run --all-files` — 27 hooks passed
- [x] New tests for every new attribute/flag and for `id_`/`is_` alias
parity
- [ ] CI lint + C++ unit tests + Rust tests
---
python/tvm_ffi/core.pyi | 9 +
python/tvm_ffi/cython/object.pxi | 51 +++-
python/tvm_ffi/cython/type_info.pxi | 33 ++-
python/tvm_ffi/dataclasses/__init__.py | 15 +-
python/tvm_ffi/dataclasses/c_class.py | 37 ++-
python/tvm_ffi/dataclasses/common.py | 74 +++++
python/tvm_ffi/dataclasses/field.py | 34 ++-
python/tvm_ffi/dataclasses/py_class.py | 11 +-
tests/python/test_dataclass_c_class.py | 288 +++++++++++++++++++-
tests/python/test_dataclass_common.py | 252 +++++++++++++++++
tests/python/test_dataclass_py_class.py | 468 ++++++++++++++++----------------
tests/python/test_object.py | 17 ++
12 files changed, 1032 insertions(+), 257 deletions(-)
diff --git a/python/tvm_ffi/core.pyi b/python/tvm_ffi/core.pyi
index 2f5cc2b..e9b117e 100644
--- a/python/tvm_ffi/core.pyi
+++ b/python/tvm_ffi/core.pyi
@@ -37,6 +37,8 @@ __dlpack_version__: tuple[int, int]
class CObject:
def __ctypes_handle__(self) -> Any: ...
def __chandle__(self) -> int: ...
+ @property
+ def id_(self) -> int: ...
def __reduce__(self) -> Any: ...
def __getstate__(self) -> dict[str, Any]: ...
def __setstate__(self, state: dict[str, Any]) -> None: ...
@@ -46,6 +48,7 @@ class CObject:
def __hash__(self) -> int: ...
def __init_handle_by_constructor__(self, fconstructor: Any, *args: Any) ->
None: ...
def same_as(self, other: Any) -> bool: ...
+ def is_(self, other: Any) -> bool: ...
def _move(self) -> ObjectRValueRef: ...
def __move_handle_from__(self, other: CObject) -> None: ...
@@ -282,6 +285,12 @@ class TypeField:
c_init: bool
c_kw_only: bool
c_has_default: bool
+ c_default: Any
+ c_default_factory: Any
+ c_repr: bool
+ c_compare: bool
+ c_hash: bool
+ c_structural_eq: str | None
dataclass_field: Any | None
def as_property(self, cls: type) -> property: ...
diff --git a/python/tvm_ffi/cython/object.pxi b/python/tvm_ffi/cython/object.pxi
index ee97ba6..6770a9d 100644
--- a/python/tvm_ffi/cython/object.pxi
+++ b/python/tvm_ffi/cython/object.pxi
@@ -114,6 +114,16 @@ cdef class CObject:
cdef uint64_t chandle = <uint64_t>self.chandle
return chandle
+ @property
+ def id_(self) -> int:
+ """The integer address of the underlying FFI handle.
+
+ Alias for :py:meth:`__chandle__`. Returns ``0`` when the
+ handle is NULL.
+ """
+ cdef uint64_t chandle = <uint64_t>self.chandle
+ return chandle
+
def __reduce__(self):
cls = type(self)
return (_new_object, (cls,), self.__getstate__())
@@ -153,6 +163,15 @@ cdef class CObject:
def same_as(self, other: object) -> bool:
return isinstance(other, CObject) and self.chandle ==
(<CObject>other).chandle
+ def is_(self, other: object) -> bool:
+ """Return ``True`` if both references point to the same FFI handle.
+
+ Alias for :py:meth:`same_as`. Checks identity of the
+ underlying handle rather than performing a structural,
+ value-based comparison.
+ """
+ return isinstance(other, CObject) and self.chandle ==
(<CObject>other).chandle
+
def __move_handle_from__(self, other: CObject) -> None:
self.chandle = (<CObject>other).chandle
(<CObject>other).chandle = NULL
@@ -488,6 +507,12 @@ cdef _type_info_create_from_type_key(object type_cls, str
type_key):
cdef str type_schema_json
cdef FieldGetter getter
cdef FieldSetter setter
+ cdef bint has_default
+ cdef bint default_from_factory
+ cdef TVMFFIAny owned_default
+ cdef object c_default
+ cdef object c_default_factory
+ cdef object c_structural_eq
cdef ByteArrayArg type_key_arg = ByteArrayArg(c_str(type_key))
# NOTE: `type_key_arg` must be kept alive until after the call to
`TVMFFITypeKeyToIndex`,
@@ -505,6 +530,24 @@ cdef _type_info_create_from_type_key(object type_cls, str
type_key):
(<FieldSetter>setter).offset = field.offset
(<FieldSetter>setter).flags = field.flags
metadata_obj = json.loads(bytearray_to_str(&field.metadata)) if
field.metadata.size != 0 else {}
+ # Decode the static default value or factory (if any) registered by
C++.
+ has_default = (field.flags & kTVMFFIFieldFlagBitMaskHasDefault) != 0
+ default_from_factory = (field.flags &
kTVMFFIFieldFlagBitMaskDefaultFromFactory) != 0
+ c_default = MISSING
+ c_default_factory = MISSING
+ if has_default:
+
CHECK_CALL(TVMFFIAnyViewToOwnedAny(&field.default_value_or_factory,
&owned_default))
+ if default_from_factory:
+ c_default_factory = make_ret(owned_default)
+ else:
+ c_default = make_ret(owned_default)
+ # Decode SEqHashIgnore / SEqHashDef into the Field.structural_eq
vocabulary.
+ if (field.flags & kTVMFFIFieldFlagBitMaskSEqHashIgnore) != 0:
+ c_structural_eq = "ignore"
+ elif (field.flags & kTVMFFIFieldFlagBitMaskSEqHashDef) != 0:
+ c_structural_eq = "def"
+ else:
+ c_structural_eq = None
fields.append(
TypeField(
name=bytearray_to_str(&field.name),
@@ -517,7 +560,13 @@ cdef _type_info_create_from_type_key(object type_cls, str
type_key):
setter=setter,
c_init=(field.flags & kTVMFFIFieldFlagBitMaskInitOff) == 0,
c_kw_only=(field.flags & kTVMFFIFieldFlagBitMaskKwOnly) != 0,
- c_has_default=(field.flags &
kTVMFFIFieldFlagBitMaskHasDefault) != 0,
+ c_has_default=has_default,
+ c_default=c_default,
+ c_default_factory=c_default_factory,
+ c_repr=(field.flags & kTVMFFIFieldFlagBitMaskReprOff) == 0,
+ c_compare=(field.flags & kTVMFFIFieldFlagBitMaskCompareOff) ==
0,
+ c_hash=(field.flags & kTVMFFIFieldFlagBitMaskHashOff) == 0,
+ c_structural_eq=c_structural_eq,
)
)
diff --git a/python/tvm_ffi/cython/type_info.pxi
b/python/tvm_ffi/cython/type_info.pxi
index 7b799b3..86b9e65 100644
--- a/python/tvm_ffi/cython/type_info.pxi
+++ b/python/tvm_ffi/cython/type_info.pxi
@@ -627,6 +627,23 @@ class TypeField:
c_init: bool = True
c_kw_only: bool = False
c_has_default: bool = False
+ # ``c_default`` / ``c_default_factory`` are populated from the C++
+ # reflection layer (``TVMFFIFieldInfo.default_value_or_factory``) for
+ # ``@c_class`` types, and from the ``Field`` descriptors for
+ # ``@py_class`` types. Both default to :data:`MISSING` when no
+ # default / factory has been registered.
+ c_default: Any = dataclasses.field(default_factory=lambda: MISSING)
+ c_default_factory: Any = dataclasses.field(default_factory=lambda: MISSING)
+ # Presentation / structural flags decoded from the reflection layer.
+ # ``c_repr`` / ``c_compare`` / ``c_hash`` default to True and flip off
+ # when ``refl::repr(false)`` / ``refl::compare(false)`` /
``refl::hash(false)``
+ # (or the corresponding ``@py_class`` ``field(...)`` kwargs) are set.
+ # ``c_structural_eq`` is ``None`` / ``"ignore"`` / ``"def"`` matching the
+ # ``Field.structural_eq`` vocabulary.
+ c_repr: bool = True
+ c_compare: bool = True
+ c_hash: bool = True
+ c_structural_eq: Optional[str] = None
dataclass_field: Any = None
def __post_init__(self):
@@ -867,7 +884,7 @@ cdef _register_one_field(
info.doc.size = 0
# --- metadata (JSON with type_schema) ---
- metadata_str = json.dumps({"type_schema": py_field.ty.to_json()})
+ metadata_str = json.dumps({"type_schema": py_field._ty_schema.to_json()})
metadata_bytes = c_str(metadata_str)
cdef ByteArrayArg metadata_arg = ByteArrayArg(metadata_bytes)
info.metadata = metadata_arg.cdata
@@ -1011,7 +1028,7 @@ def _register_fields(type_info, fields,
structure_kind=None):
cdef list type_fields = []
for py_field in fields:
# 1. Get layout
- layout = _ORIGIN_NATIVE_LAYOUT.get(py_field.ty.origin, (8, 8,
kTVMFFIObject))
+ layout = _ORIGIN_NATIVE_LAYOUT.get(py_field._ty_schema.origin, (8, 8,
kTVMFFIObject))
size = layout[0]
alignment = layout[1]
field_type_index = layout[2]
@@ -1026,7 +1043,7 @@ def _register_fields(type_info, fields,
structure_kind=None):
getter =
<TVMFFIFieldGetter><int64_t>_MAKE_FILED_GETTER(field_type_index)
setter_fn = <CObject>_MAKE_FIELD_SETTER(
field_type_index,
- <int64_t><void*>py_field.ty._converter,
+ <int64_t><void*>py_field._ty_schema._converter,
<int64_t>&_f_type_convert,
)
@@ -1051,13 +1068,19 @@ def _register_fields(type_info, fields,
structure_kind=None):
size=size,
offset=field_offset,
frozen=py_field.frozen,
- metadata={"type_schema": py_field.ty.to_json()},
+ metadata={"type_schema": py_field._ty_schema.to_json()},
getter=fgetter,
setter=fsetter,
- ty=py_field.ty,
+ ty=py_field._ty_schema,
c_init=py_field.init,
c_kw_only=py_field.kw_only,
c_has_default=(py_field.default is not MISSING or
py_field.default_factory is not MISSING),
+ c_default=py_field.default,
+ c_default_factory=py_field.default_factory,
+ c_repr=py_field.repr,
+ c_compare=py_field.compare,
+ c_hash=bool(py_field.hash),
+ c_structural_eq=py_field.structural_eq,
)
)
diff --git a/python/tvm_ffi/dataclasses/__init__.py
b/python/tvm_ffi/dataclasses/__init__.py
index bb6a039..73fdcca 100644
--- a/python/tvm_ffi/dataclasses/__init__.py
+++ b/python/tvm_ffi/dataclasses/__init__.py
@@ -16,8 +16,21 @@
# under the License.
"""FFI dataclass decorators: ``c_class`` for C++-backed types, ``py_class``
for Python-defined types."""
+from tvm_ffi.core import Object
+
from .c_class import c_class
+from .common import fields, is_dataclass, replace
from .field import KW_ONLY, Field, field
from .py_class import py_class
-__all__ = ["KW_ONLY", "Field", "c_class", "field", "py_class"]
+__all__ = [
+ "KW_ONLY",
+ "Field",
+ "Object",
+ "c_class",
+ "field",
+ "fields",
+ "is_dataclass",
+ "py_class",
+ "replace",
+]
diff --git a/python/tvm_ffi/dataclasses/c_class.py
b/python/tvm_ffi/dataclasses/c_class.py
index b94dddb..f30d657 100644
--- a/python/tvm_ffi/dataclasses/c_class.py
+++ b/python/tvm_ffi/dataclasses/c_class.py
@@ -18,14 +18,48 @@
from __future__ import annotations
+import typing
from collections.abc import Callable
-from typing import TypeVar
+from typing import Any, TypeVar
from typing_extensions import dataclass_transform
+from .field import Field
+
_T = TypeVar("_T", bound=type)
+def _attach_field_objects(cls: type, type_info: Any) -> None:
+ """Populate ``TypeField.dataclass_field`` for every own reflected field.
+
+ ``@c_class`` fields originate from C++ reflection, so there is no
+ user-supplied :class:`Field`. We synthesize one per ``TypeField``
+ and stash it on ``TypeField.dataclass_field`` so
+ :func:`~tvm_ffi.dataclasses.fields` can return it.
+ """
+ try:
+ hints = typing.get_type_hints(cls)
+ except Exception:
+ hints = {}
+ for tf in type_info.fields:
+ f = Field(
+ name=tf.name,
+ _ty_schema=tf.ty,
+ default=tf.c_default,
+ default_factory=tf.c_default_factory,
+ frozen=tf.frozen,
+ init=tf.c_init,
+ repr=tf.c_repr,
+ hash=tf.c_hash,
+ compare=tf.c_compare,
+ kw_only=tf.c_kw_only,
+ structural_eq=tf.c_structural_eq,
+ doc=tf.doc,
+ )
+ f.type = hints.get(tf.name)
+ tf.dataclass_field = f
+
+
@dataclass_transform(eq_default=False, order_default=False)
def c_class(
type_key: str,
@@ -115,6 +149,7 @@ def c_class(
type_info = getattr(cls, "__tvm_ffi_type_info__", None)
assert type_info is not None
_warn_missing_field_annotations(cls, type_info, stacklevel=2)
+ _attach_field_objects(cls, type_info)
_install_dataclass_dunders(
cls, init=init, repr=repr, eq=eq, order=order,
unsafe_hash=unsafe_hash
)
diff --git a/python/tvm_ffi/dataclasses/common.py
b/python/tvm_ffi/dataclasses/common.py
new file mode 100644
index 0000000..88c3d62
--- /dev/null
+++ b/python/tvm_ffi/dataclasses/common.py
@@ -0,0 +1,74 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+"""``dataclasses``-style helpers unified over stdlib, ``@c_class``, and
``@py_class``."""
+
+from __future__ import annotations
+
+from typing import Any
+
+from .field import Field
+
+__all__ = ["fields", "is_dataclass", "replace"]
+
+
+def is_dataclass(obj: Any) -> bool:
+ """Return True if ``obj`` is a ``@c_class`` / ``@py_class`` type or
instance."""
+ cls = obj if isinstance(obj, type) else type(obj)
+ return getattr(cls, "__tvm_ffi_type_info__", None) is not None
+
+
+def fields(obj_or_cls: Any) -> tuple[Field, ...]:
+ """Return the :class:`~tvm_ffi.dataclasses.Field` descriptors for a type.
+
+ Accepts a ``@c_class`` / ``@py_class`` type or instance and walks the
+ parent chain so inherited fields appear parent-first, matching the
+ order of the auto-generated ``__init__``.
+
+ Raises
+ ------
+ TypeError
+ If ``obj_or_cls`` is not a ``@c_class`` / ``@py_class`` type or
instance.
+
+ """
+ cls = obj_or_cls if isinstance(obj_or_cls, type) else type(obj_or_cls)
+ ti = getattr(cls, "__tvm_ffi_type_info__", None)
+ if ti is None:
+ raise TypeError(
+ f"fields() argument must be a c_class or py_class type or
instance, "
+ f"got {type(obj_or_cls).__name__}"
+ )
+ chain = []
+ while ti is not None:
+ chain.append(ti)
+ ti = ti.parent_type_info
+ out: list[Field] = []
+ for ti in reversed(chain):
+ for tf in ti.fields or ():
+ if tf.dataclass_field is not None:
+ out.append(tf.dataclass_field)
+ return tuple(out)
+
+
+def replace(obj: Any, /, **changes: Any) -> Any:
+ """Return a copy of ``obj`` with selected fields replaced.
+
+ Drop-in for :func:`dataclasses.replace` for FFI-backed instances: the
+ call is forwarded to ``obj.__replace__`` (installed by the decorator),
+ which uses the ``FFIProperty.set()`` escape hatch so frozen fields are
+ still replaceable.
+ """
+ return obj.__replace__(**changes)
diff --git a/python/tvm_ffi/dataclasses/field.py
b/python/tvm_ffi/dataclasses/field.py
index 75238ee..53f576c 100644
--- a/python/tvm_ffi/dataclasses/field.py
+++ b/python/tvm_ffi/dataclasses/field.py
@@ -40,18 +40,25 @@ else:
class Field:
"""Descriptor for a single field in a Python-defined TVM-FFI type.
- When constructed directly (low-level API), *name* and *ty* should be
- provided. When returned by :func:`field` (``@py_class`` workflow),
- *name* and *ty* are ``None`` and filled in by the decorator.
+ When constructed directly (low-level API), *name* and *_ty_schema*
+ should be provided. When returned by :func:`field` (``@py_class``
+ workflow), both are ``None`` and filled in by the decorator.
Parameters
----------
name : str | None
The field name. ``None`` when created via :func:`field`; filled
in by the ``@py_class`` decorator.
- ty : TypeSchema | None
- The type schema. ``None`` when created via :func:`field`; filled
- in by the ``@py_class`` decorator.
+ _ty_schema : TypeSchema | None
+ Private: the internal :class:`TypeSchema` used by the reflection
+ layer. ``None`` when created via :func:`field`; filled in by
+ the ``@py_class`` decorator. Consumers should use :attr:`type`
+ instead.
+ type : Any
+ The resolved Python annotation (e.g. ``int``, ``list[str]``,
+ ``Optional[X]``). Filled in by the ``@py_class`` / ``@c_class``
+ decorator via :func:`typing.get_type_hints`; ``None`` until then
+ or when the annotation cannot be resolved.
default : object
Default value for the field. Mutually exclusive with *default_factory*.
``MISSING`` when not set.
@@ -89,6 +96,7 @@ class Field:
"""
__slots__ = (
+ "_ty_schema",
"compare",
"default",
"default_factory",
@@ -100,10 +108,11 @@ class Field:
"name",
"repr",
"structural_eq",
- "ty",
+ "type",
)
name: str | None
- ty: TypeSchema | None
+ _ty_schema: TypeSchema | None
+ type: Any
default: object
default_factory: Callable[[], object] | None
frozen: bool
@@ -123,7 +132,7 @@ class Field:
def __init__( # noqa: PLR0913
self,
name: str | None = None,
- ty: TypeSchema | None = None,
+ _ty_schema: TypeSchema | None = None,
*,
default: object = MISSING,
default_factory: Callable[[], object] | None = MISSING, # type:
ignore[assignment]
@@ -153,7 +162,8 @@ class Field:
f"got {structural_eq!r}"
)
self.name = name
- self.ty = ty
+ self._ty_schema = _ty_schema
+ self.type = None
self.default = default
self.default_factory = default_factory
self.frozen = frozen
@@ -181,8 +191,8 @@ def field(
) -> Any:
"""Customize a field in a ``@py_class``-decorated class.
- Returns a :class:`Field` sentinel whose *name* and *ty* are
- ``None``. The ``@py_class`` decorator fills them in later
+ Returns a :class:`Field` sentinel whose *name* and *_ty_schema*
+ are ``None``. The ``@py_class`` decorator fills them in later
from the class annotations.
The return type is ``Any`` because ``dataclass_transform`` field
diff --git a/python/tvm_ffi/dataclasses/py_class.py
b/python/tvm_ffi/dataclasses/py_class.py
index 50c4510..9f3d1f2 100644
--- a/python/tvm_ffi/dataclasses/py_class.py
+++ b/python/tvm_ffi/dataclasses/py_class.py
@@ -194,9 +194,10 @@ def _collect_own_fields( # noqa: PLR0912
except AttributeError:
pass
- # Fill in name and ty (set by the decorator, not the user)
+ # Fill in name, schema, and resolved type (set by the decorator, not
the user)
f.name = name
- f.ty = TypeSchema.from_annotation(resolved_type)
+ f._ty_schema = TypeSchema.from_annotation(resolved_type)
+ f.type = resolved_type
# Resolve kw_only: None means "inherit from decorator"
if f.kw_only is None:
@@ -270,6 +271,12 @@ def _register_fields_into_type(
# Register fields and type-level structural eq/hash kind with the C layer.
structure_kind = _STRUCTURE_KIND_MAP.get(params.get("structural_eq"))
type_info._register_fields(own_fields, structure_kind)
+ # Attach the user's Field sentinel to each TypeField so the
+ # ``tvm_ffi.dataclasses.fields()`` compat layer can recover defaults
+ # and default_factory values. _register_fields preserves order, so
+ # own_fields and type_info.fields line up 1:1.
+ for py_field, type_field in zip(own_fields, type_info.fields):
+ type_field.dataclass_field = py_field
# Register user-defined dunder methods and read back system-generated ones.
# Non-callable entries whose names are in _FFI_TYPE_ATTR_NAMES are routed
# to TVMFFITypeRegisterAttr by the Cython layer.
diff --git a/tests/python/test_dataclass_c_class.py
b/tests/python/test_dataclass_c_class.py
index 2844c8e..4d86073 100644
--- a/tests/python/test_dataclass_c_class.py
+++ b/tests/python/test_dataclass_c_class.py
@@ -22,9 +22,14 @@ import inspect
import warnings
import pytest
-from tvm_ffi.core import TypeInfo
+import tvm_ffi.testing
+from tvm_ffi.core import MISSING, TypeInfo
+from tvm_ffi.dataclasses import Field
+from tvm_ffi.dataclasses.c_class import _attach_field_objects
from tvm_ffi.registry import _warn_missing_field_annotations
from tvm_ffi.testing import (
+ TestCompare,
+ TestHash,
_TestCxxClassBase,
_TestCxxClassDerived,
_TestCxxClassDerivedDerived,
@@ -397,3 +402,284 @@ def test_c_class_warns_only_own_fields_not_inherited() ->
None:
parent_field_names = {f.name for f in parent_type_info.fields}
for name in parent_field_names:
assert name not in msg
+
+
+# ---------------------------------------------------------------------------
+# 13. Field object attachment (_attach_field_objects)
+# ---------------------------------------------------------------------------
+
+
+def test_c_class_attaches_field_object_per_typefield() -> None:
+ """Every own reflected field gets a ``Field`` instance on
``dataclass_field``."""
+ type_info: TypeInfo = getattr(_TestCxxClassBase, "__tvm_ffi_type_info__")
+ assert type_info.fields # sanity
+ for tf in type_info.fields:
+ assert isinstance(tf.dataclass_field, Field)
+
+
+def test_c_class_field_name_matches_typefield() -> None:
+ """``Field.name`` mirrors ``TypeField.name``."""
+ type_info: TypeInfo = getattr(_TestCxxClassBase, "__tvm_ffi_type_info__")
+ for tf in type_info.fields:
+ assert tf.dataclass_field.name == tf.name
+
+
+def test_c_class_field_private_schema_mirrors_typefield() -> None:
+ """``Field._ty_schema`` is forwarded verbatim from ``TypeField.ty``.
+
+ For C++-backed fields ``TypeField.ty`` is typically ``None`` (only
+ populated by ``@py_class`` registration), so the helper should just
+ forward the value without fabricating a :class:`TypeSchema`.
+ """
+ type_info: TypeInfo = getattr(_TestCxxClassBase, "__tvm_ffi_type_info__")
+ for tf in type_info.fields:
+ assert tf.dataclass_field._ty_schema is tf.ty
+
+
+def test_c_class_field_type_resolved_from_annotation() -> None:
+ """``Field.type`` is the resolved Python annotation, not a TypeSchema."""
+ type_info: TypeInfo = getattr(_TestCxxClassBase, "__tvm_ffi_type_info__")
+ for tf in type_info.fields:
+ # _TestCxxClassBase annotates v_i64 / v_i32 as ``int``.
+ assert tf.dataclass_field.type is int
+
+
+def test_c_class_field_defaults_missing_when_unspecified() -> None:
+ """Fields with no C++ default retain ``MISSING`` on ``Field.default``.
+
+ ``_TestCxxClassBase.v_i64`` / ``v_i32`` are registered without
+ ``refl::default_value(...)``; the reflection layer should leave
+ ``c_default`` / ``c_default_factory`` as :data:`MISSING`.
+ """
+ type_info: TypeInfo = getattr(_TestCxxClassBase, "__tvm_ffi_type_info__")
+ for tf in type_info.fields:
+ assert tf.c_default is MISSING
+ assert tf.c_default_factory is MISSING
+ assert tf.dataclass_field.default is MISSING
+ assert tf.dataclass_field.default_factory is MISSING
+
+
+def test_c_class_field_default_value_from_cxx() -> None:
+ """C++ ``refl::default_value(...)`` is exposed on ``Field.default``.
+
+ ``_TestCxxClassDerived.v_f32`` registers ``refl::default_value(8.0f)``;
+ ``_TestCxxClassDerivedDerived.v_str`` registers
+ ``refl::default_value(String("default"))``. Both should round-trip
+ through ``TypeField.c_default`` / ``Field.default``.
+ """
+ derived_info: TypeInfo = getattr(_TestCxxClassDerived,
"__tvm_ffi_type_info__")
+ by_name = {tf.name: tf for tf in derived_info.fields}
+ # v_f64: no default → MISSING.
+ assert by_name["v_f64"].c_default is MISSING
+ assert by_name["v_f64"].dataclass_field.default is MISSING
+ # v_f32: C++ default 8.0f.
+ assert by_name["v_f32"].c_default == pytest.approx(8.0)
+ assert by_name["v_f32"].dataclass_field.default == pytest.approx(8.0)
+ assert by_name["v_f32"].c_default_factory is MISSING
+ assert by_name["v_f32"].dataclass_field.default_factory is MISSING
+
+ dd_info: TypeInfo = getattr(_TestCxxClassDerivedDerived,
"__tvm_ffi_type_info__")
+ dd_by_name = {tf.name: tf for tf in dd_info.fields}
+ assert dd_by_name["v_str"].c_default == "default"
+ assert dd_by_name["v_str"].dataclass_field.default == "default"
+ # v_bool has no default.
+ assert dd_by_name["v_bool"].c_default is MISSING
+
+
+def test_c_class_field_default_respects_init_false() -> None:
+ """Defaults are visible even when ``init=False`` (fields filled by C++)."""
+ subset_info: TypeInfo = getattr(_TestCxxInitSubset,
"__tvm_ffi_type_info__")
+ by_name = {tf.name: tf for tf in subset_info.fields}
+ # optional_field / note are init(false) + have C++ defaults.
+ assert by_name["optional_field"].c_default == -1
+ assert by_name["optional_field"].dataclass_field.default == -1
+ assert by_name["optional_field"].dataclass_field.init is False
+ assert by_name["note"].c_default == "default"
+ assert by_name["note"].dataclass_field.default == "default"
+ # required_field has no default.
+ assert by_name["required_field"].c_default is MISSING
+
+
+def test_c_class_field_default_respects_kw_only() -> None:
+ """Defaults are visible on kw_only fields too (``_TestCxxKwOnly.w``)."""
+ kw_info: TypeInfo = getattr(_TestCxxKwOnly, "__tvm_ffi_type_info__")
+ by_name = {tf.name: tf for tf in kw_info.fields}
+ # w is kw_only and has C++ default 100.
+ assert by_name["w"].c_default == 100
+ assert by_name["w"].dataclass_field.default == 100
+ assert by_name["w"].dataclass_field.kw_only is True
+ # x / y / z are kw_only without defaults.
+ for name in ("x", "y", "z"):
+ assert by_name[name].c_default is MISSING
+ assert by_name[name].dataclass_field.default is MISSING
+
+
+def test_c_class_field_frozen_matches_typefield() -> None:
+ """``Field.frozen`` tracks ``TypeField.frozen`` (TestIntPair fields are
frozen)."""
+ type_info: TypeInfo = getattr(tvm_ffi.testing.TestIntPair,
"__tvm_ffi_type_info__")
+ for tf in type_info.fields:
+ assert tf.dataclass_field.frozen == tf.frozen
+
+
+def test_c_class_field_init_and_kw_only_flags() -> None:
+ """``init`` / ``kw_only`` flags propagate from the reflection layer."""
+ # _TestCxxKwOnly has *all* fields kw-only.
+ kw_info: TypeInfo = getattr(_TestCxxKwOnly, "__tvm_ffi_type_info__")
+ for tf in kw_info.fields:
+ assert tf.dataclass_field.kw_only is True
+ assert tf.dataclass_field.init is True
+
+ # _TestCxxInitSubset has some fields with Init(false) (not in __init__).
+ subset_info: TypeInfo = getattr(_TestCxxInitSubset,
"__tvm_ffi_type_info__")
+ by_name = {tf.name: tf for tf in subset_info.fields}
+ assert by_name["required_field"].dataclass_field.init is True
+ # optional_field / note are marked Init(false) in C++.
+ assert by_name["optional_field"].dataclass_field.init is False
+ assert by_name["note"].dataclass_field.init is False
+
+
+def test_c_class_field_doc_matches_typefield() -> None:
+ """``Field.doc`` mirrors ``TypeField.doc``."""
+ type_info: TypeInfo = getattr(_TestCxxClassBase, "__tvm_ffi_type_info__")
+ for tf in type_info.fields:
+ assert tf.dataclass_field.doc == tf.doc
+
+
+def test_c_class_field_repr_flag_from_cxx() -> None:
+ """``refl::repr(false)`` on a C++ field propagates to ``Field.repr``.
+
+ ``_TestCxxClassBase`` registers both of its fields with
+ ``refl::repr(false)``; ``_TestCxxClassDerived`` registers its fields
+ without that modifier (default on).
+ """
+ base_info: TypeInfo = getattr(_TestCxxClassBase, "__tvm_ffi_type_info__")
+ for tf in base_info.fields:
+ assert tf.c_repr is False
+ assert tf.dataclass_field.repr is False
+ derived_info: TypeInfo = getattr(_TestCxxClassDerived,
"__tvm_ffi_type_info__")
+ for tf in derived_info.fields:
+ assert tf.c_repr is True
+ assert tf.dataclass_field.repr is True
+
+
+def test_c_class_field_compare_flag_from_cxx() -> None:
+ """``refl::compare(false)`` on a C++ field propagates to ``Field.compare``.
+
+ ``TestCompare.ignored_field`` is registered with ``refl::compare(false)``;
+ ``key`` and ``name`` are registered without it.
+ """
+ type_info: TypeInfo = getattr(TestCompare, "__tvm_ffi_type_info__")
+ by_name = {tf.name: tf for tf in type_info.fields}
+ assert by_name["key"].c_compare is True
+ assert by_name["key"].dataclass_field.compare is True
+ assert by_name["name"].c_compare is True
+ assert by_name["name"].dataclass_field.compare is True
+ assert by_name["ignored_field"].c_compare is False
+ assert by_name["ignored_field"].dataclass_field.compare is False
+
+
+def test_c_class_field_hash_flag_from_cxx() -> None:
+ """``refl::hash(false)`` on a C++ field propagates to ``Field.hash``.
+
+ ``TestHash.hash_ignored`` is registered with ``refl::hash(false)``;
+ ``key`` and ``name`` are registered without it.
+ """
+ type_info: TypeInfo = getattr(TestHash, "__tvm_ffi_type_info__")
+ by_name = {tf.name: tf for tf in type_info.fields}
+ assert by_name["key"].c_hash is True
+ assert by_name["key"].dataclass_field.hash is True
+ assert by_name["name"].c_hash is True
+ assert by_name["name"].dataclass_field.hash is True
+ assert by_name["hash_ignored"].c_hash is False
+ assert by_name["hash_ignored"].dataclass_field.hash is False
+
+
+def test_c_class_field_structural_eq_default_none() -> None:
+ """Fields registered without ``s_eq_hash_def`` / ``s_eq_hash_ignore`` stay
``None``.
+
+ None of the current C++ testing fixtures set structural_eq flags on a
+ field, so we verify the default (``None``) is preserved across the
+ reflection boundary.
+ """
+ for cls in (_TestCxxClassBase, _TestCxxClassDerived, TestCompare,
TestHash):
+ type_info: TypeInfo = getattr(cls, "__tvm_ffi_type_info__")
+ for tf in type_info.fields:
+ assert tf.c_structural_eq is None
+ assert tf.dataclass_field.structural_eq is None
+
+
+def test_c_class_attaches_only_own_fields_not_inherited() -> None:
+ """Only own fields get a Field; parent fields are attached on the parent's
TypeInfo."""
+ derived_info: TypeInfo = getattr(_TestCxxClassDerived,
"__tvm_ffi_type_info__")
+ own_names = {tf.name for tf in derived_info.fields}
+ # Derived owns v_f64 / v_f32; parent owns v_i64 / v_i32.
+ assert own_names == {"v_f64", "v_f32"}
+ for tf in derived_info.fields:
+ assert isinstance(tf.dataclass_field, Field)
+ # Parent TypeInfo already had Field objects attached when it was decorated.
+ parent_info = derived_info.parent_type_info
+ assert parent_info is not None
+ for tf in parent_info.fields:
+ assert isinstance(tf.dataclass_field, Field)
+
+
+def test_c_class_field_type_none_when_annotation_missing() -> None:
+ """Fields without a Python annotation get ``Field.type == None``."""
+ # Re-run _attach_field_objects on a bare class (no annotations) — it must
+ # not raise and should leave every Field.type == None.
+ type_info: TypeInfo = getattr(_TestCxxClassBase, "__tvm_ffi_type_info__")
+ original = [tf.dataclass_field for tf in type_info.fields]
+ bare = type("BareCls", (), {})
+ try:
+ _attach_field_objects(bare, type_info)
+ for tf in type_info.fields:
+ assert tf.dataclass_field.type is None
+ finally:
+ # Restore so later tests still see the annotated Field.type.
+ for tf, saved in zip(type_info.fields, original):
+ tf.dataclass_field = saved
+
+
+def test_c_class_attaches_field_for_new_decoration() -> None:
+ """A freshly decorated @c_class type has its own Field objects."""
+ # We don't create a new C++ type here — just re-run the decorator machinery
+ # with a duplicate key-aware workflow isn't necessary. Simply assert that
+ # the existing decorated classes all expose populated dataclass_field.
+ for cls in (_TestCxxClassBase, _TestCxxClassDerived,
_TestCxxClassDerivedDerived):
+ ti: TypeInfo = getattr(cls, "__tvm_ffi_type_info__")
+ for tf in ti.fields:
+ assert tf.dataclass_field is not None
+ assert tf.dataclass_field.name == tf.name
+
+
+def test_c_class_attach_is_idempotent() -> None:
+ """Calling ``_attach_field_objects`` twice replaces Fields without
error."""
+ type_info: TypeInfo = getattr(_TestCxxClassBase, "__tvm_ffi_type_info__")
+ original = [tf.dataclass_field for tf in type_info.fields]
+ try:
+ _attach_field_objects(_TestCxxClassBase, type_info)
+ for tf in type_info.fields:
+ assert isinstance(tf.dataclass_field, Field)
+ assert tf.dataclass_field.type is int
+ finally:
+ for tf, saved in zip(type_info.fields, original):
+ tf.dataclass_field = saved
+
+
+def test_c_class_attach_tolerates_unresolvable_hints() -> None:
+ """``typing.get_type_hints`` exceptions fall back silently; ``Field.type``
stays None."""
+
+ # Declaring a type with a string annotation that points at an undefined
name
+ # makes get_type_hints raise NameError. The helper must swallow it.
+ class BrokenAnn:
+ x: ThisNameIsNeverDefined # ty: ignore[unresolved-reference] # noqa:
F821
+
+ type_info: TypeInfo = getattr(_TestCxxClassBase, "__tvm_ffi_type_info__")
+ original = [tf.dataclass_field for tf in type_info.fields]
+ try:
+ _attach_field_objects(BrokenAnn, type_info)
+ for tf in type_info.fields:
+ assert tf.dataclass_field.type is None
+ finally:
+ for tf, saved in zip(type_info.fields, original):
+ tf.dataclass_field = saved
diff --git a/tests/python/test_dataclass_common.py
b/tests/python/test_dataclass_common.py
new file mode 100644
index 0000000..95b860d
--- /dev/null
+++ b/tests/python/test_dataclass_common.py
@@ -0,0 +1,252 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+# ruff: noqa: D102, UP006, UP045
+"""Tests for :func:`tvm_ffi.dataclasses.is_dataclass`, :func:`fields`,
:func:`replace`."""
+
+from __future__ import annotations
+
+import dataclasses as _dc
+import itertools
+import typing
+from typing import List, Optional
+
+import pytest
+import tvm_ffi
+import tvm_ffi.testing
+from tvm_ffi.core import MISSING, Object
+from tvm_ffi.dataclasses import (
+ Field,
+ field,
+ fields,
+ is_dataclass,
+ py_class,
+ replace,
+)
+
+_counter = itertools.count()
+
+
+def _k(base: str) -> str:
+ return f"testing.compat.{base}_{next(_counter)}"
+
+
+# ---------------------------------------------------------------------------
+# is_dataclass
+# ---------------------------------------------------------------------------
+class TestIsDataclass:
+ def test_c_class_instance(self) -> None:
+ p = tvm_ffi.testing.TestIntPair(1, 2)
+ assert is_dataclass(p) is True
+
+ def test_c_class_type(self) -> None:
+ assert is_dataclass(tvm_ffi.testing.TestIntPair) is True
+
+ def test_py_class_instance(self) -> None:
+ @py_class(_k("PC"))
+ class PC(Object):
+ x: int
+
+ assert is_dataclass(PC(x=1)) is True
+
+ def test_py_class_type(self) -> None:
+ @py_class(_k("PCT"))
+ class PCT(Object):
+ x: int
+
+ assert is_dataclass(PCT) is True
+
+ def test_non_dataclass_types(self) -> None:
+ assert is_dataclass(42) is False
+ assert is_dataclass("hi") is False
+ assert is_dataclass(object()) is False
+ assert is_dataclass(int) is False
+
+ def test_plain_object_subclass(self) -> None:
+ """Object subclass without @py_class / @c_class shouldn't qualify."""
+
+ class Plain(Object):
+ pass
+
+ assert is_dataclass(Plain) is False
+
+ def test_stdlib_dataclass_not_recognised(self) -> None:
+ """``common.is_dataclass`` only recognises FFI types."""
+
+ @_dc.dataclass
+ class D:
+ x: int
+
+ assert is_dataclass(D) is False
+ assert is_dataclass(D(1)) is False
+
+
+# ---------------------------------------------------------------------------
+# fields
+# ---------------------------------------------------------------------------
+class TestFields:
+ def test_returns_tuple_of_field(self) -> None:
+ p = tvm_ffi.testing.TestIntPair(1, 2)
+ result = fields(p)
+ assert isinstance(result, tuple)
+ assert all(isinstance(f, Field) for f in result)
+
+ def test_c_class_basic(self) -> None:
+ fs = fields(tvm_ffi.testing.TestIntPair)
+ assert [f.name for f in fs] == ["a", "b"]
+ assert all(f.type is int for f in fs)
+ # TestIntPair registers no defaults; the Field descriptors reflect
that.
+ assert all(f.default is MISSING for f in fs)
+
+ def test_c_class_default_value_from_cxx(self) -> None:
+ """``fields()`` exposes ``refl::default_value(...)`` from C++.
+
+ ``_TestCxxClassDerived.v_f32`` has ``refl::default_value(8.0f)``.
+ """
+ fs = fields(tvm_ffi.testing._TestCxxClassDerived)
+ by_name = {f.name: f for f in fs}
+ assert by_name["v_f32"].default == pytest.approx(8.0)
+ assert by_name["v_f32"].default_factory is MISSING
+ # v_f64 has no C++ default.
+ assert by_name["v_f64"].default is MISSING
+
+ def test_c_class_instance_and_type_equivalent(self) -> None:
+ p = tvm_ffi.testing.TestIntPair(1, 2)
+ assert fields(p) == fields(tvm_ffi.testing.TestIntPair)
+
+ def test_py_class_with_default(self) -> None:
+ @py_class(_k("PCDef"))
+ class PCDef(Object):
+ x: int
+ y: str = field(default="hi")
+
+ fs = fields(PCDef)
+ names = [f.name for f in fs]
+ assert names == ["x", "y"]
+ (fx, fy) = fs
+ assert fx.default is MISSING
+ assert fx.default_factory is MISSING
+ assert fy.default == "hi"
+ assert fy.default_factory is MISSING
+
+ def test_py_class_with_default_factory(self) -> None:
+ @py_class(_k("PCFact"))
+ class PCFact(Object):
+ items: List[int] = field(default_factory=list)
+
+ (f,) = fields(PCFact)
+ assert f.default is MISSING
+ assert f.default_factory is list
+
+ def test_py_class_optional_type_resolved(self) -> None:
+ @py_class(_k("PCOpt"))
+ class PCOpt(Object):
+ v: Optional[int] = field(default=None)
+
+ (f,) = fields(PCOpt)
+ # Depending on the Python version, get_type_hints may preserve
+ # Optional[int] or normalize it to Union[int, None]; inspect the
+ # type's args instead of relying on str() formatting.
+ args = typing.get_args(f.type)
+ assert int in args and type(None) in args
+ assert f.default is None
+
+ def test_inheritance_parent_first(self) -> None:
+ @py_class(_k("PCP"))
+ class P(Object):
+ x: int
+ y: str = field(default="p")
+
+ @py_class(_k("PCC"))
+ class C(P):
+ z: float = field(default=1.5)
+
+ assert [f.name for f in fields(C)] == ["x", "y", "z"]
+ assert fields(C)[2].default == 1.5
+
+ def test_c_class_inheritance(self) -> None:
+ """@c_class derived types also walk the parent chain."""
+ fs = fields(tvm_ffi.testing._TestCxxClassDerived)
+ # Parent v_i64/v_i32 come before child v_f64/v_f32.
+ assert [f.name for f in fs] == ["v_i64", "v_i32", "v_f64", "v_f32"]
+
+ def test_non_dataclass_raises(self) -> None:
+ with pytest.raises(TypeError, match="c_class or py_class"):
+ fields(42)
+
+ def test_field_attribute_access(self) -> None:
+ """Returned Field descriptors expose ``name`` / ``type`` /
``default``."""
+ (fa, _) = fields(tvm_ffi.testing.TestIntPair)
+ assert fa.name == "a"
+ assert fa.type is int
+ assert fa.default is MISSING
+
+
+# ---------------------------------------------------------------------------
+# replace
+# ---------------------------------------------------------------------------
+class TestReplace:
+ def test_c_class_single_field(self) -> None:
+ p = tvm_ffi.testing.TestIntPair(1, 2)
+ p2 = replace(p, a=10)
+ assert (p2.a, p2.b) == (10, 2)
+ assert (p.a, p.b) == (1, 2) # original unchanged
+ assert not p.same_as(p2)
+
+ def test_c_class_multiple_fields(self) -> None:
+ p = tvm_ffi.testing.TestIntPair(1, 2)
+ p2 = replace(p, a=7, b=8)
+ assert (p2.a, p2.b) == (7, 8)
+
+ def test_c_class_no_changes_is_copy(self) -> None:
+ p = tvm_ffi.testing.TestIntPair(1, 2)
+ p2 = replace(p)
+ assert (p2.a, p2.b) == (1, 2)
+ assert not p.same_as(p2)
+
+ def test_py_class_basic(self) -> None:
+ @py_class(_k("PCR"))
+ class PCR(Object):
+ x: int = field(default=5)
+ y: str = field(default="hi")
+
+ obj = PCR()
+ obj2 = replace(obj, x=99)
+ assert obj2.x == 99
+ assert obj2.y == "hi"
+ assert obj.x == 5 # original unchanged
+
+ def test_py_class_inherited_fields(self) -> None:
+ @py_class(_k("PCRParent"))
+ class P(Object):
+ x: int
+
+ @py_class(_k("PCRChild"))
+ class C(P):
+ y: str
+
+ obj = C(x=1, y="a")
+ obj2 = replace(obj, x=99)
+ assert obj2.x == 99
+ assert obj2.y == "a"
+
+ def test_replace_frozen_field(self) -> None:
+ """replace() goes through __replace__, which uses the frozen-bypass
setter."""
+ # TestIntPair.a and .b are read-only (frozen c_class fields).
+ p = tvm_ffi.testing.TestIntPair(3, 4)
+ p2 = replace(p, a=10)
+ assert p2.a == 10
+ assert p.a == 3 # still read-only, original untouched
diff --git a/tests/python/test_dataclass_py_class.py
b/tests/python/test_dataclass_py_class.py
index 0b9d7db..04f8773 100644
--- a/tests/python/test_dataclass_py_class.py
+++ b/tests/python/test_dataclass_py_class.py
@@ -1298,7 +1298,7 @@ class TestRegisterPyClass:
type_key = _unique_key_ff("DupPreserve")
cls1 = type("DupPreserve1", (core.Object,), {"__slots__": ()})
info1 = core._register_py_class(parent_info, type_key, cls1)
- info1._register_fields([Field(name="x", ty=TypeSchema("int"))])
+ info1._register_fields([Field(name="x", _ty_schema=TypeSchema("int"))])
setattr(cls1, "__tvm_ffi_type_info__", info1)
_add_class_attrs(cls1, info1)
@@ -1320,7 +1320,7 @@ class TestFieldRegistration:
def test_int_field_registered(self) -> None:
cls = _make_type(
"FldInt",
- [Field(name="x", ty=TypeSchema("int"), default=MISSING)],
+ [Field(name="x", _ty_schema=TypeSchema("int"), default=MISSING)],
)
info = getattr(cls, "__tvm_ffi_type_info__")
assert len(info.fields) == 1
@@ -1329,7 +1329,7 @@ class TestFieldRegistration:
def test_float_field_registered(self) -> None:
cls = _make_type(
"FldFloat",
- [Field(name="val", ty=TypeSchema("float"), default=0.0)],
+ [Field(name="val", _ty_schema=TypeSchema("float"), default=0.0)],
)
info = getattr(cls, "__tvm_ffi_type_info__")
assert info.fields[0].name == "val"
@@ -1337,7 +1337,7 @@ class TestFieldRegistration:
def test_str_field_registered(self) -> None:
cls = _make_type(
"FldStr",
- [Field(name="s", ty=TypeSchema("str"), default="hello")],
+ [Field(name="s", _ty_schema=TypeSchema("str"), default="hello")],
)
info = getattr(cls, "__tvm_ffi_type_info__")
assert info.fields[0].name == "s"
@@ -1345,7 +1345,7 @@ class TestFieldRegistration:
def test_bool_field_registered(self) -> None:
cls = _make_type(
"FldBool",
- [Field(name="flag", ty=TypeSchema("bool"), default=False)],
+ [Field(name="flag", _ty_schema=TypeSchema("bool"), default=False)],
)
info = getattr(cls, "__tvm_ffi_type_info__")
assert info.fields[0].name == "flag"
@@ -1354,9 +1354,9 @@ class TestFieldRegistration:
cls = _make_type(
"FldMulti",
[
- Field(name="a", ty=TypeSchema("int"), default=MISSING),
- Field(name="b", ty=TypeSchema("float"), default=0.0),
- Field(name="c", ty=TypeSchema("str"), default="x"),
+ Field(name="a", _ty_schema=TypeSchema("int"), default=MISSING),
+ Field(name="b", _ty_schema=TypeSchema("float"), default=0.0),
+ Field(name="c", _ty_schema=TypeSchema("str"), default="x"),
],
)
info = getattr(cls, "__tvm_ffi_type_info__")
@@ -1367,9 +1367,9 @@ class TestFieldRegistration:
cls = _make_type(
"FldOff",
[
- Field(name="a", ty=TypeSchema("int"), default=MISSING),
- Field(name="b", ty=TypeSchema("float"), default=MISSING),
- Field(name="c", ty=TypeSchema("str"), default=MISSING),
+ Field(name="a", _ty_schema=TypeSchema("int"), default=MISSING),
+ Field(name="b", _ty_schema=TypeSchema("float"),
default=MISSING),
+ Field(name="c", _ty_schema=TypeSchema("str"), default=MISSING),
],
)
info = getattr(cls, "__tvm_ffi_type_info__")
@@ -1380,7 +1380,7 @@ class TestFieldRegistration:
def test_ffi_init_method_registered(self) -> None:
cls = _make_type(
"FldInit",
- [Field(name="x", ty=TypeSchema("int"), default=MISSING)],
+ [Field(name="x", _ty_schema=TypeSchema("int"), default=MISSING)],
)
info = getattr(cls, "__tvm_ffi_type_info__")
ffi_init = core._lookup_type_attr(info.type_index, "__ffi_init__")
@@ -1392,13 +1392,13 @@ class TestFieldRegistration:
[
Field(
name="visible",
- ty=TypeSchema("int"),
+ _ty_schema=TypeSchema("int"),
default=MISSING,
repr=True,
),
Field(
name="hidden",
- ty=TypeSchema("int"),
+ _ty_schema=TypeSchema("int"),
default=0,
repr=False,
),
@@ -1415,20 +1415,20 @@ class TestFieldDescriptor:
"""Field class: validation, defaults, default_factory checks."""
def test_compare_default_is_false(self) -> None:
- f = Field(name="x", ty=TypeSchema("int"))
+ f = Field(name="x", _ty_schema=TypeSchema("int"))
assert f.compare is False
def test_default_and_factory_mutually_exclusive(self) -> None:
with pytest.raises(ValueError):
- Field(name="x", ty=TypeSchema("int"), default=0,
default_factory=lambda: 0)
+ Field(name="x", _ty_schema=TypeSchema("int"), default=0,
default_factory=lambda: 0)
def test_factory_must_be_callable(self) -> None:
with pytest.raises(TypeError, match="callable"):
- Field(name="x", ty=TypeSchema("int"), default_factory=0) # ty:
ignore[invalid-argument-type]
+ Field(name="x", _ty_schema=TypeSchema("int"), default_factory=0)
# ty: ignore[invalid-argument-type]
def test_non_callable_factory_rejected(self) -> None:
with pytest.raises(TypeError, match="callable"):
- Field(name="x", ty=TypeSchema("int"),
default_factory="not_callable") # ty: ignore[invalid-argument-type]
+ Field(name="x", _ty_schema=TypeSchema("int"),
default_factory="not_callable") # ty: ignore[invalid-argument-type]
# ###########################################################################
@@ -1441,8 +1441,8 @@ class TestConstruction:
Cls = _make_type(
"ConKw",
[
- Field(name="x", ty=TypeSchema("int"), default=MISSING),
- Field(name="y", ty=TypeSchema("float"), default=MISSING),
+ Field(name="x", _ty_schema=TypeSchema("int"), default=MISSING),
+ Field(name="y", _ty_schema=TypeSchema("float"),
default=MISSING),
],
)
obj = Cls(x=42, y=3.14)
@@ -1453,8 +1453,8 @@ class TestConstruction:
Cls = _make_type(
"ConPos",
[
- Field(name="x", ty=TypeSchema("int"), default=MISSING),
- Field(name="y", ty=TypeSchema("float"), default=MISSING),
+ Field(name="x", _ty_schema=TypeSchema("int"), default=MISSING),
+ Field(name="y", _ty_schema=TypeSchema("float"),
default=MISSING),
],
)
obj = Cls(10, 2.5)
@@ -1465,8 +1465,8 @@ class TestConstruction:
Cls = _make_type(
"ConMixed",
[
- Field(name="x", ty=TypeSchema("int"), default=MISSING),
- Field(name="y", ty=TypeSchema("float"), default=MISSING),
+ Field(name="x", _ty_schema=TypeSchema("int"), default=MISSING),
+ Field(name="y", _ty_schema=TypeSchema("float"),
default=MISSING),
],
)
obj = Cls(7, y=1.5)
@@ -1476,28 +1476,28 @@ class TestConstruction:
def test_default_value_int(self) -> None:
Cls = _make_type(
"ConDefInt",
- [Field(name="x", ty=TypeSchema("int"), default=99)],
+ [Field(name="x", _ty_schema=TypeSchema("int"), default=99)],
)
assert Cls().x == 99
def test_default_value_float(self) -> None:
Cls = _make_type(
"ConDefFloat",
- [Field(name="x", ty=TypeSchema("float"), default=1.5)],
+ [Field(name="x", _ty_schema=TypeSchema("float"), default=1.5)],
)
assert Cls().x == pytest.approx(1.5)
def test_default_value_str(self) -> None:
Cls = _make_type(
"ConDefStr",
- [Field(name="s", ty=TypeSchema("str"), default="hello")],
+ [Field(name="s", _ty_schema=TypeSchema("str"), default="hello")],
)
assert Cls().s == "hello"
def test_override_default(self) -> None:
Cls = _make_type(
"ConOverride",
- [Field(name="x", ty=TypeSchema("int"), default=0)],
+ [Field(name="x", _ty_schema=TypeSchema("int"), default=0)],
)
assert Cls(x=42).x == 42
@@ -1505,8 +1505,8 @@ class TestConstruction:
Cls = _make_type(
"ConReqOpt",
[
- Field(name="required", ty=TypeSchema("int"), default=MISSING),
- Field(name="optional", ty=TypeSchema("float"), default=0.0),
+ Field(name="required", _ty_schema=TypeSchema("int"),
default=MISSING),
+ Field(name="optional", _ty_schema=TypeSchema("float"),
default=0.0),
],
)
obj = Cls(required=5)
@@ -1516,7 +1516,7 @@ class TestConstruction:
def test_missing_required_raises(self) -> None:
Cls = _make_type(
"ConMissing",
- [Field(name="x", ty=TypeSchema("int"), default=MISSING)],
+ [Field(name="x", _ty_schema=TypeSchema("int"), default=MISSING)],
)
with pytest.raises(TypeError):
Cls()
@@ -1524,7 +1524,7 @@ class TestConstruction:
def test_extra_kwarg_raises(self) -> None:
Cls = _make_type(
"ConExtra",
- [Field(name="x", ty=TypeSchema("int"), default=MISSING)],
+ [Field(name="x", _ty_schema=TypeSchema("int"), default=MISSING)],
)
with pytest.raises(TypeError):
Cls(x=1, bogus=2)
@@ -1532,14 +1532,14 @@ class TestConstruction:
def test_str_field_construction(self) -> None:
Cls = _make_type(
"ConStr",
- [Field(name="name", ty=TypeSchema("str"), default=MISSING)],
+ [Field(name="name", _ty_schema=TypeSchema("str"),
default=MISSING)],
)
assert Cls(name="world").name == "world"
def test_bool_field_construction(self) -> None:
Cls = _make_type(
"ConBool",
- [Field(name="flag", ty=TypeSchema("bool"), default=MISSING)],
+ [Field(name="flag", _ty_schema=TypeSchema("bool"),
default=MISSING)],
)
assert Cls(flag=True).flag is True
assert Cls(flag=False).flag is False
@@ -1548,10 +1548,10 @@ class TestConstruction:
Cls = _make_type(
"ConKwOnly",
[
- Field(name="x", ty=TypeSchema("int"), default=MISSING),
+ Field(name="x", _ty_schema=TypeSchema("int"), default=MISSING),
Field(
name="y",
- ty=TypeSchema("int"),
+ _ty_schema=TypeSchema("int"),
default=MISSING,
kw_only=True,
),
@@ -1565,10 +1565,10 @@ class TestConstruction:
Cls = _make_type(
"ConKwOnlyReject",
[
- Field(name="x", ty=TypeSchema("int"), default=MISSING),
+ Field(name="x", _ty_schema=TypeSchema("int"), default=MISSING),
Field(
name="y",
- ty=TypeSchema("int"),
+ _ty_schema=TypeSchema("int"),
default=MISSING,
kw_only=True,
),
@@ -1580,7 +1580,7 @@ class TestConstruction:
def test_isinstance_check(self) -> None:
Cls = _make_type(
"ConIsInstance",
- [Field(name="x", ty=TypeSchema("int"), default=MISSING)],
+ [Field(name="x", _ty_schema=TypeSchema("int"), default=MISSING)],
)
obj = Cls(x=1)
assert isinstance(obj, Cls)
@@ -1596,14 +1596,14 @@ class TestGetterSetter:
def test_get_int(self) -> None:
Cls = _make_type(
"GSInt",
- [Field(name="x", ty=TypeSchema("int"), default=MISSING)],
+ [Field(name="x", _ty_schema=TypeSchema("int"), default=MISSING)],
)
assert Cls(x=42).x == 42
def test_set_int(self) -> None:
Cls = _make_type(
"GSSetInt",
- [Field(name="x", ty=TypeSchema("int"), default=MISSING)],
+ [Field(name="x", _ty_schema=TypeSchema("int"), default=MISSING)],
)
obj = Cls(x=1)
obj.x = 100
@@ -1612,14 +1612,14 @@ class TestGetterSetter:
def test_get_float(self) -> None:
Cls = _make_type(
"GSFloat",
- [Field(name="val", ty=TypeSchema("float"), default=MISSING)],
+ [Field(name="val", _ty_schema=TypeSchema("float"),
default=MISSING)],
)
assert Cls(val=3.14).val == pytest.approx(3.14)
def test_set_float(self) -> None:
Cls = _make_type(
"GSSetFloat",
- [Field(name="val", ty=TypeSchema("float"), default=MISSING)],
+ [Field(name="val", _ty_schema=TypeSchema("float"),
default=MISSING)],
)
obj = Cls(val=1.0)
obj.val = 2.718
@@ -1628,14 +1628,14 @@ class TestGetterSetter:
def test_get_str(self) -> None:
Cls = _make_type(
"GSStr",
- [Field(name="s", ty=TypeSchema("str"), default=MISSING)],
+ [Field(name="s", _ty_schema=TypeSchema("str"), default=MISSING)],
)
assert Cls(s="hello").s == "hello"
def test_set_str(self) -> None:
Cls = _make_type(
"GSSetStr",
- [Field(name="s", ty=TypeSchema("str"), default=MISSING)],
+ [Field(name="s", _ty_schema=TypeSchema("str"), default=MISSING)],
)
obj = Cls(s="hello")
obj.s = "world"
@@ -1644,14 +1644,14 @@ class TestGetterSetter:
def test_get_bool(self) -> None:
Cls = _make_type(
"GSBool",
- [Field(name="flag", ty=TypeSchema("bool"), default=MISSING)],
+ [Field(name="flag", _ty_schema=TypeSchema("bool"),
default=MISSING)],
)
assert Cls(flag=True).flag is True
def test_set_bool(self) -> None:
Cls = _make_type(
"GSSetBool",
- [Field(name="flag", ty=TypeSchema("bool"), default=MISSING)],
+ [Field(name="flag", _ty_schema=TypeSchema("bool"),
default=MISSING)],
)
obj = Cls(flag=True)
obj.flag = False
@@ -1660,7 +1660,7 @@ class TestGetterSetter:
def test_mutation_isolated(self) -> None:
Cls = _make_type(
"GSIsolate",
- [Field(name="x", ty=TypeSchema("int"), default=MISSING)],
+ [Field(name="x", _ty_schema=TypeSchema("int"), default=MISSING)],
)
a = Cls(x=1)
b = Cls(x=1)
@@ -1672,9 +1672,9 @@ class TestGetterSetter:
Cls = _make_type(
"GSMultiMut",
[
- Field(name="a", ty=TypeSchema("int"), default=MISSING),
- Field(name="b", ty=TypeSchema("float"), default=MISSING),
- Field(name="c", ty=TypeSchema("str"), default=MISSING),
+ Field(name="a", _ty_schema=TypeSchema("int"), default=MISSING),
+ Field(name="b", _ty_schema=TypeSchema("float"),
default=MISSING),
+ Field(name="c", _ty_schema=TypeSchema("str"), default=MISSING),
],
)
obj = Cls(a=1, b=2.0, c="x")
@@ -1691,7 +1691,7 @@ class TestGetterSetter:
[
Field(
name="arr",
- ty=TypeSchema("Array", (TypeSchema("int"),)),
+ _ty_schema=TypeSchema("Array", (TypeSchema("int"),)),
default=MISSING,
),
],
@@ -1714,7 +1714,7 @@ class TestObjectRefFields:
[
Field(
name="arr",
- ty=TypeSchema("Array", (TypeSchema("int"),)),
+ _ty_schema=TypeSchema("Array", (TypeSchema("int"),)),
default=MISSING,
),
],
@@ -1730,7 +1730,7 @@ class TestObjectRefFields:
[
Field(
name="arr",
- ty=TypeSchema("Array", (TypeSchema("int"),)),
+ _ty_schema=TypeSchema("Array", (TypeSchema("int"),)),
default=MISSING,
),
],
@@ -1742,13 +1742,13 @@ class TestObjectRefFields:
def test_nested_object_field(self) -> None:
Inner = _make_type(
"ObjInner",
- [Field(name="val", ty=TypeSchema("int"), default=MISSING)],
+ [Field(name="val", _ty_schema=TypeSchema("int"), default=MISSING)],
)
inner_info = getattr(Inner, "__tvm_ffi_type_info__")
inner_schema = TypeSchema(inner_info.type_key,
origin_type_index=inner_info.type_index)
Outer = _make_type(
"ObjOuter",
- [Field(name="child", ty=inner_schema, default=MISSING)],
+ [Field(name="child", _ty_schema=inner_schema, default=MISSING)],
)
assert Outer(child=Inner(val=42)).child.val == 42
@@ -1765,7 +1765,7 @@ class TestOptionalFields:
[
Field(
name="x",
- ty=TypeSchema("Optional", (TypeSchema("int"),)),
+ _ty_schema=TypeSchema("Optional", (TypeSchema("int"),)),
default=MISSING,
),
],
@@ -1778,7 +1778,7 @@ class TestOptionalFields:
[
Field(
name="x",
- ty=TypeSchema("Optional", (TypeSchema("int"),)),
+ _ty_schema=TypeSchema("Optional", (TypeSchema("int"),)),
default=None,
),
],
@@ -1791,7 +1791,7 @@ class TestOptionalFields:
[
Field(
name="s",
- ty=TypeSchema("Optional", (TypeSchema("str"),)),
+ _ty_schema=TypeSchema("Optional", (TypeSchema("str"),)),
default=MISSING,
),
],
@@ -1804,7 +1804,7 @@ class TestOptionalFields:
[
Field(
name="s",
- ty=TypeSchema("Optional", (TypeSchema("str"),)),
+ _ty_schema=TypeSchema("Optional", (TypeSchema("str"),)),
default=None,
),
],
@@ -1817,7 +1817,7 @@ class TestOptionalFields:
[
Field(
name="x",
- ty=TypeSchema("Optional", (TypeSchema("int"),)),
+ _ty_schema=TypeSchema("Optional", (TypeSchema("int"),)),
default=MISSING,
),
],
@@ -1832,7 +1832,7 @@ class TestOptionalFields:
[
Field(
name="x",
- ty=TypeSchema("Optional", (TypeSchema("int"),)),
+ _ty_schema=TypeSchema("Optional", (TypeSchema("int"),)),
default=None,
),
],
@@ -1847,17 +1847,17 @@ class TestOptionalFields:
[
Field(
name="a",
- ty=TypeSchema("Optional", (TypeSchema("int"),)),
+ _ty_schema=TypeSchema("Optional", (TypeSchema("int"),)),
default=None,
),
Field(
name="b",
- ty=TypeSchema("Optional", (TypeSchema("str"),)),
+ _ty_schema=TypeSchema("Optional", (TypeSchema("str"),)),
default=None,
),
Field(
name="c",
- ty=TypeSchema("Optional", (TypeSchema("float"),)),
+ _ty_schema=TypeSchema("Optional", (TypeSchema("float"),)),
default=None,
),
],
@@ -1877,7 +1877,7 @@ class TestOptionalFields:
[
Field(
name="ref",
- ty=TypeSchema("Optional", (TypeSchema("Object"),)),
+ _ty_schema=TypeSchema("Optional", (TypeSchema("Object"),)),
default=None,
),
],
@@ -1896,7 +1896,7 @@ class TestOptionalFields:
[
Field(
name="val",
- ty=TypeSchema("Union", (TypeSchema("int"),
TypeSchema("str"))),
+ _ty_schema=TypeSchema("Union", (TypeSchema("int"),
TypeSchema("str"))),
default=MISSING,
),
],
@@ -1913,7 +1913,7 @@ class TestOptionalFields:
[
Field(
name="val",
- ty=TypeSchema("Union", (TypeSchema("int"),
TypeSchema("str"))),
+ _ty_schema=TypeSchema("Union", (TypeSchema("int"),
TypeSchema("str"))),
default=MISSING,
),
],
@@ -1929,7 +1929,7 @@ class TestOptionalFields:
[
Field(
name="val",
- ty=TypeSchema(
+ _ty_schema=TypeSchema(
"Optional",
(TypeSchema("Union", (TypeSchema("int"),
TypeSchema("str"))),),
),
@@ -1956,28 +1956,28 @@ class TestAnyField:
def test_any_holds_int(self) -> None:
Cls = _make_type(
"AnyI",
- [Field(name="val", ty=TypeSchema("Any"), default=None)],
+ [Field(name="val", _ty_schema=TypeSchema("Any"), default=None)],
)
assert Cls(val=42).val == 42
def test_any_holds_str(self) -> None:
Cls = _make_type(
"AnyS",
- [Field(name="val", ty=TypeSchema("Any"), default=None)],
+ [Field(name="val", _ty_schema=TypeSchema("Any"), default=None)],
)
assert Cls(val="hello").val == "hello"
def test_any_holds_none(self) -> None:
Cls = _make_type(
"AnyN",
- [Field(name="val", ty=TypeSchema("Any"), default=None)],
+ [Field(name="val", _ty_schema=TypeSchema("Any"), default=None)],
)
assert Cls().val is None
def test_any_holds_object(self) -> None:
Cls = _make_type(
"AnyObj",
- [Field(name="val", ty=TypeSchema("Any"), default=None)],
+ [Field(name="val", _ty_schema=TypeSchema("Any"), default=None)],
)
arr = tvm_ffi.Array([1, 2])
assert len(Cls(val=arr).val) == 2
@@ -1985,7 +1985,7 @@ class TestAnyField:
def test_any_type_change(self) -> None:
Cls = _make_type(
"AnyChg",
- [Field(name="val", ty=TypeSchema("Any"), default=None)],
+ [Field(name="val", _ty_schema=TypeSchema("Any"), default=None)],
)
obj = Cls()
obj.val = 42
@@ -2010,7 +2010,7 @@ class TestDefaultFactory:
[
Field(
name="data",
- ty=TypeSchema("Array", (TypeSchema("int"),)),
+ _ty_schema=TypeSchema("Array", (TypeSchema("int"),)),
default_factory=lambda: tvm_ffi.Array([]),
),
],
@@ -2025,7 +2025,7 @@ class TestDefaultFactory:
[
Field(
name="items",
- ty=TypeSchema("Array", (TypeSchema("int"),)),
+ _ty_schema=TypeSchema("Array", (TypeSchema("int"),)),
default_factory=lambda: tvm_ffi.Array([1, 2, 3]),
),
],
@@ -2037,14 +2037,14 @@ class TestDefaultFactory:
def test_factory_override(self) -> None:
Cls = _make_type(
"DFOverride",
- [Field(name="x", ty=TypeSchema("int"), default_factory=lambda:
42)],
+ [Field(name="x", _ty_schema=TypeSchema("int"),
default_factory=lambda: 42)],
)
assert Cls(x=99).x == 99
def test_factory_str(self) -> None:
Cls = _make_type(
"DFStr",
- [Field(name="s", ty=TypeSchema("str"), default_factory=lambda:
"generated")],
+ [Field(name="s", _ty_schema=TypeSchema("str"),
default_factory=lambda: "generated")],
)
assert Cls().s == "generated"
@@ -2059,8 +2059,8 @@ class TestFieldRepr:
Cls = _make_type(
"ReprBasic",
[
- Field(name="x", ty=TypeSchema("int"), default=MISSING),
- Field(name="y", ty=TypeSchema("float"), default=0.0),
+ Field(name="x", _ty_schema=TypeSchema("int"), default=MISSING),
+ Field(name="y", _ty_schema=TypeSchema("float"), default=0.0),
],
)
r = ReprPrint(Cls(x=42, y=3.14))
@@ -2070,14 +2070,14 @@ class TestFieldRepr:
def test_repr_str_field(self) -> None:
Cls = _make_type(
"ReprStr",
- [Field(name="name", ty=TypeSchema("str"), default=MISSING)],
+ [Field(name="name", _ty_schema=TypeSchema("str"),
default=MISSING)],
)
assert '"hello"' in ReprPrint(Cls(name="hello"))
def test_repr_bool_field(self) -> None:
Cls = _make_type(
"ReprBool",
- [Field(name="flag", ty=TypeSchema("bool"), default=MISSING)],
+ [Field(name="flag", _ty_schema=TypeSchema("bool"),
default=MISSING)],
)
assert "flag=True" in ReprPrint(Cls(flag=True))
@@ -2085,10 +2085,10 @@ class TestFieldRepr:
Cls = _make_type(
"ReprExcl",
[
- Field(name="visible", ty=TypeSchema("int"), default=MISSING),
+ Field(name="visible", _ty_schema=TypeSchema("int"),
default=MISSING),
Field(
name="hidden",
- ty=TypeSchema("int"),
+ _ty_schema=TypeSchema("int"),
default=0,
repr=False,
),
@@ -2101,14 +2101,14 @@ class TestFieldRepr:
def test_python_repr_delegates(self) -> None:
Cls = _make_type(
"ReprDeleg",
- [Field(name="x", ty=TypeSchema("int"), default=MISSING)],
+ [Field(name="x", _ty_schema=TypeSchema("int"), default=MISSING)],
)
assert "x=7" in repr(Cls(x=7))
def test_repr_contains_type_key(self) -> None:
Cls = _make_type(
"ReprKey",
- [Field(name="x", ty=TypeSchema("int"), default=MISSING)],
+ [Field(name="x", _ty_schema=TypeSchema("int"), default=MISSING)],
)
info = getattr(Cls, "__tvm_ffi_type_info__")
assert info.type_key in ReprPrint(Cls(x=1))
@@ -2119,7 +2119,7 @@ class TestFieldRepr:
[
Field(
name="x",
- ty=TypeSchema("Optional", (TypeSchema("int"),)),
+ _ty_schema=TypeSchema("Optional", (TypeSchema("int"),)),
default=None,
),
],
@@ -2133,7 +2133,7 @@ class TestFieldRepr:
[
Field(
name="items",
- ty=TypeSchema("Array", (TypeSchema("int"),)),
+ _ty_schema=TypeSchema("Array", (TypeSchema("int"),)),
default=MISSING,
),
],
@@ -2154,13 +2154,13 @@ class TestFieldHash:
[
Field(
name="x",
- ty=TypeSchema("int"),
+ _ty_schema=TypeSchema("int"),
default=MISSING,
compare=True,
),
Field(
name="y",
- ty=TypeSchema("float"),
+ _ty_schema=TypeSchema("float"),
default=MISSING,
compare=True,
),
@@ -2176,7 +2176,7 @@ class TestFieldHash:
[
Field(
name="x",
- ty=TypeSchema("int"),
+ _ty_schema=TypeSchema("int"),
default=MISSING,
compare=True,
),
@@ -2192,13 +2192,13 @@ class TestFieldHash:
[
Field(
name="key",
- ty=TypeSchema("int"),
+ _ty_schema=TypeSchema("int"),
default=MISSING,
compare=True,
),
Field(
name="ignored",
- ty=TypeSchema("int"),
+ _ty_schema=TypeSchema("int"),
default=0,
hash=False,
),
@@ -2211,7 +2211,7 @@ class TestFieldHash:
def test_hash_dunder_installed(self) -> None:
Cls = _make_type(
"HashDunder",
- [Field(name="x", ty=TypeSchema("int"), default=MISSING)],
+ [Field(name="x", _ty_schema=TypeSchema("int"), default=MISSING)],
eq=True,
unsafe_hash=True,
)
@@ -2223,7 +2223,7 @@ class TestFieldHash:
[
Field(
name="x",
- ty=TypeSchema("int"),
+ _ty_schema=TypeSchema("int"),
default=MISSING,
compare=True,
),
@@ -2239,7 +2239,7 @@ class TestFieldHash:
[
Field(
name="x",
- ty=TypeSchema("int"),
+ _ty_schema=TypeSchema("int"),
default=MISSING,
compare=True,
),
@@ -2262,13 +2262,13 @@ class TestFieldEquality:
[
Field(
name="x",
- ty=TypeSchema("int"),
+ _ty_schema=TypeSchema("int"),
default=MISSING,
compare=True,
),
Field(
name="y",
- ty=TypeSchema("float"),
+ _ty_schema=TypeSchema("float"),
default=MISSING,
compare=True,
),
@@ -2283,7 +2283,7 @@ class TestFieldEquality:
[
Field(
name="x",
- ty=TypeSchema("int"),
+ _ty_schema=TypeSchema("int"),
default=MISSING,
compare=True,
),
@@ -2298,13 +2298,13 @@ class TestFieldEquality:
[
Field(
name="key",
- ty=TypeSchema("int"),
+ _ty_schema=TypeSchema("int"),
default=MISSING,
compare=True,
),
Field(
name="ignored",
- ty=TypeSchema("int"),
+ _ty_schema=TypeSchema("int"),
default=0,
compare=False,
),
@@ -2317,7 +2317,7 @@ class TestFieldEquality:
"""Fields with compare=False (default) are ignored by RecursiveEq."""
Cls = _make_type(
"CmpOff",
- [Field(name="x", ty=TypeSchema("int"), default=MISSING)],
+ [Field(name="x", _ty_schema=TypeSchema("int"), default=MISSING)],
eq=True,
)
assert RecursiveEq(Cls(x=1), Cls(x=2))
@@ -2328,7 +2328,7 @@ class TestFieldEquality:
[
Field(
name="x",
- ty=TypeSchema("int"),
+ _ty_schema=TypeSchema("int"),
default=MISSING,
compare=True,
),
@@ -2343,7 +2343,7 @@ class TestFieldEquality:
[
Field(
name="x",
- ty=TypeSchema("int"),
+ _ty_schema=TypeSchema("int"),
default=MISSING,
compare=True,
),
@@ -2359,7 +2359,7 @@ class TestFieldEquality:
[
Field(
name="x",
- ty=TypeSchema("int"),
+ _ty_schema=TypeSchema("int"),
default=MISSING,
compare=True,
),
@@ -2376,7 +2376,7 @@ class TestFieldEquality:
[
Field(
name="s",
- ty=TypeSchema("str"),
+ _ty_schema=TypeSchema("str"),
default=MISSING,
compare=True,
),
@@ -2392,13 +2392,13 @@ class TestFieldEquality:
[
Field(
name="x",
- ty=TypeSchema("int"),
+ _ty_schema=TypeSchema("int"),
default=MISSING,
compare=True,
),
Field(
name="y",
- ty=TypeSchema("float"),
+ _ty_schema=TypeSchema("float"),
default=MISSING,
compare=True,
),
@@ -2431,7 +2431,7 @@ class TestFieldEdgeCases:
def test_bool_true_and_false(self) -> None:
Cls = _make_type(
"EdgeBool",
- [Field(name="flag", ty=TypeSchema("bool"), default=MISSING)],
+ [Field(name="flag", _ty_schema=TypeSchema("bool"),
default=MISSING)],
)
assert Cls(flag=True).flag is True
assert Cls(flag=False).flag is False
@@ -2439,7 +2439,7 @@ class TestFieldEdgeCases:
def test_bool_default_false(self) -> None:
Cls = _make_type(
"EdgeBoolDef",
- [Field(name="flag", ty=TypeSchema("bool"), default=False)],
+ [Field(name="flag", _ty_schema=TypeSchema("bool"), default=False)],
)
assert Cls().flag is False
@@ -2449,13 +2449,13 @@ class TestFieldEdgeCases:
[
Field(
name="i",
- ty=TypeSchema("int"),
+ _ty_schema=TypeSchema("int"),
default=MISSING,
compare=True,
),
- Field(name="f", ty=TypeSchema("float"), default=MISSING),
- Field(name="s", ty=TypeSchema("str"), default=MISSING),
- Field(name="b", ty=TypeSchema("bool"), default=MISSING),
+ Field(name="f", _ty_schema=TypeSchema("float"),
default=MISSING),
+ Field(name="s", _ty_schema=TypeSchema("str"), default=MISSING),
+ Field(name="b", _ty_schema=TypeSchema("bool"),
default=MISSING),
],
)
obj = Cls(i=42, f=3.14, s="test", b=True)
@@ -2468,13 +2468,13 @@ class TestFieldEdgeCases:
Cls = _make_type(
"EdgeMixed",
[
- Field(name="count", ty=TypeSchema("int"), default=MISSING),
+ Field(name="count", _ty_schema=TypeSchema("int"),
default=MISSING),
Field(
name="items",
- ty=TypeSchema("Array", (TypeSchema("int"),)),
+ _ty_schema=TypeSchema("Array", (TypeSchema("int"),)),
default=MISSING,
),
- Field(name="label", ty=TypeSchema("str"), default=""),
+ Field(name="label", _ty_schema=TypeSchema("str"), default=""),
],
)
obj = Cls(count=3, items=[1, 2, 3])
@@ -2486,10 +2486,10 @@ class TestFieldEdgeCases:
Cls = _make_type(
"EdgeMultiDef",
[
- Field(name="i", ty=TypeSchema("int"), default=0),
- Field(name="f", ty=TypeSchema("float"), default=1.0),
- Field(name="s", ty=TypeSchema("str"), default="default"),
- Field(name="b", ty=TypeSchema("bool"), default=True),
+ Field(name="i", _ty_schema=TypeSchema("int"), default=0),
+ Field(name="f", _ty_schema=TypeSchema("float"), default=1.0),
+ Field(name="s", _ty_schema=TypeSchema("str"),
default="default"),
+ Field(name="b", _ty_schema=TypeSchema("bool"), default=True),
],
)
obj = Cls()
@@ -2502,8 +2502,8 @@ class TestFieldEdgeCases:
Cls = _make_type(
"EdgeZero",
[
- Field(name="i", ty=TypeSchema("int"), default=MISSING),
- Field(name="f", ty=TypeSchema("float"), default=MISSING),
+ Field(name="i", _ty_schema=TypeSchema("int"), default=MISSING),
+ Field(name="f", _ty_schema=TypeSchema("float"),
default=MISSING),
],
)
obj = Cls(i=0, f=0.0)
@@ -2514,8 +2514,8 @@ class TestFieldEdgeCases:
Cls = _make_type(
"EdgeNeg",
[
- Field(name="i", ty=TypeSchema("int"), default=MISSING),
- Field(name="f", ty=TypeSchema("float"), default=MISSING),
+ Field(name="i", _ty_schema=TypeSchema("int"), default=MISSING),
+ Field(name="f", _ty_schema=TypeSchema("float"),
default=MISSING),
],
)
obj = Cls(i=-42, f=-3.14)
@@ -2525,7 +2525,7 @@ class TestFieldEdgeCases:
def test_large_int(self) -> None:
Cls = _make_type(
"EdgeLargeInt",
- [Field(name="x", ty=TypeSchema("int"), default=MISSING)],
+ [Field(name="x", _ty_schema=TypeSchema("int"), default=MISSING)],
)
large = 2**62
assert Cls(x=large).x == large
@@ -2533,14 +2533,14 @@ class TestFieldEdgeCases:
def test_empty_string_field(self) -> None:
Cls = _make_type(
"EdgeEmptyStr",
- [Field(name="s", ty=TypeSchema("str"), default=MISSING)],
+ [Field(name="s", _ty_schema=TypeSchema("str"), default=MISSING)],
)
assert Cls(s="").s == ""
def test_long_string_field(self) -> None:
Cls = _make_type(
"EdgeLongStr",
- [Field(name="s", ty=TypeSchema("str"), default=MISSING)],
+ [Field(name="s", _ty_schema=TypeSchema("str"), default=MISSING)],
)
long_str = "a" * 1000
assert Cls(s=long_str).s == long_str
@@ -2554,10 +2554,10 @@ class TestFieldEdgeCases:
Cls = _make_type(
"EdgeInitFalse",
[
- Field(name="visible", ty=TypeSchema("int"), default=MISSING),
+ Field(name="visible", _ty_schema=TypeSchema("int"),
default=MISSING),
Field(
name="internal",
- ty=TypeSchema("int"),
+ _ty_schema=TypeSchema("int"),
default=0,
init=False,
),
@@ -2571,10 +2571,10 @@ class TestFieldEdgeCases:
Cls = _make_type(
"EdgeInitFalseReject",
[
- Field(name="visible", ty=TypeSchema("int"), default=MISSING),
+ Field(name="visible", _ty_schema=TypeSchema("int"),
default=MISSING),
Field(
name="internal",
- ty=TypeSchema("int"),
+ _ty_schema=TypeSchema("int"),
default=0,
init=False,
),
@@ -2587,10 +2587,10 @@ class TestFieldEdgeCases:
Cls = _make_type(
"EdgeInitFalseWrite",
[
- Field(name="visible", ty=TypeSchema("int"), default=MISSING),
+ Field(name="visible", _ty_schema=TypeSchema("int"),
default=MISSING),
Field(
name="internal",
- ty=TypeSchema("int"),
+ _ty_schema=TypeSchema("int"),
default=0,
init=False,
),
@@ -2610,11 +2610,11 @@ class TestFieldInheritance:
def test_child_fields_after_parent(self) -> None:
Parent = _make_type(
"InhParent",
- [Field(name="x", ty=TypeSchema("int"), default=MISSING)],
+ [Field(name="x", _ty_schema=TypeSchema("int"), default=MISSING)],
)
Child = _make_type(
"InhChild",
- [Field(name="y", ty=TypeSchema("int"), default=MISSING)],
+ [Field(name="y", _ty_schema=TypeSchema("int"), default=MISSING)],
parent=Parent,
)
obj = Child(1, 2)
@@ -2624,11 +2624,11 @@ class TestFieldInheritance:
def test_child_field_offsets_non_overlapping(self) -> None:
Parent = _make_type(
"InhParentOff",
- [Field(name="x", ty=TypeSchema("int"), default=MISSING)],
+ [Field(name="x", _ty_schema=TypeSchema("int"), default=MISSING)],
)
Child = _make_type(
"InhChildOff",
- [Field(name="y", ty=TypeSchema("int"), default=MISSING)],
+ [Field(name="y", _ty_schema=TypeSchema("int"), default=MISSING)],
parent=Parent,
)
p_info = getattr(Parent, "__tvm_ffi_type_info__")
@@ -2639,11 +2639,11 @@ class TestFieldInheritance:
def test_mutation_no_aliasing(self) -> None:
Parent = _make_type(
"InhParentAlias",
- [Field(name="x", ty=TypeSchema("int"), default=MISSING)],
+ [Field(name="x", _ty_schema=TypeSchema("int"), default=MISSING)],
)
Child = _make_type(
"InhChildAlias",
- [Field(name="y", ty=TypeSchema("int"), default=MISSING)],
+ [Field(name="y", _ty_schema=TypeSchema("int"), default=MISSING)],
parent=Parent,
)
obj = Child(1, 2)
@@ -2655,16 +2655,16 @@ class TestFieldInheritance:
"""Object → A → B → C: all fields accessible and non-overlapping."""
A = _make_type(
"InhA",
- [Field(name="a", ty=TypeSchema("int"), default=MISSING)],
+ [Field(name="a", _ty_schema=TypeSchema("int"), default=MISSING)],
)
B = _make_type(
"InhB",
- [Field(name="b", ty=TypeSchema("str"), default=MISSING)],
+ [Field(name="b", _ty_schema=TypeSchema("str"), default=MISSING)],
parent=A,
)
C = _make_type(
"InhC",
- [Field(name="c", ty=TypeSchema("float"), default=MISSING)],
+ [Field(name="c", _ty_schema=TypeSchema("float"), default=MISSING)],
parent=B,
)
obj = C(a=1, b="two", c=3.0)
@@ -2675,16 +2675,16 @@ class TestFieldInheritance:
def test_three_level_offsets_non_overlapping(self) -> None:
A = _make_type(
"InhAOff",
- [Field(name="a", ty=TypeSchema("int"), default=MISSING)],
+ [Field(name="a", _ty_schema=TypeSchema("int"), default=MISSING)],
)
B = _make_type(
"InhBOff",
- [Field(name="b", ty=TypeSchema("int"), default=MISSING)],
+ [Field(name="b", _ty_schema=TypeSchema("int"), default=MISSING)],
parent=A,
)
C = _make_type(
"InhCOff",
- [Field(name="c", ty=TypeSchema("int"), default=MISSING)],
+ [Field(name="c", _ty_schema=TypeSchema("int"), default=MISSING)],
parent=B,
)
a_info = getattr(A, "__tvm_ffi_type_info__")
@@ -2698,16 +2698,16 @@ class TestFieldInheritance:
def test_three_level_mutation_no_aliasing(self) -> None:
A = _make_type(
"InhAMut",
- [Field(name="a", ty=TypeSchema("int"), default=MISSING)],
+ [Field(name="a", _ty_schema=TypeSchema("int"), default=MISSING)],
)
B = _make_type(
"InhBMut",
- [Field(name="b", ty=TypeSchema("int"), default=MISSING)],
+ [Field(name="b", _ty_schema=TypeSchema("int"), default=MISSING)],
parent=A,
)
C = _make_type(
"InhCMut",
- [Field(name="c", ty=TypeSchema("int"), default=MISSING)],
+ [Field(name="c", _ty_schema=TypeSchema("int"), default=MISSING)],
parent=B,
)
obj = C(a=1, b=2, c=3)
@@ -2723,16 +2723,16 @@ class TestFieldInheritance:
def test_three_level_isinstance(self) -> None:
A = _make_type(
"InhAIs",
- [Field(name="a", ty=TypeSchema("int"), default=MISSING)],
+ [Field(name="a", _ty_schema=TypeSchema("int"), default=MISSING)],
)
B = _make_type(
"InhBIs",
- [Field(name="b", ty=TypeSchema("int"), default=MISSING)],
+ [Field(name="b", _ty_schema=TypeSchema("int"), default=MISSING)],
parent=A,
)
C = _make_type(
"InhCIs",
- [Field(name="c", ty=TypeSchema("int"), default=MISSING)],
+ [Field(name="c", _ty_schema=TypeSchema("int"), default=MISSING)],
parent=B,
)
obj = C(a=1, b=2, c=3)
@@ -2744,16 +2744,16 @@ class TestFieldInheritance:
def test_three_level_deep_copy(self) -> None:
A = _make_type(
"InhACopy",
- [Field(name="a", ty=TypeSchema("int"), default=MISSING)],
+ [Field(name="a", _ty_schema=TypeSchema("int"), default=MISSING)],
)
B = _make_type(
"InhBCopy",
- [Field(name="b", ty=TypeSchema("int"), default=MISSING)],
+ [Field(name="b", _ty_schema=TypeSchema("int"), default=MISSING)],
parent=A,
)
C = _make_type(
"InhCCopy",
- [Field(name="c", ty=TypeSchema("int"), default=MISSING)],
+ [Field(name="c", _ty_schema=TypeSchema("int"), default=MISSING)],
parent=B,
)
obj = C(a=1, b=2, c=3)
@@ -2772,16 +2772,16 @@ class TestFieldInheritance:
GP = _make_type(
"OffGP",
[
- Field(name="x", ty=TypeSchema("int"), default=MISSING),
- Field(name="y", ty=TypeSchema("int"), default=MISSING),
+ Field(name="x", _ty_schema=TypeSchema("int"), default=MISSING),
+ Field(name="y", _ty_schema=TypeSchema("int"), default=MISSING),
],
)
P = _make_type("OffP", [], parent=GP)
C = _make_type(
"OffC",
[
- Field(name="a", ty=TypeSchema("int"), default=MISSING),
- Field(name="b", ty=TypeSchema("int"), default=MISSING),
+ Field(name="a", _ty_schema=TypeSchema("int"), default=MISSING),
+ Field(name="b", _ty_schema=TypeSchema("int"), default=MISSING),
],
parent=P,
)
@@ -2796,8 +2796,8 @@ class TestFieldInheritance:
GP = _make_type(
"OffGP2",
[
- Field(name="x", ty=TypeSchema("int"), default=MISSING),
- Field(name="y", ty=TypeSchema("int"), default=MISSING),
+ Field(name="x", _ty_schema=TypeSchema("int"), default=MISSING),
+ Field(name="y", _ty_schema=TypeSchema("int"), default=MISSING),
],
)
P = _make_type("OffP2", [], parent=GP)
@@ -2810,14 +2810,14 @@ class TestFieldInheritance:
GP = _make_type(
"OffGP3",
[
- Field(name="x", ty=TypeSchema("int"), default=MISSING),
- Field(name="y", ty=TypeSchema("int"), default=MISSING),
+ Field(name="x", _ty_schema=TypeSchema("int"), default=MISSING),
+ Field(name="y", _ty_schema=TypeSchema("int"), default=MISSING),
],
)
P = _make_type("OffP3", [], parent=GP)
C = _make_type(
"OffC3",
- [Field(name="a", ty=TypeSchema("int"), default=MISSING)],
+ [Field(name="a", _ty_schema=TypeSchema("int"), default=MISSING)],
parent=P,
)
p_info = getattr(P, "__tvm_ffi_type_info__")
@@ -2828,13 +2828,13 @@ class TestFieldInheritance:
"""GP(x) -> Mid1() -> Mid2() -> Leaf(a): offsets propagated through
two empty layers."""
GP = _make_type(
"OffGP4",
- [Field(name="x", ty=TypeSchema("int"), default=MISSING)],
+ [Field(name="x", _ty_schema=TypeSchema("int"), default=MISSING)],
)
Mid1 = _make_type("OffMid1", [], parent=GP)
Mid2 = _make_type("OffMid2", [], parent=Mid1)
Leaf = _make_type(
"OffLeaf",
- [Field(name="a", ty=TypeSchema("int"), default=MISSING)],
+ [Field(name="a", _ty_schema=TypeSchema("int"), default=MISSING)],
parent=Mid2,
)
obj = Leaf(x=1, a=2)
@@ -2849,16 +2849,16 @@ class TestFieldInheritance:
GP = _make_type(
"OffGP5",
[
- Field(name="x", ty=TypeSchema("int"), default=MISSING),
- Field(name="y", ty=TypeSchema("int"), default=MISSING),
+ Field(name="x", _ty_schema=TypeSchema("int"), default=MISSING),
+ Field(name="y", _ty_schema=TypeSchema("int"), default=MISSING),
],
)
P = _make_type("OffP5", [], parent=GP)
C = _make_type(
"OffC5",
[
- Field(name="a", ty=TypeSchema("int"), default=MISSING),
- Field(name="b", ty=TypeSchema("int"), default=MISSING),
+ Field(name="a", _ty_schema=TypeSchema("int"), default=MISSING),
+ Field(name="b", _ty_schema=TypeSchema("int"), default=MISSING),
],
parent=P,
)
@@ -2932,10 +2932,10 @@ class TestMutualReferences:
Foo,
foo_info,
[
- Field(name="a", ty=TypeSchema("str"), default=MISSING),
+ Field(name="a", _ty_schema=TypeSchema("str"), default=MISSING),
Field(
name="bar",
- ty=TypeSchema("Optional", (bar_schema,)),
+ _ty_schema=TypeSchema("Optional", (bar_schema,)),
default=None,
),
],
@@ -2946,7 +2946,7 @@ class TestMutualReferences:
[
Field(
name="foo",
- ty=TypeSchema("Optional", (foo_schema,)),
+ _ty_schema=TypeSchema("Optional", (foo_schema,)),
default=None,
),
],
@@ -2965,10 +2965,10 @@ class TestMutualReferences:
Bar,
bar_info,
[
- Field(name="val", ty=TypeSchema("int"), default=MISSING),
+ Field(name="val", _ty_schema=TypeSchema("int"),
default=MISSING),
Field(
name="next",
- ty=TypeSchema("Optional", (bar_schema,)),
+ _ty_schema=TypeSchema("Optional", (bar_schema,)),
default=None,
),
],
@@ -2990,7 +2990,7 @@ class TestMutualReferences:
Foo,
foo_info,
[
- Field(name="x", ty=TypeSchema("int"), default=MISSING),
+ Field(name="x", _ty_schema=TypeSchema("int"), default=MISSING),
],
)
self._finalize(
@@ -2999,7 +2999,7 @@ class TestMutualReferences:
[
Field(
name="foo",
- ty=TypeSchema("Optional", (foo_schema,)),
+ _ty_schema=TypeSchema("Optional", (foo_schema,)),
default=None,
),
],
@@ -3022,7 +3022,7 @@ class TestNativeParentInheritance:
assert parent_info is not None
Child = _make_type(
"InhNativeChild",
- [Field(name="extra", ty=TypeSchema("int"), default=MISSING)],
+ [Field(name="extra", _ty_schema=TypeSchema("int"),
default=MISSING)],
parent=_TestObjectBase,
)
child_info = getattr(Child, "__tvm_ffi_type_info__")
@@ -3032,7 +3032,7 @@ class TestNativeParentInheritance:
def test_preserves_parent_fields(self) -> None:
Child = _make_type(
"InhNativePreserve",
- [Field(name="extra", ty=TypeSchema("int"), default=MISSING)],
+ [Field(name="extra", _ty_schema=TypeSchema("int"),
default=MISSING)],
parent=_TestObjectBase,
)
obj = Child(extra=7, v_i64=1, v_f64=2.0, v_str="x")
@@ -3044,7 +3044,7 @@ class TestNativeParentInheritance:
def test_mutation_no_aliasing(self) -> None:
Child = _make_type(
"InhNativeMut",
- [Field(name="extra", ty=TypeSchema("int"), default=MISSING)],
+ [Field(name="extra", _ty_schema=TypeSchema("int"),
default=MISSING)],
parent=_TestObjectBase,
)
obj = Child(extra=7, v_i64=1, v_f64=2.0, v_str="x")
@@ -3057,7 +3057,7 @@ class TestNativeParentInheritance:
def test_parent_method_uses_parent_state(self) -> None:
Child = _make_type(
"InhNativeMethod",
- [Field(name="extra", ty=TypeSchema("int"), default=MISSING)],
+ [Field(name="extra", _ty_schema=TypeSchema("int"),
default=MISSING)],
parent=_TestObjectBase,
)
obj = Child(extra=7, v_i64=1, v_f64=2.0, v_str="x")
@@ -3066,7 +3066,7 @@ class TestNativeParentInheritance:
def test_copy_preserves_all_fields(self) -> None:
Child = _make_type(
"InhNativeCopy",
- [Field(name="extra", ty=TypeSchema("int"), default=MISSING)],
+ [Field(name="extra", _ty_schema=TypeSchema("int"),
default=MISSING)],
parent=_TestObjectBase,
)
obj = Child(extra=7, v_i64=1, v_f64=2.0, v_str="x")
@@ -3079,7 +3079,7 @@ class TestNativeParentInheritance:
def test_deepcopy_preserves_all_fields(self) -> None:
Child = _make_type(
"InhNativeDeepCopy",
- [Field(name="extra", ty=TypeSchema("int"), default=MISSING)],
+ [Field(name="extra", _ty_schema=TypeSchema("int"),
default=MISSING)],
parent=_TestObjectBase,
)
obj = Child(extra=7, v_i64=1, v_f64=2.0, v_str="x")
@@ -3102,13 +3102,13 @@ class TestDeepCopy:
[
Field(
name="x",
- ty=TypeSchema("int"),
+ _ty_schema=TypeSchema("int"),
default=MISSING,
compare=True,
),
Field(
name="s",
- ty=TypeSchema("str"),
+ _ty_schema=TypeSchema("str"),
default=MISSING,
compare=True,
),
@@ -3126,7 +3126,7 @@ class TestDeepCopy:
[
Field(
name="items",
- ty=TypeSchema("Array", (TypeSchema("int"),)),
+ _ty_schema=TypeSchema("Array", (TypeSchema("int"),)),
default=MISSING,
),
],
@@ -3139,7 +3139,7 @@ class TestDeepCopy:
def test_deep_copy_mutate_independent(self) -> None:
Cls = _make_type(
"DCMut",
- [Field(name="x", ty=TypeSchema("int"), default=MISSING)],
+ [Field(name="x", _ty_schema=TypeSchema("int"), default=MISSING)],
)
obj = Cls(x=1)
obj_copy = DeepCopy(obj)
@@ -3149,7 +3149,7 @@ class TestDeepCopy:
def test_python_deepcopy_dunder(self) -> None:
Cls = _make_type(
"DCPython",
- [Field(name="x", ty=TypeSchema("int"), default=MISSING)],
+ [Field(name="x", _ty_schema=TypeSchema("int"), default=MISSING)],
)
obj = Cls(x=42)
obj_copy = copy.deepcopy(obj)
@@ -3169,7 +3169,7 @@ class TestMemoryLifetime:
[
Field(
name="arr",
- ty=TypeSchema("Array", (TypeSchema("int"),)),
+ _ty_schema=TypeSchema("Array", (TypeSchema("int"),)),
default=MISSING,
),
],
@@ -3186,7 +3186,7 @@ class TestMemoryLifetime:
[
Field(
name="arr",
- ty=TypeSchema("Array", (TypeSchema("int"),)),
+ _ty_schema=TypeSchema("Array", (TypeSchema("int"),)),
default=MISSING,
),
],
@@ -3202,7 +3202,7 @@ class TestMemoryLifetime:
def test_str_field_any_storage(self) -> None:
Cls = _make_type(
"MemStr",
- [Field(name="s", ty=TypeSchema("str"), default=MISSING)],
+ [Field(name="s", _ty_schema=TypeSchema("str"), default=MISSING)],
)
assert Cls(s="hi").s == "hi"
long_str = "a" * 500
@@ -3219,8 +3219,8 @@ class TestBoolAlignment:
Cls = _make_type(
"BoolAlign",
[
- Field(name="flag", ty=TypeSchema("bool"), default=MISSING),
- Field(name="val", ty=TypeSchema("int"), default=MISSING),
+ Field(name="flag", _ty_schema=TypeSchema("bool"),
default=MISSING),
+ Field(name="val", _ty_schema=TypeSchema("int"),
default=MISSING),
],
)
info = getattr(Cls, "__tvm_ffi_type_info__")
@@ -3232,8 +3232,8 @@ class TestBoolAlignment:
Cls = _make_type(
"BoolAlignVal",
[
- Field(name="flag", ty=TypeSchema("bool"), default=MISSING),
- Field(name="val", ty=TypeSchema("int"), default=MISSING),
+ Field(name="flag", _ty_schema=TypeSchema("bool"),
default=MISSING),
+ Field(name="val", _ty_schema=TypeSchema("int"),
default=MISSING),
],
)
obj = Cls(flag=True, val=42)
@@ -3247,9 +3247,9 @@ class TestBoolAlignment:
Cls = _make_type(
"MultiBool",
[
- Field(name="a", ty=TypeSchema("bool"), default=MISSING),
- Field(name="b", ty=TypeSchema("bool"), default=MISSING),
- Field(name="c", ty=TypeSchema("bool"), default=MISSING),
+ Field(name="a", _ty_schema=TypeSchema("bool"),
default=MISSING),
+ Field(name="b", _ty_schema=TypeSchema("bool"),
default=MISSING),
+ Field(name="c", _ty_schema=TypeSchema("bool"),
default=MISSING),
],
)
info = getattr(Cls, "__tvm_ffi_type_info__")
@@ -3263,10 +3263,10 @@ class TestBoolAlignment:
Cls = _make_type(
"BoolIntBoolInt",
[
- Field(name="b1", ty=TypeSchema("bool"), default=MISSING),
- Field(name="i1", ty=TypeSchema("int"), default=MISSING),
- Field(name="b2", ty=TypeSchema("bool"), default=MISSING),
- Field(name="i2", ty=TypeSchema("int"), default=MISSING),
+ Field(name="b1", _ty_schema=TypeSchema("bool"),
default=MISSING),
+ Field(name="i1", _ty_schema=TypeSchema("int"),
default=MISSING),
+ Field(name="b2", _ty_schema=TypeSchema("bool"),
default=MISSING),
+ Field(name="i2", _ty_schema=TypeSchema("int"),
default=MISSING),
],
)
obj = Cls(b1=True, i1=100, b2=False, i2=200)
@@ -3285,7 +3285,7 @@ class TestTypeConversionErrors:
def test_set_int_field_to_str_raises(self) -> None:
Cls = _make_type(
"ErrIntStr",
- [Field(name="x", ty=TypeSchema("int"), default=MISSING)],
+ [Field(name="x", _ty_schema=TypeSchema("int"), default=MISSING)],
)
obj = Cls(x=1)
with pytest.raises((TypeError, RuntimeError)):
@@ -3294,7 +3294,7 @@ class TestTypeConversionErrors:
def test_set_str_field_to_int_raises(self) -> None:
Cls = _make_type(
"ErrStrInt",
- [Field(name="s", ty=TypeSchema("str"), default=MISSING)],
+ [Field(name="s", _ty_schema=TypeSchema("str"), default=MISSING)],
)
obj = Cls(s="hello")
with pytest.raises((TypeError, RuntimeError)):
@@ -3303,7 +3303,7 @@ class TestTypeConversionErrors:
def test_construct_with_wrong_type_raises(self) -> None:
Cls = _make_type(
"ErrInit",
- [Field(name="x", ty=TypeSchema("int"), default=MISSING)],
+ [Field(name="x", _ty_schema=TypeSchema("int"), default=MISSING)],
)
with pytest.raises((TypeError, RuntimeError)):
Cls(x="bad")
@@ -3312,7 +3312,7 @@ class TestTypeConversionErrors:
"""Failed type-checked mutation preserves old value."""
Cls = _make_type(
"ErrPreserve",
- [Field(name="x", ty=TypeSchema("int"), default=MISSING)],
+ [Field(name="x", _ty_schema=TypeSchema("int"), default=MISSING)],
)
obj = Cls(x=42)
with pytest.raises((TypeError, RuntimeError)):
@@ -3333,7 +3333,7 @@ class TestTypeConversionErrors:
[
Field(
name="child",
- ty=TypeSchema("Object"),
+ _ty_schema=TypeSchema("Object"),
default=MISSING,
),
],
@@ -3349,7 +3349,7 @@ class TestTypeConversionErrors:
[
Field(
name="child",
- ty=TypeSchema("Object"),
+ _ty_schema=TypeSchema("Object"),
default=MISSING,
),
],
@@ -3364,7 +3364,7 @@ class TestTypeConversionErrors:
[
Field(
name="child",
- ty=TypeSchema("Optional", (TypeSchema("Object"),)),
+ _ty_schema=TypeSchema("Optional", (TypeSchema("Object"),)),
default=None,
),
],
@@ -3379,7 +3379,7 @@ class TestTypeConversionErrors:
def test_set_bool_field_to_str_raises(self) -> None:
Cls = _make_type(
"ErrBoolStr",
- [Field(name="b", ty=TypeSchema("bool"), default=MISSING)],
+ [Field(name="b", _ty_schema=TypeSchema("bool"), default=MISSING)],
)
obj = Cls(b=True)
with pytest.raises((TypeError, RuntimeError)):
@@ -3391,7 +3391,7 @@ class TestTypeConversionErrors:
[
Field(
name="arr",
- ty=TypeSchema("Array", (TypeSchema("int"),)),
+ _ty_schema=TypeSchema("Array", (TypeSchema("int"),)),
default=MISSING,
),
],
@@ -3405,8 +3405,8 @@ class TestTypeConversionErrors:
Cls = _make_type(
"ErrMulti",
[
- Field(name="x", ty=TypeSchema("int"), default=MISSING),
- Field(name="y", ty=TypeSchema("str"), default=MISSING),
+ Field(name="x", _ty_schema=TypeSchema("int"), default=MISSING),
+ Field(name="y", _ty_schema=TypeSchema("str"), default=MISSING),
],
)
with pytest.raises((TypeError, RuntimeError)):
@@ -3418,7 +3418,7 @@ class TestTypeConversionErrors:
[
Field(
name="x",
- ty=TypeSchema("Optional", (TypeSchema("int"),)),
+ _ty_schema=TypeSchema("Optional", (TypeSchema("int"),)),
default=None,
),
],
@@ -3439,7 +3439,7 @@ class TestSetterGetterCornerCases:
def test_bool_field_accepts_true_false(self) -> None:
Cls = _make_type(
"SGBool",
- [Field(name="b", ty=TypeSchema("bool"), default=MISSING)],
+ [Field(name="b", _ty_schema=TypeSchema("bool"), default=MISSING)],
)
obj = Cls(b=True)
assert obj.b is True
@@ -3450,7 +3450,7 @@ class TestSetterGetterCornerCases:
"""Python bool is a subclass of int — FFI should accept it."""
Cls = _make_type(
"SGIntBool",
- [Field(name="x", ty=TypeSchema("int"), default=MISSING)],
+ [Field(name="x", _ty_schema=TypeSchema("int"), default=MISSING)],
)
obj = Cls(x=True)
assert obj.x == 1
@@ -3462,7 +3462,7 @@ class TestSetterGetterCornerCases:
def test_float_field_inf_nan(self) -> None:
Cls = _make_type(
"SGFloatEdge",
- [Field(name="f", ty=TypeSchema("float"), default=MISSING)],
+ [Field(name="f", _ty_schema=TypeSchema("float"), default=MISSING)],
)
obj = Cls(f=float("inf"))
assert math.isinf(obj.f)
@@ -3474,7 +3474,7 @@ class TestSetterGetterCornerCases:
def test_float_field_accepts_int(self) -> None:
Cls = _make_type(
"SGFloatInt",
- [Field(name="f", ty=TypeSchema("float"), default=MISSING)],
+ [Field(name="f", _ty_schema=TypeSchema("float"), default=MISSING)],
)
obj = Cls(f=42)
assert obj.f == pytest.approx(42.0)
@@ -3484,7 +3484,7 @@ class TestSetterGetterCornerCases:
def test_str_field_unicode(self) -> None:
Cls = _make_type(
"SGStrUni",
- [Field(name="s", ty=TypeSchema("str"), default=MISSING)],
+ [Field(name="s", _ty_schema=TypeSchema("str"), default=MISSING)],
)
obj = Cls(s="日本語テスト 🎉")
assert obj.s == "日本語テスト 🎉"
@@ -3492,7 +3492,7 @@ class TestSetterGetterCornerCases:
def test_str_field_null_bytes(self) -> None:
Cls = _make_type(
"SGStrNull",
- [Field(name="s", ty=TypeSchema("str"), default=MISSING)],
+ [Field(name="s", _ty_schema=TypeSchema("str"), default=MISSING)],
)
s = "hello\x00world"
obj = Cls(s=s)
@@ -3503,7 +3503,7 @@ class TestSetterGetterCornerCases:
def test_repeated_mutation_same_field(self) -> None:
Cls = _make_type(
"SGRepeat",
- [Field(name="x", ty=TypeSchema("int"), default=MISSING)],
+ [Field(name="x", _ty_schema=TypeSchema("int"), default=MISSING)],
)
obj = Cls(x=0)
for i in range(100):
@@ -3514,7 +3514,7 @@ class TestSetterGetterCornerCases:
"""Stress: repeated str assignment should not leak."""
Cls = _make_type(
"SGRepeatStr",
- [Field(name="s", ty=TypeSchema("str"), default=MISSING)],
+ [Field(name="s", _ty_schema=TypeSchema("str"), default=MISSING)],
)
obj = Cls(s="init")
for i in range(100):
@@ -3528,7 +3528,7 @@ class TestSetterGetterCornerCases:
[
Field(
name="arr",
- ty=TypeSchema("Array", (TypeSchema("int"),)),
+ _ty_schema=TypeSchema("Array", (TypeSchema("int"),)),
default=MISSING,
),
],
@@ -3543,13 +3543,13 @@ class TestSetterGetterCornerCases:
def test_nested_two_levels(self) -> None:
Inner = _make_type(
"SGInner",
- [Field(name="val", ty=TypeSchema("int"), default=MISSING)],
+ [Field(name="val", _ty_schema=TypeSchema("int"), default=MISSING)],
)
inner_info = getattr(Inner, "__tvm_ffi_type_info__")
inner_schema = TypeSchema(inner_info.type_key,
origin_type_index=inner_info.type_index)
Outer = _make_type(
"SGOuter",
- [Field(name="child", ty=inner_schema, default=MISSING)],
+ [Field(name="child", _ty_schema=inner_schema, default=MISSING)],
)
obj = Outer(child=Inner(val=42))
assert obj.child.val == 42
@@ -3563,10 +3563,10 @@ class TestSetterGetterCornerCases:
Cls = _make_type(
"SGSelfRef",
[
- Field(name="val", ty=TypeSchema("int"), default=MISSING),
+ Field(name="val", _ty_schema=TypeSchema("int"),
default=MISSING),
Field(
name="next",
- ty=TypeSchema("Optional", (TypeSchema("Object"),)),
+ _ty_schema=TypeSchema("Optional", (TypeSchema("Object"),)),
default=None,
),
],
@@ -3588,7 +3588,7 @@ class TestSetterGetterCornerCases:
Cls = _make_type(
"SGFactoryCount",
- [Field(name="x", ty=TypeSchema("int"), default_factory=factory)],
+ [Field(name="x", _ty_schema=TypeSchema("int"),
default_factory=factory)],
)
a = Cls()
b = Cls()
@@ -3603,18 +3603,18 @@ class TestSetterGetterCornerCases:
Cls = _make_type(
"SGKitchenSink",
[
- Field(name="i", ty=TypeSchema("int"), default=MISSING),
- Field(name="f", ty=TypeSchema("float"), default=MISSING),
- Field(name="b", ty=TypeSchema("bool"), default=MISSING),
- Field(name="s", ty=TypeSchema("str"), default=MISSING),
+ Field(name="i", _ty_schema=TypeSchema("int"), default=MISSING),
+ Field(name="f", _ty_schema=TypeSchema("float"),
default=MISSING),
+ Field(name="b", _ty_schema=TypeSchema("bool"),
default=MISSING),
+ Field(name="s", _ty_schema=TypeSchema("str"), default=MISSING),
Field(
name="arr",
- ty=TypeSchema("Array", (TypeSchema("int"),)),
+ _ty_schema=TypeSchema("Array", (TypeSchema("int"),)),
default=MISSING,
),
Field(
name="opt",
- ty=TypeSchema("Optional", (TypeSchema("int"),)),
+ _ty_schema=TypeSchema("Optional", (TypeSchema("int"),)),
default=None,
),
],
diff --git a/tests/python/test_object.py b/tests/python/test_object.py
index edb6fec..9ad7b3c 100644
--- a/tests/python/test_object.py
+++ b/tests/python/test_object.py
@@ -173,6 +173,23 @@ def test_register_object_wires_init() -> None:
assert bare.__chandle__() == 0
+def test_id_and_is() -> None:
+ x = tvm_ffi.testing.TestIntPair(1, 2)
+ y = tvm_ffi.testing.TestIntPair(1, 2)
+
+ # id_ is the integer handle, aliasing __chandle__.
+ assert x.id_ == x.__chandle__()
+ assert x.id_ != 0
+ assert x.id_ != y.id_
+
+ # is_ tests handle identity, aliasing same_as.
+ assert x.is_(x)
+ assert not x.is_(y)
+ assert x.is_(x) == x.same_as(x)
+ assert not x.is_(object())
+ assert not x.is_(None)
+
+
def test_object_protocol() -> None:
class CompactObject:
def __init__(self, backend_obj: Any) -> None: