From 2ef9bc75d4b52a351f8662f5e8e6b607245e87d2 Mon Sep 17 00:00:00 2001 From: Kodi Arfer Date: Thu, 3 Aug 2017 17:38:26 -0700 Subject: [PATCH 1/3] Clean up `with` --- hy/compiler.py | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/hy/compiler.py b/hy/compiler.py index 2143fd4..8b3e1c2 100755 --- a/hy/compiler.py +++ b/hy/compiler.py @@ -1452,25 +1452,22 @@ class HyASTCompiler(object): raise HyTypeError(expr, "with expects a list, received `{0}'".format( type(args).__name__)) - if len(args) < 1: - raise HyTypeError(expr, "with needs [[arg (expr)]] or [[(expr)]]]") - - args.reverse() - ctx = self.compile(args.pop(0)) + if len(args) not in (1, 2): + raise HyTypeError(expr, "with needs [arg (expr)] or [(expr)]") thing = None - if args != []: + if len(args) == 2: thing = self._storeize(args[0], self.compile(args.pop(0))) + ctx = self.compile(args.pop(0)) body = self._compile_branch(expr) + # Store the result of the body in a tempvar var = self.get_anon_var() name = ast.Name(id=ast_str(var), arg=ast_str(var), ctx=ast.Store(), lineno=expr.start_line, col_offset=expr.start_column) - - # Store the result of the body in a tempvar body += ast.Assign(targets=[name], value=body.force_expr, lineno=expr.start_line, From f5ee5f4ee5e5b9727c41f3cf096e7aeda14b70f6 Mon Sep 17 00:00:00 2001 From: Kodi Arfer Date: Sun, 23 Jul 2017 15:22:10 -0700 Subject: [PATCH 2/3] Whitespace fix --- tests/native_tests/with_test.hy | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/tests/native_tests/with_test.hy b/tests/native_tests/with_test.hy index 0d52ff6..a071942 100644 --- a/tests/native_tests/with_test.hy +++ b/tests/native_tests/with_test.hy @@ -34,12 +34,12 @@ (assert (= t2 2)) (assert (= t3 3)))) - (defn test-quince-with [] - "NATIVE: test four withs, one with no args" - (with [t1 (WithTest 1) - t2 (WithTest 2) - t3 (WithTest 3) - _ (WithTest 4)] - (assert (= t1 1)) - (assert (= t2 2)) - (assert (= t3 3)))) +(defn test-quince-with [] + "NATIVE: test four withs, one with no args" + (with [t1 (WithTest 1) + t2 (WithTest 2) + t3 (WithTest 3) + _ (WithTest 4)] + (assert (= t1 1)) + (assert (= t2 2)) + (assert (= t3 3)))) From 8d40a682328fa71ff141b36df05ba4e64e9b82b9 Mon Sep 17 00:00:00 2001 From: Kodi Arfer Date: Sat, 19 Aug 2017 07:35:41 -0700 Subject: [PATCH 3/3] Initialize the return variable of `with` --- NEWS | 2 ++ docs/language/api.rst | 7 +++++++ hy/compiler.py | 17 +++++++++++++++-- tests/native_tests/with_test.hy | 25 +++++++++++++++++++++++++ 4 files changed, 49 insertions(+), 2 deletions(-) diff --git a/NEWS b/NEWS index 81be812..ab49999 100644 --- a/NEWS +++ b/NEWS @@ -27,6 +27,8 @@ Changes from 0.13.0 * `exec` now works under Python 2 * No TypeError from multi-arity defn returning values evaluating to None * try form now possible in defmacro/deftag + * Fixed a crash when `with` suppresses an exception. `with` now returns + `None` in this case. [ Misc. Improvements ] * `read`, `read_str`, and `eval` are exposed and documented as top-level diff --git a/docs/language/api.rst b/docs/language/api.rst index 88da5d3..bfac38e 100644 --- a/docs/language/api.rst +++ b/docs/language/api.rst @@ -1678,6 +1678,13 @@ screen. The file is automatically closed after it has been processed. (with [f (open "NEWS")] (print (.read f))) +``with`` returns the value of its last form, unless it suppresses an exception +(because the context manager's ``__exit__`` method returned true), in which +case it returns ``None``. So, the previous example could also be written + +.. code-block:: clj + + (print (with [f (open "NEWS")] (.read f))) with-decorator -------------- diff --git a/hy/compiler.py b/hy/compiler.py index 8b3e1c2..354359b 100755 --- a/hy/compiler.py +++ b/hy/compiler.py @@ -1472,6 +1472,16 @@ class HyASTCompiler(object): value=body.force_expr, lineno=expr.start_line, col_offset=expr.start_column) + # Initialize the tempvar to None in case the `with` exits + # early with an exception. + initial_assign = ast.Assign(targets=[name], + value=ast.Name( + id=ast_str("None"), + ctx=ast.Load(), + lineno=expr.start_line, + col_offset=expr.start_column), + lineno=expr.start_line, + col_offset=expr.start_column) the_with = ast.With(context_expr=ctx.force_expr, lineno=expr.start_line, @@ -1483,7 +1493,7 @@ class HyASTCompiler(object): the_with.items = [ast.withitem(context_expr=ctx.force_expr, optional_vars=thing)] - ret = ctx + the_with + ret = Result(stmts = [initial_assign]) + ctx + the_with ret.contains_yield = ret.contains_yield or body.contains_yield # And make our expression context our temp variable expr_name = ast.Name(id=ast_str(var), arg=ast_str(var), @@ -1491,7 +1501,10 @@ class HyASTCompiler(object): lineno=expr.start_line, col_offset=expr.start_column) - ret += Result(expr=expr_name, temp_variables=[expr_name, name]) + ret += Result(expr=expr_name) + # We don't give the Result any temp_vars because we don't want + # Result.rename to touch `name`. Otherwise, initial_assign will + # clobber any preexisting value of the renamed-to variable. return ret diff --git a/tests/native_tests/with_test.hy b/tests/native_tests/with_test.hy index a071942..984d436 100644 --- a/tests/native_tests/with_test.hy +++ b/tests/native_tests/with_test.hy @@ -43,3 +43,28 @@ (assert (= t1 1)) (assert (= t2 2)) (assert (= t3 3)))) + +(defclass SuppressZDE [object] + (defn --enter-- [self]) + (defn --exit-- [self exc-type exc-value traceback] + (and (not (none? exc-type)) (issubclass exc-type ZeroDivisionError)))) + +(defn test-exception-suppressing-with [] + ; https://github.com/hylang/hy/issues/1320 + + (setv x (with [(SuppressZDE)] 5)) + (assert (= x 5)) + + (setv y (with [(SuppressZDE)] (/ 1 0))) + (assert (none? y)) + + (setv z (with [(SuppressZDE)] (/ 1 0) 5)) + (assert (none? z)) + + (defn f [] (with [(SuppressZDE)] (/ 1 0))) + (assert (none? (f))) + + (setv w 7 l []) + (setv w (with [(SuppressZDE)] (.append l w) (/ 1 0) 5)) + (assert (none? w)) + (assert (= l [7])))