|
3 | 3 | from __future__ import annotations |
4 | 4 |
|
5 | 5 | from collections.abc import Iterable, Sequence |
6 | | -from typing import TYPE_CHECKING, Final |
| 6 | +from typing import TYPE_CHECKING, Final, cast |
| 7 | +from typing_extensions import TypeGuard |
7 | 8 |
|
8 | 9 | import mypy.subtypes |
9 | 10 | import mypy.typeops |
@@ -340,6 +341,16 @@ def _infer_constraints( |
340 | 341 | if isinstance(actual, AnyType) and actual.type_of_any == TypeOfAny.suggestion_engine: |
341 | 342 | return [] |
342 | 343 |
|
| 344 | + # type[A | B] is always represented as type[A] | type[B] internally. |
| 345 | + # This makes our constraint solver choke on type[T] <: type[A] | type[B], |
| 346 | + # solving T as generic meet(A, B) which is often `object`. Force unwrap such unions |
| 347 | + # if both sides are type[...] or unions thereof. See `testTypeVarType` test |
| 348 | + type_type_unwrapped = False |
| 349 | + if _is_type_type(template) and _is_type_type(actual): |
| 350 | + type_type_unwrapped = True |
| 351 | + template = _unwrap_type_type(template) |
| 352 | + actual = _unwrap_type_type(actual) |
| 353 | + |
343 | 354 | # If the template is simply a type variable, emit a Constraint directly. |
344 | 355 | # We need to handle this case before handling Unions for two reasons: |
345 | 356 | # 1. "T <: Union[U1, U2]" is not equivalent to "T <: U1 or T <: U2", |
@@ -373,6 +384,11 @@ def _infer_constraints( |
373 | 384 | if direction == SUPERTYPE_OF and isinstance(actual, UnionType): |
374 | 385 | res = [] |
375 | 386 | for a_item in actual.items: |
| 387 | + # `orig_template` has to be preserved intact in case it's recursive. |
| 388 | + # If we unwraped ``type[...]`` previously, wrap the item back again, |
| 389 | + # as ``type[...]`` can't be removed from `orig_template`. |
| 390 | + if type_type_unwrapped: |
| 391 | + a_item = TypeType.make_normalized(a_item) |
376 | 392 | res.extend(infer_constraints(orig_template, a_item, direction)) |
377 | 393 | return res |
378 | 394 |
|
@@ -411,6 +427,26 @@ def _infer_constraints( |
411 | 427 | return template.accept(ConstraintBuilderVisitor(actual, direction, skip_neg_op)) |
412 | 428 |
|
413 | 429 |
|
| 430 | +def _is_type_type(tp: ProperType) -> TypeGuard[TypeType | UnionType]: |
| 431 | + """Is ``tp`` a ``type[...]`` or a union thereof? |
| 432 | +
|
| 433 | + ``Type[A | B]`` is internally represented as ``type[A] | type[B]``, and this |
| 434 | + troubles the solver sometimes. |
| 435 | + """ |
| 436 | + return ( |
| 437 | + isinstance(tp, TypeType) |
| 438 | + or isinstance(tp, UnionType) |
| 439 | + and all(isinstance(get_proper_type(o), TypeType) for o in tp.items) |
| 440 | + ) |
| 441 | + |
| 442 | + |
| 443 | +def _unwrap_type_type(tp: TypeType | UnionType) -> ProperType: |
| 444 | + """Extract the inner type from ``type[...]`` expression or a union thereof.""" |
| 445 | + if isinstance(tp, TypeType): |
| 446 | + return tp.item |
| 447 | + return UnionType.make_union([cast(TypeType, get_proper_type(o)).item for o in tp.items]) |
| 448 | + |
| 449 | + |
414 | 450 | def infer_constraints_if_possible( |
415 | 451 | template: Type, actual: Type, direction: int |
416 | 452 | ) -> list[Constraint] | None: |
|
0 commit comments