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