Skip to main content

rustpython_vm/convert/
try_from.rs

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