Make gzip.GzipFile inherit IO[bytes] to match BZ2File/LZMAFile - #21798
Closed
sarbojitrana wants to merge 1 commit into
Closed
Make gzip.GzipFile inherit IO[bytes] to match BZ2File/LZMAFile#21798sarbojitrana wants to merge 1 commit into
sarbojitrana wants to merge 1 commit into
Conversation
GzipFile only inherited from BaseStream, unlike its sibling classes bz2.BZ2File and lzma.LZMAFile, which both additionally inherit IO[bytes]. Since typing.IO is a nominal (non-protocol) class, mypy's subtype check for "is this overloaded function assignable to this Callable type" requires an actual inheritance relationship on the return type. Without it, none of gzip.open's overloads (returning GzipFile, TextIOWrapper, or unions of those) were considered assignable to Callable[[str, str], IO], producing a false positive "Incompatible types in assignment" error. Fixes python#21784
Contributor
|
According to mypy_primer, this change doesn't affect type check results on a corpus of open source code. ✅ |
Author
|
hi @JukkaL could you please review this pull request |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #21784
Root cause
typing.IOis a nominal class, not aProtocol, so mypy's subtype check for "is this overloaded function assignable to thisCallabletype" (visit_overloadedinmypy/subtypes.py, theisinstance(right, CallableType)branch) requires each overload's return type to be an actual (nominal) subtype ofIO, not just structurally compatible.gzip.openhas 4 overloads returningGzipFile,TextIOWrapper, or unions of the two.TextIOWrapperalready inheritsTextIO(→IO[str]), butgzip.GzipFileonly inherited fromBaseStream(→BufferedIOBase→IOBase), with noIOin its MRO at all. So none ofgzip.open's overloads qualified, and the assignment was rejected.By contrast,
builtins.open's overloads all return classes that do inherit the matchingIOvariant (FileIO,BufferedReader/Writer/Randomall inheritBinaryIO, and the final fallback overload returnsIO[Any]directly), which is whyfoo: Callable[[str, str], IO] = opentype-checks fine today — this is the asymmetry the issue reporter noticed.Crucially,
gzip.GzipFile's sibling classes in the same compression-module family,bz2.BZ2Fileandlzma.LZMAFile, are already declared asclass BZ2File(BaseStream, IO[bytes]):/class LZMAFile(BaseStream, IO[bytes]):.GzipFilewas simply missing the sameIO[bytes]base — an inconsistency inmypy/typeshed/stdlib/gzip.pyirather than a bug in mypy's core type-checking logic.Fix
In
mypy/typeshed/stdlib/gzip.pyi:class GzipFile(BaseStream):→class GzipFile(BaseStream, IO[bytes]):(matchingBZ2File/LZMAFile)from typing import IO, ...mode: objectneeded a# type: ignore[assignment], sinceIO.modeis declared as astrproperty butGzipFile.modeis genuinely anint(READ/WRITEconstants) at runtime — the same kind of unavoidable, deliberate mismatch already suppressed elsewhere in typeshed for other IO subclasses (e.g. the# type: ignore[misc]comments onFileIO/BufferedReader/BufferedWriter/BufferedRandom/TextIOWrapper/LZMAFile).Proof / verification
Before the fix, both examples from the issue fail:
After the fix, the original repro is clean:
Verification performed:
python -m mypy.stubtest gzip— passes, no new stub/runtime mismatches introduced (confirmed the 4 pre-existingio.Reader/io.Writerstubtest errors are unrelated and present identically before this change).python -m mypy --config-file mypy_self_check.ini mypy— mypy's self-check passes clean.testOverloadedGzipOpenAssignableToCallableReturningIO_Issue21784totest-data/unit/pythoneval.test(this suite runs against the real stdlib stubs via CPython, matching how the issue actually manifests). Verified it fails without the fix and passes with it.python -m pytest -q mypy/test/):12391 passed, 360 skipped, 10 xfailed, with a single unrelated flaky failure (testAnyTypeAlias2, aResourceWarningfrom subprocess cleanup underpytest-xdist, unrelated to typing/gzip) that passes in isolation and reproduces identically onmasterwithout this change.Scope note
The issue's second comment shows a related-but-distinct scenario (assigning
gzip.openandopento the same local variable acrossif/elsebranches) that still errors after this fix. That's a different code path — joining twoOverloadedtypes when widening a variable's type across control-flow branches — and is unrelated toGzipFile's missingIObase. Left out of scope here to keep this change minimal and low-risk.