cpython/objects/
tuple.rs

1// Copyright (c) 2015 Daniel Grunwald
2//
3// Permission is hereby granted, free of charge, to any person obtaining a copy of this
4// software and associated documentation files (the "Software"), to deal in the Software
5// without restriction, including without limitation the rights to use, copy, modify, merge,
6// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
7// to whom the Software is furnished to do so, subject to the following conditions:
8//
9// The above copyright notice and this permission notice shall be included in all copies or
10// substantial portions of the Software.
11//
12// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
13// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
14// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
15// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
16// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
17// DEALINGS IN THE SOFTWARE.
18
19use std::slice;
20
21use super::exc;
22use super::object::PyObject;
23use crate::conversion::{FromPyObject, ToPyObject};
24use crate::err::{self, PyErr, PyResult};
25use crate::ffi::{self, Py_ssize_t};
26use crate::python::{PyDrop, Python, PythonObject, ToPythonPointer};
27
28/// Represents a Python tuple object.
29pub struct PyTuple(PyObject);
30
31pyobject_newtype!(PyTuple, PyTuple_Check, PyTuple_Type);
32
33impl PyTuple {
34    /// Construct a new tuple with the given elements.
35    pub fn new(py: Python, elements: &[PyObject]) -> PyTuple {
36        unsafe {
37            let len = elements.len();
38            let ptr = ffi::PyTuple_New(len as Py_ssize_t);
39            let t = err::result_cast_from_owned_ptr::<PyTuple>(py, ptr).unwrap();
40            for (i, e) in elements.iter().enumerate() {
41                ffi::PyTuple_SetItem(ptr, i as Py_ssize_t, e.steal_ptr(py));
42            }
43            t
44        }
45    }
46
47    /// Retrieves the empty tuple.
48    pub fn empty(py: Python) -> PyTuple {
49        unsafe { err::result_cast_from_owned_ptr::<PyTuple>(py, ffi::PyTuple_New(0)).unwrap() }
50    }
51
52    /// Gets the length of the tuple.
53    #[inline]
54    pub fn len(&self, _py: Python) -> usize {
55        unsafe {
56            // non-negative Py_ssize_t should always fit into Rust uint
57            ffi::PyTuple_GET_SIZE(self.0.as_ptr()) as usize
58        }
59    }
60
61    /// Gets the item at the specified index.
62    ///
63    /// Panics if the index is out of range.
64    pub fn get_item(&self, py: Python, index: usize) -> PyObject {
65        // TODO: reconsider whether we should panic
66        // It's quite inconsistent that this method takes `Python` when `len()` does not.
67        assert!(index < self.len(py));
68        unsafe {
69            PyObject::from_borrowed_ptr(
70                py,
71                ffi::PyTuple_GET_ITEM(self.0.as_ptr(), index as Py_ssize_t),
72            )
73        }
74    }
75
76    #[inline]
77    pub fn as_slice<'a>(&'a self, py: Python) -> &'a [PyObject] {
78        // This is safe because PyObject has the same memory layout as *mut ffi::PyObject,
79        // and because tuples are immutable.
80        // (We don't even need a Python token, thanks to immutability)
81        unsafe {
82            let ptr = self.0.as_ptr() as *mut ffi::PyTupleObject;
83            PyObject::borrow_from_owned_ptr_slice(slice::from_raw_parts(
84                (*ptr).ob_item.as_ptr(),
85                self.len(py),
86            ))
87        }
88    }
89
90    #[inline]
91    pub fn iter(&self, py: Python) -> slice::Iter<PyObject> {
92        self.as_slice(py).iter()
93    }
94}
95
96fn wrong_tuple_length(py: Python, t: &PyTuple, expected_length: usize) -> PyErr {
97    let msg = format!(
98        "Expected tuple of length {}, but got tuple of length {}.",
99        expected_length,
100        t.len(py)
101    );
102    PyErr::new_lazy_init(
103        py.get_type::<exc::ValueError>(),
104        Some(msg.to_py_object(py).into_object()),
105    )
106}
107
108macro_rules! tuple_conversion ({$length:expr,$(($refN:ident, $n:tt, $T:ident)),+} => {
109    /// Converts a Rust tuple to a Python `tuple`.
110    impl <$($T: ToPyObject),+> ToPyObject for ($($T,)+) {
111        type ObjectType = PyTuple;
112
113        fn to_py_object(&self, py: Python) -> PyTuple {
114            PyTuple::new(py, &[
115                $(py_coerce_expr!(self.$n.to_py_object(py)).into_object(),)+
116            ])
117        }
118
119        fn into_py_object(self, py: Python) -> PyTuple {
120            PyTuple::new(py, &[
121                $(py_coerce_expr!(self.$n.into_py_object(py)).into_object(),)+
122            ])
123        }
124    }
125
126    /// Converts a Python `tuple` to a Rust tuple.
127    ///
128    /// Note: only accepts Python `tuple` (or derived classes);
129    /// other types are not accepted.
130    impl <'s, $($T: FromPyObject<'s>),+> FromPyObject<'s> for ($($T,)+) {
131        fn extract(py: Python, obj: &'s PyObject) -> PyResult<Self> {
132            let t = obj.cast_as::<PyTuple>(py)?;
133            let slice = t.as_slice(py);
134            if slice.len() == $length {
135                Ok((
136                    $( slice[$n].extract::<$T>(py)?, )+
137                ))
138            } else {
139                Err(wrong_tuple_length(py, t, $length))
140            }
141        }
142    }
143});
144
145tuple_conversion!(1, (ref0, 0, A));
146tuple_conversion!(2, (ref0, 0, A), (ref1, 1, B));
147tuple_conversion!(3, (ref0, 0, A), (ref1, 1, B), (ref2, 2, C));
148tuple_conversion!(4, (ref0, 0, A), (ref1, 1, B), (ref2, 2, C), (ref3, 3, D));
149tuple_conversion!(
150    5,
151    (ref0, 0, A),
152    (ref1, 1, B),
153    (ref2, 2, C),
154    (ref3, 3, D),
155    (ref4, 4, E)
156);
157tuple_conversion!(
158    6,
159    (ref0, 0, A),
160    (ref1, 1, B),
161    (ref2, 2, C),
162    (ref3, 3, D),
163    (ref4, 4, E),
164    (ref5, 5, F)
165);
166tuple_conversion!(
167    7,
168    (ref0, 0, A),
169    (ref1, 1, B),
170    (ref2, 2, C),
171    (ref3, 3, D),
172    (ref4, 4, E),
173    (ref5, 5, F),
174    (ref6, 6, G)
175);
176tuple_conversion!(
177    8,
178    (ref0, 0, A),
179    (ref1, 1, B),
180    (ref2, 2, C),
181    (ref3, 3, D),
182    (ref4, 4, E),
183    (ref5, 5, F),
184    (ref6, 6, G),
185    (ref7, 7, H)
186);
187tuple_conversion!(
188    9,
189    (ref0, 0, A),
190    (ref1, 1, B),
191    (ref2, 2, C),
192    (ref3, 3, D),
193    (ref4, 4, E),
194    (ref5, 5, F),
195    (ref6, 6, G),
196    (ref7, 7, H),
197    (ref8, 8, I)
198);
199
200// Empty tuple:
201
202/// An empty struct that represents the empty argument list.
203/// Corresponds to the empty tuple `()` in Python.
204///
205/// # Example
206/// ```
207/// let gil_guard = cpython::Python::acquire_gil();
208/// let py = gil_guard.python();
209/// let os = py.import("os").unwrap();
210/// let pid = os.call(py, "getpid", cpython::NoArgs, None).unwrap();
211/// ```
212#[derive(Copy, Clone, Debug)]
213pub struct NoArgs;
214
215/// Converts `NoArgs` to an empty Python tuple.
216impl ToPyObject for NoArgs {
217    type ObjectType = PyTuple;
218
219    fn to_py_object(&self, py: Python) -> PyTuple {
220        PyTuple::empty(py)
221    }
222}
223
224extract!(obj to NoArgs;
225    /// Returns `Ok(NoArgs)` if the input is an empty Python tuple.
226    /// Otherwise, returns an error.
227    py => {
228        let t = obj.cast_as::<PyTuple>(py)?;
229        if t.len(py) == 0 {
230            Ok(NoArgs)
231        } else {
232            Err(wrong_tuple_length(py, t, 0))
233        }
234    }
235);
236
237#[cfg(test)]
238mod test {
239    use crate::conversion::ToPyObject;
240    use crate::python::{Python, PythonObject};
241
242    #[test]
243    fn test_len() {
244        let gil = Python::acquire_gil();
245        let py = gil.python();
246        let tuple = (1, 2, 3).to_py_object(py);
247        assert_eq!(3, tuple.len(py));
248        assert_eq!((1, 2, 3), tuple.into_object().extract(py).unwrap());
249    }
250}