Skip to content

Commit

Permalink
add try/catch/else (#42211)
Browse files Browse the repository at this point in the history
This PR allows the use of `else` inside try-blocks, which is only taken
if no exception was caught inside `try`. It is still combinable with
`finally` as well.

If an error is thrown inside the `else` block, the current semantics are
that the error does not get caught and is thrown like normal, but the
`finally` block is still run afterwards. This seemed like the most
sensible option to me.

I am not very confident about the implementation of linearization for
`trycatchelse` here, so I would appreciate it if @JeffBezanson could
give this a thorough review, so that I don't miss any edge cases.

I thought we had an issue for this already, but I couldn't find
anything. `else` might also not be the best keyword here, so maybe we
can come up with something clearer. But it of course has the advantage
that it is already a Julia keyword, so we don't need to add a new one.

Co-authored-by: Jeff Bezanson <jeff.bezanson@gmail.com>
  • Loading branch information
simeonschaub and JeffBezanson authored Oct 18, 2021
1 parent cd19e97 commit 0357b2a
Show file tree
Hide file tree
Showing 7 changed files with 146 additions and 28 deletions.
2 changes: 2 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ New language features
* The default behavior of observing `@inbounds` declarations is now an option via `auto` in `--check-bounds=yes|no|auto` ([#41551])
* New function `eachsplit(str)` for iteratively performing `split(str)`.
* ``, ``, and `` are now allowed as identifier characters ([#42314]).
* `try`-blocks can now optionally have an `else`-block which is executed right after the main body only if
no errors were thrown. ([#42211])

Language changes
----------------
Expand Down
5 changes: 4 additions & 1 deletion base/show.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2132,12 +2132,15 @@ function show_unquoted(io::IO, ex::Expr, indent::Int, prec::Int, quote_level::In
elseif head === :line && 1 <= nargs <= 2
show_linenumber(io, args...)

elseif head === :try && 3 <= nargs <= 4
elseif head === :try && 3 <= nargs <= 5
iob = IOContext(io, beginsym=>false)
show_block(iob, "try", args[1], indent, quote_level)
if is_expr(args[3], :block)
show_block(iob, "catch", args[2] === false ? Any[] : args[2], args[3]::Expr, indent, quote_level)
end
if nargs >= 5 && is_expr(args[5], :block)
show_block(iob, "else", Any[], args[5]::Expr, indent, quote_level)
end
if nargs >= 4 && is_expr(args[4], :block)
show_block(iob, "finally", Any[], args[4]::Expr, indent, quote_level)
end
Expand Down
7 changes: 7 additions & 0 deletions src/ast.scm
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,13 @@
"\n"
(indented-block (cdr (cadddr e)) ilvl))
"")
(if (length> e 5)
(let ((els (cadddddr e)))
(if (and (pair? els) (eq? (car els) 'block))
(string (string.rep " " ilvl) "else\n"
(indented-block (cdr els) ilvl))
""))
"")
(if (length> e 4)
(let ((fin (caddddr e)))
(if (and (pair? fin) (eq? (car fin) 'block))
Expand Down
34 changes: 26 additions & 8 deletions src/julia-parser.scm
Original file line number Diff line number Diff line change
Expand Up @@ -1509,29 +1509,33 @@
(let loop ((nxt (peek-token s))
(catchb #f)
(catchv #f)
(finalb #f))
(finalb #f)
(elseb #f))
(take-token s)
(cond
((eq? nxt 'end)
(list* 'try try-block (or catchv '(false))
(or catchb (if finalb '(false) (error "try without catch or finally")))
(if finalb (list finalb) '())))
(cond (elseb (list (or finalb '(false)) elseb))
(finalb (list finalb))
(else '()))))
((and (eq? nxt 'catch)
(not catchb))
(let ((nl (memv (peek-token s) '(#\newline #\;))))
(if (eqv? (peek-token s) #\;)
(take-token s))
(if (memq (require-token s) '(end finally))
(if (memq (require-token s) '(end finally else))
(loop (require-token s)
'(block)
#f
finalb)
finalb
elseb)
(let* ((loc (line-number-node s))
(var (if nl #f (parse-eq* s)))
(var? (and (not nl) (or (symbol? var)
(and (length= var 2) (eq? (car var) '$))
(error (string "invalid syntax \"catch " (deparse var) "\"")))))
(catch-block (if (eq? (require-token s) 'finally)
(catch-block (if (memq (require-token s) '(finally else))
`(block ,(line-number-node s))
(parse-block s))))
(loop (require-token s)
Expand All @@ -1543,16 +1547,30 @@
'()
(cdr catch-block))))
(if var? var '(false))
finalb)))))
finalb
elseb)))))
((and (eq? nxt 'finally)
(not finalb))
(let ((fb (if (eq? (require-token s) 'catch)
(let ((fb (if (memq (require-token s) '(catch else))
'(block)
(parse-block s))))
(loop (require-token s)
catchb
catchv
fb)))
fb
elseb)))
((and (eq? nxt 'else)
(not elseb))
(if (or (not catchb) finalb)
(error "else inside try block needs to be immediately after catch"))
(let ((eb (if (eq? (require-token s) 'finally)
'(block)
(parse-block s))))
(loop (require-token s)
catchb
catchv
finalb
eb)))
(else (expect-end-error nxt 'try))))))
((return) (let ((t (peek-token s)))
(if (or (eqv? t #\newline) (closing-token? t))
Expand Down
52 changes: 33 additions & 19 deletions src/julia-syntax.scm
Original file line number Diff line number Diff line change
Expand Up @@ -1332,25 +1332,29 @@
(let ((tryb (cadr e))
(var (caddr e))
(catchb (cadddr e)))
(cond ((length= e 5)
(cond ((and (length> e 4) (not (equal? (caddddr e) '(false))))
(if (has-unmatched-symbolic-goto? tryb)
(error "goto from a try/finally block is not permitted"))
(let ((finalb (cadddr (cdr e))))
(let ((finalb (caddddr e)))
(expand-forms
`(tryfinally
,(if (not (equal? catchb '(false)))
`(try ,tryb ,var ,catchb)
`(scope-block ,tryb))
,(if (and (equal? catchb '(false)) (length= e 5))
`(scope-block ,tryb)
`(try ,tryb ,var ,catchb (false) ,@(cdddddr e)))
(scope-block ,finalb)))))
((length= e 4)
(expand-forms
(if (symbol-like? var)
`(trycatch (scope-block ,tryb)
(scope-block
(block (= ,var (the_exception))
,catchb)))
`(trycatch (scope-block ,tryb)
(scope-block ,catchb)))))
((length> e 3)
(and (length> e 6) (error "invalid \"try\" form"))
(let ((elseb (if (length= e 6) (cdddddr e) '())))
(expand-forms
`(,(if (null? elseb) 'trycatch 'trycatchelse)
(scope-block ,tryb)
(scope-block
,(if (symbol-like? var)
`(scope-block
(block (= ,var (the_exception))
,catchb))
`(scope-block ,catchb)))
,@elseb))))
(else
(error "invalid \"try\" form")))))

Expand Down Expand Up @@ -3587,7 +3591,7 @@ f(x) = yt(x)
((eq? (car e) 'symboliclabel)
(kill)
#t)
((memq (car e) '(if elseif trycatch tryfinally))
((memq (car e) '(if elseif trycatch tryfinally trycatchelse))
(let ((prev (table.clone live)))
(if (eager-any (lambda (e) (begin0 (visit e)
(kill)))
Expand Down Expand Up @@ -3653,7 +3657,7 @@ f(x) = yt(x)
(and cv (vinfo:asgn cv) (vinfo:capt cv)))))

(define (toplevel-preserving? e)
(and (pair? e) (memq (car e) '(if elseif block trycatch tryfinally))))
(and (pair? e) (memq (car e) '(if elseif block trycatch tryfinally trycatchelse))))

(define (map-cl-convert exprs fname lam namemap defined toplevel interp opaq)
(if toplevel
Expand Down Expand Up @@ -4446,9 +4450,10 @@ f(x) = yt(x)
;; (= tok (enter L)) - push handler with catch block at label L, yielding token
;; (leave n) - pop N exception handlers
;; (pop_exception tok) - pop exception stack back to state of associated enter
((trycatch tryfinally)
((trycatch tryfinally trycatchelse)
(let ((handler-token (make-ssavalue))
(catch (make-label))
(els (and (eq? (car e) 'trycatchelse) (make-label)))
(endl (make-label))
(last-finally-handler finally-handler)
(finally (if (eq? (car e) 'tryfinally) (new-mutable-var) #f))
Expand All @@ -4465,11 +4470,20 @@ f(x) = yt(x)
;; handler block postfix
(if (and val v1) (emit-assignment val v1))
(if tail
(begin (if v1 (emit-return v1))
(begin (if els
(begin (if (and (not val) v1) (emit v1))
(emit '(leave 1)))
(if v1 (emit-return v1)))
(if (not finally) (set! endl #f)))
(begin (emit '(leave 1))
(emit `(goto ,endl))))
(emit `(goto ,(or els endl)))))
(set! handler-level (- handler-level 1))
;; emit else block
(if els
(begin (mark-label els)
(let ((v3 (compile (cadddr e) break-labels value tail))) ;; emit else block code
(if val (emit-assignment val v3)))
(emit `(goto ,endl))))
;; emit either catch or finally block
(mark-label catch)
(emit `(leave 1))
Expand Down
1 change: 1 addition & 0 deletions src/utils.scm
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@

(define (caddddr x) (car (cdr (cdr (cdr (cdr x))))))
(define (cdddddr x) (cdr (cdr (cdr (cdr (cdr x))))))
(define (cadddddr x) (car (cdddddr x)))

(define (table.clone t)
(let ((nt (table)))
Expand Down
73 changes: 73 additions & 0 deletions test/syntax.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2983,3 +2983,76 @@ macro m42220()
end
@test @m42220()() isa Vector{Float64}
@test @m42220()(Bool) isa Vector{Bool}

@testset "try else" begin
fails(f) = try f() catch; true else false end
@test fails(error)
@test !fails(() -> 1 + 2)

@test_throws ParseError Meta.parse("try foo() else bar() end")
@test_throws ParseError Meta.parse("try foo() else bar() catch; baz() end")
@test_throws ParseError Meta.parse("try foo() catch; baz() finally foobar() else bar() end")
@test_throws ParseError Meta.parse("try foo() finally foobar() else bar() catch; baz() end")

err = try
try
1 + 2
catch
else
error("foo")
end
catch e
e
end
@test err == ErrorException("foo")

x = 0
err = try
try
1 + 2
catch
else
error("foo")
finally
x += 1
end
catch e
e
end
@test err == ErrorException("foo")
@test x == 1

x = 0
err = try
try
1 + 2
catch
5 + 6
else
3 + 4
finally
x += 1
end
catch e
e
end
@test err == 3 + 4
@test x == 1

x = 0
err = try
try
error()
catch
5 + 6
else
3 + 4
finally
x += 1
end
catch e
e
end
@test err == 5 + 6
@test x == 1
end

0 comments on commit 0357b2a

Please sign in to comment.