Repository: numpy/numpy
File: numpy/_core/src/multiarray/ctors.c (C)

Code before:
    }

    /* If the cache is NULL, then the object is considered a scalar */
    *was_scalar = (cache == NULL);

    if (dtype == NULL) {
        dtype = PyArray_DescrFromType(NPY_DEFAULT_TYPE);
    }

    if (min_depth != 0 && ndim < min_depth) {
        PyErr_SetString(PyExc_ValueError,
                "object of too small depth for desired array");
        npy_free_coercion_cache(cache);
        goto cleanup;
    }
    if (max_depth != 0 && ndim > max_depth) {
        PyErr_SetString(PyExc_ValueError,
                "object too deep for desired array");
        npy_free_coercion_cache(cache);
        goto cleanup;
    }

    /* Got the correct parameters, but the cache may already hold the result */
    if (cache != NULL && !(cache->sequence)) {
        /*
         * There is only a single array-like and it was converted, it
         * may still have the incorrect type, but that is handled below.
         */
        assert(cache->converted_obj == op);
        arr = (PyArrayObject *)(cache->arr_or_sequence);
        /* we may need to cast or assert flags (e.g. copy) */
        if (was_copied_by__array__ == 1) {
            flags = flags & ~NPY_ARRAY_ENSURECOPY;
        }
        // PyArray_FromArray steals a reference to the dtype
        Py_INCREF(dtype);
        ret = (PyArrayObject *)PyArray_FromArray(arr, dtype, flags);
        npy_unlink_coercion_cache(cache);
        goto cleanup;
    }
    else if (cache == NULL && PyArray_IsScalar(op, Void) &&
            !(((PyVoidScalarObject *)op)->flags & NPY_ARRAY_OWNDATA) &&
             ((in_descr == NULL) && (in_DType == NULL))) {
        /*
         * Special case, we return a *view* into void scalars, mainly to
         * allow things similar to the "reversed" assignment:
         *    arr[indx]["field"] = val  # instead of arr["field"][indx] = val
         *
         * It is unclear that this is necessary in this particular code path.
         * Note that this path is only activated when the user did _not_
         * provide a dtype (newtype is NULL).

Review comment (performance):
Reverting this change per diff below the fold resolves gh-29721 for me.

<details>

```diff
--- a/numpy/_core/src/multiarray/ctors.c
+++ b/numpy/_core/src/multiarray/ctors.c
@@ -1593,7 +1593,7 @@ PyArray_FromAny_int(PyObject *op, PyArray_Descr *in_descr,
         npy_free_coercion_cache(cache);
         goto cleanup;
     }
-    if (ndim > max_depth && (in_DType == NULL || in_DType->type_num != NPY_OBJECT)) {
+    if (max_depth != 0 && ndim > max_depth) {
         PyErr_SetString(PyExc_ValueError,
                 "object too deep for desired array");
         npy_free_coercion_cache(cache);
```

</details>

Application of that patch also passes the full NumPy testsuite locally on x86_64 Linux, so I kind of wonder if this is really justified, or at least if there is some way to keep the same behavior for error handling.

Code after:
    }

    Py_BEGIN_CRITICAL_SECTION(op);

    ndim = PyArray_DiscoverDTypeAndShape(
            op, max_depth, dims, &cache, in_DType, in_descr, &dtype,
            copy, &was_copied_by__array__);

    if (ndim < 0) {
        goto cleanup;
    }

    /* If the cache is NULL, then the object is considered a scalar */
    *was_scalar = (cache == NULL);

    if (dtype == NULL) {
        dtype = PyArray_DescrFromType(NPY_DEFAULT_TYPE);
    }

    if (min_depth != 0 && ndim < min_depth) {
        PyErr_SetString(PyExc_ValueError,
                "object of too small depth for desired array");
        npy_free_coercion_cache(cache);
        goto cleanup;
    }
    if (ndim > max_depth && (in_DType == NULL || in_DType->type_num != NPY_OBJECT)) {
        PyErr_SetString(PyExc_ValueError,
                "object too deep for desired array");
        npy_free_coercion_cache(cache);
        goto cleanup;
    }

    /* Got the correct parameters, but the cache may already hold the result */
    if (cache != NULL && !(cache->sequence)) {
        /*
         * There is only a single array-like and it was converted, it
         * may still have the incorrect type, but that is handled below.
         */
        assert(cache->converted_obj == op);
        arr = (PyArrayObject *)(cache->arr_or_sequence);
        /* we may need to cast or assert flags (e.g. copy) */
        if (was_copied_by__array__ == 1) {
            flags = flags & ~NPY_ARRAY_ENSURECOPY;
        }
        // PyArray_FromArray steals a reference to the dtype
        Py_INCREF(dtype);
        ret = (PyArrayObject *)PyArray_FromArray(arr, dtype, flags);
        npy_unlink_coercion_cache(cache);
        goto cleanup;
    }
    else if (cache == NULL && PyArray_IsScalar(op, Void) &&