diff --git a/base/generator.jl b/base/generator.jl index 1f981de8dc788..9feecb727c075 100644 --- a/base/generator.jl +++ b/base/generator.jl @@ -83,12 +83,24 @@ This means that most iterators are assumed to implement [`length`](@ref). This trait is generally used to select between algorithms that pre-allocate space for their result, and algorithms that resize their result incrementally. +Most iterators' `IteratorSize` category can be determined from their type. In this case +the generic `IteratorSize(iterator) = IteratorSize(typeof(iterator))` fallback applies. +However, some iterators (e.g. [`Iterators.cycle`](@ref)) have a size category that can only +be determined at runtime. In this case the `IteratorSize(itr)` method will not be type +stable and the `IteratorSize(::Type)` method will return `SizeUnknown`. + ```jldoctest julia> Base.IteratorSize(1:5) Base.HasShape{1}() julia> Base.IteratorSize((2,3)) Base.HasLength() + +julia> Base.IteratorSize(Iterators.cycle(1:5)) +Base.IsInfinite() + +julia> Base.IteratorSize(Iterators.cycle(1:0)) +Base.HasLength() ``` """ IteratorSize(x) = IteratorSize(typeof(x)) diff --git a/base/iterators.jl b/base/iterators.jl index a0b3a6cd4672d..5dd34c18aae72 100644 --- a/base/iterators.jl +++ b/base/iterators.jl @@ -988,7 +988,13 @@ cycle(xs, n::Integer) = flatten(repeated(xs, n)) eltype(::Type{Cycle{I}}) where {I} = eltype(I) IteratorEltype(::Type{Cycle{I}}) where {I} = IteratorEltype(I) -IteratorSize(::Type{Cycle{I}}) where {I} = IsInfinite() # XXX: this is false if iterator ever becomes empty +function IteratorSize(::Type{Cycle{I}}) where I + # TODO: find a better way of communicating the size of a cycle + # IsInfinite() would be false if iterator ever becomes empty + IteratorSize(I) === IsInfinite() ? IsInfinite() : SizeUnknown() +end +IteratorSize(it::Cycle) = isempty(it.xs) ? HasLength() : IsInfinite() +length(it::Cycle) = isempty(it.xs) ? 0 : throw(ArgumentError("Cannot compute length of infinite iterator")) iterate(it::Cycle) = iterate(it.xs) isdone(it::Cycle) = isdone(it.xs) diff --git a/test/iterators.jl b/test/iterators.jl index 3616a17b31d31..e7ae8f0002bae 100644 --- a/test/iterators.jl +++ b/test/iterators.jl @@ -1049,3 +1049,15 @@ end @testset "Iterators docstrings" begin @test isempty(Docs.undocumented_names(Iterators)) end + +@testset "cycle IteratorSize (#53169)" begin + @test Base.IteratorSize(Iterators.cycle(1:3)) == Base.IsInfinite() + @test Base.IteratorSize(Iterators.cycle(1:0)) == Base.HasLength() + @test length(Iterators.cycle(1:0)) == 0 + @test collect(Iterators.cycle(1:0))::Vector{Int} == Int[] + @test Base.IteratorSize(Iterators.cycle(Iterators.Repeated(6))) == Base.IsInfinite() + + @test Base.IteratorSize(typeof(Iterators.cycle(1:3))) == Base.SizeUnknown() + @test Base.IteratorSize(typeof(Iterators.cycle(1:0))) == Base.SizeUnknown() + @test Base.IteratorSize(typeof(Iterators.cycle(Iterators.Repeated(6)))) == Base.IsInfinite() +end