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::{mem, ptr};
use std::ffi::CString;
use std::os::raw::{c_char, c_int, c_uint, c_ulonglong, c_void};
use pyo3_ffi::*;
@ -27,10 +28,10 @@ unsafe extern "C" fn id_new(
kwds: *mut PyObject,
) -> *mut PyObject {
if PyTuple_Size(args) != 0 || !kwds.is_null() {
PyErr_SetString(
PyExc_TypeError,
"Id() takes no arguments\0".as_ptr().cast::<c_char>(),
);
// We use pyo3-ffi's `c_str!` macro to create null-terminated literals because
// Rust's string literals are not null-terminated
// 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();
}
@ -81,8 +82,12 @@ unsafe extern "C" fn id_richcompare(
pyo3_ffi::Py_GT => slf > other,
pyo3_ffi::Py_GE => slf >= other,
unrecognized => {
let msg = format!("unrecognized richcompare opcode {}\0", unrecognized);
PyErr_SetString(PyExc_SystemError, msg.as_ptr().cast::<c_char>());
let msg = CString::new(&*format!(
"unrecognized richcompare opcode {}",
unrecognized
))
.unwrap();
PyErr_SetString(PyExc_SystemError, msg.as_ptr());
return ptr::null_mut();
}
};
@ -101,7 +106,7 @@ static mut SLOTS: &[PyType_Slot] = &[
},
PyType_Slot {
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,
},
PyType_Slot {
@ -123,7 +128,7 @@ static mut SLOTS: &[PyType_Slot] = &[
];
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,
itemsize: 0,
flags: (Py_TPFLAGS_DEFAULT | Py_TPFLAGS_IMMUTABLETYPE) as c_uint,

View File

@ -1,13 +1,11 @@
use core::{mem, ptr};
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 {
m_base: PyModuleDef_HEAD_INIT,
m_name: "sequential\0".as_ptr().cast::<c_char>(),
m_doc: "A library for generating sequential ids, written in Rust.\0"
.as_ptr()
.cast::<c_char>(),
m_name: c_str!("sequential").as_ptr(),
m_doc: c_str!("A library for generating sequential ids, written in Rust.").as_ptr(),
m_size: mem::size_of::<sequential_state>() as Py_ssize_t,
m_methods: std::ptr::null_mut(),
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() {
PyErr_SetString(
PyExc_SystemError,
"cannot locate type object\0".as_ptr().cast::<c_char>(),
c_str!("cannot locate type object").as_ptr(),
);
return -1;
}
(*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(

View File

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

View File

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

View File

@ -330,8 +330,8 @@ or [`PyRefMut`] instead of `&mut self`.
Then you can access a parent class by `self_.as_super()` as `&PyRef<Self::BaseClass>`,
or by `self_.into_super()` as `PyRef<Self::BaseClass>` (and similar for the `PyRefMut`
case). For convenience, `self_.as_ref()` can also be used to get `&Self::BaseClass`
directly; however, this approach does not let you access base clases higher in the
inheritance hierarchy, for which you would need to chain multiple `as_super` or
directly; however, this approach does not let you access base clases higher in the
inheritance hierarchy, for which you would need to chain multiple `as_super` or
`into_super` calls.
```rust
@ -400,7 +400,7 @@ impl SubSubClass {
let val2 = self_.as_super().val2;
(val1, val2, self_.val3)
}
fn double_values(mut self_: PyRefMut<'_, Self>) {
self_.as_super().as_super().val1 *= 2;
self_.as_super().val2 *= 2;
@ -1187,7 +1187,7 @@ Python::with_gil(|py| {
})
```
Ordering of enum variants is optionally added using `#[pyo3(ord)]`.
Ordering of enum variants is optionally added using `#[pyo3(ord)]`.
*Note: Implementation of the `PartialOrd` trait is required when passing the `ord` argument. If not implemented, a compile time error is raised.*
```rust
@ -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();
DOC.get_or_try_init(py, || {
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)
}
}

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

View File

@ -114,7 +114,7 @@ extern "C" {
#[cfg(not(any(Py_3_8, PyPy)))]
#[inline]
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" {

View File

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

View File

@ -88,10 +88,8 @@
//!
//! static mut MODULE_DEF: PyModuleDef = PyModuleDef {
//! m_base: PyModuleDef_HEAD_INIT,
//! m_name: "string_sum\0".as_ptr().cast::<c_char>(),
//! m_doc: "A Python module written in Rust.\0"
//! .as_ptr()
//! .cast::<c_char>(),
//! m_name: c_str!("string_sum").as_ptr(),
//! m_doc: c_str!("A Python module written in Rust.").as_ptr(),
//! m_size: 0,
//! m_methods: unsafe { METHODS.as_mut_ptr().cast() },
//! m_slots: std::ptr::null_mut(),
@ -102,14 +100,12 @@
//!
//! static mut METHODS: [PyMethodDef; 2] = [
//! PyMethodDef {
//! ml_name: "sum_as_string\0".as_ptr().cast::<c_char>(),
//! ml_name: c_str!("sum_as_string").as_ptr(),
//! ml_meth: PyMethodDefPointer {
//! _PyCFunctionFast: sum_as_string,
//! },
//! ml_flags: METH_FASTCALL,
//! ml_doc: "returns the sum of two integers as a string\0"
//! .as_ptr()
//! .cast::<c_char>(),
//! ml_doc: c_str!("returns the sum of two integers as a string").as_ptr(),
//! },
//! // A zeroed PyMethodDef to mark the end of the array.
//! PyMethodDef::zeroed()
@ -130,9 +126,7 @@
//! if nargs != 2 {
//! PyErr_SetString(
//! PyExc_TypeError,
//! "sum_as_string() expected 2 positional arguments\0"
//! .as_ptr()
//! .cast::<c_char>(),
//! c_str!("sum_as_string() expected 2 positional arguments").as_ptr(),
//! );
//! return std::ptr::null_mut();
//! }
@ -141,9 +135,7 @@
//! if PyLong_Check(arg1) == 0 {
//! PyErr_SetString(
//! PyExc_TypeError,
//! "sum_as_string() expected an int for positional argument 1\0"
//! .as_ptr()
//! .cast::<c_char>(),
//! c_str!("sum_as_string() expected an int for positional argument 1").as_ptr(),
//! );
//! return std::ptr::null_mut();
//! }
@ -157,9 +149,7 @@
//! if PyLong_Check(arg2) == 0 {
//! PyErr_SetString(
//! PyExc_TypeError,
//! "sum_as_string() expected an int for positional argument 2\0"
//! .as_ptr()
//! .cast::<c_char>(),
//! c_str!("sum_as_string() expected an int for positional argument 2").as_ptr(),
//! );
//! return std::ptr::null_mut();
//! }
@ -177,7 +167,7 @@
//! None => {
//! PyErr_SetString(
//! 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()
//! }
@ -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::bltinmodule::*;
pub use self::boolobject::*;

View File

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

View File

@ -29,9 +29,10 @@ impl ConstSpec<'_> {
}
/// Null-terminated Python name
pub fn null_terminated_python_name(&self) -> TokenStream {
let name = format!("{}\0", self.python_name());
quote!({#name})
pub fn null_terminated_python_name(&self, ctx: &Ctx) -> TokenStream {
let Ctx { pyo3_path } = ctx;
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 {
syn::LitStr::new(&format!("{}\0", self.python_name), self.python_name.span())
pub fn null_terminated_python_name(&self, ctx: &Ctx) -> TokenStream {
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(
@ -830,7 +834,7 @@ impl<'a> FnSpec<'a> {
/// calling convention.
pub fn get_methoddef(&self, wrapper: impl ToTokens, doc: &PythonDoc, ctx: &Ctx) -> TokenStream {
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 {
CallingConvention::Noargs => quote! {
#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.
pub fn get_doc(&self, attrs: &[syn::Attribute]) -> PythonDoc {
pub fn get_doc(&self, attrs: &[syn::Attribute], ctx: &Ctx) -> PythonDoc {
let text_signature = self
.text_signature_call_signature()
.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

View File

@ -92,7 +92,7 @@ pub fn pymodule_module_impl(mut module: syn::ItemMod) -> Result<TokenStream> {
let options = PyModuleOptions::from_attrs(attrs)?;
let ctx = &Ctx::new(&options.krate);
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 full_name = if let Some(module) = &options.module {
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 name = options.name.unwrap_or_else(|| ident.unraw());
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);
@ -402,10 +402,11 @@ pub fn pymodule_function_impl(mut function: syn::ItemFn) -> Result<TokenStream>
fn module_initialization(name: &syn::Ident, ctx: &Ctx) -> TokenStream {
let Ctx { pyo3_path } = ctx;
let pyinit_symbol = format!("PyInit_{}", name);
let name = name.to_string();
quote! {
#[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;
#[doc(hidden)]

View File

@ -1,7 +1,7 @@
use std::borrow::Cow;
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::parse::{Parse, ParseStream};
use syn::punctuated::Punctuated;
@ -21,9 +21,10 @@ use crate::pymethod::{
impl_py_getter_def, impl_py_setter_def, MethodAndMethodDef, MethodAndSlotDef, PropertyType,
SlotDef, __GETITEM__, __HASH__, __INT__, __LEN__, __REPR__, __RICHCMP__,
};
use crate::pyversions;
use crate::utils::{self, apply_renaming_rule, PythonDoc};
use crate::utils::{is_abi3, Ctx};
use crate::{pyversions, PyFunctionOptions};
use crate::PyFunctionOptions;
/// If the class is derived from a Rust `struct` or `enum`.
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
@ -225,9 +226,9 @@ pub fn build_py_class(
methods_type: PyClassMethodsType,
) -> syn::Result<TokenStream> {
args.options.take_pyo3_options(&mut class.attrs)?;
let doc = utils::get_doc(&class.attrs, None);
let ctx = &Ctx::new(&args.options.krate);
let doc = utils::get_doc(&class.attrs, None, ctx);
if let Some(lt) = class.generics.lifetimes().next() {
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");
}
let doc = utils::get_doc(&enum_.attrs, None);
let doc = utils::get_doc(&enum_.attrs, None, ctx);
let enum_ = PyClassEnum::new(enum_)?;
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 wrapper_ident = format_ident!("__pymethod_variant_cls_{}__", member);
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 associated_method = quote! {
@ -1580,7 +1581,7 @@ fn complex_enum_variant_field_getter<'a>(
let property_type = crate::pymethod::PropertyType::Function {
self_type: &self_type,
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)?;
@ -2014,7 +2015,10 @@ impl<'a> PyClassImplsBuilder<'a> {
fn impl_pyclassimpl(&self, ctx: &Ctx) -> Result<TokenStream> {
let Ctx { pyo3_path } = ctx;
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 base = match &self.attr.options.extends {
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 = 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! {

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 wrapper_ident = format_ident!("__pymethod_{}__", member);
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 associated_method = quote! {

View File

@ -227,21 +227,21 @@ pub fn gen_py_method(
(_, FnType::Fn(_)) => GeneratedPyMethod::Method(impl_py_method_def(
cls,
spec,
&spec.get_doc(meth_attrs),
&spec.get_doc(meth_attrs, ctx),
None,
ctx,
)?),
(_, FnType::FnClass(_)) => GeneratedPyMethod::Method(impl_py_method_def(
cls,
spec,
&spec.get_doc(meth_attrs),
&spec.get_doc(meth_attrs, ctx),
Some(quote!(#pyo3_path::ffi::METH_CLASS)),
ctx,
)?),
(_, FnType::FnStatic) => GeneratedPyMethod::Method(impl_py_method_def(
cls,
spec,
&spec.get_doc(meth_attrs),
&spec.get_doc(meth_attrs, ctx),
Some(quote!(#pyo3_path::ffi::METH_STATIC)),
ctx,
)?),
@ -255,7 +255,7 @@ pub fn gen_py_method(
PropertyType::Function {
self_type,
spec,
doc: spec.get_doc(meth_attrs),
doc: spec.get_doc(meth_attrs, ctx),
},
ctx,
)?),
@ -264,7 +264,7 @@ pub fn gen_py_method(
PropertyType::Function {
self_type,
spec,
doc: spec.get_doc(meth_attrs),
doc: spec.get_doc(meth_attrs, ctx),
},
ctx,
)?),
@ -499,7 +499,7 @@ fn impl_py_class_attribute(
};
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 associated_method = quote! {
@ -560,8 +560,8 @@ pub fn impl_py_setter_def(
ctx: &Ctx,
) -> Result<MethodAndMethodDef> {
let Ctx { pyo3_path } = ctx;
let python_name = property_type.null_terminated_python_name()?;
let doc = property_type.doc();
let python_name = property_type.null_terminated_python_name(ctx)?;
let doc = property_type.doc(ctx);
let mut holders = Holders::new();
let setter_impl = match property_type {
PropertyType::Descriptor {
@ -746,8 +746,8 @@ pub fn impl_py_getter_def(
ctx: &Ctx,
) -> Result<MethodAndMethodDef> {
let Ctx { pyo3_path } = ctx;
let python_name = property_type.null_terminated_python_name()?;
let doc = property_type.doc();
let python_name = property_type.null_terminated_python_name(ctx)?;
let doc = property_type.doc(ctx);
let mut holders = Holders::new();
let body = match property_type {
@ -870,7 +870,8 @@ pub enum PropertyType<'a> {
}
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 {
PropertyType::Descriptor {
field,
@ -885,23 +886,22 @@ impl PropertyType<'_> {
if let Some(rule) = renaming_rule {
name = utils::apply_renaming_rule(*rule, &name);
}
name.push('\0');
name
}
(None, None) => {
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 {
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),
}

View File

@ -1,5 +1,5 @@
use proc_macro2::{Span, TokenStream};
use quote::ToTokens;
use quote::{quote, ToTokens};
use syn::{punctuated::Punctuated, Token};
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
/// this to the documentation suitable for Python to extract this into the `__text_signature__`
/// 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
// (assume text_signature is itself well-formed)
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
if !current_part.is_empty() {
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| {
parts.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 {
// Just a string doc - return directly with nul terminator
current_part.push('\0');
PythonDoc(current_part.to_token_stream())
}
current_part.to_token_stream()
};
PythonDoc(quote!(#pyo3_path::ffi::c_str!(#tokens)))
}
impl quote::ToTokens for PythonDoc {

View File

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

View File

@ -64,28 +64,15 @@ macro_rules! rational_conversion {
impl<'py> FromPyObject<'py> for Ratio<$int> {
fn extract_bound(obj: &Bound<'py, PyAny>) -> PyResult<Self> {
let py = obj.py();
let py_numerator_obj = unsafe {
Bound::from_owned_ptr_or_err(
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 py_numerator_obj = obj.getattr(crate::intern!(py, "numerator"))?;
let py_denominator_obj = obj.getattr(crate::intern!(py, "denominator"))?;
let numerator_owned = unsafe {
Bound::from_owned_ptr_or_err(
py,
ffi::PyNumber_Long(py_numerator_obj?.as_ptr()),
)?
Bound::from_owned_ptr_or_err(py, ffi::PyNumber_Long(py_numerator_obj.as_ptr()))?
};
let denominator_owned = unsafe {
Bound::from_owned_ptr_or_err(
py,
ffi::PyNumber_Long(py_denominator_obj?.as_ptr()),
ffi::PyNumber_Long(py_denominator_obj.as_ptr()),
)?
};
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 {
ffi::PyErr_SetString(
PyTypeError::type_object_raw(py).cast(),
"exceptions must derive from BaseException\0"
.as_ptr()
.cast(),
ffi::c_str!("exceptions must derive from BaseException").as_ptr(),
)
} else {
ffi::PyErr_SetObject(ptype.as_ptr(), pvalue.as_ptr())

View File

@ -736,10 +736,10 @@ impl PyUnicodeDecodeError {
let pos = err.valid_up_to();
PyUnicodeDecodeError::new_bound(
py,
CStr::from_bytes_with_nul(b"utf-8\0").unwrap(),
ffi::c_str!("utf-8"),
input,
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,
impl_::freelist::FreeList,
impl_::pycell::{GetBorrowChecker, PyClassMutability, PyClassObjectLayout},
internal_tricks::extract_c_string,
pyclass_init::PyObjectInit,
types::any::PyAnyMethods,
types::PyBool,
@ -214,7 +213,7 @@ pub trait PyClassImpl: Sized + 'static {
/// specialization in to the `#[pyclass]` macro from the `#[pymethods]` macro.
pub fn build_pyclass_doc(
class_name: &'static str,
doc: &'static str,
doc: &'static CStr,
text_signature: Option<&'static str>,
) -> PyResult<Cow<'static, CStr>> {
if let Some(text_signature) = text_signature {
@ -222,12 +221,12 @@ pub fn build_pyclass_doc(
"{}{}\n--\n\n{}",
class_name,
text_signature,
doc.trim_end_matches('\0')
doc.to_str().unwrap(),
))
.map_err(|_| PyValueError::new_err("class doc cannot contain nul bytes"))?;
Ok(Cow::Owned(doc))
} else {
extract_c_string(doc, "class doc cannot contain nul bytes")
Ok(Cow::Borrowed(doc))
}
}

View File

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

View File

@ -2,7 +2,6 @@ use crate::callback::IntoPyCallbackOutput;
use crate::exceptions::PyStopAsyncIteration;
use crate::gil::LockGIL;
use crate::impl_::panic::PanicTrap;
use crate::internal_tricks::extract_c_string;
use crate::pycell::{PyBorrowError, PyBorrowMutError};
use crate::pyclass::boolean_struct::False;
use crate::types::any::PyAnyMethods;
@ -12,7 +11,6 @@ use crate::{
ffi, Borrowed, Bound, DowncastError, Py, PyAny, PyClass, PyClassInitializer, PyErr, PyObject,
PyRef, PyRefMut, PyResult, PyTraverseError, PyTypeCheck, PyVisit, Python,
};
use std::borrow::Cow;
use std::ffi::CStr;
use std::fmt;
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)]
pub struct PyMethodDef {
pub(crate) ml_name: &'static str,
pub(crate) ml_name: &'static CStr,
pub(crate) ml_meth: PyMethodType,
pub(crate) ml_flags: c_int,
pub(crate) ml_doc: &'static str,
pub(crate) ml_doc: &'static CStr,
}
#[derive(Copy, Clone)]
pub struct PyClassAttributeDef {
pub(crate) name: &'static str,
pub(crate) name: &'static CStr,
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)]
pub struct PyGetterDef {
pub(crate) name: &'static str,
pub(crate) name: &'static CStr,
pub(crate) meth: Getter,
pub(crate) doc: &'static str,
pub(crate) doc: &'static CStr,
}
#[derive(Clone)]
pub struct PySetterDef {
pub(crate) name: &'static str,
pub(crate) name: &'static CStr,
pub(crate) meth: Setter,
pub(crate) doc: &'static str,
pub(crate) doc: &'static CStr,
}
unsafe impl Sync for PyMethodDef {}
@ -125,44 +117,44 @@ unsafe impl Sync for PySetterDef {}
impl PyMethodDef {
/// Define a function with no `*args` and `**kwargs`.
pub const fn noargs(
name: &'static str,
ml_name: &'static CStr,
cfunction: ffi::PyCFunction,
doc: &'static str,
ml_doc: &'static CStr,
) -> Self {
Self {
ml_name: name,
ml_name,
ml_meth: PyMethodType::PyCFunction(cfunction),
ml_flags: ffi::METH_NOARGS,
ml_doc: doc,
ml_doc,
}
}
/// Define a function that can take `*args` and `**kwargs`.
pub const fn cfunction_with_keywords(
name: &'static str,
ml_name: &'static CStr,
cfunction: ffi::PyCFunctionWithKeywords,
doc: &'static str,
ml_doc: &'static CStr,
) -> Self {
Self {
ml_name: name,
ml_name,
ml_meth: PyMethodType::PyCFunctionWithKeywords(cfunction),
ml_flags: ffi::METH_VARARGS | ffi::METH_KEYWORDS,
ml_doc: doc,
ml_doc,
}
}
/// Define a function that can take `*args` and `**kwargs`.
#[cfg(not(Py_LIMITED_API))]
pub const fn fastcall_cfunction_with_keywords(
name: &'static str,
ml_name: &'static CStr,
cfunction: ffi::_PyCFunctionFastWithKeywords,
doc: &'static str,
ml_doc: &'static CStr,
) -> Self {
Self {
ml_name: name,
ml_name,
ml_meth: PyMethodType::PyCFunctionFastWithKeywords(cfunction),
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`
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 {
PyMethodType::PyCFunction(meth) => ffi::PyMethodDefPointer { PyCFunction: meth },
PyMethodType::PyCFunctionWithKeywords(meth) => ffi::PyMethodDefPointer {
@ -184,22 +176,18 @@ impl PyMethodDef {
},
};
let name = get_name(self.ml_name)?;
let doc = get_doc(self.ml_doc)?;
let def = ffi::PyMethodDef {
ml_name: name.as_ptr(),
ffi::PyMethodDef {
ml_name: self.ml_name.as_ptr(),
ml_meth: meth,
ml_flags: self.ml_flags,
ml_doc: doc.as_ptr(),
};
let destructor = PyMethodDefDestructor { name, doc };
Ok((def, destructor))
ml_doc: self.ml_doc.as_ptr(),
}
}
}
impl PyClassAttributeDef {
/// 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 }
}
}
@ -222,7 +210,7 @@ pub(crate) type Setter =
impl PyGetterDef {
/// 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 {
name,
meth: getter,
@ -233,7 +221,7 @@ impl PyGetterDef {
impl PySetterDef {
/// 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 {
name,
meth: setter,
@ -284,22 +272,6 @@ where
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`
pub struct IterBaseTag;

View File

@ -1,6 +1,6 @@
//! 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(
not(any(PyPy, GraalPy)),
@ -49,12 +49,9 @@ unsafe impl Sync for ModuleDef {}
impl ModuleDef {
/// Make new module definition with given module name.
///
/// # Safety
/// `name` and `doc` must be null-terminated strings.
pub const unsafe fn new(
name: &'static str,
doc: &'static str,
name: &'static CStr,
doc: &'static CStr,
initializer: ModuleInitializer,
) -> Self {
const INIT: ffi::PyModuleDef = ffi::PyModuleDef {
@ -70,8 +67,8 @@ impl ModuleDef {
};
let ffi_def = UnsafeCell::new(ffi::PyModuleDef {
m_name: name.as_ptr().cast(),
m_doc: doc.as_ptr().cast(),
m_name: name.as_ptr(),
m_doc: doc.as_ptr(),
..INIT
});
@ -215,10 +212,12 @@ impl PyAddToModule for ModuleDef {
mod tests {
use std::{
borrow::Cow,
ffi::CStr,
sync::atomic::{AtomicBool, Ordering},
};
use crate::{
ffi,
types::{any::PyAnyMethods, module::PyModuleMethods, PyModule},
Bound, PyResult, Python,
};
@ -229,8 +228,8 @@ mod tests {
fn module_init() {
static MODULE_DEF: ModuleDef = unsafe {
ModuleDef::new(
"test_module\0",
"some doc\0",
ffi::c_str!("test_module"),
ffi::c_str!("some doc"),
ModuleInitializer(|m| {
m.add("SOME_CONSTANT", 42)?;
Ok(())
@ -270,8 +269,8 @@ mod tests {
fn module_def_new() {
// 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.
static NAME: &str = "test_module\0";
static DOC: &str = "some doc\0";
static NAME: &CStr = ffi::c_str!("test_module");
static DOC: &CStr = ffi::c_str!("some doc");
static INIT_CALLED: AtomicBool = AtomicBool::new(false);

View File

@ -1,13 +1,4 @@
use std::{
borrow::Cow,
ffi::{CStr, CString},
};
use crate::{
exceptions::PyValueError,
ffi::{Py_ssize_t, PY_SSIZE_T_MAX},
PyResult,
};
use crate::ffi::{Py_ssize_t, PY_SSIZE_T_MAX};
pub struct PrivateMarker;
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);
}
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
#[inline]
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(
$module::__PYO3_NAME.as_ptr().cast(),
$module::__PYO3_NAME.as_ptr(),
::std::option::Option::Some($module::__pyo3_init),
);
}

View File

@ -652,7 +652,7 @@ impl<'py> Python<'py> {
) -> PyResult<Bound<'py, PyAny>> {
let code = CString::new(code)?;
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() {
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() {
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,
tp_dealloc_with_gc, PyClassItemsIter,
},
pymethods::{get_doc, get_name, Getter, Setter},
pymethods::{Getter, Setter},
trampoline::trampoline,
},
internal_tricks::ptr_from_ref,
@ -15,7 +15,6 @@ use crate::{
Py, PyClass, PyGetterDef, PyMethodDefType, PyResult, PySetterDef, PyTypeInfo, Python,
};
use std::{
borrow::Cow,
collections::HashMap,
ffi::{CStr, CString},
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 {
slots: Vec<ffi::PyType_Slot>,
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
/// PyType_FromSpec API for... there's no reason this should work,
/// except for that it does and we have tests.
@ -173,32 +172,25 @@ impl PyTypeBuilder {
fn pymethod_def(&mut self, def: &PyMethodDefType) {
match def {
PyMethodDefType::Getter(getter) => {
self.getset_builders
.entry(getter.name)
.or_default()
.add_getter(getter);
}
PyMethodDefType::Setter(setter) => {
self.getset_builders
.entry(setter.name)
.or_default()
.add_setter(setter);
}
PyMethodDefType::Getter(getter) => self
.getset_builders
.entry(getter.name)
.or_default()
.add_getter(getter),
PyMethodDefType::Setter(setter) => self
.getset_builders
.entry(setter.name)
.or_default()
.add_setter(setter),
PyMethodDefType::Method(def)
| PyMethodDefType::Class(def)
| PyMethodDefType::Static(def) => {
let (def, destructor) = def.as_method_def().unwrap();
// FIXME: stop leaking destructor
std::mem::forget(destructor);
self.method_defs.push(def);
}
| PyMethodDefType::Static(def) => self.method_defs.push(def.as_method_def()),
// These class attributes are added after the type gets created by LazyStaticType
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);
// Safety: Py_tp_methods expects a raw vec of PyMethodDef
unsafe { self.push_raw_vec_slot(ffi::Py_tp_methods, method_defs) };
@ -210,11 +202,11 @@ impl PyTypeBuilder {
.getset_builders
.iter()
.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);
Ok(def)
def
})
.collect::<PyResult<_>>()?;
.collect();
// PyPy automatically adds __dict__ getter / setter.
#[cfg(not(PyPy))]
@ -261,7 +253,7 @@ impl PyTypeBuilder {
}
property_defs.push(ffi::PyGetSetDef {
name: "__dict__\0".as_ptr().cast(),
name: ffi::c_str!("__dict__").as_ptr(),
get: Some(get_dict),
set: Some(ffi::PyObject_GenericSetDict),
doc: ptr::null(),
@ -300,7 +292,7 @@ impl PyTypeBuilder {
}
}
Ok(getset_destructors)
getset_destructors
}
fn set_is_basetype(mut self, is_basetype: bool) -> Self {
@ -358,7 +350,7 @@ impl PyTypeBuilder {
#[cfg(Py_3_9)]
{
#[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 {
name: name.as_ptr().cast(),
type_code: ffi::Py_T_PYSSIZET,
@ -372,12 +364,15 @@ impl PyTypeBuilder {
// __dict__ support
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
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
@ -417,7 +412,7 @@ impl PyTypeBuilder {
// on some platforms (like windows)
#![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) }
@ -531,7 +526,7 @@ unsafe extern "C" fn no_constructor_defined(
#[derive(Default)]
struct GetSetDefBuilder {
doc: Option<&'static str>,
doc: Option<&'static CStr>,
getter: Option<Getter>,
setter: Option<Setter>,
}
@ -555,13 +550,7 @@ impl GetSetDefBuilder {
self.setter = Some(setter.meth)
}
fn as_get_set_def(
&self,
name: &'static str,
) -> PyResult<(ffi::PyGetSetDef, GetSetDefDestructor)> {
let name = get_name(name)?;
let doc = self.doc.map(get_doc).transpose()?;
fn as_get_set_def(&self, name: &'static CStr) -> (ffi::PyGetSetDef, GetSetDefDestructor) {
let getset_type = match (self.getter, self.setter) {
(Some(getter), None) => GetSetDefType::Getter(getter),
(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 {
name,
doc,
closure: getset_type,
};
Ok((getset_def, destructor))
(getset_def, destructor)
}
}
#[allow(dead_code)] // a stack of fields which are purely to cache until dropped
struct GetSetDefDestructor {
name: Cow<'static, CStr>,
doc: Option<Cow<'static, CStr>>,
closure: GetSetDefType,
}

View File

@ -8,7 +8,7 @@ use crate::types::module::PyModuleMethods;
use crate::PyNativeType;
use crate::{
ffi,
impl_::pymethods::{self, PyMethodDef, PyMethodDefDestructor},
impl_::pymethods::{self, PyMethodDef},
types::{PyCapsule, PyDict, PyModule, PyString, PyTuple},
};
use crate::{Bound, IntoPy, Py, PyAny, PyResult, Python};
@ -30,8 +30,8 @@ impl PyCFunction {
)]
pub fn new_with_keywords<'a>(
fun: ffi::PyCFunctionWithKeywords,
name: &'static str,
doc: &'static str,
name: &'static CStr,
doc: &'static CStr,
py_or_module: PyFunctionArguments<'a>,
) -> PyResult<&'a Self> {
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).
///
/// 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>(
py: Python<'py>,
fun: ffi::PyCFunctionWithKeywords,
name: &'static str,
doc: &'static str,
name: &'static CStr,
doc: &'static CStr,
module: Option<&Bound<'py, PyModule>>,
) -> PyResult<Bound<'py, Self>> {
Self::internal_new(
@ -66,8 +69,8 @@ impl PyCFunction {
)]
pub fn new<'a>(
fun: ffi::PyCFunction,
name: &'static str,
doc: &'static str,
name: &'static CStr,
doc: &'static CStr,
py_or_module: PyFunctionArguments<'a>,
) -> PyResult<&'a Self> {
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.
///
/// 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>(
py: Python<'py>,
fun: ffi::PyCFunction,
name: &'static str,
doc: &'static str,
name: &'static CStr,
doc: &'static CStr,
module: Option<&Bound<'py, PyModule>>,
) -> PyResult<Bound<'py, Self>> {
Self::internal_new(py, &PyMethodDef::noargs(name, fun, doc), module)
@ -98,8 +104,8 @@ impl PyCFunction {
)]
pub fn new_closure<'a, F, R>(
py: Python<'a>,
name: Option<&'static str>,
doc: Option<&'static str>,
name: Option<&'static CStr>,
doc: Option<&'static CStr>,
closure: F,
) -> PyResult<&'a PyCFunction>
where
@ -131,29 +137,27 @@ impl PyCFunction {
/// ```
pub fn new_closure_bound<'py, F, R>(
py: Python<'py>,
name: Option<&'static str>,
doc: Option<&'static str>,
name: Option<&'static CStr>,
doc: Option<&'static CStr>,
closure: F,
) -> PyResult<Bound<'py, Self>>
where
F: Fn(&Bound<'_, PyTuple>, Option<&Bound<'_, PyDict>>) -> R + Send + 'static,
R: crate::callback::IntoPyCallbackOutput<*mut ffi::PyObject>,
{
let method_def = pymethods::PyMethodDef::cfunction_with_keywords(
name.unwrap_or("pyo3-closure\0"),
run_closure::<F, R>,
doc.unwrap_or("\0"),
);
let (def, def_destructor) = method_def.as_method_def()?;
let name = name.unwrap_or(ffi::c_str!("pyo3-closure"));
let doc = doc.unwrap_or(ffi::c_str!(""));
let method_def =
pymethods::PyMethodDef::cfunction_with_keywords(name, run_closure::<F, R>, doc);
let def = method_def.as_method_def();
let capsule = PyCapsule::new_bound(
py,
ClosureDestructor::<F> {
closure,
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
@ -178,11 +182,10 @@ impl PyCFunction {
} else {
(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));
std::mem::forget(destructor);
let module_name_ptr = module_name
.as_ref()
@ -196,10 +199,7 @@ impl PyCFunction {
}
}
fn closure_capsule_name() -> &'static CStr {
// TODO replace this with const CStr once MSRV new enough
CStr::from_bytes_with_nul(b"pyo3-closure\0").unwrap()
}
static CLOSURE_CAPSULE_NAME: &CStr = ffi::c_str!("pyo3-closure");
unsafe extern "C" fn run_closure<F, R>(
capsule_ptr: *mut ffi::PyObject,
@ -218,7 +218,7 @@ where
kwargs,
|py, capsule_ptr, args, kwargs| {
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>);
let args = Bound::ref_from_ptr(py, &args).downcast_unchecked::<PyTuple>();
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
// to this member.
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

View File

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

View File

@ -3,7 +3,6 @@
use pyo3::{buffer::PyBuffer, exceptions::PyBufferError, ffi, prelude::*};
use std::{
ffi::CStr,
os::raw::{c_int, c_void},
ptr,
};
@ -48,7 +47,7 @@ impl TestBufferErrors {
(*view).readonly = 1;
(*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).ndim = 1;
@ -72,7 +71,7 @@ impl TestBufferErrors {
(*view).itemsize += 1;
}
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),
}

View File

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