build: upgrade mypy to 2.3.0 - #68
Merged
Merged
Conversation
Owner
Author
|
@codex review for deterministic/RagaliQ ownership, dependency reproducibility, Python 3.14 compatibility, public-clone portability, paid-call safety, golden-fixture integrity, marker correctness, public API compatibility, and unintended behaviour changes |
|
Codex Review: Didn't find any major issues. Already looking forward to the next diff. Reviewed commit: 鈩癸笍 About Codex in GitHubCodex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 馃憤. When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback". |
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.
Upgrades
mypyfrom 2.2.0 to 2.3.0. This one needs an abstract-requirement edit: the existing<2.3ceiling excludes 2.3.0, so a lock refresh alone cannot reach it.Version delta
mypy>=2.2.0,<2.3to>=2.3.0,<2.4(pyproject.toml:21)2.3.0 is the only stable release between 2.2.0 and latest, and 2.3.0 is latest. The new ceiling keeps the repository's existing convention of a one-minor window for mypy.
Lock regenerated with the second form of
.agents/skills/upgrade-dependencies/SKILL.mdstep 5 (retain existing preferences, resolve only required changes), not--upgrade:No transitive churn. 2.3.0 raises its own floor from
librt>=0.12.0tolibrt>=0.13.0, andlibrtwas already locked at exactly 0.13.0, so nothing else had to move:Breaking changes, and which touch this repository
python/mypypublishes no GitHub Releases;CHANGELOG.mdat tagv2.3.0is the authoritative note. Upstream's standing policy, quoted from that file:So 2.3.0 is a feature release that upstream expects may legitimately produce new errors. The full change set was read and classified.
Exactly one change in the release can add errors. Every other type-checking change either removes a false positive or removes narrowing.
__replace__exempted ininfer_variance__eq__in membership narrowingwarn_unreachable = True;--strictdoes not imply it andpyproject.tomldoes not set it.frozendictmembership narrowingfrozendictexists inbuiltins.pyionly undersys.version_info >= (3, 15);python_version = "3.14".Coroutinefor unannotatedasync deffunc.type is None, which--disallow-untyped-defsalready forbids. The oneasync defin scope (eval/ragaliq_adapter.py:29) is fully annotated.TypeVarin the checked scope, and notype: ignore[...]ordisable_error_codefor it to affect.PYODIDEenv varplatformFinalinstance attributes read-only at runtime--native-parserto become defaultmypy/options.py:389still readsself.native_parser = False. Watch item for 2.4.On the two
__replace__fixes, the reason they are inert matters. "This repository has no dataclasses" would be the wrong dismissal.mypy/plugins/dataclasses.py:388-389gates_add_dunder_replaceonpython_version >= (3, 13)alone, and with noplugins = ["pydantic.mypy"]configured the four pydantic models go through the genericdataclass_transformpath. Atpython_version = "3.14"they do get a synthesized__replace__. They are inert because the triggers are absent:intersect_instancesto build an ad-hoc intersection of two such classes. Everyisinstance/issubclasstarget in the checked scope is a builtin or ABC (str,int,bool,dict,list,tuple,Mapping,np.ndarray) - never a model class. And the cross-basecheck_compatibilitypath needs a class with two or more bases; there is none in scope.__replace__member. The one PEP 695 generic,models.py:20 class _ImmutableMapping[Key, Value](Mapping[Key, Value]), has no__replace__; the four classes that do get one are non-generic. The two sets are disjoint.On typeshed. Only 12 stdlib stubs changed, and
pathlib.pyi,os/__init__.pyiandjson/*.pyiare not among them - which covers every stdlib import in the checked scope.stubs/changed only forlibrt, not for any third-party distribution;numpy,pydantic,openaiandragaliqall shippy.typed, so they are checked from inline source types no mypy release can alter. The largest typeshed change,typing_extensions.pyi(+70/-14, includingTypedDictandTypeVarTuplebecoming distinct symbols rather than re-exports at 3.14), is inert here for a reason verified by grep rather than assumed:TypedDictcomes fromtyping, nottyping_extensions, andtyping_extensionsis imported nowhere in the checked scope.errorcodes.pyis byte-identical between the tags and the--strictflag table is unchanged, so no new diagnostic can arrive under a newly-enabled code.Prediction recorded before running anything
Two parts, both recorded before any lock was regenerated.
uv lock --checkthen the byte-exactcmp- both of which run before "Run all repository hooks". The locks must be regenerated in the same commit.Success: no issues found in 5 source files. This was recorded as a structural claim, not a hope: every 2.3.0 behaviour change removes a false positive or removes narrowing except PR 21675, and that one's trigger shape does not exist in this repository. Coverage stays at 96.05%, all twelve hooks pass, the free suite is unaffected because mypy is not imported by any test, and no README text needs editing because lines 77, 394 and 430 mention mypy without a version number.A dry run recorded before any file was edited, using
uvxso the committed environment was untouched:What actually happened
Exactly the prediction.
uv lock --checkpylock.tomluv pip checkpytest eval/ -qpre-commit run --all-filespre-commit validate-configruff format --check .ruff check .agent-policy-symbolsFree validation command and result:
One incidental observation, recorded because it was surfaced by this work and because it is not a defect in this change. Running
uv pip syncagainst a long-lived local.venvreporteduv pip checkfinding "multiple installed distributions" for mypy. The cause was pre-existing pollution in that developer venv:mypy-2.2.0.dist-info/licenses/containedLICENSE 3files - macOS duplicate-copy names that no uv install writes and that therefore appear in noRECORD, so uninstall could not remove the directory. Recreating the venv with the documenteduv venv --python 3.14plusuv pip syncgivesuv pip checkexit 0, and the clean-clone run below confirms a fresh install is clean..venv/is gitignored; nothing entered the repository.The mutation question
Which behaviours of this dependency does the suite exercise? The mypy hook and the documented
mypycommand exercise strict checking over exactly five files, named as explicitargswithpass_filenames: false:That is a genuine assertion surface -
--strictbundles thirteen flags, andSuccessover those files is a real result. But its scope is exactly five files, and it is produced by a hook, not by a pytest node.For each breaking change that touches a real call site, which test would have failed?
pre-commit run --all-filesand the CI "Run all repository hooks" step. This is the one upgrade in the audit where the existing gate genuinely would have caught the change.warn_unused_ignoreson a now-unnecessarytype: ignoretype: ignorein the five checked files.typing_extensionschangesThe finding, stated plainly. The gap is not in what mypy checks, it is in what mypy is pointed at. Four tracked Python files are outside
[tool.mypy] filesand were never type-checked at 2.2.0 either:benchmark_retrieval.py,eval/check_agent_policy_symbols.py,eval/conftest.py, andeval/test_verdigrise.py. Nothing in this upgrade's green result says anything about them. Concretely,eval/test_verdigrise.pycarries seven# type: ignore[index]comments (lines 1111, 1113, 1115, 1117, 1142, 1144, 1146) that sit permanently outside--warn-unused-ignores, so a mypy release that made any of them unnecessary could never be detected. That is a standing scope decision, not a regression introduced here, and it is deliberately not changed in this pull request - widening the type-checking scope during a version upgrade would confound the upgrade's own evidence.Lock byte-comparison
pylock.tomlinvariants re-checked after the export:Clean-clone transcript
Isolated temp directory, both provider key variables unset and confirmed absent by presence check only, no sibling
../RagaliQreachable, README install commands verbatim with bareuvas documented.The clone-to-green figure is a warm-cache number: the uv cache was already populated (17G), so 4s measures command execution, not first contact. A cold-cache clone would additionally download 54 wheels.
Hidden costs
<2.4means 2.4.0 will again require a source edit rather than a lock refresh. That is the intended trade - a tight window makes each tool bump a reviewed event - and it is worth restating rather than discovering later.--native-parserwill become the default. Testing that flag against this scope before 2.4 ships is the cheap way to de-risk that upgrade; it is out of scope here.PYODIDEenvironment variable would now alter mypy's inferred platform where it previously could not. Irrelevant on darwin and ubuntu, but it is a new sensitivity to the ambient environment.