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-tirx.git


The following commit(s) were added to refs/heads/main by this push:
     new 503e143c [FEAT][RUST] Define IR types in Rust (#27)
503e143c is described below

commit 503e143c2eeb2982da0ba7769f391cc2bea94c32
Author: Linzhang Li <[email protected]>
AuthorDate: Thu Oct 22 22:07:47 2026 -0400

    [FEAT][RUST] Define IR types in Rust (#27)
    
    ## Summary
    
    Rust could build the IR types C++ defines, but not define a new type of
    expressions, as
    `PrimType` and `TupleType` are: a Rust type could not extend `ir.Type`
    with fields that C++ and
    Python see.
    
    This bumps `3rdparty/tvm-ffi` to 8fd9303, the head of apache/tvm-ffi
    main, which has the merged
    apache/tvm-ffi#869 on top of apache/tvm-ffi#870. Its `#[rs_class]`
    defines an object type from
    its object struct, whose first field `base` is the parent's object, and
    registers the reflection
    of every other field; `def_object_ref!` defines the reference to it.
    This PR also regenerates the
    bindings with that stubgen:
    
    - A complete type that is not final now has a public `<Name>Obj::new`
    and implements
    `ShallowCopy`, which `rs_class` needs of the parent. This covers
    `ir.Type`, `ir.Expr`,
      `ir.Stmt`, `ir.Attrs` and seven more types.
    - The wrapper's `new` takes a scalar field as its own type and any other
    as `impl Into<T>`, as
    the `new` of an `rs_class` object does. The hand-written callers drop
    the `.into()` they
      applied to an argument, which the new parameter types make ambiguous.
    - Under `custom-new`, the generated allocator is `from_fields` instead
    of
    `from_complete_fields` (apache/tvm-ffi#870), and the hand-written
    constructors call it by that
      name.
    
    A Rust type can then extend `ir.Type`:
    
    ```rust
    use tvm_tirx::ir::{Location, PrimType, Type, TypeObj};
    use tvm_tirx::tvm_ffi::{self, def_object_ref, rs_class};
    
    /// The type of a pair of values.
    #[rs_class("my_ext.PairType", final)]
    pub struct PairTypeNode {
        base: TypeObj,
        pub first: Type,
        pub second: Type,
        /// Not part of structural equality or hashing.
        #[reflect(seq_hash_ignore)]
        pub note: tvm_ffi::String,
    }
    
    def_object_ref!(pub PairType, Type, PairTypeNode);
    
    impl PairType {
        pub fn new(first: impl Into<Type>, second: impl Into<Type>) -> Self {
            PairTypeNode::new(TypeObj::new(Location::unknown()), first, second, 
"").into()
        }
    }
    
    let pair = PairType::new(PrimType::int(32), PrimType::float(32));
    let ty: Type = pair.into(); // an `ir.Type` that C++ sees as 
`my_ext.PairType`
    ```
    
    The C++ structural equality and hashing, deep copy, `repr`, the
    TVMScript printer and Python see
    its fields as they see a C++ type's. `tests/ir/type.rs` checks this end
    to end:
    
    - it registers under `ir.Type` as a `TreeNode`, with each field's offset
    and flags;
    - the C ABI getters read its own fields and the inherited `loc`;
    - the C++ engines compare and hash it, and skip a `seq_hash_ignore`
    field;
    - `repr` and TVMScript print it;
    - C++ deep-copies it, also as the type of a `Var` inside an `Evaluate`;
    - the Rust structural engine maps it, a copy-on-write edit keeps the
    other fields, and it
      converts through `Any` and the type hierarchy.
---
 3rdparty/tvm-ffi                           |   2 +-
 rust/src/backend/cuda/op.rs                |  36 ++--
 rust/src/ir/attrs.rs                       |  13 +-
 rust/src/ir/base.rs                        |   4 +-
 rust/src/ir/expr.rs                        | 225 ++++++++++++++++--------
 rust/src/ir/function.rs                    |  13 +-
 rust/src/ir/global_info.rs                 |   9 +-
 rust/src/ir/location.rs                    |  26 ++-
 rust/src/ir/module.rs                      |  15 +-
 rust/src/ir/op.rs                          |   4 +-
 rust/src/ir/prim/expr.rs                   | 266 +++++++++++++++++++++--------
 rust/src/ir/prim/vector_expr.rs            |  47 ++---
 rust/src/ir/stmt.rs                        | 183 +++++++++++---------
 rust/src/ir/transform/core.rs              |  20 ++-
 rust/src/ir/type.rs                        |  69 +++++---
 rust/src/lib.rs                            |  54 +++++-
 rust/src/sym/iter_affine_map.rs            |  12 +-
 rust/src/tirx/function.rs                  |  34 ++--
 rust/src/tirx/index_map.rs                 |  12 +-
 rust/src/tirx/layout.rs                    |   4 +-
 rust/src/tirx/op.rs                        |  14 +-
 rust/src/tirx/tensor.rs                    |  25 ++-
 rust/src/tirx/tile/instruction.rs          |  32 ++--
 rust/src/tirx/type.rs                      |   4 +-
 rust/tests/ir/transform.rs                 |   2 +-
 rust/tests/ir/transform/core.rs            |   2 +-
 rust/tests/ir/type.rs                      | 234 ++++++++++++++++++++++++-
 rust/tests/sym/int_set.rs                  |   2 +-
 rust/tests/sym/iter_affine_map.rs          |   2 +-
 rust/tests/sym/main.rs                     |   2 +-
 rust/tests/tirx/transform/function_pass.rs |   4 +-
 31 files changed, 967 insertions(+), 404 deletions(-)

diff --git a/3rdparty/tvm-ffi b/3rdparty/tvm-ffi
index 6628c464..8fd93039 160000
--- a/3rdparty/tvm-ffi
+++ b/3rdparty/tvm-ffi
@@ -1 +1 @@
-Subproject commit 6628c464e3b824d09c9cf65ac0c6c24f973f6d56
+Subproject commit 8fd93039ff332d6262d1f6a47f626b6f91a84080
diff --git a/rust/src/backend/cuda/op.rs b/rust/src/backend/cuda/op.rs
index 1d2a118f..5352ded7 100644
--- a/rust/src/backend/cuda/op.rs
+++ b/rust/src/backend/cuda/op.rs
@@ -126,9 +126,9 @@ impl TCGen05InstrDescriptorAttrs {
     /// Lossless complete-field allocation.
     #[allow(non_snake_case)]
     pub fn new(
-        d_dtype: String,
-        a_dtype: String,
-        b_dtype: String,
+        d_dtype: impl Into<String>,
+        a_dtype: impl Into<String>,
+        b_dtype: impl Into<String>,
         M: i64,
         N: i64,
         K: i64,
@@ -141,9 +141,9 @@ impl TCGen05InstrDescriptorAttrs {
         is_sparse: bool,
     ) -> Self {
         let obj = TCGen05InstrDescriptorAttrsObj::new(
-            d_dtype,
-            a_dtype,
-            b_dtype,
+            d_dtype.into(),
+            a_dtype.into(),
+            b_dtype.into(),
             M,
             N,
             K,
@@ -255,11 +255,11 @@ impl TCGen05InstrDescriptorBlockScaledAttrs {
     /// Lossless complete-field allocation.
     #[allow(non_snake_case)]
     pub fn new(
-        d_dtype: String,
-        a_dtype: String,
-        b_dtype: String,
-        sfa_dtype: String,
-        sfb_dtype: String,
+        d_dtype: impl Into<String>,
+        a_dtype: impl Into<String>,
+        b_dtype: impl Into<String>,
+        sfa_dtype: impl Into<String>,
+        sfb_dtype: impl Into<String>,
         M: i64,
         N: i64,
         K: i64,
@@ -271,11 +271,11 @@ impl TCGen05InstrDescriptorBlockScaledAttrs {
         is_sparse: bool,
     ) -> Self {
         let obj = TCGen05InstrDescriptorBlockScaledAttrsObj::new(
-            d_dtype,
-            a_dtype,
-            b_dtype,
-            sfa_dtype,
-            sfb_dtype,
+            d_dtype.into(),
+            a_dtype.into(),
+            b_dtype.into(),
+            sfa_dtype.into(),
+            sfb_dtype.into(),
             M,
             N,
             K,
@@ -362,7 +362,7 @@ impl TensorMapEncodeTiledAttrObj {
 impl TensorMapEncodeTiledAttr {
     /// Lossless complete-field allocation.
     pub fn new(
-        descriptor_dtype: DLDataType,
+        descriptor_dtype: impl Into<DLDataType>,
         rank: i64,
         interleave: i64,
         swizzle: i64,
@@ -371,7 +371,7 @@ impl TensorMapEncodeTiledAttr {
         force_cu_dtype: i64,
     ) -> Self {
         let obj = TensorMapEncodeTiledAttrObj::new(
-            descriptor_dtype,
+            descriptor_dtype.into(),
             rank,
             interleave,
             swizzle,
diff --git a/rust/src/ir/attrs.rs b/rust/src/ir/attrs.rs
index bc1c2b18..a63332cf 100644
--- a/rust/src/ir/attrs.rs
+++ b/rust/src/ir/attrs.rs
@@ -29,6 +29,7 @@ use tvm_ffi::Any;
 use tvm_ffi::Map;
 use tvm_ffi::Object;
 use tvm_ffi::ObjectArc;
+use tvm_ffi::ShallowCopy;
 use tvm_ffi::String;
 // tvm-ffi-stubgen(end)
 
@@ -60,7 +61,7 @@ impl Deref for Attrs {
 }
 
 impl AttrsObj {
-    pub(crate) fn new() -> Self {
+    pub fn new() -> Self {
         let base = Object::new();
         Self { base }
     }
@@ -73,6 +74,12 @@ impl Attrs {
         Self { base: ObjectArc::new(obj) }
     }
 }
+
+impl ShallowCopy for AttrsObj {
+    fn shallow_copy(&self) -> Self {
+        Self { base: ShallowCopy::shallow_copy(&self.base) }
+    }
+}
 // tvm-ffi-stubgen(end)
 
 // tvm-ffi-stubgen(begin): object/ir.DictAttrs
@@ -120,8 +127,8 @@ impl DictAttrsObj {
 
 impl DictAttrs {
     /// Lossless complete-field allocation.
-    pub fn new(dict: Map<String, Any>) -> Self {
-        let obj = DictAttrsObj::new(dict);
+    pub fn new(dict: impl Into<Map<String, Any>>) -> Self {
+        let obj = DictAttrsObj::new(dict.into());
         Self { base: ObjectArc::new(obj) }
     }
 }
diff --git a/rust/src/ir/base.rs b/rust/src/ir/base.rs
index 2a7e0233..c1f8650e 100644
--- a/rust/src/ir/base.rs
+++ b/rust/src/ir/base.rs
@@ -70,8 +70,8 @@ impl EnvFuncObj {
 
 impl EnvFunc {
     /// Lossless complete-field allocation.
-    pub fn new(name: String, func: Function) -> Self {
-        let obj = EnvFuncObj::new(name, func);
+    pub fn new(name: impl Into<String>, func: impl Into<Function>) -> Self {
+        let obj = EnvFuncObj::new(name.into(), func.into());
         Self { base: ObjectArc::new(obj) }
     }
 }
diff --git a/rust/src/ir/expr.rs b/rust/src/ir/expr.rs
index 1733df7d..4ca0b3cf 100644
--- a/rust/src/ir/expr.rs
+++ b/rust/src/ir/expr.rs
@@ -29,7 +29,7 @@
 //! `i32`, `i64`, `bool`, `f32` or `f64` converts into an [`Expr`] as C++ 
converts it implicitly.
 
 // `custom-new` reserves `new` for a constructor with the C++ one's semantics 
(defaults, implicit
-// conversions, a derived type); the generated `from_complete_fields` 
allocates the fields as given.
+// conversions, a derived type); the generated `from_fields` allocates the 
fields as given.
 // tvm-ffi-stubgen(custom-new): ir.GlobalVar
 // tvm-ffi-stubgen(custom-new): ir.Tuple
 // tvm-ffi-stubgen(custom-new): ir.TensorLoad
@@ -87,6 +87,7 @@ use tvm_ffi::Object;
 use tvm_ffi::ObjectArc;
 use tvm_ffi::ObjectRefCore;
 use tvm_ffi::Result;
+use tvm_ffi::ShallowCopy;
 use tvm_ffi::String;
 use tvm_ffi::tvm_ffi_sys::TVMFFITypeIndex;
 // tvm-ffi-stubgen(end)
@@ -121,7 +122,7 @@ impl Deref for Expr {
 }
 
 impl ExprObj {
-    pub(crate) fn new(loc: Location, ty: Type) -> Self {
+    pub fn new(loc: Location, ty: Type) -> Self {
         let base = Object::new();
         Self { base, loc, ty }
     }
@@ -129,11 +130,21 @@ impl ExprObj {
 
 impl Expr {
     /// Lossless complete-field allocation.
-    pub fn new(loc: Location, ty: Type) -> Self {
-        let obj = ExprObj::new(loc, ty);
+    pub fn new(loc: impl Into<Location>, ty: impl Into<Type>) -> Self {
+        let obj = ExprObj::new(loc.into(), ty.into());
         Self { base: ObjectArc::new(obj) }
     }
 }
+
+impl ShallowCopy for ExprObj {
+    fn shallow_copy(&self) -> Self {
+        Self {
+            base: ShallowCopy::shallow_copy(&self.base),
+            loc: Clone::clone(&self.loc),
+            ty: Clone::clone(&self.ty),
+        }
+    }
+}
 // tvm-ffi-stubgen(end)
 
 /// One index of [`Expr::subscript`]: an expression, or a `start:stop:step` 
slice whose missing
@@ -209,7 +220,7 @@ impl Deref for StagingExprObj {
 }
 
 impl StagingExprObj {
-    pub(crate) fn new(loc: Location, ty: Type) -> Self {
+    pub fn new(loc: Location, ty: Type) -> Self {
         let base = ExprObj::new(loc, ty);
         Self { base }
     }
@@ -217,12 +228,18 @@ impl StagingExprObj {
 
 impl StagingExpr {
     /// Lossless complete-field allocation.
-    pub fn new(loc: Location, ty: Type) -> Self {
-        let obj = StagingExprObj::new(loc, ty);
+    pub fn new(loc: impl Into<Location>, ty: impl Into<Type>) -> Self {
+        let obj = StagingExprObj::new(loc.into(), ty.into());
         Self { base: ObjectArc::new(obj) }
     }
 }
 
+impl ShallowCopy for StagingExprObj {
+    fn shallow_copy(&self) -> Self {
+        Self { base: ShallowCopy::shallow_copy(&self.base) }
+    }
+}
+
 tvm_ffi::impl_object_upcast!(StagingExpr => Expr);
 // tvm-ffi-stubgen(end)
 
@@ -261,7 +278,7 @@ impl Deref for OpaqueExprObj {
 }
 
 impl OpaqueExprObj {
-    pub(crate) fn new(loc: Location, ty: Type) -> Self {
+    pub fn new(loc: Location, ty: Type) -> Self {
         let base = ExprObj::new(loc, ty);
         Self { base }
     }
@@ -269,12 +286,18 @@ impl OpaqueExprObj {
 
 impl OpaqueExpr {
     /// Lossless complete-field allocation.
-    pub fn new(loc: Location, ty: Type) -> Self {
-        let obj = OpaqueExprObj::new(loc, ty);
+    pub fn new(loc: impl Into<Location>, ty: impl Into<Type>) -> Self {
+        let obj = OpaqueExprObj::new(loc.into(), ty.into());
         Self { base: ObjectArc::new(obj) }
     }
 }
 
+impl ShallowCopy for OpaqueExprObj {
+    fn shallow_copy(&self) -> Self {
+        Self { base: ShallowCopy::shallow_copy(&self.base) }
+    }
+}
+
 tvm_ffi::impl_object_upcast!(OpaqueExpr => Expr);
 // tvm-ffi-stubgen(end)
 
@@ -323,8 +346,12 @@ impl GlobalVarObj {
 
 impl GlobalVar {
     /// Lossless complete-field allocation.
-    pub fn from_complete_fields(loc: Location, ty: Type, name_hint: String) -> 
Self {
-        let obj = GlobalVarObj::new(loc, ty, name_hint);
+    pub fn from_fields(
+        loc: impl Into<Location>,
+        ty: impl Into<Type>,
+        name_hint: impl Into<String>,
+    ) -> Self {
+        let obj = GlobalVarObj::new(loc.into(), ty.into(), name_hint.into());
         Self { base: ObjectArc::new(obj) }
     }
 }
@@ -335,8 +362,8 @@ tvm_ffi::impl_object_upcast!(GlobalVar => Expr);
 impl GlobalVar {
     /// The global variable `name_hint`, of a missing type (C++ 
`GlobalVar(name_hint, loc)`).
     pub fn new(name_hint: &str, loc: impl Into<Location>) -> Self {
-        let ty = MissingType::new().into();
-        Self::from_complete_fields(loc.into(), ty, String::from(name_hint))
+        let ty = MissingType::new();
+        Self::from_fields(loc.into(), ty, String::from(name_hint))
     }
 }
 
@@ -385,8 +412,12 @@ impl TupleObj {
 
 impl Tuple {
     /// Lossless complete-field allocation.
-    pub fn from_complete_fields(loc: Location, ty: Type, fields: Array<Expr>) 
-> Self {
-        let obj = TupleObj::new(loc, ty, fields);
+    pub fn from_fields(
+        loc: impl Into<Location>,
+        ty: impl Into<Type>,
+        fields: impl Into<Array<Expr>>,
+    ) -> Self {
+        let obj = TupleObj::new(loc.into(), ty.into(), fields.into());
         Self { base: ObjectArc::new(obj) }
     }
 }
@@ -410,7 +441,7 @@ impl Tuple {
                     TupleType::new(field_types, UnknownLoc::new()).into()
                 }
             };
-        Self::from_complete_fields(loc.into(), ty, fields)
+        Self::from_fields(loc.into(), ty, fields)
     }
 }
 
@@ -460,8 +491,13 @@ impl TupleGetItemObj {
 
 impl TupleGetItem {
     /// Lossless complete-field allocation.
-    pub fn new(loc: Location, ty: Type, tuple_value: Expr, index: i32) -> Self 
{
-        let obj = TupleGetItemObj::new(loc, ty, tuple_value, index);
+    pub fn new(
+        loc: impl Into<Location>,
+        ty: impl Into<Type>,
+        tuple_value: impl Into<Expr>,
+        index: i32,
+    ) -> Self {
+        let obj = TupleGetItemObj::new(loc.into(), ty.into(), 
tuple_value.into(), index);
         Self { base: ObjectArc::new(obj) }
     }
 }
@@ -564,13 +600,13 @@ impl TensorLoadObj {
 
 impl TensorLoad {
     /// Lossless complete-field allocation.
-    pub fn from_complete_fields(
-        loc: Location,
-        ty: Type,
-        source: Expr,
-        indices: Array<Expr>,
+    pub fn from_fields(
+        loc: impl Into<Location>,
+        ty: impl Into<Type>,
+        source: impl Into<Expr>,
+        indices: impl Into<Array<Expr>>,
     ) -> Self {
-        let obj = TensorLoadObj::new(loc, ty, source, indices);
+        let obj = TensorLoadObj::new(loc.into(), ty.into(), source.into(), 
indices.into());
         Self { base: ObjectArc::new(obj) }
     }
 }
@@ -599,14 +635,9 @@ impl TensorLoad {
                 format!("Type {} does not support TensorLoad", 
support::type_key(ty_index));
             return Err(support::check_failed(tvm_ffi::TYPE_ERROR, 
"infer.has_value()", &message));
         };
-        let load = Self::from_complete_fields(loc, MissingType::new().into(), 
source, indices);
+        let load = Self::from_fields(loc, MissingType::new(), source, indices);
         let ty: PrimType = 
support::NativeHook::from_any(infer)?.call_as(&load)?;
-        Ok(Self::from_complete_fields(
-            load.loc.clone(),
-            ty.into(),
-            load.source.clone(),
-            load.indices.clone(),
-        ))
+        Ok(Self::from_fields(load.loc.clone(), ty, load.source.clone(), 
load.indices.clone()))
     }
 }
 
@@ -645,7 +676,7 @@ impl Deref for ConstantObj {
 }
 
 impl ConstantObj {
-    pub(crate) fn new(loc: Location, ty: Type) -> Self {
+    pub fn new(loc: Location, ty: Type) -> Self {
         let base = ExprObj::new(loc, ty);
         Self { base }
     }
@@ -653,12 +684,18 @@ impl ConstantObj {
 
 impl Constant {
     /// Lossless complete-field allocation.
-    pub fn new(loc: Location, ty: Type) -> Self {
-        let obj = ConstantObj::new(loc, ty);
+    pub fn new(loc: impl Into<Location>, ty: impl Into<Type>) -> Self {
+        let obj = ConstantObj::new(loc.into(), ty.into());
         Self { base: ObjectArc::new(obj) }
     }
 }
 
+impl ShallowCopy for ConstantObj {
+    fn shallow_copy(&self) -> Self {
+        Self { base: ShallowCopy::shallow_copy(&self.base) }
+    }
+}
+
 tvm_ffi::impl_object_upcast!(Constant => Expr);
 // tvm-ffi-stubgen(end)
 
@@ -707,8 +744,8 @@ impl FloatImmObj {
 
 impl FloatImm {
     /// Lossless complete-field allocation.
-    pub fn new(loc: Location, ty: Type, value: f64) -> Self {
-        let obj = FloatImmObj::new(loc, ty, value);
+    pub fn new(loc: impl Into<Location>, ty: impl Into<Type>, value: f64) -> 
Self {
+        let obj = FloatImmObj::new(loc.into(), ty.into(), value);
         Self { base: ObjectArc::new(obj) }
     }
 }
@@ -722,7 +759,7 @@ impl FloatImm {
     pub fn from_dtype(dtype: DLDataType, value: f64, loc: impl Into<Location>) 
-> Result<Self> {
         let ty = PrimType::from_dtype(dtype)?;
         check_float_imm(dtype, value)?;
-        Ok(Self::new(loc.into(), ty.into(), value))
+        Ok(Self::new(loc.into(), ty, value))
     }
 
     /// The `float32` constant `value` at the unknown location, which every 
`f32` is
@@ -931,8 +968,8 @@ impl IntImmObj {
 
 impl IntImm {
     /// Lossless complete-field allocation.
-    pub fn new(loc: Location, ty: Type, value: BigInt) -> Self {
-        let obj = IntImmObj::new(loc, ty, value);
+    pub fn new(loc: impl Into<Location>, ty: impl Into<Type>, value: impl 
Into<BigInt>) -> Self {
+        let obj = IntImmObj::new(loc.into(), ty.into(), value.into());
         Self { base: ObjectArc::new(obj) }
     }
 }
@@ -950,7 +987,7 @@ impl IntImm {
     ) -> Result<Self> {
         let (value, ty) = (value.into(), PrimType::from_dtype(dtype)?);
         check_int_imm(dtype, &value)?;
-        Ok(Self::new(loc.into(), ty.into(), value))
+        Ok(Self::new(loc.into(), ty, value))
     }
 
     /// The `int32` constant `value` at the unknown location, which every 
`i32` is
@@ -1134,8 +1171,8 @@ impl GenericConstObj {
 
 impl GenericConst {
     /// Lossless complete-field allocation.
-    pub fn new(loc: Location, ty: Type, value: Any) -> Self {
-        let obj = GenericConstObj::new(loc, ty, value);
+    pub fn new(loc: impl Into<Location>, ty: impl Into<Type>, value: impl 
Into<Any>) -> Self {
+        let obj = GenericConstObj::new(loc.into(), ty.into(), value.into());
         Self { base: ObjectArc::new(obj) }
     }
 }
@@ -1218,8 +1255,12 @@ impl DataTypeImmObj {
 
 impl DataTypeImm {
     /// Lossless complete-field allocation.
-    pub fn new(loc: Location, ty: Type, value: DLDataType) -> Self {
-        let obj = DataTypeImmObj::new(loc, ty, value);
+    pub fn new(
+        loc: impl Into<Location>,
+        ty: impl Into<Type>,
+        value: impl Into<DLDataType>,
+    ) -> Self {
+        let obj = DataTypeImmObj::new(loc.into(), ty.into(), value.into());
         Self { base: ObjectArc::new(obj) }
     }
 }
@@ -1231,7 +1272,7 @@ impl DataTypeImm {
     /// The data type `value` as a constant of type [`AnyType`]
     /// (C++ `DataTypeImm(value, loc)`).
     pub fn from_dtype(value: DLDataType, loc: impl Into<Location>) -> Self {
-        let ty = AnyType::new(UnknownLoc::new().into()).into();
+        let ty = AnyType::new(UnknownLoc::new());
         Self::new(loc.into(), ty, value)
     }
 }
@@ -1281,8 +1322,12 @@ impl StringImmObj {
 
 impl StringImm {
     /// Lossless complete-field allocation.
-    pub fn from_complete_fields(loc: Location, ty: Type, value: String) -> 
Self {
-        let obj = StringImmObj::new(loc, ty, value);
+    pub fn from_fields(
+        loc: impl Into<Location>,
+        ty: impl Into<Type>,
+        value: impl Into<String>,
+    ) -> Self {
+        let obj = StringImmObj::new(loc.into(), ty.into(), value.into());
         Self { base: ObjectArc::new(obj) }
     }
 }
@@ -1294,8 +1339,8 @@ impl StringImm {
     /// The string constant `value`, of type [`StringType`]
     /// (C++ `StringImm(value, loc)`).
     pub fn new(value: &str, loc: impl Into<Location>) -> Self {
-        let ty = StringType::new(UnknownLoc::new().into()).into();
-        Self::from_complete_fields(loc.into(), ty, String::from(value))
+        let ty = StringType::new(UnknownLoc::new());
+        Self::from_fields(loc.into(), ty, String::from(value))
     }
 }
 
@@ -1354,15 +1399,22 @@ impl CallObj {
 
 impl Call {
     /// Lossless complete-field allocation.
-    pub fn from_complete_fields(
-        loc: Location,
-        ty: Type,
-        op: Expr,
-        args: Array<Expr>,
-        attrs: Option<Attrs>,
-        ty_args: Array<Type>,
+    pub fn from_fields(
+        loc: impl Into<Location>,
+        ty: impl Into<Type>,
+        op: impl Into<Expr>,
+        args: impl Into<Array<Expr>>,
+        attrs: impl Into<Option<Attrs>>,
+        ty_args: impl Into<Array<Type>>,
     ) -> Self {
-        let obj = CallObj::new(loc, ty, op, args, attrs, ty_args);
+        let obj = CallObj::new(
+            loc.into(),
+            ty.into(),
+            op.into(),
+            args.into(),
+            attrs.into(),
+            ty_args.into(),
+        );
         Self { base: ObjectArc::new(obj) }
     }
 }
@@ -1385,12 +1437,11 @@ impl Call {
         let args = args.into_iter().map(Into::into).collect::<Array<Expr>>();
         let ty_args = 
ty_args.into_iter().map(Into::into).collect::<Array<Type>>();
         if let Some(ty) = ty {
-            return Ok(Self::from_complete_fields(loc, ty.clone(), op, args, 
attrs, ty_args));
+            return Ok(Self::from_fields(loc, ty.clone(), op, args, attrs, 
ty_args));
         }
-        let call =
-            Self::from_complete_fields(loc, MissingType::new().into(), op, 
args, attrs, ty_args);
+        let call = Self::from_fields(loc, MissingType::new(), op, args, attrs, 
ty_args);
         let ty = call.reinfer_type()?;
-        Ok(Self::from_complete_fields(
+        Ok(Self::from_fields(
             call.loc.clone(),
             ty,
             call.op.clone(),
@@ -1476,8 +1527,13 @@ impl TensorRegionObj {
 
 impl TensorRegion {
     /// Lossless complete-field allocation.
-    pub fn new(loc: Location, ty: Type, source: Expr, region: Array<Range>) -> 
Self {
-        let obj = TensorRegionObj::new(loc, ty, source, region);
+    pub fn new(
+        loc: impl Into<Location>,
+        ty: impl Into<Type>,
+        source: impl Into<Expr>,
+        region: impl Into<Array<Range>>,
+    ) -> Self {
+        let obj = TensorRegionObj::new(loc.into(), ty.into(), source.into(), 
region.into());
         Self { base: ObjectArc::new(obj) }
     }
 }
@@ -1523,7 +1579,7 @@ impl Deref for VarObj {
 }
 
 impl VarObj {
-    pub(crate) fn new(loc: Location, ty: Type, name: String) -> Self {
+    pub fn new(loc: Location, ty: Type, name: String) -> Self {
         let base = ExprObj::new(loc, ty);
         Self { base, name }
     }
@@ -1531,12 +1587,22 @@ impl VarObj {
 
 impl Var {
     /// Lossless complete-field allocation.
-    pub fn from_complete_fields(loc: Location, ty: Type, name: String) -> Self 
{
-        let obj = VarObj::new(loc, ty, name);
+    pub fn from_fields(
+        loc: impl Into<Location>,
+        ty: impl Into<Type>,
+        name: impl Into<String>,
+    ) -> Self {
+        let obj = VarObj::new(loc.into(), ty.into(), name.into());
         Self { base: ObjectArc::new(obj) }
     }
 }
 
+impl ShallowCopy for VarObj {
+    fn shallow_copy(&self) -> Self {
+        Self { base: ShallowCopy::shallow_copy(&self.base), name: 
Clone::clone(&self.name) }
+    }
+}
+
 tvm_ffi::impl_object_upcast!(Var => Expr);
 // tvm-ffi-stubgen(end)
 
@@ -1544,7 +1610,7 @@ impl Var {
     /// The variable `name` of type `ty` (C++ `Var(name, ty, loc)`); a 
variable whose type is not
     /// known yet takes [`MissingType::new`].
     pub fn new(name: &str, ty: impl Into<Type>, loc: impl Into<Location>) -> 
Self {
-        Self::from_complete_fields(loc.into(), ty.into(), String::from(name))
+        Self::from_fields(loc.into(), ty.into(), String::from(name))
     }
 }
 
@@ -1594,8 +1660,13 @@ impl LambdaExprObj {
 
 impl LambdaExpr {
     /// Lossless complete-field allocation.
-    pub fn from_complete_fields(loc: Location, ty: Type, vars: Array<Var>, 
body: Expr) -> Self {
-        let obj = LambdaExprObj::new(loc, ty, vars, body);
+    pub fn from_fields(
+        loc: impl Into<Location>,
+        ty: impl Into<Type>,
+        vars: impl Into<Array<Var>>,
+        body: impl Into<Expr>,
+    ) -> Self {
+        let obj = LambdaExprObj::new(loc.into(), ty.into(), vars.into(), 
body.into());
         Self { base: ObjectArc::new(obj) }
     }
 }
@@ -1614,7 +1685,7 @@ impl LambdaExpr {
         let (vars, body) = 
(vars.into_iter().map(Into::into).collect::<Array<Var>>(), body.into());
         let arg_types = vars.iter().map(|var| 
var.ty.clone()).collect::<Vec<Type>>();
         let ty = FuncType::new(arg_types, body.ty.clone());
-        Self::from_complete_fields(loc.into(), ty.into(), vars, body)
+        Self::from_fields(loc.into(), ty, vars, body)
     }
 
     /// The body with `arguments` substituted for the variables 
(`ir.LambdaExprApply`).
@@ -1666,8 +1737,12 @@ impl RangeObj {
 
 impl Range {
     /// Lossless complete-field allocation.
-    pub fn from_complete_fields(begin: Expr, extent: Expr, loc: Location) -> 
Self {
-        let obj = RangeObj::new(begin, extent, loc);
+    pub fn from_fields(
+        begin: impl Into<Expr>,
+        extent: impl Into<Expr>,
+        loc: impl Into<Location>,
+    ) -> Self {
+        let obj = RangeObj::new(begin.into(), extent.into(), loc.into());
         Self { base: ObjectArc::new(obj) }
     }
 }
@@ -1683,7 +1758,7 @@ impl Range {
     ) -> Result<Self> {
         let (begin, end) = (begin.into(), end.into());
         let extent = Self::get_extent_from_begin_end(begin.clone(), end)?;
-        Ok(Self::from_complete_fields(begin, extent, loc.into()))
+        Ok(Self::from_fields(begin, extent, loc.into()))
     }
 
     /// The range of `extent` values from `min_value` (C++ 
`Range::FromMinExtent`).
@@ -1695,7 +1770,7 @@ impl Range {
         let (min_value, extent) = (min_value.into(), extent.into());
         support::prim_dtype(&min_value)?;
         support::prim_dtype(&extent)?;
-        Ok(Self::from_complete_fields(min_value, extent, loc.into()))
+        Ok(Self::from_fields(min_value, extent, loc.into()))
     }
 
     /// The extent `end - begin` of the range `[begin, end)`: the other 
operand when `end` adds
diff --git a/rust/src/ir/function.rs b/rust/src/ir/function.rs
index 55894fba..3d30d098 100644
--- a/rust/src/ir/function.rs
+++ b/rust/src/ir/function.rs
@@ -45,6 +45,7 @@ use tvm_ffi::Map;
 use tvm_ffi::ObjectArc;
 use tvm_ffi::RValueRef;
 use tvm_ffi::Result;
+use tvm_ffi::ShallowCopy;
 use tvm_ffi::String;
 // tvm-ffi-stubgen(end)
 
@@ -84,7 +85,7 @@ impl Deref for BaseFuncObj {
 }
 
 impl BaseFuncObj {
-    pub(crate) fn new(loc: Location, ty: Type, attrs: DictAttrs) -> Self {
+    pub fn new(loc: Location, ty: Type, attrs: DictAttrs) -> Self {
         let base = ExprObj::new(loc, ty);
         Self { base, attrs }
     }
@@ -92,12 +93,18 @@ impl BaseFuncObj {
 
 impl BaseFunc {
     /// Lossless complete-field allocation.
-    pub fn new(loc: Location, ty: Type, attrs: DictAttrs) -> Self {
-        let obj = BaseFuncObj::new(loc, ty, attrs);
+    pub fn new(loc: impl Into<Location>, ty: impl Into<Type>, attrs: impl 
Into<DictAttrs>) -> Self {
+        let obj = BaseFuncObj::new(loc.into(), ty.into(), attrs.into());
         Self { base: ObjectArc::new(obj) }
     }
 }
 
+impl ShallowCopy for BaseFuncObj {
+    fn shallow_copy(&self) -> Self {
+        Self { base: ShallowCopy::shallow_copy(&self.base), attrs: 
Clone::clone(&self.attrs) }
+    }
+}
+
 tvm_ffi::impl_object_upcast!(BaseFunc => Expr);
 // tvm-ffi-stubgen(end)
 
diff --git a/rust/src/ir/global_info.rs b/rust/src/ir/global_info.rs
index d7d74e99..60b088f8 100644
--- a/rust/src/ir/global_info.rs
+++ b/rust/src/ir/global_info.rs
@@ -27,6 +27,7 @@
 use std::ops::Deref;
 use tvm_ffi::Object;
 use tvm_ffi::ObjectArc;
+use tvm_ffi::ShallowCopy;
 // tvm-ffi-stubgen(end)
 
 // tvm-ffi-stubgen(begin): object/ir.GlobalInfo
@@ -57,7 +58,7 @@ impl Deref for GlobalInfo {
 }
 
 impl GlobalInfoObj {
-    pub(crate) fn new() -> Self {
+    pub fn new() -> Self {
         let base = Object::new();
         Self { base }
     }
@@ -70,4 +71,10 @@ impl GlobalInfo {
         Self { base: ObjectArc::new(obj) }
     }
 }
+
+impl ShallowCopy for GlobalInfoObj {
+    fn shallow_copy(&self) -> Self {
+        Self { base: ShallowCopy::shallow_copy(&self.base) }
+    }
+}
 // tvm-ffi-stubgen(end)
diff --git a/rust/src/ir/location.rs b/rust/src/ir/location.rs
index 18ed01ce..a75917c7 100644
--- a/rust/src/ir/location.rs
+++ b/rust/src/ir/location.rs
@@ -25,7 +25,7 @@
 //! caller). [`Location::unknown`] and [`SourceName::get`] wrap the native 
constructors.
 
 // `custom-new` reserves `new` for a constructor with the native one's 
semantics (the shared
-// `UnknownLoc` instance); the generated `from_complete_fields` allocates the 
fields as given.
+// `UnknownLoc` instance); the generated `from_fields` allocates the fields as 
given.
 // tvm-ffi-stubgen(custom-new): ir.UnknownLoc
 // Names the hand-written wrappers use: the import section keeps them even 
when no generated block
 // does.
@@ -38,6 +38,7 @@ use std::ops::Deref;
 use tvm_ffi::Object;
 use tvm_ffi::ObjectArc;
 use tvm_ffi::Result;
+use tvm_ffi::ShallowCopy;
 use tvm_ffi::String;
 // tvm-ffi-stubgen(end)
 
@@ -79,8 +80,8 @@ impl SourceNameObj {
 
 impl SourceName {
     /// Lossless complete-field allocation.
-    pub fn new(name: String) -> Self {
-        let obj = SourceNameObj::new(name);
+    pub fn new(name: impl Into<String>) -> Self {
+        let obj = SourceNameObj::new(name.into());
         Self { base: ObjectArc::new(obj) }
     }
 }
@@ -121,7 +122,7 @@ impl Deref for Location {
 }
 
 impl LocationObj {
-    pub(crate) fn new() -> Self {
+    pub fn new() -> Self {
         let base = Object::new();
         Self { base }
     }
@@ -134,6 +135,12 @@ impl Location {
         Self { base: ObjectArc::new(obj) }
     }
 }
+
+impl ShallowCopy for LocationObj {
+    fn shallow_copy(&self) -> Self {
+        Self { base: ShallowCopy::shallow_copy(&self.base) }
+    }
+}
 // tvm-ffi-stubgen(end)
 
 // tvm-ffi-stubgen(begin): object/ir.UnknownLoc
@@ -180,7 +187,7 @@ impl UnknownLocObj {
 
 impl UnknownLoc {
     /// Lossless complete-field allocation.
-    pub fn from_complete_fields() -> Self {
+    pub fn from_fields() -> Self {
         let obj = UnknownLocObj::new();
         Self { base: ObjectArc::new(obj) }
     }
@@ -281,13 +288,14 @@ impl SourceLocObj {
 impl SourceLoc {
     /// Lossless complete-field allocation.
     pub fn new(
-        source_name: SourceName,
+        source_name: impl Into<SourceName>,
         start_line: i32,
         start_column: i32,
         end_line: i32,
         end_column: i32,
     ) -> Self {
-        let obj = SourceLocObj::new(source_name, start_line, start_column, 
end_line, end_column);
+        let obj =
+            SourceLocObj::new(source_name.into(), start_line, start_column, 
end_line, end_column);
         Self { base: ObjectArc::new(obj) }
     }
 }
@@ -343,8 +351,8 @@ impl CallSiteLocObj {
 
 impl CallSiteLoc {
     /// Lossless complete-field allocation.
-    pub fn new(callee: Location, caller: Location) -> Self {
-        let obj = CallSiteLocObj::new(callee, caller);
+    pub fn new(callee: impl Into<Location>, caller: impl Into<Location>) -> 
Self {
+        let obj = CallSiteLocObj::new(callee.into(), caller.into());
         Self { base: ObjectArc::new(obj) }
     }
 }
diff --git a/rust/src/ir/module.rs b/rust/src/ir/module.rs
index 70a18c6b..5b5dc150 100644
--- a/rust/src/ir/module.rs
+++ b/rust/src/ir/module.rs
@@ -98,12 +98,17 @@ impl IRModuleObj {
 impl IRModule {
     /// Lossless complete-field allocation.
     pub fn new(
-        functions: Map<GlobalVar, BaseFunc>,
-        attrs: DictAttrs,
-        global_infos: Map<String, Array<GlobalInfo>>,
-        global_var_map: Map<String, GlobalVar>,
+        functions: impl Into<Map<GlobalVar, BaseFunc>>,
+        attrs: impl Into<DictAttrs>,
+        global_infos: impl Into<Map<String, Array<GlobalInfo>>>,
+        global_var_map: impl Into<Map<String, GlobalVar>>,
     ) -> Self {
-        let obj = IRModuleObj::new(functions, attrs, global_infos, 
global_var_map);
+        let obj = IRModuleObj::new(
+            functions.into(),
+            attrs.into(),
+            global_infos.into(),
+            global_var_map.into(),
+        );
         Self { base: ObjectArc::new(obj) }
     }
 }
diff --git a/rust/src/ir/op.rs b/rust/src/ir/op.rs
index dd3e696c..5cf0d53f 100644
--- a/rust/src/ir/op.rs
+++ b/rust/src/ir/op.rs
@@ -88,8 +88,8 @@ impl ArgumentInfoObj {
 
 impl ArgumentInfo {
     /// Lossless complete-field allocation.
-    pub fn new(name: String, doc: String) -> Self {
-        let obj = ArgumentInfoObj::new(name, doc);
+    pub fn new(name: impl Into<String>, doc: impl Into<String>) -> Self {
+        let obj = ArgumentInfoObj::new(name.into(), doc.into());
         Self { base: ObjectArc::new(obj) }
     }
 }
diff --git a/rust/src/ir/prim/expr.rs b/rust/src/ir/prim/expr.rs
index c5ee15ce..9280ce1a 100644
--- a/rust/src/ir/prim/expr.rs
+++ b/rust/src/ir/prim/expr.rs
@@ -27,7 +27,7 @@
 //! [`super::op`].
 
 // `custom-new` reserves `new` for a constructor with the C++ one's semantics 
(operand type checks
-// and the derived result type); the generated `from_complete_fields` 
allocates the fields as given.
+// and the derived result type); the generated `from_fields` allocates the 
fields as given.
 // tvm-ffi-stubgen(custom-new): prim.Cast
 // tvm-ffi-stubgen(custom-new): prim.LShift
 // tvm-ffi-stubgen(custom-new): prim.RShift
@@ -127,8 +127,12 @@ impl CastObj {
 
 impl Cast {
     /// Lossless complete-field allocation.
-    pub fn from_complete_fields(loc: Location, ty: Type, value: Expr) -> Self {
-        let obj = CastObj::new(loc, ty, value);
+    pub fn from_fields(
+        loc: impl Into<Location>,
+        ty: impl Into<Type>,
+        value: impl Into<Expr>,
+    ) -> Self {
+        let obj = CastObj::new(loc.into(), ty.into(), value.into());
         Self { base: ObjectArc::new(obj) }
     }
 }
@@ -182,8 +186,13 @@ impl LShiftObj {
 
 impl LShift {
     /// Lossless complete-field allocation.
-    pub fn from_complete_fields(loc: Location, ty: Type, a: Expr, b: Expr) -> 
Self {
-        let obj = LShiftObj::new(loc, ty, a, b);
+    pub fn from_fields(
+        loc: impl Into<Location>,
+        ty: impl Into<Type>,
+        a: impl Into<Expr>,
+        b: impl Into<Expr>,
+    ) -> Self {
+        let obj = LShiftObj::new(loc.into(), ty.into(), a.into(), b.into());
         Self { base: ObjectArc::new(obj) }
     }
 }
@@ -237,8 +246,13 @@ impl RShiftObj {
 
 impl RShift {
     /// Lossless complete-field allocation.
-    pub fn from_complete_fields(loc: Location, ty: Type, a: Expr, b: Expr) -> 
Self {
-        let obj = RShiftObj::new(loc, ty, a, b);
+    pub fn from_fields(
+        loc: impl Into<Location>,
+        ty: impl Into<Type>,
+        a: impl Into<Expr>,
+        b: impl Into<Expr>,
+    ) -> Self {
+        let obj = RShiftObj::new(loc.into(), ty.into(), a.into(), b.into());
         Self { base: ObjectArc::new(obj) }
     }
 }
@@ -292,8 +306,13 @@ impl BitwiseAndObj {
 
 impl BitwiseAnd {
     /// Lossless complete-field allocation.
-    pub fn from_complete_fields(loc: Location, ty: Type, a: Expr, b: Expr) -> 
Self {
-        let obj = BitwiseAndObj::new(loc, ty, a, b);
+    pub fn from_fields(
+        loc: impl Into<Location>,
+        ty: impl Into<Type>,
+        a: impl Into<Expr>,
+        b: impl Into<Expr>,
+    ) -> Self {
+        let obj = BitwiseAndObj::new(loc.into(), ty.into(), a.into(), 
b.into());
         Self { base: ObjectArc::new(obj) }
     }
 }
@@ -347,8 +366,13 @@ impl BitwiseOrObj {
 
 impl BitwiseOr {
     /// Lossless complete-field allocation.
-    pub fn from_complete_fields(loc: Location, ty: Type, a: Expr, b: Expr) -> 
Self {
-        let obj = BitwiseOrObj::new(loc, ty, a, b);
+    pub fn from_fields(
+        loc: impl Into<Location>,
+        ty: impl Into<Type>,
+        a: impl Into<Expr>,
+        b: impl Into<Expr>,
+    ) -> Self {
+        let obj = BitwiseOrObj::new(loc.into(), ty.into(), a.into(), b.into());
         Self { base: ObjectArc::new(obj) }
     }
 }
@@ -402,8 +426,13 @@ impl BitwiseXorObj {
 
 impl BitwiseXor {
     /// Lossless complete-field allocation.
-    pub fn from_complete_fields(loc: Location, ty: Type, a: Expr, b: Expr) -> 
Self {
-        let obj = BitwiseXorObj::new(loc, ty, a, b);
+    pub fn from_fields(
+        loc: impl Into<Location>,
+        ty: impl Into<Type>,
+        a: impl Into<Expr>,
+        b: impl Into<Expr>,
+    ) -> Self {
+        let obj = BitwiseXorObj::new(loc.into(), ty.into(), a.into(), 
b.into());
         Self { base: ObjectArc::new(obj) }
     }
 }
@@ -456,8 +485,8 @@ impl BitwiseNotObj {
 
 impl BitwiseNot {
     /// Lossless complete-field allocation.
-    pub fn from_complete_fields(loc: Location, ty: Type, a: Expr) -> Self {
-        let obj = BitwiseNotObj::new(loc, ty, a);
+    pub fn from_fields(loc: impl Into<Location>, ty: impl Into<Type>, a: impl 
Into<Expr>) -> Self {
+        let obj = BitwiseNotObj::new(loc.into(), ty.into(), a.into());
         Self { base: ObjectArc::new(obj) }
     }
 }
@@ -511,8 +540,13 @@ impl AddObj {
 
 impl Add {
     /// Lossless complete-field allocation.
-    pub fn from_complete_fields(loc: Location, ty: Type, a: Expr, b: Expr) -> 
Self {
-        let obj = AddObj::new(loc, ty, a, b);
+    pub fn from_fields(
+        loc: impl Into<Location>,
+        ty: impl Into<Type>,
+        a: impl Into<Expr>,
+        b: impl Into<Expr>,
+    ) -> Self {
+        let obj = AddObj::new(loc.into(), ty.into(), a.into(), b.into());
         Self { base: ObjectArc::new(obj) }
     }
 }
@@ -566,8 +600,13 @@ impl SubObj {
 
 impl Sub {
     /// Lossless complete-field allocation.
-    pub fn from_complete_fields(loc: Location, ty: Type, a: Expr, b: Expr) -> 
Self {
-        let obj = SubObj::new(loc, ty, a, b);
+    pub fn from_fields(
+        loc: impl Into<Location>,
+        ty: impl Into<Type>,
+        a: impl Into<Expr>,
+        b: impl Into<Expr>,
+    ) -> Self {
+        let obj = SubObj::new(loc.into(), ty.into(), a.into(), b.into());
         Self { base: ObjectArc::new(obj) }
     }
 }
@@ -621,8 +660,13 @@ impl MulObj {
 
 impl Mul {
     /// Lossless complete-field allocation.
-    pub fn from_complete_fields(loc: Location, ty: Type, a: Expr, b: Expr) -> 
Self {
-        let obj = MulObj::new(loc, ty, a, b);
+    pub fn from_fields(
+        loc: impl Into<Location>,
+        ty: impl Into<Type>,
+        a: impl Into<Expr>,
+        b: impl Into<Expr>,
+    ) -> Self {
+        let obj = MulObj::new(loc.into(), ty.into(), a.into(), b.into());
         Self { base: ObjectArc::new(obj) }
     }
 }
@@ -676,8 +720,13 @@ impl DivObj {
 
 impl Div {
     /// Lossless complete-field allocation.
-    pub fn from_complete_fields(loc: Location, ty: Type, a: Expr, b: Expr) -> 
Self {
-        let obj = DivObj::new(loc, ty, a, b);
+    pub fn from_fields(
+        loc: impl Into<Location>,
+        ty: impl Into<Type>,
+        a: impl Into<Expr>,
+        b: impl Into<Expr>,
+    ) -> Self {
+        let obj = DivObj::new(loc.into(), ty.into(), a.into(), b.into());
         Self { base: ObjectArc::new(obj) }
     }
 }
@@ -731,8 +780,13 @@ impl ModObj {
 
 impl Mod {
     /// Lossless complete-field allocation.
-    pub fn from_complete_fields(loc: Location, ty: Type, a: Expr, b: Expr) -> 
Self {
-        let obj = ModObj::new(loc, ty, a, b);
+    pub fn from_fields(
+        loc: impl Into<Location>,
+        ty: impl Into<Type>,
+        a: impl Into<Expr>,
+        b: impl Into<Expr>,
+    ) -> Self {
+        let obj = ModObj::new(loc.into(), ty.into(), a.into(), b.into());
         Self { base: ObjectArc::new(obj) }
     }
 }
@@ -786,8 +840,13 @@ impl FloorDivObj {
 
 impl FloorDiv {
     /// Lossless complete-field allocation.
-    pub fn from_complete_fields(loc: Location, ty: Type, a: Expr, b: Expr) -> 
Self {
-        let obj = FloorDivObj::new(loc, ty, a, b);
+    pub fn from_fields(
+        loc: impl Into<Location>,
+        ty: impl Into<Type>,
+        a: impl Into<Expr>,
+        b: impl Into<Expr>,
+    ) -> Self {
+        let obj = FloorDivObj::new(loc.into(), ty.into(), a.into(), b.into());
         Self { base: ObjectArc::new(obj) }
     }
 }
@@ -841,8 +900,13 @@ impl FloorModObj {
 
 impl FloorMod {
     /// Lossless complete-field allocation.
-    pub fn from_complete_fields(loc: Location, ty: Type, a: Expr, b: Expr) -> 
Self {
-        let obj = FloorModObj::new(loc, ty, a, b);
+    pub fn from_fields(
+        loc: impl Into<Location>,
+        ty: impl Into<Type>,
+        a: impl Into<Expr>,
+        b: impl Into<Expr>,
+    ) -> Self {
+        let obj = FloorModObj::new(loc.into(), ty.into(), a.into(), b.into());
         Self { base: ObjectArc::new(obj) }
     }
 }
@@ -896,8 +960,13 @@ impl MinObj {
 
 impl Min {
     /// Lossless complete-field allocation.
-    pub fn from_complete_fields(loc: Location, ty: Type, a: Expr, b: Expr) -> 
Self {
-        let obj = MinObj::new(loc, ty, a, b);
+    pub fn from_fields(
+        loc: impl Into<Location>,
+        ty: impl Into<Type>,
+        a: impl Into<Expr>,
+        b: impl Into<Expr>,
+    ) -> Self {
+        let obj = MinObj::new(loc.into(), ty.into(), a.into(), b.into());
         Self { base: ObjectArc::new(obj) }
     }
 }
@@ -951,8 +1020,13 @@ impl MaxObj {
 
 impl Max {
     /// Lossless complete-field allocation.
-    pub fn from_complete_fields(loc: Location, ty: Type, a: Expr, b: Expr) -> 
Self {
-        let obj = MaxObj::new(loc, ty, a, b);
+    pub fn from_fields(
+        loc: impl Into<Location>,
+        ty: impl Into<Type>,
+        a: impl Into<Expr>,
+        b: impl Into<Expr>,
+    ) -> Self {
+        let obj = MaxObj::new(loc.into(), ty.into(), a.into(), b.into());
         Self { base: ObjectArc::new(obj) }
     }
 }
@@ -1006,8 +1080,13 @@ impl EQObj {
 
 impl EQ {
     /// Lossless complete-field allocation.
-    pub fn from_complete_fields(loc: Location, ty: Type, a: Expr, b: Expr) -> 
Self {
-        let obj = EQObj::new(loc, ty, a, b);
+    pub fn from_fields(
+        loc: impl Into<Location>,
+        ty: impl Into<Type>,
+        a: impl Into<Expr>,
+        b: impl Into<Expr>,
+    ) -> Self {
+        let obj = EQObj::new(loc.into(), ty.into(), a.into(), b.into());
         Self { base: ObjectArc::new(obj) }
     }
 }
@@ -1061,8 +1140,13 @@ impl NEObj {
 
 impl NE {
     /// Lossless complete-field allocation.
-    pub fn from_complete_fields(loc: Location, ty: Type, a: Expr, b: Expr) -> 
Self {
-        let obj = NEObj::new(loc, ty, a, b);
+    pub fn from_fields(
+        loc: impl Into<Location>,
+        ty: impl Into<Type>,
+        a: impl Into<Expr>,
+        b: impl Into<Expr>,
+    ) -> Self {
+        let obj = NEObj::new(loc.into(), ty.into(), a.into(), b.into());
         Self { base: ObjectArc::new(obj) }
     }
 }
@@ -1116,8 +1200,13 @@ impl LTObj {
 
 impl LT {
     /// Lossless complete-field allocation.
-    pub fn from_complete_fields(loc: Location, ty: Type, a: Expr, b: Expr) -> 
Self {
-        let obj = LTObj::new(loc, ty, a, b);
+    pub fn from_fields(
+        loc: impl Into<Location>,
+        ty: impl Into<Type>,
+        a: impl Into<Expr>,
+        b: impl Into<Expr>,
+    ) -> Self {
+        let obj = LTObj::new(loc.into(), ty.into(), a.into(), b.into());
         Self { base: ObjectArc::new(obj) }
     }
 }
@@ -1171,8 +1260,13 @@ impl LEObj {
 
 impl LE {
     /// Lossless complete-field allocation.
-    pub fn from_complete_fields(loc: Location, ty: Type, a: Expr, b: Expr) -> 
Self {
-        let obj = LEObj::new(loc, ty, a, b);
+    pub fn from_fields(
+        loc: impl Into<Location>,
+        ty: impl Into<Type>,
+        a: impl Into<Expr>,
+        b: impl Into<Expr>,
+    ) -> Self {
+        let obj = LEObj::new(loc.into(), ty.into(), a.into(), b.into());
         Self { base: ObjectArc::new(obj) }
     }
 }
@@ -1226,8 +1320,13 @@ impl GTObj {
 
 impl GT {
     /// Lossless complete-field allocation.
-    pub fn from_complete_fields(loc: Location, ty: Type, a: Expr, b: Expr) -> 
Self {
-        let obj = GTObj::new(loc, ty, a, b);
+    pub fn from_fields(
+        loc: impl Into<Location>,
+        ty: impl Into<Type>,
+        a: impl Into<Expr>,
+        b: impl Into<Expr>,
+    ) -> Self {
+        let obj = GTObj::new(loc.into(), ty.into(), a.into(), b.into());
         Self { base: ObjectArc::new(obj) }
     }
 }
@@ -1281,8 +1380,13 @@ impl GEObj {
 
 impl GE {
     /// Lossless complete-field allocation.
-    pub fn from_complete_fields(loc: Location, ty: Type, a: Expr, b: Expr) -> 
Self {
-        let obj = GEObj::new(loc, ty, a, b);
+    pub fn from_fields(
+        loc: impl Into<Location>,
+        ty: impl Into<Type>,
+        a: impl Into<Expr>,
+        b: impl Into<Expr>,
+    ) -> Self {
+        let obj = GEObj::new(loc.into(), ty.into(), a.into(), b.into());
         Self { base: ObjectArc::new(obj) }
     }
 }
@@ -1336,8 +1440,13 @@ impl AndObj {
 
 impl And {
     /// Lossless complete-field allocation.
-    pub fn from_complete_fields(loc: Location, ty: Type, a: Expr, b: Expr) -> 
Self {
-        let obj = AndObj::new(loc, ty, a, b);
+    pub fn from_fields(
+        loc: impl Into<Location>,
+        ty: impl Into<Type>,
+        a: impl Into<Expr>,
+        b: impl Into<Expr>,
+    ) -> Self {
+        let obj = AndObj::new(loc.into(), ty.into(), a.into(), b.into());
         Self { base: ObjectArc::new(obj) }
     }
 }
@@ -1391,8 +1500,13 @@ impl OrObj {
 
 impl Or {
     /// Lossless complete-field allocation.
-    pub fn from_complete_fields(loc: Location, ty: Type, a: Expr, b: Expr) -> 
Self {
-        let obj = OrObj::new(loc, ty, a, b);
+    pub fn from_fields(
+        loc: impl Into<Location>,
+        ty: impl Into<Type>,
+        a: impl Into<Expr>,
+        b: impl Into<Expr>,
+    ) -> Self {
+        let obj = OrObj::new(loc.into(), ty.into(), a.into(), b.into());
         Self { base: ObjectArc::new(obj) }
     }
 }
@@ -1445,8 +1559,8 @@ impl NotObj {
 
 impl Not {
     /// Lossless complete-field allocation.
-    pub fn from_complete_fields(loc: Location, ty: Type, a: Expr) -> Self {
-        let obj = NotObj::new(loc, ty, a);
+    pub fn from_fields(loc: impl Into<Location>, ty: impl Into<Type>, a: impl 
Into<Expr>) -> Self {
+        let obj = NotObj::new(loc.into(), ty.into(), a.into());
         Self { base: ObjectArc::new(obj) }
     }
 }
@@ -1507,14 +1621,20 @@ impl SelectObj {
 
 impl Select {
     /// Lossless complete-field allocation.
-    pub fn from_complete_fields(
-        loc: Location,
-        ty: Type,
-        condition: Expr,
-        true_value: Expr,
-        false_value: Expr,
+    pub fn from_fields(
+        loc: impl Into<Location>,
+        ty: impl Into<Type>,
+        condition: impl Into<Expr>,
+        true_value: impl Into<Expr>,
+        false_value: impl Into<Expr>,
     ) -> Self {
-        let obj = SelectObj::new(loc, ty, condition, true_value, false_value);
+        let obj = SelectObj::new(
+            loc.into(),
+            ty.into(),
+            condition.into(),
+            true_value.into(),
+            false_value.into(),
+        );
         Self { base: ObjectArc::new(obj) }
     }
 }
@@ -1569,14 +1689,14 @@ impl LetObj {
 
 impl Let {
     /// Lossless complete-field allocation.
-    pub fn from_complete_fields(
-        loc: Location,
-        ty: Type,
-        var: Var,
-        value: Expr,
-        body: Expr,
+    pub fn from_fields(
+        loc: impl Into<Location>,
+        ty: impl Into<Type>,
+        var: impl Into<Var>,
+        value: impl Into<Expr>,
+        body: impl Into<Expr>,
     ) -> Self {
-        let obj = LetObj::new(loc, ty, var, value, body);
+        let obj = LetObj::new(loc.into(), ty.into(), var.into(), value.into(), 
body.into());
         Self { base: ObjectArc::new(obj) }
     }
 }
@@ -1599,7 +1719,7 @@ impl Cast {
             let (lhs, rhs) = (ty.dtype.lanes, value_dtype.lanes);
             return Err(support::compare_failed(tvm_ffi::INTERNAL_ERROR, 
condition, lhs, rhs, ""));
         }
-        Ok(Self::from_complete_fields(loc.into(), ty.into(), value))
+        Ok(Self::from_fields(loc.into(), ty, value))
     }
 }
 
@@ -1667,7 +1787,7 @@ macro_rules! binary_new {
                 ) -> Result<Self> {
                     let (a, b) = (a.into(), b.into());
                     let ty = binary_type(stringify!($node), $operands, &a, 
&b)?;
-                    Ok(Self::from_complete_fields(loc.into(), ty, a, b))
+                    Ok(Self::from_fields(loc.into(), ty, a, b))
                 }
             }
         )+
@@ -1738,7 +1858,7 @@ impl And {
     pub fn new(a: impl Into<Expr>, b: impl Into<Expr>, loc: impl 
Into<Location>) -> Result<Self> {
         let (a, b) = (a.into(), b.into());
         let ty = logical_type(&a, &b)?;
-        Ok(Self::from_complete_fields(loc.into(), ty, a, b))
+        Ok(Self::from_fields(loc.into(), ty, a, b))
     }
 }
 
@@ -1747,7 +1867,7 @@ impl Or {
     pub fn new(a: impl Into<Expr>, b: impl Into<Expr>, loc: impl 
Into<Location>) -> Result<Self> {
         let (a, b) = (a.into(), b.into());
         let ty = logical_type(&a, &b)?;
-        Ok(Self::from_complete_fields(loc.into(), ty, a, b))
+        Ok(Self::from_fields(loc.into(), ty, a, b))
     }
 }
 
@@ -1761,7 +1881,7 @@ impl BitwiseNot {
             let message = "BitwiseNot requires an integer or boolean operand";
             return Err(support::check_failed(tvm_ffi::TYPE_ERROR, condition, 
message));
         }
-        Ok(Self::from_complete_fields(loc.into(), a.ty.clone(), a))
+        Ok(Self::from_fields(loc.into(), a.ty.clone(), a))
     }
 }
 
@@ -1774,7 +1894,7 @@ impl Not {
             let condition = "a_ty.MatchesCode(DLDataTypeCode::kDLBool)";
             return Err(support::check_failed(tvm_ffi::INTERNAL_ERROR, 
condition, ""));
         }
-        Ok(Self::from_complete_fields(loc.into(), bool_type(a_dtype)?.into(), 
a))
+        Ok(Self::from_fields(loc.into(), bool_type(a_dtype)?, a))
     }
 }
 
@@ -1823,7 +1943,7 @@ impl Select {
             ));
         }
         let ty = true_value.ty.clone();
-        Ok(Self::from_complete_fields(loc.into(), ty, condition, true_value, 
false_value))
+        Ok(Self::from_fields(loc.into(), ty, condition, true_value, 
false_value))
     }
 }
 
@@ -1851,6 +1971,6 @@ impl Let {
             return Err(support::check_failed(tvm_ffi::INTERNAL_ERROR, 
condition, ""));
         }
         let ty = body.ty.clone();
-        Ok(Self::from_complete_fields(loc.into(), ty, var, value, body))
+        Ok(Self::from_fields(loc.into(), ty, var, value, body))
     }
 }
diff --git a/rust/src/ir/prim/vector_expr.rs b/rust/src/ir/prim/vector_expr.rs
index 651d35ee..d2b31b94 100644
--- a/rust/src/ir/prim/vector_expr.rs
+++ b/rust/src/ir/prim/vector_expr.rs
@@ -26,7 +26,7 @@
 //! checks the operands and derives the vector type.
 
 // `custom-new` reserves `new` for a constructor with the C++ one's semantics 
(operand checks and the
-// derived vector type); the generated `from_complete_fields` allocates the 
fields as given.
+// derived vector type); the generated `from_fields` allocates the fields as 
given.
 // tvm-ffi-stubgen(custom-new): prim.Ramp
 // tvm-ffi-stubgen(custom-new): prim.Broadcast
 // tvm-ffi-stubgen(custom-new): prim.Shuffle
@@ -125,14 +125,14 @@ impl RampObj {
 
 impl Ramp {
     /// Lossless complete-field allocation.
-    pub fn from_complete_fields(
-        loc: Location,
-        ty: Type,
-        base_: Expr,
-        stride: Expr,
-        lanes: Expr,
+    pub fn from_fields(
+        loc: impl Into<Location>,
+        ty: impl Into<Type>,
+        base_: impl Into<Expr>,
+        stride: impl Into<Expr>,
+        lanes: impl Into<Expr>,
     ) -> Self {
-        let obj = RampObj::new(loc, ty, base_, stride, lanes);
+        let obj = RampObj::new(loc.into(), ty.into(), base_.into(), 
stride.into(), lanes.into());
         Self { base: ObjectArc::new(obj) }
     }
 }
@@ -186,8 +186,13 @@ impl BroadcastObj {
 
 impl Broadcast {
     /// Lossless complete-field allocation.
-    pub fn from_complete_fields(loc: Location, ty: Type, value: Expr, lanes: 
Expr) -> Self {
-        let obj = BroadcastObj::new(loc, ty, value, lanes);
+    pub fn from_fields(
+        loc: impl Into<Location>,
+        ty: impl Into<Type>,
+        value: impl Into<Expr>,
+        lanes: impl Into<Expr>,
+    ) -> Self {
+        let obj = BroadcastObj::new(loc.into(), ty.into(), value.into(), 
lanes.into());
         Self { base: ObjectArc::new(obj) }
     }
 }
@@ -241,13 +246,13 @@ impl ShuffleObj {
 
 impl Shuffle {
     /// Lossless complete-field allocation.
-    pub fn from_complete_fields(
-        loc: Location,
-        ty: Type,
-        vectors: Array<Expr>,
-        indices: Array<Expr>,
+    pub fn from_fields(
+        loc: impl Into<Location>,
+        ty: impl Into<Type>,
+        vectors: impl Into<Array<Expr>>,
+        indices: impl Into<Array<Expr>>,
     ) -> Self {
-        let obj = ShuffleObj::new(loc, ty, vectors, indices);
+        let obj = ShuffleObj::new(loc.into(), ty.into(), vectors.into(), 
indices.into());
         Self { base: ObjectArc::new(obj) }
     }
 }
@@ -279,8 +284,8 @@ impl Ramp {
             false => cast_scalar(stride, base_dtype)?,
         };
         let (dtype, lanes) = vector_lanes(base_dtype, lanes)?;
-        let ty = PrimType::from_dtype(dtype)?.into();
-        Ok(Self::from_complete_fields(loc.into(), ty, base, stride, lanes))
+        let ty = PrimType::from_dtype(dtype)?;
+        Ok(Self::from_fields(loc.into(), ty, base, stride, lanes))
     }
 }
 
@@ -298,8 +303,8 @@ impl Broadcast {
             return Err(support::check_failed(tvm_ffi::INTERNAL_ERROR, 
"value_ty.IsScalar()", ""));
         }
         let (dtype, lanes) = vector_lanes(value_dtype, lanes)?;
-        let ty = PrimType::from_dtype(dtype)?.into();
-        Ok(Self::from_complete_fields(loc.into(), ty, value, lanes))
+        let ty = PrimType::from_dtype(dtype)?;
+        Ok(Self::from_fields(loc.into(), ty, value, lanes))
     }
 }
 
@@ -347,7 +352,7 @@ impl Shuffle {
             ));
         }
         let ty = PrimType::from_dtype(with_lanes(base_dtype, indices.len() as 
u16))?;
-        Ok(Self::from_complete_fields(loc.into(), ty.into(), vectors, indices))
+        Ok(Self::from_fields(loc.into(), ty, vectors, indices))
     }
 }
 
diff --git a/rust/src/ir/stmt.rs b/rust/src/ir/stmt.rs
index 2cf2f2be..41944db0 100644
--- a/rust/src/ir/stmt.rs
+++ b/rust/src/ir/stmt.rs
@@ -26,7 +26,7 @@
 //! as C++ converts it.
 
 // `custom-new` reserves `new` for a constructor with the C++ one's semantics 
(defaults, implicit
-// conversions, validation); the generated `from_complete_fields` allocates 
the fields as given.
+// conversions, validation); the generated `from_fields` allocates the fields 
as given.
 // tvm-ffi-stubgen(custom-new): ir.Bind
 // tvm-ffi-stubgen(custom-new): ir.AssertStmt
 // tvm-ffi-stubgen(custom-new): ir.For
@@ -81,6 +81,7 @@ use tvm_ffi::Object;
 use tvm_ffi::ObjectArc;
 use tvm_ffi::ObjectRefCore;
 use tvm_ffi::Result;
+use tvm_ffi::ShallowCopy;
 use tvm_ffi::String;
 use tvm_ffi::tvm_ffi_sys::TVMFFITypeIndex;
 // tvm-ffi-stubgen(end)
@@ -114,7 +115,7 @@ impl Deref for Stmt {
 }
 
 impl StmtObj {
-    pub(crate) fn new(loc: Location) -> Self {
+    pub fn new(loc: Location) -> Self {
         let base = Object::new();
         Self { base, loc }
     }
@@ -122,11 +123,17 @@ impl StmtObj {
 
 impl Stmt {
     /// Lossless complete-field allocation.
-    pub fn new(loc: Location) -> Self {
-        let obj = StmtObj::new(loc);
+    pub fn new(loc: impl Into<Location>) -> Self {
+        let obj = StmtObj::new(loc.into());
         Self { base: ObjectArc::new(obj) }
     }
 }
+
+impl ShallowCopy for StmtObj {
+    fn shallow_copy(&self) -> Self {
+        Self { base: ShallowCopy::shallow_copy(&self.base), loc: 
Clone::clone(&self.loc) }
+    }
+}
 // tvm-ffi-stubgen(end)
 
 // tvm-ffi-stubgen(begin): object/ir.Bind
@@ -175,8 +182,12 @@ impl BindObj {
 
 impl Bind {
     /// Lossless complete-field allocation.
-    pub fn from_complete_fields(loc: Location, var: Var, value: Expr) -> Self {
-        let obj = BindObj::new(loc, var, value);
+    pub fn from_fields(
+        loc: impl Into<Location>,
+        var: impl Into<Var>,
+        value: impl Into<Expr>,
+    ) -> Self {
+        let obj = BindObj::new(loc.into(), var.into(), value.into());
         Self { base: ObjectArc::new(obj) }
     }
 }
@@ -197,7 +208,7 @@ impl Bind {
             let condition = "ffi::StructuralEqual()(value->ty, var->ty)";
             return Err(support::check_failed(tvm_ffi::INTERNAL_ERROR, 
condition, ""));
         }
-        Ok(Self::from_complete_fields(loc.into(), var, value))
+        Ok(Self::from_fields(loc.into(), var, value))
     }
 }
 
@@ -253,13 +264,18 @@ impl AssertStmtObj {
 
 impl AssertStmt {
     /// Lossless complete-field allocation.
-    pub fn from_complete_fields(
-        loc: Location,
-        condition: Expr,
-        error_kind: StringImm,
-        message_parts: Array<StringImm>,
+    pub fn from_fields(
+        loc: impl Into<Location>,
+        condition: impl Into<Expr>,
+        error_kind: impl Into<StringImm>,
+        message_parts: impl Into<Array<StringImm>>,
     ) -> Self {
-        let obj = AssertStmtObj::new(loc, condition, error_kind, 
message_parts);
+        let obj = AssertStmtObj::new(
+            loc.into(),
+            condition.into(),
+            error_kind.into(),
+            message_parts.into(),
+        );
         Self { base: ObjectArc::new(obj) }
     }
 }
@@ -288,7 +304,7 @@ impl AssertStmt {
             let check = "condition_ty.MatchesCode(DLDataTypeCode::kDLBool)";
             return Err(support::check_failed(tvm_ffi::INTERNAL_ERROR, check, 
&message));
         }
-        Ok(Self::from_complete_fields(loc.into(), condition, error_kind, 
message_parts))
+        Ok(Self::from_fields(loc.into(), condition, error_kind, message_parts))
     }
 }
 
@@ -352,17 +368,26 @@ impl ForObj {
 
 impl For {
     /// Lossless complete-field allocation.
-    pub fn from_complete_fields(
-        loc: Location,
-        var: Var,
-        begin: Expr,
-        end: Expr,
-        step: Expr,
+    pub fn from_fields(
+        loc: impl Into<Location>,
+        var: impl Into<Var>,
+        begin: impl Into<Expr>,
+        end: impl Into<Expr>,
+        step: impl Into<Expr>,
         kind: i32,
-        body: SeqStmt,
-        annotations: Map<String, Any>,
+        body: impl Into<SeqStmt>,
+        annotations: impl Into<Map<String, Any>>,
     ) -> Self {
-        let obj = ForObj::new(loc, var, begin, end, step, kind, body, 
annotations);
+        let obj = ForObj::new(
+            loc.into(),
+            var.into(),
+            begin.into(),
+            end.into(),
+            step.into(),
+            kind,
+            body.into(),
+            annotations.into(),
+        );
         Self { base: ObjectArc::new(obj) }
     }
 }
@@ -472,16 +497,7 @@ impl For {
         same_type(&end, "var.ty() == end.ty()")?;
         let step = promote(step)?;
         same_type(&step, "var.ty() == step.ty()")?;
-        Ok(Self::from_complete_fields(
-            loc.into(),
-            var,
-            begin,
-            end,
-            step,
-            kind as i32,
-            body,
-            annotations,
-        ))
+        Ok(Self::from_fields(loc.into(), var, begin, end, step, kind as i32, 
body, annotations))
     }
 }
 
@@ -542,8 +558,12 @@ impl WhileObj {
 
 impl While {
     /// Lossless complete-field allocation.
-    pub fn new(loc: Location, condition: Expr, body: SeqStmt) -> Self {
-        let obj = WhileObj::new(loc, condition, body);
+    pub fn new(
+        loc: impl Into<Location>,
+        condition: impl Into<Expr>,
+        body: impl Into<SeqStmt>,
+    ) -> Self {
+        let obj = WhileObj::new(loc.into(), condition.into(), body.into());
         Self { base: ObjectArc::new(obj) }
     }
 }
@@ -617,13 +637,13 @@ impl TensorStoreObj {
 
 impl TensorStore {
     /// Lossless complete-field allocation.
-    pub fn from_complete_fields(
-        loc: Location,
-        dest: Expr,
-        indices: Array<Expr>,
-        value: Expr,
+    pub fn from_fields(
+        loc: impl Into<Location>,
+        dest: impl Into<Expr>,
+        indices: impl Into<Array<Expr>>,
+        value: impl Into<Expr>,
     ) -> Self {
-        let obj = TensorStoreObj::new(loc, dest, indices, value);
+        let obj = TensorStoreObj::new(loc.into(), dest.into(), indices.into(), 
value.into());
         Self { base: ObjectArc::new(obj) }
     }
 }
@@ -663,7 +683,7 @@ impl TensorStore {
             let message = "Packed hook result type mismatch";
             return Err(tvm_ffi::Error::new(tvm_ffi::TYPE_ERROR, message, ""));
         }
-        Ok(Self::from_complete_fields(loc.into(), dest, indices, value))
+        Ok(Self::from_fields(loc.into(), dest, indices, value))
     }
 }
 
@@ -725,16 +745,24 @@ impl RegionStmtObj {
 
 impl RegionStmt {
     /// Lossless complete-field allocation.
-    pub fn from_complete_fields(
-        loc: Location,
-        op: Op,
-        args: Array<Expr>,
-        body_params: Array<Var>,
-        attrs: DictAttrs,
-        body: SeqStmt,
-        result_vars: Array<Var>,
+    pub fn from_fields(
+        loc: impl Into<Location>,
+        op: impl Into<Op>,
+        args: impl Into<Array<Expr>>,
+        body_params: impl Into<Array<Var>>,
+        attrs: impl Into<DictAttrs>,
+        body: impl Into<SeqStmt>,
+        result_vars: impl Into<Array<Var>>,
     ) -> Self {
-        let obj = RegionStmtObj::new(loc, op, args, body_params, attrs, body, 
result_vars);
+        let obj = RegionStmtObj::new(
+            loc.into(),
+            op.into(),
+            args.into(),
+            body_params.into(),
+            attrs.into(),
+            body.into(),
+            result_vars.into(),
+        );
         Self { base: ObjectArc::new(obj) }
     }
 }
@@ -790,8 +818,7 @@ impl RegionStmt {
                 return Err(support::check_failed(tvm_ffi::VALUE_ERROR, 
condition, message));
             }
         }
-        let region =
-            Self::from_complete_fields(loc.into(), op, args, body_params, 
attrs, body, result_vars);
+        let region = Self::from_fields(loc.into(), op, args, body_params, 
attrs, body, result_vars);
         if let Some(validate) = support::NativeHook::of_op(&region.op, 
"FRegionValidate")? {
             validate.call_void(&region)?;
         }
@@ -810,10 +837,10 @@ fn region_body_params(op: &Op, args: &Array<Expr>, attrs: 
&DictAttrs) -> Result<
         );
         return Err(support::check_failed(tvm_ffi::VALUE_ERROR, 
"IsRegionOp(op)", &message));
     };
-    let signature = Call::from_complete_fields(
-        UnknownLoc::new().into(),
-        MissingType::new().into(),
-        op.clone().into(),
+    let signature = Call::from_fields(
+        UnknownLoc::new(),
+        MissingType::new(),
+        op.clone(),
         args.clone(),
         Some(attrs.clone().into()),
         Array::new(vec![]),
@@ -888,8 +915,8 @@ impl SeqStmtObj {
 
 impl SeqStmt {
     /// Lossless complete-field allocation.
-    pub fn from_complete_fields(loc: Location, seq: Array<Stmt>) -> Self {
-        let obj = SeqStmtObj::new(loc, seq);
+    pub fn from_fields(loc: impl Into<Location>, seq: impl Into<Array<Stmt>>) 
-> Self {
+        let obj = SeqStmtObj::new(loc.into(), seq.into());
         Self { base: ObjectArc::new(obj) }
     }
 }
@@ -902,7 +929,7 @@ impl SeqStmt {
     /// (C++ `SeqStmt(seq, loc)`).
     pub fn new(seq: impl IntoIterator<Item = impl Into<Stmt>>, loc: impl 
Into<Location>) -> Self {
         let seq = seq.into_iter().map(Into::into).collect::<Array<Stmt>>();
-        Self::from_complete_fields(loc.into(), flatten(seq))
+        Self::from_fields(loc.into(), flatten(seq))
     }
 }
 
@@ -938,7 +965,7 @@ fn into_body(stmt: Stmt) -> Result<SeqStmt> {
         return Ok(tvm_ffi::ObjectRefCast::try_cast(stmt).expect("a SeqStmt"));
     }
     let loc = stmt.loc.clone();
-    Ok(SeqStmt::from_complete_fields(loc, flatten(Array::new(vec![stmt]))))
+    Ok(SeqStmt::from_fields(loc, flatten(Array::new(vec![stmt]))))
 }
 
 // tvm-ffi-stubgen(begin): object/ir.If
@@ -993,13 +1020,13 @@ impl IfObj {
 
 impl If {
     /// Lossless complete-field allocation.
-    pub fn from_complete_fields(
-        loc: Location,
-        condition: Expr,
-        then_case: SeqStmt,
-        else_case: Option<SeqStmt>,
+    pub fn from_fields(
+        loc: impl Into<Location>,
+        condition: impl Into<Expr>,
+        then_case: impl Into<SeqStmt>,
+        else_case: impl Into<Option<SeqStmt>>,
     ) -> Self {
-        let obj = IfObj::new(loc, condition, then_case, else_case);
+        let obj = IfObj::new(loc.into(), condition.into(), then_case.into(), 
else_case.into());
         Self { base: ObjectArc::new(obj) }
     }
 }
@@ -1020,7 +1047,7 @@ impl If {
         support::prim_dtype(&condition)?;
         let then_case = into_body(then_case.into())?;
         let else_case = else_case.map(|stmt| 
into_body(stmt.clone())).transpose()?;
-        Ok(Self::from_complete_fields(loc.into(), condition, then_case, 
else_case))
+        Ok(Self::from_fields(loc.into(), condition, then_case, else_case))
     }
 }
 
@@ -1069,8 +1096,8 @@ impl EvaluateObj {
 
 impl Evaluate {
     /// Lossless complete-field allocation.
-    pub fn from_complete_fields(loc: Location, value: Expr) -> Self {
-        let obj = EvaluateObj::new(loc, value);
+    pub fn from_fields(loc: impl Into<Location>, value: impl Into<Expr>) -> 
Self {
+        let obj = EvaluateObj::new(loc.into(), value.into());
         Self { base: ObjectArc::new(obj) }
     }
 }
@@ -1081,7 +1108,7 @@ tvm_ffi::impl_object_upcast!(Evaluate => Stmt);
 impl Evaluate {
     /// Evaluate `value` for its side effects (C++ `Evaluate(value, loc)`).
     pub fn new(value: impl Into<Expr>, loc: impl Into<Location>) -> Self {
-        Self::from_complete_fields(loc.into(), value.into())
+        Self::from_fields(loc.into(), value.into())
     }
 }
 
@@ -1129,8 +1156,8 @@ impl BreakObj {
 
 impl Break {
     /// Lossless complete-field allocation.
-    pub fn new(loc: Location) -> Self {
-        let obj = BreakObj::new(loc);
+    pub fn new(loc: impl Into<Location>) -> Self {
+        let obj = BreakObj::new(loc.into());
         Self { base: ObjectArc::new(obj) }
     }
 }
@@ -1185,8 +1212,8 @@ impl ReturnObj {
 
 impl Return {
     /// Lossless complete-field allocation.
-    pub fn from_complete_fields(loc: Location, value: Expr) -> Self {
-        let obj = ReturnObj::new(loc, value);
+    pub fn from_fields(loc: impl Into<Location>, value: impl Into<Expr>) -> 
Self {
+        let obj = ReturnObj::new(loc.into(), value.into());
         Self { base: ObjectArc::new(obj) }
     }
 }
@@ -1197,7 +1224,7 @@ tvm_ffi::impl_object_upcast!(Return => Stmt);
 impl Return {
     /// Return `value` from the enclosing function (C++ `Return(value, loc)`).
     pub fn new(value: impl Into<Expr>, loc: impl Into<Location>) -> Self {
-        Self::from_complete_fields(loc.into(), value.into())
+        Self::from_fields(loc.into(), value.into())
     }
 }
 
@@ -1245,8 +1272,8 @@ impl ContinueObj {
 
 impl Continue {
     /// Lossless complete-field allocation.
-    pub fn new(loc: Location) -> Self {
-        let obj = ContinueObj::new(loc);
+    pub fn new(loc: impl Into<Location>) -> Self {
+        let obj = ContinueObj::new(loc.into());
         Self { base: ObjectArc::new(obj) }
     }
 }
diff --git a/rust/src/ir/transform/core.rs b/rust/src/ir/transform/core.rs
index b46b11a9..ccab94df 100644
--- a/rust/src/ir/transform/core.rs
+++ b/rust/src/ir/transform/core.rs
@@ -84,8 +84,8 @@ impl PassInfoObj {
 
 impl PassInfo {
     /// Lossless complete-field allocation.
-    pub fn new(opt_level: i32, name: String) -> Self {
-        let obj = PassInfoObj::new(opt_level, name);
+    pub fn new(opt_level: i32, name: impl Into<String>) -> Self {
+        let obj = PassInfoObj::new(opt_level, name.into());
         Self { base: ObjectArc::new(obj) }
     }
 }
@@ -170,12 +170,18 @@ impl PassContext {
     /// Lossless complete-field allocation.
     pub fn new(
         opt_level: i32,
-        required_pass: Array<String>,
-        disabled_pass: Array<String>,
-        config: Map<String, Any>,
-        instruments: Array<PassInstrument>,
+        required_pass: impl Into<Array<String>>,
+        disabled_pass: impl Into<Array<String>>,
+        config: impl Into<Map<String, Any>>,
+        instruments: impl Into<Array<PassInstrument>>,
     ) -> Self {
-        let obj = PassContextObj::new(opt_level, required_pass, disabled_pass, 
config, instruments);
+        let obj = PassContextObj::new(
+            opt_level,
+            required_pass.into(),
+            disabled_pass.into(),
+            config.into(),
+            instruments.into(),
+        );
         Self { base: ObjectArc::new(obj) }
     }
 }
diff --git a/rust/src/ir/type.rs b/rust/src/ir/type.rs
index 09ec21ae..e3968d56 100644
--- a/rust/src/ir/type.rs
+++ b/rust/src/ir/type.rs
@@ -29,7 +29,7 @@
 //! siblings, as C++ writes `PrimType::Int(32)`.
 
 // `custom-new` reserves `new` for a constructor with the C++ one's semantics 
(defaults, the shared
-// `MissingType`, `NoneType` and `TensorRegionType` nodes); the generated 
`from_complete_fields`
+// `MissingType`, `NoneType` and `TensorRegionType` nodes); the generated 
`from_fields`
 // allocates the fields as given.
 // tvm-ffi-stubgen(custom-new): ir.MissingType
 // tvm-ffi-stubgen(custom-new): ir.NoneType
@@ -61,6 +61,7 @@ use tvm_ffi::Object;
 use tvm_ffi::ObjectArc;
 use tvm_ffi::ObjectRefCore;
 use tvm_ffi::Result;
+use tvm_ffi::ShallowCopy;
 use tvm_ffi::VALUE_ERROR;
 // tvm-ffi-stubgen(end)
 
@@ -93,7 +94,7 @@ impl Deref for Type {
 }
 
 impl TypeObj {
-    pub(crate) fn new(loc: Location) -> Self {
+    pub fn new(loc: Location) -> Self {
         let base = Object::new();
         Self { base, loc }
     }
@@ -101,11 +102,17 @@ impl TypeObj {
 
 impl Type {
     /// Lossless complete-field allocation.
-    pub fn new(loc: Location) -> Self {
-        let obj = TypeObj::new(loc);
+    pub fn new(loc: impl Into<Location>) -> Self {
+        let obj = TypeObj::new(loc.into());
         Self { base: ObjectArc::new(obj) }
     }
 }
+
+impl ShallowCopy for TypeObj {
+    fn shallow_copy(&self) -> Self {
+        Self { base: ShallowCopy::shallow_copy(&self.base), loc: 
Clone::clone(&self.loc) }
+    }
+}
 // tvm-ffi-stubgen(end)
 
 // tvm-ffi-stubgen(begin): object/ir.MissingType
@@ -152,8 +159,8 @@ impl MissingTypeObj {
 
 impl MissingType {
     /// Lossless complete-field allocation.
-    pub fn from_complete_fields(loc: Location) -> Self {
-        let obj = MissingTypeObj::new(loc);
+    pub fn from_fields(loc: impl Into<Location>) -> Self {
+        let obj = MissingTypeObj::new(loc.into());
         Self { base: ObjectArc::new(obj) }
     }
 }
@@ -218,8 +225,8 @@ impl NoneTypeObj {
 
 impl NoneType {
     /// Lossless complete-field allocation.
-    pub fn from_complete_fields(loc: Location) -> Self {
-        let obj = NoneTypeObj::new(loc);
+    pub fn from_fields(loc: impl Into<Location>) -> Self {
+        let obj = NoneTypeObj::new(loc.into());
         Self { base: ObjectArc::new(obj) }
     }
 }
@@ -283,8 +290,8 @@ impl AnyTypeObj {
 
 impl AnyType {
     /// Lossless complete-field allocation.
-    pub fn new(loc: Location) -> Self {
-        let obj = AnyTypeObj::new(loc);
+    pub fn new(loc: impl Into<Location>) -> Self {
+        let obj = AnyTypeObj::new(loc.into());
         Self { base: ObjectArc::new(obj) }
     }
 }
@@ -338,8 +345,8 @@ impl TensorRegionTypeObj {
 
 impl TensorRegionType {
     /// Lossless complete-field allocation.
-    pub fn from_complete_fields(loc: Location) -> Self {
-        let obj = TensorRegionTypeObj::new(loc);
+    pub fn from_fields(loc: impl Into<Location>) -> Self {
+        let obj = TensorRegionTypeObj::new(loc.into());
         Self { base: ObjectArc::new(obj) }
     }
 }
@@ -404,8 +411,8 @@ impl OpaqueTypeObj {
 
 impl OpaqueType {
     /// Lossless complete-field allocation.
-    pub fn new(loc: Location) -> Self {
-        let obj = OpaqueTypeObj::new(loc);
+    pub fn new(loc: impl Into<Location>) -> Self {
+        let obj = OpaqueTypeObj::new(loc.into());
         Self { base: ObjectArc::new(obj) }
     }
 }
@@ -461,8 +468,8 @@ impl PrimTypeObj {
 
 impl PrimType {
     /// Lossless complete-field allocation.
-    pub fn new(loc: Location, dtype: DLDataType) -> Self {
-        let obj = PrimTypeObj::new(loc, dtype);
+    pub fn new(loc: impl Into<Location>, dtype: impl Into<DLDataType>) -> Self 
{
+        let obj = PrimTypeObj::new(loc.into(), dtype.into());
         Self { base: ObjectArc::new(obj) }
     }
 }
@@ -588,8 +595,8 @@ impl StringTypeObj {
 
 impl StringType {
     /// Lossless complete-field allocation.
-    pub fn new(loc: Location) -> Self {
-        let obj = StringTypeObj::new(loc);
+    pub fn new(loc: impl Into<Location>) -> Self {
+        let obj = StringTypeObj::new(loc.into());
         Self { base: ObjectArc::new(obj) }
     }
 }
@@ -646,8 +653,12 @@ impl PtrTypeObj {
 
 impl PtrType {
     /// Lossless complete-field allocation.
-    pub fn from_complete_fields(loc: Location, element_type: Type, scope: 
MemoryScope) -> Self {
-        let obj = PtrTypeObj::new(loc, element_type, scope);
+    pub fn from_fields(
+        loc: impl Into<Location>,
+        element_type: impl Into<Type>,
+        scope: impl Into<MemoryScope>,
+    ) -> Self {
+        let obj = PtrTypeObj::new(loc.into(), element_type.into(), 
scope.into());
         Self { base: ObjectArc::new(obj) }
     }
 }
@@ -665,7 +676,7 @@ impl PtrType {
             let message = "PtrType element_type cannot be Type::Missing()";
             return Err(support::check_failed(tvm_ffi::INTERNAL_ERROR, 
condition, message));
         }
-        Ok(Self::from_complete_fields(UnknownLoc::new().into(), element_type, 
scope.clone()))
+        Ok(Self::from_fields(UnknownLoc::new(), element_type, scope.clone()))
     }
 }
 
@@ -714,8 +725,8 @@ impl TupleTypeObj {
 
 impl TupleType {
     /// Lossless complete-field allocation.
-    pub fn from_complete_fields(loc: Location, fields: Array<Type>) -> Self {
-        let obj = TupleTypeObj::new(loc, fields);
+    pub fn from_fields(loc: impl Into<Location>, fields: impl 
Into<Array<Type>>) -> Self {
+        let obj = TupleTypeObj::new(loc.into(), fields.into());
         Self { base: ObjectArc::new(obj) }
     }
 }
@@ -730,7 +741,7 @@ impl TupleType {
         loc: impl Into<Location>,
     ) -> Self {
         let fields = 
fields.into_iter().map(Into::into).collect::<Array<Type>>();
-        Self::from_complete_fields(loc.into(), fields)
+        Self::from_fields(loc.into(), fields)
     }
 }
 
@@ -780,8 +791,12 @@ impl FuncTypeObj {
 
 impl FuncType {
     /// Lossless complete-field allocation.
-    pub fn from_complete_fields(loc: Location, arg_types: Array<Type>, 
ret_type: Type) -> Self {
-        let obj = FuncTypeObj::new(loc, arg_types, ret_type);
+    pub fn from_fields(
+        loc: impl Into<Location>,
+        arg_types: impl Into<Array<Type>>,
+        ret_type: impl Into<Type>,
+    ) -> Self {
+        let obj = FuncTypeObj::new(loc.into(), arg_types.into(), 
ret_type.into());
         Self { base: ObjectArc::new(obj) }
     }
 }
@@ -796,6 +811,6 @@ impl FuncType {
         ret_type: impl Into<Type>,
     ) -> Self {
         let arg_types = 
arg_types.into_iter().map(Into::into).collect::<Array<Type>>();
-        Self::from_complete_fields(UnknownLoc::new().into(), arg_types, 
ret_type.into())
+        Self::from_fields(UnknownLoc::new(), arg_types, ret_type.into())
     }
 }
diff --git a/rust/src/lib.rs b/rust/src/lib.rs
index 019877c4..521650fd 100644
--- a/rust/src/lib.rs
+++ b/rust/src/lib.rs
@@ -72,7 +72,7 @@
 //! as given, without the checks, defaults or derived fields of the native 
constructor. Where the
 //! native constructor does more than that (`ir::prim::Add` checks its operand 
types,
 //! `tirx::Function` derives its type from the signature), `new` is reserved 
for a constructor with
-//! those semantics, and the allocation of the fields as given is 
`from_complete_fields`. The IR
+//! those semantics, and the allocation of the fields as given is 
`from_fields`. The IR
 //! constructors are written in Rust after the C++ ones, with the same checks, 
defaults, derived
 //! fields and errors; they call into C++ only for the nodes C++ shares 
(`UnknownLoc`,
 //! `MissingType` and the `PrimType` of each dtype, fetched once per thread), 
for the hooks an
@@ -91,6 +91,50 @@
 //! The unknown location is [`ir::UNKNOWN_LOC`] as a constructor argument and
 //! [`ir::Location::unknown`] as a value.
 //!
+//! # Defining a type in Rust
+//!
+//! [`tvm_ffi::rs_class`] defines a new IR type, a type of expressions as 
[`ir::PrimType`] and
+//! [`ir::TupleType`] are, from its object struct, whose first field `base` is 
the [`ir::TypeObj`]
+//! it extends, which holds the location. It registers the type under 
`ir.Type` with the
+//! reflection of every other field, so that the C++ structural equality and 
hashing, deep copy,
+//! `repr`, the TVMScript printer and Python see the fields as they see a C++ 
type's.
+//! [`tvm_ffi::def_object_ref`] defines the reference, which converts into 
[`ir::Type`]. The
+//! object's `new` takes the `TypeObj`, then the fields in order:
+//!
+//! ```no_run
+//! use tvm_tirx::ir::{Location, PrimType, Type, TypeObj};
+//! use tvm_tirx::tvm_ffi::{self, def_object_ref, rs_class, ObjectRefCore};
+//!
+//! /// The type of a pair of values.
+//! #[rs_class("my_ext.PairType", final)]
+//! pub struct PairTypeNode {
+//!     base: TypeObj,
+//!     pub first: Type,
+//!     pub second: Type,
+//!     /// Not part of structural equality or hashing.
+//!     #[reflect(seq_hash_ignore)]
+//!     pub note: tvm_ffi::String,
+//! }
+//!
+//! def_object_ref!(pub PairType, Type, PairTypeNode);
+//!
+//! impl PairType {
+//!     pub fn new(first: impl Into<Type>, second: impl Into<Type>) -> Self {
+//!         PairTypeNode::new(TypeObj::new(Location::unknown()), first, 
second, "").into()
+//!     }
+//! }
+//!
+//! fn main() {
+//!     let pair = PairType::new(PrimType::int(32), PrimType::float(32));
+//!     let ty: Type = pair.into(); // an `ir.Type` that C++ sees as 
`my_ext.PairType`
+//!     assert!(ty.as_node::<PairTypeNode>().is_some());
+//! }
+//! ```
+//!
+//! The generated code names `tvm_ffi`, which `use tvm_tirx::tvm_ffi` brings 
into scope in a crate
+//! that depends only on `tvm-tirx`. The parent's object must have a public 
`new` and implement
+//! `ShallowCopy`; the bindings generate both for a complete type that is not 
final, as `ir.Type`.
+//!
 //! # Generated and hand-written code
 //!
 //! The code between a `// tvm-ffi-stubgen(begin)` and its `// 
tvm-ffi-stubgen(end)` is generated
@@ -114,11 +158,9 @@
 //! tvm-ffi-stubgen rust/src --target rust --dlls 
build/lib/libtvm_tirx_compiler.so
 //! ```
 //!
-//! `tvm-ffi-stubgen` comes with the `tvm_ffi` Python package, which must be 
tvm-ffi `f45854e` or
-//! later: that stubgen names a generated type by the module of its file 
(`crate::ir::expr::Expr`).
-//! The older one of `3rdparty/tvm-ffi`, and of the `.venv` it installs, names 
it by its type key
-//! and reports the files that use another module as stale. The blocks are 
formatted as rustfmt
-//! formats them, so `cargo fmt --check` and `tvm-ffi-stubgen --check` agree.
+//! `tvm-ffi-stubgen` comes with the `tvm_ffi` Python package, which must be 
the one of
+//! `3rdparty/tvm-ffi`: the blocks are what its generator emits. The blocks 
are formatted as
+//! rustfmt formats them, so `cargo fmt --check` and `tvm-ffi-stubgen --check` 
agree.
 //!
 //! # Tests
 //!
diff --git a/rust/src/sym/iter_affine_map.rs b/rust/src/sym/iter_affine_map.rs
index c88e8c3c..7ababb3a 100644
--- a/rust/src/sym/iter_affine_map.rs
+++ b/rust/src/sym/iter_affine_map.rs
@@ -131,11 +131,11 @@ impl IterMapResultObj {
 impl IterMapResult {
     /// Lossless complete-field allocation.
     pub fn new(
-        indices: Array<IterSumExpr>,
-        errors: Array<String>,
-        padding_predicate: Option<Expr>,
+        indices: impl Into<Array<IterSumExpr>>,
+        errors: impl Into<Array<String>>,
+        padding_predicate: impl Into<Option<Expr>>,
     ) -> Self {
-        let obj = IterMapResultObj::new(indices, errors, padding_predicate);
+        let obj = IterMapResultObj::new(indices.into(), errors.into(), 
padding_predicate.into());
         Self { base: ObjectArc::new(obj) }
     }
 }
@@ -180,8 +180,8 @@ impl IterMarkObj {
 
 impl IterMark {
     /// Lossless complete-field allocation.
-    pub fn new(source: Expr, extent: Expr) -> Self {
-        let obj = IterMarkObj::new(source, extent);
+    pub fn new(source: impl Into<Expr>, extent: impl Into<Expr>) -> Self {
+        let obj = IterMarkObj::new(source.into(), extent.into());
         Self { base: ObjectArc::new(obj) }
     }
 }
diff --git a/rust/src/tirx/function.rs b/rust/src/tirx/function.rs
index 7a76549c..289e4628 100644
--- a/rust/src/tirx/function.rs
+++ b/rust/src/tirx/function.rs
@@ -25,7 +25,7 @@
 //! [`renew_def`] gives its variables fresh definitions.
 
 // `custom-new` reserves `new` for a constructor with the C++ one's semantics 
(`ty` derived from the
-// signature); the generated `from_complete_fields` allocates the fields as 
given.
+// signature); the generated `from_fields` allocates the fields as given.
 // tvm-ffi-stubgen(custom-new): tirx.Function
 
 // Names the hand-written wrappers use: the import section keeps them even 
when no generated block
@@ -111,15 +111,22 @@ impl FunctionObj {
 
 impl Function {
     /// Lossless complete-field allocation.
-    pub fn from_complete_fields(
-        loc: Location,
-        ty: Type,
-        attrs: DictAttrs,
-        params: Array<Var>,
-        ret_type: Type,
-        body: Option<SeqStmt>,
+    pub fn from_fields(
+        loc: impl Into<Location>,
+        ty: impl Into<Type>,
+        attrs: impl Into<DictAttrs>,
+        params: impl Into<Array<Var>>,
+        ret_type: impl Into<Type>,
+        body: impl Into<Option<SeqStmt>>,
     ) -> Self {
-        let obj = FunctionObj::new(loc, ty, attrs, params, ret_type, body);
+        let obj = FunctionObj::new(
+            loc.into(),
+            ty.into(),
+            attrs.into(),
+            params.into(),
+            ret_type.into(),
+            body.into(),
+        );
         Self { base: ObjectArc::new(obj) }
     }
 }
@@ -146,14 +153,7 @@ impl Function {
         };
         let param_types = params.iter().map(|param| 
param.ty.clone()).collect::<Vec<Type>>();
         let ty = FuncType::new(param_types, ret_type.clone());
-        Self::from_complete_fields(
-            loc.into(),
-            ty.into(),
-            attrs.into(),
-            params,
-            ret_type,
-            body.cloned(),
-        )
+        Self::from_fields(loc.into(), ty, attrs.into(), params, ret_type, 
body.cloned())
     }
 }
 
diff --git a/rust/src/tirx/index_map.rs b/rust/src/tirx/index_map.rs
index d80a5f55..21d4ad1d 100644
--- a/rust/src/tirx/index_map.rs
+++ b/rust/src/tirx/index_map.rs
@@ -92,11 +92,15 @@ impl IndexMapObj {
 impl IndexMap {
     /// Lossless complete-field allocation.
     pub fn new(
-        initial_indices: Array<Var>,
-        final_indices: Array<Expr>,
-        inverse_index_map: Option<ObjectRef>,
+        initial_indices: impl Into<Array<Var>>,
+        final_indices: impl Into<Array<Expr>>,
+        inverse_index_map: impl Into<Option<ObjectRef>>,
     ) -> Self {
-        let obj = IndexMapObj::new(initial_indices, final_indices, 
inverse_index_map);
+        let obj = IndexMapObj::new(
+            initial_indices.into(),
+            final_indices.into(),
+            inverse_index_map.into(),
+        );
         Self { base: ObjectArc::new(obj) }
     }
 }
diff --git a/rust/src/tirx/layout.rs b/rust/src/tirx/layout.rs
index d72490ef..5debdc77 100644
--- a/rust/src/tirx/layout.rs
+++ b/rust/src/tirx/layout.rs
@@ -156,8 +156,8 @@ impl IterObj {
 
 impl Iter {
     /// Lossless complete-field allocation.
-    pub fn new(extent: Expr, stride: Expr, axis: Axis) -> Self {
-        let obj = IterObj::new(extent, stride, axis);
+    pub fn new(extent: impl Into<Expr>, stride: impl Into<Expr>, axis: impl 
Into<Axis>) -> Self {
+        let obj = IterObj::new(extent.into(), stride.into(), axis.into());
         Self { base: ObjectArc::new(obj) }
     }
 }
diff --git a/rust/src/tirx/op.rs b/rust/src/tirx/op.rs
index 3e910e04..3fd11a44 100644
--- a/rust/src/tirx/op.rs
+++ b/rust/src/tirx/op.rs
@@ -100,13 +100,17 @@ impl CallFFIKernelAttrObj {
 impl CallFFIKernelAttr {
     /// Lossless complete-field allocation.
     pub fn new(
-        launch_params: Array<String>,
-        launch_fields: Array<String>,
+        launch_params: impl Into<Array<String>>,
+        launch_fields: impl Into<Array<String>>,
         num_kernel_args: i64,
-        kernel_attrs: Map<String, i64>,
+        kernel_attrs: impl Into<Map<String, i64>>,
     ) -> Self {
-        let obj =
-            CallFFIKernelAttrObj::new(launch_params, launch_fields, 
num_kernel_args, kernel_attrs);
+        let obj = CallFFIKernelAttrObj::new(
+            launch_params.into(),
+            launch_fields.into(),
+            num_kernel_args,
+            kernel_attrs.into(),
+        );
         Self { base: ObjectArc::new(obj) }
     }
 }
diff --git a/rust/src/tirx/tensor.rs b/rust/src/tirx/tensor.rs
index ed1d56b7..d7d5b7d9 100644
--- a/rust/src/tirx/tensor.rs
+++ b/rust/src/tirx/tensor.rs
@@ -25,7 +25,7 @@
 //! [`TensorType::new`].
 
 // `custom-new` reserves `new` for a constructor with the C++ one's semantics 
(defaults); the
-// generated `from_complete_fields` allocates the fields as given.
+// generated `from_fields` allocates the fields as given.
 // tvm-ffi-stubgen(custom-new): tirx.TensorType
 
 // Names the hand-written wrappers use: the import section keeps them even 
when no generated block
@@ -112,15 +112,22 @@ impl TensorTypeObj {
 
 impl TensorType {
     /// Lossless complete-field allocation.
-    pub fn from_complete_fields(
-        loc: Location,
-        dtype: PrimType,
-        scope: MemoryScope,
-        shape: Array<Expr>,
+    pub fn from_fields(
+        loc: impl Into<Location>,
+        dtype: impl Into<PrimType>,
+        scope: impl Into<MemoryScope>,
+        shape: impl Into<Array<Expr>>,
         data_alignment: i32,
-        layout: Option<Layout>,
+        layout: impl Into<Option<Layout>>,
     ) -> Self {
-        let obj = TensorTypeObj::new(loc, dtype, scope, shape, data_alignment, 
layout);
+        let obj = TensorTypeObj::new(
+            loc.into(),
+            dtype.into(),
+            scope.into(),
+            shape.into(),
+            data_alignment,
+            layout.into(),
+        );
         Self { base: ObjectArc::new(obj) }
     }
 }
@@ -166,7 +173,7 @@ impl TensorType {
             true => alloc_alignment()?,
             false => data_alignment,
         };
-        Ok(Self::from_complete_fields(
+        Ok(Self::from_fields(
             loc.into(),
             dtype.into(),
             scope.into(),
diff --git a/rust/src/tirx/tile/instruction.rs 
b/rust/src/tirx/tile/instruction.rs
index 8db80ad5..8a0bd9ff 100644
--- a/rust/src/tirx/tile/instruction.rs
+++ b/rust/src/tirx/tile/instruction.rs
@@ -97,8 +97,8 @@ impl ScopeAttrsObj {
 
 impl ScopeAttrs {
     /// Lossless complete-field allocation.
-    pub fn new(scope: ThreadScope) -> Self {
-        let obj = ScopeAttrsObj::new(scope);
+    pub fn new(scope: impl Into<ThreadScope>) -> Self {
+        let obj = ScopeAttrsObj::new(scope.into());
         Self { base: ObjectArc::new(obj) }
     }
 }
@@ -170,24 +170,24 @@ impl TrnAttrsObj {
 impl TrnAttrs {
     /// Lossless complete-field allocation.
     pub fn new(
-        scope: ThreadScope,
-        max_inst_size: Optional<i64>,
-        opcode: String,
-        reduce_op: String,
-        op0: String,
-        op1: String,
-        axes: Array<i64>,
+        scope: impl Into<ThreadScope>,
+        max_inst_size: impl Into<Optional<i64>>,
+        opcode: impl Into<String>,
+        reduce_op: impl Into<String>,
+        op0: impl Into<String>,
+        op1: impl Into<String>,
+        axes: impl Into<Array<i64>>,
         negate: bool,
         reverse1: bool,
     ) -> Self {
         let obj = TrnAttrsObj::new(
-            scope,
-            max_inst_size,
-            opcode,
-            reduce_op,
-            op0,
-            op1,
-            axes,
+            scope.into(),
+            max_inst_size.into(),
+            opcode.into(),
+            reduce_op.into(),
+            op0.into(),
+            op1.into(),
+            axes.into(),
             negate,
             reverse1,
         );
diff --git a/rust/src/tirx/type.rs b/rust/src/tirx/type.rs
index f2a05686..a874198c 100644
--- a/rust/src/tirx/type.rs
+++ b/rust/src/tirx/type.rs
@@ -75,8 +75,8 @@ impl TensorMapTypeObj {
 
 impl TensorMapType {
     /// Lossless complete-field allocation.
-    pub fn new(loc: Location) -> Self {
-        let obj = TensorMapTypeObj::new(loc);
+    pub fn new(loc: impl Into<Location>) -> Self {
+        let obj = TensorMapTypeObj::new(loc.into());
         Self { base: ObjectArc::new(obj) }
     }
 }
diff --git a/rust/tests/ir/transform.rs b/rust/tests/ir/transform.rs
index d115599a..964ae6ca 100644
--- a/rust/tests/ir/transform.rs
+++ b/rust/tests/ir/transform.rs
@@ -41,6 +41,6 @@ fn logging_pass(name: &str, log: &Log) -> ModulePass {
             log.lock().unwrap().push(entry.clone());
             Ok(module)
         },
-        PassInfo::new(0, name.into()),
+        PassInfo::new(0, name),
     )
 }
diff --git a/rust/tests/ir/transform/core.rs b/rust/tests/ir/transform/core.rs
index b6ef7b9f..496c4b89 100644
--- a/rust/tests/ir/transform/core.rs
+++ b/rust/tests/ir/transform/core.rs
@@ -67,7 +67,7 @@ fn module_and_sequential_passes() {
         |_module, _ctx| {
             Err(tvm_tirx::tvm_ffi::Error::new(tvm_tirx::tvm_ffi::VALUE_ERROR, 
"refused", ""))
         },
-        PassInfo::new(0, "failing".into()),
+        PassInfo::new(0, "failing"),
     );
     assert!(Pass::from(&failing).run(empty_module()).is_err());
 
diff --git a/rust/tests/ir/type.rs b/rust/tests/ir/type.rs
index f1f4b124..0ef08433 100644
--- a/rust/tests/ir/type.rs
+++ b/rust/tests/ir/type.rs
@@ -17,16 +17,26 @@
  * under the License.
  */
 
-//! Tests of `ir::type`.
+//! Tests of `ir::type`, and of an `ir.Type` defined in Rust with `rs_class` 
over the generated
+//! `TypeObj`: seen through the FFI type table, the C++ engines (structural 
equality and hashing,
+//! deep copy, `repr`, TVMScript), the Rust engines and the IR that uses it.
+
+use std::mem::offset_of;
 
 use tvm_tirx::ir::{
-    UNKNOWN_LOC, Location, dtype, FuncType, MissingType, NoneType, PrimType, 
PtrType,
-    TensorRegionType, TupleType, Type,
+    UNKNOWN_LOC, Location, dtype, Evaluate, FuncType, MissingType, NoneType, 
PrimType, PtrType,
+    TensorRegionType, TupleType, Type, TypeObj, Var, VarObj,
+};
+use tvm_tirx::tvm_ffi::{self, def_object_ref, rs_class, structural_map};
+use tvm_tirx::tvm_ffi::{
+    Any, AnyView, Array, DLDataType, DLDataTypeExt, DefRegionKind, 
FieldGetter, MapDispatch,
+    ObjectCore, ObjectRefCore, Result, ShallowCopy, String as FfiString, 
StructuralView, WalkOrder,
 };
-use tvm_tirx::tvm_ffi::{Array, DLDataType, DLDataTypeExt, ObjectRefCore};
 
 use super::{scope};
-use crate::common::{agree, native, serial, structural_equal};
+use crate::common::{
+    agree, assert_type_contract, global, native, runtime_type_info, serial, 
structural_equal,
+};
 
 #[test]
 fn types() {
@@ -104,3 +114,217 @@ fn types_agree_with_cpp() {
     let region: Type = native("ir.TensorRegionType", 
&[]).unwrap().try_into().unwrap();
     assert!(TensorRegionType::new().same_as(&region));
 }
+
+/// A pair of types with a tag, defined in Rust.
+#[rs_class("testing.rust.PairType", final)]
+pub struct PairTypeNode {
+    base: TypeObj,
+    /// The first element.
+    pub lhs: Type,
+    /// The second element.
+    pub rhs: Type,
+    pub tag: i64,
+    /// Not part of structural equality or hashing.
+    #[reflect(seq_hash_ignore)]
+    pub note: FfiString,
+}
+
+def_object_ref!(pub PairType, Type, PairTypeNode);
+
+impl PairType {
+    /// A copy with the fields `edit` assigns replaced (C++ `CopyOnWrite()`).
+    fn with(&self, edit: impl FnOnce(&mut PairTypeNode)) -> Self {
+        let mut node = self.shallow_copy();
+        edit(&mut node);
+        node.into()
+    }
+}
+
+fn pair(lhs: Type, rhs: Type, tag: i64, note: &str) -> PairType {
+    PairTypeNode::new(TypeObj::new(Location::unknown()), lhs, rhs, tag, 
note).into()
+}
+
+fn int_float_pair(tag: i64, note: &str) -> PairType {
+    pair(PrimType::int(32).into(), PrimType::float(32).into(), tag, note)
+}
+
+/// How C++ prints a node (`ffi.ReprPrint`).
+fn repr<T>(value: &T) -> String
+where
+    for<'a> AnyView<'a>: From<&'a T>,
+{
+    let text = 
global("ffi.ReprPrint").call_packed(&[AnyView::from(value)]).unwrap();
+    FfiString::try_from(text).unwrap().as_str().to_string()
+}
+
+/// C++ `ffi::StructuralHash()(value)`.
+fn structural_hash<T>(value: &T) -> Result<i64>
+where
+    for<'a> AnyView<'a>: From<&'a T>,
+{
+    let flag = <AnyView as From<&bool>>::from;
+    global("ffi.StructuralHash")
+        .call_packed(&[AnyView::from(value), flag(&false), flag(&false)])?
+        .try_into()
+}
+
+#[test]
+fn rust_type_registers_under_ir_type_with_its_fields_reflected() {
+    let _serial = serial();
+    // The first use registers the type and its reflection.
+    assert_type_contract::<PairTypeNode, TypeObj>(true);
+
+    let info = runtime_type_info::<PairTypeNode>();
+    let metadata = unsafe { &*info.metadata };
+    assert_eq!(metadata.structural_eq_hash_kind, 1, "TreeNode, as every 
ir.Type");
+    let fields: Vec<(String, i64, i64)> = (0..info.num_fields as usize)
+        .map(|index| {
+            let field = unsafe { &*info.fields.add(index) };
+            (field.name.as_str().to_string(), field.offset, field.flags)
+        })
+        .collect();
+    assert_eq!(
+        fields,
+        vec![
+            ("lhs".to_string(), offset_of!(PairTypeNode, lhs) as i64, 0),
+            ("rhs".to_string(), offset_of!(PairTypeNode, rhs) as i64, 0),
+            ("tag".to_string(), offset_of!(PairTypeNode, tag) as i64, 0),
+            ("note".to_string(), offset_of!(PairTypeNode, note) as i64, 1 << 
3),
+        ]
+    );
+}
+
+#[test]
+fn rust_type_reflection_reads_own_and_inherited_fields() {
+    let _serial = serial();
+    let value = int_float_pair(7, "seven");
+    let get = |field: &str| -> Any {
+        FieldGetter::new(PairTypeNode::type_index(), 
field).unwrap().get_any(&*value).unwrap()
+    };
+    assert!(Type::try_from(get("lhs")).unwrap().same_as(&value.lhs));
+    assert!(Type::try_from(get("rhs")).unwrap().same_as(&value.rhs));
+    assert_eq!(i64::try_from(get("tag")).unwrap(), 7);
+    assert_eq!(FfiString::try_from(get("note")).unwrap().as_str(), "seven");
+    // `loc` belongs to the parent `ir.Type`.
+    assert!(Location::try_from(get("loc")).unwrap().same_as(&value.loc));
+}
+
+#[test]
+fn rust_type_cpp_structural_equal_and_hash_walk_the_fields() {
+    let _serial = serial();
+    let a = int_float_pair(7, "a");
+    let b = int_float_pair(7, "b");
+    let c = int_float_pair(8, "a");
+    assert!(structural_equal(&a, &b), "the note is ignored");
+    assert!(!structural_equal(&a, &c));
+    assert_eq!(structural_hash(&a).unwrap(), structural_hash(&b).unwrap());
+    assert_ne!(structural_hash(&a).unwrap(), structural_hash(&c).unwrap());
+
+    // Nested in itself, as any type.
+    let int32: Type = PrimType::int(32).into();
+    let outer_a = pair(a.clone().into(), int32.clone(), 0, "");
+    let outer_b = pair(b.into(), int32.clone(), 0, "");
+    let outer_c = pair(c.into(), int32, 0, "");
+    assert!(structural_equal(&outer_a, &outer_b));
+    assert!(!structural_equal(&outer_a, &outer_c));
+    assert_eq!(structural_hash(&outer_a).unwrap(), 
structural_hash(&outer_b).unwrap());
+}
+
+#[test]
+fn rust_type_cpp_repr_and_tvmscript_print_the_node() {
+    let _serial = serial();
+    let value = int_float_pair(7, "seven");
+    let text = repr(&value);
+    assert!(text.contains("testing.rust.PairType"), "{text}");
+    assert!(text.contains("tag=7"), "{text}");
+    assert!(text.contains("seven"), "{text}");
+
+    let script = global("script.printer.Script")
+        .call_packed(&[AnyView::from(&value), AnyView::new()])
+        .unwrap();
+    let script = FfiString::try_from(script).unwrap();
+    assert!(script.as_str().contains("testing.rust.Pair("), "{}", 
script.as_str());
+    assert!(script.as_str().contains("\"seven\""), "{}", script.as_str());
+}
+
+#[test]
+fn rust_type_cpp_deep_copy_goes_through_the_shallow_copy_and_the_setters() {
+    let _serial = serial();
+    let value = int_float_pair(7, "seven");
+    let copy = 
global("ffi.DeepCopy").call_packed(&[AnyView::from(&value)]).unwrap();
+    let copy = PairType::try_from(copy).unwrap();
+    assert!(!copy.same_as(&value));
+    assert!(structural_equal(&copy, &value));
+    assert_eq!(copy.tag, 7);
+    assert_eq!(copy.note.as_str(), "seven");
+}
+
+/// Adds one to every integer leaf.
+struct IncrementIntegers;
+
+impl MapDispatch for IncrementIntegers {
+    fn dispatch_map(&mut self, value: &StructuralView, _: DefRegionKind) -> 
Option<Result<Any>> {
+        value.cast::<i64>().map(|integer| Ok(Any::from(integer + 1)))
+    }
+}
+
+#[test]
+fn rust_type_rust_engines_descend_through_the_fields() {
+    let _serial = serial();
+    let value = int_float_pair(7, "seven");
+    // Shared with `value`, so the engine copies the node through 
`__ffi_shallow_copy__`.
+    let mapped =
+        structural_map(value.clone(), &mut IncrementIntegers, 
WalkOrder::PostOrder).unwrap();
+    let mapped = PairType::try_from(mapped).unwrap();
+    assert!(!mapped.same_as(&value));
+    assert_eq!(mapped.tag, 8);
+    assert_eq!(mapped.note.as_str(), "seven");
+    assert!(mapped.lhs.same_as(&value.lhs));
+    assert_eq!(value.tag, 7);
+}
+
+#[test]
+fn rust_type_with_is_copy_on_write() {
+    let _serial = serial();
+    let value = int_float_pair(7, "seven");
+    let edited = value.with(|node| node.tag = 9);
+    assert!(!edited.same_as(&value));
+    assert_eq!(edited.tag, 9);
+    assert_eq!(value.tag, 7);
+    assert!(edited.lhs.same_as(&value.lhs));
+    assert!(edited.rhs.same_as(&value.rhs));
+    // The generated `ShallowCopy` of `TypeObj` keeps the location.
+    assert!(edited.loc.same_as(&value.loc));
+    assert_eq!(edited.note.as_str(), "seven");
+}
+
+#[test]
+fn rust_type_converts_through_any_and_the_type_hierarchy() {
+    let _serial = serial();
+    let value = int_float_pair(1, "");
+    let as_type: Type = value.clone().into();
+    assert!(as_type.as_node::<PairTypeNode>().is_some());
+    let any = Any::from(as_type);
+    assert_eq!(any.type_index(), PairTypeNode::type_index());
+    let back = PairType::try_from(any).unwrap();
+    assert!(back.same_as(&value));
+}
+
+#[test]
+fn rust_type_of_a_variable_takes_part_in_ir() {
+    let _serial = serial();
+    // The type of a variable, as `PrimType` or `TupleType` is one.
+    let x = Var::new("x", int_float_pair(7, "seven"), UNKNOWN_LOC);
+    assert_eq!(x.ty.as_node::<PairTypeNode>().unwrap().tag, 7);
+    assert!(structural_equal(&x.ty, &int_float_pair(7, "other")));
+    assert!(!structural_equal(&x.ty, &int_float_pair(8, "seven")));
+
+    // Inside IR, which C++ deep-copies through the shallow copy and the 
setters.
+    let stmt = Evaluate::new(x.clone(), UNKNOWN_LOC);
+    let copy = 
global("ffi.DeepCopy").call_packed(&[AnyView::from(&stmt)]).unwrap();
+    let copy = Evaluate::try_from(copy).unwrap();
+    let copied = copy.value.as_node::<VarObj>().unwrap();
+    let ty = copied.ty.as_node::<PairTypeNode>().unwrap();
+    assert_eq!((ty.tag, ty.note.as_str()), (7, "seven"));
+    assert!(structural_equal(&copied.ty, &x.ty));
+}
diff --git a/rust/tests/sym/int_set.rs b/rust/tests/sym/int_set.rs
index 4871f0cb..33719c6f 100644
--- a/rust/tests/sym/int_set.rs
+++ b/rust/tests/sym/int_set.rs
@@ -93,7 +93,7 @@ fn int_sets() {
 fn region_bounds() {
     let x = var("x");
     let var_dom = Map::from_iter([(x.clone(), range(0, 16))]);
-    let region = [Range::from_complete_fields(x.clone().into(), int32(1), 
Location::unknown())];
+    let region = [Range::from_fields(x.clone(), int32(1), 
Location::unknown())];
     let predicate = Expr::from(true);
 
     let lower = estimate_region_lower_bound(region.clone(), &var_dom, 
&predicate, None).unwrap();
diff --git a/rust/tests/sym/iter_affine_map.rs 
b/rust/tests/sym/iter_affine_map.rs
index 6362c72f..206dbeca 100644
--- a/rust/tests/sym/iter_affine_map.rs
+++ b/rust/tests/sym/iter_affine_map.rs
@@ -127,7 +127,7 @@ fn iter_maps() {
     let inverse = inverse_affine_iter_map([detected_index], [output]).unwrap();
     assert_eq!(inverse.len(), 2);
 
-    let mark = IterMark::new(x.clone().into(), int32(4));
+    let mark = IterMark::new(x.clone(), int32(4));
     let split = IterSplitExpr::new(mark, int32(1), int32(4), 
int32(1)).unwrap();
     assert!(analyzer.can_prove_equal(split.scale().unwrap(), 
int32(1)).unwrap());
     let sum = IterSumExpr::new([split], int32(0));
diff --git a/rust/tests/sym/main.rs b/rust/tests/sym/main.rs
index f636ab73..faed540a 100644
--- a/rust/tests/sym/main.rs
+++ b/rust/tests/sym/main.rs
@@ -50,7 +50,7 @@ fn var(name: &str) -> Var {
 }
 
 fn range(begin: i64, extent: i64) -> Range {
-    Range::from_complete_fields(int32(begin), int32(extent), 
Location::unknown())
+    Range::from_fields(int32(begin), int32(extent), Location::unknown())
 }
 
 struct Fixture {
diff --git a/rust/tests/tirx/transform/function_pass.rs 
b/rust/tests/tirx/transform/function_pass.rs
index 6f5dc871..7c0355a7 100644
--- a/rust/tests/tirx/transform/function_pass.rs
+++ b/rust/tests/tirx/transform/function_pass.rs
@@ -39,13 +39,13 @@ fn function_passes() {
             counter.fetch_add(1, Ordering::Relaxed);
             Ok(Some(func))
         },
-        PassInfo::new(0, "rust.Count".into()),
+        PassInfo::new(0, "rust.Count"),
     );
     let out = Pass::from(&counting).run(module_of("main", &func)).unwrap();
     assert_eq!(runs.load(Ordering::Relaxed), 1);
     assert!(out.contains("main").unwrap());
     let dropping =
-        FunctionPass::new(|_func, _module, _ctx| Ok(None), PassInfo::new(0, 
"rust.Drop".into()));
+        FunctionPass::new(|_func, _module, _ctx| Ok(None), PassInfo::new(0, 
"rust.Drop"));
     assert!(!Pass::from(&dropping)
         .run(module_of("main", &func))
         .unwrap()

Reply via email to