Skip to content

Commit 49f08cd

Browse files
omusvtjnash
andcommitted
Fix duplicate error when using generator in Dict
Fixes: #33147 Replaces/Closes: #40445 Co-authored-by: Jameson Nash <[email protected]>
1 parent 6e7db14 commit 49f08cd

File tree

5 files changed

+76
-62
lines changed

5 files changed

+76
-62
lines changed

base/abstractdict.jl

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -579,6 +579,55 @@ _tablesz(x::T) where T <: Integer = x < 16 ? T(16) : one(T)<<(top_set_bit(x-one(
579579

580580
TP{K,V} = Union{Type{Tuple{K,V}},Type{Pair{K,V}}}
581581

582+
# validate the contents of this iterator by testing the iteration protocol (isiterable) on it
583+
_throw_dict_kv_error() = throw(ArgumentError("AbstractDict(kv): kv needs to be an iterator of 2-tuples or pairs"))
584+
585+
function grow_to!(dest::AbstractDict, itr)
586+
applicable(iterate, itr) || _throw_dict_kv_error()
587+
y = iterate(itr)
588+
y === nothing && return dest
589+
kv, st = y
590+
applicable(iterate, kv) || _throw_dict_kv_error()
591+
k = iterate(kv)
592+
k === nothing || _throw_dict_kv_error()
593+
k, kvst = k
594+
v = iterate(kv, kvst)
595+
v === nothing || _throw_dict_kv_error()
596+
v, kvst = v
597+
iterate(kv, kvst) === nothing || _throw_dict_kv_error()
598+
if !(dest isa AbstractDict{typeof(k), typeof(v)})
599+
dest = empty(dest, typeof(k), typeof(v))
600+
end
601+
dest[k] = v
602+
return grow_to!(dest, itr, st)
603+
end
604+
605+
function grow_to!(dest::AbstractDict{K,V}, itr, st) where {K, V}
606+
y = iterate(itr, st)
607+
while y !== nothing
608+
kv, st = y
609+
applicable(iterate, kv) || _throw_dict_kv_error()
610+
kst = iterate(kv)
611+
kst === nothing && _throw_dict_kv_error()
612+
k, kvst = kst
613+
vst = iterate(kv, kvst)
614+
vst === nothing && _throw_dict_kv_error()
615+
v, kvst = vst
616+
iterate(kv, kvst) === nothing || _throw_dict_kv_error()
617+
if isa(k, K) && isa(v, V)
618+
dest[k] = v
619+
else
620+
new = empty(dest, promote_typejoin(K, typeof(k)), promote_typejoin(V, typeof(v)))
621+
merge!(new, dest)
622+
new[k] = v
623+
return grow_to!(new, itr, st)
624+
end
625+
y = iterate(itr, st)
626+
end
627+
return dest
628+
end
629+
630+
582631
dict_with_eltype(DT_apply, kv, ::TP{K,V}) where {K,V} = DT_apply(K, V)(kv)
583632
dict_with_eltype(DT_apply, kv::Generator, ::TP{K,V}) where {K,V} = DT_apply(K, V)(kv)
584633
dict_with_eltype(DT_apply, ::Type{Pair{K,V}}) where {K,V} = DT_apply(K, V)()

base/dict.jl

Lines changed: 1 addition & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -114,45 +114,7 @@ const AnyDict = Dict{Any,Any}
114114
Dict(ps::Pair{K,V}...) where {K,V} = Dict{K,V}(ps)
115115
Dict(ps::Pair...) = Dict(ps)
116116

117-
function Dict(kv)
118-
try
119-
dict_with_eltype((K, V) -> Dict{K, V}, kv, eltype(kv))
120-
catch
121-
if !isiterable(typeof(kv)) || !all(x->isa(x,Union{Tuple,Pair}),kv)
122-
throw(ArgumentError("Dict(kv): kv needs to be an iterator of tuples or pairs"))
123-
else
124-
rethrow()
125-
end
126-
end
127-
end
128-
129-
function grow_to!(dest::AbstractDict{K, V}, itr) where V where K
130-
y = iterate(itr)
131-
y === nothing && return dest
132-
((k,v), st) = y
133-
dest2 = empty(dest, typeof(k), typeof(v))
134-
dest2[k] = v
135-
grow_to!(dest2, itr, st)
136-
end
137-
138-
# this is a special case due to (1) allowing both Pairs and Tuples as elements,
139-
# and (2) Pair being invariant. a bit annoying.
140-
function grow_to!(dest::AbstractDict{K,V}, itr, st) where V where K
141-
y = iterate(itr, st)
142-
while y !== nothing
143-
(k,v), st = y
144-
if isa(k,K) && isa(v,V)
145-
dest[k] = v
146-
else
147-
new = empty(dest, promote_typejoin(K,typeof(k)), promote_typejoin(V,typeof(v)))
148-
merge!(new, dest)
149-
new[k] = v
150-
return grow_to!(new, itr, st)
151-
end
152-
y = iterate(itr, st)
153-
end
154-
return dest
155-
end
117+
Dict(kv) = dict_with_eltype((K, V) -> Dict{K, V}, kv, eltype(kv))
156118

157119
empty(a::AbstractDict, ::Type{K}, ::Type{V}) where {K, V} = Dict{K, V}()
158120

base/iddict.jl

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -53,18 +53,7 @@ IdDict(ps::Pair{K}...) where {K} = IdDict{K,Any}(ps)
5353
IdDict(ps::(Pair{K,V} where K)...) where {V} = IdDict{Any,V}(ps)
5454
IdDict(ps::Pair...) = IdDict{Any,Any}(ps)
5555

56-
function IdDict(kv)
57-
try
58-
dict_with_eltype((K, V) -> IdDict{K, V}, kv, eltype(kv))
59-
catch
60-
if !applicable(iterate, kv) || !all(x->isa(x,Union{Tuple,Pair}),kv)
61-
throw(ArgumentError(
62-
"IdDict(kv): kv needs to be an iterator of tuples or pairs"))
63-
else
64-
rethrow()
65-
end
66-
end
67-
end
56+
IdDict(kv) = dict_with_eltype((K, V) -> IdDict{K, V}, kv, eltype(kv))
6857

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

base/weakkeydict.jl

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -54,17 +54,7 @@ WeakKeyDict(ps::Pair{K}...) where {K} = WeakKeyDict{K,Any}(ps)
5454
WeakKeyDict(ps::(Pair{K,V} where K)...) where {V} = WeakKeyDict{Any,V}(ps)
5555
WeakKeyDict(ps::Pair...) = WeakKeyDict{Any,Any}(ps)
5656

57-
function WeakKeyDict(kv)
58-
try
59-
Base.dict_with_eltype((K, V) -> WeakKeyDict{K, V}, kv, eltype(kv))
60-
catch
61-
if !isiterable(typeof(kv)) || !all(x->isa(x,Union{Tuple,Pair}),kv)
62-
throw(ArgumentError("WeakKeyDict(kv): kv needs to be an iterator of tuples or pairs"))
63-
else
64-
rethrow()
65-
end
66-
end
67-
end
57+
WeakKeyDict(kv) = Base.dict_with_eltype((K, V) -> WeakKeyDict{K, V}, kv, eltype(kv))
6858

6959
function _cleanup_locked(h::WeakKeyDict)
7060
if h.dirty

test/dict.jl

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,30 @@ end
162162

163163
# issue #39117
164164
@test Dict(t[1]=>t[2] for t in zip((1,"2"), (2,"2"))) == Dict{Any,Any}(1=>2, "2"=>"2")
165+
166+
@testset "issue #33147" begin
167+
expected = try; Base._throw_dict_kv_error(); catch e; e; end
168+
@test_throws expected Dict(i for i in 1:2)
169+
@test_throws expected Dict(nothing for i in 1:2)
170+
@test_throws expected Dict(() for i in 1:2)
171+
@test_throws expected Dict((i, i, i) for i in 1:2)
172+
@test_throws expected Dict(nothing)
173+
@test_throws expected Dict((1,))
174+
@test_throws expected Dict(1:2)
175+
@test_throws expected Dict(((),))
176+
@test_throws expected IdDict(((),))
177+
@test_throws expected WeakKeyDict(((),))
178+
@test_throws expected IdDict(nothing)
179+
@test_throws expected WeakKeyDict(nothing)
180+
@test Dict(1:0) isa Dict
181+
@test Dict(()) isa Dict
182+
try
183+
Dict(i => error("$i") for i in 1:3)
184+
catch ex
185+
@test ex isa ErrorException
186+
@test length(Base.catch_stack()) == 1
187+
end
188+
end
165189
end
166190

167191
@testset "empty tuple ctor" begin

0 commit comments

Comments
 (0)