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

tqchen pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/tvm-ffi.git


The following commit(s) were added to refs/heads/main by this push:
     new 1a297689 [FFI][REFACTOR] Clarify DefRegionKind (#769)
1a297689 is described below

commit 1a2976896412110327500467977bcb112f426627
Author: Tianqi Chen <[email protected]>
AuthorDate: Tue Sep 8 13:38:03 2026 -0400

    [FFI][REFACTOR] Clarify DefRegionKind (#769)
    
    Rename the two def-region kinds from Recursive / NonRecursive to Pattern
    /
    Simple, with the matching field-flag bits and the Python, Rust and doc
    mirrors: a pattern def region matches the value's type as a pattern,
    binding
    its free variables; a simple def region defines the variable alone and
    walks
    its type as uses. The comments now state the binding rule and the clamp
    that
    custom hooks on FreeVar types must apply.
    
    A pattern region propagates: entering any kind inside it has no effect,
    so a
    simple def nested under a pattern (a pattern field, or map_free_vars at
    the
    top level) still binds the free variables in its type. Applied in the
    equal,
    hash, visit and mutate engines and in the Rust visitor and mutator, with
    a
    test for the nested case.
    
    Python keeps "def" as the only alias of "def-pattern".
    
    No ABI changes involved: enum values and flag bit positions are
    unchanged.
---
 docs/concepts/structural_eq_hash.rst               | 82 ++++++++++------------
 include/tvm/ffi/c_api.h                            | 40 ++++-------
 include/tvm/ffi/extra/structural_mutate.h          | 22 ++++--
 include/tvm/ffi/extra/structural_visit.h           | 28 +++++---
 include/tvm/ffi/reflection/registry.h              | 28 ++++----
 python/tvm_ffi/cython/base.pxi                     |  8 +--
 python/tvm_ffi/cython/object.pxi                   |  8 +--
 python/tvm_ffi/cython/type_info.pxi                | 12 ++--
 python/tvm_ffi/dataclasses/field.py                | 40 +++++------
 python/tvm_ffi/structural.py                       |  4 +-
 python/tvm_ffi/stub/utils.py                       |  2 +-
 rust/tvm-ffi-sys/src/c_api.rs                      |  8 +--
 rust/tvm-ffi/src/extra/structural_mutate.rs        |  8 ++-
 rust/tvm-ffi/src/extra/structural_visit.rs         | 47 +++++++------
 rust/tvm-ffi/tests/test_structural_visit.rs        | 12 ++--
 .../tests/test_structural_visitor_alignment.rs     |  8 +--
 src/ffi/extra/structural_equal.cc                  | 33 +++++----
 src/ffi/extra/structural_hash.cc                   | 27 ++++---
 tests/cpp/extra/test_structural_equal_hash.cc      | 16 ++++-
 tests/cpp/extra/test_structural_visit.cc           | 28 ++++----
 tests/cpp/testing_object.h                         | 10 +--
 21 files changed, 251 insertions(+), 220 deletions(-)

diff --git a/docs/concepts/structural_eq_hash.rst 
b/docs/concepts/structural_eq_hash.rst
index 51335645..b013f11b 100644
--- a/docs/concepts/structural_eq_hash.rst
+++ b/docs/concepts/structural_eq_hash.rst
@@ -635,14 +635,14 @@ Use for:
   redundant to compare.
 - **Debug annotations** — names, comments, metadata for human consumption.
 
-``structural_eq="def-recursive"`` / ``"def-non-recursive"`` — Definition region
+``structural_eq="def-pattern"`` / ``"def-simple"`` — Definition region
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
 .. code-block:: python
 
    @py_class(structural_eq="tree")
    class Lambda(Object):
-       params: list[Var] = field(structural_eq="def-recursive")
+       params: list[Var] = field(structural_eq="def-pattern")
        body: Expr
 
 **Meaning**: "This field introduces new variable bindings. When comparing
@@ -654,40 +654,36 @@ variable"; the ``"def-*"`` flags on a field say "this 
field is where
 variables are defined." Together they enable alpha-equivalence:
 comparing functions up to consistent variable renaming.
 
-There are two flavors of definition region, distinguished by what
-happens when a ``"var"`` reached through the field carries its own
-sub-fields (for example, a shape annotation in the var's type):
-
-- ``"def-recursive"`` (alias: ``"def"``) — the variable's sub-fields
-  stay inside the definition region. Any free variables encountered
-  in those sub-fields are themselves treated as fresh definitions at
-  the same site. One example is **function parameter lists**, where
-  the value var and any shape parameters in its type are co-introduced
-  together at the function boundary.
-
-- ``"def-non-recursive"`` — only the immediate variable(s) reached
-  through the field bind. The variable's sub-fields are walked
-  outside the definition region, so any free variables there are
-  *use* references that must resolve against an outer-scope binding.
-  One example is a **normal binding** whose value type references
-  outer-scope shape parameters (a ``let v = expr`` where ``v``'s
-  type refers to vars defined earlier).
-
-When the distinction does not matter (no nested free vars under the
-bound variable), either flavor works and ``"def-recursive"`` is the
-conventional default — that's why the bare ``"def"`` alias resolves
-to it.
+There are two kinds of definition region, distinguished by how the
+bound variable's type is treated:
+
+- ``"def-pattern"`` (alias: ``"def"``) — the variable's type is matched
+  as a pattern. The variable and every free variable in its type bind on
+  first occurrence and must match on later ones. Example: **function
+  parameter lists**, where ``x: Tensor([n, m])`` introduces ``x``, ``n``
+  and ``m`` together.
+
+- ``"def-simple"`` — the variable alone is defined. Its type is walked as
+  uses, so variables appearing in it must already be bound. Example: a
+  **normal binding** ``let v = expr`` whose type refers to vars defined
+  earlier.
+
+A pattern region propagates: a ``"def-simple"`` field reached inside a
+pattern region (or under ``map_free_vars``) behaves as a pattern, since
+the enclosing pattern already binds every free variable. When the
+distinction does not matter (no free vars in the bound variable's type),
+either kind works and ``"def-pattern"`` is the conventional default —
+that's why the bare ``"def"`` alias resolves to it.
 
 Use for:
 
-- **Function parameter lists** — ``"def-recursive"`` so shape
-  parameters in each param's type co-introduce at the same site.
+- **Function parameter lists** — ``"def-pattern"``, so the shape
+  variables in each parameter's type are introduced with it.
 - **Normal binding left-hand sides** (let bindings, for-loop
-  iterators) whose value type references outer-scope vars —
-  ``"def-non-recursive"`` so those references don't rebind.
-- **Any field that introduces names into scope** — pick the flavor
-  that matches the binding form's contract; default to
-  ``"def-recursive"`` when in doubt.
+  iterators) whose type refers to outer-scope vars — ``"def-simple"``,
+  so those references stay uses.
+- **Any field that introduces names into scope** — pick the kind that
+  matches the binding form; default to ``"def-pattern"`` when in doubt.
 
 
 .. _sequal-shash:
@@ -740,16 +736,16 @@ field-level ``"def-*"`` flags and controls whether the 
sub-value is
 compared/hashed inside a definition region:
 
 - ``0`` — not in a def region (matches ``None`` on a field).
-- ``1`` — recursive def region (matches ``"def-recursive"``, alias
+- ``1`` — pattern def region (matches ``"def-pattern"``, alias
   ``"def"``).
-- ``2`` — non-recursive def region (matches ``"def-non-recursive"``).
+- ``2`` — simple def region (matches ``"def-simple"``).
 
 For back-compat with the original single-flag API, the callback also
-accepts a plain ``bool``: ``True`` is treated as ``1`` (recursive) and
+accepts a plain ``bool``: ``True`` is treated as ``1`` (pattern) and
 ``False`` as ``0`` (not in a def region). The Python examples below
 use ``True`` / ``False`` for that reason; pass an explicit ``2`` (or
-the ``kTVMFFIDefRegionKindNonRecursive`` enum value from C++) when the
-non-recursive kind is needed.
+the ``kTVMFFIDefRegionKindSimple`` enum value from C++) when the
+simple kind is needed.
 
 The ``field_name`` argument on ``eq_cb`` is used only for mismatch path
 reporting from :py:func:`~tvm_ffi.get_first_structural_mismatch`.
@@ -1191,14 +1187,14 @@ Callbacks passed through ``with_def_region_kind`` 
receive
 ``(value, def_region_kind)``.  The kind is one of:
 
 - ``DefRegionKind.NONE`` for an ordinary use.
-- ``DefRegionKind.DEF_RECURSIVE`` for a recursive definition region.
-- ``DefRegionKind.DEF_NON_RECURSIVE`` for a non-recursive definition.
+- ``DefRegionKind.DEF_PATTERN`` for a pattern definition region.
+- ``DefRegionKind.DEF_SIMPLE`` for a simple definition.
 
 The field annotations described earlier in this document establish these
-regions.  Recursive definitions propagate the definition mode into the defined
-value's children.  A non-recursive definition applies to the FreeVar identity
-itself, while its unannotated children are treated as ordinary uses.  Explicit
-nested definition annotations establish their own region.
+regions.  A pattern definition matches the defined value's type as a pattern,
+binding the free variables found there, and propagates: kinds entered inside
+it have no effect.  A simple definition applies to the FreeVar itself, while
+its type is walked as ordinary uses.
 
 .. code-block:: python
 
diff --git a/include/tvm/ffi/c_api.h b/include/tvm/ffi/c_api.h
index 21092cbe..019bd665 100644
--- a/include/tvm/ffi/c_api.h
+++ b/include/tvm/ffi/c_api.h
@@ -974,13 +974,13 @@ typedef enum {
    */
   kTVMFFIFieldFlagBitMaskSEqHashIgnore = 1 << 3,
   /*!
-   * \brief The field enters a recursive def region.
+   * \brief The field enters a pattern def region.
    *
    * This is an optional meta-data for structural eq/hash.
    *
    * \sa TVMFFIDefRegionKind for the def-region semantics.
    */
-  kTVMFFIFieldFlagBitMaskSEqHashDefRecursive = 1 << 4,
+  kTVMFFIFieldFlagBitMaskSEqHashDefPattern = 1 << 4,
   /*!
    * \brief The default_value_or_factory is a callable factory function () -> 
Any.
    *
@@ -1041,7 +1041,7 @@ typedef enum {
    */
   kTVMFFIFieldFlagBitSetterIsFunctionObj = 1 << 11,
   /*!
-   * \brief The field enters a non-recursive def region.
+   * \brief The field enters a simple def region.
    *
    * This is an optional meta-data for structural eq/hash.
    *
@@ -1050,7 +1050,7 @@ typedef enum {
    * \note Bit 1 << 12 is used here because bits 1 << 5 .. 1 << 11 are
    *       already taken by other field flags above.
    */
-  kTVMFFIFieldFlagBitMaskSEqHashDefNonRecursive = 1 << 12,
+  kTVMFFIFieldFlagBitMaskSEqHashDefSimple = 1 << 12,
 #ifdef __cplusplus
 };
 #else
@@ -1140,33 +1140,21 @@ typedef enum {
    */
   kTVMFFIDefRegionKindNone = 0,
   /*!
-   * \brief In a recursive def region.
+   * \brief A pattern def region: the value's type is matched as a pattern.
    *
-   * When we see a free var for the first time, we define the var, and
-   * the sub-fields of the var (e.g. its struct_info / type_annotation /
-   * shape) are also still in the def region — any free vars discovered
-   * inside those sub-fields are themselves treated as fresh defs at the
-   * same site.
-   *
-   * One example is function parameter lists: the value var and any
-   * shape parameters in its type are co-introduced at the same binding
-   * site.
+   * The descent recurses through the type. The value variable and every free
+   * variable in the type bind on first occurrence and must match on later 
ones.
+   * A pattern region propagates: kinds entered inside it have no effect.
    */
-  kTVMFFIDefRegionKindRecursive = 1,
+  kTVMFFIDefRegionKindPattern = 1,
   /*!
-   * \brief In a non-recursive def region.
-   *
-   * When we see a free var for the first time, we define the var, but
-   * the sub-fields of the var are NOT in the def region — they are
-   * treated as use references that must resolve against an outer
-   * binding. Free vars found in those sub-fields therefore do not
-   * rebind; if they are not already bound, equality fails.
+   * \brief A simple def region: the variable alone is defined here.
    *
-   * One example is a normal binding whose value type contains shape
-   * parameters: the value var is introduced fresh, but its shape
-   * parameters reference vars defined in an outer scope.
+   * Its type is walked as uses: variables appearing in the type must already
+   * be bound. Inside a pattern region this kind has no effect; the pattern
+   * propagates.
    */
-  kTVMFFIDefRegionKindNonRecursive = 2,
+  kTVMFFIDefRegionKindSimple = 2,
 #ifdef __cplusplus
 };
 #else
diff --git a/include/tvm/ffi/extra/structural_mutate.h 
b/include/tvm/ffi/extra/structural_mutate.h
index f53b678d..d6dac496 100644
--- a/include/tvm/ffi/extra/structural_mutate.h
+++ b/include/tvm/ffi/extra/structural_mutate.h
@@ -276,6 +276,9 @@ class StructuralMutatorObj : public Object {
   /*!
    * \brief Return the current def-region context.
    * \return The active def-region kind.
+   * \note A custom mutate hook for a FreeVar type must apply the simple-def 
clamp itself: when
+   *       this is kTVMFFIDefRegionKindSimple, descend the variable's type 
under
+   *       kTVMFFIDefRegionKindNone. The reflected walk does this on its own.
    */
   TVM_FFI_INLINE TVMFFIDefRegionKind def_region_kind() const { return 
def_region_mode_; }
 
@@ -285,10 +288,16 @@ class StructuralMutatorObj : public Object {
    * \param kind The def-region kind to set during the callback.
    * \param callback A nullary callable that performs recursive mutation.
    * \return The value returned by \p callback.
+   * \note Inside a pattern region this is a no-op: the pattern propagates, so 
\p kind is
+   *       ignored and the callback runs under the pattern.
    */
   template <typename Callback>
   TVM_FFI_INLINE auto WithDefRegionKind(TVMFFIDefRegionKind kind, Callback&& 
callback)
       -> decltype(std::forward<Callback>(callback)()) {
+    // Precedence: a pattern region propagates; entering any kind inside it 
has no effect.
+    if (def_region_mode_ == kTVMFFIDefRegionKindPattern) {
+      return std::forward<Callback>(callback)();
+    }
     class Scope {
      public:
       Scope(StructuralMutatorObj* mutator, TVMFFIDefRegionKind kind)
@@ -538,12 +547,12 @@ TVM_FFI_INLINE static Expected<Any> 
MutateReflectedFieldsExpected(StructuralMuta
           }
 
           Expected<Any> mutated_field = [&]() -> Expected<Any> {
-            if (field_info->flags & 
kTVMFFIFieldFlagBitMaskSEqHashDefNonRecursive) {
-              return 
mutator->WithDefRegionKind(kTVMFFIDefRegionKindNonRecursive, [&]() {
+            if (field_info->flags & kTVMFFIFieldFlagBitMaskSEqHashDefSimple) {
+              return mutator->WithDefRegionKind(kTVMFFIDefRegionKindSimple, 
[&]() {
                 return mutator->MutateExpected(field_value);
               });
-            } else if (field_info->flags & 
kTVMFFIFieldFlagBitMaskSEqHashDefRecursive) {
-              return mutator->WithDefRegionKind(kTVMFFIDefRegionKindRecursive, 
[&]() {
+            } else if (field_info->flags & 
kTVMFFIFieldFlagBitMaskSEqHashDefPattern) {
+              return mutator->WithDefRegionKind(kTVMFFIDefRegionKindPattern, 
[&]() {
                 return mutator->MutateExpected(field_value);
               });
             } else {
@@ -581,10 +590,9 @@ TVM_FFI_INLINE static Expected<Any> 
MutateReflectedFieldsExpected(StructuralMuta
         });
   };
 
-  // A non-recursive definition applies to the FreeVar itself, but its fields 
are uses. The
+  // A simple definition applies to the FreeVar itself, but its fields are 
uses. The
   // complete field traversal are clamped to None, then the definition region 
is restored.
-  if (mutator->def_region_kind() == kTVMFFIDefRegionKindNonRecursive &&
-      type_info->metadata != nullptr &&
+  if (mutator->def_region_kind() == kTVMFFIDefRegionKindSimple && 
type_info->metadata != nullptr &&
       type_info->metadata->structural_eq_hash_kind == 
kTVMFFISEqHashKindFreeVar) {
     mutator->WithDefRegionKind(kTVMFFIDefRegionKindNone, mutate_fields);
   } else {
diff --git a/include/tvm/ffi/extra/structural_visit.h 
b/include/tvm/ffi/extra/structural_visit.h
index 2e6179c8..93fa6cb5 100644
--- a/include/tvm/ffi/extra/structural_visit.h
+++ b/include/tvm/ffi/extra/structural_visit.h
@@ -168,6 +168,9 @@ class StructuralVisitorObj : public Object {
   /*!
    * \brief Return the current def-region context.
    * \return The active def-region kind.
+   * \note A custom visit hook for a FreeVar type must apply the simple-def 
clamp itself: when
+   *       this is kTVMFFIDefRegionKindSimple, descend the variable's type 
under
+   *       kTVMFFIDefRegionKindNone. The reflected walk does this on its own.
    */
   TVM_FFI_INLINE TVMFFIDefRegionKind def_region_kind() const { return 
def_region_mode_; }
 
@@ -177,9 +180,15 @@ class StructuralVisitorObj : public Object {
    * \param kind The def-region kind to set during the callback.
    * \param callback A nullary callable that performs recursive visiting.
    * \return The value returned by \p callback.
+   * \note Inside a pattern region this is a no-op: the pattern propagates, so 
\p kind is
+   *       ignored and the callback runs under the pattern.
    */
   template <typename Callback>
   TVM_FFI_INLINE auto WithDefRegionKind(TVMFFIDefRegionKind kind, Callback&& 
callback) {
+    // Precedence: a pattern region propagates; entering any kind inside it 
has no effect.
+    if (def_region_mode_ == kTVMFFIDefRegionKindPattern) {
+      return std::forward<Callback>(callback)();
+    }
     class Scope {
      public:
       Scope(StructuralVisitorObj* visitor, TVMFFIDefRegionKind kind)
@@ -362,14 +371,12 @@ TVM_FFI_INLINE static Expected<Optional<VisitInterrupt>> 
VisitReflectedFieldsExp
             return true;
           }
 
-          if (field_info->flags & 
kTVMFFIFieldFlagBitMaskSEqHashDefNonRecursive) {
-            result = 
visitor->WithDefRegionKind(kTVMFFIDefRegionKindNonRecursive, [&]() {
-              return visitor->VisitExpected(field_value);
-            });
-          } else if (field_info->flags & 
kTVMFFIFieldFlagBitMaskSEqHashDefRecursive) {
-            result = visitor->WithDefRegionKind(kTVMFFIDefRegionKindRecursive, 
[&]() {
-              return visitor->VisitExpected(field_value);
-            });
+          if (field_info->flags & kTVMFFIFieldFlagBitMaskSEqHashDefSimple) {
+            result = visitor->WithDefRegionKind(
+                kTVMFFIDefRegionKindSimple, [&]() { return 
visitor->VisitExpected(field_value); });
+          } else if (field_info->flags & 
kTVMFFIFieldFlagBitMaskSEqHashDefPattern) {
+            result = visitor->WithDefRegionKind(
+                kTVMFFIDefRegionKindPattern, [&]() { return 
visitor->VisitExpected(field_value); });
           } else {
             result = visitor->VisitExpected(field_value);
           }
@@ -378,10 +385,9 @@ TVM_FFI_INLINE static Expected<Optional<VisitInterrupt>> 
VisitReflectedFieldsExp
     return result;
   };
 
-  // A non-recursive definition applies to the FreeVar itself, but its fields 
are uses. The
+  // A simple definition applies to the FreeVar itself, but its fields are 
uses. The
   // complete field traversal are clamped to None, then the definition region 
is restored.
-  if (visitor->def_region_kind() == kTVMFFIDefRegionKindNonRecursive &&
-      type_info->metadata != nullptr &&
+  if (visitor->def_region_kind() == kTVMFFIDefRegionKindSimple && 
type_info->metadata != nullptr &&
       type_info->metadata->structural_eq_hash_kind == 
kTVMFFISEqHashKindFreeVar) {
     return visitor->WithDefRegionKind(kTVMFFIDefRegionKindNone, visit_fields);
   }
diff --git a/include/tvm/ffi/reflection/registry.h 
b/include/tvm/ffi/reflection/registry.h
index 2d1b3a43..612e5a38 100644
--- a/include/tvm/ffi/reflection/registry.h
+++ b/include/tvm/ffi/reflection/registry.h
@@ -223,25 +223,27 @@ class AttachFieldFlag : public InfoTrait {
   explicit AttachFieldFlag(int32_t flag) : flag_(flag) {}
 
   /*!
-   * \brief Attach kTVMFFIFieldFlagBitMaskSEqHashDefRecursive
+   * \brief Attach kTVMFFIFieldFlagBitMaskSEqHashDefPattern
    *
-   * The field enters a recursive def region: free vars discovered both at
-   * the field's value and inside that value's sub-fields bind as fresh
-   * defs at the same site. Use for "function-style" bindings.
+   * The field enters a pattern def region: the value's type is matched as a
+   * pattern. The value variable and every free variable found in its type bind
+   * on first occurrence and must match on later ones. Use for function-style
+   * bindings such as parameter lists, where shape variables are introduced
+   * alongside the parameters.
    */
-  TVM_FFI_INLINE static AttachFieldFlag SEqHashDefRecursive() {
-    return AttachFieldFlag(kTVMFFIFieldFlagBitMaskSEqHashDefRecursive);
+  TVM_FFI_INLINE static AttachFieldFlag SEqHashDefPattern() {
+    return AttachFieldFlag(kTVMFFIFieldFlagBitMaskSEqHashDefPattern);
   }
   /*!
-   * \brief Attach kTVMFFIFieldFlagBitMaskSEqHashDefNonRecursive
+   * \brief Attach kTVMFFIFieldFlagBitMaskSEqHashDefSimple
    *
-   * The field enters a non-recursive def region: only the immediate free
-   * var at the field's value binds; free vars in its sub-fields are uses
-   * that must already be bound by an outer def region. Use for "let-style"
-   * bindings whose sub-fields reference outer-scope vars.
+   * The field enters a simple def region: the variable alone is defined, and
+   * its type is walked as uses, so variables appearing in the type must 
already
+   * be bound. Use for let-style bindings whose type refers to outer-scope
+   * variables. Entered inside a pattern region it stays a pattern region.
    */
-  TVM_FFI_INLINE static AttachFieldFlag SEqHashDefNonRecursive() {
-    return AttachFieldFlag(kTVMFFIFieldFlagBitMaskSEqHashDefNonRecursive);
+  TVM_FFI_INLINE static AttachFieldFlag SEqHashDefSimple() {
+    return AttachFieldFlag(kTVMFFIFieldFlagBitMaskSEqHashDefSimple);
   }
   /*!
    * \brief Attach kTVMFFIFieldFlagBitMaskSEqHashIgnore
diff --git a/python/tvm_ffi/cython/base.pxi b/python/tvm_ffi/cython/base.pxi
index 20b9306a..da727199 100644
--- a/python/tvm_ffi/cython/base.pxi
+++ b/python/tvm_ffi/cython/base.pxi
@@ -208,7 +208,7 @@ cdef extern from "tvm/ffi/c_api.h":
         kTVMFFIFieldFlagBitMaskHasDefault = 1 << 1
         kTVMFFIFieldFlagBitMaskIsStaticMethod = 1 << 2
         kTVMFFIFieldFlagBitMaskSEqHashIgnore = 1 << 3
-        kTVMFFIFieldFlagBitMaskSEqHashDefRecursive = 1 << 4
+        kTVMFFIFieldFlagBitMaskSEqHashDefPattern = 1 << 4
         kTVMFFIFieldFlagBitMaskDefaultFromFactory = 1 << 5
         kTVMFFIFieldFlagBitMaskReprOff = 1 << 6
         kTVMFFIFieldFlagBitMaskCompareOff = 1 << 7
@@ -216,7 +216,7 @@ cdef extern from "tvm/ffi/c_api.h":
         kTVMFFIFieldFlagBitMaskInitOff = 1 << 9
         kTVMFFIFieldFlagBitMaskKwOnly = 1 << 10
         kTVMFFIFieldFlagBitSetterIsFunctionObj = 1 << 11
-        kTVMFFIFieldFlagBitMaskSEqHashDefNonRecursive = 1 << 12
+        kTVMFFIFieldFlagBitMaskSEqHashDefSimple = 1 << 12
 
     ctypedef int (*TVMFFIFieldGetter)(void* field, TVMFFIAny* result) noexcept
     ctypedef int (*TVMFFIFieldSetter)(void* field, const TVMFFIAny* value) 
noexcept
@@ -252,8 +252,8 @@ cdef extern from "tvm/ffi/c_api.h":
 
     cdef enum TVMFFIDefRegionKind:
         kTVMFFIDefRegionKindNone = 0
-        kTVMFFIDefRegionKindRecursive = 1
-        kTVMFFIDefRegionKindNonRecursive = 2
+        kTVMFFIDefRegionKindPattern = 1
+        kTVMFFIDefRegionKindSimple = 2
 
     ctypedef struct TVMFFITypeMetadata:
         TVMFFIByteArray doc
diff --git a/python/tvm_ffi/cython/object.pxi b/python/tvm_ffi/cython/object.pxi
index 30e3ea90..3219158f 100644
--- a/python/tvm_ffi/cython/object.pxi
+++ b/python/tvm_ffi/cython/object.pxi
@@ -566,10 +566,10 @@ cdef _type_info_create_from_type_key(object type_cls, str 
type_key):
         # Decode SEqHashIgnore / SEqHashDef* into the Field.structural_eq 
vocabulary.
         if (field.flags & kTVMFFIFieldFlagBitMaskSEqHashIgnore) != 0:
             c_structural_eq = "ignore"
-        elif (field.flags & kTVMFFIFieldFlagBitMaskSEqHashDefRecursive) != 0:
-            c_structural_eq = "def-recursive"
-        elif (field.flags & kTVMFFIFieldFlagBitMaskSEqHashDefNonRecursive) != 
0:
-            c_structural_eq = "def-non-recursive"
+        elif (field.flags & kTVMFFIFieldFlagBitMaskSEqHashDefPattern) != 0:
+            c_structural_eq = "def-pattern"
+        elif (field.flags & kTVMFFIFieldFlagBitMaskSEqHashDefSimple) != 0:
+            c_structural_eq = "def-simple"
         else:
             c_structural_eq = None
         fields.append(
diff --git a/python/tvm_ffi/cython/type_info.pxi 
b/python/tvm_ffi/cython/type_info.pxi
index ffdb73ce..438ce6c7 100644
--- a/python/tvm_ffi/cython/type_info.pxi
+++ b/python/tvm_ffi/cython/type_info.pxi
@@ -1037,14 +1037,14 @@ cdef _register_one_field(
     cdef object field_structure = getattr(py_field, "structural_eq", None)
     if field_structure == "ignore":
         flags |= kTVMFFIFieldFlagBitMaskSEqHashIgnore
-    elif field_structure == "def" or field_structure == "def-recursive":
+    elif field_structure in ("def-pattern", "def"):
         # ``"def"`` is the legacy short form, kept as a Python-side synonym for
-        # ``"def-recursive"`` since the C-level rename of the underlying flag
-        # (``kTVMFFIFieldFlagBitMaskSEqHashDef`` -> ``...SEqHashDefRecursive``)
+        # ``"def-pattern"`` since the C-level rename of the underlying flag
+        # (``kTVMFFIFieldFlagBitMaskSEqHashDef`` -> ``...SEqHashDefPattern``)
         # only changed the constant name, not the recursive semantics.
-        flags |= kTVMFFIFieldFlagBitMaskSEqHashDefRecursive
-    elif field_structure == "def-non-recursive":
-        flags |= kTVMFFIFieldFlagBitMaskSEqHashDefNonRecursive
+        flags |= kTVMFFIFieldFlagBitMaskSEqHashDefPattern
+    elif field_structure == "def-simple":
+        flags |= kTVMFFIFieldFlagBitMaskSEqHashDefSimple
     info.flags = flags
 
     # --- native layout ---
diff --git a/python/tvm_ffi/dataclasses/field.py 
b/python/tvm_ffi/dataclasses/field.py
index 041b57fb..cb6f99e9 100644
--- a/python/tvm_ffi/dataclasses/field.py
+++ b/python/tvm_ffi/dataclasses/field.py
@@ -152,19 +152,15 @@ class Field:
           structural comparison and hashing.
         - ``"ignore"``: the field is excluded from structural equality
           and hashing entirely (e.g. source spans, caches).
-        - ``"def-recursive"`` (alias: ``"def"``): the field is a
-          **recursive definition region** that introduces new variable
-          bindings.  Free variables encountered anywhere in this field's
-          subtree (including inside the var's own sub-fields) are
-          mapped by position. One example is function parameter lists,
-          where the value var and any shape parameters in its type are
-          co-introduced at the same site.
-        - ``"def-non-recursive"``: the field is a **non-recursive
-          definition region**.  Only the immediate free var(s) at this
-          field's value bind; free vars inside their sub-fields must
-          resolve against an outer binding (use semantics). One example
-          is a normal binding whose value type contains shape
-          parameters that reference outer-scope vars.
+        - ``"def-pattern"`` (alias: ``"def"``): the field is a **pattern
+          definition region**: the bound variable's type is matched as a
+          pattern, and the variable and every free variable in its type
+          bind on first occurrence. Example: function parameter lists,
+          where ``x: Tensor([n, m])`` introduces ``x``, ``n`` and ``m``.
+        - ``"def-simple"``: the field is a **simple definition region**:
+          the variable alone is defined, and its type is walked as uses,
+          so variables appearing in it must already be bound. Inside a
+          pattern region this kind has no effect; the pattern propagates.
     doc : str | None
         Optional docstring for the field.
     converter : Callable[[Any], Any]
@@ -206,11 +202,11 @@ class Field:
 
     #: Valid values for the *structural_eq* parameter.
     #:
-    #: ``"def"`` is kept as a Python-side alias for ``"def-recursive"`` to
+    #: ``"def"`` is kept as an alias for ``"def-pattern"`` to
     #: preserve back-compat with code written against the old single-flag
     #: ``SEqHashDef`` API.
     _VALID_STRUCTURAL_EQ_VALUES: ClassVar[frozenset[str | None]] = frozenset(
-        {None, "ignore", "def", "def-recursive", "def-non-recursive"}
+        {None, "ignore", "def", "def-pattern", "def-simple"}
     )
 
     def __init__(  # noqa: PLR0913
@@ -313,12 +309,12 @@ def field(  # noqa: PLR0913
     structural_eq
         Structural equality/hashing annotation. ``None`` (default) means
         the field participates normally. ``"ignore"`` excludes the field
-        from structural comparison and hashing. ``"def-recursive"``
-        (alias ``"def"``) marks the field as a recursive definition
-        region: free vars in the field's whole subtree bind. 
``"def-non-recursive"``
-        marks it as a non-recursive definition region: only immediate
-        free vars bind; nested free vars must resolve against an outer
-        binding.
+        from structural comparison and hashing. ``"def-pattern"``
+        (alias ``"def"``) marks the field as a pattern definition
+        region: the bound variable's type is matched as a pattern and its
+        free vars bind. ``"def-simple"`` marks it as a simple definition
+        region: the variable alone is defined and its type is walked as
+        uses. A pattern region propagates over a nested simple one.
     doc
         Optional docstring for the field.
     converter
@@ -342,7 +338,7 @@ def field(  # noqa: PLR0913
 
         @py_class(structural_eq="tree")
         class MyFunc(Object):
-            params: Array = field(structural_eq="def")
+            params: Array = field(structural_eq="def-pattern")
             body: Expr
             span: Object = field(structural_eq="ignore")
 
diff --git a/python/tvm_ffi/structural.py b/python/tvm_ffi/structural.py
index 2618d0eb..a45a1979 100644
--- a/python/tvm_ffi/structural.py
+++ b/python/tvm_ffi/structural.py
@@ -100,8 +100,8 @@ class DefRegionKind(IntEnum):
     """
 
     NONE = 0
-    DEF_RECURSIVE = 1
-    DEF_NON_RECURSIVE = 2
+    DEF_PATTERN = 1
+    DEF_SIMPLE = 2
 
 
 def structural_equal(
diff --git a/python/tvm_ffi/stub/utils.py b/python/tvm_ffi/stub/utils.py
index ae22c275..d6b8bca9 100644
--- a/python/tvm_ffi/stub/utils.py
+++ b/python/tvm_ffi/stub/utils.py
@@ -104,7 +104,7 @@ class NamedTypeSchema(TypeSchema):
       none). ``default_is_factory`` marks a ``default_factory`` registration,
       whose value only exists by calling the factory through the FFI.
     - ``structural_eq``: the decoded structural-equality flag
-      (``"ignore"``, ``"def-recursive"``, ``"def-non-recursive"`` or ``None``).
+      (``"ignore"``, ``"def-pattern"``, ``"def-simple"`` or ``None``).
     - ``frozen``: ``True`` for read-only (``def_ro``) fields.
     """
 
diff --git a/rust/tvm-ffi-sys/src/c_api.rs b/rust/tvm-ffi-sys/src/c_api.rs
index 203446c8..50ace68c 100644
--- a/rust/tvm-ffi-sys/src/c_api.rs
+++ b/rust/tvm-ffi-sys/src/c_api.rs
@@ -109,7 +109,7 @@ pub enum TVMFFIFieldFlagBitMask {
     kTVMFFIFieldFlagBitMaskHasDefault = 1 << 1,
     kTVMFFIFieldFlagBitMaskIsStaticMethod = 1 << 2,
     kTVMFFIFieldFlagBitMaskSEqHashIgnore = 1 << 3,
-    kTVMFFIFieldFlagBitMaskSEqHashDefRecursive = 1 << 4,
+    kTVMFFIFieldFlagBitMaskSEqHashDefPattern = 1 << 4,
     kTVMFFIFieldFlagBitMaskDefaultFromFactory = 1 << 5,
     kTVMFFIFieldFlagBitMaskReprOff = 1 << 6,
     kTVMFFIFieldFlagBitMaskCompareOff = 1 << 7,
@@ -117,7 +117,7 @@ pub enum TVMFFIFieldFlagBitMask {
     kTVMFFIFieldFlagBitMaskInitOff = 1 << 9,
     kTVMFFIFieldFlagBitMaskKwOnly = 1 << 10,
     kTVMFFIFieldFlagBitSetterIsFunctionObj = 1 << 11,
-    kTVMFFIFieldFlagBitMaskSEqHashDefNonRecursive = 1 << 12,
+    kTVMFFIFieldFlagBitMaskSEqHashDefSimple = 1 << 12,
 }
 
 /// Definition-region mode used by structural traversal.
@@ -125,8 +125,8 @@ pub enum TVMFFIFieldFlagBitMask {
 #[derive(Debug, Copy, Clone, PartialEq, Eq)]
 pub enum TVMFFIDefRegionKind {
     kTVMFFIDefRegionKindNone = 0,
-    kTVMFFIDefRegionKindRecursive = 1,
-    kTVMFFIDefRegionKindNonRecursive = 2,
+    kTVMFFIDefRegionKindPattern = 1,
+    kTVMFFIDefRegionKindSimple = 2,
 }
 
 /// Structural equality/hash participation kind stored in type metadata.
diff --git a/rust/tvm-ffi/src/extra/structural_mutate.rs 
b/rust/tvm-ffi/src/extra/structural_mutate.rs
index 8e54dd8b..1ba0986b 100644
--- a/rust/tvm-ffi/src/extra/structural_mutate.rs
+++ b/rust/tvm-ffi/src/extra/structural_mutate.rs
@@ -2074,8 +2074,8 @@ fn with_current_driver_context<D, T>(
 fn def_region_from_raw(kind: i32) -> Result<DefRegionKind> {
     match kind {
         x if x == DefRegionKind::None as i32 => Ok(DefRegionKind::None),
-        x if x == DefRegionKind::Recursive as i32 => 
Ok(DefRegionKind::Recursive),
-        x if x == DefRegionKind::NonRecursive as i32 => 
Ok(DefRegionKind::NonRecursive),
+        x if x == DefRegionKind::Pattern as i32 => Ok(DefRegionKind::Pattern),
+        x if x == DefRegionKind::Simple as i32 => Ok(DefRegionKind::Simple),
         _ => Err(runtime_error("invalid structural definition-region kind")),
     }
 }
@@ -2354,6 +2354,10 @@ fn with_mutator_def_region<T>(
 ) -> T {
     unsafe {
         let previous = (*mutator).def_region_mode;
+        // Precedence: a pattern region propagates; entering any kind inside 
it has no effect.
+        if previous == DefRegionKind::Pattern as i32 {
+            return callback();
+        }
         (*mutator).def_region_mode = kind as i32;
         struct Restore {
             mutator: StructuralMutatorHandle,
diff --git a/rust/tvm-ffi/src/extra/structural_visit.rs 
b/rust/tvm-ffi/src/extra/structural_visit.rs
index 511a5a93..b6cef1c8 100644
--- a/rust/tvm-ffi/src/extra/structural_visit.rs
+++ b/rust/tvm-ffi/src/extra/structural_visit.rs
@@ -57,7 +57,7 @@ use crate::function::Function;
 use crate::object::{Object, ObjectArc, ObjectCore};
 use crate::reflection::TypeAttrColumn;
 use crate::tvm_ffi_sys::TVMFFIFieldFlagBitMask::{
-    kTVMFFIFieldFlagBitMaskSEqHashDefNonRecursive, 
kTVMFFIFieldFlagBitMaskSEqHashDefRecursive,
+    kTVMFFIFieldFlagBitMaskSEqHashDefSimple, 
kTVMFFIFieldFlagBitMaskSEqHashDefPattern,
     kTVMFFIFieldFlagBitMaskSEqHashIgnore,
 };
 use crate::tvm_ffi_sys::{
@@ -69,8 +69,8 @@ use 
super::structural_common::{impl_callback_chain_tuple_arities, with_structura
 
 const STRUCTURAL_VISIT_ATTR: &str = "__s_visit__";
 const FLAG_SEQ_HASH_IGNORE: i64 = kTVMFFIFieldFlagBitMaskSEqHashIgnore as i64;
-const FLAG_SEQ_HASH_DEF_RECURSIVE: i64 = 
kTVMFFIFieldFlagBitMaskSEqHashDefRecursive as i64;
-const FLAG_SEQ_HASH_DEF_NON_RECURSIVE: i64 = 
kTVMFFIFieldFlagBitMaskSEqHashDefNonRecursive as i64;
+const FLAG_SEQ_HASH_DEF_PATTERN: i64 = 
kTVMFFIFieldFlagBitMaskSEqHashDefPattern as i64;
+const FLAG_SEQ_HASH_DEF_SIMPLE: i64 = kTVMFFIFieldFlagBitMaskSEqHashDefSimple 
as i64;
 
 /// What a callback asks the Rust walker to do with the current value.
 pub enum WalkResult {
@@ -123,8 +123,8 @@ pub enum WalkOrder {
 
 /// Definition-region state active at the current value.
 ///
-/// Reflected fields marked `SEqHashDefRecursive` or
-/// `SEqHashDefNonRecursive` override the inherited state for that field's
+/// Reflected fields marked `SEqHashDefPattern` or
+/// `SEqHashDefSimple` override the inherited state for that field's
 /// complete recursive visit.
 #[repr(i32)]
 #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
@@ -133,20 +133,20 @@ pub enum DefRegionKind {
     #[default]
     None = 0,
     /// Definitions apply recursively through the visited value.
-    Recursive = 1,
-    /// Definitions apply to the visited value using non-recursive semantics.
-    NonRecursive = 2,
+    Pattern = 1,
+    /// Definitions apply to the visited value alone; its type is walked as 
uses.
+    Simple = 2,
 }
 
 const _: () = {
     assert!(DefRegionKind::None as i32 == 
TVMFFIDefRegionKind::kTVMFFIDefRegionKindNone as i32);
     assert!(
-        DefRegionKind::Recursive as i32
-            == TVMFFIDefRegionKind::kTVMFFIDefRegionKindRecursive as i32
+        DefRegionKind::Pattern as i32
+            == TVMFFIDefRegionKind::kTVMFFIDefRegionKindPattern as i32
     );
     assert!(
-        DefRegionKind::NonRecursive as i32
-            == TVMFFIDefRegionKind::kTVMFFIDefRegionKindNonRecursive as i32
+        DefRegionKind::Simple as i32
+            == TVMFFIDefRegionKind::kTVMFFIDefRegionKindSimple as i32
     );
 };
 
@@ -1775,6 +1775,10 @@ fn with_visitor_def_region<T>(
 ) -> T {
     unsafe {
         let previous = (*visitor).def_region_mode;
+        // Precedence: a pattern region propagates; entering any kind inside 
it has no effect.
+        if previous == DefRegionKind::Pattern as i32 {
+            return callback();
+        }
         (*visitor).def_region_mode = kind as i32;
         struct Restore {
             visitor: StructuralVisitorHandle,
@@ -1794,8 +1798,8 @@ fn with_visitor_def_region<T>(
 fn def_region_from_raw(kind: i32) -> Result<DefRegionKind> {
     match kind {
         x if x == DefRegionKind::None as i32 => Ok(DefRegionKind::None),
-        x if x == DefRegionKind::Recursive as i32 => 
Ok(DefRegionKind::Recursive),
-        x if x == DefRegionKind::NonRecursive as i32 => 
Ok(DefRegionKind::NonRecursive),
+        x if x == DefRegionKind::Pattern as i32 => Ok(DefRegionKind::Pattern),
+        x if x == DefRegionKind::Simple as i32 => Ok(DefRegionKind::Simple),
         _ => Err(runtime_error("invalid structural definition-region kind")),
     }
 }
@@ -1869,16 +1873,19 @@ fn finish(result: NativeResult) -> 
Result<Option<VisitInterrupt>> {
 
 #[inline]
 pub(crate) fn field_def_region(field: &TVMFFIFieldInfo, inherited: 
DefRegionKind) -> DefRegionKind {
-    if field.flags & FLAG_SEQ_HASH_DEF_NON_RECURSIVE != 0 {
-        DefRegionKind::NonRecursive
-    } else if field.flags & FLAG_SEQ_HASH_DEF_RECURSIVE != 0 {
-        DefRegionKind::Recursive
+    // Precedence: a pattern region propagates; entering any kind inside it 
has no effect.
+    if inherited == DefRegionKind::Pattern {
+        DefRegionKind::Pattern
+    } else if field.flags & FLAG_SEQ_HASH_DEF_SIMPLE != 0 {
+        DefRegionKind::Simple
+    } else if field.flags & FLAG_SEQ_HASH_DEF_PATTERN != 0 {
+        DefRegionKind::Pattern
     } else {
         inherited
     }
 }
 
-/// A non-recursive definition applies to a FreeVar value itself, but not to
+/// A simple definition applies to a FreeVar value itself, but not to
 /// the FreeVar's own reflected children: nested free vars there must resolve
 /// against an outer binding instead of rebinding. Mirrors C++
 /// `VisitReflectedFieldsExpected`.
@@ -1887,7 +1894,7 @@ pub(crate) fn free_var_child_region(
     inherited: DefRegionKind,
     structural_eq_hash_kind: i32,
 ) -> DefRegionKind {
-    if inherited == DefRegionKind::NonRecursive
+    if inherited == DefRegionKind::Simple
         && structural_eq_hash_kind == 
TVMFFISEqHashKind::kTVMFFISEqHashKindFreeVar as i32
     {
         DefRegionKind::None
diff --git a/rust/tvm-ffi/tests/test_structural_visit.rs 
b/rust/tvm-ffi/tests/test_structural_visit.rs
index 8a141cd7..f2a67f47 100644
--- a/rust/tvm-ffi/tests/test_structural_visit.rs
+++ b/rust/tvm-ffi/tests/test_structural_visit.rs
@@ -309,7 +309,7 @@ impl StructuralVisitor for ManualRegionVisitor {
         if let Some(array) = value.cast::<Array<i64>>() {
             // Override the state for exactly this child's subtree...
             let overridden = array.get(0).unwrap();
-            if let Some(interrupt) = self.visit_child(&overridden, 
DefRegionKind::NonRecursive)? {
+            if let Some(interrupt) = self.visit_child(&overridden, 
DefRegionKind::Simple)? {
                 return Ok(Some(interrupt));
             }
             // ...and forward the received state to inherit it.
@@ -330,7 +330,7 @@ fn manual_child_visit_can_override_def_region() {
     assert!(structural_visit(&root, &mut probe).unwrap().is_none());
     assert_eq!(
         probe.seen,
-        vec![DefRegionKind::NonRecursive, DefRegionKind::None]
+        vec![DefRegionKind::Simple, DefRegionKind::None]
     );
 }
 
@@ -987,7 +987,7 @@ impl StructuralVisitor for InheritedRegionProbe {
             self.at_root = false;
             let outer = value.cast::<Array<Array<i64>>>().unwrap();
             let inner = outer.get(0).unwrap();
-            return self.visit_child(&inner, DefRegionKind::Recursive);
+            return self.visit_child(&inner, DefRegionKind::Pattern);
         }
         self.seen.push(def_region_kind);
         self.default_visit_children(value, def_region_kind)
@@ -1002,7 +1002,7 @@ fn def_region_is_inherited_through_containers() {
         seen: Vec::new(),
     };
     assert!(structural_visit(&root, &mut probe).unwrap().is_none());
-    assert_eq!(probe.seen, vec![DefRegionKind::Recursive; 3]);
+    assert_eq!(probe.seen, vec![DefRegionKind::Pattern; 3]);
 }
 
 #[test]
@@ -1297,7 +1297,7 @@ fn callback_visit_with_overrides_child_def_region() {
         (
             |array: Array<i64>, visitor: &mut VisitContext<'_, ()>| {
                 for value in array.iter() {
-                    if let Some(interrupt) = visitor.visit_with(&value, 
DefRegionKind::Recursive)? {
+                    if let Some(interrupt) = visitor.visit_with(&value, 
DefRegionKind::Pattern)? {
                         return Ok(Some(interrupt));
                     }
                 }
@@ -1312,7 +1312,7 @@ fn callback_visit_with_overrides_child_def_region() {
     .is_none());
     assert_eq!(
         *seen.borrow(),
-        vec![(1, DefRegionKind::Recursive), (2, DefRegionKind::Recursive),]
+        vec![(1, DefRegionKind::Pattern), (2, DefRegionKind::Pattern),]
     );
 }
 
diff --git a/rust/tvm-ffi/tests/test_structural_visitor_alignment.rs 
b/rust/tvm-ffi/tests/test_structural_visitor_alignment.rs
index 2d964bdf..6724e9bd 100644
--- a/rust/tvm-ffi/tests/test_structural_visitor_alignment.rs
+++ b/rust/tvm-ffi/tests/test_structural_visitor_alignment.rs
@@ -70,13 +70,13 @@ impl StructuralVisitor for RecordingVisitor {
         }
 
         // C++ analog: `TFuncObj::StructuralVisit` — visit "params"
-        // (element 0) under a recursive definition region, then the "body"
+        // (element 0) under a pattern definition region, then the "body"
         // (element 1) under the inherited state.
         if let Some(array) = value.cast::<Array<i64>>() {
-            // C++: visitor->WithDefRegionKind(kTVMFFIDefRegionKindRecursive,
+            // C++: visitor->WithDefRegionKind(kTVMFFIDefRegionKindPattern,
             //          [&] { return visitor->VisitExpected(self->params); })
             let params = array.get(0).unwrap();
-            if let Some(interrupt) = self.visit_child(&params, 
DefRegionKind::Recursive)? {
+            if let Some(interrupt) = self.visit_child(&params, 
DefRegionKind::Pattern)? {
                 return Ok(Some(interrupt));
             }
             // C++: visitor->VisitExpected(self->body)  (inherits the state)
@@ -104,7 +104,7 @@ fn records_values_and_def_region_modes() {
         visitor.modes,
         vec![
             DefRegionKind::None,      // the array itself
-            DefRegionKind::Recursive, // element 0: the "params" position
+            DefRegionKind::Pattern, // element 0: the "params" position
             DefRegionKind::None,      // element 1: the "body" position
         ]
     );
diff --git a/src/ffi/extra/structural_equal.cc 
b/src/ffi/extra/structural_equal.cc
index 237287da..52f9c6f9 100644
--- a/src/ffi/extra/structural_equal.cc
+++ b/src/ffi/extra/structural_equal.cc
@@ -31,6 +31,7 @@
 #include <tvm/ffi/reflection/accessor.h>
 #include <tvm/ffi/string.h>
 
+#include <algorithm>
 #include <cmath>
 #include <unordered_map>
 #include <utility>
@@ -180,13 +181,13 @@ class StructEqualHandler {
       }
       return success;
     }
-    // FreeVar path. In a non-recursive def region the FreeVar's own
+    // FreeVar path. In a simple def region the FreeVar's own
     // sub-fields are walked outside the def region (nested free vars
     // there must resolve against an outer binding, not rebind), so we
     // clamp ``def_region_kind_`` to ``kNone`` around the CompareFields
     // call and restore before the binding decision below.
     TVMFFIDefRegionKind saved_def_region_kind = def_region_kind_;
-    if (def_region_kind_ == kTVMFFIDefRegionKindNonRecursive) {
+    if (def_region_kind_ == kTVMFFIDefRegionKindSimple) {
       def_region_kind_ = kTVMFFIDefRegionKindNone;
     }
     bool success = CompareFields(lhs, rhs, type_info);
@@ -220,13 +221,17 @@ class StructEqualHandler {
         Any lhs_value = getter(lhs);
         Any rhs_value = getter(rhs);
         // Dispatch on the def-region flags.
-        constexpr int64_t kSEqHashDefAny = 
kTVMFFIFieldFlagBitMaskSEqHashDefRecursive |
-                                           
kTVMFFIFieldFlagBitMaskSEqHashDefNonRecursive;
+        constexpr int64_t kSEqHashDefAny =
+            kTVMFFIFieldFlagBitMaskSEqHashDefPattern | 
kTVMFFIFieldFlagBitMaskSEqHashDefSimple;
         if (field_info->flags & kSEqHashDefAny) {
           TVMFFIDefRegionKind new_kind =
-              (field_info->flags & 
kTVMFFIFieldFlagBitMaskSEqHashDefNonRecursive)
-                  ? kTVMFFIDefRegionKindNonRecursive
-                  : kTVMFFIDefRegionKindRecursive;
+              (field_info->flags & kTVMFFIFieldFlagBitMaskSEqHashDefSimple)
+                  ? kTVMFFIDefRegionKindSimple
+                  : kTVMFFIDefRegionKindPattern;
+          // Precedence: a pattern region propagates; entering any kind inside 
it has no effect.
+          if (def_region_kind_ == kTVMFFIDefRegionKindPattern) {
+            new_kind = kTVMFFIDefRegionKindPattern;
+          }
           std::swap(new_kind, def_region_kind_);
           success = CompareAny(lhs_value, rhs_value);
           std::swap(new_kind, def_region_kind_);
@@ -262,6 +267,10 @@ class StructEqualHandler {
                   (def_region_kind == kTVMFFIDefRegionKindNone)
                       ? def_region_kind_
                       : static_cast<TVMFFIDefRegionKind>(def_region_kind);
+              // Precedence: a pattern region propagates; entering any kind 
inside it has no effect.
+              if (def_region_kind_ == kTVMFFIDefRegionKindPattern) {
+                new_kind = kTVMFFIDefRegionKindPattern;
+              }
               std::swap(new_kind, def_region_kind_);
               bool sub_success = CompareAny(inner_lhs, inner_rhs);
               std::swap(new_kind, def_region_kind_);
@@ -432,8 +441,8 @@ class StructEqualHandler {
   }
   // Current def-region kind. ``kNone`` means we are not in a def region;
   // free vars discovered here do not bind (they must already be bound by an
-  // outer scope or comparison falls back to pointer identity). ``kRecursive``
-  // and ``kNonRecursive`` enable binding for the field-flag-driven walk and
+  // outer scope or comparison falls back to pointer identity). ``kPattern``
+  // and ``kSimple`` enable binding for the field-flag-driven walk and
   // for the custom-callback path respectively (see CompareObject).
   TVMFFIDefRegionKind def_region_kind_{kTVMFFIDefRegionKindNone};
   // whether we compare tensor data
@@ -452,8 +461,7 @@ class StructEqualHandler {
 bool StructuralEqual::Equal(const Any& lhs, const Any& rhs, bool map_free_vars,
                             bool skip_tensor_content) {
   StructEqualHandler handler;
-  handler.def_region_kind_ =
-      map_free_vars ? kTVMFFIDefRegionKindRecursive : kTVMFFIDefRegionKindNone;
+  handler.def_region_kind_ = map_free_vars ? kTVMFFIDefRegionKindPattern : 
kTVMFFIDefRegionKindNone;
   handler.skip_tensor_content_ = skip_tensor_content;
   return handler.CompareAny(lhs, rhs);
 }
@@ -463,8 +471,7 @@ Optional<reflection::AccessPathPair> 
StructuralEqual::GetFirstMismatch(const Any
                                                                        bool 
map_free_vars,
                                                                        bool 
skip_tensor_content) {
   StructEqualHandler handler;
-  handler.def_region_kind_ =
-      map_free_vars ? kTVMFFIDefRegionKindRecursive : kTVMFFIDefRegionKindNone;
+  handler.def_region_kind_ = map_free_vars ? kTVMFFIDefRegionKindPattern : 
kTVMFFIDefRegionKindNone;
   handler.skip_tensor_content_ = skip_tensor_content;
   std::vector<reflection::AccessStep> lhs_reverse_path;
   std::vector<reflection::AccessStep> rhs_reverse_path;
diff --git a/src/ffi/extra/structural_hash.cc b/src/ffi/extra/structural_hash.cc
index 21c5545d..f2fb13c8 100644
--- a/src/ffi/extra/structural_hash.cc
+++ b/src/ffi/extra/structural_hash.cc
@@ -131,7 +131,7 @@ class StructuralHashHandler {
     if (structural_eq_hash_kind != kTVMFFISEqHashKindFreeVar) {
       hash_value = HashFields(obj, type_info, obj->GetTypeKeyHash());
     } else {
-      // FreeVar path. In a non-recursive def region the FreeVar's own
+      // FreeVar path. In a simple def region the FreeVar's own
       // sub-fields are walked outside the def region (nested free vars
       // there hash by pointer, matching use semantics), so we clamp
       // ``def_region_kind_`` to ``kNone`` around the HashFields call and
@@ -144,7 +144,7 @@ class StructuralHashHandler {
       // is observable to FreeVars hashed later in the same traversal;
       // skipping the walk would silently change those subsequent hashes.
       TVMFFIDefRegionKind saved_def_region_kind = def_region_kind_;
-      if (def_region_kind_ == kTVMFFIDefRegionKindNonRecursive) {
+      if (def_region_kind_ == kTVMFFIDefRegionKindSimple) {
         def_region_kind_ = kTVMFFIDefRegionKindNone;
       }
       hash_value = HashFields(obj, type_info, obj->GetTypeKeyHash());
@@ -183,13 +183,17 @@ class StructuralHashHandler {
           reflection::FieldGetter getter(field_info);
           Any field_value = getter(obj);
           // Dispatch on the def-region flags (mirror of the equality side).
-          constexpr int64_t kSEqHashDefAny = 
kTVMFFIFieldFlagBitMaskSEqHashDefRecursive |
-                                             
kTVMFFIFieldFlagBitMaskSEqHashDefNonRecursive;
+          constexpr int64_t kSEqHashDefAny =
+              kTVMFFIFieldFlagBitMaskSEqHashDefPattern | 
kTVMFFIFieldFlagBitMaskSEqHashDefSimple;
           if (field_info->flags & kSEqHashDefAny) {
             TVMFFIDefRegionKind new_kind =
-                (field_info->flags & 
kTVMFFIFieldFlagBitMaskSEqHashDefNonRecursive)
-                    ? kTVMFFIDefRegionKindNonRecursive
-                    : kTVMFFIDefRegionKindRecursive;
+                (field_info->flags & kTVMFFIFieldFlagBitMaskSEqHashDefSimple)
+                    ? kTVMFFIDefRegionKindSimple
+                    : kTVMFFIDefRegionKindPattern;
+            // Precedence: a pattern region propagates; entering any kind 
inside it has no effect.
+            if (def_region_kind_ == kTVMFFIDefRegionKindPattern) {
+              new_kind = kTVMFFIDefRegionKindPattern;
+            }
             std::swap(new_kind, def_region_kind_);
             init_hash = details::StableHashCombine(init_hash, 
HashAny(field_value));
             std::swap(new_kind, def_region_kind_);
@@ -209,6 +213,10 @@ class StructuralHashHandler {
                   (def_region_kind == kTVMFFIDefRegionKindNone)
                       ? def_region_kind_
                       : static_cast<TVMFFIDefRegionKind>(def_region_kind);
+              // Precedence: a pattern region propagates; entering any kind 
inside it has no effect.
+              if (def_region_kind_ == kTVMFFIDefRegionKindPattern) {
+                new_kind = kTVMFFIDefRegionKindPattern;
+              }
               std::swap(new_kind, def_region_kind_);
               uint64_t hv = HashAny(val);
               std::swap(new_kind, def_region_kind_);
@@ -349,7 +357,7 @@ class StructuralHashHandler {
   }
 
   // Current def-region kind. ``kNone`` means we are not in a def region; free
-  // vars hash by pointer. ``kRecursive`` and ``kNonRecursive`` enable
+  // vars hash by pointer. ``kPattern`` and ``kSimple`` enable
   // ``free_var_counter_``-based hashing for the field-flag-driven walk and
   // for the custom-callback path respectively (see HashObject).
   TVMFFIDefRegionKind def_region_kind_{kTVMFFIDefRegionKindNone};
@@ -366,8 +374,7 @@ class StructuralHashHandler {
 
 uint64_t StructuralHash::Hash(const Any& value, bool map_free_vars, bool 
skip_tensor_content) {
   StructuralHashHandler handler;
-  handler.def_region_kind_ =
-      map_free_vars ? kTVMFFIDefRegionKindRecursive : kTVMFFIDefRegionKindNone;
+  handler.def_region_kind_ = map_free_vars ? kTVMFFIDefRegionKindPattern : 
kTVMFFIDefRegionKindNone;
   handler.skip_tensor_content_ = skip_tensor_content;
   return handler.HashAny(value);
 }
diff --git a/tests/cpp/extra/test_structural_equal_hash.cc 
b/tests/cpp/extra/test_structural_equal_hash.cc
index c22d41ce..9438983f 100644
--- a/tests/cpp/extra/test_structural_equal_hash.cc
+++ b/tests/cpp/extra/test_structural_equal_hash.cc
@@ -248,10 +248,10 @@ TEST(StructuralEqualHash, CustomTreeNode) {
   EXPECT_TRUE(StructuralEqual()(diff_fa_fc, expected_diff_fa_fc));
 }
 
-// Regression tests for the SEqHashDefRecursive vs SEqHashDefNonRecursive
+// Regression tests for the SEqHashDefPattern vs SEqHashDefSimple
 // distinction. ``TDefHolder`` has two sibling fields:
-//   - ``def_recursive``     tagged AttachFieldFlag::SEqHashDefRecursive()
-//   - ``def_non_recursive`` tagged AttachFieldFlag::SEqHashDefNonRecursive()
+//   - ``def_recursive``     tagged AttachFieldFlag::SEqHashDefPattern()
+//   - ``def_non_recursive`` tagged AttachFieldFlag::SEqHashDefSimple()
 // each holding a ``TVarWithDep`` (a FreeVar with a sub-field ``dep`` that
 // can itself reference another FreeVar). The four sub-cases below cover
 // the observable behaviors of the two flags.
@@ -310,6 +310,16 @@ TEST(StructuralEqualHash, NonRecursiveDef) {
     EXPECT_EQ(StructuralHash::Hash(lhs, /*map_free_vars=*/true),
               StructuralHash::Hash(rhs, /*map_free_vars=*/true));
   }
+  {
+    // (e) A simple def inside a pattern region stays a pattern: the same
+    // objects as (b) are equal once map_free_vars makes the outer region a 
pattern.
+    TVarWithDep shared("shared", std::nullopt);
+    TDefHolder lhs(shared, TVarWithDep("c", TVar("p")));
+    TDefHolder rhs(shared, TVarWithDep("d", TVar("q")));
+    EXPECT_TRUE(StructuralEqual::Equal(lhs, rhs, /*map_free_vars=*/true));
+    EXPECT_EQ(StructuralHash::Hash(lhs, /*map_free_vars=*/true),
+              StructuralHash::Hash(rhs, /*map_free_vars=*/true));
+  }
 }
 
 TEST(StructuralEqualHash, List) {
diff --git a/tests/cpp/extra/test_structural_visit.cc 
b/tests/cpp/extra/test_structural_visit.cc
index 75513128..3319cbfd 100644
--- a/tests/cpp/extra/test_structural_visit.cc
+++ b/tests/cpp/extra/test_structural_visit.cc
@@ -196,9 +196,9 @@ TEST(StructuralVisitor, TraversesFunction) {
   TestVisitorObj* test_visitor = AsTestVisitor(visitor);
   ASSERT_EQ(test_visitor->visited.size(), 4U);
   EXPECT_TRUE(test_visitor->visited[0].same_as(params));
-  EXPECT_EQ(test_visitor->modes[0], kTVMFFIDefRegionKindRecursive);
+  EXPECT_EQ(test_visitor->modes[0], kTVMFFIDefRegionKindPattern);
   EXPECT_TRUE(test_visitor->visited[1].same_as(param));
-  EXPECT_EQ(test_visitor->modes[1], kTVMFFIDefRegionKindRecursive);
+  EXPECT_EQ(test_visitor->modes[1], kTVMFFIDefRegionKindPattern);
   EXPECT_TRUE(test_visitor->visited[2].same_as(body));
   EXPECT_EQ(test_visitor->modes[2], kTVMFFIDefRegionKindNone);
   EXPECT_TRUE(test_visitor->visited[3].same_as(body_value));
@@ -266,9 +266,9 @@ TEST(StructuralVisitor, UsesFuncHook) {
   EXPECT_TRUE(test_visitor->visited[0].same_as(root));
   EXPECT_EQ(test_visitor->modes[0], kTVMFFIDefRegionKindNone);
   EXPECT_TRUE(test_visitor->visited[1].same_as(params));
-  EXPECT_EQ(test_visitor->modes[1], kTVMFFIDefRegionKindRecursive);
+  EXPECT_EQ(test_visitor->modes[1], kTVMFFIDefRegionKindPattern);
   EXPECT_TRUE(test_visitor->visited[2].same_as(param));
-  EXPECT_EQ(test_visitor->modes[2], kTVMFFIDefRegionKindRecursive);
+  EXPECT_EQ(test_visitor->modes[2], kTVMFFIDefRegionKindPattern);
   EXPECT_TRUE(test_visitor->visited[3].same_as(body));
   EXPECT_EQ(test_visitor->modes[3], kTVMFFIDefRegionKindNone);
   EXPECT_TRUE(test_visitor->visited[4].same_as(body_value));
@@ -293,9 +293,9 @@ TEST(StructuralVisitor, RestoresFuncDefRegion) {
   EXPECT_TRUE(test_visitor->visited[0].same_as(root));
   EXPECT_EQ(test_visitor->modes[0], kTVMFFIDefRegionKindNone);
   EXPECT_TRUE(test_visitor->visited[1].same_as(params));
-  EXPECT_EQ(test_visitor->modes[1], kTVMFFIDefRegionKindRecursive);
+  EXPECT_EQ(test_visitor->modes[1], kTVMFFIDefRegionKindPattern);
   EXPECT_TRUE(test_visitor->visited[2].same_as(param));
-  EXPECT_EQ(test_visitor->modes[2], kTVMFFIDefRegionKindRecursive);
+  EXPECT_EQ(test_visitor->modes[2], kTVMFFIDefRegionKindPattern);
   EXPECT_EQ(test_visitor->def_region_kind(), kTVMFFIDefRegionKindNone);
 }
 
@@ -307,20 +307,20 @@ TEST(StructuralVisitor, 
ExplicitDefRegionsOverrideFreeVarFieldClamp) {
   StructuralVisitor visitor = MakeTestVisitor();
 
   Expected<Optional<VisitInterrupt>> result = visitor->WithDefRegionKind(
-      kTVMFFIDefRegionKindNonRecursive, [&]() { return 
visitor->VisitExpected(root); });
+      kTVMFFIDefRegionKindSimple, [&]() { return visitor->VisitExpected(root); 
});
 
   ASSERT_TRUE(result.is_ok());
   EXPECT_FALSE(result.value().has_value());
   TestVisitorObj* test_visitor = AsTestVisitor(visitor);
   ASSERT_EQ(test_visitor->visited.size(), 4U);
   EXPECT_TRUE(test_visitor->visited[0].same_as(root));
-  EXPECT_EQ(test_visitor->modes[0], kTVMFFIDefRegionKindNonRecursive);
+  EXPECT_EQ(test_visitor->modes[0], kTVMFFIDefRegionKindSimple);
   EXPECT_TRUE(test_visitor->visited[1].same_as(holder));
   EXPECT_EQ(test_visitor->modes[1], kTVMFFIDefRegionKindNone);
   EXPECT_TRUE(test_visitor->visited[2].same_as(recursive));
-  EXPECT_EQ(test_visitor->modes[2], kTVMFFIDefRegionKindRecursive);
+  EXPECT_EQ(test_visitor->modes[2], kTVMFFIDefRegionKindPattern);
   EXPECT_TRUE(test_visitor->visited[3].same_as(non_recursive));
-  EXPECT_EQ(test_visitor->modes[3], kTVMFFIDefRegionKindNonRecursive);
+  EXPECT_EQ(test_visitor->modes[3], kTVMFFIDefRegionKindSimple);
   EXPECT_EQ(test_visitor->def_region_kind(), kTVMFFIDefRegionKindNone);
 }
 
@@ -690,9 +690,9 @@ TEST(StructuralVisit, CallbackDrivenTraversal) {
       [](const TPairObj* pair,
          StructuralVisitorObj* visitor) -> Expected<Optional<VisitInterrupt>> {
         TVM_FFI_S_VISIT_MAYBE_EARLY_RETURN(visitor->WithDefRegionKind(
-            kTVMFFIDefRegionKindRecursive, [&] { return 
visitor->VisitExpected(pair->lhs); }));
+            kTVMFFIDefRegionKindPattern, [&] { return 
visitor->VisitExpected(pair->lhs); }));
         TVM_FFI_S_VISIT_MAYBE_EARLY_RETURN(visitor->WithDefRegionKind(
-            kTVMFFIDefRegionKindNonRecursive, [&] { return 
visitor->VisitExpected(pair->rhs); }));
+            kTVMFFIDefRegionKindSimple, [&] { return 
visitor->VisitExpected(pair->rhs); }));
         return Optional<VisitInterrupt>(std::nullopt);
       },
       [&](const TVarWithDepObj* var,
@@ -707,8 +707,8 @@ TEST(StructuralVisit, CallbackDrivenTraversal) {
   ASSERT_TRUE(result.value().has_value());
   EXPECT_EQ(result.value().value()->value.cast<String>(), "found stop");
   ASSERT_EQ(trace.size(), 2u);
-  EXPECT_EQ(trace[0], std::make_pair(std::string("lhs"), 
kTVMFFIDefRegionKindRecursive));
-  EXPECT_EQ(trace[1], std::make_pair(std::string("stop"), 
kTVMFFIDefRegionKindNonRecursive));
+  EXPECT_EQ(trace[0], std::make_pair(std::string("lhs"), 
kTVMFFIDefRegionKindPattern));
+  EXPECT_EQ(trace[1], std::make_pair(std::string("stop"), 
kTVMFFIDefRegionKindSimple));
 
   using CallbackLayer = StructuralWalkWithVisitCount<>;
   using ComposedLayer = StructuralVisitOuterLayer<CallbackLayer>;
diff --git a/tests/cpp/testing_object.h b/tests/cpp/testing_object.h
index b5cf64e0..4d4cc65a 100644
--- a/tests/cpp/testing_object.h
+++ b/tests/cpp/testing_object.h
@@ -323,7 +323,7 @@ class TObjectPtrHolder : public ObjectRef {
 // FreeVar test object that has a sub-field referencing another FreeVar.
 // This models the "var with nested vars" case (analogous to a relax::Var
 // whose struct_info contains tir shape vars). It is used to exercise the
-// difference between SEqHashDefRecursive and SEqHashDefNonRecursive at the
+// difference between SEqHashDefPattern and SEqHashDefSimple at the
 // FFI layer: under recursive semantics the nested ``dep`` var rebinds
 // transitively; under non-recursive semantics it is treated as a use of an
 // outer-scope binding and equality fails when no such outer binding exists.
@@ -377,9 +377,9 @@ class TDefHolderObj : public Object {
     namespace refl = tvm::ffi::reflection;
     refl::ObjectDef<TDefHolderObj>()
         .def_ro("def_recursive", &TDefHolderObj::def_recursive,
-                refl::AttachFieldFlag::SEqHashDefRecursive())
+                refl::AttachFieldFlag::SEqHashDefPattern())
         .def_ro("def_non_recursive", &TDefHolderObj::def_non_recursive,
-                refl::AttachFieldFlag::SEqHashDefNonRecursive());
+                refl::AttachFieldFlag::SEqHashDefSimple());
   }
 
   static constexpr TVMFFISEqHashKind _type_s_eq_hash_kind = 
kTVMFFISEqHashKindTreeNode;
@@ -410,7 +410,7 @@ class TFuncObj : public Object {
     const auto* self = value.cast<const TFuncObj*>();
 
     TVM_FFI_S_VISIT_MAYBE_EARLY_RETURN(visitor->WithDefRegionKind(
-        kTVMFFIDefRegionKindRecursive, [&]() { return 
visitor->VisitExpected(self->params); }));
+        kTVMFFIDefRegionKindPattern, [&]() { return 
visitor->VisitExpected(self->params); }));
 
     auto body_result = visitor->VisitExpected(self->body);
     TVM_FFI_S_VISIT_MAYBE_EARLY_RETURN(body_result);
@@ -420,7 +420,7 @@ class TFuncObj : public Object {
   static void RegisterReflection() {
     namespace refl = tvm::ffi::reflection;
     refl::ObjectDef<TFuncObj>()
-        .def_ro("params", &TFuncObj::params, 
refl::AttachFieldFlag::SEqHashDefRecursive())
+        .def_ro("params", &TFuncObj::params, 
refl::AttachFieldFlag::SEqHashDefPattern())
         .def_ro("body", &TFuncObj::body)
         .def_ro("comment", &TFuncObj::comment, 
refl::AttachFieldFlag::SEqHashIgnore());
     refl::EnsureTypeAttrColumn(refl::type_attr::kStructuralVisit);

Reply via email to