From 1364d673dbb3791b6abf4cc203b0abef5ad9263c Mon Sep 17 00:00:00 2001 From: SimonDanisch Date: Thu, 11 Jun 2026 12:27:41 +0200 Subject: [PATCH 1/3] OffsetInteger: drop mixed-Integer comparison methods, fix sub_with_overflow The mixed OffsetInteger/Integer methods for ==, >=, <=, <, > were redundant: Base's promotion machinery (the promote_rule and convert defined right above) produces identical semantics, exactly like mixed +/-/* always worked here. Each of them invalidated compiled comparison code in unrelated packages whenever GeometryBasics loads - ~1.8k method instances in a Makie session, measured with SnoopCompile (e.g. an HTTP server's whole listener chain recompiles because it contains an abstractly-inferred `>`). Also makes sub_with_overflow on OffsetInteger actually work: the macro interpolation generated bodies calling an unqualified sub_with_overflow that never resolved in this module, so every call has thrown an UndefVarError since the code was written. Now defined (and qualified) via Base.Checked; the mixed-Integer methods stay because Base has no promoting fallback for it. --- src/offsetintegers.jl | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/src/offsetintegers.jl b/src/offsetintegers.jl index f0f0d85c..2fdb7f31 100644 --- a/src/offsetintegers.jl +++ b/src/offsetintegers.jl @@ -67,17 +67,35 @@ for op in (:(+), :(-), :(*), :(/), :div) end end -for op in (:(==), :(>=), :(<=), :(<), :(>), :sub_with_overflow) +# No mixed OffsetInteger/Integer methods for the comparison operators: they +# go through Base's promotion machinery (promote_rule + convert above) with +# identical semantics, just like mixed +/-/* already do. Defining e.g. +# `==(::Integer, ::OffsetInteger)` invalidates compiled comparison code in +# unrelated packages whenever GeometryBasics loads (~1.8k method instances, +# measured via SnoopCompile in a Makie session). +for op in (:(==), :(>=), :(<=), :(<), :(>)) @eval begin function Base.$op(x::OffsetInteger{O}, y::OffsetInteger{O}) where {O} return $op(x.i, y.i) end Base.$op(x::OffsetInteger, y::OffsetInteger) = $op(value(x), value(y)) - Base.$op(x::OffsetInteger, y::Integer) = $op(value(x), y) - Base.$op(x::Integer, y::OffsetInteger) = $op(x, value(y)) end end +# sub_with_overflow has no promoting `(::Integer, ::Integer)` fallback in +# Base, so the mixed methods must stay. (The bodies must be qualified: the +# old `$op(...)` interpolation produced an unqualified `sub_with_overflow` +# call that never resolved in this module, so these methods used to throw +# an UndefVarError when called.) +function Base.Checked.sub_with_overflow(x::OffsetInteger{O}, y::OffsetInteger{O}) where {O} + return Base.Checked.sub_with_overflow(x.i, y.i) +end +function Base.Checked.sub_with_overflow(x::OffsetInteger, y::OffsetInteger) + return Base.Checked.sub_with_overflow(value(x), value(y)) +end +Base.Checked.sub_with_overflow(x::OffsetInteger, y::Integer) = Base.Checked.sub_with_overflow(value(x), y) +Base.Checked.sub_with_overflow(x::Integer, y::OffsetInteger) = Base.Checked.sub_with_overflow(x, value(y)) + Base.promote_rule(::Type{IT}, ::Type{<:OffsetInteger}) where {IT<:Integer} = IT function Base.promote_rule(::Type{OffsetInteger{O1,T1}}, From d86b3fc8230cd68155ba70d52f792c878e3f0c0f Mon Sep 17 00:00:00 2001 From: SimonDanisch Date: Fri, 12 Jun 2026 14:23:41 +0200 Subject: [PATCH 2/3] remove unused sub_with_overflow --- src/offsetintegers.jl | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/src/offsetintegers.jl b/src/offsetintegers.jl index 2fdb7f31..40116a32 100644 --- a/src/offsetintegers.jl +++ b/src/offsetintegers.jl @@ -82,20 +82,6 @@ for op in (:(==), :(>=), :(<=), :(<), :(>)) end end -# sub_with_overflow has no promoting `(::Integer, ::Integer)` fallback in -# Base, so the mixed methods must stay. (The bodies must be qualified: the -# old `$op(...)` interpolation produced an unqualified `sub_with_overflow` -# call that never resolved in this module, so these methods used to throw -# an UndefVarError when called.) -function Base.Checked.sub_with_overflow(x::OffsetInteger{O}, y::OffsetInteger{O}) where {O} - return Base.Checked.sub_with_overflow(x.i, y.i) -end -function Base.Checked.sub_with_overflow(x::OffsetInteger, y::OffsetInteger) - return Base.Checked.sub_with_overflow(value(x), value(y)) -end -Base.Checked.sub_with_overflow(x::OffsetInteger, y::Integer) = Base.Checked.sub_with_overflow(value(x), y) -Base.Checked.sub_with_overflow(x::Integer, y::OffsetInteger) = Base.Checked.sub_with_overflow(x, value(y)) - Base.promote_rule(::Type{IT}, ::Type{<:OffsetInteger}) where {IT<:Integer} = IT function Base.promote_rule(::Type{OffsetInteger{O1,T1}}, From 68a5c7bc3e175bacf27591a2f335ff54fb16d19a Mon Sep 17 00:00:00 2001 From: SimonDanisch Date: Fri, 12 Jun 2026 19:44:27 +0200 Subject: [PATCH 3/3] add some more tests --- test/runtests.jl | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/test/runtests.jl b/test/runtests.jl index 5fb98ded..6bd224c8 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -338,6 +338,45 @@ end @test <(x, x1) end end + + # With a nonzero offset `raw != value`, so this exercises the arithmetic + # and the same-offset comparison method `op(x.i, y.i)` for an offset != 0. + let O = 3 + a = OffsetInteger{O}(2) + b = OffsetInteger{O}(4) + @test GeometryBasics.value(a) == 2 + @test GeometryBasics.raw(a) == 2 + O + @test Base.to_index(a) == 2 + @test -(a) == OffsetInteger{O}(-2) + @test abs(OffsetInteger{O}(-2)) == OffsetInteger{O}(2) + @test +(a, b) == OffsetInteger{O}(6) + @test -(b, a) == OffsetInteger{O}(2) + @test *(a, b) == OffsetInteger{O}(8) + @test div(b, a) == OffsetInteger{O}(2) + @test a == OffsetInteger{O}(2) + @test a != b + @test a < b + @test a <= b + @test b > a + @test b >= a + end + + # Comparing OffsetIntegers with *different* offsets goes through the + # value-based fallback method `op(value(x), value(y))`. + let + a = OffsetInteger{0}(5) # value 5 + b = OffsetInteger{3}(5) # value 5 + c = OffsetInteger{3}(7) # value 7 + @test a == b + @test a <= b + @test a >= b + @test !(a < b) + @test a < c + @test a <= c + @test c > a + @test c >= a + @test a != c + end end @testset "Tests from GeometryTypes" begin