From d76eee2a962c8ceabb6a341768d91c8a4bb83c96 Mon Sep 17 00:00:00 2001 From: Dave Halter Date: Sat, 22 Jul 2017 19:00:24 +0200 Subject: [PATCH] Add 'named arguments must follow bare *' issue and a few conftest tweaks. --- conftest.py | 27 +++++++++++++++++++++++--- parso/python/normalizer.py | 7 +++++++ test/normalizer_issue_files/python3.py | 9 +++++++++ test/test_python_errors.py | 9 +++++++++ 4 files changed, 49 insertions(+), 3 deletions(-) diff --git a/conftest.py b/conftest.py index 273737d..bf66a26 100644 --- a/conftest.py +++ b/conftest.py @@ -109,21 +109,42 @@ def each_py2_version(): class Checker(): def __init__(self, version, is_passing): - self._version = version + self.version = version self._is_passing = is_passing def parse(self, code): if self._is_passing: - return parso.parse(code, version=self._version, error_recovery=False) + return parso.parse(code, version=self.version, error_recovery=False) else: self._invalid_syntax(code) def _invalid_syntax(self, code): with pytest.raises(parso.ParserSyntaxError): - module = parso.parse(code, version=self._version, error_recovery=False) + module = parso.parse(code, version=self.version, error_recovery=False) # For debugging print(module.children) + def get_error(self, code): + errors = list(parso.parse(code, version=self.version)._iter_errors()) + assert bool(errors) != self._is_passing + if errors: + return errors[0] + + def get_error_message(self, code): + error = self.get_error(code) + if error is None: + return + return error.message + + def assert_no_error_in_passing(self, code): + if self._is_passing: + assert not list(parso.parse(code, version=self.version)._iter_errors()) + + +@pytest.fixture +def works_not_in_py(each_version): + return Checker(each_version, False) + @pytest.fixture def works_in_py2(each_version): diff --git a/parso/python/normalizer.py b/parso/python/normalizer.py index edc8b3e..ea1c339 100644 --- a/parso/python/normalizer.py +++ b/parso/python/normalizer.py @@ -199,6 +199,13 @@ class ErrorFinder(Normalizer): and self._context.is_async_funcdef(): yield_ = leaf.parent.parent self._add_syntax_error("'yield from' inside async function", yield_) + elif leaf.value == '*': + params = leaf.parent + if params.type == 'parameters' and params: + after = params.children[params.children.index(leaf) + 1:] + after = [child for child in after if child not in (',', ')')] + if len(after) == 0: + self._add_syntax_error("named arguments must follow bare *", leaf) return '' def _add_indentation_error(self, message, spacing): diff --git a/test/normalizer_issue_files/python3.py b/test/normalizer_issue_files/python3.py index 7d69492..566e903 100644 --- a/test/normalizer_issue_files/python3.py +++ b/test/normalizer_issue_files/python3.py @@ -79,3 +79,12 @@ def b(a:int=1): if alpha[:-i]: *a, b = (1, 2, 3) + + +# Named only arguments +def foo(*, asdf): + pass + + +def foo2(bar, *, asdf=2): + pass diff --git a/test/test_python_errors.py b/test/test_python_errors.py index 5a09b27..1978b9e 100644 --- a/test/test_python_errors.py +++ b/test/test_python_errors.py @@ -174,3 +174,12 @@ def test_future_import_first(): assert is_issue('"";1\n' + i1) assert is_issue('""\n%s\nfrom x import a\n%s', i1, i2) assert is_issue('%s\n""\n%s', i1, i2) + + +def test_named_argument_issues(works_not_in_py): + message = works_not_in_py.get_error_message('def foo(*): pass') + if works_not_in_py.version.startswith('2'): + message + + works_not_in_py.assert_no_error_in_passing('def foo(*, name): pass') + works_not_in_py.assert_no_error_in_passing('def foo(bar, *, name=1): pass')