Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rely on @checked in OverflowContexts and reexport #16

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion Project.toml
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
name = "CheckedArithmetic"
uuid = "2c4a1fb8-30c1-4c71-8b84-dff8d59868ee"
authors = ["Tim Holy <[email protected]>"]
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"
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -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,
103 changes: 8 additions & 95 deletions src/CheckedArithmetic.jl
Original file line number Diff line number Diff line change
@@ -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 ",
26 changes: 2 additions & 24 deletions test/runtests.jl
Original file line number Diff line number Diff line change
@@ -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