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 a35b3eab [STUBGEN][RUST] Rename the wrapper slot to `base` and tighten
`rust_ident` (#742)
a35b3eab is described below
commit a35b3eab56e577dd5414f8a53b941111cd3cc08e
Author: Linzhang Li <[email protected]>
AuthorDate: Fri Sep 4 21:54:17 2026 -0400
[STUBGEN][RUST] Rename the wrapper slot to `base` and tighten `rust_ident`
(#742)
## Summary
For every reflected object the Rust target emits two structs: `XObj`,
the `#[repr(C)]` mirror whose first field `base` holds the parent, and
`X`, the reference wrapper that owns an `ObjectArc<XObj>`. That wrapper
field was named `data`, which made `base` and `data` the two names a
reflected field could not use; `rust_ident` spelled such fields `base_`
/ `data_`, and TVM has both (`tirx.Ramp.base`, `tirx.DeclBuffer.data`).
This PR names the wrapper field `base` too, so `base` is the only
reserved name and a reflected `data` stays `data`. `derive(ObjectRef)`
now takes the slot name from the struct's first field instead of
requiring `data`, so the crate's own wrappers and the generated ones
both derive. `rust_ident` also becomes exact: it unwraps a single
trailing underscore (`imports_` -> `imports`) and a dunder name
(`__dict__` -> `dict`) and leaves every other spelling alone, instead of
stripping all trailing underscores.
## Changes
- `rust/tvm-ffi-macros/src/object_macros.rs`: `derive_object_ref` reads
the first field's identifier and emits it in `data` / `into_data` /
`from_data` and the `Any` conversions, instead of hard-coding `data`.
- `rust_generator/codegen.py`: the wrapper struct, its `Deref`, and the
allocator body use `base`.
- `rust_generator/consts.py`: `RUST_RESERVED_MEMBERS = {"base"}`.
- `rust_generator/utils.py`: `rust_ident` maps `name_` and `__name__` to
`name`; `__name`, `_name`, `name__`, `_` stay verbatim.
- `examples/rust_stubgen/`: regenerated `mod.rs`; `test_rust_ident` and
the goldens updated.
## Testing
`test_stubgen_rust.py`, `test_stubgen.py`, `test_stub_layout.py`: 121
passed; ruff clean. `cargo test` in `rust/` passes with the macro
change. The example regenerates an identical `mod.rs` and `cargo run`
prints `a=1 b=2 kind=PairKind(1)` and `sum=3`.
Signed-off-by: yuchuan <[email protected]>
---
.../rust/src/generated/rust_stubgen/mod.rs | 6 ++--
python/tvm_ffi/stub/rust_generator/codegen.py | 7 ++--
python/tvm_ffi/stub/rust_generator/consts.py | 8 ++---
python/tvm_ffi/stub/rust_generator/utils.py | 14 +++++---
rust/tvm-ffi-macros/src/object_macros.rs | 37 ++++++++++-----------
tests/python/test_stubgen_rust.py | 38 +++++++++++++---------
6 files changed, 59 insertions(+), 51 deletions(-)
diff --git a/examples/rust_stubgen/rust/src/generated/rust_stubgen/mod.rs
b/examples/rust_stubgen/rust/src/generated/rust_stubgen/mod.rs
index d1085be2..01afb51b 100644
--- a/examples/rust_stubgen/rust/src/generated/rust_stubgen/mod.rs
+++ b/examples/rust_stubgen/rust/src/generated/rust_stubgen/mod.rs
@@ -79,13 +79,13 @@ const _: () = {
#[repr(C)]
#[derive(tvm_ffi::derive::ObjectRef, Clone)]
pub struct IntPair {
- data: ObjectArc<IntPairObj>,
+ base: ObjectArc<IntPairObj>,
}
impl Deref for IntPair {
type Target = IntPairObj;
fn deref(&self) -> &IntPairObj {
- &self.data
+ &self.base
}
}
@@ -100,7 +100,7 @@ impl IntPair {
/// Lossless complete-field allocation.
pub fn new(a: i64, b: i64, kind: PairKind) -> Self {
let obj = IntPairObj::new(a, b, kind);
- Self { data: ObjectArc::new(obj) }
+ Self { base: ObjectArc::new(obj) }
}
}
// tvm-ffi-stubgen(end)
diff --git a/python/tvm_ffi/stub/rust_generator/codegen.py
b/python/tvm_ffi/stub/rust_generator/codegen.py
index 78d778fb..bed9af62 100644
--- a/python/tvm_ffi/stub/rust_generator/codegen.py
+++ b/python/tvm_ffi/stub/rust_generator/codegen.py
@@ -444,7 +444,7 @@ class _ObjectRenderer:
"pub fn from_complete_fields" if custom else "pub fn new",
params,
(f"obj = {self.obj_struct}::new", forward),
- "Self { data: ObjectArc::new(obj) }",
+ "Self { base: ObjectArc::new(obj) }",
),
"}",
]
@@ -478,12 +478,11 @@ class _ObjectRenderer:
"#[repr(C)]",
"#[derive(tvm_ffi::derive::ObjectRef, Clone)]",
f"pub struct {self.leaf} {{",
- # a reflected `data` field becomes `data_` (`rust_ident`)
- f" data: ObjectArc<{self.obj_struct}>,",
+ f" base: ObjectArc<{self.obj_struct}>,",
"}",
]
)
- sections.append(self._deref_lines(self.leaf, self.obj_struct, "data"))
+ sections.append(self._deref_lines(self.leaf, self.obj_struct, "base"))
if has_parent:
sections.append(self._deref_lines(self.obj_struct, base, "base"))
if has_accessors:
diff --git a/python/tvm_ffi/stub/rust_generator/consts.py
b/python/tvm_ffi/stub/rust_generator/consts.py
index 0636cbfb..b6543dbf 100644
--- a/python/tvm_ffi/stub/rust_generator/consts.py
+++ b/python/tvm_ffi/stub/rust_generator/consts.py
@@ -112,10 +112,10 @@ RUST_KEYWORDS = frozenset(
)
RUST_NOT_RAW_IDENTIFIERS = frozenset({"self", "Self", "super", "crate"})
-#: Member names the generated structs use themselves: ``base`` is the parent
slot of every object
-#: struct, ``data`` the wrapper's ``ObjectArc``. A reflected field with one of
these names would
-#: collide (or shadow through ``Deref``), so ``rust_ident`` spells it
``base_`` / ``data_``.
-RUST_RESERVED_MEMBERS = frozenset({"base", "data"})
+#: The one member name the generated structs use themselves: ``base`` is the
parent slot of every
+#: object struct and the ``ObjectArc`` slot of every reference wrapper. A
reflected field of that
+#: name would collide (or shadow through ``Deref``), so ``rust_ident`` spells
it ``base_``.
+RUST_RESERVED_MEMBERS = frozenset({"base"})
#: ``rustfmt``'s default ``max_width``; a wider allocator signature wraps one
parameter per line.
RUST_MAX_WIDTH = 100
diff --git a/python/tvm_ffi/stub/rust_generator/utils.py
b/python/tvm_ffi/stub/rust_generator/utils.py
index 27510efd..d38e35bc 100644
--- a/python/tvm_ffi/stub/rust_generator/utils.py
+++ b/python/tvm_ffi/stub/rust_generator/utils.py
@@ -133,12 +133,18 @@ def _generic(base: str | None, *params: str | None) ->
str | None:
def rust_ident(name: str) -> str:
- """Spell a reflected field name in Rust: drop the C++ trailing underscore,
escape collisions.
+ """Spell a reflected field name in Rust.
- Keywords become raw identifiers; the four that cannot, and the names the
- generated structs use themselves (``base``, ``data``), get a trailing
underscore.
+ Two naming conventions are unwrapped, and nothing else is renamed: a C++
+ member with one trailing underscore (``global_var_map_``) loses it, and a
+ dunder name (``__dict__``) loses both pairs. Keywords become raw
+ identifiers; the four that cannot, and ``base`` (the member name the
+ generated structs use themselves), get a trailing underscore.
"""
- name = name.rstrip("_") or name
+ if len(name) > 4 and name.startswith("__") and name.endswith("__"):
+ name = name[2:-2]
+ elif len(name) > 1 and name.endswith("_") and not name.endswith("__"):
+ name = name[:-1]
if name in C.RUST_NOT_RAW_IDENTIFIERS or name in C.RUST_RESERVED_MEMBERS:
return f"{name}_"
if name in C.RUST_KEYWORDS:
diff --git a/rust/tvm-ffi-macros/src/object_macros.rs
b/rust/tvm-ffi-macros/src/object_macros.rs
index 68acb3df..9a0fe807 100644
--- a/rust/tvm-ffi-macros/src/object_macros.rs
+++ b/rust/tvm-ffi-macros/src/object_macros.rs
@@ -133,36 +133,33 @@ pub fn derive_object_ref(input: proc_macro::TokenStream)
-> TokenStream {
let derive_input = syn::parse_macro_input!(input as DeriveInput);
let struct_name = derive_input.ident.clone();
- // search for field name base and derive the base type
- // we expect base always to be the first field
- let data_ty = match &derive_input.data {
- syn::Data::Struct(s) => s.fields.iter().next().and_then(|f| {
- let (data_id, data_ty) = (f.ident.clone()?, f.ty.clone());
- if data_id == "data" {
- // The transitive case of subtyping
- Some(data_ty)
- } else {
- None
- }
- }),
+ // The `ObjectArc<T>` slot is the first field; its name is the struct's
choice
+ // (`data` in this crate, `base` in stubgen output, so that reflected
fields
+ // named `data` keep their name).
+ let (data_id, data_ty) = match &derive_input.data {
+ syn::Data::Struct(s) => s
+ .fields
+ .iter()
+ .next()
+ .and_then(|f| Some((f.ident.clone()?, f.ty.clone()))),
_ => panic!("derive only works for structs"),
}
- .expect("First field must be `data: ObjectArc<T>`");
+ .expect("First field must be `<name>: ObjectArc<T>`");
let mut expanded = quote! {
unsafe impl #tvm_ffi_crate::object::ObjectRefCore for #struct_name {
type ContainerType = <#data_ty as std::ops::Deref>::Target;
#[inline]
fn data(this: &Self) -> &ObjectArc<Self::ContainerType> {
- &this.data
+ &this.#data_id
}
#[inline]
fn into_data(this: Self) -> ObjectArc<Self::ContainerType> {
- this.data
+ this.#data_id
}
#[inline]
unsafe fn from_data(data: ObjectArc<Self::ContainerType>) -> Self {
- Self { data}
+ Self { #data_id: data }
}
}
@@ -202,7 +199,7 @@ pub fn derive_object_ref(input: proc_macro::TokenStream) ->
TokenStream {
type ContainerType = <#struct_name as
#tvm_ffi_crate::object::ObjectRefCore>
::ContainerType;
let data_ptr =
#tvm_ffi_crate::object::ObjectArc::<ContainerType>::as_raw(
- &src.data
+ &src.#data_id
);
let object_ptr =
data_ptr as *mut ContainerType as *mut
#tvm_ffi_crate::tvm_ffi_sys::TVMFFIObject;
@@ -232,7 +229,7 @@ pub fn derive_object_ref(input: proc_macro::TokenStream) ->
TokenStream {
data_ptr as *mut #tvm_ffi_crate::tvm_ffi_sys::TVMFFIObject
);
Self {
- data : #tvm_ffi_crate::object::ObjectArc::from_raw(
+ #data_id: #tvm_ffi_crate::object::ObjectArc::from_raw(
data_ptr as *mut ContainerType
)
}
@@ -246,7 +243,7 @@ pub fn derive_object_ref(input: proc_macro::TokenStream) ->
TokenStream {
type ContainerType = <#struct_name as
#tvm_ffi_crate::object::ObjectRefCore>
::ContainerType;
let data_ptr = #tvm_ffi_crate::object::ObjectArc::into_raw(
- src.data
+ src.#data_id
);
let object_ptr =
data_ptr as *mut ContainerType as *mut
#tvm_ffi_crate::tvm_ffi_sys::TVMFFIObject;
@@ -263,7 +260,7 @@ pub fn derive_object_ref(input: proc_macro::TokenStream) ->
TokenStream {
::ContainerType;
let data_ptr = data.data_union.v_obj as *mut ContainerType;
Self {
- data :
#tvm_ffi_crate::object::ObjectArc::<ContainerType>::from_raw(data_ptr)
+ #data_id:
#tvm_ffi_crate::object::ObjectArc::<ContainerType>::from_raw(data_ptr)
}
}
diff --git a/tests/python/test_stubgen_rust.py
b/tests/python/test_stubgen_rust.py
index 446e1ade..f2532cb5 100644
--- a/tests/python/test_stubgen_rust.py
+++ b/tests/python/test_stubgen_rust.py
@@ -198,12 +198,18 @@ def test_render_rust_type_without_mirror(schema:
TypeSchema) -> None:
def test_rust_ident() -> None:
assert rust_ident("value") == "value"
- assert rust_ident("imports_") == "imports"
+ assert rust_ident("imports_") == "imports" # C++ member convention: one
trailing underscore
+ assert rust_ident("__dict__") == "dict" # dunder convention: both pairs
+ assert rust_ident("__dict") == "__dict" # neither convention: kept
verbatim
+ assert rust_ident("_private") == "_private"
+ assert rust_ident("odd__") == "odd__"
+ assert rust_ident("_") == "_"
+ assert rust_ident("____") == "____"
assert rust_ident("type") == "r#type"
assert rust_ident("self") == "self_"
assert rust_ident("crate") == "crate_"
assert rust_ident("base") == "base_" # the parent slot of every generated
object struct
- assert rust_ident("data") == "data_" # the wrapper's `ObjectArc` member
+ assert rust_ident("data") == "data" # the wrapper slot is `base`, so
`data` is free
# ---------------------------------------------------------------------------
@@ -288,13 +294,13 @@ pub struct PairObj {{
#[repr(C)]
#[derive(tvm_ffi::derive::ObjectRef, Clone)]
pub struct Pair {{
- data: ObjectArc<PairObj>,
+ base: ObjectArc<PairObj>,
}}
impl Deref for Pair {{
type Target = PairObj;
fn deref(&self) -> &PairObj {{
- &self.data
+ &self.base
}}
}}
@@ -509,13 +515,13 @@ pub struct IterVarObj {{
#[repr(C)]
#[derive(tvm_ffi::derive::ObjectRef, Clone)]
pub struct IterVar {{
- data: ObjectArc<IterVarObj>,
+ base: ObjectArc<IterVarObj>,
}}
impl Deref for IterVar {{
type Target = IterVarObj;
fn deref(&self) -> &IterVarObj {{
- &self.data
+ &self.base
}}
}}
@@ -659,13 +665,13 @@ const _: () = {
#[repr(C)]
#[derive(tvm_ffi::derive::ObjectRef, Clone)]
pub struct Expr {
- data: ObjectArc<ExprObj>,
+ base: ObjectArc<ExprObj>,
}
impl Deref for Expr {
type Target = ExprObj;
fn deref(&self) -> &ExprObj {
- &self.data
+ &self.base
}
}
@@ -680,7 +686,7 @@ impl Expr {
/// Lossless complete-field allocation.
pub fn new(span: Option<Span>, ty: Type) -> Self {
let obj = ExprObj::new(span, ty);
- Self { data: ObjectArc::new(obj) }
+ Self { base: ObjectArc::new(obj) }
}
}"""
@@ -704,13 +710,13 @@ const _: () = {
#[repr(C)]
#[derive(tvm_ffi::derive::ObjectRef, Clone)]
pub struct Add {
- data: ObjectArc<AddObj>,
+ base: ObjectArc<AddObj>,
}
impl Deref for Add {
type Target = AddObj;
fn deref(&self) -> &AddObj {
- &self.data
+ &self.base
}
}
@@ -732,7 +738,7 @@ impl Add {
/// Lossless complete-field allocation.
pub fn new(span: Option<Span>, ty: PrimType, a: PrimExpr, b: PrimExpr) ->
Self {
let obj = AddObj::new(span, ty, a, b);
- Self { data: ObjectArc::new(obj) }
+ Self { base: ObjectArc::new(obj) }
}
}
@@ -815,9 +821,9 @@ def test_reserved_member_names_get_a_trailing_underscore()
-> None:
is_final=True,
)
text, _ = _render(info)
- assert " base: Object,\n pub base_: i64,\n pub data_: i64,\n" in
text
- assert " pub fn new(base_: i64, data_: i64) -> Self {" in text
- assert " Self { base, base_, data_ }" in text
+ assert " base: Object,\n pub base_: i64,\n pub data: i64,\n" in
text
+ assert " pub fn new(base_: i64, data: i64) -> Self {" in text
+ assert " Self { base, base_, data }" in text
# The opaque form keeps the reflected name on the C ABI side.
text, _ = _render(_info("demo.Node", (_field("base", "int"),)))
assert (
@@ -895,7 +901,7 @@ def test_custom_new_renames_the_wrapper_allocator() -> None:
"impl Add {\n /// Lossless complete-field allocation.\n"
" pub fn from_complete_fields(span: Span, ty: Type, a: Expr, b:
Expr) -> Self {\n"
" let obj = AddObj::new(span, ty, a, b);\n"
- " Self { data: ObjectArc::new(obj) }\n"
+ " Self { base: ObjectArc::new(obj) }\n"
" }\n}"
) in text
assert " pub fn new(" not in text