From da06daed0b5e93f6cd7d5bce7c36d4057759f37e Mon Sep 17 00:00:00 2001 From: Evgeni Burovski Date: Sat, 10 Jan 2026 22:37:04 +0100 Subject: [PATCH 1/6] torch.broadcast_arrays: make it return a tuple --- array_api_compat/torch/_aliases.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/array_api_compat/torch/_aliases.py b/array_api_compat/torch/_aliases.py index 4a7e9c00..10e8186e 100644 --- a/array_api_compat/torch/_aliases.py +++ b/array_api_compat/torch/_aliases.py @@ -711,9 +711,9 @@ def astype( return x.to(dtype=dtype, copy=copy) -def broadcast_arrays(*arrays: Array) -> list[Array]: +def broadcast_arrays(*arrays: Array) -> tuple[Array, ...]: shape = torch.broadcast_shapes(*[a.shape for a in arrays]) - return [torch.broadcast_to(a, shape) for a in arrays] + return tuple(torch.broadcast_to(a, shape) for a in arrays) # Note that these named tuples aren't actually part of the standard namespace, # but I don't see any issue with exporting the names here regardless. From ddf995387f8337e19d80a463d1384501c1fc708b Mon Sep 17 00:00:00 2001 From: Evgeni Burovski Date: Sat, 10 Jan 2026 22:37:18 +0100 Subject: [PATCH 2/6] cupy.broadcast_arrays: make it return a tuple --- array_api_compat/cupy/_aliases.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/array_api_compat/cupy/_aliases.py b/array_api_compat/cupy/_aliases.py index 2e512fc8..badfe390 100644 --- a/array_api_compat/cupy/_aliases.py +++ b/array_api_compat/cupy/_aliases.py @@ -139,6 +139,11 @@ def take_along_axis(x: Array, indices: Array, /, *, axis: int = -1) -> Array: return cp.take_along_axis(x, indices, axis=axis) +# https://github.com/cupy/cupy/pull/9582 +def broadcast_arrays(*arrays: Array) -> tuple[Array, ...]: + return tuple(cp.broadcast_arrays(*arrays)) + + # These functions are completely new here. If the library already has them # (i.e., numpy 2.0), use the library version instead of our wrapper. if hasattr(cp, 'vecdot'): @@ -161,7 +166,8 @@ def take_along_axis(x: Array, indices: Array, /, *, axis: int = -1) -> Array: 'atan2', 'atanh', 'bitwise_left_shift', 'bitwise_invert', 'bitwise_right_shift', 'bool', 'concat', 'count_nonzero', 'pow', 'sign', - 'ceil', 'floor', 'trunc', 'take_along_axis'] + 'ceil', 'floor', 'trunc', 'take_along_axis', + 'broadcast_arrays',] def __dir__() -> list[str]: From 94c1706d20a2b07331cbb2028ba12696e8c6e4b6 Mon Sep 17 00:00:00 2001 From: Evgeni Burovski Date: Sun, 11 Jan 2026 11:07:42 +0100 Subject: [PATCH 3/6] torch.meshgrid: make it return tuple, not list --- array_api_compat/torch/_aliases.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/array_api_compat/torch/_aliases.py b/array_api_compat/torch/_aliases.py index 10e8186e..27f0a263 100644 --- a/array_api_compat/torch/_aliases.py +++ b/array_api_compat/torch/_aliases.py @@ -897,10 +897,11 @@ def sign(x: Array, /) -> Array: return out -def meshgrid(*arrays: Array, indexing: Literal['xy', 'ij'] = 'xy') -> list[Array]: - # enforce the default of 'xy' - # TODO: is the return type a list or a tuple - return list(torch.meshgrid(*arrays, indexing=indexing)) +def meshgrid(*arrays: Array, indexing: Literal['xy', 'ij'] = 'xy') -> tuple[Array, ...]: + # torch <= 2.9 emits a UserWarning: "torch.meshgrid: in an upcoming release, it + # will be required to pass the indexing argument." + # Thus always pass it explicitly. + return torch.meshgrid(*arrays, indexing=indexing) __all__ = ['asarray', 'result_type', 'can_cast', From af5fd5ca220c5b177d92faa21e3ed28ba9b646d7 Mon Sep 17 00:00:00 2001 From: Evgeni Burovski Date: Sun, 11 Jan 2026 11:19:38 +0100 Subject: [PATCH 4/6] __array_namespace_info().devices() : returns a tuple not list --- array_api_compat/cupy/_info.py | 2 +- array_api_compat/dask/array/_info.py | 4 ++-- array_api_compat/numpy/_info.py | 4 ++-- array_api_compat/torch/_info.py | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/array_api_compat/cupy/_info.py b/array_api_compat/cupy/_info.py index 78e48a33..aef10e85 100644 --- a/array_api_compat/cupy/_info.py +++ b/array_api_compat/cupy/_info.py @@ -333,4 +333,4 @@ def devices(self): __array_namespace_info__.dtypes """ - return [cuda.Device(i) for i in range(cuda.runtime.getDeviceCount())] + return tuple(cuda.Device(i) for i in range(cuda.runtime.getDeviceCount())) diff --git a/array_api_compat/dask/array/_info.py b/array_api_compat/dask/array/_info.py index 2f39fc4b..3a7285d5 100644 --- a/array_api_compat/dask/array/_info.py +++ b/array_api_compat/dask/array/_info.py @@ -379,7 +379,7 @@ def dtypes( return res raise ValueError(f"unsupported kind: {kind!r}") - def devices(self) -> list[Device]: + def devices(self) -> tuple[Device]: """ The devices supported by Dask. @@ -404,4 +404,4 @@ def devices(self) -> list[Device]: ['cpu', DASK_DEVICE] """ - return ["cpu", _DASK_DEVICE] + return ("cpu", _DASK_DEVICE) diff --git a/array_api_compat/numpy/_info.py b/array_api_compat/numpy/_info.py index c625c13e..9ba004da 100644 --- a/array_api_compat/numpy/_info.py +++ b/array_api_compat/numpy/_info.py @@ -332,7 +332,7 @@ def dtypes( return res raise ValueError(f"unsupported kind: {kind!r}") - def devices(self) -> list[Device]: + def devices(self) -> tuple[Device]: """ The devices supported by NumPy. @@ -357,7 +357,7 @@ def devices(self) -> list[Device]: ['cpu'] """ - return ["cpu"] + return ("cpu",) __all__ = ["__array_namespace_info__"] diff --git a/array_api_compat/torch/_info.py b/array_api_compat/torch/_info.py index 818e5d37..050c7846 100644 --- a/array_api_compat/torch/_info.py +++ b/array_api_compat/torch/_info.py @@ -366,4 +366,4 @@ def devices(self): break i += 1 - return devices + return tuple(devices) From 0959644ac11a1b38c7f5e3b45430417e1ffc3236 Mon Sep 17 00:00:00 2001 From: Evgeni Burovski Date: Mon, 23 Feb 2026 13:22:07 +0100 Subject: [PATCH 5/6] TST: update xfails for the tuples/lists spec change --- dask-xfails.txt | 7 +++++++ numpy-1-22-xfails.txt | 7 +++++++ numpy-1-26-xfails.txt | 8 +++++++- torch-xfails.txt | 5 +++++ 4 files changed, 26 insertions(+), 1 deletion(-) diff --git a/dask-xfails.txt b/dask-xfails.txt index 5ce24254..4d5a89af 100644 --- a/dask-xfails.txt +++ b/dask-xfails.txt @@ -129,6 +129,13 @@ array_api_tests/test_linalg.py::test_matrix_norm array_api_tests/test_linalg.py::test_qr array_api_tests/test_manipulation_functions.py::test_roll +# 2025.12 support +array_api_tests/test_has_names.py::test_has_names[manipulation-broadcast_shapes] +array_api_tests/test_signatures.py::test_func_signature[broadcast_shapes] +array_api_tests/test_data_type_functions.py::TestBroadcastShapes::test_broadcast_shapes +array_api_tests/test_data_type_functions.py::TestBroadcastShapes::test_empty +array_api_tests/test_data_type_functions.py::TestBroadcastShapes::test_error + # Stubs have a comment: (**note**: libraries may return ``NaN`` to match Python behavior.) array_api_tests/test_special_cases.py::test_binary[floor_divide(x1_i is +infinity and isfinite(x2_i) and x2_i > 0) -> +infinity] array_api_tests/test_special_cases.py::test_binary[floor_divide(x1_i is +infinity and isfinite(x2_i) and x2_i < 0) -> -infinity] diff --git a/numpy-1-22-xfails.txt b/numpy-1-22-xfails.txt index e2df8b47..c2e13d35 100644 --- a/numpy-1-22-xfails.txt +++ b/numpy-1-22-xfails.txt @@ -152,6 +152,13 @@ array_api_tests/test_signatures.py::test_func_signature[bitwise_right_shift] array_api_tests/test_signatures.py::test_func_signature[bitwise_xor] array_api_tests/test_data_type_functions.py::TestResultType::test_with_scalars +# 2025.12 support + +# older numpies return lists not tuples +array_api_tests/test_creation_functions.py::test_meshgrid +array_api_tests/test_data_type_functions.py::test_broadcast_arrays + + # Stubs have a comment: (**note**: libraries may return ``NaN`` to match Python behavior.); Apparently,NumPy does just that array_api_tests/test_special_cases.py::test_binary[floor_divide(x1_i is +infinity and isfinite(x2_i) and x2_i > 0) -> +infinity] array_api_tests/test_special_cases.py::test_binary[floor_divide(x1_i is +infinity and isfinite(x2_i) and x2_i < 0) -> -infinity] diff --git a/numpy-1-26-xfails.txt b/numpy-1-26-xfails.txt index 407cc531..f09be4a4 100644 --- a/numpy-1-26-xfails.txt +++ b/numpy-1-26-xfails.txt @@ -42,9 +42,15 @@ array_api_tests/test_signatures.py::test_array_method_signature[__dlpack__] # 2024.12 support array_api_tests/test_data_type_functions.py::TestResultType::test_with_scalars - array_api_tests/test_operators_and_elementwise_functions.py::test_where_with_scalars +# 2025.12 support + +# older numpies return lists not tuples +array_api_tests/test_creation_functions.py::test_meshgrid +array_api_tests/test_data_type_functions.py::test_broadcast_arrays + + # Stubs have a comment: (**note**: libraries may return ``NaN`` to match Python behavior.); Apparently, NumPy does just that array_api_tests/test_special_cases.py::test_binary[floor_divide(x1_i is +infinity and isfinite(x2_i) and x2_i > 0) -> +infinity] array_api_tests/test_special_cases.py::test_binary[floor_divide(x1_i is +infinity and isfinite(x2_i) and x2_i < 0) -> -infinity] diff --git a/torch-xfails.txt b/torch-xfails.txt index 3c2a0028..84271a56 100644 --- a/torch-xfails.txt +++ b/torch-xfails.txt @@ -144,6 +144,11 @@ array_api_tests/test_signatures.py::test_func_signature[from_dlpack] # Argument 'max_version' missing from signature array_api_tests/test_signatures.py::test_array_method_signature[__dlpack__] +# 2025.12 support + +# broadcast_shapes emits a RuntimeError where the spec says ValueError +array_api_tests/test_data_type_functions.py::TestBroadcastShapes::test_error + # 2024.12 support: binary functions reject python scalar arguments array_api_tests/test_operators_and_elementwise_functions.py::test_binary_with_scalars_real[atan2] From d2e2f0ec06a94cdb98dee53444926b28bf9bc5b2 Mon Sep 17 00:00:00 2001 From: Evgeni Burovski Date: Mon, 23 Feb 2026 13:07:51 +0000 Subject: [PATCH 6/6] cupy.meshgrid: return a tuple not a list --- array_api_compat/cupy/_aliases.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/array_api_compat/cupy/_aliases.py b/array_api_compat/cupy/_aliases.py index badfe390..f91805f2 100644 --- a/array_api_compat/cupy/_aliases.py +++ b/array_api_compat/cupy/_aliases.py @@ -1,6 +1,7 @@ from __future__ import annotations from builtins import bool as py_bool +from typing import Literal import cupy as cp @@ -144,6 +145,10 @@ def broadcast_arrays(*arrays: Array) -> tuple[Array, ...]: return tuple(cp.broadcast_arrays(*arrays)) +def meshgrid(*arrays: Array, indexing: Literal['xy', 'ij'] = 'xy') -> tuple[Array, ...]: + return tuple(cp.meshgrid(*arrays, indexing=indexing)) + + # These functions are completely new here. If the library already has them # (i.e., numpy 2.0), use the library version instead of our wrapper. if hasattr(cp, 'vecdot'): @@ -167,7 +172,7 @@ def broadcast_arrays(*arrays: Array) -> tuple[Array, ...]: 'bitwise_invert', 'bitwise_right_shift', 'bool', 'concat', 'count_nonzero', 'pow', 'sign', 'ceil', 'floor', 'trunc', 'take_along_axis', - 'broadcast_arrays',] + 'broadcast_arrays', 'meshgrid'] def __dir__() -> list[str]: