Skip to main content

rustpython_vm/convert/
try_from.rs

1use crate::{
2    Py, VirtualMachine,
3    builtins::PyFloat,
4    object::{AsObject, PyObject, PyObjectRef, PyPayload, PyRef, PyResult},
5};
6use malachite_bigint::Sign;
7use num_traits::ToPrimitive;
8
9/// Implemented by any type that can be created from a Python object.
10///
11/// Any type that implements `TryFromObject` is automatically `FromArgs`, and
12/// so can be accepted as a argument to a built-in function.
13pub trait TryFromObject: Sized {
14    /// Attempt to convert a Python object to a value of this type.
15    fn try_from_object(vm: &VirtualMachine, obj: PyObjectRef) -> PyResult<Self>;
16}
17
18/// Rust-side only version of TryFromObject to reduce unnecessary Rc::clone
19impl<T: for<'a> TryFromBorrowedObject<'a>> TryFromObject for T {
20    fn try_from_object(vm: &VirtualMachine, obj: PyObjectRef) -> PyResult<Self> {
21        TryFromBorrowedObject::try_from_borrowed_object(vm, &obj)
22    }
23}
24
25impl PyObjectRef {
26    pub fn try_into_value<T>(self, vm: &VirtualMachine) -> PyResult<T>
27    where
28        T: TryFromObject,
29    {
30        T::try_from_object(vm, self)
31    }
32}
33
34impl PyObject {
35    pub fn try_to_value<'a, T>(&'a self, vm: &VirtualMachine) -> PyResult<T>
36    where
37        T: 'a + TryFromBorrowedObject<'a>,
38    {
39        T::try_from_borrowed_object(vm, self)
40    }
41
42    pub fn try_to_ref<'a, T>(&'a self, vm: &VirtualMachine) -> PyResult<&'a Py<T>>
43    where
44        T: 'a + PyPayload,
45    {
46        self.try_to_value::<&Py<T>>(vm)
47    }
48
49    pub fn try_value_with<T, F, R>(&self, f: F, vm: &VirtualMachine) -> PyResult<R>
50    where
51        T: PyPayload,
52        F: Fn(&T) -> PyResult<R>,
53    {
54        let class = T::class(&vm.ctx);
55        let py_ref = if self.fast_isinstance(class) {
56            self.downcast_ref()
57                .ok_or_else(|| vm.new_downcast_runtime_error(class, self))?
58        } else {
59            return Err(vm.new_downcast_type_error(class, self));
60        };
61        f(py_ref)
62    }
63}
64
65/// Lower-cost variation of `TryFromObject`
66pub trait TryFromBorrowedObject<'a>: Sized
67where
68    Self: 'a,
69{
70    /// Attempt to convert a Python object to a value of this type.
71    fn try_from_borrowed_object(vm: &VirtualMachine, obj: &'a PyObject) -> PyResult<Self>;
72}
73
74impl<T> TryFromObject for PyRef<T>
75where
76    T: PyPayload,
77{
78    #[inline]
79    fn try_from_object(vm: &VirtualMachine, obj: PyObjectRef) -> PyResult<Self> {
80        let class = T::class(&vm.ctx);
81        if obj.fast_isinstance(class) {
82            T::try_downcast_from(&obj, vm)?;
83            Ok(unsafe { obj.downcast_unchecked() })
84        } else {
85            Err(vm.new_downcast_type_error(class, &obj))
86        }
87    }
88}
89
90impl TryFromObject for PyObjectRef {
91    #[inline]
92    fn try_from_object(_vm: &VirtualMachine, obj: PyObjectRef) -> PyResult<Self> {
93        Ok(obj)
94    }
95}
96
97impl<T: TryFromObject> TryFromObject for Option<T> {
98    fn try_from_object(vm: &VirtualMachine, obj: PyObjectRef) -> PyResult<Self> {
99        if vm.is_none(&obj) {
100            Ok(None)
101        } else {
102            T::try_from_object(vm, obj).map(Some)
103        }
104    }
105}
106
107impl<'a, T: 'a + TryFromObject> TryFromBorrowedObject<'a> for Vec<T> {
108    fn try_from_borrowed_object(vm: &VirtualMachine, value: &'a PyObject) -> PyResult<Self> {
109        vm.extract_elements_with(value, |obj| T::try_from_object(vm, obj))
110    }
111}
112
113impl<'a, T: PyPayload> TryFromBorrowedObject<'a> for &'a Py<T> {
114    fn try_from_borrowed_object(vm: &VirtualMachine, obj: &'a PyObject) -> PyResult<Self> {
115        let class = T::class(&vm.ctx);
116        if obj.fast_isinstance(class) {
117            obj.downcast_ref()
118                .ok_or_else(|| vm.new_downcast_runtime_error(class, &obj))
119        } else {
120            Err(vm.new_downcast_type_error(class, &obj))
121        }
122    }
123}
124
125impl TryFromObject for core::time::Duration {
126    fn try_from_object(vm: &VirtualMachine, obj: PyObjectRef) -> PyResult<Self> {
127        if let Some(float) = obj.downcast_ref::<PyFloat>() {
128            let f = float.to_f64();
129            if f.is_nan() {
130                return Err(vm.new_value_error("Invalid value NaN (not a number)"));
131            }
132            if f < 0.0 {
133                return Err(vm.new_value_error("negative duration"));
134            }
135            if !f.is_finite() || f > u64::MAX as f64 {
136                return Err(vm.new_overflow_error("timestamp too large to convert to C PyTime_t"));
137            }
138            // Convert float to Duration using floor rounding (_PyTime_ROUND_FLOOR)
139            let secs = f.trunc() as u64;
140            let frac = f.fract();
141            // Use floor to round down the nanoseconds
142            let nanos = (frac * 1_000_000_000.0).floor() as u32;
143            Ok(Self::new(secs, nanos))
144        } else if let Some(int) = obj.try_index_opt(vm) {
145            let int = int?;
146            let bigint = int.as_bigint();
147            if bigint.sign() == Sign::Minus {
148                return Err(vm.new_value_error("negative duration"));
149            }
150
151            let sec = bigint
152                .to_u64()
153                .ok_or_else(|| vm.new_value_error("value out of range"))?;
154            Ok(Self::from_secs(sec))
155        } else {
156            Err(vm.new_type_error(format!(
157                "expected an int or float for duration, got {}",
158                obj.class()
159            )))
160        }
161    }
162}