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
31 changes: 2 additions & 29 deletions crates/vm/src/stdlib/_ctypes/array.rs
Original file line number Diff line number Diff line change
Expand Up @@ -338,35 +338,8 @@ impl PyCArrayType {
}

#[pymethod]
fn from_param(zelf: PyObjectRef, value: PyObjectRef, vm: &VirtualMachine) -> PyResult {
// zelf is the array type class that from_param was called on
let cls = zelf
.downcast::<PyType>()
.map_err(|_| vm.new_type_error("from_param: expected a type"))?;

// 1. If already an instance of the requested type, return it
if value.is_instance(cls.as_object(), vm)? {
return Ok(value);
}

// 2. Check for CArgObject (PyCArg_CheckExact)
if let Some(carg) = value.downcast_ref::<super::_ctypes::CArgObject>() {
// Check if the wrapped object is an instance of the requested type
if carg.obj.is_instance(cls.as_object(), vm)? {
return Ok(value); // Return the CArgObject as-is
}
}

// 3. Check for _as_parameter_ attribute
if let Ok(as_parameter) = value.get_attr("_as_parameter_", vm) {
return Self::from_param(cls.as_object().to_owned(), as_parameter, vm);
}

Err(vm.new_type_error(format!(
"expected {} instance instead of {}",
cls.name(),
value.class().name()
)))
fn from_param(zelf: PyTypeRef, value: PyObjectRef, vm: &VirtualMachine) -> PyResult {
PyCData::from_param(zelf, value, vm)
}
}

Expand Down
38 changes: 38 additions & 0 deletions crates/vm/src/stdlib/_ctypes/base.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1248,6 +1248,44 @@ impl CDataValue {
}

impl PyCData {
pub(super) fn from_param(cls: PyTypeRef, value: PyObjectRef, vm: &VirtualMachine) -> PyResult {
// If already an instance of the requested type, return it
if value.is_instance(cls.as_object(), vm)? {
return Ok(value);
}

if let Some(carg) = value.downcast_ref::<super::_ctypes::CArgObject>() {
// Snapshot the initialized prototype before calling Python. An array
// accepts byref(element), not byref(array); aggregates without a
// prototype cannot accept a CArgObject through this conversion.
let proto = cls.stg_info_opt().and_then(|info| info.proto.clone());
if let Some(proto) = proto
&& carg.obj.is_instance(proto.as_object(), vm)?
{
return Ok(value);
}
return Err(vm.new_type_error(format!(
"expected {} instance instead of pointer to {}",
cls.name(),
carg.obj.class().name()
)));
}

if let Some(as_parameter) = vm.get_attribute_opt(&value, "_as_parameter_")? {
// Recursive adaptation uses the generic converter, even when the
// initial call came through a pointer type's specialized converter.
return vm.with_recursion(" while processing _as_parameter_", || {
Self::from_param(cls, as_parameter, vm)
});
}

Err(vm.new_type_error(format!(
"expected {} instance instead of {}",
cls.name(),
value.class().name()
)))
}

/// Create from StgInfo (PyCData_MallocBuffer pattern)
pub(crate) fn from_stg_info(stg_info: &StgInfo) -> Self {
Self {
Expand Down
72 changes: 19 additions & 53 deletions crates/vm/src/stdlib/_ctypes/pointer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -102,36 +102,21 @@ impl PyCPointerType {
}

#[pymethod]
fn from_param(zelf: PyObjectRef, value: PyObjectRef, vm: &VirtualMachine) -> PyResult {
// zelf is the pointer type class that from_param was called on
let cls = zelf
.downcast::<PyType>()
.map_err(|_| vm.new_type_error("from_param: expected a type"))?;

// 1. None is allowed for pointer types
fn from_param(cls: PyTypeRef, value: PyObjectRef, vm: &VirtualMachine) -> PyResult {
// None is allowed for pointer types
if vm.is_none(&value) {
return Ok(value);
}

// 1.5 CArgObject (from byref()) - check if underlying obj is instance of _type_
if let Some(carg) = value.downcast_ref::<super::_ctypes::CArgObject>()
&& let Ok(type_attr) = cls.as_object().get_attr("_type_", vm)
&& let Ok(type_ref) = type_attr.downcast::<PyType>()
&& carg.obj.is_instance(type_ref.as_object(), vm)?
{
return Ok(value);
}

// 2. If already an instance of the requested type, return it
if value.is_instance(cls.as_object(), vm)? {
return Ok(value);
}
// Conversion follows the initialized prototype, not a later assignment
// to _type_. Release the metadata borrow before instance/subclass checks.
let proto = cls.stg_info(vm)?.proto.clone().ok_or_else(|| {
vm.new_type_error("cannot convert argument: POINTER _type_ type is not set")
})?;

// 3. If value is an instance of _type_ (the pointed-to type), wrap with byref
if let Ok(type_attr) = cls.as_object().get_attr("_type_", vm)
&& let Ok(type_ref) = type_attr.downcast::<PyType>()
&& value.is_instance(type_ref.as_object(), vm)?
{
// A direct referent is implicitly passed by reference. This special
// conversion does not apply to values found through _as_parameter_.
if value.is_instance(proto.as_object(), vm)? {
// Return byref(value)
return super::_ctypes::byref(
super::_ctypes::ByRefArgs {
Expand All @@ -142,39 +127,20 @@ impl PyCPointerType {
);
}

// 4. Array/Pointer instances with compatible proto
// Array/Pointer instances with compatible proto
// "Array instances are also pointers when the item types are the same."
let is_pointer_or_array = value.downcast_ref::<PyCPointer>().is_some()
|| value.downcast_ref::<super::array::PyCArray>().is_some();

if is_pointer_or_array {
let is_compatible = {
if let Some(value_stginfo) = value.class().stg_info_opt()
&& let Some(ref value_proto) = value_stginfo.proto
&& let Some(cls_stginfo) = cls.stg_info_opt()
&& let Some(ref cls_proto) = cls_stginfo.proto
{
// Check if value's proto is a subclass of target's proto
value_proto.fast_issubclass(cls_proto)
} else {
false
}
};
if is_compatible {
if value.downcast_ref::<PyCPointer>().is_some()
|| value.downcast_ref::<super::array::PyCArray>().is_some()
{
let value_proto = value.class().stg_info(vm)?.proto.clone();
if let Some(value_proto) = value_proto
&& value_proto.as_object().is_subclass(proto.as_object(), vm)?
{
return Ok(value);
}
}

// 5. Check for _as_parameter_ attribute
if let Ok(as_parameter) = value.get_attr("_as_parameter_", vm) {
return Self::from_param(cls.as_object().to_owned(), as_parameter, vm);
}

Err(vm.new_type_error(format!(
"expected {} instance instead of {}",
cls.name(),
value.class().name()
)))
PyCData::from_param(cls, value, vm)
}

// PyCPointerType_set_type: Complete an incomplete pointer type
Expand Down
23 changes: 2 additions & 21 deletions crates/vm/src/stdlib/_ctypes/structure.rs
Original file line number Diff line number Diff line change
Expand Up @@ -144,27 +144,8 @@ impl PyCStructType {
}

#[pymethod]
fn from_param(zelf: PyObjectRef, value: PyObjectRef, vm: &VirtualMachine) -> PyResult {
// zelf is the structure type class that from_param was called on
let cls = zelf
.downcast::<PyType>()
.map_err(|_| vm.new_type_error("from_param: expected a type"))?;

// 1. If already an instance of the requested type, return it
if value.is_instance(cls.as_object(), vm)? {
return Ok(value);
}

// 2. Check for _as_parameter_ attribute
if let Ok(as_parameter) = value.get_attr("_as_parameter_", vm) {
return Self::from_param(cls.as_object().to_owned(), as_parameter, vm);
}

Err(vm.new_type_error(format!(
"expected {} instance instead of {}",
cls.name(),
value.class().name()
)))
fn from_param(zelf: PyTypeRef, value: PyObjectRef, vm: &VirtualMachine) -> PyResult {
PyCData::from_param(zelf, value, vm)
}

// CDataType methods - delegated to PyCData implementations
Expand Down
44 changes: 2 additions & 42 deletions crates/vm/src/stdlib/_ctypes/union.rs
Original file line number Diff line number Diff line change
Expand Up @@ -365,48 +365,8 @@ impl PyCUnionType {
}

#[pymethod]
fn from_param(zelf: PyObjectRef, value: PyObjectRef, vm: &VirtualMachine) -> PyResult {
// zelf is the union type class that from_param was called on
let cls = zelf
.downcast::<PyType>()
.map_err(|_| vm.new_type_error("from_param: expected a type"))?;

// 1. If already an instance of the requested type, return it
if value.is_instance(cls.as_object(), vm)? {
return Ok(value);
}

// 2. Check for CArgObject (PyCArg_CheckExact)
if let Some(carg) = value.downcast_ref::<super::_ctypes::CArgObject>() {
// Check against proto (for pointer types)
if let Some(stg_info) = cls.stg_info_opt()
&& let Some(ref proto) = stg_info.proto
&& carg.obj.is_instance(proto.as_object(), vm)?
{
return Ok(value);
}
// Fallback: check if the wrapped object is an instance of the requested type
if carg.obj.is_instance(cls.as_object(), vm)? {
return Ok(value); // Return the CArgObject as-is
}
// CArgObject but wrong type
return Err(vm.new_type_error(format!(
"expected {} instance instead of pointer to {}",
cls.name(),
carg.obj.class().name()
)));
}

// 3. Check for _as_parameter_ attribute
if let Ok(as_parameter) = value.get_attr("_as_parameter_", vm) {
return Self::from_param(cls.as_object().to_owned(), as_parameter, vm);
}

Err(vm.new_type_error(format!(
"expected {} instance instead of {}",
cls.name(),
value.class().name()
)))
fn from_param(zelf: PyTypeRef, value: PyObjectRef, vm: &VirtualMachine) -> PyResult {
PyCData::from_param(zelf, value, vm)
}

// CDataType methods - delegated to PyCData implementations
Expand Down
Loading