From 4c8b224851bd2109915c308ab8653b58a27fdf2b Mon Sep 17 00:00:00 2001 From: Khanh Le Date: Wed, 17 Jun 2026 12:25:33 -0500 Subject: [PATCH 1/3] Fix TypeIs narrowing before isinstance --- mypy/checker.py | 18 +++++++++++++++--- test-data/unit/check-typeis.test | 19 +++++++++++++++++++ 2 files changed, 34 insertions(+), 3 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index 33705c98e10c3..8ede926a6d23b 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -6591,11 +6591,23 @@ def find_isinstance_check_helper( if len(node.args) != 2: # the error will be reported elsewhere return {}, {} if literal(expr) == LITERAL_TYPE: + original_type = self.lookup_type(expr) + current_type = self.expr_checker.narrow_type_from_binder( + expr, original_type + ) + yes_type, no_type = self.conditional_types_with_intersection( + current_type, self.get_isinstance_type(node.args[1]), expr + ) + if ( + self.binder.get(expr) is not None + and yes_type is not None + and is_subtype(current_type, yes_type, ignore_promotions=True) + ): + yes_type = current_type return conditional_types_to_typemaps( expr, - *self.conditional_types_with_intersection( - self.lookup_type(expr), self.get_isinstance_type(node.args[1]), expr - ), + yes_type, + no_type, ) elif refers_to_fullname(node.callee, "builtins.issubclass"): if len(node.args) != 2: # the error will be reported elsewhere diff --git a/test-data/unit/check-typeis.test b/test-data/unit/check-typeis.test index 65ee837452f5b..6b63f9aa080cf 100644 --- a/test-data/unit/check-typeis.test +++ b/test-data/unit/check-typeis.test @@ -26,6 +26,25 @@ def main(a: Union[Point, Line, int]) -> None: [builtins fixtures/tuple.pyi] +[case testTypeIsAndIsinstanceWithGenericAlias] +from typing import Any, Generic, TypeVar +from typing_extensions import TypeAlias, TypeIs + +T = TypeVar("T") +class Slice(Generic[T]): pass + +SliceInt: TypeAlias = Slice[int | None] +SliceStr: TypeAlias = Slice[str | None] + +def is_slice_int(obj: Any) -> TypeIs[SliceInt]: pass + +def main(obj: SliceInt | SliceStr) -> None: + if is_slice_int(obj): + pass + elif isinstance(obj, Slice): + reveal_type(obj) # N: Revealed type is "__main__.Slice[builtins.str | None]" +[builtins fixtures/isinstance.pyi] + [case testTypeIsTypeArgsNone] from typing_extensions import TypeIs def foo(a: object) -> TypeIs: # E: TypeIs must have exactly one type argument From 0af3bc4130431b23df16fa8f01610a4d86a5c26f Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 17 Jun 2026 17:35:32 +0000 Subject: [PATCH 2/3] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- mypy/checker.py | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index 8ede926a6d23b..c2409661a6e2f 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -6592,9 +6592,7 @@ def find_isinstance_check_helper( return {}, {} if literal(expr) == LITERAL_TYPE: original_type = self.lookup_type(expr) - current_type = self.expr_checker.narrow_type_from_binder( - expr, original_type - ) + current_type = self.expr_checker.narrow_type_from_binder(expr, original_type) yes_type, no_type = self.conditional_types_with_intersection( current_type, self.get_isinstance_type(node.args[1]), expr ) @@ -6604,11 +6602,7 @@ def find_isinstance_check_helper( and is_subtype(current_type, yes_type, ignore_promotions=True) ): yes_type = current_type - return conditional_types_to_typemaps( - expr, - yes_type, - no_type, - ) + return conditional_types_to_typemaps(expr, yes_type, no_type) elif refers_to_fullname(node.callee, "builtins.issubclass"): if len(node.args) != 2: # the error will be reported elsewhere return {}, {} From 8fc57a23b212c40173e163583cd0e20c08e12673 Mon Sep 17 00:00:00 2001 From: Khanh Le Date: Wed, 17 Jun 2026 12:44:25 -0500 Subject: [PATCH 3/3] Fix isinstance narrowing from Any --- mypy/checker.py | 1 + test-data/unit/check-isinstance.test | 9 +++++++++ 2 files changed, 10 insertions(+) diff --git a/mypy/checker.py b/mypy/checker.py index c2409661a6e2f..4c643bb4317b3 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -6599,6 +6599,7 @@ def find_isinstance_check_helper( if ( self.binder.get(expr) is not None and yes_type is not None + and not isinstance(get_proper_type(current_type), AnyType) and is_subtype(current_type, yes_type, ignore_promotions=True) ): yes_type = current_type diff --git a/test-data/unit/check-isinstance.test b/test-data/unit/check-isinstance.test index acd81839fcdc7..b63b5f6c8a6f4 100644 --- a/test-data/unit/check-isinstance.test +++ b/test-data/unit/check-isinstance.test @@ -3248,6 +3248,15 @@ def foo(x: object, t: type[Any]): reveal_type(x) # N: Revealed type is "Any" [builtins fixtures/isinstance.pyi] +[case testAssertIsinstanceAny] +from typing import Any + +def f(x: Any) -> str: + assert isinstance(x, str) + reveal_type(x) # N: Revealed type is "builtins.str" + return x +[builtins fixtures/isinstance.pyi] + [case testIsInstanceObject] # flags: --strict-equality --warn-unreachable from typing import Any