Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions mypy/meet.py
Original file line number Diff line number Diff line change
Expand Up @@ -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, 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,
Expand Down Expand Up @@ -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):
Expand Down
25 changes: 25 additions & 0 deletions test-data/unit/check-typeform.test
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Loading