Repository: scikit-hep/uproot5

Issue:
`uproot.dask` raises error on empty branches
See example below. The exact file used is <https://github.com/iris-hep/func_adl_uproot/blob/61b35593b4512dfaf3a75054aa13ffe2fbc602b6/tests/empty_branches_tree_file.root>. It's a perfectly valid tree; there are just no entries, so I expect to get empty arrays rather than an error.

```python
>>> import uproot
>>> uproot.__version__
'5.0.0rc2'
>>> uproot.dask('tests/empty_branches_tree_file.root')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/user/miniconda3/envs/func_adl_uproot_rc/lib/python3.10/site-packages/uproot/_dask.py", line 143, in dask
    return _get_dak_array(
  File "/home/user/miniconda3/envs/func_adl_uproot_rc/lib/python3.10/site-packages/uproot/_dask.py", line 483, in _get_dak_array
    ].basket_entry_start_stop(0)
  File "/home/user/miniconda3/envs/func_adl_uproot_rc/lib/python3.10/site-packages/uproot/behaviors/TBranch.py", line 2112, in basket_entry_start_stop
    raise IndexError(
IndexError: branch 'int_branch' has 0 baskets; cannot get starting entry for basket 0
in file tests/empty_branches_tree_file.root
```


Discussion:
That's right, and also this is handled correctly by `concatenate`, `arrays` and `array`:

```python
>>> uproot.concatenate('empty_branches_tree_file.root')
<Array [] type='0 * {int_branch: int32, float_branch: float32}'>
>>> uproot.open('empty_branches_tree_file.root:tree').arrays()
<Array [] type='0 * {int_branch: int32, float_branch: float32}'>
>>> uproot.open('empty_branches_tree_file.root:tree/int_branch').array()
<Array [] type='0 * int32'>
>>> next(uproot.open('empty_branches_tree_file.root:tree').iterate())
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration
```

So it's something in the `uproot.dask` logic, and @kkothari2001 should take a look. I'll put this file in [scikit-hep-testdata](https://github.com/scikit-hep/scikit-hep-testdata) so that we can add it to the tests.

Thanks!
It's ready: [scikit-hep-testdata 0.4.20](https://pypi.org/project/scikit-hep-testdata/0.4.20/) has uproot-issue-697.root, which should be used in a test to ensure that `uproot.dask` can make an empty Dask array from it. (Maybe that array will need to have one partition, but that partition can be an empty array.)

Resolving patch:
diff --git a/src/uproot/_dask.py b/src/uproot/_dask.py
--- a/src/uproot/_dask.py
+++ b/src/uproot/_dask.py
@@ -440,6 +440,10 @@ def foreach(start):
             for start in range(entry_start, entry_stop, entry_step):
                 foreach(start)
 
+        if len(chunk_args) == 0:
+            chunks.append(0)
+            chunk_args.append((0, 0, 0))
+
         dask_dict[key] = _dask_array_from_map(
             _UprootReadNumpy(hasbranches, key),
             chunk_args,
@@ -637,14 +641,11 @@ def foreach(start):
         for start in range(entry_start, entry_stop, entry_step):
             foreach(start)
 
-    first_basket_start, first_basket_stop = hasbranches[0][
-        common_keys[0]
-    ].basket_entry_start_stop(0)
-    first_basket = hasbranches[0].arrays(
-        common_keys, entry_start=first_basket_start, entry_stop=first_basket_stop
-    )
-    meta = dask_awkward.core.typetracer_array(first_basket)
+    empty_arr = hasbranches[0].arrays(common_keys, entry_start=0, entry_stop=0)
+    meta = dask_awkward.core.typetracer_array(empty_arr)
 
+    if len(partition_args) == 0:
+        partition_args.append((0, 0, 0))
     return dask_awkward.from_map(
         _UprootRead(hasbranches, common_keys),
         partition_args,


Test patch:
diff --git a/tests/test_0700-dask-empty-arrays.py b/tests/test_0700-dask-empty-arrays.py
new file mode 100644
--- /dev/null
+++ b/tests/test_0700-dask-empty-arrays.py
@@ -0,0 +1,48 @@
+# BSD 3-Clause License; see https://github.com/scikit-hep/uproot4/blob/main/LICENSE
+
+import numpy
+import pytest
+import skhep_testdata
+
+import uproot
+
+dask = pytest.importorskip("dask")
+dask_awkward = pytest.importorskip("dask_awkward")
+from dask_awkward.testutils import assert_eq
+
+
+def test_dask_numpy_empty_arrays():
+    test_path = skhep_testdata.data_path("uproot-issue-697.root") + ":tree"
+    ttree = uproot.open(test_path)
+
+    np_arrays = ttree.arrays(library="np")
+    dask_arrays = uproot.dask(test_path, library="np")
+
+    assert list(dask_arrays.keys()) == list(
+        np_arrays.keys()
+    ), "Different keys detected in dictionary of dask arrays and dictionary of numpy arrays"
+
+    for key in np_arrays.keys():
+        comp = dask_arrays[key].compute() == np_arrays[key]
+        assert comp.all(), f"Incorrect array at key {key}"
+
+
+def test_dask_awkward_empty_arrays():
+    test_path = skhep_testdata.data_path("uproot-issue-697.root") + ":tree"
+    ttree = uproot.open(test_path)
+
+    ak_array = ttree.arrays()
+    dak_array = uproot.dask(test_path, library="ak")
+
+    assert_eq(dak_array, ak_array)
+
+
+def test_no_common_tree_branches():
+    test_path1 = skhep_testdata.data_path("uproot-Zmumu.root") + ":events"
+    test_path2 = skhep_testdata.data_path("uproot-issue-697.root") + ":tree"
+
+    with pytest.raises(ValueError):
+        dask_arrays = uproot.dask([test_path1, test_path2], library="np")
+
+    with pytest.raises(ValueError):
+        dask_arrays = uproot.dask([test_path1, test_path2])