@@ -303,6 +303,20 @@ def _assigned_locally(name_node: nodes.Name) -> bool:
303303 )
304304
305305
306+ def _is_before (node : nodes .NodeNG , reference_node : nodes .NodeNG ) -> bool :
307+ """
308+ Returns True if node appears before reference_node, False otherwise.
309+ """
310+ if node .lineno < reference_node .lineno :
311+ return True
312+ if (
313+ node .lineno == reference_node .lineno
314+ and node .col_offset < reference_node .col_offset
315+ ):
316+ return True
317+ return False
318+
319+
306320def _has_locals_call_after_node (stmt : nodes .NodeNG , scope : nodes .FunctionDef ) -> bool :
307321 skip_nodes = (
308322 nodes .FunctionDef ,
@@ -531,7 +545,9 @@ def mark_as_consumed(self, name: str, consumed_nodes: list[nodes.NodeNG]) -> Non
531545 else :
532546 del self .to_consume [name ]
533547
534- def get_next_to_consume (self , node : nodes .Name ) -> list [nodes .NodeNG ] | None :
548+ def get_next_to_consume (
549+ self , node : nodes .Name , is_nonlocal : bool
550+ ) -> list [nodes .NodeNG ] | None :
535551 """Return a list of the nodes that define `node` from this scope.
536552
537553 If it is uncertain whether a node will be consumed, such as for statements in
@@ -542,6 +558,12 @@ def get_next_to_consume(self, node: nodes.Name) -> list[nodes.NodeNG] | None:
542558 parent_node = node .parent
543559 found_nodes = self .to_consume .get (name )
544560 node_statement = node .statement ()
561+
562+ # Filter out all nodes that define `node`, as it is a nonlocal
563+ if is_nonlocal :
564+ self .consumed_uncertain [node .name ] += found_nodes if found_nodes else []
565+ return []
566+
545567 if (
546568 found_nodes
547569 and isinstance (parent_node , nodes .Assign )
@@ -561,13 +583,6 @@ def get_next_to_consume(self, node: nodes.Name) -> list[nodes.NodeNG] | None:
561583 ):
562584 found_nodes = None
563585
564- # Before filtering, check that this node's name is not a nonlocal
565- if any (
566- isinstance (child , nodes .Nonlocal ) and node .name in child .names
567- for child in node .frame ().get_children ()
568- ):
569- return found_nodes
570-
571586 # And no comprehension is under the node's frame
572587 if VariablesChecker ._comprehension_between_frame_and_node (node ):
573588 return found_nodes
@@ -723,7 +738,7 @@ def _uncertain_nodes_if_tests(
723738 name = other_node .name
724739 elif isinstance (other_node , (nodes .Import , nodes .ImportFrom )):
725740 name = node .name
726- elif isinstance (other_node , nodes .ClassDef ):
741+ elif isinstance (other_node , ( nodes .FunctionDef , nodes . ClassDef ) ):
727742 name = other_node .name
728743 else :
729744 continue
@@ -1268,6 +1283,7 @@ def __init__(self, linter: PyLinter) -> None:
12681283 self ._reported_type_checking_usage_scopes : dict [
12691284 str , list [nodes .LocalsDictNodeNG ]
12701285 ] = {}
1286+ self ._nonlocal_nodes_stack : list [list [nodes .Nonlocal ]] = []
12711287 self ._postponed_evaluation_enabled = False
12721288
12731289 @utils .only_required_for_messages (
@@ -1434,6 +1450,9 @@ def leave_setcomp(self, _: nodes.SetComp) -> None:
14341450 def visit_functiondef (self , node : nodes .FunctionDef ) -> None :
14351451 """Visit function: update consumption analysis variable and check locals."""
14361452 self ._to_consume .append (NamesConsumer (node , "function" ))
1453+ self ._nonlocal_nodes_stack .append (
1454+ [n for n in node .body if isinstance (n , nodes .Nonlocal )]
1455+ )
14371456 if not (
14381457 self .linter .is_message_enabled ("redefined-outer-name" )
14391458 or self .linter .is_message_enabled ("redefined-builtin" )
@@ -1483,6 +1502,7 @@ def visit_functiondef(self, node: nodes.FunctionDef) -> None:
14831502 def leave_functiondef (self , node : nodes .FunctionDef ) -> None :
14841503 """Leave function: check function's locals are consumed."""
14851504 self ._check_metaclasses (node )
1505+ self ._nonlocal_nodes_stack .pop ()
14861506
14871507 if node .type_comment_returns :
14881508 self ._store_type_annotation_node (node .type_comment_returns )
@@ -1761,7 +1781,9 @@ def _check_consumer(
17611781 self ._check_late_binding_closure (node )
17621782 return (VariableVisitConsumerAction .RETURN , None )
17631783
1764- found_nodes = current_consumer .get_next_to_consume (node )
1784+ found_nodes = current_consumer .get_next_to_consume (
1785+ node , self ._is_nonlocal (node )
1786+ )
17651787 if found_nodes is None :
17661788 return (VariableVisitConsumerAction .CONTINUE , None )
17671789 if not found_nodes :
@@ -1940,6 +1962,13 @@ def _check_consumer(
19401962
19411963 return (VariableVisitConsumerAction .RETURN , found_nodes )
19421964
1965+ def _is_nonlocal (self , node : nodes .Name ) -> bool :
1966+ return any (
1967+ node .name in nonlocal_node .names and _is_before (nonlocal_node , node )
1968+ for nonlocal_scope in self ._nonlocal_nodes_stack
1969+ for nonlocal_node in nonlocal_scope
1970+ )
1971+
19431972 def _report_unfound_name_definition (
19441973 self ,
19451974 node : nodes .NodeNG ,
@@ -1964,6 +1993,8 @@ def _report_unfound_name_definition(
19641993 and node .scope () in self ._reported_type_checking_usage_scopes [node .name ]
19651994 ):
19661995 return False
1996+ if self ._is_nonlocal (node ):
1997+ return False
19671998
19681999 confidence = HIGH
19692000 if node .name in current_consumer .names_under_always_false_test :
@@ -2291,19 +2322,11 @@ def _is_variable_violation(
22912322 # x = b if (b := True) else False
22922323 maybe_before_assign = False
22932324 elif (
2294- isinstance ( # pylint: disable=too-many-boolean-expressions
2295- defnode , nodes .NamedExpr
2296- )
2325+ isinstance (defnode , nodes .NamedExpr )
22972326 and frame is defframe
22982327 and defframe .parent_of (stmt )
22992328 and stmt is defstmt
2300- and (
2301- (
2302- defnode .lineno == node .lineno
2303- and defnode .col_offset < node .col_offset
2304- )
2305- or (defnode .lineno < node .lineno )
2306- )
2329+ and _is_before (defnode , node )
23072330 ):
23082331 # Relation of a name to the same name in a named expression
23092332 # Could be used before assignment if self-referencing:
0 commit comments