From 1c65fb6daf27f79943be91531d48763b77485f5d Mon Sep 17 00:00:00 2001 From: sanyam-brudite Date: Fri, 24 Apr 2026 18:29:56 +0530 Subject: [PATCH 1/2] Fix type narrowing with not isinstance(x, cls) in classmethods Adjust get_type_range_of_type to set is_upper_bound=False for Self types, allowing precise narrowing in negative checks. Fixes #21271. --- mypy/checker.py | 7 +++++-- test-data/unit/check-isinstance.test | 13 +++++++++++++ 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index fbaee0174d49..b4648b060fa7 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -8173,10 +8173,13 @@ def get_type_range_of_type(self, typ: Type) -> TypeRange | None: # Type[A] means "any type that is a subtype of A" rather than "precisely type A" # we indicate this by setting is_upper_bound flag is_upper_bound = True - if isinstance(typ.item, NoneType): + item = get_proper_type(typ.item) + if isinstance(item, TypeVarType) and item.id.is_self(): + is_upper_bound = False + elif isinstance(item, NoneType): # except for Type[None], because "'NoneType' is not an acceptable base type" is_upper_bound = False - if isinstance(typ.item, Instance) and typ.item.type.is_final: + elif isinstance(item, Instance) and item.type.is_final: is_upper_bound = False return TypeRange(typ.item, is_upper_bound=is_upper_bound) if isinstance(typ, AnyType): diff --git a/test-data/unit/check-isinstance.test b/test-data/unit/check-isinstance.test index c5695864d6b1..80f0df207e3b 100644 --- a/test-data/unit/check-isinstance.test +++ b/test-data/unit/check-isinstance.test @@ -3322,3 +3322,16 @@ def f2(x: A | None, t: type[A]): else: reveal_type(x) # N: Revealed type is "None" [builtins fixtures/isinstancelist.pyi] + +[case testNarrowingSelfInClassMethod] +# flags: --warn-unreachable +from typing import Self, Union + +class Foo: + @classmethod + def foo(cls, x: Union[Self, float]) -> None: + if isinstance(x, cls): + reveal_type(x) # N: Revealed type is "Self`0" + else: + reveal_type(x) # N: Revealed type is "builtins.float" +[builtins fixtures/isinstancelist.pyi] From 777bb73ab21fec8ca8b2df3e797efd562607eacc Mon Sep 17 00:00:00 2001 From: sanyam-brudite Date: Fri, 24 Apr 2026 18:38:42 +0530 Subject: [PATCH 2/2] Fix redundant get_proper_type call in checker.py --- mypy/checker.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mypy/checker.py b/mypy/checker.py index b4648b060fa7..ab8f9aeeba79 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -8173,7 +8173,7 @@ def get_type_range_of_type(self, typ: Type) -> TypeRange | None: # Type[A] means "any type that is a subtype of A" rather than "precisely type A" # we indicate this by setting is_upper_bound flag is_upper_bound = True - item = get_proper_type(typ.item) + item = typ.item if isinstance(item, TypeVarType) and item.id.is_self(): is_upper_bound = False elif isinstance(item, NoneType):