Repository navigation
Fix self in identity alias - #22182
Open
Moussa-Mikhail wants to merge 2 commits into
Open
Moussa-Mikhail wants to merge 2 commits into
Moussa-Mikhail wants to merge 2 commits into
Conversation
This comment has been minimized.
This comment has been minimized.
…otation is_expected_self_type() already unwraps Annotated[Self, ...] so a method like "def m(self: Annotated[Self, ...])" is recognized as equivalent to a bare "self: Self". A generic type alias that's equally transparent (e.g. "type Ident[T] = T") had no equivalent handling, so "self: Ident[Self]" was wrongly rejected with "Method cannot have explicit self annotation and Self type". Adds expand_type_alias_for_self_check(), which substitutes the alias's own type parameters with the arguments it was referenced with (using mypy's existing substitution, mypy.expandtype.expand_type) and recurses is_expected_self_type() on the result -- the same way the existing Annotated[...] case recurses on its one argument. This is handled in both places an explicit self-type annotation is checked: the plain/ instance-method branch (self: Ident[Self]) and the classmethod branch, which needs it in two different positions depending on how the alias and type[...] are nested -- cls: type[Ident[Self]] unwraps through the existing TypeType case first and lands in the plain branch, while cls: Ident[type[Self]] needs the same handling in the classmethod branch's own UnboundType case, recursing with is_classmethod=True preserved -- same as the ANNOTATED_TYPE_NAMES case right next to it. Using real substitution instead of hand-matching "is the target exactly the alias's sole type parameter" also covers, without any extra code: an alias with extra, unused type parameters, in any order (e.g. type Extra[T, S] = T used as Extra[Self, int] or Extra[int, Self]); and a target that only reduces to the expected shape once substituted (e.g. type UnionSame[T] = T | T, which collapses once both T's become Self). Self supplied for an alias's *unused* parameter is correctly still rejected, since the substitution never looks at that slot. Also drops the redundant "sym is not None" repeated on every branch below the symbol lookup, in both the classmethod and instance-method cases, in favor of a single early return when the lookup fails -- the control flow in each branch already made the None case fall through to the same result, so this part is a pure refactor with no behavior change. expand_type_alias_for_self_check()'s args parameter takes UnboundType.args directly, which mypy's own types.py types as a Sequence[Type] (stored internally as a tuple), not a list -- declared as list[Type] at first, which only surfaced as a real error under mypy's own self-check (runtests.py self), not under ordinary testing. Fixed to Sequence[Type], imported from collections.abc to match this module's existing convention for Callable/Collection/Iterable/Iterator. Verified: bare self: Self, Annotated[Self, ...], an explicit concrete self-type, and a non-Self alias argument (Ident[int], which should and does still raise a real self-type error) are all unaffected; both classmethod orderings, extra-unused-parameter aliases (both orders), and the union-collapse case all work; Self in an alias's unused slot still correctly errors. Self-type test suite 152/152. runtests.py self: 10 errors, matching an unmodified checkout exactly (pre-existing environment gaps -- missing psutil/tomli/filelock/setuptools stubs), confirmed by running the identical check against the unmodified base commit. Full test suite (mypy/test + mypyc/test): 13627 passed, 410 skipped, 10 xfailed; the 12 failures and 3 collection errors present are pre-existing environment gaps (missing attrs/mypyc-build deps), confirmed identical on an unmodified checkout. The code above and in this commit was written by Claude and reviewed by Moussa Mikhail. The issue and the PR description accompanying it are his own, written by him. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01REqZNys4H8tiFU7Bb6Qj2B
Adds testTypingSelfIdentityTypeAlias (mirroring the existing
testTypingSelfAnnotatedType) plus a minimal regression case, covering:
self: Ident[Self], both orderings of the classmethod form
(cls: Ident[type[Self]] and cls: type[Ident[Self]]), and a negative case
confirming a non-Self alias argument (Ident[int]) still raises the real
self-type error.
Adds testTypingSelfIdentityTypeAliasSubstitution covering what the
substitution-based implementation handles beyond the literal "type X[T]
= T" shape, for free: a union that only collapses to Self once
substituted (type UnionSame[T] = T | T), an alias with an extra unused
type parameter in either position (type Extra[T, S] = T and type
Extra2[S, T] = T), the classmethod form of the same, and a negative case
confirming Self supplied for an alias's *unused* parameter is still
correctly rejected, not silently accepted.
Writing the classmethod cases in the first test is what surfaced the gap
the previous commit's classmethod-branch handling fixes -- cls:
Ident[type[Self]] was still being wrongly rejected until that commit
added the same handling to the classmethod branch, not just the plain
branch.
Separately, found and worked around an unrelated pre-existing mypy issue
while writing these tests: a generic type alias whose value is *exactly*
a bare type parameter (type Ident[T] = T) crashes the full checker
(AssertionError: Var(TypeVar), in checker.named_type) under the test
suite's minimal typing.pyi stub, where TypeVar is a dummy (`TypeVar = 0`)
rather than a real class -- checker.py's alias-value-expression check
calls named_type("typing.TypeVar") and asserts it resolves to a TypeInfo.
A wrapped alias (type Wrapped[T] = list[T]) does not trigger it, so it's
specific to the bare-identity shape. [typing fixtures/typing-full.pyi]
(which has a real TypeVar class) sidesteps it; the new cases declare it
for that reason. Not touched here -- it's unrelated to self-annotation
handling and worth its own report.
Verified: all new cases pass; self-type test suite 152/152 (was 149
before either commit). Full test suite (mypy/test + mypyc/test): 13627
passed, 410 skipped, 10 xfailed, with the same 12 failures / 3 collection
errors as before these changes, confirmed identical to an unmodified
checkout (pre-existing environment gaps: missing attrs/mypyc-build
deps).
The code above and in this commit was written by Claude and reviewed by
Moussa Mikhail. The issue and the PR description accompanying it are his
own, written by him.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01REqZNys4H8tiFU7Bb6Qj2B
Moussa-Mikhail
force-pushed
the
fix-self-in-identity-alias
branch
from
October 12, 2026 01:33
1ecaab8 to
5a326cd
Compare
Contributor
|
According to mypy_primer, this change doesn't affect type check results on a corpus of open source code. ✅ |
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 #22181
This PR expands type aliases in annotations of self and cls so that
is_expected_self_typecan handle them like any other annotation. It also refactors some repetitive if conditions by early returning when they aren't true. Using mypy's own type substitution means type aliases with strange shapes (e.g. type Extra[T, S] = T, UnionSame[T] = T | T) work without special-casing them.The code in this PR was written by Claude and reviewed by me.