From 776e151370b76df994f79eb6840823457b832c08 Mon Sep 17 00:00:00 2001 From: Jarry Shaw Date: Fri, 13 Dec 2019 11:55:53 +0800 Subject: [PATCH] Revised implementation * search ancestors of namedexpr_test directly for comprehensions * added test samples for invalid namedexpr_test syntax --- parso/python/errors.py | 94 +++++++++++++++++----------------------- test/failing_examples.py | 22 ++++++++++ 2 files changed, 62 insertions(+), 54 deletions(-) diff --git a/parso/python/errors.py b/parso/python/errors.py index 913af98..9ae5329 100644 --- a/parso/python/errors.py +++ b/parso/python/errors.py @@ -1041,6 +1041,46 @@ class _NamedExprRule(_CheckAssignmentRule): def is_issue(self, namedexpr_test): first = namedexpr_test.children[0] + + # defined names + exprlist = list() + + def process_comp_for(comp_for): + if comp_for.type == 'sync_comp_for': + comp = comp_for + elif comp_for.type == 'comp_for': + comp = comp_for.children[1] + exprlist.extend(_get_for_stmt_definition_exprs(comp)) + + def search_all_comp_ancestors(node): + ancestors = list() + while True: + node = node.parent + if node is None: + return ancestors + if node.type in ['testlist_comp', 'dictorsetmaker']: + for child in node.children: + if child.type in _COMP_FOR_TYPES: + process_comp_for(child) + ancestors.append(node) + break + + # check assignment expressions in comprehensions + search_all = search_all_comp_ancestors(namedexpr_test) + if search_all: + if self._normalizer.context.node.type == 'classdef': + self.add_issue( + namedexpr_test, message='assignment expression within a comprehension cannot be used in a class body') + + namelist = [expr.value for expr in exprlist] + if first.type == 'name' and first.value in namelist: + # [i := 0 for i, j in range(5)] + # [[(i := i) for j in range(5)] for i in range(5)] + # [i for i, j in range(5) if True or (i := 1)] + # [False and (i := 0) for i, j in range(5)] + self.add_issue( + namedexpr_test, message='assignment expression cannot rebind comprehension iteration variable %r' % first.value) + if first.type == 'lambdef': # (lambda: x := 1) self.add_issue(namedexpr_test, message='cannot use named assignment with lambda') @@ -1058,57 +1098,3 @@ class _NamedExprRule(_CheckAssignmentRule): self.add_issue(namedexpr_test, message='cannot use named assignment with attribute') else: self._check_assignment(first, is_namedexpr=True) - - -@ErrorFinder.register_rule(type='testlist_comp') -@ErrorFinder.register_rule(type='dictorsetmaker') -class _ComprehensionRule(SyntaxRule): - # testlist_comp: (namedexpr_test|star_expr) ( comp_for | (',' (namedexpr_test|star_expr))* [','] ) - # dictorsetmaker: ( ((test ':' test | '**' expr) - # (comp_for | (',' (test ':' test | '**' expr)) * [','])) | - # ((test | star_expr) - # (comp_for | (',' (test | star_expr)) * [',']))) - - def is_issue(self, node): - exprlist = list() - namedexpr_list = list() - - def process_comp(comp_for): - if comp_for.type in _COMP_FOR_TYPES: - if comp_for.type == 'sync_comp_for': - comp = comp_for - elif comp_for.type == 'comp_for': - comp = comp_for.children[1] - exprlist.extend(_get_for_stmt_definition_exprs(comp)) - - if len(comp.children) > 4: - comp_iter = comp.children[4] - process_comp(comp_iter) - else: - # skip assignment expressions in comp_for - namedexpr_list.extend(_get_namedexpr(comp_for)) - - for child in node.children: - process_comp(child) - - # not a comprehension - if exprlist is None: - return - - # in class body - in_class = self._normalizer.context.node.type == 'classdef' - - namelist = [expr.value for expr in exprlist] - for expr in namedexpr_list: - if in_class: - # class Example: - # [(j := i) for i in range(5)] - self.add_issue(expr, message='assignment expression within a comprehension cannot be used in a class body') - - first = expr.children[0] - if first.type == 'name' and first.value in namelist: - # [i := 0 for i, j in range(5)] - # [[(i := i) for j in range(5)] for i in range(5)] - # [i for i, j in range(5) if True or (i := 1)] - # [False and (i := 0) for i, j in range(5)] - self.add_issue(expr, message='assignment expression cannot rebind comprehension iteration variable %r' % first.value) diff --git a/test/failing_examples.py b/test/failing_examples.py index c15cbf8..c4f247a 100644 --- a/test/failing_examples.py +++ b/test/failing_examples.py @@ -319,3 +319,25 @@ if sys.version_info[:2] < (3, 8): continue '''), # 'continue' not supported inside 'finally' clause" ] + +if sys.version_info[:2] >= (3, 8): + # assignment expressions from issue#89 + FAILING_EXAMPLES += [ + # Case 2 + '(lambda: x := 1)', + # Case 3 + '(a[i] := x)', + # Case 4 + '(a.b := c)', + # Case 5 + '[i:= 0 for i, j in range(5)]', + '[[(i:= i) for j in range(5)] for i in range(5)]', + '[i for i, j in range(5) if True or (i:= 1)]', + '[False and (i:= 0) for i, j in range(5)]', + # Case 6 + '[i+1 for i in (i:= range(5))]', + '[i+1 for i in (j:= range(5))]', + '[i+1 for i in (lambda: (j:= range(5)))()]', + # Case 7 + 'class Example:\n [(j := i) for i in range(5)]', + ]