diff --git a/NEWS.md b/NEWS.md index 18f3a3bb07780..70ad68291c22b 100644 --- a/NEWS.md +++ b/NEWS.md @@ -79,6 +79,7 @@ New library functions * New function `stack(x)` which generalises `reduce(hcat, x::Vector{<:Vector})` to any dimensionality, and allows any iterators of iterators. Method `stack(f, x)` generalises `mapreduce(f, hcat, x)` and is efficient. ([#43334]) +* New functions `delete`, `deleteat`, and `insert` provide non-mutating counterparts to `delete!`, `deleteat!`, and `insert!` ([#46453]). * New macro `@allocations` which is similar to `@allocated` except reporting the total number of allocations rather than the total size of memory allocated. ([#47367]) @@ -95,6 +96,7 @@ Library changes * `@time` now separates out % time spent recompiling invalidated methods ([#45015]). * `eachslice` now works over multiple dimensions; `eachslice`, `eachrow` and `eachcol` return a `Slices` object, which allows dispatching to provide more efficient methods ([#32310]). +* The non-mutationg `Base.setindex` function now has `AbstractDict` support ([#46453]). * `@kwdef` is now exported and added to the public API ([#46273]) * An issue with order of operations in `fld1` is now fixed ([#28973]). * Sorting is now always stable by default as `QuickSort` was stabilized in ([#45222]). diff --git a/base/abstractdict.jl b/base/abstractdict.jl index 39de4c441a966..f12f4ee5d2225 100644 --- a/base/abstractdict.jl +++ b/base/abstractdict.jl @@ -548,6 +548,22 @@ end # that we need to avoid dispatch loops if setindex!(t,v,k) is not defined.) getindex(t::AbstractDict, k1, k2, ks...) = getindex(t, tuple(k1,k2,ks...)) setindex!(t::AbstractDict, v, k1, k2, ks...) = setindex!(t, v, tuple(k1,k2,ks...)) +function setindex(src::AbstractDict{K,V}, val::V, key::K) where {K,V} + dst = copy(src) + dst[key] = val + return dst +end +function setindex(src::AbstractDict{K,V}, val, key) where {K,V} + dst = empty(src, promote_type(K,typeof(key)), promote_type(V,typeof(val))) + if haslength(src) + sizehint!(dst, length(src)) + end + for (k,v) in src + dst[k] = v + end + dst[key] = val + return dst +end get!(t::AbstractDict, key, default) = get!(() -> default, t, key) function get!(default::Callable, t::AbstractDict{K,V}, key) where K where V diff --git a/base/accumulate.jl b/base/accumulate.jl index eeb9759e125c7..a159f1d389412 100644 --- a/base/accumulate.jl +++ b/base/accumulate.jl @@ -434,3 +434,10 @@ function _accumulate1!(op, B, v1, A::AbstractVector, dim::Integer) end return B end + +function setindex(t::Tuple, v, inds::AbstractVector{<:Integer}) + Base.@_propagate_inbounds_meta + foldl(ntuple(identity, length(inds)); init=t) do acc, i + Base.setindex(acc, v[i], inds[i]) + end +end diff --git a/base/array.jl b/base/array.jl index 64d0ac05fd507..8f292ceb949fa 100644 --- a/base/array.jl +++ b/base/array.jl @@ -1007,6 +1007,64 @@ function setindex!(A::Array{T}, X::Array{T}, c::Colon) where T return A end +""" + setindex(collection, value, key...) + +Create a new collection with the element/elements of the location specified by `key` +replaced with `value`(s). + +Note that this may be particularly expensive when `collection` is large and whose storage +cannot be shared between seperate instances. + +# Implementation + +`setindex(collection, value, key...)` must have the property such that + +```julia +y1 = setindex(x, value, key...) + +y2 = copy′(x) +@assert convert.(eltype(y2), x) == y2 + +y2[key...] = value +@assert convert.(eltype(y2), y1) == y2 +``` + +with a suitable definition of `copy′` such that `y2[key...] = value` succeeds. + +`setindex` should support more combinations of arguments by widening collection +type as required. +""" +function setindex end + +function setindex(x::AbstractArray, v, i...) + @_propagate_inbounds_meta + inds = to_indices(x, i) + if inds isa Tuple{Vararg{Integer}} + T = promote_type(eltype(x), typeof(v)) + else + T = promote_type(eltype(x), eltype(v)) + end + y = similar(x, T) + copy!(y, x) + y[inds...] = v + return y +end +function setindex(t::Tuple, v, inds::AbstractUnitRange{<:Integer}) + @boundscheck checkindex(Bool, eachindex(t), inds) || throw(BoundsError(t, inds)) + @boundscheck setindex_shape_check(v, length(inds)) + start = first(inds) + stop = last(inds) + offset = start - firstindex(v) + ntuple(Val{nfields(t)}()) do i + if i < start || i > stop + getfield(t, i) + else + v[i - offset] + end + end +end + # efficiently grow an array _growbeg!(a::Vector, delta::Integer) = @@ -1145,6 +1203,63 @@ function _append!(a, ::IteratorSize, iter) a end +""" + insert(a::AbstractVector, index::Integer, item) + +Returns a copy of `a` with `item` inserted at `index`. + +See also: [`insert!`](@ref), [`deleteat`](@ref), [`delete`](@ref) + +# Examples +```jldoctest +julia> A = Any[1, 2, 3] +3-element Vector{Any}: + 1 + 2 + 3 + +julia> insert(A, 3, "here") == [1, 2, "here", 3] +true + +julia> A == [1, 2, 3] +true +```` +""" +function insert(src::AbstractVector, index::Integer, item) + src_idx = firstindex(src) + src_end = lastindex(src) + if index == (src_end + 1) + dst = similar(src, promote_type(eltype(src), typeof(item)), length(src) + 1) + for dst_idx in eachindex(dst) + @inbounds if isassigned(src, src_idx) + @inbounds dst[dst_idx] = src[src_idx] + end + src_idx += 1 + end + @inbounds dst[end] = item + else + @boundscheck (src_end < index || index < src_idx) && throw(BoundsError(src, index)) + dst = similar(src, promote_type(eltype(src), typeof(item)), length(src) + 1) + dst_idx = firstindex(dst) + while src_idx < index + @inbounds if isassigned(src, src_idx) + @inbounds dst[dst_idx] = src[src_idx] + end + dst_idx += 1 + src_idx += 1 + end + setindex!(dst, item, dst_idx) + dst_idx += 1 + for i in src_idx:src_end + @inbounds if isassigned(src, i) + @inbounds dst[dst_idx] = src[i] + end + dst_idx += 1 + end + end + dst +end + """ prepend!(a::Vector, collections...) -> collection @@ -1633,6 +1748,117 @@ function deleteat!(a::Vector, inds::AbstractVector{Bool}) return a end +""" + deleteat(a::Vector, i::Integer) + +Remove the item at the given `i` and return the modified `a`. Subsequent items +are shifted to fill the resulting gap. + +See also: [`deleteat!`](@ref), [`delete`](@ref), [`insert`](@ref) + +# Examples +```jldoctest +julia> x = [6, 5, 4] +3-element Vector{Int64}: + 6 + 5 + 4 + +julia> deleteat(x, 2) == [6, 4] +true + +julia> deleteat(x, [2, 3]) == [6] +true + +julia> x == [6, 5, 4] +true +``` +""" +function deleteat(src::AbstractVector, i::Integer) + src_idx = firstindex(src) + src_end = lastindex(src) + src_idx <= i <= src_end || throw(BoundsError(src, i)) + dst = similar(src, length(src) - 1) + dst_idx = firstindex(dst) + while src_idx <= src_end + if src_idx != i + @inbounds isassigned(src, src_idx) && setindex!(dst, src[src_idx], dst_idx) + dst_idx += 1 + end + src_idx += 1 + end + dst +end +function deleteat(src::AbstractVector, inds::AbstractVector{Bool}) + n = length(src) + length(inds) == n || throw(BoundsError(src, inds)) + dst = similar(src, n - count(inds)) + dst_idx = firstindex(dst) + src_idx = firstindex(src) + for index in inds + if !index + @inbounds isassigned(src, src_idx) && setindex!(dst, src[src_idx], dst_idx) + dst_idx += 1 + end + src_idx += 1 + end + dst +end +function deleteat(src::AbstractVector, inds::AbstractUnitRange{<:Integer}) + srcinds = eachindex(src) + N = length(srcinds) + dst = similar(src, N - length(inds)) + src_idx = first(srcinds) + dst_idx = firstindex(dst) + while src_idx < first(inds) + @inbounds isassigned(src, src_idx) && setindex!(dst, src[src_idx], dst_idx) + src_idx += 1 + dst_idx += 1 + end + src_idx = last(inds) + 1 + while src_idx <= N + @inbounds isassigned(src, src_idx) && setindex!(dst, src[src_idx], dst_idx) + src_idx += 1 + dst_idx += 1 + end + dst +end +function deleteat(src::AbstractVector, inds) + itr = iterate(inds) + if itr === nothing + copy(src) + else + len = length(src) - length(inds) + # `length(inds) > length(src)` then `inds` has repeated or out of bounds indices + len > 0 || throw(BoundsError(src, inds)) + index, state = itr + dst = similar(src, len) + dst_idx = firstindex(dst) + src_idx = firstindex(src) + src_end = lastindex(src) + src_idx > index && throw(BoundsError(src, inds)) + while true + (src_end < index || index < src_idx) && throw(BoundsError(src, inds)) + while src_idx < index + @inbounds isassigned(src, src_idx) && setindex!(dst, src[src_idx], dst_idx) + dst_idx += 1 + src_idx += 1 + end + src_idx += 1 + itr = iterate(inds, state) + itr === nothing && break + itr[1] <= index && throw(ArgumentError("indices must be unique and sorted")) + index, state = itr + end + while src_idx <= src_end + @inbounds isassigned(src, src_idx) && setindex!(dst, src[src_idx], dst_idx) + dst_idx += 1 + src_idx += 1 + end + dst + end +end + const _default_splice = [] """ diff --git a/base/dict.jl b/base/dict.jl index a4af5cecc24b9..9cf5292cd2d5a 100644 --- a/base/dict.jl +++ b/base/dict.jl @@ -395,7 +395,6 @@ function setindex!(h::Dict{K,Any}, v, key::K) where K return h end - """ get!(collection, key, default) @@ -669,6 +668,30 @@ function delete!(h::Dict, key) return h end +""" + delete(collection, key) + +Create and return a new collection containing all the elements from `collection` except for +the mapping corresponding to `key`. + +Note that this may be particularly expensive when `collection` is large and whose storage +cannot be shared between seperate instances. + +See also: [`delete!`](@ref). + +# Examples +```jldoctest +julia> d = Dict("a"=>1, "b"=>2); + +julia> delete(d, "b") == Dict("a"=>1) +true + +julia> d == Dict("a"=>1, "b"=>2) +true +``` +""" +delete(collection, k) = delete!(copy(collection), k) + function skip_deleted(h::Dict, i) L = length(h.slots) for i = i:L @@ -821,6 +844,39 @@ function get(default::Callable, dict::ImmutableDict, key) return default() end +function delete(d::ImmutableDict{K,V}, key) where {K,V} + if isdefined(d, :parent) + if isequal(d.key, key) + d.parent + else + ImmutableDict{K,V}(delete(d.parent, key), d.key, d.value) + end + else + d + end +end + +function setindex(d::ImmutableDict, v, k) + if isdefined(d, :parent) + if isequal(d.key, k) + d0 = d.parent + v0 = v + k0 = k + else + d0 = setindex(d.parent, v, k) + v0 = d.value + k0 = d.key + end + else + d0 = d + v0 = v + k0 = k + end + K = promote_type(keytype(d0), typeof(k0)) + V = promote_type(valtype(d0), typeof(v0)) + ImmutableDict{K,V}(d0, k0, v0) +end + # this actually defines reverse iteration (e.g. it should not be used for merge/copy/filter type operations) function iterate(d::ImmutableDict{K,V}, t=d) where {K, V} !isdefined(t, :parent) && return nothing diff --git a/base/exports.jl b/base/exports.jl index 266a4aa8038fb..143bdfca03d7d 100644 --- a/base/exports.jl +++ b/base/exports.jl @@ -492,6 +492,7 @@ export # dequeues append!, insert!, + insert, pop!, popat!, prepend!, @@ -512,7 +513,9 @@ export count!, count, delete!, + delete, deleteat!, + deleteat, keepat!, eltype, empty!, diff --git a/base/namedtuple.jl b/base/namedtuple.jl index c994cd977be08..3a789e20ef682 100644 --- a/base/namedtuple.jl +++ b/base/namedtuple.jl @@ -381,8 +381,43 @@ julia> Base.setindex(nt, "a", :a) (a = "a",) ``` """ -function setindex(nt::NamedTuple, v, idx::Symbol) - merge(nt, (; idx => v)) +setindex(nt::NamedTuple, v, idx::Symbol) = merge(nt, (; idx => v)) +function setindex(nt::NamedTuple, vs, idxs::AbstractVector{Symbol}) + length(vs) == length(idxs) || throw_setindex_mismatch(v, idxs) + merge(nt, (; [i => v for (i,v) in zip(idxs, vs)]...)) +end + +""" + delete(nt::NamedTuple, key::Symbol) + delete(nt::NamedTuple, key::Integer) + +Constructs a new `NamedTuple` with the field corresponding to `key` removed. +If no field corresponds to `key`, `nt` is returned. + +See also: [`delete!`](@ref), [`deleteat`](@ref) + +# Examples +```jldoctest +julia> nt = (a = 1, b = 2, c = 3) +(a = 1, b = 2, c = 3) + +julia> delete(nt, :b) +(a = 1, c = 3) + +julia> delete(nt, 2) +(a = 1, c = 3) + +julia> delete(nt, :z) +(a = 1, b = 2, c = 3) +``` +""" +function delete(nt::NamedTuple, i::Symbol) + fidx = fieldindex(typeof(nt), i, false) + fidx === 0 ? nt : unsafe_delete(nt, fidx) +end +delete(nt::NamedTuple, i::Integer) = (i < 1 || i > length(nt)) ? nt : unsafe_delete(nt, i) +function unsafe_delete(nt::NamedTuple{names}, i::Integer) where {names} + NamedTuple{_deleteat(names, i)}(_deleteat(Tuple(nt), i)) end """ diff --git a/base/tuple.jl b/base/tuple.jl index 689645b35fcbb..e22d6094215e4 100644 --- a/base/tuple.jl +++ b/base/tuple.jl @@ -38,7 +38,7 @@ get(f::Callable, t::Tuple, i::Integer) = i in 1:length(t) ? getindex(t, i) : f() # returns new tuple; N.B.: becomes no-op if `i` is out-of-bounds """ - setindex(c::Tuple, v, i::Integer) + setindex(x::Tuple, v, i::Integer) Creates a new tuple similar to `x` with the value at index `i` set to `v`. Throws a `BoundsError` when out of bounds. @@ -54,12 +54,66 @@ function setindex(x::Tuple, v, i::Integer) @inline _setindex(v, i, x...) end - function _setindex(v, i::Integer, args::Vararg{Any,N}) where {N} @inline return ntuple(j -> ifelse(j == i, v, args[j]), Val{N}()) end +""" + deleteat(x::Tuple, i::Integer) + +Creates a new tuple similar to `x` with the value at index `i` removed. +Throws a `BoundsError` when out of bounds. + +See also: [`deleteat!`](@ref), [`delete`](@ref) + +# Examples +```jldoctest +julia> deleteat((1, 2, 3), 3) == (1, 2) +true + +julia> deleteat((1, 2, 3), 4) +ERROR: BoundsError: attempt to access Tuple{Int64, Int64, Int64} at index [4] +[...] +``` +""" +function deleteat(x::Tuple, i::Integer) + @boundscheck checkindex(Bool, eachindex(x), i) || throw(BoundsError(x, i)) + _deleteat(x, i) +end +function _deleteat(x::Tuple{Vararg{Any,N}}, i::Integer) where {N} + @inline + ntuple(j -> j < i ? getfield(x, j) : getfield(x, j + 1), Val{N-1}()) +end + +""" + deleteat(t::Tuple, inds) + +Return a new tuple without the itmes at the indices given by `inds`. + +# Examples + +```jldoctest +julia> x = (6, 5, 4); + +julia> deleteat(x, [2, 3]) +(6,) +``` +""" +deleteat(t::Tuple, inds) = (deleteat!(Any[t...], inds)...,) + +function insert(x::Tuple{Vararg{Any,N}}, index::Integer, item) where {N} + @boundscheck 1 <= index <= (length(x) + 1) || throw(BoundsError(x, index)) + ntuple(Val{N+1}()) do j + if j == index + item + elseif j < index + getfield(x, j) + else + getfield(x, j - 1) + end + end +end ## iterating ## diff --git a/test/arrayops.jl b/test/arrayops.jl index c2698b3c70a90..b846e74627a83 100644 --- a/test/arrayops.jl +++ b/test/arrayops.jl @@ -3075,3 +3075,123 @@ end @test c + zero(c) == c end end + +@testset "deleteat" begin + a = [1:10;] + ainds = eachindex(a) + for idx in Any[1, 2, 5, 9, 10, 1:0, 2:1, 1:1, 2:2, 1:2, 2:4, 9:8, 10:9, 9:9, 10:10, + 8:9, 9:10, 6:9, 7:10] + + a_remaining = setdiff(ainds, idx) + # integer indexing with AbstractArray + @test deleteat(a, idx) == a_remaining + + # integer indexing with non-AbstractArray iterable + @test deleteat(a, (i for i in idx)) == a_remaining + + # logical indexing + @test deleteat(a, map(in(idx), ainds)) == a_remaining + end + @test deleteat(a, 11:10) == [1:10;] + @test deleteat(a, [1,3,5,7:10...]) == [2,4,6] + @test_throws BoundsError deleteat(a, 13) + @test_throws BoundsError deleteat(a, [1,13]) + @test_throws ArgumentError deleteat(a, [3,2]) # not sorted + @test_throws BoundsError deleteat(a, 5:20) + @test_throws BoundsError deleteat(a, Bool[]) + @test_throws BoundsError deleteat(a, [true]) + @test_throws BoundsError deleteat(a, falses(11)) + @test_throws BoundsError deleteat(a, [0]) + + @test_throws BoundsError deleteat([], 1) + @test_throws BoundsError deleteat([], [1]) + @test_throws BoundsError deleteat([], [2]) + @test deleteat([], []) == [] + @test deleteat([], Bool[]) == [] + let a = Vector{Any}(undef, 2) + a[1] = 1 + @test isassigned(deleteat(a, [2]), 1) + @test !isassigned(deleteat(a, [1]), 1) + end +end + +==ₜ(_, _) = false +==ₜ(x::T, y::T) where T = x == y + +@testset "setindex" begin + @test @inferred(Base.setindex(Int[1, 2], 3.0, 2)) ==ₜ [1.0, 3.0] + @test @inferred(Base.setindex(Int[1, 2, 3], :two, 2)) ==ₜ [1, :two, 3] + + @testset "no mutation" begin + arr = [1] + @test Base.setindex(arr, 2, 1) ==ₜ [2] + @test arr == [1] + end + + @test @inferred(Base.setindex(Int[1, 2], 3.0, 2)) ==ₜ [1.0, 3.0] + @test @inferred(Base.setindex(Int[1, 2, 3], :two, 2)) ==ₜ [1, :two, 3] + + @testset "no mutation" begin + arr = [1] + @test Base.setindex(arr, 2, 1) ==ₜ [2] + @test arr == [1] + end + + @testset "$(typeof(x))" for x in [ + zeros(3), + falses(3), + ] + y = Base.setindex(x, true, 1) + @test iszero(x) # x is not mutated + @test y[1] == true + @test iszero(x[CartesianIndices(size(x)) .== [CartesianIndex(1)]]) + + y2 = Base.setindex(x, one.(x), :) + @test iszero(x) + @test all(isone, y2) + end + + @testset "$(typeof(x))" for x in [ + zeros(3, 3), + falses(3, 3), + ] + y = Base.setindex(x, true, 1, 1) + @test iszero(x) # x is not mutated + @test y[1, 1] == true + @test iszero(x[CartesianIndices(size(x)) .== [CartesianIndex(1, 1)]]) + + y2 = Base.setindex(x, one.(x), :, :) + @test iszero(x) + @test all(isone, y2) + end + + @testset "$(typeof(x))" for x in [ + zeros(3, 3, 3), + falses(3, 3, 3), + ] + y = Base.setindex(x, true, 1, 1, 1) + @test iszero(x) # x is not mutated + @test y[1, 1, 1] == true + @test iszero(x[CartesianIndices(size(x)) .== [CartesianIndex(1, 1, 1)]]) + + y2 = Base.setindex(x, one.(x), :, :, :) + @test iszero(x) + @test all(isone, y2) + end + + @test_throws DimensionMismatch Base.setindex((1, 2, 3), [10, 11], 3:3) + @test_throws BoundsError Base.setindex((1, 2, 3), [10, 11], 3:4) +end + +@testset "insert" begin + v = Int[1, 2] + @test @inferred(insert(v, 1, :two)) ==ₜ [:two, 1, 2] + @test @inferred(insert(v, 2, 3.0)) ==ₜ [1.0, 3.0, 2.0] + @test @inferred(insert(v, 3, 3)) ==ₜ [1, 2, 3] + + @test insert(v, 1, "here") == ["here", 1, 2] + @test insert(v, 2, "here") == [1, "here", 2] + @test insert(v, 3, "here") == [1, 2, "here"] + @test_throws BoundsError insert(v, 0, 5) + @test_throws BoundsError insert(v, 0, 5) +end diff --git a/test/dict.jl b/test/dict.jl index 65f8939bc6dfc..209967862d095 100644 --- a/test/dict.jl +++ b/test/dict.jl @@ -1363,3 +1363,41 @@ end sizehint!(d, 10) @test length(d.slots) < 100 end + +@testset "setindex" begin + ==ₜ(_, _) = false + ==ₜ(x::T, y::T) where T = x == y + + @test @inferred(Base.setindex(Dict(:a=>1, :b=>2), 10, :a)) ==ₜ + Dict(:a=>10, :b=>2) + @test @inferred(Base.setindex(Dict(:a=>1, :b=>2), 3, "c")) ==ₜ + Dict(:a=>1, :b=>2, "c"=>3) + @test @inferred(Base.setindex(Dict(:a=>1, :b=>2), 3.0, :c)) ==ₜ + Dict(:a=>1.0, :b=>2.0, :c=>3.0) + + @testset "no mutation" begin + dict = Dict(:a=>1, :b=>2) + @test Base.setindex(dict, 10, :a) ==ₜ Dict(:a=>10, :b=>2) + @test dict == Dict(:a=>1, :b=>2) + + iddict = IdDict{Any,String}(true => "yes", 1 => "no", 1.0 => "maybe") + @test Base.setindex(iddict, :yes, true) ==ₜ IdDict{Any,Any}(true => :yes, 1 => "no", 1.0 => "maybe") + @test iddict == iddict + end + + d1 = ImmutableDict{Symbol,Int}() + d2 = ImmutableDict(d1, :a => 1) + @test Base.setindex(d1, 1, :a) == d2 + @test Base.setindex(d2, 2, :b) == ImmutableDict(d2, :b => 2) +end + +@testset "delete" begin + @test delete(Dict(:a=>1.0, :b=>2.0, :c=>3.0), :b) == Dict(:a=>1.0, :c=>3.0) + + d1 = ImmutableDict{Symbol,Int}() + d2 = ImmutableDict(d1, :a => 1) + d3 = ImmutableDict(d2, :b => 2) + @test delete(d2, :a) == d1 + @test delete(d2, :c) == d2 + @test delete(d3, :a) == ImmutableDict(d1, :b => 2) +end diff --git a/test/namedtuple.jl b/test/namedtuple.jl index 6c41ab788b733..6f4eb70efc868 100644 --- a/test/namedtuple.jl +++ b/test/namedtuple.jl @@ -305,6 +305,9 @@ let nt0 = NamedTuple(), nt1 = (a=33,), nt2 = (a=0, b=:v) @test Base.setindex(nt1, "value", :a) == (a="value",) @test Base.setindex(nt1, "value", :a) isa NamedTuple{(:a,),<:Tuple{AbstractString}} end +@test Base.setindex((a = 3, b = 1), (1, 2, 3), [:a, :b, :c]) === (a = 1, b = 2, c = 3) + + # @NamedTuple @testset "@NamedTuple" begin diff --git a/test/tuple.jl b/test/tuple.jl index 945590c2dbf4b..f50cf45c5b3b7 100644 --- a/test/tuple.jl +++ b/test/tuple.jl @@ -642,6 +642,13 @@ end f() = Base.setindex((1:1, 2:2, 3:3), 9, 1) @test @inferred(f()) == (9, 2:2, 3:3) + + @test Base.setindex((1,), [2], 1:1) === (2, ) + @test Base.setindex((1, 2, 3, 4), [3, 2], 2:3) === (1, 3, 2, 4) + @test Base.setindex((1, 2, 3, 4), [4, 3, 2, 1], 1:4) === (4, 3, 2, 1) + @test Base.setindex((1,), [2], [1]) === (2, ) + @test Base.setindex((1, 2, 3, 4), [3, 2], [2,3]) === (1, 3, 2, 4) + @test Base.setindex((1, 2, 3, 4), [4, 3, 2, 1], [1,2,3,4]) === (4, 3, 2, 1) end @testset "inferable range indexing with constant values" begin @@ -758,6 +765,11 @@ g42457(a, b) = Base.isequal(a, b) ? 1 : 2.0 # issue #46049: setindex(::Tuple) regression @inferred Base.setindex((1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16), 42, 1) +@testset "insert" begin + @test insert((1,2), 1, "here") == ("here", 1, 2) + @test insert((1,2), 2, "here") == (1, "here", 2) + @test insert((1,2), 3, "here") == (1, 2, "here") +end # issue #47326 function fun1_47326(args...) head..., tail = args @@ -778,4 +790,4 @@ namedtup = (;a=1, b=2, c=3) Tuple{ NamedTuple{(:a, :b), Tuple{Int, Int}}, NamedTuple{(:c,), Tuple{Int}}, - } + } \ No newline at end of file