pyo3/inspect/
types.rs

1//! Data types used to describe runtime Python types.
2
3use std::borrow::Cow;
4use std::fmt::{Display, Formatter};
5
6/// Designation of a Python type.
7///
8/// This enum is used to handle advanced types, such as types with generics.
9/// Its [`Display`] implementation can be used to convert to the type hint notation (e.g. `List[int]`).
10#[derive(Debug, Clone, Eq, PartialEq)]
11pub enum TypeInfo {
12    /// The type `typing.Any`, which represents any possible value (unknown type).
13    Any,
14    /// The type `typing.None`.
15    None,
16    /// The type `typing.NoReturn`, which represents functions that never return (they can still panic / throw, similar to `never` in Rust).
17    NoReturn,
18    /// The type `typing.Callable`.
19    ///
20    /// The first argument represents the parameters of the callable:
21    /// - `Some` of a vector of types to represent the signature,
22    /// - `None` if the signature is unknown (allows any number of arguments with type `Any`).
23    ///
24    /// The second argument represents the return type.
25    Callable(Option<Vec<TypeInfo>>, Box<TypeInfo>),
26    /// The type `typing.tuple`.
27    ///
28    /// The argument represents the contents of the tuple:
29    /// - `Some` of a vector of types to represent the accepted types,
30    /// - `Some` of an empty vector for the empty tuple,
31    /// - `None` if the number and type of accepted values is unknown.
32    ///
33    /// If the number of accepted values is unknown, but their type is, use [`Self::UnsizedTypedTuple`].
34    Tuple(Option<Vec<TypeInfo>>),
35    /// The type `typing.Tuple`.
36    ///
37    /// Use this variant to represent a tuple of unknown size but of known types.
38    ///
39    /// If the type is unknown, or if the number of elements is known, use [`Self::Tuple`].
40    UnsizedTypedTuple(Box<TypeInfo>),
41    /// A Python class.
42    Class {
43        /// The module this class comes from.
44        module: ModuleName,
45        /// The name of this class, as it appears in a type hint.
46        name: Cow<'static, str>,
47        /// The generics accepted by this class (empty vector if this class is not generic).
48        type_vars: Vec<TypeInfo>,
49    },
50}
51
52/// Declares which module a type is a part of.
53#[derive(Debug, Clone, Eq, PartialEq)]
54pub enum ModuleName {
55    /// The type is built-in: it doesn't need to be imported.
56    Builtin,
57    /// The type is in the current module: it doesn't need to be imported in this module, but needs to be imported in others.
58    CurrentModule,
59    /// The type is in the specified module.
60    Module(Cow<'static, str>),
61}
62
63impl TypeInfo {
64    /// Returns the module in which a type is declared.
65    ///
66    /// Returns `None` if the type is declared in the current module.
67    pub fn module_name(&self) -> Option<&str> {
68        match self {
69            TypeInfo::Any
70            | TypeInfo::None
71            | TypeInfo::NoReturn
72            | TypeInfo::Callable(_, _)
73            | TypeInfo::Tuple(_)
74            | TypeInfo::UnsizedTypedTuple(_) => Some("typing"),
75            TypeInfo::Class { module, .. } => match module {
76                ModuleName::Builtin => Some("builtins"),
77                ModuleName::CurrentModule => None,
78                ModuleName::Module(name) => Some(name),
79            },
80        }
81    }
82
83    /// Returns the name of a type.
84    ///
85    /// The name of a type is the part of the hint that is not generic (e.g. `List` instead of `List[int]`).
86    pub fn name(&self) -> Cow<'_, str> {
87        Cow::from(match self {
88            TypeInfo::Any => "Any",
89            TypeInfo::None => "None",
90            TypeInfo::NoReturn => "NoReturn",
91            TypeInfo::Callable(_, _) => "Callable",
92            TypeInfo::Tuple(_) => "Tuple",
93            TypeInfo::UnsizedTypedTuple(_) => "Tuple",
94            TypeInfo::Class { name, .. } => name,
95        })
96    }
97}
98
99// Utilities for easily instantiating TypeInfo structures for built-in/common types.
100impl TypeInfo {
101    /// The Python `Optional` type.
102    pub fn optional_of(t: TypeInfo) -> TypeInfo {
103        TypeInfo::Class {
104            module: ModuleName::Module(Cow::from("typing")),
105            name: Cow::from("Optional"),
106            type_vars: vec![t],
107        }
108    }
109
110    /// The Python `Union` type.
111    pub fn union_of(types: &[TypeInfo]) -> TypeInfo {
112        TypeInfo::Class {
113            module: ModuleName::Module(Cow::from("typing")),
114            name: Cow::from("Union"),
115            type_vars: types.to_vec(),
116        }
117    }
118
119    /// The Python `List` type.
120    pub fn list_of(t: TypeInfo) -> TypeInfo {
121        TypeInfo::Class {
122            module: ModuleName::Module(Cow::from("typing")),
123            name: Cow::from("List"),
124            type_vars: vec![t],
125        }
126    }
127
128    /// The Python `Sequence` type.
129    pub fn sequence_of(t: TypeInfo) -> TypeInfo {
130        TypeInfo::Class {
131            module: ModuleName::Module(Cow::from("typing")),
132            name: Cow::from("Sequence"),
133            type_vars: vec![t],
134        }
135    }
136
137    /// The Python `Set` type.
138    pub fn set_of(t: TypeInfo) -> TypeInfo {
139        TypeInfo::Class {
140            module: ModuleName::Module(Cow::from("typing")),
141            name: Cow::from("Set"),
142            type_vars: vec![t],
143        }
144    }
145
146    /// The Python `FrozenSet` type.
147    pub fn frozen_set_of(t: TypeInfo) -> TypeInfo {
148        TypeInfo::Class {
149            module: ModuleName::Module(Cow::from("typing")),
150            name: Cow::from("FrozenSet"),
151            type_vars: vec![t],
152        }
153    }
154
155    /// The Python `Iterable` type.
156    pub fn iterable_of(t: TypeInfo) -> TypeInfo {
157        TypeInfo::Class {
158            module: ModuleName::Module(Cow::from("typing")),
159            name: Cow::from("Iterable"),
160            type_vars: vec![t],
161        }
162    }
163
164    /// The Python `Iterator` type.
165    pub fn iterator_of(t: TypeInfo) -> TypeInfo {
166        TypeInfo::Class {
167            module: ModuleName::Module(Cow::from("typing")),
168            name: Cow::from("Iterator"),
169            type_vars: vec![t],
170        }
171    }
172
173    /// The Python `Dict` type.
174    pub fn dict_of(k: TypeInfo, v: TypeInfo) -> TypeInfo {
175        TypeInfo::Class {
176            module: ModuleName::Module(Cow::from("typing")),
177            name: Cow::from("Dict"),
178            type_vars: vec![k, v],
179        }
180    }
181
182    /// The Python `Mapping` type.
183    pub fn mapping_of(k: TypeInfo, v: TypeInfo) -> TypeInfo {
184        TypeInfo::Class {
185            module: ModuleName::Module(Cow::from("typing")),
186            name: Cow::from("Mapping"),
187            type_vars: vec![k, v],
188        }
189    }
190
191    /// Convenience factory for non-generic builtins (e.g. `int`).
192    pub fn builtin(name: &'static str) -> TypeInfo {
193        TypeInfo::Class {
194            module: ModuleName::Builtin,
195            name: Cow::from(name),
196            type_vars: vec![],
197        }
198    }
199}
200
201impl Display for TypeInfo {
202    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
203        match self {
204            TypeInfo::Any | TypeInfo::None | TypeInfo::NoReturn => write!(f, "{}", self.name()),
205            TypeInfo::Callable(input, output) => {
206                write!(f, "Callable[")?;
207
208                if let Some(input) = input {
209                    write!(f, "[")?;
210                    let mut comma = false;
211                    for arg in input {
212                        if comma {
213                            write!(f, ", ")?;
214                        }
215                        write!(f, "{}", arg)?;
216                        comma = true;
217                    }
218                    write!(f, "]")?;
219                } else {
220                    write!(f, "...")?;
221                }
222
223                write!(f, ", {}]", output)
224            }
225            TypeInfo::Tuple(types) => {
226                write!(f, "Tuple[")?;
227
228                if let Some(types) = types {
229                    if types.is_empty() {
230                        write!(f, "()")?;
231                    } else {
232                        let mut comma = false;
233                        for t in types {
234                            if comma {
235                                write!(f, ", ")?;
236                            }
237                            write!(f, "{}", t)?;
238                            comma = true;
239                        }
240                    }
241                } else {
242                    write!(f, "...")?;
243                }
244
245                write!(f, "]")
246            }
247            TypeInfo::UnsizedTypedTuple(t) => write!(f, "Tuple[{}, ...]", t),
248            TypeInfo::Class {
249                name, type_vars, ..
250            } => {
251                write!(f, "{}", name)?;
252
253                if !type_vars.is_empty() {
254                    write!(f, "[")?;
255
256                    let mut comma = false;
257                    for var in type_vars {
258                        if comma {
259                            write!(f, ", ")?;
260                        }
261                        write!(f, "{}", var)?;
262                        comma = true;
263                    }
264
265                    write!(f, "]")
266                } else {
267                    Ok(())
268                }
269            }
270        }
271    }
272}
273
274#[cfg(test)]
275mod test {
276    use std::borrow::Cow;
277
278    use crate::inspect::types::{ModuleName, TypeInfo};
279
280    #[track_caller]
281    pub fn assert_display(t: &TypeInfo, expected: &str) {
282        assert_eq!(format!("{}", t), expected)
283    }
284
285    #[test]
286    fn basic() {
287        assert_display(&TypeInfo::Any, "Any");
288        assert_display(&TypeInfo::None, "None");
289        assert_display(&TypeInfo::NoReturn, "NoReturn");
290
291        assert_display(&TypeInfo::builtin("int"), "int");
292    }
293
294    #[test]
295    fn callable() {
296        let any_to_int = TypeInfo::Callable(None, Box::new(TypeInfo::builtin("int")));
297        assert_display(&any_to_int, "Callable[..., int]");
298
299        let sum = TypeInfo::Callable(
300            Some(vec![TypeInfo::builtin("int"), TypeInfo::builtin("int")]),
301            Box::new(TypeInfo::builtin("int")),
302        );
303        assert_display(&sum, "Callable[[int, int], int]");
304    }
305
306    #[test]
307    fn tuple() {
308        let any = TypeInfo::Tuple(None);
309        assert_display(&any, "Tuple[...]");
310
311        let triple = TypeInfo::Tuple(Some(vec![
312            TypeInfo::builtin("int"),
313            TypeInfo::builtin("str"),
314            TypeInfo::builtin("bool"),
315        ]));
316        assert_display(&triple, "Tuple[int, str, bool]");
317
318        let empty = TypeInfo::Tuple(Some(vec![]));
319        assert_display(&empty, "Tuple[()]");
320
321        let typed = TypeInfo::UnsizedTypedTuple(Box::new(TypeInfo::builtin("bool")));
322        assert_display(&typed, "Tuple[bool, ...]");
323    }
324
325    #[test]
326    fn class() {
327        let class1 = TypeInfo::Class {
328            module: ModuleName::CurrentModule,
329            name: Cow::from("MyClass"),
330            type_vars: vec![],
331        };
332        assert_display(&class1, "MyClass");
333
334        let class2 = TypeInfo::Class {
335            module: ModuleName::CurrentModule,
336            name: Cow::from("MyClass"),
337            type_vars: vec![TypeInfo::builtin("int"), TypeInfo::builtin("bool")],
338        };
339        assert_display(&class2, "MyClass[int, bool]");
340    }
341
342    #[test]
343    fn collections() {
344        let int = TypeInfo::builtin("int");
345        let bool = TypeInfo::builtin("bool");
346        let str = TypeInfo::builtin("str");
347
348        let list = TypeInfo::list_of(int.clone());
349        assert_display(&list, "List[int]");
350
351        let sequence = TypeInfo::sequence_of(bool.clone());
352        assert_display(&sequence, "Sequence[bool]");
353
354        let optional = TypeInfo::optional_of(str.clone());
355        assert_display(&optional, "Optional[str]");
356
357        let iterable = TypeInfo::iterable_of(int.clone());
358        assert_display(&iterable, "Iterable[int]");
359
360        let iterator = TypeInfo::iterator_of(bool);
361        assert_display(&iterator, "Iterator[bool]");
362
363        let dict = TypeInfo::dict_of(int.clone(), str.clone());
364        assert_display(&dict, "Dict[int, str]");
365
366        let mapping = TypeInfo::mapping_of(int, str.clone());
367        assert_display(&mapping, "Mapping[int, str]");
368
369        let set = TypeInfo::set_of(str.clone());
370        assert_display(&set, "Set[str]");
371
372        let frozen_set = TypeInfo::frozen_set_of(str);
373        assert_display(&frozen_set, "FrozenSet[str]");
374    }
375
376    #[test]
377    fn complicated() {
378        let int = TypeInfo::builtin("int");
379        assert_display(&int, "int");
380
381        let bool = TypeInfo::builtin("bool");
382        assert_display(&bool, "bool");
383
384        let str = TypeInfo::builtin("str");
385        assert_display(&str, "str");
386
387        let any = TypeInfo::Any;
388        assert_display(&any, "Any");
389
390        let params = TypeInfo::union_of(&[int.clone(), str]);
391        assert_display(&params, "Union[int, str]");
392
393        let func = TypeInfo::Callable(Some(vec![params, any]), Box::new(bool));
394        assert_display(&func, "Callable[[Union[int, str], Any], bool]");
395
396        let dict = TypeInfo::mapping_of(int, func);
397        assert_display(
398            &dict,
399            "Mapping[int, Callable[[Union[int, str], Any], bool]]",
400        );
401    }
402}
403
404#[cfg(test)]
405mod conversion {
406    use std::collections::{HashMap, HashSet};
407
408    use crate::inspect::types::test::assert_display;
409    use crate::{FromPyObject, IntoPyObject};
410
411    #[test]
412    fn unsigned_int() {
413        assert_display(&usize::type_output(), "int");
414        assert_display(&usize::type_input(), "int");
415
416        assert_display(&u8::type_output(), "int");
417        assert_display(&u8::type_input(), "int");
418
419        assert_display(&u16::type_output(), "int");
420        assert_display(&u16::type_input(), "int");
421
422        assert_display(&u32::type_output(), "int");
423        assert_display(&u32::type_input(), "int");
424
425        assert_display(&u64::type_output(), "int");
426        assert_display(&u64::type_input(), "int");
427    }
428
429    #[test]
430    fn signed_int() {
431        assert_display(&isize::type_output(), "int");
432        assert_display(&isize::type_input(), "int");
433
434        assert_display(&i8::type_output(), "int");
435        assert_display(&i8::type_input(), "int");
436
437        assert_display(&i16::type_output(), "int");
438        assert_display(&i16::type_input(), "int");
439
440        assert_display(&i32::type_output(), "int");
441        assert_display(&i32::type_input(), "int");
442
443        assert_display(&i64::type_output(), "int");
444        assert_display(&i64::type_input(), "int");
445    }
446
447    #[test]
448    fn float() {
449        assert_display(&f32::type_output(), "float");
450        assert_display(&f32::type_input(), "float");
451
452        assert_display(&f64::type_output(), "float");
453        assert_display(&f64::type_input(), "float");
454    }
455
456    #[test]
457    fn bool() {
458        assert_display(&bool::type_output(), "bool");
459        assert_display(&bool::type_input(), "bool");
460    }
461
462    #[test]
463    fn text() {
464        assert_display(&String::type_output(), "str");
465        assert_display(&String::type_input(), "str");
466
467        assert_display(&<&[u8]>::type_output(), "Union[bytes, List[int]]");
468        assert_display(&<&[String]>::type_output(), "Union[bytes, List[str]]");
469        assert_display(
470            &<&[u8] as crate::conversion::FromPyObjectBound>::type_input(),
471            "bytes",
472        );
473    }
474
475    #[test]
476    fn collections() {
477        assert_display(&<Vec<usize>>::type_output(), "List[int]");
478        assert_display(&<Vec<usize>>::type_input(), "Sequence[int]");
479
480        assert_display(&<HashSet<usize>>::type_output(), "Set[int]");
481        assert_display(&<HashSet<usize>>::type_input(), "Set[int]");
482
483        assert_display(&<HashMap<usize, f32>>::type_output(), "Dict[int, float]");
484        assert_display(&<HashMap<usize, f32>>::type_input(), "Mapping[int, float]");
485
486        assert_display(&<(usize, f32)>::type_input(), "Tuple[int, float]");
487    }
488}