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 86c4042 feat: add `ffi.GetInvalidObject` global function for MISSING
singleton (#447)
86c4042 is described below
commit 86c4042d66bf432a3c4a217be1eeab3568329b5b
Author: Junru Shao <[email protected]>
AuthorDate: Sun Feb 15 04:15:57 2026 -0800
feat: add `ffi.GetInvalidObject` global function for MISSING singleton
(#447)
C++ changes:
- Register `ffi.GetInvalidObject` global function in container.cc
- Remove `ffi.MapGetMissingObject` in favor of the new name
Python changes:
- Initialize `MISSING` in core.pyx via `_get_global_func` after
`Function` is registered
- Re-export `MISSING` in container.py from core
- Add `MISSING: Object` to core.pyi type stub
- Update _ffi_api.py stubs and __all__
Tests:
- Add `test_missing_object` verifying singleton identity across imports
and Map.get() integration
---
python/tvm_ffi/_ffi_api.py | 6 +++---
python/tvm_ffi/container.py | 2 +-
python/tvm_ffi/core.pyi | 1 +
python/tvm_ffi/cython/core.pyx | 3 +++
src/ffi/container.cc | 17 +++++++++--------
tests/python/test_container.py | 15 +++++++++++++++
6 files changed, 32 insertions(+), 12 deletions(-)
diff --git a/python/tvm_ffi/_ffi_api.py b/python/tvm_ffi/_ffi_api.py
index a76188d..92606c9 100644
--- a/python/tvm_ffi/_ffi_api.py
+++ b/python/tvm_ffi/_ffi_api.py
@@ -24,7 +24,7 @@ from .registry import init_ffi_api as _FFI_INIT_FUNC
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from collections.abc import Mapping, Sequence
- from tvm_ffi import Module, Object
+ from tvm_ffi import Module
from tvm_ffi.access_path import AccessPath
from typing import Any, Callable
# isort: on
@@ -42,6 +42,7 @@ if TYPE_CHECKING:
def Bytes(_0: bytes, /) -> bytes: ...
def FromJSONGraph(_0: Any, /) -> Any: ...
def FromJSONGraphString(_0: str, /) -> Any: ...
+ def GetInvalidObject() -> Any: ...
def FunctionListGlobalNamesFunctor() -> Callable[..., Any]: ...
def FunctionRemoveGlobal(_0: str, /) -> bool: ...
def GetFirstStructuralMismatch(_0: Any, _1: Any, _2: bool, _3: bool, /) ->
tuple[AccessPath, AccessPath] | None: ...
@@ -66,7 +67,6 @@ if TYPE_CHECKING:
def MapForwardIterFunctor(_0: Mapping[Any, Any], /) -> Callable[..., Any]:
...
def MapGetItem(_0: Mapping[Any, Any], _1: Any, /) -> Any: ...
def MapGetItemOrMissing(_0: Mapping[Any, Any], _1: Any, /) -> Any: ...
- def MapGetMissingObject() -> Object: ...
def MapSize(_0: Mapping[Any, Any], /) -> int: ...
def ModuleClearImports(_0: Module, /) -> None: ...
def ModuleGetFunction(_0: Module, _1: str, _2: bool, /) -> Callable[...,
Any] | None: ...
@@ -105,6 +105,7 @@ __all__ = [
"FunctionRemoveGlobal",
"GetFirstStructuralMismatch",
"GetGlobalFuncMetadata",
+ "GetInvalidObject",
"GetRegisteredTypeKeys",
"List",
"ListAppend",
@@ -125,7 +126,6 @@ __all__ = [
"MapForwardIterFunctor",
"MapGetItem",
"MapGetItemOrMissing",
- "MapGetMissingObject",
"MapSize",
"ModuleClearImports",
"ModuleGetFunction",
diff --git a/python/tvm_ffi/container.py b/python/tvm_ffi/container.py
index 1b55fc8..2a358d2 100644
--- a/python/tvm_ffi/container.py
+++ b/python/tvm_ffi/container.py
@@ -79,7 +79,7 @@ K = TypeVar("K")
V = TypeVar("V")
_DefaultT = TypeVar("_DefaultT")
-MISSING = _ffi_api.MapGetMissingObject()
+from .core import MISSING
def getitem_helper(
diff --git a/python/tvm_ffi/core.pyi b/python/tvm_ffi/core.pyi
index 2cee79c..3ad1fff 100644
--- a/python/tvm_ffi/core.pyi
+++ b/python/tvm_ffi/core.pyi
@@ -24,6 +24,7 @@ from enum import IntEnum
from typing import Any, Callable
# Public module-level variables referenced by Python code
+MISSING: Object
ERROR_NAME_TO_TYPE: dict[str, type]
ERROR_TYPE_TO_NAME: dict[type, str]
diff --git a/python/tvm_ffi/cython/core.pyx b/python/tvm_ffi/cython/core.pyx
index 6ffdc7e..b80beda 100644
--- a/python/tvm_ffi/cython/core.pyx
+++ b/python/tvm_ffi/cython/core.pyx
@@ -38,3 +38,6 @@ include "./tensor.pxi"
_register_object_by_index(kTVMFFITensor, Tensor)
include "./function.pxi"
_register_object_by_index(kTVMFFIFunction, Function)
+
+# Global invalid/missing object singleton
+MISSING = _get_global_func("ffi.GetInvalidObject", False)()
diff --git a/src/ffi/container.cc b/src/ffi/container.cc
index f3171a8..6ebc7c0 100644
--- a/src/ffi/container.cc
+++ b/src/ffi/container.cc
@@ -153,14 +153,15 @@ TVM_FFI_STATIC_INIT_BLOCK() {
[](const ffi::MapObj* n) -> ffi::Function {
return ffi::Function::FromTyped(MapForwardIterFunctor(n->begin(),
n->end()));
})
- .def("ffi.MapGetMissingObject", GetMissingObject)
- .def("ffi.MapGetItemOrMissing", [](const ffi::MapObj* n, const Any& k)
-> Any {
- try {
- return n->at(k);
- } catch (const tvm::ffi::Error& e) {
- return GetMissingObject();
- }
- });
+ .def("ffi.MapGetItemOrMissing",
+ [](const ffi::MapObj* n, const Any& k) -> Any {
+ try {
+ return n->at(k);
+ } catch (const tvm::ffi::Error& e) {
+ return GetMissingObject();
+ }
+ })
+ .def("ffi.GetInvalidObject", []() -> ObjectRef { return
GetMissingObject(); });
}
} // namespace ffi
} // namespace tvm
diff --git a/tests/python/test_container.py b/tests/python/test_container.py
index ec289cd..bb6384e 100644
--- a/tests/python/test_container.py
+++ b/tests/python/test_container.py
@@ -23,6 +23,8 @@ from typing import Any
import pytest
import tvm_ffi
from tvm_ffi import testing
+from tvm_ffi.container import MISSING as CONTAINER_MISSING
+from tvm_ffi.core import MISSING
if sys.version_info >= (3, 9):
# PEP 585 generics
@@ -499,3 +501,16 @@ def test_seq_cross_conv_incompatible_array_to_list() ->
None:
arr = tvm_ffi.Array(["not", "ints"])
with pytest.raises(TypeError):
testing.schema_id_list_int(arr) # type: ignore[arg-type]
+
+
+def test_missing_object() -> None:
+ """Test that MISSING is a valid singleton and works with Map.get()."""
+ # MISSING should be an Object instance
+ assert isinstance(MISSING, tvm_ffi.Object)
+ # MISSING should be the same object across imports
+ assert MISSING.same_as(CONTAINER_MISSING)
+ # Map.get() should use MISSING internally
+ m = tvm_ffi.Map({"a": 1})
+ assert m.get("a") == 1
+ assert m.get("b") is None
+ assert m.get("b", 42) == 42