1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
use crate::{
    builtins::PyFloat,
    object::{AsObject, PyObject, PyObjectRef, PyPayload, PyRef, PyResult},
    Py, VirtualMachine,
};
use num_traits::ToPrimitive;

/// Implemented by any type that can be created from a Python object.
///
/// Any type that implements `TryFromObject` is automatically `FromArgs`, and
/// so can be accepted as a argument to a built-in function.
pub trait TryFromObject: Sized {
    /// Attempt to convert a Python object to a value of this type.
    fn try_from_object(vm: &VirtualMachine, obj: PyObjectRef) -> PyResult<Self>;
}

/// Rust-side only version of TryFromObject to reduce unnecessary Rc::clone
impl<T: for<'a> TryFromBorrowedObject<'a>> TryFromObject for T {
    fn try_from_object(vm: &VirtualMachine, obj: PyObjectRef) -> PyResult<Self> {
        TryFromBorrowedObject::try_from_borrowed_object(vm, &obj)
    }
}

impl PyObjectRef {
    pub fn try_into_value<T>(self, vm: &VirtualMachine) -> PyResult<T>
    where
        T: TryFromObject,
    {
        T::try_from_object(vm, self)
    }
}

impl PyObject {
    pub fn try_to_value<'a, T: 'a>(&'a self, vm: &VirtualMachine) -> PyResult<T>
    where
        T: TryFromBorrowedObject<'a>,
    {
        T::try_from_borrowed_object(vm, self)
    }

    pub fn try_to_ref<'a, T: 'a>(&'a self, vm: &VirtualMachine) -> PyResult<&'a Py<T>>
    where
        T: PyPayload,
    {
        self.try_to_value::<&Py<T>>(vm)
    }

    pub fn try_value_with<T, F, R>(&self, f: F, vm: &VirtualMachine) -> PyResult<R>
    where
        T: PyPayload,
        F: Fn(&T) -> PyResult<R>,
    {
        let class = T::class(&vm.ctx);
        let py_ref = if self.fast_isinstance(class) {
            self.downcast_ref()
                .ok_or_else(|| vm.new_downcast_runtime_error(class, self))?
        } else {
            return Err(vm.new_downcast_type_error(class, self));
        };
        f(py_ref)
    }
}

/// Lower-cost variation of `TryFromObject`
pub trait TryFromBorrowedObject<'a>: Sized
where
    Self: 'a,
{
    /// Attempt to convert a Python object to a value of this type.
    fn try_from_borrowed_object(vm: &VirtualMachine, obj: &'a PyObject) -> PyResult<Self>;
}

impl<T> TryFromObject for PyRef<T>
where
    T: PyPayload,
{
    #[inline]
    fn try_from_object(vm: &VirtualMachine, obj: PyObjectRef) -> PyResult<Self> {
        let class = T::class(&vm.ctx);
        if obj.fast_isinstance(class) {
            obj.downcast()
                .map_err(|obj| vm.new_downcast_runtime_error(class, &obj))
        } else {
            Err(vm.new_downcast_type_error(class, &obj))
        }
    }
}

impl TryFromObject for PyObjectRef {
    #[inline]
    fn try_from_object(_vm: &VirtualMachine, obj: PyObjectRef) -> PyResult<Self> {
        Ok(obj)
    }
}

impl<T: TryFromObject> TryFromObject for Option<T> {
    fn try_from_object(vm: &VirtualMachine, obj: PyObjectRef) -> PyResult<Self> {
        if vm.is_none(&obj) {
            Ok(None)
        } else {
            T::try_from_object(vm, obj).map(Some)
        }
    }
}

impl<'a, T: 'a + TryFromObject> TryFromBorrowedObject<'a> for Vec<T> {
    fn try_from_borrowed_object(vm: &VirtualMachine, value: &'a PyObject) -> PyResult<Self> {
        vm.extract_elements_with(value, |obj| T::try_from_object(vm, obj))
    }
}

impl<'a, T: PyPayload> TryFromBorrowedObject<'a> for &'a Py<T> {
    fn try_from_borrowed_object(vm: &VirtualMachine, obj: &'a PyObject) -> PyResult<Self> {
        let class = T::class(&vm.ctx);
        if obj.fast_isinstance(class) {
            obj.downcast_ref()
                .ok_or_else(|| vm.new_downcast_runtime_error(class, &obj))
        } else {
            Err(vm.new_downcast_type_error(class, &obj))
        }
    }
}

impl TryFromObject for std::time::Duration {
    fn try_from_object(vm: &VirtualMachine, obj: PyObjectRef) -> PyResult<Self> {
        use std::time::Duration;
        if let Some(float) = obj.payload::<PyFloat>() {
            Ok(Duration::from_secs_f64(float.to_f64()))
        } else if let Some(int) = obj.try_index_opt(vm) {
            let sec = int?
                .as_bigint()
                .to_u64()
                .ok_or_else(|| vm.new_value_error("value out of range".to_owned()))?;
            Ok(Duration::from_secs(sec))
        } else {
            Err(vm.new_type_error(format!(
                "expected an int or float for duration, got {}",
                obj.class()
            )))
        }
    }
}