From 4c8b224851bd2109915c308ab8653b58a27fdf2b Mon Sep 17 00:00:00 2001 From: Khanh Le Date: Wed, 17 Jun 2026 12:25:33 -0500 Subject: [PATCH 1/4] 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/4] [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/4] 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 From 0b2c0c1f842170d2cb7a10328f8e706dadec1f8f Mon Sep 17 00:00:00 2001 From: Khanh Le Date: Thu, 18 Jun 2026 10:37:19 -0500 Subject: [PATCH 4/4] Revise isinstance narrowing with prior binder type --- mypy/checker.py | 44 ++++++++++++++++++++++++++-- mypy/ipc.py | 2 +- test-data/unit/check-isinstance.test | 9 ------ 3 files changed, 43 insertions(+), 12 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index 4c643bb4317b3..5df290b408f3f 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -6594,12 +6594,12 @@ def find_isinstance_check_helper( 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 + original_type, self.get_isinstance_type(node.args[1]), expr ) if ( self.binder.get(expr) is not None and yes_type is not None - and not isinstance(get_proper_type(current_type), AnyType) + and not has_any_type(current_type) and is_subtype(current_type, yes_type, ignore_promotions=True) ): yes_type = current_type @@ -9354,6 +9354,46 @@ def is_valid_inferred_type( return not typ.accept(InvalidInferredTypes()) +def has_any_type(typ: Type) -> bool: + return _has_any_type(typ, set()) + + +def _has_any_type(typ: Type, seen: set[int]) -> bool: + if id(typ) in seen: + return False + seen.add(id(typ)) + if isinstance(typ, TypeAliasType): + return any(_has_any_type(arg, seen) for arg in typ.args) + proper_type = get_proper_type(typ) + if isinstance(proper_type, AnyType): + return proper_type.type_of_any != TypeOfAny.special_form + if isinstance(proper_type, Instance): + return any(_has_any_type(arg, seen) for arg in proper_type.args) + if isinstance(proper_type, UnionType): + return any(_has_any_type(item, seen) for item in proper_type.items) + if isinstance(proper_type, TupleType): + return any(_has_any_type(item, seen) for item in proper_type.items) + if isinstance(proper_type, CallableType): + return any(_has_any_type(arg, seen) for arg in proper_type.arg_types) or _has_any_type( + proper_type.ret_type, seen + ) + if isinstance(proper_type, TypeType): + return _has_any_type(proper_type.item, seen) + if isinstance(proper_type, TypeVarType): + return any(_has_any_type(value, seen) for value in proper_type.values) or _has_any_type( + proper_type.upper_bound, seen + ) + if isinstance(proper_type, TypeVarTupleType): + return _has_any_type(proper_type.upper_bound, seen) + if isinstance(proper_type, TypedDictType): + return any(_has_any_type(item, seen) for item in proper_type.items.values()) + if isinstance(proper_type, Overloaded): + return any(_has_any_type(item, seen) for item in proper_type.items) + if isinstance(proper_type, UnpackType): + return _has_any_type(proper_type.type, seen) + return False + + class InvalidInferredTypes(BoolTypeQuery): """Find type components that are not valid for an inferred type. diff --git a/mypy/ipc.py b/mypy/ipc.py index 08ca0caf75f12..e02499302b000 100644 --- a/mypy/ipc.py +++ b/mypy/ipc.py @@ -340,7 +340,7 @@ def connection_name(self) -> str: # for AF_UNIX sockets return os.path.join(self.sock_directory, self.name) else: - name = self.sock.getsockname() + name: object = self.sock.getsockname() assert isinstance(name, str) return name diff --git a/test-data/unit/check-isinstance.test b/test-data/unit/check-isinstance.test index b63b5f6c8a6f4..acd81839fcdc7 100644 --- a/test-data/unit/check-isinstance.test +++ b/test-data/unit/check-isinstance.test @@ -3248,15 +3248,6 @@ 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