Skip to content

Commit

Permalink
Fix #5399: Fix false negative for undefined-variable when a type an…
Browse files Browse the repository at this point in the history
…notation is accessed multiple times
  • Loading branch information
jacobtylerwalls committed Jan 26, 2022
1 parent 44ad84a commit ba0a8ac
Show file tree
Hide file tree
Showing 9 changed files with 43 additions and 21 deletions.
6 changes: 6 additions & 0 deletions ChangeLog
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,12 @@ Release date: TBA

Closes #5568

* Fix false negative for ``undefined-variable`` and related variable messages
when the same undefined variable is used as a type annotation and is
accessed multiple times, or is used as a default argument to a function.

Closes #5399

* Pyreverse - add output in mermaidjs format

* Emit ``used-before-assignment`` instead of ``undefined-variable`` when attempting
Expand Down
6 changes: 6 additions & 0 deletions doc/whatsnew/2.13.rst
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,12 @@ Other Changes
Closes #4798
Closes #5081

* Fix false negative for ``undefined-variable`` and related variable messages
when the same undefined variable is used as a type annotation and is
accessed multiple times, or is used as a default argument to a function.

Closes #5399

* Emit ``used-before-assignment`` instead of ``undefined-variable`` when attempting
to access unused type annotations.

Expand Down
15 changes: 5 additions & 10 deletions pylint/checkers/variables.py
Original file line number Diff line number Diff line change
Expand Up @@ -1528,10 +1528,7 @@ def _check_consumer(
)
and node.name in node.root().locals
):
self.add_message(
"undefined-variable", args=node.name, node=node
)
return (VariableVisitConsumerAction.CONSUME, found_nodes)
return (VariableVisitConsumerAction.CONTINUE, None)

elif base_scope_type != "lambda":
# E0601 may *not* occurs in lambda scope.
Expand Down Expand Up @@ -1585,9 +1582,7 @@ def _check_consumer(
elif isinstance(defstmt, nodes.ClassDef):
is_first_level_ref = self._is_first_level_self_reference(node, defstmt)
if is_first_level_ref == 2:
self.add_message(
"used-before-assignment", node=node, args=node.name, confidence=HIGH
)
return (VariableVisitConsumerAction.CONTINUE, None)
if is_first_level_ref:
return (VariableVisitConsumerAction.RETURN, None)

Expand Down Expand Up @@ -1988,15 +1983,15 @@ def _is_first_level_self_reference(
refers to its own class.
Return values correspond to:
0 = Continue
0 = Continue and consume names
1 = Break
2 = Break + emit message
2 = Continue without consuming names
"""
if node.frame(future=True).parent == defstmt and node.statement(
future=True
) == node.frame(future=True):
# Check if used as type annotation
# Break but don't emit message if postponed evaluation is enabled
# Break if postponed evaluation is enabled
if utils.is_node_in_type_annotation_context(node):
if not utils.is_postponed_evaluation_enabled(node):
return 2
Expand Down
15 changes: 12 additions & 3 deletions tests/functional/u/undefined/undefined_variable.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ def bad_default(var, default=unknown2): # [undefined-variable]
"""function with default arg's value set to an nonexistent name"""
print(var, default)
print(xxxx) # [undefined-variable]
augvar += 1 # [undefined-variable]
del vardel # [undefined-variable]
augvar += 1 # [undefined-variable, unused-variable]
del vardel # [undefined-variable, unused-variable]

LMBD = lambda x, y=doesnotexist: x+y # [undefined-variable]
LMBD2 = lambda x, y: x+z # [undefined-variable]
Expand Down Expand Up @@ -133,7 +133,7 @@ class Ancestor1(object):
""" No op """

NANA = BAT # [undefined-variable]
del BAT
del BAT # [undefined-variable]


class KeywordArgument(object):
Expand Down Expand Up @@ -356,3 +356,12 @@ def global_var_mixed_assignment():

GLOBAL_VAR: int
GLOBAL_VAR_TWO: int


class RepeatedReturnAnnotations:
def x(self, o: RepeatedReturnAnnotations) -> bool: # [undefined-variable]
pass
def y(self) -> RepeatedReturnAnnotations: # [undefined-variable]
pass
def z(self) -> RepeatedReturnAnnotations: # [undefined-variable]
pass
6 changes: 6 additions & 0 deletions tests/functional/u/undefined/undefined_variable.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ undefined-variable:23:8:23:20::Undefined variable '__revision__':UNDEFINED
undefined-variable:27:29:27:37:bad_default:Undefined variable 'unknown2':UNDEFINED
undefined-variable:30:10:30:14:bad_default:Undefined variable 'xxxx':UNDEFINED
undefined-variable:31:4:31:10:bad_default:Undefined variable 'augvar':UNDEFINED
unused-variable:31:4:31:10:bad_default:Unused variable 'augvar':UNDEFINED
undefined-variable:32:8:32:14:bad_default:Undefined variable 'vardel':UNDEFINED
unused-variable:32:8:32:14:bad_default:Unused variable 'vardel':UNDEFINED
undefined-variable:34:19:34:31:<lambda>:Undefined variable 'doesnotexist':UNDEFINED
undefined-variable:35:23:35:24:<lambda>:Undefined variable 'z':UNDEFINED
used-before-assignment:38:4:38:9::Using variable 'POUET' before assignment:CONTROL_FLOW
Expand All @@ -19,6 +21,7 @@ used-before-assignment:98:26:98:35:TestClass.MissingAncestor:Using variable 'Anc
used-before-assignment:105:36:105:41:TestClass.test1.UsingBeforeDefinition:Using variable 'Empty' before assignment:HIGH
undefined-variable:119:10:119:14:Self:Undefined variable 'Self':UNDEFINED
undefined-variable:135:7:135:10::Undefined variable 'BAT':UNDEFINED
undefined-variable:136:4:136:7::Undefined variable 'BAT':UNDEFINED
used-before-assignment:146:31:146:38:KeywordArgument.test1:Using variable 'enabled' before assignment:HIGH
undefined-variable:149:32:149:40:KeywordArgument.test2:Undefined variable 'disabled':UNDEFINED
undefined-variable:154:22:154:25:KeywordArgument.<lambda>:Undefined variable 'arg':UNDEFINED
Expand All @@ -33,3 +36,6 @@ used-before-assignment:294:7:294:8:undefined_annotation:Using variable 'x' befor
undefined-variable:324:11:324:12:decorated3:Undefined variable 'x':UNDEFINED
undefined-variable:329:19:329:20:decorated4:Undefined variable 'y':UNDEFINED
undefined-variable:350:10:350:20:global_var_mixed_assignment:Undefined variable 'GLOBAL_VAR':HIGH
undefined-variable:362:19:362:44:RepeatedReturnAnnotations.x:Undefined variable 'RepeatedReturnAnnotations':UNDEFINED
undefined-variable:364:19:364:44:RepeatedReturnAnnotations.y:Undefined variable 'RepeatedReturnAnnotations':UNDEFINED
undefined-variable:366:19:366:44:RepeatedReturnAnnotations.z:Undefined variable 'RepeatedReturnAnnotations':UNDEFINED
2 changes: 1 addition & 1 deletion tests/functional/u/use/used_before_assignment_py37.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ def second_correct_typing_method(self, other: List[MyClass]) -> bool:
return self == other[0]

def incorrect_default_method(
self, other=MyClass() # [used-before-assignment]
self, other=MyClass() # [undefined-variable]
) -> bool:
return self == other

Expand Down
2 changes: 1 addition & 1 deletion tests/functional/u/use/used_before_assignment_py37.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
used-before-assignment:17:20:17:27:MyClass.incorrect_default_method:Using variable 'MyClass' before assignment:HIGH
undefined-variable:17:20:17:27:MyClass.incorrect_default_method:Undefined variable 'MyClass':UNDEFINED
6 changes: 3 additions & 3 deletions tests/functional/u/use/used_before_assignment_typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,17 @@ class MyClass:
"""Type annotation or default values for first level methods can't refer to their own class"""

def incorrect_typing_method(
self, other: MyClass # [used-before-assignment]
self, other: MyClass # [undefined-variable]
) -> bool:
return self == other

def incorrect_nested_typing_method(
self, other: List[MyClass] # [used-before-assignment]
self, other: List[MyClass] # [undefined-variable]
) -> bool:
return self == other[0]

def incorrect_default_method(
self, other=MyClass() # [used-before-assignment]
self, other=MyClass() # [undefined-variable]
) -> bool:
return self == other

Expand Down
6 changes: 3 additions & 3 deletions tests/functional/u/use/used_before_assignment_typing.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
used-before-assignment:12:21:12:28:MyClass.incorrect_typing_method:Using variable 'MyClass' before assignment:HIGH
used-before-assignment:17:26:17:33:MyClass.incorrect_nested_typing_method:Using variable 'MyClass' before assignment:HIGH
used-before-assignment:22:20:22:27:MyClass.incorrect_default_method:Using variable 'MyClass' before assignment:HIGH
undefined-variable:12:21:12:28:MyClass.incorrect_typing_method:Undefined variable 'MyClass':UNDEFINED
undefined-variable:17:26:17:33:MyClass.incorrect_nested_typing_method:Undefined variable 'MyClass':UNDEFINED
undefined-variable:22:20:22:27:MyClass.incorrect_default_method:Undefined variable 'MyClass':UNDEFINED

0 comments on commit ba0a8ac

Please sign in to comment.