Full Code of JuliaArrays/OffsetArrays.jl for AI

master b0d729d61518 cached
26 files
204.0 KB
67.3k tokens
1 requests
Download .txt
Showing preview only (213K chars total). Download the full file or copy to clipboard to get everything.
Repository: JuliaArrays/OffsetArrays.jl
Branch: master
Commit: b0d729d61518
Files: 26
Total size: 204.0 KB

Directory structure:
gitextract_gvhhoopx/

├── .github/
│   ├── dependabot.yml
│   └── workflows/
│       ├── CompatHelper.yml
│       ├── TagBot.yml
│       ├── UnitTest.yml
│       ├── docs.yml
│       └── invalidations.yml
├── .gitignore
├── LICENSE.md
├── Project.toml
├── README.md
├── benchmark/
│   └── benchmarks.jl
├── docs/
│   ├── .gitignore
│   ├── Project.toml
│   ├── make.jl
│   └── src/
│       ├── index.md
│       ├── internals.md
│       └── reference.md
├── ext/
│   └── OffsetArraysAdaptExt.jl
├── src/
│   ├── OffsetArrays.jl
│   ├── axes.jl
│   ├── origin.jl
│   ├── precompile.jl
│   └── utils.jl
└── test/
    ├── customranges.jl
    ├── origin.jl
    └── runtests.jl

================================================
FILE CONTENTS
================================================

================================================
FILE: .github/dependabot.yml
================================================
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
version: 2
updates:
  - package-ecosystem: "github-actions"
    directory: "/" # Location of package manifests
    schedule:
      interval: "weekly"


================================================
FILE: .github/workflows/CompatHelper.yml
================================================
name: CompatHelper
on:
  schedule:
    - cron: 0 0 * * *
  workflow_dispatch:
permissions:
  contents: write
  pull-requests: write
jobs:
  CompatHelper:
    runs-on: ubuntu-latest
    steps:
      - name: Check if Julia is already available in the PATH
        id: julia_in_path
        run: which julia
        continue-on-error: true
      - name: Install Julia, but only if it is not already available in the PATH
        uses: julia-actions/setup-julia@v2
        with:
          version: '1'
          arch: ${{ runner.arch }}
        if: steps.julia_in_path.outcome != 'success'
      - name: "Add the General registry via Git"
        run: |
          import Pkg
          ENV["JULIA_PKG_SERVER"] = ""
          Pkg.Registry.add("General")
        shell: julia --color=yes {0}
      - name: "Install CompatHelper"
        run: |
          import Pkg
          name = "CompatHelper"
          uuid = "aa819f21-2bde-4658-8897-bab36330d9b7"
          version = "3"
          Pkg.add(; name, uuid, version)
        shell: julia --color=yes {0}
      - name: "Run CompatHelper"
        run: |
          import CompatHelper
          CompatHelper.main()
        shell: julia --color=yes {0}
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          COMPATHELPER_PRIV: ${{ secrets.DOCUMENTER_KEY }}
          # COMPATHELPER_PRIV: ${{ secrets.COMPATHELPER_PRIV }}


================================================
FILE: .github/workflows/TagBot.yml
================================================
name: TagBot
on:
  issue_comment:
    types:
      - created
  workflow_dispatch:
jobs:
  TagBot:
    if: github.event_name == 'workflow_dispatch' || github.actor == 'JuliaTagBot'
    runs-on: ubuntu-latest
    steps:
      - uses: JuliaRegistries/TagBot@v1
        with:
          token: ${{ secrets.GITHUB_TOKEN }}
          ssh: ${{ secrets.TAGBOT }}


================================================
FILE: .github/workflows/UnitTest.yml
================================================
name: Unit test

on:
  push:
    tags:
      - 'v*'
    branches:
      - master
    paths-ignore:
      - 'LICENSE.md'
      - 'README.md'
      - '.github/workflows/TagBot.yml'
  pull_request:
    paths-ignore:
      - 'LICENSE.md'
      - 'README.md'
      - '.github/workflows/TagBot.yml'
  schedule:
    - cron: '20 00 1 * *'

concurrency:
  group: build-${{ github.event.pull_request.number || github.ref }}-${{ github.workflow }}
  cancel-in-progress: true

jobs:
  test:
    runs-on: ${{ matrix.os }}
    strategy:
      # allow-failure is not supported yet
      # https://github.com/actions/toolkit/issues/399
      fail-fast: false
      matrix:
        julia-version: ['1.0', 'lts', '1', 'pre']
        os: [ubuntu-latest, windows-latest, macOS-latest]
        julia-arch: [x64]
        # only test one 32-bit job
        include:
          - os: ubuntu-latest
            julia-version: '1'
            julia-arch: x86

    steps:
      - uses: actions/checkout@v6
      - uses: julia-actions/setup-julia@v2
        with:
          version: ${{ matrix.julia-version }}
          arch: ${{ matrix.julia-arch }}
      - uses: julia-actions/cache@v3
      - name: "Unit Test"
        uses: julia-actions/julia-runtest@v1
      - uses: julia-actions/julia-processcoverage@v1
      - uses: codecov/codecov-action@v6
        with:
          token: ${{ secrets.CODECOV_TOKEN }}
          files: lcov.info


================================================
FILE: .github/workflows/docs.yml
================================================
name: Documentation

on:
  pull_request:
  push:
    branches:
      - 'master'
      - 'release-'
    tags: '*'
  release:
    types: [published]

jobs:
  build:
    runs-on: ${{ matrix.os }}
    strategy:
      matrix:
        julia-version: [1]
        os: [ubuntu-latest]
    steps:
      - uses: actions/checkout@v6
      - uses: julia-actions/setup-julia@latest
        with:
          version: ${{ matrix.julia-version }}
      - name: Cache artifacts
        uses: actions/cache@v5
        env:
          cache-name: cache-artifacts
        with:
          path: ~/.julia/artifacts
          key: ${{ runner.os }}-test-${{ env.cache-name }}-${{ hashFiles('**/Project.toml') }}
          restore-keys: |
            ${{ runner.os }}-test-${{ env.cache-name }}-
            ${{ runner.os }}-test-
            ${{ runner.os }}-
      - name: Install dependencies
        run: julia --project=docs/ -e 'using Pkg; Pkg.develop(PackageSpec(path=pwd())); Pkg.instantiate()'
      - name: Build and deploy
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: julia --project=docs/ docs/make.jl


================================================
FILE: .github/workflows/invalidations.yml
================================================
name: Invalidations
on: pull_request

jobs:
  evaluate:
    runs-on: ubuntu-latest
    steps:
    - uses: julia-actions/setup-julia@v2
      with:
        version: '1.10'
    - uses: actions/checkout@v6
    - uses: julia-actions/julia-buildpkg@latest
    - uses: julia-actions/julia-invalidations@v1
      id: invs_pr

    - uses: actions/checkout@v6
      with:
        ref: 'master'
    - uses: julia-actions/julia-buildpkg@latest
    - uses: julia-actions/julia-invalidations@v1
      id: invs_master

    - name: Report invalidation counts
      run: |
        echo "Invalidations on master: ${{ steps.invs_master.outputs.total }} (${{ steps.invs_master.outputs.deps }} via deps)"
        echo "This branch: ${{ steps.invs_pr.outputs.total }} (${{ steps.invs_pr.outputs.deps }} via deps)"
      shell: bash
    - name: PR doesn't increase number of invalidations
      run: |
        if (( ${{ steps.invs_pr.outputs.total }} > ${{ steps.invs_master.outputs.total }} )); then
            exit 1
        fi
      shell: bash


================================================
FILE: .gitignore
================================================
Manifest.toml
Manifest-v*.*.toml
docs/build
LocalPreferences.toml
.vscode


================================================
FILE: LICENSE.md
================================================
The FArrayMod.jl package is licensed under the MIT "Expat" License:

> Copyright (c) 2014:
>  * Alexander Samoilov
>
> Permission is hereby granted, free of charge, to any person obtaining
> a copy of this software and associated documentation files (the
> "Software"), to deal in the Software without restriction, including
> without limitation the rights to use, copy, modify, merge, publish,
> distribute, sublicense, and/or sell copies of the Software, and to
> permit persons to whom the Software is furnished to do so, subject to
> the following conditions:
>
> The above copyright notice and this permission notice shall be
> included in all copies or substantial portions of the Software.
>
> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
> EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
> MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
> IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
> CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
> TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
> SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.


================================================
FILE: Project.toml
================================================
name = "OffsetArrays"
uuid = "6fe1bfb0-de20-5000-8ca7-80f57d26f881"
version = "1.17.0"

[deps]
Adapt = "79e6a3ab-5dfb-504d-930d-738a2a938a0e"

[weakdeps]
Adapt = "79e6a3ab-5dfb-504d-930d-738a2a938a0e"

[extensions]
OffsetArraysAdaptExt = "Adapt"

[compat]
Adapt = "2, 3, 4"
Aqua = "0.8"
CatIndices = "0.2"
DelimitedFiles = "<0.0.1, 1"
DistributedArrays = "0.6"
Documenter = "0.27, 1"
EllipsisNotation = "1"
FillArrays = "0.11, 0.13, 1"
LinearAlgebra = "<0.0.1, 1"
StaticArrays = "1"
Test = "<0.0.1, 1"
julia = "0.7, 1"

[extras]
Adapt = "79e6a3ab-5dfb-504d-930d-738a2a938a0e"
Aqua = "4c88cf16-eb10-579e-8560-4a9242c79595"
CatIndices = "aafaddc9-749c-510e-ac4f-586e18779b91"
DelimitedFiles = "8bb1440f-4735-579b-a4ab-409b98df4dab"
DistributedArrays = "aaf54ef3-cdf8-58ed-94cc-d582ad619b94"
Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4"
EllipsisNotation = "da5c29d0-fa7d-589e-88eb-ea29b0a81949"
FillArrays = "1a297f60-69ca-5386-bcde-b61e274b549b"
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
StaticArrays = "90137ffa-7385-5640-81b9-e52037218182"
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"

[targets]
test = ["Adapt", "Aqua", "CatIndices", "DistributedArrays", "DelimitedFiles", "Documenter", "EllipsisNotation", "FillArrays", "LinearAlgebra", "StaticArrays", "Test"]


================================================
FILE: README.md
================================================
# OffsetArrays.jl

| Documentation |  Build Status | Code coverage | Version |
| :-: | :-: | :-: | :-: |
| [![][docs-stable-img]][docs-stable-url] |  [![][action-img]][action-url] | [![][codecov-img]][codecov-url] | [![][ver-img]][ver-url]  |
| [![][docs-dev-img]][docs-dev-url]  |  [![][pkgeval-img]][pkgeval-url] | | [![][deps-img]][deps-url] |

## Introduction

OffsetArrays provides Julia users with arrays that have arbitrary
indices, similar to those found in some other programming languages
like Fortran.

An `OffsetArray` is a lightweight wrapper around an `AbstractArray` that shifts its indices.
Generally, indexing into an `OffsetArray` should be as performant as the parent array.

## Usage

There are two ways to construct `OffsetArray`s: by specifying the axes of the array, or
by specifying its origin.

The first way to construct an `OffsetArray` by specifying its axes is:

```julia
OA = OffsetArray(A, axis1, axis2, ...)
```

where you want `OA` to have axes `(axis1, axis2, ...)` and be indexed by values that
fall within these axis ranges. Example:

```julia
julia> using OffsetArrays

julia> A = Float64.(reshape(1:15, 3, 5))
3×5 Matrix{Float64}:
 1.0  4.0  7.0  10.0  13.0
 2.0  5.0  8.0  11.0  14.0
 3.0  6.0  9.0  12.0  15.0

julia> axes(A) # indices of a Matrix start from 1 along each axis
(Base.OneTo(3), Base.OneTo(5))

julia> OA = OffsetArray(A, -1:1, 0:4) # OA will have the axes (-1:1, 0:4)
3×5 OffsetArray(::Matrix{Float64}, -1:1, 0:4) with eltype Float64 with indices -1:1×0:4:
 1.0  4.0  7.0  10.0  13.0
 2.0  5.0  8.0  11.0  14.0
 3.0  6.0  9.0  12.0  15.0

julia> OA[-1, 0]
1.0

julia> OA[1, 4]
15.0
```

The second way to construct an `OffsetArray` is by specifying the origin, that is, the first index
along each axis. This is particularly useful if one wants, eg., arrays that are 0-indexed as opposed
to 1-indexed.

A convenient way to construct an `OffsetArray` this way is by using `OffsetArrays.Origin`:

```julia
julia> using OffsetArrays: Origin

julia> Origin(0)(A) # indices begin at 0 along all axes
3×5 OffsetArray(::Matrix{Float64}, 0:2, 0:4) with eltype Float64 with indices 0:2×0:4:
 1.0  4.0  7.0  10.0  13.0
 2.0  5.0  8.0  11.0  14.0
 3.0  6.0  9.0  12.0  15.0

julia> Origin(2, 3)(A) # indices begin at 2 along the first axis and 3 along the second
3×5 OffsetArray(::Matrix{Float64}, 2:4, 3:7) with eltype Float64 with indices 2:4×3:7:
 1.0  4.0  7.0  10.0  13.0
 2.0  5.0  8.0  11.0  14.0
 3.0  6.0  9.0  12.0  15.0
```

While the examples here refer to the common case where the parent arrays have indices starting at 1,
this is not necessary. An `OffsetArray` may wrap any array that has integer indices, irrespective of
where the indices begin.

## How to go back to 1-indexed arrays

Certain libraries, such as `LinearAlgebra`, require arrays to be indexed from 1. Passing an `OffsetArray`
with shifted indices would lead to an error here.

```julia
julia> A = Float64.(reshape(1:16, 4, 4));

julia> AO = Origin(0)(A);

julia> using LinearAlgebra

julia> Diagonal(AO)
ERROR: ArgumentError: offset arrays are not supported but got an array with index other than 1
```

The way to obtain a `1`-indexed array from an `OffsetArray` is by using `OffsetArrays.no_offset_view`.

An example of this is:

```julia
julia> OffsetArrays.no_offset_view(AO)
4×4 Matrix{Float64}:
 1.0  5.0   9.0  13.0
 2.0  6.0  10.0  14.0
 3.0  7.0  11.0  15.0
 4.0  8.0  12.0  16.0
```

This may now be passed to `LinearAlgebra`:

```julia
julia> D = Diagonal(OffsetArrays.no_offset_view(AO))
4×4 Diagonal{Float64, Vector{Float64}}:
 1.0   ⋅     ⋅     ⋅
  ⋅   6.0    ⋅     ⋅
  ⋅    ⋅   11.0    ⋅
  ⋅    ⋅     ⋅   16.0
```

If we want to restore the original indices of `AO`, we may wrap an `OffsetArray` around the `Diagonal` as:

```julia
julia> Origin(AO)(D)
4×4 OffsetArray(::Diagonal{Float64, Vector{Float64}}, 0:3, 0:3) with eltype Float64 with indices 0:3×0:3:
 1.0   ⋅     ⋅     ⋅
  ⋅   6.0    ⋅     ⋅
  ⋅    ⋅   11.0    ⋅
  ⋅    ⋅     ⋅   16.0
```

Here, `Origin(AO)` is able to automatically infer and use the indices of `AO`.

## Best practice on adopting OffsetArrays

For some applications, OffsetArrays give users an easy-to-understand interface. However, handling
the non-conventional axes of OffsetArrays requires extra care. Otherwise, the code might
error, crash, or return incorrect results. You can read [the Julialang documentation on
offset](https://docs.julialang.org/en/v1/devdocs/offset-arrays/) for more information. Here
we briefly summarize some of the best practices for users and package authors.

### There is no need to support OffsetArrays for every function

You don't need to support offset arrays for _internal functions_ that only consume standard 1-based
arrays -- it doesn't change or improve anything.

You don't need to support offset arrays for functions that _have no well-defined behavior on custom
axes_. For instance, many linear algebra functions such as matrix multiplication `A * B` does not
have an agreed behavior for offset arrays. In this case, it is a better practice to let users do the
conversion.

The helper function `Base.require_one_based_indexing` can be used to early check the axes and throw
a meaningful error. If your interface functions do not intend to support offset arrays, we recommend
you add this check before starting the real computation.

### use `axes` instead of `size`/`length`

Many implementations assume the array axes start at 1 by writing loops such as `for i in
1:length(x)` or `for i in 1:size(x, 1)`. A better practice is to use `for i in eachindex(x)` or `for
i in axes(x, 1)` -- `axes` provides more information than `size` with no performance overhead.

Also, if you know what indices type you want to use, [`LinearIndices`][doc_LinearIndices] and
[`CartesianIndices`][doc_CartesianIndices] allow you to loop multidimensional arrays efficiently
without worrying about the axes.

### test against OffsetArrays

For package authors that declare support for `AbstractArray`, we recommend having a few test cases
against `OffsetArray` to ensure the function works well for arrays with custom axes. This gives you
more confidence that users don't run into strange situations.

For package users that want to use offset arrays, many numerical correctness issues come from the
fact that `@inbounds` is used inappropriately with the 1-based indexing assumption. Thus for debug
purposes, it is not a bad idea to start Julia with `--check-bounds=yes`, which turns all `@inbounds`
into a no-op and uncover potential out-of-bound errors.

<!-- badges -->

[pkgeval-img]: https://juliaci.github.io/NanosoldierReports/pkgeval_badges/O/OffsetArrays.svg
[pkgeval-url]: https://juliaci.github.io/NanosoldierReports/pkgeval_badges/report.html

[action-img]: https://github.com/JuliaArrays/OffsetArrays.jl/workflows/Unit%20test/badge.svg
[action-url]: https://github.com/JuliaArrays/OffsetArrays.jl/actions

[codecov-img]: https://codecov.io/github/JuliaArrays/OffsetArrays.jl/coverage.svg?branch=master
[codecov-url]: https://codecov.io/gh/JuliaArrays/OffsetArrays.jl

[docs-stable-img]: https://img.shields.io/badge/docs-stable-blue.svg
[docs-stable-url]: https://juliaarrays.github.io/OffsetArrays.jl/stable/
[docs-dev-img]: https://img.shields.io/badge/docs-dev-blue.svg
[docs-dev-url]: https://juliaarrays.github.io/OffsetArrays.jl/dev/

[ver-img]: https://juliahub.com/docs/OffsetArrays/version.svg
[ver-url]: https://juliahub.com/ui/Packages/OffsetArrays/UDEDl

[deps-img]: https://juliahub.com/docs/OffsetArrays/deps.svg
[deps-url]: https://juliahub.com/ui/Packages/OffsetArrays/UDEDl

[doc_LinearIndices]: https://docs.julialang.org/en/v1/base/arrays/#Base.LinearIndices
[doc_CartesianIndices]: https://docs.julialang.org/en/v1/base/arrays/#Base.IteratorsMD.CartesianIndices


================================================
FILE: benchmark/benchmarks.jl
================================================
using BenchmarkTools
using OffsetArrays

const dim = 1000

x = Array{Float64}(undef, 2*dim);
y = OffsetArray{Float64}(undef, -dim + 1 : dim);
x2d = Array{Float64}(undef, 2*dim, 2*dim);
y2d = OffsetArray{Float64}(undef, -dim + 1 : dim, -dim + 1 : dim);

s = OffsetVector(1:dim, 0);
sur = 1:dim;
sior = OffsetArrays.IdOffsetRange(parent(s));

fill1d(x) = for i in axes(x,1); x[i] = i; end
fill2d(x) = for j in axes(x,2); for i in axes(x,1); x[i,j] = i + j; end; end
update(x) = for i in axes(x,1); x[i] = x[i] + i; end
update2d(x) = for j in axes(x,2); for i in axes(x,1); x[i,j] = x[i,j] + i + j; end; end
update_eachindex(x) = for i in eachindex(x); x[i] = x[i] + i; end

unsafe_fill(x) = @inbounds(for i in axes(x,1); x[i] = i; end)
unsafe_fill2d(x) = @inbounds(for j in axes(x,2); for i in axes(x,1); x[i,j] = i + j; end; end)
unsafe_update(x) = @inbounds(for i in axes(x,1); x[i] = x[i] + i; end)
unsafe_update2d(x) = @inbounds(for j in axes(x,2); for i in axes(x,1); x[i,j] = x[i,j] + i + j; end; end)
unsafe_update_eachindex(x) = @inbounds(for i in eachindex(x); x[i] = x[i] + i; end)

vectorlinearindexing(a, ax) = a[ax]
vectorCartesianindexing(a, ax1, ax2) = a[ax1, ax2]
nestedvectorlinearindexing(a, ax1, ax2) = a[ax1[ax2]]

macro showbenchmark(ex)
	print(ex, " : ")
	quote
		println(@benchmark esc($ex))
	end
end

@showbenchmark fill1d(x)
@showbenchmark fill1d(y)
@showbenchmark fill2d(x2d)
@showbenchmark fill2d(y2d)
@showbenchmark update(x)
@showbenchmark update(y)
@showbenchmark update2d(x2d)
@showbenchmark update2d(y2d)
@showbenchmark update_eachindex(x)
@showbenchmark update_eachindex(y)

@showbenchmark unsafe_fill(x)
@showbenchmark unsafe_fill(y)
@showbenchmark unsafe_fill2d(x2d)
@showbenchmark unsafe_fill2d(y2d)
@showbenchmark unsafe_update(x)
@showbenchmark unsafe_update(y)
@showbenchmark unsafe_update2d(x2d)
@showbenchmark unsafe_update2d(y2d)
@showbenchmark unsafe_update_eachindex(x)
@showbenchmark unsafe_update_eachindex(y)

# Benchmarks of vector indexing using OffsetRanges as axes
@showbenchmark vectorlinearindexing(x, s)
@showbenchmark vectorlinearindexing(x, sur)
@showbenchmark vectorlinearindexing(x, sior)
@showbenchmark vectorlinearindexing(y, s)
@showbenchmark vectorlinearindexing(y, sur)
@showbenchmark vectorlinearindexing(y, sior)

@showbenchmark vectorlinearindexing(sur, s)
@showbenchmark vectorlinearindexing(sur, sur)
@showbenchmark vectorlinearindexing(sur, sior)

@showbenchmark vectorCartesianindexing(x2d, s, s)
@showbenchmark vectorCartesianindexing(x2d, sur, sur)
@showbenchmark vectorCartesianindexing(x2d, sior, sior)

@showbenchmark nestedvectorlinearindexing(x, s, s)
@showbenchmark nestedvectorlinearindexing(x, sur, sur)
@showbenchmark nestedvectorlinearindexing(x, s, sior)
@showbenchmark nestedvectorlinearindexing(x, sur, sior)
@showbenchmark nestedvectorlinearindexing(x, sior, sior)
@showbenchmark vectorlinearindexing(x, sior[sior])
@showbenchmark nestedvectorlinearindexing(x, sur, sior)
@showbenchmark vectorlinearindexing(x, sur[sior])
@showbenchmark nestedvectorlinearindexing(x, sior, sur)
@showbenchmark vectorlinearindexing(x, sior[sur])
@showbenchmark nestedvectorlinearindexing(x, sur, sur)


================================================
FILE: docs/.gitignore
================================================
build/
site/


================================================
FILE: docs/Project.toml
================================================
[deps]
Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4"
JSON = "682c06a0-de6a-54ab-a142-c8b1cf79cde6"
OffsetArrays = "6fe1bfb0-de20-5000-8ca7-80f57d26f881"

[compat]
Documenter = "0.27"
JSON = "0.21"


================================================
FILE: docs/make.jl
================================================
using Documenter, JSON
using OffsetArrays

DocMeta.setdocmeta!(OffsetArrays, :DocTestSetup, :(using OffsetArrays); recursive=true)

makedocs(
    sitename = "OffsetArrays",
    format = Documenter.HTML(prettyurls = get(ENV, "CI", nothing) == "true"),
    pages = ["index.md", "internals.md", "reference.md"],
    modules = [OffsetArrays],
    doctestfilters = [r"at \./.*", r"at /home.*", r"top-level scope.*", r"\[\d*\]\s*$"],   # for backtraces
)

# a workdaround to github action that only push preview when PR has "push_preview" labels
# issue: https://github.com/JuliaDocs/Documenter.jl/issues/1225
function should_push_preview(event_path = get(ENV, "GITHUB_EVENT_PATH", nothing))
    event_path === nothing && return false
    event = JSON.parsefile(event_path)
    haskey(event, "pull_request") || return false
    labels = [x["name"] for x in event["pull_request"]["labels"]]
    return "push_preview" in labels
 end

deploydocs(
    repo = "github.com:JuliaArrays/OffsetArrays.jl.git",
    push_preview = should_push_preview()
)


================================================
FILE: docs/src/index.md
================================================
# OffsetArrays.jl

OffsetArrays provides Julia users with arrays that have arbitrary
indices, similar to those found in some other programming languages
like Fortran. Below is the basic usage found in the README, followed
by a couple of short examples illustrating circumstances in which
OffsetArrays can be useful. For a lengthier discussion, see
[this blog post](https://julialang.org/blog/2017/04/offset-arrays/).

## Usage

You can construct such arrays as follows:

```julia
OA = OffsetArray(A, axis1, axis2, ...)
```

where you want `OA` to have axes `(axis1, axis2, ...)` and be indexed by values that
fall within these axis ranges.

```@repl index
using OffsetArrays

A = Float64.(reshape(1:15, 3, 5))

OA = OffsetArray(A, -1:1, 0:4) # OA will have axes (-1:1, 0:4)

OA = OffsetArray(A, CartesianIndex(-1, 0):CartesianIndex(1, 4))

OA[-1,0], OA[1,4]
```

You could also pass integers as offsets, where `0` means no offsets are applied:

```@repl index
OA = OffsetArray(A, -2, -1)
```

When you create a new `OffsetArray` on the top of another `OffsetArray`, the offsets are
accumulated:

```@repl index
OOA = OffsetArray(OA, 2, 1)
```

For the special cases that you want to compensate the offset back to the ordinary 1-based array, you
can use [`OffsetArrays.no_offset_view(A)`](@ref). Furthermore, you could use
`Base.require_one_based_indexing` if you want to ensure the array does not have offsets.

```@repl index
OffsetArrays.no_offset_view(OA)

Base.require_one_based_indexing(ans)

Base.require_one_based_indexing(OA)
```

[`OffsetArrays.Origin`](@ref) can be convenient if you want to directly specify the origin of the output
OffsetArray, it will automatically compute the corresponding offsets. For example:

```@repl index
OffsetArray(A, OffsetArrays.Origin(-1, -1))
OffsetArray(OA, OffsetArrays.Origin(-1, -1))
```

An equivalent — but possibly more convenient — way to specify the origin of an array is

```@repl index
OffsetArrays.Origin(-1, -1)(A)
```

Sometimes, it will be convenient to shift the center coordinate of the given array to `(0, 0, ...)`,
`OffsetArrays.centered` is a helper for this very purpose:

```@repl index
Ao = OffsetArrays.centered(A)
Ao[0, 0] == 8.0
```

and `OffsetArrays.center` tells you the center coordinate of given array:

```@repl index
c = OffsetArrays.center(A)
A[c...] == 8.0
```

## Example: Relativistic Notation

Suppose we have a position vector `r = [:x, :y, :z]` which is naturally one-based, ie. `r[1] == :x`, `r[2] == :y`,  `r[3] == :z` and we also want to construct a relativistic position vector which includes time as the 0th component. This can be done with OffsetArrays like

```jldoctest; setup = :(using OffsetArrays)
julia> r = [:x, :y, :z];

julia> x = OffsetVector([:t, r...], 0:3)
4-element OffsetArray(::Vector{Symbol}, 0:3) with eltype Symbol with indices 0:3:
 :t
 :x
 :y
 :z

julia> x[0]
:t

julia> x[1:3]
3-element Vector{Symbol}:
 :x
 :y
 :z
```

## Example: Polynomials

Suppose one wants to represent the Laurent polynomial

```math
6/x + 5 - 2*x + 3*x^2 + x^3
```

The coefficients of this polynomial are a naturally `-1` based list, since the `n`th element of the list
(counting from `-1`) `6, 5, -2, 3, 1` is the coefficient corresponding to the `n`th power of `x`. This Laurent polynomial can be evaluated at say `x = 2` as follows.

```jldoctest; setup = :(using OffsetArrays)
julia> coeffs = OffsetVector([6, 5, -2, 3, 1], -1:3)
5-element OffsetArray(::Vector{Int64}, -1:3) with eltype Int64 with indices -1:3:
  6
  5
 -2
  3
  1

julia> polynomial(x, coeffs) = sum(coeffs[n]*x^n for n in eachindex(coeffs))
polynomial (generic function with 1 method)

julia> polynomial(2.0, coeffs)
24.0
```

Notice our use of the `eachindex` function which does not assume that the given array starts at `1`.


================================================
FILE: docs/src/internals.md
================================================
# For developers

Writing code that supports OffsetArrays is generally fairly straightforward.
The majority of cases can be handled with these tips:

- replace many uses of `size` with `axes`
- replace `1:length(A)` with `eachindex(A)`, or if you need an integer index with `LinearIndices(A)`
- replace explicit allocations like `Array{Int}(undef, size(B))` with `similar(Array{Int}, axes(B))`

More information can be found in [Julia's developer documentation](https://docs.julialang.org/en/v1/devdocs/offset-arrays/).
The most subtle issues tend to arise around the axes, and further detail specific to
OffsetArrays.jl follows below.

## Internals

How does OffsetArrays work? The fundamental principle is very simple:
an `OffsetArray` is just a wrapper around a "parent" array, together
with an index offset:

```jldoctest oa; setup=:(using OffsetArrays)
julia> oa = OffsetArray([1 2; 3 4], 0:1, 5:6)
2×2 OffsetArray(::Matrix{Int64}, 0:1, 5:6) with eltype Int64 with indices 0:1×5:6:
 1  2
 3  4

julia> parent(oa)
2×2 Matrix{Int64}:
 1  2
 3  4

julia> oa.offsets
(-1, 4)
```

So `parent(oa)` is the original array we constructed it with, and `oa.offsets` is a tuple,
each entry encoding the index-shift to be applied along the corresponding axis.
When you index `oa[i,j]`, it "translates" the `i,j` indexes back to the parent array's
indexes and then returns the value in the parent.

## The axes of OffsetArrays

The internal of offset computing is achieved by [`IdOffsetRange`](@ref OffsetArrays.IdOffsetRange)
type:

```jldoctest oa
julia> ax = axes(oa, 2)
OffsetArrays.IdOffsetRange(values=5:6, indices=5:6)
```

This has a similar design to `Base.IdentityUnitRange` that `ax[x] == x` always holds.

```jldoctest oa
julia> ax[5]
5
julia> ax[1]
ERROR: BoundsError: attempt to access 2-element OffsetArrays.IdOffsetRange{Int64, Base.OneTo{Int64}} with indices 5:6 at index [1]
[...]
```

This property makes sure that they tend to be their own axes:

```jldoctest oa
julia> axes(ax)
(OffsetArrays.IdOffsetRange(values=5:6, indices=5:6),)

julia> axes(ax[ax])
(OffsetArrays.IdOffsetRange(values=5:6, indices=5:6),)
```

This example of indexing is [idempotent](https://en.wikipedia.org/wiki/Idempotence).
This is a useful characteristic for ensuring the "fundamental axiom" of generalized indexing,
that `a[ax][i] == a[ax[i]]`:

```jldoctest; setup=:(using OffsetArrays)
julia> oa2 = OffsetArray([5, 10, 15, 20], 0:3)
4-element OffsetArray(::Vector{Int64}, 0:3) with eltype Int64 with indices 0:3:
  5
 10
 15
 20

julia> ax2 = axes(oa2, 1)
OffsetArrays.IdOffsetRange(values=0:3, indices=0:3)

julia> oa2[2]
15

julia> oa2[ax2][2]
15

julia> oa2[ax2[2]]
15
```

`IdOffsetRange`s apply the offset both to the values and the indices of the range, and otherwise preserve the parent range.

!!! warning

    There are circumstances where constructing a specific type of `IdOffsetRange` cannot be supported without changing the axes of the range (see [`OffsetArrays.IdOffsetRange`](@ref).)
    In the future, this package will distinguish between *construction*  and *conversion*:

    - construction (aka, *coercion*) will always succeed, even if it has to change the axes of the result (Examples: `RangeType(rng)`, `typeof(rng1)(rng2)`)
    - conversion will succeed only if it can preserve both the values and the axes (Examples: `convert(RangeType, rng)`, `oftype(rng1, rng2)`)

    While these behave equivalently now (conversion currently performs coercion), developers are encouraged to "future-proof" their code by choosing the behavior appropriate for each usage.


## Wrapping other offset array types

An `OffsetArray` may wrap any subtype of `AbstractArray`, including ones that do not use `1`-based indexing. Such arrays however need to satisfy the fundamental axiom of idempotent indexing for things to work correctly. In other words, an axis of an offset array needs to have the same values as its own axis. This property is built into `OffsetArray`s if the parent uses 1-based indexing, but it's up to the user to ensure the correctness in case a type is to be wrapped that uses offset indices.

We demonstrate this through an example by creating a custom 0-based range type that we wrap in an `OffsetArray`:

```jldoctest zerobasedrange; setup=:(using OffsetArrays)
julia> struct ZeroBasedRange{T,A<:AbstractRange{T}} <: AbstractRange{T}
           a :: A
           function ZeroBasedRange(a::AbstractRange{T}) where {T}
               @assert !Base.has_offset_axes(a)
               new{T, typeof(a)}(a)
           end
       end;

julia> Base.parent(A::ZeroBasedRange) = A.a;

julia> Base.first(A::ZeroBasedRange) = first(A.a);

julia> Base.length(A::ZeroBasedRange) = length(A.a);

julia> Base.last(A::ZeroBasedRange) = last(A.a);

julia> Base.size(A::ZeroBasedRange) = size(A.a);

julia> Base.axes(A::ZeroBasedRange) = map(x -> 0:x-1, size(A.a));

julia> Base.getindex(A::ZeroBasedRange, i::Int) = A.a[i + 1];

julia> Base.step(A::ZeroBasedRange) = step(A.a);

julia> function Base.show(io::IO, A::ZeroBasedRange)
           show(io, A.a)
           print(io, " with indices $(axes(A,1))")
       end;
```

This definition of a `ZeroBasedRange` appears to have the correct indices, for example:

```jldoctest zerobasedrange
julia> z = ZeroBasedRange(1:4)
1:4 with indices 0:3

julia> z[0]
1

julia> z[3]
4
```

However this does not use idempotent indexing, as the axis of a `ZeroBasedRange` is not its own axis.

```jldoctest zerobasedrange
julia> axes(z, 1)
0:3

julia> axes(axes(z, 1), 1)
Base.OneTo(4)
```

This will lead to complications in certain functions --- for example `LinearIndices` --- that tends to implicitly assume idempotent indexing. In this case the `LinearIndices` of `z` will not match its axis.

```jldoctest zerobasedrange
julia> LinearIndices(z)
4-element LinearIndices{1, Tuple{UnitRange{Int64}}}:
 1
 2
 3
 4
```

Wrapping such a type in an `OffsetArray` might lead to unexpected bugs.

```jldoctest zerobasedrange
julia> zo = OffsetArray(z, 1);

julia> axes(zo, 1)
OffsetArrays.IdOffsetRange(values=1:4, indices=2:5)

julia> Array(zo)
ERROR: BoundsError: attempt to access 4-element UnitRange{Int64} at index [5]
[...]
```

The `Array` conversion errors despite `zo` having 1-based indices. The function `axes(zo, 1)` hints at the underlying problem --- the values and the indices of the axis are different. We may check that the axis of `zo` is not its own axis:

```jldoctest zerobasedrange
julia> axes(zo, 1)
OffsetArrays.IdOffsetRange(values=1:4, indices=2:5)

julia> axes(axes(zo, 1), 1)
OffsetArrays.IdOffsetRange(values=2:5, indices=2:5)
```

In this case the bug may be fixed by defining the `axes` of a `ZeroBasedRange` to be idempotent, for example using the `OffsetArrays.IdentityUnitRange` wrapper:

```jldoctest zerobasedrange
julia> Base.axes(A::ZeroBasedRange) = map(x -> OffsetArrays.IdentityUnitRange(0:x-1), size(A.a))

julia> axes(zo, 1)
OffsetArrays.IdOffsetRange(values=1:4, indices=1:4)
```

With this new definition, the values and indices of the axis are identical, which makes indexing idempotent. The conversion to an `Array` works as expected now:

```jldoctest zerobasedrange
julia> Array(zo)
4-element Vector{Int64}:
 1
 2
 3
 4
```

## Caveats

Because `IdOffsetRange` behaves quite differently to the normal `UnitRange` type, there are some
cases that you should be aware of, especially when you are working with multi-dimensional arrays.

One such cases is `getindex`:

```jldoctest getindex; setup = :(using OffsetArrays)
julia> Ao = zeros(-3:3, -3:3); Ao[:] .= 1:49;

julia> Ao[-3:0, :] |> axes # the first dimension does not preserve offsets
(OffsetArrays.IdOffsetRange(values=1:4, indices=1:4), OffsetArrays.IdOffsetRange(values=-3:3, indices=-3:3))

julia> Ao[-3:0, -3:3] |> axes # neither dimensions preserve offsets
(Base.OneTo(4), Base.OneTo(7))

julia> Ao[axes(Ao)...] |> axes # offsets are preserved
(OffsetArrays.IdOffsetRange(values=-3:3, indices=-3:3), OffsetArrays.IdOffsetRange(values=-3:3, indices=-3:3))

julia> Ao[:] |> axes # This is linear indexing
(Base.OneTo(49),)
```

Note that if you pass a `UnitRange`, the offsets in corresponding dimension will not be preserved.
This might look weird at first, but since it follows the `a[ax][i] == a[ax[i]]` rule, it is not a
bug.

```jldoctest getindex
julia> I = -3:0; # UnitRange always starts at index 1

julia> Ao[I, 0][1] == Ao[I[1], 0]
true

julia> ax = axes(Ao, 1) # ax starts at index -3
OffsetArrays.IdOffsetRange(values=-3:3, indices=-3:3)

julia> Ao[ax, 0][1] == Ao[ax[1], 0]
true
```

## Using custom axis types

While a wide variety of `AbstractUnitRange`s provided by `Base` may be used as indices to construct an `OffsetArray`, at times it might be convenient to define custom types. The `OffsetArray` constructor accepts any type that may be converted to an `AbstractUnitRange`. This proceeds through a two-step process. Let's assume that the constructor called is `OffsetArray(A, indstup)`, where `indstup` is a `Tuple` of indices.

1. At the first step, the constructor calls `to_indices(A, axes(A), indstup)` to lower `indstup` to a `Tuple` of `AbstractUnitRange`s. This step converts --- among other things --- `Colon`s to axis ranges. Custom types may extend `Base.to_indices(A, axes(A), indstup)` with the desired conversion of `indstup` to `Tuple{Vararg{AbstractUnitRange{Int}}}` if this is feasible.

2. At the second step, the result obtained from the previous step treated again to convert it to a `Tuple` of `AbstractUnitRange`s to handle cases where the first step doesn't achieve this. An additional customization option may be specified at this stage: a type may be converted either to a single `AbstractUnitRange{Int}`, or to a `Tuple` of them. A type might specify which of these two behaviours is desired by extending [`OffsetArrays.AxisConversionStyle`](@ref). An example of a type that is acted upon at this stage is `CartesianIndices`, which is converted to a `Tuple` of `AbstractUnitRange`s.

For example, here are a couple of custom type that facilitate zero-based indexing:

```jldoctest; setup = :(using OffsetArrays)
julia> struct ZeroBasedIndexing end

julia> Base.to_indices(A, inds, ::Tuple{ZeroBasedIndexing}) = map(x -> 0:length(x)-1, inds)

julia> a = zeros(3, 3);

julia> oa = OffsetArray(a, ZeroBasedIndexing());

julia> axes(oa)
(OffsetArrays.IdOffsetRange(values=0:2, indices=0:2), OffsetArrays.IdOffsetRange(values=0:2, indices=0:2))
```

In this example we had to define the action of `to_indices` as the type `ZeroBasedIndexing` did not have a familiar hierarchy. Things are even simpler if we subtype `AbstractUnitRange`, in which case we need to define `first` and `length` for the custom range to be able to use it as an axis:

```jldoctest; setup = :(using OffsetArrays)
julia> struct ZeroTo <: AbstractUnitRange{Int}
       n :: Int
       ZeroTo(n) = new(n < 0 ? -1 : n)
       end

julia> Base.first(::ZeroTo) = 0

julia> Base.length(r::ZeroTo) = r.n + 1

julia> oa = OffsetArray(zeros(2,2), ZeroTo(1), ZeroTo(1));

julia> axes(oa)
(OffsetArrays.IdOffsetRange(values=0:1, indices=0:1), OffsetArrays.IdOffsetRange(values=0:1, indices=0:1))
```

Note that zero-based indexing may also be achieved using the pre-defined type [`OffsetArrays.Origin`](@ref).


================================================
FILE: docs/src/reference.md
================================================
# Reference

```@docs
OffsetArray
OffsetVector
OffsetMatrix
OffsetArrays.Origin
OffsetArrays.IdOffsetRange
OffsetArrays.no_offset_view
OffsetArrays.AxisConversionStyle
OffsetArrays.center
OffsetArrays.centered
```


================================================
FILE: ext/OffsetArraysAdaptExt.jl
================================================
module OffsetArraysAdaptExt

using OffsetArrays, Adapt

##
# Adapt allows for automatic conversion of CPU OffsetArrays to GPU OffsetArrays
##
import Adapt
Adapt.adapt_structure(to, O::OffsetArray) = OffsetArrays.parent_call(x -> Adapt.adapt(to, x), O)

@static if isdefined(Adapt, :parent_type)
    # To support Adapt 3.0 which doesn't have parent_type defined
    Adapt.parent_type(::Type{OffsetArray{T,N,AA}}) where {T,N,AA} = AA
    Adapt.unwrap_type(W::Type{<:OffsetArray}) = unwrap_type(parent_type(W))
end

end


================================================
FILE: src/OffsetArrays.jl
================================================
module OffsetArrays

using Base: tail, @propagate_inbounds
@static if !isdefined(Base, :IdentityUnitRange)
    const IdentityUnitRange = Base.Slice
else
    using Base: IdentityUnitRange
end

export OffsetArray, OffsetMatrix, OffsetVector

const IIUR = IdentityUnitRange{<:AbstractUnitRange{<:Integer}}

include("axes.jl")
include("utils.jl")
include("origin.jl")

# Technically we know the length of CartesianIndices but we need to convert it first, so here we
# don't put it in OffsetAxisKnownLength.
const OffsetAxisKnownLength = Union{Integer, AbstractUnitRange}
const OffsetAxis = Union{OffsetAxisKnownLength, Colon}
const ArrayInitializer = Union{UndefInitializer, Missing, Nothing}

## OffsetArray
"""
    OffsetArray(A, indices...)

Return an `AbstractArray` that shares element type and size with the first argument but
uses the supplied `indices` to infer its axes. If all the indices are `AbstractUnitRange`s then
these are directly used as the axis span along each dimension. Refer to the examples below for other
permissible types.

Alternatively it's possible to specify the coordinates of one corner of the array
and have the axes be computed automatically from the size of `A`.
This constructor makes it convenient to shift to
an arbitrary starting index along each axis, for example to a zero-based indexing scheme followed by
arrays in languages such as C and Python.
See [`Origin`](@ref) and the examples below for this usage.

# Example: offsets

There are two types of `indices`: integers and ranges-like types.

Integers are recognized as offsets, where `0` means no offsets are applied:

```jldoctest; setup=:(using OffsetArrays)
julia> A = OffsetArray(reshape(1:6, 2, 3), -1, -2)
2×3 OffsetArray(reshape(::UnitRange{$Int}, 2, 3), 0:1, -1:1) with eltype $Int with indices 0:1×-1:1:
 1  3  5
 2  4  6

julia> A[0, 1]
5
```

Examples of range-like types are: `UnitRange` (e.g, `-1:2`), `CartesianIndices`,
and `Colon()` (or concisely `:`). A `UnitRange` specifies the axis span along one particular dimension,
`CartesianIndices` specify the axis spans along multiple dimensions, and a `Colon` is a placeholder
that specifies that the `OffsetArray` shares its axis with its parent along that dimension.

```jldoctest; setup=:(using OffsetArrays)
julia> OffsetArray(reshape(1:6, 2, 3), 0:1, -1:1)
2×3 OffsetArray(reshape(::UnitRange{$Int}, 2, 3), 0:1, -1:1) with eltype $Int with indices 0:1×-1:1:
 1  3  5
 2  4  6

julia> OffsetArray(reshape(1:6, 2, 3), :, -1:1) # : as a placeholder to indicate that no offset is to be applied to the first dimension
2×3 OffsetArray(reshape(::UnitRange{$Int}, 2, 3), 1:2, -1:1) with eltype $Int with indices 1:2×-1:1:
 1  3  5
 2  4  6
```

Use `CartesianIndices` to specify the coordinates of two diagonally opposite corners:

```jldoctest; setup=:(using OffsetArrays)
julia> OffsetArray(reshape(1:6, 2, 3), CartesianIndex(0, -1):CartesianIndex(1, 1))
2×3 OffsetArray(reshape(::UnitRange{$Int}, 2, 3), 0:1, -1:1) with eltype $Int with indices 0:1×-1:1:
 1  3  5
 2  4  6
```

Integers and range-like types may not be combined in the same call:

```julia
julia> OffsetArray(reshape(1:6, 2, 3), 0, -1:1)
ERROR: [...]
```

# Example: origin

[`OffsetArrays.Origin`](@ref) may be used to specify the origin of the OffsetArray. The term origin here
refers to the corner with the lowest values of coordinates, such as the left edge for an `AbstractVector`,
the bottom left corner for an `AbstractMatrix` and so on. The coordinates of the origin sets the starting
index of the array along each dimension.

```jldoctest; setup=:(using OffsetArrays)
julia> a = [1 2; 3 4];

julia> OffsetArray(a, OffsetArrays.Origin(0, 1))
2×2 OffsetArray(::$(Array{Int,2}), 0:1, 1:2) with eltype $Int with indices 0:1×1:2:
 1  2
 3  4

julia> OffsetArray(a, OffsetArrays.Origin(0)) # set the origin to zero along each dimension
2×2 OffsetArray(::$(Array{Int, 2}), 0:1, 0:1) with eltype $Int with indices 0:1×0:1:
 1  2
 3  4
```


"""
struct OffsetArray{T,N,AA<:AbstractArray{T,N}} <: AbstractArray{T,N}
    parent::AA
    offsets::NTuple{N,Int}
    @inline function OffsetArray{T, N, AA}(parent::AA, offsets::NTuple{N, Int}; checkoverflow = true) where {T, N, AA<:AbstractArray{T,N}}
        # allocation of `map` on tuple is optimized away
        checkoverflow && map(overflow_check, axes(parent), offsets)
        new{T, N, AA}(parent, offsets)
    end
end

"""
    OffsetVector(v, index)

Type alias and convenience constructor for one-dimensional [`OffsetArray`](@ref)s.
"""
const OffsetVector{T,AA<:AbstractVector{T}} = OffsetArray{T,1,AA}

"""
    OffsetMatrix(A, index1, index2)

Type alias and convenience constructor for two-dimensional [`OffsetArray`](@ref)s.
"""
const OffsetMatrix{T,AA<:AbstractMatrix{T}} = OffsetArray{T,2,AA}

# checks if the offset may be added to the range without overflowing
function overflow_check(r::AbstractUnitRange, offset::Integer)
    Base.hastypemax(eltype(r)) || return nothing
    # This gives some performance boost https://github.com/JuliaLang/julia/issues/33273
    throw_upper_overflow_error(val) = throw(OverflowError("offset should be <= $(typemax(Int) - val) corresponding to the axis $r, received an offset $offset"))
    throw_lower_overflow_error(val) = throw(OverflowError("offset should be >= $(typemin(Int) - val) corresponding to the axis $r, received an offset $offset"))

    # With ranges in the picture, first(r) might not necessarily be < last(r)
    # we therefore use the min and max of first(r) and last(r) to check for overflow
    firstlast_min, firstlast_max = minmax(first(r), last(r))

    if offset > 0 && firstlast_max > typemax(Int) - offset
        throw_upper_overflow_error(firstlast_max)
    elseif offset < 0 && firstlast_min < typemin(Int) - offset
        throw_lower_overflow_error(firstlast_min)
    end
    return nothing
end

# Tuples of integers are treated as offsets
# Empty Tuples are handled here
@inline function OffsetArray(A::AbstractArray, offsets::Tuple{Vararg{Integer}}; kw...)
    _checkindices(A, offsets, "offsets")
    OffsetArray{eltype(A), ndims(A), typeof(A)}(A, offsets; kw...)
end

# These methods are necessary to disallow incompatible dimensions for
# the OffsetVector and the OffsetMatrix constructors
for (FT, ND) in ((:OffsetVector, :1), (:OffsetMatrix, :2))
    @eval @inline function $FT(A::AbstractArray{<:Any,$ND}, offsets::Tuple{Vararg{Integer}}; kw...)
        _checkindices(A, offsets, "offsets")
        OffsetArray{eltype(A), $ND, typeof(A)}(A, offsets; kw...)
    end
    FTstr = string(FT)
    @eval @inline function $FT(A::AbstractArray, offsets::Tuple{Vararg{Integer}}; kw...)
        throw(ArgumentError($FTstr*" requires a "*string($ND)*"D array"))
    end
end

## OffsetArray constructors
for FT in (:OffsetArray, :OffsetVector, :OffsetMatrix)
    # Nested OffsetArrays may strip off the wrapper and collate the offsets
    # empty tuples are handled here
    @eval @inline function $FT(A::OffsetArray, offsets::Tuple{Vararg{Int}}; checkoverflow = true)
        _checkindices(A, offsets, "offsets")
        # ensure that the offsets may be added together without an overflow
        checkoverflow && map(overflow_check, axes(A), offsets)
        I = map(+, _offsets(A, parent(A)), offsets)
        $FT(parent(A), I, checkoverflow = false)
    end
    @eval @inline function $FT(A::OffsetArray, offsets::Tuple{Integer,Vararg{Integer}}; kw...)
        $FT(A, map(Int, offsets); kw...)
    end

    # In general, indices get converted to AbstractUnitRanges.
    # CartesianIndices{N} get converted to N ranges
    @eval @inline function $FT(A::AbstractArray, inds::Tuple{Any,Vararg{Any}}; kw...)
        $FT(A, _toAbstractUnitRanges(to_indices(A, axes(A), inds)); kw...)
    end

    # convert ranges to offsets
    @eval @inline function $FT(A::AbstractArray, inds::Tuple{AbstractUnitRange,Vararg{AbstractUnitRange}}; kw...)
        _checkindices(A, inds, "indices")
        # Performance gain by wrapping the error in a function: see https://github.com/JuliaLang/julia/issues/37558
        throw_dimerr(lA, lI) = throw(DimensionMismatch("supplied axes do not agree with the size of the array (got size $lA for the array and $lI for the indices"))
        lA = size(A)
        lI = map(length, inds)
        lA == lI || throw_dimerr(lA, lI)
        $FT(A, map(_offset, axes(A), inds); kw...)
    end

    @eval @inline $FT(A::AbstractArray, inds...; kw...) = $FT(A, inds; kw...)
    @eval @inline $FT(A::AbstractArray; checkoverflow = false) = $FT(A, ntuple(zero, Val(ndims(A))), checkoverflow = checkoverflow)

    @eval @inline $FT(A::AbstractArray, origin::Origin; checkoverflow = true) = $FT(A, origin.index .- first.(axes(A)); checkoverflow = checkoverflow)
end

(o::Origin)(A::AbstractArray) = OffsetArray(no_offset_view(A), o)
Origin(A::AbstractArray) = Origin(first.(axes(A)))

# conversion-related methods
@inline OffsetArray{T}(M::AbstractArray, I...; kw...) where {T} = OffsetArray{T,ndims(M)}(M, I...; kw...)

@inline function OffsetArray{T,N}(M::AbstractArray{<:Any,N}, I...; kw...) where {T,N}
    M2 = _of_eltype(T, M)
    OffsetArray{T,N}(M2, I...; kw...)
end
@inline OffsetArray{T,N}(M::OffsetArray{T,N}, I...; kw...) where {T,N} = OffsetArray(M, I...; kw...)
@inline OffsetArray{T,N}(M::AbstractArray{T,N}, I...; kw...) where {T,N} = OffsetArray{T,N,typeof(M)}(M, I...; kw...)

@inline OffsetArray{T,N,A}(M::AbstractArray{<:Any,N}, I...; kw...) where {T,N,A<:AbstractArray{T,N}} = OffsetArray{T,N,A}(M, I; kw...)
@inline function OffsetArray{T,N,A}(M::AbstractArray{<:Any,N}, I::NTuple{N,Int}; checkoverflow = true) where {T,N,A<:AbstractArray{T,N}}
    checkoverflow && map(overflow_check, axes(M), I)
    Mv = no_offset_view(M)
    MvA = convert(A, Mv)::A
    Iof = map(+, _offsets(M), I)
    OffsetArray{T,N,A}(MvA, Iof, checkoverflow = false)
end
@inline function OffsetArray{T, N, AA}(parent::AbstractArray{<:Any,N}, offsets::NTuple{N, Integer}; kw...) where {T, N, AA<:AbstractArray{T,N}}
    OffsetArray{T, N, AA}(parent, map(Int, offsets)::NTuple{N,Int}; kw...)
end
@inline function OffsetArray{T,N,A}(M::AbstractArray{<:Any,N}, I::Tuple{AbstractUnitRange,Vararg{AbstractUnitRange}}; kw...) where {T,N,A<:AbstractArray{T,N}}
    _checkindices(M, I, "indices")
    # Performance gain by wrapping the error in a function: see https://github.com/JuliaLang/julia/issues/37558
    throw_dimerr(lA, lI) = throw(DimensionMismatch("supplied axes do not agree with the size of the array (got size $lA for the array and $lI for the indices"))
    lM = size(M)
    lI = map(length, I)
    lM == lI || throw_dimerr(lM, lI)
    OffsetArray{T,N,A}(M, map(_offset, axes(M), I); kw...)
end
@inline function OffsetArray{T,N,A}(M::AbstractArray{<:Any,N}, I::Tuple; kw...) where {T,N,A<:AbstractArray{T,N}}
    OffsetArray{T,N,A}(M, _toAbstractUnitRanges(to_indices(M, axes(M), I)); kw...)
end
@inline function OffsetArray{T,N,A}(M::AbstractArray{<:Any,N}; kw...) where {T,N,A<:AbstractArray{T,N}}
    Mv = no_offset_view(M)
    MvA = convert(A, Mv)::A
    OffsetArray{T,N,A}(MvA, _offsets(M); kw...)
end
@inline OffsetArray{T,N,A}(M::A; checkoverflow = false) where {T,N,A<:AbstractArray{T,N}} = OffsetArray{T,N,A}(M, ntuple(zero, Val(N)); checkoverflow = checkoverflow)

Base.convert(::Type{T}, M::AbstractArray) where {T<:OffsetArray} = M isa T ? M : T(M)

@inline AbstractArray{T,N}(M::OffsetArray{S,N}) where {T,S,N} = OffsetArray{T}(M)

# array initialization
@inline function OffsetArray{T,N}(init::ArrayInitializer, inds::Tuple{Vararg{OffsetAxisKnownLength}}; kw...) where {T,N}
    _checkindices(N, inds, "indices")
    AA = Array{T,N}(init, map(_indexlength, inds))
    OffsetArray{T, N, typeof(AA)}(AA, map(_indexoffset, inds); kw...)
end
@inline function OffsetArray{T, N}(init::ArrayInitializer, inds::Tuple; kw...) where {T, N}
    OffsetArray{T, N}(init, _toAbstractUnitRanges(inds); kw...)
end
@inline OffsetArray{T,N}(init::ArrayInitializer, inds...; kw...) where {T,N} = OffsetArray{T,N}(init, inds; kw...)

@inline OffsetArray{T}(init::ArrayInitializer, inds::NTuple{N, OffsetAxisKnownLength}; kw...) where {T,N} = OffsetArray{T,N}(init, inds; kw...)
@inline function OffsetArray{T}(init::ArrayInitializer, inds::Tuple; kw...) where {T}
    OffsetArray{T}(init, _toAbstractUnitRanges(inds); kw...)
end
@inline OffsetArray{T}(init::ArrayInitializer, inds...; kw...) where {T} = OffsetArray{T}(init, inds; kw...)

Base.IndexStyle(::Type{OA}) where {OA<:OffsetArray} = IndexStyle(parenttype(OA))
parenttype(::Type{OffsetArray{T,N,AA}}) where {T,N,AA} = AA
parenttype(A::OffsetArray) = parenttype(typeof(A))

Base.parent(A::OffsetArray) = A.parent

# TODO: Ideally we would delegate to the parent's broadcasting implementation, but that
#       is currently broken in sufficiently many implementation, namely RecursiveArrayTools, DistributedArrays
#       and StaticArrays, that it will take concentrated effort to get this working across the ecosystem.
#       The goal would be to have `OffsetArray(CuArray) .+ 1 == OffsetArray{CuArray}`.
# Base.Broadcast.BroadcastStyle(::Type{<:OffsetArray{<:Any, <:Any, AA}}) where AA = Base.Broadcast.BroadcastStyle(AA)

@inline Base.size(A::OffsetArray) = size(parent(A))
# specializing length isn't necessary, as length(A) = prod(size(A)),
# but specializing length enables constant-propagation for statically sized arrays
# see https://github.com/JuliaArrays/OffsetArrays.jl/pull/304
@inline Base.length(A::OffsetArray) = length(parent(A))

@inline Base.axes(A::OffsetArray) = map(IdOffsetRange, axes(parent(A)), A.offsets)
@inline Base.axes(A::OffsetArray, d) = d <= ndims(A) ? IdOffsetRange(axes(parent(A), d), A.offsets[d]) : IdOffsetRange(axes(parent(A), d))
@inline Base.axes1(A::OffsetArray{T,0}) where {T} = IdOffsetRange(axes(parent(A), 1))  # we only need to specialize this one

# Issue 128
# See https://github.com/JuliaLang/julia/issues/37274 for the issue reported in Base
# The fix https://github.com/JuliaLang/julia/pull/39404 should be available on v1.6
# The following method is added on older Julia versions to ensure correct behavior for OffsetVectors
if VERSION < v"1.6"
    @inline function Base.compute_linindex(A::OffsetVector, I::NTuple{N,Any}) where N
        IP = Base.fill_to_length(axes(A), Base.OneTo(1), Val(N))
        Base.compute_linindex(first(LinearIndices(A)), 1, IP, I)
    end
end

# Utils to translate a function to the parent while preserving offsets
unwrap(x) = x, identity
unwrap(x::OffsetArray) = parent(x), data -> OffsetArray(data, x.offsets, checkoverflow = false)
function parent_call(f, x)
    parent, wrap_offset = unwrap(x)
    wrap_offset(f(parent))
end

Base.similar(A::OffsetArray, ::Type{T}, dims::Dims) where T =
    similar(parent(A), T, dims)
function Base.similar(A::AbstractArray, ::Type{T}, shape::Tuple{OffsetAxisKnownLength,Vararg{OffsetAxisKnownLength}}) where T
    # strip IdOffsetRanges to extract the parent range and use it to generate the array
    new_shape = map(_strip_IdOffsetRange, shape)
    # route through _similar_axes_or_length to avoid a stack overflow if map(_strip_IdOffsetRange, shape) === shape
    # This tries to use new_shape directly in similar if similar(A, T, ::typeof(new_shape)) is defined
    # If this fails, it calls similar(A, T, map(_indexlength, new_shape)) to use the size along each axis
    # to generate the new array
    P = _similar_axes_or_length(A, T, new_shape, shape)
    return OffsetArray(P, map(_offset, axes(P), shape))
end
Base.similar(::Type{A}, sz::Tuple{Vararg{Int}}) where {A<:OffsetArray} = similar(Array{eltype(A)}, sz)
function Base.similar(::Type{T}, shape::Tuple{OffsetAxisKnownLength,Vararg{OffsetAxisKnownLength}}) where {T<:AbstractArray}
    new_shape = map(_strip_IdOffsetRange, shape)
    P = _similar_axes_or_length(T, new_shape, shape)
    OffsetArray(P, map(_offset, axes(P), shape))
end
# Try to use the axes to generate the parent array type
# This is useful if the axes have special meanings, such as with static arrays
# This method is hit if at least one axis provided to similar(A, T, axes) is an IdOffsetRange
# For example this is hit when similar(A::OffsetArray) is called,
# which expands to similar(A, eltype(A), axes(A))
_similar_axes_or_length(A, T, ax, ::Any) = similar(A, T, ax)
_similar_axes_or_length(AT, ax, ::Any) = similar(AT, ax)
# Handle the general case by resorting to lengths along each axis
# This is hit if none of the axes provided to similar(A, T, axes) are IdOffsetRanges,
# and if similar(A, T, axes::AX) is not defined for the type AX.
# In this case the best that we can do is to create a mutable array of the correct size
_similar_axes_or_length(A, T, ax::I, ::I) where {I} = similar(A, T, map(_indexlength, ax))
_similar_axes_or_length(AT, ax::I, ::I) where {I} = similar(AT, map(_indexlength, ax))

# reshape accepts a single colon
# this method is limited to AbstractUnitRange{<:Integer} to avoid method overwritten errors if Base defines the same,
# see https://github.com/JuliaLang/julia/pull/56850
Base.reshape(A::AbstractArray, inds::Union{Integer, Colon, AbstractUnitRange{<:Integer}}...) = reshape(A, inds)
function Base.reshape(A::AbstractArray, inds::Tuple{Vararg{OffsetAxis}})
    AR = reshape(no_offset_view(A), map(_indexlength, inds))
    O = OffsetArray(AR, map(_offset, axes(AR), inds))
    return _popreshape(O, axes(AR), _filterreshapeinds(inds))
end

# Reshaping OffsetArrays can "pop" the original OffsetArray wrapper and return
# an OffsetArray(reshape(...)) instead of an OffsetArray(reshape(OffsetArray(...)))
# Short-circuit for AbstractVectors if the axes are compatible to get around the Base restriction
# to 1-based vectors
function _reshape(A::AbstractVector, inds::Tuple{OffsetAxis})
    @noinline throw_dimerr(ind::Integer) = throw(
        DimensionMismatch("parent has $(size(A,1)) elements, which is incompatible with length $ind"))
    @noinline throw_dimerr(ind) = throw(
        DimensionMismatch("parent has $(size(A,1)) elements, which is incompatible with indices $ind"))
    _checksize(first(inds), size(A,1)) || throw_dimerr(first(inds))
    A
end
_reshape(A, inds) = _reshape2(A, inds)
_reshape2(A, inds) = reshape(A, inds)
# avoid a stackoverflow by relegating to the parent if no_offset_view returns an offsetarray
_reshape2(A::OffsetArray, inds) = reshape(parent(A), inds)
_reshape_nov(A, inds) = _reshape(no_offset_view(A), inds)

# And for non-offset axes, we can just return a reshape of the parent directly
Base.reshape(A::OffsetArray, inds::Tuple{Integer,Vararg{Integer}}) = _reshape_nov(A, inds)
Base.reshape(A::OffsetArray, inds::Dims) = _reshape_nov(A, inds)
if VERSION < v"1.10.7"
    # the specialized reshape(parent::AbstractVector, ::Tuple{Colon}) is available in Base at least on this version
    Base.reshape(A::OffsetVector, ::Tuple{Colon}) = A
    Base.reshape(A::OffsetArray, inds::Tuple{Vararg{Union{Int,Colon}}}) = _reshape_nov(A, inds)
end

# permutedims in Base does not preserve axes, and can not be fixed in a non-breaking way
# This is a stopgap solution
Base.permutedims(v::OffsetVector) = reshape(v, (1, axes(v, 1)))

if VERSION < v"1.12.0-DEV.343" # available in Base beyond this version
    Base.fill(v, inds::NTuple{N, Union{Integer, AbstractUnitRange}}) where {N} =
        fill!(similar(Array{typeof(v)}, inds), v)
    Base.zeros(::Type{T}, inds::NTuple{N, Union{Integer, AbstractUnitRange}}) where {T, N} =
        fill!(similar(Array{T}, inds), zero(T))
    Base.ones(::Type{T}, inds::NTuple{N, Union{Integer, AbstractUnitRange}}) where {T, N} =
        fill!(similar(Array{T}, inds), one(T))
    Base.trues(inds::NTuple{N, Union{Integer, AbstractUnitRange}}) where {N} =
        fill!(similar(BitArray, inds), true)
    Base.falses(inds::NTuple{N, Union{Integer, AbstractUnitRange}}) where {N} =
        fill!(similar(BitArray, inds), false)
end

Base.zero(A::OffsetArray) = parent_call(zero, A)
Base.fill!(A::OffsetArray, x) = parent_call(Ap -> fill!(Ap, x), A)

## Indexing

# Note this gets the index of the parent *array*, not the index of the parent *range*
# Here's how one can think about this:
#   Δi = i - first(r)
#   i′ = first(r.parent) + Δi
# and one obtains the result below.
parentindex(r::IdOffsetRange, i) = i - r.offset

@propagate_inbounds Base.getindex(A::OffsetArray{<:Any,0})  = A.parent[]

@inline function Base.getindex(A::OffsetArray{<:Any,N}, I::Vararg{Int,N}) where N
    @boundscheck checkbounds(A, I...)
    J = map(parentindex, axes(A), I)
    @inbounds parent(A)[J...]
end

@propagate_inbounds Base.getindex(A::OffsetArray{<:Any,N}, c::Vararg{Colon,N}) where N =
    parent_call(x -> getindex(x, c...), A)

# With one Colon we use linear indexing.
# In this case we may forward the index to the parent, as the information about the axes is lost
# The exception to this is with OffsetVectors where the axis information is preserved,
# but that case is handled by getindex(::OffsetArray{<:Any,N}, ::Vararg{Colon,N})
@propagate_inbounds Base.getindex(A::OffsetArray, c::Colon) = A.parent[:]

@inline function Base.getindex(A::OffsetVector, i::Int)
    @boundscheck checkbounds(A, i)
    @inbounds parent(A)[parentindex(Base.axes1(A), i)]
end
@propagate_inbounds Base.getindex(A::OffsetArray, i::Int)  = parent(A)[i]

@inline function Base.setindex!(A::OffsetArray{T,N}, val, I::Vararg{Int,N}) where {T,N}
    @boundscheck checkbounds(A, I...)
    J = map(parentindex, axes(A), I)
    @inbounds parent(A)[J...] = val
    A
end

@inline function Base.setindex!(A::OffsetVector, val, i::Int)
    @boundscheck checkbounds(A, i)
    @inbounds parent(A)[parentindex(Base.axes1(A), i)] = val
    A
end
@propagate_inbounds function Base.setindex!(A::OffsetArray, val, i::Int)
    parent(A)[i] = val
    A
end

@inline Base.iterate(a::OffsetArray, i...) = iterate(parent(a), i...)

Base.in(x, A::OffsetArray) = in(x, parent(A))
Base.copy(A::OffsetArray) = parent_call(copy, A)

Base.strides(A::OffsetArray) = strides(parent(A))
Base.elsize(::Type{OffsetArray{T,N,A}}) where {T,N,A} = Base.elsize(A)
Base.cconvert(P::Type{Ptr{T}}, A::OffsetArray{T}) where {T} = Base.cconvert(P, parent(A))
if VERSION < v"1.11-"
    @inline Base.unsafe_convert(::Type{Ptr{T}}, A::OffsetArray{T}) where {T} = Base.unsafe_convert(Ptr{T}, parent(A))
end

# For fast broadcasting: ref https://discourse.julialang.org/t/why-is-there-a-performance-hit-on-broadcasting-with-offsetarrays/32194
Base.dataids(A::OffsetArray) = Base.dataids(parent(A))
Broadcast.broadcast_unalias(dest::OffsetArray, src::OffsetArray) = parent(dest) === parent(src) ? src : Broadcast.unalias(dest, src)

### Special handling for AbstractRange
const OffsetRange{T} = OffsetVector{T,<:AbstractRange{T}}
const OffsetUnitRange{T} = OffsetVector{T,<:AbstractUnitRange{T}}

Base.step(a::OffsetRange) = step(parent(a))

Base.checkindex(::Type{Bool}, inds::AbstractUnitRange, or::OffsetRange) = Base.checkindex(Bool, inds, parent(or))

# Certain special methods for linear indexing with integer ranges (or OffsetRanges)
# These may bypass the default getindex(A, I...) pathway if the parent types permit this
# For example AbstractUnitRanges and Arrays have special linear indexing behavior defined

# If both the arguments are offset, we may unwrap the indices to call (::OffsetArray)[::AbstractRange{Int}]
@propagate_inbounds function Base.getindex(A::OffsetArray, r::OffsetRange{Int})
    _indexedby(A[parent(r)], axes(r))
end
# If the indices are offset, we may unwrap them and pass the parent to getindex
@propagate_inbounds function Base.getindex(A::AbstractRange, r::OffsetRange{Int})
    _indexedby(A[parent(r)], axes(r))
end

# An OffsetUnitRange might use the rapid getindex(::Array, ::AbstractUnitRange{Int}) for contiguous indexing
@propagate_inbounds function Base.getindex(A::Array, r::OffsetUnitRange{Int})
    B = A[_contiguousindexingtype(parent(r))]
    OffsetArray(B, axes(r), checkoverflow = false)
end

# avoid hitting the slow method getindex(::Array, ::AbstractRange{Int})
# instead use the faster getindex(::Array, ::UnitRange{Int})
if VERSION <= v"1.7.0-DEV.1039"
    @propagate_inbounds function Base.getindex(A::Array, r::Union{IdOffsetRange, IIUR})
        B = A[_contiguousindexingtype(r)]
        _indexedby(B, axes(r))
    end
end

# Linear Indexing of OffsetArrays with AbstractUnitRanges may use the faster contiguous indexing methods
@inline function Base.getindex(A::OffsetArray, r::AbstractUnitRange{Int})
    @boundscheck checkbounds(A, r)
    # nD OffsetArrays do not have their linear indices shifted, so we may forward the indices provided to the parent
    @inbounds B = parent(A)[_contiguousindexingtype(r)]
    _indexedby(B, axes(r))
end
@inline function Base.getindex(A::OffsetVector, r::AbstractUnitRange{Int})
    @boundscheck checkbounds(A, r)
    # OffsetVectors may have their linear indices shifted, so we subtract the offset from the indices provided
    @inbounds B = parent(A)[_subtractoffset(r, A.offsets[1])]
    _indexedby(B, axes(r))
end

# This method added mainly to index an OffsetRange with another range
@inline function Base.getindex(A::OffsetVector, r::AbstractRange{Int})
    @boundscheck checkbounds(A, r)
    @inbounds B = parent(A)[_subtractoffset(r, A.offsets[1])]
    _indexedby(B, axes(r))
end

# In general we would pass through getindex(A, I...) which calls to_indices(A, I) and finally to_index(I)
# An OffsetUnitRange{Int} has an equivalent IdOffsetRange with the same values and axes,
# something similar also holds for OffsetUnitRange{BigInt}
# We may replace the former with the latter in an indexing operation to obtain a performance boost
@inline function Base.to_index(r::OffsetUnitRange{<:Union{Int,BigInt}})
    of = first(axes(r,1)) - 1
    IdOffsetRange(_subtractoffset(parent(r), of), of)
end

@inline function _boundscheck_index_retaining_axes(r, s)
    @boundscheck checkbounds(r, s)
    @inbounds pr = r[UnitRange(s)]
    _indexedby(pr, axes(s))
end
@inline _boundscheck_return(r, s) = (@boundscheck checkbounds(r, s); s)

for OR in [:IIUR, :IdOffsetRange]
    for R in [:StepRange, :StepRangeLen, :LinRange, :UnitRange]
        @eval @inline Base.getindex(r::$R, s::$OR) = _boundscheck_index_retaining_axes(r, s)
    end

    # this method is needed for ambiguity resolution
    @eval @inline function Base.getindex(r::StepRangeLen{T,<:Base.TwicePrecision,<:Base.TwicePrecision}, s::$OR) where T
        _boundscheck_index_retaining_axes(r, s)
    end
end
Base.getindex(r::Base.OneTo, s::IdOffsetRange) = _boundscheck_index_retaining_axes(r, s)
if VERSION < v"1.7.0-beta2"
    Base.getindex(r::Base.OneTo, s::IIUR) = _boundscheck_index_retaining_axes(r, s)
end

# These methods are added to avoid ambiguities with Base.
# The ones involving Base types should be ported to Base and version-limited here
@inline Base.getindex(r::IdentityUnitRange, s::IIUR) = _boundscheck_return(r, s)
@inline Base.getindex(r::IdentityUnitRange, s::IdOffsetRange) = _boundscheck_return(r, s)
if IdentityUnitRange !== Base.Slice
    @inline Base.getindex(r::Base.Slice, s::IIUR) = _boundscheck_return(r, s)
    @inline Base.getindex(r::Base.Slice, s::IdOffsetRange) = _boundscheck_return(r, s)
end

# eltype conversion
# This may use specialized map methods for the parent
Base.map(::Type{T}, O::OffsetArray) where {T} = parent_call(x -> map(T, x), O)
Base.map(::Type{T}, r::IdOffsetRange) where {T<:Real} = _indexedby(map(T, UnitRange(r)), axes(r))
if eltype(IIUR) === Int
    # This is type-piracy, but there is no way to convert an IdentityUnitRange to a non-Int type in Base
    Base.map(::Type{T}, r::IdentityUnitRange) where {T<:Real} = _indexedby(map(T, UnitRange(r)), axes(r))
end

if VERSION < v"1.7.2"
    # mapreduce is faster with an IdOffsetRange than with an OffsetUnitRange on Julia 1.6
    # We therefore convert OffsetUnitRanges to IdOffsetRanges with the same values and axes
    function Base.mapreduce(f, op, A1::OffsetUnitRange{<:Integer}, As::OffsetUnitRange{<:Integer}...; kw...)
        As = (A1, As...)
        ofs = map(A -> first(axes(A,1)) - 1, As)
        AIds = map((A, of) -> IdOffsetRange(_subtractoffset(parent(A), of), of), As, ofs)
        mapreduce(f, op, AIds...; kw...)
    end
end

# Optimize certain reductions that treat an OffsetVector as a list
for f in [:minimum, :maximum, :extrema, :sum]
    @eval Base.$f(r::OffsetRange) = $f(parent(r))
end

function Base.show(io::IO, r::OffsetRange)
    show(io, r.parent)
    print(io, " with indices ", UnitRange(axes(r, 1)))
end
Base.show(io::IO, ::MIME"text/plain", r::OffsetRange) = show(io, r)


### Some mutating functions defined only for OffsetVector ###

Base.resize!(A::OffsetVector, nl::Integer) = (resize!(A.parent, nl); A)
Base.push!(A::OffsetVector, x...) = (push!(A.parent, x...); A)
Base.pop!(A::OffsetVector) = pop!(A.parent)
Base.append!(A::OffsetVector, items) = (append!(A.parent, items); A)
Base.empty!(A::OffsetVector) = (empty!(A.parent); A)

# These functions keep the summary compact
const OffsetIndices = Union{IdOffsetRange, IdentityUnitRange{<:IdOffsetRange}}
function Base.inds2string(inds::Tuple{OffsetIndices, Vararg{OffsetIndices}})
    Base.inds2string(map(UnitRange, inds))
end
Base.showindices(io::IO, ind1::IdOffsetRange, inds::IdOffsetRange...) = Base.showindices(io, map(UnitRange, (ind1, inds...))...)

function Base.showarg(io::IO, @nospecialize(a::OffsetArray), toplevel)
    print(io, "OffsetArray(")
    Base.showarg(io, parent(a), false)
    Base.showindices(io, axes(a)...)
    print(io, ')')
    if toplevel
        print(io, " with eltype ", eltype(a))
    end
end

function Base.replace_in_print_matrix(A::OffsetArray{<:Any,2}, i::Integer, j::Integer, s::AbstractString)
    J = map(parentindex, axes(A), (i,j))
    Base.replace_in_print_matrix(parent(A), J..., s)
end
function Base.replace_in_print_matrix(A::OffsetArray{<:Any,1}, i::Integer, j::Integer, s::AbstractString)
    ip = parentindex(axes(A,1), i)
    Base.replace_in_print_matrix(parent(A), ip, j, s)
end

# Actual unsafe_wrap implementation
@inline function _unsafe_wrap(pointer::Ptr{T}, inds::NTuple{N, OffsetAxisKnownLength}; own = false, kw...) where {T,N}
    _checkindices(N, inds, "indices")
    AA = Base.unsafe_wrap(Array, pointer, map(_indexlength, inds); own=own)
    OffsetArray{T, N, typeof(AA)}(AA, map(_indexoffset, inds); kw...)
end
const OffsetArrayUnion{T,N} = Union{Type{OffsetArray}, Type{OffsetArray{T}}, Type{OffsetArray{T,N}}, Type{OffsetArray{T1, N} where T1}} where {T,N}

"""
    Base.unsafe_wrap(OffsetArray, pointer::Ptr{T}, inds...; own=false, kw...)
    Base.unsafe_wrap(OffsetArray, pointer::Ptr{T}, inds::Tuple; own=false, kw...)

Construct an `OffsetArray` around a pointer with axes defined by `inds` which may be Integers or AbstractUnitRanges.
If the keyword `own` is true, then Julia will free the pointer when the array is garbage collected.
Other keywords are forwarded to the `OffsetArray` constructor.
"""
@inline function Base.unsafe_wrap(::OffsetArrayUnion{T,N}, pointer::Ptr{T}, inds::NTuple{N, OffsetAxisKnownLength}; kw...) where {T,N}
    _unsafe_wrap(pointer, inds; kw...)
end
# Avoid ambiguity
@inline function Base.unsafe_wrap(::OffsetArrayUnion{T,N}, pointer::Ptr{T}, inds::NTuple{N, <:Integer}; kw...) where {T,N}
    _unsafe_wrap(pointer, inds; kw...)
end
@inline function Base.unsafe_wrap(::OffsetArrayUnion{T,N}, pointer::Ptr{T}, inds::Vararg{OffsetAxisKnownLength,N}; kw...) where {T,N}
    _unsafe_wrap(pointer, inds; kw...)
end
# Avoid ambiguity
@inline function Base.unsafe_wrap(::OffsetArrayUnion{T,N}, pointer::Ptr{T}, inds::Vararg{Integer,N}; kw...) where {T,N}
    _unsafe_wrap(pointer, inds; kw...)
end

"""
    no_offset_view(A)

Return an `AbstractArray` that shares structure and underlying data with the argument,
but uses 1-based indexing. May just return the argument when applicable.
Not exported.

The default implementation uses `OffsetArrays`, but other types should use something more
specific to remove a level of indirection when applicable.

```jldoctest; setup=:(using OffsetArrays)
julia> A = [1 3 5; 2 4 6];

julia> O = OffsetArray(A, 0:1, -1:1)
2×3 OffsetArray(::$(Matrix{Int}), 0:1, -1:1) with eltype $Int with indices 0:1×-1:1:
 1  3  5
 2  4  6

julia> OffsetArrays.no_offset_view(O)[1,1] = -9
-9

julia> A
2×3 $(Matrix{Int}):
 -9  3  5
  2  4  6
```
"""
no_offset_view(A::OffsetArray) = no_offset_view(parent(A))
if isdefined(Base, :IdentityUnitRange)
    # valid only if Slice is distinguished from IdentityUnitRange
    _onebasedslice(S::Base.Slice) = Base.Slice(Base.OneTo(length(S)))
    _onebasedslice(S::Base.Slice{<:Base.OneTo}) = S
    _onebasedslice(S) = S
    _isoffsetslice(::Any) = false
    _isoffsetslice(::Base.Slice) = true
    _isoffsetslice(::Base.Slice{<:Base.OneTo}) = false
    function no_offset_view(S::SubArray)
        #= If a view contains an offset Slice axis,
        i.e. it is a view of an offset array along the offset axis,
        we shift the axis to a 1-based one.
        E.g. Slice(2:3) -> Slice(Base.OneTo(2))
        We transform the `parent` as well as the `parentindices`,
        so that the view still points to the same elements, even though the indices have changed.
        This way, we retain the axis of the view as a `Slice`
        =#
        P = parent(S)
        pinds = parentindices(S)
        #=
        Check if all the axes are `Slice`s and the parent has `OneTo` axes,
        in which case we may unwrap the `OffsetArray` and forward the view to the parent.
        =#
        may_pop_parent = all(_isoffsetslice, pinds) && P isa OffsetArray && all(x -> x isa Base.OneTo, axes(parent(P)))
        if may_pop_parent
            return no_offset_view(P)
        end
        #=
        we convert offset `Slice`s to 1-based ones using `_onebasedslice`.
        The next call, `no_offset_view`, is a no-op on a `Slice{<:OneTo}`,
        while it converts the offset axes to 1-based ones.
        Eventually, we end up with a `Tuple` comprising `Slice{<:OneTo}`s and other 1-based axes.

        The difference between `_onebasedslice` and `no_offset_view` is that
        the latter does not change the value of the range, while the former does.
        =#
        newviewinds = map(no_offset_view ∘ _onebasedslice, pinds)
        needs_shifting = any(_isoffsetslice, pinds)
        P_maybeshiftedinds = if needs_shifting
            t = Origin(parent(S)).index
            neworigin = ntuple(i -> _isoffsetslice(pinds[i]) ? 1 : t[i], length(t))
            Origin(neworigin)(P)
        else
            P
        end
        view(P_maybeshiftedinds, newviewinds...)
    end
end
no_offset_view(a::Array) = a
no_offset_view(i::Number) = i
no_offset_view(A::AbstractArray) = _no_offset_view(axes(A), A)
_no_offset_view(::Tuple{}, A::AbstractArray{T,0}) where T = A
_no_offset_view(::Tuple{Base.OneTo, Vararg{Base.OneTo}}, A::AbstractArray) = A
_no_offset_view(::Any, A::AbstractArray) = _no_offset_view(A)
_no_offset_view(A::AbstractArray) = OffsetArray(A, Origin(1))
_no_offset_view(A::AbstractUnitRange) = UnitRange(A)

#####
# center/centered
# These two helpers are deliberately not exported; their meaning can be very different in
# other scenarios and will be very likely to cause name conflicts if exported.
#####

if VERSION < v"1.4"
   _halfroundInt(v, r::RoundingMode) = round(Int, v/2, r)
else
   _halfroundInt(v, r::RoundingMode) = div(v, 2, r)
end

"""
    center(A, [r::RoundingMode=RoundDown])::Dims

Return the center coordinate of given array `A`. If `size(A, k)` is even,
a rounding procedure will be applied with mode `r`.

!!! compat "OffsetArrays 1.9"
    This method requires at least OffsetArrays 1.9.

# Examples

```jldoctest; setup=:(using OffsetArrays)
julia> A = reshape(collect(1:9), 3, 3)
3×3 $(Matrix{Int}):
 1  4  7
 2  5  8
 3  6  9

julia> c = OffsetArrays.center(A)
(2, 2)

julia> A[c...]
5

julia> Ao = OffsetArray(A, -2, -2); # axes (-1:1, -1:1)

julia> c = OffsetArrays.center(Ao)
(0, 0)

julia> Ao[c...]
5
```

To shift the center coordinate of the given array to `(0, 0, ...)`, you
can use [`centered`](@ref OffsetArrays.centered).
"""
function center(A::AbstractArray, r::RoundingMode=RoundDown)
    map(axes(A)) do inds
        _halfroundInt(length(inds)-1, r) + first(inds)
    end
end

"""
    centered(A, cp=center(A)) -> Ao

Shift the center coordinate/point `cp` of array `A` to `(0, 0, ..., 0)`. Internally, this is
equivalent to `OffsetArray(A, .-cp)`.

!!! compat "OffsetArrays 1.9"
    This method requires at least OffsetArrays 1.9.

# Examples

```jldoctest; setup=:(using OffsetArrays)
julia> A = reshape(collect(1:9), 3, 3)
3×3 $(Matrix{Int}):
 1  4  7
 2  5  8
 3  6  9

julia> Ao = OffsetArrays.centered(A); # axes (-1:1, -1:1)

julia> Ao[0, 0]
5

julia> Ao = OffsetArray(A, OffsetArrays.Origin(0)); # axes (0:2, 0:2)

julia> Aoo = OffsetArrays.centered(Ao); # axes (-1:1, -1:1)

julia> Aoo[0, 0]
5
```

Users are allowed to pass `cp` to change how "center point" is interpreted, but the meaning of the
output array should be reinterpreted as well. For instance, if `cp = map(last, axes(A))` then this
function no longer shifts the center point but instead the bottom-right point to `(0, 0, ..., 0)`.
A commonly usage of `cp` is to change the rounding behavior when the array is of even size at some
dimension:

```jldoctest; setup=:(using OffsetArrays)
julia> A = reshape(collect(1:4), 2, 2) # Ideally the center should be (1.5, 1.5) but OffsetArrays only support integer offsets
2×2 $(Matrix{Int}):
 1  3
 2  4

julia> OffsetArrays.centered(A, OffsetArrays.center(A, RoundUp)) # set (2, 2) as the center point
2×2 OffsetArray(::$(Matrix{Int}), -1:0, -1:0) with eltype $(Int) with indices -1:0×-1:0:
 1  3
 2  4

julia> OffsetArrays.centered(A, OffsetArrays.center(A, RoundDown)) # set (1, 1) as the center point
2×2 OffsetArray(::$(Matrix{Int}), 0:1, 0:1) with eltype $(Int) with indices 0:1×0:1:
 1  3
 2  4
```

See also [`center`](@ref OffsetArrays.center).
"""
centered(A::AbstractArray, cp::Dims=center(A)) = OffsetArray(A, .-cp)

centered(A::AbstractArray, i::CartesianIndex) = centered(A, Tuple(i))

if VERSION < v"1.12.0-DEV.1713"
    # The Base implementations are fixed in https://github.com/JuliaLang/julia/pull/56464 and https://github.com/JuliaLang/julia/pull/56474
    # we therefore limit these specializations to older versions of julia

    # we may pass the searchsorted* functions to the parent, and wrap the offset
    for f in [:searchsortedfirst, :searchsortedlast, :searchsorted]
        _safe_f = Symbol("_safe_" * String(f))
        @eval function $_safe_f(v::OffsetVector, x, ilo, ihi, o::Base.Ordering)
            offset = v.offsets[1]
            $f(parent(v), x, ilo - offset, ihi - offset, o) .+ offset
        end
        @eval Base.$f(v::OffsetVector, x, ilo::T, ihi::T, o::Base.Ordering) where T<:Integer =
            $_safe_f(v, x, ilo, ihi, o)
    end

    if VERSION <= v"1.2"
        # ambiguity warnings in earlier versions
        for f in [:searchsortedfirst, :searchsortedlast, :searchsorted]
            _safe_f = Symbol("_safe_" * String(f))
            @eval Base.$f(v::OffsetVector, x, ilo::Int, ihi::Int, o::Base.Ordering) =
                $_safe_f(v, x, ilo, ihi, o)
        end
    end
end

if VERSION < v"1.1.0-DEV.783"
    Base.copyfirst!(dest::OffsetArray, src::OffsetArray) = (maximum!(parent(dest), parent(src)); return dest)
end

if VERSION <= v"1.7.0-DEV.400"
    # https://github.com/JuliaLang/julia/pull/39393
    # index for zero-argument getindex should be first linear index instead of 1 (#194)
    Base._to_linear_index(A::OffsetArray) = first(LinearIndices(A))
end

if !isdefined(Base, :get_extension)
  include("../ext/OffsetArraysAdaptExt.jl")
end

if Base.VERSION >= v"1.4.2"
    include("precompile.jl")
    _precompile_()
end


##
# Deprecations
##

# This is a bad API design as it introduces counter intuitive results (#250)
@deprecate centered(A::AbstractArray, r::RoundingMode) OffsetArray(A, .-center(A, r)) false

end # module


================================================
FILE: src/axes.jl
================================================
"""
    ro = IdOffsetRange(r::AbstractUnitRange, offset=0)

Construct an "identity offset range". Numerically, `collect(ro) == collect(r) .+ offset`,
with the additional property that `axes(ro, 1) = axes(r, 1) .+ offset`.
When `r` starts at 1, then `ro[i] == i` and even `ro[ro] == ro`,
i.e., it's the "identity," which is the origin of the "Id" in `IdOffsetRange`.

# Examples

The most common case is shifting a range that starts at 1 (either `1:n` or `Base.OneTo(n)`):
```jldoctest ior
julia> using OffsetArrays: IdOffsetRange

julia> ro = IdOffsetRange(1:3, -2)
IdOffsetRange(values=-1:1, indices=-1:1)

julia> axes(ro, 1)
IdOffsetRange(values=-1:1, indices=-1:1)

julia> ro[-1]
-1

julia> ro[3]
ERROR: BoundsError: attempt to access 3-element $(IdOffsetRange{Int, UnitRange{Int}}) with indices -1:1 at index [3]
```

If the range doesn't start at 1, the values may be different from the indices:
```jldoctest ior
julia> ro = IdOffsetRange(11:13, -2)
IdOffsetRange(values=9:11, indices=-1:1)

julia> axes(ro, 1)     # 11:13 is indexed by 1:3, and the offset is also applied to the axes
IdOffsetRange(values=-1:1, indices=-1:1)

julia> ro[-1]
9

julia> ro[3]
ERROR: BoundsError: attempt to access 3-element $(IdOffsetRange{Int, UnitRange{Int}}) with indices -1:1 at index [3]
```

# Extended help

Construction/coercion preserves the (shifted) values of the input range, but may modify
the indices if required by the specified types. For example,

    r = OffsetArrays.IdOffsetRange{Int,UnitRange{Int}}(3:4)

has `r[1] == 3` and `r[2] == 4`, whereas

    r = OffsetArrays.IdOffsetRange{Int,Base.OneTo{Int}}(3:4)

has `r[3] == 3` and `r[4] == 4`, and `r[1]` would throw a `BoundsError`.
In this latter case, a shift in the axes was needed because `Base.OneTo` ranges
must start with value 1.

!!! warning

    In the future, *conversion* will preserve both the values and
    the indices, throwing an error when this is not achievable. For instance,

        r = convert(OffsetArrays.IdOffsetRange{Int,UnitRange{Int}}, 3:4)

    has `r[1] == 3` and `r[2] == 4` and would satisfy `r == 3:4`, whereas

    ```julia
    julia> convert(OffsetArrays.IdOffsetRange{Int,Base.OneTo{Int}}, 3:4)    # future behavior, not present behavior
    ERROR: ArgumentError: first element must be 1, got 3
    ```

    where the error will arise because the result could not have the same axes as the input.

    An important corollary is that `typeof(r1)(r2)` and `oftype(r1, r2)` will behave differently:
    the first coerces `r2` to be of the type of `r1`, whereas the second converts.
    Developers are urged to future-proof their code by choosing the behavior appropriate for each usage.
"""
struct IdOffsetRange{T<:Integer,I<:AbstractUnitRange{T}} <: AbstractUnitRange{T}
    parent::I
    offset::T

    function IdOffsetRange{T,I}(r::I, offset::T) where {T<:Integer,I<:AbstractUnitRange{T}}
        _bool_check(T, r, offset)
        new{T,I}(r, offset)
    end

    #= This method is necessary to avoid a StackOverflowError in IdOffsetRange{T,I}(r::IdOffsetRange, offset::Integer).
    The type signature in that method is more specific than IdOffsetRange{T,I}(r::I, offset::T),
    so it ends up calling itself if I <: IdOffsetRange.
    =#
    function IdOffsetRange{T,IdOffsetRange{T,I}}(r::IdOffsetRange{T,I}, offset::T) where {T<:Integer,I<:AbstractUnitRange{T}}
        _bool_check(T, r, offset)
        new{T,IdOffsetRange{T,I}}(r, offset)
    end
end

function _bool_check(::Type{Bool}, r, offset)
    # disallow the construction of IdOffsetRange{Bool, UnitRange{Bool}}(true:true, true)
    if offset && (first(r) || last(r))
        throw(ArgumentError("values = $r and offset = $offset can not produce a boolean range"))
    end
    return nothing
end
_bool_check(::Type, r, offset) = nothing

# Construction/coercion from arbitrary AbstractUnitRanges
function IdOffsetRange{T,I}(r::AbstractUnitRange, offset::Integer = 0) where {T<:Integer,I<:AbstractUnitRange{T}}
    rc, o = offset_coerce(I, r)
    return IdOffsetRange{T,I}(rc, convert(T, o+offset)::T)
end
function IdOffsetRange{T}(r::AbstractUnitRange, offset::Integer = 0) where T<:Integer
    rc = convert(AbstractUnitRange{T}, r)::AbstractUnitRange{T}
    return IdOffsetRange{T,typeof(rc)}(rc, convert(T, offset)::T)
end
IdOffsetRange(r::AbstractUnitRange{T}, offset::Integer = 0) where T<:Integer =
    IdOffsetRange{T,typeof(r)}(r, convert(T, offset)::T)

# Coercion from other IdOffsetRanges
IdOffsetRange{T,I}(r::IdOffsetRange{T,I}) where {T<:Integer,I<:AbstractUnitRange{T}} = r
function IdOffsetRange{T,I}(r::IdOffsetRange, offset::Integer = 0) where {T<:Integer,I<:AbstractUnitRange{T}}
    rc, offset_rc = offset_coerce(I, r.parent)
    return IdOffsetRange{T,I}(rc, convert(T, r.offset + offset + offset_rc)::T)
end
IdOffsetRange{T}(r::IdOffsetRange{T}) where {T<:Integer} = r
function IdOffsetRange{T}(r::IdOffsetRange, offset::Integer = 0) where T<:Integer
    return IdOffsetRange{T}(r.parent, r.offset + offset)
end
IdOffsetRange(r::IdOffsetRange) = r

# Constructor to make `show` round-trippable
# try to preserve typeof(values) if the indices are known to be 1-based
_subtractindexoffset(values, indices::Union{Base.OneTo, IdentityUnitRange{<:Base.OneTo}}, offset) = values
_subtractindexoffset(values, indices, offset) = _subtractoffset(values, offset)
function IdOffsetRange(; values::AbstractUnitRange{<:Integer}, indices::AbstractUnitRange{<:Integer})
    length(values) == length(indices) || throw(ArgumentError("values and indices must have the same length"))
    values_nooffset = no_offset_view(values)
    offset = first(indices) - 1
    values_minus_offset = _subtractindexoffset(values_nooffset, indices, offset)
    return IdOffsetRange(values_minus_offset, offset)
end

# Conversions to an AbstractUnitRange{Int} (and to an OrdinalRange{Int,Int} on Julia v"1.6") are necessary
# to evaluate CartesianIndices for BigInt ranges, as their axes are also BigInt ranges
Base.AbstractUnitRange{T}(r::IdOffsetRange) where {T<:Integer} = IdOffsetRange{T}(r)

# https://github.com/JuliaLang/julia/pull/40038
if v"1.6" <= VERSION < v"1.9.0-DEV.642"
    Base.OrdinalRange{T,T}(r::IdOffsetRange) where {T<:Integer} = IdOffsetRange{T}(r)
end

# TODO: uncomment these when Julia is ready
# # Conversion preserves both the values and the indices, throwing an InexactError if this
# # is not possible.
# Base.convert(::Type{IdOffsetRange{T,I}}, r::IdOffsetRange{T,I}) where {T<:Integer,I<:AbstractUnitRange{T}} = r
# Base.convert(::Type{IdOffsetRange{T,I}}, r::IdOffsetRange) where {T<:Integer,I<:AbstractUnitRange{T}} =
#     IdOffsetRange{T,I}(convert(I, r.parent), r.offset)
# Base.convert(::Type{IdOffsetRange{T,I}}, r::AbstractUnitRange) where {T<:Integer,I<:AbstractUnitRange{T}} =
#     IdOffsetRange{T,I}(convert(I, r), 0)

offset_coerce(::Type{Base.OneTo{T}}, r::Base.OneTo) where T<:Integer = convert(Base.OneTo{T}, r), 0
function offset_coerce(::Type{Base.OneTo{T}}, r::AbstractUnitRange) where T<:Integer
    o = first(r) - 1
    return Base.OneTo{T}(last(r) - o), o
end
# function offset_coerce(::Type{Base.OneTo{T}}, r::IdOffsetRange) where T<:Integer
#     rc, o = offset_coerce(Base.OneTo{T}, r.parent)

# Fallback, specialze this method if `convert(I, r)` doesn't do what you need
offset_coerce(::Type{I}, r::AbstractUnitRange) where I<:AbstractUnitRange =
    convert(I, r)::I, 0

@inline Base.parent(r::IdOffsetRange) = r.parent
@inline Base.axes(r::IdOffsetRange) = (axes1(r),)
@inline axes1(r::IdOffsetRange) = IdOffsetRange(Base.axes1(r.parent), r.offset)
if VERSION < v"1.8.2"
    Base.axes1(r::IdOffsetRange) = axes1(r)
end
@inline Base.unsafe_indices(r::IdOffsetRange) = (axes1(r),)
@inline Base.length(r::IdOffsetRange) = length(r.parent)
@inline Base.isempty(r::IdOffsetRange) = isempty(r.parent)
#= We specialize on reduced_indices to work around cases where the parent axis type doesn't
support reduced_index, but the axes do support reduced_indices
The difference is that reduced_index expects the axis type to remain unchanged,
which may not always be possible, eg. for statically sized axes
See https://github.com/JuliaArrays/OffsetArrays.jl/issues/204
=#
function Base.reduced_indices(inds::Tuple{IdOffsetRange, Vararg{IdOffsetRange}}, d::Int)
    parents_reduced = Base.reduced_indices(map(parent, inds), d)
    ntuple(i -> IdOffsetRange(parents_reduced[i], inds[i].offset), Val(length(inds)))
end
Base.reduced_index(i::IdOffsetRange) = typeof(i)(first(i):first(i))
# Workaround for #92 on Julia < 1.4
Base.reduced_index(i::IdentityUnitRange{<:IdOffsetRange}) = typeof(i)(first(i):first(i))
if VERSION < v"1.8.2"
    for f in [:firstindex, :lastindex]
        @eval @inline Base.$f(r::IdOffsetRange) = $f(r.parent) + r.offset
    end
end
for f in [:first, :last]
    # coerce the type to deal with values that get promoted on addition (eg. Bool)
    @eval @inline Base.$f(r::IdOffsetRange) = eltype(r)($f(r.parent) + r.offset)
end

# Iteration for an IdOffsetRange
@inline Base.iterate(r::IdOffsetRange, i...) = _iterate(r, i...)
# In general we iterate over the parent term by term and add the offset.
# This might have some performance degradation when coupled with bounds-checking
# See https://github.com/JuliaArrays/OffsetArrays.jl/issues/214
@inline function _iterate(r::IdOffsetRange, i...)
    ret = iterate(r.parent, i...)
    ret === nothing && return nothing
    return (eltype(r)(ret[1] + r.offset), ret[2])
end
# Base.OneTo(n) is known to be exactly equivalent to the range 1:n,
# and has no specialized iteration defined for it,
# so we may add the offset to the range directly and iterate over the result
# This gets around the performance issue described in issue #214
# We use the helper function _addoffset to evaluate the range instead of broadcasting
# just in case this makes it easy for the compiler.
@inline _iterate(r::IdOffsetRange{<:Integer, <:Base.OneTo}, i...) = iterate(_addoffset(r.parent, r.offset), i...)

@inline function Base.getindex(r::IdOffsetRange, i::Integer)
    i isa Bool && throw(ArgumentError("invalid index: $i of type Bool"))
    @boundscheck checkbounds(r, i)
    @inbounds eltype(r)(r.parent[i - r.offset] + r.offset)
end

# Logical indexing following https://github.com/JuliaLang/julia/pull/31829
#= Helper function to perform logical indxeing for boolean ranges
The code implemented is a branch-free version of the following:

    range(first(s) ? first(r) : last(r), length=Int(last(s)))

See https://github.com/JuliaArrays/OffsetArrays.jl/pull/224#discussion_r595635143

Logical indexing does not preserve indices, unlike other forms of vector indexing
=#
@inline function _getindex(r, s::AbstractUnitRange{Bool})
    range(first(r) * first(s) + last(r) * !first(s), length=Int(last(s)))
end
@inline function _getindex(r, s::StepRange{Bool})
    range(first(r) * first(s) + last(r) * !first(s), step = oneunit(step(s)), length=Int(last(s)))
end
@inline function _getindex(r, s::AbstractUnitRange)
    @inbounds rs = r.parent[_subtractoffset(s, r.offset)] .+ r.offset
    _indexedby(rs, axes(s))
end
@inline function _getindex(r, s::StepRange)
    rs = @inbounds r.parent[s .- r.offset] .+ r.offset
    _indexedby(rs, axes(s))
end

for T in [:AbstractUnitRange, :StepRange]
    @eval @inline function Base.getindex(r::IdOffsetRange, s::$T{<:Integer})
        @boundscheck checkbounds(r, s)
        return _getindex(r, s)
    end
end

# These methods are necessary to avoid ambiguity
for R in [:IIUR, :IdOffsetRange]
    @eval @inline function Base.getindex(r::IdOffsetRange, s::$R)
        @boundscheck checkbounds(r, s)
        return _getindex(r, s)
    end
end

# offset-preserve broadcasting
Broadcast.broadcasted(::Base.Broadcast.DefaultArrayStyle{1}, ::typeof(-), r::IdOffsetRange, x::Integer) =
    IdOffsetRange(r.parent .- x, r.offset)
Broadcast.broadcasted(::Base.Broadcast.DefaultArrayStyle{1}, ::typeof(+), r::IdOffsetRange, x::Integer) =
    IdOffsetRange(r.parent .+ x, r.offset)
Broadcast.broadcasted(::Base.Broadcast.DefaultArrayStyle{1}, ::typeof(+), x::Integer, r::IdOffsetRange) =
    IdOffsetRange(x .+ r.parent, r.offset)
Broadcast.broadcasted(::Base.Broadcast.DefaultArrayStyle{1}, ::typeof(big), r::IdOffsetRange) =
    IdOffsetRange(big.(r.parent), r.offset)

Base.show(io::IO, r::IdOffsetRange) = print(io, IdOffsetRange, "(values=",first(r), ':', last(r),", indices=",first(eachindex(r)),':',last(eachindex(r)), ")")

# Optimizations
@inline Base.checkindex(::Type{Bool}, inds::IdOffsetRange, i::Real) = Base.checkindex(Bool, inds.parent, i - inds.offset)

if VERSION < v"1.5.2"
    # issue 100, 133: IdOffsetRange as another index-preserving case shouldn't comtribute offsets
    # fixed by https://github.com/JuliaLang/julia/pull/37204
    @inline Base.compute_offset1(parent, stride1::Integer, dims::Tuple{Int}, inds::Tuple{IdOffsetRange}, I::Tuple) =
        Base.compute_linindex(parent, I) - stride1*first(Base.axes1(inds[1]))
end

# This was deemed "too private" to extend: see issue #184
# # Fixes an inference failure in Base.mapfirst!
# # Test: A = OffsetArray(rand(4,4), (-3,5)); R = similar(A, (1:1, 6:9)); maximum!(R, A)
# if isdefined(Base, :_firstslice)
#     Base._firstslice(i::IdOffsetRange) = IdOffsetRange(Base._firstslice(i.parent), i.offset)
# end


================================================
FILE: src/origin.jl
================================================
"""
    Origin(indices...)
    Origin(origin::Tuple)
    Origin(origin::CartesianIndex)

A helper type to construct OffsetArray with a given origin. This is not exported.

The `origin` of an array is defined as the tuple of the first index along each axis, i.e., `first.(axes(A))`.

# Example

```jldoctest origin; setup=:(using OffsetArrays)
julia> a = [1 2; 3 4];

julia> using OffsetArrays: Origin

julia> OffsetArray(a, Origin(0, 1))
2×2 OffsetArray(::$(Array{Int,2}), 0:1, 1:2) with eltype $Int with indices 0:1×1:2:
 1  2
 3  4

julia> OffsetArray(a, Origin(0)) # short notation for `Origin(0, 0)`
2×2 OffsetArray(::$(Array{Int, 2}), 0:1, 0:1) with eltype $Int with indices 0:1×0:1:
 1  2
 3  4
```

An `Origin` object is callable, and it may shift the origin of an array to the specified point.

```jldoctest origin
julia> b = Origin(0)(a) # shift the origin of the array to (0,0)
2×2 OffsetArray(::$(Array{Int, 2}), 0:1, 0:1) with eltype $Int with indices 0:1×0:1:
 1  2
 3  4
```

The type `Origin`, when called with an `AbstractArray` as the argument, will return an instance
corresponding ot the origin of the array.

```jldoctest origin
julia> origin_b = Origin(b) # retrieve the origin of the array as an Origin instance
Origin(0, 0)

julia> origin_b(ones(2,2)) # shift the origin of another array to that of b, in this case to (0,0)
2×2 OffsetArray(::$(Array{Float64, 2}), 0:1, 0:1) with eltype Float64 with indices 0:1×0:1:
 1.0  1.0
 1.0  1.0
```

!!! tip
    One may broadcast an `Origin` instance over multiple arrays to shift them all to the same origin.
    ```jldoctest
    julia> using OffsetArrays: Origin

    julia> a = [1 2; 3 4]; # origin at (1,1)

    julia> b = Origin(2,3)(a); # origin at (2,3)

    julia> c = Origin(4)(a); # origin at (4,4)

    julia> ao, bo, co = Origin(0).((a, b, c)); # shift all origins to (0,0)

    julia> first.(axes(ao)) == first.(axes(bo)) == first.(axes(co)) == (0,0)
    true

    julia> ao, bo, co = Origin(b).((a, b, c)); # shift all origins to that of b

    julia> first.(axes(ao)) == first.(axes(bo)) == first.(axes(co)) == (2,3)
    true

    julia> ao, bo, co = OffsetArray.((a, b, c), Origin(b)); # another way to do the same

    julia> first.(axes(ao)) == first.(axes(bo)) == first.(axes(co)) == (2,3)
    true
    ```
"""
struct Origin{T<:Union{Tuple{Vararg{Int}}, Int}}
    index::T
end
Origin(I::Tuple{Vararg{Int}}) = Origin{typeof(I)}(I)
Origin(I::Tuple{Vararg{Number}}) = Origin(map(Int, I))
Origin(I::CartesianIndex) = Origin(Tuple(I))
Origin(I::Number...) = Origin(I)
# Origin(0) != Origin((0, )) but they work the same with broadcasting
Origin(n::Number) = Origin{Int}(Int(n))

Base.Broadcast.broadcastable(o::Origin) = Ref(o)

_showidx(index::Integer) = "(" * string(index) * ")"
_showidx(index::Tuple) = string(index)
Base.show(io::IO, o::Origin) = print(io, "Origin", _showidx(o.index))


================================================
FILE: src/precompile.jl
================================================
function _precompile_()
    ccall(:jl_generating_output, Cint, ()) == 1 || return nothing
    Base.precompile(Tuple{typeof(Base.showarg),IOBuffer,OffsetArray{Int, 0, Array{Int, 0}},Bool})   # time: 0.037824474
    Base.precompile(Tuple{Type{IdOffsetRange{Int, Base.OneTo{Int}}},UnitRange{Int}})   # time: 0.009825722
    Base.precompile(Tuple{typeof(Base.inds2string),Tuple{IdOffsetRange{Int, Base.OneTo{Int}}, IdOffsetRange{Int, Base.OneTo{Int}}}})   # time: 0.0080779
    Base.precompile(Tuple{typeof(zeros),Tuple{IdOffsetRange{Int, Base.OneTo{Int}}}})   # time: 0.007713056
    Base.precompile(Tuple{typeof(ones),Tuple{IdOffsetRange{Int, Base.OneTo{Int}}}})   # time: 0.007713056
    Base.precompile(Tuple{typeof(trues),Tuple{UnitRange{Int}, UnitRange{Int}}})   # time: 0.005478372
    Base.precompile(Tuple{typeof(falses),Tuple{UnitRange{Int}, UnitRange{Int}}})   # time: 0.005478372
    Base.precompile(Tuple{typeof(firstindex),IdOffsetRange{Int, Base.OneTo{Int}}})   # time: 0.004100289
end


================================================
FILE: src/utils.jl
================================================
### Low-level utilities ###

_indexoffset(r::AbstractRange) = first(r) - 1
_indexoffset(i::Integer) = 0
_indexlength(r::AbstractRange) = length(r)
_indexlength(i::Integer) = Int(i)
_indexlength(i::Colon) = Colon()

# utility methods used in reshape
# we don't use _indexlength in this to avoid converting the arguments to Int
_checksize(ind::Integer, s) = ind == s
_checksize(ind::AbstractUnitRange, s) = length(ind) == s

_toaxis(i::Integer) = Base.OneTo(i)
_toaxis(i) = i

_strip_IdOffsetRange(r::IdOffsetRange) = parent(r)
_strip_IdOffsetRange(r) = r

_offset(axparent::AbstractUnitRange, ax::AbstractUnitRange) = first(ax) - first(axparent)
_offset(axparent::AbstractUnitRange, ::Union{Integer, Colon}) = 1 - first(axparent)

_offsets(A::AbstractArray) = map(ax -> first(ax) - 1, axes(A))
_offsets(A::AbstractArray, B::AbstractArray) = map(_offset, axes(B), axes(A))

"""
    OffsetArrays.AxisConversionStyle(typeof(indices))

`AxisConversionStyle` declares if `indices` should be converted to a single `AbstractUnitRange{Int}`
or to a `Tuple{Vararg{AbstractUnitRange{Int}}}` while flattening custom types into indices.
This method is called after `to_indices(A::Array, axes(A), indices)` to provide
further information in case `to_indices` does not return a `Tuple` of `AbstractUnitRange{Int}`.

Custom index types should extend `AxisConversionStyle` and return either `OffsetArray.SingleRange()`,
which is the default, or `OffsetArray.TupleOfRanges()`. In the former case, the type `T` should
define `Base.convert(::Type{AbstractUnitRange{Int}}, ::T)`, whereas in the latter it should define
`Base.convert(::Type{Tuple{Vararg{AbstractUnitRange{Int}}}}, ::T)`.

An example of the latter is `CartesianIndices`, which is converted to a `Tuple` of
`AbstractUnitRange{Int}` while flattening the indices.

# Example
```jldoctest; setup=:(using OffsetArrays)
julia> struct NTupleOfUnitRanges{N}
           x ::NTuple{N, UnitRange{Int}}
       end

julia> Base.to_indices(A, inds, t::Tuple{NTupleOfUnitRanges{N}}) where {N} = t;

julia> OffsetArrays.AxisConversionStyle(::Type{NTupleOfUnitRanges{N}}) where {N} = OffsetArrays.TupleOfRanges();

julia> Base.convert(::Type{Tuple{Vararg{AbstractUnitRange{Int}}}}, t::NTupleOfUnitRanges) = t.x;

julia> a = zeros(3, 3);

julia> inds = NTupleOfUnitRanges((3:5, 2:4));

julia> oa = OffsetArray(a, inds);

julia> axes(oa, 1) == 3:5
true

julia> axes(oa, 2) == 2:4
true
```
"""
abstract type AxisConversionStyle end
struct SingleRange <: AxisConversionStyle end
struct TupleOfRanges <: AxisConversionStyle end

AxisConversionStyle(::Type) = SingleRange()
AxisConversionStyle(::Type{<:CartesianIndices}) = TupleOfRanges()

_convertTupleAbstractUnitRange(x) = _convertTupleAbstractUnitRange(AxisConversionStyle(typeof(x)), x)
_convertTupleAbstractUnitRange(::SingleRange, x) = (convert(AbstractUnitRange{Int}, x),)
_convertTupleAbstractUnitRange(::TupleOfRanges, x) = convert(Tuple{Vararg{AbstractUnitRange{Int}}}, x)

_toAbstractUnitRanges(t::Tuple) = (_convertTupleAbstractUnitRange(first(t))..., _toAbstractUnitRanges(tail(t))...)
_toAbstractUnitRanges(::Tuple{}) = ()

# ensure that the indices are consistent in the constructor
_checkindices(A::AbstractArray, indices, label) = _checkindices(ndims(A), indices, label)
function _checkindices(N::Integer, indices, label)
    throw_argumenterror(N, indices, label) = throw(ArgumentError(label*" $indices are not compatible with a $(N)D array"))
    N == length(indices) || throw_argumenterror(N, indices, label)
end

@inline _indexedby(r::AbstractVector, ax::Tuple{Any}) = _indexedby(r, ax[1])
@inline _indexedby(r::AbstractUnitRange{<:Integer}, ::Base.OneTo) = no_offset_view(r)
@inline _indexedby(r::AbstractUnitRange{Bool}, ::Base.OneTo) = no_offset_view(r)
@inline _indexedby(r::AbstractVector, ::Base.OneTo) = no_offset_view(r)
@inline function _indexedby(r::AbstractUnitRange{<:Integer}, ax::AbstractUnitRange)
	of = convert(eltype(r), first(ax) - 1)
	IdOffsetRange(_subtractoffset(r, of), of)
end
@inline _indexedby(r::AbstractUnitRange{Bool}, ax::AbstractUnitRange) = OffsetArray(r, ax)
@inline _indexedby(r::AbstractVector, ax::AbstractUnitRange) = OffsetArray(r, ax)

# These functions are equivalent to the broadcasted operation r .- of
# However these ensure that the result is an AbstractRange even if a specific
# broadcasting behavior is not defined for a custom type
@inline _subtractoffset(r::AbstractUnitRange, of) = UnitRange(first(r) - of, last(r) - of)
@inline _subtractoffset(r::AbstractRange, of) = range(first(r) - of, stop = last(r) - of, step = step(r))

# similar to _subtractoffset, except these evaluate r .+ of
@inline _addoffset(r::AbstractUnitRange, of) = UnitRange(first(r) + of, last(r) + of)
@inline _addoffset(r::AbstractRange, of) = range(first(r) + of, stop = last(r) + of, step = step(r))

if VERSION <= v"1.7.0-DEV.1039"
    _contiguousindexingtype(r::AbstractUnitRange{<:Integer}) = UnitRange{Int}(r)
else
    _contiguousindexingtype(r::AbstractUnitRange{<:Integer}) = r
end

_of_eltype(::Type{T}, M::AbstractArray{T}) where {T} = M
_of_eltype(T, M::AbstractArray) = map(T, M)

# filter the arguments to reshape to check if there are any ranges
# If not, we may pop the parent array
_filterreshapeinds(t::Tuple{AbstractUnitRange, Vararg{Any}}) = t
_filterreshapeinds(t::Tuple) = _filterreshapeinds(tail(t))
_filterreshapeinds(t::Tuple{}) = t
_popreshape(A::AbstractArray, ax::Tuple{Vararg{Base.OneTo}}, inds::Tuple{}) = no_offset_view(A)
_popreshape(A::AbstractArray, ax, inds) = A


================================================
FILE: test/customranges.jl
================================================
# Useful for testing indexing
struct ZeroBasedRange{T,A<:AbstractRange{T}} <: AbstractRange{T}
    a :: A
    function ZeroBasedRange(a::AbstractRange{T}) where {T}
        @assert !Base.has_offset_axes(a)
        new{T, typeof(a)}(a)
    end
end

struct ZeroBasedUnitRange{T,A<:AbstractUnitRange{T}} <: AbstractUnitRange{T}
    a :: A
    function ZeroBasedUnitRange(a::AbstractUnitRange{T}) where {T}
        @assert !Base.has_offset_axes(a)
        new{T, typeof(a)}(a)
    end
end

for Z in [:ZeroBasedRange, :ZeroBasedUnitRange]
    @eval Base.parent(A::$Z) = A.a
    @eval Base.first(A::$Z) = first(A.a)
    @eval Base.length(A::$Z) = length(A.a)
    @eval Base.last(A::$Z) = last(A.a)
    @eval Base.size(A::$Z) = size(A.a)
    @eval Base.axes(A::$Z) = map(x -> IdentityUnitRange(0:x-1), size(A.a))
    @eval Base.getindex(A::$Z, i::Int) = A.a[i + 1]
    @eval Base.getindex(A::$Z, i::Integer) = A.a[i + 1]
    @eval Base.firstindex(A::$Z) = 0
    @eval Base.step(A::$Z) = step(A.a)
    @eval OffsetArrays.no_offset_view(A::$Z) = A.a
    @eval function Base.show(io::IO, A::$Z)
        show(io, A.a)
        print(io, " with indices $(axes(A,1))")
    end
end

for Z in [:ZeroBasedRange, :ZeroBasedUnitRange]
    for R in [:AbstractRange, :AbstractUnitRange, :StepRange]
        @eval @inline function Base.getindex(A::$Z, r::$R{<:Integer})
            @boundscheck checkbounds(A, r)
            OffsetArrays._indexedby(A.a[r .+ 1], axes(r))
        end
    end

    for R in [:ZeroBasedUnitRange, :ZeroBasedRange]
        @eval @inline function Base.getindex(A::$Z, r::$R{<:Integer})
            @boundscheck checkbounds(A, r)
            OffsetArrays._indexedby(A.a[r.a .+ 1], axes(r))
        end
    end

    for R in [:IIUR, :IdOffsetRange]
        @eval @inline function Base.getindex(A::$Z, r::$R)
            @boundscheck checkbounds(A, r)
            OffsetArrays._indexedby(A.a[r .+ 1], axes(r))
        end
    end

    for R in [:AbstractUnitRange, :IdOffsetRange, :IdentityUnitRange, :SliceIntUR, :StepRange, :StepRangeLen, :LinRange]
        @eval @inline function Base.getindex(A::$R, r::$Z)
            @boundscheck checkbounds(A, r)
            OffsetArrays._indexedby(A[r.a], axes(r))
        end
    end
    @eval @inline function Base.getindex(A::StepRangeLen{<:Any,<:Base.TwicePrecision,<:Base.TwicePrecision}, r::$Z)
        @boundscheck checkbounds(A, r)
        OffsetArrays._indexedby(A[r.a], axes(r))
    end

    @eval Base.reshape(z::$Z, inds::Tuple{}) = reshape(parent(z), inds)
    @eval Base.reshape(z::$Z, inds::Tuple{Int, Vararg{Int}}) = reshape(parent(z), inds)
    @eval Base.reshape(z::$Z, inds::Tuple{Union{Int, AbstractUnitRange{<:Integer}}, Vararg{Union{Int, AbstractUnitRange{<:Integer}}}}) = reshape(parent(z), inds)
end

# A basic range that does not have specialized vector indexing methods defined
# In this case the best that we may do is to return an OffsetArray
# Despite this, an indexing operation involving this type should preserve the axes of the indices
struct CustomRange{T,A<:AbstractRange{T}} <: AbstractRange{T}
    a :: A
end
Base.parent(r::CustomRange) = r.a
Base.size(r::CustomRange) = size(parent(r))
Base.length(r::CustomRange) = length(parent(r))
Base.axes(r::CustomRange) = axes(parent(r))
Base.first(r::CustomRange) = first(parent(r))
Base.last(r::CustomRange) = last(parent(r))
Base.step(r::CustomRange) = step(parent(r))
Base.getindex(r::CustomRange, i::Int) = getindex(parent(r), i)


================================================
FILE: test/origin.jl
================================================
using OffsetArrays: Origin
@testset "Origin" begin
    get_origin(A::AbstractArray) = first.(axes(A))

    @test Origin(0) != Origin((0, ))
    @test Origin(CartesianIndex(1, 2)) === Origin((1, 2)) === Origin(1, 2)

    @test Origin(Int32.((1,2))) == Origin(Int64.((1,2)))
    @test Origin(Int32.((1,2))...) == Origin(Int64.((1,2))...) == Origin((1.0, 2.0))
    @test Origin(Int32(1)) == Origin(Int64(1)) == Origin(1.0)
    @test_throws Exception Origin(1.5)

    # 0d
    A = OffsetArray(zeros())
    B = OffsetArray(zeros(), Origin())
    @test axes(A) == axes(B)

    # 1d
    v = [1, 2]
    @test get_origin(OffsetArray(v, Origin(2))) == (2, )
    ov = OffsetArray(v, -3)
    @test get_origin(OffsetArray(ov, Origin(2))) == (2, )
    @test get_origin(OffsetVector(ov, Origin(2))) == (2, )
    @test get_origin(OffsetArray(ov, Origin((2, )))) == (2, )

    # 2d
    a = [1 2;3 4]
    @test get_origin(OffsetArray(a, Origin(0))) == (0, 0)
    oa = OffsetArray(a, -3, -3)
    @test get_origin(OffsetArray(oa, Origin(0))) == (0, 0)
    @test get_origin(OffsetMatrix(oa, Origin(0))) == (0, 0)
    @test get_origin(OffsetArray(oa, Origin(1, 2))) == (1, 2)

    # 3d
    a = ones(3, 3, 3)
    @test get_origin(OffsetArray(a, Origin(0))) == (0, 0, 0)
    oa = OffsetArray(a, -3, -3, -3)
    @test get_origin(OffsetArray(oa, Origin(0))) == (0, 0, 0)
    @test get_origin(OffsetArray(oa, Origin(1, 2, 3))) == (1, 2, 3)

    # Scalar broadcasting
    let
        a = [ [1,2,3], [4,5,6] ]
        oa = OffsetVector.(a, Origin(0))
        @test get_origin.(oa) == [ (0,), (0,) ]

        a = [ [1 2; 3 4], [5 6 7; 8 9 10] ]
        oa = OffsetArray.(a, Origin(0, -1))
        @test get_origin.(oa) == [ (0,-1), (0,-1) ]
    end

    @testset "as a callable" begin
        a = [1 2; 3 4];
        @test OffsetArray(a, Origin(2)) == Origin(2)(a)
        for (index, firstinds) in Any[(1, (1,1)), ((2,3), (2,3))]
            b = Origin(index)(a)
            @test first.(axes(b)) == firstinds
            @test Origin(b) == Origin(firstinds)
            @test Origin(OffsetArrays.no_offset_view(b)) == Origin(ntuple(_ -> 1, Val(ndims(b))))
        end
        # compatibility with other array types
        @test Origin(Ones(2,2)) == Origin(1,1)
        @test Origin(SMatrix{2,2,Int,4}(1,2,3,4)) == Origin(1,1)
    end
    @testset "display" begin
        io = IOBuffer()
        show(io, Origin(1))
        @test String(take!(io)) == "Origin(1)"
        show(io, Origin(1, 1))
        @test String(take!(io)) == "Origin(1, 1)"
    end

    @testset "avoid overflow (issue #279)" begin
        A = Origin(typemin(Int)+1)(rand(3,3))
        B = Origin(typemax(Int)-4)(A)
        @test first.(axes(B)) == ntuple(_ -> typemax(Int)-4, Val(ndims(B)))
    end
end


================================================
FILE: test/runtests.jl
================================================
using Adapt
using Aqua
using Base: Slice
using CatIndices: BidirectionalVector
using DelimitedFiles
using DistributedArrays
using Documenter
using EllipsisNotation
using FillArrays
using LinearAlgebra
using OffsetArrays
using OffsetArrays: IdentityUnitRange, no_offset_view, IIUR, Origin, IdOffsetRange
using StaticArrays
using Test

const SliceIntUR = Slice{<:AbstractUnitRange{<:Integer}}

DocMeta.setdocmeta!(OffsetArrays, :DocTestSetup, :(using OffsetArrays); recursive=true)

no_offset_axes(x, d) = no_offset_view(axes(x, d))
no_offset_axes(x) = map(no_offset_view, axes(x))

# https://github.com/JuliaLang/julia/pull/29440
if VERSION < v"1.1.0-DEV.389"
    Base.:(:)(I::CartesianIndex{N}, J::CartesianIndex{N}) where N =
        CartesianIndices(map((i,j) -> i:j, Tuple(I), Tuple(J)))
end

# Custom index types
struct ZeroBasedIndexing end
struct NewColon end
struct TupleOfRanges{N}
    x ::NTuple{N, UnitRange{Int}}
end

include("customranges.jl")

function same_value(r1, r2)
    length(r1) == length(r2) || return false
    for (v1, v2) in zip(r1, r2)
        v1 == v2 || return false
    end
    return true
end

@testset "Project meta quality checks" begin
    Aqua.test_all(OffsetArrays, piracies=false)
    if VERSION >= v"1.2"
        doctest(OffsetArrays, manual = false)
    end
end

@testset "IdOffsetRange" begin

    function check_indexed_by(r, rindx)
        for i in rindx
            r[i]
        end
        @test_throws BoundsError r[minimum(rindx)-1]
        @test_throws BoundsError r[maximum(rindx)+1]
        return nothing
    end

    ro = OffsetArrays.IdOffsetRange(Base.OneTo(3))
    rs = OffsetArrays.IdOffsetRange(3:5, -2)
    @test typeof(ro) !== typeof(rs)
    @test same_value(ro, 1:3)
    check_indexed_by(ro, 1:3)
    @test same_value(rs, 1:3)
    check_indexed_by(rs, -1:1)
    @test firstindex(ro) == 1
    @test lastindex(ro) == 3
    @test firstindex(rs) == -1
    @test lastindex(rs) == 1
    @test @inferred(typeof(ro)(ro)) === ro
    @test @inferred(OffsetArrays.IdOffsetRange{Int}(ro))   === ro
    @test @inferred(OffsetArrays.IdOffsetRange{Int16}(ro)) === OffsetArrays.IdOffsetRange(Base.OneTo(Int16(3)))
    @test @inferred(OffsetArrays.IdOffsetRange(ro))        === ro
    @test parent(ro) === ro.parent
    @test parent(rs) === rs.parent
    # construction/coercion preserves the values, altering the axes if needed
    r2 = @inferred(typeof(rs)(ro))
    @test typeof(r2) === typeof(rs)
    @test same_value(ro, 1:3)
    check_indexed_by(ro, 1:3)
    r2 = @inferred(typeof(ro)(rs))
    @test typeof(r2) === typeof(ro)
    @test same_value(r2, 1:3)
    check_indexed_by(r2, 1:3)
    # check the example in the comments
    r = OffsetArrays.IdOffsetRange{Int,UnitRange{Int}}(3:4)
    @test same_value(r, 3:4)
    check_indexed_by(r, 1:2)
    r = OffsetArrays.IdOffsetRange{Int,Base.OneTo{Int}}(3:4)
    @test same_value(r, 3:4)
    check_indexed_by(r, 3:4)
    r = OffsetArrays.IdOffsetRange{Int,Base.OneTo{Int}}(3:4, -2)
    @test same_value(r, 1:2)
    check_indexed_by(r, 1:2)

    r = OffsetArrays.IdOffsetRange{Int32, Base.OneTo{Int32}}(Base.OneTo(Int64(2)), 3)
    @test same_value(r, 4:5)
    check_indexed_by(r, 4:5)

    r = IdOffsetRange{Int, UnitRange{Int}}(IdOffsetRange(3:5, 2), 2)
    @test typeof(r) == IdOffsetRange{Int, UnitRange{Int}}
    @test same_value(r, 7:9)
    check_indexed_by(r, 5:7)

    r = IdOffsetRange{Int, Base.OneTo{Int}}(IdOffsetRange(Base.OneTo(3), 1), 1)
    @test typeof(r) == IdOffsetRange{Int,Base.OneTo{Int}}
    @test same_value(r, 3:5)
    check_indexed_by(r, 3:5)

    rp = Base.OneTo(3)
    r = IdOffsetRange(rp)
    r2 = IdOffsetRange{Int,typeof(r)}(r, 1)
    @test same_value(r2, 2:4)
    check_indexed_by(r2, 2:4)

    r2 = IdOffsetRange{Int32,IdOffsetRange{Int32,Base.OneTo{Int32}}}(r, 1)
    @test typeof(r2) == IdOffsetRange{Int32,IdOffsetRange{Int32,Base.OneTo{Int32}}}
    @test same_value(r2, 2:4)
    check_indexed_by(r2, 2:4)

    # eltype coercion through the AbstractUnitRange constructor
    ro = OffsetArrays.IdOffsetRange(Base.OneTo(3))
    @test @inferred(AbstractUnitRange{Int}(ro)) === ro
    rb = IdOffsetRange(Base.OneTo(big(3)))
    @test @inferred(AbstractUnitRange{Int}(rb)) === IdOffsetRange(Base.OneTo(3))

    # Constructor that's round-trippable with `show`
    rrt = IdOffsetRange(values=7:9, indices=-1:1)
    @test same_value(rrt, 7:9)
    check_indexed_by(rrt, -1:1)
    @test_throws ArgumentError IdOffsetRange(values=7:9, indices=-1:2)
    @test_throws ArgumentError IdOffsetRange(values=7:9, indices=-1:0)
    @test_throws TypeError IdOffsetRange(values=7:9, indices=-1)
    @test_throws UndefKeywordError IdOffsetRange(values=7:9)
    @test_throws UndefKeywordError IdOffsetRange(indices=-1:1)
    @test_throws MethodError IdOffsetRange(7:9, indices=-1:1)
    @test_throws MethodError IdOffsetRange(-1:1, values=7:9)

    p = IdOffsetRange(1:3, 2)
    q = IdOffsetRange(values = p .- 2, indices = p)
    @test same_value(q, 1:3)
    check_indexed_by(q, p)

    @testset for indices in Any[Base.OneTo(3), IdentityUnitRange(Base.OneTo(3))]
        p = IdOffsetRange(values = IdOffsetRange(1:3, 2), indices = indices)
        @test same_value(p, 3:5)
        check_indexed_by(p, 1:3)
        q = IdOffsetRange(values = Base.OneTo(3), indices = indices)
        @test same_value(q, 1:3)
        @test q isa IdOffsetRange{Int, Base.OneTo{Int}}
    end

    # conversion preserves both the values and the axes, throwing an error if this is not possible
    @test @inferred(oftype(ro, ro)) === ro
    @test @inferred(convert(OffsetArrays.IdOffsetRange{Int}, ro)) === ro
    @test @inferred(convert(OffsetArrays.IdOffsetRange{Int}, rs)) === rs
    @test @inferred(convert(OffsetArrays.IdOffsetRange{Int16}, ro)) === OffsetArrays.IdOffsetRange(Base.OneTo(Int16(3)))
    r2 = @inferred(oftype(rs, ro))
    @test typeof(r2) === typeof(rs)
    @test same_value(r2, 1:3)
    check_indexed_by(r2, 1:3)
    # These two broken tests can be fixed by uncommenting the `convert` definitions
    # in axes.jl, but unfortunately Julia may not quite be ready for this. (E.g. `reinterpretarray.jl`)
    @test_broken try oftype(ro, rs); false catch err true end  # replace with line below
    # @test_throws ArgumentError oftype(ro, rs)
    @test @inferred(oftype(ro, Base.OneTo(2))) === OffsetArrays.IdOffsetRange(Base.OneTo(2))
    @test @inferred(oftype(ro, 1:2)) === OffsetArrays.IdOffsetRange(Base.OneTo(2))
    @test_broken try oftype(ro, 3:4); false catch err true end
    # @test_throws ArgumentError oftype(ro, 3:4)

    # broadcasting behavior with scalars (issue #104)
    r3 = (1 .+ OffsetArrays.IdOffsetRange(3:5, -1) .+ 1) .- 1
    @test r3 isa OffsetArrays.IdOffsetRange
    @test same_value(r3, 3:5)
    check_indexed_by(r3, axes(r3,1))

    r = OffsetArrays.IdOffsetRange(3:5, -1)
    rc = copyto!(similar(r), r)
    n = big(typemax(Int))
    @test @inferred(broadcast(+, r, n)) == @inferred(broadcast(+, n, r)) == rc .+ n
    @test @inferred(broadcast(-, r, n)) == rc .- n
    @test @inferred(broadcast(big, r)) == big.(rc)
    for n in Any[2, big(typemax(Int))]
        @test @inferred(broadcast(+, r, n)) == @inferred(broadcast(+, n, r)) == rc .+ n
    end

    @testset "Idempotent indexing" begin
        @testset "Indexing into an IdOffsetRange" begin
            r = OffsetArrays.IdOffsetRange(3:5, -1)
            # Indexing with IdentityUnitRange
            s = IdentityUnitRange(0:2)
            @test axes(r[s]) == axes(s)
            for i in eachindex(s)
                @test r[s[i]] == r[s][i]
            end

            # Indexing with IdOffsetRange
            s = OffsetArrays.IdOffsetRange(-4:-2, 4)
            @test axes(r[s]) == axes(s)
            for i in eachindex(s)
                @test r[s[i]] == r[s][i]
            end

            # Indexing with UnitRange
            s = 0:2
            @test axes(r[s]) == axes(s)
            for i in eachindex(s)
                @test r[s[i]] == r[s][i]
            end
        end
        @testset "Indexing using an IdOffsetRange" begin
            r = OffsetArrays.IdOffsetRange(3:5, -1)
            # Indexing into an IdentityUnitRange
            s = IdentityUnitRange(-1:5)
            @test axes(s[r]) == axes(r)
            for i in eachindex(r)
                @test s[r[i]] == s[r][i]
            end

            # Indexing into an UnitRange
            s = -3:6
            @test axes(s[r]) == axes(r)
            for i in eachindex(r)
                @test s[r[i]] == s[r][i]
            end
        end
    end

    # Test reduced index
    rred = Base.reduced_index(r)
    @test typeof(rred) == typeof(r)
    @test length(rred) == 1
    @test first(rred) == first(r)

    @testset "reduced_indices" begin
        a = reshape(1:24, 2, 3, 4)
        sa = OffsetArray(a, (2, 3, 4));
        @testset for dim in 1:ndims(sa)
            sasum = sum(sa, dims = dim)
            @test parent(sasum) == sum(a, dims = dim)
            find = firstindex(sa, dim)
            @test no_offset_axes(sasum, dim) == find:find
        end
    end

    @testset "conversion to AbstractUnitRange" begin
        r = IdOffsetRange(1:2)
        @test AbstractUnitRange{Int}(r) === r
        r2 = IdOffsetRange(big(1):big(2))
        @test AbstractUnitRange{Int}(r2) === r
        @test AbstractUnitRange{BigInt}(r2) === r2

        if v"1.5" < VERSION
            @test OrdinalRange{Int,Int}(r2) === r
            @test OrdinalRange{BigInt,BigInt}(r2) === r2
        end
    end

    @testset "Bool IdOffsetRange (issue #223)" begin
        for b1 in [false, true], b2 in [false, true]
            r = IdOffsetRange(b1:b2)
            @test first(r) === b1
            @test last(r) === b2
        end
        @test_throws ArgumentError IdOffsetRange(true:true, true)
        @test_throws ArgumentError IdOffsetRange{Bool,UnitRange{Bool}}(true:true, true)
        @test_throws ArgumentError IdOffsetRange{Bool,IdOffsetRange{Bool,UnitRange{Bool}}}(IdOffsetRange(true:true), true)
    end

    @testset "Logical indexing" begin
        @testset "indexing with a single bool" begin
            r = IdOffsetRange(1:2)
            @test_throws ArgumentError r[true]
            @test_throws ArgumentError r[false]
        end
        @testset "indexing with a Bool UnitRange" begin
            r = IdOffsetRange(1:0)

            @test r[true:false] == 1:0
            @test r[true:false] == collect(r)[true:false]
            @test_throws BoundsError r[true:true]
            @test_throws BoundsError r[false:false]
            @test_throws BoundsError r[false:true]

            r = IdOffsetRange(1:1)

            @test r[true:true] == 1:1
            @test r[true:true] == collect(r)[true:true]

            @test r[false:false] == 1:0
            @test r[false:false] == collect(r)[false:false]

            @test_throws BoundsError r[true:false]
            @test_throws BoundsError r[false:true]

            r = IdOffsetRange(1:2)

            @test r[false:true] == 2:2
            @test r[false:true] == collect(r)[false:true]

            @test_throws BoundsError r[true:true]
            @test_throws BoundsError r[true:false]
            @test_throws BoundsError r[false:false]
        end
        @testset "indexing with a Bool IdOffsetRange" begin
            # bounds-checking requires the axes of the indices to match that of the array
            function testlogicalindexing(r, r2)
                r3 = r[r2];
                @test no_offset_view(r3) == collect(r)[collect(r2)]
            end

            r = IdOffsetRange(10:9)
            r2 = IdOffsetRange(true:false)
            testlogicalindexing(r, r2)

            r = IdOffsetRange(10:10)
            r2 = IdOffsetRange(false:false)
            testlogicalindexing(r, r2)
            r2 = IdOffsetRange(true:true)
            testlogicalindexing(r, r2)

            r = IdOffsetRange(10:10, 1)
            r2 = IdOffsetRange(false:false, 1) # effectively true:true with indices 2:2
            testlogicalindexing(r, r2)

            r = IdOffsetRange(10:11)
            r2 = IdOffsetRange(false:true)
            testlogicalindexing(r, r2)
        end
        @testset "indexing with a Bool StepRange" begin
            r = IdOffsetRange(1:0)

            @test r[true:true:false] == 1:1:0
            @test_throws BoundsError r[true:true:true]
            @test_throws BoundsError r[false:true:false]
            @test_throws BoundsError r[false:true:true]

            r = IdOffsetRange(1:1)

            @test r[true:true:true] == 1:1:1
            @test r[true:true:true] == collect(r)[true:true:true]
            @test axes(r[true:true:true], 1) == 1:1

            @test r[false:true:false] == 1:1:0
            @test r[false:true:false] == collect(r)[false:true:false]

            # StepRange{Bool,Int}
            s = StepRange(true, 1, true)
            @test r[s] == 1:1:1
            @test r[s] == collect(r)[s]

            s = StepRange(true, 2, true)
            @test r[s] == 1:1:1
            @test r[s] == collect(r)[s]

            s = StepRange(false, 1, false)
            @test r[s] == 1:1:0
            @test r[s] == collect(r)[s]

            s = StepRange(false, 2, false)
            @test r[s] == 1:1:0
            @test r[s] == collect(r)[s]

            @test_throws BoundsError r[true:true:false]
            @test_throws BoundsError r[false:true:true]

            r = IdOffsetRange(1:2)

            @test r[false:true:true] == 2:1:2
            @test r[false:true:true] == collect(r)[false:true:true]

            # StepRange{Bool,Int}
            s = StepRange(false, 1, true)
            @test r[s] == 2:1:2
            @test r[s] == collect(r)[s]

            @test_throws BoundsError r[true:true:true]
            @test_throws BoundsError r[true:true:false]
            @test_throws BoundsError r[false:true:false]
        end
    end

    @testset "iteration" begin
        # parent has Base.OneTo axes
        A = ones(4:10)
        ax = axes(A, 1)
        ind, st = iterate(ax)
        @test A[ind] == A[4]
        ind, st = iterate(ax, st)
        @test A[ind] == A[5]

        # parent doesn't have Base.OneTo axes
        B = @view A[:]
        C = OffsetArray(B, 0)
        ax = axes(C, 1)
        ind, st = iterate(ax)
        @test C[ind] == C[4]
        ind, st = iterate(ax, st)
        @test C[ind] == C[5]
    end
end

# used in testing the constructor
struct WeirdInteger{T} <: Integer
    x :: T
end
# assume that it doesn't behave as expected
Base.Int(a::WeirdInteger) = a

@testset "Constructors" begin
    @testset "Single-entry arrays in dims 0:5" begin
        for n = 0:5
            for z in (OffsetArray(ones(Int,ntuple(d->1,n)), ntuple(x->x-1,n)),
                    fill!(OffsetArray{Float64}(undef, ntuple(x->x:x, n)), 1),
                    fill!(OffsetArray{Float64}(undef, ntuple(x->x:x, n)...), 1),
                    fill!(OffsetArray{Float64,n}(undef, ntuple(x->x:x, n)), 1),
                    fill!(OffsetArray{Float64,n}(undef, ntuple(x->x:x, n)...), 1))
                @test length(LinearIndices(z)) == 1
                @test no_offset_axes(z) == ntuple(x->x:x, n)
                @test z[1] == 1
            end
        end
        a0 = reshape([3])
        a = OffsetArray(a0)
        @test axes(a) == ()
        @test ndims(a) == 0
        @test a[] == 3
        @test a === OffsetArray(a, ())
        @test_throws ArgumentError OffsetArray(a, 0)
        @test_throws ArgumentError OffsetArray(a0, 0)
    end

    @testset "OffsetVector" begin
        # initialization
        one_based_axes = Any[
            (Base.OneTo(4), ),
            (1:4, ),
            (big(1):big(4), ),
            (CartesianIndex(1):CartesianIndex(4), ),
            (IdentityUnitRange(1:4), ),
            (IdOffsetRange(1:4),),
            (IdOffsetRange(3:6, -2),)
        ]

        offset_axes = Any[
            (-1:2, ),
            (big(-1):big(2), ),
            (CartesianIndex(-1):CartesianIndex(2), ),
            (IdentityUnitRange(-1:2), ),
            (IdOffsetRange(-1:2),),
            (IdOffsetRange(3:6, -4),)
        ]

        offsets = size.(one_based_axes[1], 1)
        offsets_big = map(big, offsets)

        for inds in Any[offsets, offsets_big, one_based_axes...]
            # test indices API
            a = OffsetVector{Float64}(undef, inds)
            @test eltype(a) === Float64
            @test axes(a) === axes(OffsetVector{Float64}(undef, inds...)) === axes(OffsetArray{Float64, 1}(undef, inds)) === axes(OffsetArray{Float64}(undef, inds))
            @test axes(a) === (IdOffsetRange(Base.OneTo(4), 0), )
            @test a.offsets === (0, )
            @test axes(a.parent) == (Base.OneTo(4), )

            a = OffsetVector{Nothing}(nothing, inds)
            @test eltype(a) === Nothing
            @test axes(a) === axes(OffsetVector{Nothing}(nothing, inds...)) === axes(OffsetArray{Nothing, 1}(nothing, inds))
            @test axes(a) === (IdOffsetRange(Base.OneTo(4), 0), )

            a = OffsetVector{Missing}(missing, inds)
            @test eltype(a) === Missing
            @test axes(a) === axes(OffsetVector{Missing}(missing, inds...)) === axes(OffsetArray{Missing, 1}(missing, inds))
            @test axes(a) === (IdOffsetRange(Base.OneTo(4), 0), )
        end

        # nested OffsetVectors
        for inds in Any[offsets, offsets_big]
            a = OffsetVector{Float64}(undef, inds)
            b = OffsetVector(a, inds); b2 = OffsetVector(a, inds...);
            @test eltype(b) === eltype(b2) === Float64
            @test axes(b, 1) === axes(b2, 1) === IdOffsetRange(Base.OneTo(4), 4)
        end

        # offset indexing
        for inds in offset_axes
            # test offsets
            a = OffsetVector{Float64}(undef, inds)
            ax = (IdOffsetRange(Base.OneTo(4), -2), )
            @test a.offsets === (-2, )
            @test axes(a.parent) == (Base.OneTo(4), )
            @test axes(a) === ax
            a = OffsetVector{Nothing}(nothing, inds)
            @test axes(a) === ax
            a = OffsetVector{Missing}(missing, inds)
            @test axes(a) === ax

            for (T, t) in Any[(Nothing, nothing), (Missing, missing)]
                a = OffsetVector{Union{T, Vector{Int}}}(undef, inds)
                @test !isassigned(a, -1)
                @test eltype(a) === Union{T, Vector{Int}}
                @test axes(a) === ax

                a = OffsetVector{Union{T, Vector{Int}}}(t, inds)
                @test a[-1] === t
            end
        end
        @test_throws Union{ArgumentError, ErrorException} OffsetVector{Float64}(undef, -2) # only positive number works

        # convenient constructors
        a = rand(4)
        for inds in offset_axes
            oa1 = OffsetVector(a, inds...)
            oa2 = OffsetVector(a, inds)
            oa3 = OffsetArray(a, inds...)
            oa4 = OffsetArray(a, inds)
            @test oa1 === oa2 === oa3 === oa4
            @test axes(oa1) === (IdOffsetRange(Base.OneTo(4), -2), )
            @test parent(oa1) === a
            @test oa1.offsets === (-2, )
        end

        oa = OffsetArray(a, :)
        @test oa === OffsetArray(a, (:, )) === OffsetArray(a, axes(a)) === OffsetVector(a, :) === OffsetVector(a, axes(a))
        @test oa == a
        @test axes(oa) == axes(a)
        @test axes(oa) !== axes(a)

        # nested offset array
        a = rand(4)
        oa = OffsetArray(a, -1)
        for inds in Any[.-oa.offsets, one_based_axes...]
            ooa = OffsetArray(oa, inds)
            @test typeof(parent(ooa)) <: Vector
            @test ooa === OffsetArray(oa, inds...) === OffsetVector(oa, inds) === OffsetVector(oa, inds...)
            @test ooa == a
            @test axes(ooa) == axes(a)
            @test axes(ooa) !== axes(a)
        end

        # overflow bounds check
        v = rand(5)
        @test axes(OffsetVector(v, typemax(Int)-length(v))) == (IdOffsetRange(axes(v)[1], typemax(Int)-length(v)), )
        @test_throws OverflowError OffsetVector(v, typemax(Int)-length(v)+1)
        ao = OffsetArray(v, typemin(Int))
        ao2 = OffsetArray{Float64, 1, typeof(ao)}(ao, (-1, ))
        @test no_offset_axes(ao2, 1) == typemin(Int) .+ (0:length(v)-1)
        ao2 = OffsetArray(ao, (-1,))
        @test no_offset_axes(ao2, 1) == typemin(Int) .+ (0:length(v)-1)
        @test_throws OverflowError OffsetArray{Float64, 1, typeof(ao)}(ao, (-2, )) # inner Constructor
        @test_throws OverflowError OffsetArray(ao, (-2, )) # convenient constructor accumulate offsets
        @test_throws OverflowError OffsetVector(1:0, typemax(Int))
        @test_throws OverflowError OffsetVector(OffsetVector(1:0, 0), typemax(Int))
        @test_throws OverflowError OffsetArray(zeros(Int, typemax(Int):typemax(Int)), 2)
        @test_throws OverflowError OffsetArray(v, OffsetArrays.Origin(typemax(Int)))

        b = OffsetArray(OffsetArray(big(1):2, 1), typemax(Int)-1)
        @test no_offset_axes(b, 1) == big(typemax(Int)) .+ (1:2)

        @testset "OffsetRange" begin
            for r in Any[1:100, big(1):big(2)]
                a = OffsetVector(r, 4)
                @test first(r) in a
                @test !(last(r) + 1 in a)
            end

            @testset "BigInt axes" begin
                r = OffsetArray(1:big(2)^65, 4000)
                @test eltype(r) === BigInt
                @test no_offset_axes(r, 1) == (big(1):big(2)^65) .+ 4000
            end
        end

        # disallow OffsetVector(::Array{<:Any, N}, offsets) where N != 1
        @test_throws ArgumentError OffsetVector(zeros(2,2), (2, 2))
        @test_throws ArgumentError OffsetVector(zeros(2,2), 2, 2)
        @test_throws ArgumentError OffsetVector(zeros(2,2), (1:2, 1:2))
        @test_throws ArgumentError OffsetVector(zeros(2,2), 1:2, 1:2)
        @test_throws ArgumentError OffsetVector(zeros(), ())
        @test_throws ArgumentError OffsetVector(zeros())
        @test_throws ArgumentError OffsetVector(zeros(2,2), ())
        @test_throws ArgumentError OffsetVector(zeros(2,2))
        @test_throws ArgumentError OffsetVector(zeros(2,2), 2)
        @test_throws ArgumentError OffsetVector(zeros(2,2), (2,))
        @test_throws ArgumentError OffsetVector(zeros(2:3,2:3), 2, 3)
        @test_throws ArgumentError OffsetVector(zeros(2:3,2:3), (2, 4))
        @test_throws ArgumentError OffsetVector(zeros(2:3,2:3), ())
        @test_throws ArgumentError OffsetVector(zeros(2:3,2:3))

        # eltype of an OffsetArray should match that of the parent (issue #162)
        @test_throws TypeError OffsetVector{Float64,Vector{ComplexF64}}
        # ndim of an OffsetArray should match that of the parent
        @test_throws TypeError OffsetVector{Float64,Matrix{Float64}}
    end

    @testset "OffsetMatrix" begin
        # initialization

        one_based_axes = Any[
                (Base.OneTo(4), Base.OneTo(3)),
                (1:4, 1:3),
                (big(1):big(4), big(1):big(3)),
                (CartesianIndex(1, 1):CartesianIndex(4, 3), ),
                (CartesianIndex(1):CartesianIndex(4), CartesianIndex(1):CartesianIndex(3)),
                (CartesianIndex(1):CartesianIndex(4), 1:3),
                (IdentityUnitRange(1:4), IdentityUnitRange(1:3)),
                (IdOffsetRange(1:4), IdOffsetRange(1:3)),
                (IdOffsetRange(3:6, -2), IdOffsetRange(3:5, -2)),
                (IdOffsetRange(3:6, -2), IdentityUnitRange(1:3)),
                (IdOffsetRange(3:6, -2), 1:3),
        ]

        offset_axes = Any[
                (-1:2, 0:2),
                (big(-1):big(2), big(0):big(2)),
                (CartesianIndex(-1, 0):CartesianIndex(2, 2), ),
                (-1:2, CartesianIndex(0):CartesianIndex(2)),
                (CartesianIndex(-1):CartesianIndex(2), CartesianIndex(0):CartesianIndex(2)),
                (CartesianIndex(-1):CartesianIndex(2), 0:2),
                (IdentityUnitRange(-1:2), 0:2),
                (IdOffsetRange(-1:2), IdOffsetRange(0:2)),
                (IdOffsetRange(3:6, -4), IdOffsetRange(2:4, -2)),
                (IdOffsetRange(3:6, -4), IdentityUnitRange(0:2)),
                (IdOffsetRange(-1:2), 0:2),
        ]

        offsets = size.(one_based_axes[1], 1)
        offsets_big = map(big, offsets)

        for inds in Any[offsets, offsets_big, one_based_axes...]
            # test API
            a = OffsetMatrix{Float64}(undef, inds)
            ax = (IdOffsetRange(Base.OneTo(4), 0), IdOffsetRange(Base.OneTo(3), 0))
            @test eltype(a) === Float64
            @test axes(a) === axes(OffsetMatrix{Float64}(undef, inds...)) === axes(OffsetArray{Float64, 2}(undef, inds)) === axes(OffsetArray{Float64, 2}(undef, inds...)) === axes(OffsetArray{Float64}(undef, inds))
            @test axes(a) === ax
            @test a.offsets === (0, 0)
            @test axes(a.parent) == (Base.OneTo(4), Base.OneTo(3))

            a = OffsetMatrix{Nothing}(nothing, inds)
            @test eltype(a) === Nothing
            @test axes(a) === axes(OffsetMatrix{Nothing}(nothing, inds...)) === axes(OffsetArray{Nothing, 2}(nothing, inds)) === axes(OffsetArray{Nothing, 2}(nothing, inds...))
            @test axes(a) === ax

            a = OffsetMatrix{Missing}(missing, inds)
            @test eltype(a) === Missing
            @test axes(a) === axes(OffsetMatrix{Missing}(missing, inds...)) === axes(OffsetArray{Missing, 2}(missing, inds)) === axes(OffsetArray{Missing, 2}(missing, inds...))
            @test axes(a) === ax
        end
        @test_throws Union{ArgumentError, ErrorException} OffsetMatrix{Float64}(undef, 2, -2) # only positive numbers works

        # nested OffsetMatrices
        for inds in Any[offsets, offsets_big]
            a = OffsetMatrix{Float64}(undef, inds)
            b = OffsetMatrix(a, inds); b2 = OffsetMatrix(a, inds...);
            @test eltype(b) === eltype(b2) === Float64
            @test axes(b, 1) === axes(b2, 1) === IdOffsetRange(Base.OneTo(4), 4)
            @test axes(b, 2) === axes(b2, 2) === IdOffsetRange(Base.OneTo(3), 3)
        end

        for inds in offset_axes
            # test offsets
            a = OffsetMatrix{Float64}(undef, inds)
            ax = (IdOffsetRange(Base.OneTo(4), -2), IdOffsetRange(Base.OneTo(3), -1))
            @test a.offsets === (-2, -1)
            @test axes(a.parent) == (Base.OneTo(4), Base.OneTo(3))
            @test axes(a) === ax
            a = OffsetMatrix{Nothing}(nothing, inds)
            @test axes(a) === ax
            a = OffsetMatrix{Missing}(missing, inds)
            @test axes(a) === ax

            for (T, t) in Any[(Nothing, nothing), (Missing, missing)]
                a = OffsetMatrix{Union{T, Vector{Int}}}(undef, inds)
                @test !isassigned(a, -1, 0)
                @test eltype(a) === Union{T, Vector{Int}}
                @test axes(a) === ax

                a = OffsetMatrix{Union{T, Vector{Int}}}(t, inds)
                @test a[-1, 0] === t
            end
        end

        # convenient constructors
        a = rand(4, 3)
        for inds in offset_axes
            ax = (IdOffsetRange(Base.OneTo(4), -2), IdOffsetRange(Base.OneTo(3), -1))
            oa1 = OffsetMatrix(a, inds...)
            oa2 = OffsetMatrix(a, inds)
            oa3 = OffsetArray(a, inds...)
            oa4 = OffsetArray(a, inds)
            @test oa1 === oa2 === oa3 === oa4
            @test axes(oa1) === ax
            @test parent(oa1) === a
            @test oa1.offsets === (-2, -1)
        end
        oa = OffsetArray(a, :, axes(a, 2))
        @test oa === OffsetArray(a, (axes(oa, 1), :)) === OffsetArray(a, axes(a)) === OffsetMatrix(a, (axes(oa, 1), :)) === OffsetMatrix(a, axes(a))
        @test oa == a
        @test axes(oa) == axes(a)
        @test axes(oa) !== axes(a)

        oa = OffsetMatrix(a, :, 2:4)
        @test oa === OffsetMatrix(a, axes(a, 1), 2:4) === OffsetMatrix(a, (axes(oa, 1), 2:4))

        # nested offset array
        a = rand(4, 3)
        oa = OffsetArray(a, -1, -2)
        for inds in Any[.-oa.offsets, one_based_axes...]
            ooa = OffsetArray(oa, inds)
            @test ooa === OffsetArray(oa, inds...) === OffsetMatrix(oa, inds) === OffsetMatrix(oa, inds...)
            @test typeof(parent(ooa)) <: Matrix
            @test ooa == a
            @test axes(ooa) == axes(a)
            @test axes(ooa) !== axes(a)
        end

        # overflow bounds check
        a = rand(4, 3)
        @test axes(OffsetMatrix(a, typemax(Int)-size(a, 1), 0)) == (IdOffsetRange(axes(a)[1], typemax(Int)-size(a, 1)), axes(a, 2))
        @test_throws OverflowError OffsetMatrix(a, typemax(Int)-size(a,1)+1, 0)
        @test_throws OverflowError OffsetMatrix(a, 0, typemax(Int)-size(a, 2)+1)

        # disallow OffsetMatrix(::Array{<:Any, N}, offsets) where N != 2
        @test_throws ArgumentError OffsetMatrix(zeros(2), (2,))
        @test_throws ArgumentError OffsetMatrix(zeros(2), 2)
        @test_throws ArgumentError OffsetMatrix(zeros(2), (1:2,))
        @test_throws ArgumentError OffsetMatrix(zeros(2), 1:2)
        @test_throws ArgumentError OffsetMatrix(zeros(), ())
        @test_throws ArgumentError OffsetMatrix(zeros())
        @test_throws ArgumentError OffsetMatrix(zeros(2), ())
        @test_throws ArgumentError OffsetMatrix(zeros(2))
        @test_throws ArgumentError OffsetMatrix(zeros(2), (1, 2))
        @test_throws ArgumentError OffsetMatrix(zeros(2), 1, 2)
        @test_throws ArgumentError OffsetMatrix(zeros(2:3), (2,))
        @test_throws ArgumentError OffsetMatrix(zeros(2:3), 2)
        @test_throws ArgumentError OffsetMatrix(zeros(2:3, 1:2, 1:2), (2,0,0))
        @test_throws ArgumentError OffsetMatrix(zeros(2:3, 1:2, 1:2), 2,0,0)
        @test_throws ArgumentError OffsetMatrix(zeros(2:3, 1:2, 1:2), ())
        @test_throws ArgumentError OffsetMatrix(zeros(2:3, 1:2, 1:2))

        # eltype of an OffsetArray should match that of the parent (issue #162)
        @test_throws TypeError OffsetMatrix{Float64,Matrix{ComplexF64}}
        # ndim of an OffsetArray should match that of the parent
        @test_throws TypeError OffsetMatrix{Float64,Vector{Float64}}
    end

    # no need to duplicate the 2D case here,
    # only add some special test cases
    @testset "OffsetArray" begin
        a = rand(2, 2, 2)
        oa = OffsetArray(a, 0:1, 3:4, 2:3)
        @test OffsetArray(a, CartesianIndices(axes(oa))) == oa
        @test no_offset_axes(OffsetArray(a, :, CartesianIndices((3:4, 2:3)))) == (1:2, 3:4, 2:3)
        @test no_offset_axes(OffsetArray(a, 10:11, CartesianIndices((3:4, 2:3)) )) == (10:11, 3:4, 2:3)
        @test no_offset_axes(OffsetArray(a, CartesianIndices((3:4, 2:3)), :)) == (3:4, 2:3, 1:2)
        @test no_offset_axes(OffsetArray(a, CartesianIndices((3:4, 2:3)), 10:11)) == (3:4, 2:3, 10:11)
        @test no_offset_axes(OffsetArray(a, :, :, CartesianIndices((3:4,)) )) == (1:2, 1:2, 3:4)
        @test no_offset_axes(OffsetArray(a, 10:11, :, CartesianIndices((3:4,)) )) == (10:11, 1:2, 3:4)
        @test no_offset_axes(OffsetArray(a, 10:11, 2:3, CartesianIndices((3:4,)) )) == (10:11, 2:3, 3:4)

        # ignore empty CartesianIndices
        @test OffsetArray(a, CartesianIndices(()), 0:1, :, 2:3) == OffsetArray(a, 0:1, :, 2:3)
        @test OffsetArray(a, 0:1, CartesianIndices(()), :, 2:3) == OffsetArray(a, 0:1, :, 2:3)
        @test OffsetArray(a, 0:1, :,  CartesianIndices(()), 2:3) == OffsetArray(a, 0:1, :, 2:3)
        @test OffsetArray(a, 0:1, :, 2:3, CartesianIndices(())) == OffsetArray(a, 0:1, :, 2:3)

        # nested OffsetArrays
        for offsets in [(1,1,1), big.((1,1,1))]
            ob = OffsetArray(oa, offsets); ob2 = OffsetArray(oa, offsets...);
            @test eltype(ob) === eltype(ob2) === Float64
            @test axes(ob, 1) === axes(ob2, 1) === IdOffsetRange(Base.OneTo(2), 0)
            @test axes(ob, 2) === axes(ob2, 2) === IdOffsetRange(Base.OneTo(2), 3)
            @test axes(ob, 3) === axes(ob2, 3) === IdOffsetRange(Base.OneTo(2), 2)
        end

        indices = (-1:1, -7:7, -1:2, -5:5, -1:1, -3:3, -2:2, -1:1)
        y = OffsetArray{Float64}(undef, indices...);
        @test axes(y) === axes(OffsetArray{Float64}(undef, indices))
        @test axes(y) === axes(OffsetArray{Float64, length(indices)}(undef, indices...))
        @test no_offset_axes(y) == (-1:1, -7:7, -1:2, -5:5, -1:1, -3:3, -2:2, -1:1)
        @test eltype(y) === Float64

        @test_throws ArgumentError OffsetArray{Float64, 2}(undef, indices)
        @test_throws ArgumentError OffsetArray(y, indices[1:2])

        @test ndims(OffsetArray(zeros(), ())) == 0
        @test Base.axes1(OffsetArray(zeros(), ())) === OffsetArrays.IdOffsetRange(Base.OneTo(1))

        @testset "convenience constructors" begin
            ax = (2:3, 4:5)

            for f in (zeros, ones)
                a = f(Float64, ax)
                @test no_offset_axes(a) == ax
                @test eltype(a) == Float64
            end

            for f in (trues, falses)
                a = f(ax)
                @test no_offset_axes(a) == ax
                @test eltype(a) == Bool
            end
        end

        # eltype of an OffsetArray should match that of the parent (issue #162)
        @test_throws TypeError OffsetArray{Float64,2,Matrix{ComplexF64}}
        # ndim of an OffsetArray should match that of the parent
        @test_throws TypeError OffsetArray{Float64,3,Matrix{Float64}}

        # should throw a TypeError if the offsets can not be converted to Ints
        @test_throws TypeError OffsetVector{Int,Vector{Int}}(zeros(Int,2), (WeirdInteger(1),))
    end

    @testset "custom range types" begin
        @testset "EllipsisNotation" begin
            @testset "Vector" begin
                v = rand(5)
                @test axes(OffsetArray(v, ..)) == axes(v)
                @test OffsetArray(v, ..) == OffsetArray(v, :)
                @test axes(OffsetVector(v, ..)) == axes(v)
                @test OffsetVector(v, ..) == OffsetVector(v, :)

                @test no_offset_axes(OffsetArray(v, .., 2:6)) == (2:6, )
                @test OffsetArray(v, .., 2:6) == OffsetArray(v, 2:6)
                @test no_offset_axes(OffsetVector(v, .., 2:6)) == (2:6, )
                @test OffsetVector(v, .., 2:6) == OffsetVector(v, 2:6)
            end
            @testset "Matrix" begin
                m = rand(2, 2)
                @test axes(OffsetArray(m, ..)) == axes(m)
                @test OffsetArray(m, ..) == OffsetArray(m, :, :)
                @test axes(OffsetMatrix(m, ..)) == axes(m)
                @test OffsetMatrix(m, ..) == OffsetMatrix(m, :, :)

                @test no_offset_axes(OffsetArray(m, .., 2:3)) == (axes(m, 1), 2:3)
                @test OffsetArray(m, .., 2:3) == OffsetArray(m, :, 2:3)
                @test no_offset_axes(OffsetMatrix(m, .., 2:3)) == (axes(m, 1), 2:3)
                @test OffsetMatrix(m, .., 2:3) == OffsetMatrix(m, :, 2:3)

                @test no_offset_axes(OffsetArray(m, .., 2:3, 3:4)) == (2:3, 3:4)
                @test OffsetArray(m, .., 2:3, 3:4) == OffsetArray(m, 2:3, 3:4)
                @test no_offset_axes(OffsetMatrix(m, .., 2:3, 3:4)) == (2:3, 3:4)
                @test OffsetMatrix(m, .., 2:3, 3:4) == OffsetMatrix(m, 2:3, 3:4)
            end
            @testset "3D Array" begin
                a = rand(2, 2, 2)
                @test axes(OffsetArray(a, ..)) == axes(a)
                @test OffsetArray(a, ..) == OffsetArray(a, :, :, :)

                @test no_offset_axes(OffsetArray(a, .., 2:3)) == (axes(a)[1:2]..., 2:3)
                @test OffsetArray(a, .., 2:3) == OffsetArray(a, :, :, 2:3)

                @test no_offset_axes(OffsetArray(a, .., 2:3, 3:4)) == (axes(a, 1), 2:3, 3:4)
                @test OffsetArray(a, .., 2:3, 3:4) == OffsetArray(a, :, 2:3, 3:4)

                @test no_offset_axes(OffsetArray(a, 2:3, .., 3:4)) == (2:3, axes(a, 2), 3:4)
                @test OffsetArray(a, 2:3, .., 3:4) == OffsetArray(a, 2:3, :, 3:4)

                @test no_offset_axes(OffsetArray(a, .., 4:5, 2:3, 3:4)) == (4:5, 2:3, 3:4)
                @test OffsetArray(a, .., 4:5, 2:3, 3:4) == OffsetArray(a, 4:5, 2:3, 3:4)
            end
        end
        @testset "ZeroBasedIndexing" begin
            Base.to_indices(A, inds, ::Tuple{ZeroBasedIndexing}) = map(x -> 0:length(x) - 1, inds)

            a = zeros(3,3)
            oa = OffsetArray(a, ZeroBasedIndexing())
            @test no_offset_axes(oa) == (0:2, 0:2)
        end
        @testset "TupleOfRanges" begin
            Base.to_indices(A, inds, t::Tuple{TupleOfRanges{N}}) where {N} = t
            OffsetArrays.AxisConversionStyle(::Type{TupleOfRanges{N}}) where {N} =
                OffsetArrays.TupleOfRanges()

            Base.convert(::Type{Tuple{Vararg{AbstractUnitRange{Int}}}}, t::TupleOfRanges) = t.x

            a = zeros(3,3)
            inds = TupleOfRanges((3:5, 2:4))
            oa = OffsetArray(a, inds)
            @test no_offset_axes(oa) == inds.x
        end
        @testset "NewColon" begin
            Base.to_indices(A, inds, t::Tuple{NewColon,Vararg{Any}}) =
                (_uncolon(inds, t), to_indices(A, Base.tail(inds), Base.tail(t))...)

            _uncolon(inds::Tuple{}, I::Tuple{NewColon, Vararg{Any}}) = OneTo(1)
            _uncolon(inds::Tuple, I::Tuple{NewColon, Vararg{Any}}) = inds[1]

            a = zeros(3, 3)
            oa = OffsetArray(a, (NewColon(), 2:4))
            @test no_offset_axes(oa) == (axes(a,1), 2:4)
        end
    end

    @testset "Offset range construction" begin
        r = -2:5
        for AT in Any[OffsetArray, OffsetVector]
            y = AT(r, r)
            @test no_offset_axes(y) == (r,)
            @test step(y) == step(r)
            y = AT(r, (r,))
            @test no_offset_axes(y) == (r,)
            y = AT(r, CartesianIndices((r, )))
            @test no_offset_axes(y) == (r, )
        end
    end

    @testset "size/length" begin
        for p in Any[SA[1,2,3,4], 1:4, [1:4;]]
            for A in Any[OffsetArray(p, 4),
                    OffsetArray(reshape(p, 2, 2), 3, 4),
                    OffsetArray(reshape(p, 2, 1, 2), 3, 0, 4),
                    OffsetArray(reshape(p, Val(1)), 2)]
                @test size(A) == size(parent(A))
                @test length(A) == length(parent(A))
            end
        end
    end
end

@testset "Axes supplied to constructor correspond to final result" begin
    # Ref https://github.com/JuliaArrays/OffsetArrays.jl/pull/65#issuecomment-457181268
    B = BidirectionalVector([1, 2, 3], -2)
    A = OffsetArray(B, -1:1)
    @test no_offset_axes(A) == (-1:1,)
end

@testset "unwrap" begin
    for A in [ones(2, 2), ones(2:3, 2:3), ZeroBasedRange(1:4)]
        p, f = OffsetArrays.unwrap(A)
        @test f(map(y -> y^2, p)) == A.^2
    end
end

@testset "Traits" begin
    A0 = [1 3; 2 4]
    A = OffsetArray(A0, (-1,2))                   # IndexLinear
    S = OffsetArray(view(A0, 1:2, 1:2), (-1,2))   # IndexCartesian
    @test axes(A) === axes(S)
    @test no_offset_axes(A) == no_offset_axes(S) == (0:1, 3:4)
    @test axes(A, 1) === OffsetArrays.IdOffsetRange(Base.OneTo(2), -1)
    @test size(A) == size(A0)
    @test size(A, 1) == size(A0, 1)
    @test length(A) == length(A0)
    @test A == OffsetArray(A0, 0:1, 3:4)
    @test_throws DimensionMismatch OffsetArray(A0, 0:2, 3:4)
    @test_throws DimensionMismatch OffsetArray(A0, 0:1, 2:4)
    @test eachindex(IndexLinear(), A) == eachindex(IndexLinear(), parent(A))
    @test eachindex(IndexCartesian(), A) == CartesianIndices(A) == CartesianIndices(axes(A))
    @test eachindex(S) == eachindex(IndexCartesian(), S) == CartesianIndices(S)
    @test eachindex(IndexLinear(), S) == eachindex(IndexLinear(), A0)
    A = ones(5:6)
    @test eachindex(IndexLinear(), A) === axes(A, 1)

    A = OffsetArray(big(1):big(2), 1)
    B = OffsetArray(1:2, 1)
    @test CartesianIndices(A) == CartesianIndices(B)
    @test LinearIndices(A) == LinearIndices(B)
    @test eachindex(A) == eachindex(B)
end

@testset "Scalar indexing" begin
    A0 = [1 3; 2 4]
    A = OffsetArray(A0, (-1,2))
    S = OffsetArray(view(A0, 1:2, 1:2), (-1,2))

    @test @inferred(A[0,3]) == @inferred(A[0,3,1]) == @inferred(A[1]) == @inferred(S[0,3]) == @inferred(S[0,3,1]) == @inferred(S[1]) == 1
    @test A[1,3] == A[1,3,1] == A[2] == S[1,3] == S[1,3,1] == S[2] == 2
    @test A[0,4] == A[0,4,1] == A[3] == S[0,4] == S[0,4,1] == S[3] == 3
    @test A[1,4] == A[1,4,1] == A[4] == S[1,4] == S[1,4,1] == S[4] == 4
    @test @inbounds(A[0,3]) == @inbounds(A[0,3,1]) == @inbounds(A[1]) == @inbounds(S[0,3]) == @inbounds(S[0,3,1]) == @inbounds(S[1]) == 1
    @test @inbounds(A[1,3]) == @inbounds(A[1,3,1]) == @inbounds(A[2]) == @inbounds(S[1,3]) == @inbounds(S[1,3,1]) == @inbounds(S[2]) == 2
    @test @inbounds(A[0,4]) == @inbounds(A[0,4,1]) == @inbounds(A[3]) == @inbounds(S[0,4]) == @inbounds(S[0,4,1]) == @inbounds(S[3]) == 3
    @test @inbounds(A[1,4]) == @inbounds(A[1,4,1]) == @inbounds(A[4]) == @inbounds(S[1,4]) == @inbounds(S[1,4,1]) == @inbounds(S[4]) == 4
    @test_throws BoundsError(A, (1,1)) A[1,1]
    @test_throws BoundsError(A, (1,1)) A[1,1] = 4
    @test_throws BoundsError(S, (1,1)) S[1,1]
    @test_throws BoundsError(S, (1,1)) S[1,1] = 4
    @test_throws BoundsError(A, (0,3,2)) A[0,3,2]
    @test_throws BoundsError(A, (0,3,2)) A[0,3,2] = 4
    @test_throws BoundsError(A, (0,3,0)) A[0,3,0]
    @test_throws BoundsError(A, (0,3,0)) A[0,3,0] = 4
    Ac = copy(A)
    Ac[0,3] = 10
    @test Ac[0,3] == 10
    Ac[0,3,1] = 11
    @test Ac[0,3] == 11
    @inbounds Ac[0,3,1] = 12
    @test Ac[0,3] == 12

    y = OffsetArray{Float64}(undef, -1:1, -7:7, -3:-1, -5:5, -1:1, -3:3, -2:2, -1:1)
    y[-1,-7,-3,-5,-1,-3,-2,-1] = 14
    y[-1,-7,-3,-5,-1,-3,-2,-1] += 5
    @test y[-1,-7,-3,-5,-1,-3,-2,-1] == 19

    @testset "setindex!" begin
        A = OffsetArray(ones(2,2), 1:2, 1:2)
        @test setindex!(A, 2, 1, 1) === A
        @test A[1,1] == 2
        @test setindex!(A, 2, 1) === A
        @test A[1] == 2

        v = OffsetArray(ones(3), 4:6)
        @test setindex!(A, 2, 4) === A
        @test A[4] == 2
    end

    @testset "Zero-index indexing (#194)" begin
        @test OffsetArray([6], 2:2)[] == 6
        @test OffsetArray(fill(6, 1, 1), 2:2, 3:3)[] == 6
        @test OffsetArray(fill(6))[] == 6
        @test_throws BoundsError OffsetArray([6,7], 2:3)[]
        @test_throws BoundsError OffsetArray([6 7], 2:2, 2:3)[]
        @test_throws BoundsError OffsetArray([], 2:1)[]
    end
end

_comp(x::Integer, y::Integer) = x == y
_comp(x::Any, y::Any) = isapprox(Real(x), Real(y), atol = 1e-14, rtol = 1e-8)
function test_indexing_axes_and_vals(r1, r2)
    r12 = r1[r2]
    if axes(r12, 1) != axes(r2, 1)
        @show r1 r2 r12 axes(r12, 1) axes(r2, 1)
    end
    @test axes(r12, 1) == axes(r2, 1)

    if axes(r12, 1) == axes(r2, 1)
        res1 = try
            _comp(first(r12), r1[first(r2)])
        catch
            @show r1 r2
            rethrow()
        end
        res2 = try
            _comp(last(r12), r1[last(r2)])
        catch
            @show r1 r2
            rethrow()
        end
        if !(res1 & res2)
            @show r1 r2
        end
        @test res1
        @test res2
        for i in eachindex(r2)
            @test _comp(r12[i], r1[r2[i]])
        end
    end
end

@testset "Vector indexing" begin
    A0 = [1 3; 2 4]
    A = OffsetArray(A0, (-1,2))
    S = OffsetArray(view(A0, 1:2, 1:2), (-1,2))

    @test A[:, 3] == S[:, 3] == OffsetArray([1,2], (A.offsets[1],))
    @test A[:, 4] == S[:, 4] == OffsetArray([3,4], (A.offsets[1],))
    @test_throws BoundsError A[:, 1]
    @test_throws BoundsError S[:, 1]
    @test A[0, :] == S[0, :] == OffsetArray([1,3], (A.offsets[2],))
    @test A[1, :] == S[1, :] == OffsetArray([2,4], (A.offsets[2],))
    @test_throws BoundsError A[2, :]
    @test_throws BoundsError S[2, :]
    @test A[0:1, 3] == S[0:1, 3] == [1,2]
    @test A[[1,0], 3] == S[[1,0], 3] == [2,1]
    @test A[0, 3:4] == S[0, 3:4] == [1,3]
    @test A[1, [4,3]] == S[1, [4,3]] == [4,2]
    @test A[:, :] == S[:, :] == A

    # Indexing a nD OffsetArray with n colons preserves the type
    r1 = OffsetArray(IdentityUnitRange(100:1000), 3)
    @test r1[:] === r1

    # In general with more colons than dimensions,
    # the type might not be preserved but the values and the leading axes should be
    r2 = r1[:,:]
    @test axes(r2, 1) == axes(r1, 1)
    @test same_value(r1, r2)

    s = @SVector[i for i in 1:3]
    so = OffsetArray(s, 3)
    @test so[:] === so

    a = Ones(3, 2, 1)
    ao = OffsetArray(a, axes(a))
    @test ao[:,:,:] === ao
    @test same_value(ao[:], ao)
    @test same_value(ao[:,:], ao)

    # Indexing an nD OffsetArray with one Colon preserves only the values.
    # This uses linear indexing
    a = ones(2:3, 2:3)
    b = a[:]
    @test same_value(a, b)

    vals = (1,2,3,4,5,6,7,8)
    s = SArray{Tuple{2,2,2},Int,3,8}(vals)
    so = OffsetArray(s, axes(s));
    so2 = so[:]
    @test same_value(so2, s)

    # Test r1[inds] for various combinations of types

    # AbstractArrays with 1-based indices
    indslist1 = Any[
            OffsetArray(5:8, 0),
            # This currently errors for IdentityUnitRange
            # see https://github.com/JuliaLang/julia/issues/39997
            # OffsetArray(big(5):big(80), 0),
            OffsetArray(5:2:9, 0),
            OffsetArray(9:-2:5, 0),
            OffsetArray(IdentityUnitRange(5:8), -4),
            OffsetArray(IdOffsetRange(5:8), 0),
            ]

    # AbstractRanges with 1-based indices
    indslist2 = Any[
            5:8,
            # This currently errors for IdentityUnitRange
            # see https://github.com/JuliaLang/julia/issues/39997
            # big(5):big(80),
            5:2:9,
            9:-2:5,
            IdOffsetRange(5:8),
            IdOffsetRange(ZeroBasedUnitRange(4:7), 1),
            ]

    for r1 in Any[
        # AbstractArrays
        collect(1:100),
        reshape(collect(-1:100), -1:100),
        collect(reshape(1:400, 20, 20)),
        reshape(collect(1:21^2), -10:10, -10:10),

        # OffsetRanges
        OffsetArray(10:1000, 0), # 1-based index
        OffsetArray(UnitRange(10.0, 1000.0), 0), # 1-based index
        OffsetArray(10:3:1000, 3), # offset index
        OffsetArray(10.0:3:1000.0, 0), # 1-based index
        OffsetArray(10.0:3:1000.0, 3), # offset index
        OffsetArray(IdOffsetRange(10:1000, 1), -1), # 1-based index
        OffsetArray(IdOffsetRange(10:1000, 1), 3), # offset index
        OffsetArray(IdOffsetRange(IdOffsetRange(10:1000, -4), 1), 3), # 1-based index
        OffsetArray(IdOffsetRange(IdOffsetRange(10:1000, -1), 1), 3), # offset index

        # AbstractRanges
        Base.OneTo(1000),
        CustomRange(Base.OneTo(1000)),
        Slice(Base.OneTo(1000)),
        1:1000,
        UnitRange(1.0, 1000.0),
        1:3:1000,
        1000:-3:1,
        1.0:3.0:1000.0,
        StepRangeLen(Float64(1), Float64(1000), 1000),
        LinRange(1, 1000, 1000),
        Base.Slice(Base.OneTo(1000)), # 1-based index
        IdentityUnitRange(Base.OneTo(1000)), # 1-based index
        IdOffsetRange(Base.OneTo(1000)), # 1-based index
        IdentityUnitRange(2:1000), # offset index
        IdOffsetRange(ZeroBasedUnitRange(1:1000), 1), # 1-based index
        IdOffsetRange(ZeroBasedUnitRange(1:1000), 2), # offset index
        ZeroBasedUnitRange(1:1000), # offset range
        ZeroBasedRange(1:1000), # offset range
        ZeroBasedRange(1:1:1000), # offset range
        CustomRange(ZeroBasedRange(1:1:1000)), # offset range
        ]

        # AbstractArrays with 1-based indices
        for r2 in indslist1
            test_indexing_axes_and_vals(r1, r2)
            test_indexing_axes_and_vals(r1, collect(r2))
        end

        # AbstractRanges with 1-based indices
        for r2 in indslist2

            test_indexing_axes_and_vals(r1, r2)
            test_indexing_axes_and_vals(r1, collect(r2))

            if r1 isa AbstractRange && !(r1 isa CustomRange) && axes(r2, 1) isa Base.OneTo
                @test r1[r2] isa AbstractRange
            end
        end
    end

    # Indexing with IdentityUnitRange(::Base.OneTo) or Base.Slice(::OneTo) is special.
    # This is because axes(::IdentityUnitRange{<:Base.OneTo}, 1) isa Base.OneTo, and not an IdentityUnitRange.
    # These therefore may pass through no_offset_view unchanged.
    # This had led to a stack-overflow in indexing, as getindex was using no_offset_view.
    # Issue 209
    for r1 in Any[
        # This set of tests is for ranges r1 that have 1-based indices
        UnitRange(1.0, 99.0),
        1:99,
        Base.OneTo(99),
        1:1:99,
        99:-1:1,
        1.0:1.0:99.0,
        StepRangeLen(Float64(1), Float64(99), 99),
        LinRange(1, 99, 99),
        Base.Slice(Base.OneTo(99)),
        IdentityUnitRange(Base.OneTo(99)),
        IdOffsetRange(Base.OneTo(99)),
        ]

        for r2 in Any[
            IdentityUnitRange(Base.OneTo(3)),
            Base.Slice(Base.OneTo(3)),
            IdOffsetRange(Base.OneTo(3)),
            ]

            test_indexing_axes_and_vals(r1, r2)
            test_indexing_axes_and_vals(r1, collect(r2))
            if axes(r2, 1) isa Base.OneTo
                @test r1[r2] isa AbstractRange
            end
        end
    end
end

@testset "Vector indexing with offset ranges" begin
    r = OffsetArray(8:10, -1:1)
    r1 = r[0:1]
    @test r1 === 9:10
    r1 = (8:10)[OffsetArray(1:2, -5:-4)]
    @test no_offset_axes(r1) == (-5:-4,)
    @test no_offset_view(r1) == 8:9
    r1 = OffsetArray(8:10, -1:1)[OffsetArray(0:1, -5:-4)]
    @test no_offset_axes(r1) == (-5:-4,)
    @test no_offset_view(r1) == 9:10

    a = OffsetVector(3:4, 10:11)
    ax = OffsetArrays.IdOffsetRange(5:6, 5)
    @test axes(a[ax]) == axes(ax)
    for i in axes(ax,1)
        @test a[ax[i]] == a[ax][i]
    end

    ax = IdentityUnitRange(10:11)
    @test axes(a[ax]) == axes(ax)
    for i in axes(ax,1)
        @test a[ax[i]] == a[ax][i]
    end

    # AbstractArrays with offset axes
    indslist1 = Any[OffsetArray(5:9, 40),
            OffsetArray(5:2:9, 40),
            OffsetArray(9:-2:5, 40),
            OffsetArray(IdentityUnitRange(5:8), 2),
            OffsetArray(IdOffsetRange(5:8, 1), 3),
            OffsetArray(IdOffsetRange(IdOffsetRange(5:8, 4), 1), 3),
            OffsetArray(IdOffsetRange(IdentityUnitRange(5:8), 1), 3),
            OffsetArray(IdentityUnitRange(IdOffsetRange(5:8, 1)), 3),
            ]

    # AbstractRanges with offset axes
    indslist2 = Any[IdOffsetRange(5:8, 1),
            IdentityUnitRange(5:8),
            IdOffsetRange(Base.OneTo(3), 4),
            IdOffsetRange(IdOffsetRange(5:8, 2), 1),
            IdOffsetRange(IdOffsetRange(IdOffsetRange(5:8, -1), 2), 1),
            IdentityUnitRange(IdOffsetRange(1:4, 5)),
            IdOffsetRange(IdentityUnitRange(15:20), -2),
            ZeroBasedUnitRange(5:8),
            ZeroBasedRange(5:8),
            ZeroBasedRange(5:2:9),
            ZeroBasedRange(9:-2:5),
            ]

    for r1 in Any[
        # AbstractArrays
        collect(1:100),
        reshape(collect(-1:100), -1:100),
        collect(reshape(1:400, 20, 20)),
        reshape(collect(1:21^2), -10:10, -10:10),

        # OffsetRanges
        OffsetArray(10:1000, 0), # 1-based index
        OffsetArray(UnitRange(10.0, 1000.0), 0), # 1-based index
        OffsetArray(10:1000, 3), # offset index
        OffsetArray(10:3:1000, 0), # 1-based index
        OffsetArray(10:3:1000, 3), # offset index
        OffsetArray(10.0:3:1000.0, 0), # 1-based index
        OffsetArray(10.0:3:1000.0, 3), # offset index
        OffsetArray(IdOffsetRange(10:1000, -3), 3), # 1-based index
        OffsetArray(IdOffsetRange(10:1000, 1), 3), # offset index
        OffsetArray(IdOffsetRange(IdOffsetRange(10:1000, -4), 1), 3), # 1-based index
        OffsetArray(IdOffsetRange(IdOffsetRange(10:1000, -1), 1), 3), # offset index

        # AbstractRanges
        Base.OneTo(1000),
        Slice(Base.OneTo(1000)),
        CustomRange(Base.OneTo(1000)),
        1:1000,
        UnitRange(1.0, 1000.0),
        1:2:2000,
        2000:-1:1,
        1.0:2.0:2000.0,
        StepRangeLen(Float64(1), Float64(1000), 1000),
        LinRange(1.0, 2000.0, 2000),
        Base.Slice(Base.OneTo(1000)), # 1-based index
        IdOffsetRange(Base.OneTo(1000)), # 1-based index
        IdOffsetRange(1:1000, 0), # 1-based index
        IdOffsetRange(Base.OneTo(1000), 4), # offset index
        IdOffsetRange(1:1000, 4), # offset index
        IdOffsetRange(ZeroBasedUnitRange(1:1000), 1), # 1-based index
        IdOffsetRange(ZeroBasedUnitRange(1:1000), 2), # offset index
        IdentityUnitRange(ZeroBasedUnitRange(1:1000)), # 1-based index
        IdentityUnitRange(5:1000), # offset index
        ZeroBasedUnitRange(1:1000), # offset index
        ZeroBasedRange(1:1000), # offset index
        ZeroBasedRange(1:1:1000), # offset index
        ZeroBasedUnitRange(IdentityUnitRange(1:1000)), # offset index
        CustomRange(ZeroBasedUnitRange(IdentityUnitRange(1:1000))), # offset index
        ]

        # AbstractArrays with offset axes
        for r2 in indslist1
            test_indexing_axes_and_vals(r1, r2)
            r2_dense = OffsetArray(collect(r2), axes(r2))
            test_indexing_axes_and_vals(r1, r2_dense)
        end

        # AbstractRanges with offset axes
        for r2 in indslist2

            test_indexing_axes_and_vals(r1, r2)
            r2_dense = OffsetArray(collect(r2), axes(r2))
            test_indexing_axes_and_vals(r1, r2_dense)

            # This might not hold for all ranges, but holds for the known ones being tested here
            if r1 isa AbstractUnitRange{<:Integer} && r2 isa AbstractUnitRange{<:Integer}
                @test r1[r2] isa AbstractUnitRange{<:Integer}
            end
        end
    end
end

@testset "LinearIndexing" begin
    r = OffsetArray(ZeroBasedRange(3:4), 1);
    @test LinearIndices(r) == axes(r,1)
    r = OffsetArray(ZeroBasedRange(3:4), 2);
    @test LinearIndices(r) == axes(r,1)
end

@testset "CartesianIndexing" begin
    A0 = [1 3; 2 4]
    A = OffsetArray(A0, (-1,2))
    S = OffsetArray(view(A0, 1:2, 1:2), (-1,2))

    @test A[CartesianIndex((0,3))] == S[CartesianIndex((0,3))] == 1
    @test A[CartesianIndex((0,3)),1] == S[CartesianIndex((0,3)),1] == 1
    @test @inbounds(A[CartesianIndex((0,3))]) == @inbounds(S[CartesianIndex((0,3))]) == 1
    @test @inbounds(A[CartesianIndex((0,3)),1]) == @inbounds(S[CartesianIndex((0,3)),1]) == 1
    @test_throws BoundsError A[CartesianIndex(1,1)]
    @test_throws BoundsError A[CartesianIndex(1,1),0]
    @test_throws BoundsError A[CartesianIndex(1,1),2]
    @test_throws BoundsError S[CartesianIndex(1,1)]
    @test_throws BoundsError S[CartesianIndex(1,1),0]
    @test_throws BoundsError S[CartesianIndex(1,1),2]
    @test eachindex(A) == 1:4
    @test eachindex(S) == CartesianIndices(IdentityUnitRange.((0:1,3:4)))
end

@testset "IdentityUnitRange indexing" begin
    # 155
    a = OffsetVector(3:4, 2:3)
    ax = IdentityUnitRange(2:3)
    @test a[ax[2]] == a[ax][2]

    s = -2:2:4
    r = 5:8
    y = OffsetArray(s, r)
    @test no_offset_axes(y) == (r,)
    @test step(y) == step(s)

    a = OffsetVector(3:4, 10:11)
    ax = OffsetArrays.IdOffsetRange(5:6, 5)
    @test axes(a[ax]) == axes(ax)
    for i in axes(ax,1)
        @test a[ax[i]] == a[ax][i]
    end

    ax = IdentityUnitRange(10:11)
    @test axes(a[ax]) == axes(ax)
    for i in axes(ax,1)
        @test a[ax[i]] == a[ax][i]
    end
end

@testset "view" begin
    A0 = [1 3; 2 4]
    A = OffsetArray(A0, (-1,2))

    S = view(A, :, 3)
    @test S == OffsetArray([1,2], (A.offsets[1],))
    @test S[0] == 1
    @test S[1] == 2
    @test_throws BoundsError S[2]
    @test axes(S) == (IdentityUnitRange(0:1),)
    S = view(A, 0, :)
    @test S == OffsetArray([1,3], (A.offsets[2],))
    @test S[3] == 1
    @test S[4] == 3
    @test_throws BoundsError S[1]
    @test axes(S) == (IdentityUnitRange(3:4),)
    S = view(A, 0:0, 4)
    @test S == [3]
    @test S[1] == 3
    @test_throws BoundsError S[0]
    @test axes(S) === (Base.OneTo(1),)
    S = view(A, 1, 3:4)
    @test S == [2,4]
    @test S[1] == 2
    @test S[2] == 4
    @test_throws BoundsError S[3]
    @test axes(S) === (Base.OneTo(2),)
    S = view(A, :, :)
    @test S == A
    @test S[0,3] == S[1] == 1
    @test S[1,3] == S[2] == 2
    @test S[0,4] == S[3] == 3
    @test S[1,4] == S[4] == 4
    @test_throws BoundsError S[1,1]
    @test axes(S) == IdentityUnitRange.((0:1, 3:4))
    S = view(A, axes(A)...)
    @test S == A
    @test S[0,3] == S[1] == 1
    @test S[1,3] == S[2] == 2
    @test S[0,4] == S[3] == 3
    @test S[1,4] == S[4] == 4
    @test_throws BoundsError S[1,1]
    @test no_offset_axes(S) == (0:1, 3:4)
    # issue 100
    S = view(A, axes(A, 1), 3)
    @test S == A[:, 3]
    @test S[0] == 1
    @test S[1] == 2
    @test_throws BoundsError S[length(S)]
    @test no_offset_axes(S) == (0:1, )
    # issue 100
    S = view(A, 1, axes(A, 2))
    @test S == A[1, :]
    @test S[3] == 2
    @test S[4] == 4
    @test_throws BoundsError S[1]
    @test no_offset_axes(S) == (3:4, )

    # issue 133
    r = OffsetArrays.IdOffsetRange(1:2, -1)
    v1 = view(A, r, 3)
    @test v1[0] == 1
    @test v1[1] == 2
    @test axes(v1, 1) == axes(r, 1)
    v2 = view(A, UnitRange(r), 3)
    for (indflat, indoffset) in enumerate(r)
        @test v1[indoffset] == v2[indflat]
    end

    # issue 133
    r = OffsetArrays.IdOffsetRange(1:2, 2)
    v1 = view(A, 1, r)
    @test v1[3] == 2
    @test v1[4] == 4
    @test axes(v1, 1) == axes(r, 1)
    v2 = view(A, 1, UnitRange(r))
    for (indflat, indoffset) in enumerate(r)
        @test v1[indoffset] == v2[indflat]
    end

    # issue 133
    a12 = zeros(3:8, 3:4)
    r = OffsetArrays.IdOffsetRange(Base.OneTo(3), 5)
    a12[r, 4] .= 3
    @test all(a12[r, 4] .== 3)
    @test all(a12[UnitRange(r), 4] .== 3)

    A0 = collect(reshape(1:24, 2, 3, 4))
    A = OffsetArray(A0, (-1,2,1))
    S = view(A, axes(A, 1), 3:4, axes(A, 3))
    @test S == A[:, 3:4, :]
    @test S[0, 1, 2] == A[0, 3, 2]
    @test S[0, 2, 2] == A[0, 4, 2]
    @test S[1, 1, 2] == A[1, 3, 2]
    @test no_offset_axes(S) == (0:1, Base.OneTo(2), 2:5)

    # issue #186
    a = reshape(1:12, 3, 4)
    r = OffsetArrays.IdOffsetRange(3:4)
    av = view(a, :, r)
    @test av == a[:, 3:4]
    @test axes(av) == (axes(a,1), axes(r,1))
    r = OffsetArrays.IdOffsetRange(1:2,2)
    av = view(a, :, r)
    @test no_offset_view(av) == a[:, 3:4]
    @test axes(av) == (axes(a,1), axes(r,1))
    r = OffsetArrays.IdOffsetRange(2:3)
    av1d = view(a, r, 3)
    @test av1d == a[2:3, 3]
    @test axes(av1d) == (axes(r,1),)
    r = OffsetArrays.IdOffsetRange(Base.OneTo(2), 1)
    av1d = view(a, r, 3)
    @test no_offset_view(av1d) == a[2:3, 3]
    @test axes(av1d) == (axes(r,1),)

    # fix IdOffsetRange(::IdOffsetRange, offset) nesting from #178
    b = 1:20
    bov = OffsetArray(view(b, 3:4), 3:4)
    c = @view b[bov]
    @test same_value(c, 3:4)
    @test no_offset_axes(c,1) == 3:4
    d = OffsetArray(c, 1:2)
    @test same_value(d, c)
    @test axes(d,1) == 1:2

    # Issue 128
    a = OffsetArray(1:3, 0:2);
    b = @view a[0]
    @test b[] == b[1] == 1

    a = reshape(1:16, 4, 4);
    for ax1 in (:, axes(a,1), UnitRange(axes(a,1))),
        ax2 in (:, axes(a,2), UnitRange(axes(a,2)))
            av = @view a[ax1, ax2]
            av_nooffset = OffsetArrays.no_offset_view(av)
            @test axes(av_nooffset) === axes(av)
    end
end

@testset "iteration" begin
    A0 = [1 3; 2 4]
    A = OffsetArray(A0, (-1,2))

    let a
        for (a,d) in zip(A, A0)
            @test a == d
        end
    end

    v = ones(10)
    for r in Any[1:1:10, 1:10], s in Any[r, collect(r)]
        so = OffsetArray(s)
        @test Float64[v[i] for i in s] == Float64[v[i] for i in so]
    end
end

@testset "show/summary" begin
    A0 = [1 3; 2 4]
    A = OffsetArray(A0, (-1,2))
    S = OffsetArray(view(A0, 1:2, 1:2), (-1,2))

    @test sprint(show, A) == "[1 3; 2 4]"
    @test sprint(show, S) == "[1 3; 2 4]"
    strs = split(strip(sprint(show, MIME("text/plain"), A)), '\n')
    @test strs[2] == " 1  3"
    @test strs[3] == " 2  4"
    v = OffsetArray(rand(3), (-2,))
    @test sprint(show, v) == sprint(show, parent(v))
    io = IOBuffer()
    function cmp_showf(printfunc, io, A)
        ioc = IOContext(io, :limit=>true, :compact=>true)
        printfunc(ioc, A)
        str1 = String(take!(io))
        printfunc(ioc, parent(A))
        str2 = String(take!(io))
        @test str1 == str2
    end
    cmp_showf(Base.print_matrix, io, OffsetArray(rand(5,5), (10,-9)))       # rows&cols fit
    cmp_showf(Base.print_matrix, io, OffsetArray(rand(10^3,5), (10,-9)))    # columns fit
    cmp_showf(Base.print_matrix, io, OffsetArray(rand(5,10^3), (10,-9)))    # rows fit
    cmp_showf(Base.print_matrix, io, OffsetArray(rand(10^3,10^3), (10,-9))) # neither fits

    a = OffsetArray([1 2; 3 4], -1:0, 5:6)
    shownsz = VERSION >= v"1.2.0-DEV.229" ? Base.dims2string(size(a))*' ' : ""
    @test summary(a) == "$(shownsz)OffsetArray(::$(typeof(parent(a))), -1:0, 5:6) with eltype $(Int) with indices -1:0×5:6"
    shownsz = VERSION >= v"1.2.0-DEV.229" ? Base.dims2string(size(view(a, :, 5)))*' ' : ""
    @test summary(view(a, :, 5)) == "$(shownsz)view(OffsetArray(::$(typeof(parent(a))), -1:0, 5:6), :, 5) with eltype $(Int) with indices -1:0"
    a = OffsetArray(reshape([1]))
    @test summary(a) == "0-dimensional OffsetArray(::$(typeof(parent(a)))) with eltype $(Int)"

    a = OffsetArray([1 2; 3 4], -1:0, 5:6)
    io = IOBuffer()
    show(io, axes(a, 1))
    @test String(take!(io)) == "IdOffsetRange(values=-1:0, indices=-1:0)"   # not qualified because of the using OffsetArrays: IdOffsetRange at top
    show(io, axes(a, 2))
    @test String(take!(io)) == "IdOffsetRange(values=5:6, indices=5:6)"
    rrtable = IdOffsetRange(values=7:9, indices=-1:1)
    rrted = eval(Meta.parse(string(rrtable)))
    @test pairs(rrtable) == pairs(rrted)

    @test Base.inds2string(axes(a)) == Base.inds2string(map(UnitRange, axes(a)))
    @test Base.inds2string((IdOffsetRange(3:4),)) == "3:4"
    @test Base.inds2string((IdentityUnitRange(IdOffsetRange(3:4)),)) == "3:4"
    # check that the following doesn't throw
    @test Base.inds2string(()) isa Any

    show(io, OffsetArray(3:5, 0:2))
    @test String(take!(io)) == "3:5 with indices 0:2"

    show(io, MIME"text/plain"(), OffsetArray(3:5, 0:2))
    @test String(take!(io)) == "3:5 with indices 0:2"

    # issue #198
    for r in Any[axes(OffsetVector(1:10, -5), 1), 1:1:2, 1.0:1.0:2.0, 1:-1:-5]
        a = OffsetVector(r, 5)
        show(io, a)
        @test String(take!(io)) == "$r with indices $(UnitRange(axes(a,1)))"
    end

    d = Diagonal([1,2,3])
    Base.print_array(io, d)
    s1 = String(take!(io))
    od = OffsetArray(d, -1:1, 3:5)
    Base.print_array(io, od)
    s2 = String(take!(io))
    @test s1 == s2

    @test Base.replace_in_print_matrix(od, -1, 3, " ") == Base.replace_in_print_matrix(d, 1, 1, " ")
    @test Base.replace_in_print_matrix(od, -1, 4, " ") == Base.replace_in_print_matrix(d, 1, 2, " ")

    v = rand(3)
    ov = OffsetArray(v, (-2,))
    @test Base.replace_in_print_matrix(ov, -1, 1, " ") == Base.replace_in_print_matrix(v, 1, 1, " ")

    # Avoid returning the value of toplevel if it is false
    # showarg should only print values, it shouldn't return anything
    @test Base.showarg(io, a, false) === nothing
    # check the other case too for good measure
    @test Base.showarg(io, a, true) === nothing
end

@testset "readdlm/writedlm" begin
    A0 = [1 3; 2 4]
    A = OffsetArray(A0, (-1,2))

    io = IOBuffer()
    writedlm(io, A)
    seek(io, 0)
    @test readdlm(io, eltype(A)) == parent(A)
end

@testset "similar" begin
    A0 = [1 3; 2 4]
    A = OffsetArray(A0, (-1,2))

    B = similar(A, Float32)
    @test isa(B, OffsetArray{Float32,2})
    @test axes(B) === axes(A)
    B = similar(A, (3,4))
    @test isa(B, Array{Int,2})
    @test size(B) == (3,4)
    @test axes(B) === (Base.OneTo(3), Base.OneTo(4))
    B = similar(A, (-3:3,1:4))
    @test isa(B, OffsetArray{Int,2})
    @test axes(B) == IdentityUnitRange.((-3:3, 1:4))
    B = similar(parent(A), (-3:3,1:4))
    @test isa(B, OffsetArray{Int,2})
    @test axes(B) == IdentityUnitRange.((-3:3, 1:4))
    @test isa([x for x in [1,2,3]], Vector{Int})
    @test similar(Array{Int}, (0:0, 0:0)) isa OffsetArray{Int, 2}
    @test similar(Array{Int}, (1, 1)) isa Matrix{Int}
    @test similar(Array{Int}, (Base.OneTo(1), Base.OneTo(1))) isa Matrix{Int}
    B = similar(Array{Int}, (0:0, 3))
    @test isa(B, OffsetArray{Int, 2})
    @test no_offset_axes(B) == (0:0, 1:3)

    s = @SVector[i for i in 1:10]
    so = OffsetArray(s, 4);
    @test typeof(parent(similar(so))) == typeof(similar(s))
    @test typeof(parent(similar(so, eltype(so), axes(so)))) == typeof(similar(s))

    s = SArray{Tuple{2,2,2},Int,3,8}((1,2,3,4,5,6,7,8))
    so = OffsetArray(s, 0, 0, 0)
    A = similar(so)
    @test A isa OffsetArray
    @test parent(A) isa StaticArray
    for ax in Any[(axes(s,1), axes(so)[2:3]...), (axes(so,1), axes(s)[2:3]...)]
        A = similar(so, Int, ax)
        @test A isa OffsetArray
        @test parent(A) isa StaticArray
        @test axes(A) == ax
    end

    # check with an unseen axis type
    A = similar(ones(1), Int, ZeroBasedUnitRange(3:4), 4:5)
    @test eltype(A) === Int
    @test no_offset_axes(A) == (3:4, 4:5)
    A = similar(ones(1), Int, IdOffsetRange(ZeroBasedUnitRange(3:4), 2), 4:5)
    @test eltype(A) === Int
    @test no_offset_axes(A) == (5:6, 4:5)
    A = similar(ones(1), Int, IdOffsetRange(ZeroBasedUnitRange(3:4), 2), 4)
    @test eltype(A) === Int
    @test no_offset_axes(A) == (5:6, 1:4)

    # test for similar(::Type, ax)
    indsoffset = (IdOffsetRange(SOneTo(2), 2),)
    A = similar(Array{Int}, indsoffset)
    @test parent(A) isa StaticArray
    @test no_offset_axes(A) == (3:4,)
    A = similar(Array{Int}, (indsoffset..., SOneTo(3)))
    @test parent(A) isa StaticArray
    @test no_offset_axes(A) == (3:4, 1:3)

    s = SArray{Tuple{2,2},Int,2,4}((1,2,3,4));
    so = OffsetArray(s, 2, 2);
    so2 = so .+ 1;
    @test parent(so2) isa StaticArray

    @test_throws MethodError similar(A, (:,))
    @test_throws MethodError similar(A, (: ,:))
    @test_throws MethodError similar(A, (: ,2))
    @test_throws MethodError similar(A, Float64, (: ,:))
    @test_throws MethodError similar(A, Float64, (: ,2))

    function testsimilar(args...)
        try
           similar(args...)
        catch e
            @test e isa MethodError
            io = IOBuffer()
            showerror(io, e)
            s = split(String(take!(io)),'\n')[1]
            @test occursin(repr(similar), s)
        end
    end

    testsimilar(typeof(A), (:, :))
    testsimilar(typeof(A), (:, 2))
    testsimilar(typeof(A), (:, 1:3))

    @testset "similar with OffsetArray type (issue #263)" begin
        for i in Any[[1,2,3], 1:3, SVector{2,Int}(1,2), reshape(1:4, 2, 2)]
            k = OffsetArray(i, map(x -> -2, size(i)))
            j = similar(typeof(k), axes(k))
            @test axes(j) == axes(k)
            @test eltype(j) == eltype(k)
            j = similar(typeof(k), size(k))
            @test eltype(j) == eltype(k)
            @test size(j) == size(k)
            @test all(==(1), first.(axes(j)))
        end
    end
end

# custom FillArray with BigInt axes, used to test `reshape`
struct MyBigFill{T,N} <: AbstractArray{T,N}
    val :: T
    axes :: NTuple{N,Base.OneTo{BigInt}}
end
MyBigFill(val, sz::Tuple{}) = MyBigFill{typeof(val),0}(val, sz)
MyBigFill(val, sz::NTuple{N,BigInt}) where {N} = MyBigFill(val, map(Base.OneTo, sz))
MyBigFill(val, sz::Tuple{Vararg{Integer}}) = MyBigFill(val, map(BigInt, sz))
Base.size(M::MyBigFill) = map(length, M.axes)
Base.axes(M::MyBigFill) = M.axes
function Base.getindex(M::MyBigFill{<:Any,N}, ind::Vararg{Integer,N}) where {N}
    checkbounds(M, ind...)
    M.val
end
function Base.isassigned(M::MyBigFill{<:Any,N}, ind::Vararg{BigInt,N}) where {N}
    checkbounds(M, ind...)
    true
end
function Base.reshape(M::MyBigFill, ind::NTuple{N,BigInt}) where {N}
    length(M) == prod(ind) || throw(ArgumentError("length mismatch in reshape"))
    MyBigFill(M.val, ind)
end
Base.reshape(M::MyBigFill, ind::Tuple{}) = MyBigFill(M.val, ind)

@testset "reshape" begin
    A0 = [1 3; 2 4]
    A = OffsetArray(A0, (-1,2))

    B = reshape(A0, -10:-9, 9:10)
    @test isa(B, OffsetArray{Int,2})
    @test parent(B) == A0
    @test axes(B) == IdentityUnitRange.((-10:-9, 9:10))
    B = reshape(A, -10:-9, 9:10)
    @test isa(B, OffsetArray{Int,2})
    @test pointer(parent(B)) === pointer(A0)
    @test axes(B) == IdentityUnitRange.((-10:-9, 9:10))
    b = reshape(A, -7:-4)
    @test axes(b) == (IdentityUnitRange(-7:-4),)
    @test isa(parent(b), Vector{Int})
    @test pointer(parent(b)) === pointer(parent(A))
    @test parent(b) == A0[:]
    a = OffsetArray(rand(3,3,3), -1:1, 0:2, 3:5)
    # Offset axes are required for reshape(::OffsetArray, ::Val) support
    b = reshape(a, Val(2))
    @test isa(b, OffsetArray{Float64,2})
    @test pointer(parent(b)) === pointer(parent(a))
    @test axes(b) == IdentityUnitRange.((-1:1, 1:9))
    b = reshape(a, Val(4))
    @test isa(b, OffsetArray{Float64,4})
    @test pointer(parent(b)) === pointer(parent(a))
    @test axes(b) == (axes(a)..., IdentityUnitRange(1:1))

    @test reshape(OffsetArray(-1:0, -1:0), :, 1) == reshape(-1:0, 2, 1)
    @test reshape(OffsetArray(-1:2, -1:2), -2:-1, :) == reshape(-1:2, -2:-1, 2)

    @test reshape(OffsetArray(-1:0, -1:0), :) == OffsetArray(-1:0, -1:0)
    @test reshape(A, :) == reshape(A0, :)

    # reshape with one Colon for AbstractArrays
    B = reshape(A0, -10:-9, :)
    @test B isa OffsetArray{Int,2}
    @test parent(B) == A0
    @test no_offset_axes(B, 1) == -10:-9
    @test axes(B, 2) == axes(A0, 2)

    B = reshape(A0, -10:-9, 3:3, :)
    @test B isa OffsetArray{Int,3}
    @test same_value(A0, B)
    @test no_offset_axes(B, 1) == -10:-9
    @test no_offset_axes(B, 2) == 3:3
    @test axes(B, 3) == 1:2

    B = reshape(A0, -10:-9, 3:4, :)
    @test B isa OffsetArray{Int,3}
    @test same_value(A0, B)
    @test no_offset_axes(B, 1) == -10:-9
    @test no_offset_axes(B, 2) == 3:4
    @test axes(B, 3) == 1:1

    # pop the parent
    B = reshape(A, size(A))
    @test B == A0
    B = reshape(A, (Base.OneTo(2), 2))
    @test B == A0
    B = reshape(A, (2,:))
    @test B == A0

    # julialang/julia #33614
    A = OffsetArray(-1:0, (-2,))
    @test reshape(A, :) == A
    Arsc = reshape(A, :, 1)
    Arss = reshape(A, 2, 1)
    @test Arsc[1,1] == Arss[1,1] == -1
    @test Arsc[2,1] == Arss[2,1] == 0
    @test_throws BoundsError Arsc[0,1]
    @test_throws BoundsError Arss[0,1]
    A = OffsetArray([-1,0], (-2,))
    Arsc = reshape(A, :, 1)
    Arsc[1,1] = 5
    @test first(A) == 5

    @testset "issue #235" begin
        Vec64  = zeros(6)
        ind_a_64 = 3
        ind_a_32 =Int32.(ind_a_64)
        @test reshape(Vec64, ind_a_32, :) == reshape(Vec64, ind_a_64, :)
    end

    R = reshape(zeros(6), 2, :)
    @test R isa Matrix
    @test axes(R) == (1:2, 1:3)
    R = reshape(zeros(6,1), 2, :)
    @test R isa Matrix
    @test axes(R) == (1:2, 1:3)

    R = reshape(zeros(6), 1:2, :)
    @test axes(R) == (1:2, 1:3)
    R = reshape(zeros(6,1), 1:2, :)
    @test axes(R) == (1:2, 1:3)

    r = OffsetArray(ZeroBasedRange(3:4), 1);
    @test reshape(r, 2) == reshape(r, big(2)) == 3:4
    @test reshape(r, (2,)) == reshape(r, (big(2),)) == 3:4
    @test reshape(r, :) == 3:4
    @test reshape(r, (:,)) == 3:4
    @test reshape(r, big(2), 1) == reshape(3:4, 2, 1)

    # getindex for a reshaped array that wraps an offset array is broken on 1.0
    if VERSION >= v"1.1"
        @test reshape(r, (2,:,4:4)) == OffsetArray(reshape(3:4, 2, 1, 1), 1:2, 1:1, 4:4)
    end

    # reshape works even if the parent doesn't have 1-based indices
    # this works even if the parent doesn't support the reshape
    r = OffsetArray(IdentityUnitRange(0:1), -1)
    @test reshape(r, 2) == 0:1
    @test reshape(r, (2,)) == 0:1
    @test reshape(r, :) == OffsetArray(0:1, -1:0)
    @test reshape(r, (:,)) == OffsetArray(0:1, -1:0)

    @test reshape(ones(2:3, 4:5), (2, :)) == ones(2,2)

    # more than one colon is not allowed
    @test_throws Exception reshape(ones(3:4, 4:5, 1:2), :, :, 2)
    @test_throws Exception reshape(ones(3:4, 4:5, 1:2), :, 2, :)

    A = OffsetArray(rand(4, 4), -1, -1);
    B = reshape(A, (2, :))
    @test axes(B, 1) == 1:2
    @test axes(B, 2) == 1:8

    # some more exotic vector types
    r = OffsetVector(CustomRange(ZeroBasedRange(0:2)), -2)
    r2 = reshape(r, :)
    @test r2 == r
    r2 = reshape(r, 3)
    @test axes(r2, 1) == 1:3
    @test r2 == no_offset_view(r)
    @test_throws Exception reshape(r, length(r) + 1)
    @test_throws Exception reshape(r, 1:length(r) + 1)
    rp = parent(r)
    @test no_offset_axes(reshape(rp, 4:6), 1) == 4:6
    @test axes(reshape(r, (3,1))) == (1:3, 1:1)

    # reshape with one single colon becomes a `vec`
    A = OffsetArray(rand(4, 4), -1, -1)
    @test reshape(A, (:, )) == vec(A)
    @test reshape(A, :) == vec(A)

    # ensure that there's no ambiguity using AbstractArray and Tuple{Vararg{OffsetAxis}}
    @test reshape(Fill(0), ()) === Fill(0)
    # This test is broken currently on julia v"1.12.0-DEV.780"
    @test try
        reshape(Fill(2,6), big(2), :) == Fill(2, 2, 3)
    catch e
        e isa TypeError || rethrow()
    end
    @testset "Tuple{Vararg{Integer}}" begin
        M = MyBigFill(4, (2, 3))
        O = OffsetArray(M)
        @test vec(O) isa MyBigFill
        @test vec(O) == vec(M)

        M = MyBigFill(4, (1,1))
        O = OffsetArray(M)
        @test reshape(O) == reshape(M)
        @test reshape(O) isa MyBigFill
    end
end

@testset "permutedims" begin
    a = OffsetArray(1:2, 2:3)
    @test permutedims(a) == reshape(1:2, 1, 2:3)
    a = OffsetArray([10,11], Base.OneTo(2))
    @test permutedims(a) == reshape(10:11, 1, 1:2)
    a = OffsetArray(SVector{2}(1,2), 3:4)
    @test permutedims(a) == reshape(1:2, 1, 3:4)

    # check that the 2D case is unaffected
    a = OffsetArray(reshape(1:2, 1, 2), 2:2, 4:5)
    b = permutedims(a)
    @test a[2,:] == b[:,2]
end

@testset "Indexing with OffsetArray axes" begin
    A0 = [1 3; 2 4]

    i1 = OffsetArray([2,1], (-5,))
    i1 = OffsetArray([2,1], -5)
    b = A0[i1, 1]
    @test axes(b) == (IdentityUnitRange(-4:-3),)
    @test b[-4] == 2
    @test b[-3] == 1
    b = A0[1,i1]
    @test axes(b) == (IdentityUnitRange(-4:-3),)
    @test b[-4] == 3
    @test b[-3] == 1
    v = view(A0, i1, 1)
    @test axes(v) == (IdentityUnitRange(-4:-3),)
    v = view(A0, 1:1, i1)
    @test axes(v) == (Base.OneTo(1), IdentityUnitRange(-4:-3))

    for r in (1:10, 1:1:10, StepRangeLen(1, 1, 10), LinRange(1, 10, 10), 0.1:0.2:0.9)
        for s in (IdentityUnitRange(2:3), OffsetArray(2:3, 2:3))
            @test axes(r[s]) == axes(s)
        end
    end
end

@testset "logical indexing" begin
    A0 = [1 3; 2 4]
    A = OffsetArray(A0, (-1,2))

    @test A[A .> 2] == [3,4]
end

@testset "copyto!" begin
    a = OffsetArray{Int}(undef, (-3:-1,))
    fill!(a, -1)
    copyto!(a, (1,2))   # non-array iterables
    @test a[-3] == 1
    @test a[-2] == 2
    @test a[-1] == -1
    fill!(a, -1)
    copyto!(a, -2, (1,2))
    @test a[-3] == -1
    @test a[-2] == 1
    @test a[-1] == 2
    @test_throws BoundsError copyto!(a, 1, (1,2))
    fill!(a, -1)
    copyto!(a, -2, (1,2,3), 2)
    @test a[-3] == -1
    @test a[-2] == 2
    @test a[-1] == 3
    @test_throws BoundsError copyto!(a, -2, (1,2,3), 1)
    fill!(a, -1)
    copyto!(a, -2, (1,2,3), 1, 2)
    @test a[-3] == -1
    @test a[-2] == 1
    @test a[-1] == 2

    b = 1:2    # copy between AbstractArrays
    bo = OffsetArray(1:2, (-3,))
    if VERSION < v"1.5-"
        @test_throws BoundsError copyto!(a, b)
        fill!(a, -1)
        copyto!(a, bo)
        @test a[-3] == -1
        @test a[-2] == 1
        @test a[-1] == 2
    else
        # the behavior of copyto! is corrected as the documentation says "first n element"
        # https://github.com/JuliaLang/julia/pull/34049
        fill!(a, -1)
        copyto!(a, bo)
        @test a[-3] == 1
        @test a[-2] == 2
        @test a[-1] == -1
    end
    fill!(a, -1)
    copyto!(a, -2, bo)
    @test a[-3] == -1
    @test a[-2] == 1
    @test a[-1] == 2
    @test_throws BoundsError copyto!(a, -4, bo)
    @test_throws BoundsError copyto!(a, -1, bo)
    fill!(a, -1)
    copyto!(a, -3, b, 2)
    @test a[-3] == 2
    @test a[-2] == a[-1] == -1
    @test_throws BoundsError copyto!(a, -3, b, 1, 4)
    am = OffsetArray{Int}(undef, (1:1, 7:9))  # for testing linear indexing
    fill!(am, -1)
    copyto!(am, b)
    @test am[1] == 1
    @test am[2] == 2
    @test am[3] == -1
    @test am[1,7] == 1
    @test am[1,8] == 2
    @test am[1,9] == -1
end

@testset "map" begin
    am = OffsetArray{Int}(undef, (1:1, 7:9))  # for testing linear indexing
    fill!(am, -1)
    copyto!(am, 1:2)

    dest = similar(am)
    map!(+, dest, am, am)
    @test dest[1,7] == 2
    @test dest[1,8] == 4
    @test dest[1,9] == -2

    @testset "eltype conversion" begin
        a = OffsetArray(1:2, 1)
        b = map(BigInt, a)
        @test eltype(b) == BigInt
        @test b == a
        @test parent(b) isa AbstractRange

        for ri in Any[2:3, Base.OneTo(2)]
            for r in Any[IdentityUnitRange(ri), IdOffsetRange(ri), IdOffsetRange(ri, 1), OffsetArray(ri), OffsetArray(ri, 2)]
                for T in [Int8, Int16, Int32, Int64, Int128, BigInt, Float32, Float64, BigFloat]
                    r2 = map(T, r)
                    @test eltype(r2) == T
                    @test axes(r2) == axes(r)
                    @test all(((x,y),) -> isequal(x,y), zip(r, r2))
                end
            end
        end

        @testset "Bool" begin
            for ri in Any[0:0, 0:1, 1:0, 1:1, Base.OneTo(0), Base.OneTo(1)]
                for r = Any[IdentityUnitRange(ri), IdOffsetRange(ri), IdOffsetRange(ri .- 1, 1), OffsetVector(ri)]
                    r2 = map(Bool, r)
                    @test eltype(r2) == Bool
                    @test axes(r2) == axes(r)
                    @test all(((x,y),) -> isequal(x,y), zip(r, r2))
                end
            end
        end
    end
end

@testset "reductions" begin
    A = OffsetArray(rand(Int,4,4), (-3,5))
    @test maximum(A) == maximum(parent(A))
    @test minimum(A) == minimum(parent(A))
    @test extrema(A) == extrema(parent(A))
    @test sum(A) == sum(parent(A))
    @test sum(A, dims=1) == OffsetArray(sum(parent(A), dims=1), A.offsets)
    @test sum(A, dims=2) == OffsetArray(sum(parent(A), dims=2), A.offsets)
    @test sum(A, dims=(1,2)) == OffsetArray(sum(parent(A), dims=(1,2)), A.offsets)
    @test sum(view(OffsetArray(reshape(1:27, 3, 3, 3), 0, 0, 0), :, :, 1:2), dims=(2,3)) == reshape([51,57,63], 3, 1, 1)
    C = similar(A)
    cumsum!(C, A, dims = 1)
    @test parent(C) == cumsum(parent(A), dims = 1)
    @test parent(cumsum(A, dims = 1)) == cumsum(parent(A), dims = 1)
    cumsum!(C, A, dims = 2)
    @test parent(C) == cumsum(parent(A), dims = 2)
    R = similar(A, (1:1, 6:9))
    maximum!(R, A)
    @test parent(R) == maximum(parent(A), dims = 1)
    R = similar(A, (-2:1, 1:1))
    maximum!(R, A)
    @test parent(R) == maximum(parent(A), dims = 2)
    amin, iamin = findmin(A)
    pmin, ipmin = findmin(parent(A))
    @test amin == pmin
    @test A[iamin] == amin
    @test amin == parent(A)[ipmin]
    amax, iamax = findmax(A)
    pmax, ipmax = findmax(parent(A))
    @test amax == pmax
    @test A[iamax] == amax
    @test amax == parent(A)[ipmax]

    amin, amax = extrema(parent(A))
    @test clamp.(A, (amax+amin)/2, amax) == OffsetArray(clamp.(parent(A), (amax+amin)/2, amax), axes(A))

    @testset "mapreduce for OffsetRange" begin
        rangelist = Any[
            # AbstractUnitRanges
            5:100, UnitRange(5.0, 20.0), false:true,
            IdOffsetRange(4:5),
            IdentityUnitRange(4:5),
            IdOffsetRange(1:10, 4),
            # AbstractRanges
            2:4:14, 1.5:1.0:10.5,
            ]

        for r in rangelist

            a = OffsetVector(r, 2);
            @test mapreduce(identity, +, a) == mapreduce(identity, +, r)
            @test mapreduce(x -> x^2, (x,y) -> x, a) == mapreduce(x -> x^2, (x,y) -> x, r)

            b = mapreduce(identity, +, a, dims = 1)
            br = mapreduce(identity, +, r, dims = 1)
            @test no_offset_view(b) == no_offset_view(br)
            @test no_offset_axes(b, 1) == first(axes(a,1)):first(axes(a,1))

            @test mapreduce(identity, +, a, init = 3) == mapreduce(identity, +, r, init = 3)
            if VERSION >= v"1.2"
                @test mapreduce((x,y) -> x*y, +, a, a) == mapreduce((x,y) -> x*y, +, r, r)
                @test mapreduce((x,y) -> x*y, +, a, a, init = 10) == mapreduce((x,y) -> x*y, +, r, r, init = 10)
            end

            for f in [sum, minimum, maximum]
                @test f(a) == f(r)

                b = f(a, dims = 1);
                br = f(r, dims = 1)
                @test no_offset_view(b) == no_offset_view(br)
                @test no_offset_axes(b, 1) == first(axes(a,1)):first(axes(a,1))

                b = f(a, dims = 2);
                br = f(r, dims = 2)
                @test no_offset_view(b) == no_offset_view(br)
                @test axes(b, 1) == axes(a,1)
            end

            @test extrema(a) == extrema(r)
        end
    end
end

# v  = OffsetArray([1,1e100,1,-1e100], (-3,))*1000
# v2 = OffsetArray([1,-1e100,1,1e100], (5,))*1000
# @test isa(v, OffsetArray)
# cv  = OffsetArray([1,1e100,1e100,2], (-3,))*1000
# cv2 = OffsetArray([1,-1e100,-1e100,2], (5,))*1000
# @test isequal(cumsum_kbn(v), cv)
# @test isequal(cumsum_kbn(v2), cv2)
# @test isequal(sum_kbn(v), sum_kbn(parent(v)))

@testset "Collections" begin
    A = OffsetArray(rand(4,4), (-3,5))

    @test unique(A, dims=1) == OffsetArray(parent(A), 0, first(axes(A, 2)) - 1)
    @test unique(A, dims=2) == OffsetArray(parent(A), first(axes(A, 1)) - 1, 0)
    v = OffsetArray(rand(8), (-2,))
    @test sort(v) == OffsetArray(sort(parent(v)), v.offsets)
    @test sortslices(A; dims=1) == OffsetArray(sortslices(parent(A); dims=1), A.offsets)
    @test sortslices(A; dims=2) == OffsetArray(sortslices(parent(A); dims=2), A.offsets)
    @test sort(A, dims = 1) == OffsetArray(sort(parent(A), dims = 1), A.offsets)
    @test sort(A, dims = 2) == OffsetArray(sort(parent(A), dims = 2), A.offsets)

    @test mapslices(sort, A, dims = 1) == OffsetArray(mapslices(sort, parent(A), dims = 1), A.offsets)
    @test mapslices(sort, A, dims = 2) == OffsetArray(mapslices(sort, parent(A), dims = 2), A.offsets)
end

@testset "rot/reverse" begin
    A = OffsetArray(rand(4,4), (-3,5))

    @test rotl90(A) == OffsetArray(rotl90(parent(A)), A.offsets[[2,1]])
    @test rotr90(A) == OffsetArray(rotr90(parent(A)), A.offsets[[2,1]])
    @test reverse(A, dims = 1) == OffsetArray(reverse(parent(A), dims = 1), A.offsets)
    @test reverse(A, dims = 2) == OffsetArray(reverse(parent(A), dims = 2), A.offsets)
end

@testset "fill" begin
    B = fill(5, 1:3, -1:1)
    @test no_offset_axes(B) == (1:3,-1:1)
    @test all(B.==5)

    B = fill(5, (1:3, -1:1))
    @test no_offset_axes(B) == (1:3,-1:1)
    @test all(B.==5)

    B = fill(5, 3, -1:1)
    @test no_offset_axes(B) == (1:3,-1:1)
    @test all(B.==5)

    @testset "fill!" begin
        D = dzeros((2,2))
        DO = OffsetArray(D, 2, 2)
        fill!(DO, 1)
        @test all(isequal(1), DO)
        @test all(iszero, zero(DO))

        S = SVector{2,Int}(1,1)
        SO = OffsetVector(S, -1)
        @test zero(SO) isa typeof(SO)
    end
end

@testset "broadcasting" begin
    A = OffsetArray(rand(4,4), (-3,5))

    @test A.+1 == OffsetArray(parent(A).+1, A.offsets)
    @test 2*A == OffsetArray(2*parent(A), A.offsets)
    @test A+A == OffsetArray(parent(A)+parent(A), A.offsets)
    @test A.*A == OffsetArray(parent(A).*parent(A), A.offsets)
end

@testset "@inbounds" begin
    a = OffsetArray(zeros(7), -3:3)
    unsafe_fill!(x) = @inbounds(for i in axes(x,1); x[i] = i; end)
    function unsafe_sum(x)
        s = zero(eltype(x))
        @inbounds for i in axes(x,1)
            s += x[i]
        end
        s
    end
    unsafe_fill!(a)
    for i = -3:3
        @test a[i] == i
    end
    @test unsafe_sum(a) == 0
end

@testset "Resizing OffsetVectors" begin
    local a = OffsetVector(rand(5),-3)
    axes(a,1) == -2:2
    length(a) == 5
    resize!(a,3)
    length(a) == 3
    axes(a,1) == -2:0
    @test_throws ArgumentError resize!(a,-3)
end

####
#### type defined for testing no_offset_view
####

struct NegativeArray{T,N,S <: AbstractArray{T,N}} <: AbstractArray{T,N}
    parent::S
end

# Note: this defines the axes-of-the-axes to be OneTo.
# In general this isn't recommended, because
#    positionof(A, i, j, ...) == map(getindex, axes(A), (i, j, ...))
# is quite desirable, and this requires that the axes be "identity" ranges, i.e.,
# `r[i] == i`.
# Nevertheless it's useful to test this on a "broken" implementation
# to make sure we still get the right answer.
Base.axes(A::NegativeArray) = map(n -> (-n):(-1), size(A.parent))

Base.size(A::NegativeArray) = size(A.parent)

function Base.getindex(A::NegativeArray{T,N}, I::Vararg{Int,N}) where {T,N}
    getindex(A.parent, (I .+ size(A.parent) .+ 1)...)
end

struct PointlessWrapper{T,N, A <: AbstractArray{T,N}} <: AbstractArray{T,N}
    parent :: A
end
Base.parent(x::PointlessWrapper) = x.parent
Base.size(x::PointlessWrapper) = size(parent(x))
Base.axes(x::PointlessWrapper) = axes(parent(x))
Base.getindex(x::PointlessWrapper, i...) = x.parent[i...]

@testset "no offset view" begin
    # OffsetArray fallback
    A = randn(3, 3)
    @inferred no_offset_view(A)
    O1 = OffsetArray(A, -1:1, 0:2)
    O2 = OffsetArray(O1, -2:0, -3:(-1))
    @test no_offset_view(O2) ≡ A
    @inferred no_offset_view(O1)
    @inferred no_offset_view(O2)

    P = PointlessWrapper(A)
    @test @inferred(no_offset_view(P)) === P
    @test @inferred(no_offset_view(A)) === A
    a0 = reshape([1])
    @test @inferred(no_offset_view(a0)) === a0
    a0v = view(a0)
    @test @inferred(no_offset_view(a0v)) === a0v

    # generic fallback
    A = collect(reshape(1:12, 3, 4))
    N = NegativeArray(A)
    @test N[-3, -4] == 1
    V = no_offset_view(N)
    @test collect(V) == A
    A = reshape(view([5], 1, 1))
    @test no_offset_view(A) == A

    # bidirectional
    B = BidirectionalVector([1, 2, 3])
    pushfirst!(B, 0)
    OB = OffsetArrays.no_offset_view(B)
    @test no_offset_axes(OB, 1) == 1:4
    @test collect(OB) == 0:3

    # issue #198
    offax = axes(OffsetVector(1:10, -5), 1)
    noffax = OffsetArrays.no_offset_view(offax)
    @test noffax == -4:5
    @test axes(noffax, 1) == 1:10   # ideally covered by the above, but current it isn't
    @test isa(noffax, AbstractUnitRange)

    r = Base.OneTo(4)
    @test OffsetArrays.no_offset_view(r) isa typeof(r)

    # SubArrays
    A = reshape(1:12, 3, 4)
    V = view(A, OffsetArrays.IdentityUnitRange(2:3), OffsetArrays.IdentityUnitRange(2:3))
    if collect(V) == [5 8; 6 9]   # julia 1.0 has a bug here
        @test OffsetArrays.no_offset_view(V) == [5 8; 6 9]
    end
    V = view(A, OffsetArrays.IdentityUnitRange(2:3), 2)
    @test V != [5;6]
    if collect(V) == [5;6]
        @test OffsetArrays.no_offset_view(V) == [5;6]
    end
    O = OffsetArray(A, -1:1, 0:3)
    V = view(O, 0:1, 1:2)
    @test V == OffsetArrays.no_offset_view(V) == [5 8; 6 9]
    r1, r2 = OffsetArrays.IdOffsetRange(1:3, -2), OffsetArrays.IdentityUnitRange(2:3)
    V = view(O, r1, r2)
    @test V != collect(V)
    @test OffsetArrays.no_offset_view(V) == collect(V)
    V = @view O[:,:]
    @test IndexStyle(A) == IndexStyle(O) == IndexStyle(V) == IndexStyle(OffsetArrays.no_offset_view(V)) == IndexLinear()

    @testset "issue #375" begin
        arr = OffsetArray(reshape(1:15, 3, 5), 2, 3)
        arr_no_offset = OffsetArrays.no_offset_view(@view arr[:, 4])
        @test all(!Base.has_offset_axes, axes(arr_no_offset))
    end
end

@testset "no nesting" begin
    A = randn(2, 3)
    x = A[2, 2]
    O1 = OffsetArray(A, -1:0, -1:1)
    O2 = OffsetArray(O1, 0:1, 0:2)
    @test parent(O1) ≡ parent(O2)
    @test eltype(O1) ≡ eltype(O2)
    O2[1, 1] = x + 1            # just a sanity check
    @test A[2, 2] == x + 1
end

@testset "mutating functions for OffsetVector" begin
    # push!
    o = OffsetVector(Int[], -1)
    @test push!(o) === o
    @test no_offset_axes(o, 1) == 0:-1
    @test push!(o, 1) === o
    @test no_offset_axes(o, 1) == 0:0
    @test o[end] == 1
    @test push!(o, 2, 3) === o
    @test no_offset_axes(o, 1) == 0:2
    @test o[end-1:end] == [2, 3]
    # pop!
    o = OffsetVector([1, 2, 3], -1)
    @test pop!(o) == 3
    @test no_offset_axes(o, 1) == 0:1
    # append!
    o = OffsetVector([1, 2, 3], -1)
    append!(o, [4, 5])
    @test no_offset_axes(o, 1) == 0:4
    # empty!
    o = OffsetVector([1, 2, 3], -1)
    @test empty!(o) === o
    @test no_offset_axes(o, 1) == 0:-1
end

@testset "searchsorted (#85)" begin
    o = OffsetVector([1,3,4,5],-2)
    @test searchsortedfirst(o,-2) == -1
    @test searchsortedfirst(o, 1) == -1
    @test searchsortedfirst(o, 2) ==  0
    @test searchsortedfirst(o, 5) ==  2
    @test searchsortedfirst(o, 6) ==  3
    @test searchsortedlast(o, -2) == -2
    @test searchsortedlast(o,  1) == -1
    @test searchsortedlast(o,  2) == -1
    @test searchsortedlast(o,  5) ==  2
    @test searchsortedlast(o,  6) ==  2
    @test searchsorted(o, -2) == -1:-2
    @test searchsorted(o,  1) == -1:-1
    @test searchsorted(o,  2) ==  0:-1
    @test searchsorted(o,  5) ==  2:2
    @test searchsorted(o,  6) ==  3:2

    if VERSION > v"1.2"
        # OffsetVector of another offset vector
        v = OffsetVector(Base.IdentityUnitRange(4:10),-2)
        @test searchsortedfirst(v, first(v)-1) == firstindex(v)
        for i in axes(v,1)
            @test searchsortedfirst(v, v[i]) == i
        end
        @test searchsortedfirst(v, last(v)+1) == lastindex(v)+1
        @test searchsortedlast(v, first(v)-1) == firstindex(v)-1
        for i in axes(v,1)
            @test searchsortedlast(v, v[i]) == i
        end
        @test searchsortedlast(v, last(v)+1) == lastindex(v)
        @test searchsorted(v, first(v)-1) === firstindex(v) .+ (0:-1)
        for i in axes(v,1)
            @test searchsorted(v, v[i]) == i:i
        end
        @test searchsorted(v, last(v)+1) === lastindex(v) .+ (1:0)
    end

    v = OffsetVector{Float64, OffsetVector{Float64, Vector{Float64}}}(OffsetVector([2,2,3,3,3,4], 3), 4)
    @test searchsortedfirst(v, minimum(v)-1) == firstindex(v)
    for el in unique(v)
        @test searchsortedfirst(v, el) == findfirst(isequal(el), v)
    end
    @test searchsortedfirst(v, maximum(v)+1) == lastindex(v)+1

    @test searchsortedlast(v, minimum(v)-1) == firstindex(v)-1
    for el in unique(v)
        @test searchsortedlast(v, el) == findlast(isequal(el), v)
    end
    @test searchsortedlast(v, maximum(v)+1) == lastindex(v)

    @test searchsorted(v, minimum(v)-1) === firstindex(v) .+ (0:-1)
    for el in unique(v)
        @test searchsorted(v, el) == findfirst(isequal(el), v):findlast(isequal(el), v)
    end
    @test searchsorted(v, maximum(v)+1) === lastindex(v) .+ (1:0)

    soa = OffsetArray([2,2,3], typemax(Int)-3)
    @test searchsortedfirst(soa, 1) == firstindex(soa) == typemax(Int)-2
    @test searchsortedfirst(soa, 2) == firstindex(soa) == typemax(Int)-2
    @test searchsortedfirst(soa, 3) == lastindex(soa) == typemax(Int)

    soa = OffsetArray([2,2,3], typemin(Int))
    @test searchsortedlast(soa, 2) == firstindex(soa) + 1 == typemin(Int) + 2
    @test searchsortedlast(soa, 3) == lastindex(soa) == typemin(Int) + 3
    @test searchsortedlast(soa, 1) == typemin(Int)
Download .txt
gitextract_gvhhoopx/

├── .github/
│   ├── dependabot.yml
│   └── workflows/
│       ├── CompatHelper.yml
│       ├── TagBot.yml
│       ├── UnitTest.yml
│       ├── docs.yml
│       └── invalidations.yml
├── .gitignore
├── LICENSE.md
├── Project.toml
├── README.md
├── benchmark/
│   └── benchmarks.jl
├── docs/
│   ├── .gitignore
│   ├── Project.toml
│   ├── make.jl
│   └── src/
│       ├── index.md
│       ├── internals.md
│       └── reference.md
├── ext/
│   └── OffsetArraysAdaptExt.jl
├── src/
│   ├── OffsetArrays.jl
│   ├── axes.jl
│   ├── origin.jl
│   ├── precompile.jl
│   └── utils.jl
└── test/
    ├── customranges.jl
    ├── origin.jl
    └── runtests.jl
Condensed preview — 26 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (216K chars).
[
  {
    "path": ".github/dependabot.yml",
    "chars": 255,
    "preview": "# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates\nversion: 2\nupda"
  },
  {
    "path": ".github/workflows/CompatHelper.yml",
    "chars": 1381,
    "preview": "name: CompatHelper\non:\n  schedule:\n    - cron: 0 0 * * *\n  workflow_dispatch:\npermissions:\n  contents: write\n  pull-requ"
  },
  {
    "path": ".github/workflows/TagBot.yml",
    "chars": 354,
    "preview": "name: TagBot\non:\n  issue_comment:\n    types:\n      - created\n  workflow_dispatch:\njobs:\n  TagBot:\n    if: github.event_n"
  },
  {
    "path": ".github/workflows/UnitTest.yml",
    "chars": 1411,
    "preview": "name: Unit test\n\non:\n  push:\n    tags:\n      - 'v*'\n    branches:\n      - master\n    paths-ignore:\n      - 'LICENSE.md'\n"
  },
  {
    "path": ".github/workflows/docs.yml",
    "chars": 1119,
    "preview": "name: Documentation\n\non:\n  pull_request:\n  push:\n    branches:\n      - 'master'\n      - 'release-'\n    tags: '*'\n  relea"
  },
  {
    "path": ".github/workflows/invalidations.yml",
    "chars": 1027,
    "preview": "name: Invalidations\non: pull_request\n\njobs:\n  evaluate:\n    runs-on: ubuntu-latest\n    steps:\n    - uses: julia-actions/"
  },
  {
    "path": ".gitignore",
    "chars": 74,
    "preview": "Manifest.toml\nManifest-v*.*.toml\ndocs/build\nLocalPreferences.toml\n.vscode\n"
  },
  {
    "path": "LICENSE.md",
    "chars": 1174,
    "preview": "The FArrayMod.jl package is licensed under the MIT \"Expat\" License:\n\n> Copyright (c) 2014:\n>  * Alexander Samoilov\n>\n> P"
  },
  {
    "path": "Project.toml",
    "chars": 1284,
    "preview": "name = \"OffsetArrays\"\nuuid = \"6fe1bfb0-de20-5000-8ca7-80f57d26f881\"\nversion = \"1.17.0\"\n\n[deps]\nAdapt = \"79e6a3ab-5dfb-50"
  },
  {
    "path": "README.md",
    "chars": 7786,
    "preview": "# OffsetArrays.jl\n\n| Documentation |  Build Status | Code coverage | Version |\n| :-: | :-: | :-: | :-: |\n| [![][docs-sta"
  },
  {
    "path": "benchmark/benchmarks.jl",
    "chars": 3167,
    "preview": "using BenchmarkTools\nusing OffsetArrays\n\nconst dim = 1000\n\nx = Array{Float64}(undef, 2*dim);\ny = OffsetArray{Float64}(un"
  },
  {
    "path": "docs/.gitignore",
    "chars": 13,
    "preview": "build/\nsite/\n"
  },
  {
    "path": "docs/Project.toml",
    "chars": 203,
    "preview": "[deps]\nDocumenter = \"e30172f5-a6a5-5a46-863b-614d45cd2de4\"\nJSON = \"682c06a0-de6a-54ab-a142-c8b1cf79cde6\"\nOffsetArrays = "
  },
  {
    "path": "docs/make.jl",
    "chars": 1038,
    "preview": "using Documenter, JSON\nusing OffsetArrays\n\nDocMeta.setdocmeta!(OffsetArrays, :DocTestSetup, :(using OffsetArrays); recur"
  },
  {
    "path": "docs/src/index.md",
    "chars": 3789,
    "preview": "# OffsetArrays.jl\n\nOffsetArrays provides Julia users with arrays that have arbitrary\nindices, similar to those found in "
  },
  {
    "path": "docs/src/internals.md",
    "chars": 11261,
    "preview": "# For developers\n\nWriting code that supports OffsetArrays is generally fairly straightforward.\nThe majority of cases can"
  },
  {
    "path": "docs/src/reference.md",
    "chars": 214,
    "preview": "# Reference\n\n```@docs\nOffsetArray\nOffsetVector\nOffsetMatrix\nOffsetArrays.Origin\nOffsetArrays.IdOffsetRange\nOffsetArrays."
  },
  {
    "path": "ext/OffsetArraysAdaptExt.jl",
    "chars": 517,
    "preview": "module OffsetArraysAdaptExt\n\nusing OffsetArrays, Adapt\n\n##\n# Adapt allows for automatic conversion of CPU OffsetArrays t"
  },
  {
    "path": "src/OffsetArrays.jl",
    "chars": 39897,
    "preview": "module OffsetArrays\n\nusing Base: tail, @propagate_inbounds\n@static if !isdefined(Base, :IdentityUnitRange)\n    const Ide"
  },
  {
    "path": "src/axes.jl",
    "chars": 13255,
    "preview": "\"\"\"\n    ro = IdOffsetRange(r::AbstractUnitRange, offset=0)\n\nConstruct an \"identity offset range\". Numerically, `collect("
  },
  {
    "path": "src/origin.jl",
    "chars": 2869,
    "preview": "\"\"\"\n    Origin(indices...)\n    Origin(origin::Tuple)\n    Origin(origin::CartesianIndex)\n\nA helper type to construct Offs"
  },
  {
    "path": "src/precompile.jl",
    "chars": 997,
    "preview": "function _precompile_()\n    ccall(:jl_generating_output, Cint, ()) == 1 || return nothing\n    Base.precompile(Tuple{type"
  },
  {
    "path": "src/utils.jl",
    "chars": 5513,
    "preview": "### Low-level utilities ###\n\n_indexoffset(r::AbstractRange) = first(r) - 1\n_indexoffset(i::Integer) = 0\n_indexlength(r::"
  },
  {
    "path": "test/customranges.jl",
    "chars": 3458,
    "preview": "# Useful for testing indexing\nstruct ZeroBasedRange{T,A<:AbstractRange{T}} <: AbstractRange{T}\n    a :: A\n    function Z"
  },
  {
    "path": "test/origin.jl",
    "chars": 2746,
    "preview": "using OffsetArrays: Origin\n@testset \"Origin\" begin\n    get_origin(A::AbstractArray) = first.(axes(A))\n\n    @test Origin("
  },
  {
    "path": "test/runtests.jl",
    "chars": 104127,
    "preview": "using Adapt\nusing Aqua\nusing Base: Slice\nusing CatIndices: BidirectionalVector\nusing DelimitedFiles\nusing DistributedArr"
  }
]

About this extraction

This page contains the full source code of the JuliaArrays/OffsetArrays.jl GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 26 files (204.0 KB), approximately 67.3k tokens. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!