From 3159b1744caee14068b4d875bb5adaa5ffbebd44 Mon Sep 17 00:00:00 2001 From: Claudiu Popa Date: Wed, 25 Sep 2019 09:40:18 +0200 Subject: [PATCH] Exempt type checking definitions defined in both clauses of a type checking guard Close #3127 --- pylint/checkers/variables.py | 24 ++++++++-- tests/functional/u/undefined_variable.py | 16 ++++++- tests/functional/u/undefined_variable.txt | 54 +++++++++++------------ 3 files changed, 61 insertions(+), 33 deletions(-) diff --git a/pylint/checkers/variables.py b/pylint/checkers/variables.py index e4be053cda..dcbf4ec0c5 100644 --- a/pylint/checkers/variables.py +++ b/pylint/checkers/variables.py @@ -1338,12 +1338,28 @@ def _is_variable_violation( # Single statement function, with the statement on the # same line as the function definition maybee0601 = False + + # Look for type checking definitions inside a type checking guard. if isinstance(defstmt, (astroid.Import, astroid.ImportFrom)): defstmt_parent = defstmt.parent - if isinstance( - defstmt_parent, astroid.If - ) and not defstmt_parent.parent_of(node): - if defstmt_parent.test.as_string() in TYPING_TYPE_CHECKS_GUARDS: + + if ( + isinstance(defstmt_parent, astroid.If) + and defstmt_parent.test.as_string() in TYPING_TYPE_CHECKS_GUARDS + ): + # Exempt those definitions that are used inside the type checking + # guard or that are defined in both type checking guard branches. + used_in_branch = defstmt_parent.parent_of(node) + defined_in_or_else = False + + for definition in defstmt_parent.orelse: + if isinstance(definition, astroid.Assign): + defined_in_or_else = any( + target.name == name for target in definition.targets + ) + break + + if not used_in_branch and not defined_in_or_else: maybee0601 = True return maybee0601, annotation_return, use_outer_definition diff --git a/tests/functional/u/undefined_variable.py b/tests/functional/u/undefined_variable.py index 71df929601..909898f16d 100644 --- a/tests/functional/u/undefined_variable.py +++ b/tests/functional/u/undefined_variable.py @@ -1,6 +1,10 @@ # pylint: disable=missing-docstring, multiple-statements, useless-object-inheritance,import-outside-toplevel # pylint: disable=too-few-public-methods, no-init, no-self-use,bare-except,broad-except, import-error from __future__ import print_function + +# pylint: disable=wrong-import-position +from typing import TYPE_CHECKING + DEFINED = 1 if DEFINED != 1: @@ -247,8 +251,6 @@ def onclick(event): plt.show(block=True) -# pylint: disable=wrong-import-position -from typing import TYPE_CHECKING if TYPE_CHECKING: from datetime import datetime @@ -264,3 +266,13 @@ def func_should_fail(_dt: datetime): # [used-before-assignment] from typing_extensions import Literal AllowedValues = Literal['hello', 'world'] + + +if TYPE_CHECKING: + from collections import Counter +else: + Counter = object + + +def tick(counter: Counter, name: str) -> None: + counter[name] += 1 diff --git a/tests/functional/u/undefined_variable.txt b/tests/functional/u/undefined_variable.txt index 5f9ca9747c..4a0e83aecf 100644 --- a/tests/functional/u/undefined_variable.txt +++ b/tests/functional/u/undefined_variable.txt @@ -1,27 +1,27 @@ -undefined-variable:7::Undefined variable 'unknown' -undefined-variable:13:in_method:Undefined variable 'nomoreknown' -undefined-variable:16::Undefined variable '__revision__' -undefined-variable:18::Undefined variable '__revision__' -undefined-variable:22:bad_default:Undefined variable 'unknown2' -undefined-variable:25:bad_default:Undefined variable 'xxxx' -undefined-variable:26:bad_default:Undefined variable 'augvar' -undefined-variable:27:bad_default:Undefined variable 'vardel' -undefined-variable:29::Undefined variable 'doesnotexist' -undefined-variable:30::Undefined variable 'z' -used-before-assignment:38::Using variable 'POUETT' before assignment -used-before-assignment:51::Using variable 'PLOUF' before assignment -used-before-assignment:60:if_branch_test:Using variable 'xxx' before assignment -used-before-assignment:86:test_arguments:Using variable 'TestClass' before assignment -used-before-assignment:90:TestClass:Using variable 'Ancestor' before assignment -used-before-assignment:93:TestClass.MissingAncestor:Using variable 'Ancestor1' before assignment -used-before-assignment:100:TestClass.test1.UsingBeforeDefinition:Using variable 'Empty' before assignment -undefined-variable:114:Self:Undefined variable 'Self' -undefined-variable:130::Undefined variable 'BAT' -used-before-assignment:141:KeywordArgument.test1:Using variable 'enabled' before assignment -undefined-variable:144:KeywordArgument.test2:Undefined variable 'disabled' -undefined-variable:149:KeywordArgument.:Undefined variable 'arg' -undefined-variable:161::Undefined variable 'unicode_2' -undefined-variable:171::Undefined variable 'unicode_4' -undefined-variable:226:LambdaClass4.:Undefined variable 'LambdaClass4' -undefined-variable:234:LambdaClass5.:Undefined variable 'LambdaClass5' -used-before-assignment:257:func_should_fail:Using variable 'datetime' before assignment +undefined-variable:11::Undefined variable 'unknown' +undefined-variable:17:in_method:Undefined variable 'nomoreknown' +undefined-variable:20::Undefined variable '__revision__' +undefined-variable:22::Undefined variable '__revision__' +undefined-variable:26:bad_default:Undefined variable 'unknown2' +undefined-variable:29:bad_default:Undefined variable 'xxxx' +undefined-variable:30:bad_default:Undefined variable 'augvar' +undefined-variable:31:bad_default:Undefined variable 'vardel' +undefined-variable:33::Undefined variable 'doesnotexist' +undefined-variable:34::Undefined variable 'z' +used-before-assignment:42::Using variable 'POUETT' before assignment +used-before-assignment:55::Using variable 'PLOUF' before assignment +used-before-assignment:64:if_branch_test:Using variable 'xxx' before assignment +used-before-assignment:90:test_arguments:Using variable 'TestClass' before assignment +used-before-assignment:94:TestClass:Using variable 'Ancestor' before assignment +used-before-assignment:97:TestClass.MissingAncestor:Using variable 'Ancestor1' before assignment +used-before-assignment:104:TestClass.test1.UsingBeforeDefinition:Using variable 'Empty' before assignment +undefined-variable:118:Self:Undefined variable 'Self' +undefined-variable:134::Undefined variable 'BAT' +used-before-assignment:145:KeywordArgument.test1:Using variable 'enabled' before assignment +undefined-variable:148:KeywordArgument.test2:Undefined variable 'disabled' +undefined-variable:153:KeywordArgument.:Undefined variable 'arg' +undefined-variable:165::Undefined variable 'unicode_2' +undefined-variable:175::Undefined variable 'unicode_4' +undefined-variable:230:LambdaClass4.:Undefined variable 'LambdaClass4' +undefined-variable:238:LambdaClass5.:Undefined variable 'LambdaClass5' +used-before-assignment:259:func_should_fail:Using variable 'datetime' before assignment