Skip to main content

rustpython_vm/vm/
vm_object.rs

1use super::PyMethod;
2use crate::{
3    builtins::{PyBaseExceptionRef, PyList, PyStrInterned, pystr::AsPyStr},
4    function::IntoFuncArgs,
5    object::{AsObject, PyObject, PyObjectRef, PyResult},
6    stdlib::sys,
7    vm::VirtualMachine,
8};
9
10/// PyObject support
11impl VirtualMachine {
12    #[track_caller]
13    #[cold]
14    fn _py_panic_failed(&self, exc: PyBaseExceptionRef, msg: &str) -> ! {
15        cfg_select! {
16            all(
17                target_arch = "wasm32",
18                not(any(target_os = "emscripten", target_os = "wasi"))
19            ) => cfg_select! {
20                feature = "wasmbind" => {
21                    use wasm_bindgen::prelude::*;
22                    #[wasm_bindgen]
23                    extern "C" {
24                        #[wasm_bindgen(js_namespace = console)]
25                        fn error(s: &str);
26                    }
27                    let mut s = String::new();
28                    self.write_exception(&mut s, &exc).unwrap();
29                    error(&s);
30                    panic!("{msg}; exception backtrace above")
31                }
32                _ => {
33                    use crate::convert::ToPyObject;
34                    let err_string: String = exc.to_pyobject(self).repr(self).unwrap().to_string();
35                    eprintln!("{err_string}");
36                    panic!("{msg}; python exception not available")
37                }
38            },
39            _ => {
40                self.print_exception(&exc);
41                self.flush_std();
42                panic!("{msg}")
43            }
44        }
45    }
46
47    /// Returns true if the file object's `closed` attribute is truthy.
48    fn file_is_closed(&self, file: &PyObject) -> bool {
49        file.get_attr("closed", self)
50            .is_ok_and(|v| v.try_to_bool(self).unwrap_or_default())
51    }
52
53    pub(crate) fn flush_std(&self) -> i32 {
54        let vm = self;
55        let status = if let Ok(stdout) = sys::get_stdout(vm)
56            && !vm.is_none(&stdout)
57            && !vm.file_is_closed(&stdout)
58            && let Err(e) = vm.call_method(&stdout, identifier!(vm, flush).as_str(), ())
59        {
60            vm.run_unraisable(e, None, stdout);
61            -1
62        } else {
63            0
64        };
65
66        if let Ok(stderr) = sys::get_stderr(vm)
67            && !vm.is_none(&stderr)
68            && !vm.file_is_closed(&stderr)
69        {
70            let _ = vm.call_method(&stderr, identifier!(vm, flush).as_str(), ());
71        }
72        status
73    }
74
75    #[track_caller]
76    pub fn unwrap_pyresult<T>(&self, result: PyResult<T>) -> T {
77        match result {
78            Ok(x) => x,
79            Err(exc) => {
80                self._py_panic_failed(exc, "called `vm.unwrap_pyresult()` on an `Err` value")
81            }
82        }
83    }
84
85    #[track_caller]
86    pub fn expect_pyresult<T>(&self, result: PyResult<T>, msg: &str) -> T {
87        match result {
88            Ok(x) => x,
89            Err(exc) => self._py_panic_failed(exc, msg),
90        }
91    }
92
93    /// Test whether a python object is `None`.
94    pub fn is_none(&self, obj: &PyObject) -> bool {
95        obj.is(&self.ctx.none)
96    }
97
98    pub fn option_if_none(&self, obj: PyObjectRef) -> Option<PyObjectRef> {
99        if self.is_none(&obj) { None } else { Some(obj) }
100    }
101
102    pub fn unwrap_or_none(&self, obj: Option<PyObjectRef>) -> PyObjectRef {
103        obj.unwrap_or_else(|| self.ctx.none())
104    }
105
106    pub fn call_get_descriptor_specific(
107        &self,
108        descr: &PyObject,
109        obj: Option<&PyObject>,
110        cls: Option<&PyObject>,
111    ) -> Option<PyResult> {
112        let descr_get = descr.class().slots().descr_get.load()?;
113        Some(descr_get(descr, obj, cls, self))
114    }
115
116    pub fn call_get_descriptor(&self, descr: &PyObject, obj: &PyObject) -> Option<PyResult> {
117        self.call_get_descriptor_specific(descr, Some(obj), Some(obj.class().as_object()))
118    }
119
120    pub fn call_if_get_descriptor(&self, attr: &PyObject, obj: PyObjectRef) -> PyResult {
121        self.call_get_descriptor(attr, &obj)
122            .unwrap_or_else(|| Ok(attr.to_owned()))
123    }
124
125    #[inline]
126    pub fn call_method<T>(&self, obj: &PyObject, method_name: &str, args: T) -> PyResult
127    where
128        T: IntoFuncArgs,
129    {
130        flame_guard!(format!("call_method({:?})", method_name));
131
132        let dynamic_name;
133        let name = match self.ctx.interned_str(method_name) {
134            Some(name) => name.as_pystr(&self.ctx),
135            None => {
136                dynamic_name = self.ctx.new_str(method_name);
137                &dynamic_name
138            }
139        };
140        PyMethod::get(obj.to_owned(), name, self)?.invoke(args, self)
141    }
142
143    pub fn dir(&self, obj: Option<PyObjectRef>) -> PyResult<PyList> {
144        let seq = match obj {
145            Some(obj) => self
146                .get_special_method(&obj, identifier!(self, __dir__))?
147                .ok_or_else(|| self.new_type_error("object does not provide __dir__"))?
148                .invoke((), self)?,
149            None => self.call_method(
150                self.current_locals()?.as_object(),
151                identifier!(self, keys).as_str(),
152                (),
153            )?,
154        };
155        let items: Vec<_> = seq.try_to_value(self)?;
156        let lst = PyList::from(items);
157        lst.sort(Default::default(), self)?;
158        Ok(lst)
159    }
160
161    #[inline]
162    pub(crate) fn get_special_method(
163        &self,
164        obj: &PyObject,
165        method: &'static PyStrInterned,
166    ) -> PyResult<Option<PyMethod>> {
167        PyMethod::get_special::<false>(obj, method, self)
168    }
169
170    /// NOT PUBLIC API
171    #[doc(hidden)]
172    pub fn call_special_method(
173        &self,
174        obj: &PyObject,
175        method: &'static PyStrInterned,
176        args: impl IntoFuncArgs,
177    ) -> PyResult {
178        // lookup_method: AttributeError from a data descriptor is kept.
179        PyMethod::get_special_ex::<false>(obj, method, self, true)?
180            .ok_or_else(|| self.new_attribute_error(method.as_str().to_owned()))?
181            .invoke(args, self)
182    }
183
184    /// Same as __builtins__.print in Python.
185    /// A convenience function to provide a simple way to print objects for debug purpose.
186    // NOTE: Keep the interface simple.
187    pub fn print(&self, args: impl IntoFuncArgs) -> PyResult<()> {
188        let ret = self.builtins.get_attr("print", self)?.call(args, self)?;
189        debug_assert!(self.is_none(&ret));
190        Ok(())
191    }
192
193    #[deprecated(note = "in favor of `obj.call(args, vm)`")]
194    pub fn invoke(&self, obj: &impl AsObject, args: impl IntoFuncArgs) -> PyResult {
195        obj.as_object().call(args, self)
196    }
197}