@@ -96,7 +96,22 @@ impl PyGenericAlias {
96
96
origin,
97
97
args,
98
98
parameters,
99
- starred : false , // default to false, will be set to true for Unpack[...]
99
+ starred : false , // default to false
100
+ }
101
+ }
102
+
103
+ fn with_tuple_args (
104
+ origin : PyTypeRef ,
105
+ args : PyTupleRef ,
106
+ starred : bool ,
107
+ vm : & VirtualMachine ,
108
+ ) -> Self {
109
+ let parameters = make_parameters ( & args, vm) ;
110
+ Self {
111
+ origin,
112
+ args,
113
+ parameters,
114
+ starred,
100
115
}
101
116
}
102
117
@@ -166,6 +181,15 @@ impl PyGenericAlias {
166
181
self . starred
167
182
}
168
183
184
+ #[ pygetset]
185
+ fn __typing_unpacked_tuple_args__ ( & self , vm : & VirtualMachine ) -> PyObjectRef {
186
+ if self . starred && self . origin . is ( vm. ctx . types . tuple_type ) {
187
+ self . args . clone ( ) . into ( )
188
+ } else {
189
+ vm. ctx . none ( )
190
+ }
191
+ }
192
+
169
193
#[ pymethod]
170
194
fn __getitem__ ( zelf : PyRef < Self > , needle : PyObjectRef , vm : & VirtualMachine ) -> PyResult {
171
195
let new_args = subs_parameters (
@@ -237,7 +261,7 @@ pub(crate) fn make_parameters(args: &Py<PyTuple>, vm: &VirtualMachine) -> PyTupl
237
261
continue ;
238
262
}
239
263
240
- // Check for __typing_subst__ attribute (like CPython)
264
+ // Check for __typing_subst__ attribute
241
265
if arg. get_attr ( identifier ! ( vm, __typing_subst__) , vm) . is_ok ( ) {
242
266
// Use tuple_add equivalent logic
243
267
if tuple_index ( & parameters, arg) . is_none ( ) {
@@ -336,139 +360,142 @@ fn subs_tvars(
336
360
. unwrap_or ( Ok ( obj) )
337
361
}
338
362
363
+ // CPython's _unpack_args equivalent
364
+ fn unpack_args ( item : PyObjectRef , vm : & VirtualMachine ) -> PyResult < PyTupleRef > {
365
+ let mut new_args = Vec :: new ( ) ;
366
+
367
+ let arg_items = if let Ok ( tuple) = item. try_to_ref :: < PyTuple > ( vm) {
368
+ tuple. as_slice ( ) . to_vec ( )
369
+ } else {
370
+ vec ! [ item]
371
+ } ;
372
+
373
+ for item in arg_items {
374
+ // Skip PyType objects - they can't be unpacked
375
+ if item. class ( ) . is ( vm. ctx . types . type_type ) {
376
+ new_args. push ( item) ;
377
+ continue ;
378
+ }
379
+
380
+ // Try to get __typing_unpacked_tuple_args__
381
+ if let Ok ( sub_args) = item. get_attr ( identifier ! ( vm, __typing_unpacked_tuple_args__) , vm) {
382
+ if !sub_args. is ( & vm. ctx . none ) {
383
+ if let Ok ( tuple) = sub_args. try_to_ref :: < PyTuple > ( vm) {
384
+ // Check for ellipsis at the end
385
+ let has_ellipsis_at_end = if tuple. len ( ) > 0 {
386
+ tuple. as_slice ( ) [ tuple. len ( ) - 1 ] . is ( & vm. ctx . ellipsis )
387
+ } else {
388
+ false
389
+ } ;
390
+
391
+ if !has_ellipsis_at_end {
392
+ // Safe to unpack - add all elements's PyList_SetSlice
393
+ for arg in tuple. iter ( ) {
394
+ new_args. push ( arg. clone ( ) ) ;
395
+ }
396
+ continue ;
397
+ }
398
+ }
399
+ }
400
+ }
401
+
402
+ // Default case: add the item as-is's PyList_Append
403
+ new_args. push ( item) ;
404
+ }
405
+
406
+ Ok ( PyTuple :: new_ref ( new_args, & vm. ctx ) )
407
+ }
408
+
339
409
// _Py_subs_parameters
340
410
pub fn subs_parameters (
341
- alias : PyObjectRef , // The GenericAlias object itself
411
+ alias : PyObjectRef , // = self
342
412
args : PyTupleRef ,
343
413
parameters : PyTupleRef ,
344
- needle : PyObjectRef ,
414
+ item : PyObjectRef ,
345
415
vm : & VirtualMachine ,
346
416
) -> PyResult < PyTupleRef > {
347
- let num_params = parameters. len ( ) ;
348
- if num_params == 0 {
417
+ let n_params = parameters. len ( ) ;
418
+ if n_params == 0 {
349
419
return Err ( vm. new_type_error ( format ! ( "{} is not a generic class" , alias. repr( vm) ?) ) ) ;
350
420
}
351
421
352
- // Handle __typing_prepare_subst__ for each parameter
353
- // Following CPython: each prepare function transforms the args
354
- let mut prepared_args = needle. clone ( ) ;
355
-
356
- // Ensure args is a tuple
357
- if prepared_args. try_to_ref :: < PyTuple > ( vm) . is_err ( ) {
358
- prepared_args = PyTuple :: new_ref ( vec ! [ prepared_args] , & vm. ctx ) . into ( ) ;
359
- }
422
+ // Step 1: Unpack args
423
+ let mut item: PyObjectRef = unpack_args ( item, vm) ?. into ( ) ;
360
424
425
+ // Step 2: Call __typing_prepare_subst__ on each parameter
361
426
for param in parameters. iter ( ) {
362
427
if let Ok ( prepare) = param. get_attr ( identifier ! ( vm, __typing_prepare_subst__) , vm) {
363
428
if !prepare. is ( & vm. ctx . none ) {
364
- // Call prepare(cls, args) where cls is the GenericAlias
365
- prepared_args = prepare. call ( ( alias. clone ( ) , prepared_args) , vm) ?;
429
+ // Call prepare(self, item)
430
+ item = if item. try_to_ref :: < PyTuple > ( vm) . is_ok ( ) {
431
+ prepare. call ( ( alias. clone ( ) , item. clone ( ) ) , vm) ?
432
+ } else {
433
+ // Create a tuple with the single item's "O(O)" format
434
+ let tuple_args = PyTuple :: new_ref ( vec ! [ item. clone( ) ] , & vm. ctx ) ;
435
+ prepare. call ( ( alias. clone ( ) , tuple_args. to_pyobject ( vm) ) , vm) ?
436
+ } ;
366
437
}
367
438
}
368
439
}
369
440
370
- let items = prepared_args. try_to_ref :: < PyTuple > ( vm) ;
371
- let arg_items = match items {
372
- Ok ( tuple) => tuple. as_slice ( ) ,
373
- Err ( _) => std:: slice:: from_ref ( & prepared_args) ,
441
+ // Step 3: Extract final arg items
442
+ let arg_items = if let Ok ( tuple) = item. try_to_ref :: < PyTuple > ( vm) {
443
+ tuple. as_slice ( ) . to_vec ( )
444
+ } else {
445
+ vec ! [ item]
374
446
} ;
447
+ let n_items = arg_items. len ( ) ;
375
448
376
- let num_items = arg_items. len ( ) ;
377
-
378
- // Check if we need to apply default values
379
- if num_items < num_params {
380
- // Count how many parameters have defaults
381
- let mut params_with_defaults = 0 ;
382
- for param in parameters. iter ( ) . rev ( ) {
383
- if let Ok ( has_default) = vm. call_method ( param, "has_default" , ( ) ) {
384
- if has_default. try_to_bool ( vm) ? {
385
- params_with_defaults += 1 ;
386
- } else {
387
- break ; // No more defaults from this point backwards
388
- }
389
- } else {
390
- break ;
391
- }
392
- }
393
-
394
- let min_required = num_params - params_with_defaults;
395
- if num_items < min_required {
396
- let repr_str = alias. repr ( vm) ?;
397
- return Err ( vm. new_type_error ( format ! (
398
- "Too few arguments for {repr_str}; actual {num_items}, expected at least {min_required}"
399
- ) ) ) ;
400
- }
401
- } else if num_items > num_params {
402
- let repr_str = alias. repr ( vm) ?;
449
+ if n_items != n_params {
403
450
return Err ( vm. new_type_error ( format ! (
404
- "Too many arguments for {repr_str}; actual {num_items}, expected {num_params}"
451
+ "Too {} arguments for {}; actual {}, expected {}" ,
452
+ if n_items > n_params { "many" } else { "few" } ,
453
+ alias. repr( vm) ?,
454
+ n_items,
455
+ n_params
405
456
) ) ) ;
406
457
}
407
458
408
- let mut new_args = Vec :: with_capacity ( args. len ( ) ) ;
459
+ // Step 4: Replace all type variables
460
+ let mut new_args = Vec :: new ( ) ;
409
461
410
462
for arg in args. iter ( ) {
411
- // Skip bare Python classes
463
+ // Skip PyType objects
412
464
if arg. class ( ) . is ( vm. ctx . types . type_type ) {
413
465
new_args. push ( arg. clone ( ) ) ;
414
466
continue ;
415
467
}
416
468
417
- // Check if this is an unpacked TypeVarTuple
469
+ // Check if this is an unpacked TypeVarTuple's _is_unpacked_typevartuple
418
470
let unpack = is_unpacked_typevartuple ( arg, vm) ?;
419
471
420
- // Check for __typing_subst__ attribute directly (like CPython)
421
- if let Ok ( subst) = arg. get_attr ( identifier ! ( vm, __typing_subst__) , vm) {
422
- if let Some ( idx) = tuple_index ( parameters. as_slice ( ) , arg) {
423
- if idx < num_items {
424
- // Call __typing_subst__ with the argument
425
- let substituted = subst. call ( ( arg_items[ idx] . clone ( ) , ) , vm) ?;
426
-
427
- if unpack {
428
- // Unpack the tuple if it's a TypeVarTuple
429
- if let Ok ( tuple) = substituted. try_to_ref :: < PyTuple > ( vm) {
430
- for elem in tuple. iter ( ) {
431
- new_args. push ( elem. clone ( ) ) ;
432
- }
433
- } else {
434
- new_args. push ( substituted) ;
435
- }
436
- } else {
437
- new_args. push ( substituted) ;
438
- }
439
- } else {
440
- // Use default value if available
441
- if let Ok ( default_val) = vm. call_method ( arg, "__default__" , ( ) ) {
442
- if !default_val. is ( & vm. ctx . typing_no_default ) {
443
- new_args. push ( default_val) ;
444
- } else {
445
- return Err ( vm. new_type_error ( format ! (
446
- "No argument provided for parameter at index {idx}"
447
- ) ) ) ;
448
- }
449
- } else {
450
- return Err ( vm. new_type_error ( format ! (
451
- "No argument provided for parameter at index {idx}"
452
- ) ) ) ;
453
- }
454
- }
472
+ // Try __typing_subst__ method first,
473
+ let substituted_arg = if let Ok ( subst) = arg. get_attr ( identifier ! ( vm, __typing_subst__) , vm)
474
+ {
475
+ // Find parameter index's tuple_index
476
+ if let Some ( iparam) = tuple_index ( parameters. as_slice ( ) , arg) {
477
+ subst. call ( ( arg_items[ iparam] . clone ( ) , ) , vm) ?
455
478
} else {
456
- new_args. push ( arg. clone ( ) ) ;
479
+ // This shouldn't happen in well-formed generics but handle gracefully
480
+ subs_tvars ( arg. clone ( ) , & parameters, & arg_items, vm) ?
457
481
}
458
482
} else {
459
- let subst_arg = subs_tvars ( arg . clone ( ) , & parameters , arg_items , vm ) ? ;
460
- if unpack {
461
- // Unpack the tuple if it's a TypeVarTuple
462
- if let Ok ( tuple ) = subst_arg . try_to_ref :: < PyTuple > ( vm ) {
463
- for elem in tuple . iter ( ) {
464
- new_args . push ( elem . clone ( ) ) ;
465
- }
466
- } else {
467
- new_args. push ( subst_arg ) ;
483
+ // Use subs_tvars for objects with __parameters__
484
+ subs_tvars ( arg . clone ( ) , & parameters , & arg_items , vm ) ?
485
+ } ;
486
+
487
+ if unpack {
488
+ // Handle unpacked TypeVarTuple's tuple_extend
489
+ if let Ok ( tuple ) = substituted_arg . try_to_ref :: < PyTuple > ( vm ) {
490
+ for elem in tuple . iter ( ) {
491
+ new_args. push ( elem . clone ( ) ) ;
468
492
}
469
493
} else {
470
- new_args. push ( subst_arg) ;
494
+ // This shouldn't happen but handle gracefully
495
+ new_args. push ( substituted_arg) ;
471
496
}
497
+ } else {
498
+ new_args. push ( substituted_arg) ;
472
499
}
473
500
}
474
501
@@ -565,9 +592,27 @@ impl Representable for PyGenericAlias {
565
592
}
566
593
567
594
impl Iterable for PyGenericAlias {
595
+ // ga_iter
596
+ // cspell:ignore gaiterobject
597
+ // TODO: gaiterobject
568
598
fn iter ( zelf : PyRef < Self > , vm : & VirtualMachine ) -> PyResult {
569
- // Return an iterator over the args tuple
570
- Ok ( zelf. args . clone ( ) . to_pyobject ( vm) . get_iter ( vm) ?. into ( ) )
599
+ // CPython's ga_iter creates an iterator that yields one starred GenericAlias
600
+ // we don't have gaiterobject yet
601
+
602
+ let starred_alias = PyGenericAlias :: with_tuple_args (
603
+ zelf. origin . clone ( ) ,
604
+ zelf. args . clone ( ) ,
605
+ true , // starred
606
+ vm,
607
+ ) ;
608
+ let starred_ref = PyRef :: new_ref (
609
+ starred_alias,
610
+ vm. ctx . types . generic_alias_type . to_owned ( ) ,
611
+ None ,
612
+ ) ;
613
+ let items = vec ! [ starred_ref. into( ) ] ;
614
+ let iter_tuple = PyTuple :: new_ref ( items, & vm. ctx ) ;
615
+ Ok ( iter_tuple. to_pyobject ( vm) . get_iter ( vm) ?. into ( ) )
571
616
}
572
617
}
573
618
0 commit comments