Merge pull request #1500 from vodik/deprecations

Deal with some deprecation and resource warnings / fix (return) semantics
This commit is contained in:
Tuukka Turto 2018-02-27 23:20:49 +02:00 committed by GitHub
commit c1e5c3e48c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 87 additions and 37 deletions

View File

@ -64,7 +64,7 @@ ifeq (Python 2.6,$(findstring Python 2.6,$(shell python -V 2>&1)))
endif endif
$(pip) install -r requirements-travis.txt $(pip) install -r requirements-travis.txt
$(pip) install coveralls $(pip) install coveralls
$(pip) install --allow-all-external -e . $(pip) install -e .
coveralls: coveralls:
$(coveralls) $(coveralls)

View File

@ -1,5 +1,12 @@
.. default-role:: code .. default-role:: code
Unreleased
==============================
Bug Fixes
------------------------------
* Fix `(return)` so it works correctly to exit a Python 2 generator
0.14.0 0.14.0
============================== ==============================

View File

@ -16,6 +16,7 @@ from hy._compat import (
raise_empty) raise_empty)
from hy.macros import require, macroexpand, tag_macroexpand from hy.macros import require, macroexpand, tag_macroexpand
import hy.importer import hy.importer
import hy.inspect
import traceback import traceback
import importlib import importlib
@ -24,7 +25,6 @@ import ast
import sys import sys
import keyword import keyword
import copy import copy
import inspect
from collections import defaultdict from collections import defaultdict
from cmath import isnan from cmath import isnan
@ -441,10 +441,11 @@ class HyASTCompiler(object):
# _compile_table[atom_type] is a method for compiling this # _compile_table[atom_type] is a method for compiling this
# type of atom, so call it. If it has an extra parameter, # type of atom, so call it. If it has an extra parameter,
# pass in `atom_type`. # pass in `atom_type`.
arity = len(inspect.getargspec(_compile_table[atom_type])[0]) atom_compiler = _compile_table[atom_type]
ret = (_compile_table[atom_type](self, atom, atom_type) arity = hy.inspect.get_arity(atom_compiler)
ret = (atom_compiler(self, atom, atom_type)
if arity == 3 if arity == 3
else _compile_table[atom_type](self, atom)) else atom_compiler(self, atom))
if not isinstance(ret, Result): if not isinstance(ret, Result):
ret = Result() + ret ret = Result() + ret
return ret return ret
@ -2040,7 +2041,9 @@ class HyASTCompiler(object):
@checkargs(max=1) @checkargs(max=1)
def compile_return(self, expr): def compile_return(self, expr):
ret = Result() ret = Result()
if len(expr) > 1: if len(expr) == 1:
return asty.Return(expr, value=None)
ret += self.compile(expr[1]) ret += self.compile(expr[1])
return ret + asty.Return(expr, value=ret.force_expr) return ret + asty.Return(expr, value=ret.force_expr)

View File

@ -29,15 +29,15 @@
(try (while True (try (while True
(yield (get self index)) (yield (get self index))
(setv index (inc index))) (setv index (inc index)))
(except [_ IndexError] (except [IndexError]
(raise StopIteration)))) (return))))
--len-- (fn [self] --len-- (fn [self]
"length of the sequence, dangerous for infinite sequences" "length of the sequence, dangerous for infinite sequences"
(setv index (. self high-water)) (setv index (. self high-water))
(try (while True (try (while True
(get self index) (get self index)
(setv index (inc index))) (setv index (inc index)))
(except [_ IndexError] (except [IndexError]
(len (. self cache))))) (len (. self cache)))))
max-items-in-repr 10 max-items-in-repr 10
--str-- (fn [self] --str-- (fn [self]

View File

@ -422,7 +422,10 @@ Raises ValueError for (not (pos? n))."
(for* [val citer] (for* [val citer]
(yield val) (yield val)
(for* [_ (range skip)] (for* [_ (range skip)]
(next citer)))) (try
(next citer)
(except [StopIteration]
(return))))))
(defn zero? [n] (defn zero? [n]
"Check if `n` equals 0." "Check if `n` equals 0."

View File

@ -2,6 +2,8 @@
# This file is part of Hy, which is free software licensed under the Expat # This file is part of Hy, which is free software licensed under the Expat
# license. See the LICENSE. # license. See the LICENSE.
from __future__ import absolute_import
from hy.compiler import hy_compile, HyTypeError from hy.compiler import hy_compile, HyTypeError
from hy.models import HyObject, HyExpression, HySymbol, replace_hy_obj from hy.models import HyObject, HyExpression, HySymbol, replace_hy_obj
from hy.lex import tokenize, LexException from hy.lex import tokenize, LexException

37
hy/inspect.py Normal file
View File

@ -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))

View File

@ -2,9 +2,8 @@
# This file is part of Hy, which is free software licensed under the Expat # This file is part of Hy, which is free software licensed under the Expat
# license. See the LICENSE. # license. See the LICENSE.
from inspect import getargspec, formatargspec import hy.inspect
from hy.models import replace_hy_obj, HyExpression, HySymbol from hy.models import replace_hy_obj, HyExpression, HySymbol
from hy.errors import HyTypeError, HyMacroExpansionError from hy.errors import HyTypeError, HyMacroExpansionError
from collections import defaultdict from collections import defaultdict
@ -36,8 +35,7 @@ def macro(name):
def _(fn): def _(fn):
fn.__name__ = '({})'.format(name) fn.__name__ = '({})'.format(name)
try: try:
argspec = getargspec(fn) fn._hy_macro_pass_compiler = hy.inspect.has_kwargs(fn)
fn._hy_macro_pass_compiler = argspec.keywords is not None
except Exception: except Exception:
# An exception might be raised if fn has arguments with # An exception might be raised if fn has arguments with
# names that are invalid in Python. # 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 # can continue running. Unfortunately, the error message that might get
# raised later on while expanding a macro might not make sense at all. # raised later on while expanding a macro might not make sense at all.
argspec = getargspec(fn) formatted_args = hy.inspect.format_args(fn)
formatted_args = formatargspec(*argspec)
fn_str = 'lambda {}: None'.format( fn_str = 'lambda {}: None'.format(
formatted_args.lstrip('(').rstrip(')')) formatted_args.lstrip('(').rstrip(')'))
empty_fn = eval(fn_str) empty_fn = eval(fn_str)

View File

@ -20,3 +20,6 @@ ignore_errors = True
# Be sure to include Hy test functions whose names end with "?", # Be sure to include Hy test functions whose names end with "?",
# which will be mangled to begin with "is_". # which will be mangled to begin with "is_".
python_functions=test_* is_test_* python_functions=test_* is_test_*
filterwarnings =
once::DeprecationWarning
once::PendingDeprecationWarning

View File

@ -8,7 +8,7 @@ from __future__ import unicode_literals
from hy import HyString from hy import HyString
from hy.models import HyObject from hy.models import HyObject
from hy.compiler import hy_compile 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.errors import HyCompileError, HyTypeError
from hy.lex.exceptions import LexException from hy.lex.exceptions import LexException
from hy._compat import PY3 from hy._compat import PY3
@ -30,6 +30,10 @@ def can_compile(expr):
return hy_compile(import_buffer_to_hst(expr), "__main__") 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): def cant_compile(expr):
try: try:
hy_compile(import_buffer_to_hst(expr), "__main__") hy_compile(import_buffer_to_hst(expr), "__main__")
@ -664,3 +668,8 @@ def test_ast_good_yield_from():
def test_ast_bad_yield_from(): def test_ast_bad_yield_from():
"Make sure AST can't compile invalid yield-from" "Make sure AST can't compile invalid yield-from"
cant_compile("(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))")

View File

@ -37,19 +37,9 @@ def run_cmd(cmd, stdin_data=None, expect=0, dontwritebytecode=False):
universal_newlines=True, universal_newlines=True,
shell=False, shell=False,
env=env) env=env)
if stdin_data is not None: output = p.communicate(input=stdin_data)
p.stdin.write(stdin_data) assert p.wait() == expect
p.stdin.flush() return output
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
def rm(fpath): def rm(fpath):

View File

@ -304,11 +304,11 @@ def test_nospace():
def test_escapes(): def test_escapes():
""" Ensure we can escape things """ """ Ensure we can escape things """
entry = tokenize("(foo \"foo\\n\")")[0] entry = tokenize(r"""(foo "foo\n")""")[0]
assert entry[1] == "foo\n" assert entry[1] == "foo\n"
entry = tokenize("(foo \"foo\\s\")")[0] entry = tokenize(r"""(foo r"foo\s")""")[0]
assert entry[1] == "foo\\s" assert entry[1] == r"foo\s"
def test_unicode_escapes(): def test_unicode_escapes():

View File

@ -4,8 +4,8 @@ skipsdist = True
[testenv] [testenv]
commands = commands =
pip install --allow-all-external -e . pip install -e .
pytest pytest {posargs}
passenv = passenv =
TERM TERM
deps = deps =