From 9f519ed208a90d2c5365108847494e989528b2e4 Mon Sep 17 00:00:00 2001 From: Kodi Arfer Date: Mon, 8 Apr 2019 14:52:44 -0400 Subject: [PATCH 1/7] Depend on astor master We need it for Python 3.8. --- setup.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 8ee8be6..ef25ac5 100755 --- a/setup.py +++ b/setup.py @@ -31,7 +31,11 @@ class Install(install): "." + filename[:-len(".hy")]) install.run(self) -install_requires = ['rply>=0.7.7', 'astor>=0.7.1', 'funcparserlib>=0.3.6', 'clint>=0.4'] +install_requires = [ + 'rply>=0.7.7', + 'astor @ https://github.com/berkerpeksag/astor/archive/master.zip', + 'funcparserlib>=0.3.6', + 'clint>=0.4'] if os.name == 'nt': install_requires.append('pyreadline>=2.1') From 0c7ada1e63092aa6031e39edd667eb50b94ec83f Mon Sep 17 00:00:00 2001 From: Kodi Arfer Date: Mon, 8 Apr 2019 15:36:42 -0400 Subject: [PATCH 2/7] Ignore SyntaxWarnings while testing Python 3.8 introduces SyntaxWarnings for some things we test, like trying to call a string literal as if it were a function. --- setup.cfg | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.cfg b/setup.cfg index e033c3b..3cce154 100644 --- a/setup.cfg +++ b/setup.cfg @@ -22,3 +22,4 @@ python_functions=test_* is_test_* hyx_test_* hyx_is_test_* filterwarnings = once::DeprecationWarning once::PendingDeprecationWarning + ignore::SyntaxWarning From 8df0a41d7d01632f9ee733e277766986ae984228 Mon Sep 17 00:00:00 2001 From: Kodi Arfer Date: Mon, 8 Apr 2019 15:38:03 -0400 Subject: [PATCH 3/7] Provide Module(..., type_ignores) for Python 3.8 --- hy/_compat.py | 1 + hy/cmdline.py | 5 +++-- hy/compiler.py | 2 +- tests/native_tests/language.hy | 7 ++++--- 4 files changed, 9 insertions(+), 6 deletions(-) diff --git a/hy/_compat.py b/hy/_compat.py index ddeb859..a2ab7a5 100644 --- a/hy/_compat.py +++ b/hy/_compat.py @@ -11,6 +11,7 @@ import sys, keyword, textwrap PY3 = sys.version_info[0] >= 3 PY36 = sys.version_info >= (3, 6) PY37 = sys.version_info >= (3, 7) +PY38 = sys.version_info >= (3, 8) # The value of UCS4 indicates whether Unicode strings are stored as UCS-4. # It is always true on Pythons >= 3.3, which use USC-4 on all systems. diff --git a/hy/cmdline.py b/hy/cmdline.py index f65379d..d38ec08 100644 --- a/hy/cmdline.py +++ b/hy/cmdline.py @@ -270,8 +270,9 @@ class HyREPL(code.InteractiveConsole, object): try: # Mush the two AST chunks into a single module for # conversion into Python. - new_ast = ast.Module(exec_ast.body + - [ast.Expr(eval_ast.body)]) + new_ast = ast.Module( + exec_ast.body + [ast.Expr(eval_ast.body)], + type_ignores=[]) print(astor.to_source(new_ast)) except Exception: msg = 'Exception in AST callback:\n{}\n'.format( diff --git a/hy/compiler.py b/hy/compiler.py index 32610c5..750d8d2 100755 --- a/hy/compiler.py +++ b/hy/compiler.py @@ -2131,7 +2131,7 @@ def hy_compile(tree, module, root=ast.Module, get_expr=False, key=lambda a: not (isinstance(a, ast.ImportFrom) and a.module == '__future__')) - ret = root(body=body) + ret = root(body=body, type_ignores=[]) if get_expr: expr = ast.Expression(body=expr) diff --git a/tests/native_tests/language.hy b/tests/native_tests/language.hy index 01af5d5..687376c 100644 --- a/tests/native_tests/language.hy +++ b/tests/native_tests/language.hy @@ -11,7 +11,7 @@ pytest) (import sys) -(import [hy._compat [PY3 PY37]]) +(import [hy._compat [PY3 PY37 PY38]]) (defn test-sys-argv [] "NATIVE: test sys.argv" @@ -1550,10 +1550,11 @@ cee\"} dee" "ey bee\ncee dee")) (defn test-disassemble [] "NATIVE: Test the disassemble function" (assert (= (disassemble '(do (leaky) (leaky) (macros))) (cond - [PY3 "Module( + [PY3 (.format "Module( body=[Expr(value=Call(func=Name(id='leaky'), args=[], keywords=[])), Expr(value=Call(func=Name(id='leaky'), args=[], keywords=[])), - Expr(value=Call(func=Name(id='macros'), args=[], keywords=[]))])"] + Expr(value=Call(func=Name(id='macros'), args=[], keywords=[]))]{})" + (if PY38 ",\n type_ignores=[]" ""))] [True "Module( body=[ Expr(value=Call(func=Name(id='leaky'), args=[], keywords=[], starargs=None, kwargs=None)), From f236ec8d9ac87d9ee8180765e7b37d9eccd83ea1 Mon Sep 17 00:00:00 2001 From: Kodi Arfer Date: Mon, 8 Apr 2019 15:41:04 -0400 Subject: [PATCH 4/7] Test Python 3.8 on Travis --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 02f2a51..4234503 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,6 +6,7 @@ python: - "3.5" - "3.6" - "3.7" + - 3.8-dev - pypy2.7-6.0 - pypy3.5-6.0 install: From 63ba27b36d98a2b21f2f1511a12a53d98828961e Mon Sep 17 00:00:00 2001 From: Kodi Arfer Date: Mon, 8 Apr 2019 15:42:56 -0400 Subject: [PATCH 5/7] Update trove classifiers and NEWS --- NEWS.rst | 1 + setup.py | 1 + 2 files changed, 2 insertions(+) diff --git a/NEWS.rst b/NEWS.rst index 14842fb..f580a7f 100644 --- a/NEWS.rst +++ b/NEWS.rst @@ -9,6 +9,7 @@ Removals New Features ------------------------------ +* Python 3.8 is now supported. * Format strings with embedded Hy code (e.g., `f"The sum is {(+ x y)}"`) are now supported, even on Pythons earlier than 3.6. * New list? function. diff --git a/setup.py b/setup.py index ef25ac5..71e84e4 100755 --- a/setup.py +++ b/setup.py @@ -83,6 +83,7 @@ setup( "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", "Topic :: Software Development :: Code Generators", "Topic :: Software Development :: Compilers", "Topic :: Software Development :: Libraries", From 7b3ef423c1d267cb069bf746aaa2b196bb67aa2b Mon Sep 17 00:00:00 2001 From: Kodi Arfer Date: Tue, 9 Apr 2019 16:12:25 -0400 Subject: [PATCH 6/7] Use html.escape instead of cgi.escape cgi.escape is gone as of Python 3.8. --- docs/conf.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index d537b60..682dbcf 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -2,7 +2,7 @@ # # This file is execfile()d with the current directory set to its containing dir. -import re, os, sys, time, cgi +import re, os, sys, time, html sys.path.append(os.path.abspath("..")) extensions = ['sphinx.ext.intersphinx'] @@ -28,7 +28,7 @@ copyright = u'%s the authors' % time.strftime('%Y') version = ".".join(hy_version.split(".")[:-1]) # The full version, including alpha/beta/rc tags. release = hy_version -hy_descriptive_version = cgi.escape(hy_version) +hy_descriptive_version = html.escape(hy_version) if "+" in hy_version: hy_descriptive_version += " (unstable)" From 6c74cf1f0748b0656c4f9eafb2b59a4053aa2e37 Mon Sep 17 00:00:00 2001 From: Kodi Arfer Date: Thu, 18 Apr 2019 14:28:19 -0400 Subject: [PATCH 7/7] Add `setx` for assignment expressions --- NEWS.rst | 1 + conftest.py | 5 +++-- docs/language/api.rst | 15 ++++++++++++++ hy/compiler.py | 21 +++++++++++-------- tests/native_tests/py38_only_tests.hy | 29 +++++++++++++++++++++++++++ 5 files changed, 61 insertions(+), 10 deletions(-) create mode 100644 tests/native_tests/py38_only_tests.hy diff --git a/NEWS.rst b/NEWS.rst index f580a7f..18aa8ab 100644 --- a/NEWS.rst +++ b/NEWS.rst @@ -12,6 +12,7 @@ New Features * Python 3.8 is now supported. * Format strings with embedded Hy code (e.g., `f"The sum is {(+ x y)}"`) are now supported, even on Pythons earlier than 3.6. +* Added a special form `setx` to create Python 3.8 assignment expressions. * New list? function. * New tuple? function. diff --git a/conftest.py b/conftest.py index adc035b..9f65b48 100644 --- a/conftest.py +++ b/conftest.py @@ -4,7 +4,7 @@ import importlib import py import pytest import hy -from hy._compat import PY3, PY36 +from hy._compat import PY3, PY36, PY38 NATIVE_TESTS = os.path.join("", "tests", "native_tests", "") @@ -13,7 +13,8 @@ _fspath_pyimport = py.path.local.pyimport def pytest_ignore_collect(path, config): return (("py3_only" in path.basename and not PY3) or - ("py36_only" in path.basename and not PY36) or None) + ("py36_only" in path.basename and not PY36) or + ("py38_only" in path.basename and not PY38) or None) def pyimport_patch_mismatch(self, **kwargs): diff --git a/docs/language/api.rst b/docs/language/api.rst index 93993d1..f37f0c4 100644 --- a/docs/language/api.rst +++ b/docs/language/api.rst @@ -412,6 +412,21 @@ They can be used to assign multiple variables at once: => +``setv`` always returns ``None``. + + +setx +----- + +Whereas ``setv`` creates an assignment statement, ``setx`` creates an assignment expression (see :pep:`572`). It requires Python 3.8 or later. Only one target–value pair is allowed, and the target must be a bare symbol, but the ``setx`` form returns the assigned value instead of ``None``. + +:: + + => (when (> (setx x (+ 1 2)) 0) + ... (print x "is greater than 0")) + 3 is greater than 0 + + defclass -------- diff --git a/hy/compiler.py b/hy/compiler.py index 750d8d2..9a20daf 100755 --- a/hy/compiler.py +++ b/hy/compiler.py @@ -15,7 +15,7 @@ from hy.errors import (HyCompileError, HyTypeError, HyLanguageError, from hy.lex import mangle, unmangle, hy_parse, parse_one_thing, LexException from hy._compat import (string_types, str_type, bytes_type, long_type, PY3, - PY36, reraise) + PY36, PY38, reraise) from hy.macros import require, load_macros, macroexpand, tag_macroexpand import hy.core @@ -1399,15 +1399,16 @@ class HyASTCompiler(object): expr, target=target, value=ret.force_expr, op=op()) @special("setv", [many(FORM + FORM)]) + @special((PY38, "setx"), [times(1, 1, SYM + FORM)]) def compile_def_expression(self, expr, root, pairs): if not pairs: return asty.Name(expr, id='None', ctx=ast.Load()) result = Result() for pair in pairs: - result += self._compile_assign(*pair) + result += self._compile_assign(root, *pair) return result - def _compile_assign(self, name, result): + def _compile_assign(self, root, name, result): str_name = "%s" % name if str_name in (["None"] + (["True", "False"] if PY3 else [])): @@ -1427,14 +1428,18 @@ class HyASTCompiler(object): and isinstance(name, HySymbol) and '.' not in name): result.rename(name) - # Throw away .expr to ensure that (setv ...) returns None. - result.expr = None + if root != HySymbol("setx"): + # Throw away .expr to ensure that (setv ...) returns None. + result.expr = None else: st_name = self._storeize(name, ld_name) - result += asty.Assign( + node = (asty.NamedExpr + if root == HySymbol("setx") + else asty.Assign) + result += node( name if hasattr(name, "start_line") else result, - targets=[st_name], - value=result.force_expr) + value=result.force_expr, + target=st_name, targets=[st_name]) return result diff --git a/tests/native_tests/py38_only_tests.hy b/tests/native_tests/py38_only_tests.hy new file mode 100644 index 0000000..ca337a5 --- /dev/null +++ b/tests/native_tests/py38_only_tests.hy @@ -0,0 +1,29 @@ +;; Copyright 2019 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.8. +;; conftest.py skips this file when running on Python <3.8. + +(import pytest) + +(defn test-setx [] + (setx y (+ (setx x (+ "a" "b")) "c")) + (assert (= x "ab")) + (assert (= y "abc")) + + (setv l []) + (for [x [1 2 3]] + (when (>= (setx y (+ x 8)) 10) + (.append l y))) + (assert (= l [10 11])) + + (setv a ["apple" None "banana"]) + (setv filtered (lfor + i (range (len a)) + :if (is-not (setx v (get a i)) None) + v)) + (assert (= filtered ["apple" "banana"])) + (assert (= v "banana")) + (with [(pytest.raises NameError)] + i))