diff --git a/parso/python/errors.py b/parso/python/errors.py index 71f3b88..6dd6175 100644 --- a/parso/python/errors.py +++ b/parso/python/errors.py @@ -16,6 +16,7 @@ ALLOWED_FUTURES = ( 'all_feature_names', 'nested_scopes', 'generators', 'division', 'absolute_import', 'with_statement', 'print_function', 'unicode_literals', ) +_COMP_FOR_TYPES = ('comp_for', 'sync_comp_for') def _iter_stmts(scope): @@ -34,12 +35,12 @@ def _iter_stmts(scope): def _get_comprehension_type(atom): first, second = atom.children[:2] - if second.type == 'testlist_comp' and second.children[1].type == 'comp_for': + if second.type == 'testlist_comp' and second.children[1].type in _COMP_FOR_TYPES: if first == '[': return 'list comprehension' else: return 'generator expression' - elif second.type == 'dictorsetmaker' and second.children[-1].type == 'comp_for': + elif second.type == 'dictorsetmaker' and second.children[-1].type in _COMP_FOR_TYPES: if second.children[1] == ':': return 'dict comprehension' else: @@ -460,17 +461,13 @@ class _YieldFromCheck(SyntaxRule): @ErrorFinder.register_rule(type='name') class _NameChecks(SyntaxRule): message = 'cannot assign to __debug__' - message_keyword = 'assignment to keyword' message_none = 'cannot assign to None' def is_issue(self, leaf): self._normalizer.context.add_name(leaf) if leaf.value == '__debug__' and leaf.is_definition(): - if self._normalizer.version < (3, 0): - return True - else: - self.add_issue(leaf, message=self.message_keyword) + return True if leaf.value == 'None' and self._normalizer.version < (3, 0) \ and leaf.is_definition(): self.add_issue(leaf, message=self.message_none) @@ -538,7 +535,7 @@ class _StarStarCheck(SyntaxRule): def is_issue(self, leaf): if leaf.parent.type == 'dictorsetmaker': comp_for = leaf.get_next_sibling().get_next_sibling() - return comp_for is not None and comp_for.type == 'comp_for' + return comp_for is not None and comp_for.type in _COMP_FOR_TYPES @ErrorFinder.register_rule(value='yield') @@ -637,7 +634,7 @@ class _StarExprRule(SyntaxRule): return True if node.parent.type == 'testlist_comp': # [*[] for a in [1]] - if node.parent.children[1].type == 'comp_for': + if node.parent.children[1].type in _COMP_FOR_TYPES: self.add_issue(node, message=self.message_iterable_unpacking) if self._normalizer.version <= (3, 4): n = search_ancestor(node, 'for_stmt', 'expr_stmt') @@ -730,10 +727,16 @@ class _ArgumentRule(SyntaxRule): if node.children[1] == '=' and first.type != 'name': if first.type == 'lambdef': # f(lambda: 1=1) - message = "lambda cannot contain assignment" + if self._normalizer.version < (3, 8): + message = "lambda cannot contain assignment" + else: + message = 'expression cannot contain assignment, perhaps you meant "=="?' else: # f(+x=1) - message = "keyword can't be an expression" + if self._normalizer.version < (3, 8): + message = "keyword can't be an expression" + else: + message = 'expression cannot contain assignment, perhaps you meant "=="?' self.add_issue(first, message=message) @@ -757,7 +760,7 @@ class _ArglistRule(SyntaxRule): def is_issue(self, node): first_arg = node.children[0] if first_arg.type == 'argument' \ - and first_arg.children[1].type in ('comp_for', 'sync_comp_for'): + and first_arg.children[1].type in _COMP_FOR_TYPES: # e.g. foo(x for x in [], b) return len(node.children) >= 2 else: @@ -890,7 +893,13 @@ class _CheckAssignmentRule(SyntaxRule): error = _get_comprehension_type(node) if error is None: if second.type == 'dictorsetmaker': - error = 'literal' + if self._normalizer.version < (3, 8): + error = 'literal' + else: + if second.children[1] == ':': + error = 'dict display' + else: + error = 'set display' elif first in ('(', '['): if second.type == 'yield_expr': error = 'yield expression' @@ -902,7 +911,10 @@ class _CheckAssignmentRule(SyntaxRule): else: # Everything handled, must be useless brackets. self._check_assignment(second, is_deletion) elif type_ == 'keyword': - error = 'keyword' + if self._normalizer.version < (3, 8): + error = 'keyword' + else: + error = str(node.value) elif type_ == 'operator': if node.value == '...': error = 'Ellipsis' @@ -936,19 +948,23 @@ class _CheckAssignmentRule(SyntaxRule): error = 'operator' if error is not None: - message = "can't %s %s" % ("delete" if is_deletion else "assign to", error) + cannot = "can't" if self._normalizer.version < (3, 8) else "cannot" + message = ' '.join([cannot, "delete" if is_deletion else "assign to", error]) self.add_issue(node, message=message) @ErrorFinder.register_rule(type='comp_for') +@ErrorFinder.register_rule(type='sync_comp_for') class _CompForRule(_CheckAssignmentRule): message = "asynchronous comprehension outside of an asynchronous function" def is_issue(self, node): # Some of the nodes here are already used, so no else if - expr_list = node.children[1 + int(node.children[0] == 'async')] - if expr_list.type != 'expr_list': # Already handled. - self._check_assignment(expr_list) + if node.type != 'comp_for' or self._normalizer.version < (3, 8): + # comp_for was replaced by sync_comp_for in Python 3.8. + expr_list = node.children[1 + int(node.children[0] == 'async')] + if expr_list.type != 'expr_list': # Already handled. + self._check_assignment(expr_list) return node.children[0] == 'async' \ and not self._normalizer.context.is_async_funcdef() diff --git a/test/failing_examples.py b/test/failing_examples.py index f407220..c2cc930 100644 --- a/test/failing_examples.py +++ b/test/failing_examples.py @@ -251,10 +251,6 @@ GLOBAL_NONLOCAL_ERROR = [ if sys.version_info >= (3, 6): FAILING_EXAMPLES += GLOBAL_NONLOCAL_ERROR - FAILING_EXAMPLES += [ - # Raises multiple errors in previous versions. - 'async def foo():\n def nofoo():[x async for x in []]', - ] if sys.version_info >= (3, 5): FAILING_EXAMPLES += [ # Raises different errors so just ignore them for now. diff --git a/test/test_python_errors.py b/test/test_python_errors.py index 4b73cde..71a67eb 100644 --- a/test/test_python_errors.py +++ b/test/test_python_errors.py @@ -41,6 +41,29 @@ def test_python_exception_matches(code): assert line_nr is None or line_nr == error.start_pos[0] +def test_non_async_in_async(): + """ + This example doesn't work with FAILING_EXAMPLES, because the line numbers + are not always the same / incorrect in Python 3.8. + """ + if sys.version_info[:2] < (3, 5): + pytest.skip() + + # Raises multiple errors in previous versions. + code = 'async def foo():\n def nofoo():[x async for x in []]' + wanted, line_nr = _get_actual_exception(code) + + errors = _get_error_list(code) + if errors: + error, = errors + actual = error.message + assert actual in wanted + if sys.version_info[:2] < (3, 8): + assert line_nr == error.start_pos[0] + else: + assert line_nr == 0 # For whatever reason this is zero in Python 3.8+ + + @pytest.mark.parametrize( ('code', 'positions'), [ ('1 +', [(1, 3)]), @@ -103,7 +126,8 @@ def _get_actual_exception(code): # The python 3.5+ way, a bit nicer. wanted = 'SyntaxError: positional argument follows keyword argument' elif wanted == 'SyntaxError: assignment to keyword': - return [wanted, "SyntaxError: can't assign to keyword"], line_nr + return [wanted, "SyntaxError: can't assign to keyword", + 'SyntaxError: cannot assign to __debug__'], line_nr elif wanted == 'SyntaxError: assignment to None': # Python 2.6 does has a slightly different error. wanted = 'SyntaxError: cannot assign to None'