diff --git a/doc/data/messages/p/possibly-used-before-assignment/bad.py b/doc/data/messages/p/possibly-used-before-assignment/bad.py new file mode 100644 index 0000000000..8e7f0cb02a --- /dev/null +++ b/doc/data/messages/p/possibly-used-before-assignment/bad.py @@ -0,0 +1,4 @@ +def check_lunchbox(items: list[str]): + if not items: + empty = True + print(empty) # [possibly-used-before-assignment] diff --git a/doc/data/messages/p/possibly-used-before-assignment/details.rst b/doc/data/messages/p/possibly-used-before-assignment/details.rst new file mode 100644 index 0000000000..4737d26685 --- /dev/null +++ b/doc/data/messages/p/possibly-used-before-assignment/details.rst @@ -0,0 +1,15 @@ +If you rely on a pattern like: + +.. sourcecode:: python + + if guarded(): + var = 1 + + if guarded(): + print(var) # emits possibly-used-before-assignment + +you may be concerned that ``possibly-used-before-assignment`` is not totally useful +in this instance. However, consider that pylint, as a static analysis tool, does +not know if ``guarded()`` is deterministic or talks to +a database. (Likewise, for ``guarded`` instead of ``guarded()``, any other +part of your program may have changed its value in the meantime.) diff --git a/doc/data/messages/p/possibly-used-before-assignment/good.py b/doc/data/messages/p/possibly-used-before-assignment/good.py new file mode 100644 index 0000000000..6bb478f5f8 --- /dev/null +++ b/doc/data/messages/p/possibly-used-before-assignment/good.py @@ -0,0 +1,5 @@ +def check_lunchbox(items: list[str]): + empty = False + if not items: + empty = True + print(empty) diff --git a/doc/user_guide/checkers/features.rst b/doc/user_guide/checkers/features.rst index 7dce5c43b8..647c77e57c 100644 --- a/doc/user_guide/checkers/features.rst +++ b/doc/user_guide/checkers/features.rst @@ -1374,6 +1374,9 @@ Variables checker Messages Used when an invalid (non-string) object occurs in __all__. :no-name-in-module (E0611): *No name %r in module %r* Used when a name cannot be found in a module. +:possibly-used-before-assignment (E0606): *Possibly using variable %r before assignment* + Emitted when a local variable is accessed before its assignment took place in + both branches of an if/else switch. :undefined-variable (E0602): *Undefined variable %r* Used when an undefined variable is accessed. :undefined-all-variable (E0603): *Undefined variable name %r in __all__* diff --git a/doc/user_guide/messages/messages_overview.rst b/doc/user_guide/messages/messages_overview.rst index e928ac1a24..f5d6411f30 100644 --- a/doc/user_guide/messages/messages_overview.rst +++ b/doc/user_guide/messages/messages_overview.rst @@ -139,6 +139,7 @@ All messages in the error category: error/not-in-loop error/notimplemented-raised error/positional-only-arguments-expected + error/possibly-used-before-assignment error/potential-index-error error/raising-bad-type error/raising-non-exception diff --git a/doc/whatsnew/fragments/1727.new_check b/doc/whatsnew/fragments/1727.new_check new file mode 100644 index 0000000000..30b5d3442e --- /dev/null +++ b/doc/whatsnew/fragments/1727.new_check @@ -0,0 +1,4 @@ +Add check ``possibly-used-before-assignment`` when relying on names after an ``if/else`` +switch when one branch failed to define the name, raise, or return. + +Closes #1727 diff --git a/pylint/checkers/variables.py b/pylint/checkers/variables.py index e38bec03e3..ebc27c1e33 100644 --- a/pylint/checkers/variables.py +++ b/pylint/checkers/variables.py @@ -403,6 +403,12 @@ def _has_locals_call_after_node(stmt: nodes.NodeNG, scope: nodes.FunctionDef) -> "invalid-all-format", "Used when __all__ has an invalid format.", ), + "E0606": ( + "Possibly using variable %r before assignment", + "possibly-used-before-assignment", + "Emitted when a local variable is accessed before its assignment took place " + "in both branches of an if/else switch.", + ), "E0611": ( "No name %r in module %r", "no-name-in-module", @@ -537,6 +543,8 @@ def __init__(self, node: nodes.NodeNG, scope_type: str) -> None: copy.copy(node.locals), {}, collections.defaultdict(list), scope_type ) self.node = node + self.names_under_always_false_test: set[str] = set() + self.names_defined_under_one_branch_only: set[str] = set() def __repr__(self) -> str: _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: if VariablesChecker._comprehension_between_frame_and_node(node): return found_nodes - # Filter out assignments guarded by always false conditions - if found_nodes: - uncertain_nodes = self._uncertain_nodes_in_false_tests(found_nodes, node) - self.consumed_uncertain[node.name] += uncertain_nodes - uncertain_nodes_set = set(uncertain_nodes) - found_nodes = [n for n in found_nodes if n not in uncertain_nodes_set] - # Filter out assignments in ExceptHandlers that node is not contained in if found_nodes: found_nodes = [ @@ -652,6 +653,13 @@ def get_next_to_consume(self, node: nodes.Name) -> list[nodes.NodeNG] | None: or n.statement().parent_of(node) ] + # Filter out assignments guarded by always false conditions + if found_nodes: + uncertain_nodes = self._uncertain_nodes_if_tests(found_nodes, node) + self.consumed_uncertain[node.name] += uncertain_nodes + uncertain_nodes_set = set(uncertain_nodes) + found_nodes = [n for n in found_nodes if n not in uncertain_nodes_set] + # Filter out assignments in an Except clause that the node is not # contained in, assuming they may fail if found_nodes: @@ -688,8 +696,9 @@ def get_next_to_consume(self, node: nodes.Name) -> list[nodes.NodeNG] | None: return found_nodes - @staticmethod - def _inferred_to_define_name_raise_or_return(name: str, node: nodes.NodeNG) -> bool: + def _inferred_to_define_name_raise_or_return( + self, name: str, node: nodes.NodeNG + ) -> bool: """Return True if there is a path under this `if_node` that is inferred to define `name`, raise, or return. """ @@ -716,8 +725,8 @@ def _inferred_to_define_name_raise_or_return(name: str, node: nodes.NodeNG) -> b if not isinstance(node, nodes.If): return False - # Be permissive if there is a break - if any(node.nodes_of_class(nodes.Break)): + # Be permissive if there is a break or a continue + if any(node.nodes_of_class(nodes.Break, nodes.Continue)): return True # 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 # Only search else branch when test condition is inferred to be false if all_inferred and only_search_else: - return NamesConsumer._branch_handles_name(name, node.orelse) - # Only search if branch when test condition is inferred to be true - if all_inferred and only_search_if: - return NamesConsumer._branch_handles_name(name, node.body) + self.names_under_always_false_test.add(name) + return self._branch_handles_name(name, node.orelse) # Search both if and else branches - return NamesConsumer._branch_handles_name( - name, node.body - ) or NamesConsumer._branch_handles_name(name, node.orelse) - - @staticmethod - def _branch_handles_name(name: str, body: Iterable[nodes.NodeNG]) -> bool: + if_branch_handles = self._branch_handles_name(name, node.body) + else_branch_handles = self._branch_handles_name(name, node.orelse) + if if_branch_handles ^ else_branch_handles: + self.names_defined_under_one_branch_only.add(name) + elif name in self.names_defined_under_one_branch_only: + self.names_defined_under_one_branch_only.remove(name) + return if_branch_handles and else_branch_handles + + def _branch_handles_name(self, name: str, body: Iterable[nodes.NodeNG]) -> bool: return any( NamesConsumer._defines_name_raises_or_returns(name, if_body_stmt) or isinstance( @@ -762,17 +772,15 @@ def _branch_handles_name(name: str, body: Iterable[nodes.NodeNG]) -> bool: nodes.While, ), ) - and NamesConsumer._inferred_to_define_name_raise_or_return( - name, if_body_stmt - ) + and self._inferred_to_define_name_raise_or_return(name, if_body_stmt) for if_body_stmt in body ) - def _uncertain_nodes_in_false_tests( + def _uncertain_nodes_if_tests( self, found_nodes: list[nodes.NodeNG], node: nodes.NodeNG ) -> list[nodes.NodeNG]: - """Identify nodes of uncertain execution because they are defined under - tests that evaluate false. + """Identify nodes of uncertain execution because they are defined under if + tests. Don't identify a node if there is a path that is inferred to define the name, raise, or return (e.g. any executed if/elif/else branch). @@ -808,7 +816,7 @@ def _uncertain_nodes_in_false_tests( continue # Name defined in the if/else control flow - if NamesConsumer._inferred_to_define_name_raise_or_return(name, outer_if): + if self._inferred_to_define_name_raise_or_return(name, outer_if): continue uncertain_nodes.append(other_node) @@ -930,7 +938,7 @@ def _uncertain_nodes_in_except_blocks( @staticmethod def _defines_name_raises_or_returns(name: str, node: nodes.NodeNG) -> bool: - if isinstance(node, (nodes.Raise, nodes.Assert, nodes.Return)): + if isinstance(node, (nodes.Raise, nodes.Assert, nodes.Return, nodes.Continue)): return True if ( isinstance(node, nodes.AnnAssign) @@ -1993,11 +2001,19 @@ def _report_unfound_name_definition( ): return - confidence = ( - CONTROL_FLOW if node.name in current_consumer.consumed_uncertain else HIGH - ) + confidence = HIGH + if node.name in current_consumer.names_under_always_false_test: + confidence = INFERENCE + elif node.name in current_consumer.consumed_uncertain: + confidence = CONTROL_FLOW + + if node.name in current_consumer.names_defined_under_one_branch_only: + msg = "possibly-used-before-assignment" + else: + msg = "used-before-assignment" + self.add_message( - "used-before-assignment", + msg, args=node.name, node=node, confidence=confidence, diff --git a/pylintrc b/pylintrc index 3896d4e353..434fa23836 100644 --- a/pylintrc +++ b/pylintrc @@ -109,6 +109,7 @@ disable= # We anticipate #3512 where it will become optional fixme, consider-using-assignment-expr, + possibly-used-before-assignment, [REPORTS] diff --git a/tests/functional/r/redefined/redefined_except_handler.txt b/tests/functional/r/redefined/redefined_except_handler.txt index a0ccc6b9b3..1184bdd816 100644 --- a/tests/functional/r/redefined/redefined_except_handler.txt +++ b/tests/functional/r/redefined/redefined_except_handler.txt @@ -1,4 +1,4 @@ redefined-outer-name:11:4:12:12::Redefining name 'err' from outer scope (line 8):UNDEFINED redefined-outer-name:57:8:58:16::Redefining name 'err' from outer scope (line 51):UNDEFINED -used-before-assignment:69:14:69:29:func:Using variable 'CustomException' before assignment:CONTROL_FLOW +used-before-assignment:69:14:69:29:func:Using variable 'CustomException' before assignment:HIGH redefined-outer-name:71:4:72:12:func:Redefining name 'CustomException' from outer scope (line 62):UNDEFINED diff --git a/tests/functional/u/undefined/undefined_variable.txt b/tests/functional/u/undefined/undefined_variable.txt index b41f9ae46f..ea707c910a 100644 --- a/tests/functional/u/undefined/undefined_variable.txt +++ b/tests/functional/u/undefined/undefined_variable.txt @@ -27,7 +27,7 @@ undefined-variable:166:4:166:13::Undefined variable 'unicode_2':UNDEFINED undefined-variable:171:4:171:13::Undefined variable 'unicode_3':UNDEFINED undefined-variable:226:25:226:37:LambdaClass4.:Undefined variable 'LambdaClass4':UNDEFINED undefined-variable:234:25:234:37:LambdaClass5.:Undefined variable 'LambdaClass5':UNDEFINED -used-before-assignment:255:26:255:34:func_should_fail:Using variable 'datetime' before assignment:CONTROL_FLOW +used-before-assignment:255:26:255:34:func_should_fail:Using variable 'datetime' before assignment:INFERENCE undefined-variable:291:18:291:24:not_using_loop_variable_accordingly:Undefined variable 'iteree':UNDEFINED undefined-variable:308:27:308:28:undefined_annotation:Undefined variable 'x':UNDEFINED used-before-assignment:309:7:309:8:undefined_annotation:Using variable 'x' before assignment:HIGH diff --git a/tests/functional/u/undefined/undefined_variable_py38.txt b/tests/functional/u/undefined/undefined_variable_py38.txt index 1674707a57..5a3533dc93 100644 --- a/tests/functional/u/undefined/undefined_variable_py38.txt +++ b/tests/functional/u/undefined/undefined_variable_py38.txt @@ -7,4 +7,4 @@ undefined-variable:106:6:106:19::Undefined variable 'else_assign_2':INFERENCE used-before-assignment:141:10:141:16:type_annotation_used_improperly_after_comprehension:Using variable 'my_int' before assignment:HIGH used-before-assignment:148:10:148:16:type_annotation_used_improperly_after_comprehension_2:Using variable 'my_int' before assignment:HIGH used-before-assignment:186:9:186:10::Using variable 'z' before assignment:HIGH -used-before-assignment:193:6:193:19::Using variable 'NEVER_DEFINED' before assignment:CONTROL_FLOW +used-before-assignment:193:6:193:19::Using variable 'NEVER_DEFINED' before assignment:INFERENCE diff --git a/tests/functional/u/used/used_before_assignment.py b/tests/functional/u/used/used_before_assignment.py index 16f33939cb..83f85cc215 100644 --- a/tests/functional/u/used/used_before_assignment.py +++ b/tests/functional/u/used/used_before_assignment.py @@ -60,7 +60,7 @@ def redefine_time_import_with_global(): pass else: VAR4 = False -if VAR4: # [used-before-assignment] +if VAR4: # [possibly-used-before-assignment] pass if FALSE: @@ -70,7 +70,7 @@ def redefine_time_import_with_global(): VAR5 = True else: VAR5 = True -if VAR5: +if VAR5: # [possibly-used-before-assignment] pass if FALSE: @@ -116,7 +116,7 @@ def redefine_time_import_with_global(): VAR11 = num if VAR11: VAR12 = False -print(VAR12) +print(VAR12) # [possibly-used-before-assignment] def turn_on2(**kwargs): """https://github.com/pylint-dev/pylint/issues/7873""" @@ -180,3 +180,21 @@ def give_me_none(): class T: # pylint: disable=invalid-name, too-few-public-methods, undefined-variable '''Issue #8754, no crash from unexpected assignment between attribute and variable''' T.attr = attr + + +if outer(): + NOT_ALWAYS_DEFINED = True +print(NOT_ALWAYS_DEFINED) # [used-before-assignment] + + +def inner_if_continues_outer_if_has_no_other_statements(): + for i in range(5): + if isinstance(i, int): + # Testing no assignment here, before the inner if + if i % 2 == 0: + order = None + else: + continue + else: + order = None + print(order) diff --git a/tests/functional/u/used/used_before_assignment.txt b/tests/functional/u/used/used_before_assignment.txt index 37b25ab49b..127a5d7de4 100644 --- a/tests/functional/u/used/used_before_assignment.txt +++ b/tests/functional/u/used/used_before_assignment.txt @@ -4,9 +4,12 @@ used-before-assignment:10:4:10:9:outer:Using variable 'inner' before assignment: used-before-assignment:19:20:19:40:ClassWithProperty:Using variable 'redefine_time_import' before assignment:HIGH used-before-assignment:23:0:23:9::Using variable 'calculate' before assignment:HIGH used-before-assignment:31:10:31:14:redefine_time_import:Using variable 'time' before assignment:HIGH -used-before-assignment:45:3:45:7::Using variable 'VAR2' before assignment:CONTROL_FLOW -used-before-assignment:63:3:63:7::Using variable 'VAR4' before assignment:CONTROL_FLOW -used-before-assignment:78:3:78:7::Using variable 'VAR6' before assignment:CONTROL_FLOW -used-before-assignment:113:6:113:11::Using variable 'VAR10' before assignment:CONTROL_FLOW -used-before-assignment:144:10:144:14::Using variable 'SALE' before assignment:CONTROL_FLOW -used-before-assignment:176:10:176:18::Using variable 'ALL_DONE' before assignment:CONTROL_FLOW +used-before-assignment:45:3:45:7::Using variable 'VAR2' before assignment:INFERENCE +possibly-used-before-assignment:63:3:63:7::Possibly using variable 'VAR4' before assignment:INFERENCE +possibly-used-before-assignment:73:3:73:7::Possibly using variable 'VAR5' before assignment:INFERENCE +used-before-assignment:78:3:78:7::Using variable 'VAR6' before assignment:INFERENCE +used-before-assignment:113:6:113:11::Using variable 'VAR10' before assignment:INFERENCE +possibly-used-before-assignment:119:6:119:11::Possibly using variable 'VAR12' before assignment:CONTROL_FLOW +used-before-assignment:144:10:144:14::Using variable 'SALE' before assignment:INFERENCE +used-before-assignment:176:10:176:18::Using variable 'ALL_DONE' before assignment:INFERENCE +used-before-assignment:187:6:187:24::Using variable 'NOT_ALWAYS_DEFINED' before assignment:INFERENCE diff --git a/tests/functional/u/used/used_before_assignment_except_handler_for_try_with_return.txt b/tests/functional/u/used/used_before_assignment_except_handler_for_try_with_return.txt index 5f2be351dd..4cac0253f9 100644 --- a/tests/functional/u/used/used_before_assignment_except_handler_for_try_with_return.txt +++ b/tests/functional/u/used/used_before_assignment_except_handler_for_try_with_return.txt @@ -1,7 +1,7 @@ used-before-assignment:16:14:16:29:function:Using variable 'failure_message' before assignment:CONTROL_FLOW used-before-assignment:120:10:120:13:func_invalid1:Using variable 'msg' before assignment:CONTROL_FLOW used-before-assignment:131:10:131:13:func_invalid2:Using variable 'msg' before assignment:CONTROL_FLOW -used-before-assignment:150:10:150:13:func_invalid3:Using variable 'msg' before assignment:CONTROL_FLOW +used-before-assignment:150:10:150:13:func_invalid3:Using variable 'msg' before assignment:INFERENCE used-before-assignment:163:10:163:13:func_invalid4:Using variable 'msg' before assignment:CONTROL_FLOW used-before-assignment:175:10:175:13:func_invalid5:Using variable 'msg' before assignment:CONTROL_FLOW used-before-assignment:187:10:187:13:func_invalid6:Using variable 'msg' before assignment:CONTROL_FLOW diff --git a/tests/functional/u/used/used_before_assignment_issue1081.py b/tests/functional/u/used/used_before_assignment_issue1081.py index d478bdeecc..cd317671f1 100644 --- a/tests/functional/u/used/used_before_assignment_issue1081.py +++ b/tests/functional/u/used/used_before_assignment_issue1081.py @@ -16,7 +16,7 @@ def used_before_assignment_2(a): def used_before_assignment_3(a): - if x == a: # [used-before-assignment] + if x == a: # [possibly-used-before-assignment] if x > 3: x = 2 # [redefined-outer-name] diff --git a/tests/functional/u/used/used_before_assignment_issue1081.txt b/tests/functional/u/used/used_before_assignment_issue1081.txt index 857c4826ba..81b38f17dc 100644 --- a/tests/functional/u/used/used_before_assignment_issue1081.txt +++ b/tests/functional/u/used/used_before_assignment_issue1081.txt @@ -2,6 +2,6 @@ used-before-assignment:7:7:7:8:used_before_assignment_1:Using variable 'x' befor redefined-outer-name:8:12:8:13:used_before_assignment_1:Redefining name 'x' from outer scope (line 3):UNDEFINED used-before-assignment:13:7:13:8:used_before_assignment_2:Using variable 'x' before assignment:HIGH redefined-outer-name:15:4:15:5:used_before_assignment_2:Redefining name 'x' from outer scope (line 3):UNDEFINED -used-before-assignment:19:7:19:8:used_before_assignment_3:Using variable 'x' before assignment:HIGH +possibly-used-before-assignment:19:7:19:8:used_before_assignment_3:Possibly using variable 'x' before assignment:CONTROL_FLOW redefined-outer-name:21:12:21:13:used_before_assignment_3:Redefining name 'x' from outer scope (line 3):UNDEFINED redefined-outer-name:30:4:30:5:not_used_before_assignment_2:Redefining name 'x' from outer scope (line 3):UNDEFINED diff --git a/tests/functional/u/used/used_before_assignment_issue2615.txt b/tests/functional/u/used/used_before_assignment_issue2615.txt index 567f562305..419770fcb5 100644 --- a/tests/functional/u/used/used_before_assignment_issue2615.txt +++ b/tests/functional/u/used/used_before_assignment_issue2615.txt @@ -1,3 +1,3 @@ -used-before-assignment:12:14:12:17:main:Using variable 'res' before assignment:CONTROL_FLOW +used-before-assignment:12:14:12:17:main:Using variable 'res' before assignment:INFERENCE used-before-assignment:30:18:30:35:nested_except_blocks:Using variable 'more_bad_division' before assignment:CONTROL_FLOW -used-before-assignment:31:18:31:21:nested_except_blocks:Using variable 'res' before assignment:CONTROL_FLOW +used-before-assignment:31:18:31:21:nested_except_blocks:Using variable 'res' before assignment:INFERENCE diff --git a/tests/functional/u/used/used_before_assignment_issue626.txt b/tests/functional/u/used/used_before_assignment_issue626.txt index 3d0e572463..1ee575ba3e 100644 --- a/tests/functional/u/used/used_before_assignment_issue626.txt +++ b/tests/functional/u/used/used_before_assignment_issue626.txt @@ -1,5 +1,5 @@ unused-variable:5:4:6:12:main1:Unused variable 'e':UNDEFINED -used-before-assignment:8:10:8:11:main1:Using variable 'e' before assignment:CONTROL_FLOW +used-before-assignment:8:10:8:11:main1:Using variable 'e' before assignment:HIGH unused-variable:21:4:22:12:main3:Unused variable 'e':UNDEFINED unused-variable:31:4:32:12:main4:Unused variable 'e':UNDEFINED -used-before-assignment:44:10:44:11:main4:Using variable 'e' before assignment:CONTROL_FLOW +used-before-assignment:44:10:44:11:main4:Using variable 'e' before assignment:HIGH diff --git a/tests/functional/u/used/used_before_assignment_postponed_evaluation.txt b/tests/functional/u/used/used_before_assignment_postponed_evaluation.txt index 88a9587369..15681c6dba 100644 --- a/tests/functional/u/used/used_before_assignment_postponed_evaluation.txt +++ b/tests/functional/u/used/used_before_assignment_postponed_evaluation.txt @@ -1 +1 @@ -used-before-assignment:10:6:10:9::Using variable 'var' before assignment:CONTROL_FLOW +used-before-assignment:10:6:10:9::Using variable 'var' before assignment:INFERENCE diff --git a/tests/functional/u/used/used_before_assignment_scoping.txt b/tests/functional/u/used/used_before_assignment_scoping.txt index 32b6d3e1bc..007f59a27c 100644 --- a/tests/functional/u/used/used_before_assignment_scoping.txt +++ b/tests/functional/u/used/used_before_assignment_scoping.txt @@ -1,2 +1,2 @@ -used-before-assignment:10:13:10:21:func_two:Using variable 'datetime' before assignment:CONTROL_FLOW -used-before-assignment:16:12:16:20:func:Using variable 'datetime' before assignment:CONTROL_FLOW +used-before-assignment:10:13:10:21:func_two:Using variable 'datetime' before assignment:INFERENCE +used-before-assignment:16:12:16:20:func:Using variable 'datetime' before assignment:INFERENCE diff --git a/tests/functional/u/used/used_before_assignment_typing.py b/tests/functional/u/used/used_before_assignment_typing.py index 9ec040ff62..9316285cda 100644 --- a/tests/functional/u/used/used_before_assignment_typing.py +++ b/tests/functional/u/used/used_before_assignment_typing.py @@ -167,32 +167,32 @@ class ConditionalImportGuardedWhenUsed: # pylint: disable=too-few-public-method class TypeCheckingMultiBranch: # pylint: disable=too-few-public-methods,unused-variable """Test for defines in TYPE_CHECKING if/elif/else branching""" - def defined_in_elif_branch(self) -> calendar.Calendar: - print(bisect) + def defined_in_elif_branch(self) -> calendar.Calendar: # [possibly-used-before-assignment] + print(bisect) # [possibly-used-before-assignment] return calendar.Calendar() def defined_in_else_branch(self) -> urlopen: - print(zoneinfo) + print(zoneinfo) # [used-before-assignment] print(pprint()) print(collections()) return urlopen - def defined_in_nested_if_else(self) -> heapq: + def defined_in_nested_if_else(self) -> heapq: # [possibly-used-before-assignment] print(heapq) return heapq - def defined_in_try_except(self) -> array: - print(types) - print(copy) - print(numbers) + def defined_in_try_except(self) -> array: # [used-before-assignment] + print(types) # [used-before-assignment] + print(copy) # [used-before-assignment] + print(numbers) # [used-before-assignment] return array - def defined_in_loops(self) -> json: - print(email) - print(mailbox) - print(mimetypes) + def defined_in_loops(self) -> json: # [used-before-assignment] + print(email) # [used-before-assignment] + print(mailbox) # [used-before-assignment] + print(mimetypes) # [used-before-assignment] return json - def defined_in_with(self) -> base64: - print(binascii) + def defined_in_with(self) -> base64: # [used-before-assignment] + print(binascii) # [used-before-assignment] return base64 diff --git a/tests/functional/u/used/used_before_assignment_typing.txt b/tests/functional/u/used/used_before_assignment_typing.txt index 12794f0e95..24900a3f95 100644 --- a/tests/functional/u/used/used_before_assignment_typing.txt +++ b/tests/functional/u/used/used_before_assignment_typing.txt @@ -1,5 +1,19 @@ undefined-variable:69:21:69:28:MyClass.incorrect_typing_method:Undefined variable 'MyClass':UNDEFINED undefined-variable:74:26:74:33:MyClass.incorrect_nested_typing_method:Undefined variable 'MyClass':UNDEFINED undefined-variable:79:20:79:27:MyClass.incorrect_default_method:Undefined variable 'MyClass':UNDEFINED -used-before-assignment:140:35:140:39:MyFourthClass.is_close:Using variable 'math' before assignment:CONTROL_FLOW -used-before-assignment:153:20:153:28:VariableAnnotationsGuardedByTypeChecking:Using variable 'datetime' before assignment:CONTROL_FLOW +used-before-assignment:140:35:140:39:MyFourthClass.is_close:Using variable 'math' before assignment:INFERENCE +used-before-assignment:153:20:153:28:VariableAnnotationsGuardedByTypeChecking:Using variable 'datetime' before assignment:INFERENCE +possibly-used-before-assignment:170:40:170:48:TypeCheckingMultiBranch.defined_in_elif_branch:Possibly using variable 'calendar' before assignment:INFERENCE +possibly-used-before-assignment:171:14:171:20:TypeCheckingMultiBranch.defined_in_elif_branch:Possibly using variable 'bisect' before assignment:INFERENCE +used-before-assignment:175:14:175:22:TypeCheckingMultiBranch.defined_in_else_branch:Using variable 'zoneinfo' before assignment:INFERENCE +possibly-used-before-assignment:180:43:180:48:TypeCheckingMultiBranch.defined_in_nested_if_else:Possibly using variable 'heapq' before assignment:INFERENCE +used-before-assignment:184:39:184:44:TypeCheckingMultiBranch.defined_in_try_except:Using variable 'array' before assignment:INFERENCE +used-before-assignment:185:14:185:19:TypeCheckingMultiBranch.defined_in_try_except:Using variable 'types' before assignment:INFERENCE +used-before-assignment:186:14:186:18:TypeCheckingMultiBranch.defined_in_try_except:Using variable 'copy' before assignment:INFERENCE +used-before-assignment:187:14:187:21:TypeCheckingMultiBranch.defined_in_try_except:Using variable 'numbers' before assignment:INFERENCE +used-before-assignment:190:34:190:38:TypeCheckingMultiBranch.defined_in_loops:Using variable 'json' before assignment:INFERENCE +used-before-assignment:191:14:191:19:TypeCheckingMultiBranch.defined_in_loops:Using variable 'email' before assignment:INFERENCE +used-before-assignment:192:14:192:21:TypeCheckingMultiBranch.defined_in_loops:Using variable 'mailbox' before assignment:INFERENCE +used-before-assignment:193:14:193:23:TypeCheckingMultiBranch.defined_in_loops:Using variable 'mimetypes' before assignment:INFERENCE +used-before-assignment:196:33:196:39:TypeCheckingMultiBranch.defined_in_with:Using variable 'base64' before assignment:INFERENCE +used-before-assignment:197:14:197:22:TypeCheckingMultiBranch.defined_in_with:Using variable 'binascii' before assignment:INFERENCE diff --git a/tests/test_func.py b/tests/test_func.py index d74e7ac6f1..351acceca5 100644 --- a/tests/test_func.py +++ b/tests/test_func.py @@ -44,8 +44,7 @@ class LintTestUsingModule: output: str | None = None def _test_functionality(self) -> None: - if self.module: - tocheck = [self.package + "." + self.module] + tocheck = [self.package + "." + self.module] if self.module else [] if self.depends: tocheck += [ self.package + f".{name.replace('.py', '')}" for name, _ in self.depends