mirror of
https://github.com/davidhalter/parso.git
synced 2025-12-06 21:04:29 +08:00
Fix various issues regarding starred expressions
- Properly check for starred expression deletion - Check for starred expressions not in tuple/list/set (when not in assignment) - Fix a bug that considered starred expression assignment `[*x] = 1` as invalid - Enhance test cases for valid and invalid `del` statements and starred expressions
This commit is contained in:
@@ -147,6 +147,18 @@ def _remove_parens(atom):
|
|||||||
return atom
|
return atom
|
||||||
|
|
||||||
|
|
||||||
|
def _skip_parens_bottom_up(node):
|
||||||
|
"""
|
||||||
|
Returns an ancestor node of an expression, skipping all levels of parens
|
||||||
|
bottom-up.
|
||||||
|
"""
|
||||||
|
while node.parent is not None:
|
||||||
|
node = node.parent
|
||||||
|
if node.type != 'atom' or node.children[0] != '(':
|
||||||
|
return node
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
def _iter_params(parent_node):
|
def _iter_params(parent_node):
|
||||||
return (n for n in parent_node.children if n.type == 'param')
|
return (n for n in parent_node.children if n.type == 'param')
|
||||||
|
|
||||||
@@ -730,24 +742,39 @@ class _FutureImportRule(SyntaxRule):
|
|||||||
@ErrorFinder.register_rule(type='star_expr')
|
@ErrorFinder.register_rule(type='star_expr')
|
||||||
class _StarExprRule(SyntaxRule):
|
class _StarExprRule(SyntaxRule):
|
||||||
message_iterable_unpacking = "iterable unpacking cannot be used in comprehension"
|
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):
|
def is_issue(self, node):
|
||||||
|
def check_delete_starred(node):
|
||||||
|
while node.parent is not None:
|
||||||
|
node = node.parent
|
||||||
|
if node.type == 'del_stmt':
|
||||||
|
return True
|
||||||
|
if node.type not in (*_STAR_EXPR_PARENTS, 'atom'):
|
||||||
|
return False
|
||||||
|
return False
|
||||||
|
|
||||||
|
if check_delete_starred(node):
|
||||||
|
if self._normalizer.version >= (3, 9):
|
||||||
|
self.add_issue(node, message="cannot delete starred")
|
||||||
|
else:
|
||||||
|
self.add_issue(node, message="can't use starred expression here")
|
||||||
|
return
|
||||||
|
|
||||||
if node.parent.type == 'testlist_comp':
|
if node.parent.type == 'testlist_comp':
|
||||||
# [*[] for a in [1]]
|
# [*[] for a in [1]]
|
||||||
if node.parent.children[1].type in _COMP_FOR_TYPES:
|
if node.parent.children[1].type in _COMP_FOR_TYPES:
|
||||||
self.add_issue(node, message=self.message_iterable_unpacking)
|
self.add_issue(node, message=self.message_iterable_unpacking)
|
||||||
|
return
|
||||||
|
|
||||||
|
ancestor = _skip_parens_bottom_up(node)
|
||||||
|
# starred expression not in tuple/list/set
|
||||||
|
if ancestor.type not in (*_STAR_EXPR_PARENTS, 'dictorsetmaker', 'atom'):
|
||||||
|
self.add_issue(node, message="can't use starred expression here")
|
||||||
|
|
||||||
|
|
||||||
@ErrorFinder.register_rule(types=_STAR_EXPR_PARENTS)
|
@ErrorFinder.register_rule(types=_STAR_EXPR_PARENTS)
|
||||||
class _StarExprParentRule(SyntaxRule):
|
class _StarExprParentRule(SyntaxRule):
|
||||||
def is_issue(self, node):
|
def is_issue(self, node):
|
||||||
if node.parent.type == 'del_stmt':
|
|
||||||
if self._normalizer.version >= (3, 9):
|
|
||||||
self.add_issue(node.parent, message="cannot delete starred")
|
|
||||||
else:
|
|
||||||
self.add_issue(node.parent, message="can't use starred expression here")
|
|
||||||
else:
|
|
||||||
def is_definition(node, ancestor):
|
def is_definition(node, ancestor):
|
||||||
if ancestor is None:
|
if ancestor is None:
|
||||||
return False
|
return False
|
||||||
@@ -1079,8 +1106,12 @@ class _CheckAssignmentRule(SyntaxRule):
|
|||||||
error = "starred"
|
error = "starred"
|
||||||
else:
|
else:
|
||||||
self.add_issue(node, message="can't use starred expression here")
|
self.add_issue(node, message="can't use starred expression here")
|
||||||
elif not search_ancestor(node, *_STAR_EXPR_PARENTS) and not is_aug_assign:
|
else:
|
||||||
self.add_issue(node, message="starred assignment target must be in a list or tuple")
|
ancestor = _skip_parens_bottom_up(node)
|
||||||
|
if ancestor.type not in _STAR_EXPR_PARENTS and not is_aug_assign \
|
||||||
|
and not (ancestor.type == 'atom' and ancestor.children[0] == '['):
|
||||||
|
message = "starred assignment target must be in a list or tuple"
|
||||||
|
self.add_issue(node, message=message)
|
||||||
|
|
||||||
self._check_assignment(node.children[1])
|
self._check_assignment(node.children[1])
|
||||||
|
|
||||||
|
|||||||
@@ -145,6 +145,44 @@ FAILING_EXAMPLES = [
|
|||||||
'([False], a) = x',
|
'([False], a) = x',
|
||||||
'def x(): from math import *',
|
'def x(): from math import *',
|
||||||
|
|
||||||
|
# invalid del statements
|
||||||
|
'del x + y',
|
||||||
|
'del x(y)',
|
||||||
|
'async def foo(): del await x',
|
||||||
|
'def foo(): del (yield x)',
|
||||||
|
'del [x for x in range(10)]',
|
||||||
|
'del *x',
|
||||||
|
'del *x,',
|
||||||
|
'del (*x,)',
|
||||||
|
'del [*x]',
|
||||||
|
'del x, *y',
|
||||||
|
'del *x.y,',
|
||||||
|
'del *x[y],',
|
||||||
|
'del *x[y::], z',
|
||||||
|
'del x, (y, *z)',
|
||||||
|
'del (x, *[y, z])',
|
||||||
|
'del [x, *(y, [*z])]',
|
||||||
|
'del {}',
|
||||||
|
'del {x}',
|
||||||
|
'del {x, y}',
|
||||||
|
'del {x, *y}',
|
||||||
|
|
||||||
|
# invalid starred expressions
|
||||||
|
'*x',
|
||||||
|
'(*x)',
|
||||||
|
'((*x))',
|
||||||
|
'1 + (*x)',
|
||||||
|
'*x; 1',
|
||||||
|
'1; *x',
|
||||||
|
'1\n*x',
|
||||||
|
'x = *y',
|
||||||
|
'x: int = *y',
|
||||||
|
'def foo(): return *x',
|
||||||
|
'def foo(): yield *x',
|
||||||
|
'f"{*x}"',
|
||||||
|
'for *x in 1: pass',
|
||||||
|
'[1 for *x in 1]',
|
||||||
|
|
||||||
# str/bytes combinations
|
# str/bytes combinations
|
||||||
'"s" b""',
|
'"s" b""',
|
||||||
'"s" b"" ""',
|
'"s" b"" ""',
|
||||||
@@ -198,6 +236,9 @@ FAILING_EXAMPLES = [
|
|||||||
'[*[] for a in [1]]',
|
'[*[] for a in [1]]',
|
||||||
'async def bla():\n def x(): await bla()',
|
'async def bla():\n def x(): await bla()',
|
||||||
'del None',
|
'del None',
|
||||||
|
'del True',
|
||||||
|
'del False',
|
||||||
|
'del ...',
|
||||||
|
|
||||||
# Errors of global / nonlocal
|
# Errors of global / nonlocal
|
||||||
dedent('''
|
dedent('''
|
||||||
|
|||||||
@@ -415,11 +415,32 @@ def test_unparenthesized_genexp(source, no_errors):
|
|||||||
('*x = 2', False),
|
('*x = 2', False),
|
||||||
('(*y) = 1', False),
|
('(*y) = 1', False),
|
||||||
('((*z)) = 1', False),
|
('((*z)) = 1', False),
|
||||||
|
('*a,', True),
|
||||||
|
('*a, = 1', True),
|
||||||
|
('(*a,)', True),
|
||||||
|
('(*a,) = 1', True),
|
||||||
|
('[*a]', True),
|
||||||
|
('[*a] = 1', True),
|
||||||
|
('a, *b', True),
|
||||||
('a, *b = 1', True),
|
('a, *b = 1', True),
|
||||||
|
('a, *b, c', True),
|
||||||
('a, *b, c = 1', True),
|
('a, *b, c = 1', True),
|
||||||
|
('a, (*b), c', True),
|
||||||
('a, (*b), c = 1', True),
|
('a, (*b), c = 1', True),
|
||||||
|
('a, ((*b)), c', True),
|
||||||
('a, ((*b)), c = 1', True),
|
('a, ((*b)), c = 1', True),
|
||||||
|
('a, (*b, c), d', True),
|
||||||
('a, (*b, c), d = 1', True),
|
('a, (*b, c), d = 1', True),
|
||||||
|
('*a.b,', True),
|
||||||
|
('*a.b, = 1', True),
|
||||||
|
('*a[b],', True),
|
||||||
|
('*a[b], = 1', True),
|
||||||
|
('*a[b::], c', True),
|
||||||
|
('*a[b::], c = 1', True),
|
||||||
|
('(a, *[b, c])', True),
|
||||||
|
('(a, *[b, c]) = 1', True),
|
||||||
|
('[a, *(b, [*c])]', True),
|
||||||
|
('[a, *(b, [*c])] = 1', True),
|
||||||
('[*(1,2,3)]', True),
|
('[*(1,2,3)]', True),
|
||||||
('{*(1,2,3)}', True),
|
('{*(1,2,3)}', True),
|
||||||
('[*(1,2,3),]', True),
|
('[*(1,2,3),]', True),
|
||||||
@@ -432,3 +453,35 @@ def test_unparenthesized_genexp(source, no_errors):
|
|||||||
)
|
)
|
||||||
def test_starred_expr(source, no_errors):
|
def test_starred_expr(source, no_errors):
|
||||||
assert bool(_get_error_list(source, version="3")) ^ no_errors
|
assert bool(_get_error_list(source, version="3")) ^ no_errors
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
'code', [
|
||||||
|
'() = ()',
|
||||||
|
'() = []',
|
||||||
|
'[] = ()',
|
||||||
|
'[] = []',
|
||||||
|
]
|
||||||
|
)
|
||||||
|
def test_valid_empty_assignment(code):
|
||||||
|
assert not _get_error_list(code)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
'code', [
|
||||||
|
'del ()',
|
||||||
|
'del []',
|
||||||
|
'del x',
|
||||||
|
'del x,',
|
||||||
|
'del x, y',
|
||||||
|
'del (x, y)',
|
||||||
|
'del [x, y]',
|
||||||
|
'del (x, [y, z])',
|
||||||
|
'del x.y, x[y]',
|
||||||
|
'del f(x)[y::]',
|
||||||
|
'del x[[*y]]',
|
||||||
|
'del x[[*y]::]',
|
||||||
|
]
|
||||||
|
)
|
||||||
|
def test_valid_del(code):
|
||||||
|
assert not _get_error_list(code)
|
||||||
|
|||||||
Reference in New Issue
Block a user