Skip to content

Commit 0160d1d

Browse files
committed
Merge remote-tracking branch 'upstream/master' into bugfix/st-synthetic-named-expr-in-match
2 parents 2da85ea + 9be49b3 commit 0160d1d

File tree

4 files changed

+148
-7
lines changed

4 files changed

+148
-7
lines changed

mypy/checker.py

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5429,17 +5429,21 @@ def _get_recursive_sub_patterns_map(
54295429

54305430
return sub_patterns_map
54315431

5432-
def infer_variable_types_from_type_maps(self, type_maps: list[TypeMap]) -> dict[Var, Type]:
5433-
all_captures: dict[Var, list[tuple[NameExpr, Type]]] = defaultdict(list)
5432+
def infer_variable_types_from_type_maps(
5433+
self, type_maps: list[TypeMap]
5434+
) -> dict[SymbolNode, Type]:
5435+
# Type maps may contain variables inherited from previous code which are not
5436+
# necessary `Var`s (e.g. a function defined earlier with the same name).
5437+
all_captures: dict[SymbolNode, list[tuple[NameExpr, Type]]] = defaultdict(list)
54345438
for tm in type_maps:
54355439
if tm is not None:
54365440
for expr, typ in tm.items():
54375441
if isinstance(expr, NameExpr):
54385442
node = expr.node
5439-
assert isinstance(node, Var)
5443+
assert node is not None
54405444
all_captures[node].append((expr, typ))
54415445

5442-
inferred_types: dict[Var, Type] = {}
5446+
inferred_types: dict[SymbolNode, Type] = {}
54435447
for var, captures in all_captures.items():
54445448
already_exists = False
54455449
types: list[Type] = []
@@ -5463,16 +5467,19 @@ def infer_variable_types_from_type_maps(self, type_maps: list[TypeMap]) -> dict[
54635467
new_type = UnionType.make_union(types)
54645468
# Infer the union type at the first occurrence
54655469
first_occurrence, _ = captures[0]
5470+
# If it didn't exist before ``match``, it's a Var.
5471+
assert isinstance(var, Var)
54665472
inferred_types[var] = new_type
54675473
self.infer_variable_type(var, first_occurrence, new_type, first_occurrence)
54685474
return inferred_types
54695475

5470-
def remove_capture_conflicts(self, type_map: TypeMap, inferred_types: dict[Var, Type]) -> None:
5476+
def remove_capture_conflicts(
5477+
self, type_map: TypeMap, inferred_types: dict[SymbolNode, Type]
5478+
) -> None:
54715479
if type_map:
54725480
for expr, typ in list(type_map.items()):
54735481
if isinstance(expr, NameExpr):
54745482
node = expr.node
5475-
assert isinstance(node, Var)
54765483
if node not in inferred_types or not is_subtype(typ, inferred_types[node]):
54775484
del type_map[expr]
54785485

mypy/constraints.py

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@
33
from __future__ import annotations
44

55
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
78

89
import mypy.subtypes
910
import mypy.typeops
@@ -340,6 +341,16 @@ def _infer_constraints(
340341
if isinstance(actual, AnyType) and actual.type_of_any == TypeOfAny.suggestion_engine:
341342
return []
342343

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+
343354
# If the template is simply a type variable, emit a Constraint directly.
344355
# We need to handle this case before handling Unions for two reasons:
345356
# 1. "T <: Union[U1, U2]" is not equivalent to "T <: U1 or T <: U2",
@@ -373,6 +384,11 @@ def _infer_constraints(
373384
if direction == SUPERTYPE_OF and isinstance(actual, UnionType):
374385
res = []
375386
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)
376392
res.extend(infer_constraints(orig_template, a_item, direction))
377393
return res
378394

@@ -411,6 +427,26 @@ def _infer_constraints(
411427
return template.accept(ConstraintBuilderVisitor(actual, direction, skip_neg_op))
412428

413429

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+
414450
def infer_constraints_if_possible(
415451
template: Type, actual: Type, direction: int
416452
) -> list[Constraint] | None:

test-data/unit/check-python310.test

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2472,6 +2472,57 @@ def nested_in_dict(d: dict[str, Any]) -> int:
24722472

24732473
[builtins fixtures/dict.pyi]
24742474

2475+
[case testMatchRebindsOuterFunctionName]
2476+
# flags: --warn-unreachable
2477+
from typing_extensions import Literal
2478+
2479+
def x() -> tuple[Literal["test"]]: ...
2480+
2481+
match x():
2482+
case (x,) if x == "test": # E: Incompatible types in capture pattern (pattern captures type "Literal['test']", variable has type "Callable[[], Tuple[Literal['test']]]")
2483+
reveal_type(x) # N: Revealed type is "def () -> Tuple[Literal['test']]"
2484+
case foo:
2485+
foo
2486+
2487+
[builtins fixtures/dict.pyi]
2488+
2489+
[case testMatchRebindsInnerFunctionName]
2490+
# flags: --warn-unreachable
2491+
class Some:
2492+
value: int | str
2493+
__match_args__ = ("value",)
2494+
2495+
def fn1(x: Some | int | str) -> None:
2496+
match x:
2497+
case int():
2498+
def value():
2499+
return 1
2500+
reveal_type(value) # N: Revealed type is "def () -> Any"
2501+
case str():
2502+
def value():
2503+
return 1
2504+
reveal_type(value) # N: Revealed type is "def () -> Any"
2505+
case Some(value): # E: Incompatible types in capture pattern (pattern captures type "Union[int, str]", variable has type "Callable[[], Any]")
2506+
pass
2507+
2508+
def fn2(x: Some | int | str) -> None:
2509+
match x:
2510+
case int():
2511+
def value() -> str:
2512+
return ""
2513+
reveal_type(value) # N: Revealed type is "def () -> builtins.str"
2514+
case str():
2515+
def value() -> int: # E: All conditional function variants must have identical signatures \
2516+
# N: Original: \
2517+
# N: def value() -> str \
2518+
# N: Redefinition: \
2519+
# N: def value() -> int
2520+
return 1
2521+
reveal_type(value) # N: Revealed type is "def () -> builtins.str"
2522+
case Some(value): # E: Incompatible types in capture pattern (pattern captures type "Union[int, str]", variable has type "Callable[[], str]")
2523+
pass
2524+
[builtins fixtures/dict.pyi]
2525+
24752526
[case testMatchFunctionCall]
24762527
# flags: --warn-unreachable
24772528

test-data/unit/check-typevar-unbound.test

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,3 +69,50 @@ from typing import TypeVar
6969
T = TypeVar("T")
7070
def f(t: T) -> None:
7171
a, *b = t # E: "object" object is not iterable
72+
73+
[case testTypeVarType]
74+
from typing import Mapping, Type, TypeVar, Union
75+
T = TypeVar("T")
76+
77+
class A: ...
78+
class B: ...
79+
80+
lookup_table: Mapping[str, Type[Union[A,B]]]
81+
def load(lookup_table: Mapping[str, Type[T]], lookup_key: str) -> T:
82+
...
83+
reveal_type(load(lookup_table, "a")) # N: Revealed type is "Union[__main__.A, __main__.B]"
84+
85+
lookup_table_a: Mapping[str, Type[A]]
86+
def load2(lookup_table: Mapping[str, Type[Union[T, int]]], lookup_key: str) -> T:
87+
...
88+
reveal_type(load2(lookup_table_a, "a")) # N: Revealed type is "__main__.A"
89+
90+
[builtins fixtures/tuple.pyi]
91+
92+
[case testTypeVarTypeAssignment]
93+
# Adapted from https://github.com/python/mypy/issues/12115
94+
from typing import TypeVar, Type, Callable, Union, Any
95+
96+
t1: Type[bool] = bool
97+
t2: Union[Type[bool], Type[str]] = bool
98+
99+
T1 = TypeVar("T1", bound=Union[bool, str])
100+
def foo1(t: Type[T1]) -> None: ...
101+
foo1(t1)
102+
foo1(t2)
103+
104+
T2 = TypeVar("T2", bool, str)
105+
def foo2(t: Type[T2]) -> None: ...
106+
foo2(t1)
107+
# Rejected correctly: T2 cannot be Union[bool, str]
108+
foo2(t2) # E: Value of type variable "T2" of "foo2" cannot be "Union[bool, str]"
109+
110+
T3 = TypeVar("T3")
111+
def foo3(t: Type[T3]) -> None: ...
112+
foo3(t1)
113+
foo3(t2)
114+
115+
def foo4(t: Type[Union[bool, str]]) -> None: ...
116+
foo4(t1)
117+
foo4(t2)
118+
[builtins fixtures/tuple.pyi]

0 commit comments

Comments
 (0)