Repository: nv-legate/cunumeric

Issue:
crash using result from `np.sum`
```
>>> import cunumeric
>>> x = cunumeric.ones((5,))
>>> y = x.sum()
>>> cunumeric.zeros(y, dtype=int)
Traceback (most recent call last):
  File "<console>", line 1, in <module>
  File "/Users/rohany/Documents/nvidia/install/lib/python3.9/site-packages/cunumeric/coverage.py", line 107, in wrapper
    return func(*args, **kwargs)
  File "/Users/rohany/Documents/nvidia/install/lib/python3.9/site-packages/cunumeric/module.py", line 296, in zeros
    return full(shape, 0, dtype=dtype)
  File "/Users/rohany/Documents/nvidia/install/lib/python3.9/site-packages/cunumeric/module.py", line 368, in full
    result = empty(shape, dtype=val.dtype)
  File "/Users/rohany/Documents/nvidia/install/lib/python3.9/site-packages/cunumeric/module.py", line 89, in empty
    return ndarray(shape=shape, dtype=dtype)
  File "/Users/rohany/Documents/nvidia/install/lib/python3.9/site-packages/cunumeric/coverage.py", line 107, in wrapper
    return func(*args, **kwargs)
  File "/Users/rohany/Documents/nvidia/install/lib/python3.9/site-packages/cunumeric/array.py", line 197, in __init__
    self._thunk = runtime.create_empty_thunk(shape, dtype, inputs)
  File "/Users/rohany/Documents/nvidia/install/lib/python3.9/site-packages/cunumeric/runtime.py", line 576, in create_empty_thunk
    self.is_eager_shape(computed_shape)
  File "/Users/rohany/Documents/nvidia/install/lib/python3.9/site-packages/cunumeric/runtime.py", line 593, in is_eager_shape
    volume = calculate_volume(shape)
  File "/Users/rohany/Documents/nvidia/install/lib/python3.9/site-packages/cunumeric/utils.py", line 97, in calculate_volume
    if len(shape) == 0:
  File "/Users/rohany/Documents/nvidia/install/lib/python3.9/site-packages/cunumeric/coverage.py", line 107, in wrapper
    return func(*args, **kwargs)
  File "/Users/rohany/Documents/nvidia/install/lib/python3.9/site-packages/cunumeric/array.py", line 1125, in __len__
    return self.shape[0]
IndexError: tuple index out of range
```


Discussion:
fyi, your reproducer is incorrect in NumPy, as you can't pass a floating point value as a size. here's a correct reproducer:
```
import cunumeric
x = cunumeric.ones((5,), dtype=int)
y = x.sum()
cunumeric.zeros(y, dtype=int)
```

Resolving patch:
diff --git a/cunumeric/array.py b/cunumeric/array.py
--- a/cunumeric/array.py
+++ b/cunumeric/array.py
@@ -19,7 +19,16 @@
 from collections.abc import Iterable
 from functools import reduce, wraps
 from inspect import signature
-from typing import Any, Callable, Optional, Set, TypeVar, Union
+from typing import (
+    TYPE_CHECKING,
+    Any,
+    Callable,
+    Optional,
+    Sequence,
+    Set,
+    TypeVar,
+    Union,
+)
 
 import numpy as np
 import pyarrow
@@ -40,6 +49,9 @@
 from .runtime import runtime
 from .utils import dot_modes
 
+if TYPE_CHECKING:
+    from .types import NdShapeLike
+
 FALLBACK_WARNING = (
     "cuNumeric has not fully implemented {name} "
     + "and is falling back to canonical numpy. "
@@ -171,12 +183,14 @@ def __init__(
         # `inputs` being a cuNumeric ndarray is definitely a bug
         assert not isinstance(inputs, ndarray)
         if thunk is None:
+            assert shape is not None
+            sanitized_shape = self._sanitize_shape(shape)
             if not isinstance(dtype, np.dtype):
                 dtype = np.dtype(dtype)
             if buffer is not None:
                 # Make a normal numpy array for this buffer
                 np_array = np.ndarray(
-                    shape=shape,
+                    shape=sanitized_shape,
                     dtype=dtype,
                     buffer=buffer,
                     offset=offset,
@@ -194,11 +208,39 @@ def __init__(
                         for inp in inputs
                         if isinstance(inp, ndarray)
                     ]
-                self._thunk = runtime.create_empty_thunk(shape, dtype, inputs)
+                self._thunk = runtime.create_empty_thunk(
+                    sanitized_shape, dtype, inputs
+                )
         else:
             self._thunk = thunk
         self._legate_data = None
 
+    @staticmethod
+    def _sanitize_shape(
+        shape: Union[NdShapeLike, Sequence[Any], np.ndarray, ndarray]
+    ):
+        if isinstance(shape, (ndarray, np.ndarray)):
+            if shape.ndim == 0:
+                seq = (shape.__array__().item(),)
+            else:
+                seq = tuple(shape.__array__())
+        elif np.isscalar(shape):
+            seq = (shape,)
+        else:
+            seq = tuple(shape)
+        try:
+            # Unfortunately, we can't do this check using
+            # 'isinstance(value, int)', as the values in a NumPy ndarray
+            # don't satisfy the predicate (they have numpy value types,
+            # such as numpy.int64).
+            result = tuple(operator.index(value) for value in seq)
+        except TypeError:
+            raise TypeError(
+                "expected a sequence of integers or a single integer, "
+                f"got '{shape}'"
+            )
+        return result
+
     # Support for the Legate data interface
     @property
     def __legate_data_interface__(self):
diff --git a/cunumeric/runtime.py b/cunumeric/runtime.py
--- a/cunumeric/runtime.py
+++ b/cunumeric/runtime.py
@@ -48,8 +48,6 @@
     from legate.core._legion.future import Future
     from legate.core.operation import AutoTask, ManualTask
 
-    from .types import NdShapeLike
-
 _supported_dtypes = {
     np.bool_: ty.bool_,
     np.int8: ty.int8,
@@ -569,17 +567,15 @@ def find_or_create_array_thunk(
 
     def create_empty_thunk(
         self,
-        shape: NdShapeLike,
+        shape: NdShape,
         dtype: np.dtype[Any],
         inputs: Optional[Sequence[NumPyThunk]] = None,
     ) -> NumPyThunk:
-        computed_shape = (shape,) if isinstance(shape, int) else shape
         if self.is_supported_type(dtype) and not (
-            self.is_eager_shape(computed_shape)
-            and self.are_all_eager_inputs(inputs)
+            self.is_eager_shape(shape) and self.are_all_eager_inputs(inputs)
         ):
             store = self.legate_context.create_store(
-                dtype, shape=computed_shape, optimize_scalar=True
+                dtype, shape=shape, optimize_scalar=True
             )
             return DeferredArray(self, store, dtype=dtype)
         else:


Test patch:
diff --git a/tests/integration/test_array_creation.py b/tests/integration/test_array_creation.py
--- a/tests/integration/test_array_creation.py
+++ b/tests/integration/test_array_creation.py
@@ -124,6 +124,30 @@ def test_arange_with_dtype():
     assert x.dtype == y.dtype
 
 
+def test_zero_with_nd_ndarray_shape():
+    shape = num.array([2, 3, 4])
+    x = num.zeros(shape)
+    y = np.zeros(shape)
+    assert np.array_equal(x, y)
+
+    shape = np.array([2, 3, 4])
+    x = num.zeros(shape)
+    y = np.zeros(shape)
+    assert np.array_equal(x, y)
+
+
+def test_zero_with_0d_ndarray_shape():
+    shape = num.array(3)
+    x = num.zeros(shape)
+    y = np.zeros(shape)
+    assert np.array_equal(x, y)
+
+    shape = np.array(3)
+    x = num.zeros(shape)
+    y = np.zeros(shape)
+    assert np.array_equal(x, y)
+
+
 if __name__ == "__main__":
     import sys