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:

Reply via email to