Skip to content

Commit fa8d751

Browse files
committed
Restore compatibility with Rust 1.41.
This version is currently supported by Debian stable and Alpine Linux. Fixes #1420
1 parent 479d67a commit fa8d751

File tree

16 files changed

+98
-56
lines changed

16 files changed

+98
-56
lines changed

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ jobs:
5858
platform: { os: "windows-latest", python-architecture: "x64" }
5959
include:
6060
# Test minimal supported Rust version
61-
- rust: 1.45.0
61+
- rust: 1.41.1
6262
python-version: 3.9
6363
platform: { os: "ubuntu-latest", python-architecture: "x64", rust-target: "x86_64-unknown-linux-gnu" }
6464
msrv: "MSRV"

Cargo.toml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,15 @@ edition = "2018"
1717
[dependencies]
1818
cfg-if = { version = "1.0" }
1919
ctor = { version = "0.1", optional = true }
20-
indoc = { version = "1.0.3", optional = true }
20+
# must stay at 0.3.x for Rust 1.41 compatibility
21+
indoc = { version = "0.3.6", optional = true }
2122
inventory = { version = "0.1.4", optional = true }
2223
libc = "0.2.62"
2324
parking_lot = "0.11.0"
2425
num-bigint = { version = "0.3", optional = true }
2526
num-complex = { version = "0.3", optional = true }
26-
paste = { version = "1.0.3", optional = true }
27+
# must stay at 0.1.x for Rust 1.41 compatibility
28+
paste = { version = "0.1.18", optional = true }
2729
pyo3-macros = { path = "pyo3-macros", version = "=0.13.1", optional = true }
2830
unindent = { version = "0.1.4", optional = true }
2931
hashbrown = { version = "0.9", optional = true }

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
[![Actions Status](https://github.com/PyO3/pyo3/workflows/Test/badge.svg)](https://github.com/PyO3/pyo3/actions)
44
[![codecov](https://codecov.io/gh/PyO3/pyo3/branch/master/graph/badge.svg)](https://codecov.io/gh/PyO3/pyo3)
55
[![crates.io](http://meritbadge.herokuapp.com/pyo3)](https://crates.io/crates/pyo3)
6-
[![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)
6+
[![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)
77
[![Join the dev chat](https://img.shields.io/gitter/room/nwjs/nw.js.svg)](https://gitter.im/PyO3/Lobby)
88

99
[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
1818

1919
## Usage
2020

21-
PyO3 supports Python 3.6 and up. The minimum required Rust version is 1.45.0.
21+
PyO3 supports Python 3.6 and up. The minimum required Rust version is 1.41.
2222

2323
Building with PyPy is also possible (via cpyext) for Python 3.6, targeted PyPy version is 7.3+.
2424
Please refer to the [pypy section in the guide](https://pyo3.rs/master/pypy.html).

pyo3-macros-backend/src/method.rs

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@ pub fn parse_method_receiver(arg: &syn::FnArg) -> syn::Result<SelfType> {
115115

116116
impl<'a> FnSpec<'a> {
117117
/// Parser function signature and function attributes
118+
#[allow(clippy::manual_strip)] // for strip_prefix replacement supporting rust < 1.45
118119
pub fn parse(
119120
sig: &'a syn::Signature,
120121
meth_attrs: &mut Vec<syn::Attribute>,
@@ -139,10 +140,12 @@ impl<'a> FnSpec<'a> {
139140

140141
// strip get_ or set_
141142
let strip_fn_name = |prefix: &'static str| {
142-
name.unraw()
143-
.to_string()
144-
.strip_prefix(prefix)
145-
.map(|rest| syn::Ident::new(rest, name.span()))
143+
let ident = name.unraw().to_string();
144+
if ident.starts_with(prefix) {
145+
Some(syn::Ident::new(&ident[prefix.len()..], ident.span()))
146+
} else {
147+
None
148+
}
146149
};
147150

148151
// Parse receiver & function type for various method types

pyo3-macros-backend/src/pyfunction.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -174,8 +174,8 @@ pub fn parse_name_attribute(attrs: &mut Vec<syn::Attribute>) -> syn::Result<Opti
174174
Ok(Some(ident))
175175
}
176176
[(_, span)] => bail_spanned!(*span => "expected string literal for #[name] argument"),
177-
[_first_attr, second_attr, ..] => bail_spanned!(
178-
second_attr.1 => "#[name] can not be specified multiple times"
177+
slice => bail_spanned!(
178+
slice[1].1 => "#[name] can not be specified multiple times"
179179
),
180180
}
181181
}

pyo3-macros-backend/src/pymethod.rs

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -703,8 +703,13 @@ pub(crate) fn impl_py_getter_def(
703703

704704
/// Split an argument of pyo3::Python from the front of the arg list, if present
705705
fn split_off_python_arg<'a>(args: &'a [FnArg<'a>]) -> (Option<&FnArg>, &[FnArg]) {
706-
match args {
707-
[py, args @ ..] if utils::is_python(&py.ty) => (Some(py), args),
708-
args => (None, args),
706+
if args
707+
.get(0)
708+
.map(|py| utils::is_python(&py.ty))
709+
.unwrap_or(false)
710+
{
711+
(Some(&args[0]), &args[1..])
712+
} else {
713+
(None, args)
709714
}
710715
}

pyo3-macros/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
//! This crate declares only the proc macro attributes, as a crate defining proc macro attributes
33
//! must not contain any other public items.
44
5+
extern crate proc_macro;
6+
57
use proc_macro::TokenStream;
68
use pyo3_macros_backend::{
79
build_derive_from_pyobject, build_py_class, build_py_function, build_py_methods,

src/buffer.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,12 @@ impl ElementType {
4848
pub fn from_format(format: &CStr) -> ElementType {
4949
match format.to_bytes() {
5050
[char] | [b'@', char] => native_element_type_from_type_char(*char),
51-
[modifier, char] if matches!(modifier, b'=' | b'<' | b'>' | b'!') => {
51+
[modifier, char]
52+
if (*modifier == b'='
53+
|| *modifier == b'<'
54+
|| *modifier == b'>'
55+
|| *modifier == b'!') =>
56+
{
5257
standard_element_type_from_type_char(*char)
5358
}
5459
_ => ElementType::Unknown,

src/err/mod.rs

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -594,12 +594,8 @@ mod tests {
594594
assert!(debug_str.starts_with("PyErr { "));
595595
assert!(debug_str.ends_with(" }"));
596596

597-
let mut fields = debug_str
598-
.strip_prefix("PyErr { ")
599-
.unwrap()
600-
.strip_suffix(" }")
601-
.unwrap()
602-
.split(", ");
597+
// strip "PyErr { " and " }"
598+
let mut fields = debug_str["PyErr { ".len()..debug_str.len() - 2].split(", ");
603599

604600
assert_eq!(fields.next().unwrap(), "type: <class 'Exception'>");
605601
if py.version_info() >= (3, 7) {

src/ffi/cpython/abstract_.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ use std::os::raw::{c_char, c_int, c_void};
33

44
#[cfg(all(Py_3_8, not(PyPy)))]
55
use crate::ffi::{
6-
vectorcallfunc, PyCallable_Check, PyThreadState, PyThreadState_GET, PyTuple_Check,
7-
PyType_HasFeature, Py_TPFLAGS_HAVE_VECTORCALL,
6+
pyport::PY_SSIZE_T_MAX, vectorcallfunc, PyCallable_Check, PyThreadState, PyThreadState_GET,
7+
PyTuple_Check, PyType_HasFeature, Py_TPFLAGS_HAVE_VECTORCALL,
88
};
99
#[cfg(all(Py_3_8, not(PyPy)))]
1010
use libc::size_t;
@@ -43,7 +43,7 @@ const PY_VECTORCALL_ARGUMENTS_OFFSET: Py_ssize_t =
4343
#[cfg(all(Py_3_8, not(PyPy)))]
4444
#[inline(always)]
4545
pub unsafe fn PyVectorcall_NARGS(n: size_t) -> Py_ssize_t {
46-
assert!(n <= (Py_ssize_t::MAX as size_t));
46+
assert!(n <= (PY_SSIZE_T_MAX as size_t));
4747
(n as Py_ssize_t) & !PY_VECTORCALL_ARGUMENTS_OFFSET
4848
}
4949

src/freelist.rs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ where
8484
crate::pyclass::default_new::<Self>(py, subtype) as _
8585
}
8686

87+
#[allow(clippy::clippy::collapsible_if)] // for if cfg!
8788
unsafe fn dealloc(py: Python, self_: *mut Self::Layout) {
8889
(*self_).py_drop(py);
8990
let obj = PyAny::from_borrowed_ptr_or_panic(py, self_ as _);
@@ -93,9 +94,10 @@ where
9394
let free = get_type_free(ty).unwrap_or_else(|| tp_free_fallback(ty));
9495
free(obj as *mut c_void);
9596

96-
#[cfg(Py_3_8)]
97-
if ffi::PyType_HasFeature(ty, ffi::Py_TPFLAGS_HEAPTYPE) != 0 {
98-
ffi::Py_DECREF(ty as *mut ffi::PyObject);
97+
if cfg!(Py_3_8) {
98+
if ffi::PyType_HasFeature(ty, ffi::Py_TPFLAGS_HEAPTYPE) != 0 {
99+
ffi::Py_DECREF(ty as *mut ffi::PyObject);
100+
}
99101
}
100102
}
101103
}

src/gil.rs

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ pub(crate) fn gil_is_acquired() -> bool {
7171
/// }
7272
/// ```
7373
#[cfg(all(Py_SHARED, not(PyPy)))]
74+
#[allow(clippy::clippy::collapsible_if)] // for if cfg!
7475
pub fn prepare_freethreaded_python() {
7576
// Protect against race conditions when Python is not yet initialized and multiple threads
7677
// concurrently call 'prepare_freethreaded_python()'. Note that we do not protect against
@@ -86,9 +87,10 @@ pub fn prepare_freethreaded_python() {
8687

8788
// Changed in version 3.7: This function is now called by Py_Initialize(), so you don’t
8889
// have to call it yourself anymore.
89-
#[cfg(not(Py_3_7))]
90-
if ffi::PyEval_ThreadsInitialized() == 0 {
91-
ffi::PyEval_InitThreads();
90+
if cfg!(not(Py_3_7)) {
91+
if ffi::PyEval_ThreadsInitialized() == 0 {
92+
ffi::PyEval_InitThreads();
93+
}
9294
}
9395

9496
// Release the GIL.
@@ -136,6 +138,7 @@ pub fn prepare_freethreaded_python() {
136138
/// }
137139
/// ```
138140
#[cfg(all(Py_SHARED, not(PyPy)))]
141+
#[allow(clippy::clippy::collapsible_if)] // for if cfg!
139142
pub unsafe fn with_embedded_python_interpreter<F, R>(f: F) -> R
140143
where
141144
F: for<'p> FnOnce(Python<'p>) -> R,
@@ -150,9 +153,10 @@ where
150153

151154
// Changed in version 3.7: This function is now called by Py_Initialize(), so you don’t have to
152155
// call it yourself anymore.
153-
#[cfg(not(Py_3_7))]
154-
if ffi::PyEval_ThreadsInitialized() == 0 {
155-
ffi::PyEval_InitThreads();
156+
if cfg!(not(Py_3_7)) {
157+
if ffi::PyEval_ThreadsInitialized() == 0 {
158+
ffi::PyEval_InitThreads();
159+
}
156160
}
157161

158162
// Safe: the GIL is already held because of the Py_IntializeEx call.

src/lib.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -224,7 +224,7 @@ pub mod proc_macro {
224224
#[macro_export]
225225
macro_rules! wrap_pyfunction {
226226
($function_name: ident) => {{
227-
&pyo3::paste::paste! { [<__pyo3_get_function_ $function_name>] }
227+
&pyo3::paste::expr! { [<__pyo3_get_function_ $function_name>] }
228228
}};
229229

230230
($function_name: ident, $arg: expr) => {
@@ -257,7 +257,7 @@ macro_rules! wrap_pyfunction {
257257
#[macro_export]
258258
macro_rules! raw_pycfunction {
259259
($function_name: ident) => {{
260-
pyo3::paste::paste! { [<__pyo3_raw_ $function_name>] }
260+
pyo3::paste::expr! { [<__pyo3_raw_ $function_name>] }
261261
}};
262262
}
263263

@@ -267,7 +267,7 @@ macro_rules! raw_pycfunction {
267267
#[macro_export]
268268
macro_rules! wrap_pymodule {
269269
($module_name:ident) => {{
270-
pyo3::paste::paste! {
270+
pyo3::paste::expr! {
271271
&|py| unsafe { pyo3::PyObject::from_owned_ptr(py, [<PyInit_ $module_name>]()) }
272272
}
273273
}};

src/pyclass.rs

Lines changed: 33 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ pub trait PyClassAlloc: PyTypeInfo + Sized {
8585
///
8686
/// # Safety
8787
/// `self_` must be a valid pointer to the Python heap.
88+
#[allow(clippy::clippy::collapsible_if)] // for if cfg!
8889
unsafe fn dealloc(py: Python, self_: *mut Self::Layout) {
8990
(*self_).py_drop(py);
9091
let obj = self_ as *mut ffi::PyObject;
@@ -93,9 +94,10 @@ pub trait PyClassAlloc: PyTypeInfo + Sized {
9394
let free = get_type_free(ty).unwrap_or_else(|| tp_free_fallback(ty));
9495
free(obj as *mut c_void);
9596

96-
#[cfg(Py_3_8)]
97-
if ffi::PyType_HasFeature(ty, ffi::Py_TPFLAGS_HEAPTYPE) != 0 {
98-
ffi::Py_DECREF(ty as *mut ffi::PyObject);
97+
if cfg!(Py_3_8) {
98+
if ffi::PyType_HasFeature(ty, ffi::Py_TPFLAGS_HEAPTYPE) != 0 {
99+
ffi::Py_DECREF(ty as *mut ffi::PyObject);
100+
}
99101
}
100102
}
101103
}
@@ -194,8 +196,7 @@ where
194196
slots.maybe_push(ffi::Py_tp_new, new.map(|v| v as _));
195197
slots.maybe_push(ffi::Py_tp_call, call.map(|v| v as _));
196198

197-
#[cfg(Py_3_9)]
198-
{
199+
if cfg!(Py_3_9) {
199200
let members = py_class_members::<T>();
200201
if !members.is_empty() {
201202
slots.push(ffi::Py_tp_members, into_raw(members))
@@ -265,18 +266,18 @@ fn tp_init_additional<T: PyClass>(type_object: *mut ffi::PyTypeObject) {
265266

266267
// Setting buffer protocols via slots doesn't work until Python 3.9, so on older versions we
267268
// must manually fixup the type object.
268-
#[cfg(not(Py_3_9))]
269-
if let Some(buffer) = T::get_buffer() {
270-
unsafe {
271-
(*(*type_object).tp_as_buffer).bf_getbuffer = buffer.bf_getbuffer;
272-
(*(*type_object).tp_as_buffer).bf_releasebuffer = buffer.bf_releasebuffer;
269+
if cfg!(not(Py_3_9)) {
270+
if let Some(buffer) = T::get_buffer() {
271+
unsafe {
272+
(*(*type_object).tp_as_buffer).bf_getbuffer = buffer.bf_getbuffer;
273+
(*(*type_object).tp_as_buffer).bf_releasebuffer = buffer.bf_releasebuffer;
274+
}
273275
}
274276
}
275277

276278
// Setting tp_dictoffset and tp_weaklistoffset via slots doesn't work until Python 3.9, so on
277279
// older versions again we must fixup the type object.
278-
#[cfg(not(Py_3_9))]
279-
{
280+
if cfg!(not(Py_3_9)) {
280281
// __dict__ support
281282
if let Some(dict_offset) = PyCell::<T>::dict_offset() {
282283
unsafe {
@@ -400,6 +401,13 @@ fn py_class_members<T: PyClass>() -> Vec<ffi::structmember::PyMemberDef> {
400401
members
401402
}
402403

404+
// Stub needed since the `if cfg!()` above still compiles contained code.
405+
#[cfg(not(Py_3_9))]
406+
fn py_class_members<T: PyClass>() -> Vec<ffi::structmember::PyMemberDef> {
407+
vec![]
408+
}
409+
410+
#[allow(clippy::clippy::collapsible_if)] // for if cfg!
403411
fn py_class_properties<T: PyClass>() -> Vec<ffi::PyGetSetDef> {
404412
let mut defs = std::collections::HashMap::new();
405413

@@ -429,7 +437,16 @@ fn py_class_properties<T: PyClass>() -> Vec<ffi::PyGetSetDef> {
429437

430438
// PyPy doesn't automatically adds __dict__ getter / setter.
431439
// PyObject_GenericGetDict not in the limited API until Python 3.10.
432-
#[cfg(not(any(PyPy, all(Py_LIMITED_API, not(Py_3_10)))))]
440+
push_dict_getset::<T>(&mut props);
441+
442+
if !props.is_empty() {
443+
props.push(unsafe { std::mem::zeroed() });
444+
}
445+
props
446+
}
447+
448+
#[cfg(not(any(PyPy, all(Py_LIMITED_API, not(Py_3_10)))))]
449+
fn push_dict_getset<T: PyClass>(props: &mut Vec<ffi::PyGetSetDef>) {
433450
if !T::Dict::IS_DUMMY {
434451
props.push(ffi::PyGetSetDef {
435452
name: "__dict__\0".as_ptr() as *mut c_char,
@@ -439,12 +456,11 @@ fn py_class_properties<T: PyClass>() -> Vec<ffi::PyGetSetDef> {
439456
closure: ptr::null_mut(),
440457
});
441458
}
442-
if !props.is_empty() {
443-
props.push(unsafe { std::mem::zeroed() });
444-
}
445-
props
446459
}
447460

461+
#[cfg(any(PyPy, all(Py_LIMITED_API, not(Py_3_10))))]
462+
fn push_dict_getset<T: PyClass>(_: &mut Vec<ffi::PyGetSetDef>) {}
463+
448464
/// This trait is implemented for `#[pyclass]` and handles following two situations:
449465
/// 1. In case `T` is `Send`, stub `ThreadChecker` is used and does nothing.
450466
/// This implementation is used by default. Compile fails if `T: !Send`.

tests/test_compile_error.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,18 @@ fn test_compile_errors() {
99
t.compile_fail("tests/ui/invalid_pymethods.rs");
1010
t.compile_fail("tests/ui/invalid_pymethod_names.rs");
1111
t.compile_fail("tests/ui/reject_generics.rs");
12-
t.compile_fail("tests/ui/static_ref.rs");
1312

13+
tests_rust_1_45(&t);
1414
tests_rust_1_48(&t);
1515
tests_rust_1_49(&t);
1616

17+
#[rustversion::since(1.45)]
18+
fn tests_rust_1_45(t: &trybuild::TestCases) {
19+
t.compile_fail("tests/ui/static_ref.rs");
20+
}
21+
#[rustversion::before(1.45)]
22+
fn tests_rust_1_45(_t: &trybuild::TestCases) {}
23+
1724
#[rustversion::since(1.48)]
1825
fn tests_rust_1_48(t: &trybuild::TestCases) {
1926
t.compile_fail("tests/ui/invalid_result_conversion.rs");

tests/test_datetime.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ macro_rules! assert_check_exact {
3838
unsafe {
3939
use pyo3::{AsPyPointer, ffi::*};
4040
assert!($check_func(($obj).as_ptr()) != 0);
41-
assert!(pyo3::paste::paste!([<$check_func Exact>])(($obj).as_ptr()) != 0);
41+
assert!(pyo3::paste::expr!([<$check_func Exact>])(($obj).as_ptr()) != 0);
4242
}
4343
};
4444
}
@@ -48,7 +48,7 @@ macro_rules! assert_check_only {
4848
unsafe {
4949
use pyo3::{AsPyPointer, ffi::*};
5050
assert!($check_func(($obj).as_ptr()) != 0);
51-
assert!(pyo3::paste::paste!([<$check_func Exact>])(($obj).as_ptr()) == 0);
51+
assert!(pyo3::paste::expr!([<$check_func Exact>])(($obj).as_ptr()) == 0);
5252
}
5353
};
5454
}

0 commit comments

Comments
 (0)