From 4ee174f606b5e3a8ef63102b05590a29bd0a554c Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Sun, 13 Aug 2023 13:13:59 -0400 Subject: [PATCH 01/19] Emit `used-before-assignment` after if/else switches --- doc/whatsnew/fragments/1727.false_negative | 4 ++ pylint/checkers/variables.py | 44 +++++++++++----------- 2 files changed, 26 insertions(+), 22 deletions(-) create mode 100644 doc/whatsnew/fragments/1727.false_negative diff --git a/doc/whatsnew/fragments/1727.false_negative b/doc/whatsnew/fragments/1727.false_negative new file mode 100644 index 0000000000..7055606bf0 --- /dev/null +++ b/doc/whatsnew/fragments/1727.false_negative @@ -0,0 +1,4 @@ +Emit ``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 925088f60d..c2f0afcfc6 100644 --- a/pylint/checkers/variables.py +++ b/pylint/checkers/variables.py @@ -536,6 +536,7 @@ 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() def __repr__(self) -> str: _to_consumes = [f"{k}->{v}" for k, v in self._atomic.to_consume.items()] @@ -637,7 +638,7 @@ def get_next_to_consume(self, node: nodes.Name) -> list[nodes.NodeNG] | None: # Filter out assignments guarded by always false conditions if found_nodes: - uncertain_nodes = self._uncertain_nodes_in_false_tests(found_nodes, node) + 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] @@ -687,8 +688,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. """ @@ -738,17 +740,14 @@ 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) + return self._branch_handles_name(name, node.body) and self._branch_handles_name( + name, node.orelse + ) - @staticmethod - def _branch_handles_name(name: str, body: Iterable[nodes.NodeNG]) -> bool: + 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( @@ -761,17 +760,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). @@ -807,7 +804,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) @@ -1974,9 +1971,12 @@ 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.consumed_uncertain: + confidence = CONTROL_FLOW + if node.name in current_consumer.names_under_always_false_test: + confidence = INFERENCE + self.add_message( "used-before-assignment", args=node.name, From 9b42d2115362117a748ca78743d29386eb93d4f7 Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Sat, 20 Jan 2024 16:20:08 -0500 Subject: [PATCH 02/19] Update expected results --- .../u/undefined/undefined_variable.txt | 2 +- .../u/undefined/undefined_variable_py38.txt | 2 +- .../u/used/used_before_assignment.py | 4 +-- .../u/used/used_before_assignment.txt | 14 ++++++---- ...ent_except_handler_for_try_with_return.txt | 2 +- .../used/used_before_assignment_issue1081.txt | 2 +- .../used/used_before_assignment_issue2615.txt | 4 +-- ...before_assignment_postponed_evaluation.txt | 2 +- .../u/used/used_before_assignment_scoping.txt | 4 +-- .../u/used/used_before_assignment_typing.py | 28 +++++++++---------- .../u/used/used_before_assignment_typing.txt | 18 ++++++++++-- 11 files changed, 49 insertions(+), 33 deletions(-) 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 0d1a938b9e..89281ffc15 100644 --- a/tests/functional/u/used/used_before_assignment.py +++ b/tests/functional/u/used/used_before_assignment.py @@ -70,7 +70,7 @@ def redefine_time_import_with_global(): VAR5 = True else: VAR5 = True -if VAR5: +if VAR5: # [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) # [used-before-assignment] def turn_on2(**kwargs): """https://github.com/pylint-dev/pylint/issues/7873""" diff --git a/tests/functional/u/used/used_before_assignment.txt b/tests/functional/u/used/used_before_assignment.txt index 37b25ab49b..ef84ba982e 100644 --- a/tests/functional/u/used/used_before_assignment.txt +++ b/tests/functional/u/used/used_before_assignment.txt @@ -4,9 +4,11 @@ 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 +used-before-assignment:63:3:63:7::Using variable 'VAR4' before assignment:INFERENCE +used-before-assignment:73:3:73:7::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 +used-before-assignment:119:6:119:11::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 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.txt b/tests/functional/u/used/used_before_assignment_issue1081.txt index 857c4826ba..fd451b289f 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 +used-before-assignment:19:7:19:8:used_before_assignment_3: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_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..5fc12fa615 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: # [used-before-assignment] + print(bisect) # [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: # [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..1cdac6eddc 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 +used-before-assignment:170:40:170:48:TypeCheckingMultiBranch.defined_in_elif_branch:Using variable 'calendar' before assignment:INFERENCE +used-before-assignment:171:14:171:20:TypeCheckingMultiBranch.defined_in_elif_branch:Using variable 'bisect' before assignment:INFERENCE +used-before-assignment:175:14:175:22:TypeCheckingMultiBranch.defined_in_else_branch:Using variable 'zoneinfo' before assignment:INFERENCE +used-before-assignment:180:43:180:48:TypeCheckingMultiBranch.defined_in_nested_if_else: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 From f17a83c268b259a295870930529bff91c92dc5e9 Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Sat, 20 Jan 2024 16:22:20 -0500 Subject: [PATCH 03/19] Add news --- doc/whatsnew/fragments/1727.false_negative.1 | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 doc/whatsnew/fragments/1727.false_negative.1 diff --git a/doc/whatsnew/fragments/1727.false_negative.1 b/doc/whatsnew/fragments/1727.false_negative.1 new file mode 100644 index 0000000000..f462b124e9 --- /dev/null +++ b/doc/whatsnew/fragments/1727.false_negative.1 @@ -0,0 +1,19 @@ +``used-before-assignment`` is now emitted when relying on variable assignments +that were not exhaustively made in every if/else branch. + +If you rely on a pattern like this: +``` +if guarded(): + var = 1 + +if guarded(): + print(var) # now emits used-before-assignment +``` + +...you may be concerned that ``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, has side effects, or talks to +a database. (Likewise, for ``guarded`` instead of ``guarded()``, any other +part of your program may have changed the value in the meantime.) + +Closes #1727 From 1c09c0aa5cf170f0be05a4e93f31057bf2d22466 Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Sat, 20 Jan 2024 16:31:58 -0500 Subject: [PATCH 04/19] Add annotation; move example to details.rst --- .../u/used-before-assignment/details.rst | 15 +++++++++++++++ doc/whatsnew/fragments/1727.false_negative.1 | 19 ------------------- pylint/checkers/variables.py | 2 +- 3 files changed, 16 insertions(+), 20 deletions(-) create mode 100644 doc/data/messages/u/used-before-assignment/details.rst delete mode 100644 doc/whatsnew/fragments/1727.false_negative.1 diff --git a/doc/data/messages/u/used-before-assignment/details.rst b/doc/data/messages/u/used-before-assignment/details.rst new file mode 100644 index 0000000000..9abd9538e1 --- /dev/null +++ b/doc/data/messages/u/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) # now emits used-before-assignment + +you may be concerned that ``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, has side effects, 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/whatsnew/fragments/1727.false_negative.1 b/doc/whatsnew/fragments/1727.false_negative.1 deleted file mode 100644 index f462b124e9..0000000000 --- a/doc/whatsnew/fragments/1727.false_negative.1 +++ /dev/null @@ -1,19 +0,0 @@ -``used-before-assignment`` is now emitted when relying on variable assignments -that were not exhaustively made in every if/else branch. - -If you rely on a pattern like this: -``` -if guarded(): - var = 1 - -if guarded(): - print(var) # now emits used-before-assignment -``` - -...you may be concerned that ``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, has side effects, or talks to -a database. (Likewise, for ``guarded`` instead of ``guarded()``, any other -part of your program may have changed the value in the meantime.) - -Closes #1727 diff --git a/pylint/checkers/variables.py b/pylint/checkers/variables.py index c2f0afcfc6..4ae0239437 100644 --- a/pylint/checkers/variables.py +++ b/pylint/checkers/variables.py @@ -536,7 +536,7 @@ 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() + self.names_under_always_false_test: set[str] = set() def __repr__(self) -> str: _to_consumes = [f"{k}->{v}" for k, v in self._atomic.to_consume.items()] From 7a983a2ed11374477bcb7e3f7f6ceb611f2b3754 Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Fri, 23 Feb 2024 18:42:24 -0500 Subject: [PATCH 05/19] Fix false negative --- pylint/checkers/variables.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/pylint/checkers/variables.py b/pylint/checkers/variables.py index fa516f37f1..951f207ae1 100644 --- a/pylint/checkers/variables.py +++ b/pylint/checkers/variables.py @@ -637,13 +637,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_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 ExceptHandlers that node is not contained in if found_nodes: found_nodes = [ @@ -653,6 +646,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: From 99b2fc967f39c24005d0ace38e68f0b2fdead090 Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Fri, 23 Feb 2024 18:48:49 -0500 Subject: [PATCH 06/19] Update output --- tests/functional/r/redefined/redefined_except_handler.txt | 2 +- tests/functional/u/used/used_before_assignment_issue626.txt | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) 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/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 From a7713836d975010cbc1ddacf47e68cf9bb231c49 Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Fri, 23 Feb 2024 18:49:22 -0500 Subject: [PATCH 07/19] Treat continue just like break --- pylint/checkers/variables.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pylint/checkers/variables.py b/pylint/checkers/variables.py index 951f207ae1..dd3f71676b 100644 --- a/pylint/checkers/variables.py +++ b/pylint/checkers/variables.py @@ -718,8 +718,8 @@ def _inferred_to_define_name_raise_or_return( 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 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? From f06f2f562dd8092ffffaf461c80251123489288b Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Sat, 24 Feb 2024 08:45:01 -0500 Subject: [PATCH 08/19] Add test case --- pylint/checkers/variables.py | 2 +- tests/functional/u/used/used_before_assignment.py | 13 +++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/pylint/checkers/variables.py b/pylint/checkers/variables.py index dd3f71676b..3e7fcadeff 100644 --- a/pylint/checkers/variables.py +++ b/pylint/checkers/variables.py @@ -927,7 +927,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) diff --git a/tests/functional/u/used/used_before_assignment.py b/tests/functional/u/used/used_before_assignment.py index 2f4f02acf8..1474d14707 100644 --- a/tests/functional/u/used/used_before_assignment.py +++ b/tests/functional/u/used/used_before_assignment.py @@ -180,3 +180,16 @@ 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 + + +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) From 8c758e21cefc62cc63dd1f66a1a4b96c9d614695 Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Sat, 24 Feb 2024 08:51:54 -0500 Subject: [PATCH 09/19] Don't unnecessarily lower confidence level --- pylint/checkers/variables.py | 2 +- .../u/undefined/undefined_variable.txt | 2 +- .../u/undefined/undefined_variable_py38.txt | 2 +- .../u/used/used_before_assignment.txt | 14 ++++---- ...ent_except_handler_for_try_with_return.txt | 2 +- .../used/used_before_assignment_issue2615.txt | 4 +-- ...before_assignment_postponed_evaluation.txt | 2 +- .../u/used/used_before_assignment_scoping.txt | 4 +-- .../u/used/used_before_assignment_typing.txt | 32 +++++++++---------- 9 files changed, 32 insertions(+), 32 deletions(-) diff --git a/pylint/checkers/variables.py b/pylint/checkers/variables.py index 3e7fcadeff..a9643b0872 100644 --- a/pylint/checkers/variables.py +++ b/pylint/checkers/variables.py @@ -1993,7 +1993,7 @@ def _report_unfound_name_definition( confidence = HIGH if node.name in current_consumer.consumed_uncertain: confidence = CONTROL_FLOW - if node.name in current_consumer.names_under_always_false_test: + elif node.name in current_consumer.names_under_always_false_test: confidence = INFERENCE self.add_message( diff --git a/tests/functional/u/undefined/undefined_variable.txt b/tests/functional/u/undefined/undefined_variable.txt index ea707c910a..b41f9ae46f 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:INFERENCE +used-before-assignment:255:26:255:34:func_should_fail:Using variable 'datetime' before assignment:CONTROL_FLOW 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 5a3533dc93..1674707a57 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:INFERENCE +used-before-assignment:193:6:193:19::Using variable 'NEVER_DEFINED' before assignment:CONTROL_FLOW diff --git a/tests/functional/u/used/used_before_assignment.txt b/tests/functional/u/used/used_before_assignment.txt index ef84ba982e..b1af3688e1 100644 --- a/tests/functional/u/used/used_before_assignment.txt +++ b/tests/functional/u/used/used_before_assignment.txt @@ -4,11 +4,11 @@ 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:INFERENCE -used-before-assignment:63:3:63:7::Using variable 'VAR4' before assignment:INFERENCE -used-before-assignment:73:3:73:7::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 +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:73:3:73:7::Using variable 'VAR5' 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:119:6:119:11::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: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 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 4cac0253f9..5f2be351dd 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:INFERENCE +used-before-assignment:150:10:150:13:func_invalid3:Using variable 'msg' before assignment:CONTROL_FLOW 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_issue2615.txt b/tests/functional/u/used/used_before_assignment_issue2615.txt index 419770fcb5..567f562305 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:INFERENCE +used-before-assignment:12:14:12:17:main:Using variable 'res' before assignment:CONTROL_FLOW 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:INFERENCE +used-before-assignment:31:18:31:21:nested_except_blocks:Using variable 'res' before assignment:CONTROL_FLOW 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 15681c6dba..88a9587369 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:INFERENCE +used-before-assignment:10:6:10:9::Using variable 'var' before assignment:CONTROL_FLOW diff --git a/tests/functional/u/used/used_before_assignment_scoping.txt b/tests/functional/u/used/used_before_assignment_scoping.txt index 007f59a27c..32b6d3e1bc 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:INFERENCE -used-before-assignment:16:12:16:20:func:Using variable 'datetime' before assignment:INFERENCE +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 diff --git a/tests/functional/u/used/used_before_assignment_typing.txt b/tests/functional/u/used/used_before_assignment_typing.txt index 1cdac6eddc..fb6770e24f 100644 --- a/tests/functional/u/used/used_before_assignment_typing.txt +++ b/tests/functional/u/used/used_before_assignment_typing.txt @@ -1,19 +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:INFERENCE -used-before-assignment:153:20:153:28:VariableAnnotationsGuardedByTypeChecking:Using variable 'datetime' before assignment:INFERENCE -used-before-assignment:170:40:170:48:TypeCheckingMultiBranch.defined_in_elif_branch:Using variable 'calendar' before assignment:INFERENCE -used-before-assignment:171:14:171:20:TypeCheckingMultiBranch.defined_in_elif_branch:Using variable 'bisect' before assignment:INFERENCE -used-before-assignment:175:14:175:22:TypeCheckingMultiBranch.defined_in_else_branch:Using variable 'zoneinfo' before assignment:INFERENCE -used-before-assignment:180:43:180:48:TypeCheckingMultiBranch.defined_in_nested_if_else: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 +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:170:40:170:48:TypeCheckingMultiBranch.defined_in_elif_branch:Using variable 'calendar' before assignment:CONTROL_FLOW +used-before-assignment:171:14:171:20:TypeCheckingMultiBranch.defined_in_elif_branch:Using variable 'bisect' before assignment:CONTROL_FLOW +used-before-assignment:175:14:175:22:TypeCheckingMultiBranch.defined_in_else_branch:Using variable 'zoneinfo' before assignment:CONTROL_FLOW +used-before-assignment:180:43:180:48:TypeCheckingMultiBranch.defined_in_nested_if_else:Using variable 'heapq' before assignment:CONTROL_FLOW +used-before-assignment:184:39:184:44:TypeCheckingMultiBranch.defined_in_try_except:Using variable 'array' before assignment:CONTROL_FLOW +used-before-assignment:185:14:185:19:TypeCheckingMultiBranch.defined_in_try_except:Using variable 'types' before assignment:CONTROL_FLOW +used-before-assignment:186:14:186:18:TypeCheckingMultiBranch.defined_in_try_except:Using variable 'copy' before assignment:CONTROL_FLOW +used-before-assignment:187:14:187:21:TypeCheckingMultiBranch.defined_in_try_except:Using variable 'numbers' before assignment:CONTROL_FLOW +used-before-assignment:190:34:190:38:TypeCheckingMultiBranch.defined_in_loops:Using variable 'json' before assignment:CONTROL_FLOW +used-before-assignment:191:14:191:19:TypeCheckingMultiBranch.defined_in_loops:Using variable 'email' before assignment:CONTROL_FLOW +used-before-assignment:192:14:192:21:TypeCheckingMultiBranch.defined_in_loops:Using variable 'mailbox' before assignment:CONTROL_FLOW +used-before-assignment:193:14:193:23:TypeCheckingMultiBranch.defined_in_loops:Using variable 'mimetypes' before assignment:CONTROL_FLOW +used-before-assignment:196:33:196:39:TypeCheckingMultiBranch.defined_in_with:Using variable 'base64' before assignment:CONTROL_FLOW +used-before-assignment:197:14:197:22:TypeCheckingMultiBranch.defined_in_with:Using variable 'binascii' before assignment:CONTROL_FLOW From 48a0f25b6bb6fe5334935ecfd1c770876c8e04b9 Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Sat, 24 Feb 2024 08:59:29 -0500 Subject: [PATCH 10/19] Add test --- tests/functional/u/used/used_before_assignment.py | 5 +++++ tests/functional/u/used/used_before_assignment.txt | 1 + 2 files changed, 6 insertions(+) diff --git a/tests/functional/u/used/used_before_assignment.py b/tests/functional/u/used/used_before_assignment.py index 1474d14707..bcfb0f0614 100644 --- a/tests/functional/u/used/used_before_assignment.py +++ b/tests/functional/u/used/used_before_assignment.py @@ -182,6 +182,11 @@ class T: # pylint: disable=invalid-name, too-few-public-methods, undefined-vari 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): diff --git a/tests/functional/u/used/used_before_assignment.txt b/tests/functional/u/used/used_before_assignment.txt index b1af3688e1..e816f100e1 100644 --- a/tests/functional/u/used/used_before_assignment.txt +++ b/tests/functional/u/used/used_before_assignment.txt @@ -12,3 +12,4 @@ used-before-assignment:113:6:113:11::Using variable 'VAR10' before assignment:CO used-before-assignment:119:6:119:11::Using variable 'VAR12' 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:187:6:187:24::Using variable 'NOT_ALWAYS_DEFINED' before assignment:CONTROL_FLOW From 35fb7d25796d316775362c9ee1277201ae6036e5 Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Fri, 5 Apr 2024 20:45:54 -0400 Subject: [PATCH 11/19] Reverse confidence order --- pylint/checkers/variables.py | 6 ++-- .../u/undefined/undefined_variable.txt | 2 +- .../u/undefined/undefined_variable_py38.txt | 2 +- .../u/used/used_before_assignment.txt | 16 +++++----- ...ent_except_handler_for_try_with_return.txt | 2 +- .../used/used_before_assignment_issue2615.txt | 4 +-- ...before_assignment_postponed_evaluation.txt | 2 +- .../u/used/used_before_assignment_scoping.txt | 4 +-- .../u/used/used_before_assignment_typing.txt | 32 +++++++++---------- 9 files changed, 35 insertions(+), 35 deletions(-) diff --git a/pylint/checkers/variables.py b/pylint/checkers/variables.py index a9643b0872..0494ba45ce 100644 --- a/pylint/checkers/variables.py +++ b/pylint/checkers/variables.py @@ -1991,10 +1991,10 @@ def _report_unfound_name_definition( return confidence = HIGH - if node.name in current_consumer.consumed_uncertain: - confidence = CONTROL_FLOW - elif node.name in current_consumer.names_under_always_false_test: + if node.name in current_consumer.names_under_always_false_test: confidence = INFERENCE + elif node.name in current_consumer.consumed_uncertain: + confidence = CONTROL_FLOW self.add_message( "used-before-assignment", 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.txt b/tests/functional/u/used/used_before_assignment.txt index e816f100e1..c0282f3a39 100644 --- a/tests/functional/u/used/used_before_assignment.txt +++ b/tests/functional/u/used/used_before_assignment.txt @@ -4,12 +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:73:3:73:7::Using variable 'VAR5' 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:45:3:45:7::Using variable 'VAR2' before assignment:INFERENCE +used-before-assignment:63:3:63:7::Using variable 'VAR4' before assignment:INFERENCE +used-before-assignment:73:3:73:7::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 used-before-assignment:119:6:119:11::Using variable 'VAR12' 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:187:6:187:24::Using variable 'NOT_ALWAYS_DEFINED' 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_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_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.txt b/tests/functional/u/used/used_before_assignment_typing.txt index fb6770e24f..1cdac6eddc 100644 --- a/tests/functional/u/used/used_before_assignment_typing.txt +++ b/tests/functional/u/used/used_before_assignment_typing.txt @@ -1,19 +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:170:40:170:48:TypeCheckingMultiBranch.defined_in_elif_branch:Using variable 'calendar' before assignment:CONTROL_FLOW -used-before-assignment:171:14:171:20:TypeCheckingMultiBranch.defined_in_elif_branch:Using variable 'bisect' before assignment:CONTROL_FLOW -used-before-assignment:175:14:175:22:TypeCheckingMultiBranch.defined_in_else_branch:Using variable 'zoneinfo' before assignment:CONTROL_FLOW -used-before-assignment:180:43:180:48:TypeCheckingMultiBranch.defined_in_nested_if_else:Using variable 'heapq' before assignment:CONTROL_FLOW -used-before-assignment:184:39:184:44:TypeCheckingMultiBranch.defined_in_try_except:Using variable 'array' before assignment:CONTROL_FLOW -used-before-assignment:185:14:185:19:TypeCheckingMultiBranch.defined_in_try_except:Using variable 'types' before assignment:CONTROL_FLOW -used-before-assignment:186:14:186:18:TypeCheckingMultiBranch.defined_in_try_except:Using variable 'copy' before assignment:CONTROL_FLOW -used-before-assignment:187:14:187:21:TypeCheckingMultiBranch.defined_in_try_except:Using variable 'numbers' before assignment:CONTROL_FLOW -used-before-assignment:190:34:190:38:TypeCheckingMultiBranch.defined_in_loops:Using variable 'json' before assignment:CONTROL_FLOW -used-before-assignment:191:14:191:19:TypeCheckingMultiBranch.defined_in_loops:Using variable 'email' before assignment:CONTROL_FLOW -used-before-assignment:192:14:192:21:TypeCheckingMultiBranch.defined_in_loops:Using variable 'mailbox' before assignment:CONTROL_FLOW -used-before-assignment:193:14:193:23:TypeCheckingMultiBranch.defined_in_loops:Using variable 'mimetypes' before assignment:CONTROL_FLOW -used-before-assignment:196:33:196:39:TypeCheckingMultiBranch.defined_in_with:Using variable 'base64' before assignment:CONTROL_FLOW -used-before-assignment:197:14:197:22:TypeCheckingMultiBranch.defined_in_with:Using variable 'binascii' 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 +used-before-assignment:170:40:170:48:TypeCheckingMultiBranch.defined_in_elif_branch:Using variable 'calendar' before assignment:INFERENCE +used-before-assignment:171:14:171:20:TypeCheckingMultiBranch.defined_in_elif_branch:Using variable 'bisect' before assignment:INFERENCE +used-before-assignment:175:14:175:22:TypeCheckingMultiBranch.defined_in_else_branch:Using variable 'zoneinfo' before assignment:INFERENCE +used-before-assignment:180:43:180:48:TypeCheckingMultiBranch.defined_in_nested_if_else: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 From 76fe5cae631b28cb5369f773fb208115935029ff Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Fri, 5 Apr 2024 21:25:06 -0400 Subject: [PATCH 12/19] First stab at distinguishing new message --- pylint/checkers/variables.py | 24 +++++++++++++++---- pylintrc | 1 + .../u/used/used_before_assignment.py | 6 ++--- .../u/used/used_before_assignment.txt | 6 ++--- .../used/used_before_assignment_issue1081.py | 2 +- .../used/used_before_assignment_issue1081.txt | 2 +- .../u/used/used_before_assignment_typing.py | 6 ++--- .../u/used/used_before_assignment_typing.txt | 6 ++--- 8 files changed, 35 insertions(+), 18 deletions(-) diff --git a/pylint/checkers/variables.py b/pylint/checkers/variables.py index 0494ba45ce..cc36af56eb 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", @@ -538,6 +544,7 @@ def __init__(self, node: nodes.NodeNG, scope_type: str) -> None: ) 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()] @@ -744,9 +751,13 @@ def _inferred_to_define_name_raise_or_return( self.names_under_always_false_test.add(name) return self._branch_handles_name(name, node.orelse) # Search both if and else branches - return self._branch_handles_name(name, node.body) and self._branch_handles_name( - name, node.orelse - ) + 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( @@ -1996,8 +2007,13 @@ def _report_unfound_name_definition( 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/u/used/used_before_assignment.py b/tests/functional/u/used/used_before_assignment.py index bcfb0f0614..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: # [used-before-assignment] +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) # [used-before-assignment] +print(VAR12) # [possibly-used-before-assignment] def turn_on2(**kwargs): """https://github.com/pylint-dev/pylint/issues/7873""" diff --git a/tests/functional/u/used/used_before_assignment.txt b/tests/functional/u/used/used_before_assignment.txt index c0282f3a39..127a5d7de4 100644 --- a/tests/functional/u/used/used_before_assignment.txt +++ b/tests/functional/u/used/used_before_assignment.txt @@ -5,11 +5,11 @@ used-before-assignment:19:20:19:40:ClassWithProperty:Using variable 'redefine_ti 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:INFERENCE -used-before-assignment:63:3:63:7::Using variable 'VAR4' before assignment:INFERENCE -used-before-assignment:73:3:73:7::Using variable 'VAR5' 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 -used-before-assignment:119:6:119:11::Using variable 'VAR12' before assignment:CONTROL_FLOW +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_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 fd451b289f..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:CONTROL_FLOW +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_typing.py b/tests/functional/u/used/used_before_assignment_typing.py index 5fc12fa615..9316285cda 100644 --- a/tests/functional/u/used/used_before_assignment_typing.py +++ b/tests/functional/u/used/used_before_assignment_typing.py @@ -167,8 +167,8 @@ 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: # [used-before-assignment] - print(bisect) # [used-before-assignment] + 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: @@ -177,7 +177,7 @@ def defined_in_else_branch(self) -> urlopen: print(collections()) return urlopen - def defined_in_nested_if_else(self) -> heapq: # [used-before-assignment] + def defined_in_nested_if_else(self) -> heapq: # [possibly-used-before-assignment] print(heapq) return heapq diff --git a/tests/functional/u/used/used_before_assignment_typing.txt b/tests/functional/u/used/used_before_assignment_typing.txt index 1cdac6eddc..24900a3f95 100644 --- a/tests/functional/u/used/used_before_assignment_typing.txt +++ b/tests/functional/u/used/used_before_assignment_typing.txt @@ -3,10 +3,10 @@ undefined-variable:74:26:74:33:MyClass.incorrect_nested_typing_method: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:INFERENCE used-before-assignment:153:20:153:28:VariableAnnotationsGuardedByTypeChecking:Using variable 'datetime' before assignment:INFERENCE -used-before-assignment:170:40:170:48:TypeCheckingMultiBranch.defined_in_elif_branch:Using variable 'calendar' before assignment:INFERENCE -used-before-assignment:171:14:171:20:TypeCheckingMultiBranch.defined_in_elif_branch:Using variable 'bisect' 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 -used-before-assignment:180:43:180:48:TypeCheckingMultiBranch.defined_in_nested_if_else:Using variable 'heapq' 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 From 79fac0fc0707227d4c7db7fb7a1aab19a42bebb7 Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Mon, 8 Apr 2024 20:38:52 -0400 Subject: [PATCH 13/19] Initialize `tocheck` variable --- tests/test_func.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/test_func.py b/tests/test_func.py index d74e7ac6f1..c241dc0824 100644 --- a/tests/test_func.py +++ b/tests/test_func.py @@ -44,6 +44,7 @@ class LintTestUsingModule: output: str | None = None def _test_functionality(self) -> None: + tocheck = [] if self.module: tocheck = [self.package + "." + self.module] if self.depends: From 3fefce6d1dc55abf712575e2ec07a0e2fb252801 Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Mon, 8 Apr 2024 20:42:41 -0400 Subject: [PATCH 14/19] Add good/bad --- doc/data/messages/p/possibly-used-before-assignment/bad.py | 4 ++++ doc/data/messages/p/possibly-used-before-assignment/good.py | 5 +++++ 2 files changed, 9 insertions(+) create mode 100644 doc/data/messages/p/possibly-used-before-assignment/bad.py create mode 100644 doc/data/messages/p/possibly-used-before-assignment/good.py 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..c739b929cb --- /dev/null +++ b/doc/data/messages/p/possibly-used-before-assignment/bad.py @@ -0,0 +1,4 @@ +def func(value): + if value: + has_value = True + print(has_value) 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..518a284f6c --- /dev/null +++ b/doc/data/messages/p/possibly-used-before-assignment/good.py @@ -0,0 +1,5 @@ +def func(value): + value = False + if value: + has_value = True + print(has_value) From 282150b34a1a491fd16d0e2cf2f9bc656ba8a4d3 Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Mon, 8 Apr 2024 20:45:31 -0400 Subject: [PATCH 15/19] Reclassify changelog as new check --- doc/whatsnew/fragments/1727.false_negative | 4 ---- doc/whatsnew/fragments/1727.new_check | 4 ++++ 2 files changed, 4 insertions(+), 4 deletions(-) delete mode 100644 doc/whatsnew/fragments/1727.false_negative create mode 100644 doc/whatsnew/fragments/1727.new_check diff --git a/doc/whatsnew/fragments/1727.false_negative b/doc/whatsnew/fragments/1727.false_negative deleted file mode 100644 index 7055606bf0..0000000000 --- a/doc/whatsnew/fragments/1727.false_negative +++ /dev/null @@ -1,4 +0,0 @@ -Emit ``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/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 From a6dfb212c681566cc02d15ee52798548ee7aa589 Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Mon, 8 Apr 2024 21:06:52 -0400 Subject: [PATCH 16/19] Move details.rst --- .../possibly-used-before-assignment}/details.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) rename doc/data/messages/{u/used-before-assignment => p/possibly-used-before-assignment}/details.rst (73%) diff --git a/doc/data/messages/u/used-before-assignment/details.rst b/doc/data/messages/p/possibly-used-before-assignment/details.rst similarity index 73% rename from doc/data/messages/u/used-before-assignment/details.rst rename to doc/data/messages/p/possibly-used-before-assignment/details.rst index 9abd9538e1..c9fa695e9d 100644 --- a/doc/data/messages/u/used-before-assignment/details.rst +++ b/doc/data/messages/p/possibly-used-before-assignment/details.rst @@ -6,9 +6,9 @@ If you rely on a pattern like: var = 1 if guarded(): - print(var) # now emits used-before-assignment + print(var) # emits possibly-used-before-assignment -you may be concerned that ``used-before-assignment`` is not totally useful +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, has side effects, or talks to a database. (Likewise, for ``guarded`` instead of ``guarded()``, any other From cc80eb9c619d965b5010d72ba7bee2586f51270c Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Mon, 8 Apr 2024 21:07:58 -0400 Subject: [PATCH 17/19] Add expected msg --- doc/data/messages/p/possibly-used-before-assignment/bad.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/data/messages/p/possibly-used-before-assignment/bad.py b/doc/data/messages/p/possibly-used-before-assignment/bad.py index c739b929cb..c17f881863 100644 --- a/doc/data/messages/p/possibly-used-before-assignment/bad.py +++ b/doc/data/messages/p/possibly-used-before-assignment/bad.py @@ -1,4 +1,4 @@ def func(value): if value: has_value = True - print(has_value) + print(has_value) # [possibly-used-before-assignment] From d40710e2c4a0f50bac7b6143fba3dff6738562c1 Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Tue, 9 Apr 2024 08:38:27 -0400 Subject: [PATCH 18/19] Update docs --- doc/user_guide/checkers/features.rst | 3 +++ doc/user_guide/messages/messages_overview.rst | 1 + 2 files changed, 4 insertions(+) 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 From b362668fafe07c38eeb71dfaf2daa14e21c98e58 Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Tue, 16 Apr 2024 07:49:37 -0400 Subject: [PATCH 19/19] Apply suggestions from code review Co-authored-by: Pierre Sassoulas --- .../messages/p/possibly-used-before-assignment/bad.py | 8 ++++---- .../p/possibly-used-before-assignment/details.rst | 2 +- .../messages/p/possibly-used-before-assignment/good.py | 10 +++++----- pylint/checkers/variables.py | 2 +- tests/test_func.py | 4 +--- 5 files changed, 12 insertions(+), 14 deletions(-) diff --git a/doc/data/messages/p/possibly-used-before-assignment/bad.py b/doc/data/messages/p/possibly-used-before-assignment/bad.py index c17f881863..8e7f0cb02a 100644 --- a/doc/data/messages/p/possibly-used-before-assignment/bad.py +++ b/doc/data/messages/p/possibly-used-before-assignment/bad.py @@ -1,4 +1,4 @@ -def func(value): - if value: - has_value = True - print(has_value) # [possibly-used-before-assignment] +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 index c9fa695e9d..4737d26685 100644 --- a/doc/data/messages/p/possibly-used-before-assignment/details.rst +++ b/doc/data/messages/p/possibly-used-before-assignment/details.rst @@ -10,6 +10,6 @@ If you rely on a pattern like: 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, has side effects, or talks to +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 index 518a284f6c..6bb478f5f8 100644 --- a/doc/data/messages/p/possibly-used-before-assignment/good.py +++ b/doc/data/messages/p/possibly-used-before-assignment/good.py @@ -1,5 +1,5 @@ -def func(value): - value = False - if value: - has_value = True - print(has_value) +def check_lunchbox(items: list[str]): + empty = False + if not items: + empty = True + print(empty) diff --git a/pylint/checkers/variables.py b/pylint/checkers/variables.py index cc36af56eb..ebc27c1e33 100644 --- a/pylint/checkers/variables.py +++ b/pylint/checkers/variables.py @@ -725,7 +725,7 @@ def _inferred_to_define_name_raise_or_return( if not isinstance(node, nodes.If): return False - # Be permissive if there is a break or continue + # Be permissive if there is a break or a continue if any(node.nodes_of_class(nodes.Break, nodes.Continue)): return True diff --git a/tests/test_func.py b/tests/test_func.py index c241dc0824..351acceca5 100644 --- a/tests/test_func.py +++ b/tests/test_func.py @@ -44,9 +44,7 @@ class LintTestUsingModule: output: str | None = None def _test_functionality(self) -> None: - tocheck = [] - 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