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


The following commit(s) were added to refs/heads/main by this push:
     new 9cb42a65bb [REFACTOR][IR] Represent missing type information with 
MissingType (#20542)
9cb42a65bb is described below

commit 9cb42a65bbe0cfda5a6a239ecd3e9362a694bf96
Author: Tianqi Chen <[email protected]>
AuthorDate: Sun Oct 4 11:48:04 2026 -0400

    [REFACTOR][IR] Represent missing type information with MissingType (#20542)
---
 docs/reference/api/python/script/ir_builder.rst   |  2 +-
 include/tvm/ir/base_expr.h                        | 35 ++++++++++++++++++-----
 include/tvm/relax/expr_functor.h                  |  2 +-
 include/tvm/relax/type.h                          |  6 ++--
 include/tvm/relax/type_functor.h                  |  3 +-
 include/tvm/tirx/op.h                             |  2 +-
 python/tvm/ir/__init__.py                         | 12 +++++++-
 python/tvm/ir/expr.py                             |  2 +-
 python/tvm/ir/type.py                             | 20 +++++++++----
 python/tvm/relax/expr.py                          |  2 +-
 python/tvm/relax/frontend/nn/subroutine.py        |  4 +--
 python/tvm/relax/op/__init__.py                   |  5 ++--
 python/tvm/relax/testing/ast_printer.py           |  2 +-
 python/tvm/script/ir_builder/__init__.py          |  2 ++
 src/ir/expr.cc                                    |  7 +++--
 src/ir/prim/const_fold.h                          |  2 +-
 src/ir/prim/expr.cc                               |  2 +-
 src/ir/prim/op.cc                                 |  2 +-
 src/ir/type.cc                                    | 34 +++++++++++++---------
 src/relax/analysis/type_analysis.cc               |  6 ++++
 src/relax/analysis/well_formed.cc                 | 12 ++++----
 src/relax/backend/vm/lower_runtime_builtin.cc     | 10 +++----
 src/relax/distributed/transform/lower_distir.cc   |  2 +-
 src/relax/distributed/transform/utils.cc          |  2 +-
 src/relax/ir/block_builder.cc                     | 31 ++++++++++----------
 src/relax/ir/dependent_type.cc                    | 11 +++----
 src/relax/ir/emit_te.cc                           |  3 +-
 src/relax/ir/expr.cc                              | 11 ++++---
 src/relax/ir/expr_functor.cc                      |  8 +++---
 src/relax/op/op.cc                                | 10 +++----
 src/relax/op/op_common.h                          |  2 +-
 src/relax/script/ir_builder/ir.cc                 |  2 +-
 src/relax/script/printer/binding.cc               |  9 +++---
 src/relax/script/printer/call.cc                  |  7 +++--
 src/relax/script/printer/function.cc              |  4 +--
 src/relax/transform/decompose_ops.cc              |  2 +-
 src/relax/transform/normalize.cc                  |  2 +-
 src/relax/transform/rewrite_dataflow_reshape.cc   |  3 +-
 src/relax/transform/update_vdevice.cc             |  2 +-
 src/relax/transform/utils.h                       |  5 ++--
 src/script/ir_builder/ir.cc                       |  2 +-
 src/script/printer/ir/prim_type.cc                | 10 +++++++
 src/script/printer/ir/utils.cc                    |  4 +--
 src/script/printer/script_printer.cc              |  1 +
 src/sym/z3_prover.cc                              |  2 +-
 src/target/llvm/codegen_llvm.cc                   |  2 +-
 src/tirx/ir/buffer_common.h                       |  2 +-
 src/tirx/ir/function.cc                           |  2 +-
 src/tirx/op/op.cc                                 |  2 +-
 src/tirx/script/printer/expr.cc                   |  2 +-
 src/tirx/script/printer/function.cc               |  2 +-
 tests/cpp/expr_test.cc                            |  4 +--
 tests/python/ir/test_ir_type.py                   |  2 +-
 tests/python/ir/test_nonnullable_ir.py            |  2 +-
 tests/python/relax/test_analysis_well_formed.py   |  2 +-
 tests/python/relax/test_expr.py                   |  2 +-
 tests/python/script/test_constructor_contracts.py |  2 +-
 57 files changed, 205 insertions(+), 130 deletions(-)

diff --git a/docs/reference/api/python/script/ir_builder.rst 
b/docs/reference/api/python/script/ir_builder.rst
index 35ff6bfa95..564519134a 100644
--- a/docs/reference/api/python/script/ir_builder.rst
+++ b/docs/reference/api/python/script/ir_builder.rst
@@ -23,7 +23,7 @@ tvm.script.ir_builder
 .. automodule:: tvm.script.ir_builder
    :members:
    :imported-members:
-   :exclude-members: Call, DataTypeImm, FuncType, GenericConst, PrimType, 
Range, StringImm, StringType, Type
+   :exclude-members: Call, DataTypeImm, FuncType, GenericConst, MissingType, 
PrimType, Range, StringImm, StringType, Type
 
 tvm.relax.script.ir_builder.distributed
 ***************************************
diff --git a/include/tvm/ir/base_expr.h b/include/tvm/ir/base_expr.h
index cbda0a4dff..9ce81b1978 100644
--- a/include/tvm/ir/base_expr.h
+++ b/include/tvm/ir/base_expr.h
@@ -79,15 +79,36 @@ class TypeNode : public ffi::Object {
  */
 class Type : public ffi::ObjectRef {
  public:
-  /*! \brief Sentinel for a type that has not been populated yet. */
+  /*! \brief Construct a MissingType for type information not yet populated. */
   TVM_DLL static Type Missing();
 
-  /*! \return whether this is the missing-type sentinel. */
-  TVM_DLL bool IsMissing() const;
-
   TVM_FFI_DEFINE_OBJECT_REF_METHODS_NOTNULLABLE(Type, ffi::ObjectRef, 
TypeNode);
 };
 
+/*!
+ * \brief Type information that has not been supplied or computed.
+ *
+ * MissingType is not a concrete type, wildcard, or inference variable. Unlike
+ * AnyType and Void, it must be resolved before a fully typed IR boundary.
+ */
+class MissingTypeNode final : public TypeNode {
+ public:
+  static void RegisterReflection() {
+    namespace refl = tvm::ffi::reflection;
+    refl::ObjectDef<MissingTypeNode>();
+  }
+
+  TVM_FFI_DECLARE_OBJECT_INFO_FINAL("ir.MissingType", MissingTypeNode, 
TypeNode);
+};
+
+/*! \brief Managed reference to the MissingTypeNode singleton. */
+class MissingType final : public Type {
+ public:
+  TVM_DLL MissingType();
+
+  TVM_FFI_DEFINE_OBJECT_REF_METHODS_NOTNULLABLE(MissingType, Type, 
MissingTypeNode);
+};
+
 /*!
  * \brief Type marker for opaque construction-time expressions.
  *
@@ -311,10 +332,10 @@ class ExprNode : public ffi::Object {
   /*!
    * \brief The deduced or annotated type of the expression.
    *
-   * Type::Missing() denotes type information that will be populated by
+   * MissingType() denotes type information that will be populated by
    * later analysis passes instead of expression constructors.
    */
-  mutable Type ty = Type::Missing();
+  mutable Type ty = MissingType();
 
   static void RegisterReflection() {
     namespace refl = tvm::ffi::reflection;
@@ -322,7 +343,7 @@ class ExprNode : public ffi::Object {
     refl::ObjectDef<ExprNode>()
         .def_ro("span", &ExprNode::span, refl::DefaultValue(Span()),
                 refl::AttachFieldFlag::SEqHashIgnore())
-        .def_ro("ty", &ExprNode::ty, refl::DefaultValue(Type::Missing()));
+        .def_ro("ty", &ExprNode::ty, refl::DefaultValue(MissingType()));
   }
 
   static constexpr TVMFFISEqHashKind _type_s_eq_hash_kind = 
kTVMFFISEqHashKindTreeNode;
diff --git a/include/tvm/relax/expr_functor.h b/include/tvm/relax/expr_functor.h
index cc722a6d8d..e6d305a4e4 100644
--- a/include/tvm/relax/expr_functor.h
+++ b/include/tvm/relax/expr_functor.h
@@ -524,7 +524,7 @@ class ExprMutatorBase : public ExprFunctor<Expr(const 
Expr&)> {
   bool VisitAndCheckTypeFieldUnchanged(const ffi::ObjectRef& ty) {
     if (const TypeNode* ty_node = ty.as<TypeNode>()) {
       Type type = ffi::GetRef<Type>(ty_node);
-      return type.IsMissing() || this->VisitExprDepTypeField(type).same_as(ty);
+      return type.as<MissingType>().has_value() || 
this->VisitExprDepTypeField(type).same_as(ty);
     } else {
       return true;
     }
diff --git a/include/tvm/relax/type.h b/include/tvm/relax/type.h
index 5b3b652024..8e4fb36e9e 100644
--- a/include/tvm/relax/type.h
+++ b/include/tvm/relax/type.h
@@ -175,7 +175,7 @@ class TensorTypeNode : public TypeNode {
   ffi::Optional<ffi::Array<PrimExpr>> GetShape() const {
     if (!shape.has_value()) return {};
     const Expr& shape_expr = this->shape.value();
-    if (shape_expr->ty.IsMissing()) return {};
+    if (shape_expr->ty.as<MissingType>().has_value()) return {};
     if (const auto* shape_ty = shape_expr->ty.as<ShapeTypeNode>()) {
       return shape_ty->values;
     }
@@ -366,7 +366,7 @@ inline ffi::Optional<T> MatchType(const Expr& expr) {
  */
 template <typename T>
 inline const T* GetTypeAs(const Expr& expr) {
-  TVM_FFI_ICHECK(!expr->ty.IsMissing())
+  TVM_FFI_ICHECK(!expr->ty.as<MissingType>().has_value())
       << "The type is not populated, check if you have normalized the expr";
   return expr->ty.as<T>();
 }
@@ -378,7 +378,7 @@ inline const T* GetTypeAs(const Expr& expr) {
  * \return underlying Relax type.
  */
 inline Type GetType(const Expr& expr) {
-  TVM_FFI_ICHECK(!expr->ty.IsMissing())
+  TVM_FFI_ICHECK(!expr->ty.as<MissingType>().has_value())
       << "The type is not populated, check if you have normalized the expr";
   return expr->ty;
 }
diff --git a/include/tvm/relax/type_functor.h b/include/tvm/relax/type_functor.h
index b1997e0709..5ce7f7c61a 100644
--- a/include/tvm/relax/type_functor.h
+++ b/include/tvm/relax/type_functor.h
@@ -73,8 +73,7 @@ class TypeFunctor<R(const Type& n, Args...)> {
    */
   virtual R VisitType(const Type& n, Args... args) {
     TVM_FFI_ICHECK(n.defined());
-    TVM_FFI_ICHECK_NE(n->type_index(), TypeNode::RuntimeTypeIndex())
-        << "TypeFunctor cannot visit Type::Missing()";
+    TVM_FFI_ICHECK(!n.as<MissingType>().has_value()) << "TypeFunctor cannot 
visit Type::Missing()";
     static FType vtable = InitVTable();
     return vtable(n, this, std::forward<Args>(args)...);
   }
diff --git a/include/tvm/tirx/op.h b/include/tvm/tirx/op.h
index 32b417ba2e..0940833810 100644
--- a/include/tvm/tirx/op.h
+++ b/include/tvm/tirx/op.h
@@ -338,7 +338,7 @@ TVM_DECLARE_INTRIN_BINARY(ldexp);
  * \return The check results
  */
 inline bool IsPointerType(const Type& type, DLDataType element_type) {
-  if (type.IsMissing()) return false;
+  if (type.as<MissingType>().has_value()) return false;
   if (const auto* ptr_type = type.as<PointerTypeNode>()) {
     if (const auto* prim_type = ptr_type->element_type.as<PrimTypeNode>()) {
       return prim_type->dtype == element_type;
diff --git a/python/tvm/ir/__init__.py b/python/tvm/ir/__init__.py
index fe264b16ba..7703a6dc40 100644
--- a/python/tvm/ir/__init__.py
+++ b/python/tvm/ir/__init__.py
@@ -33,7 +33,17 @@ from .base import (
 
 # Register Type before Expr.  Expr's reflected ``ty`` field otherwise creates
 # an auto-generated Type wrapper before the concrete Python class is available.
-from .type import AnyType, FuncType, OpaqueType, PointerType, PrimType, 
StringType, TupleType, Type
+from .type import (
+    AnyType,
+    FuncType,
+    MissingType,
+    OpaqueType,
+    PointerType,
+    PrimType,
+    StringType,
+    TupleType,
+    Type,
+)
 from .expr import (
     Call,
     Constant,
diff --git a/python/tvm/ir/expr.py b/python/tvm/ir/expr.py
index 177029702e..1177f52c56 100644
--- a/python/tvm/ir/expr.py
+++ b/python/tvm/ir/expr.py
@@ -48,7 +48,7 @@ class Expr(Node):
     ty: "tvm.ir.Type"
 
     def __getitem__(self, index):
-        if self.ty.is_missing():
+        if isinstance(self.ty, tvm.ir.MissingType):
             # Preserve Relax's pre-normalization tuple access: operator calls
             # have a missing result type until the block builder infers it.
             return TupleGetItem(self, index)
diff --git a/python/tvm/ir/type.py b/python/tvm/ir/type.py
index e0fbf6cd09..54172b637f 100644
--- a/python/tvm/ir/type.py
+++ b/python/tvm/ir/type.py
@@ -30,18 +30,14 @@ class Type(Node, Scriptable):
 
     @staticmethod
     def missing():
-        """Return the sentinel for missing type information."""
+        """Construct a MissingType for missing type information."""
         return _ffi_api.TypeMissing()
 
     @staticmethod
     def Missing():
-        """Return the sentinel for missing type information."""
+        """Construct a MissingType for missing type information."""
         return _ffi_api.TypeMissing()
 
-    def is_missing(self):
-        """Return whether this is the missing-type sentinel."""
-        return _ffi_api.TypeIsMissing(self)
-
     def __eq__(self, other):
         """Compare two types for structural equivalence."""
         return bool(tvm_ffi.structural_equal(self, other))
@@ -54,6 +50,18 @@ class Type(Node, Scriptable):
         return self.is_(other)
 
 
+@tvm_ffi.register_object("ir.MissingType")
+class MissingType(Type):
+    """Type information that has not been supplied or computed.
+
+    Unlike AnyType or Void, this is not a concrete type and must be resolved
+    before a boundary that requires fully typed IR.
+    """
+
+    def __init__(self):
+        self.__init_handle_by_constructor__(_ffi_api.MissingType)
+
+
 @tvm_ffi.register_object("ir.AnyType")
 class AnyType(Type):
     """The top type, which admits any value."""
diff --git a/python/tvm/relax/expr.py b/python/tvm/relax/expr.py
index 0c3a1f579f..917dcfd031 100644
--- a/python/tvm/relax/expr.py
+++ b/python/tvm/relax/expr.py
@@ -89,7 +89,7 @@ _op_ffi_api = None  # pylint: disable=invalid-name
 
 
 def _is_tensor_or_missing_type(ty: Type) -> bool:
-    return isinstance(ty, tvm.relax.TensorType) or ty.is_missing()
+    return isinstance(ty, tvm.relax.TensorType | tvm.ir.MissingType)
 
 
 def _binary_op_helper(lhs: Expr, rhs: Expr, op: Callable):
diff --git a/python/tvm/relax/frontend/nn/subroutine.py 
b/python/tvm/relax/frontend/nn/subroutine.py
index a589919b9f..b3bb6138ab 100644
--- a/python/tvm/relax/frontend/nn/subroutine.py
+++ b/python/tvm/relax/frontend/nn/subroutine.py
@@ -45,7 +45,7 @@ def _normalize_expr(block_builder, arg, as_relax_expr=False):
     if isinstance(arg, tuple):
         arg = relax.Tuple([_normalize_expr(block_builder, element) for element 
in arg])
 
-    if isinstance(arg, relax.Expr) and arg.ty.is_missing():
+    if isinstance(arg, relax.Expr) and isinstance(arg.ty, ir.MissingType):
         arg = block_builder.emit(arg)
 
     if isinstance(arg, nn.Tensor) and as_relax_expr:
@@ -108,7 +108,7 @@ class SubroutineMixin:
             out = subroutine(*subroutine_args)
 
             if is_nn_tensor_output:
-                if out.ty.is_missing():
+                if isinstance(out.ty, ir.MissingType):
                     out = block_builder.emit(out, 
name_hint=f"{subroutine.name_hint}_output")
                 out = nn.Tensor(_expr=out)
             return out
diff --git a/python/tvm/relax/op/__init__.py b/python/tvm/relax/op/__init__.py
index d8628c806d..f1035fe43e 100644
--- a/python/tvm/relax/op/__init__.py
+++ b/python/tvm/relax/op/__init__.py
@@ -193,9 +193,8 @@ def _register_op_make():
         return op(lhs)
 
     def _call(func, *args, attrs=None):
-        if not (
-            isinstance(func.ty, expr.tvm.ir.FuncType | expr.tvm.relax.FuncType)
-            or func.ty.is_missing()
+        if not isinstance(
+            func.ty, expr.tvm.ir.FuncType | expr.tvm.relax.FuncType | 
expr.tvm.ir.MissingType
         ):
             return NotImplemented
         return expr.tvm.ir.Call(func, args, attrs=attrs)
diff --git a/python/tvm/relax/testing/ast_printer.py 
b/python/tvm/relax/testing/ast_printer.py
index 9a467eb577..0ffdb74d96 100644
--- a/python/tvm/relax/testing/ast_printer.py
+++ b/python/tvm/relax/testing/ast_printer.py
@@ -91,7 +91,7 @@ class ASTPrinter(ExprFunctor):
         Handles whether to include the ty fields.
         """
         fields = kwargs.copy()
-        if not node.ty.is_missing() and self.include_ty_annotations:
+        if not isinstance(node.ty, tvm.ir.MissingType) and 
self.include_ty_annotations:
             fields["ty"] = self.visit_ty_(node.ty)
         return self.build_ast_node(nodename, force_newline=force_newline, 
**fields)
 
diff --git a/python/tvm/script/ir_builder/__init__.py 
b/python/tvm/script/ir_builder/__init__.py
index 9e1f1f7f0f..30f92ab098 100644
--- a/python/tvm/script/ir_builder/__init__.py
+++ b/python/tvm/script/ir_builder/__init__.py
@@ -21,6 +21,7 @@ from tvm.ir import (
     DataTypeImm,
     FuncType,
     GenericConst,
+    MissingType,
     PrimType,
     Range,
     StringImm,
@@ -67,6 +68,7 @@ __all__ = [
     "GenericConst",
     "IRBuilder",
     "IRModuleFrame",
+    "MissingType",
     "PrimType",
     "Range",
     "StringImm",
diff --git a/src/ir/expr.cc b/src/ir/expr.cc
index 3399805126..7d07ab95d9 100644
--- a/src/ir/expr.cc
+++ b/src/ir/expr.cc
@@ -725,7 +725,7 @@ Tuple::Tuple(ffi::Array<Expr> fields, Span span) : 
Expr(ffi::UnsafeInit{}) {
   ffi::Optional<Type> tuple_ty = [&]() -> ffi::Optional<Type> {
     ffi::Array<Type> field_ty;
     for (const Expr& field : fields) {
-      if (field->ty.IsMissing()) {
+      if (field->ty.as<MissingType>().has_value()) {
         return std::nullopt;
       }
       field_ty.push_back(field->ty);
@@ -821,7 +821,8 @@ TVM_FFI_STATIC_INIT_BLOCK() {
 
 // Constants
 GenericConst::GenericConst(ffi::Any value, Type ty, Span span) : 
Constant(ffi::UnsafeInit{}) {
-  TVM_FFI_CHECK(!ty.IsMissing(), TypeError) << "GenericConst requires an 
expression type";
+  TVM_FFI_CHECK(!ty.as<MissingType>().has_value(), TypeError)
+      << "GenericConst requires an expression type";
   TVM_FFI_CHECK(!value.as<ffi::BigInt>() && !value.as<bool>() && 
!value.as<double>() &&
                     !value.as<ffi::String>(),
                 TypeError)
@@ -1217,7 +1218,7 @@ Type Call::ReinferType(const CallNode* call) {
   TVM_FFI_CHECK(infer_type.count(op.value()), ValueError)
       << "No context-free FInferType hook is registered for " << op.value();
   Type result = infer_type[op.value()].CallExpected(call).value();
-  TVM_FFI_CHECK(!result.IsMissing(), InternalError)
+  TVM_FFI_CHECK(!result.as<MissingType>().has_value(), InternalError)
       << "FInferType for " << op.value() << " returned Type::Missing()";
   return result;
 }
diff --git a/src/ir/prim/const_fold.h b/src/ir/prim/const_fold.h
index 77d42f09e9..0a0f611ec7 100644
--- a/src/ir/prim/const_fold.h
+++ b/src/ir/prim/const_fold.h
@@ -79,7 +79,7 @@ inline bool IsIndexType(DLDataType type) {
 
 inline bool IsIndexTypedExpr(const ExprNode* expr) {
   TVM_FFI_DCHECK(expr != nullptr);
-  TVM_FFI_DCHECK(!expr->ExprNode::ty.IsMissing());
+  TVM_FFI_DCHECK(!expr->ExprNode::ty.as<MissingType>().has_value());
   const auto* prim_ty = expr->ExprNode::ty.as<PrimTypeNode>();
   TVM_FFI_DCHECK(prim_ty != nullptr);
   return IsIndexType(prim_ty->dtype);
diff --git a/src/ir/prim/expr.cc b/src/ir/prim/expr.cc
index 8a64472d7a..295d0b7745 100644
--- a/src/ir/prim/expr.cc
+++ b/src/ir/prim/expr.cc
@@ -40,7 +40,7 @@ int GetLanesOrVScaleFactor(const PrimType& ty) {
 TVM_FFI_INLINE const PrimTypeNode* GetPrimTypeNode(const PrimExpr& expr) {
   const auto* node = expr.get();
   TVM_FFI_DCHECK(node != nullptr);
-  TVM_FFI_DCHECK(!node->ExprNode::ty.IsMissing());
+  TVM_FFI_DCHECK(!node->ExprNode::ty.as<MissingType>().has_value());
   const auto* prim_ty = node->ExprNode::ty.as<PrimTypeNode>();
   TVM_FFI_DCHECK(prim_ty != nullptr);
   return prim_ty;
diff --git a/src/ir/prim/op.cc b/src/ir/prim/op.cc
index 39be7bb5a8..bdbdba9d06 100644
--- a/src/ir/prim/op.cc
+++ b/src/ir/prim/op.cc
@@ -34,7 +34,7 @@ TVM_FFI_INLINE const PrimTypeNode* GetPrimTypeNode(const 
PrimExpr& expr) {
   // Avoid PrimExpr::ty() ObjectRef materialization on binary operator hot 
paths.
   const auto* node = expr.get();
   TVM_FFI_DCHECK(node != nullptr);
-  TVM_FFI_DCHECK(!node->ExprNode::ty.IsMissing());
+  TVM_FFI_DCHECK(!node->ExprNode::ty.as<MissingType>().has_value());
   const auto* prim_ty = node->ExprNode::ty.as<PrimTypeNode>();
   TVM_FFI_DCHECK(prim_ty != nullptr);
   return prim_ty;
diff --git a/src/ir/type.cc b/src/ir/type.cc
index e824b4a380..dfa2601f07 100644
--- a/src/ir/type.cc
+++ b/src/ir/type.cc
@@ -63,7 +63,7 @@ ffi::ObjectPtr<PrimTypeNode> GetCachedPrimTypeNode(DLDataType 
dtype) {
 
 TVM_FFI_INLINE ffi::Expected<ffi::Optional<ffi::VisitInterrupt>> TypeVisit(
     ffi::StructuralVisitorObj*, ffi::AnyView) noexcept {
-  // Type::Missing() is the only concrete TypeNode value; span is ignored 
debug metadata.
+  // Field-less types are leaves; span is ignored debug metadata.
   return std::nullopt;
 }
 
@@ -246,14 +246,7 @@ TVM_FFI_INLINE ffi::Expected<ffi::UnchangedOr<ffi::Any>> 
TupleTypeMaybeInplaceMu
 
 }  // namespace
 
-Type Type::Missing() {
-  static Type missing = []() {
-    Type type(ffi::UnsafeInit{});
-    type.data_ = ffi::make_object<TypeNode>();
-    return type;
-  }();
-  return missing;
-}
+Type Type::Missing() { return MissingType(); }
 
 TVM_FFI_STATIC_INIT_BLOCK() {
   namespace refl = tvm::ffi::reflection;
@@ -263,12 +256,24 @@ TVM_FFI_STATIC_INIT_BLOCK() {
       .attr(refl::type_attr::kStructuralMutate, 
ffi::FStructuralMutate::FromNative<&TypeMutate>())
       .attr(refl::type_attr::kStructuralMaybeInplaceMutate,
             ffi::FStructuralMutate::FromNative<&TypeMaybeInplaceMutate>());
-  refl::GlobalDef()
-      .def("ir.TypeMissing", []() { return Type::Missing(); })
-      .def("ir.TypeIsMissing", [](Type type) { return type.IsMissing(); });
+  refl::GlobalDef().def("ir.TypeMissing", []() { return Type::Missing(); });
+}
+
+MissingType::MissingType() : Type(ffi::UnsafeInit{}) {
+  static const auto singleton = ffi::make_object<MissingTypeNode>();
+  data_ = singleton;
 }
 
-bool Type::IsMissing() const { return this->same_as(Type::Missing()); }
+TVM_FFI_STATIC_INIT_BLOCK() {
+  namespace refl = tvm::ffi::reflection;
+  MissingTypeNode::RegisterReflection();
+  refl::TypeAttrDef<MissingTypeNode>()
+      .attr(refl::type_attr::kStructuralVisit, 
ffi::FStructuralVisit::FromNative<&TypeVisit>())
+      .attr(refl::type_attr::kStructuralMutate, 
ffi::FStructuralMutate::FromNative<&TypeMutate>())
+      .attr(refl::type_attr::kStructuralMaybeInplaceMutate,
+            ffi::FStructuralMutate::FromNative<&TypeMaybeInplaceMutate>());
+  refl::GlobalDef().def("ir.MissingType", []() { return MissingType(); });
+}
 
 AnyType::AnyType(Span span) : Type(ffi::UnsafeInit{}) {
   ffi::ObjectPtr<AnyTypeNode> n = ffi::make_object<AnyTypeNode>();
@@ -390,7 +395,8 @@ TVM_FFI_STATIC_INIT_BLOCK() {
 
 // PointerType
 PointerType::PointerType(Type element_type, ffi::String storage_scope) : 
Type(ffi::UnsafeInit{}) {
-  TVM_FFI_ICHECK(!element_type.IsMissing()) << "PointerType element_type 
cannot be Type::Missing()";
+  TVM_FFI_ICHECK(!element_type.as<MissingType>().has_value())
+      << "PointerType element_type cannot be Type::Missing()";
   ffi::ObjectPtr<PointerTypeNode> n = ffi::make_object<PointerTypeNode>();
   if (storage_scope.empty()) {
     n->storage_scope = "global";
diff --git a/src/relax/analysis/type_analysis.cc 
b/src/relax/analysis/type_analysis.cc
index a60ffbab82..42ffa76f0b 100644
--- a/src/relax/analysis/type_analysis.cc
+++ b/src/relax/analysis/type_analysis.cc
@@ -307,6 +307,8 @@ class TypeBaseChecker : public 
TypeFunctor<BaseCheckResult(const Type&, const Ty
   explicit TypeBaseChecker(sym::AnalyzerObj* ana) : analyzer_(ana) {}
 
   BaseCheckResult VisitType(const Type& lhs, const Type& other) override {
+    TVM_FFI_ICHECK(!lhs.as<MissingType>().has_value() && 
!other.as<MissingType>().has_value())
+        << "Type analysis requires populated types";
     // quick path
     // Note: subclass may disable this quick path if we need to go over all 
type.
     if (lhs.same_as(other)) return BaseCheckResult::kPass;
@@ -632,6 +634,8 @@ class TypeBasePreconditionCollector : public 
TypeFunctor<PrimExpr(const Type&, c
   explicit TypeBasePreconditionCollector() {}
 
   PrimExpr VisitType(const Type& lhs, const Type& other) override {
+    TVM_FFI_ICHECK(!lhs.as<MissingType>().has_value() && 
!other.as<MissingType>().has_value())
+        << "Type analysis requires populated types";
     if (lhs.same_as(other)) {
       // Early bail-out if the Type has reference equality.
       return IntImm::Bool(true);
@@ -990,6 +994,8 @@ class TypeLCAFinder : public TypeFunctor<Type(const Type&, 
const Type&)> {
   explicit TypeLCAFinder(sym::AnalyzerObj* ana) : analyzer_(ana) {}
 
   Type VisitType(const Type& lhs, const Type& other) final {
+    TVM_FFI_ICHECK(!lhs.as<MissingType>().has_value() && 
!other.as<MissingType>().has_value())
+        << "Type analysis requires populated types";
     // quick path
     if (lhs.same_as(other)) return lhs;
     return TypeFunctor::VisitType(lhs, other);
diff --git a/src/relax/analysis/well_formed.cc 
b/src/relax/analysis/well_formed.cc
index 6e1965b85a..b5113b9365 100644
--- a/src/relax/analysis/well_formed.cc
+++ b/src/relax/analysis/well_formed.cc
@@ -186,7 +186,7 @@ class WellFormedChecker : public relax::ExprVisitor, public 
relax::TypeVisitor {
   }
 
   void VisitExpr(const Expr& expr) final {
-    if (!expr.as<OpNode>() && expr->ty.IsMissing()) {
+    if (!expr.as<OpNode>() && expr->ty.as<MissingType>().has_value()) {
       TVM_FFI_VISIT_THROW(TypeError, expr) << "The ty of Expr " << expr << " 
is missing.";
     }
     relax::ExprVisitor::VisitExpr(expr);
@@ -202,7 +202,7 @@ class WellFormedChecker : public relax::ExprVisitor, public 
relax::TypeVisitor {
       }
     }
 
-    if (!op->ty.IsMissing()) {
+    if (!op->ty.as<MissingType>().has_value()) {
       if (!op->ty->IsInstance<FuncTypeNode>()) {
         TVM_FFI_VISIT_THROW(TypeError, var)
             << "The ty of GlobalVar " << ffi::GetRef<Expr>(op) << " must be 
either FuncType.";
@@ -323,7 +323,7 @@ class WellFormedChecker : public relax::ExprVisitor, public 
relax::TypeVisitor {
       CheckType(param.get());
     }
     // check function ret_ty
-    if (!op->ret_ty.IsMissing()) {
+    if (!op->ret_ty.as<MissingType>().has_value()) {
       this->VisitType(op->ret_ty);
     } else {
       TVM_FFI_VISIT_THROW(TypeError, ffi::GetRef<Expr>(op)) << "Function must 
have defined ret_ty";
@@ -428,7 +428,8 @@ class WellFormedChecker : public relax::ExprVisitor, public 
relax::TypeVisitor {
       has_infer_type =
           op_map_infer_type_.count(op.value()) || 
op_map_infer_type_with_builder_.count(op.value());
     }
-    if (check_ty && !call->ty.IsMissing() && (!call->ty.as<PrimTypeNode>() || 
has_infer_type)) {
+    if (check_ty && !call->ty.as<MissingType>().has_value() &&
+        (!call->ty.as<PrimTypeNode>() || has_infer_type)) {
       // The `InferType` method isn't currently exposed by the
       // Normalizer, and can only be called indirectly by normalizing
       // an expression that does not yet have `Type`.
@@ -544,7 +545,8 @@ class WellFormedChecker : public relax::ExprVisitor, public 
relax::TypeVisitor {
 
     this->VisitVarDef(binding->var);
 
-    if (check_ty && !binding->var->ty.IsMissing() && 
!binding->value->ty.IsMissing()) {
+    if (check_ty && !binding->var->ty.as<MissingType>().has_value() &&
+        !binding->value->ty.as<MissingType>().has_value()) {
       auto expr_ty = GetType(binding->value);
       auto var_ty = GetType(binding->var);
       if (!IsBaseOf(var_ty, expr_ty)) {
diff --git a/src/relax/backend/vm/lower_runtime_builtin.cc 
b/src/relax/backend/vm/lower_runtime_builtin.cc
index 9872cb81f8..93012d950e 100644
--- a/src/relax/backend/vm/lower_runtime_builtin.cc
+++ b/src/relax/backend/vm/lower_runtime_builtin.cc
@@ -125,7 +125,7 @@ class LowerRuntimeBuiltinMutator : public ExprMutator {
 
   Expr Reshape(const Call& call_node) {
     TVM_FFI_ICHECK(call_node->args.size() == 2);
-    TVM_FFI_ICHECK(!call_node->ty.IsMissing());
+    TVM_FFI_ICHECK(!call_node->ty.as<MissingType>().has_value());
     auto arg = call_node->args[1];
 
     TVM_FFI_CHECK(arg->ty->IsInstance<ShapeTypeNode>(), TypeError)
@@ -140,14 +140,14 @@ class LowerRuntimeBuiltinMutator : public ExprMutator {
 
   Expr ShapeOf(const Call& call_node) {
     TVM_FFI_ICHECK(call_node->args.size() == 1);
-    TVM_FFI_ICHECK(!call_node->ty.IsMissing());
+    TVM_FFI_ICHECK(!call_node->ty.as<MissingType>().has_value());
     return Call::Unchecked(Type::Missing(), builtin_shape_of_, 
call_node->args, Attrs(),
                            {GetType(call_node)});
   }
 
   Expr TensorToShape(const Call& call_node) {
     TVM_FFI_ICHECK(call_node->args.size() == 1);
-    TVM_FFI_ICHECK(!call_node->ty.IsMissing());
+    TVM_FFI_ICHECK(!call_node->ty.as<MissingType>().has_value());
 
     return Call::Unchecked(Type::Missing(), builtin_tensor_to_shape_, 
call_node->args, Attrs(),
                            {GetType(call_node)});
@@ -155,7 +155,7 @@ class LowerRuntimeBuiltinMutator : public ExprMutator {
 
   Expr CallPyFunc(const Call& call_node) {
     TVM_FFI_ICHECK(call_node->args.size() == 2);
-    TVM_FFI_ICHECK(!call_node->ty.IsMissing());
+    TVM_FFI_ICHECK(!call_node->ty.as<MissingType>().has_value());
 
     // Create tuple with function name and arguments tuple
     ffi::Array<Expr> tuple_fields;
@@ -171,7 +171,7 @@ class LowerRuntimeBuiltinMutator : public ExprMutator {
   Expr ToDevice(const Call& call_node) {
     // TODO(yongwww): replace ToVDeviceAttrs with related Expr
     TVM_FFI_ICHECK(call_node->args.size() == 1);
-    TVM_FFI_ICHECK(!call_node->ty.IsMissing());
+    TVM_FFI_ICHECK(!call_node->ty.as<MissingType>().has_value());
     auto attrs = call_node->attrs.as<ToVDeviceAttrs>();
     ffi::Array<Expr> args;
     args.push_back(call_node->args[0]);
diff --git a/src/relax/distributed/transform/lower_distir.cc 
b/src/relax/distributed/transform/lower_distir.cc
index 121f546a94..4bc9d35b38 100644
--- a/src/relax/distributed/transform/lower_distir.cc
+++ b/src/relax/distributed/transform/lower_distir.cc
@@ -114,7 +114,7 @@ class DistIRSharder : public ExprMutator {
   }
 
   Expr ShardInputParamTensorAndConstant(Expr input) {
-    TVM_FFI_ICHECK(!input->ty.IsMissing());
+    TVM_FFI_ICHECK(!input->ty.as<MissingType>().has_value());
     Type old_ty = GetType(input);
     Type new_ty = ConvertType(old_ty, false);
     if (const auto* var = input.as<VarNode>()) {
diff --git a/src/relax/distributed/transform/utils.cc 
b/src/relax/distributed/transform/utils.cc
index 586ac9efec..df93811ca1 100644
--- a/src/relax/distributed/transform/utils.cc
+++ b/src/relax/distributed/transform/utils.cc
@@ -48,7 +48,7 @@ bool TypeCompatibleWithRelax(ffi::Array<Type> tys) {
 bool IsDistIRFunc(Function func) {
   ffi::Array<Type> param_tys;
   for (const auto& param : func->params) {
-    TVM_FFI_ICHECK(!param->ty.IsMissing());
+    TVM_FFI_ICHECK(!param->ty.as<MissingType>().has_value());
     param_tys.push_back(param->ty.as_or_throw<Type>());
   }
   bool compatible_with_dist_ir = TypeCompatibleWithDistIR(param_tys);
diff --git a/src/relax/ir/block_builder.cc b/src/relax/ir/block_builder.cc
index cf0293b5f4..af635a71fc 100644
--- a/src/relax/ir/block_builder.cc
+++ b/src/relax/ir/block_builder.cc
@@ -90,7 +90,7 @@ class BlockBuilderImpl : public BlockBuilderNode {
       GlobalVar gvar(func_name);
 
       Type finfo = Type::Missing();
-      if (!func->ty.IsMissing()) {
+      if (!func->ty.as<MissingType>().has_value()) {
         finfo = GetType(func);
       } else if (auto* prim_func = func.as<tirx::PrimFuncNode>()) {
         // NOTE: use a slightly different type than checked type
@@ -275,8 +275,8 @@ class BlockBuilderImpl : public BlockBuilderNode {
             << "Cannot emit dataflow var in non-dataflow block";
       }
       // normalized check
-      TVM_FFI_ICHECK(!var_binding->var->ty.IsMissing());
-      TVM_FFI_ICHECK(!var_binding->value->ty.IsMissing());
+      TVM_FFI_ICHECK(!var_binding->var->ty.as<MissingType>().has_value());
+      TVM_FFI_ICHECK(!var_binding->value->ty.as<MissingType>().has_value());
       cur_frame->bindings.push_back(binding);
       binding_table_.insert_or_assign(var_binding->var, var_binding->value);
     } else if (const auto* match_cast = binding.as<MatchCastNode>()) {
@@ -285,8 +285,8 @@ class BlockBuilderImpl : public BlockBuilderNode {
             << "Cannot emit dataflow var in non-dataflow block";
       }
       // normalized check
-      TVM_FFI_ICHECK(!match_cast->var->ty.IsMissing());
-      TVM_FFI_ICHECK(!match_cast->value->ty.IsMissing());
+      TVM_FFI_ICHECK(!match_cast->var->ty.as<MissingType>().has_value());
+      TVM_FFI_ICHECK(!match_cast->value->ty.as<MissingType>().has_value());
       // NOTE match shape do not follow simple binding rule
       // as a result should not appear in binding table.
       cur_frame->bindings.push_back(binding);
@@ -532,7 +532,7 @@ class Normalizer : public BlockBuilderImpl, private 
ExprFunctor<Expr(const Expr&
     // After Normalize: an Expr always have
     // ty (with the exception of Op).
     if (!normalized->IsInstance<OpNode>()) {
-      TVM_FFI_ICHECK(!normalized->ty.IsMissing())
+      TVM_FFI_ICHECK(!normalized->ty.as<MissingType>().has_value())
           << "The ty of an Expr except OpNode after "
              "normalization must not be missing. However, this Expr does not 
have ty: "
           << normalized;
@@ -590,7 +590,8 @@ class Normalizer : public BlockBuilderImpl, private 
ExprFunctor<Expr(const Expr&
   Expr VisitVar_(const typename T::ContainerType* var) {
     // Parameters and free-vars must be present with type
     // Other vars must have already been normalized through binding
-    TVM_FFI_ICHECK(!var->ty.IsMissing()) << "Var " << var->name << " does not 
have type.";
+    TVM_FFI_ICHECK(!var->ty.template as<MissingType>().has_value())
+        << "Var " << var->name << " does not have type.";
     return ffi::GetRef<Var>(var);
   }
 
@@ -629,7 +630,7 @@ class Normalizer : public BlockBuilderImpl, private 
ExprFunctor<Expr(const Expr&
 
     Tuple tuple = unchanged ? ffi::GetRef<Tuple>(op) : Tuple(new_fields, 
op->span);
     // Update tuple fields.
-    if (tuple->ty.IsMissing()) {
+    if (tuple->ty.as<MissingType>().has_value()) {
       ffi::Array<Type> tuple_ty;
       for (Expr field : tuple->fields) {
         tuple_ty.push_back(GetType(field));
@@ -663,7 +664,7 @@ class Normalizer : public BlockBuilderImpl, private 
ExprFunctor<Expr(const Expr&
       }
     }();
 
-    if (call->ty.IsMissing()) {
+    if (call->ty.as<MissingType>().has_value()) {
       auto inferred_ty = InferType(call);
       UpdateType(call, inferred_ty);
     }
@@ -732,7 +733,7 @@ class Normalizer : public BlockBuilderImpl, private 
ExprFunctor<Expr(const Expr&
     }();
 
     // only do shape/type inference if the SeqExpr does not have shape/type
-    if (seq_expr->ty.IsMissing()) {
+    if (seq_expr->ty.as<MissingType>().has_value()) {
       UpdateType(seq_expr, EraseToWellDefinedInScope(GetType(seq_expr->body)));
     }
     return seq_expr;
@@ -751,7 +752,7 @@ class Normalizer : public BlockBuilderImpl, private 
ExprFunctor<Expr(const Expr&
         return If(new_cond, new_true, new_false, op->span);
       }
     }();
-    if (if_node->ty.IsMissing()) {
+    if (if_node->ty.as<MissingType>().has_value()) {
       auto true_info = EraseToWellDefinedInScope(GetType(new_true));
       auto false_info = EraseToWellDefinedInScope(GetType(new_false));
       UpdateType(if_node, TypeLCA(true_info, false_info));
@@ -765,7 +766,7 @@ class Normalizer : public BlockBuilderImpl, private 
ExprFunctor<Expr(const Expr&
     TupleGetItem node = new_tuple.same_as(op->tuple) ? 
ffi::GetRef<TupleGetItem>(op)
                                                      : TupleGetItem(new_tuple, 
op->index);
 
-    if (node->ty.IsMissing()) {
+    if (node->ty.as<MissingType>().has_value()) {
       auto opt = MatchType<TupleType>(node->tuple);
       TVM_FFI_ICHECK(opt) << "The type of Tuple must be TupleType, "
                           << "but expression " << node->tuple << " has type " 
<< node->tuple->ty;
@@ -790,7 +791,7 @@ class Normalizer : public BlockBuilderImpl, private 
ExprFunctor<Expr(const Expr&
     if (!new_value.same_as(binding->value)) {
       binding = VarBinding(binding->var, new_value, binding->span);
     }
-    if (binding->var->ty.IsMissing()) {
+    if (binding->var->ty.as<MissingType>().has_value()) {
       UpdateType(binding->var, GetType(new_value));
     }
     return binding;
@@ -801,7 +802,7 @@ class Normalizer : public BlockBuilderImpl, private 
ExprFunctor<Expr(const Expr&
     if (!new_value.same_as(binding->value)) {
       binding = MatchCast(binding->var, new_value, binding->ty, binding->span);
     }
-    if (binding->var->ty.IsMissing()) {
+    if (binding->var->ty.as<MissingType>().has_value()) {
       UpdateType(binding->var, binding->ty);
     }
     return binding;
@@ -865,7 +866,7 @@ class Normalizer : public BlockBuilderImpl, private 
ExprFunctor<Expr(const Expr&
       return op_map_infer_ty[op](call, ffi::GetRef<BlockBuilder>(this));
     } else {
       // derive using function parameters
-      TVM_FFI_ICHECK(!call->op->ty.IsMissing());
+      TVM_FFI_ICHECK(!call->op->ty.as<MissingType>().has_value());
       auto opt = MatchType<FuncType>(call->op);
       TVM_FFI_ICHECK(opt) << "Call->op must contains a function type";
       FuncType finfo = opt.value();
diff --git a/src/relax/ir/dependent_type.cc b/src/relax/ir/dependent_type.cc
index 6cc4070b35..231b3c9f4f 100644
--- a/src/relax/ir/dependent_type.cc
+++ b/src/relax/ir/dependent_type.cc
@@ -365,11 +365,12 @@ TVM_FFI_STATIC_INIT_BLOCK() {
 
 // Helper functions
 void UpdateType(Expr expr, Type ty) {
-  TVM_FFI_ICHECK(expr->ty.IsMissing()) << "To ensure idempotency, "
-                                       << "the expression passed to UpdateType 
"
-                                       << "must not have any prior type.  "
-                                       << "However, expression " << expr << " 
has type " << expr->ty
-                                       << ", which cannot be overwritten with 
" << ty;
+  TVM_FFI_ICHECK(expr->ty.as<MissingType>().has_value())
+      << "To ensure idempotency, "
+      << "the expression passed to UpdateType "
+      << "must not have any prior type.  "
+      << "However, expression " << expr << " has type " << expr->ty
+      << ", which cannot be overwritten with " << ty;
   expr->ty = ty;
 }
 
diff --git a/src/relax/ir/emit_te.cc b/src/relax/ir/emit_te.cc
index 24c3d2c86a..5880a41cd9 100644
--- a/src/relax/ir/emit_te.cc
+++ b/src/relax/ir/emit_te.cc
@@ -54,7 +54,8 @@ te::Tensor TETensor(Expr value, ffi::Map<tirx::Var, PrimExpr> 
tir_var_map, std::
     n->shape = std::move(shape);
     return te::PlaceholderOp(n).output(0);
   }
-  TVM_FFI_ICHECK(!value->ty.IsMissing()) << "value must be normalized and 
contain Type";
+  TVM_FFI_ICHECK(!value->ty.as<MissingType>().has_value())
+      << "value must be normalized and contain Type";
   auto* tensor_ty = GetTypeAs<TensorTypeNode>(value);
   TVM_FFI_ICHECK(tensor_ty) << "Value must be a tensor";
   auto* shape_expr = tensor_ty->shape.as<ShapeExprNode>();
diff --git a/src/relax/ir/expr.cc b/src/relax/ir/expr.cc
index b07914edce..1b28fc4e5e 100644
--- a/src/relax/ir/expr.cc
+++ b/src/relax/ir/expr.cc
@@ -647,13 +647,14 @@ Function::Function(ffi::Array<Var> params, Expr body, 
ffi::Optional<Type> ret_ty
   ffi::Array<Type> param_ty;
 
   for (const Var& param : params) {
-    TVM_FFI_ICHECK(!param->ty.IsMissing()) << "relax.Function requires params 
to contain ty";
+    TVM_FFI_ICHECK(!param->ty.as<MissingType>().has_value())
+        << "relax.Function requires params to contain ty";
     param_ty.push_back(GetType(param));
   }
 
   ffi::Optional<Type> body_ty;
 
-  if (!body->ty.IsMissing()) {
+  if (!body->ty.as<MissingType>().has_value()) {
     body_ty = GetType(body);
   }
 
@@ -720,7 +721,8 @@ Function Function::CreateEmpty(ffi::Array<Var> params, Type 
ret_ty, bool is_pure
                                Span span) {
   ffi::Array<Type> param_ty;
   for (const Var& param : params) {
-    TVM_FFI_ICHECK(!param->ty.IsMissing()) << "relax.Function requires params 
to contain ty.";
+    TVM_FFI_ICHECK(!param->ty.as<MissingType>().has_value())
+        << "relax.Function requires params to contain ty.";
     param_ty.push_back(GetType(param));
   }
 
@@ -817,7 +819,8 @@ TVM_FFI_STATIC_INIT_BLOCK() {
 
 Expr GetShapeOf(const Expr& expr) {
   // default case, to be normalized.
-  TVM_FFI_ICHECK(!expr->ty.IsMissing()) << "GetShapeOf can only be applied to 
normalized expr";
+  TVM_FFI_ICHECK(!expr->ty.as<MissingType>().has_value())
+      << "GetShapeOf can only be applied to normalized expr";
   auto* tinfo = GetTypeAs<TensorTypeNode>(expr);
 
   TVM_FFI_ICHECK(tinfo != nullptr) << "ShapeOf can only be applied to expr 
with TensorType";
diff --git a/src/relax/ir/expr_functor.cc b/src/relax/ir/expr_functor.cc
index f9e3679467..f4679abf34 100644
--- a/src/relax/ir/expr_functor.cc
+++ b/src/relax/ir/expr_functor.cc
@@ -113,7 +113,7 @@ void ExprVisitor::DefaultTypeFieldVisitor::VisitType_(const 
FuncTypeNode* op) {
 }
 
 void VisitExprDepTypeFieldIfNeeded(ExprVisitor* visitor, const Type& ty) {
-  if (!ty.IsMissing()) {
+  if (!ty.as<MissingType>().has_value()) {
     auto* ty_node = ty.as<TypeNode>();
     TVM_FFI_DCHECK(ty_node != nullptr);
     visitor->VisitExprDepTypeField(ffi::GetRef<Type>(ty_node));
@@ -521,7 +521,7 @@ Expr ExprMutatorBase::VisitExpr_(const CallNode* call_node) 
{
   }
 
   Type ret_ty = call_node->ty;
-  if (!ret_ty.IsMissing()) {
+  if (!ret_ty.as<MissingType>().has_value()) {
     ret_ty = this->VisitExprDepTypeField(ret_ty);
   }
   bool ret_ty_unchanged = ret_ty.same_as(call_node->ty);
@@ -1048,10 +1048,10 @@ ffi::Optional<Expr> ExprMutator::LookupBinding(const 
Var& var) {
 }
 
 Var ExprMutator::WithType(Var var, Type ty) {
-  TVM_FFI_ICHECK(!ty.IsMissing());
+  TVM_FFI_ICHECK(!ty.as<MissingType>().has_value());
 
   // TODO(relax-team) add TypeEqual check
-  if (!var->ty.IsMissing()) {
+  if (!var->ty.as<MissingType>().has_value()) {
     // use same-as as a quick path
     if (var->ty.same_as(ty) || ffi::StructuralEqual()(var->ty, ty)) {
       return var;
diff --git a/src/relax/op/op.cc b/src/relax/op/op.cc
index 29b268250d..52574c8492 100644
--- a/src/relax/op/op.cc
+++ b/src/relax/op/op.cc
@@ -409,7 +409,7 @@ ffi::Optional<Type> InferCallTIROutputTypeFromArguments(
       dummy_callee_ty,
       Call::Unchecked(Type::Missing(), Var("dummy_callee", dummy_callee_ty), 
dummy_args),
       BlockBuilder::Create(std::nullopt));
-  if (derived_ret_ty.IsMissing()) {
+  if (derived_ret_ty.as<MissingType>().has_value()) {
     return std::nullopt;
   }
 
@@ -1106,7 +1106,7 @@ TVM_FFI_STATIC_INIT_BLOCK() {
 Type ReturnTensorToShapeType(const CallNode* call_node) {
   const Call call = ffi::GetRef<Call>(call_node);
   TVM_FFI_ICHECK(call->args.size() == 1);
-  TVM_FFI_ICHECK(!call->args[0]->ty.IsMissing());
+  TVM_FFI_ICHECK(!call->args[0]->ty.as<MissingType>().has_value());
   const auto* tensor_ty = GetTypeAs<TensorTypeNode>(call->args[0]);
   TVM_FFI_ICHECK(tensor_ty);
   TVM_FFI_ICHECK_EQ(tensor_ty->ndim, 1)
@@ -1144,7 +1144,7 @@ TVM_FFI_STATIC_INIT_BLOCK() {
 Type ReturnShapeToTensorType(const CallNode* call_node) {
   const Call call = ffi::GetRef<Call>(call_node);
   TVM_FFI_ICHECK(call->args.size() == 1);
-  TVM_FFI_ICHECK(!call->args[0]->ty.IsMissing());
+  TVM_FFI_ICHECK(!call->args[0]->ty.as<MissingType>().has_value());
   const auto* ty = GetTypeAs<ShapeTypeNode>(call->args[0]);
   TVM_FFI_ICHECK(ty);
   int32_t ndim = ty->ndim;
@@ -1505,7 +1505,7 @@ TVM_FFI_STATIC_INIT_BLOCK() {
 Type InferToVDeviceType(const CallNode* call_node) {
   const Call call = ffi::GetRef<Call>(call_node);
   TVM_FFI_ICHECK(call->args.size() == 1);
-  TVM_FFI_ICHECK(!call->args[0]->ty.IsMissing());
+  TVM_FFI_ICHECK(!call->args[0]->ty.as<MissingType>().has_value());
   TensorType data_ty = GetUnaryInputTensorType(call);
   auto attrs = call->attrs.as<ToVDeviceAttrs>();
   VDevice vdev = attrs->dst_vdevice;
@@ -1540,7 +1540,7 @@ TVM_FFI_STATIC_INIT_BLOCK() {
 Type InferHintOnDeviceType(const CallNode* call_node) {
   const Call call = ffi::GetRef<Call>(call_node);
   TVM_FFI_ICHECK(call->args.size() == 1);
-  TVM_FFI_ICHECK(!call->args[0]->ty.IsMissing());
+  TVM_FFI_ICHECK(!call->args[0]->ty.as<MissingType>().has_value());
   TensorType data_ty = GetUnaryInputTensorType(call);
   return data_ty;
 }
diff --git a/src/relax/op/op_common.h b/src/relax/op/op_common.h
index 8d25435fe8..2b7c997aa6 100644
--- a/src/relax/op/op_common.h
+++ b/src/relax/op/op_common.h
@@ -115,7 +115,7 @@ namespace detail {
 /*! \brief Implementation helper for GetArgType */
 template <typename ArgType>
 ArgType GetArgTypeByIndex(const Call& call, const Op& op, size_t index) {
-  if (call->args[index]->ty.IsMissing()) {
+  if (call->args[index]->ty.as<MissingType>().has_value()) {
     TVM_FFI_VISIT_THROW(InternalError, call)
         << op << " op should have arguments with defined Type.  "
         << "However, args[" << index << "] has undefined type.";
diff --git a/src/relax/script/ir_builder/ir.cc 
b/src/relax/script/ir_builder/ir.cc
index 172a490e59..df642ee457 100644
--- a/src/relax/script/ir_builder/ir.cc
+++ b/src/relax/script/ir_builder/ir.cc
@@ -274,7 +274,7 @@ tvm::Var Emit(const tvm::relax::Expr& expr, const 
ffi::Optional<tvm::Type>& anno
   const tvm::relax::BlockBuilder& block_builder = GetBlockBuilder();
   if (annotate_ty.has_value()) {
     const auto& ty = annotate_ty.value();
-    if (expr->ty.IsMissing()) {
+    if (expr->ty.as<MissingType>().has_value()) {
       tvm::relax::UpdateType(expr, ty);
     } else {
       TVM_FFI_ICHECK(tvm::relax::TypeBaseCheck(ty, GetType(expr)) !=
diff --git a/src/relax/script/printer/binding.cc 
b/src/relax/script/printer/binding.cc
index 5db6e124de..4a96a4beab 100644
--- a/src/relax/script/printer/binding.cc
+++ b/src/relax/script/printer/binding.cc
@@ -45,7 +45,8 @@ ffi::Optional<ExprDoc> 
MatchCastDocTranslate(DocTranslatorObj* d, ffi::AnyView i
           ->Call({d->Translate(binding->value).value(), 
d->Translate(binding->ty).value()});
   IdDoc lhs = VarDoc(d, binding->var);
   ffi::Optional<ExprDoc> annotation = std::nullopt;
-  if (!binding->var->ty.IsMissing()) annotation = 
d->Translate(binding->var->ty).value();
+  if (!binding->var->ty.as<MissingType>().has_value())
+    annotation = d->Translate(binding->var->ty).value();
   if (!d->GetExtraConfig<bool>("relax.show_all_ty", true)) annotation = 
std::nullopt;
   d->Emit(AssignDoc(lhs, rhs, annotation), 
ffi::GetRef<ffi::ObjectRef>(binding));
   return std::nullopt;
@@ -112,14 +113,14 @@ ffi::Optional<ExprDoc> 
VarBindingDocTranslate(DocTranslatorObj* d, ffi::AnyView
       }
     }
   }
-  bool inferable = inferred.has_value() && !inferred.value().IsMissing() &&
+  bool inferable = inferred.has_value() && 
!inferred.value().as<MissingType>().has_value() &&
                    ffi::StructuralEqual()(binding->var->ty, inferred.value());
   bool explicit_output_type = output_type_argument && inferable;
   // Primitive aliases need their annotation. Without context-free inference,
   // keep the binding type and let the parser's deferred inference use it.
   bool elide_annotation = infer_vdevice || explicit_output_type ||
                           (!show_all_ty && 
!binding->var->ty.as<PrimTypeNode>() && inferable);
-  if (!binding->var->ty.IsMissing() && !elide_annotation) {
+  if (!binding->var->ty.as<MissingType>().has_value() && !elide_annotation) {
     annotation = d->Translate(binding->var->ty).value();
   }
   d->Emit(AssignDoc(lhs, rhs.value(), annotation), 
ffi::GetRef<ffi::ObjectRef>(binding));
@@ -148,7 +149,7 @@ ffi::Optional<ExprDoc> IfDocTranslate(DocTranslatorObj* d, 
ffi::AnyView input,
   Var var = ffi::GetRef<Var>(static_cast<const VarNode*>(destination));
   ffi::Optional<IdDoc> lhs = VarDoc(d, var);
   ffi::Optional<ExprDoc> annotation = std::nullopt;
-  if (!var->ty.IsMissing()) annotation = d->Translate(var->ty).value();
+  if (!var->ty.as<MissingType>().has_value()) annotation = 
d->Translate(var->ty).value();
   ExprDoc condition = d->Translate(branch->cond).value();
   d->Emit(IfDoc(condition, RelaxSeqBody(d, branch->true_branch.get(), lhs, 
annotation, destination),
                 RelaxSeqBody(d, branch->false_branch.get(), lhs, annotation, 
destination)),
diff --git a/src/relax/script/printer/call.cc b/src/relax/script/printer/call.cc
index d6ffdd3dde..a9f1f6a812 100644
--- a/src/relax/script/printer/call.cc
+++ b/src/relax/script/printer/call.cc
@@ -62,7 +62,7 @@ namespace {
 // Relax's named constructors defer result typing until a binding supplies it.
 // A standalone named call therefore reconstructs only a missing stored result.
 bool HasRelaxCallResult(const CallNode* call, const ffi::Object* destination) {
-  if (call->ty.IsMissing()) return true;
+  if (call->ty.as<MissingType>().has_value()) return true;
   const auto* var = destination && destination->IsInstance<VarNode>()
                         ? static_cast<const VarNode*>(destination)
                         : nullptr;
@@ -266,8 +266,9 @@ ffi::Optional<ExprDoc> 
CallDefaultDocTranslate(DocTranslatorObj* d, ffi::AnyView
     return d->Translate(call->op).value()->Call(args);
   }
   ffi::Optional<Type> inferred = std::nullopt;
-  if (call->op.as<Op>() && std::all_of(call->args.begin(), call->args.end(),
-                                       [](const Expr& arg) { return 
!arg->ty.IsMissing(); })) {
+  if (call->op.as<Op>() && std::all_of(call->args.begin(), call->args.end(), 
[](const Expr& arg) {
+        return !arg->ty.as<MissingType>().has_value();
+      })) {
     try {
       inferred = Call::ReinferType(call);
     } catch (const ffi::Error&) {
diff --git a/src/relax/script/printer/function.cc 
b/src/relax/script/printer/function.cc
index 3e7eceb2f2..d0ac72a071 100644
--- a/src/relax/script/printer/function.cc
+++ b/src/relax/script/printer/function.cc
@@ -55,14 +55,14 @@ ffi::Optional<ExprDoc> 
FunctionDocTranslate(DocTranslatorObj* d, ffi::AnyView in
   for (const Var& param : func->params) {
     IdDoc lhs = param_ids[param_index++];
     ffi::Optional<ExprDoc> annotation = std::nullopt;
-    if (!param->ty.IsMissing()) annotation = d->Translate(param->ty).value();
+    if (!param->ty.as<MissingType>().has_value()) annotation = 
d->Translate(param->ty).value();
     AssignDoc argument(lhs, std::nullopt, annotation);
     d->RecordOrigin(argument, param);
     args.push_back(argument);
   }
   auto signature_candidates = CopyImplicitDefs(d);
   ffi::Optional<ExprDoc> ret_type = std::nullopt;
-  if (!func->ret_ty.IsMissing()) {
+  if (!func->ret_ty.as<MissingType>().has_value()) {
     ret_type = d->Translate(func->ret_ty).value();
   }
   ffi::Array<ffi::String> decorator_keys;
diff --git a/src/relax/transform/decompose_ops.cc 
b/src/relax/transform/decompose_ops.cc
index f0ee758e44..66ce48ca92 100644
--- a/src/relax/transform/decompose_ops.cc
+++ b/src/relax/transform/decompose_ops.cc
@@ -144,7 +144,7 @@ Expr DecomposeLayerNorm(const Call& call) {
 }
 
 Expr TensorToShape(const Call& call_node, const BlockBuilder& builder) {
-  TVM_FFI_ICHECK(!call_node->ty.IsMissing());
+  TVM_FFI_ICHECK(!call_node->ty.as<MissingType>().has_value());
   Expr expr = call_node->args[0];
   const ShapeTypeNode* ty = GetTypeAs<ShapeTypeNode>(call_node);
   TVM_FFI_ICHECK(ty);
diff --git a/src/relax/transform/normalize.cc b/src/relax/transform/normalize.cc
index 0757b4f290..765b4df946 100644
--- a/src/relax/transform/normalize.cc
+++ b/src/relax/transform/normalize.cc
@@ -147,7 +147,7 @@ class NormalizeMutator : public ExprMutatorBase {
 
   void VisitBinding_(const VarBindingNode* binding) {
     Expr new_value = this->VisitExpr(binding->value);
-    if (binding->var->ty.IsMissing()) {
+    if (binding->var->ty.as<MissingType>().has_value()) {
       UpdateType(binding->var, GetType(new_value));
     }
 
diff --git a/src/relax/transform/rewrite_dataflow_reshape.cc 
b/src/relax/transform/rewrite_dataflow_reshape.cc
index 90bc41fc2b..630e9a538f 100644
--- a/src/relax/transform/rewrite_dataflow_reshape.cc
+++ b/src/relax/transform/rewrite_dataflow_reshape.cc
@@ -125,7 +125,8 @@ class DataflowReshapeRewriter : public ExprMutator {
     // as the number of elements in the result. There are operators that could 
have a reshape
     // pattern that don't meet this requirement (e.g. strided_slice), and they 
should not be
     // converted to reshape.
-    TVM_FFI_ICHECK(!inp->ty.IsMissing() && !call->ty.IsMissing());
+    TVM_FFI_ICHECK(!inp->ty.as<MissingType>().has_value() &&
+                   !call->ty.as<MissingType>().has_value());
     TensorType inp_ty = inp->ty.as_or_throw<TensorType>();
     TensorType res_ty = call->ty.as_or_throw<TensorType>();
 
diff --git a/src/relax/transform/update_vdevice.cc 
b/src/relax/transform/update_vdevice.cc
index 860596c611..e4650ce676 100644
--- a/src/relax/transform/update_vdevice.cc
+++ b/src/relax/transform/update_vdevice.cc
@@ -43,7 +43,7 @@ class VDeviceMutator : public ExprMutator {
 
   Expr VisitExpr(const Expr& expr) final {
     auto visited_expr = ExprMutator::VisitExpr(expr);
-    if (!visited_expr->ty.IsMissing()) {
+    if (!visited_expr->ty.as<MissingType>().has_value()) {
       auto* tinfo = GetTypeAs<TensorTypeNode>(visited_expr);
       bool unchanged = true;
       if (tinfo != nullptr) {
diff --git a/src/relax/transform/utils.h b/src/relax/transform/utils.h
index 3424b4b364..328a8f72bf 100644
--- a/src/relax/transform/utils.h
+++ b/src/relax/transform/utils.h
@@ -243,7 +243,8 @@ class SymbolicVarRenewMutator : public ExprMutator {
   using relax::ExprMutator::VisitExpr_;
 
   static Var CopyVar(const VarNode* op, Type ty) {
-    ffi::Optional<Type> ty_annotation = ty.IsMissing() ? std::nullopt : 
ffi::Optional<Type>(ty);
+    ffi::Optional<Type> ty_annotation =
+        ty.as<MissingType>().has_value() ? std::nullopt : 
ffi::Optional<Type>(ty);
     if (op->IsInstance<DataflowVarNode>()) {
       return DataflowVar(op->name, std::move(ty_annotation), op->span);
     }
@@ -251,7 +252,7 @@ class SymbolicVarRenewMutator : public ExprMutator {
   }
 
   Type RenewType(const VarNode* op) {
-    return op->ty.IsMissing() ? op->ty : this->VisitExprDepTypeField(op->ty);
+    return op->ty.as<MissingType>().has_value() ? op->ty : 
this->VisitExprDepTypeField(op->ty);
   }
 
   Var RenewVarDefinition(const VarNode* op) {
diff --git a/src/script/ir_builder/ir.cc b/src/script/ir_builder/ir.cc
index 1a667fb216..364f513505 100644
--- a/src/script/ir_builder/ir.cc
+++ b/src/script/ir_builder/ir.cc
@@ -54,7 +54,7 @@ IRModuleFrame IRModule() {
 // each dialect registers its own handler that maps a function of that
 // type to the appropriate ty.
 inline ffi::Optional<Type> GetGlobalVarType(const BaseFunc& func) {
-  if (!func->ty.IsMissing()) {
+  if (!func->ty.as<MissingType>().has_value()) {
     return func->ty;
   }
   // Registry: "script.ir_builder.decl_function.<type-key>" — per-function-kind
diff --git a/src/script/printer/ir/prim_type.cc 
b/src/script/printer/ir/prim_type.cc
index bf64ce7965..b3ef7ccec3 100644
--- a/src/script/printer/ir/prim_type.cc
+++ b/src/script/printer/ir/prim_type.cc
@@ -32,6 +32,16 @@ namespace details {
 
 namespace {
 
+ffi::Optional<ExprDoc> MissingTypeDocTranslate(DocTranslatorObj*, ffi::AnyView,
+                                               const ffi::Object*) {
+  return NamespaceDoc("ir")->Attr("MissingType")->Call({});
+}
+
+TVM_FFI_STATIC_INIT_BLOCK() {
+  ffi::reflection::TypeAttrDef<MissingTypeNode>().attr(
+      kDocTranslate, FDocTranslate::FromNative<&MissingTypeDocTranslate>());
+}
+
 ffi::Optional<ExprDoc> IntImmDocTranslate(DocTranslatorObj* d, ffi::AnyView 
input,
                                           const ffi::Object*) {
   const auto* imm =
diff --git a/src/script/printer/ir/utils.cc b/src/script/printer/ir/utils.cc
index 2840ddcb3b..23769909ed 100644
--- a/src/script/printer/ir/utils.cc
+++ b/src/script/printer/ir/utils.cc
@@ -107,8 +107,8 @@ ExprDoc TypeValueImpl(DocTranslatorObj* d, const Type& 
type, bool dtype_literal)
         ->Attr("FuncType")
         ->Call({ListDoc(args), TypeValue(d, function->ret_type, false)});
   }
-  if (type.IsMissing()) {
-    return NamespaceDoc("ir")->Attr("Type")->Attr("missing")->Call({});
+  if (type.as<MissingType>().has_value()) {
+    return NamespaceDoc("ir")->Attr("MissingType")->Call({});
   }
   if (auto pointer = type.as<PointerTypeNode>()) {
     if (auto primitive = pointer->element_type.as<PrimType>();
diff --git a/src/script/printer/script_printer.cc 
b/src/script/printer/script_printer.cc
index 4c6ce2337c..3ad12527c8 100644
--- a/src/script/printer/script_printer.cc
+++ b/src/script/printer/script_printer.cc
@@ -63,6 +63,7 @@ TVM_FFI_STATIC_INIT_BLOCK() {
   RegisterScriptRepr<GlobalVarNode>();
   RegisterScriptRepr<IRModuleNode>();
   RegisterScriptRepr<IntImmNode>();
+  RegisterScriptRepr<MissingTypeNode>();
   RegisterScriptRepr<PointerTypeNode>();
   RegisterScriptRepr<PrimTypeNode>();
   RegisterScriptRepr<RangeNode>();
diff --git a/src/sym/z3_prover.cc b/src/sym/z3_prover.cc
index 143e698d47..288de36201 100644
--- a/src/sym/z3_prover.cc
+++ b/src/sym/z3_prover.cc
@@ -743,7 +743,7 @@ class Z3Prover::Impl : tvm::ExprFunctor<z3::expr(const 
Expr&)> {
   /// @brief Check if the expression type is supported by z3 integer 
operations.
   static bool IsZ3SupportedExpr(const ExprNode* expr) {
     TVM_FFI_DCHECK(expr != nullptr);
-    TVM_FFI_DCHECK(!expr->ExprNode::ty.IsMissing());
+    TVM_FFI_DCHECK(!expr->ExprNode::ty.as<MissingType>().has_value());
     PrimType prim_ty = expr->ExprNode::ty.as_or_throw<PrimType>();
     return (prim_ty->dtype.code == 
static_cast<uint8_t>(DLDataTypeCode::kDLInt) ||
             prim_ty->dtype.code == 
static_cast<uint8_t>(DLDataTypeCode::kDLUInt) ||
diff --git a/src/target/llvm/codegen_llvm.cc b/src/target/llvm/codegen_llvm.cc
index f1cd7aa728..3b9abaccd5 100644
--- a/src/target/llvm/codegen_llvm.cc
+++ b/src/target/llvm/codegen_llvm.cc
@@ -2308,7 +2308,7 @@ void CodeGenLLVM::Dispatch_(const BindNode* op) {
   // Therefore, to have the correct LLVM type for pointers, we may
   // need to introduce a pointer-cast, even though pointer-to-pointer
   // casts are not expressible with the `prim::CastNode`.
-  if (is_pointer && !v->ty.IsMissing()) {
+  if (is_pointer && !v->ty.as<MissingType>().has_value()) {
     TVM_FFI_ICHECK(op->value->ty.as<PointerTypeNode>())
         << "Variable " << op->var << " is a pointer with type " << op->value
         << ", but is being bound to expression with type " << op->value->ty;
diff --git a/src/tirx/ir/buffer_common.h b/src/tirx/ir/buffer_common.h
index 26138a7cbd..c728e3fad0 100644
--- a/src/tirx/ir/buffer_common.h
+++ b/src/tirx/ir/buffer_common.h
@@ -41,7 +41,7 @@ namespace tirx {
  * type. Otherwise the object is nullopt.
  */
 inline std::optional<PrimType> GetPointerType(const Type& type) {
-  if (!type.IsMissing()) {
+  if (!type.as<MissingType>().has_value()) {
     if (auto* ptr_type = type.as<PointerTypeNode>()) {
       if (auto* prim_type = ptr_type->element_type.as<PrimTypeNode>()) {
         return ffi::GetRef<PrimType>(prim_type);
diff --git a/src/tirx/ir/function.cc b/src/tirx/ir/function.cc
index 4cd2282bc1..3a745ac477 100644
--- a/src/tirx/ir/function.cc
+++ b/src/tirx/ir/function.cc
@@ -146,7 +146,7 @@ TVM_FFI_INLINE ffi::Expected<ffi::UnchangedOr<ffi::Any>> 
PrimFuncMaybeInplaceMut
 PrimFunc::PrimFunc(ffi::Array<tirx::Var> params, ffi::Optional<Stmt> body, 
Type ret_type,
                    DictAttrs attrs, Span span)
     : BaseFunc(ffi::UnsafeInit{}) {
-  if (ret_type.IsMissing()) {
+  if (ret_type.as<MissingType>().has_value()) {
     ret_type = VoidType();
   }
 
diff --git a/src/tirx/op/op.cc b/src/tirx/op/op.cc
index 877e5f583e..4ca6bedf32 100644
--- a/src/tirx/op/op.cc
+++ b/src/tirx/op/op.cc
@@ -67,7 +67,7 @@ Type GetType(const PrimExpr& expr) {
   if (auto* ptr = expr.as<tirx::VarNode>()) {
     // If Var has a more refined type annotation,
     // return the type anotation
-    if (!ptr->ty.IsMissing()) {
+    if (!ptr->ty.as<MissingType>().has_value()) {
       return ptr->ty;
     }
   }
diff --git a/src/tirx/script/printer/expr.cc b/src/tirx/script/printer/expr.cc
index 405729ea02..f92676e1ea 100644
--- a/src/tirx/script/printer/expr.cc
+++ b/src/tirx/script/printer/expr.cc
@@ -91,7 +91,7 @@ TVM_FFI_STATIC_INIT_BLOCK() {
 bool CanTranslateExplicitResultCall(const CallNode* call) {
   return !call->attrs.defined() && call->ty_args.empty() && 
call->ty.as<PrimType>() &&
          std::all_of(call->args.begin(), call->args.end(), [](const Expr& arg) 
{
-           return !arg->ty.IsMissing() && !arg.as<TensorRegionNode>();
+           return !arg->ty.as<MissingType>().has_value() && 
!arg.as<TensorRegionNode>();
          });
 }
 
diff --git a/src/tirx/script/printer/function.cc 
b/src/tirx/script/printer/function.cc
index 4da3b582f8..09c2a5f8d6 100644
--- a/src/tirx/script/printer/function.cc
+++ b/src/tirx/script/printer/function.cc
@@ -134,7 +134,7 @@ void PrintPrimFunc(DocTranslatorObj* d, const 
tirx::PrimFuncNode* func, ExprDoc
           
ExprStmtDoc(NamespaceDoc("tirx")->Attr("func_attr")->Call({DictDoc(keys, 
values)})));
     }
     ffi::Optional<ExprDoc> ret_type = std::nullopt;
-    if (!func->ret_type.IsMissing() && !IsVoidType(func->ret_type)) {
+    if (!func->ret_type.as<MissingType>().has_value() && 
!IsVoidType(func->ret_type)) {
       ret_type = d->Translate(func->ret_type).value();
     }
     doc = FunctionDoc(IdDoc(name), args, {decorator}, ret_type, body);
diff --git a/tests/cpp/expr_test.cc b/tests/cpp/expr_test.cc
index 69d692c682..748f0f4d37 100644
--- a/tests/cpp/expr_test.cc
+++ b/tests/cpp/expr_test.cc
@@ -70,8 +70,8 @@ TEST(Expr, RequiredIRReferences) {
   CheckRequiredIRReference<relax::Function>();
   CheckRequiredIRReference<Type>();
   CheckRequiredIRReference<PrimType>();
-  EXPECT_TRUE(Type::Missing().IsMissing());
-  EXPECT_TRUE(ffi::Any(Type::Missing()).cast<Type>().IsMissing());
+  EXPECT_TRUE(Type::Missing().as<MissingType>().has_value());
+  
EXPECT_TRUE(ffi::Any(Type::Missing()).cast<Type>().as<MissingType>().has_value());
   
EXPECT_THROW(ffi::Array<ffi::Any>({ffi::Any()}).as_or_throw<ffi::Array<Expr>>(),
 ffi::Error);
 }
 
diff --git a/tests/python/ir/test_ir_type.py b/tests/python/ir/test_ir_type.py
index bda4e5e04e..7bac117a32 100644
--- a/tests/python/ir/test_ir_type.py
+++ b/tests/python/ir/test_ir_type.py
@@ -33,7 +33,7 @@ def test_missing_type():
     missing = tvm.ir.Type.missing()
 
     assert isinstance(missing, tvm.ir.Type)
-    assert missing.is_missing()
+    assert isinstance(missing, tvm.ir.MissingType)
 
 
 def test_prim_type():
diff --git a/tests/python/ir/test_nonnullable_ir.py 
b/tests/python/ir/test_nonnullable_ir.py
index ebdc3ce725..dcbb69ba11 100644
--- a/tests/python/ir/test_nonnullable_ir.py
+++ b/tests/python/ir/test_nonnullable_ir.py
@@ -59,7 +59,7 @@ def test_optional_statement_fields_and_roundtrip():
 def test_offset_default_and_missing_type_remain_values():
     tensor = tvm.tirx.decl_tensor((8,), "float32", elem_offset=None)
     assert int(tensor.ty.elem_offset) == 0
-    assert tvm.ir.Type.missing().is_missing()
+    assert isinstance(tvm.ir.Type.missing(), tvm.ir.MissingType)
     # False and zero are valid primitive expressions, not absence.
     assert int(tvm.tirx.Evaluate(False).value) == 0
     assert int(tvm.tirx.Evaluate(0).value) == 0
diff --git a/tests/python/relax/test_analysis_well_formed.py 
b/tests/python/relax/test_analysis_well_formed.py
index 84c1905572..1d925b8114 100644
--- a/tests/python/relax/test_analysis_well_formed.py
+++ b/tests/python/relax/test_analysis_well_formed.py
@@ -172,7 +172,7 @@ def test_unchecked_call_constructor():
     call = tvm.ir.Call.unchecked("relax.add", [x], attrs={"key": 1}, span=span)
     assert isinstance(call, tvm.ir.Call)
     assert call.op.same_as(op)
-    assert call.ty.is_missing()
+    assert isinstance(call.ty, tvm.ir.MissingType)
     assert call.span.same_as(span)
     assert isinstance(call.attrs, tvm.ir.DictAttrs)
     assert len(call.ty_args) == 0
diff --git a/tests/python/relax/test_expr.py b/tests/python/relax/test_expr.py
index 8dbed8951e..feb1f3f570 100644
--- a/tests/python/relax/test_expr.py
+++ b/tests/python/relax/test_expr.py
@@ -44,7 +44,7 @@ def _check_json_roundtrip(x):
 
 def _check_type_missing(ty):
     assert isinstance(ty, tvm.ir.Type)
-    assert ty.is_missing()
+    assert isinstance(ty, tvm.ir.MissingType)
 
 
 def test_var() -> None:
diff --git a/tests/python/script/test_constructor_contracts.py 
b/tests/python/script/test_constructor_contracts.py
index 33dbf12fff..53627eca60 100644
--- a/tests/python/script/test_constructor_contracts.py
+++ b/tests/python/script/test_constructor_contracts.py
@@ -32,7 +32,7 @@ def test_call_type_and_validation_contract():
     for constructor in (ir.Call, I.Call):
         call = constructor("tirx.exp", [x], span=span)
         assert isinstance(call, I.Call)
-        assert call.ty.is_missing()
+        assert isinstance(call.ty, ir.MissingType)
         assert call.span.same_as(span)
         ir.assert_structural_equal(
             constructor("tirx.exp", [x], ty="float32"),

Reply via email to