Skip to content

Implement an abstract AbstractOffsetArray type - #371

Open
simone-silvestri wants to merge 35 commits into
JuliaArrays:masterfrom
simone-silvestri:master
Open

simone-silvestri wants to merge 35 commits into
JuliaArrays:masterfrom
simone-silvestri:master

Conversation

@simone-silvestri

@simone-silvestri simone-silvestri commented Mar 12, 2025 •

Copy link
Copy Markdown

Addresses #370 by providing an extension point for packages that need to store offsets differently.

Adds OffsetArrays.AbstractOffsetArray{T,N}, an abstract supertype of OffsetArray, so that packages can store offsets differently (narrower integers, type parameters) while reusing OffsetArrays' indexing, axes and printing machinery.

Interface. A subtype defines Base.parent, OffsetArrays.offsets (an NTuple{N,<:Integer}) and OffsetArrays.parenttype; IndexStyle, elsize, pointer, similar, broadcasting, Adapt, printing, etc. follow. Base.parent throws for a subtype that forgets to define it, to avoid recursing through Base's identity fallback. Subtypes are responsible for calling OffsetArrays.overflow_check in their constructors, and may extend OffsetArrays.unwrap to keep their own type through copy, zero, map(T, A), all-Colon indexing and adapt; other operations producing offset axes return an OffsetArray. The range specialisations (OffsetRange) remain specific to OffsetVector.

Public API. AbstractOffsetArray, AbstractOffsetVector, AbstractOffsetMatrix, offsets, parenttype, unwrap and overflow_check are documented and declared public on Julia ≥ 1.11. overflow_check is specified for axes with Int endpoints.

Compatibility. OffsetArray behaves as before, with two exceptions: its supertype is now AbstractOffsetArray rather than AbstractArray directly, and fill!(A, x) now returns A itself instead of a new wrapper around the same parent. Minimum Julia version is 1.0 (the 0.7 compat entry is dropped). Bumps the version to 1.18.0.

@codecov

codecov Bot commented Jan 29, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 97.26027% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 96.86%. Comparing base (b27ac81) to head (4cd3751).

Files with missing lines Patch % Lines
src/OffsetArrays.jl 97.14% 2 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master     #371      +/-   ##
==========================================
+ Coverage   96.63%   96.86%   +0.23%     
==========================================
  Files           6        6              
  Lines         475      479       +4     
==========================================
+ Hits          459      464       +5     
+ Misses         16       15       -1     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@giordano giordano left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll defer the decision about adding a new type parameter to the maintainers of the package

Comment thread ext/OffsetArraysAdaptExt.jl Outdated
Comment thread src/OffsetArrays.jl Outdated
Comment thread src/OffsetArrays.jl Outdated
Comment thread src/OffsetArrays.jl Outdated
simone-silvestri and others added 6 commits May 1, 2026 17:59
Co-authored-by: Mosè Giordano <765740+giordano@users.noreply.github.com>
Co-authored-by: Mosè Giordano <765740+giordano@users.noreply.github.com>
Co-authored-by: Mosè Giordano <765740+giordano@users.noreply.github.com>
Co-authored-by: Mosè Giordano <765740+giordano@users.noreply.github.com>
@simone-silvestri

Copy link
Copy Markdown
Author

Would it be ok if I find a way to implement this functionality without introducing a new parameter?

@simone-silvestri

simone-silvestri commented Sep 8, 2026 •

Copy link
Copy Markdown
Author

Actually, thinking a bit about it, it seems to be a bit to difficult to achieve without an extra parameter. Another possibility would be to just allow this feature in downstream packages, for example by defining an abstract type AbstractOffsetArray{T, N} which could be subtyped and move most of the methods to target AbstractOffsetArray. Would that be an option?

@jishnub

jishnub commented Sep 8, 2026

Copy link
Copy Markdown
Member

Yes, that makes sense

@simone-silvestri simone-silvestri changed the title Trying to allow Int8 as indices Implement an abstract AbstractOffsetArray type Sep 8, 2026
@giordano
giordano requested a review from jishnub September 27, 2026 08:42
@giordano

Copy link
Copy Markdown
Contributor

@jishnub can you please have another look at this PR? It's good to go from my point of view, but I'd appreciate your review before merging this and tagging a new release.

giordano and others added 8 commits October 17, 2026 14:03
Drop the field-reading defaults for `parent` and `offsets` on
`AbstractOffsetArray` and make the three accessors (`Base.parent`,
`OffsetArrays.offsets`, `OffsetArrays.parenttype`) the documented subtype
interface, defined for `OffsetArray` only. `parenttype` moves next to them
and gains a generic instance method.

`Base.parent(::AbstractOffsetArray)` now throws an `ArgumentError` naming
the interface: without that guard Base's `parent(a::AbstractArray) = a`
makes a subtype that forgets `parent` recurse through `axes` into a
`StackOverflowError`.

Document `AbstractOffsetVector`, add `AbstractOffsetMatrix` for parity
with `OffsetMatrix`, and list both plus `parenttype` in the reference.
The manual example and the test subtypes define the three methods; tests
cover the aliases, `parenttype`, and the error for incomplete subtypes.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VuNog1u33YxkfTsqyeDB8F
`Base.IndexStyle`, `Base.elsize`, `Adapt.parent_type` and
`Adapt.unwrap_type` were the only methods still restricted to the
concrete `OffsetArray`, because they need the parent type without an
instance. Now that `parenttype` is part of the subtype interface they
dispatch on `AbstractOffsetArray`.

Without `elsize`, `strides` advertised subtypes as strided but
`pointer(B, i)` and pointers into views of `B` threw a `MethodError`;
without `IndexStyle`, subtypes silently fell back to Cartesian indexing.

The test subtypes drop their hand-written `IndexStyle` methods, and the
tests cover `IndexStyle`, `elsize`, `pointer(B, i)`, a pointer into a
column view, and `adapt`/`Adapt.parent_type`/`unwrap_type` on a subtype.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VuNog1u33YxkfTsqyeDB8F
`fill!` went through `parent_call`, which fills the parent and then
rebuilds the wrapper as an `OffsetArray`. For `OffsetArray` itself the
rebuilt wrapper is `===` to the argument, but for any other subtype the
data was mutated in place while a different wrapper type was returned,
breaking the `fill!` contract. Fill the parent and return `A` directly,
and stop listing `fill!` among the operations `unwrap` controls.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VuNog1u33YxkfTsqyeDB8F
Only the `OffsetArray` constructor runs `overflow_check`; `axes` on an
`AbstractOffsetArray` adds the stored offsets to the parent's axes
unchecked, so a subtype that stores offsets without validating them can
produce wrapped-around axes silently.

Give `overflow_check` a docstring (it is specified for `Int` axes and
accepts any `Integer` offset), state in the `AbstractOffsetArray`
docstring and the manual that a subtype's constructor is responsible for
calling it, and have the manual example and the test subtype do so.
The test constructs a parent whose axis ends at `typemax(Int)` and
checks that the subtype rejects an offset that would overflow.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VuNog1u33YxkfTsqyeDB8F
`AbstractOffsetArray`, `AbstractOffsetVector`, `AbstractOffsetMatrix`,
`offsets`, `parenttype`, `unwrap` and `overflow_check` are documented
extension points, so declare them `public` on Julia >= 1.11. The
declaration is parsed from a string because the keyword does not parse
on older versions, which the package still supports. The test uses the
same version bound so the file still loads there.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VuNog1u33YxkfTsqyeDB8F
State precisely which operations go through `unwrap` (`copy`, `zero`,
`map(T, A)`, all-`Colon` indexing, `Adapt.adapt`) and which always
return an `OffsetArray` (`similar`, broadcasting, range indexing,
`AbstractArray{T}(A)`, `convert`, `reshape` to offset axes).

Note that the range specialisations are keyed on `OffsetVector` wrapping
an `AbstractRange`, so a subtype wrapping a range uses the generic
`AbstractArray` fallbacks, and test that those give the same values.

Comment in `showarg` that `nameof` yields the struct name, so the
`OffsetVector`/`OffsetMatrix` aliases keep printing as `OffsetArray`,
and pin that. Add the `sizeof` comparison that states the motivation of
issue 370: narrow offsets make the wrapper smaller than an `OffsetArray`.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VuNog1u33YxkfTsqyeDB8F
`similar(::Type{<:OffsetArray}, dims)` allocates an `Array{eltype}`; the
same default serves any subtype, so widen the signature. Without it,
`similar(S, dims)` for a subtype type `S` hits Base's `T(undef, dims)`
fallback and throws, and so does `similar(S, offset_axes)`, which
bottoms out in this method. `Array{eltype(A)}` rather than
`parenttype(A)` keeps partially specified types such as `S{Float64}`
working, as for `OffsetVector{Float64}`.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VuNog1u33YxkfTsqyeDB8F
@giordano

Copy link
Copy Markdown
Contributor

Mumble mumble mumble...tests haven't been running in this repo since last year for some reason, sigh

@giordano giordano closed this Oct 19, 2026
@giordano giordano reopened this Oct 19, 2026
giordano and others added 4 commits October 19, 2026 19:02
The testset resized the parent vector after `permutedims` had reshaped
it. On Julia < 1.11 `reshape` marks an `Array` as shared and Base then
refuses to resize it, so `pop!` and `push!` failed on 1.10 with "cannot
resize array with shared data". Resize a vector that has not been
reshaped instead; the `permutedims` checks are unchanged.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VuNog1u33YxkfTsqyeDB8F
Compare the pointer of a view with offset axes against the OffsetArray
reference rather than the parent: on old Julia versions Base computes
the pointer of such a view one element off, for OffsetArray and
subtypes alike, so the previous assertion could not hold there.

Use `adapt(Array, N)` and check for `OffsetArray{Int,2}`: converting
the eltype through `adapt(Array{Float64}, ...)` is Adapt 4 behaviour,
and the test is about the wrapper being rebuilt, not about the eltype.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VuNog1u33YxkfTsqyeDB8F
@giordano

Copy link
Copy Markdown
Contributor

I got some help for a review from a couple of bots and they flagged some gaps in the interface (I'm not very familiar with this package, so it's not surprising I had missed them), which should now be fixed. After separately fixing the CI infrastructure in #396 (it was in a fairly bad shape), tests are now passing in Julia v1.0 through v1.15.

@jishnub I'm somewhat inclined to merge this next week if I don't hear objections in the meantime.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants