@@ -310,6 +310,15 @@ def not_declared_in_type_params(self, tvar_name: str) -> bool:
310
310
311
311
def visit_unbound_type_nonoptional (self , t : UnboundType , defining_literal : bool ) -> Type :
312
312
sym = self .lookup_qualified (t .name , t )
313
+ param_spec_name = None
314
+ if t .name .endswith ((".args" , ".kwargs" )):
315
+ param_spec_name = t .name .rsplit ("." , 1 )[0 ]
316
+ maybe_param_spec = self .lookup_qualified (param_spec_name , t )
317
+ if maybe_param_spec and isinstance (maybe_param_spec .node , ParamSpecExpr ):
318
+ sym = maybe_param_spec
319
+ else :
320
+ param_spec_name = None
321
+
313
322
if sym is not None :
314
323
node = sym .node
315
324
if isinstance (node , PlaceholderNode ):
@@ -362,17 +371,23 @@ def visit_unbound_type_nonoptional(self, t: UnboundType, defining_literal: bool)
362
371
if tvar_def is None :
363
372
if self .allow_unbound_tvars :
364
373
return t
374
+ name = param_spec_name or t .name
365
375
if self .defining_alias and self .not_declared_in_type_params (t .name ):
366
- msg = f'ParamSpec "{ t . name } " is not included in type_params'
376
+ msg = f'ParamSpec "{ name } " is not included in type_params'
367
377
else :
368
- msg = f'ParamSpec "{ t . name } " is unbound'
378
+ msg = f'ParamSpec "{ name } " is unbound'
369
379
self .fail (msg , t , code = codes .VALID_TYPE )
370
380
return AnyType (TypeOfAny .from_error )
371
381
assert isinstance (tvar_def , ParamSpecType )
372
382
if len (t .args ) > 0 :
373
383
self .fail (
374
384
f'ParamSpec "{ t .name } " used with arguments' , t , code = codes .VALID_TYPE
375
385
)
386
+ if param_spec_name is not None and not self .allow_param_spec_literals :
387
+ self .fail (
388
+ "ParamSpec components are not allowed here" , t , code = codes .VALID_TYPE
389
+ )
390
+ return AnyType (TypeOfAny .from_error )
376
391
# Change the line number
377
392
return ParamSpecType (
378
393
tvar_def .name ,
@@ -1113,46 +1128,57 @@ def visit_callable_type(
1113
1128
variables , _ = self .bind_function_type_variables (t , t )
1114
1129
type_guard = self .anal_type_guard (t .ret_type )
1115
1130
type_is = self .anal_type_is (t .ret_type )
1131
+
1116
1132
arg_kinds = t .arg_kinds
1117
- if len (arg_kinds ) >= 2 and arg_kinds [- 2 ] == ARG_STAR and arg_kinds [- 1 ] == ARG_STAR2 :
1118
- arg_types = self .anal_array (t .arg_types [:- 2 ], nested = nested ) + [
1119
- self .anal_star_arg_type (t .arg_types [- 2 ], ARG_STAR , nested = nested ),
1120
- self .anal_star_arg_type (t .arg_types [- 1 ], ARG_STAR2 , nested = nested ),
1121
- ]
1122
- # If nested is True, it means we are analyzing a Callable[...] type, rather
1123
- # than a function definition type. We need to "unpack" ** TypedDict annotation
1124
- # here (for function definitions it is done in semanal).
1125
- if nested and isinstance (arg_types [- 1 ], UnpackType ):
1133
+ arg_types = []
1134
+ param_spec_with_args = param_spec_with_kwargs = None
1135
+ param_spec_invalid = False
1136
+ for kind , ut in zip (arg_kinds , t .arg_types ):
1137
+ if kind == ARG_STAR :
1138
+ param_spec_with_args , at = self .anal_star_arg_type (ut , kind , nested = nested )
1139
+ elif kind == ARG_STAR2 :
1140
+ param_spec_with_kwargs , at = self .anal_star_arg_type (ut , kind , nested = nested )
1141
+ else :
1142
+ if param_spec_with_args :
1143
+ param_spec_invalid = True
1144
+ self .fail (
1145
+ "Arguments not allowed after ParamSpec.args" , t , code = codes .VALID_TYPE
1146
+ )
1147
+ at = self .anal_type (ut , nested = nested , allow_unpack = False )
1148
+ arg_types .append (at )
1149
+
1150
+ if nested and arg_types :
1151
+ # If we've got a Callable[[Unpack[SomeTypedDict]], None], make sure
1152
+ # Unpack is interpreted as `**` and not as `*`.
1153
+ last = arg_types [- 1 ]
1154
+ if isinstance (last , UnpackType ):
1126
1155
# TODO: it would be better to avoid this get_proper_type() call.
1127
- unpacked = get_proper_type (arg_types [- 1 ].type )
1128
- if isinstance (unpacked , TypedDictType ):
1129
- arg_types [- 1 ] = unpacked
1156
+ p_at = get_proper_type (last .type )
1157
+ if isinstance (p_at , TypedDictType ) and not last .from_star_syntax :
1158
+ # Automatically detect Unpack[Foo] in Callable as backwards
1159
+ # compatible syntax for **Foo, if Foo is a TypedDict.
1160
+ arg_kinds [- 1 ] = ARG_STAR2
1161
+ arg_types [- 1 ] = p_at
1130
1162
unpacked_kwargs = True
1131
- arg_types = self .check_unpacks_in_list (arg_types )
1132
- else :
1133
- star_index = None
1163
+ arg_types = self .check_unpacks_in_list (arg_types )
1164
+
1165
+ if not param_spec_invalid and param_spec_with_args != param_spec_with_kwargs :
1166
+ # If already invalid, do not report more errors - definition has
1167
+ # to be fixed anyway
1168
+ name = param_spec_with_args or param_spec_with_kwargs
1169
+ self .fail (
1170
+ f'ParamSpec must have "*args" typed as "{ name } .args" and "**kwargs" typed as "{ name } .kwargs"' ,
1171
+ t ,
1172
+ code = codes .VALID_TYPE ,
1173
+ )
1174
+ param_spec_invalid = True
1175
+
1176
+ if param_spec_invalid :
1134
1177
if ARG_STAR in arg_kinds :
1135
- star_index = arg_kinds .index (ARG_STAR )
1136
- star2_index = None
1178
+ arg_types [arg_kinds .index (ARG_STAR )] = AnyType (TypeOfAny .from_error )
1137
1179
if ARG_STAR2 in arg_kinds :
1138
- star2_index = arg_kinds .index (ARG_STAR2 )
1139
- arg_types = []
1140
- for i , ut in enumerate (t .arg_types ):
1141
- at = self .anal_type (
1142
- ut , nested = nested , allow_unpack = i in (star_index , star2_index )
1143
- )
1144
- if nested and isinstance (at , UnpackType ) and i == star_index :
1145
- # TODO: it would be better to avoid this get_proper_type() call.
1146
- p_at = get_proper_type (at .type )
1147
- if isinstance (p_at , TypedDictType ) and not at .from_star_syntax :
1148
- # Automatically detect Unpack[Foo] in Callable as backwards
1149
- # compatible syntax for **Foo, if Foo is a TypedDict.
1150
- at = p_at
1151
- arg_kinds [i ] = ARG_STAR2
1152
- unpacked_kwargs = True
1153
- arg_types .append (at )
1154
- if nested :
1155
- arg_types = self .check_unpacks_in_list (arg_types )
1180
+ arg_types [arg_kinds .index (ARG_STAR2 )] = AnyType (TypeOfAny .from_error )
1181
+
1156
1182
# If there were multiple (invalid) unpacks, the arg types list will become shorter,
1157
1183
# we need to trim the kinds/names as well to avoid crashes.
1158
1184
arg_kinds = t .arg_kinds [: len (arg_types )]
@@ -1207,7 +1233,7 @@ def anal_type_is_arg(self, t: UnboundType, fullname: str) -> Type | None:
1207
1233
return self .anal_type (t .args [0 ])
1208
1234
return None
1209
1235
1210
- def anal_star_arg_type (self , t : Type , kind : ArgKind , nested : bool ) -> Type :
1236
+ def anal_star_arg_type (self , t : Type , kind : ArgKind , nested : bool ) -> tuple [ str | None , Type ] :
1211
1237
"""Analyze signature argument type for *args and **kwargs argument."""
1212
1238
if isinstance (t , UnboundType ) and t .name and "." in t .name and not t .args :
1213
1239
components = t .name .split ("." )
@@ -1234,15 +1260,15 @@ def anal_star_arg_type(self, t: Type, kind: ArgKind, nested: bool) -> Type:
1234
1260
)
1235
1261
else :
1236
1262
assert False , kind
1237
- return make_paramspec (
1263
+ return tvar_name , make_paramspec (
1238
1264
tvar_def .name ,
1239
1265
tvar_def .fullname ,
1240
1266
tvar_def .id ,
1241
1267
named_type_func = self .named_type ,
1242
1268
line = t .line ,
1243
1269
column = t .column ,
1244
1270
)
1245
- return self .anal_type (t , nested = nested , allow_unpack = True )
1271
+ return None , self .anal_type (t , nested = nested , allow_unpack = True )
1246
1272
1247
1273
def visit_overloaded (self , t : Overloaded ) -> Type :
1248
1274
# Overloaded types are manually constructed in semanal.py by analyzing the
@@ -2586,18 +2612,7 @@ def _seems_like_callable(self, type: UnboundType) -> bool:
2586
2612
2587
2613
def visit_unbound_type (self , t : UnboundType ) -> None :
2588
2614
name = t .name
2589
- node = None
2590
-
2591
- # Special case P.args and P.kwargs for ParamSpecs only.
2592
- if name .endswith ("args" ):
2593
- if name .endswith ((".args" , ".kwargs" )):
2594
- base = "." .join (name .split ("." )[:- 1 ])
2595
- n = self .api .lookup_qualified (base , t )
2596
- if n is not None and isinstance (n .node , ParamSpecExpr ):
2597
- node = n
2598
- name = base
2599
- if node is None :
2600
- node = self .api .lookup_qualified (name , t )
2615
+ node = self .api .lookup_qualified (name , t )
2601
2616
if node and node .fullname in SELF_TYPE_NAMES :
2602
2617
self .has_self_type = True
2603
2618
if (
0 commit comments