Skip to content

Commit

Permalink
Fix error messages in @ require precondition testing macro
Browse files Browse the repository at this point in the history
With a little hackery we can extract the function name from the internal
`#self#` without the need to pattern match backtraces.

Copies fixes from JuliaWeb/URIs.jl#33
  • Loading branch information
c42f committed Sep 14, 2021
1 parent 03749a4 commit b2f1d9b
Show file tree
Hide file tree
Showing 2 changed files with 30 additions and 26 deletions.
45 changes: 19 additions & 26 deletions src/debug.jl
Original file line number Diff line number Diff line change
Expand Up @@ -25,36 +25,29 @@ end
sprintcompact(x) = sprint(show, x; context=:compact => true)
printlncompact(x) = println(sprintcompact(x))


function method_name(bt)
for f in bt
for i in StackTraces.lookup(f)
n = sprint(StackTraces.show_spec_linfo, i)
if n != "macro expansion"
return n
end
end
end
return "unknown method"
# Get the calling function. See https://github.com/JuliaLang/julia/issues/6733
# (The macro form @__FUNCTION__ is hard to escape correctly, so just us a function.)
function _funcname_expr()
return :($(esc(Expr(:isdefined, Symbol("#self#")))) ? nameof($(esc(Symbol("#self#")))) : nothing)
end

@noinline function precondition_error(msg, bt)
msg = string(method_name(bt), " requires ", msg)
return ArgumentError(msg)
@noinline function precondition_error(msg, calling_funcname)
calling_funcname = calling_funcname === nothing ? "unknown" : calling_funcname
return ArgumentError("$calling_funcname() requires $msg")
end


"""
@require precondition [message]
Throw `ArgumentError` if `precondition` is false.
"""
macro require(condition, msg = string(condition))
esc(:(if ! $condition throw(precondition_error($msg, backtrace())) end))
macro require(condition, msg = "`$condition`")
:(if ! $(esc(condition)) throw(precondition_error($(esc(msg)), $(_funcname_expr()))) end)
end


@noinline function postcondition_error(msg, bt, ls="", l="", rs="", r="")
msg = string(method_name(bt), " failed to ensure ", msg)
@noinline function postcondition_error(msg, calling_funcname, ls="", l="", rs="", r="")
calling_funcname = calling_funcname === nothing ? "unknown" : calling_funcname
msg = "$calling_funcname() failed to ensure $msg"
if ls != ""
msg = string(msg, "\n", ls, " = ", sprint(show, l),
"\n", rs, " = ", sprint(show, r))
Expand All @@ -79,7 +72,7 @@ iscondition(ex) = isa(ex, Expr) &&
@ensure postcondition [message]
Throw `ArgumentError` if `postcondition` is false.
"""
macro ensure(condition, msg = string(condition))
macro ensure(condition, msg = "`$condition`")

if DEBUG_LEVEL[] < 0
return :()
Expand All @@ -88,14 +81,14 @@ macro ensure(condition, msg = string(condition))
if iscondition(condition)
l,r = condition.args[2], condition.args[3]
ls, rs = string(l), string(r)
return esc(quote
if ! $condition
return quote
if ! $(esc(condition))
# FIXME double-execution of condition l and r!
throw(postcondition_error($msg, backtrace(),
$ls, $l, $rs, $r))
throw(postcondition_error($(esc(msg)), $(_funcname_expr()),
$ls, $(esc(l)), $rs, $(esc(r))))
end
end)
end
end

esc(:(if ! $condition throw(postcondition_error($msg, backtrace())) end))
:(if ! $(esc(condition)) throw(postcondition_error($(esc(msg)), $(_funcname_expr()))) end)
end
11 changes: 11 additions & 0 deletions test/utils.jl
Original file line number Diff line number Diff line change
Expand Up @@ -69,3 +69,14 @@ import HTTP.URIs
@test HTTP.ConnectionRequest.getproxy("http", "http://julialang.org/") == "http://user:pass@server:80"
end
end # testset


@testset "debug.jl" begin
function foo(x, y)
HTTP.@require x > 10
HTTP.@ensure y > 10
end

@test_throws ArgumentError("foo() requires `x > 10`") foo(1, 11)
@test_throws AssertionError("foo() failed to ensure `y > 10`\ny = 1\n10 = 10") foo(11, 1)
end

0 comments on commit b2f1d9b

Please sign in to comment.