diff --git a/conftest.py b/conftest.py index 7302742..1a32be3 100644 --- a/conftest.py +++ b/conftest.py @@ -1,7 +1,7 @@ import _pytest import hy import os -from hy._compat import PY3, PY35 +from hy._compat import PY3, PY35, PY36 NATIVE_TESTS = os.path.join("", "tests", "native_tests", "") @@ -10,7 +10,8 @@ def pytest_collect_file(parent, path): and NATIVE_TESTS in path.dirname + os.sep and path.basename != "__init__.hy" and not ("py3_only" in path.basename and not PY3) - and not ("py35_only" in path.basename and not PY35)): + and not ("py35_only" in path.basename and not PY35) + and not ("py36_only" in path.basename and not PY36)): m = _pytest.python.pytest_pycollect_makemodule(path, parent) # Spoof the module name to avoid hitting an assertion in pytest. m.name = m.name[:-len(".hy")] + ".py" diff --git a/hy/_compat.py b/hy/_compat.py index 73decf9..32c7665 100644 --- a/hy/_compat.py +++ b/hy/_compat.py @@ -22,6 +22,7 @@ import sys PY3 = sys.version_info[0] >= 3 PY35 = sys.version_info >= (3, 5) +PY36 = sys.version_info >= (3, 6) str_type = str if PY3 else unicode # NOQA bytes_type = bytes if PY3 else str # NOQA diff --git a/hy/compiler.py b/hy/compiler.py index 8ab1adb..b3fdf5a 100755 --- a/hy/compiler.py +++ b/hy/compiler.py @@ -1830,16 +1830,16 @@ class HyASTCompiler(object): return result @builds("for*") + @builds("for/a*", iff=PY35) @checkargs(min=1) def compile_for_expression(self, expression): - expression.pop(0) # for + root = expression.pop(0) args = expression.pop(0) - if not isinstance(args, HyList): raise HyTypeError(expression, - "`for` expects a list, received `{0}`".format( - type(args).__name__)) + "`{0}` expects a list, received `{1}`".format( + root, type(args).__name__)) try: target_name, iterable = args @@ -1864,11 +1864,12 @@ class HyASTCompiler(object): body = self._compile_branch(expression) body += body.expr_as_stmt() - ret += asty.For(expression, - target=target, - iter=ret.force_expr, - body=body.stmts, - orelse=orel.stmts) + node = asty.For if root == 'for*' else asty.AsyncFor + ret += node(expression, + target=target, + iter=ret.force_expr, + body=body.stmts, + orelse=orel.stmts) ret.contains_yield = body.contains_yield diff --git a/hy/core/macros.hy b/hy/core/macros.hy index 0052a16..c266256 100644 --- a/hy/core/macros.hy +++ b/hy/core/macros.hy @@ -108,11 +108,7 @@ used as the result." root))) -(defmacro for [args &rest body] - "Build a for-loop with `args` as a [element coll] bracket pair and run `body`. - -Args may contain multiple pairs, in which case it executes a nested for-loop -in order of the given pairs." +(defmacro _for [node args &rest body] (setv body (list body)) (if (empty? body) (macro-error None "`for' requires a body to evaluate")) @@ -124,10 +120,26 @@ in order of the given pairs." (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) `(for* [~@args] (do ~@body) ~@belse) + (= (len args) 2) `(~node [~@args] (do ~@body) ~@belse) (do (setv alist (cut args 0 None 2)) - `(for* [(, ~@alist) (genexpr (, ~@alist) [~@args])] (do ~@body) ~@belse)))) + `(~node [(, ~@alist) (genexpr (, ~@alist) [~@args])] (do ~@body) ~@belse)))) + + +(defmacro for [args &rest body] + "Build a for-loop with `args` as a [element coll] bracket pair and run `body`. + +Args may contain multiple pairs, in which case it executes a nested for-loop +in order of the given pairs." + `(_for for* ~args ~@body)) + + +(defmacro for/a [args &rest body] + "Build a for/a-loop with `args` as a [element coll] bracket pair and run `body`. + +Args may contain multiple pairs, in which case it executes a nested for/a-loop +in order of the given pairs." + `(_for for/a* ~args ~@body)) (defmacro -> [head &rest rest] diff --git a/tests/native_tests/py36_only_tests.hy b/tests/native_tests/py36_only_tests.hy new file mode 100644 index 0000000..cee5634 --- /dev/null +++ b/tests/native_tests/py36_only_tests.hy @@ -0,0 +1,39 @@ +;; Copyright 2017 the authors. +;; This file is part of Hy, which is free software licensed under the Expat +;; license. See the LICENSE. + +;; Tests where the emitted code relies on Python ≥3.6. +;; conftest.py skips this file when running on Python <3.6. + +(import [asyncio [get-event-loop sleep]]) + + +(defn run-coroutine [coro] + "Run a coroutine until its done in the default event loop.""" + (.run_until_complete (get-event-loop) (coro))) + + +(defn test-for/a [] + (defn/a numbers [] + (for [i [1 2]] + (yield i))) + + (run-coroutine + (fn/a [] + (setv x 0) + (for/a [a (numbers)] + (setv x (+ x a))) + (assert (= x 3))))) + +(defn test-for/a-else [] + (defn/a numbers [] + (for [i [1 2]] + (yield i))) + + (run-coroutine + (fn/a [] + (setv x 0) + (for/a [a (numbers)] + (setv x (+ x a)) + (else (setv x (+ x 50)))) + (assert (= x 53)))))