Restore compatibility with Rust 1.41.

This version is currently supported by Debian stable and Alpine Linux.

Fixes #1420
This commit is contained in:
Georg Brandl 2021-02-10 16:07:25 +01:00
parent 479d67a1eb
commit fa8d7518ca
16 changed files with 98 additions and 56 deletions

View File

@ -58,7 +58,7 @@ jobs:
platform: { os: "windows-latest", python-architecture: "x64" }
include:
# Test minimal supported Rust version
- rust: 1.45.0
- rust: 1.41.1
python-version: 3.9
platform: { os: "ubuntu-latest", python-architecture: "x64", rust-target: "x86_64-unknown-linux-gnu" }
msrv: "MSRV"

View File

@ -17,13 +17,15 @@ edition = "2018"
[dependencies]
cfg-if = { version = "1.0" }
ctor = { version = "0.1", optional = true }
indoc = { version = "1.0.3", optional = true }
# must stay at 0.3.x for Rust 1.41 compatibility
indoc = { version = "0.3.6", optional = true }
inventory = { version = "0.1.4", optional = true }
libc = "0.2.62"
parking_lot = "0.11.0"
num-bigint = { version = "0.3", optional = true }
num-complex = { version = "0.3", optional = true }
paste = { version = "1.0.3", optional = true }
# must stay at 0.1.x for Rust 1.41 compatibility
paste = { version = "0.1.18", optional = true }
pyo3-macros = { path = "pyo3-macros", version = "=0.13.1", optional = true }
unindent = { version = "0.1.4", optional = true }
hashbrown = { version = "0.9", optional = true }

View File

@ -3,7 +3,7 @@
[![Actions Status](https://github.com/PyO3/pyo3/workflows/Test/badge.svg)](https://github.com/PyO3/pyo3/actions)
[![codecov](https://codecov.io/gh/PyO3/pyo3/branch/master/graph/badge.svg)](https://codecov.io/gh/PyO3/pyo3)
[![crates.io](http://meritbadge.herokuapp.com/pyo3)](https://crates.io/crates/pyo3)
[![minimum rustc 1.45](https://img.shields.io/badge/rustc-1.45+-blue.svg)](https://rust-lang.github.io/rfcs/2495-min-rust-version.html)
[![minimum rustc 1.41](https://img.shields.io/badge/rustc-1.41+-blue.svg)](https://rust-lang.github.io/rfcs/2495-min-rust-version.html)
[![Join the dev chat](https://img.shields.io/gitter/room/nwjs/nw.js.svg)](https://gitter.im/PyO3/Lobby)
[Rust](http://www.rust-lang.org/) bindings for [Python](https://www.python.org/). This includes running and interacting with Python code from a Rust binary, as well as writing native Python modules.
@ -18,7 +18,7 @@ A comparison with rust-cpython can be found [in the guide](https://pyo3.rs/maste
## Usage
PyO3 supports Python 3.6 and up. The minimum required Rust version is 1.45.0.
PyO3 supports Python 3.6 and up. The minimum required Rust version is 1.41.
Building with PyPy is also possible (via cpyext) for Python 3.6, targeted PyPy version is 7.3+.
Please refer to the [pypy section in the guide](https://pyo3.rs/master/pypy.html).

View File

@ -115,6 +115,7 @@ pub fn parse_method_receiver(arg: &syn::FnArg) -> syn::Result<SelfType> {
impl<'a> FnSpec<'a> {
/// Parser function signature and function attributes
#[allow(clippy::manual_strip)] // for strip_prefix replacement supporting rust < 1.45
pub fn parse(
sig: &'a syn::Signature,
meth_attrs: &mut Vec<syn::Attribute>,
@ -139,10 +140,12 @@ impl<'a> FnSpec<'a> {
// strip get_ or set_
let strip_fn_name = |prefix: &'static str| {
name.unraw()
.to_string()
.strip_prefix(prefix)
.map(|rest| syn::Ident::new(rest, name.span()))
let ident = name.unraw().to_string();
if ident.starts_with(prefix) {
Some(syn::Ident::new(&ident[prefix.len()..], ident.span()))
} else {
None
}
};
// Parse receiver & function type for various method types

View File

@ -174,8 +174,8 @@ pub fn parse_name_attribute(attrs: &mut Vec<syn::Attribute>) -> syn::Result<Opti
Ok(Some(ident))
}
[(_, span)] => bail_spanned!(*span => "expected string literal for #[name] argument"),
[_first_attr, second_attr, ..] => bail_spanned!(
second_attr.1 => "#[name] can not be specified multiple times"
slice => bail_spanned!(
slice[1].1 => "#[name] can not be specified multiple times"
),
}
}

View File

@ -703,8 +703,13 @@ pub(crate) fn impl_py_getter_def(
/// Split an argument of pyo3::Python from the front of the arg list, if present
fn split_off_python_arg<'a>(args: &'a [FnArg<'a>]) -> (Option<&FnArg>, &[FnArg]) {
match args {
[py, args @ ..] if utils::is_python(&py.ty) => (Some(py), args),
args => (None, args),
if args
.get(0)
.map(|py| utils::is_python(&py.ty))
.unwrap_or(false)
{
(Some(&args[0]), &args[1..])
} else {
(None, args)
}
}

View File

@ -2,6 +2,8 @@
//! This crate declares only the proc macro attributes, as a crate defining proc macro attributes
//! must not contain any other public items.
extern crate proc_macro;
use proc_macro::TokenStream;
use pyo3_macros_backend::{
build_derive_from_pyobject, build_py_class, build_py_function, build_py_methods,

View File

@ -48,7 +48,12 @@ impl ElementType {
pub fn from_format(format: &CStr) -> ElementType {
match format.to_bytes() {
[char] | [b'@', char] => native_element_type_from_type_char(*char),
[modifier, char] if matches!(modifier, b'=' | b'<' | b'>' | b'!') => {
[modifier, char]
if (*modifier == b'='
|| *modifier == b'<'
|| *modifier == b'>'
|| *modifier == b'!') =>
{
standard_element_type_from_type_char(*char)
}
_ => ElementType::Unknown,

View File

@ -594,12 +594,8 @@ mod tests {
assert!(debug_str.starts_with("PyErr { "));
assert!(debug_str.ends_with(" }"));
let mut fields = debug_str
.strip_prefix("PyErr { ")
.unwrap()
.strip_suffix(" }")
.unwrap()
.split(", ");
// strip "PyErr { " and " }"
let mut fields = debug_str["PyErr { ".len()..debug_str.len() - 2].split(", ");
assert_eq!(fields.next().unwrap(), "type: <class 'Exception'>");
if py.version_info() >= (3, 7) {

View File

@ -3,8 +3,8 @@ use std::os::raw::{c_char, c_int, c_void};
#[cfg(all(Py_3_8, not(PyPy)))]
use crate::ffi::{
vectorcallfunc, PyCallable_Check, PyThreadState, PyThreadState_GET, PyTuple_Check,
PyType_HasFeature, Py_TPFLAGS_HAVE_VECTORCALL,
pyport::PY_SSIZE_T_MAX, vectorcallfunc, PyCallable_Check, PyThreadState, PyThreadState_GET,
PyTuple_Check, PyType_HasFeature, Py_TPFLAGS_HAVE_VECTORCALL,
};
#[cfg(all(Py_3_8, not(PyPy)))]
use libc::size_t;
@ -43,7 +43,7 @@ const PY_VECTORCALL_ARGUMENTS_OFFSET: Py_ssize_t =
#[cfg(all(Py_3_8, not(PyPy)))]
#[inline(always)]
pub unsafe fn PyVectorcall_NARGS(n: size_t) -> Py_ssize_t {
assert!(n <= (Py_ssize_t::MAX as size_t));
assert!(n <= (PY_SSIZE_T_MAX as size_t));
(n as Py_ssize_t) & !PY_VECTORCALL_ARGUMENTS_OFFSET
}

View File

@ -84,6 +84,7 @@ where
crate::pyclass::default_new::<Self>(py, subtype) as _
}
#[allow(clippy::clippy::collapsible_if)] // for if cfg!
unsafe fn dealloc(py: Python, self_: *mut Self::Layout) {
(*self_).py_drop(py);
let obj = PyAny::from_borrowed_ptr_or_panic(py, self_ as _);
@ -93,9 +94,10 @@ where
let free = get_type_free(ty).unwrap_or_else(|| tp_free_fallback(ty));
free(obj as *mut c_void);
#[cfg(Py_3_8)]
if ffi::PyType_HasFeature(ty, ffi::Py_TPFLAGS_HEAPTYPE) != 0 {
ffi::Py_DECREF(ty as *mut ffi::PyObject);
if cfg!(Py_3_8) {
if ffi::PyType_HasFeature(ty, ffi::Py_TPFLAGS_HEAPTYPE) != 0 {
ffi::Py_DECREF(ty as *mut ffi::PyObject);
}
}
}
}

View File

@ -71,6 +71,7 @@ pub(crate) fn gil_is_acquired() -> bool {
/// }
/// ```
#[cfg(all(Py_SHARED, not(PyPy)))]
#[allow(clippy::clippy::collapsible_if)] // for if cfg!
pub fn prepare_freethreaded_python() {
// Protect against race conditions when Python is not yet initialized and multiple threads
// concurrently call 'prepare_freethreaded_python()'. Note that we do not protect against
@ -86,9 +87,10 @@ pub fn prepare_freethreaded_python() {
// Changed in version 3.7: This function is now called by Py_Initialize(), so you dont
// have to call it yourself anymore.
#[cfg(not(Py_3_7))]
if ffi::PyEval_ThreadsInitialized() == 0 {
ffi::PyEval_InitThreads();
if cfg!(not(Py_3_7)) {
if ffi::PyEval_ThreadsInitialized() == 0 {
ffi::PyEval_InitThreads();
}
}
// Release the GIL.
@ -136,6 +138,7 @@ pub fn prepare_freethreaded_python() {
/// }
/// ```
#[cfg(all(Py_SHARED, not(PyPy)))]
#[allow(clippy::clippy::collapsible_if)] // for if cfg!
pub unsafe fn with_embedded_python_interpreter<F, R>(f: F) -> R
where
F: for<'p> FnOnce(Python<'p>) -> R,
@ -150,9 +153,10 @@ where
// Changed in version 3.7: This function is now called by Py_Initialize(), so you dont have to
// call it yourself anymore.
#[cfg(not(Py_3_7))]
if ffi::PyEval_ThreadsInitialized() == 0 {
ffi::PyEval_InitThreads();
if cfg!(not(Py_3_7)) {
if ffi::PyEval_ThreadsInitialized() == 0 {
ffi::PyEval_InitThreads();
}
}
// Safe: the GIL is already held because of the Py_IntializeEx call.

View File

@ -224,7 +224,7 @@ pub mod proc_macro {
#[macro_export]
macro_rules! wrap_pyfunction {
($function_name: ident) => {{
&pyo3::paste::paste! { [<__pyo3_get_function_ $function_name>] }
&pyo3::paste::expr! { [<__pyo3_get_function_ $function_name>] }
}};
($function_name: ident, $arg: expr) => {
@ -257,7 +257,7 @@ macro_rules! wrap_pyfunction {
#[macro_export]
macro_rules! raw_pycfunction {
($function_name: ident) => {{
pyo3::paste::paste! { [<__pyo3_raw_ $function_name>] }
pyo3::paste::expr! { [<__pyo3_raw_ $function_name>] }
}};
}
@ -267,7 +267,7 @@ macro_rules! raw_pycfunction {
#[macro_export]
macro_rules! wrap_pymodule {
($module_name:ident) => {{
pyo3::paste::paste! {
pyo3::paste::expr! {
&|py| unsafe { pyo3::PyObject::from_owned_ptr(py, [<PyInit_ $module_name>]()) }
}
}};

View File

@ -85,6 +85,7 @@ pub trait PyClassAlloc: PyTypeInfo + Sized {
///
/// # Safety
/// `self_` must be a valid pointer to the Python heap.
#[allow(clippy::clippy::collapsible_if)] // for if cfg!
unsafe fn dealloc(py: Python, self_: *mut Self::Layout) {
(*self_).py_drop(py);
let obj = self_ as *mut ffi::PyObject;
@ -93,9 +94,10 @@ pub trait PyClassAlloc: PyTypeInfo + Sized {
let free = get_type_free(ty).unwrap_or_else(|| tp_free_fallback(ty));
free(obj as *mut c_void);
#[cfg(Py_3_8)]
if ffi::PyType_HasFeature(ty, ffi::Py_TPFLAGS_HEAPTYPE) != 0 {
ffi::Py_DECREF(ty as *mut ffi::PyObject);
if cfg!(Py_3_8) {
if ffi::PyType_HasFeature(ty, ffi::Py_TPFLAGS_HEAPTYPE) != 0 {
ffi::Py_DECREF(ty as *mut ffi::PyObject);
}
}
}
}
@ -194,8 +196,7 @@ where
slots.maybe_push(ffi::Py_tp_new, new.map(|v| v as _));
slots.maybe_push(ffi::Py_tp_call, call.map(|v| v as _));
#[cfg(Py_3_9)]
{
if cfg!(Py_3_9) {
let members = py_class_members::<T>();
if !members.is_empty() {
slots.push(ffi::Py_tp_members, into_raw(members))
@ -265,18 +266,18 @@ fn tp_init_additional<T: PyClass>(type_object: *mut ffi::PyTypeObject) {
// Setting buffer protocols via slots doesn't work until Python 3.9, so on older versions we
// must manually fixup the type object.
#[cfg(not(Py_3_9))]
if let Some(buffer) = T::get_buffer() {
unsafe {
(*(*type_object).tp_as_buffer).bf_getbuffer = buffer.bf_getbuffer;
(*(*type_object).tp_as_buffer).bf_releasebuffer = buffer.bf_releasebuffer;
if cfg!(not(Py_3_9)) {
if let Some(buffer) = T::get_buffer() {
unsafe {
(*(*type_object).tp_as_buffer).bf_getbuffer = buffer.bf_getbuffer;
(*(*type_object).tp_as_buffer).bf_releasebuffer = buffer.bf_releasebuffer;
}
}
}
// Setting tp_dictoffset and tp_weaklistoffset via slots doesn't work until Python 3.9, so on
// older versions again we must fixup the type object.
#[cfg(not(Py_3_9))]
{
if cfg!(not(Py_3_9)) {
// __dict__ support
if let Some(dict_offset) = PyCell::<T>::dict_offset() {
unsafe {
@ -400,6 +401,13 @@ fn py_class_members<T: PyClass>() -> Vec<ffi::structmember::PyMemberDef> {
members
}
// Stub needed since the `if cfg!()` above still compiles contained code.
#[cfg(not(Py_3_9))]
fn py_class_members<T: PyClass>() -> Vec<ffi::structmember::PyMemberDef> {
vec![]
}
#[allow(clippy::clippy::collapsible_if)] // for if cfg!
fn py_class_properties<T: PyClass>() -> Vec<ffi::PyGetSetDef> {
let mut defs = std::collections::HashMap::new();
@ -429,7 +437,16 @@ fn py_class_properties<T: PyClass>() -> Vec<ffi::PyGetSetDef> {
// PyPy doesn't automatically adds __dict__ getter / setter.
// PyObject_GenericGetDict not in the limited API until Python 3.10.
#[cfg(not(any(PyPy, all(Py_LIMITED_API, not(Py_3_10)))))]
push_dict_getset::<T>(&mut props);
if !props.is_empty() {
props.push(unsafe { std::mem::zeroed() });
}
props
}
#[cfg(not(any(PyPy, all(Py_LIMITED_API, not(Py_3_10)))))]
fn push_dict_getset<T: PyClass>(props: &mut Vec<ffi::PyGetSetDef>) {
if !T::Dict::IS_DUMMY {
props.push(ffi::PyGetSetDef {
name: "__dict__\0".as_ptr() as *mut c_char,
@ -439,12 +456,11 @@ fn py_class_properties<T: PyClass>() -> Vec<ffi::PyGetSetDef> {
closure: ptr::null_mut(),
});
}
if !props.is_empty() {
props.push(unsafe { std::mem::zeroed() });
}
props
}
#[cfg(any(PyPy, all(Py_LIMITED_API, not(Py_3_10))))]
fn push_dict_getset<T: PyClass>(_: &mut Vec<ffi::PyGetSetDef>) {}
/// This trait is implemented for `#[pyclass]` and handles following two situations:
/// 1. In case `T` is `Send`, stub `ThreadChecker` is used and does nothing.
/// This implementation is used by default. Compile fails if `T: !Send`.

View File

@ -9,11 +9,18 @@ fn test_compile_errors() {
t.compile_fail("tests/ui/invalid_pymethods.rs");
t.compile_fail("tests/ui/invalid_pymethod_names.rs");
t.compile_fail("tests/ui/reject_generics.rs");
t.compile_fail("tests/ui/static_ref.rs");
tests_rust_1_45(&t);
tests_rust_1_48(&t);
tests_rust_1_49(&t);
#[rustversion::since(1.45)]
fn tests_rust_1_45(t: &trybuild::TestCases) {
t.compile_fail("tests/ui/static_ref.rs");
}
#[rustversion::before(1.45)]
fn tests_rust_1_45(_t: &trybuild::TestCases) {}
#[rustversion::since(1.48)]
fn tests_rust_1_48(t: &trybuild::TestCases) {
t.compile_fail("tests/ui/invalid_result_conversion.rs");

View File

@ -38,7 +38,7 @@ macro_rules! assert_check_exact {
unsafe {
use pyo3::{AsPyPointer, ffi::*};
assert!($check_func(($obj).as_ptr()) != 0);
assert!(pyo3::paste::paste!([<$check_func Exact>])(($obj).as_ptr()) != 0);
assert!(pyo3::paste::expr!([<$check_func Exact>])(($obj).as_ptr()) != 0);
}
};
}
@ -48,7 +48,7 @@ macro_rules! assert_check_only {
unsafe {
use pyo3::{AsPyPointer, ffi::*};
assert!($check_func(($obj).as_ptr()) != 0);
assert!(pyo3::paste::paste!([<$check_func Exact>])(($obj).as_ptr()) == 0);
assert!(pyo3::paste::expr!([<$check_func Exact>])(($obj).as_ptr()) == 0);
}
};
}