Skip to content

Instantly share code, notes, and snippets.

@c42f
Last active September 23, 2019 07:37
Show Gist options
  • Save c42f/006cb5de1bfbd6ac8c684d97d60d31f6 to your computer and use it in GitHub Desktop.
Save c42f/006cb5de1bfbd6ac8c684d97d60d31f6 to your computer and use it in GitHub Desktop.
Sketch of lowering for improved exception handling
using Base.Meta
struct ErrorResult{E}
e::E
end
function insert_wraparg!(ex, name)
@assert isexpr(ex, :call) # todo
insert!(ex.args, 2, name)
ex
end
isquoted(ex) = ex isa QuoteNode || isexpr(ex, (:quote, :meta))
function has_throws(ex)
if ex isa Expr
if (ex.head == :call && ex.args[1] == :throw) ||
(ex.head == :macrocall && ex.args[1] == Symbol("@th"))
return true
elseif !isquoted(ex)
return any(has_throws, ex.args)
end
else
return false
end
end
function expand_throws_body!(ex)
if ex isa Expr
if ex.head == :call && ex.args[1] == :throw
# `throw(e)` is implicitly
# `throw(e) throws Exception`
exc = gensym("exc")
mexc = gensym("matched_exc")
ex = quote
$exc = $(ex.args[2])
if $exc isa Exception
$mexc = __exception_matcher__($exc)
if $mexc isa $ErrorResult
return $mexc
else
throw($exc)
end
end
end
elseif ex.head == :macrocall && ex.args[1] == Symbol("@th")
# `@th ex1 throws exc_type` is a standin for "real syntax" like
# `ex1 throws exc_type`
ex1 = ex.args[3]
@assert ex.args[4] == :throws
exc_type = ex.args[5]
res = gensym("res")
matcher = gensym("exc_matcher")
ex = quote
$matcher(e) = e isa $exc_type ? __exception_matcher__(e) : e
$res = $(insert_wraparg!(ex1, matcher))
if $res isa $ErrorResult # TODO (top ErrorResult) ?
return $res
end
$res
end
elseif !isquoted(ex)
map!(expand_throws_body!, ex.args, ex.args)
end
end
ex
end
function argname(ex)
if ex isa Symbol
return ex
end
if isexpr(ex, :(::))
argname(ex.args[1])
elseif isexpr(ex, :(=))
argname(ex.args[1])
else
error("invalid function argument $ex") # TODO more cases...
end
end
function expand_throws!(ex)
@assert isexpr(ex, :function)
@assert isexpr(ex.args[1], :call) # FIXME `where`, etc
callex = ex.args[1]
if has_throws(ex)
# FIXME parameters
shim = Expr(:function, copy(callex),
Expr(:call, callex.args[1], Base.identity, map(argname, callex.args[2:end])...))
insert_wraparg!(ex.args[1], :__exception_matcher__)
expand_throws_body!(ex.args[2])
ex = Expr(:toplevel, ex, shim)
end
ex
end
macro throws(ex)
esc(expand_throws!(ex))
end
#=
function bar()
throw ErrorException("Blah")
end
function foo(x::Int)
if x > 1
y = bar() throws Exception
else
y = 2
end
y + 2
end
function foo(s::String)
"Hi "*s
end
x = 10
try
z = foo(x) throws Exception
@show z
catch exc::ErrorException
@show exc
end
try
z = foo(x)
@show z
catch exc
@show exc
end
foo("A")
# Turns into:
function bar(_match_expected_errors_1)
e = _match_expected_errors_1(ErrorException("Blah"))
if e isa ErrorResult
return e
else
throw(e)
end
end
function foo(_match_expected_errors_1, x::Int)
if x > 1
_match_expected_errors_2(e) = e isa Exception ? _match_expected_errors_1(e) : e
y = bar(_match_expected_errors_2)
if y isa ErrorResult
return y
end
else
y = 2
end
y + 2
end
@inline foo(x::Int) = foo(identity, x)
=#
@throws function bar()
throw(ErrorException("Blah"))
end
@throws function foo(x::Int)
if x > 1
y = @th bar() throws Exception
else
y = 2
end
y + 2
end
function foo(s::String)
"Hi "*s
end
# Don't really need the following definition, as cannot throw
# @inline foo(_match_expected_errors_1, s::String) = foo(s)
# New try form:
#
# x = 10
# try
# z = foo(x) throws Exception
# @show z
# catch exc::ErrorException
# @show exc
# end
x = 2
# catch
_match_expected_errors_0(e) = e isa ErrorException ? ErrorResult(e) : e
# try body
_match_expected_errors_1(e) = e isa Exception ? _match_expected_errors_0(e) : e
z_ = foo(_match_expected_errors_1, x)
if !(z_ isa ErrorResult)
z = z_
@show z
else
exc = z_.e
# catch body
@show exc
end
# @try foo() ErrorException=>1
# Normal try
try
z = foo(x)
@show z
catch exc
@show exc
end
@show foo("A")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment