diff --git a/Project.toml b/Project.toml index 3be9f49..3514386 100644 --- a/Project.toml +++ b/Project.toml @@ -1,17 +1,19 @@ name = "CheckedArithmetic" uuid = "2c4a1fb8-30c1-4c71-8b84-dff8d59868ee" authors = ["Tim Holy "] -version = "0.2.0" +version = "0.2.1" [deps] CheckedArithmeticCore = "740b204e-26e5-40b1-866a-9c367e60c4b6" Dates = "ade2ca70-3891-5945-98fb-dc099432e06a" LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" +OverflowContexts = "649716ba-0eb1-4560-ace2-251185f55281" Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" [compat] CheckedArithmeticCore = "0.1" julia = "1" +OverflowContexts = "0.2.4" [extras] Pkg = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f" diff --git a/README.md b/README.md index 9dabb37..f3624b0 100644 --- a/README.md +++ b/README.md @@ -32,6 +32,8 @@ will not detect overflow caused by `f`. The [`Base.Checked` module](https://github.com/JuliaLang/julia/blob/master/base/checked.jl) defines numerous checked operations. These can be specialized for custom types. +**Note:** `@checked` originates from [OverflowContexts.jl](https://github.com/JuliaMath/OverflowContexts.jl) as of v0.2.1, which includes other functionality not exposed in this package. + ## `@check` `@check` performs an operation in two different ways, diff --git a/src/CheckedArithmetic.jl b/src/CheckedArithmetic.jl index 56cb814..ee9ae54 100644 --- a/src/CheckedArithmetic.jl +++ b/src/CheckedArithmetic.jl @@ -1,109 +1,22 @@ module CheckedArithmetic +using OverflowContexts using CheckedArithmeticCore import CheckedArithmeticCore: safearg_type, safearg, safeconvert, accumulatortype, acc using Base.Meta: isexpr +using Base.Checked: checked_neg, checked_add, checked_sub, checked_mul, checked_abs +if VERSION ≥ v"1.11-alpha" + using Base.Checked: checked_pow +end + using LinearAlgebra: Factorization, UniformScaling using Random: AbstractRNG using Dates -export @checked, @check +export @check export accumulatortype, acc # re-export - -const op_checked = Dict( - Symbol("unary-") => :(Base.Checked.checked_neg), - :abs => :(Base.Checked.checked_abs), - :+ => :(Base.Checked.checked_add), - :- => :(Base.Checked.checked_sub), - :* => :(Base.Checked.checked_mul), - :÷ => :(Base.Checked.checked_div), - :div => :(Base.Checked.checked_div), - :% => :(Base.Checked.checked_rem), - :rem => :(Base.Checked.checked_rem), - :fld => :(Base.Checked.checked_fld), - :mod => :(Base.Checked.checked_mod), - :cld => :(Base.Checked.checked_cld), - ) - -function replace_op!(expr::Expr, op_map::Dict) - if expr.head == :call - f, len = expr.args[1], length(expr.args) - op = isexpr(f, :.) ? f.args[2].value : f # handle module-scoped functions - if op === :+ && len == 2 # unary + - # no action required - elseif op === :- && len == 2 # unary - - op = get(op_map, Symbol("unary-"), op) - if isexpr(f, :.) - f.args[2].value = op - expr.args[1] = f - else - expr.args[1] = op - end - else # arbitrary call - op = get(op_map, op, op) - if isexpr(f, :.) - f.args[2].value = op - expr.args[1] = f - else - expr.args[1] = op - end - end - for a in Iterators.drop(expr.args, 1) - if isa(a, Expr) - replace_op!(a, op_map) - end - end - else - for a in expr.args - if isa(a, Expr) - replace_op!(a, op_map) - end - end - end - return expr -end - -""" - @checked expr - -Perform all the operations in `expr` using checked arithmetic. - -# Examples - -```jldoctest -julia> 0xff + 0x10 # operation that overflows -0x0f - -julia> @checked 0xff + 0x10 -ERROR: OverflowError: 255 + 16 overflowed for type UInt8 -``` - -You can also wrap method definitions (or blocks of code) in `@checked`: - -```jldoctest -julia> plus(x, y) = x + y; minus(x, y) = x - y -minus (generic function with 1 method) - -julia> @show plus(0xff, 0x10) minus(0x10, 0x20); -plus(0xff, 0x10) = 0x0f -minus(0x10, 0x20) = 0xf0 - -julia> @checked (plus(x, y) = x + y; minus(x, y) = x - y) -minus (generic function with 1 method) - -julia> plus(0xff, 0x10) -ERROR: OverflowError: 255 + 16 overflowed for type UInt8 - -julia> minus(0x10, 0x20) -ERROR: OverflowError: 16 - 32 overflowed for type UInt8 -``` -""" -macro checked(expr) - isa(expr, Expr) || return expr - expr = copy(expr) - return esc(replace_op!(expr, op_checked)) -end +export @checked, checked_neg, checked_add, checked_sub, checked_mul, checked_pow, checked_negsub, checked_abs # re-export macro check(expr, kws...) isexpr(expr, :call) || error("expected :call expression, got ", diff --git a/test/runtests.jl b/test/runtests.jl index 0efdbfb..5111289 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -8,11 +8,6 @@ Pkg.test("CheckedArithmeticCore") @test isempty(detect_ambiguities(CheckedArithmetic, Base, Core)) -@checked begin - plus(x, y) = x + y - minus(x, y) = x - y -end - function sumsquares(A::AbstractArray) s = zero(accumulatortype(eltype(A))) for a in A @@ -23,20 +18,8 @@ end @testset "CheckedArithmetic.jl" begin @testset "@checked" begin - @test @checked(abs(Int8(-2))) === Int8(2) - @test_throws OverflowError @checked(abs(typemin(Int8))) - @test @checked(+2) === 2 - @test @checked(+UInt(2)) === UInt(2) - @test @checked(-2) === -2 - @test_throws OverflowError @checked(-UInt(2)) - @test @checked(0x10 + 0x20) === 0x30 - @test_throws OverflowError @checked(0xf0 + 0x20) - @test @checked(0x30 - 0x20) === 0x10 - @test_throws OverflowError @checked(0x10 - 0x20) - @test @checked(-7) === -7 - @test_throws OverflowError @checked(-UInt(7)) - @test @checked(0x10*0x02) === 0x20 - @test_throws OverflowError @checked(0x10*0x10) + # Julia errors by default, so this is just for security. + # OverflowContexts does not test for this. @test @checked(7 ÷ 2) === 3 @test_throws DivideError @checked(typemin(Int8)÷Int8(-1)) @test @checked(div(0x7, 0x2)) === 0x3 @@ -51,11 +34,6 @@ end @test_throws DivideError @checked(mod(typemin(Int8), Int8(0))) @test @checked(cld(typemax(Int8), Int8(-1))) === -typemax(Int8) @test_throws DivideError @checked(cld(typemin(Int8), Int8(-1))) - - @test plus(0x10, 0x20) === 0x30 - @test_throws OverflowError plus(0xf0, 0x20) - @test minus(0x30, 0x20) === 0x10 - @test_throws OverflowError minus(0x20, 0x30) end @testset "@check" begin