From 621c0c203913211c18e744d6dd34dce60da93a02 Mon Sep 17 00:00:00 2001 From: Dextheking1 Date: Tue, 22 Sep 2026 22:39:05 +0200 Subject: [PATCH 1/2] Fix false positive unreachable error for isinstance(x, UnionType) on TypeForm With --warn-unreachable, mypy reported 'Statement is unreachable' for isinstance(t, UnionType) when t: TypeForm[Any]. This is a false positive: at runtime a type expression like int | str is an instance of types.UnionType, so the check can succeed. Teach is_overlapping_types that a TypeForm whose item can be a union (Any, a union, or an unconstrained type variable) overlaps with types.UnionType. TypeForm[int] and friends remain correctly unreachable, and the bare-Union isinstance case is untouched. Fixes #21910. --- mypy/meet.py | 29 +++++++++++++++++++++++++++++ test-data/unit/check-typeform.test | 25 +++++++++++++++++++++++++ 2 files changed, 54 insertions(+) diff --git a/mypy/meet.py b/mypy/meet.py index bfc6d88a1e209..13f950bf92c19 100644 --- a/mypy/meet.py +++ b/mypy/meet.py @@ -348,6 +348,23 @@ def are_related_types( ) +def _type_form_item_can_be_union(item: Type) -> bool: + """Can a value of TypeForm[item] be an instance of types.UnionType? + + A type expression is a types.UnionType instance at runtime exactly when it + is a union expression such as `int | str`. + """ + item = get_proper_type(item) + if isinstance(item, AnyType) or isinstance(item, UnionType): + return True + if isinstance(item, TypeVarType): + if item.values: + return any(_type_form_item_can_be_union(v) for v in item.values) + # An unconstrained type variable can be bound to a union. + return True + return False + + def is_overlapping_types( left: Type, right: Type, @@ -522,6 +539,18 @@ def _type_object_overlap(left: Type, right: Type) -> bool: # 1. Type[C] vs Callable[..., C] overlap even if the latter is not class object. if isinstance(left, TypeType) and isinstance(right, CallableType): return _is_overlapping_types(left.item, right.ret_type) + # 2b. TypeForm[T] vs types.UnionType: a type expression like `int | str` + # is an instance of types.UnionType at runtime, so e.g. + # `isinstance(t, UnionType)` where `t: TypeForm[Any]` can be true. + # (Checked before case 2 below, which would otherwise return False.) + if ( + isinstance(left, TypeType) + and left.is_type_form + and isinstance(right, Instance) + and right.type.fullname == "types.UnionType" + and _type_form_item_can_be_union(left.item) + ): + return True # 2. Type[C] vs Meta, where Meta is a metaclass for C. if isinstance(left, TypeType) and isinstance(right, Instance): if isinstance(left.item, Instance): diff --git a/test-data/unit/check-typeform.test b/test-data/unit/check-typeform.test index d08826e233f76..826d9c412a00e 100644 --- a/test-data/unit/check-typeform.test +++ b/test-data/unit/check-typeform.test @@ -808,3 +808,28 @@ from typing import Optional, TypeForm typx: TypeForm = Optional # E: Incompatible types in assignment (expression has type "int", variable has type "TypeForm[Any]") [builtins fixtures/primitives.pyi] [typing fixtures/typing-full.pyi] + + +-- isinstance() with types.UnionType and TypeForm (see #21910) + +[case testTypeFormIsinstanceUnionType] +# flags: --warn-unreachable +from types import UnionType +from typing import Any +from typing_extensions import TypeForm + +def f(t: TypeForm[Any]) -> None: + if isinstance(t, UnionType): + reveal_type(t) # N: Revealed type is "types.UnionType" + else: + reveal_type(t) # N: Revealed type is "TypeForm[Any]" + +def g(t: TypeForm[int | str]) -> None: + if isinstance(t, UnionType): + reveal_type(t) # N: Revealed type is "types.UnionType" + +def h(t: TypeForm[int]) -> None: + if isinstance(t, UnionType): + pass # E: Statement is unreachable +[builtins fixtures/isinstance_python3_10.pyi] +[typing fixtures/typing-full.pyi] From 8d3ee96f8bc645a537053500a5f5f0e22485fbd5 Mon Sep 17 00:00:00 2001 From: Dextheking1 Date: Wed, 23 Sep 2026 11:58:09 +0200 Subject: [PATCH 2/2] fix: merge isinstance calls for ruff SIM101 --- mypy/meet.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mypy/meet.py b/mypy/meet.py index 13f950bf92c19..6f48b8f186232 100644 --- a/mypy/meet.py +++ b/mypy/meet.py @@ -355,7 +355,7 @@ def _type_form_item_can_be_union(item: Type) -> bool: is a union expression such as `int | str`. """ item = get_proper_type(item) - if isinstance(item, AnyType) or isinstance(item, UnionType): + if isinstance(item, (AnyType, UnionType)): return True if isinstance(item, TypeVarType): if item.values: