This is an automated email from the ASF dual-hosted git repository.
tqchen 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 d711f44f [Rust] EnumRef for FFI enums (#953)
d711f44f is described below
commit d711f44f0be3e21f6e3858a2af25c155ead3ff55
Author: Tianqi Chen <[email protected]>
AuthorDate: Fri Nov 6 21:44:43 2026 -0500
[Rust] EnumRef for FFI enums (#953)
Generic support for FFI enums in the Rust crate, plus the registration
builders it needs.
- `reflection` is split into files like the C++ headers (`access_path`,
`accessor`, `registry`, `enum_def`); public paths are unchanged.
- `#[derive(Object)]` registers through one hidden entry point,
`reflection::register_object`, so `ObjectDef::finish` and the other
registration steps are private.
- `EnumObj` (the `ffi.Enum` object) and the `EnumRef` trait: `get(name)`
looks a value up in the enum type's `__ffi_enum__` registry, `name()`
borrows its name, and `attr_map::<V>(name)` returns an `EnumAttrMap` to
read an attribute. Values are singletons, compared with `same_as`; the
integer index stays private.
- `reflection::EnumDef<E>` registers a value eagerly (`new(name)`), sets
its attributes (`set_attr`) and returns it (`value()`), for enum types
without fields beyond `EnumObj`, as C++ `EnumDef` does. It updates the
shared registry through the existing `ffi.Dict*` globals.
- `reflection::TypeAttrDef<T>` registers type attributes, as C++
`TypeAttrDef` does.
- Stubgen: a type under `ffi.Enum` embeds `tvm_ffi::EnumObj` (complete,
no allocator) and implements `tvm_ffi::EnumRef`;
`ffi.IntEnum`/`ffi.StrEnum` stand-ins now sit on `EnumObj`.
- Reflection items (`FieldFlags`, `FieldGetter`, `ObjectDef`,
`SEqHashKind`, `ShallowCopy`, `TypeAttrColumn`, `UnsafeInit`) are no
longer re-exported at the crate root; use `tvm_ffi::reflection::`.
---
docs/guides/rust_lang_guide.rst | 2 +-
python/tvm_ffi/stub/rust_generator/codegen.py | 50 ++-
python/tvm_ffi/stub/rust_generator/consts.py | 7 +
python/tvm_ffi/stub/rust_generator/utils.py | 6 +-
rust/tvm-ffi-macros/src/object_macros.rs | 27 +-
rust/tvm-ffi-macros/src/rs_object.rs | 4 +-
rust/tvm-ffi/src/enum_ref.rs | 164 +++++++++
rust/tvm-ffi/src/extra/structural_eq_hash.rs | 2 +-
rust/tvm-ffi/src/lib.rs | 9 +-
rust/tvm-ffi/src/reflection.rs | 379 +--------------------
rust/tvm-ffi/src/reflection/access_path.rs | 88 +++++
.../src/{reflection.rs => reflection/accessor.rs} | 85 +----
rust/tvm-ffi/src/reflection/enum_def.rs | 282 +++++++++++++++
rust/tvm-ffi/src/reflection/registry.rs | 91 ++++-
rust/tvm-ffi/tests/test_enum.rs | 76 +++++
rust/tvm-ffi/tests/test_object_reflection.rs | 2 +-
rust/tvm-ffi/tests/test_rs_object.rs | 4 +-
rust/tvm-ffi/tests/test_structural_mutate.rs | 5 +-
rust/tvm-ffi/tests/test_structural_visit.rs | 7 +-
tests/python/test_stubgen_rust.py | 70 ++--
20 files changed, 814 insertions(+), 546 deletions(-)
diff --git a/docs/guides/rust_lang_guide.rst b/docs/guides/rust_lang_guide.rst
index c32e44f0..903aa497 100644
--- a/docs/guides/rust_lang_guide.rst
+++ b/docs/guides/rust_lang_guide.rst
@@ -347,7 +347,7 @@ first ``type_index()``, which allocating an instance calls:
Each reflected field gets a C ABI getter and setter, its doc string and its
type schema, so structural equality and hashing, ``ffi.ReprPrint``,
``ffi.DeepCopy`` and Python see it. The flags in ``#[reflect(...)]`` name
-``tvm_ffi::FieldFlags`` constants; ``name = "..."`` registers the field under
+``tvm_ffi::reflection::FieldFlags`` constants; ``name = "..."`` registers the
field under
another name, and ``doc = "..."`` sets its doc string, otherwise the field's
``///`` doc comment. Without ``#[type_s_eq_hash_kind]``, a type keeps its
nearest reflected ancestor's kind. The derive also implements ``ShallowCopy``
diff --git a/python/tvm_ffi/stub/rust_generator/codegen.py
b/python/tvm_ffi/stub/rust_generator/codegen.py
index 24281716..9f5917a8 100644
--- a/python/tvm_ffi/stub/rust_generator/codegen.py
+++ b/python/tvm_ffi/stub/rust_generator/codegen.py
@@ -38,10 +38,13 @@ A field without a Rust mirror (``Optional<Any>``,
``Union``, ``void*``, an
``ffi.*`` type the default ``ty-map`` does not list, ...) makes the type opaque
and is read as ``Any``; an ``opaque`` directive vetoes a reproducible layout; a
scalar width named by a directive is checked against the reflected field size.
-A builtin parent (``ffi.IntEnum``, say) has no
+A builtin parent the crate defines (``ffi.Enum``: ``tvm_ffi::EnumObj``) is
+embedded as it is. Any other builtin parent (``ffi.IntEnum``, say) has no
``<Leaf>Obj`` in the crate: the import section defines a header-only stand-in
-per builtin ancestor so ``derive(Object)`` computes the registry's
+per such ancestor so ``derive(Object)`` computes the registry's
``TYPE_DEPTH``, and everything under such a parent stays opaque
(``no-mirror``).
+An enum type (under ``ffi.Enum``) has no allocator, since its registry
+allocates the values, and its reference implements ``tvm_ffi::EnumRef``.
Every type key an object refers to (parent, ancestors, field types) must be
provided in the same run: ``ffi.*`` by the crate, a ``ty-map`` by a
hand-written
@@ -223,8 +226,9 @@ class _ObjectRenderer:
def _base_type(self) -> tuple[str, bool]:
"""Resolve the ``base_`` struct and whether it is a generated or
``ty-map``'d parent.
- A builtin parent below ``ffi.Object`` is embedded as its header-only
- stand-in (see :meth:`RustImports.record_builtin_base`).
+ A builtin parent below ``ffi.Object`` is embedded as the crate's object
+ struct, or as its header-only stand-in over the deepest builtin
ancestor the
+ crate defines (see :meth:`RustImports.record_builtin_base`).
"""
parent = self.info.parent_type_key
if parent is not None:
@@ -237,7 +241,12 @@ class _ObjectRenderer:
if parent not in (None, C_RUST.RUST_ROOT_TYPE_KEY, *chain):
chain.append(parent)
assert all(self._provider(key) == "crate" for key in chain),
(self.type_key, chain)
- return self.imports.record_builtin_base(chain), False
+ defined = [i for i, key in enumerate(chain) if key in
C_RUST.RUST_CRATE_OBJECTS]
+ if not defined:
+ return self.imports.record_builtin_base(chain), False
+ deepest = defined[-1]
+ root = C_RUST.RUST_CRATE_OBJECTS[chain[deepest]]
+ return self.imports.record_builtin_base(chain[deepest + 1 :], root),
False
# --- classification ----------------------------------------------------
@@ -251,11 +260,14 @@ class _ObjectRenderer:
def renderable(field: NamedTypeSchema) -> bool:
return self._field_mirror(owner_of[id(field)], field, scratch) is
not None
- # Builtin ancestors are header-only stand-ins (`_base_type`): none
below is complete.
+ # Builtin ancestors the crate does not define are header-only stand-ins
+ # (`_base_type`): none below is complete.
unmirrored = {
key
for key in self.info.ancestors
- if key != C_RUST.RUST_ROOT_TYPE_KEY and self._provider(key) ==
"crate"
+ if key != C_RUST.RUST_ROOT_TYPE_KEY
+ and key not in C_RUST.RUST_CRATE_OBJECTS
+ and self._provider(key) == "crate"
}
verdicts = classify(
infos,
@@ -669,7 +681,7 @@ class _ObjectRenderer:
ancestors = [key for key in self.info.ancestors if key !=
C_RUST.RUST_ROOT_TYPE_KEY]
if not all(self._provider(key) == "generated" for key in ancestors):
return None
- shallow_copy = self.imports.record("tvm_ffi::ShallowCopy")
+ shallow_copy = self.imports.record("tvm_ffi::reflection::ShallowCopy")
own = [rust_ident(f.name) for f in sorted(self.info.fields, key=lambda
f: f.offset or 0)]
init = [
f"base_: {shallow_copy}::shallow_copy(&self.base_)",
@@ -693,7 +705,7 @@ class _ObjectRenderer:
anything else goes through ``drop_if_defined``, which skips an
undefined field.
Emitted wherever ``ShallowCopy`` is.
"""
- unsafe_init = self.imports.record("tvm_ffi::UnsafeInit")
+ unsafe_init = self.imports.record("tvm_ffi::reflection::UnsafeInit")
addr = self.imports.record("std::ptr::addr_of_mut")
directives = self.imports.directives
drops =
[f"{unsafe_init}::drop_defined_fields({addr}!((*this).base_));"]
@@ -721,6 +733,14 @@ class _ObjectRenderer:
"}",
]
+ def _impl_sections(self, is_enum: bool) -> list[list[str]]:
+ """``ImmutableObject``, ``EnumRef`` and the upcasts, each that
applies."""
+ sections = [self._immutable_object_lines()]
+ if is_enum:
+ sections.append([f"impl tvm_ffi::EnumRef for {self.leaf} {{}}"])
+ sections.append(self._upcast_lines())
+ return [section for section in sections if section]
+
def body(self) -> list[str]:
"""Build the Rust source lines for the object."""
self.imports.reserved.update((self.leaf, self.obj_struct))
@@ -731,10 +751,11 @@ class _ObjectRenderer:
if has_parent:
self.imports.record("std::ops::Deref")
fields = self.info.fields
+ is_enum = C_RUST.RUST_ENUM_TYPE_KEY in self.info.ancestors
has_accessors = bool(fields) and not verdict.is_complete
if has_accessors:
self.imports.record("tvm_ffi::ObjectCore") # `Self::type_index()`
- self.imports.record("tvm_ffi::FieldGetter")
+ self.imports.record("tvm_ffi::reflection::FieldGetter")
self.imports.record("tvm_ffi::Result")
# The reference comes first, so a `///` written above the block
documents it.
@@ -771,18 +792,13 @@ class _ObjectRenderer:
"}",
]
)
- elif verdict.is_complete:
+ elif verdict.is_complete and not is_enum:
sections += self._allocator_sections(base, has_parent)
shallow_copy = self._shallow_copy_lines()
if shallow_copy:
sections.append(shallow_copy)
sections.append(self._unsafe_init_lines())
- thread_safe = self._immutable_object_lines()
- if thread_safe:
- sections.append(thread_safe)
- upcasts = self._upcast_lines()
- if upcasts:
- sections.append(upcasts)
+ sections += self._impl_sections(is_enum)
lines: list[str] = []
for i, section in enumerate(sections):
diff --git a/python/tvm_ffi/stub/rust_generator/consts.py
b/python/tvm_ffi/stub/rust_generator/consts.py
index 2ae25f92..99631218 100644
--- a/python/tvm_ffi/stub/rust_generator/consts.py
+++ b/python/tvm_ffi/stub/rust_generator/consts.py
@@ -123,6 +123,13 @@ RUST_MOD_MAP = {
#: Root of the object hierarchy.
RUST_ROOT_TYPE_KEY = "ffi.Object"
+#: Builtin types whose object struct the crate defines; a subtype embeds it as
``base_``.
+RUST_CRATE_OBJECTS = {"ffi.Enum": "tvm_ffi::EnumObj"}
+
+#: Root of the enum types: their registry allocates the values, and their
references
+#: implement ``tvm_ffi::EnumRef``.
+RUST_ENUM_TYPE_KEY = "ffi.Enum"
+
#: Keywords a field name may collide with: spelled ``r#name``, or ``name_``
for the four
#: that cannot be raw identifiers.
RUST_KEYWORDS = frozenset(
diff --git a/python/tvm_ffi/stub/rust_generator/utils.py
b/python/tvm_ffi/stub/rust_generator/utils.py
index 8f99d480..7a5e0667 100644
--- a/python/tvm_ffi/stub/rust_generator/utils.py
+++ b/python/tvm_ffi/stub/rust_generator/utils.py
@@ -111,12 +111,12 @@ class RustImports:
reserved: set[str] = dataclasses.field(default_factory=set)
"""Names the file's ``object/`` blocks define; an import may not take
one."""
- def record_builtin_base(self, chain: list[str]) -> str:
+ def record_builtin_base(self, chain: list[str], root: str =
"tvm_ffi::Object") -> str:
"""Record the stand-ins for the builtin ``chain`` (root side first);
return the last name.
- An empty chain (parent ``ffi.Object``) yields the crate's ``Object``.
+ The first stand-in embeds the crate's ``root`` object; an empty chain
yields ``root``.
"""
- base = self.record("tvm_ffi::Object")
+ base = self.record(root)
for key in chain:
self.builtin_mirrors.setdefault(key, base)
base = builtin_mirror_name(key)
diff --git a/rust/tvm-ffi-macros/src/object_macros.rs
b/rust/tvm-ffi-macros/src/object_macros.rs
index ed500c2e..2a7cdacf 100644
--- a/rust/tvm-ffi-macros/src/object_macros.rs
+++ b/rust/tvm-ffi-macros/src/object_macros.rs
@@ -442,12 +442,12 @@ impl Reflection {
format_ident!("{}", flag.to_string().to_uppercase(), span =
flag.span())
});
quote! {
- .field::<#ty>(
+ let def = def.field::<#ty>(
#name,
#doc,
::core::mem::offset_of!(#struct_name, #id),
#reflection::FieldFlags::NONE #(|
#reflection::FieldFlags::#flags)*,
- )?
+ )?;
}
});
// The `#[type_reflection]` function registers the methods after the
@@ -461,26 +461,25 @@ impl Reflection {
first.ident = struct_name.clone();
}
}
- quote! {
- let mut def = def;
- #path(&mut def);
- }
+ quote!(#path)
});
+ let methods = methods.unwrap_or_else(|| quote!(|_| {}));
let creator = if self.unsafe_init {
quote!(Some(#reflection::unsafe_init_creator::<#struct_name>))
} else {
quote!(None)
};
quote! {
- let registered = (|| -> #tvm_ffi_crate::Result<()> {
- let def = #reflection::ObjectDef::<#struct_name>::registering(
- tindex, #kind, #creator,
- )?
+ let registered = #reflection::register_object::<#struct_name>(
+ tindex,
+ #kind,
+ #creator,
+ |def| {
#(#fields)*
- .shallow_copy()?;
- #methods
- def.finish()
- })();
+ Ok(def)
+ },
+ #methods,
+ );
if let Err(error) = registered {
panic!("Failed to register the reflection of type key {}: {}",
#type_key, error);
}
diff --git a/rust/tvm-ffi-macros/src/rs_object.rs
b/rust/tvm-ffi-macros/src/rs_object.rs
index 90bc2cf3..506442de 100644
--- a/rust/tvm-ffi-macros/src/rs_object.rs
+++ b/rust/tvm-ffi-macros/src/rs_object.rs
@@ -220,10 +220,10 @@ fn expand(args: &RsObjectArgs, item: &DeriveInput) ->
syn::Result<TokenStream2>
// them; only through raw pointers, since some may be undefined.
let field_ids = fields.iter().map(|field| &field.ident);
let unsafe_init_impl = quote! {
- unsafe impl #tvm_ffi::UnsafeInit for #obj {
+ unsafe impl #tvm_ffi::reflection::UnsafeInit for #obj {
unsafe fn drop_defined_fields(this: *mut Self) {
unsafe {
- <#base_ty as #tvm_ffi::UnsafeInit>::drop_defined_fields(
+ <#base_ty as
#tvm_ffi::reflection::UnsafeInit>::drop_defined_fields(
::core::ptr::addr_of_mut!((*this).#base_id),
);
#(#tvm_ffi::reflection::drop_if_defined(
diff --git a/rust/tvm-ffi/src/enum_ref.rs b/rust/tvm-ffi/src/enum_ref.rs
new file mode 100644
index 00000000..79a0b8e9
--- /dev/null
+++ b/rust/tvm-ffi/src/enum_ref.rs
@@ -0,0 +1,164 @@
+/*
+ * 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.
+ */
+
+//! Enums shared across languages: each value is a named singleton of an
+//! object type whose parent is [`EnumObj`]. [`EnumRef`] looks values up;
+//! [`reflection::EnumDef`](crate::reflection::EnumDef) registers them.
+
+use crate::derive::Object;
+use crate::object::{ImmutableObject, Object, ObjectCore, ObjectRefCore};
+use crate::reflection::enum_def::{any_into, dict_get, enum_state};
+use crate::reflection::EnumAttrMap;
+use crate::type_traits::AnyCompatible;
+use crate::{AnyView, Error, Result, String, VALUE_ERROR};
+
+/// The object behind every enum value: its name and the index the registry
+/// assigned it. An enum type's object embeds it as its parent,
+/// `base_: EnumObj`.
+#[repr(C)]
+#[derive(Object)]
+#[type_key = "ffi.Enum"]
+pub struct EnumObj {
+ base_: Object,
+ // The registration order, which can differ between processes, so values
+ // are identified by name only.
+ #[allow(dead_code)]
+ index: i64,
+ name: String,
+}
+
+unsafe impl ImmutableObject for EnumObj {}
+
+impl EnumObj {
+ pub(crate) fn new(index: i64, name: &str) -> Self {
+ Self {
+ base_: Object::new(),
+ index,
+ name: String::from(name),
+ }
+ }
+}
+
+/// A reference to an enum value: one named singleton of an enum type,
+/// registered once and shared with every language.
+///
+/// [`get`](Self::get) looks a value up by name. Each name has one value, so
+/// equality is identity: compare values with
+/// [`same_as`](ObjectRefCore::same_as).
+/// [`EnumDef`](crate::reflection::EnumDef) registers values and their
+/// attributes, and [`attr_map`](Self::attr_map) reads an attribute.
+///
+/// The stub generator implements it for the enum types it binds. A
+/// hand-written reference implements it when its object's parent is
+/// [`EnumObj`].
+///
+/// # Examples
+///
+/// ```
+/// use tvm_ffi::derive::{Object, ObjectRef};
+/// use tvm_ffi::reflection::EnumDef;
+/// use tvm_ffi::{EnumObj, EnumRef, ObjectArc, ObjectRefCore};
+///
+/// #[repr(C)]
+/// #[derive(Object)]
+/// #[type_key = "my_ext.Color"]
+/// #[type_register]
+/// #[type_final]
+/// pub struct ColorObj {
+/// base_: EnumObj,
+/// }
+///
+/// #[derive(Clone, ObjectRef)]
+/// pub struct Color {
+/// data: ObjectArc<ColorObj>,
+/// }
+///
+/// impl EnumRef for Color {}
+///
+/// EnumDef::<Color>::new("red")?;
+///
+/// let red = Color::get("red")?;
+/// assert_eq!(red.name(), "red");
+/// assert!(red.same_as(&Color::get("red")?));
+/// # Ok::<(), tvm_ffi::Error>(())
+/// ```
+pub trait EnumRef: ObjectRefCore {
+ /// Look up the value registered as `name`.
+ ///
+ /// # Arguments
+ ///
+ /// * `name` - The name of the value.
+ ///
+ /// # Returns
+ ///
+ /// The value, the same object on every call.
+ ///
+ /// # Errors
+ ///
+ /// Returns a `ValueError` if the enum type has no value named `name`.
+ fn get(name: &str) -> Result<Self> {
+ let key = String::from(name);
+ let found = match enum_state(Self::ContainerType::type_index())? {
+ Some(state) => dict_get(AnyView::from(&state.indexes),
AnyView::from(&key))?,
+ None => None,
+ };
+ let Some(value) = found else {
+ return Err(Error::new(
+ VALUE_ERROR,
+ &format!(
+ "Enum `{}` has no value named `{name}`",
+ Self::ContainerType::TYPE_KEY
+ ),
+ "",
+ ));
+ };
+ any_into(value)
+ }
+
+ /// The name of the value, unique within its enum type.
+ ///
+ /// # Panics
+ ///
+ /// Panics if the object of `Self` does not have [`EnumObj`] as an
+ /// ancestor, which only an incorrect `impl EnumRef` allows.
+ fn name(&self) -> &str {
+ let object = self
+ .downcast_ref::<EnumObj>()
+ .expect("an `EnumRef` object has `EnumObj` as an ancestor");
+ object.name.as_str()
+ }
+
+ /// The attribute `name` of the values of the enum type, or `None` when no
+ /// value has it.
+ ///
+ /// The map holds the registry's column, so it also sees the attributes set
+ /// after it was read. See also
+ /// [`EnumDef::set_attr`](crate::reflection::EnumDef::set_attr).
+ ///
+ /// # Arguments
+ ///
+ /// * `name` - The name of the attribute.
+ ///
+ /// # Returns
+ ///
+ /// The attribute of every value that has it, read as `V`.
+ fn attr_map<V: AnyCompatible>(name: &str) ->
Result<Option<EnumAttrMap<Self, V>>> {
+ EnumAttrMap::lookup(name)
+ }
+}
diff --git a/rust/tvm-ffi/src/extra/structural_eq_hash.rs
b/rust/tvm-ffi/src/extra/structural_eq_hash.rs
index d388236d..cbf90b88 100644
--- a/rust/tvm-ffi/src/extra/structural_eq_hash.rs
+++ b/rust/tvm-ffi/src/extra/structural_eq_hash.rs
@@ -38,7 +38,7 @@ pub struct StructuralOptions {
/// and content, compared recursively through containers and reflected
/// fields, not by identity.
///
-/// Objects compare as their [`SEqHashKind`](crate::SEqHashKind) declares.
+/// Objects compare as their [`SEqHashKind`](crate::reflection::SEqHashKind)
declares.
///
/// # Arguments
///
diff --git a/rust/tvm-ffi/src/lib.rs b/rust/tvm-ffi/src/lib.rs
index 102f53ad..7012023d 100644
--- a/rust/tvm-ffi/src/lib.rs
+++ b/rust/tvm-ffi/src/lib.rs
@@ -49,6 +49,7 @@ pub mod collections;
pub mod derive;
pub mod device;
pub mod dtype;
+pub mod enum_ref;
pub mod error;
pub mod extra;
pub mod function;
@@ -76,6 +77,7 @@ pub use crate::collections::shape::Shape;
pub use crate::collections::tensor::{CPUNDAlloc, NDAllocator, Tensor,
TensorView};
pub use crate::device::{current_stream, with_stream};
pub use crate::dtype::DLDataTypeExt;
+pub use crate::enum_ref::{EnumObj, EnumRef};
pub use crate::error::{Error, ErrorKind, Result};
pub use crate::error::{
ATTRIBUTE_ERROR, INDEX_ERROR, INTERNAL_ERROR, KEY_ERROR, OVERFLOW_ERROR,
RUNTIME_ERROR,
@@ -105,9 +107,6 @@ pub use crate::object::{
ObjectIdentity, ObjectRefAnyCompatible, ObjectRefCore,
};
pub use crate::optional::{Optional, OptionalCompatible};
-pub use crate::reflection::{
- FieldFlags, FieldGetter, ObjectDef, SEqHashKind, ShallowCopy,
TypeAttrColumn, UnsafeInit,
-};
pub use crate::rvalue_ref::RValueRef;
pub use crate::static_init::run_static_init_blocks;
pub use crate::string::{Bytes, String};
@@ -250,7 +249,7 @@ pub use tvm_ffi_macros::match_any;
///
/// * `"type.Key"` - The type key.
/// * `final` - The type has no subtypes.
-/// * `s_eq_hash_kind = "TreeNode"` - The [`SEqHashKind`] of structural
+/// * `s_eq_hash_kind = "TreeNode"` - The
[`SEqHashKind`](crate::reflection::SEqHashKind) of structural
/// equality; by default the parent's.
/// * `child_slots = N`, `child_slots_can_overflow = bool` - Type indices kept
/// for subtypes; 0 and `true` by default.
@@ -260,7 +259,7 @@ pub use tvm_ffi_macros::match_any;
/// depends on it only through a re-export; by default `tvm_ffi`.
///
/// A field takes `#[reflect(writable, seq_hash_ignore, ...)]` for the
-/// [`FieldFlags`] of those names, and `name = "..."` or `doc = "..."` to
+/// [`FieldFlags`](crate::reflection::FieldFlags) of those names, and `name =
"..."` or `doc = "..."` to
/// register it under another name or with a doc string, by default its `///`
/// comment.
///
diff --git a/rust/tvm-ffi/src/reflection.rs b/rust/tvm-ffi/src/reflection.rs
index f8ae5fd8..1483a577 100644
--- a/rust/tvm-ffi/src/reflection.rs
+++ b/rust/tvm-ffi/src/reflection.rs
@@ -18,377 +18,20 @@
*/
//! Object reflection: look up the methods, fields and type attributes of
-//! registered types, and register Rust object types with [`ObjectDef`].
-
-use std::ffi::c_void;
-use std::ptr::NonNull;
-
-use crate::derive::{Object, ObjectRef};
-use crate::object::{Object, ObjectArc, ObjectRef};
-use crate::tvm_ffi_sys::{
- TVMFFIAny, TVMFFIByteArray, TVMFFIFieldGetter, TVMFFIFieldInfo,
TVMFFIGetTypeAttrColumn,
- TVMFFIGetTypeInfo, TVMFFIObject, TVMFFITypeAttrColumn, TVMFFITypeIndex,
TVMFFITypeKeyToIndex,
-};
-use crate::type_traits::AnyCompatible;
-use crate::{Any, AnyView, Error, Function, ObjectCore, Result, TYPE_ERROR};
+//! registered types; register Rust object types with [`ObjectDef`], type
+//! attributes with [`TypeAttrDef`] and enum values with [`EnumDef`].
+mod access_path;
+mod accessor;
+pub(crate) mod enum_def;
mod registry;
mod unsafe_init;
-pub use registry::{FieldFlags, ObjectDef, SEqHashKind, ShallowCopy};
+pub use access_path::{AccessKind, AccessPath, AccessPathObj, AccessStep,
AccessStepObj};
+pub use accessor::{get_method, get_method_by_index, FieldGetter,
TypeAttrColumn};
+pub use enum_def::{EnumAttrMap, EnumDef};
+#[doc(hidden)]
+pub use registry::register_object;
+pub use registry::{FieldFlags, ObjectDef, SEqHashKind, ShallowCopy,
TypeAttrDef};
#[doc(hidden)]
pub use unsafe_init::{drop_if_defined, unsafe_init_creator};
pub use unsafe_init::{unsafe_init, UnsafeInit};
-
-/// A registry-owned type-attribute column indexed by runtime type.
-///
-/// [`TypeAttrColumn::get`] returns owning copies. Registration must not race
-/// with reads.
-#[derive(Clone, Copy)]
-pub struct TypeAttrColumn(NonNull<TVMFFITypeAttrColumn>);
-
-// Type-attribute columns and their cells are registry-owned process-lifetime
-// data. Once registration is complete, reading a cell does not mutate the
-// registry and is safe from any thread.
-unsafe impl Send for TypeAttrColumn {}
-unsafe impl Sync for TypeAttrColumn {}
-
-impl TypeAttrColumn {
- /// Look up a registered type-attribute column by name.
- pub fn new(name: &str) -> Option<Self> {
- unsafe {
- let name = TVMFFIByteArray::from_str(name);
- NonNull::new(TVMFFIGetTypeAttrColumn(&name).cast_mut()).map(Self)
- }
- }
-
- /// The attribute of `type_index`, as an owned value, or `None` if unset.
- pub fn get(self, type_index: i32) -> Option<Any> {
- let raw = self.get_raw_ffi_any(type_index)?;
- if raw.type_index == TVMFFITypeIndex::kTVMFFINone as i32 {
- return None;
- }
- Some(Any::from(unsafe { AnyView::from_raw_ffi_any(raw) }))
- }
-
- /// The raw cell of `type_index`'s attribute, borrowed from the registry.
- ///
- /// The cell does not retain its value: the value must stay registered
while
- /// the cell is used, and registration must not race with reads. Use
- /// [`Self::get`] for an owned [`Any`].
- ///
- /// Returns `None` when the index is outside the column or the column has
no
- /// data. Unlike [`Self::get`], it returns a `None`-valued cell as `Some`.
- #[inline(always)]
- pub fn get_raw_ffi_any(self, type_index: i32) -> Option<TVMFFIAny> {
- unsafe {
- let column = self.0.as_ref();
- let index = type_index - column.begin_index;
- if index < 0 || index >= column.size || column.data.is_null() {
- None
- } else {
- Some(*column.data.offset(index as isize))
- }
- }
- }
-}
-
-/// Look up a reflected method of a type by type key and method name.
-///
-/// Same as [`get_method_by_index`], but resolves `type_key` to a type index
-/// first.
-///
-/// # Arguments
-///
-/// * `type_key` - The type key of the type that owns the method.
-/// * `method_name` - The name of the method.
-///
-/// # Returns
-///
-/// The reflected method.
-///
-/// # Errors
-///
-/// Returns an error if the type or the method does not exist.
-pub fn get_method(type_key: &str, method_name: &str) -> Result<Function> {
- unsafe {
- let type_key_arg = TVMFFIByteArray::from_str(type_key);
- let mut type_index: i32 = 0;
- crate::check_safe_call!(TVMFFITypeKeyToIndex(&type_key_arg, &mut
type_index))?;
- get_method_by_index(type_index, method_name)
- }
-}
-
-/// Look up a reflected method of a type by type index and method name, as
-/// [`get_method`] does by type key.
-///
-/// Reflected methods, registered in any language (such as with
-/// [`ObjectDef::def`]), live in the type's method table rather than the global
-/// function table. The constructor is reachable under the reserved name
-/// `__ffi_init__`. For instance methods, the first argument is the object
-/// itself.
-///
-/// `type_index` must be a registered type index, obtained for example from a
-/// live object via `Any::type_index` or from a type key; the C API treats an
-/// unregistered index as a fatal error.
-///
-/// # Arguments
-///
-/// * `type_index` - The type index of the type that owns the method.
-/// * `method_name` - The name of the method.
-///
-/// # Returns
-///
-/// The reflected method.
-///
-/// # Errors
-///
-/// Returns an error if the type has no method `method_name`.
-pub fn get_method_by_index(type_index: i32, method_name: &str) ->
Result<Function> {
- unsafe {
- let type_info = TVMFFIGetTypeInfo(type_index);
- if type_info.is_null() {
- crate::bail!(
- crate::error::TYPE_ERROR,
- "Cannot find type info for type_index={}",
- type_index
- );
- }
- let type_info = &*type_info;
- for i in 0..type_info.num_methods as usize {
- let method_info = &*type_info.methods.add(i);
- if method_info.name.as_str() != method_name {
- continue;
- }
- if !<Function as
AnyCompatible>::check_any_strict(&method_info.method) {
- crate::bail!(
- crate::error::TYPE_ERROR,
- "Method `{}` of type `{}` is not a Function",
- method_name,
- type_info.type_key.as_str()
- );
- }
- // the table entry stores the method as a non-owning AnyView;
- // copy out a strong reference
- return Ok(<Function as
AnyCompatible>::copy_from_any_view_after_check(
- &method_info.method,
- ));
- }
- crate::bail!(
- crate::error::TYPE_ERROR,
- "Cannot find method `{}` of type `{}`",
- method_name,
- type_info.type_key.as_str()
- );
- }
-}
-
-/// What an [`AccessStep`] accesses: an attribute, an array item or a map
-/// item, or one of those that is missing.
-///
-/// A closed set: the values are part of the ABI.
-#[repr(i32)]
-#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
-pub enum AccessKind {
- /// An object attribute; the key is its name.
- Attr = 0,
- /// An array item; the key is its index.
- ArrayItem = 1,
- /// A map item; the key is the map key.
- MapItem = 2,
- /// An attribute that does not exist, in an error report.
- AttrMissing = 3,
- /// An array index out of range, in an error report.
- ArrayItemMissing = 4,
- /// A map key that is absent, in an error report.
- MapItemMissing = 5,
-}
-
-/// The object behind an [`AccessStep`], laid out as the C++ `AccessStepObj`.
-#[repr(C)]
-#[derive(Object)]
-#[type_key = "ffi.reflection.AccessStep"]
-#[type_final]
-pub struct AccessStepObj {
- object: Object,
- /// What the step accesses.
- pub kind: AccessKind,
- /// The attribute name, array index or map key.
- pub key: Any,
-}
-
-/// One step of an [`AccessPath`]: an attribute, array index or map key.
-#[repr(C)]
-#[derive(ObjectRef, Clone)]
-pub struct AccessStep {
- data: ObjectArc<AccessStepObj>,
-}
-
-/// The object behind an [`AccessPath`], laid out as the C++ `AccessPathObj`.
-#[repr(C)]
-#[derive(Object)]
-#[type_key = "ffi.reflection.AccessPath"]
-#[type_final]
-pub struct AccessPathObj {
- object: Object,
- /// The path without its last step; `None` at the root.
- pub parent: Option<ObjectRef>,
- /// The last step; `None` at the root.
- pub step: Option<AccessStep>,
- /// The number of steps; 0 at the root.
- pub depth: i32,
-}
-
-/// A path from a root value to a nested one, such as `root.field[0]`, as a
-/// chain of parents; its methods are reached with `reflection::get_method`.
-#[repr(C)]
-#[derive(ObjectRef, Clone)]
-pub struct AccessPath {
- data: ObjectArc<AccessPathObj>,
-}
-
-/// Resolves a reflected field once, then uses its registered C ABI getter.
-#[derive(Clone, Copy)]
-pub struct FieldGetter {
- owner_type_index: i32,
- owner_type_depth: i32,
- field_offset: i64,
- getter: TVMFFIFieldGetter,
-}
-
-impl FieldGetter {
- /// Resolve a reflected field declared by `type_index` or one of its bases.
- pub fn new(type_index: i32, field_name: &str) -> Result<Self> {
- let type_info = unsafe { TVMFFIGetTypeInfo(type_index) };
- if type_info.is_null() {
- return Err(Error::new(
- TYPE_ERROR,
- &format!("Cannot find type info for type_index={type_index}"),
- "",
- ));
- }
-
- let field = unsafe { find_field(type_info, field_name) }.ok_or_else(||
{
- let type_key = unsafe { (*type_info).type_key.as_str() };
- Error::new(
- TYPE_ERROR,
- &format!("Cannot find reflected field `{field_name}` in type
`{type_key}`"),
- "",
- )
- })?;
- let field = unsafe { field.as_ref() };
- let getter = field.getter.ok_or_else(|| {
- Error::new(
- TYPE_ERROR,
- &format!("Reflected field `{}` has no getter",
field.name.as_str()),
- "",
- )
- })?;
- Ok(Self {
- owner_type_index: type_index,
- owner_type_depth: unsafe { (*type_info).type_depth },
- field_offset: field.offset,
- getter,
- })
- }
-
- /// Read the field as an owning [`Any`].
- ///
- /// `object` may have the declared owner type or any registered subtype.
- pub fn get_any<N: ObjectCore>(&self, object: &N) -> Result<Any> {
- let object_pointer = std::ptr::from_ref(object);
- let header = object_pointer.cast::<TVMFFIObject>();
- let dynamic_type_index = unsafe { (*header).type_index };
- if !unsafe {
- is_type_or_subtype(
- dynamic_type_index,
- self.owner_type_index,
- self.owner_type_depth,
- )
- } {
- return Err(Error::new(
- TYPE_ERROR,
- &format!(
- "Cannot read a field of type_index={} from object
type_index={dynamic_type_index}",
- self.owner_type_index
- ),
- "",
- ));
- }
-
- let field_address = unsafe {
- object_pointer
- .cast::<u8>()
- .offset(self.field_offset as isize)
- .cast_mut()
- .cast::<c_void>()
- };
- let mut result = Any::new();
- if unsafe { (self.getter)(field_address, Any::as_raw_ffi_any_mut(&mut
result)) } != 0 {
- return Err(Error::from_raised());
- }
- Ok(result)
- }
-
- /// Read and convert the field to `T`.
- pub fn get<N, T>(&self, object: &N) -> Result<T>
- where
- N: ObjectCore,
- T: TryFrom<Any, Error = Error>,
- {
- T::try_from(self.get_any(object)?)
- }
-}
-
-unsafe fn find_field(
- type_info: *const crate::tvm_ffi_sys::TVMFFITypeInfo,
- field_name: &str,
-) -> Option<NonNull<TVMFFIFieldInfo>> {
- // Prefer the most-derived declaration, then search nearest ancestors.
- if let Some(field) = find_field_at_level(type_info, field_name) {
- return Some(field);
- }
- for depth in (0..(*type_info).type_depth).rev() {
- let ancestor = *(*type_info).type_acenstors.add(depth as usize);
- if let Some(field) = find_field_at_level(ancestor, field_name) {
- return Some(field);
- }
- }
- None
-}
-
-unsafe fn find_field_at_level(
- type_info: *const crate::tvm_ffi_sys::TVMFFITypeInfo,
- field_name: &str,
-) -> Option<NonNull<TVMFFIFieldInfo>> {
- if type_info.is_null() || (*type_info).fields.is_null() {
- return None;
- }
- for index in 0..(*type_info).num_fields as usize {
- let field = (*type_info).fields.add(index);
- if (*field).name.as_str() == field_name {
- return NonNull::new(field.cast_mut());
- }
- }
- None
-}
-
-unsafe fn is_type_or_subtype(
- dynamic_type_index: i32,
- target_type_index: i32,
- target_type_depth: i32,
-) -> bool {
- if dynamic_type_index == target_type_index {
- return true;
- }
- let dynamic_info = TVMFFIGetTypeInfo(dynamic_type_index);
- if dynamic_info.is_null()
- || (*dynamic_info).type_depth <= target_type_depth
- || (*dynamic_info).type_acenstors.is_null()
- {
- return false;
- }
- let ancestor = *(*dynamic_info)
- .type_acenstors
- .add(target_type_depth as usize);
- !ancestor.is_null() && (*ancestor).type_index == target_type_index
-}
diff --git a/rust/tvm-ffi/src/reflection/access_path.rs
b/rust/tvm-ffi/src/reflection/access_path.rs
new file mode 100644
index 00000000..f74c2e50
--- /dev/null
+++ b/rust/tvm-ffi/src/reflection/access_path.rs
@@ -0,0 +1,88 @@
+/*
+ * 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.
+ */
+
+//! Access paths, which locate a value nested in another.
+
+use crate::derive::{Object, ObjectRef};
+use crate::object::{Object, ObjectArc, ObjectRef};
+use crate::Any;
+
+/// What an [`AccessStep`] accesses: an attribute, an array item or a map
+/// item, or one of those that is missing.
+///
+/// A closed set: the values are part of the ABI.
+#[repr(i32)]
+#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
+pub enum AccessKind {
+ /// An object attribute; the key is its name.
+ Attr = 0,
+ /// An array item; the key is its index.
+ ArrayItem = 1,
+ /// A map item; the key is the map key.
+ MapItem = 2,
+ /// An attribute that does not exist, in an error report.
+ AttrMissing = 3,
+ /// An array index out of range, in an error report.
+ ArrayItemMissing = 4,
+ /// A map key that is absent, in an error report.
+ MapItemMissing = 5,
+}
+
+/// The object behind an [`AccessStep`], laid out as the C++ `AccessStepObj`.
+#[repr(C)]
+#[derive(Object)]
+#[type_key = "ffi.reflection.AccessStep"]
+#[type_final]
+pub struct AccessStepObj {
+ object: Object,
+ /// What the step accesses.
+ pub kind: AccessKind,
+ /// The attribute name, array index or map key.
+ pub key: Any,
+}
+
+/// One step of an [`AccessPath`]: an attribute, array index or map key.
+#[repr(C)]
+#[derive(ObjectRef, Clone)]
+pub struct AccessStep {
+ data: ObjectArc<AccessStepObj>,
+}
+
+/// The object behind an [`AccessPath`], laid out as the C++ `AccessPathObj`.
+#[repr(C)]
+#[derive(Object)]
+#[type_key = "ffi.reflection.AccessPath"]
+#[type_final]
+pub struct AccessPathObj {
+ object: Object,
+ /// The path without its last step; `None` at the root.
+ pub parent: Option<ObjectRef>,
+ /// The last step; `None` at the root.
+ pub step: Option<AccessStep>,
+ /// The number of steps; 0 at the root.
+ pub depth: i32,
+}
+
+/// A path from a root value to a nested one, such as `root.field[0]`, as a
+/// chain of parents; its methods are reached with `reflection::get_method`.
+#[repr(C)]
+#[derive(ObjectRef, Clone)]
+pub struct AccessPath {
+ data: ObjectArc<AccessPathObj>,
+}
diff --git a/rust/tvm-ffi/src/reflection.rs
b/rust/tvm-ffi/src/reflection/accessor.rs
similarity index 80%
copy from rust/tvm-ffi/src/reflection.rs
copy to rust/tvm-ffi/src/reflection/accessor.rs
index f8ae5fd8..fe72c130 100644
--- a/rust/tvm-ffi/src/reflection.rs
+++ b/rust/tvm-ffi/src/reflection/accessor.rs
@@ -17,14 +17,11 @@
* under the License.
*/
-//! Object reflection: look up the methods, fields and type attributes of
-//! registered types, and register Rust object types with [`ObjectDef`].
+//! Reads of registered types: methods, fields and type attributes.
use std::ffi::c_void;
use std::ptr::NonNull;
-use crate::derive::{Object, ObjectRef};
-use crate::object::{Object, ObjectArc, ObjectRef};
use crate::tvm_ffi_sys::{
TVMFFIAny, TVMFFIByteArray, TVMFFIFieldGetter, TVMFFIFieldInfo,
TVMFFIGetTypeAttrColumn,
TVMFFIGetTypeInfo, TVMFFIObject, TVMFFITypeAttrColumn, TVMFFITypeIndex,
TVMFFITypeKeyToIndex,
@@ -32,13 +29,6 @@ use crate::tvm_ffi_sys::{
use crate::type_traits::AnyCompatible;
use crate::{Any, AnyView, Error, Function, ObjectCore, Result, TYPE_ERROR};
-mod registry;
-mod unsafe_init;
-pub use registry::{FieldFlags, ObjectDef, SEqHashKind, ShallowCopy};
-#[doc(hidden)]
-pub use unsafe_init::{drop_if_defined, unsafe_init_creator};
-pub use unsafe_init::{unsafe_init, UnsafeInit};
-
/// A registry-owned type-attribute column indexed by runtime type.
///
/// [`TypeAttrColumn::get`] returns owning copies. Registration must not race
@@ -122,10 +112,10 @@ pub fn get_method(type_key: &str, method_name: &str) ->
Result<Function> {
/// [`get_method`] does by type key.
///
/// Reflected methods, registered in any language (such as with
-/// [`ObjectDef::def`]), live in the type's method table rather than the global
-/// function table. The constructor is reachable under the reserved name
-/// `__ffi_init__`. For instance methods, the first argument is the object
-/// itself.
+/// [`ObjectDef::def`](super::ObjectDef::def)), live in the type's method
+/// table rather than the global function table. The constructor is reachable
+/// under the reserved name `__ffi_init__`. For instance methods, the first
+/// argument is the object itself.
///
/// `type_index` must be a registered type index, obtained for example from a
/// live object via `Any::type_index` or from a type key; the C API treats an
@@ -181,71 +171,6 @@ pub fn get_method_by_index(type_index: i32, method_name:
&str) -> Result<Functio
);
}
}
-
-/// What an [`AccessStep`] accesses: an attribute, an array item or a map
-/// item, or one of those that is missing.
-///
-/// A closed set: the values are part of the ABI.
-#[repr(i32)]
-#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
-pub enum AccessKind {
- /// An object attribute; the key is its name.
- Attr = 0,
- /// An array item; the key is its index.
- ArrayItem = 1,
- /// A map item; the key is the map key.
- MapItem = 2,
- /// An attribute that does not exist, in an error report.
- AttrMissing = 3,
- /// An array index out of range, in an error report.
- ArrayItemMissing = 4,
- /// A map key that is absent, in an error report.
- MapItemMissing = 5,
-}
-
-/// The object behind an [`AccessStep`], laid out as the C++ `AccessStepObj`.
-#[repr(C)]
-#[derive(Object)]
-#[type_key = "ffi.reflection.AccessStep"]
-#[type_final]
-pub struct AccessStepObj {
- object: Object,
- /// What the step accesses.
- pub kind: AccessKind,
- /// The attribute name, array index or map key.
- pub key: Any,
-}
-
-/// One step of an [`AccessPath`]: an attribute, array index or map key.
-#[repr(C)]
-#[derive(ObjectRef, Clone)]
-pub struct AccessStep {
- data: ObjectArc<AccessStepObj>,
-}
-
-/// The object behind an [`AccessPath`], laid out as the C++ `AccessPathObj`.
-#[repr(C)]
-#[derive(Object)]
-#[type_key = "ffi.reflection.AccessPath"]
-#[type_final]
-pub struct AccessPathObj {
- object: Object,
- /// The path without its last step; `None` at the root.
- pub parent: Option<ObjectRef>,
- /// The last step; `None` at the root.
- pub step: Option<AccessStep>,
- /// The number of steps; 0 at the root.
- pub depth: i32,
-}
-
-/// A path from a root value to a nested one, such as `root.field[0]`, as a
-/// chain of parents; its methods are reached with `reflection::get_method`.
-#[repr(C)]
-#[derive(ObjectRef, Clone)]
-pub struct AccessPath {
- data: ObjectArc<AccessPathObj>,
-}
-
/// Resolves a reflected field once, then uses its registered C ABI getter.
#[derive(Clone, Copy)]
pub struct FieldGetter {
diff --git a/rust/tvm-ffi/src/reflection/enum_def.rs
b/rust/tvm-ffi/src/reflection/enum_def.rs
new file mode 100644
index 00000000..9b39d056
--- /dev/null
+++ b/rust/tvm-ffi/src/reflection/enum_def.rs
@@ -0,0 +1,282 @@
+/*
+ * 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.
+ */
+
+//! Registration of enum values and their attributes, kept in each enum type's
+//! registry (the `__ffi_enum__` type attribute) that every language shares.
+
+use std::marker::PhantomData;
+use std::mem::{align_of, size_of};
+
+use super::{get_method, TypeAttrColumn, TypeAttrDef};
+use crate::any::TryFromTemp;
+use crate::derive::{Object, ObjectRef};
+use crate::object::{is_instance_of, Object, ObjectArc, ObjectCore, ObjectRef};
+use crate::tvm_ffi_sys::{TVMFFIGetTypeInfo, TVMFFIObject, TVMFFITypeInfo};
+use crate::type_traits::AnyCompatible;
+use crate::{Any, AnyView, EnumObj, EnumRef, Error, List, Result, String,
TYPE_ERROR, VALUE_ERROR};
+
+/// Registers a value of the enum type `E`, then its attributes.
+///
+/// [`new`](Self::new) registers the value at once, and
+/// [`set_attr`](Self::set_attr) sets each attribute on it, which
+/// [`EnumRef::attr_map`] reads. The value's object is an [`EnumObj`], so `E`'s
+/// object type adds no fields to its parent `EnumObj`.
+///
+/// # Examples
+///
+/// ```
+/// use tvm_ffi::derive::{Object, ObjectRef};
+/// use tvm_ffi::reflection::EnumDef;
+/// use tvm_ffi::{EnumObj, EnumRef, ObjectArc};
+///
+/// #[repr(C)]
+/// #[derive(Object)]
+/// #[type_key = "my_ext.Color"]
+/// #[type_register]
+/// #[type_final]
+/// pub struct ColorObj {
+/// base_: EnumObj,
+/// }
+///
+/// #[derive(Clone, ObjectRef)]
+/// pub struct Color {
+/// data: ObjectArc<ColorObj>,
+/// }
+///
+/// impl EnumRef for Color {}
+///
+/// let red = EnumDef::<Color>::new("red")?.set_attr("my_ext.hex",
0xff0000_i64)?.value();
+///
+/// let hex = Color::attr_map::<i64>("my_ext.hex")?.unwrap();
+/// assert_eq!(hex.get(&red)?, Some(0xff0000));
+/// # Ok::<(), tvm_ffi::Error>(())
+/// ```
+pub struct EnumDef<E: EnumRef> {
+ value: E,
+}
+
+impl<E: EnumRef> EnumDef<E> {
+ /// Register the value `name` of `E`.
+ ///
+ /// # Arguments
+ ///
+ /// * `name` - The name of the value.
+ ///
+ /// # Returns
+ ///
+ /// The builder of the value's attributes.
+ ///
+ /// # Errors
+ ///
+ /// Returns a `ValueError` if `E` already has a value named `name`, or a
+ /// `TypeError` if `E` is not an enum type or is an integer enum
+ /// (`ffi.IntEnum`), whose values name their own indices.
+ pub fn new(name: &str) -> Result<Self> {
+ const {
+ assert!(
+ size_of::<E::ContainerType>() == size_of::<EnumObj>()
+ && align_of::<E::ContainerType>() == align_of::<EnumObj>(),
+ "an enum type registered from Rust adds no fields to `EnumObj`"
+ )
+ };
+ let type_index = E::ContainerType::type_index();
+ let type_key = E::ContainerType::TYPE_KEY;
+ if !is_instance_of::<EnumObj>(type_index) || is_int_enum(type_index) {
+ return Err(Error::new(
+ TYPE_ERROR,
+ &format!("`{type_key}` is not an enum type whose values Rust
can register"),
+ "",
+ ));
+ }
+ let state = ensure_state::<E>()?;
+ let index = state.entries.len() as i64;
+ let key = String::from(name);
+ let indexes = AnyView::from(&state.indexes);
+ if dict_get(indexes, AnyView::from(&key))?.is_some()
+ || dict_get(indexes, AnyView::from(&index))?.is_some()
+ {
+ return Err(Error::new(
+ VALUE_ERROR,
+ &format!("Enum `{type_key}` already has a value named
`{name}`"),
+ "",
+ ));
+ }
+ let object = ObjectArc::into_raw(ObjectArc::new(EnumObj::new(index,
name)));
+ // SAFETY: `E`'s object is an `EnumObj` with `E`'s type index (asserted
+ // above), and the object is not shared yet. The deleter stays that of
+ // `EnumObj`, which drops the same fields.
+ let value = unsafe {
+ (*object.cast_mut().cast::<TVMFFIObject>()).type_index =
type_index;
+ E::from_data(ObjectArc::from_raw(object.cast()))
+ };
+ state.entries.push(Any::from(value.clone()))?;
+ dict_set(indexes, AnyView::from(&index), AnyView::from(&value))?;
+ dict_set(indexes, AnyView::from(&key), AnyView::from(&value))?;
+ Ok(Self { value })
+ }
+
+ /// Set the attribute `name` of the value.
+ ///
+ /// See also [`EnumRef::attr_map`].
+ ///
+ /// # Arguments
+ ///
+ /// * `name` - The name of the attribute.
+ /// * `value` - The value of the attribute.
+ ///
+ /// # Returns
+ ///
+ /// The builder, for the next attribute.
+ pub fn set_attr(self, name: &str, value: impl Into<Any>) -> Result<Self> {
+ let state = ensure_state::<E>()?;
+ let attrs = AnyView::from(&state.attrs);
+ let key = String::from(name);
+ let column = match dict_get(attrs, AnyView::from(&key))? {
+ Some(column) => column,
+ None => {
+ let column =
crate::cached_global_func!("ffi.Dict").call_packed(&[])?;
+ dict_set(attrs, AnyView::from(&key), AnyView::from(&column))?;
+ column
+ }
+ };
+ let value = value.into();
+ dict_set(
+ AnyView::from(&column),
+ AnyView::from(&self.value),
+ AnyView::from(&value),
+ )?;
+ Ok(self)
+ }
+
+ /// The registered value.
+ pub fn value(&self) -> E {
+ self.value.clone()
+ }
+}
+
+/// One attribute of the values of the enum type `E`, as `V`, returned by
+/// [`EnumRef::attr_map`].
+pub struct EnumAttrMap<E, V> {
+ column: Any,
+ _types: PhantomData<fn() -> (E, V)>,
+}
+
+impl<E: EnumRef, V: AnyCompatible> EnumAttrMap<E, V> {
+ pub(crate) fn lookup(name: &str) -> Result<Option<Self>> {
+ let Some(state) = enum_state(E::ContainerType::type_index())? else {
+ return Ok(None);
+ };
+ let key = String::from(name);
+ let column = dict_get(AnyView::from(&state.attrs),
AnyView::from(&key))?;
+ Ok(column.map(|column| Self {
+ column,
+ _types: PhantomData,
+ }))
+ }
+
+ /// The attribute of `value`, or `None` when it has none.
+ ///
+ /// # Errors
+ ///
+ /// Returns a `TypeError` if the attribute is not a `V`.
+ pub fn get(&self, value: &E) -> Result<Option<V>> {
+ dict_get(AnyView::from(&self.column), AnyView::from(value))?
+ .map(any_into)
+ .transpose()
+ }
+}
+
+/// The type attribute that holds an enum type's registry.
+const ENUM_STATE_ATTR: &str = "__ffi_enum__";
+
+/// The registry of one enum type: its values in registration order, the
+/// values by index and by name (`Dict<Any, ObjectRef>`), and the attribute
+/// columns (`Dict<String, Dict<ObjectRef, Any>>`).
+#[repr(C)]
+#[derive(Object)]
+#[type_key = "ffi.EnumState"]
+#[type_final]
+pub(crate) struct EnumStateObj {
+ base_: Object,
+ entries: List<Any>,
+ // The crate has no `Dict`; `dict_get` and `dict_set` call its global
+ // functions instead.
+ pub(crate) indexes: ObjectRef,
+ attrs: ObjectRef,
+}
+
+#[repr(C)]
+#[derive(ObjectRef, Clone)]
+pub(crate) struct EnumState {
+ data: ObjectArc<EnumStateObj>,
+}
+
+/// The registry of the enum type `type_index`, or `None` before its first
value.
+pub(crate) fn enum_state(type_index: i32) -> Result<Option<EnumState>> {
+ let Some(column) = TypeAttrColumn::new(ENUM_STATE_ATTR) else {
+ return Ok(None);
+ };
+ column.get(type_index).map(EnumState::try_from).transpose()
+}
+
+/// The registry of `E`, created with its first value.
+fn ensure_state<E: EnumRef>() -> Result<EnumState> {
+ if let Some(state) = enum_state(E::ContainerType::type_index())? {
+ return Ok(state);
+ }
+ let state: EnumState =
+ any_into(get_method("ffi.EnumState",
"__ffi_init__")?.call_packed(&[])?)?;
+ TypeAttrDef::<E::ContainerType>::new().attr(ENUM_STATE_ATTR,
state.clone())?;
+ Ok(state)
+}
+
+fn is_int_enum(type_index: i32) -> bool {
+ let is_int_enum =
+ |info: *const TVMFFITypeInfo| unsafe { (*info).type_key.as_str() ==
"ffi.IntEnum" };
+ unsafe {
+ let info = TVMFFIGetTypeInfo(type_index);
+ !info.is_null()
+ && (is_int_enum(info)
+ || (0..(*info).type_depth as usize)
+ .any(|depth|
is_int_enum(*(*info).type_acenstors.add(depth))))
+ }
+}
+
+pub(crate) fn dict_get(dict: AnyView<'_>, key: AnyView<'_>) ->
Result<Option<Any>> {
+ // `ffi.DictGetItemOrMissing` saves a call but returns a sentinel object
+ // that Rust cannot name.
+ let count: i64 = crate::cached_global_func!("ffi.DictCount")
+ .call_packed(&[dict, key])?
+ .try_into()?;
+ if count == 0 {
+ return Ok(None);
+ }
+ crate::cached_global_func!("ffi.DictGetItem")
+ .call_packed(&[dict, key])
+ .map(Some)
+}
+
+fn dict_set(dict: AnyView<'_>, key: AnyView<'_>, value: AnyView<'_>) ->
Result<()> {
+ crate::cached_global_func!("ffi.DictSetItem").call_packed(&[dict, key,
value])?;
+ Ok(())
+}
+
+pub(crate) fn any_into<T: AnyCompatible>(value: Any) -> Result<T> {
+
TryFromTemp::<T>::try_from(AnyView::from(&value)).map(TryFromTemp::into_value)
+}
diff --git a/rust/tvm-ffi/src/reflection/registry.rs
b/rust/tvm-ffi/src/reflection/registry.rs
index d21279d9..979412d4 100644
--- a/rust/tvm-ffi/src/reflection/registry.rs
+++ b/rust/tvm-ffi/src/reflection/registry.rs
@@ -165,7 +165,8 @@ impl BitOr for FieldFlags {
///
/// ```
/// use tvm_ffi::derive::ObjectRef;
-/// use tvm_ffi::{reflection, rs_object, Object, ObjectArc, ObjectDef, Result};
+/// use tvm_ffi::reflection::{self, ObjectDef};
+/// use tvm_ffi::{rs_object, Object, ObjectArc, Result};
///
/// #[rs_object("my_ext.MyPair", final, reflection =
MyPairObj::reflect_methods)]
/// pub struct MyPairObj {
@@ -215,8 +216,7 @@ impl<N: ObjectCore> ObjectDef<N> {
/// The caller holds `TYPE_REGISTRATION` for the builder's lifetime, and
/// `type_index` is `N`'s. `creator` returns an owned handle of an object
of
/// `N` that the field setters can fill.
- #[doc(hidden)]
- pub unsafe fn registering(
+ unsafe fn registering(
type_index: i32,
kind: Option<SEqHashKind>,
creator: Option<TVMFFIObjectCreator>,
@@ -304,8 +304,7 @@ impl<N: ObjectCore> ObjectDef<N> {
/// Registers `__ffi_shallow_copy__` from [`ShallowCopy`], which reflected
/// mutation (copy on write through the field setters) needs.
- #[doc(hidden)]
- pub fn shallow_copy(self) -> Result<Self>
+ fn shallow_copy(self) -> Result<Self>
where
N: ShallowCopy,
{
@@ -392,8 +391,7 @@ impl<N: ObjectCore> ObjectDef<N> {
/// `__ffi_init__` from the fields unless the type defines its own, then
/// `__ffi_type_final__`, which no other registered metadata exposes; and
/// reports the first failed method registration.
- #[doc(hidden)]
- pub fn finish(self) -> Result<()> {
+ fn finish(self) -> Result<()> {
if let Some(error) = self.error {
return Err(error);
}
@@ -425,6 +423,85 @@ impl<N: ObjectCore> ObjectDef<N> {
}
}
+/// Registers the reflection of `N` for `#[derive(Object)]`: its metadata,
+/// then `fields` its fields, its `__ffi_shallow_copy__`, then `methods` its
+/// methods, then its type attributes.
+///
+/// # Safety
+///
+/// As `ObjectDef::registering`; `fields` registers fields of `N` as
+/// [`ObjectDef::field`] requires.
+#[doc(hidden)]
+pub unsafe fn register_object<N: ObjectCore + ShallowCopy>(
+ type_index: i32,
+ kind: Option<SEqHashKind>,
+ creator: Option<TVMFFIObjectCreator>,
+ fields: impl FnOnce(ObjectDef<N>) -> Result<ObjectDef<N>>,
+ methods: impl FnOnce(&mut ObjectDef<N>),
+) -> Result<()> {
+ let mut def = fields(ObjectDef::registering(type_index, kind,
creator)?)?.shallow_copy()?;
+ methods(&mut def);
+ def.finish()
+}
+
+/// Registers type attributes of the object type `T`, each as soon as
+/// [`attr`](Self::attr) names it, for every language to read with
+/// [`TypeAttrColumn`](crate::reflection::TypeAttrColumn).
+///
+/// # Examples
+///
+/// ```
+/// use tvm_ffi::derive::Object;
+/// use tvm_ffi::reflection::TypeAttrDef;
+/// use tvm_ffi::reflection::TypeAttrColumn;
+/// use tvm_ffi::{Object, ObjectCore};
+///
+/// #[repr(C)]
+/// #[derive(Object)]
+/// #[type_key = "my_ext.MyPair"]
+/// #[type_register]
+/// pub struct MyPairObj {
+/// base_: Object,
+/// }
+///
+/// TypeAttrDef::<MyPairObj>::new().attr("my_ext.arity", 2_i64)?;
+///
+/// let column = TypeAttrColumn::new("my_ext.arity").unwrap();
+/// let arity: i64 = column.get(MyPairObj::type_index()).unwrap().try_into()?;
+/// assert_eq!(arity, 2);
+/// # Ok::<(), tvm_ffi::Error>(())
+/// ```
+pub struct TypeAttrDef<T: ObjectCore> {
+ _type: PhantomData<fn() -> T>,
+}
+
+impl<T: ObjectCore> TypeAttrDef<T> {
+ /// A builder for the type attributes of `T`.
+ #[allow(clippy::new_without_default)]
+ pub fn new() -> Self {
+ Self { _type: PhantomData }
+ }
+
+ /// Register the type attribute `name` of `T`.
+ ///
+ /// # Arguments
+ ///
+ /// * `name` - The name of the attribute.
+ /// * `value` - The value of the attribute.
+ ///
+ /// # Returns
+ ///
+ /// The builder, for the next attribute.
+ ///
+ /// # Errors
+ ///
+ /// Returns an error if `T` already has the attribute `name`.
+ pub fn attr(self, name: &str, value: impl Into<Any>) -> Result<Self> {
+ register_type_attr(T::type_index(), name, value.into())?;
+ Ok(self)
+ }
+}
+
/// The structural kind of the nearest ancestor of `info` with metadata.
unsafe fn inherited_kind(info: *const crate::tvm_ffi_sys::TVMFFITypeInfo) ->
i32 {
for depth in (0..(*info).type_depth).rev() {
diff --git a/rust/tvm-ffi/tests/test_enum.rs b/rust/tvm-ffi/tests/test_enum.rs
new file mode 100644
index 00000000..54aecd95
--- /dev/null
+++ b/rust/tvm-ffi/tests/test_enum.rs
@@ -0,0 +1,76 @@
+/*
+ * 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.
+ */
+
+//! An enum registered in C++ (`testing.TestEnumVariant`, values `Alpha` and
`Beta` with the
+//! attribute `code`), extended from Rust.
+
+use tvm_ffi::derive::{Object, ObjectRef};
+use tvm_ffi::reflection::EnumDef;
+use tvm_ffi::{EnumObj, EnumRef, ObjectArc, ObjectRefCore, Result, VALUE_ERROR};
+
+#[repr(C)]
+#[derive(Object)]
+#[type_key = "testing.TestEnumVariant"]
+#[type_final]
+struct TestEnumVariantObj {
+ base_: EnumObj,
+}
+
+#[derive(Clone, ObjectRef)]
+struct TestEnumVariant {
+ data: ObjectArc<TestEnumVariantObj>,
+}
+
+impl EnumRef for TestEnumVariant {}
+
+#[test]
+fn test_enum_lookup_register_and_attrs() -> Result<()> {
+ // Keep the testing library linked so its static type registrations run.
+ assert_eq!(
+ unsafe { tvm_ffi::tvm_ffi_sys::TVMFFITestingDummyTarget() },
+ 0
+ );
+
+ let alpha = TestEnumVariant::get("Alpha")?;
+ assert_eq!(alpha.name(), "Alpha");
+ assert!(alpha.same_as(&TestEnumVariant::get("Alpha")?));
+ assert_eq!(
+ TestEnumVariant::get("Delta").unwrap_err().kind(),
+ VALUE_ERROR
+ );
+
+ let code = TestEnumVariant::attr_map::<i64>("code")?.unwrap();
+ assert_eq!(code.get(&alpha)?, Some(10));
+ assert!(TestEnumVariant::attr_map::<i64>("no_such_attr")?.is_none());
+
+ let gamma = EnumDef::<TestEnumVariant>::new("Gamma")?
+ .set_attr("code", 30_i64)?
+ .value();
+ assert!(gamma.same_as(&TestEnumVariant::get("Gamma")?));
+ // The map read before the registration sees the new value.
+ assert_eq!(code.get(&gamma)?, Some(30));
+ assert_eq!(
+ EnumDef::<TestEnumVariant>::new("Beta")
+ .err()
+ .unwrap()
+ .kind(),
+ VALUE_ERROR
+ );
+ Ok(())
+}
diff --git a/rust/tvm-ffi/tests/test_object_reflection.rs
b/rust/tvm-ffi/tests/test_object_reflection.rs
index e58436a0..6071039a 100644
--- a/rust/tvm-ffi/tests/test_object_reflection.rs
+++ b/rust/tvm-ffi/tests/test_object_reflection.rs
@@ -18,7 +18,7 @@
*/
use tvm_ffi::derive::{Object, ObjectRef};
use tvm_ffi::reflection::TypeAttrColumn;
-use tvm_ffi::reflection::{FieldFlags, ObjectDef};
+use tvm_ffi::reflection::{FieldFlags, FieldGetter, ObjectDef};
use tvm_ffi::*;
use tvm_ffi_sys::{
TVMFFIFieldFlagBitMask, TVMFFIFieldInfo, TVMFFIFieldSetter,
TVMFFIGetTypeInfo,
diff --git a/rust/tvm-ffi/tests/test_rs_object.rs
b/rust/tvm-ffi/tests/test_rs_object.rs
index 7a4aadb4..848fef67 100644
--- a/rust/tvm-ffi/tests/test_rs_object.rs
+++ b/rust/tvm-ffi/tests/test_rs_object.rs
@@ -21,7 +21,9 @@ use std::borrow::Borrow;
use tvm_ffi::derive::{Object, ObjectRef};
use tvm_ffi::object::{ObjectArc, ObjectRef, ObjectRefCore};
use tvm_ffi::reflection::TypeAttrColumn;
-use tvm_ffi::reflection::{drop_if_defined, FieldFlags, ObjectDef};
+use tvm_ffi::reflection::{
+ drop_if_defined, FieldFlags, FieldGetter, ObjectDef, ShallowCopy,
UnsafeInit,
+};
use tvm_ffi::*;
use tvm_ffi_sys::{TVMFFIFieldInfo, TVMFFIGetTypeInfo, TVMFFISEqHashKind,
TVMFFITypeInfo};
diff --git a/rust/tvm-ffi/tests/test_structural_mutate.rs
b/rust/tvm-ffi/tests/test_structural_mutate.rs
index 6d9a388a..3b6059e1 100644
--- a/rust/tvm-ffi/tests/test_structural_mutate.rs
+++ b/rust/tvm-ffi/tests/test_structural_mutate.rs
@@ -22,11 +22,12 @@ use tvm_ffi::collections::map::MapObj;
use tvm_ffi::extra::structural_mutate::IntoMutateResult;
use tvm_ffi::function::FunctionObj;
use tvm_ffi::object::ObjectRef;
+use tvm_ffi::reflection::FieldGetter;
use tvm_ffi::ObjectRefCore;
use tvm_ffi::{
dispatch, structural_map, structural_mutate, structural_visit,
structural_walk, Any, AnyView,
- Array, DefRegionKind, Dispatcher, Error, FieldGetter, Function,
InplaceMode, IntoDispatcher,
- Map, MutationView, Object, ObjectArc, Result, String as FfiString,
StructuralMutatorView,
+ Array, DefRegionKind, Dispatcher, Error, Function, InplaceMode,
IntoDispatcher, Map,
+ MutationView, Object, ObjectArc, Result, String as FfiString,
StructuralMutatorView,
StructuralVisitorView, TypeIndex, Unchanged, UnchangedOr, VisitInterrupt,
WalkOrder,
WalkResult, RUNTIME_ERROR,
};
diff --git a/rust/tvm-ffi/tests/test_structural_visit.rs
b/rust/tvm-ffi/tests/test_structural_visit.rs
index fafeb596..47a2a71b 100644
--- a/rust/tvm-ffi/tests/test_structural_visit.rs
+++ b/rust/tvm-ffi/tests/test_structural_visit.rs
@@ -19,13 +19,12 @@
use std::cell::{Cell, RefCell};
use tvm_ffi::object::ObjectRef;
-use tvm_ffi::reflection::TypeAttrColumn;
+use tvm_ffi::reflection::{FieldGetter, TypeAttrColumn};
use tvm_ffi::ObjectRefCore;
use tvm_ffi::{
dispatch, structural_visit, structural_walk, Any, AnyView, Array,
DLDataType, DLDataTypeCode,
- DefRegionKind, Dispatcher, Error, FieldGetter, Function, Map, Object,
Result,
- String as FfiString, StructuralVisitorView, TypeIndex, VisitInterrupt,
WalkOrder, WalkResult,
- RUNTIME_ERROR,
+ DefRegionKind, Dispatcher, Error, Function, Map, Object, Result, String as
FfiString,
+ StructuralVisitorView, TypeIndex, VisitInterrupt, WalkOrder, WalkResult,
RUNTIME_ERROR,
};
fn runtime_error(message: &str) -> Error {
diff --git a/tests/python/test_stubgen_rust.py
b/tests/python/test_stubgen_rust.py
index 1ad7c48b..14d48e5a 100644
--- a/tests/python/test_stubgen_rust.py
+++ b/tests/python/test_stubgen_rust.py
@@ -423,7 +423,7 @@ def test_render_root_object() -> None:
"tvm_ffi::Object",
"tvm_ffi::ObjectArc",
"tvm_ffi::ObjectCore",
- "tvm_ffi::FieldGetter",
+ "tvm_ffi::reflection::FieldGetter",
"tvm_ffi::Result",
"tvm_ffi::String",
"tvm_ffi::Array",
@@ -475,7 +475,7 @@ def test_render_derived_object_cross_module() -> None:
def test_render_object_under_builtin_parent() -> None:
- """A builtin parent is embedded via header-only stand-ins so `TYPE_DEPTH`
matches the registry."""
+ """A builtin parent the crate lacks is a header-only stand-in, so
`TYPE_DEPTH` matches."""
info = _info(
"demo.Color",
(_field("value", "int"),),
@@ -484,13 +484,12 @@ def test_render_object_under_builtin_parent() -> None:
)
text, imports = _render(info)
assert " base_: FfiIntEnumObj," in text
- # One stand-in per builtin ancestor below `ffi.Object`, chained through
`base`.
- assert imports.builtin_mirrors == {"ffi.Enum": "Object", "ffi.IntEnum":
"FfiEnumObj"}
- assert "tvm_ffi::Object" in _uses(imports)
- # No Deref, upcast or crate import for the stand-ins.
+ # One stand-in per builtin ancestor below the crate's `EnumObj`, chained
through `base`.
+ assert imports.builtin_mirrors == {"ffi.IntEnum": "EnumObj"}
+ assert "tvm_ffi::EnumObj" in _uses(imports)
+ # No Deref or upcast for the stand-ins.
assert "impl Deref for ColorObj" not in text
assert "unsafe_impl_object_ref_upcast" not in text
- assert not any("Enum" in path for path in _uses(imports))
# A child of `ffi.Object` embeds the crate's header.
text, imports = _render(_info("demo.Root"))
assert " base_: Object," in text
@@ -507,25 +506,16 @@ def test_render_object_under_builtin_parent() -> None:
BUILTIN_MIRRORS_EXPECTED = """\
-use tvm_ffi::Object;
+use tvm_ffi::EnumObj;
use tvm_ffi::ObjectArc;
-/// Header-only stand-in for the builtin `ffi.Enum`; it only carries the
ancestor depth.
-#[allow(dead_code)]
-#[repr(C)]
-#[derive(tvm_ffi::derive::Object)]
-#[type_key = "ffi.Enum"]
-struct FfiEnumObj {
- base_: Object,
-}
-
/// Header-only stand-in for the builtin `ffi.IntEnum`; it only carries the
ancestor depth.
#[allow(dead_code)]
#[repr(C)]
#[derive(tvm_ffi::derive::Object)]
#[type_key = "ffi.IntEnum"]
struct FfiIntEnumObj {
- base_: FfiEnumObj,
+ base_: EnumObj,
}
/// Header-only stand-in for the builtin `ffi.StrEnum`; it only carries the
ancestor depth.
@@ -534,7 +524,7 @@ struct FfiIntEnumObj {
#[derive(tvm_ffi::derive::Object)]
#[type_key = "ffi.StrEnum"]
struct FfiStrEnumObj {
- base_: FfiEnumObj,
+ base_: EnumObj,
}"""
@@ -849,8 +839,8 @@ def test_render_complete_expr_golden() -> None:
"std::ptr::addr_of_mut",
"tvm_ffi::Object",
"tvm_ffi::ObjectArc",
- "tvm_ffi::ShallowCopy",
- "tvm_ffi::UnsafeInit",
+ "tvm_ffi::reflection::ShallowCopy",
+ "tvm_ffi::reflection::UnsafeInit",
"tvm_ffi::reflection::drop_if_defined",
}
@@ -971,7 +961,7 @@ def test_render_complete_enum_field() -> None:
assert " pub kind: Kind,\n" in text
assert " pub fn new(a: i64, kind: impl Into<Kind>) -> Self {" in text
assert {"tvm_ffi::Result", "tvm_ffi::Error", "tvm_ffi::VALUE_ERROR"} <=
_uses(imports)
- assert "tvm_ffi::FieldGetter" not in _uses(imports)
+ assert "tvm_ffi::reflection::FieldGetter" not in _uses(imports)
def test_allow_missing_docs_and_closed_enum() -> None:
@@ -1069,7 +1059,7 @@ def test_final_type_has_no_unsafe_init() -> None:
info = _info("demo.Leaf", (_field("a", "int", 24, 8),), total_size=32,
is_final=True)
text, imports = _render(info)
assert "UnsafeInit" not in text
- assert "tvm_ffi::UnsafeInit" not in _uses(imports)
+ assert "tvm_ffi::reflection::UnsafeInit" not in _uses(imports)
def test_complete_list_field_mirrors() -> None:
@@ -1279,19 +1269,16 @@ def test_opaque_directive_vetoes_a_complete_type() ->
None:
def test_builtin_parent_keeps_the_type_opaque() -> None:
- """The crate never mirrors a builtin's bytes: the header-only stand-in
cannot be complete."""
- # Fieldless under `ffi.Enum` (48 bytes): the fill criterion alone would
call this complete.
- info = _info(
- "demo.Flag", parent="ffi.Enum", ancestors=["ffi.Object", "ffi.Enum"],
total_size=48
- )
- text, _ = _render(info)
- assert "/// Opaque: parent 'ffi.Enum' is opaque (no-mirror)." in text
- assert " base_: FfiEnumObj," in text
- assert "const _: () =" not in text
+ """The crate's `EnumObj` is a complete parent; a builtin stand-in cannot
be one."""
+ # An enum type embeds `EnumObj`; its registry allocates the values, so
there is no `new`.
+ text, _ = _render(object_info_from_type_key("testing.TestEnumVariant"))
+ assert "/// Complete: reflected fields fill [48, 48) exactly." in text
+ assert " base_: EnumObj," in text
+ assert "size_of::<TestEnumVariantObj>() == 48" in text
assert "fn new(" not in text
- # The registry fixtures under `ffi.Enum` / `ffi.IntEnum` / `ffi.StrEnum`
follow the same rule.
+ assert "impl tvm_ffi::EnumRef for TestEnumVariant {}" in text
+ # The crate never mirrors the bytes of `ffi.IntEnum` / `ffi.StrEnum`.
for type_key, parent in (
- ("testing.TestEnumVariant", "ffi.Enum"),
("testing.TestCxxIntEnum", "ffi.IntEnum"),
("testing.TestCxxStrEnum", "ffi.StrEnum"),
):
@@ -1299,6 +1286,7 @@ def test_builtin_parent_keeps_the_type_opaque() -> None:
assert f"/// Opaque: parent '{parent}' is opaque (no-mirror)." in text
assert "const _: () =" not in text
assert "fn new(" not in text
+ assert f"impl tvm_ffi::EnumRef for {type_key.rpartition('.')[2]} {{}}"
in text
def test_opaque_parent_keeps_the_child_opaque() -> None:
@@ -1438,7 +1426,7 @@ def test_registry_complete_chain_is_mirrored() -> None:
"tvm_ffi::unsafe_impl_object_ref_upcast!(TestCxxClassDerivedDerived =>
TestCxxClassBase, "
"TestCxxClassDerivedDerived => TestCxxClassDerived);"
)
- assert {"tvm_ffi::String", "tvm_ffi::ShallowCopy"} <= _uses(imports)
+ assert {"tvm_ffi::String", "tvm_ffi::reflection::ShallowCopy"} <=
_uses(imports)
def test_registry_hidden_field_and_vptr_stay_opaque() -> None:
@@ -1498,7 +1486,9 @@ def test_cli_init_generates_a_module_tree(tmp_path: Path,
monkeypatch: pytest.Mo
assert (root / "mod.rs").read_text(encoding="utf-8") == "pub mod
testing;\n"
text = (root / "testing" / "mod.rs").read_text(encoding="utf-8")
assert text.startswith("#![allow(dead_code, unused_imports)]\n")
- assert "use tvm_ffi::FieldGetter;" in text # the opaque fixtures read
through getters
+ assert (
+ "use tvm_ffi::reflection::FieldGetter;" in text
+ ) # the opaque fixtures read through getters
assert '#[type_key = "testing.TestCxxClassDerivedDerived"]' in text
assert " base_: TestCxxClassDerivedObj,\n pub v_str: String," in text
assert (
@@ -1506,11 +1496,11 @@ def test_cli_init_generates_a_module_tree(tmp_path:
Path, monkeypatch: pytest.Mo
"TestCxxClassDerivedDerived => TestCxxClassDerived);"
) in text
# Builtin ancestors are mirrored once, in the import section; enum
fixtures embed the last one.
- assert text.count("struct FfiEnumObj {\n base_: Object,\n}") == 1
- assert text.count("struct FfiIntEnumObj {\n base_: FfiEnumObj,\n}") == 1
- assert text.count("struct FfiStrEnumObj {\n base_: FfiEnumObj,\n}") == 1
+ assert "struct FfiEnumObj" not in text
+ assert text.count("struct FfiIntEnumObj {\n base_: EnumObj,\n}") == 1
+ assert text.count("struct FfiStrEnumObj {\n base_: EnumObj,\n}") == 1
assert text.index("struct FfiIntEnumObj") <
text.index(f"{C.RUST_SYNTAX.begin} object/")
- assert "pub struct TestEnumVariantObj {\n base_: FfiEnumObj,\n}" in text
+ assert "pub struct TestEnumVariantObj {\n base_: EnumObj,\n}" in text
assert "pub struct TestCxxIntEnumObj {\n base_: FfiIntEnumObj,\n}" in
text
assert "pub struct TestCxxStrEnumObj {\n base_: FfiStrEnumObj,\n}" in
text
# Running again over the generated tree is a no-op.