diff --git a/parso/python/errors.py b/parso/python/errors.py index af66d9f..d9183cb 100644 --- a/parso/python/errors.py +++ b/parso/python/errors.py @@ -713,13 +713,10 @@ class _FutureImportRule(SyntaxRule): @ErrorFinder.register_rule(type='star_expr') class _StarExprRule(SyntaxRule): - message = "starred assignment target must be in a list or tuple" message_iterable_unpacking = "iterable unpacking cannot be used in comprehension" message_assignment = "can use starred expression only as assignment target" def is_issue(self, node): - if node.parent.type not in _STAR_EXPR_PARENTS: - return True if node.parent.type == 'testlist_comp': # [*[] for a in [1]] if node.parent.children[1].type in _COMP_FOR_TYPES: @@ -982,7 +979,7 @@ class _FStringRule(SyntaxRule): class _CheckAssignmentRule(SyntaxRule): - def _check_assignment(self, node, is_deletion=False, is_namedexpr=False): + def _check_assignment(self, node, is_deletion=False, is_namedexpr=False, is_aug_assign=False): error = None type_ = node.type if type_ == 'lambdef': @@ -1024,9 +1021,9 @@ class _CheckAssignmentRule(SyntaxRule): # This is not a comprehension, they were handled # further above. for child in second.children[::2]: - self._check_assignment(child, is_deletion, is_namedexpr) + self._check_assignment(child, is_deletion, is_namedexpr, is_aug_assign) else: # Everything handled, must be useless brackets. - self._check_assignment(second, is_deletion, is_namedexpr) + self._check_assignment(second, is_deletion, is_namedexpr, is_aug_assign) elif type_ == 'keyword': if node.value == "yield": error = "yield expression" @@ -1069,12 +1066,15 @@ class _CheckAssignmentRule(SyntaxRule): 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) + self._check_assignment(child, is_deletion, is_namedexpr, is_aug_assign) elif ('expr' in type_ and type_ != 'star_expr' # is a substring or '_test' in type_ or type_ in ('term', 'factor')): error = 'operator' elif type_ == "star_expr": + if not search_ancestor(node, *_STAR_EXPR_PARENTS) and not is_aug_assign: + self.add_issue(node, message="starred assignment target must be in a list or tuple") + self._check_assignment(node.children[1]) if error is not None: @@ -1109,7 +1109,7 @@ class _ExprStmtRule(_CheckAssignmentRule): if self._normalizer.version <= (3, 8) or not is_aug_assign: for before_equal in node.children[:-2:2]: - self._check_assignment(before_equal) + self._check_assignment(before_equal, is_aug_assign=is_aug_assign) if is_aug_assign: target = _remove_parens(node.children[0]) diff --git a/test/test_python_errors.py b/test/test_python_errors.py index 743cb25..0b45f11 100644 --- a/test/test_python_errors.py +++ b/test/test_python_errors.py @@ -391,3 +391,26 @@ def test_repeated_kwarg(): ) def test_unparenthesized_genexp(source, no_errors): assert bool(_get_error_list(source)) ^ no_errors + +@pytest.mark.parametrize( + ('source', 'no_errors'), [ + ('*x = 2', False), + ('(*y) = 1', False), + ('((*z)) = 1', False), + ('a, *b = 1', True), + ('a, *b, c = 1', True), + ('a, (*b), c = 1', True), + ('a, ((*b)), c = 1', True), + ('a, (*b, c), d = 1', True), + ('[*(1,2,3)]', True), + ('{*(1,2,3)}', True), + ('[*(1,2,3),]', True), + ('[*(1,2,3), *(4,5,6)]', True), + ('[0, *(1,2,3)]', True), + ('{*(1,2,3),}', True), + ('{*(1,2,3), *(4,5,6)}', True), + ('{0, *(4,5,6)}', True) + ] +) +def test_starred_expr(source, no_errors): + assert bool(_get_error_list(source, version="3")) ^ no_errors