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

junrushao 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 5bc7fcd  feat: add array __contains__ support (#377)
5bc7fcd is described below

commit 5bc7fcdebd0fae2d3650a5b18ae69154c1c92d70
Author: Guan-Ming (Wesley) Chiu <[email protected]>
AuthorDate: Fri Jan 2 16:52:24 2026 +0800

    feat: add array __contains__ support (#377)
    
    ## Why
    
    Python arrays lack native in operator support, requiring manual
    iteration to check if a value exists.
    
    ## How
    
    - Add `ffi.ArrayContains` C++ FFI function
    - Implement `__contains__` method in Python Array class
    - Add tests and type stub for Python
---
 python/tvm_ffi/_ffi_api.py     |  2 ++
 python/tvm_ffi/container.py    |  4 ++++
 src/ffi/container.cc           |  6 ++++++
 tests/cpp/test_array.cc        | 20 ++++++++++++++++++++
 tests/python/test_container.py | 21 +++++++++++++++++++++
 5 files changed, 53 insertions(+)

diff --git a/python/tvm_ffi/_ffi_api.py b/python/tvm_ffi/_ffi_api.py
index 49fbf68..9678fd0 100644
--- a/python/tvm_ffi/_ffi_api.py
+++ b/python/tvm_ffi/_ffi_api.py
@@ -38,6 +38,7 @@ if TYPE_CHECKING:
     def Array(*args: Any) -> Any: ...
     def ArrayGetItem(_0: Sequence[Any], _1: int, /) -> Any: ...
     def ArraySize(_0: Sequence[Any], /) -> int: ...
+    def ArrayContains(_0: Sequence[Any], _1: Any, /) -> bool: ...
     def Bytes(_0: bytes, /) -> bytes: ...
     def FromJSONGraph(_0: Any, /) -> Any: ...
     def FromJSONGraphString(_0: str, /) -> Any: ...
@@ -81,6 +82,7 @@ if TYPE_CHECKING:
 __all__ = [
     # tvm-ffi-stubgen(begin): __all__
     "Array",
+    "ArrayContains",
     "ArrayGetItem",
     "ArraySize",
     "Bytes",
diff --git a/python/tvm_ffi/container.py b/python/tvm_ffi/container.py
index bda4c57..d2aff29 100644
--- a/python/tvm_ffi/container.py
+++ b/python/tvm_ffi/container.py
@@ -188,6 +188,10 @@ class Array(core.Object, Sequence[T]):
             return type(self).__name__ + "(chandle=None)"
         return "[" + ", ".join([x.__repr__() for x in self]) + "]"
 
+    def __contains__(self, value: object) -> bool:
+        """Check if the array contains a value."""
+        return _ffi_api.ArrayContains(self, value)
+
     def __add__(self, other: Iterable[T]) -> Array[T]:
         """Concatenate two arrays."""
         return type(self)(itertools.chain(self, other))
diff --git a/src/ffi/container.cc b/src/ffi/container.cc
index 57eda37..dd1d004 100644
--- a/src/ffi/container.cc
+++ b/src/ffi/container.cc
@@ -70,6 +70,12 @@ TVM_FFI_STATIC_INIT_BLOCK() {
       .def("ffi.ArrayGetItem", [](const ffi::ArrayObj* n, int64_t i) -> Any { 
return n->at(i); })
       .def("ffi.ArraySize",
            [](const ffi::ArrayObj* n) -> int64_t { return 
static_cast<int64_t>(n->size()); })
+      .def("ffi.ArrayContains",
+           [](const ffi::ArrayObj* n, const Any& value) -> bool {
+             AnyEqual eq;
+             return std::any_of(n->begin(), n->end(),
+                                [&](const Any& elem) { return eq(elem, value); 
});
+           })
       .def_packed("ffi.Map",
                   [](ffi::PackedArgs args, Any* ret) {
                     TVM_FFI_ICHECK_EQ(args.size() % 2, 0);
diff --git a/tests/cpp/test_array.cc b/tests/cpp/test_array.cc
index 0204aa1..a86cc29 100644
--- a/tests/cpp/test_array.cc
+++ b/tests/cpp/test_array.cc
@@ -19,6 +19,7 @@
 #include <gtest/gtest.h>
 #include <tvm/ffi/container/array.h>
 #include <tvm/ffi/function.h>
+#include <tvm/ffi/string.h>
 
 #include "./testing_object.h"
 
@@ -292,4 +293,23 @@ TEST(Array, Upcast) {
   static_assert(details::type_contains_v<Any, Array<float>>);
 }
 
+TEST(Array, Contains) {
+  Function f = Function::GetGlobalRequired("ffi.ArrayContains");
+
+  Array<int> arr = {1, 2, 3, 4, 5};
+  EXPECT_TRUE(f(arr, 3).cast<bool>());
+  EXPECT_TRUE(f(arr, 1).cast<bool>());
+  EXPECT_TRUE(f(arr, 5).cast<bool>());
+  EXPECT_FALSE(f(arr, 10).cast<bool>());
+  EXPECT_FALSE(f(arr, 0).cast<bool>());
+
+  Array<int> empty_arr;
+  EXPECT_FALSE(f(empty_arr, 1).cast<bool>());
+
+  Array<String> str_arr = {String("hello"), String("world")};
+  EXPECT_TRUE(f(str_arr, String("hello")).cast<bool>());
+  EXPECT_TRUE(f(str_arr, String("world")).cast<bool>());
+  EXPECT_FALSE(f(str_arr, String("foo")).cast<bool>());
+}
+
 }  // namespace
diff --git a/tests/python/test_container.py b/tests/python/test_container.py
index e15f00b..8650e08 100644
--- a/tests/python/test_container.py
+++ b/tests/python/test_container.py
@@ -14,6 +14,8 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
+from __future__ import annotations
+
 import pickle
 import sys
 from typing import Any
@@ -206,3 +208,22 @@ def test_large_map_get() -> None:
     amap = tvm_ffi.convert({k: k**2 for k in range(100)})
     assert amap.get(101) is None
     assert amap.get(3) == 9
+
+
[email protected](
+    "arr, value, expected",
+    [
+        ([1, 2, 3, 4, 5], 3, True),
+        ([1, 2, 3, 4, 5], 1, True),
+        ([1, 2, 3, 4, 5], 5, True),
+        ([1, 2, 3, 4, 5], 10, False),
+        ([1, 2, 3, 4, 5], 0, False),
+        ([], 1, False),
+        (["hello", "world"], "hello", True),
+        (["hello", "world"], "world", True),
+        (["hello", "world"], "foo", False),
+    ],
+)
+def test_array_contains(arr: list[Any], value: Any, expected: bool) -> None:
+    a = tvm_ffi.convert(arr)
+    assert (value in a) == expected

Reply via email to