From b7e5c5f17abe3e40e1ca57945cd70b6acfab75f8 Mon Sep 17 00:00:00 2001 From: Kodi Arfer Date: Sun, 15 Apr 2018 16:00:36 -0700 Subject: [PATCH 1/2] Allow `while` with an empty body --- hy/compiler.py | 5 +++-- tests/compilers/test_ast.py | 1 - tests/native_tests/language.hy | 16 +++++++++++++++- 3 files changed, 18 insertions(+), 4 deletions(-) diff --git a/hy/compiler.py b/hy/compiler.py index 284c7fc..0f92131 100755 --- a/hy/compiler.py +++ b/hy/compiler.py @@ -1828,7 +1828,7 @@ class HyASTCompiler(object): return ret @builds("while") - @checkargs(min=2) + @checkargs(min=1) def compile_while_expression(self, expr): expr.pop(0) # "while" cond = expr.pop(0) @@ -1867,7 +1867,8 @@ class HyASTCompiler(object): ret = cond_compiled + asty.While( expr, test=cond_compiled.force_expr, - body=body.stmts, orelse=orel.stmts) + body=body.stmts or [asty.Pass(expr)], + orelse=orel.stmts) ret.contains_yield = body.contains_yield return ret diff --git a/tests/compilers/test_ast.py b/tests/compilers/test_ast.py index e5a0ddd..faaa772 100644 --- a/tests/compilers/test_ast.py +++ b/tests/compilers/test_ast.py @@ -97,7 +97,6 @@ def test_ast_invalid_unary_op(): def test_ast_bad_while(): "Make sure AST can't compile invalid while" cant_compile("(while)") - cant_compile("(while (True))") def test_ast_good_do(): diff --git a/tests/native_tests/language.hy b/tests/native_tests/language.hy index 6840f29..d76102c 100644 --- a/tests/native_tests/language.hy +++ b/tests/native_tests/language.hy @@ -283,7 +283,21 @@ (setv fact (* fact count)) (setv count (- count 1))) (assert (= count 0)) - (assert (= fact 120))) + (assert (= fact 120)) + + (setv l []) + (defn f [] + (.append l 1) + (len l)) + (while (!= (f) 4)) + (assert (= l [1 1 1 1])) + + (setv l []) + (defn f [] + (.append l 1) + (len l)) + (while (!= (f) 4) (do)) + (assert (= l [1 1 1 1]))) (defn test-while-loop-else [] (setv count 5) From ec1c92bf4e2b224d70c24e34fa5569478bd9e9c4 Mon Sep 17 00:00:00 2001 From: Kodi Arfer Date: Sun, 15 Apr 2018 15:59:35 -0700 Subject: [PATCH 2/2] Allow `for` with an empty body --- NEWS.rst | 1 + hy/compiler.py | 2 +- hy/core/macros.hy | 6 +----- tests/compilers/test_ast.py | 8 -------- tests/native_tests/language.hy | 10 +++++++++- 5 files changed, 12 insertions(+), 15 deletions(-) diff --git a/NEWS.rst b/NEWS.rst index 57d69a7..c1a673b 100644 --- a/NEWS.rst +++ b/NEWS.rst @@ -39,6 +39,7 @@ New Features * `defclass` in Python 3 now supports specifying metaclasses and other keyword arguments * Added a command-line option `-E` per CPython +* `while` and `for` are allowed to have empty bodies Bug Fixes ------------------------------ diff --git a/hy/compiler.py b/hy/compiler.py index 0f92131..91344ba 100755 --- a/hy/compiler.py +++ b/hy/compiler.py @@ -1820,7 +1820,7 @@ class HyASTCompiler(object): ret += node(expression, target=target, iter=ret.force_expr, - body=body.stmts, + body=body.stmts or [asty.Pass(expression)], orelse=orel.stmts) ret.contains_yield = body.contains_yield diff --git a/hy/core/macros.hy b/hy/core/macros.hy index 16432dd..a948fd8 100644 --- a/hy/core/macros.hy +++ b/hy/core/macros.hy @@ -110,15 +110,11 @@ used as the result." (defn _for [node args body] (setv body (list body)) - (if (empty? body) - (macro-error None "`for' requires a body to evaluate")) - (setv lst (get body -1)) - (setv belse (if (and (isinstance lst HyExpression) (= (get lst 0) "else")) + (setv belse (if (and body (isinstance (get body -1) HyExpression) (= (get body -1 0) "else")) [(body.pop)] [])) (if (odd? (len args)) (macro-error args "`for' requires an even number of args.") - (empty? body) (macro-error None "`for' requires a body to evaluate") (empty? args) `(do ~@body ~@belse) (= (len args) 2) `(~node [~@args] (do ~@body) ~@belse) (do diff --git a/tests/compilers/test_ast.py b/tests/compilers/test_ast.py index faaa772..c8d2999 100644 --- a/tests/compilers/test_ast.py +++ b/tests/compilers/test_ast.py @@ -534,14 +534,6 @@ def test_for_compile_error(): can_compile("(fn [] (for [x] x))") assert excinfo.value.message == "`for' requires an even number of args." - with pytest.raises(HyTypeError) as excinfo: - can_compile("(fn [] (for [x xx]))") - assert excinfo.value.message == "`for' requires a body to evaluate" - - with pytest.raises(HyTypeError) as excinfo: - can_compile("(fn [] (for [x xx] (else 1)))") - assert excinfo.value.message == "`for' requires a body to evaluate" - def test_attribute_access(): """Ensure attribute access compiles correctly""" diff --git a/tests/native_tests/language.hy b/tests/native_tests/language.hy index d76102c..d742c91 100644 --- a/tests/native_tests/language.hy +++ b/tests/native_tests/language.hy @@ -232,7 +232,15 @@ (assert (= (list ((fn [] (for [x [[1] [2 3]] y x] (yield y))))) (list-comp y [x [[1] [2 3]] y x]))) (assert (= (list ((fn [] (for [x [[1] [2 3]] y x z (range 5)] (yield z))))) - (list-comp z [x [[1] [2 3]] y x z (range 5)])))) + (list-comp z [x [[1] [2 3]] y x z (range 5)]))) + + (setv l []) + (defn f [] + (for [x [4 9 2]] + (.append l (* 10 x)) + (yield x))) + (for [_ (f)]) + (assert (= l [40 90 20]))) (defn test-nasty-for-nesting []