Skip to content

Solve generic subclass type variables from self-annotated __init__ - #22422

Open
xh123637 wants to merge 2 commits into
python:masterfrom
xh123637:fix/22395-init-overload-self-type
Open

xh123637 wants to merge 2 commits into
python:masterfrom
xh123637:fix/22395-init-overload-self-type

Conversation

@xh123637

@xh123637 xh123637 commented Nov 9, 2026

Copy link
Copy Markdown

Fixes #22395.

An __init__ overload with an explicit self annotation solved the type
parameters of the class that declares it, but not those of a generic subclass
that inherits the overload: Sub(null=True) was inferred as Sub[Never, Never]
instead of Sub[int | None, int | None].

The typing spec requires the class being constructed to be bound to the
self parameter
(https://typing.python.org/en/latest/spec/constructors.html#init-method).
The class being constructed here is Sub[_S, _G], and binding it to
Base[int | None, int | None] solves _S and _G.

This is the __init__ counterpart of the __new__ fix in #21441. That PR
made the explicit __new__ return type actually be used, but only for the
class where it is defined; a generic subclass had the same gap
(E() for class E(D[_S]) was E[Never]), so this PR fixes both paths
in the same place.

The fix is in mypy/typeops.py::class_callable: when the explicit self
annotation (for __init__) or return annotation (for __new__) is an
instance of a superclass of the class being constructed, solve the
constructed class's type variables from it (new helper
map_explicit_type_to_class), and validate the solution by mapping it back
up to the annotated class. If the annotation cannot be mapped (for example
the subclass fixes a type variable to a different type), the previous
behavior is kept. For __new__, the mapping is only attempted when the
annotation refers to the class where __new__ is defined, so the behavior
introduced in #21441 for explicit return types naming other classes is
unchanged.

This also resolves the known limitation documented in
testSelfTypeRestrictedMethodOverloadInit, where the test noted there was
"no way to map the overloaded __init__ to subtype": SubP(use_str=True)
is now SubP[str] instead of SubP[Any]. That test's expectations are
updated accordingly.

Tests run locally:

  • pytest mypy/test/testcheck.py -k "testSelfAnnotatedInitOverloadGenericSubclass or testNewReturnTypeGenericSubclass or testOverloadedGenericInit or testNewReturnType9 or testNewReturnType5": 5 passed. The two new tests fail without the fix and pass with it.
  • pytest mypy/test/testcheck.py::TypeCheckSuite::check-generics.test mypy/test/testcheck.py::TypeCheckSuite::check-classes.test: 818 passed, 2 skipped, 1 xfailed.
  • Full pytest mypy/test/testcheck.py: 8318 passed, 27 skipped, 7 xfailed.
  • ruff check and ruff format --check pass on the changed file; black applied per repo config.
  • mypy --config-file mypy_self_check.ini mypy/typeops.py: no errors.

Disclosure: this contribution was developed with the assistance of an LLM
code assistant; the fix, the tests, and the results above were verified by
running them locally.

@github-actions

This comment has been minimized.

@xh123637

Copy link
Copy Markdown
Author

Thanks for the primer run — it caught a real regression, and I have fixed it.

mkdocs (real regression, fixed in 4532b6d): PropagatingSubConfig[Validation]() in mkdocs/config/defaults.py was inferred as PropagatingSubConfig[LegacyConfig], so config.validation became LegacyConfig and the 11 attr-defined errors on .links/.nav followed. Root cause: the mapping added in this PR solved the subclass's type variables from the __init__ overload's self-annotation (SubConfig[LegacyConfig]) even when the call site had already fixed them explicitly ([Validation]). I have narrowed it so explicit call-site type arguments win over mapped ones (applied where type arguments meet the constructor callable, plus analyze_type_type_callee for type[...] values). Bare calls still solve from the annotation, and same-class explicit self-types keep their existing precedence. Added a regression test (testSelfTypeExplicitCallSiteTypeArgsWinOverMapped, fails before the narrowing, passes after). Verified locally: mkdocs checks clean again (Success: no issues found in 61 source files, identical to base mypy output), and the full testcheck.py suite is green (8319 passed, 27 skipped, 7 xfailed).

pywin32 (pre-existing, not caused by this PR): the INTERNAL ERROR in pythonwin/pywin/test/_dbgscript.py occurs identically on the base commit (1e2ea53) and on this PR — I reproduced it locally with both builds. It crashes in the native parser (mypy/nativeparse.py::read_func_def, ValueError: invalid bool value) before any type checking runs, so it is unrelated to this change; the primer diff shows the error line unchanged between base and PR, only the version string differs. No change made for it.

@github-actions

Copy link
Copy Markdown
Contributor

Diff from mypy_primer, showing the effect of this PR on open source code:

pywin32 (https://github.com/mhammond/pywin32)
  ./pythonwin/pywin/test/_dbgscript.py: error: INTERNAL ERROR -- Please try using mypy master on GitHub:
- version: 2.5.0+dev.b83696ea8099ef3772a5a2bc2c49f2b05340c37a
+ version: 2.5.0+dev.9ba5f4332381ac1f4146c954b30ea83cf2d19493

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Inherited self-annotated __init__ overload does not solve a generic subclass's type parameters

1 participant