pyo3/types/
code.rs

1use super::PyAnyMethods as _;
2use super::PyDict;
3use crate::ffi_ptr_ext::FfiPtrExt;
4use crate::py_result_ext::PyResultExt;
5use crate::{ffi, Bound, PyAny, PyErr, PyResult, Python};
6use std::ffi::CStr;
7
8/// Represents a Python code object.
9///
10/// Values of this type are accessed via PyO3's smart pointers, e.g. as
11/// [`Py<PyCode>`][crate::Py] or [`Bound<'py, PyCode>`][crate::Bound].
12#[repr(transparent)]
13pub struct PyCode(PyAny);
14
15#[cfg(not(any(Py_LIMITED_API, PyPy)))]
16pyobject_native_type_core!(
17    PyCode,
18    pyobject_native_static_type_object!(ffi::PyCode_Type),
19    #checkfunction=ffi::PyCode_Check
20);
21
22#[cfg(any(Py_LIMITED_API, PyPy))]
23pyobject_native_type_named!(PyCode);
24
25#[cfg(any(Py_LIMITED_API, PyPy))]
26impl crate::PyTypeCheck for PyCode {
27    const NAME: &'static str = "PyCode";
28    #[cfg(feature = "experimental-inspect")]
29    const PYTHON_TYPE: &'static str = "types.CodeType";
30
31    fn type_check(object: &Bound<'_, PyAny>) -> bool {
32        let py = object.py();
33        static TYPE: crate::sync::PyOnceLock<crate::Py<super::PyType>> =
34            crate::sync::PyOnceLock::new();
35
36        TYPE.import(py, "types", "CodeType")
37            .and_then(|ty| object.is_instance(ty))
38            .unwrap_or_default()
39    }
40}
41
42/// Compilation mode of [`PyCode::compile`]
43pub enum PyCodeInput {
44    /// Python grammar for isolated expressions
45    Eval,
46    /// Python grammar for sequences of statements as read from a file
47    File,
48}
49
50impl PyCode {
51    /// Compiles code in the given context.
52    ///
53    /// `input` decides whether `code` is treated as
54    /// - [`PyCodeInput::Eval`]: an isolated expression
55    /// - [`PyCodeInput::File`]: a sequence of statements
56    pub fn compile<'py>(
57        py: Python<'py>,
58        code: &CStr,
59        filename: &CStr,
60        input: PyCodeInput,
61    ) -> PyResult<Bound<'py, PyCode>> {
62        let start = match input {
63            PyCodeInput::Eval => ffi::Py_eval_input,
64            PyCodeInput::File => ffi::Py_file_input,
65        };
66        unsafe {
67            ffi::Py_CompileString(code.as_ptr(), filename.as_ptr(), start)
68                .assume_owned_or_err(py)
69                .cast_into_unchecked()
70        }
71    }
72}
73
74/// Implementation of functionality for [`PyCode`].
75///
76/// These methods are defined for the `Bound<'py, PyCode>` smart pointer, so to use method call
77/// syntax these methods are separated into a trait, because stable Rust does not yet support
78/// `arbitrary_self_types`.
79pub trait PyCodeMethods<'py> {
80    /// Runs code object.
81    ///
82    /// If `globals` is `None`, it defaults to Python module `__main__`.
83    /// If `locals` is `None`, it defaults to the value of `globals`.
84    fn run(
85        &self,
86        globals: Option<&Bound<'py, PyDict>>,
87        locals: Option<&Bound<'py, PyDict>>,
88    ) -> PyResult<Bound<'py, PyAny>>;
89}
90
91impl<'py> PyCodeMethods<'py> for Bound<'py, PyCode> {
92    fn run(
93        &self,
94        globals: Option<&Bound<'py, PyDict>>,
95        locals: Option<&Bound<'py, PyDict>>,
96    ) -> PyResult<Bound<'py, PyAny>> {
97        let mptr = unsafe {
98            ffi::compat::PyImport_AddModuleRef(ffi::c_str!("__main__").as_ptr())
99                .assume_owned_or_err(self.py())?
100        };
101        let attr = mptr.getattr(crate::intern!(self.py(), "__dict__"))?;
102        let globals = match globals {
103            Some(globals) => globals,
104            None => attr.cast::<PyDict>()?,
105        };
106        let locals = locals.unwrap_or(globals);
107
108        // If `globals` don't provide `__builtins__`, most of the code will fail if Python
109        // version is <3.10. That's probably not what user intended, so insert `__builtins__`
110        // for them.
111        //
112        // See also:
113        // - https://github.com/python/cpython/pull/24564 (the same fix in CPython 3.10)
114        // - https://github.com/PyO3/pyo3/issues/3370
115        let builtins_s = crate::intern!(self.py(), "__builtins__");
116        let has_builtins = globals.contains(builtins_s)?;
117        if !has_builtins {
118            crate::sync::with_critical_section(globals, || {
119                // check if another thread set __builtins__ while this thread was blocked on the critical section
120                let has_builtins = globals.contains(builtins_s)?;
121                if !has_builtins {
122                    // Inherit current builtins.
123                    let builtins = unsafe { ffi::PyEval_GetBuiltins() };
124
125                    // `PyDict_SetItem` doesn't take ownership of `builtins`, but `PyEval_GetBuiltins`
126                    // seems to return a borrowed reference, so no leak here.
127                    if unsafe {
128                        ffi::PyDict_SetItem(globals.as_ptr(), builtins_s.as_ptr(), builtins)
129                    } == -1
130                    {
131                        return Err(PyErr::fetch(self.py()));
132                    }
133                }
134                Ok(())
135            })?;
136        }
137
138        unsafe {
139            ffi::PyEval_EvalCode(self.as_ptr(), globals.as_ptr(), locals.as_ptr())
140                .assume_owned_or_err(self.py())
141        }
142    }
143}
144
145#[cfg(test)]
146mod tests {
147    #[test]
148    #[cfg(not(any(Py_LIMITED_API, PyPy)))]
149    fn test_type_object() {
150        use crate::types::PyTypeMethods;
151        use crate::{PyTypeInfo, Python};
152
153        Python::attach(|py| {
154            assert_eq!(super::PyCode::type_object(py).name().unwrap(), "code");
155        })
156    }
157}