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

Reply via email to