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    #[cfg(not(any(Py_LIMITED_API, PyPy, GraalPy)))]
73    pub(crate) fn empty<'py>(
74        py: Python<'py>,
75        file_name: &CStr,
76        func_name: &CStr,
77        first_line_number: i32,
78    ) -> Bound<'py, PyCode> {
79        unsafe {
80            ffi::PyCode_NewEmpty(file_name.as_ptr(), func_name.as_ptr(), first_line_number)
81                .cast::<ffi::PyObject>()
82                .assume_owned(py)
83                .cast_into_unchecked()
84        }
85    }
86}
87
88/// Implementation of functionality for [`PyCode`].
89///
90/// These methods are defined for the `Bound<'py, PyCode>` smart pointer, so to use method call
91/// syntax these methods are separated into a trait, because stable Rust does not yet support
92/// `arbitrary_self_types`.
93pub trait PyCodeMethods<'py> {
94    /// Runs code object.
95    ///
96    /// If `globals` is `None`, it defaults to Python module `__main__`.
97    /// If `locals` is `None`, it defaults to the value of `globals`.
98    fn run(
99        &self,
100        globals: Option<&Bound<'py, PyDict>>,
101        locals: Option<&Bound<'py, PyDict>>,
102    ) -> PyResult<Bound<'py, PyAny>>;
103}
104
105impl<'py> PyCodeMethods<'py> for Bound<'py, PyCode> {
106    fn run(
107        &self,
108        globals: Option<&Bound<'py, PyDict>>,
109        locals: Option<&Bound<'py, PyDict>>,
110    ) -> PyResult<Bound<'py, PyAny>> {
111        let mptr = unsafe {
112            ffi::compat::PyImport_AddModuleRef(c"__main__".as_ptr())
113                .assume_owned_or_err(self.py())?
114        };
115        let attr = mptr.getattr(crate::intern!(self.py(), "__dict__"))?;
116        let globals = match globals {
117            Some(globals) => globals,
118            None => attr.cast::<PyDict>()?,
119        };
120        let locals = locals.unwrap_or(globals);
121
122        // If `globals` don't provide `__builtins__`, most of the code will fail if Python
123        // version is <3.10. That's probably not what user intended, so insert `__builtins__`
124        // for them.
125        //
126        // See also:
127        // - https://github.com/python/cpython/pull/24564 (the same fix in CPython 3.10)
128        // - https://github.com/PyO3/pyo3/issues/3370
129        let builtins_s = crate::intern!(self.py(), "__builtins__");
130        let has_builtins = globals.contains(builtins_s)?;
131        if !has_builtins {
132            crate::sync::critical_section::with_critical_section(globals, || {
133                // check if another thread set __builtins__ while this thread was blocked on the critical section
134                let has_builtins = globals.contains(builtins_s)?;
135                if !has_builtins {
136                    // Inherit current builtins.
137                    let builtins = unsafe { ffi::PyEval_GetBuiltins() };
138
139                    // `PyDict_SetItem` doesn't take ownership of `builtins`, but `PyEval_GetBuiltins`
140                    // seems to return a borrowed reference, so no leak here.
141                    if unsafe {
142                        ffi::PyDict_SetItem(globals.as_ptr(), builtins_s.as_ptr(), builtins)
143                    } == -1
144                    {
145                        return Err(PyErr::fetch(self.py()));
146                    }
147                }
148                Ok(())
149            })?;
150        }
151
152        unsafe {
153            ffi::PyEval_EvalCode(self.as_ptr(), globals.as_ptr(), locals.as_ptr())
154                .assume_owned_or_err(self.py())
155        }
156    }
157}
158
159#[cfg(test)]
160mod tests {
161    #[test]
162    fn test_type_object() {
163        use crate::types::PyTypeMethods;
164        use crate::{PyTypeInfo, Python};
165
166        Python::attach(|py| {
167            assert_eq!(super::PyCode::type_object(py).name().unwrap(), "code");
168        })
169    }
170}