diff --git a/NEWS b/NEWS index 490e080..c606eb1 100644 --- a/NEWS +++ b/NEWS @@ -26,6 +26,9 @@ Changes from 0.12.1 * `(** a b c d)` is now equivalent to `(** a (** b (** c d)))`, not `(** (** (** a b) c) d)` * `setv` always returns None + * When a `try` form executes an `else` clause, the return value for the + `try` form is taken from `else` instead of the `try` body. For example, + `(try 1 (except [ValueError] 2) (else 3))` returns `3`. * xor: If exactly one argument is true, return it * hy.core.reserved is now hy.extra.reserved diff --git a/docs/language/api.rst b/docs/language/api.rst index 6fe6ea4..a3d81e1 100644 --- a/docs/language/api.rst +++ b/docs/language/api.rst @@ -1538,23 +1538,37 @@ or no arguments to re-raise the last ``Exception``. try --- -The ``try`` form is used to start a ``try`` / ``except`` block. The form is -used as follows: +The ``try`` form is used to catch exceptions (``except``) and run cleanup +actions (``finally``). .. code-block:: clj (try - (error-prone-function) - (except [e ZeroDivisionError] (print "Division by zero")) - (else (print "no errors")) - (finally (print "all done"))) + (error-prone-function) + (except [ZeroDivisionError] + (print "Division by zero")) + (except [[IndexError KeyboardInterrupt]] + (print "Index error or Ctrl-C")) + (except [e ValueError] + (print "ValueError:" (repr e))) + (except [e [TabError PermissionError ReferenceError]] + (print "Some sort of error:" (repr e))) + (else + (print "No errors")) + (finally + (print "All done"))) -``try`` must contain at least one ``except`` block, and may optionally include -an ``else`` or ``finally`` block. If an error is raised with a matching except -block during the execution of ``error-prone-function``, that ``except`` block -will be executed. If no errors are raised, the ``else`` block is executed. The -``finally`` block will be executed last regardless of whether or not an error -was raised. +The first argument of ``try`` is its body. (To put more than one form in the +body, use ``do``.) Then comes any number of ``except`` clauses, then optionally +an ``else`` clause, then optionally a ``finally`` clause. If an exception is +raised with a matching ``except`` clause during the execution of the body, that +``except`` clause will be executed. If no exceptions are raised, the ``else`` +clause is executed. The ``finally`` clause will be executed last regardless of +whether an exception was raised. + +The return value of ``try`` is the last form of the ``except`` clause that was +run, or the last form of ``else`` if no exception was raised, or the ``try`` +body if there is no ``else`` clause. unless diff --git a/hy/compiler.py b/hy/compiler.py index e3a4224..7bb165a 100644 --- a/hy/compiler.py +++ b/hy/compiler.py @@ -786,13 +786,6 @@ class HyASTCompiler(object): returnable = Result(expr=expr_name, temp_variables=[expr_name, name], contains_yield=body.contains_yield) - body += ast.Assign(targets=[name], - value=body.force_expr, - lineno=expr.start_line, - col_offset=expr.start_column) - - body = body.stmts - if not all(expr): raise HyTypeError(expr, "Empty list not allowed in `try'") handler_results = Result() @@ -803,13 +796,22 @@ class HyASTCompiler(object): handlers.append(handler_results.stmts.pop()) orelse = [] if expr and expr[0][0] == HySymbol("else"): - orelse = self.try_except_helper(expr.pop(0), HySymbol("else")) + orelse = self._compile_branch(expr.pop(0)[1:]) + orelse += ast.Assign(targets=[name], + value=orelse.force_expr, + lineno=expr.start_line, + col_offset=expr.start_column) + orelse += orelse.expr_as_stmt() + orelse = orelse.stmts finalbody = [] if expr and expr[0][0] == HySymbol("finally"): - finalbody = self.try_except_helper(expr.pop(0), HySymbol("finally")) + finalbody = self._compile_branch(expr.pop(0)[1:]) + finalbody += finalbody.expr_as_stmt() + finalbody = finalbody.stmts if expr: if expr[0][0] in ("except", "else", "finally"): - raise HyTypeError(expr, "Incorrect order of `except'/`else'/`finally' in `try'") + raise HyTypeError(expr, "Incorrect order " + "of `except'/`else'/`finally' in `try'") raise HyTypeError(expr, "Unknown expression in `try'") # Using (else) without (except) is verboten! @@ -831,6 +833,14 @@ class HyASTCompiler(object): ret = handler_results + body += body.expr_as_stmt() if orelse else ast.Assign( + targets=[name], + value=body.force_expr, + lineno=expr.start_line, + col_offset=expr.start_column) + body = body.stmts or [ast.Pass(lineno=expr.start_line, + col_offset=expr.start_column)] + if PY3: # Python 3.3 features a merge of TryExcept+TryFinally into Try. return ret + ast.Try( @@ -867,11 +877,6 @@ class HyASTCompiler(object): body=body, orelse=orelse) + returnable - def try_except_helper(self, hy_obj, symbol): - x = self._compile_branch(hy_obj[1:]) - x += x.expr_as_stmt() - return x.stmts - @builds("except") def magic_internal_form(self, expr): raise HyTypeError(expr, diff --git a/tests/native_tests/language.hy b/tests/native_tests/language.hy index 3746747..8e2dced 100644 --- a/tests/native_tests/language.hy +++ b/tests/native_tests/language.hy @@ -1174,20 +1174,34 @@ (assert (= foo 4))) -#@(pytest.mark.xfail (defn test-try-else-return [] "NATIVE: test that we can return from the `else` clause of a `try`" ; https://github.com/hylang/hy/issues/798 + (assert (= "ef" ((fn [] (try (+ "a" "b") (except [NameError] (+ "c" "d")) (else (+ "e" "f"))))))) + (setv foo (try (+ "A" "B") (except [NameError] (+ "C" "D")) (else (+ "E" "F")))) - (assert (= foo "EF")))) + (assert (= foo "EF")) + ; Check that the lvalue isn't assigned in the main `try` body + ; there's an `else`. + (setv x 1) + (setv y 0) + (setv x + (try (+ "G" "H") + (except [NameError] (+ "I" "J")) + (else + (setv y 1) + (assert (= x 1)) + (+ "K" "L")))) + (assert (= x "KL")) + (assert (= y 1))) (defn test-require [] "NATIVE: test requiring macros from python code"