diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 70628644447..b625f17573d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -45,7 +45,7 @@ jobs: python-version: "3.13" - name: resolve MSRV id: resolve-msrv - run: echo MSRV=`python -c 'import tomllib; print(tomllib.load(open("Cargo.toml", "rb"))["package"]["rust-version"])'` >> $GITHUB_OUTPUT + run: echo MSRV=`python -c 'import tomllib; print(tomllib.load(open("Cargo.toml", "rb"))["workspace"]["package"]["rust-version"])'` >> $GITHUB_OUTPUT semver-checks: if: github.ref != 'refs/heads/main' diff --git a/Cargo.toml b/Cargo.toml index 6f2038b9119..65f7d0c776e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,14 +10,23 @@ repository = "https://github.com/pyo3/pyo3" documentation = "https://docs.rs/crate/pyo3/" categories = ["api-bindings", "development-tools::ffi"] license = "MIT OR Apache-2.0" -exclude = ["/.gitignore", ".cargo/config", "/codecov.yml", "/Makefile", "/pyproject.toml", "/noxfile.py", "/.github", "/tests/test_compile_error.rs", "/tests/ui"] +exclude = [ + "/.gitignore", + ".cargo/config", + "/codecov.yml", + "/Makefile", + "/pyproject.toml", + "/noxfile.py", + "/.github", + "/tests/test_compile_error.rs", + "/tests/ui", +] edition = "2021" -rust-version = "1.63" +rust-version.workspace = true [dependencies] libc = "0.2.62" memoffset = "0.9" -once_cell = "1.13" # ffi bindings to the python interpreter, split into a separate crate so they can be used independently pyo3-ffi = { path = "pyo3-ffi", version = "=0.25.0" } @@ -140,6 +149,7 @@ full = [ "eyre", "hashbrown", "indexmap", + "jiff-02", "lock_api", "num-bigint", "num-complex", @@ -150,6 +160,7 @@ full = [ "rust_decimal", "serde", "smallvec", + "time", "uuid", ] @@ -163,6 +174,7 @@ members = [ "pytests", "examples", ] +package.rust-version = "1.74" [package.metadata.docs.rs] no-default-features = true diff --git a/build.rs b/build.rs index fd28b03b79d..25e3f54e331 100644 --- a/build.rs +++ b/build.rs @@ -41,7 +41,6 @@ fn configure_pyo3() -> Result<()> { println!("{cfg}") } - // Emit cfgs like `invalid_from_utf8_lint` print_feature_cfgs(); // Make `cargo test` etc work on macOS with Xcode bundled Python diff --git a/examples/Cargo.toml b/examples/Cargo.toml index f6c77eb609c..b17fd4887ad 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -3,7 +3,7 @@ name = "pyo3-examples" version = "0.0.0" publish = false edition = "2021" -rust-version = "1.63" +rust-version = "1.74" [dev-dependencies] pyo3 = { path = "..", features = ["auto-initialize", "extension-module"] } diff --git a/examples/decorator/Cargo.toml b/examples/decorator/Cargo.toml index 785895121a3..1c3586b884b 100644 --- a/examples/decorator/Cargo.toml +++ b/examples/decorator/Cargo.toml @@ -2,7 +2,7 @@ name = "decorator" version = "0.1.0" edition = "2021" -rust-version = "1.63" +rust-version = "1.74" [lib] name = "decorator" diff --git a/examples/getitem/Cargo.toml b/examples/getitem/Cargo.toml index 99430483171..fa35cf10ffc 100644 --- a/examples/getitem/Cargo.toml +++ b/examples/getitem/Cargo.toml @@ -2,7 +2,7 @@ name = "getitem" version = "0.1.0" edition = "2021" -rust-version = "1.63" +rust-version = "1.74" [lib] name = "getitem" diff --git a/examples/maturin-starter/Cargo.toml b/examples/maturin-starter/Cargo.toml index ee1ab9aff06..f65b0ed3ba7 100644 --- a/examples/maturin-starter/Cargo.toml +++ b/examples/maturin-starter/Cargo.toml @@ -2,7 +2,7 @@ name = "maturin-starter" version = "0.1.0" edition = "2021" -rust-version = "1.63" +rust-version = "1.74" [lib] name = "maturin_starter" diff --git a/examples/plugin/Cargo.toml b/examples/plugin/Cargo.toml index 062dab1ff21..4cc6b003ed5 100644 --- a/examples/plugin/Cargo.toml +++ b/examples/plugin/Cargo.toml @@ -2,11 +2,11 @@ name = "plugin_example" version = "0.1.0" edition = "2021" -rust-version = "1.63" +rust-version = "1.74" [dependencies] -pyo3={path="../../", features=["macros"]} -plugin_api={path="plugin_api"} +pyo3 = { path = "../../", features = ["macros"] } +plugin_api = { path = "plugin_api" } [workspace] diff --git a/examples/setuptools-rust-starter/Cargo.toml b/examples/setuptools-rust-starter/Cargo.toml index ffabd8df849..6132cf878e2 100644 --- a/examples/setuptools-rust-starter/Cargo.toml +++ b/examples/setuptools-rust-starter/Cargo.toml @@ -2,7 +2,7 @@ name = "setuptools-rust-starter" version = "0.1.0" edition = "2021" -rust-version = "1.63" +rust-version = "1.74" [lib] name = "setuptools_rust_starter" diff --git a/examples/word-count/Cargo.toml b/examples/word-count/Cargo.toml index 8d79c8a4ff9..514bcfe06e2 100644 --- a/examples/word-count/Cargo.toml +++ b/examples/word-count/Cargo.toml @@ -2,7 +2,7 @@ name = "word-count" version = "0.1.0" edition = "2021" -rust-version = "1.63" +rust-version = "1.74" [lib] name = "word_count" diff --git a/newsfragments/5171.packaging.md b/newsfragments/5171.packaging.md new file mode 100644 index 00000000000..cb502462c1a --- /dev/null +++ b/newsfragments/5171.packaging.md @@ -0,0 +1,2 @@ +Update MSRV to 1.74. +Drop `once_cell` dependency. diff --git a/noxfile.py b/noxfile.py index c2b95fe0842..2d1fb0a437d 100644 --- a/noxfile.py +++ b/noxfile.py @@ -1,4 +1,3 @@ -from contextlib import contextmanager import json import os import re @@ -7,6 +6,7 @@ import sys import sysconfig import tempfile +from contextlib import contextmanager from functools import lru_cache from glob import glob from pathlib import Path @@ -21,7 +21,6 @@ Tuple, ) -import nox import nox.command try: @@ -460,14 +459,6 @@ def docs(session: nox.Session) -> None: features = "full" - if get_rust_version()[:2] >= (1, 67): - # time needs MSRC 1.67+ - features += ",time" - - if get_rust_version()[:2] >= (1, 70): - # jiff needs MSRC 1.70+ - features += ",jiff-02" - shutil.rmtree(PYO3_DOCS_TARGET, ignore_errors=True) _run_cargo( session, @@ -670,10 +661,7 @@ def set_msrv_package_versions(session: nox.Session): *(Path(p).parent for p in glob("examples/*/Cargo.toml")), *(Path(p).parent for p in glob("pyo3-ffi/examples/*/Cargo.toml")), ) - min_pkg_versions = { - "trybuild": "1.0.89", - "allocator-api2": "0.2.10", - } + min_pkg_versions = {} # run cargo update first to ensure that everything is at highest # possible version, so that this matches what CI will resolve to. @@ -854,8 +842,8 @@ def update_ui_tests(session: nox.Session): env["TRYBUILD"] = "overwrite" command = ["test", "--test", "test_compile_error"] _run_cargo(session, *command, env=env) - _run_cargo(session, *command, "--features=full,jiff-02,time", env=env) - _run_cargo(session, *command, "--features=abi3,full,jiff-02,time", env=env) + _run_cargo(session, *command, "--features=full", env=env) + _run_cargo(session, *command, "--features=abi3,full", env=env) @nox.session(name="test-introspection") @@ -925,14 +913,6 @@ def _get_feature_sets() -> Tuple[Optional[str], ...]: # multiple-pymethods not supported on wasm features += ",multiple-pymethods" - if get_rust_version()[:2] >= (1, 67): - # time needs MSRC 1.67+ - features += ",time" - - if get_rust_version()[:2] >= (1, 70): - # jiff needs MSRC 1.70+ - features += ",jiff-02" - if is_rust_nightly(): features += ",nightly" diff --git a/pyo3-build-config/Cargo.toml b/pyo3-build-config/Cargo.toml index 8070860a1e9..3786b4e6f7c 100644 --- a/pyo3-build-config/Cargo.toml +++ b/pyo3-build-config/Cargo.toml @@ -9,10 +9,9 @@ repository = "https://github.com/pyo3/pyo3" categories = ["api-bindings", "development-tools::ffi"] license = "MIT OR Apache-2.0" edition = "2021" -rust-version = "1.63" +rust-version.workspace = true [dependencies] -once_cell = "1" python3-dll-a = { version = "0.2.12", optional = true } target-lexicon = "0.13" diff --git a/pyo3-build-config/src/impl_.rs b/pyo3-build-config/src/impl_.rs index 9485b8a0394..458e9a20db3 100644 --- a/pyo3-build-config/src/impl_.rs +++ b/pyo3-build-config/src/impl_.rs @@ -845,7 +845,7 @@ fn have_python_interpreter() -> bool { /// Must be called from a PyO3 crate build script. fn is_abi3() -> bool { cargo_env_var("CARGO_FEATURE_ABI3").is_some() - || env_var("PYO3_USE_ABI3_FORWARD_COMPATIBILITY").map_or(false, |os_str| os_str == "1") + || env_var("PYO3_USE_ABI3_FORWARD_COMPATIBILITY").is_some_and(|os_str| os_str == "1") } /// Gets the minimum supported Python version from PyO3 `abi3-py*` features. @@ -1464,7 +1464,7 @@ fn search_lib_dir(path: impl AsRef, cross: &CrossCompileConfig) -> Result< sysconfig_paths.extend(match &f { // Python 3.7+ sysconfigdata with platform specifics Ok(f) if starts_with(f, "_sysconfigdata_") && ends_with(f, "py") => vec![f.path()], - Ok(f) if f.metadata().map_or(false, |metadata| metadata.is_dir()) => { + Ok(f) if f.metadata().is_ok_and(|metadata| metadata.is_dir()) => { let file_name = f.file_name(); let file_name = file_name.to_string_lossy(); if file_name == "build" || file_name == "lib" { diff --git a/pyo3-build-config/src/lib.rs b/pyo3-build-config/src/lib.rs index f47c16f425d..22f0d333522 100644 --- a/pyo3-build-config/src/lib.rs +++ b/pyo3-build-config/src/lib.rs @@ -16,9 +16,7 @@ use std::{ path::{Path, PathBuf}, }; -use std::{env, process::Command, str::FromStr}; - -use once_cell::sync::OnceCell; +use std::{env, process::Command, str::FromStr, sync::OnceLock}; pub use impl_::{ cross_compiling_from_to, find_all_sysconfigdata, parse_sysconfigdata, BuildFlag, BuildFlags, @@ -109,10 +107,10 @@ fn _add_python_framework_link_args( /// Loads the configuration determined from the build environment. /// -/// Because this will never change in a given compilation run, this is cached in a `once_cell`. +/// Because this will never change in a given compilation run, this is cached in a `OnceLock`. #[cfg(feature = "resolve-config")] pub fn get() -> &'static InterpreterConfig { - static CONFIG: OnceCell = OnceCell::new(); + static CONFIG: OnceLock = OnceLock::new(); CONFIG.get_or_init(|| { // Check if we are in a build script and cross compiling to a different target. let cross_compile_config_path = resolve_cross_compile_config_path(); @@ -183,10 +181,6 @@ fn print_feature_cfg(minor_version_required: u32, cfg: &str) { /// so this function is unstable. #[doc(hidden)] pub fn print_feature_cfgs() { - print_feature_cfg(70, "rustc_has_once_lock"); - print_feature_cfg(70, "cargo_toml_lints"); - print_feature_cfg(71, "rustc_has_extern_c_unwind"); - print_feature_cfg(74, "invalid_from_utf8_lint"); print_feature_cfg(79, "c_str_lit"); // Actually this is available on 1.78, but we should avoid // https://github.com/rust-lang/rust/issues/124651 just in case @@ -201,7 +195,7 @@ pub fn print_feature_cfgs() { /// - #[doc(hidden)] pub fn print_expected_cfgs() { - if rustc_minor_version().map_or(false, |version| version < 80) { + if rustc_minor_version().is_some_and(|version| version < 80) { // rustc 1.80.0 stabilized `rustc-check-cfg` feature, don't emit before return; } @@ -280,7 +274,7 @@ pub mod pyo3_build_script_impl { } fn rustc_minor_version() -> Option { - static RUSTC_MINOR_VERSION: OnceCell> = OnceCell::new(); + static RUSTC_MINOR_VERSION: OnceLock> = OnceLock::new(); *RUSTC_MINOR_VERSION.get_or_init(|| { let rustc = env::var_os("RUSTC")?; let output = Command::new(rustc).arg("--version").output().ok()?; diff --git a/pyo3-ffi/Cargo.toml b/pyo3-ffi/Cargo.toml index 647b76fbdcd..91c1517786a 100644 --- a/pyo3-ffi/Cargo.toml +++ b/pyo3-ffi/Cargo.toml @@ -10,7 +10,7 @@ categories = ["api-bindings", "development-tools::ffi"] license = "MIT OR Apache-2.0" edition = "2021" links = "python" -rust-version = "1.63" +rust-version.workspace = true [dependencies] libc = "0.2.62" @@ -51,8 +51,8 @@ workspace = true [package.metadata.cpython] min-version = "3.7" -max-version = "3.14" # inclusive +max-version = "3.14" # inclusive [package.metadata.pypy] min-version = "3.9" -max-version = "3.11" # inclusive +max-version = "3.11" # inclusive diff --git a/pyo3-ffi/build.rs b/pyo3-ffi/build.rs index 6776cd80476..455089ee5a2 100644 --- a/pyo3-ffi/build.rs +++ b/pyo3-ffi/build.rs @@ -63,7 +63,7 @@ fn ensure_python_version(interpreter_config: &InterpreterConfig) -> Result<()> { = help: The free-threaded build of CPython does not support the limited API so this check cannot be suppressed.", interpreter_config.version, versions.max, std::env::var("CARGO_PKG_VERSION").unwrap() ); - ensure!(env_var("PYO3_USE_ABI3_FORWARD_COMPATIBILITY").map_or(false, |os_str| os_str == "1"), + ensure!(env_var("PYO3_USE_ABI3_FORWARD_COMPATIBILITY").is_some_and(|os_str| os_str == "1"), "the configured Python interpreter version ({}) is newer than PyO3's maximum supported version ({})\n\ = help: please check if an updated version of PyO3 is available. Current version: {}\n\ = help: set PYO3_USE_ABI3_FORWARD_COMPATIBILITY=1 to suppress this check and build anyway using the stable ABI", @@ -192,7 +192,7 @@ fn emit_link_config(interpreter_config: &InterpreterConfig) -> Result<()> { fn configure_pyo3() -> Result<()> { let interpreter_config = resolve_interpreter_config()?; - if env_var("PYO3_PRINT_CONFIG").map_or(false, |os_str| os_str == "1") { + if env_var("PYO3_PRINT_CONFIG").is_some_and(|os_str| os_str == "1") { print_config_and_exit(&interpreter_config); } @@ -215,7 +215,6 @@ fn configure_pyo3() -> Result<()> { println!("{line}"); } - // Emit cfgs like `invalid_from_utf8_lint` print_feature_cfgs(); Ok(()) diff --git a/pyo3-ffi/examples/sequential/Cargo.toml b/pyo3-ffi/examples/sequential/Cargo.toml index e62693303d9..950322ec0b7 100644 --- a/pyo3-ffi/examples/sequential/Cargo.toml +++ b/pyo3-ffi/examples/sequential/Cargo.toml @@ -2,7 +2,7 @@ name = "sequential" version = "0.1.0" edition = "2021" -rust-version = "1.63" +rust-version = "1.74" [lib] name = "sequential" diff --git a/pyo3-ffi/examples/string-sum/Cargo.toml b/pyo3-ffi/examples/string-sum/Cargo.toml index b0f784d26f2..29724e14c66 100644 --- a/pyo3-ffi/examples/string-sum/Cargo.toml +++ b/pyo3-ffi/examples/string-sum/Cargo.toml @@ -2,7 +2,7 @@ name = "string_sum" version = "0.1.0" edition = "2021" -rust-version = "1.63" +rust-version = "1.74" [lib] name = "string_sum" diff --git a/pyo3-ffi/src/cpython/genobject.rs b/pyo3-ffi/src/cpython/genobject.rs index 92f14d59e4b..05ee6f3f652 100644 --- a/pyo3-ffi/src/cpython/genobject.rs +++ b/pyo3-ffi/src/cpython/genobject.rs @@ -18,6 +18,7 @@ pub struct PyGenObject { pub gi_weakreflist: *mut PyObject, pub gi_name: *mut PyObject, pub gi_qualname: *mut PyObject, + #[allow(private_interfaces)] pub gi_exc_state: crate::cpython::pystate::_PyErr_StackItem, #[cfg(Py_3_11)] pub gi_origin_or_finalizer: *mut PyObject, diff --git a/pyo3-ffi/src/cpython/pystate.rs b/pyo3-ffi/src/cpython/pystate.rs index b8f6fd667b7..707d4945f49 100644 --- a/pyo3-ffi/src/cpython/pystate.rs +++ b/pyo3-ffi/src/cpython/pystate.rs @@ -31,8 +31,7 @@ pub const PyTrace_OPCODE: c_int = 7; #[cfg(not(PyPy))] #[repr(C)] #[derive(Clone, Copy)] -#[doc(hidden)] // TODO should be able to make pub(crate) after MSRV 1.74 -pub struct _PyErr_StackItem { +pub(crate) struct _PyErr_StackItem { #[cfg(not(Py_3_11))] exc_type: *mut PyObject, exc_value: *mut PyObject, diff --git a/pyo3-ffi/src/lib.rs b/pyo3-ffi/src/lib.rs index 95f59d35192..cd24fd86221 100644 --- a/pyo3-ffi/src/lib.rs +++ b/pyo3-ffi/src/lib.rs @@ -373,26 +373,13 @@ macro_rules! c_str { /// Private helper for `c_str!` macro. #[doc(hidden)] -pub const fn _cstr_from_utf8_with_nul_checked(s: &str) -> &CStr { - // TODO: Replace this implementation with `CStr::from_bytes_with_nul` when MSRV above 1.72. - let bytes = s.as_bytes(); - let len = bytes.len(); - assert!( - !bytes.is_empty() && bytes[bytes.len() - 1] == b'\0', - "string is not nul-terminated" - ); - let mut i = 0; - let non_null_len = len - 1; - while i < non_null_len { - assert!(bytes[i] != b'\0', "string contains null bytes"); - i += 1; +pub const fn _cstr_from_utf8_with_nul_checked(s: &str) -> &std::ffi::CStr { + match std::ffi::CStr::from_bytes_with_nul(s.as_bytes()) { + Ok(cstr) => cstr, + Err(_) => panic!("string contains nul bytes"), } - - unsafe { CStr::from_bytes_with_nul_unchecked(bytes) } } -use std::ffi::CStr; - pub mod compat; mod impl_; diff --git a/pyo3-ffi/src/pystate.rs b/pyo3-ffi/src/pystate.rs index 080fa11201c..2a41f937257 100644 --- a/pyo3-ffi/src/pystate.rs +++ b/pyo3-ffi/src/pystate.rs @@ -102,13 +102,13 @@ impl Drop for HangThread { // C-unwind only supported (and necessary) since 1.71. Python 3.14+ does not do // pthread_exit from PyGILState_Ensure (https://github.com/python/cpython/issues/87135). mod raw { - #[cfg(all(not(Py_3_14), rustc_has_extern_c_unwind))] + #[cfg(not(Py_3_14))] extern "C-unwind" { #[cfg_attr(PyPy, link_name = "PyPyGILState_Ensure")] pub fn PyGILState_Ensure() -> super::PyGILState_STATE; } - #[cfg(not(all(not(Py_3_14), rustc_has_extern_c_unwind)))] + #[cfg(Py_3_14)] extern "C" { #[cfg_attr(PyPy, link_name = "PyPyGILState_Ensure")] pub fn PyGILState_Ensure() -> super::PyGILState_STATE; diff --git a/pyo3-macros-backend/Cargo.toml b/pyo3-macros-backend/Cargo.toml index d626fa1ebf7..5b80e455554 100644 --- a/pyo3-macros-backend/Cargo.toml +++ b/pyo3-macros-backend/Cargo.toml @@ -9,7 +9,7 @@ repository = "https://github.com/pyo3/pyo3" categories = ["api-bindings", "development-tools::ffi"] license = "MIT OR Apache-2.0" edition = "2021" -rust-version = "1.63" +rust-version.workspace = true # Note: we use default-features = false for proc-macro related crates # not to depend on proc-macro itself. @@ -21,7 +21,8 @@ pyo3-build-config = { path = "../pyo3-build-config", version = "=0.25.0", featur quote = { version = "1", default-features = false } [dependencies.syn] -version = "2.0.59" # for `LitCStr` +# 2.0.59 for `LitCStr` +version = "2.0.59" default-features = false features = ["derive", "parsing", "printing", "clone-impls", "full", "extra-traits"] diff --git a/pyo3-macros-backend/src/pyclass.rs b/pyo3-macros-backend/src/pyclass.rs index ea2a4ae7cc3..156a4bb7498 100644 --- a/pyo3-macros-backend/src/pyclass.rs +++ b/pyo3-macros-backend/src/pyclass.rs @@ -2006,7 +2006,6 @@ fn pyclass_hash( options.eq.is_some(), options.hash.span() => "The `hash` option requires the `eq` option."; ); } - // FIXME: Use hash.map(...).unzip() on MSRV >= 1.66 match options.hash { Some(opt) => { let mut hash_impl = parse_quote_spanned! { opt.span() => diff --git a/pyo3-macros/Cargo.toml b/pyo3-macros/Cargo.toml index 7df15c23f76..a38dac4b3a1 100644 --- a/pyo3-macros/Cargo.toml +++ b/pyo3-macros/Cargo.toml @@ -9,7 +9,7 @@ repository = "https://github.com/pyo3/pyo3" categories = ["api-bindings", "development-tools::ffi"] license = "MIT OR Apache-2.0" edition = "2021" -rust-version = "1.63" +rust-version.workspace = true [lib] proc-macro = true diff --git a/pytests/Cargo.toml b/pytests/Cargo.toml index 2763cdec2e3..b744c6e16cc 100644 --- a/pytests/Cargo.toml +++ b/pytests/Cargo.toml @@ -5,7 +5,7 @@ version = "0.1.0" description = "Python-based tests for PyO3" edition = "2021" publish = false -rust-version = "1.63" +rust-version = "1.74" [dependencies] pyo3 = { path = "../", features = ["extension-module", "experimental-inspect"] } diff --git a/pytests/src/misc.rs b/pytests/src/misc.rs index b3ef5ee283e..7beafe78020 100644 --- a/pytests/src/misc.rs +++ b/pytests/src/misc.rs @@ -12,8 +12,7 @@ fn issue_219() { #[pyclass] struct LockHolder { #[allow(unused)] - // Mutex needed for the MSRV - sender: std::sync::Mutex>, + sender: std::sync::mpsc::Sender<()>, } // This will hammer the GIL once the LockHolder is dropped. @@ -28,9 +27,7 @@ fn hammer_gil_in_thread() -> LockHolder { Python::with_gil(|_py| ()); } }); - LockHolder { - sender: std::sync::Mutex::new(sender), - } + LockHolder { sender } } #[pyfunction] diff --git a/src/conversions/jiff.rs b/src/conversions/jiff.rs index 814040e5fb4..83cacc83fe7 100644 --- a/src/conversions/jiff.rs +++ b/src/conversions/jiff.rs @@ -14,8 +14,7 @@ //! ``` //! //! Note that you must use compatible versions of jiff and PyO3. -//! The required jiff version may vary based on the version of PyO3. Jiff also requires a MSRV -//! of 1.70. +//! The required jiff version may vary based on the version of PyO3. //! //! # Example: Convert a `datetime.datetime` to jiff `Zoned` //! diff --git a/src/conversions/num_bigint.rs b/src/conversions/num_bigint.rs index ebc4058b1a6..7dadf842c84 100644 --- a/src/conversions/num_bigint.rs +++ b/src/conversions/num_bigint.rs @@ -139,7 +139,7 @@ impl<'py> FromPyObject<'py> for BigInt { #[cfg(not(Py_LIMITED_API))] { let mut buffer = int_to_u32_vec::(num)?; - let sign = if buffer.last().copied().map_or(false, |last| last >> 31 != 0) { + let sign = if buffer.last().copied().is_some_and(|last| last >> 31 != 0) { // BigInt::new takes an unsigned array, so need to convert from two's complement // flip all bits, 'subtract' 1 (by adding one to the unsigned array) let mut elements = buffer.iter_mut(); @@ -195,7 +195,7 @@ impl<'py> FromPyObject<'py> for BigUint { if n_bits == 0 { return Ok(BigUint::from(0usize)); } - let bytes = int_to_py_bytes(num, (n_bits + 7) / 8, false)?; + let bytes = int_to_py_bytes(num, n_bits.div_ceil(8), false)?; Ok(BigUint::from_bytes_le(bytes.as_bytes())) } } @@ -212,7 +212,7 @@ fn int_to_u32_vec(long: &Bound<'_, PyInt>) -> PyResult(long: &Bound<'_, PyInt>) -> PyResult= 1.73 - let n_digits = { - let adjust = if n_bytes % 4 == 0 { 0 } else { 1 }; - (n_bytes_unsigned / 4) + adjust - }; + let n_digits = n_bytes_unsigned.div_ceil(4); buffer.reserve_exact(n_digits); unsafe { ffi::PyLong_AsNativeBytes( diff --git a/src/err/impls.rs b/src/err/impls.rs index 814544a86f4..36ae3c6255a 100644 --- a/src/err/impls.rs +++ b/src/err/impls.rs @@ -49,7 +49,7 @@ impl From for io::Error { impl From for PyErr { fn from(err: io::Error) -> PyErr { // If the error wraps a Python error we return it - if err.get_ref().map_or(false, |e| e.is::()) { + if err.get_ref().is_some_and(|e| e.is::()) { return *err.into_inner().unwrap().downcast().unwrap(); } match err.kind() { diff --git a/src/exceptions.rs b/src/exceptions.rs index 5f39d474a72..661604ef325 100644 --- a/src/exceptions.rs +++ b/src/exceptions.rs @@ -658,13 +658,13 @@ impl PyUnicodeDecodeError { /// # Examples /// /// ``` - /// #![cfg_attr(invalid_from_utf8_lint, allow(invalid_from_utf8))] /// use pyo3::prelude::*; /// use pyo3::exceptions::PyUnicodeDecodeError; /// /// # fn main() -> PyResult<()> { /// Python::with_gil(|py| { /// let invalid_utf8 = b"fo\xd8o"; + /// # #[allow(invalid_from_utf8)] /// let err = std::str::from_utf8(invalid_utf8).expect_err("should be invalid utf8"); /// let decode_err = PyUnicodeDecodeError::new_utf8(py, invalid_utf8, err)?; /// assert_eq!( @@ -1020,7 +1020,7 @@ mod tests { #[test] fn unicode_decode_error() { let invalid_utf8 = b"fo\xd8o"; - #[cfg_attr(invalid_from_utf8_lint, allow(invalid_from_utf8))] + #[allow(invalid_from_utf8)] let err = std::str::from_utf8(invalid_utf8).expect_err("should be invalid utf8"); Python::with_gil(|py| { let decode_err = PyUnicodeDecodeError::new_utf8(py, invalid_utf8, err).unwrap(); @@ -1077,7 +1077,7 @@ mod tests { test_exception!(PyUnicodeError); test_exception!(PyUnicodeDecodeError, |py| { let invalid_utf8 = b"fo\xd8o"; - #[cfg_attr(invalid_from_utf8_lint, allow(invalid_from_utf8))] + #[allow(invalid_from_utf8)] let err = std::str::from_utf8(invalid_utf8).expect_err("should be invalid utf8"); PyErr::from_value( PyUnicodeDecodeError::new_utf8(py, invalid_utf8, err) diff --git a/src/gil.rs b/src/gil.rs index a8a170a0bba..f62e13762f0 100644 --- a/src/gil.rs +++ b/src/gil.rs @@ -3,9 +3,9 @@ #[cfg(pyo3_disable_reference_pool)] use crate::impl_::panic::PanicTrap; use crate::{ffi, Python}; -#[cfg(not(pyo3_disable_reference_pool))] -use once_cell::sync::Lazy; use std::cell::Cell; +#[cfg(not(pyo3_disable_reference_pool))] +use std::sync::OnceLock; use std::{mem, ptr::NonNull, sync}; static START: sync::Once = sync::Once::new(); @@ -209,7 +209,7 @@ impl GILGuard { increment_gil_count(); #[cfg(not(pyo3_disable_reference_pool))] - if let Some(pool) = Lazy::get(&POOL) { + if let Some(pool) = POOL.get() { pool.update_counts(unsafe { Python::assume_gil_acquired() }); } GILGuard::Ensured { gstate } @@ -220,7 +220,7 @@ impl GILGuard { increment_gil_count(); let guard = GILGuard::Assumed; #[cfg(not(pyo3_disable_reference_pool))] - if let Some(pool) = Lazy::get(&POOL) { + if let Some(pool) = POOL.get() { pool.update_counts(guard.python()); } guard @@ -290,7 +290,12 @@ unsafe impl Send for ReferencePool {} unsafe impl Sync for ReferencePool {} #[cfg(not(pyo3_disable_reference_pool))] -static POOL: Lazy = Lazy::new(ReferencePool::new); +static POOL: OnceLock = OnceLock::new(); + +#[cfg(not(pyo3_disable_reference_pool))] +fn get_pool() -> &'static ReferencePool { + POOL.get_or_init(ReferencePool::new) +} /// A guard which can be used to temporarily release the GIL and restore on `Drop`. pub(crate) struct SuspendGIL { @@ -315,7 +320,7 @@ impl Drop for SuspendGIL { // Update counts of PyObjects / Py that were cloned or dropped while the GIL was released. #[cfg(not(pyo3_disable_reference_pool))] - if let Some(pool) = Lazy::get(&POOL) { + if let Some(pool) = POOL.get() { pool.update_counts(Python::assume_gil_acquired()); } } @@ -385,7 +390,7 @@ pub unsafe fn register_decref(obj: NonNull) { unsafe { ffi::Py_DECREF(obj.as_ptr()) } } else { #[cfg(not(pyo3_disable_reference_pool))] - POOL.register_decref(obj); + get_pool().register_decref(obj); #[cfg(all( pyo3_disable_reference_pool, not(pyo3_leak_on_drop_without_reference_pool) @@ -428,7 +433,7 @@ fn decrement_gil_count() { mod tests { use super::GIL_COUNT; #[cfg(not(pyo3_disable_reference_pool))] - use super::{gil_is_acquired, POOL}; + use super::{get_pool, gil_is_acquired}; use crate::{ffi, PyObject, Python}; use crate::{gil::GILGuard, types::any::PyAnyMethods}; use std::ptr::NonNull; @@ -441,7 +446,7 @@ mod tests { #[cfg(not(pyo3_disable_reference_pool))] fn pool_dec_refs_does_not_contain(obj: &PyObject) -> bool { - !POOL + !get_pool() .pending_decrefs .lock() .unwrap() @@ -452,7 +457,8 @@ mod tests { // function does not test anything meaningful #[cfg(not(any(pyo3_disable_reference_pool, Py_GIL_DISABLED)))] fn pool_dec_refs_contains(obj: &PyObject) -> bool { - POOL.pending_decrefs + get_pool() + .pending_decrefs .lock() .unwrap() .contains(&unsafe { NonNull::new_unchecked(obj.as_ptr()) }) @@ -634,10 +640,10 @@ mod tests { let capsule = unsafe { ffi::PyCapsule_New(ptr as _, std::ptr::null(), Some(capsule_drop)) }; - POOL.register_decref(NonNull::new(capsule).unwrap()); + get_pool().register_decref(NonNull::new(capsule).unwrap()); // Updating the counts will call decref on the capsule, which calls capsule_drop - POOL.update_counts(py); + get_pool().update_counts(py); }) } @@ -651,7 +657,7 @@ mod tests { // For GILGuard::acquire - POOL.register_decref(NonNull::new(obj.clone_ref(py).into_ptr()).unwrap()); + get_pool().register_decref(NonNull::new(obj.clone_ref(py).into_ptr()).unwrap()); #[cfg(not(Py_GIL_DISABLED))] assert!(pool_dec_refs_contains(&obj)); let _guard = GILGuard::acquire(); @@ -659,7 +665,7 @@ mod tests { // For GILGuard::assume - POOL.register_decref(NonNull::new(obj.clone_ref(py).into_ptr()).unwrap()); + get_pool().register_decref(NonNull::new(obj.clone_ref(py).into_ptr()).unwrap()); #[cfg(not(Py_GIL_DISABLED))] assert!(pool_dec_refs_contains(&obj)); let _guard2 = unsafe { GILGuard::assume() }; diff --git a/src/lib.rs b/src/lib.rs index 8d684b67c18..ea160ed0966 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,8 +4,7 @@ feature(auto_traits, negative_impls, try_trait_v2, iter_advance_by) )] #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] -#![cfg_attr(not(cargo_toml_lints), warn(unsafe_op_in_unsafe_fn))] -// necessary for MSRV 1.63 to build +#![warn(unsafe_op_in_unsafe_fn)] // Deny some lints in doctests. // Use `#[allow(...)]` locally to override. #![doc(test(attr( @@ -16,6 +15,7 @@ warnings ), allow( + unused_imports, // to make imports already in the prelude explicit unused_variables, unused_assignments, unused_extern_crates, diff --git a/src/sync.rs b/src/sync.rs index e1a74eae7bd..ba8f3da3310 100644 --- a/src/sync.rs +++ b/src/sync.rs @@ -527,7 +527,6 @@ where } } -#[cfg(rustc_has_once_lock)] mod once_lock_ext_sealed { pub trait Sealed {} impl Sealed for std::sync::OnceLock {} @@ -550,7 +549,6 @@ pub trait OnceExt: Sealed { /// Extension trait for [`std::sync::OnceLock`] which helps avoid deadlocks between the Python /// interpreter and initialization with the `OnceLock`. -#[cfg(rustc_has_once_lock)] pub trait OnceLockExt: once_lock_ext_sealed::Sealed { /// Initializes this `OnceLock` with the given closure if it has not been initialized yet. /// @@ -637,15 +635,11 @@ impl OnceExt for parking_lot::Once { } } -#[cfg(rustc_has_once_lock)] impl OnceLockExt for std::sync::OnceLock { fn get_or_init_py_attached(&self, py: Python<'_>, f: F) -> &T where F: FnOnce() -> T, { - // this trait is guarded by a rustc version config - // so clippy's MSRV check is wrong - #[allow(clippy::incompatible_msrv)] // Use self.get() first to create a fast path when initialized self.get() .unwrap_or_else(|| init_once_lock_py_attached(self, py, f)) @@ -747,7 +741,6 @@ where }); } -#[cfg(rustc_has_once_lock)] #[cold] fn init_once_lock_py_attached<'a, F, T>( lock: &'a std::sync::OnceLock, @@ -1086,7 +1079,6 @@ mod tests { test_once!(parking_lot::Once::new(), parking_lot::OnceState::poisoned); } - #[cfg(rustc_has_once_lock)] #[cfg(not(target_arch = "wasm32"))] // We are building wasm Python with pthreads disabled #[test] fn test_once_lock_ext() { diff --git a/src/types/boolobject.rs b/src/types/boolobject.rs index 28806f8c4cf..bbeae6afb48 100644 --- a/src/types/boolobject.rs +++ b/src/types/boolobject.rs @@ -180,10 +180,10 @@ impl FromPyObject<'_> for bool { let is_numpy_bool = { let ty = obj.get_type(); - ty.module().map_or(false, |module| module == "numpy") + ty.module().is_ok_and(|module| module == "numpy") && ty .name() - .map_or(false, |name| name == "bool_" || name == "bool") + .is_ok_and(|name| name == "bool_" || name == "bool") }; if is_numpy_bool { diff --git a/src/types/string.rs b/src/types/string.rs index 774624df108..59300440231 100644 --- a/src/types/string.rs +++ b/src/types/string.rs @@ -488,7 +488,7 @@ impl PartialEq for Borrowed<'_, '_, PyString> { fn eq(&self, other: &str) -> bool { #[cfg(not(Py_3_13))] { - self.to_cow().map_or(false, |s| s == other) + self.to_cow().is_ok_and(|s| s == other) } #[cfg(Py_3_13)] diff --git a/src/types/weakref/anyref.rs b/src/types/weakref/anyref.rs index d496c175376..096bf9657ce 100644 --- a/src/types/weakref/anyref.rs +++ b/src/types/weakref/anyref.rs @@ -380,10 +380,8 @@ mod tests { let obj = obj.unwrap(); assert!(obj.is_some()); - assert!( - obj.map_or(false, |obj| ptr::eq(obj.as_ptr(), object.as_ptr()) - && obj.is_exact_instance(&class)) - ); + assert!(obj.is_some_and(|obj| ptr::eq(obj.as_ptr(), object.as_ptr()) + && obj.is_exact_instance(&class))); } drop(object); @@ -424,10 +422,8 @@ mod tests { let obj = unsafe { reference.upgrade_as_unchecked::() }; assert!(obj.is_some()); - assert!( - obj.map_or(false, |obj| ptr::eq(obj.as_ptr(), object.as_ptr()) - && obj.is_exact_instance(&class)) - ); + assert!(obj.is_some_and(|obj| ptr::eq(obj.as_ptr(), object.as_ptr()) + && obj.is_exact_instance(&class))); } drop(object); @@ -465,7 +461,7 @@ mod tests { assert!(not_call_retrievable || reference.call0()?.is(&object)); assert!(reference.upgrade().is_some()); - assert!(reference.upgrade().map_or(false, |obj| obj.is(&object))); + assert!(reference.upgrade().is_some_and(|obj| obj.is(&object))); drop(object); @@ -510,7 +506,7 @@ mod tests { let obj = obj.unwrap(); assert!(obj.is_some()); - assert!(obj.map_or(false, |obj| ptr::eq(obj.as_ptr(), object.as_ptr()))); + assert!(obj.is_some_and(|obj| ptr::eq(obj.as_ptr(), object.as_ptr()))); } drop(object); @@ -548,7 +544,7 @@ mod tests { let obj = unsafe { reference.upgrade_as_unchecked::() }; assert!(obj.is_some()); - assert!(obj.map_or(false, |obj| ptr::eq(obj.as_ptr(), object.as_ptr()))); + assert!(obj.is_some_and(|obj| ptr::eq(obj.as_ptr(), object.as_ptr()))); } drop(object); @@ -584,7 +580,7 @@ mod tests { assert!(not_call_retrievable || reference.call0()?.is(&object)); assert!(reference.upgrade().is_some()); - assert!(reference.upgrade().map_or(false, |obj| obj.is(&object))); + assert!(reference.upgrade().is_some_and(|obj| obj.is(&object))); drop(object); diff --git a/src/types/weakref/proxy.rs b/src/types/weakref/proxy.rs index f60321fdd3d..5e18cdd4998 100644 --- a/src/types/weakref/proxy.rs +++ b/src/types/weakref/proxy.rs @@ -55,8 +55,7 @@ impl PyWeakrefProxy { /// let weakref = PyWeakrefProxy::new(&foo)?; /// assert!( /// // In normal situations where a direct `Bound<'py, Foo>` is required use `upgrade::` - /// weakref.upgrade() - /// .map_or(false, |obj| obj.is(&foo)) + /// weakref.upgrade().is_some_and(|obj| obj.is(&foo)) /// ); /// /// let weakref2 = PyWeakrefProxy::new(&foo)?; @@ -118,8 +117,7 @@ impl PyWeakrefProxy { /// assert!(weakref.upgrade_as::()?.is_some()); /// assert!( /// // In normal situations where a direct `Bound<'py, Foo>` is required use `upgrade::` - /// weakref.upgrade() - /// .map_or(false, |obj| obj.is(&foo)) + /// weakref.upgrade().is_some_and(|obj| obj.is(&foo)) /// ); /// assert_eq!(py.eval(c_str!("counter"), None, None)?.extract::()?, 0); /// @@ -275,9 +273,9 @@ mod tests { assert!(reference .getattr("__callback__") .err() - .map_or(false, |err| err.is_instance_of::(py))); + .is_some_and(|err| err.is_instance_of::(py))); - assert!(reference.call0().err().map_or(false, |err| { + assert!(reference.call0().err().is_some_and(|err| { let result = err.is_instance_of::(py); #[cfg(not(Py_LIMITED_API))] let result = result @@ -292,16 +290,16 @@ mod tests { assert!(reference .getattr("__class__") .err() - .map_or(false, |err| err.is_instance_of::(py))); + .is_some_and(|err| err.is_instance_of::(py))); #[cfg(not(Py_LIMITED_API))] check_repr(&reference, py.None().bind(py), None)?; assert!(reference .getattr("__callback__") .err() - .map_or(false, |err| err.is_instance_of::(py))); + .is_some_and(|err| err.is_instance_of::(py))); - assert!(reference.call0().err().map_or(false, |err| { + assert!(reference.call0().err().is_some_and(|err| { let result = err.is_instance_of::(py); #[cfg(not(Py_LIMITED_API))] let result = result @@ -329,10 +327,8 @@ mod tests { let obj = obj.unwrap(); assert!(obj.is_some()); - assert!( - obj.map_or(false, |obj| ptr::eq(obj.as_ptr(), object.as_ptr()) - && obj.is_exact_instance(&class)) - ); + assert!(obj.is_some_and(|obj| ptr::eq(obj.as_ptr(), object.as_ptr()) + && obj.is_exact_instance(&class))); } drop(object); @@ -363,10 +359,8 @@ mod tests { let obj = unsafe { reference.upgrade_as_unchecked::() }; assert!(obj.is_some()); - assert!( - obj.map_or(false, |obj| ptr::eq(obj.as_ptr(), object.as_ptr()) - && obj.is_exact_instance(&class)) - ); + assert!(obj.is_some_and(|obj| ptr::eq(obj.as_ptr(), object.as_ptr()) + && obj.is_exact_instance(&class))); } drop(object); @@ -390,7 +384,7 @@ mod tests { let reference = PyWeakrefProxy::new(&object)?; assert!(reference.upgrade().is_some()); - assert!(reference.upgrade().map_or(false, |obj| obj.is(&object))); + assert!(reference.upgrade().is_some_and(|obj| obj.is(&object))); drop(object); @@ -453,9 +447,9 @@ mod tests { assert!(reference .getattr("__callback__") .err() - .map_or(false, |err| err.is_instance_of::(py))); + .is_some_and(|err| err.is_instance_of::(py))); - assert!(reference.call0().err().map_or(false, |err| { + assert!(reference.call0().err().is_some_and(|err| { let result = err.is_instance_of::(py); #[cfg(not(Py_LIMITED_API))] let result = result @@ -470,16 +464,16 @@ mod tests { assert!(reference .getattr("__class__") .err() - .map_or(false, |err| err.is_instance_of::(py))); + .is_some_and(|err| err.is_instance_of::(py))); #[cfg(not(Py_LIMITED_API))] check_repr(&reference, py.None().bind(py), None)?; assert!(reference .getattr("__callback__") .err() - .map_or(false, |err| err.is_instance_of::(py))); + .is_some_and(|err| err.is_instance_of::(py))); - assert!(reference.call0().err().map_or(false, |err| { + assert!(reference.call0().err().is_some_and(|err| { let result = err.is_instance_of::(py); #[cfg(not(Py_LIMITED_API))] let result = result @@ -505,7 +499,7 @@ mod tests { let obj = obj.unwrap(); assert!(obj.is_some()); - assert!(obj.map_or(false, |obj| ptr::eq(obj.as_ptr(), object.as_ptr()))); + assert!(obj.is_some_and(|obj| ptr::eq(obj.as_ptr(), object.as_ptr()))); } drop(object); @@ -533,7 +527,7 @@ mod tests { let obj = unsafe { reference.upgrade_as_unchecked::() }; assert!(obj.is_some()); - assert!(obj.map_or(false, |obj| ptr::eq(obj.as_ptr(), object.as_ptr()))); + assert!(obj.is_some_and(|obj| ptr::eq(obj.as_ptr(), object.as_ptr()))); } drop(object); @@ -555,7 +549,7 @@ mod tests { let reference = PyWeakrefProxy::new(object.bind(py))?; assert!(reference.upgrade().is_some()); - assert!(reference.upgrade().map_or(false, |obj| obj.is(&object))); + assert!(reference.upgrade().is_some_and(|obj| obj.is(&object))); drop(object); @@ -611,7 +605,7 @@ mod tests { assert!(reference .getattr("__callback__") .err() - .map_or(false, |err| err.is_instance_of::(py))); + .is_some_and(|err| err.is_instance_of::(py))); assert_eq!(reference.call0()?.to_string(), "This class is callable!"); @@ -621,19 +615,19 @@ mod tests { assert!(reference .getattr("__class__") .err() - .map_or(false, |err| err.is_instance_of::(py))); + .is_some_and(|err| err.is_instance_of::(py))); #[cfg(not(Py_LIMITED_API))] check_repr(&reference, py.None().bind(py), None)?; assert!(reference .getattr("__callback__") .err() - .map_or(false, |err| err.is_instance_of::(py))); + .is_some_and(|err| err.is_instance_of::(py))); assert!(reference .call0() .err() - .map_or(false, |err| err.is_instance_of::(py) + .is_some_and(|err| err.is_instance_of::(py) & (err.value(py).to_string() == "weakly-referenced object no longer exists"))); @@ -656,10 +650,8 @@ mod tests { let obj = obj.unwrap(); assert!(obj.is_some()); - assert!( - obj.map_or(false, |obj| ptr::eq(obj.as_ptr(), object.as_ptr()) - && obj.is_exact_instance(&class)) - ); + assert!(obj.is_some_and(|obj| ptr::eq(obj.as_ptr(), object.as_ptr()) + && obj.is_exact_instance(&class))); } drop(object); @@ -690,10 +682,8 @@ mod tests { let obj = unsafe { reference.upgrade_as_unchecked::() }; assert!(obj.is_some()); - assert!( - obj.map_or(false, |obj| ptr::eq(obj.as_ptr(), object.as_ptr()) - && obj.is_exact_instance(&class)) - ); + assert!(obj.is_some_and(|obj| ptr::eq(obj.as_ptr(), object.as_ptr()) + && obj.is_exact_instance(&class))); } drop(object); @@ -717,7 +707,7 @@ mod tests { let reference = PyWeakrefProxy::new(&object)?; assert!(reference.upgrade().is_some()); - assert!(reference.upgrade().map_or(false, |obj| obj.is(&object))); + assert!(reference.upgrade().is_some_and(|obj| obj.is(&object))); drop(object); @@ -767,7 +757,7 @@ mod tests { assert!(reference .getattr("__callback__") .err() - .map_or(false, |err| err.is_instance_of::(py))); + .is_some_and(|err| err.is_instance_of::(py))); assert_eq!(reference.call0()?.to_string(), "This class is callable!"); @@ -777,19 +767,19 @@ mod tests { assert!(reference .getattr("__class__") .err() - .map_or(false, |err| err.is_instance_of::(py))); + .is_some_and(|err| err.is_instance_of::(py))); #[cfg(not(Py_LIMITED_API))] check_repr(&reference, py.None().bind(py), None)?; assert!(reference .getattr("__callback__") .err() - .map_or(false, |err| err.is_instance_of::(py))); + .is_some_and(|err| err.is_instance_of::(py))); assert!(reference .call0() .err() - .map_or(false, |err| err.is_instance_of::(py) + .is_some_and(|err| err.is_instance_of::(py) & (err.value(py).to_string() == "weakly-referenced object no longer exists"))); @@ -810,7 +800,7 @@ mod tests { let obj = obj.unwrap(); assert!(obj.is_some()); - assert!(obj.map_or(false, |obj| ptr::eq(obj.as_ptr(), object.as_ptr()))); + assert!(obj.is_some_and(|obj| ptr::eq(obj.as_ptr(), object.as_ptr()))); } drop(object); @@ -838,7 +828,7 @@ mod tests { let obj = unsafe { reference.upgrade_as_unchecked::() }; assert!(obj.is_some()); - assert!(obj.map_or(false, |obj| ptr::eq(obj.as_ptr(), object.as_ptr()))); + assert!(obj.is_some_and(|obj| ptr::eq(obj.as_ptr(), object.as_ptr()))); } drop(object); @@ -860,7 +850,7 @@ mod tests { let reference = PyWeakrefProxy::new(object.bind(py))?; assert!(reference.upgrade().is_some()); - assert!(reference.upgrade().map_or(false, |obj| obj.is(&object))); + assert!(reference.upgrade().is_some_and(|obj| obj.is(&object))); drop(object); diff --git a/src/types/weakref/reference.rs b/src/types/weakref/reference.rs index a4540f7eaf9..11c3b740403 100644 --- a/src/types/weakref/reference.rs +++ b/src/types/weakref/reference.rs @@ -67,8 +67,7 @@ impl PyWeakrefReference { /// let weakref = PyWeakrefReference::new(&foo)?; /// assert!( /// // In normal situations where a direct `Bound<'py, Foo>` is required use `upgrade::` - /// weakref.upgrade() - /// .map_or(false, |obj| obj.is(&foo)) + /// weakref.upgrade().is_some_and(|obj| obj.is(&foo)) /// ); /// /// let weakref2 = PyWeakrefReference::new(&foo)?; @@ -129,8 +128,7 @@ impl PyWeakrefReference { /// assert!(weakref.upgrade_as::()?.is_some()); /// assert!( /// // In normal situations where a direct `Bound<'py, Foo>` is required use `upgrade::` - /// weakref.upgrade() - /// .map_or(false, |obj| obj.is(&foo)) + /// weakref.upgrade().is_some_and(|obj| obj.is(&foo)) /// ); /// assert_eq!(py.eval(c_str!("counter"), None, None)?.extract::()?, 0); /// @@ -269,7 +267,7 @@ mod tests { assert!(reference .getattr("__callback__") - .map_or(false, |result| result.is_none())); + .is_ok_and(|result| result.is_none())); assert!(reference.call0()?.is(&object)); @@ -282,7 +280,7 @@ mod tests { assert!(reference .getattr("__callback__") - .map_or(false, |result| result.is_none())); + .is_ok_and(|result| result.is_none())); assert!(reference.call0()?.is_none()); @@ -305,10 +303,8 @@ mod tests { let obj = obj.unwrap(); assert!(obj.is_some()); - assert!( - obj.map_or(false, |obj| ptr::eq(obj.as_ptr(), object.as_ptr()) - && obj.is_exact_instance(&class)) - ); + assert!(obj.is_some_and(|obj| ptr::eq(obj.as_ptr(), object.as_ptr()) + && obj.is_exact_instance(&class))); } drop(object); @@ -339,10 +335,8 @@ mod tests { let obj = unsafe { reference.upgrade_as_unchecked::() }; assert!(obj.is_some()); - assert!( - obj.map_or(false, |obj| ptr::eq(obj.as_ptr(), object.as_ptr()) - && obj.is_exact_instance(&class)) - ); + assert!(obj.is_some_and(|obj| ptr::eq(obj.as_ptr(), object.as_ptr()) + && obj.is_exact_instance(&class))); } drop(object); @@ -367,7 +361,7 @@ mod tests { assert!(reference.call0()?.is(&object)); assert!(reference.upgrade().is_some()); - assert!(reference.upgrade().map_or(false, |obj| obj.is(&object))); + assert!(reference.upgrade().is_some_and(|obj| obj.is(&object))); drop(object); @@ -407,7 +401,7 @@ mod tests { assert!(reference .getattr("__callback__") - .map_or(false, |result| result.is_none())); + .is_ok_and(|result| result.is_none())); assert!(reference.call0()?.is(&object)); @@ -420,7 +414,7 @@ mod tests { assert!(reference .getattr("__callback__") - .map_or(false, |result| result.is_none())); + .is_ok_and(|result| result.is_none())); assert!(reference.call0()?.is_none()); @@ -441,7 +435,7 @@ mod tests { let obj = obj.unwrap(); assert!(obj.is_some()); - assert!(obj.map_or(false, |obj| ptr::eq(obj.as_ptr(), object.as_ptr()))); + assert!(obj.is_some_and(|obj| ptr::eq(obj.as_ptr(), object.as_ptr()))); } drop(object); @@ -469,7 +463,7 @@ mod tests { let obj = unsafe { reference.upgrade_as_unchecked::() }; assert!(obj.is_some()); - assert!(obj.map_or(false, |obj| ptr::eq(obj.as_ptr(), object.as_ptr()))); + assert!(obj.is_some_and(|obj| ptr::eq(obj.as_ptr(), object.as_ptr()))); } drop(object); @@ -492,7 +486,7 @@ mod tests { assert!(reference.call0()?.is(&object)); assert!(reference.upgrade().is_some()); - assert!(reference.upgrade().map_or(false, |obj| obj.is(&object))); + assert!(reference.upgrade().is_some_and(|obj| obj.is(&object))); drop(object); diff --git a/tests/test_buffer.rs b/tests/test_buffer.rs index 8591b6a0e1f..40bccf889b1 100644 --- a/tests/test_buffer.rs +++ b/tests/test_buffer.rs @@ -1,6 +1,6 @@ #![cfg(feature = "macros")] #![cfg(any(not(Py_LIMITED_API), Py_3_11))] -#![cfg_attr(not(cargo_toml_lints), warn(unsafe_op_in_unsafe_fn))] +#![warn(unsafe_op_in_unsafe_fn)] use pyo3::{buffer::PyBuffer, exceptions::PyBufferError, ffi, prelude::*}; use std::{ diff --git a/tests/test_buffer_protocol.rs b/tests/test_buffer_protocol.rs index f3c316d50c7..c68b7785450 100644 --- a/tests/test_buffer_protocol.rs +++ b/tests/test_buffer_protocol.rs @@ -1,6 +1,6 @@ #![cfg(feature = "macros")] #![cfg(any(not(Py_LIMITED_API), Py_3_11))] -#![cfg_attr(not(cargo_toml_lints), warn(unsafe_op_in_unsafe_fn))] +#![warn(unsafe_op_in_unsafe_fn)] use pyo3::buffer::PyBuffer; use pyo3::exceptions::PyBufferError; diff --git a/tests/test_declarative_module.rs b/tests/test_declarative_module.rs index c4c9cd1a31d..1f2ff1f29b6 100644 --- a/tests/test_declarative_module.rs +++ b/tests/test_declarative_module.rs @@ -1,11 +1,11 @@ #![cfg(feature = "macros")] -use std::sync::Once; +use std::sync::OnceLock; use pyo3::create_exception; use pyo3::exceptions::PyException; use pyo3::prelude::*; -use pyo3::sync::{GILOnceCell, OnceExt}; +use pyo3::sync::OnceLockExt; #[path = "../src/tests/common.rs"] mod common; @@ -164,18 +164,11 @@ mod declarative_module2 { } fn declarative_module(py: Python<'_>) -> &Bound<'_, PyModule> { - static MODULE: GILOnceCell> = GILOnceCell::new(); - static ONCE: Once = Once::new(); - - // Guarantee that the module is only ever initialized once; GILOnceCell can race. - // TODO: use OnceLock when MSRV >= 1.70 - ONCE.call_once_py_attached(py, || { - MODULE - .set(py, pyo3::wrap_pymodule!(declarative_module)(py)) - .expect("only ever set once"); - }); - - MODULE.get(py).expect("once is completed").bind(py) + static MODULE: OnceLock> = OnceLock::new(); + + MODULE + .get_or_init_py_attached(py, || pyo3::wrap_pymodule!(declarative_module)(py)) + .bind(py) } #[test] diff --git a/tests/test_enum.rs b/tests/test_enum.rs index c687a7daf11..1421856e63d 100644 --- a/tests/test_enum.rs +++ b/tests/test_enum.rs @@ -338,7 +338,7 @@ fn custom_eq() { impl CustomPyEq { fn __eq__(&self, other: &Bound<'_, PyAny>) -> bool { if let Ok(rhs) = other.downcast::() { - rhs.to_cow().map_or(false, |rhs| self.__str__() == rhs) + rhs.to_cow().is_ok_and(|rhs| self.__str__() == rhs) } else if let Ok(rhs) = other.downcast::() { self == rhs.get() } else { diff --git a/tests/test_gc.rs b/tests/test_gc.rs index 8bc1e53cadb..76901fd1530 100644 --- a/tests/test_gc.rs +++ b/tests/test_gc.rs @@ -1,5 +1,5 @@ #![cfg(feature = "macros")] -#![cfg_attr(not(cargo_toml_lints), warn(unsafe_op_in_unsafe_fn))] +#![warn(unsafe_op_in_unsafe_fn)] use pyo3::class::PyTraverseError; use pyo3::class::PyVisit; diff --git a/tests/test_pyfunction.rs b/tests/test_pyfunction.rs index b5af0c81836..b817850d727 100644 --- a/tests/test_pyfunction.rs +++ b/tests/test_pyfunction.rs @@ -1,5 +1,5 @@ #![cfg(feature = "macros")] -#![cfg_attr(not(cargo_toml_lints), warn(unsafe_op_in_unsafe_fn))] +#![warn(unsafe_op_in_unsafe_fn)] use std::collections::HashMap; diff --git a/tests/ui/invalid_pymethods.rs b/tests/ui/invalid_pymethods.rs index 41786cd7895..75ad12c26d2 100644 --- a/tests/ui/invalid_pymethods.rs +++ b/tests/ui/invalid_pymethods.rs @@ -40,10 +40,12 @@ impl MyClass { } } +struct NotATypeObject; + #[pymethods] impl MyClass { #[classmethod] - fn classmethod_wrong_first_argument(_x: i32) -> Self { + fn classmethod_wrong_first_argument(_t: NotATypeObject) -> Self { Self {} } } diff --git a/tests/ui/invalid_pymethods.stderr b/tests/ui/invalid_pymethods.stderr index eb8f429c937..2b6e195ab7d 100644 --- a/tests/ui/invalid_pymethods.stderr +++ b/tests/ui/invalid_pymethods.stderr @@ -35,160 +35,154 @@ error: Expected `&Bound` or `Py` as the first argument to `#[cla | ^^ error: expected receiver for `#[getter]` - --> tests/ui/invalid_pymethods.rs:54:5 + --> tests/ui/invalid_pymethods.rs:56:5 | -54 | fn getter_without_receiver() {} +56 | fn getter_without_receiver() {} | ^^ error: expected receiver for `#[setter]` - --> tests/ui/invalid_pymethods.rs:60:5 + --> tests/ui/invalid_pymethods.rs:62:5 | -60 | fn setter_without_receiver() {} +62 | fn setter_without_receiver() {} | ^^ error: static method needs #[staticmethod] attribute - --> tests/ui/invalid_pymethods.rs:66:5 + --> tests/ui/invalid_pymethods.rs:68:5 | -66 | fn text_signature_on_call() {} +68 | fn text_signature_on_call() {} | ^^ error: `text_signature` not allowed with `getter` - --> tests/ui/invalid_pymethods.rs:72:12 + --> tests/ui/invalid_pymethods.rs:74:12 | -72 | #[pyo3(text_signature = "()")] +74 | #[pyo3(text_signature = "()")] | ^^^^^^^^^^^^^^ error: `text_signature` not allowed with `setter` - --> tests/ui/invalid_pymethods.rs:79:12 + --> tests/ui/invalid_pymethods.rs:81:12 | -79 | #[pyo3(text_signature = "()")] +81 | #[pyo3(text_signature = "()")] | ^^^^^^^^^^^^^^ error: `text_signature` not allowed with `classattr` - --> tests/ui/invalid_pymethods.rs:86:12 + --> tests/ui/invalid_pymethods.rs:88:12 | -86 | #[pyo3(text_signature = "()")] +88 | #[pyo3(text_signature = "()")] | ^^^^^^^^^^^^^^ error: expected a string literal or `None` - --> tests/ui/invalid_pymethods.rs:92:30 + --> tests/ui/invalid_pymethods.rs:94:30 | -92 | #[pyo3(text_signature = 1)] +94 | #[pyo3(text_signature = 1)] | ^ error: `text_signature` may only be specified once - --> tests/ui/invalid_pymethods.rs:99:12 - | -99 | #[pyo3(text_signature = None)] - | ^^^^^^^^^^^^^^ + --> tests/ui/invalid_pymethods.rs:101:12 + | +101 | #[pyo3(text_signature = None)] + | ^^^^^^^^^^^^^^ error: `signature` not allowed with `getter` - --> tests/ui/invalid_pymethods.rs:106:12 + --> tests/ui/invalid_pymethods.rs:108:12 | -106 | #[pyo3(signature = ())] +108 | #[pyo3(signature = ())] | ^^^^^^^^^ error: `signature` not allowed with `setter` - --> tests/ui/invalid_pymethods.rs:113:12 + --> tests/ui/invalid_pymethods.rs:115:12 | -113 | #[pyo3(signature = ())] +115 | #[pyo3(signature = ())] | ^^^^^^^^^ error: `signature` not allowed with `classattr` - --> tests/ui/invalid_pymethods.rs:120:12 + --> tests/ui/invalid_pymethods.rs:122:12 | -120 | #[pyo3(signature = ())] +122 | #[pyo3(signature = ())] | ^^^^^^^^^ error: `#[new]` may not be combined with `#[classmethod]` `#[staticmethod]`, `#[classattr]`, `#[getter]`, and `#[setter]` - --> tests/ui/invalid_pymethods.rs:126:7 + --> tests/ui/invalid_pymethods.rs:128:7 | -126 | #[new] +128 | #[new] | ^^^ error: `#[new]` does not take any arguments = help: did you mean `#[new] #[pyo3(signature = ())]`? - --> tests/ui/invalid_pymethods.rs:137:7 + --> tests/ui/invalid_pymethods.rs:139:7 | -137 | #[new(signature = ())] +139 | #[new(signature = ())] | ^^^ error: `#[new]` does not take any arguments = note: this was previously accepted and ignored - --> tests/ui/invalid_pymethods.rs:143:11 + --> tests/ui/invalid_pymethods.rs:145:11 | -143 | #[new = ()] // in this form there's no suggestion to move arguments to `#[pyo3()]` attribute +145 | #[new = ()] // in this form there's no suggestion to move arguments to `#[pyo3()]` attribute | ^ error: `#[classmethod]` does not take any arguments = help: did you mean `#[classmethod] #[pyo3(signature = ())]`? - --> tests/ui/invalid_pymethods.rs:149:7 + --> tests/ui/invalid_pymethods.rs:151:7 | -149 | #[classmethod(signature = ())] +151 | #[classmethod(signature = ())] | ^^^^^^^^^^^ error: `#[staticmethod]` does not take any arguments = help: did you mean `#[staticmethod] #[pyo3(signature = ())]`? - --> tests/ui/invalid_pymethods.rs:155:7 + --> tests/ui/invalid_pymethods.rs:157:7 | -155 | #[staticmethod(signature = ())] +157 | #[staticmethod(signature = ())] | ^^^^^^^^^^^^ error: `#[classattr]` does not take any arguments = help: did you mean `#[classattr] #[pyo3(signature = ())]`? - --> tests/ui/invalid_pymethods.rs:161:7 + --> tests/ui/invalid_pymethods.rs:163:7 | -161 | #[classattr(signature = ())] +163 | #[classattr(signature = ())] | ^^^^^^^^^ error: Python functions cannot have generic type parameters - --> tests/ui/invalid_pymethods.rs:167:23 + --> tests/ui/invalid_pymethods.rs:169:23 | -167 | fn generic_method(_value: T) {} +169 | fn generic_method(_value: T) {} | ^ error: Python functions cannot have `impl Trait` arguments - --> tests/ui/invalid_pymethods.rs:172:49 + --> tests/ui/invalid_pymethods.rs:174:49 | -172 | fn impl_trait_method_first_arg(_impl_trait: impl AsRef) {} +174 | fn impl_trait_method_first_arg(_impl_trait: impl AsRef) {} | ^^^^ error: Python functions cannot have `impl Trait` arguments - --> tests/ui/invalid_pymethods.rs:177:57 + --> tests/ui/invalid_pymethods.rs:179:57 | -177 | fn impl_trait_method_second_arg(&self, _impl_trait: impl AsRef) {} +179 | fn impl_trait_method_second_arg(&self, _impl_trait: impl AsRef) {} | ^^^^ error: `pass_module` cannot be used on Python methods - --> tests/ui/invalid_pymethods.rs:182:12 + --> tests/ui/invalid_pymethods.rs:184:12 | -182 | #[pyo3(pass_module)] +184 | #[pyo3(pass_module)] | ^^^^^^^^^^^ error: Python objects are shared, so 'self' cannot be moved out of the Python interpreter. Try `&self`, `&mut self, `slf: PyRef<'_, Self>` or `slf: PyRefMut<'_, Self>`. - --> tests/ui/invalid_pymethods.rs:188:29 + --> tests/ui/invalid_pymethods.rs:190:29 | -188 | fn method_self_by_value(self) {} +190 | fn method_self_by_value(self) {} | ^^^^ error: macros cannot be used as items in `#[pymethods]` impl blocks = note: this was previously accepted and ignored - --> tests/ui/invalid_pymethods.rs:197:5 + --> tests/ui/invalid_pymethods.rs:199:5 | -197 | macro_invocation!(); +199 | macro_invocation!(); | ^^^^^^^^^^^^^^^^ -error[E0277]: the trait bound `i32: From>` is not satisfied - --> tests/ui/invalid_pymethods.rs:46:45 +error[E0277]: the trait bound `NotATypeObject: From>` is not satisfied + --> tests/ui/invalid_pymethods.rs:48:45 | -46 | fn classmethod_wrong_first_argument(_x: i32) -> Self { - | ^^^ the trait `From>` is not implemented for `i32` +48 | fn classmethod_wrong_first_argument(_t: NotATypeObject) -> Self { + | ^^^^^^^^^^^^^^ the trait `From>` is not implemented for `NotATypeObject` | - = help: the following other types implement trait `From`: - `i32` implements `From` - `i32` implements `From` - `i32` implements `From` - `i32` implements `From` - `i32` implements `From` - = note: required for `BoundRef<'_, '_, PyType>` to implement `Into` + = note: required for `BoundRef<'_, '_, PyType>` to implement `Into`