205
205
TypeType ,
206
206
TypeVarId ,
207
207
TypeVarLikeType ,
208
+ TypeVarTupleType ,
208
209
TypeVarType ,
209
210
UnboundType ,
210
211
UninhabitedType ,
211
212
UnionType ,
213
+ UnpackType ,
214
+ find_unpack_in_list ,
212
215
flatten_nested_unions ,
213
216
get_proper_type ,
214
217
get_proper_types ,
@@ -3430,6 +3433,37 @@ def is_assignable_slot(self, lvalue: Lvalue, typ: Type | None) -> bool:
3430
3433
return all (self .is_assignable_slot (lvalue , u ) for u in typ .items )
3431
3434
return False
3432
3435
3436
+ def flatten_rvalues (self , rvalues : list [Expression ]) -> list [Expression ]:
3437
+ """Flatten expression list by expanding those * items that have tuple type.
3438
+
3439
+ For each regular type item in the tuple type use a TempNode(), for an Unpack
3440
+ item use a corresponding StarExpr(TempNode()).
3441
+ """
3442
+ new_rvalues = []
3443
+ for rv in rvalues :
3444
+ if not isinstance (rv , StarExpr ):
3445
+ new_rvalues .append (rv )
3446
+ continue
3447
+ typ = get_proper_type (self .expr_checker .accept (rv .expr ))
3448
+ if not isinstance (typ , TupleType ):
3449
+ new_rvalues .append (rv )
3450
+ continue
3451
+ for t in typ .items :
3452
+ if not isinstance (t , UnpackType ):
3453
+ new_rvalues .append (TempNode (t ))
3454
+ else :
3455
+ unpacked = get_proper_type (t .type )
3456
+ if isinstance (unpacked , TypeVarTupleType ):
3457
+ fallback = unpacked .upper_bound
3458
+ else :
3459
+ assert (
3460
+ isinstance (unpacked , Instance )
3461
+ and unpacked .type .fullname == "builtins.tuple"
3462
+ )
3463
+ fallback = unpacked
3464
+ new_rvalues .append (StarExpr (TempNode (fallback )))
3465
+ return new_rvalues
3466
+
3433
3467
def check_assignment_to_multiple_lvalues (
3434
3468
self ,
3435
3469
lvalues : list [Lvalue ],
@@ -3439,18 +3473,16 @@ def check_assignment_to_multiple_lvalues(
3439
3473
) -> None :
3440
3474
if isinstance (rvalue , (TupleExpr , ListExpr )):
3441
3475
# Recursively go into Tuple or List expression rhs instead of
3442
- # using the type of rhs, because this allowed more fine grained
3476
+ # using the type of rhs, because this allows more fine- grained
3443
3477
# control in cases like: a, b = [int, str] where rhs would get
3444
3478
# type List[object]
3445
3479
rvalues : list [Expression ] = []
3446
3480
iterable_type : Type | None = None
3447
3481
last_idx : int | None = None
3448
- for idx_rval , rval in enumerate (rvalue .items ):
3482
+ for idx_rval , rval in enumerate (self . flatten_rvalues ( rvalue .items ) ):
3449
3483
if isinstance (rval , StarExpr ):
3450
3484
typs = get_proper_type (self .expr_checker .accept (rval .expr ))
3451
- if isinstance (typs , TupleType ):
3452
- rvalues .extend ([TempNode (typ ) for typ in typs .items ])
3453
- elif self .type_is_iterable (typs ) and isinstance (typs , Instance ):
3485
+ if self .type_is_iterable (typs ) and isinstance (typs , Instance ):
3454
3486
if iterable_type is not None and iterable_type != self .iterable_item_type (
3455
3487
typs , rvalue
3456
3488
):
@@ -3517,8 +3549,32 @@ def check_assignment_to_multiple_lvalues(
3517
3549
self .check_multi_assignment (lvalues , rvalue , context , infer_lvalue_type )
3518
3550
3519
3551
def check_rvalue_count_in_assignment (
3520
- self , lvalues : list [Lvalue ], rvalue_count : int , context : Context
3552
+ self ,
3553
+ lvalues : list [Lvalue ],
3554
+ rvalue_count : int ,
3555
+ context : Context ,
3556
+ rvalue_unpack : int | None = None ,
3521
3557
) -> bool :
3558
+ if rvalue_unpack is not None :
3559
+ if not any (isinstance (e , StarExpr ) for e in lvalues ):
3560
+ self .fail ("Variadic tuple unpacking requires a star target" , context )
3561
+ return False
3562
+ if len (lvalues ) > rvalue_count :
3563
+ self .fail (message_registry .TOO_MANY_TARGETS_FOR_VARIADIC_UNPACK , context )
3564
+ return False
3565
+ left_star_index = next (i for i , lv in enumerate (lvalues ) if isinstance (lv , StarExpr ))
3566
+ left_prefix = left_star_index
3567
+ left_suffix = len (lvalues ) - left_star_index - 1
3568
+ right_prefix = rvalue_unpack
3569
+ right_suffix = rvalue_count - rvalue_unpack - 1
3570
+ if left_suffix > right_suffix or left_prefix > right_prefix :
3571
+ # Case of asymmetric unpack like:
3572
+ # rv: tuple[int, *Ts, int, int]
3573
+ # x, y, *xs, z = rv
3574
+ # it is technically valid, but is tricky to reason about.
3575
+ # TODO: support this (at least if the r.h.s. unpack is a homogeneous tuple).
3576
+ self .fail (message_registry .TOO_MANY_TARGETS_FOR_VARIADIC_UNPACK , context )
3577
+ return True
3522
3578
if any (isinstance (lvalue , StarExpr ) for lvalue in lvalues ):
3523
3579
if len (lvalues ) - 1 > rvalue_count :
3524
3580
self .msg .wrong_number_values_to_unpack (rvalue_count , len (lvalues ) - 1 , context )
@@ -3552,6 +3608,13 @@ def check_multi_assignment(
3552
3608
if len (relevant_items ) == 1 :
3553
3609
rvalue_type = get_proper_type (relevant_items [0 ])
3554
3610
3611
+ if (
3612
+ isinstance (rvalue_type , TupleType )
3613
+ and find_unpack_in_list (rvalue_type .items ) is not None
3614
+ ):
3615
+ # Normalize for consistent handling with "old-style" homogeneous tuples.
3616
+ rvalue_type = expand_type (rvalue_type , {})
3617
+
3555
3618
if isinstance (rvalue_type , AnyType ):
3556
3619
for lv in lvalues :
3557
3620
if isinstance (lv , StarExpr ):
@@ -3663,7 +3726,10 @@ def check_multi_assignment_from_tuple(
3663
3726
undefined_rvalue : bool ,
3664
3727
infer_lvalue_type : bool = True ,
3665
3728
) -> None :
3666
- if self .check_rvalue_count_in_assignment (lvalues , len (rvalue_type .items ), context ):
3729
+ rvalue_unpack = find_unpack_in_list (rvalue_type .items )
3730
+ if self .check_rvalue_count_in_assignment (
3731
+ lvalues , len (rvalue_type .items ), context , rvalue_unpack = rvalue_unpack
3732
+ ):
3667
3733
star_index = next (
3668
3734
(i for i , lv in enumerate (lvalues ) if isinstance (lv , StarExpr )), len (lvalues )
3669
3735
)
@@ -3708,12 +3774,37 @@ def check_multi_assignment_from_tuple(
3708
3774
self .check_assignment (lv , self .temp_node (rv_type , context ), infer_lvalue_type )
3709
3775
if star_lv :
3710
3776
list_expr = ListExpr (
3711
- [self .temp_node (rv_type , context ) for rv_type in star_rv_types ]
3777
+ [
3778
+ self .temp_node (rv_type , context )
3779
+ if not isinstance (rv_type , UnpackType )
3780
+ else StarExpr (self .temp_node (rv_type .type , context ))
3781
+ for rv_type in star_rv_types
3782
+ ]
3712
3783
)
3713
3784
list_expr .set_line (context )
3714
3785
self .check_assignment (star_lv .expr , list_expr , infer_lvalue_type )
3715
3786
for lv , rv_type in zip (right_lvs , right_rv_types ):
3716
3787
self .check_assignment (lv , self .temp_node (rv_type , context ), infer_lvalue_type )
3788
+ else :
3789
+ # Store meaningful Any types for lvalues, errors are already given
3790
+ # by check_rvalue_count_in_assignment()
3791
+ if infer_lvalue_type :
3792
+ for lv in lvalues :
3793
+ if (
3794
+ isinstance (lv , NameExpr )
3795
+ and isinstance (lv .node , Var )
3796
+ and lv .node .type is None
3797
+ ):
3798
+ lv .node .type = AnyType (TypeOfAny .from_error )
3799
+ elif isinstance (lv , StarExpr ):
3800
+ if (
3801
+ isinstance (lv .expr , NameExpr )
3802
+ and isinstance (lv .expr .node , Var )
3803
+ and lv .expr .node .type is None
3804
+ ):
3805
+ lv .expr .node .type = self .named_generic_type (
3806
+ "builtins.list" , [AnyType (TypeOfAny .from_error )]
3807
+ )
3717
3808
3718
3809
def lvalue_type_for_inference (self , lvalues : list [Lvalue ], rvalue_type : TupleType ) -> Type :
3719
3810
star_index = next (
0 commit comments