diff --git a/parso/python/normalizer.py b/parso/python/normalizer.py index f1ededb..04aea04 100644 --- a/parso/python/normalizer.py +++ b/parso/python/normalizer.py @@ -32,6 +32,21 @@ def _iter_stmts(scope): yield child +def _get_comprehension_type(atom): + first, second = atom.children[:2] + if second.type == 'testlist_comp' and second.children[1].type == 'comp_for': + if first == '[': + return 'list comprehension' + else: + return 'generator expression' + elif second.type == 'dictorsetmaker' and second.children[-1].type == 'comp_for': + if second.children[1] == ':': + return 'dict comprehension' + else: + return 'set comprehension' + return None + + def _is_future_import(import_from): # It looks like a __future__ import that is relative is still a future # import. That feels kind of odd, but whatever. @@ -86,9 +101,13 @@ class Context(object): def is_async_funcdef(self): # Stupidly enough async funcdefs can have two different forms, # depending if a decorator is used or not. - return self.node.type == 'funcdef' \ + return self.is_function() \ and self.node.parent.type in ('async_funcdef', 'async_stmt') + + def is_function(self): + return self.node.type == 'funcdef' + @contextmanager def add_block(self, node): self.blocks.append(node) @@ -317,6 +336,29 @@ class ErrorFinder(Normalizer): if self._context.parent_context is None: message = "nonlocal declaration not allowed at module level" self._add_syntax_error(message, node) + elif self._context.is_function(): + for nonlocal_name in node.children[1::2]: + param_names = [p.name.value for p in self._context.node.params] + if nonlocal_name.value == node: + pass + elif node.type == 'expr_stmt': + for before_equal in node.children[:-2:2]: + self._check_assignment(before_equal) + elif node.type == 'with_item': + self._check_assignment(node.children[2]) + elif node.type == 'del_stmt': + child = node.children[1] + if child.type != 'expr_list': # Already handled. + self._check_assignment(child, is_deletion=True) + elif node.type == 'expr_list': + for expr in node.children[::2]: + self._check_assignment(expr) + + # Some of the nodes here are already used, so no else if + if node.type in ('for_stmt', 'comp_for', 'list_for'): + child = node.children[1] + if child.type != 'expr_list': # Already handled. + self._check_assignment(child) yield @@ -392,8 +434,21 @@ class ErrorFinder(Normalizer): # TODO probably this should get a better end_pos including # the next sibling of leaf. self._add_syntax_error(message, leaf) + return '' + def _check_assignment(self, node, is_deletion=False): + error = None + if node.type == 'lambdef': + error = 'lambda' + elif node.type == 'atom': + first, second = node.children[:2] + error = _get_comprehension_type(node) + + if error is not None: + message = "can't %s %s" % ("delete" if is_deletion else "assign to", error) + self._add_syntax_error(message, node) + def _add_indentation_error(self, message, spacing): self._add_error(903, "IndentationError: " + message, spacing) diff --git a/test/test_python_errors.py b/test/test_python_errors.py index 615a3f3..da1d165 100644 --- a/test/test_python_errors.py +++ b/test/test_python_errors.py @@ -72,6 +72,8 @@ def test_indentation_errors(code, positions): 'return', 'yield', 'try: pass\nexcept: pass\nexcept X: pass', + + # SyntaxError from Python/ast.c 'f(x for x in bar, 1)', 'from foo import a,', 'from __future__ import whatever', @@ -101,6 +103,11 @@ def test_indentation_errors(code, positions): 'f(x=2, y)', 'f(**x, *y)', 'f(**x, y=3, z)', + 'lambda a: 1 = 1', + '[x for x in y] = 1', + '{x for x in y} = 1', + '{x:x for x in y} = 1', + '(x for x in y) = 1', # SyntaxErrors from Python/symtable.c 'def f(x, x): pass',