Skip to main content

pyo3/types/
code.rs

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