Skip to main content

rustpython_vm/builtins/
genericalias.rs

1// spell-checker:ignore iparam gaiterobject
2use crate::common::lock::LazyLock;
3
4use super::type_;
5use crate::{
6    AsObject, Context, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult, TryFromObject,
7    VirtualMachine, atomic_func,
8    builtins::{PyList, PyStr, PyTuple, PyTupleRef, PyType},
9    class::PyClassImpl,
10    common::hash,
11    convert::ToPyObject,
12    function::{FuncArgs, PyComparisonValue},
13    protocol::{PyMappingMethods, PyNumberMethods},
14    types::{
15        AsMapping, AsNumber, Callable, Comparable, Constructor, GetAttr, Hashable, IterNext,
16        Iterable, PyComparisonOp, Representable,
17    },
18};
19use alloc::fmt;
20
21// Attributes that are looked up on the GenericAlias itself, not on __origin__
22static ATTR_EXCEPTIONS: [&str; 9] = [
23    "__class__",
24    "__origin__",
25    "__args__",
26    "__unpacked__",
27    "__parameters__",
28    "__typing_unpacked_tuple_args__",
29    "__mro_entries__",
30    "__reduce_ex__", // needed so we don't look up object.__reduce_ex__
31    "__reduce__",
32];
33
34// Attributes that are blocked from being looked up on __origin__
35static ATTR_BLOCKED: [&str; 3] = ["__bases__", "__copy__", "__deepcopy__"];
36
37#[pyclass(module = "types", name = "GenericAlias")]
38pub struct PyGenericAlias {
39    origin: PyObjectRef,
40    args: PyTupleRef,
41    parameters: PyTupleRef,
42    starred: bool, // for __unpacked__ attribute
43}
44
45impl fmt::Debug for PyGenericAlias {
46    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
47        f.write_str("GenericAlias")
48    }
49}
50
51impl PyPayload for PyGenericAlias {
52    #[inline]
53    fn class(ctx: &Context) -> &'static Py<PyType> {
54        ctx.types.generic_alias_type
55    }
56}
57
58impl Constructor for PyGenericAlias {
59    type Args = FuncArgs;
60
61    fn py_new(_cls: &Py<PyType>, args: Self::Args, vm: &VirtualMachine) -> PyResult<Self> {
62        if !args.kwargs.is_empty() {
63            return Err(vm.new_type_error("GenericAlias() takes no keyword arguments"));
64        }
65        let (origin, arguments): (PyObjectRef, PyObjectRef) = args.bind(vm)?;
66        let args = if let Ok(tuple) = arguments.try_to_ref::<PyTuple>(vm) {
67            tuple.to_owned()
68        } else {
69            PyTuple::new_ref(vec![arguments], &vm.ctx)
70        };
71        Ok(Self::new(origin, args, false, vm))
72    }
73}
74
75#[pyclass(
76    with(
77        AsNumber,
78        AsMapping,
79        Callable,
80        Comparable,
81        Constructor,
82        GetAttr,
83        Hashable,
84        Iterable,
85        Representable
86    ),
87    flags(BASETYPE, HAS_WEAKREF)
88)]
89impl PyGenericAlias {
90    pub fn new(
91        origin: impl Into<PyObjectRef>,
92        args: PyTupleRef,
93        starred: bool,
94        vm: &VirtualMachine,
95    ) -> Self {
96        let parameters = make_parameters(&args, vm);
97        Self {
98            origin: origin.into(),
99            args,
100            parameters,
101            starred,
102        }
103    }
104
105    /// Create a GenericAlias from an origin and PyObjectRef arguments (helper for compatibility)
106    pub fn from_args(
107        origin: impl Into<PyObjectRef>,
108        args: PyObjectRef,
109        vm: &VirtualMachine,
110    ) -> Self {
111        let args = if let Ok(tuple) = args.try_to_ref::<PyTuple>(vm) {
112            tuple.to_owned()
113        } else {
114            PyTuple::new_ref(vec![args], &vm.ctx)
115        };
116        Self::new(origin, args, false, vm)
117    }
118
119    fn repr(&self, vm: &VirtualMachine) -> PyResult<String> {
120        fn repr_item(obj: PyObjectRef, vm: &VirtualMachine) -> PyResult<String> {
121            if obj.is(&vm.ctx.ellipsis) {
122                return Ok("...".to_string());
123            }
124
125            if vm
126                .get_attribute_opt(obj.clone(), identifier!(vm, __origin__))?
127                .is_some()
128                && vm
129                    .get_attribute_opt(obj.clone(), identifier!(vm, __args__))?
130                    .is_some()
131            {
132                return Ok(obj.repr(vm)?.to_string());
133            }
134
135            match (
136                vm.get_attribute_opt(obj.clone(), identifier!(vm, __qualname__))?
137                    .and_then(|o| o.downcast_ref::<PyStr>().map(|n| n.to_string())),
138                vm.get_attribute_opt(obj.clone(), identifier!(vm, __module__))?
139                    .and_then(|o| o.downcast_ref::<PyStr>().map(|m| m.to_string())),
140            ) {
141                (None, _) | (_, None) => Ok(obj.repr(vm)?.to_string()),
142                (Some(qualname), Some(module)) => Ok(if module == "builtins" {
143                    qualname
144                } else {
145                    format!("{module}.{qualname}")
146                }),
147            }
148        }
149
150        fn repr_arg(obj: PyObjectRef, vm: &VirtualMachine) -> PyResult<String> {
151            // ParamSpec args can be lists - format their items with repr_item
152            if obj.class().is(vm.ctx.types.list_type) {
153                let list = obj.downcast_ref::<crate::builtins::PyList>().unwrap();
154                let len = list.borrow_vec().len();
155                let mut parts = Vec::with_capacity(len);
156                // Use indexed access so list mutation during repr causes IndexError
157                for i in 0..len {
158                    let item = list
159                        .borrow_vec()
160                        .get(i)
161                        .cloned()
162                        .ok_or_else(|| vm.new_index_error("list index out of range"))?;
163                    parts.push(repr_item(item, vm)?);
164                }
165                Ok(format!("[{}]", parts.join(", ")))
166            } else {
167                repr_item(obj, vm)
168            }
169        }
170
171        let repr_str = format!(
172            "{}[{}]",
173            repr_item(self.origin.clone(), vm)?,
174            if self.args.is_empty() {
175                "()".to_owned()
176            } else {
177                self.args
178                    .iter()
179                    .map(|o| repr_arg(o.clone(), vm))
180                    .collect::<PyResult<Vec<_>>>()?
181                    .join(", ")
182            }
183        );
184
185        // Add * prefix if this is a starred GenericAlias
186        Ok(if self.starred {
187            format!("*{repr_str}")
188        } else {
189            repr_str
190        })
191    }
192
193    #[pygetset]
194    fn __parameters__(&self) -> PyObjectRef {
195        self.parameters.clone().into()
196    }
197
198    #[pygetset]
199    fn __args__(&self) -> PyObjectRef {
200        self.args.clone().into()
201    }
202
203    #[pygetset]
204    fn __origin__(&self) -> PyObjectRef {
205        self.origin.clone()
206    }
207
208    #[pygetset]
209    const fn __unpacked__(&self) -> bool {
210        self.starred
211    }
212
213    #[pygetset]
214    fn __typing_unpacked_tuple_args__(&self, vm: &VirtualMachine) -> PyObjectRef {
215        if self.starred && self.origin.is(vm.ctx.types.tuple_type.as_object()) {
216            self.args.clone().into()
217        } else {
218            vm.ctx.none()
219        }
220    }
221
222    fn __getitem__(zelf: PyRef<Self>, needle: PyObjectRef, vm: &VirtualMachine) -> PyResult {
223        let new_args = subs_parameters(
224            zelf.to_owned().into(),
225            zelf.args.clone(),
226            zelf.parameters.clone(),
227            needle,
228            vm,
229        )?;
230
231        Ok(Self::new(zelf.origin.clone(), new_args, false, vm).into_pyobject(vm))
232    }
233
234    #[pymethod]
235    fn __dir__(&self, vm: &VirtualMachine) -> PyResult<PyList> {
236        let dir = vm.dir(Some(self.__origin__()))?;
237        for exc in &ATTR_EXCEPTIONS {
238            if !dir.__contains__((*exc).to_pyobject(vm), vm)? {
239                dir.append((*exc).to_pyobject(vm));
240            }
241        }
242        Ok(dir)
243    }
244
245    #[pymethod]
246    fn __reduce__(zelf: &Py<Self>, vm: &VirtualMachine) -> PyResult<PyTupleRef> {
247        if zelf.starred {
248            // (next, (iter(GenericAlias(origin, args)),))
249            let next_fn = vm.builtins.get_attr("next", vm)?;
250            let non_starred = Self::new(zelf.origin.clone(), zelf.args.clone(), false, vm);
251            let iter_obj = PyGenericAliasIterator {
252                obj: crate::common::lock::PyMutex::new(Some(non_starred.into_pyobject(vm))),
253            }
254            .into_pyobject(vm);
255            Ok(PyTuple::new_ref(
256                vec![next_fn, PyTuple::new_ref(vec![iter_obj], &vm.ctx).into()],
257                &vm.ctx,
258            ))
259        } else {
260            Ok(PyTuple::new_ref(
261                vec![
262                    vm.ctx.types.generic_alias_type.to_owned().into(),
263                    PyTuple::new_ref(vec![zelf.origin.clone(), zelf.args.clone().into()], &vm.ctx)
264                        .into(),
265                ],
266                &vm.ctx,
267            ))
268        }
269    }
270
271    #[pymethod]
272    fn __mro_entries__(&self, _bases: PyObjectRef, vm: &VirtualMachine) -> PyTupleRef {
273        PyTuple::new_ref(vec![self.__origin__()], &vm.ctx)
274    }
275
276    #[pymethod]
277    fn __instancecheck__(_zelf: PyRef<Self>, _obj: PyObjectRef, vm: &VirtualMachine) -> PyResult {
278        Err(vm.new_type_error("isinstance() argument 2 cannot be a parameterized generic"))
279    }
280
281    #[pymethod]
282    fn __subclasscheck__(_zelf: PyRef<Self>, _obj: PyObjectRef, vm: &VirtualMachine) -> PyResult {
283        Err(vm.new_type_error("issubclass() argument 2 cannot be a parameterized generic"))
284    }
285
286    fn __ror__(zelf: PyObjectRef, other: PyObjectRef, vm: &VirtualMachine) -> PyResult {
287        type_::or_(other, zelf, vm)
288    }
289
290    fn __or__(zelf: PyObjectRef, other: PyObjectRef, vm: &VirtualMachine) -> PyResult {
291        type_::or_(zelf, other, vm)
292    }
293}
294
295pub(crate) fn make_parameters(args: &Py<PyTuple>, vm: &VirtualMachine) -> PyTupleRef {
296    make_parameters_from_slice(args.as_slice(), vm)
297}
298
299fn make_parameters_from_slice(args: &[PyObjectRef], vm: &VirtualMachine) -> PyTupleRef {
300    let mut parameters: Vec<PyObjectRef> = Vec::with_capacity(args.len());
301
302    for arg in args {
303        // We don't want __parameters__ descriptor of a bare Python class.
304        if arg.class().is(vm.ctx.types.type_type) {
305            continue;
306        }
307
308        // Check for __typing_subst__ attribute
309        if arg.get_attr(identifier!(vm, __typing_subst__), vm).is_ok() {
310            if tuple_index(&parameters, arg).is_none() {
311                parameters.push(arg.clone());
312            }
313        } else if let Ok(subparams) = arg.get_attr(identifier!(vm, __parameters__), vm)
314            && let Ok(sub_params) = subparams.try_to_ref::<PyTuple>(vm)
315        {
316            for sub_param in sub_params {
317                if tuple_index(&parameters, sub_param).is_none() {
318                    parameters.push(sub_param.clone());
319                }
320            }
321        } else if arg.try_to_ref::<PyTuple>(vm).is_ok() || arg.try_to_ref::<PyList>(vm).is_ok() {
322            // Recursively extract parameters from lists/tuples (ParamSpec args)
323            let items: Vec<PyObjectRef> = if let Ok(t) = arg.try_to_ref::<PyTuple>(vm) {
324                t.as_slice().to_vec()
325            } else {
326                let list = arg.downcast_ref::<PyList>().unwrap();
327                list.borrow_vec().to_vec()
328            };
329            let sub = make_parameters_from_slice(&items, vm);
330            for sub_param in sub.iter() {
331                if tuple_index(&parameters, sub_param).is_none() {
332                    parameters.push(sub_param.clone());
333                }
334            }
335        }
336    }
337
338    PyTuple::new_ref(parameters, &vm.ctx)
339}
340
341#[inline]
342fn tuple_index(vec: &[PyObjectRef], item: &PyObject) -> Option<usize> {
343    vec.iter().position(|element| element.is(item))
344}
345
346fn is_unpacked_typevartuple(arg: &PyObject, vm: &VirtualMachine) -> PyResult<bool> {
347    if arg.class().is(vm.ctx.types.type_type) {
348        return Ok(false);
349    }
350
351    if let Ok(attr) = arg.get_attr(identifier!(vm, __typing_is_unpacked_typevartuple__), vm) {
352        attr.try_to_bool(vm)
353    } else {
354        Ok(false)
355    }
356}
357
358fn subs_tvars(
359    obj: PyObjectRef,
360    params: &Py<PyTuple>,
361    arg_items: &[PyObjectRef],
362    vm: &VirtualMachine,
363) -> PyResult {
364    obj.get_attr(identifier!(vm, __parameters__), vm)
365        .ok()
366        .and_then(|sub_params| {
367            PyTupleRef::try_from_object(vm, sub_params)
368                .ok()
369                .filter(|sub_params| !sub_params.is_empty())
370                .map(|sub_params| {
371                    let mut sub_args = Vec::new();
372
373                    for arg in sub_params.iter() {
374                        if let Some(idx) = tuple_index(params.as_slice(), arg) {
375                            let param = &params[idx];
376                            let substituted_arg = &arg_items[idx];
377
378                            // Check if this is a TypeVarTuple (has tp_iter)
379                            if param.class().slots.iter.load().is_some()
380                                && substituted_arg.try_to_ref::<PyTuple>(vm).is_ok()
381                            {
382                                // TypeVarTuple case - extend with tuple elements
383                                if let Ok(tuple) = substituted_arg.try_to_ref::<PyTuple>(vm) {
384                                    for elem in tuple {
385                                        sub_args.push(elem.clone());
386                                    }
387                                    continue;
388                                }
389                            }
390
391                            sub_args.push(substituted_arg.clone());
392                        } else {
393                            sub_args.push(arg.clone());
394                        }
395                    }
396
397                    let sub_args: PyObjectRef = PyTuple::new_ref(sub_args, &vm.ctx).into();
398                    obj.get_item(&*sub_args, vm)
399                })
400        })
401        .unwrap_or(Ok(obj))
402}
403
404// CPython's _unpack_args equivalent
405fn unpack_args(item: PyObjectRef, vm: &VirtualMachine) -> PyResult<PyTupleRef> {
406    let mut new_args = Vec::new();
407
408    let arg_items = if let Ok(tuple) = item.try_to_ref::<PyTuple>(vm) {
409        tuple.as_slice().to_vec()
410    } else {
411        vec![item]
412    };
413
414    for item in arg_items {
415        // Skip PyType objects - they can't be unpacked
416        if item.class().is(vm.ctx.types.type_type) {
417            new_args.push(item);
418            continue;
419        }
420
421        // Try to get __typing_unpacked_tuple_args__
422        if let Ok(sub_args) = item.get_attr(identifier!(vm, __typing_unpacked_tuple_args__), vm)
423            && !sub_args.is(&vm.ctx.none)
424            && let Ok(tuple) = sub_args.try_to_ref::<PyTuple>(vm)
425        {
426            // Check for ellipsis at the end
427            let has_ellipsis_at_end = tuple
428                .as_slice()
429                .last()
430                .is_some_and(|item| item.is(&vm.ctx.ellipsis));
431
432            if !has_ellipsis_at_end {
433                // Safe to unpack - add all elements's PyList_SetSlice
434                for arg in tuple {
435                    new_args.push(arg.clone());
436                }
437                continue;
438            }
439        }
440
441        // Default case: add the item as-is's PyList_Append
442        new_args.push(item);
443    }
444
445    Ok(PyTuple::new_ref(new_args, &vm.ctx))
446}
447
448// _Py_subs_parameters
449pub fn subs_parameters(
450    alias: PyObjectRef, // = self
451    args: PyTupleRef,
452    parameters: PyTupleRef,
453    item: PyObjectRef,
454    vm: &VirtualMachine,
455) -> PyResult<PyTupleRef> {
456    let n_params = parameters.len();
457    if n_params == 0 {
458        return Err(vm.new_type_error(format!("{} is not a generic class", alias.repr(vm)?)));
459    }
460
461    // Step 1: Unpack args
462    let mut item: PyObjectRef = unpack_args(item, vm)?.into();
463
464    // Step 2: Call __typing_prepare_subst__ on each parameter
465    for param in parameters.iter() {
466        if let Ok(prepare) = param.get_attr(identifier!(vm, __typing_prepare_subst__), vm)
467            && !prepare.is(&vm.ctx.none)
468        {
469            // Call prepare(self, item)
470            item = if item.try_to_ref::<PyTuple>(vm).is_ok() {
471                prepare.call((alias.clone(), item.clone()), vm)?
472            } else {
473                // Create a tuple with the single item's "O(O)" format
474                let tuple_args = PyTuple::new_ref(vec![item.clone()], &vm.ctx);
475                prepare.call((alias.clone(), tuple_args.to_pyobject(vm)), vm)?
476            };
477        }
478    }
479
480    // Step 3: Extract final arg items
481    let arg_items = if let Ok(tuple) = item.try_to_ref::<PyTuple>(vm) {
482        tuple.as_slice().to_vec()
483    } else {
484        vec![item.clone()]
485    };
486    let n_items = arg_items.len();
487
488    if n_items != n_params {
489        return Err(vm.new_type_error(format!(
490            "Too {} arguments for {}; actual {}, expected {}",
491            if n_items > n_params { "many" } else { "few" },
492            alias.repr(vm)?,
493            n_items,
494            n_params
495        )));
496    }
497
498    // Step 4: Replace all type variables
499    let mut new_args = Vec::new();
500
501    for arg in args.iter() {
502        // Skip PyType objects
503        if arg.class().is(vm.ctx.types.type_type) {
504            new_args.push(arg.clone());
505            continue;
506        }
507
508        // Recursively substitute params in lists/tuples
509        let is_list = arg.try_to_ref::<PyList>(vm).is_ok();
510        if arg.try_to_ref::<PyTuple>(vm).is_ok() || is_list {
511            let sub_items: Vec<PyObjectRef> = if let Ok(t) = arg.try_to_ref::<PyTuple>(vm) {
512                t.as_slice().to_vec()
513            } else {
514                arg.downcast_ref::<PyList>().unwrap().borrow_vec().to_vec()
515            };
516            let sub_tuple = PyTuple::new_ref(sub_items, &vm.ctx);
517            let sub_result = subs_parameters(
518                alias.clone(),
519                sub_tuple,
520                parameters.clone(),
521                item.clone(),
522                vm,
523            )?;
524            let substituted: PyObjectRef = if is_list {
525                // Convert tuple back to list
526                PyList::from(sub_result.as_slice().to_vec())
527                    .into_ref(&vm.ctx)
528                    .into()
529            } else {
530                sub_result.into()
531            };
532            new_args.push(substituted);
533            continue;
534        }
535
536        // Check if this is an unpacked TypeVarTuple
537        let unpack = is_unpacked_typevartuple(arg, vm)?;
538
539        // Try __typing_subst__ method first
540        let substituted_arg = if let Ok(subst) = arg.get_attr(identifier!(vm, __typing_subst__), vm)
541        {
542            if let Some(iparam) = tuple_index(parameters.as_slice(), arg) {
543                subst.call((arg_items[iparam].clone(),), vm)?
544            } else {
545                subs_tvars(arg.clone(), &parameters, &arg_items, vm)?
546            }
547        } else {
548            subs_tvars(arg.clone(), &parameters, &arg_items, vm)?
549        };
550
551        if unpack {
552            if let Ok(tuple) = substituted_arg.try_to_ref::<PyTuple>(vm) {
553                for elem in tuple {
554                    new_args.push(elem.clone());
555                }
556            } else {
557                new_args.push(substituted_arg);
558            }
559        } else {
560            new_args.push(substituted_arg);
561        }
562    }
563
564    Ok(PyTuple::new_ref(new_args, &vm.ctx))
565}
566
567impl AsMapping for PyGenericAlias {
568    fn as_mapping() -> &'static PyMappingMethods {
569        static AS_MAPPING: LazyLock<PyMappingMethods> = LazyLock::new(|| PyMappingMethods {
570            subscript: atomic_func!(|mapping, needle, vm| {
571                let zelf = PyGenericAlias::mapping_downcast(mapping);
572                PyGenericAlias::__getitem__(zelf.to_owned(), needle.to_owned(), vm)
573            }),
574            ..PyMappingMethods::NOT_IMPLEMENTED
575        });
576        &AS_MAPPING
577    }
578}
579
580impl AsNumber for PyGenericAlias {
581    fn as_number() -> &'static PyNumberMethods {
582        static AS_NUMBER: PyNumberMethods = PyNumberMethods {
583            or: Some(|a, b, vm| PyGenericAlias::__or__(a.to_owned(), b.to_owned(), vm)),
584            ..PyNumberMethods::NOT_IMPLEMENTED
585        };
586        &AS_NUMBER
587    }
588}
589
590impl Callable for PyGenericAlias {
591    type Args = FuncArgs;
592    fn call(zelf: &Py<Self>, args: FuncArgs, vm: &VirtualMachine) -> PyResult {
593        zelf.origin.call(args, vm).map(|obj| {
594            if let Err(exc) = obj.set_attr(identifier!(vm, __orig_class__), zelf.to_owned(), vm)
595                && !exc.fast_isinstance(vm.ctx.exceptions.attribute_error)
596                && !exc.fast_isinstance(vm.ctx.exceptions.type_error)
597            {
598                return Err(exc);
599            }
600            Ok(obj)
601        })?
602    }
603}
604
605impl Comparable for PyGenericAlias {
606    fn cmp(
607        zelf: &Py<Self>,
608        other: &PyObject,
609        op: PyComparisonOp,
610        vm: &VirtualMachine,
611    ) -> PyResult<PyComparisonValue> {
612        op.eq_only(|| {
613            let other = class_or_notimplemented!(Self, other);
614            if zelf.starred != other.starred {
615                return Ok(PyComparisonValue::Implemented(false));
616            }
617            Ok(PyComparisonValue::Implemented(
618                zelf.__origin__()
619                    .rich_compare_bool(&other.__origin__(), PyComparisonOp::Eq, vm)?
620                    && zelf.__args__().rich_compare_bool(
621                        &other.__args__(),
622                        PyComparisonOp::Eq,
623                        vm,
624                    )?,
625            ))
626        })
627    }
628}
629
630impl Hashable for PyGenericAlias {
631    #[inline]
632    fn hash(zelf: &Py<Self>, vm: &VirtualMachine) -> PyResult<hash::PyHash> {
633        Ok(zelf.origin.hash(vm)? ^ zelf.args.as_object().hash(vm)?)
634    }
635}
636
637impl GetAttr for PyGenericAlias {
638    fn getattro(zelf: &Py<Self>, attr: &Py<PyStr>, vm: &VirtualMachine) -> PyResult {
639        let attr_str = attr.as_wtf8();
640        for exc in &ATTR_EXCEPTIONS {
641            if attr_str == *exc {
642                return zelf.as_object().generic_getattr(attr, vm);
643            }
644        }
645        for blocked in &ATTR_BLOCKED {
646            if attr_str == *blocked {
647                return zelf.as_object().generic_getattr(attr, vm);
648            }
649        }
650        zelf.__origin__().get_attr(attr, vm)
651    }
652}
653
654impl Representable for PyGenericAlias {
655    #[inline]
656    fn repr_str(zelf: &Py<Self>, vm: &VirtualMachine) -> PyResult<String> {
657        zelf.repr(vm)
658    }
659}
660
661impl Iterable for PyGenericAlias {
662    fn iter(zelf: PyRef<Self>, vm: &VirtualMachine) -> PyResult {
663        Ok(PyGenericAliasIterator {
664            obj: crate::common::lock::PyMutex::new(Some(zelf.into())),
665        }
666        .into_pyobject(vm))
667    }
668}
669
670// gaiterobject - yields one starred GenericAlias then exhausts
671#[pyclass(module = "types", name = "generic_alias_iterator")]
672#[derive(Debug, PyPayload)]
673pub struct PyGenericAliasIterator {
674    obj: crate::common::lock::PyMutex<Option<PyObjectRef>>,
675}
676
677#[pyclass(with(Representable, Iterable, IterNext))]
678impl PyGenericAliasIterator {
679    #[pymethod]
680    fn __reduce__(&self, vm: &VirtualMachine) -> PyResult<PyTupleRef> {
681        let iter_fn = vm.builtins.get_attr("iter", vm)?;
682        let guard = self.obj.lock();
683        let arg: PyObjectRef = if let Some(ref obj) = *guard {
684            // Not yet exhausted: (iter, (obj,))
685            PyTuple::new_ref(vec![obj.clone()], &vm.ctx).into()
686        } else {
687            // Exhausted: (iter, ((),))
688            let empty = PyTuple::new_ref(vec![], &vm.ctx);
689            PyTuple::new_ref(vec![empty.into()], &vm.ctx).into()
690        };
691        Ok(PyTuple::new_ref(vec![iter_fn, arg], &vm.ctx))
692    }
693}
694
695impl Representable for PyGenericAliasIterator {
696    fn repr_str(_zelf: &Py<Self>, _vm: &VirtualMachine) -> PyResult<String> {
697        Ok("<generic_alias_iterator>".to_owned())
698    }
699}
700
701impl Iterable for PyGenericAliasIterator {
702    fn iter(zelf: PyRef<Self>, _vm: &VirtualMachine) -> PyResult {
703        Ok(zelf.into())
704    }
705}
706
707impl crate::types::IterNext for PyGenericAliasIterator {
708    fn next(zelf: &Py<Self>, vm: &VirtualMachine) -> PyResult<crate::protocol::PyIterReturn> {
709        use crate::protocol::PyIterReturn;
710        let mut guard = zelf.obj.lock();
711        let obj = match guard.take() {
712            Some(obj) => obj,
713            None => return Ok(PyIterReturn::StopIteration(None)),
714        };
715        // Create a starred GenericAlias from the original
716        let alias = obj
717            .downcast_ref::<PyGenericAlias>()
718            .ok_or_else(|| vm.new_type_error("generic_alias_iterator expected GenericAlias"))?;
719        let starred = PyGenericAlias::new(alias.origin.clone(), alias.args.clone(), true, vm);
720        Ok(PyIterReturn::Return(starred.into_pyobject(vm)))
721    }
722}
723
724/// Creates a GenericAlias from type parameters, equivalent to _Py_subscript_generic.
725/// This is used for PEP 695 classes to create Generic[T] from type parameters.
726// _Py_subscript_generic
727pub fn subscript_generic(type_params: PyObjectRef, vm: &VirtualMachine) -> PyResult {
728    let typing_module = vm.import("typing", 0)?;
729    let generic_type = typing_module.get_attr("Generic", vm)?;
730    let generic_alias_class = typing_module.get_attr("_GenericAlias", vm)?;
731
732    let params = if let Ok(tuple) = type_params.try_to_ref::<PyTuple>(vm) {
733        tuple.to_owned()
734    } else {
735        PyTuple::new_ref(vec![type_params], &vm.ctx)
736    };
737
738    let args = crate::stdlib::_typing::unpack_typevartuples(&params, vm)?;
739
740    generic_alias_class.call((generic_type, args.to_pyobject(vm)), vm)
741}
742
743pub fn init(context: &'static Context) {
744    PyGenericAlias::extend_class(context, context.types.generic_alias_type);
745    PyGenericAliasIterator::extend_class(context, context.types.generic_alias_iterator_type);
746}