BUG: normalize_indexer breaks _decompose_slice
What happened?
With xarray==2025.12.0, the rioxarray tests started to fail:
https://github.com/corteva/rioxarray/actions/runs/19997389961/job/57347087949
The issue was traced back to this change #10948.
What did you expect to happen?
No error.
Minimal Complete Verifiable Example
# /// script
# requires-python = ">=3.11"
# dependencies = [
# "xarray[complete]@git+https://github.com/pydata/xarray.git@main",
# ]
# ///
#
# This script automatically imports the development branch of xarray to check for issues.
# Please delete this header if you have _not_ tested this script with `uv run`!
import xarray as xr
xr.show_versions()
from xarray.core.indexing import normalize_indexer, _decompose_slice
print(_decompose_slice(slice(-1, None, -1), 8))
# (slice(0, 8, 1), slice(None, None, -1))
print(_decompose_slice(normalize_indexer(slice(-1, None, -1), 8), 8))
# Traceback (most recent call last):
# File "<stdin>", line 1, in <module>
# _decompose_slice(normalize_indexer(slice(-1, None, -1), 8), 8)
# ~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
# File ".../lib/python3.14/site-packages/xarray/core/indexing.py", # line 1217, in _decompose_slice
# exact_stop = range(start, stop, step)[-1]
# ~~~~~~~~~~~~~~~~~~~~~~~~^^^^
# IndexError: range object index out of range
Steps to reproduce
No response
MVCE confirmation
- [x] Minimal example — the example is as focused as reasonably possible to demonstrate the underlying issue in xarray.
- [x] Complete example — the example is self-contained, including all data and the text of any traceback.
- [x] Verifiable example — the example copy & pastes into an IPython prompt or Binder notebook, returning the result.
- [x] New issue — a search of GitHub Issues suggests this is not a duplicate.
- [x] Recent environment — the issue occurs with the latest version of xarray and its dependencies.
Relevant log output
=================================== FAILURES ===================================
________________________________ test_indexing _________________________________
def test_indexing():
...
# minus-stepped slice
ind = {"band": numpy.array([2, 1, 0]), "x": slice(-1, None, -1), "y": 0}
> assert_allclose(expected.isel(**ind), actual.isel(**ind))
...
testenv/lib/python3.12/site-packages/xarray/core/indexing.py:1415: in _decompose_outer_indexer
bk_slice, np_slice = _decompose_slice(k, s)
^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
key = slice(7, -1, -1), size = 8
def _decompose_slice(key: slice, size: int) -> tuple[slice, slice]:
# determine stop precisely for step > 1 case
# Use the range object to do the calculation
# e.g. [98:2:-2] -> [98:3:-2]
> exact_stop = range(start, stop, step)[-1]
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
E IndexError: range object index out of range
Anything else we need to know?
No response
Environment
xarray-2025.12.0 cftime-1.6.5 cloudpickle-3.1.2 dask-2025.11.0 fsspec-2025.12.0 netcdf4-1.7.3
I guess the problem is that slice(None, None, -1) is not representable as a concrete slice: s.indices(8) returns (7, -1, -1), which we can feed into range to get the integer indices. However, we can't feed that back into slice because that interprets stop = -1 as "to the end of the sequence", regardless of the step size. Which means the trick we used to normalize slices does not work for negative step sizes.
s = range(3, None, -1)
normalize_slice(s, 8) # normalized to slice(3, -1, -1)
# which expands to slice(3, 7, -1) (by adding the size to `stop`), which is empty
To fix that, we may have to check for negative step sizes and replace stop with None if it was -1 after normalization.
The only problem I can see with that is that cfgrib once again may not support that, but maybe we should call that a bug in cfgrib, then? @dcherian, what do you think?
Yes I believe cfgrib should add support for slice(None). IMO the rioxarray query here is reasonable and we should definitely make it work.