Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 0 additions & 6 deletions Lib/test/test_array.py
Original file line number Diff line number Diff line change
Expand Up @@ -247,17 +247,13 @@ def test_deepcopy(self):
self.assertNotEqual(id(a), id(b))
self.assertEqual(a, b)

# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_reduce_ex(self):
a = array.array(self.typecode, self.example)
for protocol in range(3):
self.assertIs(a.__reduce_ex__(protocol)[0], array.array)
for protocol in range(3, pickle.HIGHEST_PROTOCOL + 1):
self.assertIs(a.__reduce_ex__(protocol)[0], array_reconstructor)

# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_pickle(self):
for protocol in range(pickle.HIGHEST_PROTOCOL + 1):
a = array.array(self.typecode, self.example)
Expand All @@ -273,8 +269,6 @@ def test_pickle(self):
self.assertEqual(a.x, b.x)
self.assertEqual(type(a), type(b))

# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_pickle_for_empty_array(self):
for protocol in range(pickle.HIGHEST_PROTOCOL + 1):
a = array.array(self.typecode)
Expand Down
10 changes: 9 additions & 1 deletion extra_tests/snippets/stdlib_array.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from testutils import assert_raises
from array import array
from pickle import dumps, loads

a1 = array("b", [0, 1, 2, 3])

Expand Down Expand Up @@ -96,4 +97,11 @@ def test_array_frombytes():
with assert_raises(IndexError):
a[0] = 42
with assert_raises(IndexError):
del a[42]
del a[42]

test_str = '🌉abc🌐def🌉🌐'
u = array('u', test_str)
# skip as 2 bytes character enviroment with CPython is failing the test
if u.itemsize >= 4:
assert u.__reduce_ex__(1)[1][1] == list(test_str)
assert loads(dumps(u, 1)) == loads(dumps(u, 3))
160 changes: 119 additions & 41 deletions stdlib/src/array.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ mod array {
};
use crate::vm::{
builtins::{
PyByteArray, PyBytes, PyBytesRef, PyIntRef, PyList, PyListRef, PySliceRef, PyStr,
PyStrRef, PyTypeRef,
PyByteArray, PyBytes, PyBytesRef, PyDictRef, PyFloat, PyInt, PyIntRef, PyList,
PyListRef, PySliceRef, PyStr, PyStrRef, PyTypeRef,
},
class_or_notimplemented,
function::{
Expand All @@ -28,8 +28,8 @@ mod array {
AsBuffer, AsMapping, Comparable, Iterable, IteratorIterable, PyComparisonOp,
SlotConstructor, SlotIterator,
},
IdProtocol, PyComparisonValue, PyObjectRef, PyRef, PyResult, PyValue, TryFromObject,
TypeProtocol, VirtualMachine,
IdProtocol, PyComparisonValue, PyObjectRef, PyObjectWrap, PyRef, PyResult, PyValue,
TryFromObject, TypeProtocol, VirtualMachine,
};
use crossbeam_utils::atomic::AtomicCell;
use itertools::Itertools;
Expand Down Expand Up @@ -460,6 +460,14 @@ mod array {
})*
}
}

fn get_objects(&self, vm: &VirtualMachine) -> Vec<PyObjectRef> {
match self {
$(ArrayContentType::$n(v) => {
v.iter().map(|&x| x.to_object(vm)).collect()
})*
}
}
}
};
}
Expand All @@ -486,32 +494,41 @@ mod array {
trait ArrayElement: Sized {
fn try_into_from_object(vm: &VirtualMachine, obj: PyObjectRef) -> PyResult<Self>;
fn byteswap(self) -> Self;
fn to_object(self, vm: &VirtualMachine) -> PyObjectRef;
}

macro_rules! impl_array_element {
($(($t:ty, $f_into:path, $f_swap:path),)*) => {$(
($(($t:ty, $f_from:path, $f_swap:path, $f_to:path),)*) => {$(
impl ArrayElement for $t {
fn try_into_from_object(vm: &VirtualMachine, obj: PyObjectRef) -> PyResult<Self> {
$f_into(vm, obj)
$f_from(vm, obj)
}
fn byteswap(self) -> Self {
$f_swap(self)
}
fn to_object(self, vm: &VirtualMachine) -> PyObjectRef {
$f_to(self).into_object(vm)
}
}
)*};
}

impl_array_element!(
(i8, i8::try_from_object, i8::swap_bytes),
(u8, u8::try_from_object, u8::swap_bytes),
(i16, i16::try_from_object, i16::swap_bytes),
(u16, u16::try_from_object, u16::swap_bytes),
(i32, i32::try_from_object, i32::swap_bytes),
(u32, u32::try_from_object, u32::swap_bytes),
(i64, i64::try_from_object, i64::swap_bytes),
(u64, u64::try_from_object, u64::swap_bytes),
(f32, f32_try_into_from_object, f32_swap_bytes),
(f64, f64_try_into_from_object, f64_swap_bytes),
(i8, i8::try_from_object, i8::swap_bytes, PyInt::from),
(u8, u8::try_from_object, u8::swap_bytes, PyInt::from),
(i16, i16::try_from_object, i16::swap_bytes, PyInt::from),
(u16, u16::try_from_object, u16::swap_bytes, PyInt::from),
(i32, i32::try_from_object, i32::swap_bytes, PyInt::from),
(u32, u32::try_from_object, u32::swap_bytes, PyInt::from),
(i64, i64::try_from_object, i64::swap_bytes, PyInt::from),
(u64, u64::try_from_object, u64::swap_bytes, PyInt::from),
(
f32,
f32_try_into_from_object,
f32_swap_bytes,
pyfloat_from_f32
),
(f64, f64_try_into_from_object, f64_swap_bytes, PyFloat::from),
);

fn f32_swap_bytes(x: f32) -> f32 {
Expand All @@ -530,6 +547,10 @@ mod array {
ArgIntoFloat::try_from_object(vm, obj).map(|x| x.to_f64())
}

fn pyfloat_from_f32(value: f32) -> PyFloat {
PyFloat::from(value as f64)
}

impl ArrayElement for WideChar {
fn try_into_from_object(vm: &VirtualMachine, obj: PyObjectRef) -> PyResult<Self> {
PyStrRef::try_from_object(vm, obj)?
Expand All @@ -542,6 +563,9 @@ mod array {
fn byteswap(self) -> Self {
Self(self.0.swap_bytes())
}
fn to_object(self, _vm: &VirtualMachine) -> PyObjectRef {
unreachable!()
}
}

fn u32_to_char(ch: u32) -> Result<char, String> {
Expand Down Expand Up @@ -731,6 +755,38 @@ mod array {
}
}

fn _wchar_bytes_to_string(
bytes: &[u8],
item_size: usize,
vm: &VirtualMachine,
) -> PyResult<String> {
if item_size == 2 {
// safe because every configuration of bytes for the types we support are valid
let utf16 = unsafe {
std::slice::from_raw_parts(
bytes.as_ptr() as *const u16,
bytes.len() / std::mem::size_of::<u16>(),
)
};
Ok(String::from_utf16_lossy(utf16))
} else {
// safe because every configuration of bytes for the types we support are valid
let chars = unsafe {
std::slice::from_raw_parts(
bytes.as_ptr() as *const u32,
bytes.len() / std::mem::size_of::<u32>(),
)
};
chars
.iter()
.map(|&ch| {
// cpython issue 17223
u32_to_char(ch).map_err(|msg| vm.new_value_error(msg))
})
.try_collect()
}
}

fn _unicode_to_wchar_bytes(utf8: &str, item_size: usize) -> Vec<u8> {
if item_size == 2 {
utf8.encode_utf16()
Expand Down Expand Up @@ -771,31 +827,7 @@ mod array {
));
}
let bytes = array.get_bytes();
if self.itemsize() == 2 {
// safe because every configuration of bytes for the types we support are valid
let utf16 = unsafe {
std::slice::from_raw_parts(
bytes.as_ptr() as *const u16,
bytes.len() / std::mem::size_of::<u16>(),
)
};
Ok(String::from_utf16_lossy(utf16))
} else {
// safe because every configuration of bytes for the types we support are valid
let chars = unsafe {
std::slice::from_raw_parts(
bytes.as_ptr() as *const u32,
bytes.len() / std::mem::size_of::<u32>(),
)
};
chars
.iter()
.map(|&ch| {
// cpython issue 17223
u32_to_char(ch).map_err(|msg| vm.new_value_error(msg))
})
.try_collect()
}
Self::_wchar_bytes_to_string(bytes, self.itemsize(), vm)
}

fn _from_bytes(&self, b: &[u8], itemsize: usize, vm: &VirtualMachine) -> PyResult<()> {
Expand Down Expand Up @@ -1079,6 +1111,52 @@ mod array {
}
Ok(true)
}

#[pymethod(magic)]
fn reduce_ex(
zelf: PyRef<Self>,
proto: usize,
vm: &VirtualMachine,
) -> PyResult<(PyObjectRef, PyObjectRef, Option<PyDictRef>)> {
if proto < 3 {
return Self::reduce(zelf, vm);
}
let array = zelf.read();
let cls = zelf.as_object().clone_class().into_object();
let typecode = vm.ctx.new_utf8_str(array.typecode_str());
let bytes = vm.ctx.new_bytes(array.get_bytes().to_vec());
let code = MachineFormatCode::from_typecode(array.typecode()).unwrap();
let code = PyInt::from(u8::from(code)).into_object(vm);
let module = vm.import("array", None, 0)?;
let func = vm.get_attribute(module, "_array_reconstructor")?;
Ok((
func,
vm.ctx.new_tuple(vec![cls, typecode, code, bytes]),
zelf.as_object().dict(),
))
}

#[pymethod(magic)]
fn reduce(
zelf: PyRef<Self>,
vm: &VirtualMachine,
) -> PyResult<(PyObjectRef, PyObjectRef, Option<PyDictRef>)> {
let array = zelf.read();
let cls = zelf.as_object().clone_class().into_object();
let typecode = vm.ctx.new_utf8_str(array.typecode_str());
let values = if array.typecode() == 'u' {
let s = Self::_wchar_bytes_to_string(array.get_bytes(), array.itemsize(), vm)?;
s.chars().map(|x| x.into_pyobject(vm)).collect()
Comment on lines +1148 to +1149

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In windows, I tried this code with CPython:

test_str = '🌉abc🌐def🌉🌐'
u = array('u', test_str)
print(u.__reduce_ex__(1))

then

  File ".\extra_tests\snippets\stdlib_array.py", line 104, in <module>
    assert u.__reduce_ex__(1)[1][1] == list(test_str), (u.__reduce_ex__(1)[1][1], list(test_str))
AssertionError: (['\ud83c', '\udf09', 'a', 'b', 'c', '\ud83c', '\udf10', 'd', 'e', 'f', '\ud83c', '\udf09', '\ud83c', '\udf10'], ['🌉', 'a', 'b', 'c', '🌐', 'd', 'e', 'f', '🌉', '🌐'])

The values are wchar encoded in CPython but doesn't look like that here

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you mean CPython did not treat it as same in linux macos?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it seems. I think that is not exactly the platform-specific variant but sizeof(wchar_t) is different by platforms. Looks very fragile in point of view of compatibility, no wonder why it is deprecated.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't mind if you want to just skip testing for 16bit character environments.

} else {
array.get_objects(vm)
};
let values = vm.ctx.new_list(values);
Ok((
cls,
vm.ctx.new_tuple(vec![typecode, values]),
zelf.as_object().dict(),
))
}
}

impl Comparable for PyArray {
Expand Down