Skip to content

Commit 9f5f540

Browse files
authored
remove length from Stateful (#51747)
Stateful iterators do not have a consistent notion of length, as it is continuously changing as elements are removed. As the main purpose of Stateful is to take elements from multiple places, any notion of HaveShape is invalid for those cases, and thus not useful in general. Fix #47790
1 parent b7520d9 commit 9f5f540

File tree

2 files changed

+33
-48
lines changed

2 files changed

+33
-48
lines changed

base/iterators.jl

Lines changed: 12 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -973,7 +973,7 @@ cycle(xs) = Cycle(xs)
973973

974974
eltype(::Type{Cycle{I}}) where {I} = eltype(I)
975975
IteratorEltype(::Type{Cycle{I}}) where {I} = IteratorEltype(I)
976-
IteratorSize(::Type{Cycle{I}}) where {I} = IsInfinite()
976+
IteratorSize(::Type{Cycle{I}}) where {I} = IsInfinite() # XXX: this is false if iterator ever becomes empty
977977

978978
iterate(it::Cycle) = iterate(it.xs)
979979
isdone(it::Cycle) = isdone(it.xs)
@@ -1422,43 +1422,30 @@ julia> sum(a) # Sum the remaining elements
14221422
7
14231423
```
14241424
"""
1425-
mutable struct Stateful{T, VS, N<:Integer}
1425+
mutable struct Stateful{T, VS}
14261426
itr::T
14271427
# A bit awkward right now, but adapted to the new iteration protocol
14281428
nextvalstate::Union{VS, Nothing}
1429-
1430-
# Number of remaining elements, if itr is HasLength or HasShape.
1431-
# if not, store -1 - number_of_consumed_elements.
1432-
# This allows us to defer calculating length until asked for.
1433-
# See PR #45924
1434-
remaining::N
14351429
@inline function Stateful{<:Any, Any}(itr::T) where {T}
1436-
itl = iterlength(itr)
1437-
new{T, Any, typeof(itl)}(itr, iterate(itr), itl)
1430+
return new{T, Any}(itr, iterate(itr))
14381431
end
14391432
@inline function Stateful(itr::T) where {T}
14401433
VS = approx_iter_type(T)
1441-
itl = iterlength(itr)
1442-
return new{T, VS, typeof(itl)}(itr, iterate(itr)::VS, itl)
1434+
return new{T, VS}(itr, iterate(itr)::VS)
14431435
end
14441436
end
14451437

1446-
function iterlength(it)::Signed
1447-
if IteratorSize(it) isa Union{HasShape, HasLength}
1448-
return length(it)
1449-
else
1450-
-1
1451-
end
1438+
function reset!(s::Stateful)
1439+
setfield!(s, :nextvalstate, iterate(s.itr)) # bypass convert call of setproperty!
1440+
return s
14521441
end
1453-
1454-
function reset!(s::Stateful{T,VS}, itr::T=s.itr) where {T,VS}
1442+
function reset!(s::Stateful{T}, itr::T) where {T}
14551443
s.itr = itr
1456-
itl = iterlength(itr)
1457-
setfield!(s, :nextvalstate, iterate(itr))
1458-
s.remaining = itl
1459-
s
1444+
reset!(s)
1445+
return s
14601446
end
14611447

1448+
14621449
# Try to find an appropriate type for the (value, state tuple),
14631450
# by doing a recursive unrolling of the iteration protocol up to
14641451
# fixpoint.
@@ -1480,7 +1467,6 @@ end
14801467

14811468
Stateful(x::Stateful) = x
14821469
convert(::Type{Stateful}, itr) = Stateful(itr)
1483-
14841470
@inline isdone(s::Stateful, st=nothing) = s.nextvalstate === nothing
14851471

14861472
@inline function popfirst!(s::Stateful)
@@ -1490,8 +1476,6 @@ convert(::Type{Stateful}, itr) = Stateful(itr)
14901476
else
14911477
val, state = vs
14921478
Core.setfield!(s, :nextvalstate, iterate(s.itr, state))
1493-
rem = s.remaining
1494-
s.remaining = rem - typeof(rem)(1)
14951479
return val
14961480
end
14971481
end
@@ -1501,20 +1485,10 @@ end
15011485
return ns !== nothing ? ns[1] : sentinel
15021486
end
15031487
@inline iterate(s::Stateful, state=nothing) = s.nextvalstate === nothing ? nothing : (popfirst!(s), nothing)
1504-
IteratorSize(::Type{<:Stateful{T}}) where {T} = IteratorSize(T) isa HasShape ? HasLength() : IteratorSize(T)
1488+
IteratorSize(::Type{<:Stateful{T}}) where {T} = IteratorSize(T) isa IsInfinite ? IsInfinite() : SizeUnknown()
15051489
eltype(::Type{<:Stateful{T}}) where {T} = eltype(T)
15061490
IteratorEltype(::Type{<:Stateful{T}}) where {T} = IteratorEltype(T)
15071491

1508-
function length(s::Stateful)
1509-
rem = s.remaining
1510-
# If rem is actually remaining length, return it.
1511-
# else, rem is number of consumed elements.
1512-
if rem >= 0
1513-
rem
1514-
else
1515-
length(s.itr) - (typeof(rem)(1) - rem)
1516-
end
1517-
end
15181492
end # if statement several hundred lines above
15191493

15201494
"""

test/iterators.jl

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -853,8 +853,10 @@ end
853853
v, s = iterate(z)
854854
@test Base.isdone(z, s)
855855
end
856-
# Stateful wrapping mutable iterators of known length (#43245)
857-
@test length(Iterators.Stateful(Iterators.Stateful(1:5))) == 5
856+
# Stateful does not define length
857+
let s = Iterators.Stateful(Iterators.Stateful(1:5))
858+
@test_throws MethodError length(s)
859+
end
858860
end
859861

860862
@testset "pair for Svec" begin
@@ -866,6 +868,10 @@ end
866868
@testset "inference for large zip #26765" begin
867869
x = zip(1:2, ["a", "b"], (1.0, 2.0), Base.OneTo(2), Iterators.repeated("a"), 1.0:0.2:2.0,
868870
(1 for i in 1:2), Iterators.Stateful(["a", "b", "c"]), (1.0 for i in 1:2, j in 1:3))
871+
@test Base.IteratorSize(x) isa Base.SizeUnknown
872+
x = zip(1:2, ["a", "b"], (1.0, 2.0), Base.OneTo(2), Iterators.repeated("a"), 1.0:0.2:2.0,
873+
(1 for i in 1:2), Iterators.cycle(Iterators.Stateful(["a", "b", "c"])), (1.0 for i in 1:2, j in 1:3))
874+
@test Base.IteratorSize(x) isa Base.HasLength
869875
@test @inferred(length(x)) == 2
870876
z = Iterators.filter(x -> x[1] >= 1, x)
871877
@test @inferred(eltype(z)) <: Tuple{Int,String,Float64,Int,String,Float64,Any,String,Any}
@@ -874,20 +880,20 @@ end
874880
end
875881

876882
@testset "Stateful fix #30643" begin
877-
@test Base.IteratorSize(1:10) isa Base.HasShape
883+
@test Base.IteratorSize(1:10) isa Base.HasShape{1}
878884
a = Iterators.Stateful(1:10)
879-
@test Base.IteratorSize(a) isa Base.HasLength
880-
@test length(a) == 10
885+
@test Base.IteratorSize(a) isa Base.SizeUnknown
886+
@test !Base.isdone(a)
881887
@test length(collect(a)) == 10
882-
@test length(a) == 0
888+
@test Base.isdone(a)
883889
b = Iterators.Stateful(Iterators.take(1:10,3))
884-
@test Base.IteratorSize(b) isa Base.HasLength
885-
@test length(b) == 3
890+
@test Base.IteratorSize(b) isa Base.SizeUnknown
891+
@test !Base.isdone(b)
886892
@test length(collect(b)) == 3
887-
@test length(b) == 0
893+
@test Base.isdone(b)
888894
c = Iterators.Stateful(Iterators.countfrom(1))
889895
@test Base.IteratorSize(c) isa Base.IsInfinite
890-
@test length(Iterators.take(c,3)) == 3
896+
@test !Base.isdone(Iterators.take(c,3))
891897
@test length(collect(Iterators.take(c,3))) == 3
892898
d = Iterators.Stateful(Iterators.filter(isodd,1:10))
893899
@test Base.IteratorSize(d) isa Base.SizeUnknown
@@ -1010,6 +1016,11 @@ end
10101016
@test collect(Iterators.partition(lstrip("01111", '0'), 2)) == ["11", "11"]
10111017
end
10121018

1019+
let itr = (i for i in 1:9) # Base.eltype == Any
1020+
@test first(Iterators.partition(itr, 3)) isa Vector{Any}
1021+
@test collect(zip(repeat([Iterators.Stateful(itr)], 3)...)) == [(1, 2, 3), (4, 5, 6), (7, 8, 9)]
1022+
end
1023+
10131024
@testset "no single-argument map methods" begin
10141025
maps = (tuple, Returns(nothing), (() -> nothing))
10151026
mappers = (Iterators.map, map, foreach)

0 commit comments

Comments
 (0)