diff --git a/.github/workflows/JET.yml b/.github/workflows/JET.yml index 3de143d..53e46bc 100644 --- a/.github/workflows/JET.yml +++ b/.github/workflows/JET.yml @@ -48,8 +48,6 @@ jobs: julia --project=. --color=yes -e ' using JET, Test using FixedPointNumbers - println("report_package(FixedPointNumbers, toplevel_logger=nothing)") - show(JET.report_package(FixedPointNumbers, toplevel_logger=nothing)) @testset "JET" begin - JET.test_package(FixedPointNumbers; target_modules=(FixedPointNumbers,)) + JET.test_package(FixedPointNumbers) end' diff --git a/src/FixedPointNumbers.jl b/src/FixedPointNumbers.jl index 7bafaca..0b91cf7 100644 --- a/src/FixedPointNumbers.jl +++ b/src/FixedPointNumbers.jl @@ -67,6 +67,17 @@ wrapping_rem(x::Real, ::Type{X}) where {X <: FixedPoint} = _rem(x, X) saturating_rem(x::Real, ::Type{X}) where {X <: FixedPoint} = _rem(x, X) checked_rem(x::Real, ::Type{X}) where {X <: FixedPoint} = _rem(x, X) +# `Type{X} where {X <: FixedPoint}` also matches `Type{Union{}}`. Since Julia 1.10, a method of the form +# `f(..., ::Type{Union{}}, slurp...)` takes precedence over all other methods of `f`, so these calls throw a +# plain `MethodError` instead of an ambiguity error or an error from inside another method. +wrapping_mul(x, ::Type{Union{}}, slurp...) = throw(MethodError(wrapping_mul, (x, Union{}, slurp...))) +saturating_mul(x, ::Type{Union{}}, slurp...) = throw(MethodError(saturating_mul, (x, Union{}, slurp...))) +wrapping_rem(x, ::Type{Union{}}, slurp...) = throw(MethodError(wrapping_rem, (x, Union{}, slurp...))) +saturating_rem(x, ::Type{Union{}}, slurp...) = throw(MethodError(saturating_rem, (x, Union{}, slurp...))) +# `*`, `rem` and the functions of `Base.Checked` belong to Base, so the internal functions they call are guarded +_convert(::Type{Union{}}, slurp...) = throw(MethodError(convert, (Union{}, slurp...))) +_rem(x, ::Type{Union{}}, slurp...) = throw(MethodError(rem, (x, Union{}, slurp...))) + # constructor-style conversions (::Type{X})(x::X) where {X <: FixedPoint} = x (::Type{X})(x::Number) where {X <: FixedPoint} = _convert(X, x) @@ -204,6 +215,7 @@ floattype(::Type{X}) where {T <: LongInts, X <: FixedPoint{T}} = BigFloat floattype(::Type{Complex{T}}) where T = Complex{floattype(T)} floattype(::Type{Base.TwicePrecision{Float64}}) = Float64 # wider would be nice, but hardware support is paramount floattype(::Type{Base.TwicePrecision{T}}) where T<:Union{Float16,Float32} = widen(T) +floattype(::Type{Union{}}, slurp...) = throw(MethodError(floattype, (Union{}, slurp...))) float(x::FixedPoint) = convert(floattype(x), x) @@ -363,13 +375,10 @@ clamp(x::X, lo::X, hi::X) where {X <: FixedPoint} = X(clamp(x.i, lo.i, hi.i), 0) clamp(x, ::Type{X}) where {X <: FixedPoint} = clamp(x, typemin(X), typemax(X)) % X -# Since `FixedPoint` is not an integer type, it is not clear in what type -# `signed` and `unsigned` for `FixedPoint` should return values. They should -# currently throw errors in case we support "unsigned Fixed" or "signed Normed" -# in the future. The following "incomplete" code is necessary for Julia v1.0 -# etc. to prevent accidental conversion to an integer type. -signed(x::X) where {X <: FixedPoint} = signed(X)(signed(x.i), 0) -unsigned(x::X) where {X <: FixedPoint} = unsigned(X)(unsigned(x.i), 0) +# Throw a `MethodError` so that the generic fallbacks don't convert a `FixedPoint` +# to an integer type. +signed(x::FixedPoint) = throw(MethodError(signed, (x,))) +unsigned(x::FixedPoint) = throw(MethodError(unsigned, (x,))) function sign(x::X) where {T, X <: FixedPoint{T}} T <: Unsigned && return iszero(x) ? x : oneunit(X) @@ -614,6 +623,7 @@ scaledual(::Type{Tdual}, x::Union{Number,AbstractArray{<:Number}}) where Tdual = scaledual(::Type{Tdual}, x::FixedPoint) where Tdual = convert(Tdual, 1/rawone(x)), reinterpret(x) scaledual(::Type{Tdual}, x::AbstractArray{T}) where {Tdual, T <: FixedPoint} = convert(Tdual, 1/rawone(T)), reinterpret(rawtype(T), x) +scaledual(::Type{Union{}}, slurp...) = throw(MethodError(scaledual, (Union{}, slurp...))) @noinline function throw_converterror(::Type{X}, @nospecialize(x)) where X <: FixedPoint nbits = bitwidth(rawtype(X)) @@ -657,7 +667,7 @@ function Random.rand(r::AbstractRNG, ::SamplerType{X}) where X <: FixedPoint end function rand!(r::AbstractRNG, A::Array{X}, ::SamplerType{X}) where {T, X <: FixedPoint{T}} - At = unsafe_wrap(Array, reinterpret(Ptr{T}, pointer(A)), size(A)) + At = unsafe_wrap(Array, Ptr{T}(pointer(A)), size(A)) Random.rand!(r, At, SamplerType{T}()) A end diff --git a/src/fixed.jl b/src/fixed.jl index 212a95f..ce931c4 100644 --- a/src/fixed.jl +++ b/src/fixed.jl @@ -189,10 +189,13 @@ function _trunc(x::Fixed{T,f}) where {T, f} _rawone = oneunit(T) << f reinterpret(Fixed{T,f}, (x.i < 0) & (r != 0) ? t + _rawone : t) end -function _floor(x::Fixed{T,f}) where {T, f} +# The bound `T <: Signed` limits the overlap with the generic `_trunc` for `FixedPoint{<:Unsigned}` to +# `Fixed{Union{}, f}`, which has no values and gets its own method. +function _floor(x::Fixed{T,f}) where {T <: Signed, f} f == bitwidth(T) && x.i < 0 && throw_converterror(Fixed{T,f}, -1) # TODO: remove this line Fixed{T,f}(x.i & intmask(x), 0) end +_floor(x::Fixed{Union{}, f}) where f = throw(MethodError(_floor, (x,))) # COV_EXCL_LINE function _ceil(x::Fixed{T,f}) where {T, f} f == 0 && return x upper = typemax(T) & intmask(x) diff --git a/test/traits.jl b/test/traits.jl index c248b4c..0d135a4 100644 --- a/test/traits.jl +++ b/test/traits.jl @@ -18,3 +18,14 @@ struct MyReal <: Real end @test_skip(@test_throws MethodError floattype(MyReal)) # TODO: eliminate `@test_skipped` when depwarn is eliminated. See #177. end + +@testset "`Union{}` as type argument" begin + @test_throws MethodError floattype(Union{}) + @test_throws MethodError scaledual(Union{}, 0.5N0f8) + @test_throws MethodError 0.5 * Union{} + @test_throws MethodError 0.5 % Union{} + for f in (wrapping_mul, saturating_mul, Base.Checked.checked_mul, + wrapping_rem, saturating_rem, Base.Checked.checked_rem) + @test_throws MethodError f(0.5, Union{}) + end +end