diff --git a/stdlib/_typeshed/__init__.pyi b/stdlib/_typeshed/__init__.pyi index 103af47c7524..0f975e44e04e 100644 --- a/stdlib/_typeshed/__init__.pyi +++ b/stdlib/_typeshed/__init__.pyi @@ -10,6 +10,31 @@ from os import PathLike from typing import AbstractSet, Any, Container, Generic, Iterable, Protocol, TypeVar, Union from typing_extensions import Final, Literal, final +_WU = TypeVar("_WU") + +class WeakUnion(Any, Generic[_WU]): + """ + Some functions can return different types depending on passed arguments. e.g. + + * `builtins.open(name, 'rb')` returns `io.BufferedReader`, whereas + * `builtins.open(name, 'wb')` returns `io.BufferedWriter`. + + Typeshed attempts to model such scenarios accurately via `@typing.overload`, + however with with such overloaded functions there is always the case + that the return type cannot be determined statically, e.g: + + def my_open(name: str, mode: str): + return open(name, mode) + + In such cases typeshed currently returns Any. While a Union return would be + more accurate that would require all existing code that depends on the Any + (and asserts the type safety in some other way) to be updated. WeakUnion lets + typeshed annotate the semantic return type of such overloads in a backwards + compatible manner. Type checkers that do not know about this typeshed-specific + type will just treat it as Any, whereas tooling that knows about it can use + the additional information to provide more type safety or better autocompletion. + """ + _KT = TypeVar("_KT") _KT_co = TypeVar("_KT_co", covariant=True) _KT_contra = TypeVar("_KT_contra", contravariant=True) diff --git a/stdlib/builtins.pyi b/stdlib/builtins.pyi index 87623557d7bc..ad8b82703f54 100644 --- a/stdlib/builtins.pyi +++ b/stdlib/builtins.pyi @@ -21,12 +21,12 @@ from _typeshed import ( SupportsRichComparisonT, SupportsTrunc, SupportsWrite, + WeakUnion, ) from collections.abc import Callable from io import BufferedRandom, BufferedReader, BufferedWriter, FileIO, TextIOWrapper from types import CodeType, TracebackType, _Cell from typing import ( - IO, AbstractSet, Any, BinaryIO, @@ -49,6 +49,7 @@ from typing import ( SupportsFloat, SupportsInt, SupportsRound, + TextIO, TypeVar, Union, overload, @@ -1327,7 +1328,7 @@ def open( newline: str | None = ..., closefd: bool = ..., opener: _Opener | None = ..., -) -> IO[Any]: ... +) -> WeakUnion[TextIO | BinaryIO]: ... def ord(__c: str | bytes) -> int: ... class _SupportsWriteAndFlush(SupportsWrite[_T_contra], Protocol[_T_contra]): diff --git a/stdlib/subprocess.pyi b/stdlib/subprocess.pyi index f95583129cdd..913302aeb457 100644 --- a/stdlib/subprocess.pyi +++ b/stdlib/subprocess.pyi @@ -1,5 +1,5 @@ import sys -from _typeshed import Self, StrOrBytesPath +from _typeshed import Self, StrOrBytesPath, WeakUnion from types import TracebackType from typing import IO, Any, AnyStr, Callable, Generic, Iterable, Mapping, Sequence, TypeVar, Union, overload from typing_extensions import Literal @@ -633,7 +633,7 @@ if sys.version_info >= (3, 7): encoding: str | None = ..., errors: str | None = ..., text: bool | None = ..., - ) -> Any: ... # morally: -> _TXT + ) -> WeakUnion[str | bytes]: ... else: @overload @@ -755,7 +755,7 @@ else: input: _TXT | None = ..., encoding: str | None = ..., errors: str | None = ..., - ) -> Any: ... # morally: -> _TXT + ) -> WeakUnion[str | bytes]: ... PIPE: int STDOUT: int