Last active
September 23, 2019 07:37
-
-
Save c42f/006cb5de1bfbd6ac8c684d97d60d31f6 to your computer and use it in GitHub Desktop.
Sketch of lowering for improved exception handling
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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