From 13266b119ef3376dc48869773a02ab0b6a0c0444 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Tue, 2 Sep 2025 11:32:03 +0200 Subject: [PATCH 1/5] Fix type instability for exponent iterator with no variable --- src/comparison.jl | 29 +++++++++++++++++++++-------- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/src/comparison.jl b/src/comparison.jl index 8d1eba87..07e962af 100644 --- a/src/comparison.jl +++ b/src/comparison.jl @@ -459,11 +459,6 @@ struct ExponentsIterator{M,D<:Union{Nothing,Int},O} ), ) end - if length(object) == 0 && isnothing(maxdegree) - # Otherwise, it will incorrectly think that the iterator is infinite - # while it actually has zero elements - maxdegree = mindegree - end return new{M,typeof(maxdegree),typeof(object)}( object, mindegree, @@ -477,21 +472,39 @@ Base.eltype(::Type{ExponentsIterator{M,D,O}}) where {M,D,O} = O function Base.IteratorSize(::Type{<:ExponentsIterator{M,Nothing}}) where {M} return Base.IsInfinite() end +function Base.IteratorSize(it::ExponentsIterator{M,Nothing}) where {M} + if isempty(it.object) + return Base.HasLength() + end + return Base.IsInfinite() +end function Base.IteratorSize(::Type{<:ExponentsIterator{M,Int}}) where {M} return Base.HasLength() end -function Base.length(it::ExponentsIterator{M,Int}) where {M} - if it.maxdegree < it.mindegree +function _length(it::ExponentsIterator, maxdegree) + if maxdegree < it.mindegree return 0 end - len = binomial(nvariables(it) + it.maxdegree, nvariables(it)) + len = binomial(nvariables(it) + maxdegree, nvariables(it)) if it.mindegree > 0 len -= binomial(nvariables(it) + it.mindegree - 1, nvariables(it)) end return len end +function Base.length(it::ExponentsIterator{M,Int}) where {M} + return _length(it, it.maxdegree) +end + +function Base.length(it::ExponentsIterator{M,Nothing}) where {M} + if isempty(it.object) + return _length(it, it.mindegree) + else + error("The iterator is infinity because `maxdegree` is `nothing`.") + end +end + nvariables(it::ExponentsIterator) = length(it.object) _last_lex_index(n, ::Type{LexOrder}) = n From 4ee75767ecd256bb9c64c89cb0905788fec30de6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Wed, 3 Sep 2025 10:34:54 +0200 Subject: [PATCH 2/5] Add tests --- test/comparison.jl | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/comparison.jl b/test/comparison.jl index bab42246..0aa2f900 100644 --- a/test/comparison.jl +++ b/test/comparison.jl @@ -26,6 +26,9 @@ function test_errors() "Ordering `$M` is not a valid ordering, use `Graded{$M}` instead.", ) @test_throws err ExponentsIterator{M}([0], maxdegree = 2) + exps = ExponentsIterator{LexOrder}([0]) + err = ErrorException("The iterator is infinity because `maxdegree` is `nothing`.") + @test_throws err length(exps) end function test_exponents_iterator() From 388330ec220769a463a1db4c8dfc5a7c0262fbd3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Thu, 4 Sep 2025 09:41:10 +0200 Subject: [PATCH 3/5] Fix format --- test/comparison.jl | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/comparison.jl b/test/comparison.jl index 0aa2f900..439a4a35 100644 --- a/test/comparison.jl +++ b/test/comparison.jl @@ -27,7 +27,9 @@ function test_errors() ) @test_throws err ExponentsIterator{M}([0], maxdegree = 2) exps = ExponentsIterator{LexOrder}([0]) - err = ErrorException("The iterator is infinity because `maxdegree` is `nothing`.") + err = ErrorException( + "The iterator is infinity because `maxdegree` is `nothing`.", + ) @test_throws err length(exps) end From 51bb7f036ae2b69d856cb44ac163fed9971796d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Thu, 4 Sep 2025 10:36:15 +0200 Subject: [PATCH 4/5] Switch to SizeUnknown --- src/comparison.jl | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/comparison.jl b/src/comparison.jl index 07e962af..2d8ecee3 100644 --- a/src/comparison.jl +++ b/src/comparison.jl @@ -470,7 +470,11 @@ end Base.eltype(::Type{ExponentsIterator{M,D,O}}) where {M,D,O} = O function Base.IteratorSize(::Type{<:ExponentsIterator{M,Nothing}}) where {M} - return Base.IsInfinite() + # It could be `HasLength` is `IsInfinite` depending on whether `it.object` + # is empty so the size is unknown when we only have access to the type of + # the iterator. The method below gives the correct size when we have access + # to the instance + return Base.SizeUnknown() end function Base.IteratorSize(it::ExponentsIterator{M,Nothing}) where {M} if isempty(it.object) From ef785af89955f21385ac2ef7793cdcdb3d0d292c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Thu, 4 Sep 2025 10:54:10 +0200 Subject: [PATCH 5/5] Ref to Cycle PR --- src/comparison.jl | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/comparison.jl b/src/comparison.jl index 2d8ecee3..a5690f6e 100644 --- a/src/comparison.jl +++ b/src/comparison.jl @@ -469,11 +469,14 @@ struct ExponentsIterator{M,D<:Union{Nothing,Int},O} end Base.eltype(::Type{ExponentsIterator{M,D,O}}) where {M,D,O} = O +# `IteratorSize` returns something different depending on whether it is called +# in an instance or on the type. `Iterators.Cycle` has the same behavior, +# see https://github.com/JuliaLang/julia/pull/54187 function Base.IteratorSize(::Type{<:ExponentsIterator{M,Nothing}}) where {M} # It could be `HasLength` is `IsInfinite` depending on whether `it.object` # is empty so the size is unknown when we only have access to the type of # the iterator. The method below gives the correct size when we have access - # to the instance + # to the instance. return Base.SizeUnknown() end function Base.IteratorSize(it::ExponentsIterator{M,Nothing}) where {M}