add `c_str!` macro to create `&'static CStr` (#4255)

* add `c_str!` macro to create `&'static CStr`

* newsfragment, just export as `pyo3::ffi::c_str`

* fix doc link

* fix doc

* further `c_str!` based cleanups

* [review]: mejrs

Co-authored-by: Bruno Kolenbrander <59372212+mejrs@users.noreply.github.com>

* rustfmt

* build fixes

* clippy

* allow lint on MSRV

* fix GraalPy import

---------

Co-authored-by: Bruno Kolenbrander <59372212+mejrs@users.noreply.github.com>
This commit is contained in:
David Hewitt 2024-06-18 19:09:36 +01:00 committed by GitHub
parent ddff8bea25
commit 0e142f05dd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
36 changed files with 357 additions and 416 deletions

View File

@ -1,5 +1,6 @@
use core::sync::atomic::{AtomicU64, Ordering}; use core::sync::atomic::{AtomicU64, Ordering};
use core::{mem, ptr}; use core::{mem, ptr};
use std::ffi::CString;
use std::os::raw::{c_char, c_int, c_uint, c_ulonglong, c_void}; use std::os::raw::{c_char, c_int, c_uint, c_ulonglong, c_void};
use pyo3_ffi::*; use pyo3_ffi::*;
@ -27,10 +28,10 @@ unsafe extern "C" fn id_new(
kwds: *mut PyObject, kwds: *mut PyObject,
) -> *mut PyObject { ) -> *mut PyObject {
if PyTuple_Size(args) != 0 || !kwds.is_null() { if PyTuple_Size(args) != 0 || !kwds.is_null() {
PyErr_SetString( // We use pyo3-ffi's `c_str!` macro to create null-terminated literals because
PyExc_TypeError, // Rust's string literals are not null-terminated
"Id() takes no arguments\0".as_ptr().cast::<c_char>(), // On Rust 1.77 or newer you can use `c"text"` instead.
); PyErr_SetString(PyExc_TypeError, c_str!("Id() takes no arguments").as_ptr());
return ptr::null_mut(); return ptr::null_mut();
} }
@ -81,8 +82,12 @@ unsafe extern "C" fn id_richcompare(
pyo3_ffi::Py_GT => slf > other, pyo3_ffi::Py_GT => slf > other,
pyo3_ffi::Py_GE => slf >= other, pyo3_ffi::Py_GE => slf >= other,
unrecognized => { unrecognized => {
let msg = format!("unrecognized richcompare opcode {}\0", unrecognized); let msg = CString::new(&*format!(
PyErr_SetString(PyExc_SystemError, msg.as_ptr().cast::<c_char>()); "unrecognized richcompare opcode {}",
unrecognized
))
.unwrap();
PyErr_SetString(PyExc_SystemError, msg.as_ptr());
return ptr::null_mut(); return ptr::null_mut();
} }
}; };
@ -101,7 +106,7 @@ static mut SLOTS: &[PyType_Slot] = &[
}, },
PyType_Slot { PyType_Slot {
slot: Py_tp_doc, slot: Py_tp_doc,
pfunc: "An id that is increased every time an instance is created\0".as_ptr() pfunc: c_str!("An id that is increased every time an instance is created").as_ptr()
as *mut c_void, as *mut c_void,
}, },
PyType_Slot { PyType_Slot {
@ -123,7 +128,7 @@ static mut SLOTS: &[PyType_Slot] = &[
]; ];
pub static mut ID_SPEC: PyType_Spec = PyType_Spec { pub static mut ID_SPEC: PyType_Spec = PyType_Spec {
name: "sequential.Id\0".as_ptr().cast::<c_char>(), name: c_str!("sequential.Id").as_ptr(),
basicsize: mem::size_of::<PyId>() as c_int, basicsize: mem::size_of::<PyId>() as c_int,
itemsize: 0, itemsize: 0,
flags: (Py_TPFLAGS_DEFAULT | Py_TPFLAGS_IMMUTABLETYPE) as c_uint, flags: (Py_TPFLAGS_DEFAULT | Py_TPFLAGS_IMMUTABLETYPE) as c_uint,

View File

@ -1,13 +1,11 @@
use core::{mem, ptr}; use core::{mem, ptr};
use pyo3_ffi::*; use pyo3_ffi::*;
use std::os::raw::{c_char, c_int, c_void}; use std::os::raw::{c_int, c_void};
pub static mut MODULE_DEF: PyModuleDef = PyModuleDef { pub static mut MODULE_DEF: PyModuleDef = PyModuleDef {
m_base: PyModuleDef_HEAD_INIT, m_base: PyModuleDef_HEAD_INIT,
m_name: "sequential\0".as_ptr().cast::<c_char>(), m_name: c_str!("sequential").as_ptr(),
m_doc: "A library for generating sequential ids, written in Rust.\0" m_doc: c_str!("A library for generating sequential ids, written in Rust.").as_ptr(),
.as_ptr()
.cast::<c_char>(),
m_size: mem::size_of::<sequential_state>() as Py_ssize_t, m_size: mem::size_of::<sequential_state>() as Py_ssize_t,
m_methods: std::ptr::null_mut(), m_methods: std::ptr::null_mut(),
m_slots: unsafe { SEQUENTIAL_SLOTS as *const [PyModuleDef_Slot] as *mut PyModuleDef_Slot }, m_slots: unsafe { SEQUENTIAL_SLOTS as *const [PyModuleDef_Slot] as *mut PyModuleDef_Slot },
@ -42,13 +40,13 @@ unsafe extern "C" fn sequential_exec(module: *mut PyObject) -> c_int {
if id_type.is_null() { if id_type.is_null() {
PyErr_SetString( PyErr_SetString(
PyExc_SystemError, PyExc_SystemError,
"cannot locate type object\0".as_ptr().cast::<c_char>(), c_str!("cannot locate type object").as_ptr(),
); );
return -1; return -1;
} }
(*state).id_type = id_type.cast::<PyTypeObject>(); (*state).id_type = id_type.cast::<PyTypeObject>();
PyModule_AddObjectRef(module, "Id\0".as_ptr().cast::<c_char>(), id_type) PyModule_AddObjectRef(module, c_str!("Id").as_ptr(), id_type)
} }
unsafe extern "C" fn sequential_traverse( unsafe extern "C" fn sequential_traverse(

View File

@ -5,11 +5,13 @@ use std::thread;
use pyo3_ffi::*; use pyo3_ffi::*;
use sequential::PyInit_sequential; use sequential::PyInit_sequential;
static COMMAND: &'static str = " static COMMAND: &'static str = c_str!(
"
from sequential import Id from sequential import Id
s = sum(int(Id()) for _ in range(12)) s = sum(int(Id()) for _ in range(12))
\0"; "
);
// Newtype to be able to pass it to another thread. // Newtype to be able to pass it to another thread.
struct State(*mut PyThreadState); struct State(*mut PyThreadState);
@ -19,10 +21,7 @@ unsafe impl Send for State {}
#[test] #[test]
fn lets_go_fast() -> Result<(), String> { fn lets_go_fast() -> Result<(), String> {
unsafe { unsafe {
let ret = PyImport_AppendInittab( let ret = PyImport_AppendInittab(c_str!("sequential").as_ptr(), Some(PyInit_sequential));
"sequential\0".as_ptr().cast::<c_char>(),
Some(PyInit_sequential),
);
if ret == -1 { if ret == -1 {
return Err("could not add module to inittab".into()); return Err("could not add module to inittab".into());
} }
@ -122,11 +121,8 @@ unsafe fn fetch() -> String {
fn run_code() -> Result<u64, String> { fn run_code() -> Result<u64, String> {
unsafe { unsafe {
let code_obj = Py_CompileString( let code_obj =
COMMAND.as_ptr().cast::<c_char>(), Py_CompileString(COMMAND.as_ptr(), c_str!("program").as_ptr(), Py_file_input);
"program\0".as_ptr().cast::<c_char>(),
Py_file_input,
);
if code_obj.is_null() { if code_obj.is_null() {
return Err(fetch()); return Err(fetch());
} }
@ -138,7 +134,7 @@ fn run_code() -> Result<u64, String> {
} else { } else {
Py_DECREF(res_ptr); Py_DECREF(res_ptr);
} }
let sum = PyDict_GetItemString(globals, "s\0".as_ptr().cast::<c_char>()); /* borrowed reference */ let sum = PyDict_GetItemString(globals, c_str!("s").as_ptr()); /* borrowed reference */
if sum.is_null() { if sum.is_null() {
Py_DECREF(globals); Py_DECREF(globals);
return Err("globals did not have `s`".into()); return Err("globals did not have `s`".into());

View File

@ -5,10 +5,8 @@ use pyo3_ffi::*;
static mut MODULE_DEF: PyModuleDef = PyModuleDef { static mut MODULE_DEF: PyModuleDef = PyModuleDef {
m_base: PyModuleDef_HEAD_INIT, m_base: PyModuleDef_HEAD_INIT,
m_name: "string_sum\0".as_ptr().cast::<c_char>(), m_name: c_str!("string_sum").as_ptr(),
m_doc: "A Python module written in Rust.\0" m_doc: c_str!("A Python module written in Rust.").as_ptr(),
.as_ptr()
.cast::<c_char>(),
m_size: 0, m_size: 0,
m_methods: unsafe { METHODS as *const [PyMethodDef] as *mut PyMethodDef }, m_methods: unsafe { METHODS as *const [PyMethodDef] as *mut PyMethodDef },
m_slots: std::ptr::null_mut(), m_slots: std::ptr::null_mut(),
@ -19,14 +17,12 @@ static mut MODULE_DEF: PyModuleDef = PyModuleDef {
static mut METHODS: &[PyMethodDef] = &[ static mut METHODS: &[PyMethodDef] = &[
PyMethodDef { PyMethodDef {
ml_name: "sum_as_string\0".as_ptr().cast::<c_char>(), ml_name: c_str!("sum_as_string").as_ptr(),
ml_meth: PyMethodDefPointer { ml_meth: PyMethodDefPointer {
_PyCFunctionFast: sum_as_string, _PyCFunctionFast: sum_as_string,
}, },
ml_flags: METH_FASTCALL, ml_flags: METH_FASTCALL,
ml_doc: "returns the sum of two integers as a string\0" ml_doc: c_str!("returns the sum of two integers as a string").as_ptr(),
.as_ptr()
.cast::<c_char>(),
}, },
// A zeroed PyMethodDef to mark the end of the array. // A zeroed PyMethodDef to mark the end of the array.
PyMethodDef::zeroed(), PyMethodDef::zeroed(),
@ -93,9 +89,7 @@ pub unsafe extern "C" fn sum_as_string(
if nargs != 2 { if nargs != 2 {
PyErr_SetString( PyErr_SetString(
PyExc_TypeError, PyExc_TypeError,
"sum_as_string expected 2 positional arguments\0" c_str!("sum_as_string expected 2 positional arguments").as_ptr(),
.as_ptr()
.cast::<c_char>(),
); );
return std::ptr::null_mut(); return std::ptr::null_mut();
} }
@ -119,7 +113,7 @@ pub unsafe extern "C" fn sum_as_string(
None => { None => {
PyErr_SetString( PyErr_SetString(
PyExc_OverflowError, PyExc_OverflowError,
"arguments too large to add\0".as_ptr().cast::<c_char>(), c_str!("arguments too large to add").as_ptr(),
); );
std::ptr::null_mut() std::ptr::null_mut()
} }

View File

@ -1443,7 +1443,7 @@ impl pyo3::impl_::pyclass::PyClassImpl for MyClass {
static DOC: pyo3::sync::GILOnceCell<::std::borrow::Cow<'static, ::std::ffi::CStr>> = pyo3::sync::GILOnceCell::new(); static DOC: pyo3::sync::GILOnceCell<::std::borrow::Cow<'static, ::std::ffi::CStr>> = pyo3::sync::GILOnceCell::new();
DOC.get_or_try_init(py, || { DOC.get_or_try_init(py, || {
let collector = PyClassImplCollector::<Self>::new(); let collector = PyClassImplCollector::<Self>::new();
build_pyclass_doc(<MyClass as pyo3::PyTypeInfo>::NAME, "\0", collector.new_text_signature()) build_pyclass_doc(<MyClass as pyo3::PyTypeInfo>::NAME, pyo3::ffi::c_str!(""), collector.new_text_signature())
}).map(::std::ops::Deref::deref) }).map(::std::ops::Deref::deref)
} }
} }

View File

@ -0,0 +1 @@
Add `pyo3_ffi::c_str` macro to create `&'static CStr` on Rust versions which don't have 1.77's `c""` literals.

View File

@ -0,0 +1 @@
`PyCFunction::new`, `PyCFunction::new_with_keywords` and `PyCFunction::new_closure` now take `&'static CStr` name and doc arguments (previously was `&'static str`).

View File

@ -51,10 +51,8 @@ use pyo3_ffi::*;
static mut MODULE_DEF: PyModuleDef = PyModuleDef { static mut MODULE_DEF: PyModuleDef = PyModuleDef {
m_base: PyModuleDef_HEAD_INIT, m_base: PyModuleDef_HEAD_INIT,
m_name: "string_sum\0".as_ptr().cast::<c_char>(), m_name: c_str!("string_sum").as_ptr(),
m_doc: "A Python module written in Rust.\0" m_doc: c_str!("A Python module written in Rust.").as_ptr(),
.as_ptr()
.cast::<c_char>(),
m_size: 0, m_size: 0,
m_methods: unsafe { METHODS.as_mut_ptr().cast() }, m_methods: unsafe { METHODS.as_mut_ptr().cast() },
m_slots: std::ptr::null_mut(), m_slots: std::ptr::null_mut(),
@ -65,14 +63,12 @@ static mut MODULE_DEF: PyModuleDef = PyModuleDef {
static mut METHODS: [PyMethodDef; 2] = [ static mut METHODS: [PyMethodDef; 2] = [
PyMethodDef { PyMethodDef {
ml_name: "sum_as_string\0".as_ptr().cast::<c_char>(), ml_name: c_str!("sum_as_string").as_ptr(),
ml_meth: PyMethodDefPointer { ml_meth: PyMethodDefPointer {
_PyCFunctionFast: sum_as_string, _PyCFunctionFast: sum_as_string,
}, },
ml_flags: METH_FASTCALL, ml_flags: METH_FASTCALL,
ml_doc: "returns the sum of two integers as a string\0" ml_doc: c_str!("returns the sum of two integers as a string").as_ptr(),
.as_ptr()
.cast::<c_char>(),
}, },
// A zeroed PyMethodDef to mark the end of the array. // A zeroed PyMethodDef to mark the end of the array.
PyMethodDef::zeroed() PyMethodDef::zeroed()
@ -93,9 +89,7 @@ pub unsafe extern "C" fn sum_as_string(
if nargs != 2 { if nargs != 2 {
PyErr_SetString( PyErr_SetString(
PyExc_TypeError, PyExc_TypeError,
"sum_as_string() expected 2 positional arguments\0" c_str!("sum_as_string() expected 2 positional arguments").as_ptr(),
.as_ptr()
.cast::<c_char>(),
); );
return std::ptr::null_mut(); return std::ptr::null_mut();
} }
@ -104,9 +98,7 @@ pub unsafe extern "C" fn sum_as_string(
if PyLong_Check(arg1) == 0 { if PyLong_Check(arg1) == 0 {
PyErr_SetString( PyErr_SetString(
PyExc_TypeError, PyExc_TypeError,
"sum_as_string() expected an int for positional argument 1\0" c_str!("sum_as_string() expected an int for positional argument 1").as_ptr(),
.as_ptr()
.cast::<c_char>(),
); );
return std::ptr::null_mut(); return std::ptr::null_mut();
} }
@ -120,9 +112,7 @@ pub unsafe extern "C" fn sum_as_string(
if PyLong_Check(arg2) == 0 { if PyLong_Check(arg2) == 0 {
PyErr_SetString( PyErr_SetString(
PyExc_TypeError, PyExc_TypeError,
"sum_as_string() expected an int for positional argument 2\0" c_str!("sum_as_string() expected an int for positional argument 2").as_ptr(),
.as_ptr()
.cast::<c_char>(),
); );
return std::ptr::null_mut(); return std::ptr::null_mut();
} }
@ -140,7 +130,7 @@ pub unsafe extern "C" fn sum_as_string(
None => { None => {
PyErr_SetString( PyErr_SetString(
PyExc_OverflowError, PyExc_OverflowError,
"arguments too large to add\0".as_ptr().cast::<c_char>(), c_str!("arguments too large to add").as_ptr(),
); );
std::ptr::null_mut() std::ptr::null_mut()
} }

View File

@ -114,7 +114,7 @@ extern "C" {
#[cfg(not(any(Py_3_8, PyPy)))] #[cfg(not(any(Py_3_8, PyPy)))]
#[inline] #[inline]
pub unsafe fn PyIter_Check(o: *mut PyObject) -> c_int { pub unsafe fn PyIter_Check(o: *mut PyObject) -> c_int {
crate::PyObject_HasAttrString(crate::Py_TYPE(o).cast(), "__next__\0".as_ptr().cast()) crate::PyObject_HasAttrString(crate::Py_TYPE(o).cast(), c_str!("__next__").as_ptr())
} }
extern "C" { extern "C" {

View File

@ -357,8 +357,8 @@ pub unsafe fn PyDateTime_DELTA_GET_MICROSECONDS(o: *mut PyObject) -> c_int {
// but copying them seems suboptimal // but copying them seems suboptimal
#[inline] #[inline]
#[cfg(GraalPy)] #[cfg(GraalPy)]
pub unsafe fn _get_attr(obj: *mut PyObject, field: &str) -> c_int { pub unsafe fn _get_attr(obj: *mut PyObject, field: &std::ffi::CStr) -> c_int {
let result = PyObject_GetAttrString(obj, field.as_ptr().cast()); let result = PyObject_GetAttrString(obj, field.as_ptr());
Py_DecRef(result); // the original macros are borrowing Py_DecRef(result); // the original macros are borrowing
if PyLong_Check(result) == 1 { if PyLong_Check(result) == 1 {
PyLong_AsLong(result) as c_int PyLong_AsLong(result) as c_int
@ -370,55 +370,55 @@ pub unsafe fn _get_attr(obj: *mut PyObject, field: &str) -> c_int {
#[inline] #[inline]
#[cfg(GraalPy)] #[cfg(GraalPy)]
pub unsafe fn PyDateTime_GET_YEAR(o: *mut PyObject) -> c_int { pub unsafe fn PyDateTime_GET_YEAR(o: *mut PyObject) -> c_int {
_get_attr(o, "year\0") _get_attr(o, c_str!("year"))
} }
#[inline] #[inline]
#[cfg(GraalPy)] #[cfg(GraalPy)]
pub unsafe fn PyDateTime_GET_MONTH(o: *mut PyObject) -> c_int { pub unsafe fn PyDateTime_GET_MONTH(o: *mut PyObject) -> c_int {
_get_attr(o, "month\0") _get_attr(o, c_str!("month"))
} }
#[inline] #[inline]
#[cfg(GraalPy)] #[cfg(GraalPy)]
pub unsafe fn PyDateTime_GET_DAY(o: *mut PyObject) -> c_int { pub unsafe fn PyDateTime_GET_DAY(o: *mut PyObject) -> c_int {
_get_attr(o, "day\0") _get_attr(o, c_str!("day"))
} }
#[inline] #[inline]
#[cfg(GraalPy)] #[cfg(GraalPy)]
pub unsafe fn PyDateTime_DATE_GET_HOUR(o: *mut PyObject) -> c_int { pub unsafe fn PyDateTime_DATE_GET_HOUR(o: *mut PyObject) -> c_int {
_get_attr(o, "hour\0") _get_attr(o, c_str!("hour"))
} }
#[inline] #[inline]
#[cfg(GraalPy)] #[cfg(GraalPy)]
pub unsafe fn PyDateTime_DATE_GET_MINUTE(o: *mut PyObject) -> c_int { pub unsafe fn PyDateTime_DATE_GET_MINUTE(o: *mut PyObject) -> c_int {
_get_attr(o, "minute\0") _get_attr(o, c_str!("minute"))
} }
#[inline] #[inline]
#[cfg(GraalPy)] #[cfg(GraalPy)]
pub unsafe fn PyDateTime_DATE_GET_SECOND(o: *mut PyObject) -> c_int { pub unsafe fn PyDateTime_DATE_GET_SECOND(o: *mut PyObject) -> c_int {
_get_attr(o, "second\0") _get_attr(o, c_str!("second"))
} }
#[inline] #[inline]
#[cfg(GraalPy)] #[cfg(GraalPy)]
pub unsafe fn PyDateTime_DATE_GET_MICROSECOND(o: *mut PyObject) -> c_int { pub unsafe fn PyDateTime_DATE_GET_MICROSECOND(o: *mut PyObject) -> c_int {
_get_attr(o, "microsecond\0") _get_attr(o, c_str!("microsecond"))
} }
#[inline] #[inline]
#[cfg(GraalPy)] #[cfg(GraalPy)]
pub unsafe fn PyDateTime_DATE_GET_FOLD(o: *mut PyObject) -> c_int { pub unsafe fn PyDateTime_DATE_GET_FOLD(o: *mut PyObject) -> c_int {
_get_attr(o, "fold\0") _get_attr(o, c_str!("fold"))
} }
#[inline] #[inline]
#[cfg(GraalPy)] #[cfg(GraalPy)]
pub unsafe fn PyDateTime_DATE_GET_TZINFO(o: *mut PyObject) -> *mut PyObject { pub unsafe fn PyDateTime_DATE_GET_TZINFO(o: *mut PyObject) -> *mut PyObject {
let res = PyObject_GetAttrString(o, "tzinfo\0".as_ptr().cast()); let res = PyObject_GetAttrString(o, c_str!("tzinfo").as_ptr().cast());
Py_DecRef(res); // the original macros are borrowing Py_DecRef(res); // the original macros are borrowing
res res
} }
@ -426,37 +426,37 @@ pub unsafe fn PyDateTime_DATE_GET_TZINFO(o: *mut PyObject) -> *mut PyObject {
#[inline] #[inline]
#[cfg(GraalPy)] #[cfg(GraalPy)]
pub unsafe fn PyDateTime_TIME_GET_HOUR(o: *mut PyObject) -> c_int { pub unsafe fn PyDateTime_TIME_GET_HOUR(o: *mut PyObject) -> c_int {
_get_attr(o, "hour\0") _get_attr(o, c_str!("hour"))
} }
#[inline] #[inline]
#[cfg(GraalPy)] #[cfg(GraalPy)]
pub unsafe fn PyDateTime_TIME_GET_MINUTE(o: *mut PyObject) -> c_int { pub unsafe fn PyDateTime_TIME_GET_MINUTE(o: *mut PyObject) -> c_int {
_get_attr(o, "minute\0") _get_attr(o, c_str!("minute"))
} }
#[inline] #[inline]
#[cfg(GraalPy)] #[cfg(GraalPy)]
pub unsafe fn PyDateTime_TIME_GET_SECOND(o: *mut PyObject) -> c_int { pub unsafe fn PyDateTime_TIME_GET_SECOND(o: *mut PyObject) -> c_int {
_get_attr(o, "second\0") _get_attr(o, c_str!("second"))
} }
#[inline] #[inline]
#[cfg(GraalPy)] #[cfg(GraalPy)]
pub unsafe fn PyDateTime_TIME_GET_MICROSECOND(o: *mut PyObject) -> c_int { pub unsafe fn PyDateTime_TIME_GET_MICROSECOND(o: *mut PyObject) -> c_int {
_get_attr(o, "microsecond\0") _get_attr(o, c_str!("microsecond"))
} }
#[inline] #[inline]
#[cfg(GraalPy)] #[cfg(GraalPy)]
pub unsafe fn PyDateTime_TIME_GET_FOLD(o: *mut PyObject) -> c_int { pub unsafe fn PyDateTime_TIME_GET_FOLD(o: *mut PyObject) -> c_int {
_get_attr(o, "fold\0") _get_attr(o, c_str!("fold"))
} }
#[inline] #[inline]
#[cfg(GraalPy)] #[cfg(GraalPy)]
pub unsafe fn PyDateTime_TIME_GET_TZINFO(o: *mut PyObject) -> *mut PyObject { pub unsafe fn PyDateTime_TIME_GET_TZINFO(o: *mut PyObject) -> *mut PyObject {
let res = PyObject_GetAttrString(o, "tzinfo\0".as_ptr().cast()); let res = PyObject_GetAttrString(o, c_str!("tzinfo").as_ptr().cast());
Py_DecRef(res); // the original macros are borrowing Py_DecRef(res); // the original macros are borrowing
res res
} }
@ -464,19 +464,19 @@ pub unsafe fn PyDateTime_TIME_GET_TZINFO(o: *mut PyObject) -> *mut PyObject {
#[inline] #[inline]
#[cfg(GraalPy)] #[cfg(GraalPy)]
pub unsafe fn PyDateTime_DELTA_GET_DAYS(o: *mut PyObject) -> c_int { pub unsafe fn PyDateTime_DELTA_GET_DAYS(o: *mut PyObject) -> c_int {
_get_attr(o, "days\0") _get_attr(o, c_str!("days"))
} }
#[inline] #[inline]
#[cfg(GraalPy)] #[cfg(GraalPy)]
pub unsafe fn PyDateTime_DELTA_GET_SECONDS(o: *mut PyObject) -> c_int { pub unsafe fn PyDateTime_DELTA_GET_SECONDS(o: *mut PyObject) -> c_int {
_get_attr(o, "seconds\0") _get_attr(o, c_str!("seconds"))
} }
#[inline] #[inline]
#[cfg(GraalPy)] #[cfg(GraalPy)]
pub unsafe fn PyDateTime_DELTA_GET_MICROSECONDS(o: *mut PyObject) -> c_int { pub unsafe fn PyDateTime_DELTA_GET_MICROSECONDS(o: *mut PyObject) -> c_int {
_get_attr(o, "microseconds\0") _get_attr(o, c_str!("microseconds"))
} }
#[cfg(PyPy)] #[cfg(PyPy)]

View File

@ -88,10 +88,8 @@
//! //!
//! static mut MODULE_DEF: PyModuleDef = PyModuleDef { //! static mut MODULE_DEF: PyModuleDef = PyModuleDef {
//! m_base: PyModuleDef_HEAD_INIT, //! m_base: PyModuleDef_HEAD_INIT,
//! m_name: "string_sum\0".as_ptr().cast::<c_char>(), //! m_name: c_str!("string_sum").as_ptr(),
//! m_doc: "A Python module written in Rust.\0" //! m_doc: c_str!("A Python module written in Rust.").as_ptr(),
//! .as_ptr()
//! .cast::<c_char>(),
//! m_size: 0, //! m_size: 0,
//! m_methods: unsafe { METHODS.as_mut_ptr().cast() }, //! m_methods: unsafe { METHODS.as_mut_ptr().cast() },
//! m_slots: std::ptr::null_mut(), //! m_slots: std::ptr::null_mut(),
@ -102,14 +100,12 @@
//! //!
//! static mut METHODS: [PyMethodDef; 2] = [ //! static mut METHODS: [PyMethodDef; 2] = [
//! PyMethodDef { //! PyMethodDef {
//! ml_name: "sum_as_string\0".as_ptr().cast::<c_char>(), //! ml_name: c_str!("sum_as_string").as_ptr(),
//! ml_meth: PyMethodDefPointer { //! ml_meth: PyMethodDefPointer {
//! _PyCFunctionFast: sum_as_string, //! _PyCFunctionFast: sum_as_string,
//! }, //! },
//! ml_flags: METH_FASTCALL, //! ml_flags: METH_FASTCALL,
//! ml_doc: "returns the sum of two integers as a string\0" //! ml_doc: c_str!("returns the sum of two integers as a string").as_ptr(),
//! .as_ptr()
//! .cast::<c_char>(),
//! }, //! },
//! // A zeroed PyMethodDef to mark the end of the array. //! // A zeroed PyMethodDef to mark the end of the array.
//! PyMethodDef::zeroed() //! PyMethodDef::zeroed()
@ -130,9 +126,7 @@
//! if nargs != 2 { //! if nargs != 2 {
//! PyErr_SetString( //! PyErr_SetString(
//! PyExc_TypeError, //! PyExc_TypeError,
//! "sum_as_string() expected 2 positional arguments\0" //! c_str!("sum_as_string() expected 2 positional arguments").as_ptr(),
//! .as_ptr()
//! .cast::<c_char>(),
//! ); //! );
//! return std::ptr::null_mut(); //! return std::ptr::null_mut();
//! } //! }
@ -141,9 +135,7 @@
//! if PyLong_Check(arg1) == 0 { //! if PyLong_Check(arg1) == 0 {
//! PyErr_SetString( //! PyErr_SetString(
//! PyExc_TypeError, //! PyExc_TypeError,
//! "sum_as_string() expected an int for positional argument 1\0" //! c_str!("sum_as_string() expected an int for positional argument 1").as_ptr(),
//! .as_ptr()
//! .cast::<c_char>(),
//! ); //! );
//! return std::ptr::null_mut(); //! return std::ptr::null_mut();
//! } //! }
@ -157,9 +149,7 @@
//! if PyLong_Check(arg2) == 0 { //! if PyLong_Check(arg2) == 0 {
//! PyErr_SetString( //! PyErr_SetString(
//! PyExc_TypeError, //! PyExc_TypeError,
//! "sum_as_string() expected an int for positional argument 2\0" //! c_str!("sum_as_string() expected an int for positional argument 2").as_ptr(),
//! .as_ptr()
//! .cast::<c_char>(),
//! ); //! );
//! return std::ptr::null_mut(); //! return std::ptr::null_mut();
//! } //! }
@ -177,7 +167,7 @@
//! None => { //! None => {
//! PyErr_SetString( //! PyErr_SetString(
//! PyExc_OverflowError, //! PyExc_OverflowError,
//! "arguments too large to add\0".as_ptr().cast::<c_char>(), //! c_str!("arguments too large to add").as_ptr(),
//! ); //! );
//! std::ptr::null_mut() //! std::ptr::null_mut()
//! } //! }
@ -256,6 +246,53 @@ macro_rules! opaque_struct {
}; };
} }
/// This is a helper macro to create a `&'static CStr`.
///
/// It can be used on all Rust versions supported by PyO3, unlike c"" literals which
/// were stabilised in Rust 1.77.
///
/// Due to the nature of PyO3 making heavy use of C FFI interop with Python, it is
/// common for PyO3 to use CStr.
///
/// Examples:
///
/// ```rust
/// use std::ffi::CStr;
///
/// const HELLO: &CStr = pyo3_ffi::c_str!("hello");
/// static WORLD: &CStr = pyo3_ffi::c_str!("world");
/// ```
#[macro_export]
macro_rules! c_str {
($s:expr) => {{
const _: () = {
assert!(
$crate::str_contains_no_nul($s),
"string contains null bytes"
);
};
// SAFETY: the string is checked to not contain null bytes
#[allow(unsafe_op_in_unsafe_fn, unused_unsafe)] // MSRV 1.63 needs these allows
unsafe {
::std::ffi::CStr::from_bytes_with_nul_unchecked(concat!($s, "\0").as_bytes())
}
}};
}
#[doc(hidden)]
pub const fn str_contains_no_nul(s: &str) -> bool {
let bytes = s.as_bytes();
let len = s.len();
let mut i = 0;
while i < len {
if bytes[i] == 0 {
return false;
}
i += 1;
}
true
}
pub use self::abstract_::*; pub use self::abstract_::*;
pub use self::bltinmodule::*; pub use self::bltinmodule::*;
pub use self::boolobject::*; pub use self::boolobject::*;

View File

@ -101,7 +101,7 @@ pub unsafe fn PyUnicodeDecodeError_Create(
) -> *mut PyObject { ) -> *mut PyObject {
crate::_PyObject_CallFunction_SizeT( crate::_PyObject_CallFunction_SizeT(
PyExc_UnicodeDecodeError, PyExc_UnicodeDecodeError,
b"sy#nns\0".as_ptr().cast::<c_char>(), c_str!("sy#nns").as_ptr(),
encoding, encoding,
object, object,
length, length,

View File

@ -29,9 +29,10 @@ impl ConstSpec<'_> {
} }
/// Null-terminated Python name /// Null-terminated Python name
pub fn null_terminated_python_name(&self) -> TokenStream { pub fn null_terminated_python_name(&self, ctx: &Ctx) -> TokenStream {
let name = format!("{}\0", self.python_name()); let Ctx { pyo3_path } = ctx;
quote!({#name}) let name = self.python_name().to_string();
quote!(#pyo3_path::ffi::c_str!(#name))
} }
} }

View File

@ -472,8 +472,12 @@ impl<'a> FnSpec<'a> {
}) })
} }
pub fn null_terminated_python_name(&self) -> syn::LitStr { pub fn null_terminated_python_name(&self, ctx: &Ctx) -> TokenStream {
syn::LitStr::new(&format!("{}\0", self.python_name), self.python_name.span()) let Ctx { pyo3_path } = ctx;
let span = self.python_name.span();
let pyo3_path = pyo3_path.to_tokens_spanned(span);
let name = self.python_name.to_string();
quote_spanned!(self.python_name.span() => #pyo3_path::ffi::c_str!(#name))
} }
fn parse_fn_type( fn parse_fn_type(
@ -830,7 +834,7 @@ impl<'a> FnSpec<'a> {
/// calling convention. /// calling convention.
pub fn get_methoddef(&self, wrapper: impl ToTokens, doc: &PythonDoc, ctx: &Ctx) -> TokenStream { pub fn get_methoddef(&self, wrapper: impl ToTokens, doc: &PythonDoc, ctx: &Ctx) -> TokenStream {
let Ctx { pyo3_path } = ctx; let Ctx { pyo3_path } = ctx;
let python_name = self.null_terminated_python_name(); let python_name = self.null_terminated_python_name(ctx);
match self.convention { match self.convention {
CallingConvention::Noargs => quote! { CallingConvention::Noargs => quote! {
#pyo3_path::impl_::pymethods::PyMethodDef::noargs( #pyo3_path::impl_::pymethods::PyMethodDef::noargs(
@ -903,11 +907,11 @@ impl<'a> FnSpec<'a> {
} }
/// Forwards to [utils::get_doc] with the text signature of this spec. /// Forwards to [utils::get_doc] with the text signature of this spec.
pub fn get_doc(&self, attrs: &[syn::Attribute]) -> PythonDoc { pub fn get_doc(&self, attrs: &[syn::Attribute], ctx: &Ctx) -> PythonDoc {
let text_signature = self let text_signature = self
.text_signature_call_signature() .text_signature_call_signature()
.map(|sig| format!("{}{}", self.python_name, sig)); .map(|sig| format!("{}{}", self.python_name, sig));
utils::get_doc(attrs, text_signature) utils::get_doc(attrs, text_signature, ctx)
} }
/// Creates the parenthesised arguments list for `__text_signature__` snippet based on this spec's signature /// Creates the parenthesised arguments list for `__text_signature__` snippet based on this spec's signature

View File

@ -92,7 +92,7 @@ pub fn pymodule_module_impl(mut module: syn::ItemMod) -> Result<TokenStream> {
let options = PyModuleOptions::from_attrs(attrs)?; let options = PyModuleOptions::from_attrs(attrs)?;
let ctx = &Ctx::new(&options.krate); let ctx = &Ctx::new(&options.krate);
let Ctx { pyo3_path } = ctx; let Ctx { pyo3_path } = ctx;
let doc = get_doc(attrs, None); let doc = get_doc(attrs, None, ctx);
let name = options.name.unwrap_or_else(|| ident.unraw()); let name = options.name.unwrap_or_else(|| ident.unraw());
let full_name = if let Some(module) = &options.module { let full_name = if let Some(module) = &options.module {
format!("{}.{}", module.value.value(), name) format!("{}.{}", module.value.value(), name)
@ -332,7 +332,7 @@ pub fn pymodule_function_impl(mut function: syn::ItemFn) -> Result<TokenStream>
let ident = &function.sig.ident; let ident = &function.sig.ident;
let name = options.name.unwrap_or_else(|| ident.unraw()); let name = options.name.unwrap_or_else(|| ident.unraw());
let vis = &function.vis; let vis = &function.vis;
let doc = get_doc(&function.attrs, None); let doc = get_doc(&function.attrs, None, ctx);
let initialization = module_initialization(&name, ctx); let initialization = module_initialization(&name, ctx);
@ -402,10 +402,11 @@ pub fn pymodule_function_impl(mut function: syn::ItemFn) -> Result<TokenStream>
fn module_initialization(name: &syn::Ident, ctx: &Ctx) -> TokenStream { fn module_initialization(name: &syn::Ident, ctx: &Ctx) -> TokenStream {
let Ctx { pyo3_path } = ctx; let Ctx { pyo3_path } = ctx;
let pyinit_symbol = format!("PyInit_{}", name); let pyinit_symbol = format!("PyInit_{}", name);
let name = name.to_string();
quote! { quote! {
#[doc(hidden)] #[doc(hidden)]
pub const __PYO3_NAME: &'static str = concat!(stringify!(#name), "\0"); pub const __PYO3_NAME: &'static ::std::ffi::CStr = #pyo3_path::ffi::c_str!(#name);
pub(super) struct MakeDef; pub(super) struct MakeDef;
#[doc(hidden)] #[doc(hidden)]

View File

@ -1,7 +1,7 @@
use std::borrow::Cow; use std::borrow::Cow;
use proc_macro2::{Ident, Span, TokenStream}; use proc_macro2::{Ident, Span, TokenStream};
use quote::{format_ident, quote, quote_spanned}; use quote::{format_ident, quote, quote_spanned, ToTokens};
use syn::ext::IdentExt; use syn::ext::IdentExt;
use syn::parse::{Parse, ParseStream}; use syn::parse::{Parse, ParseStream};
use syn::punctuated::Punctuated; use syn::punctuated::Punctuated;
@ -21,9 +21,10 @@ use crate::pymethod::{
impl_py_getter_def, impl_py_setter_def, MethodAndMethodDef, MethodAndSlotDef, PropertyType, impl_py_getter_def, impl_py_setter_def, MethodAndMethodDef, MethodAndSlotDef, PropertyType,
SlotDef, __GETITEM__, __HASH__, __INT__, __LEN__, __REPR__, __RICHCMP__, SlotDef, __GETITEM__, __HASH__, __INT__, __LEN__, __REPR__, __RICHCMP__,
}; };
use crate::pyversions;
use crate::utils::{self, apply_renaming_rule, PythonDoc}; use crate::utils::{self, apply_renaming_rule, PythonDoc};
use crate::utils::{is_abi3, Ctx}; use crate::utils::{is_abi3, Ctx};
use crate::{pyversions, PyFunctionOptions}; use crate::PyFunctionOptions;
/// If the class is derived from a Rust `struct` or `enum`. /// If the class is derived from a Rust `struct` or `enum`.
#[derive(Copy, Clone, Debug, PartialEq, Eq)] #[derive(Copy, Clone, Debug, PartialEq, Eq)]
@ -225,9 +226,9 @@ pub fn build_py_class(
methods_type: PyClassMethodsType, methods_type: PyClassMethodsType,
) -> syn::Result<TokenStream> { ) -> syn::Result<TokenStream> {
args.options.take_pyo3_options(&mut class.attrs)?; args.options.take_pyo3_options(&mut class.attrs)?;
let doc = utils::get_doc(&class.attrs, None);
let ctx = &Ctx::new(&args.options.krate); let ctx = &Ctx::new(&args.options.krate);
let doc = utils::get_doc(&class.attrs, None, ctx);
if let Some(lt) = class.generics.lifetimes().next() { if let Some(lt) = class.generics.lifetimes().next() {
bail_spanned!( bail_spanned!(
@ -465,7 +466,7 @@ pub fn build_py_enum(
bail_spanned!(enum_.brace_token.span.join() => "#[pyclass] can't be used on enums without any variants"); bail_spanned!(enum_.brace_token.span.join() => "#[pyclass] can't be used on enums without any variants");
} }
let doc = utils::get_doc(&enum_.attrs, None); let doc = utils::get_doc(&enum_.attrs, None, ctx);
let enum_ = PyClassEnum::new(enum_)?; let enum_ = PyClassEnum::new(enum_)?;
impl_enum(enum_, &args, doc, method_type, ctx) impl_enum(enum_, &args, doc, method_type, ctx)
} }
@ -1403,7 +1404,7 @@ pub fn gen_complex_enum_variant_attr(
let member = &spec.rust_ident; let member = &spec.rust_ident;
let wrapper_ident = format_ident!("__pymethod_variant_cls_{}__", member); let wrapper_ident = format_ident!("__pymethod_variant_cls_{}__", member);
let deprecations = &spec.attributes.deprecations; let deprecations = &spec.attributes.deprecations;
let python_name = &spec.null_terminated_python_name(); let python_name = spec.null_terminated_python_name(ctx);
let variant_cls = format_ident!("{}_{}", cls, member); let variant_cls = format_ident!("{}_{}", cls, member);
let associated_method = quote! { let associated_method = quote! {
@ -1580,7 +1581,7 @@ fn complex_enum_variant_field_getter<'a>(
let property_type = crate::pymethod::PropertyType::Function { let property_type = crate::pymethod::PropertyType::Function {
self_type: &self_type, self_type: &self_type,
spec: &spec, spec: &spec,
doc: crate::get_doc(&[], None), doc: crate::get_doc(&[], None, ctx),
}; };
let getter = crate::pymethod::impl_py_getter_def(variant_cls_type, property_type, ctx)?; let getter = crate::pymethod::impl_py_getter_def(variant_cls_type, property_type, ctx)?;
@ -2014,7 +2015,10 @@ impl<'a> PyClassImplsBuilder<'a> {
fn impl_pyclassimpl(&self, ctx: &Ctx) -> Result<TokenStream> { fn impl_pyclassimpl(&self, ctx: &Ctx) -> Result<TokenStream> {
let Ctx { pyo3_path } = ctx; let Ctx { pyo3_path } = ctx;
let cls = self.cls; let cls = self.cls;
let doc = self.doc.as_ref().map_or(quote! {"\0"}, |doc| quote! {#doc}); let doc = self.doc.as_ref().map_or(
quote! {#pyo3_path::ffi::c_str!("")},
PythonDoc::to_token_stream,
);
let is_basetype = self.attr.options.subclass.is_some(); let is_basetype = self.attr.options.subclass.is_some();
let base = match &self.attr.options.extends { let base = match &self.attr.options.extends {
Some(extends_attr) => extends_attr.value.clone(), Some(extends_attr) => extends_attr.value.clone(),

View File

@ -260,7 +260,7 @@ pub fn impl_wrap_pyfunction(
let wrapper_ident = format_ident!("__pyfunction_{}", spec.name); let wrapper_ident = format_ident!("__pyfunction_{}", spec.name);
let wrapper = spec.get_wrapper_function(&wrapper_ident, None, ctx)?; let wrapper = spec.get_wrapper_function(&wrapper_ident, None, ctx)?;
let methoddef = spec.get_methoddef(wrapper_ident, &spec.get_doc(&func.attrs), ctx); let methoddef = spec.get_methoddef(wrapper_ident, &spec.get_doc(&func.attrs, ctx), ctx);
let wrapped_pyfunction = quote! { let wrapped_pyfunction = quote! {

View File

@ -186,7 +186,7 @@ pub fn gen_py_const(cls: &syn::Type, spec: &ConstSpec<'_>, ctx: &Ctx) -> MethodA
let member = &spec.rust_ident; let member = &spec.rust_ident;
let wrapper_ident = format_ident!("__pymethod_{}__", member); let wrapper_ident = format_ident!("__pymethod_{}__", member);
let deprecations = &spec.attributes.deprecations; let deprecations = &spec.attributes.deprecations;
let python_name = &spec.null_terminated_python_name(); let python_name = spec.null_terminated_python_name(ctx);
let Ctx { pyo3_path } = ctx; let Ctx { pyo3_path } = ctx;
let associated_method = quote! { let associated_method = quote! {

View File

@ -227,21 +227,21 @@ pub fn gen_py_method(
(_, FnType::Fn(_)) => GeneratedPyMethod::Method(impl_py_method_def( (_, FnType::Fn(_)) => GeneratedPyMethod::Method(impl_py_method_def(
cls, cls,
spec, spec,
&spec.get_doc(meth_attrs), &spec.get_doc(meth_attrs, ctx),
None, None,
ctx, ctx,
)?), )?),
(_, FnType::FnClass(_)) => GeneratedPyMethod::Method(impl_py_method_def( (_, FnType::FnClass(_)) => GeneratedPyMethod::Method(impl_py_method_def(
cls, cls,
spec, spec,
&spec.get_doc(meth_attrs), &spec.get_doc(meth_attrs, ctx),
Some(quote!(#pyo3_path::ffi::METH_CLASS)), Some(quote!(#pyo3_path::ffi::METH_CLASS)),
ctx, ctx,
)?), )?),
(_, FnType::FnStatic) => GeneratedPyMethod::Method(impl_py_method_def( (_, FnType::FnStatic) => GeneratedPyMethod::Method(impl_py_method_def(
cls, cls,
spec, spec,
&spec.get_doc(meth_attrs), &spec.get_doc(meth_attrs, ctx),
Some(quote!(#pyo3_path::ffi::METH_STATIC)), Some(quote!(#pyo3_path::ffi::METH_STATIC)),
ctx, ctx,
)?), )?),
@ -255,7 +255,7 @@ pub fn gen_py_method(
PropertyType::Function { PropertyType::Function {
self_type, self_type,
spec, spec,
doc: spec.get_doc(meth_attrs), doc: spec.get_doc(meth_attrs, ctx),
}, },
ctx, ctx,
)?), )?),
@ -264,7 +264,7 @@ pub fn gen_py_method(
PropertyType::Function { PropertyType::Function {
self_type, self_type,
spec, spec,
doc: spec.get_doc(meth_attrs), doc: spec.get_doc(meth_attrs, ctx),
}, },
ctx, ctx,
)?), )?),
@ -499,7 +499,7 @@ fn impl_py_class_attribute(
}; };
let wrapper_ident = format_ident!("__pymethod_{}__", name); let wrapper_ident = format_ident!("__pymethod_{}__", name);
let python_name = spec.null_terminated_python_name(); let python_name = spec.null_terminated_python_name(ctx);
let body = quotes::ok_wrap(fncall, ctx); let body = quotes::ok_wrap(fncall, ctx);
let associated_method = quote! { let associated_method = quote! {
@ -560,8 +560,8 @@ pub fn impl_py_setter_def(
ctx: &Ctx, ctx: &Ctx,
) -> Result<MethodAndMethodDef> { ) -> Result<MethodAndMethodDef> {
let Ctx { pyo3_path } = ctx; let Ctx { pyo3_path } = ctx;
let python_name = property_type.null_terminated_python_name()?; let python_name = property_type.null_terminated_python_name(ctx)?;
let doc = property_type.doc(); let doc = property_type.doc(ctx);
let mut holders = Holders::new(); let mut holders = Holders::new();
let setter_impl = match property_type { let setter_impl = match property_type {
PropertyType::Descriptor { PropertyType::Descriptor {
@ -746,8 +746,8 @@ pub fn impl_py_getter_def(
ctx: &Ctx, ctx: &Ctx,
) -> Result<MethodAndMethodDef> { ) -> Result<MethodAndMethodDef> {
let Ctx { pyo3_path } = ctx; let Ctx { pyo3_path } = ctx;
let python_name = property_type.null_terminated_python_name()?; let python_name = property_type.null_terminated_python_name(ctx)?;
let doc = property_type.doc(); let doc = property_type.doc(ctx);
let mut holders = Holders::new(); let mut holders = Holders::new();
let body = match property_type { let body = match property_type {
@ -870,7 +870,8 @@ pub enum PropertyType<'a> {
} }
impl PropertyType<'_> { impl PropertyType<'_> {
fn null_terminated_python_name(&self) -> Result<syn::LitStr> { fn null_terminated_python_name(&self, ctx: &Ctx) -> Result<TokenStream> {
let Ctx { pyo3_path } = ctx;
match self { match self {
PropertyType::Descriptor { PropertyType::Descriptor {
field, field,
@ -885,23 +886,22 @@ impl PropertyType<'_> {
if let Some(rule) = renaming_rule { if let Some(rule) = renaming_rule {
name = utils::apply_renaming_rule(*rule, &name); name = utils::apply_renaming_rule(*rule, &name);
} }
name.push('\0');
name name
} }
(None, None) => { (None, None) => {
bail_spanned!(field.span() => "`get` and `set` with tuple struct fields require `name`"); bail_spanned!(field.span() => "`get` and `set` with tuple struct fields require `name`");
} }
}; };
Ok(syn::LitStr::new(&name, field.span())) Ok(quote_spanned!(field.span() => #pyo3_path::ffi::c_str!(#name)))
} }
PropertyType::Function { spec, .. } => Ok(spec.null_terminated_python_name()), PropertyType::Function { spec, .. } => Ok(spec.null_terminated_python_name(ctx)),
} }
} }
fn doc(&self) -> Cow<'_, PythonDoc> { fn doc(&self, ctx: &Ctx) -> Cow<'_, PythonDoc> {
match self { match self {
PropertyType::Descriptor { field, .. } => { PropertyType::Descriptor { field, .. } => {
Cow::Owned(utils::get_doc(&field.attrs, None)) Cow::Owned(utils::get_doc(&field.attrs, None, ctx))
} }
PropertyType::Function { doc, .. } => Cow::Borrowed(doc), PropertyType::Function { doc, .. } => Cow::Borrowed(doc),
} }

View File

@ -1,5 +1,5 @@
use proc_macro2::{Span, TokenStream}; use proc_macro2::{Span, TokenStream};
use quote::ToTokens; use quote::{quote, ToTokens};
use syn::{punctuated::Punctuated, Token}; use syn::{punctuated::Punctuated, Token};
use crate::attributes::{CrateAttribute, RenamingRule}; use crate::attributes::{CrateAttribute, RenamingRule};
@ -81,7 +81,12 @@ pub struct PythonDoc(TokenStream);
/// If this doc is for a callable, the provided `text_signature` can be passed to prepend /// If this doc is for a callable, the provided `text_signature` can be passed to prepend
/// this to the documentation suitable for Python to extract this into the `__text_signature__` /// this to the documentation suitable for Python to extract this into the `__text_signature__`
/// attribute. /// attribute.
pub fn get_doc(attrs: &[syn::Attribute], mut text_signature: Option<String>) -> PythonDoc { pub fn get_doc(
attrs: &[syn::Attribute],
mut text_signature: Option<String>,
ctx: &Ctx,
) -> PythonDoc {
let Ctx { pyo3_path } = ctx;
// insert special divider between `__text_signature__` and doc // insert special divider between `__text_signature__` and doc
// (assume text_signature is itself well-formed) // (assume text_signature is itself well-formed)
if let Some(text_signature) = &mut text_signature { if let Some(text_signature) = &mut text_signature {
@ -120,7 +125,7 @@ pub fn get_doc(attrs: &[syn::Attribute], mut text_signature: Option<String>) ->
} }
} }
if !parts.is_empty() { let tokens = if !parts.is_empty() {
// Doc contained macro pieces - return as `concat!` expression // Doc contained macro pieces - return as `concat!` expression
if !current_part.is_empty() { if !current_part.is_empty() {
parts.push(current_part.to_token_stream()); parts.push(current_part.to_token_stream());
@ -133,15 +138,14 @@ pub fn get_doc(attrs: &[syn::Attribute], mut text_signature: Option<String>) ->
syn::token::Bracket(Span::call_site()).surround(&mut tokens, |tokens| { syn::token::Bracket(Span::call_site()).surround(&mut tokens, |tokens| {
parts.to_tokens(tokens); parts.to_tokens(tokens);
syn::token::Comma(Span::call_site()).to_tokens(tokens); syn::token::Comma(Span::call_site()).to_tokens(tokens);
syn::LitStr::new("\0", Span::call_site()).to_tokens(tokens);
}); });
PythonDoc(tokens) tokens
} else { } else {
// Just a string doc - return directly with nul terminator // Just a string doc - return directly with nul terminator
current_part.push('\0'); current_part.to_token_stream()
PythonDoc(current_part.to_token_stream()) };
} PythonDoc(quote!(#pyo3_path::ffi::c_str!(#tokens)))
} }
impl quote::ToTokens for PythonDoc { impl quote::ToTokens for PythonDoc {

View File

@ -352,7 +352,7 @@ impl<T: Element> PyBuffer<T> {
#[inline] #[inline]
pub fn format(&self) -> &CStr { pub fn format(&self) -> &CStr {
if self.0.format.is_null() { if self.0.format.is_null() {
CStr::from_bytes_with_nul(b"B\0").unwrap() ffi::c_str!("B")
} else { } else {
unsafe { CStr::from_ptr(self.0.format) } unsafe { CStr::from_ptr(self.0.format) }
} }
@ -723,125 +723,124 @@ mod tests {
fn test_element_type_from_format() { fn test_element_type_from_format() {
use super::ElementType; use super::ElementType;
use super::ElementType::*; use super::ElementType::*;
use std::ffi::CStr;
use std::mem::size_of; use std::mem::size_of;
use std::os::raw; use std::os::raw;
for (cstr, expected) in &[ for (cstr, expected) in [
// @ prefix goes to native_element_type_from_type_char // @ prefix goes to native_element_type_from_type_char
( (
"@b\0", ffi::c_str!("@b"),
SignedInteger { SignedInteger {
bytes: size_of::<raw::c_schar>(), bytes: size_of::<raw::c_schar>(),
}, },
), ),
( (
"@c\0", ffi::c_str!("@c"),
UnsignedInteger { UnsignedInteger {
bytes: size_of::<raw::c_char>(), bytes: size_of::<raw::c_char>(),
}, },
), ),
( (
"@b\0", ffi::c_str!("@b"),
SignedInteger { SignedInteger {
bytes: size_of::<raw::c_schar>(), bytes: size_of::<raw::c_schar>(),
}, },
), ),
( (
"@B\0", ffi::c_str!("@B"),
UnsignedInteger { UnsignedInteger {
bytes: size_of::<raw::c_uchar>(), bytes: size_of::<raw::c_uchar>(),
}, },
), ),
("@?\0", Bool), (ffi::c_str!("@?"), Bool),
( (
"@h\0", ffi::c_str!("@h"),
SignedInteger { SignedInteger {
bytes: size_of::<raw::c_short>(), bytes: size_of::<raw::c_short>(),
}, },
), ),
( (
"@H\0", ffi::c_str!("@H"),
UnsignedInteger { UnsignedInteger {
bytes: size_of::<raw::c_ushort>(), bytes: size_of::<raw::c_ushort>(),
}, },
), ),
( (
"@i\0", ffi::c_str!("@i"),
SignedInteger { SignedInteger {
bytes: size_of::<raw::c_int>(), bytes: size_of::<raw::c_int>(),
}, },
), ),
( (
"@I\0", ffi::c_str!("@I"),
UnsignedInteger { UnsignedInteger {
bytes: size_of::<raw::c_uint>(), bytes: size_of::<raw::c_uint>(),
}, },
), ),
( (
"@l\0", ffi::c_str!("@l"),
SignedInteger { SignedInteger {
bytes: size_of::<raw::c_long>(), bytes: size_of::<raw::c_long>(),
}, },
), ),
( (
"@L\0", ffi::c_str!("@L"),
UnsignedInteger { UnsignedInteger {
bytes: size_of::<raw::c_ulong>(), bytes: size_of::<raw::c_ulong>(),
}, },
), ),
( (
"@q\0", ffi::c_str!("@q"),
SignedInteger { SignedInteger {
bytes: size_of::<raw::c_longlong>(), bytes: size_of::<raw::c_longlong>(),
}, },
), ),
( (
"@Q\0", ffi::c_str!("@Q"),
UnsignedInteger { UnsignedInteger {
bytes: size_of::<raw::c_ulonglong>(), bytes: size_of::<raw::c_ulonglong>(),
}, },
), ),
( (
"@n\0", ffi::c_str!("@n"),
SignedInteger { SignedInteger {
bytes: size_of::<libc::ssize_t>(), bytes: size_of::<libc::ssize_t>(),
}, },
), ),
( (
"@N\0", ffi::c_str!("@N"),
UnsignedInteger { UnsignedInteger {
bytes: size_of::<libc::size_t>(), bytes: size_of::<libc::size_t>(),
}, },
), ),
("@e\0", Float { bytes: 2 }), (ffi::c_str!("@e"), Float { bytes: 2 }),
("@f\0", Float { bytes: 4 }), (ffi::c_str!("@f"), Float { bytes: 4 }),
("@d\0", Float { bytes: 8 }), (ffi::c_str!("@d"), Float { bytes: 8 }),
("@z\0", Unknown), (ffi::c_str!("@z"), Unknown),
// = prefix goes to standard_element_type_from_type_char // = prefix goes to standard_element_type_from_type_char
("=b\0", SignedInteger { bytes: 1 }), (ffi::c_str!("=b"), SignedInteger { bytes: 1 }),
("=c\0", UnsignedInteger { bytes: 1 }), (ffi::c_str!("=c"), UnsignedInteger { bytes: 1 }),
("=B\0", UnsignedInteger { bytes: 1 }), (ffi::c_str!("=B"), UnsignedInteger { bytes: 1 }),
("=?\0", Bool), (ffi::c_str!("=?"), Bool),
("=h\0", SignedInteger { bytes: 2 }), (ffi::c_str!("=h"), SignedInteger { bytes: 2 }),
("=H\0", UnsignedInteger { bytes: 2 }), (ffi::c_str!("=H"), UnsignedInteger { bytes: 2 }),
("=l\0", SignedInteger { bytes: 4 }), (ffi::c_str!("=l"), SignedInteger { bytes: 4 }),
("=l\0", SignedInteger { bytes: 4 }), (ffi::c_str!("=l"), SignedInteger { bytes: 4 }),
("=I\0", UnsignedInteger { bytes: 4 }), (ffi::c_str!("=I"), UnsignedInteger { bytes: 4 }),
("=L\0", UnsignedInteger { bytes: 4 }), (ffi::c_str!("=L"), UnsignedInteger { bytes: 4 }),
("=q\0", SignedInteger { bytes: 8 }), (ffi::c_str!("=q"), SignedInteger { bytes: 8 }),
("=Q\0", UnsignedInteger { bytes: 8 }), (ffi::c_str!("=Q"), UnsignedInteger { bytes: 8 }),
("=e\0", Float { bytes: 2 }), (ffi::c_str!("=e"), Float { bytes: 2 }),
("=f\0", Float { bytes: 4 }), (ffi::c_str!("=f"), Float { bytes: 4 }),
("=d\0", Float { bytes: 8 }), (ffi::c_str!("=d"), Float { bytes: 8 }),
("=z\0", Unknown), (ffi::c_str!("=z"), Unknown),
("=0\0", Unknown), (ffi::c_str!("=0"), Unknown),
// unknown prefix -> Unknown // unknown prefix -> Unknown
(":b\0", Unknown), (ffi::c_str!(":b"), Unknown),
] { ] {
assert_eq!( assert_eq!(
ElementType::from_format(CStr::from_bytes_with_nul(cstr.as_bytes()).unwrap()), ElementType::from_format(cstr),
*expected, expected,
"element from format &Cstr: {:?}", "element from format &Cstr: {:?}",
cstr, cstr,
); );

View File

@ -64,28 +64,15 @@ macro_rules! rational_conversion {
impl<'py> FromPyObject<'py> for Ratio<$int> { impl<'py> FromPyObject<'py> for Ratio<$int> {
fn extract_bound(obj: &Bound<'py, PyAny>) -> PyResult<Self> { fn extract_bound(obj: &Bound<'py, PyAny>) -> PyResult<Self> {
let py = obj.py(); let py = obj.py();
let py_numerator_obj = unsafe { let py_numerator_obj = obj.getattr(crate::intern!(py, "numerator"))?;
Bound::from_owned_ptr_or_err( let py_denominator_obj = obj.getattr(crate::intern!(py, "denominator"))?;
py,
ffi::PyObject_GetAttrString(obj.as_ptr(), "numerator\0".as_ptr().cast()),
)
};
let py_denominator_obj = unsafe {
Bound::from_owned_ptr_or_err(
py,
ffi::PyObject_GetAttrString(obj.as_ptr(), "denominator\0".as_ptr().cast()),
)
};
let numerator_owned = unsafe { let numerator_owned = unsafe {
Bound::from_owned_ptr_or_err( Bound::from_owned_ptr_or_err(py, ffi::PyNumber_Long(py_numerator_obj.as_ptr()))?
py,
ffi::PyNumber_Long(py_numerator_obj?.as_ptr()),
)?
}; };
let denominator_owned = unsafe { let denominator_owned = unsafe {
Bound::from_owned_ptr_or_err( Bound::from_owned_ptr_or_err(
py, py,
ffi::PyNumber_Long(py_denominator_obj?.as_ptr()), ffi::PyNumber_Long(py_denominator_obj.as_ptr()),
)? )?
}; };
let rs_numerator: $int = numerator_owned.extract()?; let rs_numerator: $int = numerator_owned.extract()?;

View File

@ -240,9 +240,7 @@ fn raise_lazy(py: Python<'_>, lazy: Box<PyErrStateLazyFn>) {
if ffi::PyExceptionClass_Check(ptype.as_ptr()) == 0 { if ffi::PyExceptionClass_Check(ptype.as_ptr()) == 0 {
ffi::PyErr_SetString( ffi::PyErr_SetString(
PyTypeError::type_object_raw(py).cast(), PyTypeError::type_object_raw(py).cast(),
"exceptions must derive from BaseException\0" ffi::c_str!("exceptions must derive from BaseException").as_ptr(),
.as_ptr()
.cast(),
) )
} else { } else {
ffi::PyErr_SetObject(ptype.as_ptr(), pvalue.as_ptr()) ffi::PyErr_SetObject(ptype.as_ptr(), pvalue.as_ptr())

View File

@ -736,10 +736,10 @@ impl PyUnicodeDecodeError {
let pos = err.valid_up_to(); let pos = err.valid_up_to();
PyUnicodeDecodeError::new_bound( PyUnicodeDecodeError::new_bound(
py, py,
CStr::from_bytes_with_nul(b"utf-8\0").unwrap(), ffi::c_str!("utf-8"),
input, input,
pos..(pos + 1), pos..(pos + 1),
CStr::from_bytes_with_nul(b"invalid utf-8\0").unwrap(), ffi::c_str!("invalid utf-8"),
) )
} }
} }

View File

@ -5,7 +5,6 @@ use crate::{
ffi, ffi,
impl_::freelist::FreeList, impl_::freelist::FreeList,
impl_::pycell::{GetBorrowChecker, PyClassMutability, PyClassObjectLayout}, impl_::pycell::{GetBorrowChecker, PyClassMutability, PyClassObjectLayout},
internal_tricks::extract_c_string,
pyclass_init::PyObjectInit, pyclass_init::PyObjectInit,
types::any::PyAnyMethods, types::any::PyAnyMethods,
types::PyBool, types::PyBool,
@ -214,7 +213,7 @@ pub trait PyClassImpl: Sized + 'static {
/// specialization in to the `#[pyclass]` macro from the `#[pymethods]` macro. /// specialization in to the `#[pyclass]` macro from the `#[pymethods]` macro.
pub fn build_pyclass_doc( pub fn build_pyclass_doc(
class_name: &'static str, class_name: &'static str,
doc: &'static str, doc: &'static CStr,
text_signature: Option<&'static str>, text_signature: Option<&'static str>,
) -> PyResult<Cow<'static, CStr>> { ) -> PyResult<Cow<'static, CStr>> {
if let Some(text_signature) = text_signature { if let Some(text_signature) = text_signature {
@ -222,12 +221,12 @@ pub fn build_pyclass_doc(
"{}{}\n--\n\n{}", "{}{}\n--\n\n{}",
class_name, class_name,
text_signature, text_signature,
doc.trim_end_matches('\0') doc.to_str().unwrap(),
)) ))
.map_err(|_| PyValueError::new_err("class doc cannot contain nul bytes"))?; .map_err(|_| PyValueError::new_err("class doc cannot contain nul bytes"))?;
Ok(Cow::Owned(doc)) Ok(Cow::Owned(doc))
} else { } else {
extract_c_string(doc, "class doc cannot contain nul bytes") Ok(Cow::Borrowed(doc))
} }
} }

View File

@ -1,5 +1,4 @@
use std::{ use std::{
borrow::Cow,
cell::RefCell, cell::RefCell,
ffi::CStr, ffi::CStr,
marker::PhantomData, marker::PhantomData,
@ -151,10 +150,8 @@ impl LazyTypeObjectInner {
for class_items in items_iter { for class_items in items_iter {
for def in class_items.methods { for def in class_items.methods {
if let PyMethodDefType::ClassAttribute(attr) = def { if let PyMethodDefType::ClassAttribute(attr) = def {
let key = attr.attribute_c_string().unwrap();
match (attr.meth)(py) { match (attr.meth)(py) {
Ok(val) => items.push((key, val)), Ok(val) => items.push((attr.name, val)),
Err(err) => { Err(err) => {
return Err(wrap_in_runtime_error( return Err(wrap_in_runtime_error(
py, py,
@ -162,7 +159,7 @@ impl LazyTypeObjectInner {
format!( format!(
"An error occurred while initializing `{}.{}`", "An error occurred while initializing `{}.{}`",
name, name,
attr.name.trim_end_matches('\0') attr.name.to_str().unwrap()
), ),
)) ))
} }
@ -198,7 +195,7 @@ impl LazyTypeObjectInner {
fn initialize_tp_dict( fn initialize_tp_dict(
py: Python<'_>, py: Python<'_>,
type_object: *mut ffi::PyObject, type_object: *mut ffi::PyObject,
items: Vec<(Cow<'static, CStr>, PyObject)>, items: Vec<(&'static CStr, PyObject)>,
) -> PyResult<()> { ) -> PyResult<()> {
// We hold the GIL: the dictionary update can be considered atomic from // We hold the GIL: the dictionary update can be considered atomic from
// the POV of other threads. // the POV of other threads.

View File

@ -2,7 +2,6 @@ use crate::callback::IntoPyCallbackOutput;
use crate::exceptions::PyStopAsyncIteration; use crate::exceptions::PyStopAsyncIteration;
use crate::gil::LockGIL; use crate::gil::LockGIL;
use crate::impl_::panic::PanicTrap; use crate::impl_::panic::PanicTrap;
use crate::internal_tricks::extract_c_string;
use crate::pycell::{PyBorrowError, PyBorrowMutError}; use crate::pycell::{PyBorrowError, PyBorrowMutError};
use crate::pyclass::boolean_struct::False; use crate::pyclass::boolean_struct::False;
use crate::types::any::PyAnyMethods; use crate::types::any::PyAnyMethods;
@ -12,7 +11,6 @@ use crate::{
ffi, Borrowed, Bound, DowncastError, Py, PyAny, PyClass, PyClassInitializer, PyErr, PyObject, ffi, Borrowed, Bound, DowncastError, Py, PyAny, PyClass, PyClassInitializer, PyErr, PyObject,
PyRef, PyRefMut, PyResult, PyTraverseError, PyTypeCheck, PyVisit, Python, PyRef, PyRefMut, PyResult, PyTraverseError, PyTypeCheck, PyVisit, Python,
}; };
use std::borrow::Cow;
use std::ffi::CStr; use std::ffi::CStr;
use std::fmt; use std::fmt;
use std::os::raw::{c_int, c_void}; use std::os::raw::{c_int, c_void};
@ -84,36 +82,30 @@ pub type PyClassAttributeFactory = for<'p> fn(Python<'p>) -> PyResult<PyObject>;
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct PyMethodDef { pub struct PyMethodDef {
pub(crate) ml_name: &'static str, pub(crate) ml_name: &'static CStr,
pub(crate) ml_meth: PyMethodType, pub(crate) ml_meth: PyMethodType,
pub(crate) ml_flags: c_int, pub(crate) ml_flags: c_int,
pub(crate) ml_doc: &'static str, pub(crate) ml_doc: &'static CStr,
} }
#[derive(Copy, Clone)] #[derive(Copy, Clone)]
pub struct PyClassAttributeDef { pub struct PyClassAttributeDef {
pub(crate) name: &'static str, pub(crate) name: &'static CStr,
pub(crate) meth: PyClassAttributeFactory, pub(crate) meth: PyClassAttributeFactory,
} }
impl PyClassAttributeDef {
pub(crate) fn attribute_c_string(&self) -> PyResult<Cow<'static, CStr>> {
extract_c_string(self.name, "class attribute name cannot contain nul bytes")
}
}
#[derive(Clone)] #[derive(Clone)]
pub struct PyGetterDef { pub struct PyGetterDef {
pub(crate) name: &'static str, pub(crate) name: &'static CStr,
pub(crate) meth: Getter, pub(crate) meth: Getter,
pub(crate) doc: &'static str, pub(crate) doc: &'static CStr,
} }
#[derive(Clone)] #[derive(Clone)]
pub struct PySetterDef { pub struct PySetterDef {
pub(crate) name: &'static str, pub(crate) name: &'static CStr,
pub(crate) meth: Setter, pub(crate) meth: Setter,
pub(crate) doc: &'static str, pub(crate) doc: &'static CStr,
} }
unsafe impl Sync for PyMethodDef {} unsafe impl Sync for PyMethodDef {}
@ -125,44 +117,44 @@ unsafe impl Sync for PySetterDef {}
impl PyMethodDef { impl PyMethodDef {
/// Define a function with no `*args` and `**kwargs`. /// Define a function with no `*args` and `**kwargs`.
pub const fn noargs( pub const fn noargs(
name: &'static str, ml_name: &'static CStr,
cfunction: ffi::PyCFunction, cfunction: ffi::PyCFunction,
doc: &'static str, ml_doc: &'static CStr,
) -> Self { ) -> Self {
Self { Self {
ml_name: name, ml_name,
ml_meth: PyMethodType::PyCFunction(cfunction), ml_meth: PyMethodType::PyCFunction(cfunction),
ml_flags: ffi::METH_NOARGS, ml_flags: ffi::METH_NOARGS,
ml_doc: doc, ml_doc,
} }
} }
/// Define a function that can take `*args` and `**kwargs`. /// Define a function that can take `*args` and `**kwargs`.
pub const fn cfunction_with_keywords( pub const fn cfunction_with_keywords(
name: &'static str, ml_name: &'static CStr,
cfunction: ffi::PyCFunctionWithKeywords, cfunction: ffi::PyCFunctionWithKeywords,
doc: &'static str, ml_doc: &'static CStr,
) -> Self { ) -> Self {
Self { Self {
ml_name: name, ml_name,
ml_meth: PyMethodType::PyCFunctionWithKeywords(cfunction), ml_meth: PyMethodType::PyCFunctionWithKeywords(cfunction),
ml_flags: ffi::METH_VARARGS | ffi::METH_KEYWORDS, ml_flags: ffi::METH_VARARGS | ffi::METH_KEYWORDS,
ml_doc: doc, ml_doc,
} }
} }
/// Define a function that can take `*args` and `**kwargs`. /// Define a function that can take `*args` and `**kwargs`.
#[cfg(not(Py_LIMITED_API))] #[cfg(not(Py_LIMITED_API))]
pub const fn fastcall_cfunction_with_keywords( pub const fn fastcall_cfunction_with_keywords(
name: &'static str, ml_name: &'static CStr,
cfunction: ffi::_PyCFunctionFastWithKeywords, cfunction: ffi::_PyCFunctionFastWithKeywords,
doc: &'static str, ml_doc: &'static CStr,
) -> Self { ) -> Self {
Self { Self {
ml_name: name, ml_name,
ml_meth: PyMethodType::PyCFunctionFastWithKeywords(cfunction), ml_meth: PyMethodType::PyCFunctionFastWithKeywords(cfunction),
ml_flags: ffi::METH_FASTCALL | ffi::METH_KEYWORDS, ml_flags: ffi::METH_FASTCALL | ffi::METH_KEYWORDS,
ml_doc: doc, ml_doc,
} }
} }
@ -172,7 +164,7 @@ impl PyMethodDef {
} }
/// Convert `PyMethodDef` to Python method definition struct `ffi::PyMethodDef` /// Convert `PyMethodDef` to Python method definition struct `ffi::PyMethodDef`
pub(crate) fn as_method_def(&self) -> PyResult<(ffi::PyMethodDef, PyMethodDefDestructor)> { pub(crate) fn as_method_def(&self) -> ffi::PyMethodDef {
let meth = match self.ml_meth { let meth = match self.ml_meth {
PyMethodType::PyCFunction(meth) => ffi::PyMethodDefPointer { PyCFunction: meth }, PyMethodType::PyCFunction(meth) => ffi::PyMethodDefPointer { PyCFunction: meth },
PyMethodType::PyCFunctionWithKeywords(meth) => ffi::PyMethodDefPointer { PyMethodType::PyCFunctionWithKeywords(meth) => ffi::PyMethodDefPointer {
@ -184,22 +176,18 @@ impl PyMethodDef {
}, },
}; };
let name = get_name(self.ml_name)?; ffi::PyMethodDef {
let doc = get_doc(self.ml_doc)?; ml_name: self.ml_name.as_ptr(),
let def = ffi::PyMethodDef {
ml_name: name.as_ptr(),
ml_meth: meth, ml_meth: meth,
ml_flags: self.ml_flags, ml_flags: self.ml_flags,
ml_doc: doc.as_ptr(), ml_doc: self.ml_doc.as_ptr(),
}; }
let destructor = PyMethodDefDestructor { name, doc };
Ok((def, destructor))
} }
} }
impl PyClassAttributeDef { impl PyClassAttributeDef {
/// Define a class attribute. /// Define a class attribute.
pub const fn new(name: &'static str, meth: PyClassAttributeFactory) -> Self { pub const fn new(name: &'static CStr, meth: PyClassAttributeFactory) -> Self {
Self { name, meth } Self { name, meth }
} }
} }
@ -222,7 +210,7 @@ pub(crate) type Setter =
impl PyGetterDef { impl PyGetterDef {
/// Define a getter. /// Define a getter.
pub const fn new(name: &'static str, getter: Getter, doc: &'static str) -> Self { pub const fn new(name: &'static CStr, getter: Getter, doc: &'static CStr) -> Self {
Self { Self {
name, name,
meth: getter, meth: getter,
@ -233,7 +221,7 @@ impl PyGetterDef {
impl PySetterDef { impl PySetterDef {
/// Define a setter. /// Define a setter.
pub const fn new(name: &'static str, setter: Setter, doc: &'static str) -> Self { pub const fn new(name: &'static CStr, setter: Setter, doc: &'static CStr) -> Self {
Self { Self {
name, name,
meth: setter, meth: setter,
@ -284,22 +272,6 @@ where
retval retval
} }
pub(crate) struct PyMethodDefDestructor {
// These members are just to avoid leaking CStrings when possible
#[allow(dead_code)]
name: Cow<'static, CStr>,
#[allow(dead_code)]
doc: Cow<'static, CStr>,
}
pub(crate) fn get_name(name: &'static str) -> PyResult<Cow<'static, CStr>> {
extract_c_string(name, "function name cannot contain NUL byte.")
}
pub(crate) fn get_doc(doc: &'static str) -> PyResult<Cow<'static, CStr>> {
extract_c_string(doc, "function doc cannot contain NUL byte.")
}
// Autoref-based specialization for handling `__next__` returning `Option` // Autoref-based specialization for handling `__next__` returning `Option`
pub struct IterBaseTag; pub struct IterBaseTag;

View File

@ -1,6 +1,6 @@
//! Implementation details of `#[pymodule]` which need to be accessible from proc-macro generated code. //! Implementation details of `#[pymodule]` which need to be accessible from proc-macro generated code.
use std::{cell::UnsafeCell, marker::PhantomData}; use std::{cell::UnsafeCell, ffi::CStr, marker::PhantomData};
#[cfg(all( #[cfg(all(
not(any(PyPy, GraalPy)), not(any(PyPy, GraalPy)),
@ -49,12 +49,9 @@ unsafe impl Sync for ModuleDef {}
impl ModuleDef { impl ModuleDef {
/// Make new module definition with given module name. /// Make new module definition with given module name.
///
/// # Safety
/// `name` and `doc` must be null-terminated strings.
pub const unsafe fn new( pub const unsafe fn new(
name: &'static str, name: &'static CStr,
doc: &'static str, doc: &'static CStr,
initializer: ModuleInitializer, initializer: ModuleInitializer,
) -> Self { ) -> Self {
const INIT: ffi::PyModuleDef = ffi::PyModuleDef { const INIT: ffi::PyModuleDef = ffi::PyModuleDef {
@ -70,8 +67,8 @@ impl ModuleDef {
}; };
let ffi_def = UnsafeCell::new(ffi::PyModuleDef { let ffi_def = UnsafeCell::new(ffi::PyModuleDef {
m_name: name.as_ptr().cast(), m_name: name.as_ptr(),
m_doc: doc.as_ptr().cast(), m_doc: doc.as_ptr(),
..INIT ..INIT
}); });
@ -215,10 +212,12 @@ impl PyAddToModule for ModuleDef {
mod tests { mod tests {
use std::{ use std::{
borrow::Cow, borrow::Cow,
ffi::CStr,
sync::atomic::{AtomicBool, Ordering}, sync::atomic::{AtomicBool, Ordering},
}; };
use crate::{ use crate::{
ffi,
types::{any::PyAnyMethods, module::PyModuleMethods, PyModule}, types::{any::PyAnyMethods, module::PyModuleMethods, PyModule},
Bound, PyResult, Python, Bound, PyResult, Python,
}; };
@ -229,8 +228,8 @@ mod tests {
fn module_init() { fn module_init() {
static MODULE_DEF: ModuleDef = unsafe { static MODULE_DEF: ModuleDef = unsafe {
ModuleDef::new( ModuleDef::new(
"test_module\0", ffi::c_str!("test_module"),
"some doc\0", ffi::c_str!("some doc"),
ModuleInitializer(|m| { ModuleInitializer(|m| {
m.add("SOME_CONSTANT", 42)?; m.add("SOME_CONSTANT", 42)?;
Ok(()) Ok(())
@ -270,8 +269,8 @@ mod tests {
fn module_def_new() { fn module_def_new() {
// To get coverage for ModuleDef::new() need to create a non-static ModuleDef, however init // To get coverage for ModuleDef::new() need to create a non-static ModuleDef, however init
// etc require static ModuleDef, so this test needs to be separated out. // etc require static ModuleDef, so this test needs to be separated out.
static NAME: &str = "test_module\0"; static NAME: &CStr = ffi::c_str!("test_module");
static DOC: &str = "some doc\0"; static DOC: &CStr = ffi::c_str!("some doc");
static INIT_CALLED: AtomicBool = AtomicBool::new(false); static INIT_CALLED: AtomicBool = AtomicBool::new(false);

View File

@ -1,13 +1,4 @@
use std::{ use crate::ffi::{Py_ssize_t, PY_SSIZE_T_MAX};
borrow::Cow,
ffi::{CStr, CString},
};
use crate::{
exceptions::PyValueError,
ffi::{Py_ssize_t, PY_SSIZE_T_MAX},
PyResult,
};
pub struct PrivateMarker; pub struct PrivateMarker;
macro_rules! private_decl { macro_rules! private_decl {
@ -193,31 +184,6 @@ pub(crate) fn slice_index_order_fail(index: usize, end: usize) -> ! {
panic!("slice index starts at {} but ends at {}", index, end); panic!("slice index starts at {} but ends at {}", index, end);
} }
pub(crate) fn extract_c_string(
src: &'static str,
err_msg: &'static str,
) -> PyResult<Cow<'static, CStr>> {
let bytes = src.as_bytes();
let cow = match bytes {
[] => {
// Empty string, we can trivially refer to a static "\0" string
Cow::Borrowed(unsafe { CStr::from_bytes_with_nul_unchecked(b"\0") })
}
[.., 0] => {
// Last byte is a nul; try to create as a CStr
let c_str =
CStr::from_bytes_with_nul(bytes).map_err(|_| PyValueError::new_err(err_msg))?;
Cow::Borrowed(c_str)
}
_ => {
// Allocate a new CString for this
let c_string = CString::new(bytes).map_err(|_| PyValueError::new_err(err_msg))?;
Cow::Owned(c_string)
}
};
Ok(cow)
}
// TODO: use ptr::from_ref on MSRV 1.76 // TODO: use ptr::from_ref on MSRV 1.76
#[inline] #[inline]
pub(crate) const fn ptr_from_ref<T>(t: &T) -> *const T { pub(crate) const fn ptr_from_ref<T>(t: &T) -> *const T {

View File

@ -214,7 +214,7 @@ macro_rules! append_to_inittab {
); );
} }
$crate::ffi::PyImport_AppendInittab( $crate::ffi::PyImport_AppendInittab(
$module::__PYO3_NAME.as_ptr().cast(), $module::__PYO3_NAME.as_ptr(),
::std::option::Option::Some($module::__pyo3_init), ::std::option::Option::Some($module::__pyo3_init),
); );
} }

View File

@ -652,7 +652,7 @@ impl<'py> Python<'py> {
) -> PyResult<Bound<'py, PyAny>> { ) -> PyResult<Bound<'py, PyAny>> {
let code = CString::new(code)?; let code = CString::new(code)?;
unsafe { unsafe {
let mptr = ffi::PyImport_AddModule("__main__\0".as_ptr().cast()); let mptr = ffi::PyImport_AddModule(ffi::c_str!("__main__").as_ptr());
if mptr.is_null() { if mptr.is_null() {
return Err(PyErr::fetch(self)); return Err(PyErr::fetch(self));
} }
@ -685,7 +685,8 @@ impl<'py> Python<'py> {
} }
} }
let code_obj = ffi::Py_CompileString(code.as_ptr(), "<string>\0".as_ptr() as _, start); let code_obj =
ffi::Py_CompileString(code.as_ptr(), ffi::c_str!("<string>").as_ptr(), start);
if code_obj.is_null() { if code_obj.is_null() {
return Err(PyErr::fetch(self)); return Err(PyErr::fetch(self));
} }

View File

@ -7,7 +7,7 @@ use crate::{
assign_sequence_item_from_mapping, get_sequence_item_from_mapping, tp_dealloc, assign_sequence_item_from_mapping, get_sequence_item_from_mapping, tp_dealloc,
tp_dealloc_with_gc, PyClassItemsIter, tp_dealloc_with_gc, PyClassItemsIter,
}, },
pymethods::{get_doc, get_name, Getter, Setter}, pymethods::{Getter, Setter},
trampoline::trampoline, trampoline::trampoline,
}, },
internal_tricks::ptr_from_ref, internal_tricks::ptr_from_ref,
@ -15,7 +15,6 @@ use crate::{
Py, PyClass, PyGetterDef, PyMethodDefType, PyResult, PySetterDef, PyTypeInfo, Python, Py, PyClass, PyGetterDef, PyMethodDefType, PyResult, PySetterDef, PyTypeInfo, Python,
}; };
use std::{ use std::{
borrow::Cow,
collections::HashMap, collections::HashMap,
ffi::{CStr, CString}, ffi::{CStr, CString},
os::raw::{c_char, c_int, c_ulong, c_void}, os::raw::{c_char, c_int, c_ulong, c_void},
@ -103,7 +102,7 @@ type PyTypeBuilderCleanup = Box<dyn Fn(&PyTypeBuilder, *mut ffi::PyTypeObject)>;
struct PyTypeBuilder { struct PyTypeBuilder {
slots: Vec<ffi::PyType_Slot>, slots: Vec<ffi::PyType_Slot>,
method_defs: Vec<ffi::PyMethodDef>, method_defs: Vec<ffi::PyMethodDef>,
getset_builders: HashMap<&'static str, GetSetDefBuilder>, getset_builders: HashMap<&'static CStr, GetSetDefBuilder>,
/// Used to patch the type objects for the things there's no /// Used to patch the type objects for the things there's no
/// PyType_FromSpec API for... there's no reason this should work, /// PyType_FromSpec API for... there's no reason this should work,
/// except for that it does and we have tests. /// except for that it does and we have tests.
@ -173,32 +172,25 @@ impl PyTypeBuilder {
fn pymethod_def(&mut self, def: &PyMethodDefType) { fn pymethod_def(&mut self, def: &PyMethodDefType) {
match def { match def {
PyMethodDefType::Getter(getter) => { PyMethodDefType::Getter(getter) => self
self.getset_builders .getset_builders
.entry(getter.name) .entry(getter.name)
.or_default() .or_default()
.add_getter(getter); .add_getter(getter),
} PyMethodDefType::Setter(setter) => self
PyMethodDefType::Setter(setter) => { .getset_builders
self.getset_builders
.entry(setter.name) .entry(setter.name)
.or_default() .or_default()
.add_setter(setter); .add_setter(setter),
}
PyMethodDefType::Method(def) PyMethodDefType::Method(def)
| PyMethodDefType::Class(def) | PyMethodDefType::Class(def)
| PyMethodDefType::Static(def) => { | PyMethodDefType::Static(def) => self.method_defs.push(def.as_method_def()),
let (def, destructor) = def.as_method_def().unwrap();
// FIXME: stop leaking destructor
std::mem::forget(destructor);
self.method_defs.push(def);
}
// These class attributes are added after the type gets created by LazyStaticType // These class attributes are added after the type gets created by LazyStaticType
PyMethodDefType::ClassAttribute(_) => {} PyMethodDefType::ClassAttribute(_) => {}
} }
} }
fn finalize_methods_and_properties(&mut self) -> PyResult<Vec<GetSetDefDestructor>> { fn finalize_methods_and_properties(&mut self) -> Vec<GetSetDefDestructor> {
let method_defs: Vec<pyo3_ffi::PyMethodDef> = std::mem::take(&mut self.method_defs); let method_defs: Vec<pyo3_ffi::PyMethodDef> = std::mem::take(&mut self.method_defs);
// Safety: Py_tp_methods expects a raw vec of PyMethodDef // Safety: Py_tp_methods expects a raw vec of PyMethodDef
unsafe { self.push_raw_vec_slot(ffi::Py_tp_methods, method_defs) }; unsafe { self.push_raw_vec_slot(ffi::Py_tp_methods, method_defs) };
@ -210,11 +202,11 @@ impl PyTypeBuilder {
.getset_builders .getset_builders
.iter() .iter()
.map(|(name, builder)| { .map(|(name, builder)| {
let (def, destructor) = builder.as_get_set_def(name)?; let (def, destructor) = builder.as_get_set_def(name);
getset_destructors.push(destructor); getset_destructors.push(destructor);
Ok(def) def
}) })
.collect::<PyResult<_>>()?; .collect();
// PyPy automatically adds __dict__ getter / setter. // PyPy automatically adds __dict__ getter / setter.
#[cfg(not(PyPy))] #[cfg(not(PyPy))]
@ -261,7 +253,7 @@ impl PyTypeBuilder {
} }
property_defs.push(ffi::PyGetSetDef { property_defs.push(ffi::PyGetSetDef {
name: "__dict__\0".as_ptr().cast(), name: ffi::c_str!("__dict__").as_ptr(),
get: Some(get_dict), get: Some(get_dict),
set: Some(ffi::PyObject_GenericSetDict), set: Some(ffi::PyObject_GenericSetDict),
doc: ptr::null(), doc: ptr::null(),
@ -300,7 +292,7 @@ impl PyTypeBuilder {
} }
} }
Ok(getset_destructors) getset_destructors
} }
fn set_is_basetype(mut self, is_basetype: bool) -> Self { fn set_is_basetype(mut self, is_basetype: bool) -> Self {
@ -358,7 +350,7 @@ impl PyTypeBuilder {
#[cfg(Py_3_9)] #[cfg(Py_3_9)]
{ {
#[inline(always)] #[inline(always)]
fn offset_def(name: &'static str, offset: ffi::Py_ssize_t) -> ffi::PyMemberDef { fn offset_def(name: &'static CStr, offset: ffi::Py_ssize_t) -> ffi::PyMemberDef {
ffi::PyMemberDef { ffi::PyMemberDef {
name: name.as_ptr().cast(), name: name.as_ptr().cast(),
type_code: ffi::Py_T_PYSSIZET, type_code: ffi::Py_T_PYSSIZET,
@ -372,12 +364,15 @@ impl PyTypeBuilder {
// __dict__ support // __dict__ support
if let Some(dict_offset) = dict_offset { if let Some(dict_offset) = dict_offset {
members.push(offset_def("__dictoffset__\0", dict_offset)); members.push(offset_def(ffi::c_str!("__dictoffset__"), dict_offset));
} }
// weakref support // weakref support
if let Some(weaklist_offset) = weaklist_offset { if let Some(weaklist_offset) = weaklist_offset {
members.push(offset_def("__weaklistoffset__\0", weaklist_offset)); members.push(offset_def(
ffi::c_str!("__weaklistoffset__"),
weaklist_offset,
));
} }
// Safety: Py_tp_members expects a raw vec of PyMemberDef // Safety: Py_tp_members expects a raw vec of PyMemberDef
@ -417,7 +412,7 @@ impl PyTypeBuilder {
// on some platforms (like windows) // on some platforms (like windows)
#![allow(clippy::useless_conversion)] #![allow(clippy::useless_conversion)]
let getset_destructors = self.finalize_methods_and_properties()?; let getset_destructors = self.finalize_methods_and_properties();
unsafe { self.push_slot(ffi::Py_tp_base, self.tp_base) } unsafe { self.push_slot(ffi::Py_tp_base, self.tp_base) }
@ -531,7 +526,7 @@ unsafe extern "C" fn no_constructor_defined(
#[derive(Default)] #[derive(Default)]
struct GetSetDefBuilder { struct GetSetDefBuilder {
doc: Option<&'static str>, doc: Option<&'static CStr>,
getter: Option<Getter>, getter: Option<Getter>,
setter: Option<Setter>, setter: Option<Setter>,
} }
@ -555,13 +550,7 @@ impl GetSetDefBuilder {
self.setter = Some(setter.meth) self.setter = Some(setter.meth)
} }
fn as_get_set_def( fn as_get_set_def(&self, name: &'static CStr) -> (ffi::PyGetSetDef, GetSetDefDestructor) {
&self,
name: &'static str,
) -> PyResult<(ffi::PyGetSetDef, GetSetDefDestructor)> {
let name = get_name(name)?;
let doc = self.doc.map(get_doc).transpose()?;
let getset_type = match (self.getter, self.setter) { let getset_type = match (self.getter, self.setter) {
(Some(getter), None) => GetSetDefType::Getter(getter), (Some(getter), None) => GetSetDefType::Getter(getter),
(None, Some(setter)) => GetSetDefType::Setter(setter), (None, Some(setter)) => GetSetDefType::Setter(setter),
@ -573,20 +562,16 @@ impl GetSetDefBuilder {
} }
}; };
let getset_def = getset_type.create_py_get_set_def(&name, doc.as_deref()); let getset_def = getset_type.create_py_get_set_def(name, self.doc);
let destructor = GetSetDefDestructor { let destructor = GetSetDefDestructor {
name,
doc,
closure: getset_type, closure: getset_type,
}; };
Ok((getset_def, destructor)) (getset_def, destructor)
} }
} }
#[allow(dead_code)] // a stack of fields which are purely to cache until dropped #[allow(dead_code)] // a stack of fields which are purely to cache until dropped
struct GetSetDefDestructor { struct GetSetDefDestructor {
name: Cow<'static, CStr>,
doc: Option<Cow<'static, CStr>>,
closure: GetSetDefType, closure: GetSetDefType,
} }

View File

@ -8,7 +8,7 @@ use crate::types::module::PyModuleMethods;
use crate::PyNativeType; use crate::PyNativeType;
use crate::{ use crate::{
ffi, ffi,
impl_::pymethods::{self, PyMethodDef, PyMethodDefDestructor}, impl_::pymethods::{self, PyMethodDef},
types::{PyCapsule, PyDict, PyModule, PyString, PyTuple}, types::{PyCapsule, PyDict, PyModule, PyString, PyTuple},
}; };
use crate::{Bound, IntoPy, Py, PyAny, PyResult, Python}; use crate::{Bound, IntoPy, Py, PyAny, PyResult, Python};
@ -30,8 +30,8 @@ impl PyCFunction {
)] )]
pub fn new_with_keywords<'a>( pub fn new_with_keywords<'a>(
fun: ffi::PyCFunctionWithKeywords, fun: ffi::PyCFunctionWithKeywords,
name: &'static str, name: &'static CStr,
doc: &'static str, doc: &'static CStr,
py_or_module: PyFunctionArguments<'a>, py_or_module: PyFunctionArguments<'a>,
) -> PyResult<&'a Self> { ) -> PyResult<&'a Self> {
let (py, module) = py_or_module.into_py_and_maybe_module(); let (py, module) = py_or_module.into_py_and_maybe_module();
@ -44,11 +44,14 @@ impl PyCFunction {
} }
/// Create a new built-in function with keywords (*args and/or **kwargs). /// Create a new built-in function with keywords (*args and/or **kwargs).
///
/// To create `name` and `doc` static strings on Rust versions older than 1.77 (which added c"" literals),
/// use the [`c_str!`](crate::ffi::c_str) macro.
pub fn new_with_keywords_bound<'py>( pub fn new_with_keywords_bound<'py>(
py: Python<'py>, py: Python<'py>,
fun: ffi::PyCFunctionWithKeywords, fun: ffi::PyCFunctionWithKeywords,
name: &'static str, name: &'static CStr,
doc: &'static str, doc: &'static CStr,
module: Option<&Bound<'py, PyModule>>, module: Option<&Bound<'py, PyModule>>,
) -> PyResult<Bound<'py, Self>> { ) -> PyResult<Bound<'py, Self>> {
Self::internal_new( Self::internal_new(
@ -66,8 +69,8 @@ impl PyCFunction {
)] )]
pub fn new<'a>( pub fn new<'a>(
fun: ffi::PyCFunction, fun: ffi::PyCFunction,
name: &'static str, name: &'static CStr,
doc: &'static str, doc: &'static CStr,
py_or_module: PyFunctionArguments<'a>, py_or_module: PyFunctionArguments<'a>,
) -> PyResult<&'a Self> { ) -> PyResult<&'a Self> {
let (py, module) = py_or_module.into_py_and_maybe_module(); let (py, module) = py_or_module.into_py_and_maybe_module();
@ -80,11 +83,14 @@ impl PyCFunction {
} }
/// Create a new built-in function which takes no arguments. /// Create a new built-in function which takes no arguments.
///
/// To create `name` and `doc` static strings on Rust versions older than 1.77 (which added c"" literals),
/// use the [`c_str!`](crate::ffi::c_str) macro.
pub fn new_bound<'py>( pub fn new_bound<'py>(
py: Python<'py>, py: Python<'py>,
fun: ffi::PyCFunction, fun: ffi::PyCFunction,
name: &'static str, name: &'static CStr,
doc: &'static str, doc: &'static CStr,
module: Option<&Bound<'py, PyModule>>, module: Option<&Bound<'py, PyModule>>,
) -> PyResult<Bound<'py, Self>> { ) -> PyResult<Bound<'py, Self>> {
Self::internal_new(py, &PyMethodDef::noargs(name, fun, doc), module) Self::internal_new(py, &PyMethodDef::noargs(name, fun, doc), module)
@ -98,8 +104,8 @@ impl PyCFunction {
)] )]
pub fn new_closure<'a, F, R>( pub fn new_closure<'a, F, R>(
py: Python<'a>, py: Python<'a>,
name: Option<&'static str>, name: Option<&'static CStr>,
doc: Option<&'static str>, doc: Option<&'static CStr>,
closure: F, closure: F,
) -> PyResult<&'a PyCFunction> ) -> PyResult<&'a PyCFunction>
where where
@ -131,29 +137,27 @@ impl PyCFunction {
/// ``` /// ```
pub fn new_closure_bound<'py, F, R>( pub fn new_closure_bound<'py, F, R>(
py: Python<'py>, py: Python<'py>,
name: Option<&'static str>, name: Option<&'static CStr>,
doc: Option<&'static str>, doc: Option<&'static CStr>,
closure: F, closure: F,
) -> PyResult<Bound<'py, Self>> ) -> PyResult<Bound<'py, Self>>
where where
F: Fn(&Bound<'_, PyTuple>, Option<&Bound<'_, PyDict>>) -> R + Send + 'static, F: Fn(&Bound<'_, PyTuple>, Option<&Bound<'_, PyDict>>) -> R + Send + 'static,
R: crate::callback::IntoPyCallbackOutput<*mut ffi::PyObject>, R: crate::callback::IntoPyCallbackOutput<*mut ffi::PyObject>,
{ {
let method_def = pymethods::PyMethodDef::cfunction_with_keywords( let name = name.unwrap_or(ffi::c_str!("pyo3-closure"));
name.unwrap_or("pyo3-closure\0"), let doc = doc.unwrap_or(ffi::c_str!(""));
run_closure::<F, R>, let method_def =
doc.unwrap_or("\0"), pymethods::PyMethodDef::cfunction_with_keywords(name, run_closure::<F, R>, doc);
); let def = method_def.as_method_def();
let (def, def_destructor) = method_def.as_method_def()?;
let capsule = PyCapsule::new_bound( let capsule = PyCapsule::new_bound(
py, py,
ClosureDestructor::<F> { ClosureDestructor::<F> {
closure, closure,
def: UnsafeCell::new(def), def: UnsafeCell::new(def),
def_destructor,
}, },
Some(closure_capsule_name().to_owned()), Some(CLOSURE_CAPSULE_NAME.to_owned()),
)?; )?;
// Safety: just created the capsule with type ClosureDestructor<F> above // Safety: just created the capsule with type ClosureDestructor<F> above
@ -178,11 +182,10 @@ impl PyCFunction {
} else { } else {
(std::ptr::null_mut(), None) (std::ptr::null_mut(), None)
}; };
let (def, destructor) = method_def.as_method_def()?; let def = method_def.as_method_def();
// FIXME: stop leaking the def and destructor // FIXME: stop leaking the def
let def = Box::into_raw(Box::new(def)); let def = Box::into_raw(Box::new(def));
std::mem::forget(destructor);
let module_name_ptr = module_name let module_name_ptr = module_name
.as_ref() .as_ref()
@ -196,10 +199,7 @@ impl PyCFunction {
} }
} }
fn closure_capsule_name() -> &'static CStr { static CLOSURE_CAPSULE_NAME: &CStr = ffi::c_str!("pyo3-closure");
// TODO replace this with const CStr once MSRV new enough
CStr::from_bytes_with_nul(b"pyo3-closure\0").unwrap()
}
unsafe extern "C" fn run_closure<F, R>( unsafe extern "C" fn run_closure<F, R>(
capsule_ptr: *mut ffi::PyObject, capsule_ptr: *mut ffi::PyObject,
@ -218,7 +218,7 @@ where
kwargs, kwargs,
|py, capsule_ptr, args, kwargs| { |py, capsule_ptr, args, kwargs| {
let boxed_fn: &ClosureDestructor<F> = let boxed_fn: &ClosureDestructor<F> =
&*(ffi::PyCapsule_GetPointer(capsule_ptr, closure_capsule_name().as_ptr()) &*(ffi::PyCapsule_GetPointer(capsule_ptr, CLOSURE_CAPSULE_NAME.as_ptr())
as *mut ClosureDestructor<F>); as *mut ClosureDestructor<F>);
let args = Bound::ref_from_ptr(py, &args).downcast_unchecked::<PyTuple>(); let args = Bound::ref_from_ptr(py, &args).downcast_unchecked::<PyTuple>();
let kwargs = Bound::ref_from_ptr_or_opt(py, &kwargs) let kwargs = Bound::ref_from_ptr_or_opt(py, &kwargs)
@ -235,9 +235,6 @@ struct ClosureDestructor<F> {
// Wrapped in UnsafeCell because Python C-API wants a *mut pointer // Wrapped in UnsafeCell because Python C-API wants a *mut pointer
// to this member. // to this member.
def: UnsafeCell<ffi::PyMethodDef>, def: UnsafeCell<ffi::PyMethodDef>,
// Used to destroy the cstrings in `def`, if necessary.
#[allow(dead_code)]
def_destructor: PyMethodDefDestructor,
} }
// Safety: F is send and none of the fields are ever mutated // Safety: F is send and none of the fields are ever mutated

View File

@ -78,7 +78,7 @@ impl<'a> PyStringData<'a> {
Err(PyUnicodeDecodeError::new_bound( Err(PyUnicodeDecodeError::new_bound(
py, py,
CStr::from_bytes_with_nul(b"utf-16\0").unwrap(), ffi::c_str!("utf-16"),
self.as_bytes(), self.as_bytes(),
0..self.as_bytes().len(), 0..self.as_bytes().len(),
CStr::from_bytes_with_nul(&message).unwrap(), CStr::from_bytes_with_nul(&message).unwrap(),
@ -90,10 +90,10 @@ impl<'a> PyStringData<'a> {
Some(s) => Ok(Cow::Owned(s)), Some(s) => Ok(Cow::Owned(s)),
None => Err(PyUnicodeDecodeError::new_bound( None => Err(PyUnicodeDecodeError::new_bound(
py, py,
CStr::from_bytes_with_nul(b"utf-32\0").unwrap(), ffi::c_str!("utf-32"),
self.as_bytes(), self.as_bytes(),
0..self.as_bytes().len(), 0..self.as_bytes().len(),
CStr::from_bytes_with_nul(b"error converting utf-32\0").unwrap(), ffi::c_str!("error converting utf-32"),
)? )?
.into()), .into()),
}, },
@ -414,8 +414,8 @@ impl<'a> Borrowed<'a, '_, PyString> {
let bytes = unsafe { let bytes = unsafe {
ffi::PyUnicode_AsEncodedString( ffi::PyUnicode_AsEncodedString(
ptr, ptr,
b"utf-8\0".as_ptr().cast(), ffi::c_str!("utf-8").as_ptr(),
b"surrogatepass\0".as_ptr().cast(), ffi::c_str!("surrogatepass").as_ptr(),
) )
.assume_owned(py) .assume_owned(py)
.downcast_into_unchecked::<PyBytes>() .downcast_into_unchecked::<PyBytes>()

View File

@ -3,7 +3,6 @@
use pyo3::{buffer::PyBuffer, exceptions::PyBufferError, ffi, prelude::*}; use pyo3::{buffer::PyBuffer, exceptions::PyBufferError, ffi, prelude::*};
use std::{ use std::{
ffi::CStr,
os::raw::{c_int, c_void}, os::raw::{c_int, c_void},
ptr, ptr,
}; };
@ -48,7 +47,7 @@ impl TestBufferErrors {
(*view).readonly = 1; (*view).readonly = 1;
(*view).itemsize = std::mem::size_of::<u32>() as isize; (*view).itemsize = std::mem::size_of::<u32>() as isize;
let msg = CStr::from_bytes_with_nul(b"I\0").unwrap(); let msg = ffi::c_str!("I");
(*view).format = msg.as_ptr() as *mut _; (*view).format = msg.as_ptr() as *mut _;
(*view).ndim = 1; (*view).ndim = 1;
@ -72,7 +71,7 @@ impl TestBufferErrors {
(*view).itemsize += 1; (*view).itemsize += 1;
} }
IncorrectFormat => { IncorrectFormat => {
(*view).format = CStr::from_bytes_with_nul(b"B\0").unwrap().as_ptr() as _; (*view).format = ffi::c_str!("B").as_ptr() as _;
} }
IncorrectAlignment => (*view).buf = (*view).buf.add(1), IncorrectAlignment => (*view).buf = (*view).buf.add(1),
} }

View File

@ -4,6 +4,7 @@ use std::collections::HashMap;
#[cfg(not(Py_LIMITED_API))] #[cfg(not(Py_LIMITED_API))]
use pyo3::buffer::PyBuffer; use pyo3::buffer::PyBuffer;
use pyo3::ffi::c_str;
use pyo3::prelude::*; use pyo3::prelude::*;
#[cfg(not(Py_LIMITED_API))] #[cfg(not(Py_LIMITED_API))]
use pyo3::types::PyDateTime; use pyo3::types::PyDateTime;
@ -344,8 +345,8 @@ fn test_pycfunction_new() {
let py_fn = PyCFunction::new_bound( let py_fn = PyCFunction::new_bound(
py, py,
c_fn, c_fn,
"py_fn", c_str!("py_fn"),
"py_fn for test (this is the docstring)", c_str!("py_fn for test (this is the docstring)"),
None, None,
) )
.unwrap(); .unwrap();
@ -402,8 +403,8 @@ fn test_pycfunction_new_with_keywords() {
let py_fn = PyCFunction::new_with_keywords_bound( let py_fn = PyCFunction::new_with_keywords_bound(
py, py,
c_fn, c_fn,
"py_fn", c_str!("py_fn"),
"py_fn for test (this is the docstring)", c_str!("py_fn for test (this is the docstring)"),
None, None,
) )
.unwrap(); .unwrap();
@ -443,8 +444,13 @@ fn test_closure() {
Ok(res) Ok(res)
}) })
}; };
let closure_py = let closure_py = PyCFunction::new_closure_bound(
PyCFunction::new_closure_bound(py, Some("test_fn"), Some("test_fn doc"), f).unwrap(); py,
Some(c_str!("test_fn")),
Some(c_str!("test_fn doc")),
f,
)
.unwrap();
py_assert!(py, closure_py, "closure_py(42) == [43]"); py_assert!(py, closure_py, "closure_py(42) == [43]");
py_assert!(py, closure_py, "closure_py.__name__ == 'test_fn'"); py_assert!(py, closure_py, "closure_py.__name__ == 'test_fn'");