summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorClaudiu Popa <pcmanticore@gmail.com>2019-05-20 09:36:51 +0200
committerClaudiu Popa <pcmanticore@gmail.com>2019-05-20 09:36:51 +0200
commit07d5e37b9d2bded9eb98e47dacd1feff6af0188a (patch)
tree7eb5bd616a30cc200bef807658956126231c027a
parent50a670b1eee2a28c771daceeb169a0db4805ddae (diff)
downloadpylint-git-07d5e37b9d2bded9eb98e47dacd1feff6af0188a.tar.gz
Support fully qualified typing imports for type annotations.
Close #2915
-rw-r--r--ChangeLog4
-rw-r--r--pylint/checkers/variables.py97
-rw-r--r--pylint/test/functional/unused_typing_imports.py19
-rw-r--r--pylint/test/functional/unused_typing_imports.rc1
4 files changed, 108 insertions, 13 deletions
diff --git a/ChangeLog b/ChangeLog
index 05c68c6a4..c5ad2ecfe 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -7,6 +7,10 @@ What's New in Pylint 2.4.0?
Release date: TBA
+* Support fully qualified typing imports for type annotations.
+
+ Close #2915
+
* Exclude ``__dict__`` from ``attribute-defined-outside-init``
* Fix pointer on spelling check when the error are more than one time in the same line.
diff --git a/pylint/checkers/variables.py b/pylint/checkers/variables.py
index 345aa76de..26a7966ef 100644
--- a/pylint/checkers/variables.py
+++ b/pylint/checkers/variables.py
@@ -62,6 +62,63 @@ PY3K = sys.version_info >= (3, 0)
METACLASS_NAME_TRANSFORMS = {"_py_abc": "abc"}
TYPING_TYPE_CHECKS_GUARDS = frozenset({"typing.TYPE_CHECKING", "TYPE_CHECKING"})
BUILTIN_RANGE = "builtins.range"
+TYPING_MODULE = "typing"
+TYPING_NAMES = frozenset(
+ {
+ "Any",
+ "Callable",
+ "ClassVar",
+ "Generic",
+ "Optional",
+ "Tuple",
+ "Type",
+ "TypeVar",
+ "Union",
+ "AbstractSet",
+ "ByteString",
+ "Container",
+ "ContextManager",
+ "Hashable",
+ "ItemsView",
+ "Iterable",
+ "Iterator",
+ "KeysView",
+ "Mapping",
+ "MappingView",
+ "MutableMapping",
+ "MutableSequence",
+ "MutableSet",
+ "Sequence",
+ "Sized",
+ "ValuesView",
+ "Awaitable",
+ "AsyncIterator",
+ "AsyncIterable",
+ "Coroutine",
+ "Collection",
+ "AsyncGenerator",
+ "AsyncContextManager",
+ "Reversible",
+ "SupportsAbs",
+ "SupportsBytes",
+ "SupportsComplex",
+ "SupportsFloat",
+ "SupportsInt",
+ "SupportsRound",
+ "Counter",
+ "Deque",
+ "Dict",
+ "DefaultDict",
+ "List",
+ "Set",
+ "FrozenSet",
+ "NamedTuple",
+ "Generator",
+ "AnyStr",
+ "Text",
+ "Pattern",
+ }
+)
def _is_from_future_import(stmt, name):
@@ -771,6 +828,11 @@ class VariablesChecker(BaseChecker):
# Filter special objects (__doc__, __all__) etc.,
# because they can be imported for exporting.
continue
+
+ if imported_name in self._type_annotation_names:
+ # Most likely a typing import if it wasn't used so far.
+ continue
+
if as_name == "_":
continue
if as_name is None:
@@ -1647,18 +1709,31 @@ class VariablesChecker(BaseChecker):
return
def _store_type_annotation_node(self, type_annotation):
-
- if isinstance(type_annotation, astroid.Name):
+ """Given a type annotation, store all the name nodes it refers to"""
+ if (
+ isinstance(type_annotation, astroid.Name)
+ and type_annotation.name in TYPING_NAMES
+ ):
self._type_annotation_names.append(type_annotation.name)
- else:
- self._type_annotation_names.extend(
- list(
- (
- annotation.name
- for annotation in type_annotation.nodes_of_class(astroid.Name)
- )
- )
- )
+ return
+
+ if not isinstance(type_annotation, astroid.Subscript):
+ return
+
+ # Check if it is namespaced by typing or not.
+ if (
+ isinstance(type_annotation.value, astroid.Attribute)
+ and isinstance(type_annotation.value.expr, astroid.Name)
+ and type_annotation.value.expr.name == TYPING_MODULE
+ ):
+ self._type_annotation_names.append(TYPING_MODULE)
+ return
+
+ self._type_annotation_names.extend(
+ annotation.name
+ for annotation in type_annotation.nodes_of_class(astroid.Name)
+ if annotation.name in TYPING_NAMES
+ )
def _store_type_annotation_names(self, node):
type_annotation = node.type_annotation
diff --git a/pylint/test/functional/unused_typing_imports.py b/pylint/test/functional/unused_typing_imports.py
index e2fd4ae8a..6edca7153 100644
--- a/pylint/test/functional/unused_typing_imports.py
+++ b/pylint/test/functional/unused_typing_imports.py
@@ -6,7 +6,18 @@ which means we were never processing them.
"""
import re
-from typing import Optional, Callable, Iterable, Any, List, Tuple, Set, NamedTuple, Pattern
+import typing
+from typing import (
+ Any,
+ Callable,
+ Iterable,
+ List,
+ NamedTuple,
+ Optional,
+ Pattern,
+ Set,
+ Tuple,
+)
def func1(arg: Optional[Callable]=None):
@@ -37,3 +48,9 @@ with ContextManager() as SOME_DICT: # type: Set[int]
def func_test_type_comment(param):
# type: (NamedTuple) -> Tuple[NamedTuple, Pattern]
return param, re.compile('good')
+
+
+def typing_fully_qualified():
+ variable = None # type: typing.Optional[str]
+ other_variable: 'typing.Optional[str]' = None
+ return variable, other_variable
diff --git a/pylint/test/functional/unused_typing_imports.rc b/pylint/test/functional/unused_typing_imports.rc
index dca49456e..0ba2b6333 100644
--- a/pylint/test/functional/unused_typing_imports.rc
+++ b/pylint/test/functional/unused_typing_imports.rc
@@ -1,3 +1,2 @@
[testoptions]
min_pyver=3.6
-max_pyver=3.7 \ No newline at end of file