Skip to content

Commit

Permalink
msrv: bump to 1.48
Browse files Browse the repository at this point in the history
  • Loading branch information
davidhewitt committed Nov 18, 2021
1 parent 2f6ea2f commit 7e8a07c
Show file tree
Hide file tree
Showing 20 changed files with 80 additions and 113 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Expand Up @@ -123,7 +123,7 @@ jobs:
rust-target: "x86_64-apple-darwin",
}
# Test minimal supported Rust version
- rust: 1.41.1
- rust: 1.48.0
python-version: "3.10"
platform:
{
Expand Down
6 changes: 6 additions & 0 deletions CHANGELOG.md
Expand Up @@ -8,6 +8,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Packaging

- Update MSRV to Rust 1.48. [#2004](https://github.com/PyO3/pyo3/pull/2004)
- Update `indoc` optional dependency to 1.0. [#2004](https://github.com/PyO3/pyo3/pull/2004)
- Update `paste` optional dependency to 1.0. [#2004](https://github.com/PyO3/pyo3/pull/2004)

### Added

- Add implementations for `Py::as_ref()` and `Py::into_ref()` for `Py<PySequence>`, `Py<PyIterator>` and `Py<PyMapping>`. [#1682](https://github.com/PyO3/pyo3/pull/1682)
Expand Down
13 changes: 4 additions & 9 deletions Cargo.toml
Expand Up @@ -21,9 +21,8 @@ parking_lot = "0.11.0"

# support crates for macros feature
pyo3-macros = { path = "pyo3-macros", version = "=0.15.0", optional = true }
# indoc must stay at 0.3.x for Rust 1.41 compatibility
indoc = { version = "0.3.6", optional = true }
paste = { version = "0.1.18", optional = true }
indoc = { version = "1.0.3", optional = true }
paste = { version = "1.0.6", optional = true }
unindent = { version = "0.1.4", optional = true }

# support crate for multiple-pymethods feature
Expand All @@ -32,7 +31,7 @@ inventory = { version = "0.1.4", optional = true }

# crate integrations that can be added using the eponymous features
anyhow = { version = "1.0", optional = true }
eyre = { version = ">= 0.4, < 0.7" , optional = true }
eyre = { version = ">= 0.4, < 0.7", optional = true }
hashbrown = { version = ">= 0.9, < 0.12", optional = true }
indexmap = { version = ">= 1.6, < 1.8", optional = true }
num-bigint = { version = "0.4", optional = true }
Expand All @@ -41,11 +40,7 @@ serde = { version = "1.0", optional = true }

[dev-dependencies]
assert_approx_eq = "1.1.0"
# O.3.5 uses the matches! macro, which isn't compatible with Rust 1.41
criterion = "=0.3.4"
# half and bitflags use if/match in const fn, which isn't compatible with Rust 1.41
half = "=1.7.1"
bitflags = "=1.2.1"
criterion = "0.3.5"
trybuild = "1.0.49"
rustversion = "1.0"
# 1.0.0 requires Rust 1.50
Expand Down
4 changes: 2 additions & 2 deletions README.md
Expand Up @@ -4,7 +4,7 @@
[![benchmark](https://github.com/PyO3/pyo3/actions/workflows/bench.yml/badge.svg)](https://pyo3.rs/dev/bench/)
[![codecov](https://codecov.io/gh/PyO3/pyo3/branch/main/graph/badge.svg)](https://codecov.io/gh/PyO3/pyo3)
[![crates.io](https://img.shields.io/crates/v/pyo3)](https://crates.io/crates/pyo3)
[![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)
[![minimum rustc 1.48](https://img.shields.io/badge/rustc-1.48+-blue.svg)](https://rust-lang.github.io/rfcs/2495-min-rust-version.html)
[![dev chat](https://img.shields.io/gitter/room/nwjs/nw.js.svg)](https://gitter.im/PyO3/Lobby)
[![contributing notes](https://img.shields.io/badge/contribute-on%20github-Green)](https://github.com/PyO3/pyo3/blob/main/Contributing.md)

Expand All @@ -18,7 +18,7 @@

PyO3 supports the following software versions:
- Python 3.6 and up (CPython and PyPy)
- Rust 1.41 and up
- Rust 1.48 and up

You can use PyO3 to write a native Python module in Rust, or to embed Python in a Rust binary. The following sections explain each of these in turn.

Expand Down
11 changes: 4 additions & 7 deletions pyo3-macros-backend/src/method.rs
Expand Up @@ -352,15 +352,12 @@ impl<'a> FnSpec<'a> {
parse_method_receiver(first_arg)
};

#[allow(clippy::manual_strip)] // for strip_prefix replacement supporting rust < 1.45
// strip get_ or set_
let strip_fn_name = |prefix: &'static str| {
let ident = name.unraw().to_string();
if ident.starts_with(prefix) {
Some(syn::Ident::new(&ident[prefix.len()..], ident.span()))
} else {
None
}
name.unraw()
.to_string()
.strip_prefix(prefix)
.map(|stripped| syn::Ident::new(stripped, name.span()))
};

let (fn_type, skip_first_arg, fixed_convention) = match fn_type_attr {
Expand Down
4 changes: 1 addition & 3 deletions pyo3-macros-backend/src/proto_method.rs
Expand Up @@ -15,13 +15,11 @@ pub struct MethodProto {
}

impl MethodProto {
// TODO: workaround for no unsized casts in const fn on Rust 1.45 (stable in 1.46)
const EMPTY_ARGS: &'static [&'static str] = &[];
pub const fn new(name: &'static str, proto: &'static str) -> Self {
MethodProto {
name,
proto,
args: MethodProto::EMPTY_ARGS,
args: &[],
with_self: false,
with_result: true,
}
Expand Down
11 changes: 3 additions & 8 deletions pyo3-macros-backend/src/pymethod.rs
Expand Up @@ -399,14 +399,9 @@ pub fn impl_py_getter_def(cls: &syn::Type, property_type: PropertyType) -> Resul

/// 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]) {
if args
.get(0)
.map(|py| utils::is_python(py.ty))
.unwrap_or(false)
{
(Some(&args[0]), &args[1..])
} else {
(None, args)
match args {
[py, args @ ..] if utils::is_python(&py.ty) => (Some(py), args),
args => (None, args),
}
}

Expand Down
12 changes: 5 additions & 7 deletions pyo3-macros-backend/src/utils.rs
Expand Up @@ -62,8 +62,6 @@ pub fn option_type_argument(ty: &syn::Type) -> Option<&syn::Type> {
#[derive(Clone)]
pub struct PythonDoc(TokenStream);

// TODO(#1782) use strip_prefix on Rust 1.45 or greater
#[allow(clippy::manual_strip)]
/// Collects all #[doc = "..."] attributes into a TokenStream evaluating to a null-terminated string
/// e.g. concat!("...", "\n", "\0")
pub fn get_doc(
Expand Down Expand Up @@ -107,11 +105,11 @@ pub fn get_doc(
// Strip single left space from literal strings, if needed.
// e.g. `/// Hello world` expands to #[doc = " Hello world"]
let doc_line = lit_str.value();
if doc_line.starts_with(' ') {
syn::LitStr::new(&doc_line[1..], lit_str.span()).to_tokens(tokens)
} else {
lit_str.to_tokens(tokens)
}
doc_line
.strip_prefix(' ')
.map(|stripped| syn::LitStr::new(stripped, lit_str.span()))
.unwrap_or(lit_str)
.to_tokens(tokens);
} else {
// This is probably a macro doc from Rust 1.54, e.g. #[doc = include_str!(...)]
token_stream.to_tokens(tokens)
Expand Down
7 changes: 1 addition & 6 deletions src/buffer.rs
Expand Up @@ -75,12 +75,7 @@ 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 (*modifier == b'='
|| *modifier == b'<'
|| *modifier == b'>'
|| *modifier == b'!') =>
{
[modifier, char] if matches!(modifier, b'=' | b'<' | b'>' | b'!') => {
standard_element_type_from_type_char(*char)
}
_ => ElementType::Unknown,
Expand Down
8 changes: 3 additions & 5 deletions src/class/impl_.rs
Expand Up @@ -542,7 +542,6 @@ pub unsafe extern "C" fn alloc_with_freelist<T: PyClassWithFreeList>(
/// # Safety
/// - `obj` must be a valid pointer to an instance of T (not a subclass).
/// - The GIL must be held.
#[allow(clippy::collapsible_if)] // for if cfg!
pub unsafe extern "C" fn free_with_freelist<T: PyClassWithFreeList>(obj: *mut c_void) {
let obj = obj as *mut ffi::PyObject;
debug_assert_eq!(
Expand All @@ -560,10 +559,9 @@ pub unsafe extern "C" fn free_with_freelist<T: PyClassWithFreeList>(obj: *mut c_
};
free(obj as *mut c_void);

if cfg!(Py_3_8) {
if ffi::PyType_HasFeature(ty, ffi::Py_TPFLAGS_HEAPTYPE) != 0 {
ffi::Py_DECREF(ty as *mut ffi::PyObject);
}
#[cfg(Py_3_8)]
if ffi::PyType_HasFeature(ty, ffi::Py_TPFLAGS_HEAPTYPE) != 0 {
ffi::Py_DECREF(ty as *mut ffi::PyObject);
}
}
}
Expand Down
1 change: 0 additions & 1 deletion src/ffi/ceval.rs
Expand Up @@ -52,7 +52,6 @@ extern "C" {
fn _Py_CheckRecursiveCall(_where: *mut c_char) -> c_int;
}

// TODO
// skipped Py_EnterRecursiveCall
// skipped Py_LeaveRecursiveCall

Expand Down
51 changes: 26 additions & 25 deletions src/gil.rs
Expand Up @@ -69,33 +69,36 @@ pub(crate) fn gil_is_acquired() -> bool {
/// # }
/// ```
#[cfg(not(PyPy))]
#[allow(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
// concurrent initialization of the Python runtime by other users of the Python C API.
START.call_once_force(|_| unsafe {
if cfg!(not(Py_3_7)) {
// Use call_once_force because if initialization panics, it's okay to try again.
if ffi::Py_IsInitialized() != 0 {
if ffi::PyEval_ThreadsInitialized() == 0 {
// We can only safely initialize threads if this thread holds the GIL.
assert!(
!ffi::PyGILState_GetThisThreadState().is_null(),
"Python threading is not initialized and cannot be initialized by this \
thread, because it is not the thread which initialized Python."
);
ffi::PyEval_InitThreads();
}
} else {
ffi::Py_InitializeEx(0);
// Use call_once_force because if initialization panics, it's okay to try again.

// TODO(#1782) - Python 3.6 legacy code
#[cfg(not(Py_3_7))]
if ffi::Py_IsInitialized() != 0 {
if ffi::PyEval_ThreadsInitialized() == 0 {
// We can only safely initialize threads if this thread holds the GIL.
assert!(
!ffi::PyGILState_GetThisThreadState().is_null(),
"Python threading is not initialized and cannot be initialized by this \
thread, because it is not the thread which initialized Python."
);
ffi::PyEval_InitThreads();

// Release the GIL.
ffi::PyEval_SaveThread();
}
} else if ffi::Py_IsInitialized() == 0 {
// In Python 3.7 and up PyEval_InitThreads is irrelevant.
} else {
ffi::Py_InitializeEx(0);
ffi::PyEval_InitThreads();

// Release the GIL.
ffi::PyEval_SaveThread();
}

// In Python 3.7 and up PyEval_InitThreads is irrelevant.
#[cfg(Py_3_7)]
if ffi::Py_IsInitialized() == 0 {
ffi::Py_InitializeEx(0);

// Release the GIL.
Expand Down Expand Up @@ -134,7 +137,6 @@ pub fn prepare_freethreaded_python() {
/// # }
/// ```
#[cfg(not(PyPy))]
#[allow(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 @@ -149,10 +151,9 @@ where

// Changed in version 3.7: This function is now called by Py_Initialize(), so you don’t have to
// call it yourself anymore.
if cfg!(not(Py_3_7)) {
if ffi::PyEval_ThreadsInitialized() == 0 {
ffi::PyEval_InitThreads();
}
#[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
2 changes: 1 addition & 1 deletion src/lib.rs
Expand Up @@ -111,7 +111,7 @@
//!
//! PyO3 supports the following software versions:
//! - Python 3.6 and up (CPython and PyPy)
//! - Rust 1.41 and up
//! - Rust 1.48 and up
//!
//! # Example: Building a native Python module
//!
Expand Down
16 changes: 6 additions & 10 deletions src/pyclass.rs
Expand Up @@ -83,7 +83,8 @@ where
slots.push(ffi::Py_tp_free, free as _);
}

if cfg!(Py_3_9) {
#[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 @@ -155,7 +156,8 @@ 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.
if cfg!(not(Py_3_9)) {
#[cfg(not(Py_3_9))]
{
if let Some(buffer) = T::get_buffer() {
unsafe {
(*(*type_object).tp_as_buffer).bf_getbuffer = buffer.bf_getbuffer;
Expand All @@ -166,7 +168,8 @@ fn tp_init_additional<T: PyClass>(type_object: *mut ffi::PyTypeObject) {

// 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.
if cfg!(not(Py_3_9)) {
#[cfg(not(Py_3_9))]
{
// __dict__ support
if let Some(dict_offset) = PyCell::<T>::dict_offset() {
unsafe {
Expand Down Expand Up @@ -258,12 +261,6 @@ 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![]
}

const PY_GET_SET_DEF_INIT: ffi::PyGetSetDef = ffi::PyGetSetDef {
name: ptr::null_mut(),
get: None,
Expand All @@ -272,7 +269,6 @@ const PY_GET_SET_DEF_INIT: ffi::PyGetSetDef = ffi::PyGetSetDef {
closure: ptr::null_mut(),
};

#[allow(clippy::collapsible_if)] // for if cfg!
fn py_class_properties(
is_dummy: bool,
for_each_method_def: &dyn Fn(&mut dyn FnMut(&[PyMethodDefType])),
Expand Down
5 changes: 3 additions & 2 deletions src/types/string.rs
Expand Up @@ -227,8 +227,9 @@ impl PyString {
pub unsafe fn data(&self) -> PyResult<PyStringData<'_>> {
let ptr = self.as_ptr();

if cfg!(not(Py_3_12)) {
#[allow(deprecated)]
#[cfg(not(Py_3_12))]
#[allow(deprecated)]
{
let ready = ffi::PyUnicode_READY(ptr);
if ready != 0 {
// Exception was created on failure.
Expand Down
11 changes: 2 additions & 9 deletions tests/test_compile_error.rs
Expand Up @@ -23,22 +23,15 @@ 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/invalid_argument_attributes.rs");
t.compile_fail("tests/ui/missing_clone.rs");
t.compile_fail("tests/ui/reject_generics.rs");
t.compile_fail("tests/ui/wrong_aspyref_lifetimes.rs");

tests_rust_1_48(&t);
tests_rust_1_49(&t);
tests_rust_1_54(&t);
tests_rust_1_55(&t);
tests_rust_1_56(&t);

#[rustversion::since(1.48)]
fn tests_rust_1_48(t: &trybuild::TestCases) {
t.compile_fail("tests/ui/missing_clone.rs");
t.compile_fail("tests/ui/wrong_aspyref_lifetimes.rs");
}
#[rustversion::before(1.48)]
fn tests_rust_1_48(_t: &trybuild::TestCases) {}

#[rustversion::since(1.49)]
fn tests_rust_1_49(t: &trybuild::TestCases) {
t.compile_fail("tests/ui/deprecations.rs");
Expand Down
4 changes: 1 addition & 3 deletions tests/test_not_msrv.rs
Expand Up @@ -2,9 +2,7 @@
//! but can't even be cfg-ed out on MSRV because the compiler doesn't support
//! the syntax.

// TODO(#1782) rustversion attribute can't go on modules until Rust 1.42, so this
// funky dance has to happen...
#[rustversion::since(1.54)]
mod requires_1_54 {
#[rustversion::since(1.54)]
include!("not_msrv/requires_1_54.rs");
}

0 comments on commit 7e8a07c

Please sign in to comment.