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):