1use crate::conversion::{FromPyObjectOwned, FromPyObjectSequence, IntoPyObject};
2#[cfg(feature = "experimental-inspect")]
3use crate::inspect::{PyStaticExpr, type_hint_identifier, type_hint_subscript};
4use crate::types::PySequence;
5use crate::types::any::PyAnyMethods;
6use crate::{Borrowed, Bound, PyErr, exceptions};
7use crate::{FromPyObject, PyAny, PyResult, PyTypeInfo, Python, err::CastError, ffi};
8
9use core::mem::MaybeUninit;
10
11impl<'py, T, const N: usize> IntoPyObject<'py> for [T; N]
12where
13 T: IntoPyObject<'py>,
14{
15 type Target = PyAny;
16 type Output = Bound<'py, Self::Target>;
17 type Error = PyErr;
18
19 #[cfg(feature = "experimental-inspect")]
20 const OUTPUT_TYPE: PyStaticExpr = T::SEQUENCE_OUTPUT_TYPE;
21
22 #[inline]
27 fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
28 T::owned_sequence_into_pyobject(self, py, crate::conversion::private::Token)
29 }
30}
31
32impl<'a, 'py, T, const N: usize> IntoPyObject<'py> for &'a [T; N]
33where
34 &'a T: IntoPyObject<'py>,
35{
36 type Target = PyAny;
37 type Output = Bound<'py, Self::Target>;
38 type Error = PyErr;
39
40 #[cfg(feature = "experimental-inspect")]
41 const OUTPUT_TYPE: PyStaticExpr = <&[T]>::OUTPUT_TYPE;
42
43 #[inline]
44 fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
45 self.as_slice().into_pyobject(py)
46 }
47}
48
49impl<'py, T, const N: usize> FromPyObject<'_, 'py> for [T; N]
50where
51 T: FromPyObjectOwned<'py>,
52{
53 type Error = PyErr;
54
55 #[cfg(feature = "experimental-inspect")]
56 const INPUT_TYPE: PyStaticExpr = type_hint_subscript!(
57 type_hint_identifier!("_typeshed", "SupportsLenAndGetItem"),
58 T::INPUT_TYPE
59 );
60
61 fn extract(obj: Borrowed<'_, 'py, PyAny>) -> PyResult<Self> {
62 if let Some(extractor) = T::sequence_extractor(obj, crate::conversion::private::Token) {
63 return extractor.to_array();
64 }
65
66 create_array_from_obj(obj)
67 }
68}
69
70fn create_array_from_obj<'py, T, const N: usize>(obj: Borrowed<'_, 'py, PyAny>) -> PyResult<[T; N]>
71where
72 T: FromPyObjectOwned<'py>,
73{
74 if unsafe { ffi::PySequence_Check(obj.as_ptr()) } == 0 {
78 return Err(CastError::new(obj, PySequence::type_object(obj.py()).into_any()).into());
79 }
80
81 let seq_len = obj.len()?;
82 if seq_len != N {
83 return Err(invalid_sequence_length(N, seq_len));
84 }
85 array_try_from_fn(|idx| {
86 obj.get_item(idx)
87 .and_then(|any| any.extract().map_err(Into::into))
88 })
89}
90
91fn array_try_from_fn<E, F, T, const N: usize>(mut cb: F) -> Result<[T; N], E>
94where
95 F: FnMut(usize) -> Result<T, E>,
96{
97 let mut array: [MaybeUninit<T>; N] = [const { MaybeUninit::uninit() }; N];
98
99 array_try_from_fn_erased(&mut array, &mut cb)?;
100
101 Ok(array.map(|elem| unsafe { elem.assume_init() }))
106}
107
108fn array_try_from_fn_erased<E, T>(
109 buffer: &mut [MaybeUninit<T>],
110 cb: &mut impl FnMut(usize) -> Result<T, E>,
111) -> Result<(), E> {
112 struct Guard<'a, T> {
116 array_mut: &'a mut [MaybeUninit<T>],
118 initialized: usize,
120 }
121
122 impl<T> Guard<'_, T> {
123 #[inline]
129 unsafe fn push_unchecked(&mut self, item: T) {
130 unsafe {
134 self.array_mut
135 .get_unchecked_mut(self.initialized)
136 .write(item);
137
138 self.initialized = self.initialized.unchecked_add(1)
139 }
140 }
141 }
142
143 impl<T> Drop for Guard<'_, T> {
144 #[inline]
145 fn drop(&mut self) {
146 debug_assert!(self.initialized <= self.array_mut.len());
147 unsafe {
149 core::ptr::drop_in_place(
150 &mut self.array_mut[..self.initialized] as *mut [_] as *mut [T],
151 );
152 }
153 }
154 }
155
156 let mut guard = Guard {
157 array_mut: buffer,
158 initialized: 0,
159 };
160
161 while guard.initialized < guard.array_mut.len() {
162 let item = cb(guard.initialized)?;
163 unsafe { guard.push_unchecked(item) };
165 }
166
167 core::mem::forget(guard);
168
169 Ok(())
170}
171
172pub(crate) fn invalid_sequence_length(expected: usize, actual: usize) -> PyErr {
173 exceptions::PyValueError::new_err(format!(
174 "expected a sequence of length {expected} (got {actual})"
175 ))
176}
177
178#[cfg(test)]
179mod tests {
180 use crate::platform::prelude::*;
181 #[cfg(panic = "unwind")]
182 use core::any::Any;
183 #[cfg(panic = "unwind")]
184 use core::sync::atomic::{AtomicUsize, Ordering};
185
186 #[cfg(panic = "unwind")]
188 mod panic {
189 extern crate std;
190 pub use std::panic::*;
191 }
192
193 use crate::{PyResult, Python, types::PyList};
194 use crate::{
195 conversion::IntoPyObject,
196 types::{PyBytes, PyBytesMethods, any::PyAnyMethods},
197 };
198
199 #[test]
200 #[cfg(panic = "unwind")]
201 fn array_try_from_fn() {
202 static DROP_COUNTER: AtomicUsize = AtomicUsize::new(0);
203 struct CountDrop;
204 impl Drop for CountDrop {
205 fn drop(&mut self) {
206 DROP_COUNTER.fetch_add(1, Ordering::SeqCst);
207 }
208 }
209 let _ = catch_unwind_silent(move || {
210 let _: Result<[CountDrop; 4], ()> = super::array_try_from_fn(|idx| {
211 #[expect(clippy::manual_assert, reason = "testing panic during array creation")]
212 if idx == 2 {
213 panic!("peek a boo");
214 }
215 Ok(CountDrop)
216 });
217 });
218 assert_eq!(DROP_COUNTER.load(Ordering::SeqCst), 2);
219 }
220
221 #[test]
222 fn test_extract_bytes_to_array() {
223 Python::attach(|py| {
224 let v: [u8; 33] = py
225 .eval(c"b'abcabcabcabcabcabcabcabcabcabcabc'", None, None)
226 .unwrap()
227 .extract()
228 .unwrap();
229 assert_eq!(&v, b"abcabcabcabcabcabcabcabcabcabcabc");
230 })
231 }
232
233 #[test]
234 fn test_extract_bytes_wrong_length() {
235 Python::attach(|py| {
236 let v: PyResult<[u8; 3]> = py.eval(c"b'abcdefg'", None, None).unwrap().extract();
237 assert_eq!(
238 v.unwrap_err().to_string(),
239 "ValueError: expected a sequence of length 3 (got 7)"
240 );
241 })
242 }
243
244 #[test]
245 fn test_extract_bytearray_to_array() {
246 Python::attach(|py| {
247 let v: [u8; 33] = py
248 .eval(
249 c"bytearray(b'abcabcabcabcabcabcabcabcabcabcabc')",
250 None,
251 None,
252 )
253 .unwrap()
254 .extract()
255 .unwrap();
256 assert_eq!(&v, b"abcabcabcabcabcabcabcabcabcabcabc");
257 })
258 }
259
260 #[test]
261 fn test_extract_small_bytearray_to_array() {
262 Python::attach(|py| {
263 let v: [u8; 3] = py
264 .eval(c"bytearray(b'abc')", None, None)
265 .unwrap()
266 .extract()
267 .unwrap();
268 assert_eq!(&v, b"abc");
269 });
270 }
271 #[test]
272 fn test_into_pyobject_array_conversion() {
273 Python::attach(|py| {
274 let array: [f32; 4] = [0.0, -16.0, 16.0, 42.0];
275 let pyobject = array.into_pyobject(py).unwrap();
276 let pylist = pyobject.cast::<PyList>().unwrap();
277 assert_eq!(pylist.get_item(0).unwrap().extract::<f32>().unwrap(), 0.0);
278 assert_eq!(pylist.get_item(1).unwrap().extract::<f32>().unwrap(), -16.0);
279 assert_eq!(pylist.get_item(2).unwrap().extract::<f32>().unwrap(), 16.0);
280 assert_eq!(pylist.get_item(3).unwrap().extract::<f32>().unwrap(), 42.0);
281 });
282 }
283
284 #[test]
285 fn test_extract_invalid_sequence_length() {
286 Python::attach(|py| {
287 let v: PyResult<[u8; 3]> = py
288 .eval(c"bytearray(b'abcdefg')", None, None)
289 .unwrap()
290 .extract();
291 assert_eq!(
292 v.unwrap_err().to_string(),
293 "ValueError: expected a sequence of length 3 (got 7)"
294 );
295 })
296 }
297
298 #[test]
299 fn test_intopyobject_array_conversion() {
300 Python::attach(|py| {
301 let array: [f32; 4] = [0.0, -16.0, 16.0, 42.0];
302 let pylist = array
303 .into_pyobject(py)
304 .unwrap()
305 .cast_into::<PyList>()
306 .unwrap();
307
308 assert_eq!(pylist.get_item(0).unwrap().extract::<f32>().unwrap(), 0.0);
309 assert_eq!(pylist.get_item(1).unwrap().extract::<f32>().unwrap(), -16.0);
310 assert_eq!(pylist.get_item(2).unwrap().extract::<f32>().unwrap(), 16.0);
311 assert_eq!(pylist.get_item(3).unwrap().extract::<f32>().unwrap(), 42.0);
312 });
313 }
314
315 #[test]
316 fn test_array_intopyobject_impl() {
317 Python::attach(|py| {
318 let bytes: [u8; 6] = *b"foobar";
319 let obj = bytes.into_pyobject(py).unwrap();
320 assert!(obj.is_instance_of::<PyBytes>());
321 let obj = obj.cast_into::<PyBytes>().unwrap();
322 assert_eq!(obj.as_bytes(), &bytes);
323
324 let nums: [u16; 4] = [0, 1, 2, 3];
325 let obj = nums.into_pyobject(py).unwrap();
326 assert!(obj.is_instance_of::<PyList>());
327 });
328 }
329
330 #[test]
331 fn test_extract_non_iterable_to_array() {
332 Python::attach(|py| {
333 let v = py.eval(c"42", None, None).unwrap();
334 v.extract::<i32>().unwrap();
335 v.extract::<[i32; 1]>().unwrap_err();
336 });
337 }
338
339 #[cfg(feature = "macros")]
340 #[test]
341 fn test_pyclass_intopy_array_conversion() {
342 #[crate::pyclass(crate = "crate")]
343 struct Foo;
344
345 Python::attach(|py| {
346 let array: [Foo; 8] = [Foo, Foo, Foo, Foo, Foo, Foo, Foo, Foo];
347 let list = array
348 .into_pyobject(py)
349 .unwrap()
350 .cast_into::<PyList>()
351 .unwrap();
352 let _bound = list.get_item(4).unwrap().cast::<Foo>().unwrap();
353 });
354 }
355
356 #[cfg(panic = "unwind")]
358 fn catch_unwind_silent<F, R>(f: F) -> Result<R, Box<dyn Any + Send + 'static>>
359 where
360 F: FnOnce() -> R + panic::UnwindSafe,
361 {
362 let prev_hook = panic::take_hook();
363 panic::set_hook(Box::new(|_| {}));
364 let result = panic::catch_unwind(f);
365 panic::set_hook(prev_hook);
366 result
367 }
368}