Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

More specific characterization of mutable collections with can_setindex and can_change_size #46500

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions base/array.jl
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,10 @@ asize_from(a::Array, n) = n > ndims(a) ? () : (arraysize(a,n), asize_from(a, n+1

allocatedinline(T::Type) = (@_total_meta; ccall(:jl_stored_inline, Cint, (Any,), T) != Cint(0))

can_change_size(::Type{<:Vector}) = true

can_setindex(::Type{<:Array}) = true

"""
Base.isbitsunion(::Type{T})

Expand Down
5 changes: 5 additions & 0 deletions base/bitarray.jl
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,9 @@ BitArray(::UndefInitializer, dims::NTuple{N,Integer}) where {N} = BitArray{N}(un
BitArray{N}(::UndefInitializer, dims::NTuple{N,Integer}) where {N} = BitArray{N}(undef, map(Int, dims)...)

const BitVector = BitArray{1}

can_change_size(::Type{BitVector}) = true

const BitMatrix = BitArray{2}

BitVector() = BitVector(undef, 0)
Expand Down Expand Up @@ -684,6 +687,8 @@ end

## Indexing: setindex! ##

can_setindex(T::Type{<:BitArray}) = true

@inline function unsafe_bitsetindex!(Bc::Array{UInt64}, x::Bool, i::Int)
i1, i2 = get_chunks_id(i)
_unsafe_bitsetindex!(Bc, x, i1, i2)
Expand Down
2 changes: 2 additions & 0 deletions base/bitset.jl
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ mutable struct BitSet <: AbstractSet{Int}
BitSet() = new(sizehint!(zeros(UInt64, 0), 4), NO_OFFSET)
end

can_change_size(::Type{<:BitSet}) = true

"""
BitSet([itr])

Expand Down
2 changes: 2 additions & 0 deletions base/dict.jl
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,8 @@ Dict() = Dict{Any,Any}()
Dict(kv::Tuple{}) = Dict()
copy(d::Dict) = Dict(d)

can_change_size(::Type{<:Dict}) = true

const AnyDict = Dict{Any,Any}

Dict(ps::Pair{K,V}...) where {K,V} = Dict{K,V}(ps)
Expand Down
1 change: 1 addition & 0 deletions base/env.jl
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ pop!(::EnvDict, k::AbstractString, def) = haskey(ENV,k) ? pop!(ENV,k) : def
delete!(::EnvDict, k::AbstractString) = (_unsetenv(k); ENV)
setindex!(::EnvDict, v, k::AbstractString) = _setenv(k,string(v))
push!(::EnvDict, kv::Pair{<:AbstractString}) = setindex!(ENV, kv.second, kv.first)
can_change_size(::Type{EnvDict}) = true

if Sys.iswindows()
GESW() = (pos = ccall(:GetEnvironmentStringsW, stdcall, Ptr{UInt16}, ()); (pos, pos))
Expand Down
2 changes: 2 additions & 0 deletions base/exports.jl
Original file line number Diff line number Diff line change
Expand Up @@ -778,6 +778,8 @@ export
parse,

# help and reflection
can_change_size,
can_setindex,
code_typed,
code_lowered,
fullname,
Expand Down
2 changes: 2 additions & 0 deletions base/iddict.jl
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@ function IdDict(kv)
end
end

can_change_size(::Type{<:IdDict}) = true

empty(d::IdDict, ::Type{K}, ::Type{V}) where {K, V} = IdDict{K,V}()

function rehash!(d::IdDict, newsz = length(d.ht))
Expand Down
2 changes: 2 additions & 0 deletions base/idset.jl
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ copymutable(s::IdSet) = typeof(s)(s)
emptymutable(s::IdSet{T}, ::Type{U}=T) where {T,U} = IdSet{U}()
copy(s::IdSet) = typeof(s)(s)

can_change_size(::Type{<:IdSet}) = true

isempty(s::IdSet) = isempty(s.dict)
length(s::IdSet) = length(s.dict)
in(@nospecialize(x), s::IdSet) = haskey(s.dict, x)
Expand Down
4 changes: 4 additions & 0 deletions base/permuteddimsarray.jl
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,10 @@ Base.size(A::PermutedDimsArray{T,N,perm}) where {T,N,perm} = genperm(size(parent
Base.axes(A::PermutedDimsArray{T,N,perm}) where {T,N,perm} = genperm(axes(parent(A)), perm)
Base.has_offset_axes(A::PermutedDimsArray) = Base.has_offset_axes(A.parent)

function can_setindex(@nospecialize T::Type{<:PermutedDimsArray})
can_setindex(fieldtype(T, :parent))
end

Base.similar(A::PermutedDimsArray, T::Type, dims::Base.Dims) = similar(parent(A), T, dims)

Base.unsafe_convert(::Type{Ptr{T}}, A::PermutedDimsArray{T}) where {T} = Base.unsafe_convert(Ptr{T}, parent(A))
Expand Down
21 changes: 21 additions & 0 deletions base/reflection.jl
Original file line number Diff line number Diff line change
Expand Up @@ -528,6 +528,27 @@ function ismutabletype(@nospecialize t)
return isa(t, DataType) && t.name.flags & 0x2 == 0x2
end

"""
can_change_size(::Type{T}) -> Bool

Returns `true` if instances of `T` may change size through operations such as `pop!` and
`popfirst!`.

See also: [`can_setindex`](@ref)
"""
can_change_size(x) = can_change_size(typeof(x))
can_change_size(T::Type) = false
can_change_size(@nospecialize T::Type{<:Pairs}) = can_change_size(fieldtype(T, :data))


"""
can_setindex(::Type{T}) -> Bool

Query whether a type can use `setindex!`. Falls back to the [`can_change_size`](@ref).
"""
can_setindex(x) = can_setindex(typeof(x))
can_setindex(T::Type) = can_change_size(T)

"""
isstructtype(T) -> Bool

Expand Down
5 changes: 5 additions & 0 deletions base/reinterpretarray.jl
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,11 @@ function _getindex(::IndexSCartesian2, A::AbstractArray{T,N}, I::Vararg{Int, N})
@_propagate_inbounds_meta
getindex(A, I...)
end

function can_setindex(@nospecialize T::Type{<:ReinterpretArray})
can_setindex(fieldtype(T, :parent))
end

function _setindex!(::IndexSCartesian2, A::AbstractArray{T,N}, v, I::Vararg{Int, N}) where {T,N}
@_propagate_inbounds_meta
setindex!(A, v, I...)
Expand Down
4 changes: 4 additions & 0 deletions base/reshapedarray.jl
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,10 @@ end
@inline _unsafe_getindex_rs(A, i::Integer) = (@inbounds ret = A[i]; ret)
@inline _unsafe_getindex_rs(A, I) = (@inbounds ret = A[I...]; ret)

function can_setindex(@nospecialize T::Type{<:ReshapedArray})
can_setindex(fieldtype(T, :parent))
end

@inline function setindex!(A::ReshapedArrayLF, val, index::Int)
@boundscheck checkbounds(A, index)
@inbounds parent(A)[index] = val
Expand Down
2 changes: 2 additions & 0 deletions base/set.jl
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@ end

empty(s::AbstractSet{T}, ::Type{U}=T) where {T,U} = Set{U}()

can_change_size(::Type{<:Set}) = true

# return an empty set with eltype T, which is mutable (can be grown)
# by default, a Set is returned
emptymutable(s::AbstractSet{T}, ::Type{U}=T) where {T,U} = Set{U}()
Expand Down
1 change: 1 addition & 0 deletions base/slicearray.jl
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,7 @@ const ColumnSlices{P<:AbstractMatrix,AX,S<:AbstractVector} = Slices{P,Tuple{Colo
IteratorSize(::Type{Slices{P,SM,AX,S,N}}) where {P,SM,AX,S,N} = HasShape{N}()
axes(s::Slices) = s.axes
size(s::Slices) = map(length, s.axes)
can_setindex(@nospecialize T::Type{<:Slices}) = can_setindex(fieldtype(T, :parent))

@inline function _slice_index(s::Slices, c...)
return map(l -> l === (:) ? (:) : c[l], s.slicemap)
Expand Down
4 changes: 4 additions & 0 deletions base/subarray.jl
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,10 @@ function getindex(V::FastContiguousSubArray{<:Any, 1}, i::Int)
r
end

function can_setindex(@nospecialize T::Type{<:SubArray})
can_setindex(fieldtype(T, :parent))
end

# Indexed assignment follows the same pattern as `getindex` above
function setindex!(V::SubArray{T,N}, x, I::Vararg{Int,N}) where {T,N}
@inline
Expand Down
2 changes: 2 additions & 0 deletions base/weakkeydict.jl
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ WeakKeyDict() = WeakKeyDict{Any,Any}()
WeakKeyDict(kv::Tuple{}) = WeakKeyDict()
copy(d::WeakKeyDict) = WeakKeyDict(d)

can_change_size(::Type{<:WeakKeyDict}) = true

WeakKeyDict(ps::Pair{K,V}...) where {K,V} = WeakKeyDict{K,V}(ps)
WeakKeyDict(ps::Pair{K}...) where {K} = WeakKeyDict{K,Any}(ps)
WeakKeyDict(ps::(Pair{K,V} where K)...) where {V} = WeakKeyDict{Any,V}(ps)
Expand Down
4 changes: 4 additions & 0 deletions stdlib/LinearAlgebra/src/adjtrans.jl
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,10 @@ Transpose(A) = Transpose{Base.promote_op(transpose,eltype(A)),typeof(A)}(A)
Base.dataids(A::Union{Adjoint, Transpose}) = Base.dataids(A.parent)
Base.unaliascopy(A::Union{Adjoint,Transpose}) = typeof(A)(Base.unaliascopy(A.parent))

function Base.can_setindex(@nospecialize T::Type{<:Union{Adjoint,Transpose}})
Base.can_setindex(fieldtype(T, :parent))
end

# wrapping lowercase quasi-constructors
"""
A'
Expand Down
4 changes: 4 additions & 0 deletions stdlib/LinearAlgebra/src/bidiag.jl
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,10 @@ end
end
end

function Base.can_setindex(@nospecialize T::Type{<:Bidiagonal})
Base.can_setindex(fieldtype(T, :ev))
end

@inline function setindex!(A::Bidiagonal, x, i::Integer, j::Integer)
@boundscheck checkbounds(A, i, j)
if i == j
Expand Down
4 changes: 4 additions & 0 deletions stdlib/LinearAlgebra/src/diagonal.jl
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,10 @@ end
diagzero(::Diagonal{T}, i, j) where {T} = zero(T)
diagzero(D::Diagonal{<:AbstractMatrix{T}}, i, j) where {T} = zeros(T, size(D.diag[i], 1), size(D.diag[j], 2))

function Base.can_setindex(@nospecialize T::Type{<:Diagonal})
Base.can_setindex(fieldtype(T, :diag))
end

function setindex!(D::Diagonal, v, i::Int, j::Int)
@boundscheck checkbounds(D, i, j)
if i == j
Expand Down
4 changes: 4 additions & 0 deletions stdlib/LinearAlgebra/src/hessenberg.jl
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,10 @@ end
getindex(H::UpperHessenberg{T}, i::Integer, j::Integer) where {T} =
i <= j+1 ? convert(T, H.data[i,j]) : zero(T)

function Base.can_setindex(@nospecialize T::Type{<:UpperHessenberg})
Base.can_setindex(fieldtype(T, :data))
end

function setindex!(A::UpperHessenberg, x, i::Integer, j::Integer)
if i > j+1
x == 0 || throw(ArgumentError("cannot set index in the lower triangular part " *
Expand Down
4 changes: 4 additions & 0 deletions stdlib/LinearAlgebra/src/symmetric.jl
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,10 @@ end
end
end

function Base.can_setindex(@nospecialize T::Type{<:Union{Symmetric,Hermitian}})
Base.can_setindex(fieldtype(T, :data))
end

function setindex!(A::Symmetric, v, i::Integer, j::Integer)
i == j || throw(ArgumentError("Cannot set a non-diagonal index in a symmetric matrix"))
setindex!(A.data, v, i, j)
Expand Down
3 changes: 3 additions & 0 deletions stdlib/LinearAlgebra/src/triangular.jl
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,9 @@ similar(A::LowerTriangular{<:Any,<:Union{Adjoint{Ti}, Transpose{Ti}}}, ::Type{T}
similar(A::UnitLowerTriangular{<:Any,<:Union{Adjoint{Ti}, Transpose{Ti}}}, ::Type{T}) where {T,Ti} =
UnitLowerTriangular(similar(parent(parent(A)), T))

function Base.can_setindex(@nospecialize T::Type{<:Union{Union{LowerTriangular,UnitLowerTriangular,UpperTriangular,UnitUpperTriangular}}})
Base.can_setindex(fieldtype(T, :data))
end

"""
LowerTriangular(A::AbstractMatrix)
Expand Down
3 changes: 3 additions & 0 deletions stdlib/LinearAlgebra/src/tridiag.jl
Original file line number Diff line number Diff line change
Expand Up @@ -458,6 +458,9 @@ logabsdet(A::SymTridiagonal; shift::Number=false) = logabsdet(ldlt(A; shift=shif
end
end

function Base.can_setindex(@nospecialize T::Type{<:SymTridiagonal})
Base.can_setindex(fieldtype(T, :ev))
end
@inline function setindex!(A::SymTridiagonal, x, i::Integer, j::Integer)
@boundscheck checkbounds(A, i, j)
if i == j
Expand Down
18 changes: 18 additions & 0 deletions test/reflection.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1001,3 +1001,21 @@ function f_no_methods end
@test (Base.infer_effects(setfield!, ()); true) # `builtin_effects` shouldn't throw on empty `argtypes`
@test (Base.infer_effects(Core.Intrinsics.arraylen, ()); true) # `intrinsic_effects` shouldn't throw on empty `argtypes`
end

@testset "can_change_size" begin
@test can_change_size([1])
@test can_change_size(Vector{Int})
@test can_change_size(Dict{Symbol,Any})
@test !can_change_size(Base.ImmutableDict{Symbol,Int64})
@test !can_change_size(Tuple{})
end

@testset "can_setindex" begin
@test !can_setindex(1:2)
@test can_setindex(Vector{Int})
@test !can_setindex(UnitRange{Int})
@test !can_setindex(Base.ImmutableDict{Int,Int})
@test !can_setindex(Tuple{})
@test !can_setindex(NamedTuple{(),Tuple{}})
@test can_setindex(Dict{Int,Int})
end