Skip to content

Commit 0967b8a

Browse files
authored
Fix crash in function with unpack and constrained TypeVar (#22107)
Fixes #21907 This fixes the crash by not normalizing callables too soon (note the `TypedDict` test passes on master, but I still add it to prevent similar regression there).
1 parent d22bde6 commit 0967b8a

4 files changed

Lines changed: 46 additions & 6 deletions

File tree

‎mypy/checker.py‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2295,7 +2295,12 @@ def expand_typevars(
22952295
result: list[tuple[FuncItem, CallableType]] = []
22962296
for substitutions in itertools.product(*subst):
22972297
mapping = dict(substitutions)
2298-
result.append((expand_func(defn, mapping), expand_type(typ, mapping)))
2298+
result.append(
2299+
(
2300+
expand_func(defn, mapping),
2301+
expand_type(typ, mapping, normalize_callables=False),
2302+
)
2303+
)
22992304
return result
23002305
else:
23012306
return [(defn, typ)]

‎mypy/expandtype.py‎

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,9 @@
5252

5353

5454
@overload
55-
def expand_type(typ: CallableType, env: Mapping[TypeVarId, Type]) -> CallableType: ...
55+
def expand_type(
56+
typ: CallableType, env: Mapping[TypeVarId, Type], normalize_callables: bool = True
57+
) -> CallableType: ...
5658

5759

5860
@overload
@@ -63,11 +65,13 @@ def expand_type(typ: ProperType, env: Mapping[TypeVarId, Type]) -> ProperType: .
6365
def expand_type(typ: Type, env: Mapping[TypeVarId, Type]) -> Type: ...
6466

6567

66-
def expand_type(typ: Type, env: Mapping[TypeVarId, Type]) -> Type:
68+
def expand_type(
69+
typ: Type, env: Mapping[TypeVarId, Type], normalize_callables: bool = True
70+
) -> Type:
6771
"""Substitute any type variable references in a type given by a type
6872
environment.
6973
"""
70-
return typ.accept(ExpandTypeVisitor(env))
74+
return typ.accept(ExpandTypeVisitor(env, normalize_callables))
7175

7276

7377
@overload
@@ -182,9 +186,15 @@ class ExpandTypeVisitor(TrivialSyntheticTypeTranslator):
182186

183187
variables: Mapping[TypeVarId, Type] # TypeVar id -> TypeVar value
184188

185-
def __init__(self, variables: Mapping[TypeVarId, Type]) -> None:
189+
def __init__(
190+
self, variables: Mapping[TypeVarId, Type], normalize_callables: bool = True
191+
) -> None:
186192
super().__init__()
187193
self.variables = variables
194+
# Usually we want normalized callables (therefore default is True), but there
195+
# are some cases when we don't. For example, when expanding callable with
196+
# type variables with values. We want to keep the shape matching argument structure.
197+
self.normalize_callables = normalize_callables
188198

189199
def visit_unbound_type(self, t: UnboundType) -> Type:
190200
return t
@@ -497,7 +507,7 @@ def visit_callable_type(self, t: CallableType) -> CallableType:
497507
type_is=t.type_is.accept(self) if t.type_is is not None else None,
498508
instance_type=instance_type,
499509
)
500-
if needs_normalization:
510+
if needs_normalization and self.normalize_callables:
501511
return expanded.with_normalized_var_args()
502512
return expanded
503513

‎test-data/unit/check-typeddict.test‎

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6148,3 +6148,18 @@ def test(f: Callable[[object], object]) -> None: ...
61486148
test(foo) # E: Argument 1 to "test" has incompatible type overloaded function; expected "Callable[[object], object]"
61496149
[builtins fixtures/dict.pyi]
61506150
[typing fixtures/typing-typeddict.pyi]
6151+
6152+
6153+
[case testTypeDictCallableUnpackExpansionNoCrash]
6154+
from typing import TypeVar, Unpack, Generic
6155+
from typing_extensions import TypedDict
6156+
6157+
class TD(TypedDict, Generic[T]):
6158+
x: int
6159+
y: T
6160+
6161+
T = TypeVar("T", int, str)
6162+
def foo(t: T, **kwargs: Unpack[TD[T]]) -> None:
6163+
reveal_type(kwargs) # N: Revealed type is "TypedDict('__main__.TD', {'x': builtins.int, 'y': builtins.int})" \
6164+
# N: Revealed type is "TypedDict('__main__.TD', {'x': builtins.int, 'y': builtins.str})"
6165+
[builtins fixtures/dict.pyi]

‎test-data/unit/check-typevar-tuple.test‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3216,6 +3216,16 @@ reveal_type(mix2(b2)) # N: Revealed type is "tuple[Any, tuple[builtins.int, bui
32163216
reveal_type(mix2(b3)) # N: Revealed type is "tuple[builtins.int, tuple[Any, builtins.str]]"
32173217
[builtins fixtures/tuple.pyi]
32183218

3219+
[case testTupleTypeCallableUnpackExpansionNoCrash]
3220+
from typing import TypeVar, Unpack
3221+
3222+
T = TypeVar("T", int, str)
3223+
3224+
def foo(*args: Unpack[tuple[int, T]]) -> None:
3225+
reveal_type(args) # N: Revealed type is "tuple[builtins.int, builtins.int]" \
3226+
# N: Revealed type is "tuple[builtins.int, builtins.str]"
3227+
[builtins fixtures/tuple.pyi]
3228+
32193229
[case testTypeVarTupleOverloadNoCrash]
32203230
from typing import Any, overload, Callable
32213231
from typing_extensions import TypeVarTuple, Unpack

0 commit comments

Comments
 (0)