Check all arguments for unparenthesized generator expressions

Previously only the first argument on the argument list checked
against the generator expressions, now all argumnets are controlled.
This commit is contained in:
Batuhan Taskaya
2020-05-23 16:57:34 +03:00
parent 6ecd975516
commit fe54800cdd
2 changed files with 62 additions and 53 deletions
+50 -53
View File
@@ -779,62 +779,59 @@ class _ArglistRule(SyntaxRule):
return "Generator expression must be parenthesized" return "Generator expression must be parenthesized"
def is_issue(self, node): def is_issue(self, node):
first_arg = node.children[0] arg_set = set()
if first_arg.type == 'argument' \ kw_only = False
and first_arg.children[1].type in _COMP_FOR_TYPES: kw_unpacking_only = False
# e.g. foo(x for x in [], b) is_old_starred = False
return len(node.children) >= 2 # In python 3 this would be a bit easier (stars are part of
else: # argument), but we have to understand both.
arg_set = set() for argument in node.children:
kw_only = False if argument == ',':
kw_unpacking_only = False continue
is_old_starred = False
# In python 3 this would be a bit easier (stars are part of
# argument), but we have to understand both.
for argument in node.children:
if argument == ',':
continue
if argument in ('*', '**'): if argument in ('*', '**'):
# Python < 3.5 has the order engraved in the grammar # Python < 3.5 has the order engraved in the grammar
# file. No need to do anything here. # file. No need to do anything here.
is_old_starred = True is_old_starred = True
continue continue
if is_old_starred: if is_old_starred:
is_old_starred = False is_old_starred = False
continue continue
if argument.type == 'argument': if argument.type == 'argument':
first = argument.children[0] first = argument.children[0]
if first in ('*', '**'): if argument.children[1].type in _COMP_FOR_TYPES and len(node.children) >= 2:
if first == '*': # a(a, b for b in c)
if kw_unpacking_only: return True
# foo(**kwargs, *args) if first in ('*', '**'):
message = "iterable argument unpacking " \ if first == '*':
"follows keyword argument unpacking" if kw_unpacking_only:
self.add_issue(argument, message=message) # foo(**kwargs, *args)
message = "iterable argument unpacking " \
"follows keyword argument unpacking"
self.add_issue(argument, message=message)
else:
kw_unpacking_only = True
else: # Is a keyword argument.
kw_only = True
if first.type == 'name':
if first.value in arg_set:
# f(x=1, x=2)
message = "keyword argument repeated"
if self._normalizer.version >= (3, 9):
message += ": {}".format(first.value)
self.add_issue(first, message=message)
else: else:
kw_unpacking_only = True arg_set.add(first.value)
else: # Is a keyword argument. else:
kw_only = True if kw_unpacking_only:
if first.type == 'name': # f(**x, y)
if first.value in arg_set: message = "positional argument follows keyword argument unpacking"
# f(x=1, x=2) self.add_issue(argument, message=message)
message = "keyword argument repeated" elif kw_only:
if self._normalizer.version >= (3, 9): # f(x=2, y)
message += ": {}".format(first.value) message = "positional argument follows keyword argument"
self.add_issue(first, message=message) self.add_issue(argument, message=message)
else:
arg_set.add(first.value)
else:
if kw_unpacking_only:
# f(**x, y)
message = "positional argument follows keyword argument unpacking"
self.add_issue(argument, message=message)
elif kw_only:
# f(x=2, y)
message = "positional argument follows keyword argument"
self.add_issue(argument, message=message)
@ErrorFinder.register_rule(type='parameters') @ErrorFinder.register_rule(type='parameters')
+12
View File
@@ -375,3 +375,15 @@ def test_repeated_kwarg():
_get_error_list("f(q=1, q=2)", version="3.9")[0].message _get_error_list("f(q=1, q=2)", version="3.9")[0].message
== "SyntaxError: keyword argument repeated: q" == "SyntaxError: keyword argument repeated: q"
) )
@pytest.mark.parametrize(
'source', [
'a(a for a in b,)'
'a(a for a in b, a)',
'a(a, a for a in b)',
'a(a, b, a for a in b, c, d)',
]
)
def test_unparenthesized_genexp(source):
assert _get_error_list(source)