diff --git a/NEWS.md b/NEWS.md index 4dcbdf9427db9..aaff6334190ad 100644 --- a/NEWS.md +++ b/NEWS.md @@ -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 ---------------- diff --git a/base/show.jl b/base/show.jl index c7d236979b956..485f9f8cc9f87 100644 --- a/base/show.jl +++ b/base/show.jl @@ -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 diff --git a/src/ast.scm b/src/ast.scm index e5148a507a4fd..a1615cc01e2fe 100644 --- a/src/ast.scm +++ b/src/ast.scm @@ -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)) diff --git a/src/julia-parser.scm b/src/julia-parser.scm index bab46f2cd4d28..32f4add447eff 100644 --- a/src/julia-parser.scm +++ b/src/julia-parser.scm @@ -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) @@ -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)) diff --git a/src/julia-syntax.scm b/src/julia-syntax.scm index 1b40704b36c3a..35acc10eec6fb 100644 --- a/src/julia-syntax.scm +++ b/src/julia-syntax.scm @@ -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"))))) @@ -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))) @@ -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 @@ -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)) @@ -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)) diff --git a/src/utils.scm b/src/utils.scm index c1a893102053c..7be6b2999a90c 100644 --- a/src/utils.scm +++ b/src/utils.scm @@ -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))) diff --git a/test/syntax.jl b/test/syntax.jl index ecfc703635c29..9bee6479e8e94 100644 --- a/test/syntax.jl +++ b/test/syntax.jl @@ -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