diff --git a/Project.toml b/Project.toml index 9a4d017..93e6ec6 100644 --- a/Project.toml +++ b/Project.toml @@ -1,15 +1,17 @@ name = "OptionalArgChecks" uuid = "dfbeeb84-381a-44da-9ec9-a723abf299c7" authors = ["Simeon Schaub"] -version = "0.1.0" +version = "0.2.0" [deps] +ArgCheck = "dce04be8-c92d-5529-be00-80e4d2c0e197" IRTools = "7869d1d1-7146-5819-86e3-90919afe41df" MacroTools = "1914dd2f-81c6-5fcd-8719-6d5c9610ff09" [compat] IRTools = "0.3" MacroTools = "0.5" +ArgCheck = "1.1" julia = "1" [extras] diff --git a/docs/make.jl b/docs/make.jl index c3ba44b..a21135e 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -20,4 +20,5 @@ makedocs(; deploydocs(; repo="github.com/simeonschaub/OptionalArgChecks.jl", + push_preview = true, ) diff --git a/docs/src/index.md b/docs/src/index.md index 0b572fc..2f3d661 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -4,8 +4,13 @@ CurrentModule = OptionalArgChecks # OptionalArgChecks -Provides two macros, [`@argcheck`](@ref) and [`@skipargcheck`](@ref) which give users -control over whether to skip argument checking for better performance. +Provides two macros, [`@mark`](@ref) and [`@elide`](@ref) which give users control over +skipping arbitrary code in functions for better performance. + +For convenience, this package also exports [`@argcheck`](https://github.com/jw3126/ArgCheck.jl) +and [`@check`](https://github.com/jw3126/ArgCheck.jl) from the package +[`ArgCheck.jl`](https://github.com/jw3126/ArgCheck.jl) and provides the macro +[`@skipargcheck`](@ref) to skip these checks. ## API diff --git a/src/OptionalArgChecks.jl b/src/OptionalArgChecks.jl index 3f0e62b..0384ca9 100644 --- a/src/OptionalArgChecks.jl +++ b/src/OptionalArgChecks.jl @@ -3,17 +3,21 @@ module OptionalArgChecks using IRTools: @dynamo, IR, recurse!, block, branches, branch! using MacroTools: postwalk -export @argcheck, @skipargcheck +export @mark, @elide, @skipargcheck + +# reexport @argcheck and @check +using ArgCheck: @argcheck, @check +export @argcheck, @check """ - @argcheck ex + @mark label ex Marks `ex` as an optional argument check, so when a function is called via -[`@skipargcheck`](@ref), `ex` will be omitted. +[`@elide`](@ref) with label `label`, `ex` will be omitted. ```jldoctest julia> function half(x::Integer) - @argcheck 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) @@ -26,26 +30,42 @@ ERROR: DomainError with 3: x has to be an even number [...] -julia> @skipargcheck half(3) +julia> @elide check_even half(3) 1 ``` """ -macro argcheck(ex) - return Expr(:block, Expr(:meta, :begin_argcheck), esc(ex), Expr(:meta, :end_argcheck)) +macro mark(label, ex) + label isa Symbol || error("label has to be a Symbol") + return Expr( + :block, + Expr(:meta, :begin_optional, label), + esc(ex), + Expr(:meta, :end_optional, label), + ) +end + +struct ElideCheck{label} + ElideCheck(label::Symbol) = new{label}() end -@dynamo function skipargcheck(x...) +@dynamo function (::ElideCheck{label})(x...) where {label} ir = IR(x...) ir === nothing && return next = iterate(ir) while next !== nothing (x, st), state = next - if Meta.isexpr(st.expr, :meta) && st.expr.args[1] === :begin_argcheck + if Meta.isexpr(st.expr, :meta) && + st.expr.args[1] === :begin_optional && + st.expr.args[2] === label + orig = block(ir, x) delete!(ir, x) (x, st), state = iterate(ir, state) - while !(Meta.isexpr(st.expr, :meta) && st.expr.args[1] === :end_argcheck) + while !(Meta.isexpr(st.expr, :meta) && + st.expr.args[1] === :end_optional && + st.expr.args[2] === label) + delete!(ir, x) (x, st), state = iterate(ir, state) end @@ -64,19 +84,34 @@ end end """ - @skipargcheck ex + @elide label ex -For every function call in `ex`, expressions wrapped in [`@argcheck`](@ref) get omitted -recursively. +For every function call in `ex`, expressions marked with label `label` using the macro +[`@mark`](@ref) get omitted recursively. """ -macro skipargcheck(ex) +macro elide(label, ex) + label isa Symbol || error("label has to be a Symbol") ex = postwalk(ex) do x if Meta.isexpr(x, :call) - pushfirst!(x.args, GlobalRef(@__MODULE__, :skipargcheck)) + pushfirst!(x.args, Expr( + :call, + GlobalRef(@__MODULE__, :ElideCheck), + Expr(:quote, label) + )) end return x end return esc(ex) end +""" + @skipargcheck ex + +Elides argument checks created with [`@argcheck`](@ref) or [`@check`](@ref), provided by the +package `ArgCheck.jl`. Is equivalent to `@elide argcheck ex`. +""" +macro skipargcheck(ex) + return :(@elide argcheck $(esc(ex))) +end + end diff --git a/test/runtests.jl b/test/runtests.jl index 87ec382..eebe9f2 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -2,7 +2,7 @@ using Test using OptionalArgChecks function f(x) - @argcheck x<2 && if x == 1 + @mark argcheck x<2 && if x == 1 return "foo" else error("Test") @@ -14,6 +14,11 @@ for x in 0:2 @test @skipargcheck f(x) == x + 3 end +g(x) = @argcheck x > 0 + +@test_throws ArgumentError g(-1) +@test g(1) === nothing + using Documenter DocMeta.setdocmeta!(OptionalArgChecks, :DocTestSetup, :(using OptionalArgChecks); recursive=true) doctest(OptionalArgChecks; manual = false)