@@ -623,8 +623,8 @@ def get_next_to_consume(self, node: nodes.Name) -> Optional[List[nodes.NodeNG]]:
623
623
):
624
624
return found_nodes
625
625
626
- # And is not part of a test in a filtered comprehension
627
- if VariablesChecker ._has_homonym_in_comprehension_test (node ):
626
+ # And no comprehension is under the node's frame
627
+ if VariablesChecker ._comprehension_between_frame_and_node (node ):
628
628
return found_nodes
629
629
630
630
# Filter out assignments in ExceptHandlers that node is not contained in
@@ -1276,8 +1276,23 @@ def leave_functiondef(self, node: nodes.FunctionDef) -> None:
1276
1276
1277
1277
global_names = _flattened_scope_names (node .nodes_of_class (nodes .Global ))
1278
1278
nonlocal_names = _flattened_scope_names (node .nodes_of_class (nodes .Nonlocal ))
1279
+ comprehension_target_names : List [str ] = []
1280
+
1281
+ for comprehension_scope in node .nodes_of_class (nodes .ComprehensionScope ):
1282
+ for generator in comprehension_scope .generators :
1283
+ self ._find_assigned_names_recursive (
1284
+ generator .target , comprehension_target_names
1285
+ )
1286
+
1279
1287
for name , stmts in not_consumed .items ():
1280
- self ._check_is_unused (name , node , stmts [0 ], global_names , nonlocal_names )
1288
+ self ._check_is_unused (
1289
+ name ,
1290
+ node ,
1291
+ stmts [0 ],
1292
+ global_names ,
1293
+ nonlocal_names ,
1294
+ comprehension_target_names ,
1295
+ )
1281
1296
1282
1297
visit_asyncfunctiondef = visit_functiondef
1283
1298
leave_asyncfunctiondef = leave_functiondef
@@ -1405,7 +1420,7 @@ def _undefined_and_used_before_checker(
1405
1420
continue
1406
1421
1407
1422
action , nodes_to_consume = self ._check_consumer (
1408
- node , stmt , frame , current_consumer , i , base_scope_type
1423
+ node , stmt , frame , current_consumer , base_scope_type
1409
1424
)
1410
1425
if nodes_to_consume :
1411
1426
# Any nodes added to consumed_uncertain by get_next_to_consume()
@@ -1476,37 +1491,37 @@ def _should_node_be_skipped(
1476
1491
1477
1492
return False
1478
1493
1494
+ def _find_assigned_names_recursive (
1495
+ self ,
1496
+ target : Union [nodes .AssignName , nodes .BaseContainer ],
1497
+ target_names : List [str ],
1498
+ ) -> None :
1499
+ """Update `target_names` in place with the names of assignment
1500
+ targets, recursively (to account for nested assignments).
1501
+ """
1502
+ if isinstance (target , nodes .AssignName ):
1503
+ target_names .append (target .name )
1504
+ elif isinstance (target , nodes .BaseContainer ):
1505
+ for elt in target .elts :
1506
+ self ._find_assigned_names_recursive (elt , target_names )
1507
+
1479
1508
# pylint: disable=too-many-return-statements
1480
1509
def _check_consumer (
1481
1510
self ,
1482
1511
node : nodes .Name ,
1483
1512
stmt : nodes .NodeNG ,
1484
1513
frame : nodes .LocalsDictNodeNG ,
1485
1514
current_consumer : NamesConsumer ,
1486
- consumer_level : int ,
1487
1515
base_scope_type : Any ,
1488
1516
) -> Tuple [VariableVisitConsumerAction , Optional [List [nodes .NodeNG ]]]:
1489
1517
"""Checks a consumer for conditions that should trigger messages."""
1490
1518
# If the name has already been consumed, only check it's not a loop
1491
1519
# variable used outside the loop.
1492
- # Avoid the case where there are homonyms inside function scope and
1493
- # comprehension current scope (avoid bug #1731)
1494
1520
if node .name in current_consumer .consumed :
1495
- if utils .is_func_decorator (current_consumer .node ) or not (
1496
- current_consumer .scope_type == "comprehension"
1497
- and self ._has_homonym_in_upper_function_scope (node , consumer_level )
1498
- # But don't catch homonyms against the filter of a comprehension,
1499
- # (like "if x" in "[x for x in expr() if x]")
1500
- # https://github.com/PyCQA/pylint/issues/5586
1501
- and not (
1502
- self ._has_homonym_in_comprehension_test (node )
1503
- # Or homonyms against values to keyword arguments
1504
- # (like "var" in "[func(arg=var) for var in expr()]")
1505
- or (
1506
- isinstance (node .scope (), nodes .ComprehensionScope )
1507
- and isinstance (node .parent , (nodes .Call , nodes .Keyword ))
1508
- )
1509
- )
1521
+ # Avoid the case where there are homonyms inside function scope and
1522
+ # comprehension current scope (avoid bug #1731)
1523
+ if utils .is_func_decorator (current_consumer .node ) or not isinstance (
1524
+ node , nodes .ComprehensionScope
1510
1525
):
1511
1526
self ._check_late_binding_closure (node )
1512
1527
self ._loopvar_name (node )
@@ -2259,8 +2274,14 @@ def _loopvar_name(self, node: astroid.Name) -> None:
2259
2274
self .add_message ("undefined-loop-variable" , args = node .name , node = node )
2260
2275
2261
2276
def _check_is_unused (
2262
- self , name , node , stmt , global_names , nonlocal_names : Iterable [str ]
2263
- ):
2277
+ self ,
2278
+ name ,
2279
+ node ,
2280
+ stmt ,
2281
+ global_names ,
2282
+ nonlocal_names : Iterable [str ],
2283
+ comprehension_target_names : List [str ],
2284
+ ) -> None :
2264
2285
# Ignore some special names specified by user configuration.
2265
2286
if self ._is_name_ignored (stmt , name ):
2266
2287
return
@@ -2282,7 +2303,8 @@ def _check_is_unused(
2282
2303
argnames = node .argnames ()
2283
2304
# Care about functions with unknown argument (builtins)
2284
2305
if name in argnames :
2285
- self ._check_unused_arguments (name , node , stmt , argnames , nonlocal_names )
2306
+ if name not in comprehension_target_names :
2307
+ self ._check_unused_arguments (name , node , stmt , argnames , nonlocal_names )
2286
2308
else :
2287
2309
if stmt .parent and isinstance (
2288
2310
stmt .parent , (nodes .Assign , nodes .AnnAssign , nodes .Tuple )
@@ -2455,46 +2477,15 @@ def _should_ignore_redefined_builtin(self, stmt):
2455
2477
def _allowed_redefined_builtin (self , name ):
2456
2478
return name in self .config .allowed_redefined_builtins
2457
2479
2458
- def _has_homonym_in_upper_function_scope (
2459
- self , node : nodes .Name , index : int
2460
- ) -> bool :
2461
- """Return whether there is a node with the same name in the
2462
- to_consume dict of an upper scope and if that scope is a function
2463
-
2464
- :param node: node to check for
2465
- :param index: index of the current consumer inside self._to_consume
2466
- :return: True if there is a node with the same name in the
2467
- to_consume dict of an upper scope and if that scope
2468
- is a function, False otherwise
2469
- """
2470
- return any (
2471
- _consumer .scope_type == "function" and node .name in _consumer .to_consume
2472
- for _consumer in self ._to_consume [index - 1 :: - 1 ]
2473
- )
2474
-
2475
2480
@staticmethod
2476
- def _has_homonym_in_comprehension_test (node : nodes .Name ) -> bool :
2477
- """Return True if `node`'s frame contains a comprehension employing an
2478
- identical name in a test.
2479
-
2480
- The name in the test could appear at varying depths:
2481
-
2482
- Examples:
2483
- [x for x in range(3) if name]
2484
- [x for x in range(3) if name.num == 1]
2485
- [x for x in range(3)] if call(name.num)]
2486
- """
2487
- closest_comprehension = utils .get_node_first_ancestor_of_type (
2488
- node , nodes .Comprehension
2489
- )
2490
- return (
2491
- closest_comprehension is not None
2492
- and node .frame (future = True ).parent_of (closest_comprehension )
2493
- and any (
2494
- test is node or test .parent_of (node )
2495
- for test in closest_comprehension .ifs
2496
- )
2481
+ def _comprehension_between_frame_and_node (node : nodes .Name ) -> bool :
2482
+ """Return True if a ComprehensionScope intervenes between `node` and its frame."""
2483
+ closest_comprehension_scope = utils .get_node_first_ancestor_of_type (
2484
+ node , nodes .ComprehensionScope
2497
2485
)
2486
+ return closest_comprehension_scope is not None and node .frame (
2487
+ future = True
2488
+ ).parent_of (closest_comprehension_scope )
2498
2489
2499
2490
def _store_type_annotation_node (self , type_annotation ):
2500
2491
"""Given a type annotation, store all the name nodes it refers to."""
0 commit comments