From 7848677b05d9c7d7b6a350ba58d3f745ee4a2771 Mon Sep 17 00:00:00 2001 From: EmmanuelNiyonshuti Date: Wed, 2 Sep 2026 14:35:21 +0200 Subject: [PATCH 1/2] fix-21917 --- mypy/semanal.py | 3 +++ test-data/unit/check-selftype.test | 13 +++++++++++++ 2 files changed, 16 insertions(+) diff --git a/mypy/semanal.py b/mypy/semanal.py index cd1b0a738974f..360a37ceb6d3e 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -259,6 +259,7 @@ ) from mypy.typeops import function_type, get_type_vars, try_getting_str_literals_from_type from mypy.types import ( + ANNOTATED_TYPE_NAMES, ASSERT_TYPE_NAMES, DATACLASS_TRANSFORM_NAMES, DEPRECATED_TYPE_NAMES, @@ -1195,6 +1196,8 @@ def is_expected_self_type(self, typ: Type, is_classmethod: bool) -> bool: return typ == self.type.self_type if isinstance(typ, UnboundType): sym = self.lookup_qualified(typ.name, typ, suppress_errors=True) + if sym is not None and sym.fullname in ANNOTATED_TYPE_NAMES and typ.args: + return self.is_expected_self_type(typ.args[0], is_classmethod=False) return sym is not None and sym.fullname in SELF_TYPE_NAMES return False diff --git a/test-data/unit/check-selftype.test b/test-data/unit/check-selftype.test index c157200352562..c4a5bc15422bf 100644 --- a/test-data/unit/check-selftype.test +++ b/test-data/unit/check-selftype.test @@ -1549,6 +1549,19 @@ reveal_type(D.meth()) # N: Revealed type is "__main__.D" reveal_type(D.bad()) # N: Revealed type is "Never" [builtins fixtures/classmethod.pyi] +[case testTypingSelfAnnotatedType] +# See: https://github.com/python/mypy/issues/21917 + +from typing import Self +from typing_extensions import Annotated + +class C: + def foo(self: Annotated[Self, "some_metadata"], x: int) -> None: + ... + +reveal_type(C.foo) # N: Revealed type is "def [Self <: __main__.C] (self: Self`2, x: builtins.int)" +[builtins fixtures/tuple.pyi] + [case testTypingSelfOverload] from typing import Self, overload, Union From f5696b431cdf8405c81a42075ffc7116d3661235 Mon Sep 17 00:00:00 2001 From: EmmanuelNiyonshuti Date: Sun, 6 Sep 2026 08:49:26 +0200 Subject: [PATCH 2/2] add a check for classmethods --- mypy/semanal.py | 2 ++ test-data/unit/check-selftype.test | 5 +++++ 2 files changed, 7 insertions(+) diff --git a/mypy/semanal.py b/mypy/semanal.py index 360a37ceb6d3e..2632ec0e42705 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -1191,6 +1191,8 @@ def is_expected_self_type(self, typ: Type, is_classmethod: bool) -> bool: sym = self.lookup_qualified(typ.name, typ, suppress_errors=True) if sym is not None and sym.fullname in TYPE_NAMES and typ.args: return self.is_expected_self_type(typ.args[0], is_classmethod=False) + if sym is not None and sym.fullname in ANNOTATED_TYPE_NAMES and typ.args: + return self.is_expected_self_type(typ.args[0], is_classmethod=True) return False if isinstance(typ, TypeVarType): return typ == self.type.self_type diff --git a/test-data/unit/check-selftype.test b/test-data/unit/check-selftype.test index c4a5bc15422bf..34ce4595439fc 100644 --- a/test-data/unit/check-selftype.test +++ b/test-data/unit/check-selftype.test @@ -1559,7 +1559,12 @@ class C: def foo(self: Annotated[Self, "some_metadata"], x: int) -> None: ... + @classmethod + def bar(cls: Annotated[type[Self], "some_metadata"], x: int) -> None: + ... + reveal_type(C.foo) # N: Revealed type is "def [Self <: __main__.C] (self: Self`2, x: builtins.int)" +reveal_type(C.bar) # N: Revealed type is "def (x: builtins.int)" [builtins fixtures/tuple.pyi] [case testTypingSelfOverload]