diff --git a/stdlib/codecs.pyi b/stdlib/codecs.pyi index 5a22853b6aee..fd33592593ce 100644 --- a/stdlib/codecs.pyi +++ b/stdlib/codecs.pyi @@ -239,7 +239,7 @@ class StreamReaderWriter(TextIO): def __next__(self) -> str: ... def __iter__(self) -> Self: ... def write(self, data: str) -> None: ... # type: ignore[override] - def writelines(self, list: Iterable[str]) -> None: ... + def writelines(self, list: Iterable[str]) -> None: ... # type: ignore[override] def reset(self) -> None: ... def seek(self, offset: int, whence: int = 0) -> None: ... # type: ignore[override] def __enter__(self) -> Self: ... @@ -272,8 +272,9 @@ class StreamRecoder(BinaryIO): def readlines(self, sizehint: int | None = None) -> list[bytes]: ... def __next__(self) -> bytes: ... def __iter__(self) -> Self: ... + # Base class accepts more types than just bytes def write(self, data: bytes) -> None: ... # type: ignore[override] - def writelines(self, list: Iterable[bytes]) -> None: ... + def writelines(self, list: Iterable[bytes]) -> None: ... # type: ignore[override] def reset(self) -> None: ... def __getattr__(self, name: str) -> Any: ... def __enter__(self) -> Self: ... diff --git a/stdlib/http/client.pyi b/stdlib/http/client.pyi index 1f16bdc2dbab..db96702f3454 100644 --- a/stdlib/http/client.pyi +++ b/stdlib/http/client.pyi @@ -101,7 +101,8 @@ class HTTPMessage(email.message.Message): def parse_headers(fp: io.BufferedIOBase, _class: Callable[[], email.message.Message] = ...) -> HTTPMessage: ... -class HTTPResponse(io.BufferedIOBase, BinaryIO): +# superclasses have incompatible write and writelines methods +class HTTPResponse(io.BufferedIOBase, BinaryIO): # type: ignore[misc] msg: HTTPMessage headers: HTTPMessage version: int diff --git a/stdlib/io.pyi b/stdlib/io.pyi index c3e07bacbe5a..29efb1107b8c 100644 --- a/stdlib/io.pyi +++ b/stdlib/io.pyi @@ -90,7 +90,7 @@ class BufferedIOBase(IOBase): def read(self, __size: int | None = ...) -> bytes: ... def read1(self, __size: int = ...) -> bytes: ... -class FileIO(RawIOBase, BinaryIO): +class FileIO(RawIOBase, BinaryIO): # type: ignore[misc] mode: str name: FileDescriptorOrPath # type: ignore[assignment] def __init__( @@ -102,7 +102,7 @@ class FileIO(RawIOBase, BinaryIO): def read(self, __size: int = -1) -> bytes: ... def __enter__(self) -> Self: ... -class BytesIO(BufferedIOBase, BinaryIO): +class BytesIO(BufferedIOBase, BinaryIO): # type: ignore[misc] def __init__(self, initial_bytes: ReadableBuffer = ...) -> None: ... # BytesIO does not contain a "name" field. This workaround is necessary # to allow BytesIO sub-classes to add this field, as it is defined @@ -113,17 +113,18 @@ class BytesIO(BufferedIOBase, BinaryIO): def getbuffer(self) -> memoryview: ... def read1(self, __size: int | None = -1) -> bytes: ... -class BufferedReader(BufferedIOBase, BinaryIO): +# superclasses have incompatible write and writelines methods +class BufferedReader(BufferedIOBase, BinaryIO): # type: ignore[misc] def __enter__(self) -> Self: ... def __init__(self, raw: RawIOBase, buffer_size: int = ...) -> None: ... def peek(self, __size: int = 0) -> bytes: ... -class BufferedWriter(BufferedIOBase, BinaryIO): +class BufferedWriter(BufferedIOBase, BinaryIO): # type: ignore[misc] def __enter__(self) -> Self: ... def __init__(self, raw: RawIOBase, buffer_size: int = ...) -> None: ... def write(self, __buffer: ReadableBuffer) -> int: ... -class BufferedRandom(BufferedReader, BufferedWriter): +class BufferedRandom(BufferedReader, BufferedWriter): # type: ignore[misc] def __enter__(self) -> Self: ... def seek(self, __target: int, __whence: int = 0) -> int: ... # stubtest needs this @@ -144,7 +145,7 @@ class TextIOBase(IOBase): def readlines(self, __hint: int = -1) -> list[str]: ... # type: ignore[override] def read(self, __size: int | None = ...) -> str: ... -class TextIOWrapper(TextIOBase, TextIO): +class TextIOWrapper(TextIOBase, TextIO): # type: ignore[misc] def __init__( self, buffer: IO[bytes], @@ -176,6 +177,7 @@ class TextIOWrapper(TextIOBase, TextIO): def __iter__(self) -> Iterator[str]: ... # type: ignore[override] def __next__(self) -> str: ... # type: ignore[override] def writelines(self, __lines: Iterable[str]) -> None: ... # type: ignore[override] + def write(self, __s: str) -> int: ... # type: ignore[override] def readline(self, __size: int = -1) -> str: ... # type: ignore[override] def readlines(self, __hint: int = -1) -> list[str]: ... # type: ignore[override] def seek(self, __cookie: int, __whence: int = 0) -> int: ... # stubtest needs this diff --git a/stdlib/lzma.pyi b/stdlib/lzma.pyi index 34bd6f3f8db1..5b0bd8389c37 100644 --- a/stdlib/lzma.pyi +++ b/stdlib/lzma.pyi @@ -104,7 +104,7 @@ class LZMACompressor: class LZMAError(Exception): ... -class LZMAFile(io.BufferedIOBase, IO[bytes]): +class LZMAFile(io.BufferedIOBase, IO[bytes]): # type: ignore[misc] # incomptatible definitions of writelines in the 2 bases def __init__( self, filename: _PathOrFile | None = None, diff --git a/stdlib/tempfile.pyi b/stdlib/tempfile.pyi index dbff6d632d02..7019a7089f71 100644 --- a/stdlib/tempfile.pyi +++ b/stdlib/tempfile.pyi @@ -1,6 +1,6 @@ import io import sys -from _typeshed import BytesPath, GenericPath, StrPath, WriteableBuffer +from _typeshed import BytesPath, GenericPath, ReadableBuffer, StrPath, WriteableBuffer from collections.abc import Iterable, Iterator from types import TracebackType from typing import IO, Any, AnyStr, Generic, overload @@ -215,8 +215,14 @@ class _TemporaryFileWrapper(Generic[AnyStr], IO[AnyStr]): def tell(self) -> int: ... def truncate(self, size: int | None = ...) -> int: ... def writable(self) -> bool: ... + @overload def write(self, s: AnyStr) -> int: ... + @overload + def write(self: _TemporaryFileWrapper[bytes], s: ReadableBuffer) -> int: ... + @overload def writelines(self, lines: Iterable[AnyStr]) -> None: ... + @overload + def writelines(self: _TemporaryFileWrapper[bytes], lines: Iterable[ReadableBuffer]) -> None: ... if sys.version_info >= (3, 11): _SpooledTemporaryFileBase = io.IOBase @@ -392,8 +398,14 @@ class SpooledTemporaryFile(IO[AnyStr], _SpooledTemporaryFileBase): def seek(self, offset: int, whence: int = ...) -> int: ... def tell(self) -> int: ... def truncate(self, size: int | None = None) -> None: ... # type: ignore[override] + @overload def write(self, s: AnyStr) -> int: ... - def writelines(self, iterable: Iterable[AnyStr]) -> None: ... # type: ignore[override] + @overload + def write(self: SpooledTemporaryFile[bytes], s: ReadableBuffer) -> int: ... + @overload + def writelines(self, iterable: Iterable[AnyStr]) -> None: ... + @overload + def writelines(self: SpooledTemporaryFile[bytes], iterable: Iterable[ReadableBuffer]) -> None: ... def __iter__(self) -> Iterator[AnyStr]: ... # type: ignore[override] # These exist at runtime only on 3.11+. def readable(self) -> bool: ... diff --git a/stdlib/typing.pyi b/stdlib/typing.pyi index efd61ad8bf43..38760485fb70 100644 --- a/stdlib/typing.pyi +++ b/stdlib/typing.pyi @@ -2,7 +2,7 @@ import collections # Needed by aliases like DefaultDict, see mypy issue 2986 import sys import typing_extensions from _collections_abc import dict_items, dict_keys, dict_values -from _typeshed import IdentityFunction, Incomplete, SupportsKeysAndGetItem +from _typeshed import IdentityFunction, Incomplete, ReadableBuffer, SupportsKeysAndGetItem from abc import ABCMeta, abstractmethod from contextlib import AbstractAsyncContextManager, AbstractContextManager from re import Match as Match, Pattern as Pattern @@ -687,9 +687,17 @@ class IO(Iterator[AnyStr], Generic[AnyStr]): @abstractmethod def writable(self) -> bool: ... @abstractmethod - def write(self, __s: AnyStr) -> int: ... + @overload + def write(self: IO[str], __s: str) -> int: ... + @abstractmethod + @overload + def write(self: IO[bytes], __s: ReadableBuffer) -> int: ... @abstractmethod - def writelines(self, __lines: Iterable[AnyStr]) -> None: ... + @overload + def writelines(self: IO[str], __lines: Iterable[str]) -> None: ... + @abstractmethod + @overload + def writelines(self: IO[bytes], __lines: Iterable[ReadableBuffer]) -> None: ... @abstractmethod def __next__(self) -> AnyStr: ... @abstractmethod diff --git a/test_cases/stdlib/typing/check_io.py b/test_cases/stdlib/typing/check_io.py new file mode 100644 index 000000000000..67f16dc91765 --- /dev/null +++ b/test_cases/stdlib/typing/check_io.py @@ -0,0 +1,21 @@ +from __future__ import annotations + +import mmap +from typing import IO, AnyStr + + +def check_write(io_bytes: IO[bytes], io_str: IO[str], io_anystr: IO[AnyStr], any_str: AnyStr, buf: mmap.mmap) -> None: + io_bytes.write(b"") + io_bytes.write(buf) + io_bytes.write("") # type: ignore + io_bytes.write(any_str) # type: ignore + + io_str.write(b"") # type: ignore + io_str.write(buf) # type: ignore + io_str.write("") + io_str.write(any_str) # type: ignore + + io_anystr.write(b"") # type: ignore + io_anystr.write(buf) # type: ignore + io_anystr.write("") # type: ignore + io_anystr.write(any_str)