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

support non symbolic labels #17

Open
wants to merge 1 commit 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
34 changes: 21 additions & 13 deletions src/OptionalArgChecks.jl
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,22 @@ using MacroTools: postwalk
export @mark, @skip, @unsafe_skipargcheck

# reexport @argcheck and @check
using ArgCheck: @argcheck, @check, LABEL_ARGCHECK
using ArgCheck: @argcheck, @check, LABEL_ARGCHECK, ArgCheck
export LABEL_ARGCHECK
export @argcheck, @check

function eval_label(label)
try
return Main.eval(label)
catch err
msg = """Cannot evaluate $label
Labels are evaluated globally in the `Main` module.
"""
@error msg
rethrow(err)
end
end

"""
@mark label ex

Expand All @@ -17,7 +30,7 @@ Marks `ex` as an optional argument check, so when a function is called via

```jldoctest
julia> function half(x::Integer)
@mark check_even iseven(x) || throw(DomainError(x, "x has to be an even number"))
@mark :check_even iseven(x) || throw(DomainError(x, "x has to be an even number"))
return x ÷ 2
end
half (generic function with 1 method)
Expand All @@ -30,12 +43,12 @@ ERROR: DomainError with 3:
x has to be an even number
[...]

julia> @skip check_even half(3)
julia> @skip :check_even half(3)
1
```
"""
macro mark(label, ex)
label isa Symbol || error("label has to be a Symbol")
label = eval_label(label)
return Expr(
:block,
Expr(:meta, :begin_optional, label),
Expand Down Expand Up @@ -94,17 +107,10 @@ end
end

function _skip(l, ex, recursive=true)
labels::Vector{Symbol} = if l isa Symbol
[l]
elseif Meta.isexpr(l, :vect)
labels = l.args
else
error("label has to be a name or array of names")
end
return _skip(labels, ex, recursive)
return _skip([l], ex, recursive)
end

function _skip(labels::Vector, ex, recursive)
function _skip(labels::AbstractVector, ex, recursive)
ex = postwalk(ex) do x
if Meta.isexpr(x, :call)
pushfirst!(x.args, Expr(
Expand All @@ -129,10 +135,12 @@ For every function call in `ex`, expressions marked with label `label` (or any o
macro skip end

macro skip(l, ex)
l = eval_label(l)
return _skip(l, ex, true)
end

macro skip(l, ex, r)
l = eval_label(l)
r = parse_recursive(r)
return _skip(l, ex, r)
end
Expand Down
130 changes: 91 additions & 39 deletions test/runtests.jl
Original file line number Diff line number Diff line change
@@ -1,9 +1,60 @@
using Test
using OptionalArgChecks

module A
struct MyLabel end
end
using .A: MyLabel
const alias_AMyLabel = A.MyLabel()

module B
struct MyLabel end
end
struct MyMainLabel end
myone = 1

@testset "non symbolic labels" begin
function f1()
@mark 1 begin
return :executed
end
return :skipped
end
function fMyMainLabel()
@mark MyMainLabel() begin
return :executed
end
return :skipped
end
function fAMyLabel()
@mark A.MyLabel() begin
return :executed
end
return :skipped
end
@test @skip(1, f1()) == :skipped
@test @skip(1.0, f1()) == :skipped
@test @skip(myone,f1()) == :skipped
@test @skip(2-1,f1()) == :skipped
@test @skip(1:3,f1()) == :skipped
@test @skip(2:3,f1()) == :executed
@test @skip(2,f1()) == :executed
@test @skip(MyMainLabel(),f1()) == :executed
@test @skip(:one,f1()) == :executed
@test @skip(A.MyLabel(),f1()) == :executed

@test @skip(A.MyLabel(), fAMyLabel()) == :skipped
@test @skip(MyLabel(), fAMyLabel()) == :skipped
@test @skip(alias_AMyLabel, fAMyLabel()) == :skipped
@test @skip([A.MyLabel(), 1], fAMyLabel()) == :skipped
@test @skip(B.MyLabel(), fAMyLabel()) == :executed
@test @skip(:MyLabel, fAMyLabel()) == :executed
@test @skip(1, fAMyLabel()) == :executed
end

@testset "argcheck" begin
function f(x)
@mark argcheck x<2 && if x == 1
@mark :argcheck x<2 && if x == 1
return "foo"
else
error("Test")
Expand All @@ -12,21 +63,21 @@ using OptionalArgChecks
end

for x in 0:2
@test @skip argcheck f(x) == x + 3
@test @skip :argcheck f(x) == x + 3
end

g(x) = @argcheck x > 0

@test_throws ArgumentError g(-1)
@test @unsafe_skipargcheck(g(-1)) === nothing
@test_throws ArgumentError @skip(argcheck, g(-1)) === nothing
@test_throws ArgumentError @skip(:argcheck, g(-1)) === nothing
@test g(1) === nothing

g(x) = @check x > 0

@test_throws Exception g(-1)
@test @unsafe_skipargcheck(g(-1)) === nothing
@test_throws Exception @skip(argcheck, g(-1)) === nothing
@test_throws Exception @skip(:argcheck, g(-1)) === nothing
@test g(1) === nothing

outer(x) = inner(x)
Expand All @@ -44,50 +95,51 @@ end

@testset "mark skip" begin
function simple()
@mark return1 begin
@mark :return1 begin
return 1
end
return 2
end

@test simple() === 1
@test @skip(does_not_exist, simple()) === 1
@test @skip(return1, simple()) === 2
@test @skip(:does_not_exist, simple()) === 1
@test @skip(:return1, simple()) === 2

@test_throws LoadError eval(:(@skip(2 + 3, simple())))
# @test_throws LoadError eval(:(@skip(2 + 3, simple())))
@skip(2 + 3, simple())

function indirect()
simple()
end

@test indirect() === 1
@test @skip(does_not_exist, indirect()) === 1
@test @skip(return1, indirect()) === 2
@test @skip(:does_not_exist, indirect()) === 1
@test @skip(:return1, indirect()) === 2

function complex()
ret = Int[]
push!(ret,1)
@mark two begin
@mark :two begin
push!(ret,2)
end
push!(ret, 3)
@mark four begin
@mark :four begin
push!(ret, 4)
end
ret
end

@test complex() == 1:4
@test @skip(does_not_exist, complex()) == 1:4
@test @skip(two, complex()) == [1,3,4]
@test @skip(four, complex()) == [1,2,3]
@test @skip([two, four], complex()) == [1,3]
@test @skip(:does_not_exist, complex()) == 1:4
@test @skip(:two, complex()) == [1,3,4]
@test @skip(:four, complex()) == [1,2,3]
@test @skip([:two, :four], complex()) == [1,3]

function nested()
ret = Int[]
@mark nested begin
@mark :nested begin
push!(ret, 1)
@mark nested begin
@mark :nested begin
push!(ret,2)
end
push!(ret,3)
Expand All @@ -97,47 +149,47 @@ end
end

@test nested() == 1:4
@test @skip(nested, nested()) == [4]
@test @skip(:nested, nested()) == [4]

function complex_nested()
ret = Char[]
push!(ret,'a')
@mark bcdg begin
@mark :bcdg begin
push!(ret,'b')
@mark cdh begin
@mark :cdh begin
push!(ret,'c')
@mark bcdg begin
@mark :bcdg begin
push!(ret,'d')
end
end
end
push!(ret,'e')
@mark fgh begin
@mark :fgh begin
push!(ret,'f')
@mark bcdg begin
@mark :bcdg begin
push!(ret,'g')
end
@mark cdh begin
@mark :cdh begin
push!(ret,'h')
end
end
ret
end

@test @skip([], complex_nested()) == 'a':'h'
@test @skip([bcdg], complex_nested()) == collect("aefh")
@test @skip([cdh], complex_nested()) == collect("abefg")
@test @skip([fgh], complex_nested()) == 'a':'e'
@test @skip([bcdg, cdh], complex_nested()) == collect("aef")
@test @skip([fgh, cdh], complex_nested()) == collect("abe")
@test @skip([bcdg, cdh, fgh], complex_nested()) == collect("ae")
@test @skip([:bcdg], complex_nested()) == collect("aefh")
@test @skip([:cdh], complex_nested()) == collect("abefg")
@test @skip([:fgh], complex_nested()) == 'a':'e'
@test @skip([:bcdg, :cdh], complex_nested()) == collect("aef")
@test @skip([:fgh, :cdh], complex_nested()) == collect("abe")
@test @skip([:bcdg, :cdh, :fgh], complex_nested()) == collect("ae")
end

@testset "recursion through function calls" begin
function inner()
ret = Int[]
push!(ret, 2)
@mark three begin
@mark :three begin
push!(ret, 3)
end
ret
Expand All @@ -148,20 +200,20 @@ end
push!(ret, 1)
x = inner()
append!(ret, x)
@mark four begin
@mark :four begin
push!(ret, 4)
end
push!(ret, 5)
ret
end

@test outer() == 1:5
@test @skip(four, outer(), recursive=true) == [1,2,3,5]
@test @skip(four, outer(), recursive=false) == [1,2,3,5]
@test @skip(three, outer(), recursive=true) == [1,2,4,5]
@test @skip(three, outer(), recursive=false) == 1:5
@test @skip([three, four], outer()) == [1,2,5]
@test @skip([three, four], outer(), recursive=false) == [1,2,3,5]
@test @skip(:four, outer(), recursive=true) == [1,2,3,5]
@test @skip(:four, outer(), recursive=false) == [1,2,3,5]
@test @skip(:three, outer(), recursive=true) == [1,2,4,5]
@test @skip(:three, outer(), recursive=false) == 1:5
@test @skip([:three, :four], outer()) == [1,2,5]
@test @skip([:three, :four], outer(), recursive=false) == [1,2,3,5]
end

using Documenter
Expand Down