From 7b87d422213fd62fd1a254d896fabab0cef86b93 Mon Sep 17 00:00:00 2001 From: Simon Gomizelj Date: Thu, 8 Feb 2018 14:45:18 -0500 Subject: [PATCH 1/8] Properly teardown subprocesses when testing --- tests/test_bin.py | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) diff --git a/tests/test_bin.py b/tests/test_bin.py index e53626e..33701ea 100644 --- a/tests/test_bin.py +++ b/tests/test_bin.py @@ -37,19 +37,9 @@ def run_cmd(cmd, stdin_data=None, expect=0, dontwritebytecode=False): universal_newlines=True, shell=False, env=env) - if stdin_data is not None: - p.stdin.write(stdin_data) - p.stdin.flush() - p.stdin.close() - # Read stdout and stderr otherwise if the PIPE buffer is full, we might - # wait for ever… - stdout = "" - stderr = "" - while p.poll() is None: - stdout += p.stdout.read() - stderr += p.stderr.read() - assert p.returncode == expect - return stdout, stderr + output = p.communicate(input=stdin_data) + assert p.wait() == expect + return output def rm(fpath): From 316220b7426bad2b45a098d3b5bf1000fd3f9b4d Mon Sep 17 00:00:00 2001 From: Simon Gomizelj Date: Thu, 8 Feb 2018 15:08:09 -0500 Subject: [PATCH 2/8] Fix AST generation of a naked return --- NEWS.rst | 7 +++++++ hy/compiler.py | 6 ++++-- tests/compilers/test_ast.py | 11 ++++++++++- 3 files changed, 21 insertions(+), 3 deletions(-) diff --git a/NEWS.rst b/NEWS.rst index 6896949..e5967d8 100644 --- a/NEWS.rst +++ b/NEWS.rst @@ -1,5 +1,12 @@ .. default-role:: code +Unreleased +============================== + +Bug Fixes +------------------------------ +* Fix `(return)` so it works correctly to exit a Python 2 generator + 0.14.0 ============================== diff --git a/hy/compiler.py b/hy/compiler.py index ccacdce..5d8f915 100755 --- a/hy/compiler.py +++ b/hy/compiler.py @@ -2040,8 +2040,10 @@ class HyASTCompiler(object): @checkargs(max=1) def compile_return(self, expr): ret = Result() - if len(expr) > 1: - ret += self.compile(expr[1]) + if len(expr) == 1: + return asty.Return(expr, value=None) + + ret += self.compile(expr[1]) return ret + asty.Return(expr, value=ret.force_expr) @builds("defclass") diff --git a/tests/compilers/test_ast.py b/tests/compilers/test_ast.py index 4836ad2..0ee88cc 100644 --- a/tests/compilers/test_ast.py +++ b/tests/compilers/test_ast.py @@ -8,7 +8,7 @@ from __future__ import unicode_literals from hy import HyString from hy.models import HyObject from hy.compiler import hy_compile -from hy.importer import import_buffer_to_hst +from hy.importer import hy_eval, import_buffer_to_hst from hy.errors import HyCompileError, HyTypeError from hy.lex.exceptions import LexException from hy._compat import PY3 @@ -30,6 +30,10 @@ def can_compile(expr): return hy_compile(import_buffer_to_hst(expr), "__main__") +def can_eval(expr): + return hy_eval(import_buffer_to_hst(expr)) + + def cant_compile(expr): try: hy_compile(import_buffer_to_hst(expr), "__main__") @@ -664,3 +668,8 @@ def test_ast_good_yield_from(): def test_ast_bad_yield_from(): "Make sure AST can't compile invalid yield-from" cant_compile("(yield-from)") + + +def test_eval_generator_with_return(): + """Ensure generators with a return statement works.""" + can_eval("(fn [] (yield 1) (yield 2) (return))") From be6ddd4bb2c856549d682a985d43c0180a24581b Mon Sep 17 00:00:00 2001 From: Simon Gomizelj Date: Thu, 8 Feb 2018 14:56:41 -0500 Subject: [PATCH 3/8] Fix deprecation warning triggered by PEP 479 --- hy/contrib/sequences.hy | 6 +++--- hy/core/language.hy | 5 ++++- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/hy/contrib/sequences.hy b/hy/contrib/sequences.hy index 08630ce..703da2b 100644 --- a/hy/contrib/sequences.hy +++ b/hy/contrib/sequences.hy @@ -29,15 +29,15 @@ (try (while True (yield (get self index)) (setv index (inc index))) - (except [_ IndexError] - (raise StopIteration)))) + (except [IndexError] + (return)))) --len-- (fn [self] "length of the sequence, dangerous for infinite sequences" (setv index (. self high-water)) (try (while True (get self index) (setv index (inc index))) - (except [_ IndexError] + (except [IndexError] (len (. self cache))))) max-items-in-repr 10 --str-- (fn [self] diff --git a/hy/core/language.hy b/hy/core/language.hy index d68a237..cda8d0a 100644 --- a/hy/core/language.hy +++ b/hy/core/language.hy @@ -422,7 +422,10 @@ Raises ValueError for (not (pos? n))." (for* [val citer] (yield val) (for* [_ (range skip)] - (next citer)))) + (try + (next citer) + (except [StopIteration] + (return)))))) (defn zero? [n] "Check if `n` equals 0." From c35d2a3a228b784d2e1294b34c913b8e5732252a Mon Sep 17 00:00:00 2001 From: Simon Gomizelj Date: Thu, 8 Feb 2018 21:43:20 -0500 Subject: [PATCH 4/8] Drop --allow-all-external, deprecated by PEP 470 --- Makefile | 2 +- tox.ini | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 4658b6f..13aaa6f 100644 --- a/Makefile +++ b/Makefile @@ -64,7 +64,7 @@ ifeq (Python 2.6,$(findstring Python 2.6,$(shell python -V 2>&1))) endif $(pip) install -r requirements-travis.txt $(pip) install coveralls - $(pip) install --allow-all-external -e . + $(pip) install -e . coveralls: $(coveralls) diff --git a/tox.ini b/tox.ini index 1f5be88..412d752 100644 --- a/tox.ini +++ b/tox.ini @@ -4,7 +4,7 @@ skipsdist = True [testenv] commands = - pip install --allow-all-external -e . + pip install -e . pytest passenv = TERM From 97d7bbb96bf1e7bfe43d9637b82f08f8820b8677 Mon Sep 17 00:00:00 2001 From: Simon Gomizelj Date: Thu, 8 Feb 2018 21:45:33 -0500 Subject: [PATCH 5/8] Add posargs to tox test runner --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 412d752..ad4e2cb 100644 --- a/tox.ini +++ b/tox.ini @@ -5,7 +5,7 @@ skipsdist = True [testenv] commands = pip install -e . - pytest + pytest {posargs} passenv = TERM deps = From ef2f4356475fb4a6390bcdbc2f80ffe480c4065f Mon Sep 17 00:00:00 2001 From: Simon Gomizelj Date: Thu, 8 Feb 2018 21:46:03 -0500 Subject: [PATCH 6/8] Add a hy.introspect module to deal with getargspec deprecation --- hy/compiler.py | 9 +++++---- hy/importer.py | 2 ++ hy/inspect.py | 37 +++++++++++++++++++++++++++++++++++++ hy/macros.py | 10 +++------- 4 files changed, 47 insertions(+), 11 deletions(-) create mode 100644 hy/inspect.py diff --git a/hy/compiler.py b/hy/compiler.py index 5d8f915..e6a888e 100755 --- a/hy/compiler.py +++ b/hy/compiler.py @@ -16,6 +16,7 @@ from hy._compat import ( raise_empty) from hy.macros import require, macroexpand, tag_macroexpand import hy.importer +import hy.inspect import traceback import importlib @@ -24,7 +25,6 @@ import ast import sys import keyword import copy -import inspect from collections import defaultdict from cmath import isnan @@ -441,10 +441,11 @@ class HyASTCompiler(object): # _compile_table[atom_type] is a method for compiling this # type of atom, so call it. If it has an extra parameter, # pass in `atom_type`. - arity = len(inspect.getargspec(_compile_table[atom_type])[0]) - ret = (_compile_table[atom_type](self, atom, atom_type) + atom_compiler = _compile_table[atom_type] + arity = hy.inspect.get_arity(atom_compiler) + ret = (atom_compiler(self, atom, atom_type) if arity == 3 - else _compile_table[atom_type](self, atom)) + else atom_compiler(self, atom)) if not isinstance(ret, Result): ret = Result() + ret return ret diff --git a/hy/importer.py b/hy/importer.py index 450de27..1b368a8 100644 --- a/hy/importer.py +++ b/hy/importer.py @@ -2,6 +2,8 @@ # This file is part of Hy, which is free software licensed under the Expat # license. See the LICENSE. +from __future__ import absolute_import + from hy.compiler import hy_compile, HyTypeError from hy.models import HyObject, HyExpression, HySymbol, replace_hy_obj from hy.lex import tokenize, LexException diff --git a/hy/inspect.py b/hy/inspect.py new file mode 100644 index 0000000..ff972ec --- /dev/null +++ b/hy/inspect.py @@ -0,0 +1,37 @@ +# Copyright 2018 the authors. +# This file is part of Hy, which is free software licensed under the Expat +# license. See the LICENSE. + +from __future__ import absolute_import + +import inspect + +try: + # Check if we have the newer inspect.signature available. + # Otherwise fallback to the legacy getargspec. + inspect.signature # noqa +except AttributeError: + def get_arity(fn): + return len(inspect.getargspec(fn)[0]) + + def has_kwargs(fn): + argspec = inspect.getargspec(fn) + return argspec.keywords is not None + + def format_args(fn): + argspec = inspect.getargspec(fn) + return inspect.formatargspec(*argspec) + +else: + def get_arity(fn): + parameters = inspect.signature(fn).parameters + return sum(1 for param in parameters.values() + if param.kind == param.POSITIONAL_OR_KEYWORD) + + def has_kwargs(fn): + parameters = inspect.signature(fn).parameters + return any(param.kind == param.VAR_KEYWORD + for param in parameters.values()) + + def format_args(fn): + return str(inspect.signature(fn)) diff --git a/hy/macros.py b/hy/macros.py index 9b1a743..0613711 100644 --- a/hy/macros.py +++ b/hy/macros.py @@ -2,9 +2,8 @@ # This file is part of Hy, which is free software licensed under the Expat # license. See the LICENSE. -from inspect import getargspec, formatargspec +import hy.inspect from hy.models import replace_hy_obj, HyExpression, HySymbol - from hy.errors import HyTypeError, HyMacroExpansionError from collections import defaultdict @@ -36,8 +35,7 @@ def macro(name): def _(fn): fn.__name__ = '({})'.format(name) try: - argspec = getargspec(fn) - fn._hy_macro_pass_compiler = argspec.keywords is not None + fn._hy_macro_pass_compiler = hy.inspect.has_kwargs(fn) except Exception: # An exception might be raised if fn has arguments with # names that are invalid in Python. @@ -136,9 +134,7 @@ def make_empty_fn_copy(fn): # can continue running. Unfortunately, the error message that might get # raised later on while expanding a macro might not make sense at all. - argspec = getargspec(fn) - formatted_args = formatargspec(*argspec) - + formatted_args = hy.inspect.format_args(fn) fn_str = 'lambda {}: None'.format( formatted_args.lstrip('(').rstrip(')')) empty_fn = eval(fn_str) From cf304714ce09ededb13ad1b12f3eb07c95149621 Mon Sep 17 00:00:00 2001 From: Simon Gomizelj Date: Thu, 8 Feb 2018 22:28:54 -0500 Subject: [PATCH 7/8] Turn on pytests's warning reporter --- setup.cfg | 3 +++ 1 file changed, 3 insertions(+) diff --git a/setup.cfg b/setup.cfg index 3923777..18c8fbc 100644 --- a/setup.cfg +++ b/setup.cfg @@ -20,3 +20,6 @@ ignore_errors = True # Be sure to include Hy test functions whose names end with "?", # which will be mangled to begin with "is_". python_functions=test_* is_test_* +filterwarnings = + once::DeprecationWarning + once::PendingDeprecationWarning From db58dacce668e0d50ef314862e1df674351b39a4 Mon Sep 17 00:00:00 2001 From: Simon Gomizelj Date: Thu, 8 Feb 2018 22:42:06 -0500 Subject: [PATCH 8/8] Fix invalid escape sequence \s in test_escapes --- tests/test_lex.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/test_lex.py b/tests/test_lex.py index c9009b2..8c236c5 100644 --- a/tests/test_lex.py +++ b/tests/test_lex.py @@ -304,11 +304,11 @@ def test_nospace(): def test_escapes(): """ Ensure we can escape things """ - entry = tokenize("(foo \"foo\\n\")")[0] + entry = tokenize(r"""(foo "foo\n")""")[0] assert entry[1] == "foo\n" - entry = tokenize("(foo \"foo\\s\")")[0] - assert entry[1] == "foo\\s" + entry = tokenize(r"""(foo r"foo\s")""")[0] + assert entry[1] == r"foo\s" def test_unicode_escapes():