Skip to content

Commit 9be49b3

Browse files
authored
Prevent crashing when match arms use name of existing callable (#18449)
Fixes #16793. Fixes crash in #13666. Previously mypy considered that variables in match/case patterns must be Var's, causing a hard crash when a name of captured pattern clashes with a name of some existing function. This PR removes such assumption about Var and allows other nodes.
1 parent 469b4e4 commit 9be49b3

File tree

2 files changed

+64
-6
lines changed

2 files changed

+64
-6
lines changed

mypy/checker.py

+13-6
Original file line numberDiff line numberDiff line change
@@ -5402,17 +5402,21 @@ def _get_recursive_sub_patterns_map(
54025402

54035403
return sub_patterns_map
54045404

5405-
def infer_variable_types_from_type_maps(self, type_maps: list[TypeMap]) -> dict[Var, Type]:
5406-
all_captures: dict[Var, list[tuple[NameExpr, Type]]] = defaultdict(list)
5405+
def infer_variable_types_from_type_maps(
5406+
self, type_maps: list[TypeMap]
5407+
) -> dict[SymbolNode, Type]:
5408+
# Type maps may contain variables inherited from previous code which are not
5409+
# necessary `Var`s (e.g. a function defined earlier with the same name).
5410+
all_captures: dict[SymbolNode, list[tuple[NameExpr, Type]]] = defaultdict(list)
54075411
for tm in type_maps:
54085412
if tm is not None:
54095413
for expr, typ in tm.items():
54105414
if isinstance(expr, NameExpr):
54115415
node = expr.node
5412-
assert isinstance(node, Var)
5416+
assert node is not None
54135417
all_captures[node].append((expr, typ))
54145418

5415-
inferred_types: dict[Var, Type] = {}
5419+
inferred_types: dict[SymbolNode, Type] = {}
54165420
for var, captures in all_captures.items():
54175421
already_exists = False
54185422
types: list[Type] = []
@@ -5436,16 +5440,19 @@ def infer_variable_types_from_type_maps(self, type_maps: list[TypeMap]) -> dict[
54365440
new_type = UnionType.make_union(types)
54375441
# Infer the union type at the first occurrence
54385442
first_occurrence, _ = captures[0]
5443+
# If it didn't exist before ``match``, it's a Var.
5444+
assert isinstance(var, Var)
54395445
inferred_types[var] = new_type
54405446
self.infer_variable_type(var, first_occurrence, new_type, first_occurrence)
54415447
return inferred_types
54425448

5443-
def remove_capture_conflicts(self, type_map: TypeMap, inferred_types: dict[Var, Type]) -> None:
5449+
def remove_capture_conflicts(
5450+
self, type_map: TypeMap, inferred_types: dict[SymbolNode, Type]
5451+
) -> None:
54445452
if type_map:
54455453
for expr, typ in list(type_map.items()):
54465454
if isinstance(expr, NameExpr):
54475455
node = expr.node
5448-
assert isinstance(node, Var)
54495456
if node not in inferred_types or not is_subtype(typ, inferred_types[node]):
54505457
del type_map[expr]
54515458

test-data/unit/check-python310.test

+51
Original file line numberDiff line numberDiff line change
@@ -2471,3 +2471,54 @@ def nested_in_dict(d: dict[str, Any]) -> int:
24712471
return 0
24722472

24732473
[builtins fixtures/dict.pyi]
2474+
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]

0 commit comments

Comments
 (0)