Repository: scikit-hep/awkward

Issue:
`depth_limit` requires a broadcast depth in addition to setting an upper limit
### Version of Awkward Array

2.1.1

### Description and code to reproduce

I'm not sure if this is a bug or intended behavior that isn't documented. Here is a pretty minimal example:

```python
>>> import awkward as ak
>>> a = ak.Array([1, 2, 3])
>>> ak.zip([a, a], depth_limit=2)
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/awkward/operations/ak_zip.py", line 147, in zip
    return _impl(
  File "/home/user/miniconda3/envs/func_adl_uproot_rc/lib/python3.10/site-packages/awkward/operations/ak_zip.py", line 241, in _impl
    out = ak._broadcasting.broadcast_and_apply(
  File "/home/user/miniconda3/envs/func_adl_uproot_rc/lib/python3.10/site-packages/awkward/_broadcasting.py", line 1061, in broadcast_and_apply
    out = apply_step(
  File "/home/user/miniconda3/envs/func_adl_uproot_rc/lib/python3.10/site-packages/awkward/_broadcasting.py", line 1040, in apply_step
    return continuation()
  File "/home/user/miniconda3/envs/func_adl_uproot_rc/lib/python3.10/site-packages/awkward/_broadcasting.py", line 757, in continuation
    outcontent = apply_step(
  File "/home/user/miniconda3/envs/func_adl_uproot_rc/lib/python3.10/site-packages/awkward/_broadcasting.py", line 1040, in apply_step
    return continuation()
  File "/home/user/miniconda3/envs/func_adl_uproot_rc/lib/python3.10/site-packages/awkward/_broadcasting.py", line 1018, in continuation
    raise ak._errors.wrap_error(
ValueError: while calling

    ak.zip(
        arrays = [<Array [1, 2, 3] type='3 * int64'>, <Array [1, 2, 3] type='...
        depth_limit = 2
        parameters = None
        with_name = None
        right_broadcast = False
        optiontype_outside_record = False
        highlevel = True
        behavior = None
    )

Error details: cannot broadcast: <class 'awkward.contents.numpyarray.NumpyArray'>, <class 'awkward.contents.numpyarray.NumpyArray'>

```

According to the [documentation](https://awkward-array.org/doc/main/reference/generated/ak.zip.html), `depth_limit` "limit[s] the number of dimensions that get broadcasted". Clearly it is not only _limiting_ how deep broadcasting can go but _requiring_ that broadcasting be possible at that depth. This is surprising to me based on both the name of the parameter and the description in the documentation.


Discussion:

Resolving patch:
diff --git a/src/awkward/operations/ak_broadcast_arrays.py b/src/awkward/operations/ak_broadcast_arrays.py
--- a/src/awkward/operations/ak_broadcast_arrays.py
+++ b/src/awkward/operations/ak_broadcast_arrays.py
@@ -216,10 +216,7 @@ def _impl(
         inputs.append(y.to_backend(backend))
 
     def action(inputs, depth, **kwargs):
-        if depth == depth_limit or (
-            depth_limit is None
-            and all(isinstance(x, ak.contents.NumpyArray) for x in inputs)
-        ):
+        if depth == depth_limit or all(x.is_numpy for x in inputs):
             return tuple(inputs)
         else:
             return None
diff --git a/src/awkward/operations/ak_zip.py b/src/awkward/operations/ak_zip.py
--- a/src/awkward/operations/ak_zip.py
+++ b/src/awkward/operations/ak_zip.py
@@ -217,16 +217,13 @@ def _impl(
         parameters["__record__"] = with_name
 
     def action(inputs, depth, **ignore):
-        if depth_limit == depth or (
-            depth_limit is None
-            and all(
-                x.purelist_depth == 1
-                or (
-                    x.purelist_depth == 2
-                    and x.purelist_parameter("__array__") in ("string", "bytestring")
-                )
-                for x in inputs
+        if depth_limit == depth or all(
+            x.purelist_depth == 1
+            or (
+                x.purelist_depth == 2
+                and x.purelist_parameter("__array__") in ("string", "bytestring")
             )
+            for x in inputs
         ):
             # If we want to zip after option types at this depth
             if optiontype_outside_record and any(x.is_option for x in inputs):


Test patch:
diff --git a/tests/test_2346_broadcast_depth_limit.py b/tests/test_2346_broadcast_depth_limit.py
new file mode 100644
--- /dev/null
+++ b/tests/test_2346_broadcast_depth_limit.py
@@ -0,0 +1,14 @@
+# BSD 3-Clause License; see https://github.com/scikit-hep/awkward-1.0/blob/main/LICENSE
+
+import pytest  # noqa: F401
+
+import awkward as ak
+
+
+def test():
+    left = ak.Array([0, 4, 5])
+    right = ak.Array([[0, 1, 2, 3], [5, 6, 7, 2], [9, 8, 2, 0]])
+
+    left_result, right_result = ak.broadcast_arrays(left, right, depth_limit=4)
+    assert left_result.to_list() == [[0, 0, 0, 0], [4, 4, 4, 4], [5, 5, 5, 5]]
+    assert right_result.to_list() == [[0, 1, 2, 3], [5, 6, 7, 2], [9, 8, 2, 0]]