Skip to content
Open
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
65 changes: 27 additions & 38 deletions crates/vm/src/stdlib/_ctypes/array.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ use crate::{
},
class::StaticType,
function::{ArgBytesLike, FuncArgs, PySetterValue},
protocol::{BufferDescriptor, PyBuffer, PyMappingMethods, PyNumberMethods, PySequenceMethods},
types::{AsBuffer, AsMapping, AsNumber, AsSequence, Constructor, Initializer},
protocol::{BufferDescriptor, PyBuffer, PyMappingMethods, PySequenceMethods},
types::{AsBuffer, AsMapping, AsSequence, Constructor, Initializer},
};
use alloc::borrow::Cow;
use num_traits::{Signed, ToPrimitive};
Expand Down Expand Up @@ -44,6 +44,27 @@ fn get_size_from_format(fmt: &str) -> usize {
code.map_or(1, |c| simple_type_size(&c).unwrap_or(1))
}

pub(super) static CTYPE_SEQUENCE_METHODS: PySequenceMethods = PySequenceMethods {
repeat: Some(|seq, length, vm| repeat_type(seq.obj, length, vm)),
..PySequenceMethods::NOT_IMPLEMENTED
};

pub(super) fn repeat_type(ctype: &PyObject, length: isize, vm: &VirtualMachine) -> PyResult {
if length < 0 {
return Err(vm.new_value_error(format!("Array length must be >= 0, not {length}")));
}
let cls = ctype
.downcast_ref::<PyType>()
.ok_or_else(|| vm.new_type_error("expected type"))?;
if let Some(info) = cls.stg_info_opt()
&& info.size != 0
&& length as usize > isize::MAX as usize / info.size
{
return Err(vm.new_overflow_error("array too large"));
}
array_type_from_ctype(ctype.to_owned(), length as usize, vm)
}

/// Creates array type for (element_type, length)
/// Uses _array_type_cache to ensure identical calls return the same type object
pub(super) fn array_type_from_ctype(
Expand Down Expand Up @@ -305,7 +326,7 @@ impl Initializer for PyCArrayType {
}
}

#[pyclass(flags(IMMUTABLETYPE), with(Initializer, AsNumber))]
#[pyclass(flags(IMMUTABLETYPE), with(Initializer, AsSequence))]
impl PyCArrayType {
#[pygetset(name = "__pointer_type__")]
fn pointer_type(zelf: PyTypeRef, vm: &VirtualMachine) -> PyResult {
Expand Down Expand Up @@ -350,41 +371,9 @@ impl PyCArrayType {
}
}

impl AsNumber for PyCArrayType {
fn as_number() -> &'static PyNumberMethods {
static AS_NUMBER: PyNumberMethods = PyNumberMethods {
multiply: Some(|a, b, vm| {
// a is a type object whose metaclass is PyCArrayType (e.g., Array_5)
let n = b
.try_index(vm)?
.as_bigint()
.to_isize()
.ok_or_else(|| vm.new_overflow_error("array size too large"))?;

if n < 0 {
return Err(vm.new_value_error(format!("Array length must be >= 0, not {n}")));
}

// Check for overflow before creating the new array type
let zelf_type = a
.downcast_ref::<PyType>()
.ok_or_else(|| vm.new_type_error("Expected type"))?;

if let Some(stg_info) = zelf_type.stg_info_opt() {
let current_size = stg_info.size;
// Check if current_size * n would overflow
if current_size != 0 && (n as usize) > isize::MAX as usize / current_size {
return Err(vm.new_overflow_error("array too large"));
}
}

// Use cached array type creation
// The element type of the new array is the current array type itself
array_type_from_ctype(a.to_owned(), n as usize, vm)
}),
..PyNumberMethods::NOT_IMPLEMENTED
};
&AS_NUMBER
impl AsSequence for PyCArrayType {
fn as_sequence() -> &'static PySequenceMethods {
&super::array::CTYPE_SEQUENCE_METHODS
}
}

Expand Down
12 changes: 9 additions & 3 deletions crates/vm/src/stdlib/_ctypes/function.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ use crate::{
builtins::{PyBytes, PyDict, PyInt, PyStr, PyTuple, PyType, PyTypeRef},
class::StaticType,
function::FuncArgs,
protocol::{BufferDescriptor, PyBuffer, PyNumberMethods},
types::{AsBuffer, AsNumber, Callable, Constructor, Initializer, Representable},
protocol::{BufferDescriptor, PyBuffer, PyNumberMethods, PySequenceMethods},
types::{AsBuffer, AsNumber, AsSequence, Callable, Constructor, Initializer, Representable},
vm::thread::with_current_vm,
};
use alloc::borrow::Cow;
Expand Down Expand Up @@ -331,7 +331,7 @@ impl Initializer for PyCFuncPtrType {
}
}

#[pyclass(flags(IMMUTABLETYPE), with(Initializer))]
#[pyclass(flags(IMMUTABLETYPE), with(AsSequence, Initializer))]
impl PyCFuncPtrType {
#[pygetset(name = "__pointer_type__")]
fn pointer_type(zelf: PyTypeRef, vm: &VirtualMachine) -> PyResult {
Expand Down Expand Up @@ -2021,6 +2021,12 @@ impl Py<PyCThunk> {
}
}

impl AsSequence for PyCFuncPtrType {
fn as_sequence() -> &'static PySequenceMethods {
&super::array::CTYPE_SEQUENCE_METHODS
}
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down
38 changes: 8 additions & 30 deletions crates/vm/src/stdlib/_ctypes/pointer.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
use super::base::CDATA_BUFFER_METHODS;
use super::{PyCArray, PyCData, PyCSimple, PyCStructure, StgInfo, StgInfoFlags};
use crate::atomic_func;
use crate::protocol::{BufferDescriptor, PyBuffer, PyMappingMethods, PyNumberMethods};
use crate::types::{AsBuffer, AsMapping, AsNumber, Constructor, Initializer};
use crate::protocol::{
BufferDescriptor, PyBuffer, PyMappingMethods, PyNumberMethods, PySequenceMethods,
};
use crate::types::{AsBuffer, AsMapping, AsNumber, AsSequence, Constructor, Initializer};
use crate::{
AsObject, Py, PyObject, PyObjectRef, PyPayload, PyResult, VirtualMachine,
builtins::{PyBytes, PyInt, PyList, PySlice, PyStr, PyType, PyTypeRef},
Expand Down Expand Up @@ -87,7 +89,7 @@ impl Initializer for PyCPointerType {
}
}

#[pyclass(flags(BASETYPE, IMMUTABLETYPE), with(AsNumber, Initializer))]
#[pyclass(flags(BASETYPE, IMMUTABLETYPE), with(AsSequence, Initializer))]
impl PyCPointerType {
#[pygetset(name = "__pointer_type__")]
fn pointer_type(zelf: PyTypeRef, vm: &VirtualMachine) -> PyResult {
Expand Down Expand Up @@ -175,16 +177,6 @@ impl PyCPointerType {
)))
}

fn __mul__(cls: PyTypeRef, n: isize, vm: &VirtualMachine) -> PyResult {
use super::array::array_type_from_ctype;

if n < 0 {
return Err(vm.new_value_error(format!("Array length must be >= 0, not {n}")));
}
// Use cached array type creation
array_type_from_ctype(cls.into(), n as usize, vm)
}

// PyCPointerType_set_type: Complete an incomplete pointer type
#[pymethod]
fn set_type(zelf: PyTypeRef, typ: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> {
Expand Down Expand Up @@ -229,23 +221,9 @@ impl PyCPointerType {
}
}

impl AsNumber for PyCPointerType {
fn as_number() -> &'static PyNumberMethods {
static AS_NUMBER: PyNumberMethods = PyNumberMethods {
multiply: Some(|a, b, vm| {
let cls = a
.downcast_ref::<PyType>()
.ok_or_else(|| vm.new_type_error("expected type"))?;
let n = b
.try_index(vm)?
.as_bigint()
.to_isize()
.ok_or_else(|| vm.new_overflow_error("array size too large"))?;
PyCPointerType::__mul__(cls.to_owned(), n, vm)
}),
..PyNumberMethods::NOT_IMPLEMENTED
};
&AS_NUMBER
impl AsSequence for PyCPointerType {
fn as_sequence() -> &'static PySequenceMethods {
&super::array::CTYPE_SEQUENCE_METHODS
}
}

Expand Down
40 changes: 7 additions & 33 deletions crates/vm/src/stdlib/_ctypes/simple.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ use super::pointer::PyCPointer;
use crate::builtins::{PyByteArray, PyBytes, PyInt, PyNone, PyStr, PyType, PyTypeRef};
use crate::convert::ToPyObject;
use crate::function::{Either, FuncArgs, OptionalArg};
use crate::protocol::{BufferDescriptor, PyBuffer, PyNumberMethods};
use crate::types::{AsBuffer, AsNumber, Constructor, Initializer, Representable};
use crate::protocol::{BufferDescriptor, PyBuffer, PyNumberMethods, PySequenceMethods};
use crate::types::{AsBuffer, AsNumber, AsSequence, Constructor, Initializer, Representable};
use crate::{
AsObject, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult, VirtualMachine,
class::PyClassDef, set_attrs,
Expand Down Expand Up @@ -226,7 +226,7 @@ fn set_primitive(_type_: &str, value: &PyObject, vm: &VirtualMachine) -> PyResul
#[repr(transparent)]
pub(crate) struct PyCSimpleType(PyType);

#[pyclass(flags(BASETYPE), with(AsNumber, Initializer))]
#[pyclass(flags(BASETYPE), with(AsSequence, Initializer))]
impl PyCSimpleType {
#[pygetset(name = "__pointer_type__")]
fn pointer_type(zelf: PyTypeRef, vm: &VirtualMachine) -> PyResult {
Expand Down Expand Up @@ -494,31 +494,11 @@ impl PyCSimpleType {
_ => Err(vm.new_type_error("wrong type")),
}
}

fn __mul__(cls: PyTypeRef, n: isize, vm: &VirtualMachine) -> PyResult {
Py::<PyCSimple>::repeat(cls, n, vm)
}
}

impl AsNumber for PyCSimpleType {
fn as_number() -> &'static PyNumberMethods {
static AS_NUMBER: PyNumberMethods = PyNumberMethods {
multiply: Some(|a, b, vm| {
// a is a PyCSimpleType instance (type object like c_char)
// b is int (array size)
let cls = a
.downcast_ref::<PyType>()
.ok_or_else(|| vm.new_type_error("expected type"))?;
let n = b
.try_index(vm)?
.as_bigint()
.to_isize()
.ok_or_else(|| vm.new_overflow_error("array size too large"))?;
Py::<PyCSimple>::repeat(cls.to_owned(), n, vm)
}),
..PyNumberMethods::NOT_IMPLEMENTED
};
&AS_NUMBER
impl AsSequence for PyCSimpleType {
fn as_sequence() -> &'static PySequenceMethods {
&super::array::CTYPE_SEQUENCE_METHODS
}
}

Expand Down Expand Up @@ -1229,13 +1209,7 @@ impl Py<PyCSimple> {

#[pyclassmethod]
fn repeat(cls: PyTypeRef, n: isize, vm: &VirtualMachine) -> PyResult {
use super::array::array_type_from_ctype;

if n < 0 {
return Err(vm.new_value_error(format!("Array length must be >= 0, not {n}")));
}
// Use cached array type creation
array_type_from_ctype(cls.into(), n as usize, vm)
super::array::repeat_type(cls.as_object(), n, vm)
}

// Simple_from_outparm - convert output parameter back to Python value
Expand Down
36 changes: 6 additions & 30 deletions crates/vm/src/stdlib/_ctypes/structure.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ use super::base::{CDATA_BUFFER_METHODS, PyCData, PyCField, StgInfo, StgInfoFlags
use crate::builtins::{PyList, PyStr, PyTuple, PyType, PyTypeRef, PyUtf8Str};
use crate::convert::ToPyObject;
use crate::function::{ArgStrictInt, FuncArgs, OptionalArg, PySetterValue};
use crate::protocol::{BufferDescriptor, PyBuffer, PyNumberMethods};
use crate::protocol::{BufferDescriptor, PyBuffer, PySequenceMethods};
use crate::stdlib::_warnings;
use crate::types::{AsBuffer, AsNumber, Constructor, Initializer, SetAttr};
use crate::types::{AsBuffer, AsSequence, Constructor, Initializer, SetAttr};
use crate::{AsObject, Py, PyObject, PyObjectRef, PyPayload, PyResult, VirtualMachine};
use alloc::borrow::Cow;
use core::fmt::Debug;
Expand Down Expand Up @@ -129,7 +129,7 @@ impl Initializer for PyCStructType {
}
}

#[pyclass(flags(BASETYPE), with(AsNumber, Constructor, Initializer, SetAttr))]
#[pyclass(flags(BASETYPE), with(AsSequence, Constructor, Initializer, SetAttr))]
impl PyCStructType {
#[pygetset(name = "__pointer_type__")]
fn pointer_type(zelf: PyTypeRef, vm: &VirtualMachine) -> PyResult {
Expand Down Expand Up @@ -565,35 +565,11 @@ impl PyCStructType {

Ok(())
}

fn __mul__(cls: PyTypeRef, n: isize, vm: &VirtualMachine) -> PyResult {
use super::array::array_type_from_ctype;

if n < 0 {
return Err(vm.new_value_error(format!("Array length must be >= 0, not {n}")));
}
// Use cached array type creation
array_type_from_ctype(cls.into(), n as usize, vm)
}
}

impl AsNumber for PyCStructType {
fn as_number() -> &'static PyNumberMethods {
static AS_NUMBER: PyNumberMethods = PyNumberMethods {
multiply: Some(|a, b, vm| {
let cls = a
.downcast_ref::<PyType>()
.ok_or_else(|| vm.new_type_error("expected type"))?;
let n = b
.try_index(vm)?
.as_bigint()
.to_isize()
.ok_or_else(|| vm.new_overflow_error("array size too large"))?;
PyCStructType::__mul__(cls.to_owned(), n, vm)
}),
..PyNumberMethods::NOT_IMPLEMENTED
};
&AS_NUMBER
impl AsSequence for PyCStructType {
fn as_sequence() -> &'static PySequenceMethods {
&super::array::CTYPE_SEQUENCE_METHODS
}
}

Expand Down
12 changes: 9 additions & 3 deletions crates/vm/src/stdlib/_ctypes/union.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ use super::{PyCData, PyCField, StgInfo};
use crate::builtins::{PyList, PyStr, PyTuple, PyType, PyTypeRef, PyUtf8Str};
use crate::convert::ToPyObject;
use crate::function::{ArgBytesLike, ArgStrictInt, FuncArgs, OptionalArg, PySetterValue};
use crate::protocol::{BufferDescriptor, PyBuffer};
use crate::protocol::{BufferDescriptor, PyBuffer, PySequenceMethods};
use crate::stdlib::_warnings;
use crate::types::{AsBuffer, Constructor, Initializer, SetAttr};
use crate::types::{AsBuffer, AsSequence, Constructor, Initializer, SetAttr};
use crate::{AsObject, Py, PyObject, PyObjectRef, PyPayload, PyResult, VirtualMachine};
use alloc::borrow::Cow;
use num_traits::ToPrimitive;
Expand Down Expand Up @@ -356,7 +356,7 @@ impl PyCUnionType {
}
}

#[pyclass(flags(BASETYPE), with(Constructor, Initializer, SetAttr))]
#[pyclass(flags(BASETYPE), with(AsSequence, Constructor, Initializer, SetAttr))]
impl PyCUnionType {
#[pygetset(name = "__pointer_type__")]
fn pointer_type(zelf: PyTypeRef, vm: &VirtualMachine) -> PyResult {
Expand Down Expand Up @@ -697,3 +697,9 @@ impl AsBuffer for PyCUnion {
Ok(buf)
}
}

impl AsSequence for PyCUnionType {
fn as_sequence() -> &'static PySequenceMethods {
&super::array::CTYPE_SEQUENCE_METHODS
}
}
36 changes: 36 additions & 0 deletions extra_tests/snippets/stdlib_ctypes.py
Original file line number Diff line number Diff line change
Expand Up @@ -469,4 +469,40 @@ def get_win_folder_via_ctypes(csidl_name: str) -> str:
gc.collect()
assert roundtrip(41) == 42


# All ctypes metaclasses use sequence repetition in either direction.
class RepeatStruct(ctypes.Structure):
_fields_ = [("value", ctypes.c_int)]


class RepeatUnion(ctypes.Union):
_fields_ = [("value", ctypes.c_int)]


for element_type in (
ctypes.c_int,
ctypes.c_int * 1,
ctypes.POINTER(ctypes.c_int),
RepeatStruct,
RepeatUnion,
Unary,
):
assert 2 * element_type is element_type * 2
Comment thread
1ndahous3 marked this conversation as resolved.


class ArrayLength:
def __index__(self):
return 2


assert ArrayLength() * ctypes.c_int is ctypes.c_int * 2


class ReflectedLength(ArrayLength):
def __rmul__(self, other):
return "reflected"


assert ctypes.c_int * ReflectedLength() == "reflected"

print("done")
Loading