Skip to content

Commit 26c79d5

Browse files
committed
Fix two main methods where typevar defaults are modified inplace
1 parent 4980ae5 commit 26c79d5

File tree

5 files changed

+146
-28
lines changed

5 files changed

+146
-28
lines changed

mypy/applytype.py

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,8 @@ def apply_generic_arguments(
9090
report_incompatible_typevar_value: Callable[[CallableType, Type, str, Context], None],
9191
context: Context,
9292
skip_unsatisfied: bool = False,
93+
*,
94+
prefer_defaults: bool = False,
9395
) -> CallableType:
9496
"""Apply generic type arguments to a callable type.
9597
@@ -100,6 +102,9 @@ def apply_generic_arguments(
100102
101103
If `skip_unsatisfied` is True, then just skip the types that don't satisfy type variable
102104
bound or constraints, instead of giving an error.
105+
106+
If `prefer_defaults` is True, we will use defaults of all variables that have one
107+
instead of making the result generic in those.
103108
"""
104109
tvars = callable.variables
105110
assert len(orig_types) <= len(tvars)
@@ -140,10 +145,6 @@ def apply_generic_arguments(
140145
callable = expand_type(callable, id_to_type)
141146
assert isinstance(callable, CallableType)
142147
return callable.copy_modified(variables=[tv for tv in tvars if tv.id not in id_to_type])
143-
else:
144-
callable = callable.copy_modified(
145-
arg_types=[expand_type(at, id_to_type) for at in callable.arg_types]
146-
)
147148

148149
# Apply arguments to TypeGuard and TypeIs if any.
149150
if callable.type_guard is not None:
@@ -167,12 +168,16 @@ def apply_generic_arguments(
167168
remaining_tvars.append(tv)
168169
continue
169170
# TypeVarLike isn't in id_to_type mapping.
170-
# Only expand the TypeVar default here.
171-
typ = expand_type(tv, id_to_type)
172-
assert isinstance(typ, TypeVarLikeType)
173-
remaining_tvars.append(typ)
171+
if prefer_defaults:
172+
# We were asked to avoid polymorphic results as much as possible.
173+
id_to_type[tv.id] = expand_type(tv.default, id_to_type)
174+
else:
175+
# Only expand the TypeVar default here.
176+
typ = tv.copy_modified(id=tv.id, default=expand_type(tv.default, id_to_type))
177+
remaining_tvars.append(typ)
174178

175179
return callable.copy_modified(
180+
arg_types=[expand_type(at, id_to_type) for at in callable.arg_types],
176181
ret_type=expand_type(callable.ret_type, id_to_type),
177182
variables=remaining_tvars,
178183
type_guard=type_guard,

mypy/checkexpr.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3292,6 +3292,8 @@ def apply_generic_arguments(
32923292
types: Sequence[Type | None],
32933293
context: Context,
32943294
skip_unsatisfied: bool = False,
3295+
*,
3296+
prefer_defaults: bool = False,
32953297
) -> CallableType:
32963298
"""Simple wrapper around mypy.applytype.apply_generic_arguments."""
32973299
return applytype.apply_generic_arguments(
@@ -3300,6 +3302,7 @@ def apply_generic_arguments(
33003302
self.msg.incompatible_typevar_value,
33013303
context,
33023304
skip_unsatisfied=skip_unsatisfied,
3305+
prefer_defaults=prefer_defaults,
33033306
)
33043307

33053308
def check_any_type_call(self, args: list[Expression], callee: Type) -> tuple[Type, Type]:
@@ -5021,7 +5024,9 @@ def apply_type_arguments_to_callable(
50215024
min_arg_count, len(type_vars), len(args), ctx
50225025
)
50235026
return AnyType(TypeOfAny.from_error)
5024-
return self.apply_generic_arguments(tp, self.split_for_callable(tp, args, ctx), ctx)
5027+
return self.apply_generic_arguments(
5028+
tp, self.split_for_callable(tp, args, ctx), ctx, prefer_defaults=True
5029+
)
50255030
if isinstance(tp, Overloaded):
50265031
for it in tp.items:
50275032
if tp.is_type_obj():
@@ -5040,7 +5045,9 @@ def apply_type_arguments_to_callable(
50405045
return AnyType(TypeOfAny.from_error)
50415046
return Overloaded(
50425047
[
5043-
self.apply_generic_arguments(it, self.split_for_callable(it, args, ctx), ctx)
5048+
self.apply_generic_arguments(
5049+
it, self.split_for_callable(it, args, ctx), ctx, prefer_defaults=True
5050+
)
50445051
for it in tp.items
50455052
]
50465053
)

mypy/tvar_scope.py

Lines changed: 23 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -10,31 +10,39 @@
1010
from mypy.types import (
1111
ParamSpecFlavor,
1212
ParamSpecType,
13+
TrivialSyntheticTypeTranslator,
14+
TypeAliasType,
1315
TypeVarId,
1416
TypeVarLikeType,
1517
TypeVarTupleType,
1618
TypeVarType,
1719
)
18-
from mypy.typetraverser import TypeTraverserVisitor
1920

2021

21-
class TypeVarLikeNamespaceSetter(TypeTraverserVisitor):
22+
class TypeVarLikeNamespaceSetter(TrivialSyntheticTypeTranslator):
2223
"""Set namespace for all TypeVarLikeTypes types."""
2324

2425
def __init__(self, namespace: str) -> None:
26+
super().__init__()
2527
self.namespace = namespace
2628

27-
def visit_type_var(self, t: TypeVarType) -> None:
28-
t.id.namespace = self.namespace
29-
super().visit_type_var(t)
29+
def visit_type_var(self, t: TypeVarType) -> TypeVarType:
30+
return t.copy_modified(
31+
id=TypeVarId(raw_id=t.id.raw_id, meta_level=t.id.meta_level, namespace=self.namespace)
32+
)
3033

31-
def visit_param_spec(self, t: ParamSpecType) -> None:
32-
t.id.namespace = self.namespace
33-
return super().visit_param_spec(t)
34+
def visit_param_spec(self, t: ParamSpecType) -> ParamSpecType:
35+
return t.copy_modified(
36+
id=TypeVarId(raw_id=t.id.raw_id, meta_level=t.id.meta_level, namespace=self.namespace)
37+
)
3438

35-
def visit_type_var_tuple(self, t: TypeVarTupleType) -> None:
36-
t.id.namespace = self.namespace
37-
super().visit_type_var_tuple(t)
39+
def visit_type_var_tuple(self, t: TypeVarTupleType) -> TypeVarTupleType:
40+
return t.copy_modified(
41+
id=TypeVarId(raw_id=t.id.raw_id, meta_level=t.id.meta_level, namespace=self.namespace)
42+
)
43+
44+
def visit_type_alias_type(self, t: TypeAliasType, /) -> TypeAliasType:
45+
return t
3846

3947

4048
class TypeVarLikeScope:
@@ -106,7 +114,7 @@ def bind_new(self, name: str, tvar_expr: TypeVarLikeExpr) -> TypeVarLikeType:
106114
self.func_id -= 1
107115
i = self.func_id
108116
namespace = self.namespace
109-
tvar_expr.default.accept(TypeVarLikeNamespaceSetter(namespace))
117+
default = tvar_expr.default.accept(TypeVarLikeNamespaceSetter(namespace))
110118

111119
if isinstance(tvar_expr, TypeVarExpr):
112120
tvar_def: TypeVarLikeType = TypeVarType(
@@ -115,7 +123,7 @@ def bind_new(self, name: str, tvar_expr: TypeVarLikeExpr) -> TypeVarLikeType:
115123
id=TypeVarId(i, namespace=namespace),
116124
values=tvar_expr.values,
117125
upper_bound=tvar_expr.upper_bound,
118-
default=tvar_expr.default,
126+
default=default,
119127
variance=tvar_expr.variance,
120128
line=tvar_expr.line,
121129
column=tvar_expr.column,
@@ -127,7 +135,7 @@ def bind_new(self, name: str, tvar_expr: TypeVarLikeExpr) -> TypeVarLikeType:
127135
id=TypeVarId(i, namespace=namespace),
128136
flavor=ParamSpecFlavor.BARE,
129137
upper_bound=tvar_expr.upper_bound,
130-
default=tvar_expr.default,
138+
default=default,
131139
line=tvar_expr.line,
132140
column=tvar_expr.column,
133141
)
@@ -138,7 +146,7 @@ def bind_new(self, name: str, tvar_expr: TypeVarLikeExpr) -> TypeVarLikeType:
138146
id=TypeVarId(i, namespace=namespace),
139147
upper_bound=tvar_expr.upper_bound,
140148
tuple_fallback=tvar_expr.tuple_fallback,
141-
default=tvar_expr.default,
149+
default=default,
142150
line=tvar_expr.line,
143151
column=tvar_expr.column,
144152
)

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

Lines changed: 100 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,7 @@ T3 = TypeVar("T3", bytes, str, default=str)
159159
P1 = ParamSpec("P1", default=[int, str])
160160
Ts1 = TypeVarTuple("Ts1", default=Unpack[Tuple[int, str]])
161161

162-
def callback1(x: str) -> None: ...
162+
def callback1(x: int) -> None: ...
163163

164164
def func_a1(x: Union[int, T1]) -> T1: ...
165165
reveal_type(func_a1(2)) # N: Revealed type is "builtins.str"
@@ -175,15 +175,112 @@ reveal_type(func_a3(2)) # N: Revealed type is "builtins.str"
175175
def func_a4(x: Union[int, T3]) -> T3: ...
176176
reveal_type(func_a4(2)) # N: Revealed type is "builtins.str"
177177

178+
def func_a5(x: Union[int, Callable[[T1], None]]) -> Callable[[T1], None]: ...
179+
reveal_type(func_a5(callback1)) # N: Revealed type is "def (builtins.int)"
180+
reveal_type(func_a5(2)) # N: Revealed type is "def (builtins.str)"
181+
178182
def func_b1(x: Union[int, Callable[P1, None]]) -> Callable[P1, None]: ...
179-
reveal_type(func_b1(callback1)) # N: Revealed type is "def (x: builtins.str)"
183+
reveal_type(func_b1(callback1)) # N: Revealed type is "def (x: builtins.int)"
180184
reveal_type(func_b1(2)) # N: Revealed type is "def (builtins.int, builtins.str)"
181185

182186
def func_c1(x: Union[int, Callable[[Unpack[Ts1]], None]]) -> Tuple[Unpack[Ts1]]: ...
183-
# reveal_type(func_c1(callback1)) # Revealed type is "Tuple[str]" # TODO
187+
# reveal_type(func_c1(callback1)) # Revealed type is "Tuple[int]" # TODO
184188
reveal_type(func_c1(2)) # N: Revealed type is "tuple[builtins.int, builtins.str]"
185189
[builtins fixtures/tuple.pyi]
186190

191+
[case testTypeVarDefaultsClassInit]
192+
from typing import Generic, TypeVar, ParamSpec, List, Union, Callable, Tuple
193+
from typing_extensions import TypeVarTuple, Unpack
194+
195+
T1 = TypeVar("T1", default=str)
196+
T2 = TypeVar("T2", bound=str, default=str)
197+
T3 = TypeVar("T3", bytes, str, default=str)
198+
P1 = ParamSpec("P1", default=[int, str])
199+
Ts1 = TypeVarTuple("Ts1", default=Unpack[Tuple[int, str]])
200+
201+
def callback1(x: int) -> None: ...
202+
203+
class A1(Generic[T1]):
204+
def __init__(self, x: Union[int, T1]) -> None: ...
205+
206+
reveal_type(A1(2)) # N: Revealed type is "__main__.A1[builtins.str]"
207+
reveal_type(A1(2.1)) # N: Revealed type is "__main__.A1[builtins.float]"
208+
209+
class A2(Generic[T2]):
210+
def __init__(self, x: Union[int, T2]) -> None: ...
211+
reveal_type(A2(2)) # N: Revealed type is "__main__.A2[builtins.str]"
212+
213+
class A3(Generic[T3]):
214+
def __init__(self, x: Union[int, T3]) -> None: ...
215+
reveal_type(A3(2)) # N: Revealed type is "__main__.A3[builtins.str]"
216+
217+
class A4(Generic[T2]):
218+
def __init__(self, x: Union[int, Callable[[T1], None]]) -> None: ...
219+
reveal_type(A4(callback1)) # N: Revealed type is "__main__.A4[builtins.str]"
220+
reveal_type(A4(2)) # N: Revealed type is "__main__.A4[builtins.str]"
221+
222+
class B1(Generic[P1]):
223+
def __init__(self, x: Union[int, Callable[P1, None]]) -> None: ...
224+
reveal_type(B1(callback1)) # N: Revealed type is "__main__.B1[[x: builtins.int]]"
225+
reveal_type(B1(2)) # N: Revealed type is "__main__.B1[[builtins.int, builtins.str]]"
226+
227+
class C1(Generic[Unpack[Ts1]]):
228+
def __init__(self, x: Union[int, Callable[[Unpack[Ts1]], None]]) -> None: ...
229+
reveal_type(C1(callback1)) # Revealed type is "Tuple[int]" # TODO # N: Revealed type is "__main__.C1[builtins.int]"
230+
reveal_type(C1(2)) # N: Revealed type is "__main__.C1[builtins.int, builtins.str]"
231+
[builtins fixtures/tuple.pyi]
232+
233+
[case testTypeVarDefaultsInTypeApplications]
234+
from typing import Generic
235+
from typing_extensions import TypeVar
236+
237+
T1 = TypeVar("T1")
238+
T2 = TypeVar("T2", default=T1)
239+
240+
class MyClass(Generic[T1, T2]): ...
241+
242+
reveal_type(MyClass[int]) # N: Revealed type is "def () -> __main__.MyClass[builtins.int, builtins.int]"
243+
244+
# `T2` now defaults to `int` for `MyClass` from now on
245+
reveal_type(MyClass[str]) # N: Revealed type is "def () -> __main__.MyClass[builtins.str, builtins.str]"
246+
reveal_type(MyClass[bytes]) # N: Revealed type is "def () -> __main__.MyClass[builtins.bytes, builtins.bytes]"
247+
248+
c_int: type[MyClass[int]]
249+
c_str: type[MyClass[str]]
250+
a_str = MyClass[str]()
251+
252+
reveal_type(c_int) # N: Revealed type is "type[__main__.MyClass[builtins.int, builtins.int]]"
253+
reveal_type(c_str) # N: Revealed type is "type[__main__.MyClass[builtins.str, builtins.str]]"
254+
reveal_type(a_str) # N: Revealed type is "__main__.MyClass[builtins.str, builtins.str]"
255+
256+
class MyClass2(Generic[T1, T2]):
257+
def __init__(self, t: T2) -> None: ...
258+
259+
reveal_type(MyClass2[int]) # N: Revealed type is "def (t: builtins.int) -> __main__.MyClass2[builtins.int, builtins.int]"
260+
261+
# `T2` now defaults to `int` for `MyClass` from now on
262+
reveal_type(MyClass2[str]) # N: Revealed type is "def (t: builtins.str) -> __main__.MyClass2[builtins.str, builtins.str]"
263+
reveal_type(MyClass2[bytes]) # N: Revealed type is "def (t: builtins.bytes) -> __main__.MyClass2[builtins.bytes, builtins.bytes]"
264+
265+
d_int: type[MyClass2[int]]
266+
d_str: type[MyClass2[str]]
267+
268+
reveal_type(d_int) # N: Revealed type is "type[__main__.MyClass2[builtins.int, builtins.int]]"
269+
reveal_type(d_str) # N: Revealed type is "type[__main__.MyClass2[builtins.str, builtins.str]]"
270+
[builtins fixtures/tuple.pyi]
271+
272+
[case testDefaultsApplicationInAliasNoCrash]
273+
# https://github.com/python/mypy/issues/19186
274+
from typing import Generic, TypeVar, TypeAlias
275+
276+
T1 = TypeVar("T1")
277+
T2 = TypeVar("T2", default=T1)
278+
279+
Alias: TypeAlias = "MyClass[T1, T2]"
280+
281+
class MyClass(Generic["T1", "T2"]): ...
282+
[builtins fixtures/tuple.pyi]
283+
187284
[case testTypeVarDefaultsClass1]
188285
# flags: --disallow-any-generics
189286
from typing import Generic, TypeVar, Union, overload

test-data/unit/lib-stub/typing.pyi

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ NoReturn = 0
2828
Never = 0
2929
NewType = 0
3030
ParamSpec = 0
31+
TypeAlias = 0
3132
TypeVarTuple = 0
3233
Unpack = 0
3334
Self = 0

0 commit comments

Comments
 (0)