From 2ade1017ec41f1dff71e140662e894121e83d3e1 Mon Sep 17 00:00:00 2001 From: David Anthoff Date: Mon, 31 Aug 2026 00:34:21 -0700 Subject: [PATCH] Fix test failures across the whole CI matrix MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Every run-tests leg was red. Three of the 13 test items failed, for four independent reasons, none of them caused by the test-item migration. - `ccall(:jl_array_grow_beg, ...)`: the C symbol is gone from Julia 1.11 on. Use `Base._growbeg!`, which wraps it and is present, with the same signature and behaviour, on every version from 1.0.5 to 1.13.0-rc3. - `pushfirst!(X, a, b, c)` reaches us as `prepend!(X, (a, b, c))`, and Base narrowed `_prepend!`'s first argument from `Any` to `Vector` in 1.10, so from 1.10 on it threw a MethodError. Add a general-iterable `prepend!` fallback. - `reverse`/`reverse!` left their positional arguments untyped, which became ambiguous with Base once 1.6 typed its own as `Integer`. Type ours to match. Their docstrings described (start, count) while the bodies always forwarded Base's (start, stop); correct the docs. - `promote_type(Any, DataValue{T})` recursed until the stack overflowed: Base's `>:Union{Missing,Nothing}` rule answers `Any` while ours answers `DataValue{Any}`, so `promote_result` re-asked the same question forever. Add the mirror-image rule so both directions agree, keeping `DataValue{Any}`. The Reduce item compared a DataValueArray reduction against Base's, which is `@simd` and therefore reassociates; the two differ by 1 ULP at N = 2050. This failed on every version including 1.0.5. Draw the test data from integers so every partial sum is exact and the comparisons can stay exact too. Verified: 13/13 items pass on 1.0.5, 1.6.7, 1.10.12, 1.11.9, 1.12.7 and 1.13.0-rc3 — each Base transition above is covered on both sides. Co-Authored-By: Claude Opus 5 --- src/array/datavaluevector.jl | 22 +++++++++++++--------- src/scalar/core.jl | 4 ++++ test/array/test_reduce.jl | 4 +++- 3 files changed, 20 insertions(+), 10 deletions(-) diff --git a/src/array/datavaluevector.jl b/src/array/datavaluevector.jl index c8fbe3a..1e32a42 100644 --- a/src/array/datavaluevector.jl +++ b/src/array/datavaluevector.jl @@ -52,7 +52,7 @@ null then this method inserts a null entry at the beginning of `X`. Returns `X`. """ function Base.pushfirst!(X::DataValueVector, v::DataValue) if isna(v) - ccall(:jl_array_grow_beg, Nothing, (Any, UInt), X.values, 1) + Base._growbeg!(X.values, 1) pushfirst!(X.isna, true) else pushfirst!(X.values, v.value) @@ -200,8 +200,8 @@ collection to `prepend!`. function Base.prepend!(X::DataValueVector, items::AbstractVector) old_length = length(X) nitems = length(items) - ccall(:jl_array_grow_beg, Nothing, (Any, UInt), X.values, nitems) - ccall(:jl_array_grow_beg, Nothing, (Any, UInt), X.isna, nitems) + Base._growbeg!(X.values, nitems) + Base._growbeg!(X.isna, nitems) if X === items copyto!(X, 1, items, nitems + 1, nitems) else @@ -210,6 +210,10 @@ function Base.prepend!(X::DataValueVector, items::AbstractVector) return X end +# `pushfirst!(X, a, b, c)` reaches us as `prepend!(X, (a, b, c))`. Base's fallback for that +# only handles `Vector`, so collect any other iterable into one first. +Base.prepend!(X::DataValueVector, items) = prepend!(X, [x for x in items]) + """ sizehint!(X::DataValueVector, newsz::Integer) @@ -246,11 +250,11 @@ end """ reverse!(X::DataValueVector, [s], [n]) -Modify `X` by reversing the first `n` elements starting at index `s` -(inclusive). If unspecified, `s` and `n` will default to `1` and `length(X)`, +Modify `X` by reversing the elements from index `s` to index `n` (both +inclusive). If unspecified, `s` and `n` will default to `1` and `length(X)`, respectively. """ -function Base.reverse!(X::DataValueVector, s=1, n=length(X)) +function Base.reverse!(X::DataValueVector, s::Integer=1, n::Integer=length(X)) if isbitstype(eltype(X)) || !any(isna, X) reverse!(X.values, s, n) reverse!(X.isna, s, n) @@ -276,11 +280,11 @@ end """ reverse(X::DataValueVector, [s], [n]) -Return a copy of `X` with the first `n` elements starting at index `s` -(inclusive) reversed. If unspecified, `s` and `n` will default to `1` and +Return a copy of `X` with the elements from index `s` to index `n` (both +inclusive) reversed. If unspecified, `s` and `n` will default to `1` and `length(X)`, respectively. """ -function Base.reverse(X::DataValueVector, s=1, n=length(X)) +function Base.reverse(X::DataValueVector, s::Integer=1, n::Integer=length(X)) return reverse!(copy(X), s, n) end diff --git a/src/scalar/core.jl b/src/scalar/core.jl index 30c47f9..acb4305 100644 --- a/src/scalar/core.jl +++ b/src/scalar/core.jl @@ -44,6 +44,10 @@ Base.convert(::Type{Missing}, ::DataValue{Union{}}) = missing Base.promote_rule(::Type{DataValue{S}}, ::Type{T}) where {S,T} = DataValue{promote_type(S, T)} Base.promote_rule(::Type{DataValue{T}}, ::Type{Any}) where {T} = DataValue{Any} +# Base promotes anything against `Any` to `Any` (see the `>:Union{Missing,Nothing}` +# rule in base/missing.jl). Without the mirror image of the rule above, the two +# directions disagree and `promote_type(Any, DataValue{T})` recurses forever. +Base.promote_rule(::Type{Any}, ::Type{DataValue{T}}) where {T} = DataValue{Any} Base.promote_rule(::Type{DataValue{Union{}}}, ::Type{Any}) = DataValue{Any} Base.promote_rule(::Type{Any}, ::Type{DataValue{Union{}}}) = DataValue{Any} Base.promote_rule(::Type{DataValue{S}}, ::Type{DataValue{T}}) where {S,T} = DataValue{promote_type(S, T)} diff --git a/test/array/test_reduce.jl b/test/array/test_reduce.jl index 103aec5..d3aa11f 100644 --- a/test/array/test_reduce.jl +++ b/test/array/test_reduce.jl @@ -8,7 +8,9 @@ DataValue(5 * x.value)) for N in (10, 2050) - A = rand(N) + # Integer-valued, so every partial sum is exact: Base reduces `X.values` with + # `@simd`, which reassociates, while the DataValueArray reduction cannot. + A = Float64.(rand(1:1000, N)) M = rand(Bool, N) i = rand(1:N) M[i] = true