Skip to content

Rely on @checked in OverflowContexts and reexport #16

New issue

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

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

Already on GitHub? # to your account

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
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 <tim.holy@gmail.com>"]
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"
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
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 ",
Expand Down
26 changes: 2 additions & 24 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
Expand Down
Loading