1use once_cell::sync::Lazy;
2
3use super::type_;
4use crate::{
5 atomic_func,
6 builtins::{PyList, PyStr, PyTuple, PyTupleRef, PyType, PyTypeRef},
7 class::PyClassImpl,
8 common::hash,
9 convert::ToPyObject,
10 function::{FuncArgs, PyComparisonValue},
11 protocol::{PyMappingMethods, PyNumberMethods},
12 types::{
13 AsMapping, AsNumber, Callable, Comparable, Constructor, GetAttr, Hashable, PyComparisonOp,
14 Representable,
15 },
16 AsObject, Context, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult, TryFromObject,
17 VirtualMachine,
18};
19use std::fmt;
20
21static ATTR_EXCEPTIONS: [&str; 8] = [
22 "__origin__",
23 "__args__",
24 "__parameters__",
25 "__mro_entries__",
26 "__reduce_ex__", "__reduce__",
28 "__copy__",
29 "__deepcopy__",
30];
31
32#[pyclass(module = "types", name = "GenericAlias")]
33pub struct PyGenericAlias {
34 origin: PyTypeRef,
35 args: PyTupleRef,
36 parameters: PyTupleRef,
37}
38
39impl fmt::Debug for PyGenericAlias {
40 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
41 f.write_str("GenericAlias")
42 }
43}
44
45impl PyPayload for PyGenericAlias {
46 fn class(ctx: &Context) -> &'static Py<PyType> {
47 ctx.types.generic_alias_type
48 }
49}
50
51impl Constructor for PyGenericAlias {
52 type Args = FuncArgs;
53
54 fn py_new(cls: PyTypeRef, args: Self::Args, vm: &VirtualMachine) -> PyResult {
55 if !args.kwargs.is_empty() {
56 return Err(vm.new_type_error("GenericAlias() takes no keyword arguments".to_owned()));
57 }
58 let (origin, arguments): (_, PyObjectRef) = args.bind(vm)?;
59 PyGenericAlias::new(origin, arguments, vm)
60 .into_ref_with_type(vm, cls)
61 .map(Into::into)
62 }
63}
64
65#[pyclass(
66 with(
67 AsNumber,
68 AsMapping,
69 Callable,
70 Comparable,
71 Constructor,
72 GetAttr,
73 Hashable,
74 Representable
75 ),
76 flags(BASETYPE)
77)]
78impl PyGenericAlias {
79 pub fn new(origin: PyTypeRef, args: PyObjectRef, vm: &VirtualMachine) -> Self {
80 let args = if let Ok(tuple) = args.try_to_ref::<PyTuple>(vm) {
81 tuple.to_owned()
82 } else {
83 PyTuple::new_ref(vec![args], &vm.ctx)
84 };
85
86 let parameters = make_parameters(&args, vm);
87 Self {
88 origin,
89 args,
90 parameters,
91 }
92 }
93
94 fn repr(&self, vm: &VirtualMachine) -> PyResult<String> {
95 fn repr_item(obj: PyObjectRef, vm: &VirtualMachine) -> PyResult<String> {
96 if obj.is(&vm.ctx.ellipsis) {
97 return Ok("...".to_string());
98 }
99
100 if vm
101 .get_attribute_opt(obj.clone(), identifier!(vm, __origin__))?
102 .is_some()
103 && vm
104 .get_attribute_opt(obj.clone(), identifier!(vm, __args__))?
105 .is_some()
106 {
107 return Ok(obj.repr(vm)?.as_str().to_string());
108 }
109
110 match (
111 vm.get_attribute_opt(obj.clone(), identifier!(vm, __qualname__))?
112 .and_then(|o| o.downcast_ref::<PyStr>().map(|n| n.as_str().to_string())),
113 vm.get_attribute_opt(obj.clone(), identifier!(vm, __module__))?
114 .and_then(|o| o.downcast_ref::<PyStr>().map(|m| m.as_str().to_string())),
115 ) {
116 (None, _) | (_, None) => Ok(obj.repr(vm)?.as_str().to_string()),
117 (Some(qualname), Some(module)) => Ok(if module == "builtins" {
118 qualname
119 } else {
120 format!("{module}.{qualname}")
121 }),
122 }
123 }
124
125 Ok(format!(
126 "{}[{}]",
127 repr_item(self.origin.clone().into(), vm)?,
128 if self.args.len() == 0 {
129 "()".to_owned()
130 } else {
131 self.args
132 .iter()
133 .map(|o| repr_item(o.clone(), vm))
134 .collect::<PyResult<Vec<_>>>()?
135 .join(", ")
136 }
137 ))
138 }
139
140 #[pygetset(magic)]
141 fn parameters(&self) -> PyObjectRef {
142 self.parameters.clone().into()
143 }
144
145 #[pygetset(magic)]
146 fn args(&self) -> PyObjectRef {
147 self.args.clone().into()
148 }
149
150 #[pygetset(magic)]
151 fn origin(&self) -> PyObjectRef {
152 self.origin.clone().into()
153 }
154
155 #[pymethod(magic)]
156 fn getitem(&self, needle: PyObjectRef, vm: &VirtualMachine) -> PyResult {
157 let new_args = subs_parameters(
158 |vm| self.repr(vm),
159 self.args.clone(),
160 self.parameters.clone(),
161 needle,
162 vm,
163 )?;
164
165 Ok(
166 PyGenericAlias::new(self.origin.clone(), new_args.to_pyobject(vm), vm)
167 .into_pyobject(vm),
168 )
169 }
170
171 #[pymethod(magic)]
172 fn dir(&self, vm: &VirtualMachine) -> PyResult<PyList> {
173 let dir = vm.dir(Some(self.origin()))?;
174 for exc in &ATTR_EXCEPTIONS {
175 if !dir.contains((*exc).to_pyobject(vm), vm)? {
176 dir.append((*exc).to_pyobject(vm));
177 }
178 }
179 Ok(dir)
180 }
181
182 #[pymethod(magic)]
183 fn reduce(zelf: &Py<Self>, vm: &VirtualMachine) -> (PyTypeRef, (PyTypeRef, PyTupleRef)) {
184 (
185 vm.ctx.types.generic_alias_type.to_owned(),
186 (zelf.origin.clone(), zelf.args.clone()),
187 )
188 }
189
190 #[pymethod(magic)]
191 fn mro_entries(&self, _bases: PyObjectRef, vm: &VirtualMachine) -> PyTupleRef {
192 PyTuple::new_ref(vec![self.origin()], &vm.ctx)
193 }
194
195 #[pymethod(magic)]
196 fn instancecheck(_zelf: PyRef<Self>, _obj: PyObjectRef, vm: &VirtualMachine) -> PyResult {
197 Err(vm
198 .new_type_error("isinstance() argument 2 cannot be a parameterized generic".to_owned()))
199 }
200
201 #[pymethod(magic)]
202 fn subclasscheck(_zelf: PyRef<Self>, _obj: PyObjectRef, vm: &VirtualMachine) -> PyResult {
203 Err(vm
204 .new_type_error("issubclass() argument 2 cannot be a parameterized generic".to_owned()))
205 }
206
207 #[pymethod(magic)]
208 fn ror(zelf: PyObjectRef, other: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef {
209 type_::or_(other, zelf, vm)
210 }
211
212 #[pymethod(magic)]
213 fn or(zelf: PyObjectRef, other: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef {
214 type_::or_(zelf, other, vm)
215 }
216}
217
218pub(crate) fn is_typevar(obj: &PyObjectRef, vm: &VirtualMachine) -> bool {
219 let class = obj.class();
220 "TypeVar" == &*class.slot_name()
221 && class
222 .get_attr(identifier!(vm, __module__))
223 .and_then(|o| o.downcast_ref::<PyStr>().map(|s| s.as_str() == "typing"))
224 .unwrap_or(false)
225}
226
227pub(crate) fn make_parameters(args: &Py<PyTuple>, vm: &VirtualMachine) -> PyTupleRef {
228 let mut parameters: Vec<PyObjectRef> = Vec::with_capacity(args.len());
229 for arg in args {
230 if is_typevar(arg, vm) {
231 if !parameters.iter().any(|param| param.is(arg)) {
232 parameters.push(arg.clone());
233 }
234 } else if let Ok(obj) = arg.get_attr(identifier!(vm, __parameters__), vm) {
235 if let Ok(sub_params) = obj.try_to_ref::<PyTuple>(vm) {
236 for sub_param in sub_params {
237 if !parameters.iter().any(|param| param.is(sub_param)) {
238 parameters.push(sub_param.clone());
239 }
240 }
241 }
242 }
243 }
244 parameters.shrink_to_fit();
245
246 PyTuple::new_ref(parameters, &vm.ctx)
247}
248
249#[inline]
250fn tuple_index(tuple: &PyTupleRef, item: &PyObjectRef) -> Option<usize> {
251 tuple.iter().position(|element| element.is(item))
252}
253
254fn subs_tvars(
255 obj: PyObjectRef,
256 params: &PyTupleRef,
257 argitems: &[PyObjectRef],
258 vm: &VirtualMachine,
259) -> PyResult {
260 obj.get_attr(identifier!(vm, __parameters__), vm)
261 .ok()
262 .and_then(|sub_params| {
263 PyTupleRef::try_from_object(vm, sub_params)
264 .ok()
265 .and_then(|sub_params| {
266 if sub_params.len() > 0 {
267 let sub_args = sub_params
268 .iter()
269 .map(|arg| {
270 if let Some(idx) = tuple_index(params, arg) {
271 argitems[idx].clone()
272 } else {
273 arg.clone()
274 }
275 })
276 .collect::<Vec<_>>();
277 let sub_args: PyObjectRef = PyTuple::new_ref(sub_args, &vm.ctx).into();
278 Some(obj.get_item(&*sub_args, vm))
279 } else {
280 None
281 }
282 })
283 })
284 .unwrap_or(Ok(obj))
285}
286
287pub fn subs_parameters<F: Fn(&VirtualMachine) -> PyResult<String>>(
288 repr: F,
289 args: PyTupleRef,
290 parameters: PyTupleRef,
291 needle: PyObjectRef,
292 vm: &VirtualMachine,
293) -> PyResult<PyTupleRef> {
294 let num_params = parameters.len();
295 if num_params == 0 {
296 return Err(vm.new_type_error(format!("There are no type variables left in {}", repr(vm)?)));
297 }
298
299 let items = needle.try_to_ref::<PyTuple>(vm);
300 let arg_items = match items {
301 Ok(tuple) => tuple.as_slice(),
302 Err(_) => std::slice::from_ref(&needle),
303 };
304
305 let num_items = arg_items.len();
306 if num_params != num_items {
307 let plural = if num_items > num_params {
308 "many"
309 } else {
310 "few"
311 };
312 return Err(vm.new_type_error(format!("Too {} arguments for {}", plural, repr(vm)?)));
313 }
314
315 let new_args = args
316 .iter()
317 .map(|arg| {
318 if is_typevar(arg, vm) {
319 let idx = tuple_index(¶meters, arg).unwrap();
320 Ok(arg_items[idx].clone())
321 } else {
322 subs_tvars(arg.clone(), ¶meters, arg_items, vm)
323 }
324 })
325 .collect::<PyResult<Vec<_>>>()?;
326
327 Ok(PyTuple::new_ref(new_args, &vm.ctx))
328}
329
330impl AsMapping for PyGenericAlias {
331 fn as_mapping() -> &'static PyMappingMethods {
332 static AS_MAPPING: Lazy<PyMappingMethods> = Lazy::new(|| PyMappingMethods {
333 subscript: atomic_func!(|mapping, needle, vm| {
334 PyGenericAlias::mapping_downcast(mapping).getitem(needle.to_owned(), vm)
335 }),
336 ..PyMappingMethods::NOT_IMPLEMENTED
337 });
338 &AS_MAPPING
339 }
340}
341
342impl AsNumber for PyGenericAlias {
343 fn as_number() -> &'static PyNumberMethods {
344 static AS_NUMBER: PyNumberMethods = PyNumberMethods {
345 or: Some(|a, b, vm| Ok(PyGenericAlias::or(a.to_owned(), b.to_owned(), vm))),
346 ..PyNumberMethods::NOT_IMPLEMENTED
347 };
348 &AS_NUMBER
349 }
350}
351
352impl Callable for PyGenericAlias {
353 type Args = FuncArgs;
354 fn call(zelf: &Py<Self>, args: FuncArgs, vm: &VirtualMachine) -> PyResult {
355 PyType::call(&zelf.origin, args, vm).map(|obj| {
356 if let Err(exc) = obj.set_attr(identifier!(vm, __orig_class__), zelf.to_owned(), vm) {
357 if !exc.fast_isinstance(vm.ctx.exceptions.attribute_error)
358 && !exc.fast_isinstance(vm.ctx.exceptions.type_error)
359 {
360 return Err(exc);
361 }
362 }
363 Ok(obj)
364 })?
365 }
366}
367
368impl Comparable for PyGenericAlias {
369 fn cmp(
370 zelf: &Py<Self>,
371 other: &PyObject,
372 op: PyComparisonOp,
373 vm: &VirtualMachine,
374 ) -> PyResult<PyComparisonValue> {
375 op.eq_only(|| {
376 let other = class_or_notimplemented!(Self, other);
377 Ok(PyComparisonValue::Implemented(
378 if !zelf
379 .origin()
380 .rich_compare_bool(&other.origin(), PyComparisonOp::Eq, vm)?
381 {
382 false
383 } else {
384 zelf.args()
385 .rich_compare_bool(&other.args(), PyComparisonOp::Eq, vm)?
386 },
387 ))
388 })
389 }
390}
391
392impl Hashable for PyGenericAlias {
393 #[inline]
394 fn hash(zelf: &Py<Self>, vm: &VirtualMachine) -> PyResult<hash::PyHash> {
395 Ok(zelf.origin.as_object().hash(vm)? ^ zelf.args.as_object().hash(vm)?)
396 }
397}
398
399impl GetAttr for PyGenericAlias {
400 fn getattro(zelf: &Py<Self>, attr: &Py<PyStr>, vm: &VirtualMachine) -> PyResult {
401 for exc in ATTR_EXCEPTIONS.iter() {
402 if *(*exc) == attr.to_string() {
403 return zelf.as_object().generic_getattr(attr, vm);
404 }
405 }
406 zelf.origin().get_attr(attr, vm)
407 }
408}
409
410impl Representable for PyGenericAlias {
411 #[inline]
412 fn repr_str(zelf: &Py<Self>, vm: &VirtualMachine) -> PyResult<String> {
413 zelf.repr(vm)
414 }
415}
416
417pub fn init(context: &Context) {
418 let generic_alias_type = &context.types.generic_alias_type;
419 PyGenericAlias::extend_class(context, generic_alias_type);
420}