From 1cbc3d818b2b279747ef8d4222262fea4b21cccb Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Sat, 29 Apr 2023 14:32:50 -0400 Subject: Fix false negative for calling module-level function before definition (#8494) --- doc/whatsnew/fragments/1144.false_negative | 3 +++ pylint/checkers/variables.py | 12 +++--------- tests/functional/u/used/used_before_assignment.py | 11 +++++++++++ tests/functional/u/used/used_before_assignment.txt | 16 +++++++++------- .../functional/u/used/used_before_assignment_ternary.txt | 6 +++--- 5 files changed, 29 insertions(+), 19 deletions(-) create mode 100644 doc/whatsnew/fragments/1144.false_negative diff --git a/doc/whatsnew/fragments/1144.false_negative b/doc/whatsnew/fragments/1144.false_negative new file mode 100644 index 000000000..9b30c197b --- /dev/null +++ b/doc/whatsnew/fragments/1144.false_negative @@ -0,0 +1,3 @@ +Emit ``used-before-assignment`` when calling module-level functions before definition. + +Closes #1144 diff --git a/pylint/checkers/variables.py b/pylint/checkers/variables.py index 08797c93c..2504c0c84 100644 --- a/pylint/checkers/variables.py +++ b/pylint/checkers/variables.py @@ -218,17 +218,11 @@ def _detect_global_scope( return node.lineno < defframe.lineno # type: ignore[no-any-return] if not isinstance(node.parent, (nodes.FunctionDef, nodes.Arguments)): return False - elif any( - not isinstance(f, (nodes.ClassDef, nodes.Module)) for f in (frame, defframe) - ): - # Not interested in other frames, since they are already - # not in a global scope. - return False break_scopes = [] - for current_scope in (scope, def_scope): + for current_scope in (scope or frame, def_scope): # Look for parent scopes. If there is anything different - # than a module or a class scope, then they frames don't + # than a module or a class scope, then the frames don't # share a global scope. parent_scope = current_scope while parent_scope: @@ -239,7 +233,7 @@ def _detect_global_scope( parent_scope = parent_scope.parent.scope() else: break - if break_scopes and len(set(break_scopes)) != 1: + if len(set(break_scopes)) > 1: # Store different scopes than expected. # If the stored scopes are, in fact, the very same, then it means # that the two frames (frame and defframe) share the same scope, diff --git a/tests/functional/u/used/used_before_assignment.py b/tests/functional/u/used/used_before_assignment.py index 91212612d..9b7d668ca 100644 --- a/tests/functional/u/used/used_before_assignment.py +++ b/tests/functional/u/used/used_before_assignment.py @@ -14,6 +14,17 @@ def outer(): outer() +class ClassWithProperty: # pylint: disable=too-few-public-methods + """This test depends on earlier and later defined module-level functions.""" + prop = property(redefine_time_import) # [used-before-assignment] + prop_defined_earlier = property(outer) + + +calculate(1.01, 2) # [used-before-assignment] +def calculate(value1: int, value2: float) -> int: + return value1 + value2 + + # pylint: disable=unused-import, wrong-import-position, import-outside-toplevel, reimported, redefined-outer-name, global-statement import time def redefine_time_import(): diff --git a/tests/functional/u/used/used_before_assignment.txt b/tests/functional/u/used/used_before_assignment.txt index 0a3c9ff2f..37b25ab49 100644 --- a/tests/functional/u/used/used_before_assignment.txt +++ b/tests/functional/u/used/used_before_assignment.txt @@ -1,10 +1,12 @@ used-before-assignment:5:19:5:22::Using variable 'MSG' before assignment:HIGH used-before-assignment:7:20:7:24::Using variable 'MSG2' before assignment:HIGH used-before-assignment:10:4:10:9:outer:Using variable 'inner' before assignment:HIGH -used-before-assignment:20:10:20:14:redefine_time_import:Using variable 'time' before assignment:HIGH -used-before-assignment:34:3:34:7::Using variable 'VAR2' before assignment:CONTROL_FLOW -used-before-assignment:52:3:52:7::Using variable 'VAR4' before assignment:CONTROL_FLOW -used-before-assignment:67:3:67:7::Using variable 'VAR6' before assignment:CONTROL_FLOW -used-before-assignment:102:6:102:11::Using variable 'VAR10' before assignment:CONTROL_FLOW -used-before-assignment:133:10:133:14::Using variable 'SALE' before assignment:CONTROL_FLOW -used-before-assignment:165:10:165:18::Using variable 'ALL_DONE' before assignment:CONTROL_FLOW +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 diff --git a/tests/functional/u/used/used_before_assignment_ternary.txt b/tests/functional/u/used/used_before_assignment_ternary.txt index d991970e4..bcb21df1c 100644 --- a/tests/functional/u/used/used_before_assignment_ternary.txt +++ b/tests/functional/u/used/used_before_assignment_ternary.txt @@ -1,3 +1,3 @@ -used-before-assignment:6:14:6:15:invalid:Using variable 'a' before assignment:HIGH -used-before-assignment:8:14:8:15:invalid:Using variable 'b' before assignment:HIGH -used-before-assignment:9:10:9:11:invalid:Using variable 'c' before assignment:HIGH +used-before-assignment:6:14:6:15:invalid:Using variable 'a' before assignment:HIGH +used-before-assignment:8:14:8:15:invalid:Using variable 'b' before assignment:HIGH +used-before-assignment:9:10:9:11:invalid:Using variable 'c' before assignment:HIGH -- cgit v1.2.1