From c6a0a2344db84e9b4c23444b50c8b1171109c98e Mon Sep 17 00:00:00 2001 From: Bas Schoenmaeckers Date: Tue, 19 May 2026 21:13:34 +0200 Subject: [PATCH 1/3] Add basic dict function to c-api --- crates/capi/src/dictobject.rs | 133 ++++++++++++++++++++++++++++++++++ crates/capi/src/lib.rs | 1 + 2 files changed, 134 insertions(+) create mode 100644 crates/capi/src/dictobject.rs diff --git a/crates/capi/src/dictobject.rs b/crates/capi/src/dictobject.rs new file mode 100644 index 00000000000..310b93964dd --- /dev/null +++ b/crates/capi/src/dictobject.rs @@ -0,0 +1,133 @@ +use crate::PyObject; +use crate::object::define_py_check; +use crate::pystate::with_vm; +use core::ffi::c_int; +use core::ptr::NonNull; +use rustpython_vm::AsObject; +use rustpython_vm::builtins::PyDict; + +define_py_check!(fn PyDict_Check, types.dict_type); +define_py_check!(exact fn PyDict_CheckExact, types.dict_type); +define_py_check!(fn PyDictKeys_Check, types.dict_keys_type); +define_py_check!(fn PyDictValues_Check, types.dict_values_type); +define_py_check!(fn PyDictItems_Check, types.dict_items_type); + +#[unsafe(no_mangle)] +pub extern "C" fn PyDict_New() -> *mut PyObject { + with_vm(|vm| vm.ctx.new_dict()) +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn PyDict_SetItem( + dict: *mut PyObject, + key: *mut PyObject, + val: *mut PyObject, +) -> c_int { + with_vm(|vm| { + let dict = unsafe { &*dict }.try_downcast_ref::(vm)?; + let key = unsafe { &*key }; + let value = unsafe { &*val }.to_owned(); + dict.set_item(key, value, vm) + }) +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn PyDict_GetItemRef( + dict: *mut PyObject, + key: *mut PyObject, + result: *mut *mut PyObject, +) -> c_int { + with_vm(|vm| { + unsafe { *result = core::ptr::null_mut() }; + let dict = unsafe { &*dict }.try_downcast_ref::(vm)?; + let key = unsafe { &*key }; + + if let Some(value) = dict.get_item_opt(key, vm)? { + unsafe { + *result = value.into_raw().as_ptr(); + } + Ok(true) + } else { + unsafe { + *result = core::ptr::null_mut(); + } + Ok(false) + } + }) +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn PyDict_Size(dict: *mut PyObject) -> isize { + with_vm(|vm| { + let dict = unsafe { &*dict }.try_downcast_ref::(vm)?; + Ok(dict.__len__()) + }) +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn PyDict_Next( + dict: *mut PyObject, + pos: *mut isize, + key: *mut *mut PyObject, + value: *mut *mut PyObject, +) -> c_int { + with_vm(|vm| { + let dict = unsafe { &*dict }.try_downcast_ref::(vm)?; + let index = unsafe { *pos } as usize; + let items = dict.items_vec(); + + if let Some((k, v)) = items.get(index) { + unsafe { + *pos += 1; + if let Some(key) = NonNull::new(key) { + key.write(k.as_object().as_raw().cast_mut()); + } + if let Some(value) = NonNull::new(value) { + value.write(v.as_object().as_raw().cast_mut()); + } + } + Ok(true) + } else { + Ok(false) + } + }) +} + +#[cfg(false)] +mod tests { + use pyo3::prelude::*; + use pyo3::types::{IntoPyDict, PyDict, PyInt}; + + #[test] + fn test_create_empty_dict() { + Python::attach(|py| { + let dict = PyDict::new(py); + assert!(dict.is_instance_of::()); + }) + } + + #[test] + fn test_create_dict_with_items() { + Python::attach(|py| { + let dict = [(1, 2), (3, 4)].into_py_dict(py)?; + let value = dict.get_item(1)?.unwrap().cast_into::()?; + assert_eq!(value, 2); + assert_eq!(dict.len(), 2); + + Ok::<_, PyErr>(()) + }) + .unwrap() + } + + #[test] + fn test_dict_iter() { + Python::attach(|py| { + let dict = [(1, 2), (3, 4)].into_py_dict(py).unwrap(); + let values = dict + .into_iter() + .flat_map(|(k, v)| [k.extract().unwrap(), v.extract().unwrap()]) + .collect::>(); + assert_eq!(values, vec![1, 2, 3, 4]); + }) + } +} diff --git a/crates/capi/src/lib.rs b/crates/capi/src/lib.rs index ac7bdf5ef11..a65f6396f2a 100644 --- a/crates/capi/src/lib.rs +++ b/crates/capi/src/lib.rs @@ -11,6 +11,7 @@ extern crate alloc; pub mod abstract_; pub mod bytesobject; pub mod ceval; +pub mod dictobject; pub mod import; pub mod longobject; pub mod object; From d4075499d1aff25a05481be68b60321ba1397b65 Mon Sep 17 00:00:00 2001 From: Bas Schoenmaeckers Date: Wed, 20 May 2026 09:45:05 +0200 Subject: [PATCH 2/3] Fix iter --- crates/capi/src/dictobject.rs | 5 ++--- crates/vm/src/builtins/dict.rs | 4 ++++ 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/crates/capi/src/dictobject.rs b/crates/capi/src/dictobject.rs index 310b93964dd..369995e53dd 100644 --- a/crates/capi/src/dictobject.rs +++ b/crates/capi/src/dictobject.rs @@ -74,11 +74,10 @@ pub unsafe extern "C" fn PyDict_Next( with_vm(|vm| { let dict = unsafe { &*dict }.try_downcast_ref::(vm)?; let index = unsafe { *pos } as usize; - let items = dict.items_vec(); - if let Some((k, v)) = items.get(index) { + if let Some((next_pos, k, v)) = dict.next_entry(index) { unsafe { - *pos += 1; + *pos = next_pos as isize; if let Some(key) = NonNull::new(key) { key.write(k.as_object().as_raw().cast_mut()); } diff --git a/crates/vm/src/builtins/dict.rs b/crates/vm/src/builtins/dict.rs index d1b215597a5..23f1a1beab6 100644 --- a/crates/vm/src/builtins/dict.rs +++ b/crates/vm/src/builtins/dict.rs @@ -248,6 +248,10 @@ impl PyDict { pub fn size(&self) -> dict_inner::DictSize { self.entries.size() } + + pub fn next_entry(&self, position: usize) -> Option<(usize, PyObjectRef, PyObjectRef)> { + self.entries.next_entry(position) + } } // Python dict methods: From a7c5bdc59eb81e22c5ad9524419daab947b86c1b Mon Sep 17 00:00:00 2001 From: Bas Schoenmaeckers Date: Wed, 20 May 2026 18:17:39 +0200 Subject: [PATCH 3/3] Do not use mapping protocol --- crates/capi/src/dictobject.rs | 4 ++-- crates/vm/src/builtins/dict.rs | 10 +++++++++- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/crates/capi/src/dictobject.rs b/crates/capi/src/dictobject.rs index 369995e53dd..dc5dd58485e 100644 --- a/crates/capi/src/dictobject.rs +++ b/crates/capi/src/dictobject.rs @@ -27,7 +27,7 @@ pub unsafe extern "C" fn PyDict_SetItem( let dict = unsafe { &*dict }.try_downcast_ref::(vm)?; let key = unsafe { &*key }; let value = unsafe { &*val }.to_owned(); - dict.set_item(key, value, vm) + dict.inner_setitem(key, value, vm) }) } @@ -42,7 +42,7 @@ pub unsafe extern "C" fn PyDict_GetItemRef( let dict = unsafe { &*dict }.try_downcast_ref::(vm)?; let key = unsafe { &*key }; - if let Some(value) = dict.get_item_opt(key, vm)? { + if let Some(value) = dict.inner_getitem_opt(key, vm)? { unsafe { *result = value.into_raw().as_ptr(); } diff --git a/crates/vm/src/builtins/dict.rs b/crates/vm/src/builtins/dict.rs index 23f1a1beab6..4b2b7c7541e 100644 --- a/crates/vm/src/builtins/dict.rs +++ b/crates/vm/src/builtins/dict.rs @@ -205,7 +205,7 @@ impl PyDict { /// Set item variant which can be called with multiple /// key types, such as str to name a notable one. - pub(crate) fn inner_setitem( + pub fn inner_setitem( &self, key: &K, value: PyObjectRef, @@ -252,6 +252,14 @@ impl PyDict { pub fn next_entry(&self, position: usize) -> Option<(usize, PyObjectRef, PyObjectRef)> { self.entries.next_entry(position) } + + pub fn inner_getitem_opt( + &self, + key: &K, + vm: &VirtualMachine, + ) -> PyResult> { + self.entries.get(vm, key) + } } // Python dict methods: