Skip to content

Commit 813a374

Browse files
author
Andy Ferris
committed
WIP: deprecate iteration on bare Associative.
Users should use `pairs(dict)` or `values(dict)` (or `keys(dict)`) to disambiguate iteration result.
1 parent bd5d5a0 commit 813a374

10 files changed

+97
-69
lines changed

NEWS.md

+4
Original file line numberDiff line numberDiff line change
@@ -738,6 +738,10 @@ Deprecated or removed
738738
* The `sum_kbn` and `cumsum_kbn` functions have been moved to the
739739
[KahanSummation](https://github.com/JuliaMath/KahanSummation.jl) package ([#24869]).
740740

741+
* Iteration (`next`, `eltype`, `in`, `map`, etc) on `Associative`s has been deprecated.
742+
Explicitly nominate `pairs(dict)`, `values(dict)` or `keys(dict)` as appropriate
743+
([#25013]).
744+
741745
Command-line option changes
742746
---------------------------
743747

base/associative.jl

+62-46
Original file line numberDiff line numberDiff line change
@@ -16,20 +16,6 @@ const secret_table_token = :__c782dbf1cf4d6a2e5e3865d7e95634f2e09b5902__
1616

1717
haskey(d::Associative, k) = in(k, keys(d))
1818

19-
function in(p::Pair, a::Associative, valcmp=(==))
20-
v = get(a,p[1],secret_table_token)
21-
if v !== secret_table_token
22-
valcmp(v, p[2]) && return true
23-
end
24-
return false
25-
end
26-
27-
function in(p, a::Associative)
28-
error("""Associative collections only contain Pairs;
29-
Either look for e.g. A=>B instead, or use the `keys` or `values`
30-
function if you are looking for a key or value respectively.""")
31-
end
32-
3319
function summary(t::Associative)
3420
n = length(t)
3521
return string(typeof(t), " with ", n, (n==1 ? " entry" : " entries"))
@@ -44,26 +30,38 @@ struct ValueIterator{T<:Associative}
4430
dict::T
4531
end
4632

47-
summary(iter::T) where {T<:Union{KeySet,ValueIterator}} =
33+
struct PairIterator{T<:Associative}
34+
dict::T
35+
end
36+
37+
function in(p::Pair, a::PairIterator, valcmp=(==))
38+
v = get(a,p[1],secret_table_token)
39+
if v !== secret_table_token
40+
valcmp(v, p[2]) && return true
41+
end
42+
return false
43+
end
44+
45+
summary(iter::T) where {T<:Union{KeySet,ValueIterator,PairIterator}} =
4846
string(T.name, " for a ", summary(iter.dict))
4947

50-
show(io::IO, iter::Union{KeySet,ValueIterator}) = show(io, collect(iter))
48+
show(io::IO, iter::Union{KeySet,ValueIterator,PairIterator}) = show(io, collect(iter))
5149

52-
length(v::Union{KeySet,ValueIterator}) = length(v.dict)
53-
isempty(v::Union{KeySet,ValueIterator}) = isempty(v.dict)
54-
_tt2(::Type{Pair{A,B}}) where {A,B} = B
55-
eltype(::Type{ValueIterator{D}}) where {D} = _tt2(eltype(D))
50+
length(v::Union{KeySet,ValueIterator,PairIterator}) = length(v.dict)
51+
isempty(v::Union{KeySet,ValueIterator,PairIterator}) = isempty(v.dict)
52+
eltype(::Type{ValueIterator{D}}) where {D} = valuetype(D)
53+
eltype(::Type{PairIterator{D}}) where {D} = pairtype(D)
5654

57-
start(v::Union{KeySet,ValueIterator}) = start(v.dict)
58-
done(v::Union{KeySet,ValueIterator}, state) = done(v.dict, state)
55+
start(v::Union{KeySet,ValueIterator,PairIterator}) = start(v.dict)
56+
done(v::Union{KeySet,ValueIterator,PairIterator}, state) = done(v.dict, state)
5957

6058
function next(v::KeySet, state)
61-
n = next(v.dict, state)
59+
n = next(PairIterator(v.dict), state)
6260
n[1][1], n[2]
6361
end
6462

6563
function next(v::ValueIterator, state)
66-
n = next(v.dict, state)
64+
n = next(PairIterator(v.dict), state)
6765
n[1][2], n[2]
6866
end
6967

@@ -136,7 +134,12 @@ This includes arrays, where the keys are the array indices.
136134
"""
137135
pairs(collection) = Generator(=>, keys(collection), values(collection))
138136

139-
pairs(a::Associative) = a
137+
pairs(a::Associative) = PairIterator(a)
138+
139+
function next(a::Associative, i)
140+
stacktrace()
141+
next(PairIterator(a), i)
142+
end
140143

141144
"""
142145
empty(a::Associative, [index_type=keytype(a)], [value_type=valtype(a)])
@@ -155,7 +158,7 @@ empty(a::Associative, ::Type{V}) where {V} = empty(a, keytype(a), V) # Note: thi
155158

156159
function copy(a::Associative)
157160
b = empty(a)
158-
for (k,v) in a
161+
for (k,v) in pairs(a)
159162
b[k] = v
160163
end
161164
return b
@@ -184,7 +187,7 @@ Dict{Int64,Int64} with 3 entries:
184187
"""
185188
function merge!(d::Associative, others::Associative...)
186189
for other in others
187-
for (k,v) in other
190+
for (k,v) in pairs(other)
188191
d[k] = v
189192
end
190193
end
@@ -223,7 +226,7 @@ Dict{Int64,Int64} with 3 entries:
223226
"""
224227
function merge!(combine::Function, d::Associative, others::Associative...)
225228
for other in others
226-
for (k,v) in other
229+
for (k,v) in pairs(other)
227230
d[k] = haskey(d, k) ? combine(d[k], v) : v
228231
end
229232
end
@@ -250,9 +253,9 @@ julia> keytype(Dict(Int32(1) => "foo"))
250253
Int32
251254
```
252255
"""
253-
keytype(::Type{Associative{K,V}}) where {K,V} = K
256+
keytype(::Type{Associative{K, V}}) where {K, V} = K
254257
keytype(a::Associative) = keytype(typeof(a))
255-
keytype(::Type{A}) where {A<:Associative} = keytype(supertype(A))
258+
keytype(::Type{A}) where {A <: Associative} = keytype(supertype(A))
256259

257260
"""
258261
valtype(type)
@@ -265,10 +268,25 @@ julia> valtype(Dict(Int32(1) => "foo"))
265268
String
266269
```
267270
"""
268-
valtype(::Type{Associative{K,V}}) where {K,V} = V
269-
valtype(::Type{A}) where {A<:Associative} = valtype(supertype(A))
271+
valtype(::Type{Associative{K, V}}) where {K, V} = V
272+
valtype(::Type{A}) where {A <: Associative} = valtype(supertype(A))
270273
valtype(a::Associative) = valtype(typeof(a))
271274

275+
"""
276+
pairtype(type)
277+
278+
Get the `Pair` type of an associative collection type. Behaves similarly to [`eltype`](@ref).
279+
280+
# Examples
281+
```jldoctest
282+
julia> pairtype(Dict(Int32(1) => "foo"))
283+
Pair{Int32,String}
284+
```
285+
"""
286+
pairtype(::Type{Associative{K, V}}) where {K, V} = Pair{K, V}
287+
pairtype(::Type{A}) where {A <: Associative} = pairtype(supertype(A))
288+
pairtype(a::Associative) = pairtype(typeof(a))
289+
272290
"""
273291
merge(d::Associative, others::Associative...)
274292
@@ -368,7 +386,7 @@ Dict{Int64,String} with 2 entries:
368386
function filter!(f, d::Associative)
369387
badkeys = Vector{keytype(d)}()
370388
try
371-
for pair in d
389+
for pair in pairs(d)
372390
# don't delete!(d, k) here, since associative types
373391
# may not support mutation during iteration
374392
f(pair) || push!(badkeys, pair.first)
@@ -384,7 +402,7 @@ end
384402

385403
function filter_in_one_pass!(f, d::Associative)
386404
try
387-
for pair in d
405+
for pair in pairs(d)
388406
if !f(pair)
389407
delete!(d, pair.first)
390408
end
@@ -399,7 +417,7 @@ function filter!_dict_deprecation(e, f, d::Associative)
399417
if isa(e, MethodError) && e.f === f
400418
depwarn("In `filter!(f, dict)`, `f` is now passed a single pair instead of two arguments.", :filter!)
401419
badkeys = Vector{keytype(d)}()
402-
for (k,v) in d
420+
for (k,v) in pairs(d)
403421
# don't delete!(d, k) here, since associative types
404422
# may not support mutation during iteration
405423
f(k, v) || push!(badkeys, k)
@@ -435,15 +453,15 @@ function filter(f, d::Associative)
435453
# don't just do filter!(f, copy(d)): avoid making a whole copy of d
436454
df = empty(d)
437455
try
438-
for pair in d
456+
for pair in pairs(d)
439457
if f(pair)
440458
df[pair.first] = pair.second
441459
end
442460
end
443461
catch e
444462
if isa(e, MethodError) && e.f === f
445463
depwarn("In `filter(f, dict)`, `f` is now passed a single pair instead of two arguments.", :filter)
446-
for (k, v) in d
464+
for (k, v) in pairs(d)
447465
if f(k, v)
448466
df[k] = v
449467
end
@@ -455,16 +473,14 @@ function filter(f, d::Associative)
455473
return df
456474
end
457475

458-
eltype(::Type{Associative{K,V}}) where {K,V} = Pair{K,V}
459-
460476
function isequal(l::Associative, r::Associative)
461477
l === r && return true
462478
if isa(l,ObjectIdDict) != isa(r,ObjectIdDict)
463479
return false
464480
end
465481
if length(l) != length(r) return false end
466-
for pair in l
467-
if !in(pair, r, isequal)
482+
for pair in pairs(l)
483+
if !in(pair, pairs(r), isequal)
468484
return false
469485
end
470486
end
@@ -477,8 +493,8 @@ function ==(l::Associative, r::Associative)
477493
return false
478494
end
479495
if length(l) != length(r) return false end
480-
for pair in l
481-
if !in(pair, r, ==)
496+
for pair in pairs(l)
497+
if !in(pair, pairs(r), ==)
482498
return false
483499
end
484500
end
@@ -488,7 +504,7 @@ end
488504
const hasha_seed = UInt === UInt64 ? 0x6d35bb51952d5539 : 0x952d5539
489505
function hash(a::Associative, h::UInt)
490506
hv = hasha_seed
491-
for (k,v) in a
507+
for (k,v) in pairs(a)
492508
hv ⊻= hash(k, hash(v))
493509
end
494510
hash(hv, h)
@@ -599,11 +615,11 @@ _oidd_nextind(a, i) = reinterpret(Int,ccall(:jl_eqtable_nextind, Csize_t, (Any,
599615

600616
start(t::ObjectIdDict) = _oidd_nextind(t.ht, 0)
601617
done(t::ObjectIdDict, i) = (i == -1)
602-
next(t::ObjectIdDict, i) = (Pair{Any,Any}(t.ht[i+1],t.ht[i+2]), _oidd_nextind(t.ht, i+2))
618+
next(t::PairIterator{<:ObjectIdDict}, i) = (Pair{Any,Any}(t.dict.ht[i+1],t.dict.ht[i+2]), _oidd_nextind(t.dict.ht, i+2))
603619

604620
function length(d::ObjectIdDict)
605621
n = 0
606-
for pair in d
622+
for pair in pairs(d)
607623
n+=1
608624
end
609625
n

base/deprecated.jl

+4
Original file line numberDiff line numberDiff line change
@@ -2186,6 +2186,10 @@ end
21862186
@deprecate_moved sum_kbn "KahanSummation"
21872187
@deprecate_moved cumsum_kbn "KahanSummation"
21882188

2189+
# PR #25013
2190+
@deprecate eltype(a::Type{Associative{K,V}}) where {K,V} pairtype(a)
2191+
@deprecate in(p::Pair, a::Associative) in(p, PairIterator(a))
2192+
21892193
# END 0.7 deprecations
21902194

21912195
# BEGIN 1.0 deprecations

base/dict.jl

+12-9
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ function show(io::IO, t::Associative{K,V}) where V where K
4343
if !show_circular(io, t)
4444
first = true
4545
n = 0
46-
for pair in t
46+
for pair in pairs(t)
4747
first || print(io, ',')
4848
first = false
4949
show(recur_io, pair)
@@ -139,9 +139,12 @@ Dict(ps::Pair{K}...) where {K} = Dict{K,Any}(ps)
139139
Dict(ps::(Pair{K,V} where K)...) where {V} = Dict{Any,V}(ps)
140140
Dict(ps::Pair...) = Dict{Any,Any}(ps)
141141

142+
pair_or_eltype(x) = eltype(x)
143+
pair_or_eltype(x::Associative) = pairtype(x)
144+
142145
function Dict(kv)
143146
try
144-
associative_with_eltype((K, V) -> Dict{K, V}, kv, eltype(kv))
147+
associative_with_eltype((K, V) -> Dict{K, V}, kv, pair_or_eltype(kv))
145148
catch e
146149
if !applicable(start, kv) || !all(x->isa(x,Union{Tuple,Pair}),kv)
147150
throw(ArgumentError("Dict(kv): kv needs to be an iterator of tuples or pairs"))
@@ -193,7 +196,7 @@ empty(a::Associative, ::Type{K}, ::Type{V}) where {K, V} = Dict{K, V}()
193196
# conversion between Dict types
194197
function convert(::Type{Dict{K,V}},d::Associative) where V where K
195198
h = Dict{K,V}()
196-
for (k,v) in d
199+
for (k,v) in pairs(d)
197200
ck = convert(K,k)
198201
if !haskey(h,ck)
199202
h[ck] = convert(V,v)
@@ -711,8 +714,8 @@ function start(t::Dict)
711714
return i
712715
end
713716
done(t::Dict, i) = i > length(t.vals)
714-
@propagate_inbounds function next(t::Dict{K,V}, i) where {K,V}
715-
return (Pair{K,V}(t.keys[i],t.vals[i]), skip_deleted(t,i+1))
717+
@propagate_inbounds function next(t::PairIterator{Dict{K,V}}, i) where {K,V}
718+
return (Pair{K,V}(t.dict.keys[i],t.dict.vals[i]), skip_deleted(t.dict,i+1))
716719
end
717720

718721
isempty(t::Dict) = (t.count == 0)
@@ -756,11 +759,11 @@ ImmutableDict
756759
ImmutableDict(KV::Pair{K,V}) where {K,V} = ImmutableDict{K,V}(KV[1], KV[2])
757760
ImmutableDict(t::ImmutableDict{K,V}, KV::Pair) where {K,V} = ImmutableDict{K,V}(t, KV[1], KV[2])
758761

759-
function in(key_value::Pair, dict::ImmutableDict, valcmp=(==))
762+
function in(key_value::Pair, dict::PairIterator{<:ImmutableDict}, valcmp=(==))
760763
key, value = key_value
761764
while isdefined(dict, :parent)
762-
if dict.key == key
763-
valcmp(value, dict.value) && return true
765+
if dict.dict.key == key
766+
valcmp(value, dict.dict.value) && return true
764767
end
765768
dict = dict.parent
766769
end
@@ -792,7 +795,7 @@ end
792795

793796
# this actually defines reverse iteration (e.g. it should not be used for merge/copy/filter type operations)
794797
start(t::ImmutableDict) = t
795-
next(::ImmutableDict{K,V}, t) where {K,V} = (Pair{K,V}(t.key, t.value), t.parent)
798+
next(::PairIterator{ImmutableDict{K,V}}, t) where {K,V} = (Pair{K,V}(t.key, t.value), t.parent)
796799
done(::ImmutableDict, t) = !isdefined(t, :parent)
797800
length(t::ImmutableDict) = count(x->true, t)
798801
isempty(t::ImmutableDict) = done(t, start(t))

base/env.jl

+3-3
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ pop!(::EnvDict, k::AbstractString) = (v = ENV[k]; _unsetenv(k); v)
8181
pop!(::EnvDict, k::AbstractString, def) = haskey(ENV,k) ? pop!(ENV,k) : def
8282
delete!(::EnvDict, k::AbstractString) = (_unsetenv(k); ENV)
8383
setindex!(::EnvDict, v, k::AbstractString) = _setenv(k,string(v))
84-
push!(::EnvDict, k::AbstractString, v) = setindex!(ENV, v, k)
84+
push!(::EnvDict, k::AbstractString, v) = setindex!(ENV, v, k) # Should this be a `Pair`?
8585

8686
if Sys.iswindows()
8787
start(hash::EnvDict) = (pos = ccall(:GetEnvironmentStringsW,stdcall,Ptr{UInt16},()); (pos,pos))
@@ -92,7 +92,7 @@ if Sys.iswindows()
9292
end
9393
return false
9494
end
95-
function next(hash::EnvDict, block::Tuple{Ptr{UInt16},Ptr{UInt16}})
95+
function next(hash::PairIterator{EnvDict}, block::Tuple{Ptr{UInt16},Ptr{UInt16}})
9696
pos = block[1]
9797
blk = block[2]
9898
len = ccall(:wcslen, UInt, (Ptr{UInt16},), pos)
@@ -109,7 +109,7 @@ else # !windows
109109
start(::EnvDict) = 0
110110
done(::EnvDict, i) = (ccall(:jl_environ, Any, (Int32,), i) === nothing)
111111

112-
function next(::EnvDict, i)
112+
function next(::PairIterator{EnvDict}, i)
113113
env = ccall(:jl_environ, Any, (Int32,), i)
114114
if env === nothing
115115
throw(BoundsError())

base/exports.jl

+1
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ export
8282
ObjectIdDict,
8383
OrdinalRange,
8484
Pair,
85+
PairIterator,
8586
PartialQuickSort,
8687
PermutedDimsArray,
8788
QuickSort,

base/inference.jl

+1-1
Original file line numberDiff line numberDiff line change
@@ -5686,7 +5686,7 @@ function remove_redundant_temp_vars!(src::CodeInfo, nargs::Int, sa::ObjectIdDict
56865686
slottypes = src.slottypes
56875687
ssavaluetypes = src.ssavaluetypes
56885688
repls = ObjectIdDict()
5689-
for (v, init) in sa
5689+
for (v, init) in pairs(sa)
56905690
repl = get_replacement(sa, v, init, nargs, slottypes, ssavaluetypes)
56915691
compare = isa(repl, TypedSlot) ? normslot(repl) : repl
56925692
if compare !== v

0 commit comments

Comments
 (0)