Skip to content

Commit 044d246

Browse files
committed
Fix 1
1 parent b9e9845 commit 044d246

File tree

7 files changed

+167
-19
lines changed

7 files changed

+167
-19
lines changed

mypy/exprtotype.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
Type,
3434
TypeList,
3535
TypeOfAny,
36+
TypeOfTypeList,
3637
UnboundType,
3738
UnionType,
3839
)
@@ -161,9 +162,12 @@ def expr_to_unanalyzed_type(
161162
else:
162163
raise TypeTranslationError()
163164
return CallableArgument(typ, name, arg_const, expr.line, expr.column)
164-
elif isinstance(expr, ListExpr):
165+
elif isinstance(expr, (ListExpr, TupleExpr)):
165166
return TypeList(
166167
[expr_to_unanalyzed_type(t, options, allow_new_syntax, expr) for t in expr.items],
168+
TypeOfTypeList.callable_args
169+
if isinstance(expr, ListExpr)
170+
else TypeOfTypeList.param_spec_defaults,
167171
line=expr.line,
168172
column=expr.column,
169173
)

mypy/message_registry.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,7 @@ def with_additional_msg(self, info: str) -> ErrorMessage:
179179
INVALID_TYPEVAR_ARG_BOUND: Final = 'Type argument {} of "{}" must be a subtype of {}'
180180
INVALID_TYPEVAR_ARG_VALUE: Final = 'Invalid type argument value for "{}"'
181181
TYPEVAR_VARIANCE_DEF: Final = 'TypeVar "{}" may only be a literal bool'
182-
TYPEVAR_ARG_MUST_BE_TYPE: Final = 'TypeVar "{}" must be a type'
182+
TYPEVAR_ARG_MUST_BE_TYPE: Final = '{} "{}" must be a type'
183183
TYPEVAR_UNEXPECTED_ARGUMENT: Final = 'Unexpected argument to "TypeVar()"'
184184
UNBOUND_TYPEVAR: Final = (
185185
"A function returning TypeVar should receive at least "

mypy/semanal.py

Lines changed: 54 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4096,12 +4096,14 @@ def process_typevar_parameters(
40964096
if has_values:
40974097
self.fail("TypeVar cannot have both values and an upper bound", context)
40984098
return None
4099-
tv_arg = self.get_typevarlike_argument(param_name, param_value, context)
4099+
tv_arg = self.get_typevarlike_argument("TypeVar", param_name, param_value, context)
41004100
if tv_arg is None:
41014101
return None
41024102
upper_bound = tv_arg
41034103
elif param_name == "default":
4104-
tv_arg = self.get_typevarlike_argument(param_name, param_value, context)
4104+
tv_arg = self.get_typevarlike_argument(
4105+
"TypeVar", param_name, param_value, context, allow_unbound_tvars=True
4106+
)
41054107
if tv_arg is None:
41064108
return None
41074109
default = tv_arg
@@ -4133,13 +4135,24 @@ def process_typevar_parameters(
41334135
return variance, upper_bound, default
41344136

41354137
def get_typevarlike_argument(
4136-
self, param_name: str, param_value: Expression, context: Context
4138+
self,
4139+
typevarlike_name: str,
4140+
param_name: str,
4141+
param_value: Expression,
4142+
context: Context,
4143+
*,
4144+
allow_unbound_tvars: bool = False,
4145+
allow_param_spec_literals: bool = False,
41374146
) -> ProperType | None:
41384147
try:
41394148
# We want to use our custom error message below, so we suppress
41404149
# the default error message for invalid types here.
41414150
analyzed = self.expr_to_analyzed_type(
4142-
param_value, allow_placeholder=True, report_invalid_types=False
4151+
param_value,
4152+
allow_placeholder=True,
4153+
report_invalid_types=False,
4154+
allow_unbound_tvars=allow_unbound_tvars,
4155+
allow_param_spec_literals=allow_param_spec_literals,
41434156
)
41444157
if analyzed is None:
41454158
# Type variables are special: we need to place them in the symbol table
@@ -4152,13 +4165,17 @@ def get_typevarlike_argument(
41524165
typ = get_proper_type(analyzed)
41534166
if isinstance(typ, AnyType) and typ.is_from_error:
41544167
self.fail(
4155-
message_registry.TYPEVAR_ARG_MUST_BE_TYPE.format(param_name), param_value
4168+
message_registry.TYPEVAR_ARG_MUST_BE_TYPE.format(typevarlike_name, param_name),
4169+
param_value,
41564170
)
41574171
# Note: we do not return 'None' here -- we want to continue
41584172
# using the AnyType as the upper bound.
41594173
return typ
41604174
except TypeTranslationError:
4161-
self.fail(message_registry.TYPEVAR_ARG_MUST_BE_TYPE.format(param_name), param_value)
4175+
self.fail(
4176+
message_registry.TYPEVAR_ARG_MUST_BE_TYPE.format(typevarlike_name, param_name),
4177+
param_value,
4178+
)
41624179
return None
41634180

41644181
def extract_typevarlike_name(self, s: AssignmentStmt, call: CallExpr) -> str | None:
@@ -4202,10 +4219,30 @@ def process_paramspec_declaration(self, s: AssignmentStmt) -> bool:
42024219
call.args[1 + n_values :], call.arg_names[1 + n_values :]
42034220
):
42044221
if param_name == "default":
4205-
tv_arg = self.get_typevarlike_argument(param_name, param_value, s)
4222+
tv_arg = self.get_typevarlike_argument(
4223+
"ParamSpec",
4224+
param_name,
4225+
param_value,
4226+
s,
4227+
allow_unbound_tvars=True,
4228+
allow_param_spec_literals=True,
4229+
)
42064230
if tv_arg is None:
42074231
return False
42084232
default = tv_arg
4233+
if isinstance(tv_arg, Parameters):
4234+
for i, arg_type in enumerate(tv_arg.arg_types):
4235+
typ = get_proper_type(arg_type)
4236+
if isinstance(typ, AnyType) and typ.is_from_error:
4237+
self.fail(
4238+
f"Argument {i} of ParamSpec default must be a type", param_value
4239+
)
4240+
elif not isinstance(default, (AnyType, UnboundType)):
4241+
self.fail(
4242+
"The default argument to ParamSpec must be a tuple expression, ellipsis, or a ParamSpec",
4243+
param_value,
4244+
)
4245+
default = AnyType(TypeOfAny.from_error)
42094246
else:
42104247
# ParamSpec is different from a regular TypeVar:
42114248
# arguments are not semantically valid. But, allowed in runtime.
@@ -4252,15 +4289,18 @@ def process_typevartuple_declaration(self, s: AssignmentStmt) -> bool:
42524289
call.args[1 + n_values :], call.arg_names[1 + n_values :]
42534290
):
42544291
if param_name == "default":
4255-
tv_arg = self.get_typevarlike_argument(param_name, param_value, s)
4292+
tv_arg = self.get_typevarlike_argument(
4293+
"TypeVarTuple", param_name, param_value, s, allow_unbound_tvars=True
4294+
)
42564295
if tv_arg is None:
42574296
return False
42584297
default = tv_arg
42594298
if not isinstance(default, UnpackType):
42604299
self.fail(
4261-
"The default argument to TypeVarTuple must be an Unpacked tuple", default
4300+
"The default argument to TypeVarTuple must be an Unpacked tuple",
4301+
param_value,
42624302
)
4263-
return False
4303+
default = AnyType(TypeOfAny.from_error)
42644304
else:
42654305
self.fail(
42664306
"The variance and bound arguments to TypeVarTuple do not have defined semantics yet",
@@ -6359,6 +6399,8 @@ def expr_to_analyzed_type(
63596399
report_invalid_types: bool = True,
63606400
allow_placeholder: bool = False,
63616401
allow_type_any: bool = False,
6402+
allow_unbound_tvars: bool = False,
6403+
allow_param_spec_literals: bool = False,
63626404
) -> Type | None:
63636405
if isinstance(expr, CallExpr):
63646406
# This is a legacy syntax intended mostly for Python 2, we keep it for
@@ -6387,6 +6429,8 @@ def expr_to_analyzed_type(
63876429
report_invalid_types=report_invalid_types,
63886430
allow_placeholder=allow_placeholder,
63896431
allow_type_any=allow_type_any,
6432+
allow_unbound_tvars=allow_unbound_tvars,
6433+
allow_param_spec_literals=allow_param_spec_literals,
63906434
)
63916435

63926436
def analyze_type_expr(self, expr: Expression) -> None:

mypy/typeanal.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@
7272
TypedDictType,
7373
TypeList,
7474
TypeOfAny,
75+
TypeOfTypeList,
7576
TypeQuery,
7677
TypeType,
7778
TypeVarLikeType,
@@ -890,10 +891,12 @@ def visit_type_list(self, t: TypeList) -> Type:
890891
else:
891892
return AnyType(TypeOfAny.from_error)
892893
else:
894+
s = "[...]" if t.list_type == TypeOfTypeList.callable_args else "(...)"
893895
self.fail(
894-
'Bracketed expression "[...]" is not valid as a type', t, code=codes.VALID_TYPE
896+
f'Bracketed expression "{s}" is not valid as a type', t, code=codes.VALID_TYPE
895897
)
896-
self.note('Did you mean "List[...]"?', t)
898+
if t.list_type == TypeOfTypeList.callable_args:
899+
self.note('Did you mean "List[...]"?', t)
897900
return AnyType(TypeOfAny.from_error)
898901

899902
def visit_callable_argument(self, t: CallableArgument) -> Type:

mypy/types.py

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,17 @@ class TypeOfAny:
196196
suggestion_engine: Final = 9
197197

198198

199+
class TypeOfTypeList:
200+
"""This class describes the different types of TypeList."""
201+
202+
__slots__ = ()
203+
204+
# List expressions for callable args
205+
callable_args: Final = 1
206+
# Tuple expressions for ParamSpec defaults
207+
param_spec_defaults: Final = 2
208+
209+
199210
def deserialize_type(data: JsonDict | str) -> Type:
200211
if isinstance(data, str):
201212
return Instance.deserialize(data)
@@ -991,13 +1002,20 @@ class TypeList(ProperType):
9911002
types before they are processed into Callable types.
9921003
"""
9931004

994-
__slots__ = ("items",)
1005+
__slots__ = ("items", "list_type")
9951006

9961007
items: list[Type]
9971008

998-
def __init__(self, items: list[Type], line: int = -1, column: int = -1) -> None:
1009+
def __init__(
1010+
self,
1011+
items: list[Type],
1012+
list_type: int = TypeOfTypeList.callable_args,
1013+
line: int = -1,
1014+
column: int = -1,
1015+
) -> None:
9991016
super().__init__(line, column)
10001017
self.items = items
1018+
self.list_type = list_type
10011019

10021020
def accept(self, visitor: TypeVisitor[T]) -> T:
10031021
assert isinstance(visitor, SyntheticTypeVisitor)
@@ -1011,7 +1029,11 @@ def __hash__(self) -> int:
10111029
return hash(tuple(self.items))
10121030

10131031
def __eq__(self, other: object) -> bool:
1014-
return isinstance(other, TypeList) and self.items == other.items
1032+
return (
1033+
isinstance(other, TypeList)
1034+
and self.items == other.items
1035+
and self.list_type == other.list_type
1036+
)
10151037

10161038

10171039
class UnpackType(ProperType):
@@ -3162,7 +3184,9 @@ def visit_callable_type(self, t: CallableType) -> str:
31623184
f"{var.name} <: {var.upper_bound.accept(self)}{f' = {var.default.accept(self)}' if var.has_default() else ''}"
31633185
)
31643186
else:
3165-
vs.append(var.name)
3187+
vs.append(
3188+
f"{var.name}{f' = {var.default.accept(self)}' if var.has_default() else ''}"
3189+
)
31663190
else:
31673191
# For other TypeVarLikeTypes, use the name and default
31683192
vs.append(
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
[case testTypeVarDefaultsBasic]
2+
import builtins
3+
from typing import Generic, TypeVar, ParamSpec, Callable, Tuple, List
4+
from typing_extensions import TypeVarTuple, Unpack
5+
6+
T1 = TypeVar("T1", default=int)
7+
P1 = ParamSpec("P1", default=(int, str))
8+
Ts1 = TypeVarTuple("Ts1", default=Unpack[Tuple[int, str]])
9+
10+
def f1(a: T1) -> List[T1]: ...
11+
reveal_type(f1) # N: Revealed type is "def [T1 = builtins.int] (a: T1`-1 = builtins.int) -> builtins.list[T1`-1 = builtins.int]"
12+
13+
def f2(a: Callable[P1, None] ) -> Callable[P1, None]: ...
14+
reveal_type(f2) # N: Revealed type is "def [P1 = [builtins.int, builtins.str]] (a: def (*P1.args, **P1.kwargs)) -> def (*P1.args, **P1.kwargs)"
15+
16+
def f3(a: Tuple[Unpack[Ts1]]) -> Tuple[Unpack[Ts1]]: ...
17+
reveal_type(f3) # N: Revealed type is "def [Ts1 = Unpack[Tuple[builtins.int, builtins.str]]] (a: Tuple[Unpack[Ts1`-1 = Unpack[Tuple[builtins.int, builtins.str]]]]) -> Tuple[Unpack[Ts1`-1 = Unpack[Tuple[builtins.int, builtins.str]]]]"
18+
19+
20+
class ClassA1(Generic[T1]): ...
21+
class ClassA2(Generic[P1]): ...
22+
class ClassA3(Generic[Unpack[Ts1]]): ...
23+
24+
reveal_type(ClassA1) # N: Revealed type is "def [T1 = builtins.int] () -> __main__.ClassA1[T1`1 = builtins.int]"
25+
reveal_type(ClassA2) # N: Revealed type is "def [P1 = [builtins.int, builtins.str]] () -> __main__.ClassA2[P1`1 = [builtins.int, builtins.str]]"
26+
reveal_type(ClassA3) # N: Revealed type is "def [Ts1 = Unpack[Tuple[builtins.int, builtins.str]]] () -> __main__.ClassA3[Unpack[Ts1`1 = Unpack[Tuple[builtins.int, builtins.str]]]]"
27+
[builtins fixtures/tuple.pyi]
28+
29+
[case testTypeVarDefaultsValid]
30+
from typing import TypeVar, ParamSpec, Any, List, Tuple
31+
from typing_extensions import TypeVarTuple, Unpack
32+
33+
S0 = TypeVar("S0")
34+
S1 = TypeVar("S1", bound=int)
35+
36+
P0 = ParamSpec("P0")
37+
Ts0 = TypeVarTuple("Ts0")
38+
39+
T1 = TypeVar("T1", default=int)
40+
T2 = TypeVar("T2", bound=float, default=int)
41+
T3 = TypeVar("T3", bound=List[Any], default=List[int])
42+
T4 = TypeVar("T4", int, str, default=int)
43+
T5 = TypeVar("T5", default=S0)
44+
T6 = TypeVar("T6", bound=float, default=S1)
45+
# T7 = TypeVar("T7", bound=List[Any], default=List[S0]) # TODO
46+
47+
P1 = ParamSpec("P1", default=())
48+
P2 = ParamSpec("P2", default=...)
49+
P3 = ParamSpec("P3", default=(int, str))
50+
P4 = ParamSpec("P4", default=P0)
51+
52+
Ts1 = TypeVarTuple("Ts1", default=Unpack[Tuple[int]])
53+
Ts2 = TypeVarTuple("Ts2", default=Unpack[Tuple[int, ...]])
54+
# Ts3 = TypeVarTuple("Ts3", default=Unpack[Ts0]) # TODO
55+
[builtins fixtures/tuple.pyi]
56+
57+
[case testTypeVarDefaultsInvalid]
58+
from typing import TypeVar, ParamSpec, Tuple
59+
from typing_extensions import TypeVarTuple, Unpack
60+
61+
T1 = TypeVar("T1", default=2) # E: TypeVar "default" must be a type
62+
T2 = TypeVar("T2", default=(int, str)) # E: Bracketed expression "(...)" is not valid as a type \
63+
# E: TypeVar "default" must be a type
64+
65+
P1 = ParamSpec("P1", default=int) # E: The default argument to ParamSpec must be a tuple expression, ellipsis, or a ParamSpec
66+
P2 = ParamSpec("P2", default=2) # E: ParamSpec "default" must be a type
67+
P3 = ParamSpec("P3", default=(2, int)) # E: Argument 0 of ParamSpec default must be a type
68+
69+
Ts1 = TypeVarTuple("Ts1", default=2) # E: TypeVarTuple "default" must be a type \
70+
# E: The default argument to TypeVarTuple must be an Unpacked tuple
71+
Ts2 = TypeVarTuple("Ts2", default=int) # E: The default argument to TypeVarTuple must be an Unpacked tuple
72+
Ts3 = TypeVarTuple("Ts3", default=Tuple[int]) # E: The default argument to TypeVarTuple must be an Unpacked tuple
73+
[builtins fixtures/tuple.pyi]

test-data/unit/semanal-errors.test

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1045,7 +1045,7 @@ c = TypeVar(1) # E: TypeVar() expects a string literal as first argument
10451045
T = TypeVar(b'T') # E: TypeVar() expects a string literal as first argument
10461046
d = TypeVar('D') # E: String argument 1 "D" to TypeVar(...) does not match variable name "d"
10471047
e = TypeVar('e', int, str, x=1) # E: Unexpected argument to "TypeVar()": "x"
1048-
f = TypeVar('f', (int, str), int) # E: Type expected
1048+
f = TypeVar('f', (int, str), int) # E: Bracketed expression "(...)" is not valid as a type
10491049
g = TypeVar('g', int) # E: TypeVar cannot have only a single constraint
10501050
h = TypeVar('h', x=(int, str)) # E: Unexpected argument to "TypeVar()": "x"
10511051
i = TypeVar('i', bound=1) # E: TypeVar "bound" must be a type

0 commit comments

Comments
 (0)