From e79f21c6a03f7fe238f019aa5b6d0b1fc649500d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrick=20H=C3=A4cker?= Date: Thu, 10 Sep 2026 04:20:36 +0200 Subject: [PATCH] Fix broadcasting --- src/methods.jl | 2 ++ test/broadcasting.jl | 35 +++++++++++++++++++++++++++++++++++ test/runtests.jl | 1 + 3 files changed, 38 insertions(+) create mode 100644 test/broadcasting.jl diff --git a/src/methods.jl b/src/methods.jl index 13868d9..26f59f8 100644 --- a/src/methods.jl +++ b/src/methods.jl @@ -3,6 +3,8 @@ # Unwrap to the storage value. The macro installs a `storagetypeof(::Type{T}) = ` constant-returning method per emulated type, so this folds to a single `reinterpret`. On Julia >= 1.13, `@emulate` additionally installs a per-type, `range`-annotated `getindex` (see `emulate.jl`) that is strictly more specific and wins dispatch; this abstract method then only serves as the fallback on older Julia (LLVM < 19, where the `range` attribute is unavailable). Base.getindex(x::EmulatedInteger) = reinterpret(x |> typeof |> storagetypeof, x) +Base.broadcastable(x::EmulatedInteger) = Ref(x) + # Value form returns a wider value (the unwrapped storage int); type form returns the storage type. Both routed through the per-type `storagetypeof` trait. Base.widen(x::EmulatedInteger) = x[] Base.widen(::Type{T}) where T<:EmulatedInteger = storagetypeof(T) diff --git a/test/broadcasting.jl b/test/broadcasting.jl new file mode 100644 index 0000000..c87a17d --- /dev/null +++ b/test/broadcasting.jl @@ -0,0 +1,35 @@ +@testset "scalar broadcasting" begin + @emulate Int3 UInt3 Int3_128 UInt3_128 + + @test Int3(2) * Int3[2] == Int3[-4] + @test UInt3(3) * UInt3[3] == UInt3[1] + + for Element in (Int3, UInt3, Int3_128, UInt3_128) + for scalar in (Element(2), typemin(Element), typemax(Element)) + vector = Element[2, 3] + expected = map(value -> scalar * value, vector) + @test Base.broadcastable(scalar)[] === scalar + @test identity.(scalar) === scalar + @test scalar .* scalar === scalar * scalar + @test scalar * vector == expected + @test vector * scalar == expected + @test scalar .* vector == expected + @test vector .* scalar == expected + @test eltype(scalar * vector) === Element + @test eltype(vector * scalar) === Element + @test eltype(scalar .* vector) === Element + @test scalar .+ vector == map(value -> scalar + value, vector) + @test scalar .* (vector .+ scalar) == map(value -> scalar * (value + scalar), vector) + destination = similar(vector) + destination .= scalar .* vector + @test destination == expected + matrix = reshape(vector, 1, 2) + @test scalar * matrix == reshape(expected, 1, 2) + @test matrix * scalar == reshape(expected, 1, 2) + @test scalar * fill(Element(2)) == fill(scalar * Element(2)) + @test eltype(scalar * fill(Element(2))) === Element + @test isempty(scalar * Element[]) + @test eltype(scalar * Element[]) === Element + end + end +end \ No newline at end of file diff --git a/test/runtests.jl b/test/runtests.jl index 1b5bcd4..e81fac2 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -17,6 +17,7 @@ values(x) = x |> fieldvalues |> collect include("comparisons.jl") include("sums.jl") +include("broadcasting.jl") # ============================================================================ # Static analysis