Skip to content

Commit

Permalink
Merge pull request #1421 from birkenfeld/rust1.41
Browse files Browse the repository at this point in the history
Restore compatibility with Rust 1.41(.1).
  • Loading branch information
davidhewitt committed Feb 11, 2021
2 parents 414c05c + fa8d751 commit 83f71d8
Show file tree
Hide file tree
Showing 16 changed files with 98 additions and 56 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
6 changes: 4 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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/building_and_distribution/pypy.html).
Expand Down
11 changes: 7 additions & 4 deletions pyo3-macros-backend/src/method.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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>,
Expand All @@ -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
Expand Down
4 changes: 2 additions & 2 deletions pyo3-macros-backend/src/pyfunction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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"
),
}
}
Expand Down
11 changes: 8 additions & 3 deletions pyo3-macros-backend/src/pymethod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
2 changes: 2 additions & 0 deletions pyo3-macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
7 changes: 6 additions & 1 deletion src/buffer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
8 changes: 2 additions & 6 deletions src/err/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
6 changes: 3 additions & 3 deletions src/ffi/cpython/abstract_.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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
}

Expand Down
8 changes: 5 additions & 3 deletions src/freelist.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 _);
Expand All @@ -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);
}
}
}
}
Expand Down
16 changes: 10 additions & 6 deletions src/gil.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -86,9 +87,10 @@ pub fn prepare_freethreaded_python() {

// Changed in version 3.7: This function is now called by Py_Initialize(), so you don’t
// 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.
Expand Down Expand Up @@ -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,
Expand All @@ -150,9 +153,10 @@ where

// Changed in version 3.7: This function is now called by Py_Initialize(), so you don’t 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.
Expand Down
6 changes: 3 additions & 3 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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) => {
Expand Down Expand Up @@ -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>] }
}};
}

Expand All @@ -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>]()) }
}
}};
Expand Down
50 changes: 33 additions & 17 deletions src/pyclass.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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);
}
}
}
}
Expand Down Expand Up @@ -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))
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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();

Expand Down Expand Up @@ -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,
Expand All @@ -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`.
Expand Down
9 changes: 8 additions & 1 deletion tests/test_compile_error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down
4 changes: 2 additions & 2 deletions tests/test_datetime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
};
}
Expand All @@ -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);
}
};
}
Expand Down

0 comments on commit 83f71d8

Please sign in to comment.