| # 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. |
| """Public helpers for describing dataclass-style defaults on FFI proxies.""" |
| |
| from __future__ import annotations |
| |
| from dataclasses import _MISSING_TYPE, MISSING |
| from typing import Any, Callable, TypeVar, cast |
| |
| _FieldValue = TypeVar("_FieldValue") |
| |
| |
| class Field: |
| """(Experimental) Descriptor placeholder returned by :func:`tvm_ffi.dataclasses.field`. |
| |
| A ``Field`` mirrors the object returned by :func:`dataclasses.field`, but it |
| is understood by :func:`tvm_ffi.dataclasses.c_class`. The decorator inspects |
| the ``Field`` instances, records the ``default_factory`` and later replaces |
| the field with a property that forwards to the underlying C++ attribute. |
| |
| Users should not instantiate ``Field`` directly - use :func:`field` instead, |
| which guarantees that ``name`` and ``default_factory`` are populated in a |
| way the decorator understands. |
| """ |
| |
| __slots__ = ("default_factory", "init", "name") |
| |
| def __init__( |
| self, |
| *, |
| name: str | None = None, |
| default_factory: Callable[[], _FieldValue] | _MISSING_TYPE = MISSING, |
| init: bool = True, |
| ) -> None: |
| """Do not call directly; use :func:`field` instead.""" |
| self.name = name |
| self.default_factory = default_factory |
| self.init = init |
| |
| |
| def field( |
| *, |
| default: _FieldValue | _MISSING_TYPE = MISSING, # type: ignore[assignment] |
| default_factory: Callable[[], _FieldValue] | _MISSING_TYPE = MISSING, # type: ignore[assignment] |
| init: bool = True, |
| ) -> _FieldValue: |
| """(Experimental) Declare a dataclass-style field on a :func:`c_class` proxy. |
| |
| Use this helper exactly like :func:`dataclasses.field` when defining the |
| Python side of a C++ class. When :func:`c_class` processes the class body it |
| replaces the placeholder with a property and arranges for ``default`` or |
| ``default_factory`` to be respected by the synthesized ``__init__``. |
| |
| Parameters |
| ---------- |
| default |
| A literal default value that populates the field when no argument |
| is given. At most one of ``default`` or ``default_factory`` may be |
| given. |
| default_factory |
| A zero-argument callable that produces the default. This matches the |
| semantics of :func:`dataclasses.field` and is useful for mutable |
| defaults such as ``list`` or ``dict``. |
| init |
| If ``True`` the field is included in the generated ``__init__``. |
| If ``False`` the field is omitted from input arguments of ``__init__``. |
| |
| Note |
| ---- |
| The decision to forward a field to the C++ ``__ffi_init__`` constructor |
| depends on its configuration: |
| |
| * If ``init=True``, the field's value (from user input or defaults) |
| is forwarded. |
| |
| * If ``init=False``: |
| |
| - With a ``default`` or ``default_factory``, its computed value is |
| forwarded. The user cannot provide this value via Python ``__init__``. |
| |
| - Without a ``default`` or ``default_factory``, the field is *not* |
| forwarded to C++ ``__ffi_init__`` and must be initialized by the |
| C++ constructor. |
| |
| Returns |
| ------- |
| Field |
| A placeholder object that :func:`c_class` will consume during class |
| registration. |
| |
| Examples |
| -------- |
| ``field`` integrates with :func:`c_class` to express defaults the same way a |
| Python ``dataclass`` would: |
| |
| .. code-block:: python |
| |
| @c_class("testing.TestCxxClassBase") |
| class PyBase: |
| v_i64: int |
| v_i32: int = field(default=16) |
| |
| |
| obj = PyBase(v_i64=4) |
| obj.v_i32 # -> 16 |
| |
| """ |
| if default is not MISSING and default_factory is not MISSING: |
| raise ValueError("Cannot specify both `default` and `default_factory`") |
| if not isinstance(init, bool): |
| raise TypeError("`init` must be a bool") |
| if default is not MISSING: |
| default_factory = _make_default_factory(default) |
| ret = Field(default_factory=default_factory, init=init) |
| return cast(_FieldValue, ret) |
| |
| |
| def _make_default_factory(value: Any) -> Callable[[], Any]: |
| """Make a default factory that returns the given value.""" |
| |
| def factory() -> Any: |
| return value |
| |
| return factory |