From 3d6389b97e8f9416304185a1babfe49e8a0dc348 Mon Sep 17 00:00:00 2001 From: Sergei Lebedev <185856+superbobry@users.noreply.github.com> Date: Sun, 27 Jun 2021 19:11:54 +0100 Subject: [PATCH] VariableChecker now accounts for attribute lookups in type comments (#4604) * VariableChecker now accounts for attribute lookups in type comments Prior to this commit VariableChecker did not recurse into attribute lookups in type comments. This lead to false positive unused-import messages in e.g. import collections d = ... # type: collections.OrderedDict Fixes #4603 --- CONTRIBUTORS.txt | 2 ++ ChangeLog | 5 +++++ pylint/checkers/variables.py | 4 ++++ pylint/constants.py | 2 ++ tests/checkers/unittest_variables.py | 20 ++++++++++++++++++++ 5 files changed, 33 insertions(+) diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt index b1de7ff30e..05e4a31e18 100644 --- a/CONTRIBUTORS.txt +++ b/CONTRIBUTORS.txt @@ -503,3 +503,5 @@ contributors: * Markus Siebenhaar: contributor * Lorena Buciu (lorena-b): contributor + +* Sergei Lebedev (superbobry): contributor diff --git a/ChangeLog b/ChangeLog index a0f12a7a88..7172972b82 100644 --- a/ChangeLog +++ b/ChangeLog @@ -186,6 +186,11 @@ Closes #4555 Closes #4023 +* Fix ``unused-import`` false positive for imported modules referenced in + attribute lookups in type comments. + + Closes #4603 + What's New in Pylint 2.8.3? =========================== diff --git a/pylint/checkers/variables.py b/pylint/checkers/variables.py index 3ddafd5994..8a46375097 100644 --- a/pylint/checkers/variables.py +++ b/pylint/checkers/variables.py @@ -1826,6 +1826,10 @@ def _store_type_annotation_node(self, type_annotation): self._type_annotation_names.append(type_annotation.name) return + if isinstance(type_annotation, astroid.Attribute): + self._store_type_annotation_node(type_annotation.expr) + return + if not isinstance(type_annotation, astroid.Subscript): return diff --git a/pylint/constants.py b/pylint/constants.py index b95d701c04..786cde7d02 100644 --- a/pylint/constants.py +++ b/pylint/constants.py @@ -1,6 +1,7 @@ # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/master/LICENSE +import platform import sys import astroid @@ -11,6 +12,7 @@ PY39_PLUS = sys.version_info[:2] >= (3, 9) PY310_PLUS = sys.version_info[:2] >= (3, 10) +IS_PYPY = platform.python_implementation() == "PyPy" PY_EXTS = (".py", ".pyc", ".pyo", ".pyw", ".so", ".dll") diff --git a/tests/checkers/unittest_variables.py b/tests/checkers/unittest_variables.py index 90daa8d5d1..2a590708d9 100644 --- a/tests/checkers/unittest_variables.py +++ b/tests/checkers/unittest_variables.py @@ -21,11 +21,13 @@ import os import re import sys +import unittest from pathlib import Path import astroid from pylint.checkers import variables +from pylint.constants import IS_PYPY from pylint.interfaces import UNDEFINED from pylint.testutils import CheckerTestCase, Message, linter, set_config @@ -191,6 +193,24 @@ def my_method(self) -> MyType: with self.assertNoMessages(): self.walk(module) + @unittest.skipIf(IS_PYPY, "PyPy does not parse type comments") + def test_attribute_in_type_comment(self): + """Ensure attribute lookups in type comments are accounted for. + + https://github.com/PyCQA/pylint/issues/4603 + """ + module = astroid.parse( + """ + import foo + from foo import Bar, Boo + a = ... # type: foo.Bar + b = ... # type: foo.Bar[Boo] + c = ... # type: Bar.Boo + """ + ) + with self.assertNoMessages(): + self.walk(module) + class TestVariablesCheckerWithTearDown(CheckerTestCase):