From 3cc05908a61aec885f3a4fc3fc9cb13cf3524108 Mon Sep 17 00:00:00 2001 From: Jishnu Bhattacharya Date: Mon, 24 Jul 2023 21:33:22 +0530 Subject: [PATCH] `Array(::AbstractRange)` should return an `Array` (#50568) Currently, `Array(r::AbstractRange)` falls back to `vcat(r)`, but certain ranges may choose to specialize `vcat(r::AbstractRange)` to not return an `Array`. This PR ensures that `Array(r)` always returns an `Array`. At present, there's some code overlap with `vcat` (just above the `Array` method added in this PR). Perhaps some of these may be replaced by `unsafe_copyto!`, but the tests for ranges include some special cases that don't support `getindex`, which complicates things a bit. I've not done this for now. In any case, the common bit of code is pretty simple, so perhaps the duplication is harmless. --- base/range.jl | 17 +++++++++++++++-- test/ranges.jl | 21 +++++++++++++++++++++ 2 files changed, 36 insertions(+), 2 deletions(-) diff --git a/base/range.jl b/base/range.jl index 859f55aece01d..e8ffe10e2ba7f 100644 --- a/base/range.jl +++ b/base/range.jl @@ -1380,8 +1380,21 @@ function vcat(rs::AbstractRange{T}...) where T return a end -Array{T,1}(r::AbstractRange{T}) where {T} = vcat(r) -collect(r::AbstractRange) = vcat(r) +# This method differs from that for AbstractArrays as it +# use iteration instead of indexing. This works even if certain +# non-standard ranges don't support indexing. +# See https://github.com/JuliaLang/julia/pull/27302 +# Similarly, collect(r::AbstractRange) uses iteration +function Array{T,1}(r::AbstractRange{T}) where {T} + a = Vector{T}(undef, length(r)) + i = 1 + for x in r + @inbounds a[i] = x + i += 1 + end + return a +end +collect(r::AbstractRange) = Array(r) _reverse(r::OrdinalRange, ::Colon) = (:)(last(r), negate(step(r)), first(r)) function _reverse(r::StepRangeLen, ::Colon) diff --git a/test/ranges.jl b/test/ranges.jl index 27a74ac854e9f..137f9c885da26 100644 --- a/test/ranges.jl +++ b/test/ranges.jl @@ -1802,6 +1802,7 @@ Base.div(x::Displacement, y::Displacement) = Displacement(div(x.val, y.val)) # required for collect (summing lengths); alternatively, should length return Int by default? Base.promote_rule(::Type{Displacement}, ::Type{Int}) = Int Base.convert(::Type{Int}, x::Displacement) = x.val +Base.Int(x::Displacement) = x.val # Unsigned complement, for testing checked_length struct UPosition <: Unsigned @@ -2499,6 +2500,25 @@ end @test (-10:2:typemax(Int))[typemax(Int)รท2+2] == typemax(Int)-9 end +@testset "collect with specialized vcat" begin + struct OneToThree <: AbstractUnitRange{Int} end + Base.size(r::OneToThree) = (3,) + Base.first(r::OneToThree) = 1 + Base.length(r::OneToThree) = 3 + Base.last(r::OneToThree) = 3 + function Base.getindex(r::OneToThree, i::Int) + checkbounds(r, i) + i + end + Base.vcat(r::OneToThree) = r + r = OneToThree() + a = Array(r) + @test a isa Vector{Int} + @test a == r + @test collect(r) isa Vector{Int} + @test collect(r) == r +end + @testset "isassigned" begin for (r, val) in ((1:3, 3), (1:big(2)^65, big(2)^65)) @test isassigned(r, lastindex(r)) @@ -2506,4 +2526,5 @@ end @test r[end] == val @test_throws ArgumentError isassigned(r, true) end + end