Repository: hylang/hy

Issue:
Missing `else` for `try`
So we can write (try (catch) (else))



Discussion:

Resolving patch:
diff --git a/hy/compiler.py b/hy/compiler.py
--- a/hy/compiler.py
+++ b/hy/compiler.py
@@ -199,6 +199,7 @@ def compile_try_expression(self, expr):
                                  expr.start_line,
                                  expr.start_column)
 
+        orelse = []
         if len(expr) == 0:
             # (try) or (try body)
             handlers = [ast.ExceptHandler(
@@ -209,8 +210,27 @@ def compile_try_expression(self, expr):
                 body=[ast.Pass(lineno=expr.start_line,
                                col_offset=expr.start_column)])]
         else:
-            # (try body except except…)
-            handlers = [self.compile(s) for s in expr]
+            handlers = []
+            for e in expr:
+                if not len(e):
+                    raise TypeError("Empty list not allowed in `try'")
+
+                if e[0] in (HySymbol("except"), HySymbol("catch")):
+                    handlers.append(self.compile(e))
+                elif e[0] == HySymbol("else"):
+                    if orelse:
+                        raise TypeError(
+                            "`try' cannot have more than one `else'")
+                    else:
+                        orelse = self._code_branch(self.compile(e[1:]),
+                                                   e.start_line,
+                                                   e.start_column)
+                else:
+                    raise TypeError("Unknown expression in `try'")
+
+            if handlers == []:
+                raise TypeError(
+                    "`try' must have at least `except' or `finally'")
 
         return Try(
             lineno=expr.start_line,
@@ -218,7 +238,7 @@ def compile_try_expression(self, expr):
             body=body,
             handlers=handlers,
             finalbody=[],
-            orelse=[])
+            orelse=orelse)
 
     @builds("catch")
     @builds("except")


Test patch:
diff --git a/tests/compilers/test_ast.py b/tests/compilers/test_ast.py
--- a/tests/compilers/test_ast.py
+++ b/tests/compilers/test_ast.py
@@ -116,8 +116,16 @@ def test_ast_good_try():
     "Make sure AST can compile valid try"
     hy_compile(tokenize("(try)"))
     hy_compile(tokenize("(try 1)"))
-    hy_compile(tokenize("(try 1 bla)"))
-    hy_compile(tokenize("(try 1 bla bla)"))
+    hy_compile(tokenize("(try 1 (except) (else 1))"))
+    hy_compile(tokenize("(try 1 (else 1) (except))"))
+
+
+def test_ast_bad_try():
+    "Make sure AST can't compile invalid try"
+    cant_compile("(try 1 bla)")
+    cant_compile("(try 1 bla bla)")
+    cant_compile("(try (do) (else 1) (else 2))")
+    cant_compile("(try 1 (else 1))")
 
 
 def test_ast_good_catch():
diff --git a/tests/native_tests/language.hy b/tests/native_tests/language.hy
--- a/tests/native_tests/language.hy
+++ b/tests/native_tests/language.hy
@@ -235,7 +235,39 @@
     (print foobar42ofthebaz)
     (catch []
         (setv foobar42ofthebaz 42)
-        (assert (= foobar42ofthebaz 42)))))
+        (assert (= foobar42ofthebaz 42))))
+
+  (let [[passed false]]
+    (try
+      (try (pass) (except) (else (bla)))
+      (except [NameError] (setv passed true)))
+    (assert passed))
+
+  (let [[x 0]]
+    (try
+      (raise IOError)
+      (except [IOError]
+              (setv x 45))
+      (else (setv x 44)))
+    (assert (= x 45)))
+
+  (let [[x 0]]
+    (try
+      (raise KeyError)
+      (except []
+              (setv x 45))
+      (else (setv x 44)))
+    (assert (= x 45)))
+
+  (let [[x 0]]
+    (try
+      (try
+        (raise KeyError)
+        (except [IOError]
+                (setv x 45))
+        (else (setv x 44)))
+      (except))
+    (assert (= x 0))))
 
 (defn test-earmuffs []
   "NATIVE: Test earmuffs"