Summary
Summary
ty raises invalid-method-override on a class that subclasses IO[str] and overrides write / writelines. The diagnostic says the base parameter is Buffer / Iterable[Buffer], but for IO[str] the typeshed overloads should resolve AnyStr to str. mypy and pyright are silent on the same file.
Minimal repro
repro.py:
from typing import IO, Iterable
class NullFile(IO[str]):
def write(self, s: str, /) -> int:
return 0
def writelines(self, lines: Iterable[str], /) -> None:
pass
Command
ty output
error[invalid-method-override]: Invalid override of method `write`
--> repro.py:5:9
|
5 | def write(self, s: str, /) -> int:
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Definition is incompatible with `IO.write`
|
::: stdlib/typing.pyi:1951:9
|
1951 | def write(self, s: AnyStr, /) -> int: ...
| -------------------------------- `IO.write` defined here
|
info: parameter `s` has an incompatible type: `Buffer` is not assignable to `str`
info: This violates the Liskov Substitution Principle
error[invalid-method-override]: Invalid override of method `writelines`
--> repro.py:8:9
|
8 | def writelines(self, lines: Iterable[str], /) -> None:
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Definition is incompatible with `IO.writelines`
|
::: stdlib/typing.pyi:1957:9
|
1957 | def writelines(self, lines: Iterable[AnyStr], /) -> None: ...
| ---------------------------------------------------- `IO.writelines` defined here
|
info: parameter `lines` has an incompatible type: `Iterable[Buffer]` is not assignable to `Iterable[str]`
info: └── protocol `Iterable[Buffer]` is not assignable to protocol `Iterable[str]`
info: └── incompatible return types: `Iterator[Buffer]` is not assignable to `Iterator[str]`
info: └── protocol `Iterator[Buffer]` is not assignable to protocol `Iterator[str]`
info: └── incompatible return types: `Buffer` is not assignable to `str`
info: This violates the Liskov Substitution Principle
Found 2 diagnostics
Cross-checks
mypy --strict repro.py: Success: no issues found in 1 source file
pyright repro.py (1.1.408): 0 errors, 0 warnings, 0 informations
Typeshed context
IO.write and IO.writelines are defined with two overloads each in typing.pyi:
class IO(Generic[AnyStr]):
@overload
def write(self: IO[bytes], s: ReadableBuffer, /) -> int: ...
@overload
def write(self, s: AnyStr, /) -> int: ...
@overload
def writelines(self: IO[bytes], lines: Iterable[ReadableBuffer], /) -> None: ...
@overload
def writelines(self, lines: Iterable[AnyStr], /) -> None: ...
_typeshed.ReadableBuffer is an alias for collections.abc.Buffer, which matches the Buffer ty reports. For a subclass of IO[str] the override should be checked against the second overload with AnyStr = str, giving s: str and lines: Iterable[str]. Something is letting Buffer leak into the comparison instead.
ty's diagnostic source pointer for IO.writelines lands on line 1957 (the generic overload), so the issue is not as simple as "ty picked the bytes overload", but the materialized parameter type the override is being checked against still comes out as Buffer.
Possibly related
Versions
- ty 0.0.33 (c512d84 2026-04-28)
- Python 3.14.3
Version
No response
Summary
Summary
ty raises
invalid-method-overrideon a class that subclassesIO[str]and overrideswrite/writelines. The diagnostic says the base parameter isBuffer/Iterable[Buffer], but forIO[str]the typeshed overloads should resolveAnyStrtostr. mypy and pyright are silent on the same file.Minimal repro
repro.py:Command
ty output
Cross-checks
mypy --strict repro.py:Success: no issues found in 1 source filepyright repro.py(1.1.408):0 errors, 0 warnings, 0 informationsTypeshed context
IO.writeandIO.writelinesare defined with two overloads each intyping.pyi:_typeshed.ReadableBufferis an alias forcollections.abc.Buffer, which matches theBufferty reports. For a subclass ofIO[str]the override should be checked against the second overload withAnyStr = str, givings: strandlines: Iterable[str]. Something is lettingBufferleak into the comparison instead.ty's diagnostic source pointer for
IO.writelineslands on line 1957 (the generic overload), so the issue is not as simple as "ty picked the bytes overload", but the materialized parameter type the override is being checked against still comes out asBuffer.Possibly related
pytestin shebang ruff#2237 (io.TextIOWrapper) — closed in Dec 2025 as duplicate of Apply #[derive(Default)] fixes suggested by Clippy ruff#2000. Same shape, more inheritance noise. The minimalIO[str]repro above still reproduces on 0.0.33.Versions
Version
No response