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