From ae725c3f1021536bb8fd5bd90658745afc952ec8 Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Fri, 17 Dec 2021 22:50:25 +0000 Subject: [PATCH] Add test to ensure certain names are not imported from `typing_extensions` (#6619) --- CONTRIBUTING.md | 7 ++++-- stdlib/importlib/abc.pyi | 4 ++-- tests/check_new_syntax.py | 49 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 56 insertions(+), 4 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e6fe83a44..bd254a9a6 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -244,10 +244,13 @@ instead in typeshed stubs. This currently affects: - `Literal` (new in Python 3.8) - `SupportsIndex` (new in Python 3.8) - `TypedDict` (new in Python 3.8) +- `Concatenate` (new in Python 3.10) +- `ParamSpec` (new in Python 3.10) - `TypeGuard` (new in Python 3.10) -An exception is `Protocol`: although it was added in Python 3.8, it -can be used in stubs regardless of Python version. +Two exceptions are `Protocol` and `runtime_checkable`: although +these were added in Python 3.8, they can be used in stubs regardless +of Python version. ### What to include diff --git a/stdlib/importlib/abc.pyi b/stdlib/importlib/abc.pyi index 47a00643e..9bb370dfd 100644 --- a/stdlib/importlib/abc.pyi +++ b/stdlib/importlib/abc.pyi @@ -12,8 +12,8 @@ from _typeshed import ( from abc import ABCMeta, abstractmethod from importlib.machinery import ModuleSpec from io import BufferedRandom, BufferedReader, BufferedWriter, FileIO, TextIOWrapper -from typing import IO, Any, BinaryIO, Iterator, Mapping, NoReturn, Protocol, Sequence, Union, overload -from typing_extensions import Literal, runtime_checkable +from typing import IO, Any, BinaryIO, Iterator, Mapping, NoReturn, Protocol, Sequence, Union, overload, runtime_checkable +from typing_extensions import Literal _Path = Union[bytes, str] diff --git a/tests/check_new_syntax.py b/tests/check_new_syntax.py index 09efdac84..906160fe7 100755 --- a/tests/check_new_syntax.py +++ b/tests/check_new_syntax.py @@ -12,6 +12,23 @@ STUBS_SUPPORTING_PYTHON_2 = frozenset( CONTEXT_MANAGER_ALIASES = {"ContextManager": "AbstractContextManager", "AsyncContextManager": "AbstractAsyncContextManager"} CONTEXTLIB_ALIAS_ALLOWLIST = frozenset({Path("stdlib/contextlib.pyi"), Path("stdlib/typing_extensions.pyi")}) +IMPORTED_FROM_TYPING_NOT_TYPING_EXTENSIONS = frozenset( + {"ClassVar", "Type", "NewType", "overload", "Text", "Protocol", "runtime_checkable", "NoReturn"} +) + +IMPORTED_FROM_COLLECTIONS_ABC_NOT_TYPING_EXTENSIONS = frozenset( + {"Awaitable", "Coroutine", "AsyncIterable", "AsyncIterator", "AsyncGenerator"} +) + +# The values in the mapping are what these are called in `collections` +IMPORTED_FROM_COLLECTIONS_NOT_TYPING_EXTENSIONS = { + "Counter": "Counter", + "Deque": "deque", + "DefaultDict": "defaultdict", + "OrderedDict": "OrderedDict", + "ChainMap": "ChainMap", +} + def check_new_syntax(tree: ast.AST, path: Path) -> list[str]: errors = [] @@ -85,6 +102,38 @@ def check_new_syntax(tree: ast.AST, path: Path) -> list[str]: if any(cls.name == "Set" for cls in imported_classes): self.set_from_collections_abc = True + elif node.module == "typing_extensions": + for imported_object in node.names: + imported_object_name = imported_object.name + if imported_object_name in IMPORTED_FROM_TYPING_NOT_TYPING_EXTENSIONS: + errors.append( + f"{path}:{node.lineno}: " + f"Use `typing.{imported_object_name}` instead of `typing_extensions.{imported_object_name}`" + ) + elif imported_object_name in IMPORTED_FROM_COLLECTIONS_ABC_NOT_TYPING_EXTENSIONS: + errors.append( + f"{path}:{node.lineno}: " + f"Use `collections.abc.{imported_object_name}` or `typing.{imported_object_name}` " + f"instead of `typing_extensions.{imported_object_name}`" + ) + elif imported_object_name in IMPORTED_FROM_COLLECTIONS_NOT_TYPING_EXTENSIONS: + errors.append( + f"{path}:{node.lineno}: " + f"Use `collections.{IMPORTED_FROM_COLLECTIONS_NOT_TYPING_EXTENSIONS[imported_object_name]}` " + f"or `typing.{imported_object_name}` instead of `typing_extensions.{imported_object_name}`" + ) + elif imported_object_name in CONTEXT_MANAGER_ALIASES: + if python_2_support_required: + errors.append( + f"{path}:{node.lineno}: " + f"Use `typing.{imported_object_name}` instead of `typing_extensions.{imported_object_name}`" + ) + else: + errors.append( + f"{path}:{node.lineno}: Use `contextlib.{CONTEXT_MANAGER_ALIASES[imported_object_name]}` " + f"instead of `typing_extensions.{imported_object_name}`" + ) + elif not python_2_support_required and path not in CONTEXTLIB_ALIAS_ALLOWLIST and node.module == "typing": for imported_class in node.names: imported_class_name = imported_class.name