@@ -1640,6 +1640,27 @@ def check_callable_call(
1640
1640
callee .type_object ().name , abstract_attributes , context
1641
1641
)
1642
1642
1643
+ var_arg = callee .var_arg ()
1644
+ if var_arg and isinstance (var_arg .typ , UnpackType ):
1645
+ # It is hard to support multiple variadic unpacks (except for old-style *args: int),
1646
+ # fail gracefully to avoid crashes later.
1647
+ seen_unpack = False
1648
+ for arg , arg_kind in zip (args , arg_kinds ):
1649
+ if arg_kind != ARG_STAR :
1650
+ continue
1651
+ arg_type = get_proper_type (self .accept (arg ))
1652
+ if not isinstance (arg_type , TupleType ) or any (
1653
+ isinstance (t , UnpackType ) for t in arg_type .items
1654
+ ):
1655
+ if seen_unpack :
1656
+ self .msg .fail (
1657
+ "Passing multiple variadic unpacks in a call is not supported" ,
1658
+ context ,
1659
+ code = codes .CALL_ARG ,
1660
+ )
1661
+ return AnyType (TypeOfAny .from_error ), callee
1662
+ seen_unpack = True
1663
+
1643
1664
formal_to_actual = map_actuals_to_formals (
1644
1665
arg_kinds ,
1645
1666
arg_names ,
@@ -2405,7 +2426,7 @@ def check_argument_types(
2405
2426
]
2406
2427
actual_kinds = [nodes .ARG_STAR ] + [nodes .ARG_POS ] * (len (actuals ) - 1 )
2407
2428
2408
- # TODO: can we really assert this? What if formal is just plain Unpack[Ts]?
2429
+ # If we got here, the callee was previously inferred to have a suffix.
2409
2430
assert isinstance (orig_callee_arg_type , UnpackType )
2410
2431
assert isinstance (orig_callee_arg_type .type , ProperType ) and isinstance (
2411
2432
orig_callee_arg_type .type , TupleType
@@ -2431,22 +2452,29 @@ def check_argument_types(
2431
2452
inner_unpack = unpacked_type .items [inner_unpack_index ]
2432
2453
assert isinstance (inner_unpack , UnpackType )
2433
2454
inner_unpacked_type = get_proper_type (inner_unpack .type )
2434
- # We assume heterogenous tuples are desugared earlier
2435
- assert isinstance (inner_unpacked_type , Instance )
2436
- assert inner_unpacked_type .type .fullname == "builtins.tuple"
2437
- callee_arg_types = (
2438
- unpacked_type .items [:inner_unpack_index ]
2439
- + [inner_unpacked_type .args [0 ]]
2440
- * (len (actuals ) - len (unpacked_type .items ) + 1 )
2441
- + unpacked_type .items [inner_unpack_index + 1 :]
2442
- )
2443
- callee_arg_kinds = [ARG_POS ] * len (actuals )
2455
+ if isinstance (inner_unpacked_type , TypeVarTupleType ):
2456
+ # This branch mimics the expanded_tuple case above but for
2457
+ # the case where caller passed a single * unpacked tuple argument.
2458
+ callee_arg_types = unpacked_type .items
2459
+ callee_arg_kinds = [
2460
+ ARG_POS if i != inner_unpack_index else ARG_STAR
2461
+ for i in range (len (unpacked_type .items ))
2462
+ ]
2463
+ else :
2464
+ # We assume heterogeneous tuples are desugared earlier.
2465
+ assert isinstance (inner_unpacked_type , Instance )
2466
+ assert inner_unpacked_type .type .fullname == "builtins.tuple"
2467
+ callee_arg_types = (
2468
+ unpacked_type .items [:inner_unpack_index ]
2469
+ + [inner_unpacked_type .args [0 ]]
2470
+ * (len (actuals ) - len (unpacked_type .items ) + 1 )
2471
+ + unpacked_type .items [inner_unpack_index + 1 :]
2472
+ )
2473
+ callee_arg_kinds = [ARG_POS ] * len (actuals )
2444
2474
elif isinstance (unpacked_type , TypeVarTupleType ):
2445
2475
callee_arg_types = [orig_callee_arg_type ]
2446
2476
callee_arg_kinds = [ARG_STAR ]
2447
2477
else :
2448
- # TODO: Any and Never can appear in Unpack (as a result of user error),
2449
- # fail gracefully here and elsewhere (and/or normalize them away).
2450
2478
assert isinstance (unpacked_type , Instance )
2451
2479
assert unpacked_type .type .fullname == "builtins.tuple"
2452
2480
callee_arg_types = [unpacked_type .args [0 ]] * len (actuals )
@@ -2458,8 +2486,10 @@ def check_argument_types(
2458
2486
assert len (actual_types ) == len (actuals ) == len (actual_kinds )
2459
2487
2460
2488
if len (callee_arg_types ) != len (actual_types ):
2461
- # TODO: Improve error message
2462
- self .chk .fail ("Invalid number of arguments" , context )
2489
+ if len (actual_types ) > len (callee_arg_types ):
2490
+ self .chk .msg .too_many_arguments (callee , context )
2491
+ else :
2492
+ self .chk .msg .too_few_arguments (callee , context , None )
2463
2493
continue
2464
2494
2465
2495
assert len (callee_arg_types ) == len (actual_types )
@@ -2764,11 +2794,17 @@ def infer_overload_return_type(
2764
2794
)
2765
2795
is_match = not w .has_new_errors ()
2766
2796
if is_match :
2767
- # Return early if possible; otherwise record info so we can
2797
+ # Return early if possible; otherwise record info, so we can
2768
2798
# check for ambiguity due to 'Any' below.
2769
2799
if not args_contain_any :
2770
2800
return ret_type , infer_type
2771
- matches .append (typ )
2801
+ p_infer_type = get_proper_type (infer_type )
2802
+ if isinstance (p_infer_type , CallableType ):
2803
+ # Prefer inferred types if possible, this will avoid false triggers for
2804
+ # Any-ambiguity caused by arguments with Any passed to generic overloads.
2805
+ matches .append (p_infer_type )
2806
+ else :
2807
+ matches .append (typ )
2772
2808
return_types .append (ret_type )
2773
2809
inferred_types .append (infer_type )
2774
2810
type_maps .append (m )
@@ -4109,6 +4145,12 @@ def visit_index_with_type(
4109
4145
# Visit the index, just to make sure we have a type for it available
4110
4146
self .accept (index )
4111
4147
4148
+ if isinstance (left_type , TupleType ) and any (
4149
+ isinstance (it , UnpackType ) for it in left_type .items
4150
+ ):
4151
+ # Normalize variadic tuples for consistency.
4152
+ left_type = expand_type (left_type , {})
4153
+
4112
4154
if isinstance (left_type , UnionType ):
4113
4155
original_type = original_type or left_type
4114
4156
# Don't combine literal types, since we may need them for type narrowing.
@@ -4129,12 +4171,15 @@ def visit_index_with_type(
4129
4171
if ns is not None :
4130
4172
out = []
4131
4173
for n in ns :
4132
- if n < 0 :
4133
- n += len (left_type .items )
4134
- if 0 <= n < len (left_type .items ):
4135
- out .append (left_type .items [n ])
4174
+ item = self .visit_tuple_index_helper (left_type , n )
4175
+ if item is not None :
4176
+ out .append (item )
4136
4177
else :
4137
4178
self .chk .fail (message_registry .TUPLE_INDEX_OUT_OF_RANGE , e )
4179
+ if any (isinstance (t , UnpackType ) for t in left_type .items ):
4180
+ self .chk .note (
4181
+ f"Variadic tuple can have length { left_type .length () - 1 } " , e
4182
+ )
4138
4183
return AnyType (TypeOfAny .from_error )
4139
4184
return make_simplified_union (out )
4140
4185
else :
@@ -4158,6 +4203,46 @@ def visit_index_with_type(
4158
4203
e .method_type = method_type
4159
4204
return result
4160
4205
4206
+ def visit_tuple_index_helper (self , left : TupleType , n : int ) -> Type | None :
4207
+ unpack_index = find_unpack_in_list (left .items )
4208
+ if unpack_index is None :
4209
+ if n < 0 :
4210
+ n += len (left .items )
4211
+ if 0 <= n < len (left .items ):
4212
+ return left .items [n ]
4213
+ return None
4214
+ unpack = left .items [unpack_index ]
4215
+ assert isinstance (unpack , UnpackType )
4216
+ unpacked = get_proper_type (unpack .type )
4217
+ if isinstance (unpacked , TypeVarTupleType ):
4218
+ # Usually we say that TypeVarTuple can't be split, be in case of
4219
+ # indexing it seems benign to just return the fallback item, similar
4220
+ # to what we do when indexing a regular TypeVar.
4221
+ middle = unpacked .tuple_fallback .args [0 ]
4222
+ else :
4223
+ assert isinstance (unpacked , Instance )
4224
+ assert unpacked .type .fullname == "builtins.tuple"
4225
+ middle = unpacked .args [0 ]
4226
+ if n >= 0 :
4227
+ if n < unpack_index :
4228
+ return left .items [n ]
4229
+ if n >= len (left .items ) - 1 :
4230
+ # For tuple[int, *tuple[str, ...], int] we allow either index 0 or 1,
4231
+ # since variadic item may have zero items.
4232
+ return None
4233
+ return UnionType .make_union (
4234
+ [middle ] + left .items [unpack_index + 1 : n + 2 ], left .line , left .column
4235
+ )
4236
+ n += len (left .items )
4237
+ if n <= 0 :
4238
+ # Similar to above, we only allow -1, and -2 for tuple[int, *tuple[str, ...], int]
4239
+ return None
4240
+ if n > unpack_index :
4241
+ return left .items [n ]
4242
+ return UnionType .make_union (
4243
+ left .items [n - 1 : unpack_index ] + [middle ], left .line , left .column
4244
+ )
4245
+
4161
4246
def visit_tuple_slice_helper (self , left_type : TupleType , slic : SliceExpr ) -> Type :
4162
4247
begin : Sequence [int | None ] = [None ]
4163
4248
end : Sequence [int | None ] = [None ]
@@ -4183,7 +4268,11 @@ def visit_tuple_slice_helper(self, left_type: TupleType, slic: SliceExpr) -> Typ
4183
4268
4184
4269
items : list [Type ] = []
4185
4270
for b , e , s in itertools .product (begin , end , stride ):
4186
- items .append (left_type .slice (b , e , s ))
4271
+ item = left_type .slice (b , e , s )
4272
+ if item is None :
4273
+ self .chk .fail (message_registry .AMBIGUOUS_SLICE_OF_VARIADIC_TUPLE , slic )
4274
+ return AnyType (TypeOfAny .from_error )
4275
+ items .append (item )
4187
4276
return make_simplified_union (items )
4188
4277
4189
4278
def try_getting_int_literals (self , index : Expression ) -> list [int ] | None :
@@ -4192,7 +4281,7 @@ def try_getting_int_literals(self, index: Expression) -> list[int] | None:
4192
4281
Otherwise, returns None.
4193
4282
4194
4283
Specifically, this function is guaranteed to return a list with
4195
- one or more ints if one one the following is true:
4284
+ one or more ints if one the following is true:
4196
4285
4197
4286
1. 'expr' is a IntExpr or a UnaryExpr backed by an IntExpr
4198
4287
2. 'typ' is a LiteralType containing an int
@@ -4223,11 +4312,30 @@ def try_getting_int_literals(self, index: Expression) -> list[int] | None:
4223
4312
def nonliteral_tuple_index_helper (self , left_type : TupleType , index : Expression ) -> Type :
4224
4313
self .check_method_call_by_name ("__getitem__" , left_type , [index ], [ARG_POS ], context = index )
4225
4314
# We could return the return type from above, but unions are often better than the join
4226
- union = make_simplified_union (left_type . items )
4315
+ union = self . union_tuple_fallback_item (left_type )
4227
4316
if isinstance (index , SliceExpr ):
4228
4317
return self .chk .named_generic_type ("builtins.tuple" , [union ])
4229
4318
return union
4230
4319
4320
+ def union_tuple_fallback_item (self , left_type : TupleType ) -> Type :
4321
+ # TODO: this duplicates logic in typeops.tuple_fallback().
4322
+ items = []
4323
+ for item in left_type .items :
4324
+ if isinstance (item , UnpackType ):
4325
+ unpacked_type = get_proper_type (item .type )
4326
+ if isinstance (unpacked_type , TypeVarTupleType ):
4327
+ unpacked_type = get_proper_type (unpacked_type .upper_bound )
4328
+ if (
4329
+ isinstance (unpacked_type , Instance )
4330
+ and unpacked_type .type .fullname == "builtins.tuple"
4331
+ ):
4332
+ items .append (unpacked_type .args [0 ])
4333
+ else :
4334
+ raise NotImplementedError
4335
+ else :
4336
+ items .append (item )
4337
+ return make_simplified_union (items )
4338
+
4231
4339
def visit_typeddict_index_expr (
4232
4340
self , td_type : TypedDictType , index : Expression , setitem : bool = False
4233
4341
) -> Type :
0 commit comments