Add bindings for PyDict_SetDefaultRef and use them in PyCode::run#5955
Add bindings for PyDict_SetDefaultRef and use them in PyCode::run#5955
Conversation
69e4dab to
13a0dc4
Compare
|
It looks like we don't have extensive coverage tests for our compat shims. Maybe we should? I'd prefer not to add infrastructure for that in this PR though. |
davidhewitt
left a comment
There was a problem hiding this comment.
Thanks, one thought on the usage of this.
Indeed we don't have full coverage of the compat shims, generally coverage of code in pyo3-ffi is quite low because it's hard to test the FFI code without the convenience layers in pyo3.
We have some tests in src/ffi/tests inside the pyo3 lib, we should probably expand those (maybe warrants an issue?).
For this particular case, maybe we should add setdefault to PyDictMethods? (We could then use that in PyCode::run for bonus safety.)
Thanks for the suggestion! I added |
davidhewitt
left a comment
There was a problem hiding this comment.
Thanks, various suggestions about the new functions
| } | ||
| } | ||
|
|
||
| fn setdefault_result_from_nonerror_return_code(code: std::ffi::c_int) -> bool { |
There was a problem hiding this comment.
Nit, possibly move this down in the file below the method implementations, not C where we need a forward reference 😉
| /// already present. If an error happens, returns PyErr. This function uses | ||
| /// [`PyDict_SetDefaultRef`](https://docs.python.org/3/c-api/dict.html#c.PyDict_SetDefaultRef) internally, so it has | ||
| /// the same thread safety guarantees as that function. |
There was a problem hiding this comment.
Is the thread safety note particularly useful for users? It seems like, following the docs, it's basically. "This function is thread safe, which is generally the case for all the APIs we offer."
| } | ||
| let py = self.py(); | ||
|
|
||
| Ok(setdefault_result_from_nonerror_return_code(inner( |
There was a problem hiding this comment.
I suggest moving the call to setdefault_result_from_nonerror_return_code inside inner - it doesn't depend on the generic parameters.
| static UNINITIALIZED: u8 = 0; | ||
| let mut ob_result = std::ptr::addr_of!(UNINITIALIZED) as *mut ffi::PyObject; |
There was a problem hiding this comment.
One option is to do this with MaybeUninit, though that would forbid being able to assert the call initialized the result.
| static UNINITIALIZED: u8 = 0; | |
| let mut ob_result = std::ptr::addr_of!(UNINITIALIZED) as *mut ffi::PyObject; | |
| let mut ob_result = MaybeUninit::new(); |
| .as_borrowed(), | ||
| &mut ob_result, | ||
| )?; | ||
| assert!(ob_result != std::ptr::addr_of!(UNINITIALIZED) as *mut ffi::PyObject); |
There was a problem hiding this comment.
Is the assert necessary? Seems like this effectively defends a bad implementation of the C API, which is an upstream bug and not our fault if incorrect?
| )?; | ||
| assert!(ob_result != std::ptr::addr_of!(UNINITIALIZED) as *mut ffi::PyObject); | ||
| // SAFETY: the interpreter should have set this to a valid owned PyObject pointer | ||
| let out_result = unsafe { ob_result.assume_owned(py) }; |
There was a problem hiding this comment.
Could use assume_owned_unchecked(py) here to avoid a branch, given the safety guarantees.
| let py = self.py(); | ||
| static UNINITIALIZED: u8 = 0; | ||
| let mut ob_result = std::ptr::addr_of!(UNINITIALIZED) as *mut ffi::PyObject; | ||
| let code = inner( |
There was a problem hiding this comment.
Similar comment that a lot of the return value handling should be moved into inner as it doesn't depend on the generic parameters.
| let res = dict.set_default_with_result("hello", "foobar"); | ||
| assert!(res.is_ok()); | ||
| let res = res.unwrap(); |
There was a problem hiding this comment.
Given that the .unwrap() will panic, I'd probably just write this as a one-line
| let res = dict.set_default_with_result("hello", "foobar"); | |
| assert!(res.is_ok()); | |
| let res = res.unwrap(); | |
| let (inserted, value) = dict.set_default_with_result("hello", "foobar").unwrap(); |
| assert!(res.is_ok()); | ||
| let res = res.unwrap(); | ||
| assert!(!res.0); | ||
| assert!(res.1.extract::<&str>().unwrap() == "world"); |
There was a problem hiding this comment.
Potentially just extract to String to eat the allocation but avoid the need for a feature gate on the test? Not really a perf concern here.
Also looks like this could be done with assert_eq!.
| assert!(res.1.extract::<&str>().unwrap() == "world"); | |
| assert_eq!(res.1.extract::<String>().unwrap(), "world"); |
| } | ||
|
|
||
| #[test] | ||
| // Python 3.10 limited API is needed for .extract::<&str> |
There was a problem hiding this comment.
Same comment about maybe using String across all the extract points to remove the feature gates.
While experimenting with a build of PyO3 that supports PEP 803 without using critical sections, I noticed that the use in
PyCode::runcan be replaced byPyDict_SetDefaultRef. This also makes the implementation a lot simpler.I added bindings for that function on 3.13 and newer and 3.15 and newer in the limited API. I also added a compat shim based on the implementation in pythoncapi-compat: https://github.com/python/pythoncapi-compat/blob/75a796764d63327268d195e2d5c044e564d0dada/pythoncapi_compat.h#L1314.