Skip to content

Commit 42c2185

Browse files
authored
Merge pull request #2463 from Kodiologist/try-empties
`try` cleanup
2 parents 18af19e + f656f51 commit 42c2185

File tree

7 files changed

+105
-150
lines changed

7 files changed

+105
-150
lines changed

NEWS.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ New Features
3737
* Python 3.12 is now supported.
3838
* `nonlocal` and `global` can now be called with no arguments, in which
3939
case they're no-ops.
40+
* `try` no longer requires `except`, `except*`, or `finally`, and it
41+
allows `else` even without `except` or `except*`.
4042
* The `py` macro now implicitly parenthesizes the input code, so Python's
4143
indentation restrictions don't apply.
4244
* `cut` now has a function version in `hy.pyops`.

docs/api.rst

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -996,10 +996,8 @@ base names, such that ``hy.core.macros.foo`` can be called as just ``foo``.
996996
list of exception types, followed by more body forms. Finally there are an
997997
optional ``else`` form and an optional ``finally`` form, which again are
998998
expressions that begin with the symbol in question and then comprise body
999-
forms. As in Python, at least one of ``except``, ``except*``, or ``finally``
1000-
is required; ``else`` is only allowed if at least one ``except`` or
1001-
``except*`` is provided; ``except*`` requires Python 3.11; and ``except``
1002-
and ``except*`` may not both be used in the same ``try``.
999+
forms. Note that ``except*`` requires Python 3.11, and ``except*`` and
1000+
``except`` may not both be used in the same ``try``.
10031001

10041002
Here's an example of several of the allowed kinds of child forms::
10051003

hy/core/result_macros.py

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1292,7 +1292,16 @@ def compile_raise_expression(compiler, expr, root, exc, cause):
12921292
],
12931293
)
12941294
def compile_try_expression(compiler, expr, root, body, catchers, orelse, finalbody):
1295+
if orelse is not None and not catchers:
1296+
# Python forbids `else` when there are no `except` clauses.
1297+
# But we can get the same effect by appending the `else` forms
1298+
# to the body.
1299+
body += list(orelse)
1300+
orelse = None
12951301
body = compiler._compile_branch(body)
1302+
if not (catchers or finalbody):
1303+
# Python forbids this, so just return the body, per `do`.
1304+
return body
12961305

12971306
return_var = asty.Name(expr, id=mangle(compiler.get_anon_var()), ctx=ast.Store())
12981307

@@ -1367,15 +1376,6 @@ def compile_try_expression(compiler, expr, root, body, catchers, orelse, finalbo
13671376
finalbody += finalbody.expr_as_stmt()
13681377
finalbody = finalbody.stmts
13691378

1370-
# Using (else) without (except) is verboten!
1371-
if orelse and not handlers:
1372-
raise compiler._syntax_error(expr, "`try' cannot have `else' without `except'")
1373-
# Likewise a bare (try) or (try BODY).
1374-
if not (handlers or finalbody):
1375-
raise compiler._syntax_error(
1376-
expr, "`try' must have an `except' or `finally' clause"
1377-
)
1378-
13791379
returnable = Result(
13801380
expr=asty.Name(expr, id=return_var.id, ctx=ast.Load()),
13811381
temp_variables=[return_var],

setup.cfg

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[tool:pytest]
22
# Be sure to include Hy test functions with mangled names.
3-
python_functions=test_* is_test_* hyx_test_* hyx_is_test_*
3+
python_functions=test_* hyx_test_*
44
filterwarnings =
55
once::DeprecationWarning
66
once::PendingDeprecationWarning

tests/compilers/test_ast.py

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -113,13 +113,7 @@ def test_ast_good_try():
113113

114114

115115
def test_ast_bad_try():
116-
cant_compile("(try)")
117-
cant_compile("(try 1)")
118-
cant_compile("(try 1 bla)")
119-
cant_compile("(try 1 bla bla)")
120-
cant_compile("(try (do bla bla))")
121116
cant_compile("(try (do) (else 1) (else 2))")
122-
cant_compile("(try 1 (else 1))")
123117
cant_compile("(try 1 (else 1) (except []))")
124118
cant_compile("(try 1 (finally 1) (except []))")
125119
cant_compile("(try 1 (except []) (finally 1) (else 1))")

tests/native_tests/try.hy

Lines changed: 87 additions & 128 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,38 @@
11
;; Tests of `try` and `raise`
22

3-
(defn test-try []
3+
(import
4+
pytest)
45

5-
(try (do) (except []))
66

7-
(try (do) (except [IOError]) (except []))
7+
(defn test-try-missing-parts []
8+
(assert (is (try) None))
9+
(assert (= (try 1) 1))
10+
(assert (is (try (except [])) None))
11+
(assert (is (try (finally)) None))
12+
(assert (= (try 1 (finally 2)) 1))
13+
(assert (is (try (else)) None))
14+
(assert (= (try 1 (else 2)) 2)))
815

9-
; test that multiple statements in a try get evaluated
16+
17+
(defn test-try-multiple-statements []
1018
(setv value 0)
1119
(try (+= value 1) (+= value 2) (except [IOError]) (except []))
12-
(assert (= value 3))
20+
(assert (= value 3)))
21+
1322

14-
; test that multiple expressions in a try get evaluated
23+
(defn test-try-multiple-expressions []
1524
; https://github.com/hylang/hy/issues/1584
25+
1626
(setv l [])
1727
(defn f [] (.append l 1))
1828
(try (f) (f) (f) (except [IOError]))
1929
(assert (= l [1 1 1]))
2030
(setv l [])
2131
(try (f) (f) (f) (except [IOError]) (else (f)))
22-
(assert (= l [1 1 1 1]))
32+
(assert (= l [1 1 1 1])))
33+
34+
35+
(defn test-raise-nullary []
2336

2437
;; Test correct (raise)
2538
(setv passed False)
@@ -38,123 +51,60 @@
3851
(raise)
3952
(except [RuntimeError]
4053
(setv passed True)))
41-
(assert passed)
42-
43-
;; Test (finally)
44-
(setv passed False)
45-
(try
46-
(do)
47-
(finally (setv passed True)))
48-
(assert passed)
49-
50-
;; Test (finally) + (raise)
51-
(setv passed False)
52-
(try
53-
(raise Exception)
54-
(except [])
55-
(finally (setv passed True)))
56-
(assert passed)
57-
58-
59-
;; Test (finally) + (raise) + (else)
60-
(setv passed False
61-
not-elsed True)
62-
(try
63-
(raise Exception)
64-
(except [])
65-
(else (setv not-elsed False))
66-
(finally (setv passed True)))
67-
(assert passed)
68-
(assert not-elsed)
69-
70-
(try
71-
(raise (KeyError))
72-
(except [[IOError]] (assert False))
73-
(except [e [KeyError]] (assert e)))
74-
75-
(try
76-
(raise (KeyError))
77-
(except [[IOError]] (assert False))
78-
(except [e [KeyError]] (assert e)))
79-
80-
(try
81-
(get [1] 3)
82-
(except [IndexError] (assert True))
83-
(except [IndexError] (do)))
84-
85-
(try
86-
(print foobar42ofthebaz)
87-
(except [IndexError] (assert False))
88-
(except [NameError] (do)))
89-
90-
(try
91-
(get [1] 3)
92-
(except [e IndexError] (assert (isinstance e IndexError))))
93-
94-
(try
95-
(get [1] 3)
96-
(except [e [IndexError NameError]] (assert (isinstance e IndexError))))
97-
98-
(try
99-
(print foobar42ofthebaz)
100-
(except [e [IndexError NameError]] (assert (isinstance e NameError))))
101-
102-
(try
103-
(print foobar42)
104-
(except [[IndexError NameError]] (do)))
54+
(assert passed))
10555

106-
(try
107-
(get [1] 3)
108-
(except [[IndexError NameError]] (do)))
10956

110-
(try
111-
(print foobar42ofthebaz)
112-
(except []))
57+
(defn test-try-clauses []
11358

114-
(try
115-
(print foobar42ofthebaz)
116-
(except [] (do)))
117-
118-
(try
119-
(print foobar42ofthebaz)
120-
(except []
121-
(setv foobar42ofthebaz 42)
122-
(assert (= foobar42ofthebaz 42))))
123-
124-
(setv passed False)
125-
(try
126-
(try (do) (except []) (else (bla)))
127-
(except [NameError] (setv passed True)))
128-
(assert passed)
59+
(defmacro try-it [body v1 v2]
60+
`(assert (= (_try-it (fn [] ~body)) [~v1 ~v2])))
61+
(defn _try-it [callback]
62+
(setv did-finally-clause? False)
63+
(try
64+
(callback)
65+
(except [ZeroDivisionError]
66+
(setv out ["aaa" None]))
67+
(except [[IndexError NameError]]
68+
(setv out ["bbb" None]))
69+
(except [e TypeError]
70+
(setv out ["ccc" (type e)]))
71+
(except [e [KeyError AttributeError]]
72+
(setv out ["ddd" (type e)]))
73+
(except []
74+
(setv out ["eee" None]))
75+
(else
76+
(setv out ["zzz" None]))
77+
(finally
78+
(setv did-finally-clause? True)))
79+
(assert did-finally-clause?)
80+
out)
81+
82+
(try-it (/ 1 0) "aaa" None)
83+
(try-it (get "foo" 5) "bbb" None)
84+
(try-it unbound "bbb" None)
85+
(try-it (abs "hi") "ccc" TypeError)
86+
(try-it (get {1 2} 3) "ddd" KeyError)
87+
(try-it True.a "ddd" AttributeError)
88+
(try-it (raise ValueError) "eee" None)
89+
(try-it "hi" "zzz" None))
90+
91+
92+
(defn test-finally-executes-for-uncaught-exception []
93+
(setv x "")
94+
(with [(pytest.raises ZeroDivisionError)]
95+
(try
96+
(+= x "a")
97+
(/ 1 0)
98+
(+= x "b")
99+
(finally
100+
(+= x "c"))))
101+
(assert (= x "ac")))
129102

130-
(setv x 0)
131-
(try
132-
(raise IOError)
133-
(except [IOError]
134-
(setv x 45))
135-
(else (setv x 44)))
136-
(assert (= x 45))
137103

138-
(setv x 0)
139-
(try
140-
(raise KeyError)
141-
(except []
142-
(setv x 45))
143-
(else (setv x 44)))
144-
(assert (= x 45))
104+
(defn test-nonsyntactical-except []
105+
#[[Test that [except ...] and ("except" ...) aren't treated like (except ...),
106+
and that the code there is evaluated normally.]]
145107

146-
(setv x 0)
147-
(try
148-
(try
149-
(raise KeyError)
150-
(except [IOError]
151-
(setv x 45))
152-
(else (setv x 44)))
153-
(except []))
154-
(assert (= x 0))
155-
156-
; test that [except ...] and ("except" ...) aren't treated like (except ...),
157-
; and that the code there is evaluated normally
158108
(setv x 0)
159109
(try
160110
(+= x 1)
@@ -186,26 +136,35 @@
186136
; https://github.com/hylang/hy/issues/798
187137

188138
(assert (= "ef" ((fn []
189-
(try (+ "a" "b")
190-
(except [NameError] (+ "c" "d"))
191-
(else (+ "e" "f")))))))
139+
(try
140+
(+ "a" "b")
141+
(except [NameError]
142+
(+ "c" "d"))
143+
(else
144+
(+ "e" "f")))))))
192145

193146
(setv foo
194-
(try (+ "A" "B")
195-
(except [NameError] (+ "C" "D"))
196-
(else (+ "E" "F"))))
147+
(try
148+
(+ "A" "B")
149+
(except [NameError]
150+
(+ "C" "D"))
151+
(else
152+
(+ "E" "F"))))
197153
(assert (= foo "EF"))
198154

199-
; Check that the lvalue isn't assigned in the main `try` body
200-
; there's an `else`.
155+
; Check that the lvalue isn't assigned by the main `try` body
156+
; when there's an `else`.
201157
(setv x 1)
202158
(setv y 0)
203159
(setv x
204-
(try (+ "G" "H")
205-
(except [NameError] (+ "I" "J"))
160+
(try
161+
(+ "G" "H")
162+
(except [NameError]
163+
(+ "I" "J"))
206164
(else
207165
(setv y 1)
208166
(assert (= x 1))
167+
; `x` still has its value from before the `try`.
209168
(+ "K" "L"))))
210169
(assert (= x "KL"))
211170
(assert (= y 1)))

tests/test_hy2py.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,15 @@ def test_hy2py_import():
2323
import subprocess
2424

2525
path = "tests/resources/pydemo_as_py.py"
26+
env = dict(os.environ)
27+
env["PYTHONIOENCODING"] = "UTF-8"
28+
env["PYTHONPATH"] = "." + os.pathsep + env.get("PYTHONPATH", "")
2629
try:
2730
with open(path, "wb") as o:
2831
subprocess.check_call(
2932
["hy2py", "tests/resources/pydemo.hy"],
3033
stdout=o,
31-
env={**os.environ, "PYTHONIOENCODING": "UTF-8"},
32-
)
34+
env=env)
3335
import tests.resources.pydemo_as_py as m
3436
finally:
3537
with contextlib.suppress(FileNotFoundError):

0 commit comments

Comments
 (0)