pyo3/inspect/
mod.rs

1//! Runtime inspection of objects exposed to Python.
2//!
3//! Tracking issue: <https://github.com/PyO3/pyo3/issues/2454>.
4
5use std::fmt;
6use std::fmt::Formatter;
7
8pub mod types;
9
10/// A [type hint](https://docs.python.org/3/glossary.html#term-type-hint).
11///
12/// This struct aims at being used in `const` contexts like in [`FromPyObject::INPUT_TYPE`](crate::FromPyObject::INPUT_TYPE) and [`IntoPyObject::OUTPUT_TYPE`](crate::IntoPyObject::OUTPUT_TYPE).
13///
14/// ```
15/// use pyo3::inspect::TypeHint;
16///
17/// const T: TypeHint = TypeHint::union(&[TypeHint::builtin("int"), TypeHint::module_attr("b", "B")]);
18/// assert_eq!(T.to_string(), "int | b.B");
19/// ```
20#[derive(Clone, Copy)]
21pub struct TypeHint {
22    inner: TypeHintExpr,
23}
24
25#[derive(Clone, Copy)]
26enum TypeHintExpr {
27    /// A local name. Used only when the module is unknown.
28    Local { id: &'static str },
29    /// A built-name like `list` or `datetime`. Used for built-in types or modules.
30    Builtin { id: &'static str },
31    /// A module member like `datetime.time` where module = `datetime` and attr = `time`
32    ModuleAttribute {
33        module: &'static str,
34        attr: &'static str,
35    },
36    /// A union `elts[0] | ... | elts[len]`
37    Union { elts: &'static [TypeHint] },
38    /// A subscript `main[*args]`
39    Subscript {
40        value: &'static TypeHint,
41        slice: &'static [TypeHint],
42    },
43}
44
45impl TypeHint {
46    /// A builtin like `int` or `list`
47    ///
48    /// ```
49    /// use pyo3::inspect::TypeHint;
50    ///
51    /// const T: TypeHint = TypeHint::builtin("int");
52    /// assert_eq!(T.to_string(), "int");
53    /// ```
54    pub const fn builtin(name: &'static str) -> Self {
55        Self {
56            inner: TypeHintExpr::Builtin { id: name },
57        }
58    }
59
60    /// A type contained in a module like `datetime.time`
61    ///
62    /// ```
63    /// use pyo3::inspect::TypeHint;
64    ///
65    /// const T: TypeHint = TypeHint::module_attr("datetime", "time");
66    /// assert_eq!(T.to_string(), "datetime.time");
67    /// ```
68    pub const fn module_attr(module: &'static str, attr: &'static str) -> Self {
69        Self {
70            inner: if matches!(module.as_bytes(), b"builtins") {
71                TypeHintExpr::Builtin { id: attr }
72            } else {
73                TypeHintExpr::ModuleAttribute { module, attr }
74            },
75        }
76    }
77
78    /// A value in the local module which module is unknown
79    #[doc(hidden)]
80    pub const fn local(name: &'static str) -> Self {
81        Self {
82            inner: TypeHintExpr::Local { id: name },
83        }
84    }
85
86    /// The union of multiple types
87    ///
88    /// ```
89    /// use pyo3::inspect::TypeHint;
90    ///
91    /// const T: TypeHint = TypeHint::union(&[TypeHint::builtin("int"), TypeHint::builtin("float")]);
92    /// assert_eq!(T.to_string(), "int | float");
93    /// ```
94    pub const fn union(elts: &'static [TypeHint]) -> Self {
95        Self {
96            inner: TypeHintExpr::Union { elts },
97        }
98    }
99
100    /// A subscribed type, often a container
101    ///
102    /// ```
103    /// use pyo3::inspect::TypeHint;
104    ///
105    /// const T: TypeHint = TypeHint::subscript(&TypeHint::builtin("dict"), &[TypeHint::builtin("int"), TypeHint::builtin("str")]);
106    /// assert_eq!(T.to_string(), "dict[int, str]");
107    /// ```
108    pub const fn subscript(value: &'static Self, slice: &'static [Self]) -> Self {
109        Self {
110            inner: TypeHintExpr::Subscript { value, slice },
111        }
112    }
113}
114
115/// Serialize the type for introspection and return the number of written bytes
116///
117/// We use the same AST as Python: <https://docs.python.org/3/library/ast.html#abstract-grammar>
118#[doc(hidden)]
119pub const fn serialize_for_introspection(hint: &TypeHint, mut output: &mut [u8]) -> usize {
120    let original_len = output.len();
121    match &hint.inner {
122        TypeHintExpr::Local { id } => {
123            output = write_slice_and_move_forward(b"{\"type\":\"local\",\"id\":\"", output);
124            output = write_slice_and_move_forward(id.as_bytes(), output);
125            output = write_slice_and_move_forward(b"\"}", output);
126        }
127        TypeHintExpr::Builtin { id } => {
128            output = write_slice_and_move_forward(b"{\"type\":\"builtin\",\"id\":\"", output);
129            output = write_slice_and_move_forward(id.as_bytes(), output);
130            output = write_slice_and_move_forward(b"\"}", output);
131        }
132        TypeHintExpr::ModuleAttribute { module, attr } => {
133            output = write_slice_and_move_forward(b"{\"type\":\"attribute\",\"module\":\"", output);
134            output = write_slice_and_move_forward(module.as_bytes(), output);
135            output = write_slice_and_move_forward(b"\",\"attr\":\"", output);
136            output = write_slice_and_move_forward(attr.as_bytes(), output);
137            output = write_slice_and_move_forward(b"\"}", output);
138        }
139        TypeHintExpr::Union { elts } => {
140            output = write_slice_and_move_forward(b"{\"type\":\"union\",\"elts\":[", output);
141            let mut i = 0;
142            while i < elts.len() {
143                if i > 0 {
144                    output = write_slice_and_move_forward(b",", output);
145                }
146                output = write_type_hint_and_move_forward(&elts[i], output);
147                i += 1;
148            }
149            output = write_slice_and_move_forward(b"]}", output);
150        }
151        TypeHintExpr::Subscript { value, slice } => {
152            output = write_slice_and_move_forward(b"{\"type\":\"subscript\",\"value\":", output);
153            output = write_type_hint_and_move_forward(value, output);
154            output = write_slice_and_move_forward(b",\"slice\":[", output);
155            let mut i = 0;
156            while i < slice.len() {
157                if i > 0 {
158                    output = write_slice_and_move_forward(b",", output);
159                }
160                output = write_type_hint_and_move_forward(&slice[i], output);
161                i += 1;
162            }
163            output = write_slice_and_move_forward(b"]}", output);
164        }
165    }
166    original_len - output.len()
167}
168
169/// Length required by [`serialize_for_introspection`]
170#[doc(hidden)]
171pub const fn serialized_len_for_introspection(hint: &TypeHint) -> usize {
172    match &hint.inner {
173        TypeHintExpr::Local { id } => 24 + id.len(),
174        TypeHintExpr::Builtin { id } => 26 + id.len(),
175        TypeHintExpr::ModuleAttribute { module, attr } => 42 + module.len() + attr.len(),
176        TypeHintExpr::Union { elts } => {
177            let mut count = 26;
178            let mut i = 0;
179            while i < elts.len() {
180                if i > 0 {
181                    count += 1;
182                }
183                count += serialized_len_for_introspection(&elts[i]);
184                i += 1;
185            }
186            count
187        }
188        TypeHintExpr::Subscript { value, slice } => {
189            let mut count = 40 + serialized_len_for_introspection(value);
190            let mut i = 0;
191            while i < slice.len() {
192                if i > 0 {
193                    count += 1;
194                }
195                count += serialized_len_for_introspection(&slice[i]);
196                i += 1;
197            }
198            count
199        }
200    }
201}
202
203impl fmt::Display for TypeHint {
204    #[inline]
205    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
206        match &self.inner {
207            TypeHintExpr::Builtin { id } | TypeHintExpr::Local { id } => id.fmt(f),
208            TypeHintExpr::ModuleAttribute { module, attr } => {
209                module.fmt(f)?;
210                f.write_str(".")?;
211                attr.fmt(f)
212            }
213            TypeHintExpr::Union { elts } => {
214                for (i, elt) in elts.iter().enumerate() {
215                    if i > 0 {
216                        f.write_str(" | ")?;
217                    }
218                    elt.fmt(f)?;
219                }
220                Ok(())
221            }
222            TypeHintExpr::Subscript { value, slice } => {
223                value.fmt(f)?;
224                f.write_str("[")?;
225                for (i, elt) in slice.iter().enumerate() {
226                    if i > 0 {
227                        f.write_str(", ")?;
228                    }
229                    elt.fmt(f)?;
230                }
231                f.write_str("]")
232            }
233        }
234    }
235}
236
237const fn write_slice_and_move_forward<'a>(value: &[u8], output: &'a mut [u8]) -> &'a mut [u8] {
238    // TODO: use copy_from_slice with MSRV 1.87+
239    let mut i = 0;
240    while i < value.len() {
241        output[i] = value[i];
242        i += 1;
243    }
244    output.split_at_mut(value.len()).1
245}
246
247const fn write_type_hint_and_move_forward<'a>(
248    value: &TypeHint,
249    output: &'a mut [u8],
250) -> &'a mut [u8] {
251    let written = serialize_for_introspection(value, output);
252    output.split_at_mut(written).1
253}
254
255#[cfg(test)]
256mod tests {
257    use super::*;
258
259    #[test]
260    fn test_to_string() {
261        const T: TypeHint = TypeHint::subscript(
262            &TypeHint::builtin("dict"),
263            &[
264                TypeHint::union(&[
265                    TypeHint::builtin("int"),
266                    TypeHint::module_attr("builtins", "float"),
267                    TypeHint::local("weird"),
268                ]),
269                TypeHint::module_attr("datetime", "time"),
270            ],
271        );
272        assert_eq!(T.to_string(), "dict[int | float | weird, datetime.time]")
273    }
274
275    #[test]
276    fn test_serialize_for_introspection() {
277        const T: TypeHint = TypeHint::subscript(
278            &TypeHint::builtin("dict"),
279            &[
280                TypeHint::union(&[TypeHint::builtin("int"), TypeHint::local("weird")]),
281                TypeHint::module_attr("datetime", "time"),
282            ],
283        );
284        const SER_LEN: usize = serialized_len_for_introspection(&T);
285        const SER: [u8; SER_LEN] = {
286            let mut out: [u8; SER_LEN] = [0; SER_LEN];
287            serialize_for_introspection(&T, &mut out);
288            out
289        };
290        assert_eq!(
291            std::str::from_utf8(&SER).unwrap(),
292            r#"{"type":"subscript","value":{"type":"builtin","id":"dict"},"slice":[{"type":"union","elts":[{"type":"builtin","id":"int"},{"type":"local","id":"weird"}]},{"type":"attribute","module":"datetime","attr":"time"}]}"#
293        )
294    }
295}