Skip to main content

pyo3/types/
code.rs

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