This is an automated email from the ASF dual-hosted git repository.

Seven-Streams 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 277e7b45 [FEAT][Rust] Generate placeholders of rs_class objects with 
`placeholder` (#884)
277e7b45 is described below

commit 277e7b452bafd77fa6725cd47f3937409208b43b
Author: Linzhang Li <[email protected]>
AuthorDate: Wed Oct 28 19:33:20 2026 -0400

    [FEAT][Rust] Generate placeholders of rs_class objects with `placeholder` 
(#884)
    
    Reflection builds some objects field by field: a JSON graph read back,
    and the `__ffi_init__`
    that `ffi._RegisterFFIInit` generates both make an empty object with the
    type's creator, then set
    each field. A C++ type gets its creator from `refl::ObjectDef`, whose
    object can start empty
    because its references may be null; a Python `py_class` allocates zeroed
    memory. A Rust object
    cannot start empty, since its references are never null, so a downstream
    project that registers a
    creator (`__ffi_new__`) for an `rs_class` type has to make the object
    from valid placeholder
    values, which reflection then overwrites.
    
    TVM TIRx does this to let Python construct, JSON graphs hold, and
    TVMScript parse the IR types an
    extension defines in Rust. It carries its own `Placeholder` trait for
    it, and every type writes
    the same impl by hand:
    
    ```rust
    impl Placeholder for PairTypeObj {
        fn placeholder() -> Self {
            Self::new(TypeObj::placeholder(), Type::placeholder(), 
Type::placeholder())
        }
    }
    ```
    
    The impl only repeats the struct: the parent's placeholder, then each
    field's, in order. This PR
    adds the trait here as `tvm_ffi::Placeholder` and lets `#[rs_class]`
    write the impl when asked:
    
    ```rust
    use tvm_ffi::object::ObjectRef;
    use tvm_ffi::{def_object_ref, rs_class, Object, Placeholder};
    
    #[rs_class("my.Point", placeholder)]
    pub struct PointObj {
        base: Object,
        pub x: i64,
        pub label: tvm_ffi::String,
    }
    
    def_object_ref!(pub Point, ObjectRef, PointObj);
    
    let point = Point::from(PointObj::placeholder());
    assert_eq!((point.x, point.label.as_str()), (0, ""));
    ```
    
    Signed-off-by: Yuchuan <[email protected]>
---
 docs/guides/rust_lang_guide.md             | 12 ++--
 rust/tvm-ffi-macros/src/lib.rs             |  4 ++
 rust/tvm-ffi-macros/src/rs_class.rs        | 28 ++++++++--
 rust/tvm-ffi/src/lib.rs                    |  3 +-
 rust/tvm-ffi/src/reflection.rs             |  2 +
 rust/tvm-ffi/src/reflection/placeholder.rs | 90 ++++++++++++++++++++++++++++++
 rust/tvm-ffi/tests/test_rs_class.rs        | 44 +++++++++++++++
 7 files changed, 174 insertions(+), 9 deletions(-)

diff --git a/docs/guides/rust_lang_guide.md b/docs/guides/rust_lang_guide.md
index c706a4a9..3dadfe5e 100644
--- a/docs/guides/rust_lang_guide.md
+++ b/docs/guides/rust_lang_guide.md
@@ -325,10 +325,14 @@ let point: Point = point3.into();
 and any other field from anything that converts into it, as the `new` of a type
 bound by `tvm-ffi-stubgen` does. The parent's object must implement
 `ShallowCopy`, as `rs_class` types and non-final C++ types bound by
-`tvm-ffi-stubgen` do. The reference dereferences to the object, and the object
-converts into it. The reference also converts into `ParentType`, the reference
-to its parent (`ObjectRef` for a type under `ffi.Object`);
-`impl_object_upcast!` converts it into the references to further ancestors.
+`tvm-ffi-stubgen` do. With `placeholder` after the type key, `#[rs_class]` also
+implements `Placeholder` for the object, from the placeholders of the parent's
+object and the fields: a valid object for a creator to make before reflection
+sets its fields, as the creator of a C++ type makes an empty one. The reference
+dereferences to the object, and the object converts into it. The reference also
+converts into `ParentType`, the reference to its parent (`ObjectRef` for a type
+under `ffi.Object`); `impl_object_upcast!` converts it into the references to
+further ancestors.
 
 ### Converting Borrowed Values into `Any`
 
diff --git a/rust/tvm-ffi-macros/src/lib.rs b/rust/tvm-ffi-macros/src/lib.rs
index 9297f36a..998670a6 100644
--- a/rust/tvm-ffi-macros/src/lib.rs
+++ b/rust/tvm-ffi-macros/src/lib.rs
@@ -169,6 +169,9 @@ pub fn derive_object_ref(input: TokenStream) -> TokenStream 
{
 ///   `derive(Object)`'s `#[type_final]`, `#[type_s_eq_hash_kind]`,
 ///   `#[type_child_slots]`, `#[type_child_slots_can_overflow]` and
 ///   `#[type_reflection]`.
+/// - `placeholder` also implements [`Placeholder`] for the object, from the
+///   placeholders of the parent's object and the fields, which must implement
+///   it too: the object a creator makes before reflection sets its fields.
 /// - `crate = path` is where the generated code finds tvm-ffi
 ///   (`crate = ::my_lib::tvm_ffi`), for a crate that depends on it only 
through
 ///   a re-export; by default `tvm_ffi`.
@@ -179,6 +182,7 @@ pub fn derive_object_ref(input: TokenStream) -> TokenStream 
{
 /// `__ffi_init__`. A field's type must implement `AnyCompatible` and `Clone`.
 ///
 /// [`FieldFlags`]: ../tvm_ffi/reflection/struct.FieldFlags.html
+/// [`Placeholder`]: ../tvm_ffi/reflection/trait.Placeholder.html
 #[proc_macro_error]
 #[proc_macro_attribute]
 pub fn rs_class(args: TokenStream, item: TokenStream) -> TokenStream {
diff --git a/rust/tvm-ffi-macros/src/rs_class.rs 
b/rust/tvm-ffi-macros/src/rs_class.rs
index 0e34d560..e10de7c7 100644
--- a/rust/tvm-ffi-macros/src/rs_class.rs
+++ b/rust/tvm-ffi-macros/src/rs_class.rs
@@ -38,6 +38,7 @@ pub(crate) fn rs_class(args: TokenStream, item: TokenStream) 
-> TokenStream {
 struct RsClassArgs {
     type_key: LitStr,
     type_final: bool,
+    placeholder: bool,
     s_eq_hash_kind: Option<LitStr>,
     child_slots: Option<LitInt>,
     child_slots_can_overflow: Option<LitBool>,
@@ -56,6 +57,7 @@ impl Parse for RsClassArgs {
         let mut args = RsClassArgs {
             type_key,
             type_final: false,
+            placeholder: false,
             s_eq_hash_kind: None,
             child_slots: None,
             child_slots_can_overflow: None,
@@ -74,11 +76,12 @@ impl Parse for RsClassArgs {
             if seen.contains(&key) {
                 return Err(syn::Error::new(name.span(), format!("duplicate 
`{key}`")));
             }
-            if key != "final" {
+            if key != "final" && key != "placeholder" {
                 input.parse::<Token![=]>()?;
             }
             match key.as_str() {
                 "final" => args.type_final = true,
+                "placeholder" => args.placeholder = true,
                 "s_eq_hash_kind" => args.s_eq_hash_kind = Some(input.parse()?),
                 "child_slots" => args.child_slots = Some(input.parse()?),
                 "child_slots_can_overflow" => args.child_slots_can_overflow = 
Some(input.parse()?),
@@ -87,9 +90,9 @@ impl Parse for RsClassArgs {
                 _ => {
                     return Err(syn::Error::new(
                         name.span(),
-                        "expected `final`, `s_eq_hash_kind = \"<kind>\"`, 
`child_slots = <n>`, \
-                         `child_slots_can_overflow = <bool>`, `reflection = 
<path>` or \
-                         `crate = <path>`",
+                        "expected `final`, `placeholder`, `s_eq_hash_kind = 
\"<kind>\"`, \
+                         `child_slots = <n>`, `child_slots_can_overflow = 
<bool>`, \
+                         `reflection = <path>` or `crate = <path>`",
                     ));
                 }
             }
@@ -209,6 +212,21 @@ fn expand(args: &RsClassArgs, item: &DeriveInput) -> 
syn::Result<TokenStream2> {
         }
     })?;
     let object_impl = expand_object(&object_input, &tvm_ffi, true);
+    // The object from the placeholders of the parent's object and the fields, 
which
+    // `new` takes as it takes any value of their types.
+    let placeholder_impl = args.placeholder.then(|| {
+        let field_tys = fields.iter().map(|field| &field.ty);
+        quote! {
+            impl #tvm_ffi::Placeholder for #obj {
+                fn placeholder() -> Self {
+                    Self::new(
+                        <#base_ty as #tvm_ffi::Placeholder>::placeholder(),
+                        #(<#field_tys as 
#tvm_ffi::Placeholder>::placeholder(),)*
+                    )
+                }
+            }
+        }
+    });
     let plain_fields = fields.iter().map(|field| {
         let mut field = (*field).clone();
         field.attrs.retain(|a| !a.path.is_ident("reflect"));
@@ -225,6 +243,8 @@ fn expand(args: &RsClassArgs, item: &DeriveInput) -> 
syn::Result<TokenStream2> {
 
         #object_impl
 
+        #placeholder_impl
+
         impl ::core::ops::Deref for #obj {
             type Target = #base_ty;
             #[inline]
diff --git a/rust/tvm-ffi/src/lib.rs b/rust/tvm-ffi/src/lib.rs
index 41930774..b1d5d285 100644
--- a/rust/tvm-ffi/src/lib.rs
+++ b/rust/tvm-ffi/src/lib.rs
@@ -74,7 +74,8 @@ pub use crate::object::{
 };
 pub use crate::optional::{Optional, OptionalCompatible};
 pub use crate::reflection::{
-    get_type_attr, FieldFlags, FieldGetter, ObjectDef, SEqHashKind, 
ShallowCopy, TypeAttrColumn,
+    get_type_attr, FieldFlags, FieldGetter, ObjectDef, Placeholder, 
SEqHashKind, ShallowCopy,
+    TypeAttrColumn,
 };
 pub use crate::rvalue_ref::RValueRef;
 pub use crate::static_init::run_static_init_blocks;
diff --git a/rust/tvm-ffi/src/reflection.rs b/rust/tvm-ffi/src/reflection.rs
index 5ababa16..92abc4d1 100644
--- a/rust/tvm-ffi/src/reflection.rs
+++ b/rust/tvm-ffi/src/reflection.rs
@@ -30,7 +30,9 @@ use crate::tvm_ffi_sys::{
 };
 use crate::{Any, AnyView, Error, ObjectCore, Result, TYPE_ERROR};
 
+mod placeholder;
 mod registry;
+pub use placeholder::Placeholder;
 pub use registry::{FieldFlags, ObjectDef, SEqHashKind, ShallowCopy};
 
 /// A registry-owned type-attribute column indexed by runtime type.
diff --git a/rust/tvm-ffi/src/reflection/placeholder.rs 
b/rust/tvm-ffi/src/reflection/placeholder.rs
new file mode 100644
index 00000000..9f9d7462
--- /dev/null
+++ b/rust/tvm-ffi/src/reflection/placeholder.rs
@@ -0,0 +1,90 @@
+/*
+ * 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.
+ */
+
+use crate::collections::list::List;
+use crate::object::Object;
+use crate::optional::{Optional, OptionalCompatible};
+use crate::type_traits::ContainerElement;
+use crate::{Any, Array, Bytes, Map, String};
+
+/// A valid value of an object or field type, which an object made before
+/// reflection sets its fields holds until then.
+///
+/// A C++ object can start empty, its references null, and the reflection then
+/// fills it (the creator of `refl::ObjectDef`); a Rust object cannot, so a
+/// creator of a Rust type (an `__ffi_new__` that a JSON graph or a generated
+/// `__ffi_init__` fills) makes the object from placeholders. `#[rs_class(...,
+/// placeholder)]` implements it for the object, from the placeholders of the
+/// parent's object and of the fields. tvm-ffi implements it for `Object`, the
+/// scalars, `String`, `Bytes`, `Any` and the containers; a field or parent 
type
+/// of another crate implements it there.
+pub trait Placeholder {
+    /// The value.
+    fn placeholder() -> Self;
+}
+
+impl Placeholder for Object {
+    fn placeholder() -> Self {
+        Object::new()
+    }
+}
+
+macro_rules! default_placeholder {
+    ($($ty:ty),* $(,)?) => {
+        $(impl Placeholder for $ty {
+            fn placeholder() -> Self {
+                Self::default()
+            }
+        })*
+    };
+}
+
+default_placeholder!(
+    bool, i8, i16, i32, i64, isize, u8, u16, u32, u64, usize, f32, f64, 
String, Bytes, Any,
+);
+
+impl<T: ContainerElement + Clone> Placeholder for Array<T> {
+    fn placeholder() -> Self {
+        Self::default()
+    }
+}
+
+impl<T: ContainerElement> Placeholder for List<T> {
+    fn placeholder() -> Self {
+        Self::default()
+    }
+}
+
+impl<K: ContainerElement, V: ContainerElement> Placeholder for Map<K, V> {
+    fn placeholder() -> Self {
+        Self::default()
+    }
+}
+
+impl<T: OptionalCompatible> Placeholder for Optional<T> {
+    fn placeholder() -> Self {
+        Self::default()
+    }
+}
+
+impl<T> Placeholder for Option<T> {
+    fn placeholder() -> Self {
+        None
+    }
+}
diff --git a/rust/tvm-ffi/tests/test_rs_class.rs 
b/rust/tvm-ffi/tests/test_rs_class.rs
index 2bde20cf..d14a6597 100644
--- a/rust/tvm-ffi/tests/test_rs_class.rs
+++ b/rust/tvm-ffi/tests/test_rs_class.rs
@@ -131,6 +131,31 @@ pub struct RsCxxChildObj {
 
 def_object_ref!(pub RsCxxChild, TestCxxClassBase, RsCxxChildObj);
 
+/// A root type with `placeholder`, whose fields have the field types tvm-ffi
+/// gives placeholders.
+#[rs_class("testing.rust.RsPlaceholder", s_eq_hash_kind = "TreeNode", 
placeholder)]
+pub struct RsPlaceholderObj {
+    base: Object,
+    pub count: i64,
+    pub ratio: f64,
+    pub flag: bool,
+    pub label: tvm_ffi::String,
+    pub tags: Array<tvm_ffi::String>,
+    pub table: Map<tvm_ffi::String, i64>,
+    pub point: Option<RsPoint>,
+}
+
+def_object_ref!(pub RsPlaceholder, ObjectRef, RsPlaceholderObj);
+
+/// A subtype with `placeholder`, whose placeholder starts from its parent's.
+#[rs_class("testing.rust.RsPlaceholderChild", final, placeholder)]
+pub struct RsPlaceholderChildObj {
+    base: RsPlaceholderObj,
+    pub z: i32,
+}
+
+def_object_ref!(pub RsPlaceholderChild, RsPlaceholder, RsPlaceholderChildObj);
+
 /// A crate that reaches tvm-ffi only through a re-export names it with 
`crate`.
 mod reexported {
     // The bare `tvm_ffi` here is not the crate, as in a crate without the 
dependency.
@@ -218,6 +243,8 @@ fn register_types() {
         RsMarkerObj::type_index();
         RsScalarsObj::type_index();
         RsCxxChildObj::type_index();
+        RsPlaceholderObj::type_index();
+        RsPlaceholderChildObj::type_index();
         reexported::ReexportedObj::type_index();
     });
 }
@@ -395,3 +422,20 @@ fn test_crate_path_names_a_reexported_tvm_ffi() {
     assert!(ObjectRef::from(&value).same_as(&value));
     assert_eq!(field_names::<reexported::ReexportedObj>(), ["v"]);
 }
+
+#[test]
+fn test_placeholder_makes_the_object_from_its_parent_and_fields() {
+    register_types();
+    let child = RsPlaceholderChild::from(RsPlaceholderChildObj::placeholder());
+    assert_eq!(
+        (child.count, child.ratio, child.flag, child.z),
+        (0, 0.0, false, 0)
+    );
+    assert_eq!(child.label.as_str(), "");
+    assert!(child.tags.is_empty() && child.table.is_empty() && 
child.point.is_none());
+    // A valid object of its type, which the reflection sees as any other.
+    let other = RsPlaceholderChild::from(RsPlaceholderChildObj::placeholder());
+    assert!(!other.same_as(&child));
+    assert!(structural_equal(&child, &other));
+    assert!(structural_equal(&deep_copy(&child), &child));
+}

Reply via email to