From 6f29c551fd189661c40439ad2cb511cd0a0f949c Mon Sep 17 00:00:00 2001 From: Batuhan Taskaya Date: Mon, 25 May 2020 23:04:14 +0300 Subject: [PATCH] Adjust invalid aug assign target for 3.9+ --- parso/python/errors.py | 124 ++++++++++++++++++++++++++++++++++++--- test/failing_examples.py | 28 +++++++++ 2 files changed, 145 insertions(+), 7 deletions(-) diff --git a/parso/python/errors.py b/parso/python/errors.py index 2142dd1..af66d9f 100644 --- a/parso/python/errors.py +++ b/parso/python/errors.py @@ -18,6 +18,79 @@ ALLOWED_FUTURES = ( ) _COMP_FOR_TYPES = ('comp_for', 'sync_comp_for') +def _get_rhs_name(node, version): + type_ = node.type + if type_ == "lambdef": + return "lambda" + elif type_ == "atom": + comprehension = _get_comprehension_type(node) + first, second = node.children[:2] + if comprehension is not None: + return comprehension + elif second.type == "dictorsetmaker": + if version < (3, 8): + return "literal" + else: + if second.children[1] == ":" or second.children[0] == "**": + return "dict display" + else: + return "set display" + elif ( + first == "(" + and (second == ")" + or (len(node.children) == 3 and node.children[1].type == "testlist_comp")) + ): + return "tuple" + elif first == "(": + return _get_rhs_name(_remove_parens(node), version=version) + elif first == "[": + return "list" + elif first == "{" and second == "}": + return "dict display" + elif first == "{" and len(node.children) > 2: + return "set display" + elif type_ == "keyword": + if "yield" in node.value: + return "yield expression" + if version < (3, 8): + return "keyword" + else: + return str(node.value) + elif type_ == "operator" and node.value == "...": + return "Ellipsis" + elif type_ == "comparison": + return "comparison" + elif type_ in ("string", "number", "strings"): + return "literal" + elif type_ == "yield_expr": + return "yield expression" + elif type_ == "test": + return "conditional expression" + elif type_ in ("atom_expr", "power"): + if node.children[0] == "await": + return "await expression" + elif node.children[-1].type == "trailer": + trailer = node.children[-1] + if trailer.children[0] == "(": + return "function call" + elif trailer.children[0] == "[": + return "subscript" + elif trailer.children[0] == ".": + return "attribute" + elif ( + ("expr" in type_ + and "star_expr" not in type_) # is a substring + or "_test" in type_ + or type_ in ("term", "factor") + ): + return "operator" + elif type_ == "star_expr": + return "starred" + elif type_ == "testlist_star_expr": + return "tuple" + elif type_ == "fstring": + return "f-string expression" + return type_ # shouldn't reach here def _iter_stmts(scope): """ @@ -926,6 +999,16 @@ class _CheckAssignmentRule(SyntaxRule): error = 'dict display' else: error = 'set display' + elif first == "{" and second == "}": + if self._normalizer.version < (3, 8): + error = 'literal' + else: + error = "dict display" + elif first == "{" and len(node.children) > 2: + if self._normalizer.version < (3, 8): + error = 'literal' + else: + error = "set display" elif first in ('(', '['): if second.type == 'yield_expr': error = 'yield expression' @@ -945,7 +1028,9 @@ class _CheckAssignmentRule(SyntaxRule): else: # Everything handled, must be useless brackets. self._check_assignment(second, is_deletion, is_namedexpr) elif type_ == 'keyword': - if self._normalizer.version < (3, 8): + if node.value == "yield": + error = "yield expression" + elif self._normalizer.version < (3, 8): error = 'keyword' else: error = str(node.value) @@ -977,6 +1062,11 @@ class _CheckAssignmentRule(SyntaxRule): error = 'subscript' elif is_namedexpr and trailer.children[0] == '.': error = 'attribute' + elif type_ == "fstring": + if self._normalizer.version < (3, 8): + error = 'literal' + else: + error = "f-string expression" elif type_ in ('testlist_star_expr', 'exprlist', 'testlist'): for child in node.children[::2]: self._check_assignment(child, is_deletion, is_namedexpr) @@ -1012,15 +1102,35 @@ class _CompForRule(_CheckAssignmentRule): @ErrorFinder.register_rule(type='expr_stmt') class _ExprStmtRule(_CheckAssignmentRule): message = "illegal expression for augmented assignment" - + extended_message = "'{target}' is an " + message def is_issue(self, node): - for before_equal in node.children[:-2:2]: - self._check_assignment(before_equal) - augassign = node.children[1] - if augassign != '=' and augassign.type != 'annassign': # Is augassign. - return node.children[0].type in ('testlist_star_expr', 'atom', 'testlist') + is_aug_assign = augassign != '=' and augassign.type != 'annassign' + if self._normalizer.version <= (3, 8) or not is_aug_assign: + for before_equal in node.children[:-2:2]: + self._check_assignment(before_equal) + + if is_aug_assign: + target = _remove_parens(node.children[0]) + # a, a[b], a.b + + if target.type == "name" or ( + target.type in ("atom_expr", "power") + and target.children[1].type == "trailer" + and target.children[-1].children[0] != "(" + ): + return False + + if self._normalizer.version <= (3, 8): + return True + else: + self.add_issue( + node, + message=self.extended_message.format( + target=_get_rhs_name(node.children[0], self._normalizer.version) + ), + ) @ErrorFinder.register_rule(type='with_item') class _WithItemRule(_CheckAssignmentRule): diff --git a/test/failing_examples.py b/test/failing_examples.py index 8294ec0..c341261 100644 --- a/test/failing_examples.py +++ b/test/failing_examples.py @@ -52,9 +52,37 @@ FAILING_EXAMPLES = [ 'f(x=2, y)', 'f(**x, *y)', 'f(**x, y=3, z)', + # augassign 'a, b += 3', '(a, b) += 3', '[a, b] += 3', + 'f() += 1', + 'lambda x:None+=1', + '{} += 1', + '{a:b} += 1', + '{1} += 1', + '{*x} += 1', + '(x,) += 1', + '(x, y if a else q) += 1', + '[] += 1', + '[1,2] += 1', + '[] += 1', + 'None += 1', + '... += 1', + 'a > 1 += 1', + '"test" += 1', + '1 += 1', + '1.0 += 1', + '(yield) += 1', + '(yield from x) += 1', + '(x if x else y) += 1', + 'a() += 1', + 'a + b += 1', + '+a += 1', + 'a and b += 1', + '*a += 1', + 'a, b += 1', + 'f"xxx" += 1', # All assignment tests 'lambda a: 1 = 1', '[x for x in y] = 1',