Skip to content

Commit 81c62e7

Browse files
simeonschaubJeffBezanson
authored andcommitted
add try/catch/else (JuliaLang#42211)
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 <[email protected]>
1 parent 62cd2df commit 81c62e7

File tree

7 files changed

+146
-28
lines changed

7 files changed

+146
-28
lines changed

NEWS.md

+2
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ New language features
1313
* The default behavior of observing `@inbounds` declarations is now an option via `auto` in `--check-bounds=yes|no|auto` ([#41551])
1414
* New function `eachsplit(str)` for iteratively performing `split(str)`.
1515
* ``, ``, and `` are now allowed as identifier characters ([#42314]).
16+
* `try`-blocks can now optionally have an `else`-block which is executed right after the main body only if
17+
no errors were thrown. ([#42211])
1618

1719
Language changes
1820
----------------

base/show.jl

+4-1
Original file line numberDiff line numberDiff line change
@@ -2132,12 +2132,15 @@ function show_unquoted(io::IO, ex::Expr, indent::Int, prec::Int, quote_level::In
21322132
elseif head === :line && 1 <= nargs <= 2
21332133
show_linenumber(io, args...)
21342134

2135-
elseif head === :try && 3 <= nargs <= 4
2135+
elseif head === :try && 3 <= nargs <= 5
21362136
iob = IOContext(io, beginsym=>false)
21372137
show_block(iob, "try", args[1], indent, quote_level)
21382138
if is_expr(args[3], :block)
21392139
show_block(iob, "catch", args[2] === false ? Any[] : args[2], args[3]::Expr, indent, quote_level)
21402140
end
2141+
if nargs >= 5 && is_expr(args[5], :block)
2142+
show_block(iob, "else", Any[], args[5]::Expr, indent, quote_level)
2143+
end
21412144
if nargs >= 4 && is_expr(args[4], :block)
21422145
show_block(iob, "finally", Any[], args[4]::Expr, indent, quote_level)
21432146
end

src/ast.scm

+7
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,13 @@
209209
"\n"
210210
(indented-block (cdr (cadddr e)) ilvl))
211211
"")
212+
(if (length> e 5)
213+
(let ((els (cadddddr e)))
214+
(if (and (pair? els) (eq? (car els) 'block))
215+
(string (string.rep " " ilvl) "else\n"
216+
(indented-block (cdr els) ilvl))
217+
""))
218+
"")
212219
(if (length> e 4)
213220
(let ((fin (caddddr e)))
214221
(if (and (pair? fin) (eq? (car fin) 'block))

src/julia-parser.scm

+26-8
Original file line numberDiff line numberDiff line change
@@ -1509,29 +1509,33 @@
15091509
(let loop ((nxt (peek-token s))
15101510
(catchb #f)
15111511
(catchv #f)
1512-
(finalb #f))
1512+
(finalb #f)
1513+
(elseb #f))
15131514
(take-token s)
15141515
(cond
15151516
((eq? nxt 'end)
15161517
(list* 'try try-block (or catchv '(false))
15171518
(or catchb (if finalb '(false) (error "try without catch or finally")))
1518-
(if finalb (list finalb) '())))
1519+
(cond (elseb (list (or finalb '(false)) elseb))
1520+
(finalb (list finalb))
1521+
(else '()))))
15191522
((and (eq? nxt 'catch)
15201523
(not catchb))
15211524
(let ((nl (memv (peek-token s) '(#\newline #\;))))
15221525
(if (eqv? (peek-token s) #\;)
15231526
(take-token s))
1524-
(if (memq (require-token s) '(end finally))
1527+
(if (memq (require-token s) '(end finally else))
15251528
(loop (require-token s)
15261529
'(block)
15271530
#f
1528-
finalb)
1531+
finalb
1532+
elseb)
15291533
(let* ((loc (line-number-node s))
15301534
(var (if nl #f (parse-eq* s)))
15311535
(var? (and (not nl) (or (symbol? var)
15321536
(and (length= var 2) (eq? (car var) '$))
15331537
(error (string "invalid syntax \"catch " (deparse var) "\"")))))
1534-
(catch-block (if (eq? (require-token s) 'finally)
1538+
(catch-block (if (memq (require-token s) '(finally else))
15351539
`(block ,(line-number-node s))
15361540
(parse-block s))))
15371541
(loop (require-token s)
@@ -1543,16 +1547,30 @@
15431547
'()
15441548
(cdr catch-block))))
15451549
(if var? var '(false))
1546-
finalb)))))
1550+
finalb
1551+
elseb)))))
15471552
((and (eq? nxt 'finally)
15481553
(not finalb))
1549-
(let ((fb (if (eq? (require-token s) 'catch)
1554+
(let ((fb (if (memq (require-token s) '(catch else))
15501555
'(block)
15511556
(parse-block s))))
15521557
(loop (require-token s)
15531558
catchb
15541559
catchv
1555-
fb)))
1560+
fb
1561+
elseb)))
1562+
((and (eq? nxt 'else)
1563+
(not elseb))
1564+
(if (or (not catchb) finalb)
1565+
(error "else inside try block needs to be immediately after catch"))
1566+
(let ((eb (if (eq? (require-token s) 'finally)
1567+
'(block)
1568+
(parse-block s))))
1569+
(loop (require-token s)
1570+
catchb
1571+
catchv
1572+
finalb
1573+
eb)))
15561574
(else (expect-end-error nxt 'try))))))
15571575
((return) (let ((t (peek-token s)))
15581576
(if (or (eqv? t #\newline) (closing-token? t))

src/julia-syntax.scm

+33-19
Original file line numberDiff line numberDiff line change
@@ -1332,25 +1332,29 @@
13321332
(let ((tryb (cadr e))
13331333
(var (caddr e))
13341334
(catchb (cadddr e)))
1335-
(cond ((length= e 5)
1335+
(cond ((and (length> e 4) (not (equal? (caddddr e) '(false))))
13361336
(if (has-unmatched-symbolic-goto? tryb)
13371337
(error "goto from a try/finally block is not permitted"))
1338-
(let ((finalb (cadddr (cdr e))))
1338+
(let ((finalb (caddddr e)))
13391339
(expand-forms
13401340
`(tryfinally
1341-
,(if (not (equal? catchb '(false)))
1342-
`(try ,tryb ,var ,catchb)
1343-
`(scope-block ,tryb))
1341+
,(if (and (equal? catchb '(false)) (length= e 5))
1342+
`(scope-block ,tryb)
1343+
`(try ,tryb ,var ,catchb (false) ,@(cdddddr e)))
13441344
(scope-block ,finalb)))))
1345-
((length= e 4)
1346-
(expand-forms
1347-
(if (symbol-like? var)
1348-
`(trycatch (scope-block ,tryb)
1349-
(scope-block
1350-
(block (= ,var (the_exception))
1351-
,catchb)))
1352-
`(trycatch (scope-block ,tryb)
1353-
(scope-block ,catchb)))))
1345+
((length> e 3)
1346+
(and (length> e 6) (error "invalid \"try\" form"))
1347+
(let ((elseb (if (length= e 6) (cdddddr e) '())))
1348+
(expand-forms
1349+
`(,(if (null? elseb) 'trycatch 'trycatchelse)
1350+
(scope-block ,tryb)
1351+
(scope-block
1352+
,(if (symbol-like? var)
1353+
`(scope-block
1354+
(block (= ,var (the_exception))
1355+
,catchb))
1356+
`(scope-block ,catchb)))
1357+
,@elseb))))
13541358
(else
13551359
(error "invalid \"try\" form")))))
13561360

@@ -3587,7 +3591,7 @@ f(x) = yt(x)
35873591
((eq? (car e) 'symboliclabel)
35883592
(kill)
35893593
#t)
3590-
((memq (car e) '(if elseif trycatch tryfinally))
3594+
((memq (car e) '(if elseif trycatch tryfinally trycatchelse))
35913595
(let ((prev (table.clone live)))
35923596
(if (eager-any (lambda (e) (begin0 (visit e)
35933597
(kill)))
@@ -3653,7 +3657,7 @@ f(x) = yt(x)
36533657
(and cv (vinfo:asgn cv) (vinfo:capt cv)))))
36543658

36553659
(define (toplevel-preserving? e)
3656-
(and (pair? e) (memq (car e) '(if elseif block trycatch tryfinally))))
3660+
(and (pair? e) (memq (car e) '(if elseif block trycatch tryfinally trycatchelse))))
36573661

36583662
(define (map-cl-convert exprs fname lam namemap defined toplevel interp opaq)
36593663
(if toplevel
@@ -4446,9 +4450,10 @@ f(x) = yt(x)
44464450
;; (= tok (enter L)) - push handler with catch block at label L, yielding token
44474451
;; (leave n) - pop N exception handlers
44484452
;; (pop_exception tok) - pop exception stack back to state of associated enter
4449-
((trycatch tryfinally)
4453+
((trycatch tryfinally trycatchelse)
44504454
(let ((handler-token (make-ssavalue))
44514455
(catch (make-label))
4456+
(els (and (eq? (car e) 'trycatchelse) (make-label)))
44524457
(endl (make-label))
44534458
(last-finally-handler finally-handler)
44544459
(finally (if (eq? (car e) 'tryfinally) (new-mutable-var) #f))
@@ -4465,11 +4470,20 @@ f(x) = yt(x)
44654470
;; handler block postfix
44664471
(if (and val v1) (emit-assignment val v1))
44674472
(if tail
4468-
(begin (if v1 (emit-return v1))
4473+
(begin (if els
4474+
(begin (if (and (not val) v1) (emit v1))
4475+
(emit '(leave 1)))
4476+
(if v1 (emit-return v1)))
44694477
(if (not finally) (set! endl #f)))
44704478
(begin (emit '(leave 1))
4471-
(emit `(goto ,endl))))
4479+
(emit `(goto ,(or els endl)))))
44724480
(set! handler-level (- handler-level 1))
4481+
;; emit else block
4482+
(if els
4483+
(begin (mark-label els)
4484+
(let ((v3 (compile (cadddr e) break-labels value tail))) ;; emit else block code
4485+
(if val (emit-assignment val v3)))
4486+
(emit `(goto ,endl))))
44734487
;; emit either catch or finally block
44744488
(mark-label catch)
44754489
(emit `(leave 1))

src/utils.scm

+1
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@
7979

8080
(define (caddddr x) (car (cdr (cdr (cdr (cdr x))))))
8181
(define (cdddddr x) (cdr (cdr (cdr (cdr (cdr x))))))
82+
(define (cadddddr x) (car (cdddddr x)))
8283

8384
(define (table.clone t)
8485
(let ((nt (table)))

test/syntax.jl

+73
Original file line numberDiff line numberDiff line change
@@ -2983,3 +2983,76 @@ macro m42220()
29832983
end
29842984
@test @m42220()() isa Vector{Float64}
29852985
@test @m42220()(Bool) isa Vector{Bool}
2986+
2987+
@testset "try else" begin
2988+
fails(f) = try f() catch; true else false end
2989+
@test fails(error)
2990+
@test !fails(() -> 1 + 2)
2991+
2992+
@test_throws ParseError Meta.parse("try foo() else bar() end")
2993+
@test_throws ParseError Meta.parse("try foo() else bar() catch; baz() end")
2994+
@test_throws ParseError Meta.parse("try foo() catch; baz() finally foobar() else bar() end")
2995+
@test_throws ParseError Meta.parse("try foo() finally foobar() else bar() catch; baz() end")
2996+
2997+
err = try
2998+
try
2999+
1 + 2
3000+
catch
3001+
else
3002+
error("foo")
3003+
end
3004+
catch e
3005+
e
3006+
end
3007+
@test err == ErrorException("foo")
3008+
3009+
x = 0
3010+
err = try
3011+
try
3012+
1 + 2
3013+
catch
3014+
else
3015+
error("foo")
3016+
finally
3017+
x += 1
3018+
end
3019+
catch e
3020+
e
3021+
end
3022+
@test err == ErrorException("foo")
3023+
@test x == 1
3024+
3025+
x = 0
3026+
err = try
3027+
try
3028+
1 + 2
3029+
catch
3030+
5 + 6
3031+
else
3032+
3 + 4
3033+
finally
3034+
x += 1
3035+
end
3036+
catch e
3037+
e
3038+
end
3039+
@test err == 3 + 4
3040+
@test x == 1
3041+
3042+
x = 0
3043+
err = try
3044+
try
3045+
error()
3046+
catch
3047+
5 + 6
3048+
else
3049+
3 + 4
3050+
finally
3051+
x += 1
3052+
end
3053+
catch e
3054+
e
3055+
end
3056+
@test err == 5 + 6
3057+
@test x == 1
3058+
end

0 commit comments

Comments
 (0)