@@ -403,6 +403,12 @@ def _has_locals_call_after_node(stmt: nodes.NodeNG, scope: nodes.FunctionDef) ->
403
403
"invalid-all-format" ,
404
404
"Used when __all__ has an invalid format." ,
405
405
),
406
+ "E0606" : (
407
+ "Possibly using variable %r before assignment" ,
408
+ "possibly-used-before-assignment" ,
409
+ "Emitted when a local variable is accessed before its assignment took place "
410
+ "in both branches of an if/else switch." ,
411
+ ),
406
412
"E0611" : (
407
413
"No name %r in module %r" ,
408
414
"no-name-in-module" ,
@@ -537,6 +543,8 @@ def __init__(self, node: nodes.NodeNG, scope_type: str) -> None:
537
543
copy .copy (node .locals ), {}, collections .defaultdict (list ), scope_type
538
544
)
539
545
self .node = node
546
+ self .names_under_always_false_test : set [str ] = set ()
547
+ self .names_defined_under_one_branch_only : set [str ] = set ()
540
548
541
549
def __repr__ (self ) -> str :
542
550
_to_consumes = [f"{ k } ->{ v } " for k , v in self ._atomic .to_consume .items ()]
@@ -636,13 +644,6 @@ def get_next_to_consume(self, node: nodes.Name) -> list[nodes.NodeNG] | None:
636
644
if VariablesChecker ._comprehension_between_frame_and_node (node ):
637
645
return found_nodes
638
646
639
- # Filter out assignments guarded by always false conditions
640
- if found_nodes :
641
- uncertain_nodes = self ._uncertain_nodes_in_false_tests (found_nodes , node )
642
- self .consumed_uncertain [node .name ] += uncertain_nodes
643
- uncertain_nodes_set = set (uncertain_nodes )
644
- found_nodes = [n for n in found_nodes if n not in uncertain_nodes_set ]
645
-
646
647
# Filter out assignments in ExceptHandlers that node is not contained in
647
648
if found_nodes :
648
649
found_nodes = [
@@ -652,6 +653,13 @@ def get_next_to_consume(self, node: nodes.Name) -> list[nodes.NodeNG] | None:
652
653
or n .statement ().parent_of (node )
653
654
]
654
655
656
+ # Filter out assignments guarded by always false conditions
657
+ if found_nodes :
658
+ uncertain_nodes = self ._uncertain_nodes_if_tests (found_nodes , node )
659
+ self .consumed_uncertain [node .name ] += uncertain_nodes
660
+ uncertain_nodes_set = set (uncertain_nodes )
661
+ found_nodes = [n for n in found_nodes if n not in uncertain_nodes_set ]
662
+
655
663
# Filter out assignments in an Except clause that the node is not
656
664
# contained in, assuming they may fail
657
665
if found_nodes :
@@ -688,8 +696,9 @@ def get_next_to_consume(self, node: nodes.Name) -> list[nodes.NodeNG] | None:
688
696
689
697
return found_nodes
690
698
691
- @staticmethod
692
- def _inferred_to_define_name_raise_or_return (name : str , node : nodes .NodeNG ) -> bool :
699
+ def _inferred_to_define_name_raise_or_return (
700
+ self , name : str , node : nodes .NodeNG
701
+ ) -> bool :
693
702
"""Return True if there is a path under this `if_node`
694
703
that is inferred to define `name`, raise, or return.
695
704
"""
@@ -716,8 +725,8 @@ def _inferred_to_define_name_raise_or_return(name: str, node: nodes.NodeNG) -> b
716
725
if not isinstance (node , nodes .If ):
717
726
return False
718
727
719
- # Be permissive if there is a break
720
- if any (node .nodes_of_class (nodes .Break )):
728
+ # Be permissive if there is a break or a continue
729
+ if any (node .nodes_of_class (nodes .Break , nodes . Continue )):
721
730
return True
722
731
723
732
# Is there an assignment in this node itself, e.g. in named expression?
@@ -739,17 +748,18 @@ def _inferred_to_define_name_raise_or_return(name: str, node: nodes.NodeNG) -> b
739
748
740
749
# Only search else branch when test condition is inferred to be false
741
750
if all_inferred and only_search_else :
742
- return NamesConsumer ._branch_handles_name (name , node .orelse )
743
- # Only search if branch when test condition is inferred to be true
744
- if all_inferred and only_search_if :
745
- return NamesConsumer ._branch_handles_name (name , node .body )
751
+ self .names_under_always_false_test .add (name )
752
+ return self ._branch_handles_name (name , node .orelse )
746
753
# Search both if and else branches
747
- return NamesConsumer ._branch_handles_name (
748
- name , node .body
749
- ) or NamesConsumer ._branch_handles_name (name , node .orelse )
750
-
751
- @staticmethod
752
- def _branch_handles_name (name : str , body : Iterable [nodes .NodeNG ]) -> bool :
754
+ if_branch_handles = self ._branch_handles_name (name , node .body )
755
+ else_branch_handles = self ._branch_handles_name (name , node .orelse )
756
+ if if_branch_handles ^ else_branch_handles :
757
+ self .names_defined_under_one_branch_only .add (name )
758
+ elif name in self .names_defined_under_one_branch_only :
759
+ self .names_defined_under_one_branch_only .remove (name )
760
+ return if_branch_handles and else_branch_handles
761
+
762
+ def _branch_handles_name (self , name : str , body : Iterable [nodes .NodeNG ]) -> bool :
753
763
return any (
754
764
NamesConsumer ._defines_name_raises_or_returns (name , if_body_stmt )
755
765
or isinstance (
@@ -762,17 +772,15 @@ def _branch_handles_name(name: str, body: Iterable[nodes.NodeNG]) -> bool:
762
772
nodes .While ,
763
773
),
764
774
)
765
- and NamesConsumer ._inferred_to_define_name_raise_or_return (
766
- name , if_body_stmt
767
- )
775
+ and self ._inferred_to_define_name_raise_or_return (name , if_body_stmt )
768
776
for if_body_stmt in body
769
777
)
770
778
771
- def _uncertain_nodes_in_false_tests (
779
+ def _uncertain_nodes_if_tests (
772
780
self , found_nodes : list [nodes .NodeNG ], node : nodes .NodeNG
773
781
) -> list [nodes .NodeNG ]:
774
- """Identify nodes of uncertain execution because they are defined under
775
- tests that evaluate false .
782
+ """Identify nodes of uncertain execution because they are defined under if
783
+ tests.
776
784
777
785
Don't identify a node if there is a path that is inferred to
778
786
define the name, raise, or return (e.g. any executed if/elif/else branch).
@@ -808,7 +816,7 @@ def _uncertain_nodes_in_false_tests(
808
816
continue
809
817
810
818
# Name defined in the if/else control flow
811
- if NamesConsumer ._inferred_to_define_name_raise_or_return (name , outer_if ):
819
+ if self ._inferred_to_define_name_raise_or_return (name , outer_if ):
812
820
continue
813
821
814
822
uncertain_nodes .append (other_node )
@@ -930,7 +938,7 @@ def _uncertain_nodes_in_except_blocks(
930
938
931
939
@staticmethod
932
940
def _defines_name_raises_or_returns (name : str , node : nodes .NodeNG ) -> bool :
933
- if isinstance (node , (nodes .Raise , nodes .Assert , nodes .Return )):
941
+ if isinstance (node , (nodes .Raise , nodes .Assert , nodes .Return , nodes . Continue )):
934
942
return True
935
943
if (
936
944
isinstance (node , nodes .AnnAssign )
@@ -1993,11 +2001,19 @@ def _report_unfound_name_definition(
1993
2001
):
1994
2002
return
1995
2003
1996
- confidence = (
1997
- CONTROL_FLOW if node .name in current_consumer .consumed_uncertain else HIGH
1998
- )
2004
+ confidence = HIGH
2005
+ if node .name in current_consumer .names_under_always_false_test :
2006
+ confidence = INFERENCE
2007
+ elif node .name in current_consumer .consumed_uncertain :
2008
+ confidence = CONTROL_FLOW
2009
+
2010
+ if node .name in current_consumer .names_defined_under_one_branch_only :
2011
+ msg = "possibly-used-before-assignment"
2012
+ else :
2013
+ msg = "used-before-assignment"
2014
+
1999
2015
self .add_message (
2000
- "used-before-assignment" ,
2016
+ msg ,
2001
2017
args = node .name ,
2002
2018
node = node ,
2003
2019
confidence = confidence ,
0 commit comments