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
8 changes: 7 additions & 1 deletion crates/vm/src/stdlib/_ctypes/base.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1875,7 +1875,12 @@ impl GetDescriptor for PyCField {
// Get PyCData from obj (works for both Structure and Union)
let cdata = Self::get_cdata_from_obj(&obj, vm)?;

if zelf.bitfield_size > 0 && zelf.proto.stg_info(vm)?.scalar_code != Some('?') {
// CPython's c_bool bitfields read the whole byte. User scalar subclasses
// return storage-backed instances rather than decoded primitive values.
if zelf.bitfield_size > 0
&& super::simple::returns_scalar(&zelf.proto)
&& zelf.proto.stg_info(vm)?.scalar_code != Some('?')
{
let buffer = cdata.buffer.read();
let data = buffer
.get(offset..offset.saturating_add(size))
Expand Down Expand Up @@ -1974,6 +1979,7 @@ impl Py<PyCField> {
let needs_swap = super::simple::is_swapped(&zelf.proto) && size > 1;

let code = zelf.proto.stg_info(vm)?.scalar_code;
// Match CPython: c_bool writes the whole byte, even for bitfields.
if zelf.bitfield_size > 0 && code != Some('?') {
let code = code.ok_or_else(|| vm.new_type_error("invalid bitfield type"))?;
let mut encoded = [0; 4];
Expand Down
45 changes: 14 additions & 31 deletions crates/vm/src/stdlib/_ctypes/function.rs
Original file line number Diff line number Diff line change
Expand Up @@ -261,16 +261,10 @@ impl ArgumentType for PyTypeRef {
{
None
} else {
// Use get_attr to traverse MRO (for subclasses like MyInt(c_int))
// Converters belong to initialized storage, not mutable _type_.
let typ = self
.as_object()
.get_attr(vm.ctx.intern_str("_type_"), vm)
.ok()
.type_code(vm)
.ok_or_else(|| vm.new_type_error("Unsupported argument type"))?;
let typ = typ
.downcast_ref::<PyStr>()
.ok_or_else(|| vm.new_type_error("Unsupported argument type"))?
.to_string();
if ffi_type_from_code(&typ).is_none() {
return Err(vm.new_type_error(format!("Unsupported argument type: {typ}")));
}
Expand Down Expand Up @@ -643,16 +637,12 @@ fn cast_check_pointertype(ctype: &PyObject, vm: &VirtualMachine) -> bool {
return true;
}

// simple pointer types via StgInfo.proto (c_void_p, c_char_p, etc.)
if let Ok(type_attr) = ctype.get_attr("_type_", vm)
&& let Some(s) = type_attr.downcast_ref::<PyStr>()
// Simple pointer types retain the representation selected at creation,
// even if the user later changes the Python-visible _type_ attribute.
if let Some(ty) = ctype.downcast_ref::<PyType>()
&& let Some(code) = ty.type_code(vm)
{
let c = s
.to_str()
.expect("_type_ is validated as ASCII at type creation");
if c.len() == 1 && "sPzUZXO".contains(c) {
return true;
}
return code.len() == 1 && "sPzUZXO".contains(code.as_str());
}

false
Expand Down Expand Up @@ -1119,15 +1109,12 @@ fn compute_ret_spec(
return RetSpec::Pointer;
}

// Simple type via its _type_ code (traversing MRO)
if let Ok(type_attr) = restype_type
.as_object()
.get_attr(vm.ctx.intern_str("_type_"), vm)
&& let Some(s) = type_attr.downcast_ref::<PyStr>()
&& let Some(code) = s.to_str()
&& ffi_type_from_code(code).is_some()
// Simple type via its initialized scalar converter.
if let Some(code) = restype_type
.type_code(vm)
.filter(|code| ffi_type_from_code(code).is_some())
{
return if simple_type_is_pointer(code) {
return if simple_type_is_pointer(&code) {
RetSpec::Pointer
} else {
RetSpec::Code(code.chars().next().unwrap_or('i'))
Expand Down Expand Up @@ -1605,15 +1592,11 @@ fn convert_raw_result(
// finalize it), which would self-deadlock against a held read guard.
let is_pointer_type = info.flags.contains(StgInfoFlags::TYPEFLAG_ISPOINTER);
let has_proto = info.proto.is_some();
let scalar_code = info.scalar_code;
drop(info);

// py_object: interpret return value as PyObject* and materialize it.
if let Ok(type_attr) = restype_type
.as_object()
.get_attr(vm.ctx.intern_str("_type_"), vm)
&& let Some(type_str) = type_attr.downcast_ref::<PyStr>()
&& type_str.to_str() == Some("O")
{
if scalar_code == Some('O') {
let ptr = NonNull::new(result_word as *mut PyObject)
.ok_or_else(|| vm.new_value_error("PyObject is NULL"))?;
unsafe {
Expand Down
35 changes: 35 additions & 0 deletions extra_tests/snippets/stdlib_ctypes.py
Original file line number Diff line number Diff line change
Expand Up @@ -972,6 +972,41 @@ class MutableInt(ctypes.c_int):
assert _array[0].value == -2
assert MutableInt(-3).value == -3

# Native calls must use the same initialized converter for arguments and results.
MutableInt._type_ = "h"
_identity = ctypes.CFUNCTYPE(ctypes.c_int, ctypes.c_int)(lambda value: value)
_identity.argtypes = [MutableInt]
assert _identity(MutableInt(-65537)) == -65537
_identity.argtypes = [ctypes.c_int]
_identity.restype = MutableInt
_result = _identity(-65537)
assert getattr(_result, "value", _result) == -65537


class MutablePointer(ctypes.c_void_p):
pass


MutablePointer._type_ = "i"
assert ctypes.cast(ctypes.c_void_p(123), MutablePointer).value == 123
MutableInt._type_ = "P"
try:
ctypes.cast(ctypes.c_void_p(123), MutableInt)
except TypeError:
pass
else:
raise AssertionError("cast accepted a scalar integer type")


class SubclassBits(ctypes.Structure):
_fields_ = [("field", MutableInt, 3)]


_bits = SubclassBits(3)
assert isinstance(_bits.field, MutableInt)
_bits.field.value = 2
assert _bits.field.value == 2

for _scalar, _text_value in ((ctypes.c_char, b"ab"), (ctypes.c_wchar, "ab")):

class MutableChar(_scalar):
Expand Down
Loading