Skip to main content

pyo3/types/
code.rs

1use super::PyDict;
2use super::{PyAnyMethods as _, PyDictMethods as _};
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, PyResult, Python};
12use core::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 builtins = unsafe { ffi::PyEval_GetBuiltins().assume_borrowed_unchecked(self.py()) };
131        globals.set_default(builtins_s, builtins)?;
132        unsafe {
133            ffi::PyEval_EvalCode(self.as_ptr(), globals.as_ptr(), locals.as_ptr())
134                .assume_owned_or_err(self.py())
135        }
136    }
137}
138
139#[cfg(test)]
140mod tests {
141    #[test]
142    fn test_type_object() {
143        use crate::types::PyTypeMethods;
144        use crate::{PyTypeInfo, Python};
145
146        Python::attach(|py| {
147            assert_eq!(super::PyCode::type_object(py).name().unwrap(), "code");
148        })
149    }
150}