123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282 |
- from typing import Dict, List, NamedTuple, Set, Union
- import astroid.bases
- from astroid import nodes
- from pylint.checkers import BaseChecker
- from pylint.checkers.utils import (
- check_messages,
- is_node_in_type_annotation_context,
- safe_infer,
- )
- from pylint.interfaces import IAstroidChecker
- from pylint.lint import PyLinter
- from pylint.utils.utils import get_global_option
- class TypingAlias(NamedTuple):
- name: str
- name_collision: bool
- DEPRECATED_TYPING_ALIASES: Dict[str, TypingAlias] = {
- "typing.Tuple": TypingAlias("tuple", False),
- "typing.List": TypingAlias("list", False),
- "typing.Dict": TypingAlias("dict", False),
- "typing.Set": TypingAlias("set", False),
- "typing.FrozenSet": TypingAlias("frozenset", False),
- "typing.Type": TypingAlias("type", False),
- "typing.Deque": TypingAlias("collections.deque", True),
- "typing.DefaultDict": TypingAlias("collections.defaultdict", True),
- "typing.OrderedDict": TypingAlias("collections.OrderedDict", True),
- "typing.Counter": TypingAlias("collections.Counter", True),
- "typing.ChainMap": TypingAlias("collections.ChainMap", True),
- "typing.Awaitable": TypingAlias("collections.abc.Awaitable", True),
- "typing.Coroutine": TypingAlias("collections.abc.Coroutine", True),
- "typing.AsyncIterable": TypingAlias("collections.abc.AsyncIterable", True),
- "typing.AsyncIterator": TypingAlias("collections.abc.AsyncIterator", True),
- "typing.AsyncGenerator": TypingAlias("collections.abc.AsyncGenerator", True),
- "typing.Iterable": TypingAlias("collections.abc.Iterable", True),
- "typing.Iterator": TypingAlias("collections.abc.Iterator", True),
- "typing.Generator": TypingAlias("collections.abc.Generator", True),
- "typing.Reversible": TypingAlias("collections.abc.Reversible", True),
- "typing.Container": TypingAlias("collections.abc.Container", True),
- "typing.Collection": TypingAlias("collections.abc.Collection", True),
- "typing.Callable": TypingAlias("collections.abc.Callable", True),
- "typing.AbstractSet": TypingAlias("collections.abc.Set", False),
- "typing.MutableSet": TypingAlias("collections.abc.MutableSet", True),
- "typing.Mapping": TypingAlias("collections.abc.Mapping", True),
- "typing.MutableMapping": TypingAlias("collections.abc.MutableMapping", True),
- "typing.Sequence": TypingAlias("collections.abc.Sequence", True),
- "typing.MutableSequence": TypingAlias("collections.abc.MutableSequence", True),
- "typing.ByteString": TypingAlias("collections.abc.ByteString", True),
- "typing.MappingView": TypingAlias("collections.abc.MappingView", True),
- "typing.KeysView": TypingAlias("collections.abc.KeysView", True),
- "typing.ItemsView": TypingAlias("collections.abc.ItemsView", True),
- "typing.ValuesView": TypingAlias("collections.abc.ValuesView", True),
- "typing.ContextManager": TypingAlias("contextlib.AbstractContextManager", False),
- "typing.AsyncContextManager": TypingAlias(
- "contextlib.AbstractAsyncContextManager", False
- ),
- "typing.Pattern": TypingAlias("re.Pattern", True),
- "typing.Match": TypingAlias("re.Match", True),
- "typing.Hashable": TypingAlias("collections.abc.Hashable", True),
- "typing.Sized": TypingAlias("collections.abc.Sized", True),
- }
- ALIAS_NAMES = frozenset(key.split(".")[1] for key in DEPRECATED_TYPING_ALIASES)
- UNION_NAMES = ("Optional", "Union")
- class DeprecatedTypingAliasMsg(NamedTuple):
- node: Union[nodes.Name, nodes.Attribute]
- qname: str
- alias: str
- parent_subscript: bool
- class TypingChecker(BaseChecker):
- """Find issue specifically related to type annotations."""
- __implements__ = (IAstroidChecker,)
- name = "typing"
- priority = -1
- msgs = {
- "W6001": (
- "'%s' is deprecated, use '%s' instead",
- "deprecated-typing-alias",
- "Emitted when a deprecated typing alias is used.",
- ),
- "R6002": (
- "'%s' will be deprecated with PY39, consider using '%s' instead%s",
- "consider-using-alias",
- "Only emitted if 'runtime-typing=no' and a deprecated "
- "typing alias is used in a type annotation context in "
- "Python 3.7 or 3.8.",
- ),
- "R6003": (
- "Consider using alternative Union syntax instead of '%s'%s",
- "consider-alternative-union-syntax",
- "Emitted when 'typing.Union' or 'typing.Optional' is used "
- "instead of the alternative Union syntax 'int | None'.",
- ),
- }
- options = (
- (
- "runtime-typing",
- {
- "default": True,
- "type": "yn",
- "metavar": "<y or n>",
- "help": (
- "Set to ``no`` if the app / library does **NOT** need to "
- "support runtime introspection of type annotations. "
- "If you use type annotations **exclusively** for type checking "
- "of an application, you're probably fine. For libraries, "
- "evaluate if some users what to access the type hints "
- "at runtime first, e.g., through ``typing.get_type_hints``. "
- "Applies to Python versions 3.7 - 3.9"
- ),
- },
- ),
- )
- _should_check_typing_alias: bool
- """The use of type aliases (PEP 585) requires Python 3.9
- or Python 3.7+ with postponed evaluation.
- """
- _should_check_alternative_union_syntax: bool
- """The use of alternative union syntax (PEP 604) requires Python 3.10
- or Python 3.7+ with postponed evaluation.
- """
- def __init__(self, linter: PyLinter) -> None:
- """Initialize checker instance."""
- super().__init__(linter=linter)
- self._alias_name_collisions: Set[str] = set()
- self._consider_using_alias_msgs: List[DeprecatedTypingAliasMsg] = []
- def open(self) -> None:
- py_version = get_global_option(self, "py-version")
- self._py37_plus = py_version >= (3, 7)
- self._py39_plus = py_version >= (3, 9)
- self._py310_plus = py_version >= (3, 10)
- self._should_check_typing_alias = self._py39_plus or (
- self._py37_plus and self.config.runtime_typing is False
- )
- self._should_check_alternative_union_syntax = self._py310_plus or (
- self._py37_plus and self.config.runtime_typing is False
- )
- def _msg_postponed_eval_hint(self, node) -> str:
- """Message hint if postponed evaluation isn't enabled."""
- if self._py310_plus or "annotations" in node.root().future_imports:
- return ""
- return ". Add 'from __future__ import annotations' as well"
- @check_messages(
- "deprecated-typing-alias",
- "consider-using-alias",
- "consider-alternative-union-syntax",
- )
- def visit_name(self, node: nodes.Name) -> None:
- if self._should_check_typing_alias and node.name in ALIAS_NAMES:
- self._check_for_typing_alias(node)
- if self._should_check_alternative_union_syntax and node.name in UNION_NAMES:
- self._check_for_alternative_union_syntax(node, node.name)
- @check_messages(
- "deprecated-typing-alias",
- "consider-using-alias",
- "consider-alternative-union-syntax",
- )
- def visit_attribute(self, node: nodes.Attribute) -> None:
- if self._should_check_typing_alias and node.attrname in ALIAS_NAMES:
- self._check_for_typing_alias(node)
- if self._should_check_alternative_union_syntax and node.attrname in UNION_NAMES:
- self._check_for_alternative_union_syntax(node, node.attrname)
- def _check_for_alternative_union_syntax(
- self,
- node: Union[nodes.Name, nodes.Attribute],
- name: str,
- ) -> None:
- """Check if alternative union syntax could be used.
- Requires
- - Python 3.10
- - OR: Python 3.7+ with postponed evaluation in
- a type annotation context
- """
- inferred = safe_infer(node)
- if not (
- isinstance(inferred, nodes.FunctionDef)
- and inferred.qname() in {"typing.Optional", "typing.Union"}
- or isinstance(inferred, astroid.bases.Instance)
- and inferred.qname() == "typing._SpecialForm"
- ):
- return
- if not (self._py310_plus or is_node_in_type_annotation_context(node)):
- return
- self.add_message(
- "consider-alternative-union-syntax",
- node=node,
- args=(name, self._msg_postponed_eval_hint(node)),
- )
- def _check_for_typing_alias(
- self,
- node: Union[nodes.Name, nodes.Attribute],
- ) -> None:
- """Check if typing alias is deprecated or could be replaced.
- Requires
- - Python 3.9
- - OR: Python 3.7+ with postponed evaluation in
- a type annotation context
- For Python 3.7+: Only emitt message if change doesn't create
- any name collisions, only ever used in a type annotation
- context, and can safely be replaced.
- """
- inferred = safe_infer(node)
- if not isinstance(inferred, nodes.ClassDef):
- return
- alias = DEPRECATED_TYPING_ALIASES.get(inferred.qname(), None)
- if alias is None:
- return
- if self._py39_plus:
- self.add_message(
- "deprecated-typing-alias",
- node=node,
- args=(inferred.qname(), alias.name),
- )
- return
- # For PY37+, check for type annotation context first
- if not is_node_in_type_annotation_context(node) and isinstance(
- node.parent, nodes.Subscript
- ):
- if alias.name_collision is True:
- self._alias_name_collisions.add(inferred.qname())
- return
- self._consider_using_alias_msgs.append(
- DeprecatedTypingAliasMsg(
- node,
- inferred.qname(),
- alias.name,
- isinstance(node.parent, nodes.Subscript),
- )
- )
- @check_messages("consider-using-alias")
- def leave_module(self, node: nodes.Module) -> None:
- """After parsing of module is complete, add messages for
- 'consider-using-alias' check. Make sure results are safe
- to recommend / collision free.
- """
- if self._py37_plus and not self._py39_plus:
- msg_future_import = self._msg_postponed_eval_hint(node)
- for msg in self._consider_using_alias_msgs:
- if msg.qname in self._alias_name_collisions:
- continue
- self.add_message(
- "consider-using-alias",
- node=msg.node,
- args=(
- msg.qname,
- msg.alias,
- msg_future_import if msg.parent_subscript else "",
- ),
- )
- # Clear all module cache variables
- self._alias_name_collisions.clear()
- self._consider_using_alias_msgs.clear()
- def register(linter: PyLinter) -> None:
- linter.register_checker(TypingChecker(linter))
|