diff --git a/.github/workflows/bench.yml b/.github/workflows/bench.yml index 012d57792e3..97aa715afa6 100644 --- a/.github/workflows/bench.yml +++ b/.github/workflows/bench.yml @@ -4,6 +4,10 @@ on: - main pull_request: +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }} + cancel-in-progress: true + name: Benchmark jobs: diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 46c04767081..bcb977d1955 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -6,6 +6,10 @@ on: - main pull_request: +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }} + cancel-in-progress: true + env: CARGO_TERM_COLOR: always @@ -15,7 +19,7 @@ jobs: steps: - uses: actions/checkout@v2 - uses: actions/setup-python@v2 - - run: pip install black==20.8b1 + - run: pip install black==22.3.0 - uses: actions-rs/toolchain@v1 with: toolchain: stable @@ -41,7 +45,8 @@ jobs: needs: [fmt] runs-on: ubuntu-latest strategy: - fail-fast: false # If one platform fails, allow the rest to keep testing. + # If one platform fails, allow the rest to keep testing if `CI-no-fail-fast` label is present + fail-fast: ${{ !contains(github.event.pull_request.labels.*.name, 'CI-no-fail-fast') }} matrix: target: [powerpc64le-unknown-linux-gnu, s390x-unknown-linux-gnu, wasm32-wasi] name: check-${{ matrix.target }} @@ -73,10 +78,11 @@ jobs: name: python${{ matrix.python-version }}-${{ matrix.platform.python-architecture }} ${{ matrix.platform.os }} ${{ matrix.msrv }} runs-on: ${{ matrix.platform.os }} strategy: - fail-fast: false # If one platform fails, allow the rest to keep testing. + # If one platform fails, allow the rest to keep testing if `CI-no-fail-fast` label is present + fail-fast: ${{ !contains(github.event.pull_request.labels.*.name, 'CI-no-fail-fast') }} matrix: rust: [stable] - python-version: [3.6, 3.7, 3.8, 3.9, "3.10", pypy-3.6, pypy-3.7, pypy-3.8] + python-version: [3.6, 3.7, 3.8, 3.9, "3.10", pypy-3.6, pypy-3.7, pypy-3.8, pypy-3.9] platform: [ { @@ -94,11 +100,6 @@ jobs: python-architecture: "x64", rust-target: "x86_64-pc-windows-msvc", }, - { - os: "windows-latest", - python-architecture: "x86", - rust-target: "i686-pc-windows-msvc", - }, ] exclude: # PyPy 3.6 is EOL and not working on macos-latest (now macos-11) @@ -107,7 +108,7 @@ jobs: # There is no 64-bit pypy on windows for pypy-3.6 - python-version: pypy-3.6 platform: { os: "windows-latest", python-architecture: "x64" } - # PyPy 3.7 on Windows doesn't release 32-bit builds any more + # PyPy doesn't release 32-bit Windows builds any more - python-version: pypy-3.7 platform: { os: "windows-latest", python-architecture: "x86" } - python-version: pypy-3.8 @@ -132,6 +133,15 @@ jobs: rust-target: "x86_64-unknown-linux-gnu", } msrv: "MSRV" + # Test 32-bit Windows only with the latest Python version + - rust: stable + python-version: "3.10" + platform: + { + os: "windows-latest", + python-architecture: "x86", + rust-target: "i686-pc-windows-msvc", + } steps: - uses: actions/checkout@v2 @@ -184,6 +194,7 @@ jobs: run: | cargo update -p indexmap --precise 1.6.2 cargo update -p hashbrown:0.11.2 --precise 0.9.1 + cargo update -p clap --precise 2.33.4 - name: Build docs run: cargo doc --no-deps --no-default-features --features "${{ steps.settings.outputs.all_additive_features }}" diff --git a/.github/workflows/guide.yml b/.github/workflows/guide.yml index 923fedd1f5f..1ce637c0cd9 100644 --- a/.github/workflows/guide.yml +++ b/.github/workflows/guide.yml @@ -8,6 +8,10 @@ on: release: types: [published] +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }} + cancel-in-progress: true + env: CARGO_TERM_COLOR: always diff --git a/Cargo.toml b/Cargo.toml index fb90960b470..331e5acd8e7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -43,6 +43,8 @@ serde = { version = "1.0", optional = true } 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" +# 2.34 uses the matches! macro, which isn't compatible with Rust 1.41 +clap = "=2.33" # 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" @@ -50,6 +52,7 @@ trybuild = "1.0.49" rustversion = "1.0" # 1.0.0 requires Rust 1.50 proptest = { version = "0.10.1", default-features = false, features = ["std"] } +serde = { version = "1.0", features = ["derive"] } serde_json = "1.0.61" # features needed to run the PyO3 test suite diff --git a/examples/pyo3-benchmarks/src/lib.rs b/examples/pyo3-benchmarks/src/lib.rs index e649fd1c65d..362e83713e9 100644 --- a/examples/pyo3-benchmarks/src/lib.rs +++ b/examples/pyo3-benchmarks/src/lib.rs @@ -1,3 +1,4 @@ +#![allow(clippy::needless_option_as_deref)] use pyo3::prelude::*; use pyo3::types::{PyDict, PyTuple}; diff --git a/examples/pyo3-pytests/src/buf_and_str.rs b/examples/pyo3-pytests/src/buf_and_str.rs index 76e530935a7..b5ce584dec1 100644 --- a/examples/pyo3-pytests/src/buf_and_str.rs +++ b/examples/pyo3-pytests/src/buf_and_str.rs @@ -14,22 +14,26 @@ impl BytesExtractor { BytesExtractor {} } - pub fn from_bytes(&mut self, bytes: &PyBytes) -> PyResult { + #[staticmethod] + pub fn from_bytes(bytes: &PyBytes) -> PyResult { let byte_vec: Vec = bytes.extract()?; Ok(byte_vec.len()) } - pub fn from_str(&mut self, string: &PyString) -> PyResult { + #[staticmethod] + pub fn from_str(string: &PyString) -> PyResult { let rust_string: String = string.extract()?; Ok(rust_string.len()) } - pub fn from_str_lossy(&mut self, string: &PyString) -> PyResult { + #[staticmethod] + pub fn from_str_lossy(string: &PyString) -> PyResult { let rust_string_lossy: String = string.to_string_lossy().to_string(); Ok(rust_string_lossy.len()) } - pub fn from_buffer(&mut self, buf: &PyAny) -> PyResult { + #[staticmethod] + pub fn from_buffer(buf: &PyAny) -> PyResult { let buf = PyBuffer::::get(buf)?; Ok(buf.item_count()) } diff --git a/examples/pyo3-pytests/src/datetime.rs b/examples/pyo3-pytests/src/datetime.rs index a3522b4845e..2977d83b12b 100644 --- a/examples/pyo3-pytests/src/datetime.rs +++ b/examples/pyo3-pytests/src/datetime.rs @@ -1,3 +1,4 @@ +#![allow(clippy::needless_option_as_deref)] use pyo3::prelude::*; use pyo3::types::{ PyDate, PyDateAccess, PyDateTime, PyDelta, PyDeltaAccess, PyTime, PyTimeAccess, PyTuple, diff --git a/examples/pyo3-pytests/tests/test_othermod.py b/examples/pyo3-pytests/tests/test_othermod.py index 08ac367a4e9..ff67bba435c 100644 --- a/examples/pyo3-pytests/tests/test_othermod.py +++ b/examples/pyo3-pytests/tests/test_othermod.py @@ -3,14 +3,14 @@ from pyo3_pytests import othermod -INTEGER32_ST = st.integers(min_value=(-(2 ** 31)), max_value=(2 ** 31 - 1)) +INTEGER32_ST = st.integers(min_value=(-(2**31)), max_value=(2**31 - 1)) USIZE_ST = st.integers(min_value=othermod.USIZE_MIN, max_value=othermod.USIZE_MAX) @given(x=INTEGER32_ST) def test_double(x): expected = x * 2 - assume(-(2 ** 31) <= expected <= (2 ** 31 - 1)) + assume(-(2**31) <= expected <= (2**31 - 1)) assert othermod.double(x) == expected diff --git a/pyo3-build-config/src/impl_.rs b/pyo3-build-config/src/impl_.rs index 6a73b3b97d6..0fa3bddb4a7 100644 --- a/pyo3-build-config/src/impl_.rs +++ b/pyo3-build-config/src/impl_.rs @@ -911,7 +911,7 @@ fn search_lib_dir(path: impl AsRef, cross: &CrossCompileConfig) -> Vec vec![f.path()], @@ -1062,7 +1062,16 @@ fn default_lib_name_unix( Some(ld_version) => format!("python{}", ld_version), None => format!("python{}.{}", version.major, version.minor), }, - PythonImplementation::PyPy => format!("pypy{}-c", version.major), + PythonImplementation::PyPy => { + if version >= (PythonVersion { major: 3, minor: 9 }) { + match ld_version { + Some(ld_version) => format!("pypy{}-c", ld_version), + None => format!("pypy{}.{}-c", version.major, version.minor), + } + } else { + format!("pypy{}-c", version.major) + } + } } } @@ -1168,6 +1177,11 @@ fn fixup_config_for_abi3( config: &mut InterpreterConfig, abi3_version: Option, ) -> Result<()> { + // PyPy doesn't support abi3; don't adjust the version + if config.implementation.is_pypy() { + return Ok(()); + } + if let Some(version) = abi3_version { ensure!( version <= config.version, @@ -1472,11 +1486,17 @@ mod tests { "python3.7md", ); - // PyPy ignores ldversion + // PyPy 3.7 ignores ldversion assert_eq!( - super::default_lib_name_unix(PythonVersion { major: 3, minor: 9 }, PyPy, Some("3.7md")), + super::default_lib_name_unix(PythonVersion { major: 3, minor: 7 }, PyPy, Some("3.7md")), "pypy3-c", ); + + // PyPy 3.9 includes ldversion + assert_eq!( + super::default_lib_name_unix(PythonVersion { major: 3, minor: 9 }, PyPy, Some("3.9d")), + "pypy3.9d-c", + ); } #[test] diff --git a/pyo3-macros-backend/src/lib.rs b/pyo3-macros-backend/src/lib.rs index f42bd3b9a6f..14d1c2565b5 100644 --- a/pyo3-macros-backend/src/lib.rs +++ b/pyo3-macros-backend/src/lib.rs @@ -3,7 +3,6 @@ #![cfg_attr(docsrs, feature(doc_cfg))] #![recursion_limit = "1024"] - // Listed first so that macros in this module are available in the rest of the crate. #[macro_use] mod utils; diff --git a/pyo3-macros-backend/src/method.rs b/pyo3-macros-backend/src/method.rs index e57ef33780b..2858338fd00 100644 --- a/pyo3-macros-backend/src/method.rs +++ b/pyo3-macros-backend/src/method.rs @@ -29,9 +29,7 @@ impl<'a> FnArg<'a> { /// Transforms a rust fn arg parsed with syn into a method::FnArg pub fn parse(arg: &'a mut syn::FnArg) -> Result { match arg { - syn::FnArg::Receiver(recv) => { - bail_spanned!(recv.span() => "unexpected receiver") - } // checked in parse_fn_type + syn::FnArg::Receiver(recv) => bail_spanned!(recv.span() => "unexpected receiver"), // checked in parse_fn_type syn::FnArg::Typed(cap) => { if let syn::Type::ImplTrait(_) = &*cap.ty { bail_spanned!(cap.ty.span() => IMPL_TRAIT_ERR); @@ -101,9 +99,7 @@ impl FnType { cls.expect("no class given for Fn with a \"self\" receiver"), error_mode, ), - FnType::FnNew | FnType::FnStatic | FnType::ClassAttribute => { - quote!() - } + FnType::FnNew | FnType::FnStatic | FnType::ClassAttribute => quote!(), FnType::FnClass => { quote! { let _slf = ::pyo3::types::PyType::from_type_ptr(_py, _slf as *mut ::pyo3::ffi::PyTypeObject); @@ -352,7 +348,8 @@ impl<'a> FnSpec<'a> { parse_method_receiver(first_arg) }; - #[allow(clippy::manual_strip)] // for strip_prefix replacement supporting rust < 1.45 + #[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(); diff --git a/pyo3-macros-backend/src/params.rs b/pyo3-macros-backend/src/params.rs index 1bbbb4727d8..c06c64e6de2 100644 --- a/pyo3-macros-backend/src/params.rs +++ b/pyo3-macros-backend/src/params.rs @@ -266,7 +266,7 @@ fn impl_arg_param( } }; - return if let syn::Type::Reference(tref) = unwrap_ty_group(arg.optional.unwrap_or(ty)) { + if let syn::Type::Reference(tref) = unwrap_ty_group(arg.optional.unwrap_or(ty)) { let mut tref = remove_lifetime(tref); if let Some(cls) = self_ { replace_self(&mut tref.elem, cls); @@ -298,5 +298,5 @@ fn impl_arg_param( Ok(quote_arg_span! { let #arg_name = #arg_value_or_default; }) - }; + } } diff --git a/pyo3-macros-backend/src/pyimpl.rs b/pyo3-macros-backend/src/pyimpl.rs index 54e248c9593..e0dcebd6e74 100644 --- a/pyo3-macros-backend/src/pyimpl.rs +++ b/pyo3-macros-backend/src/pyimpl.rs @@ -36,7 +36,7 @@ pub fn build_py_methods( pub fn impl_methods( ty: &syn::Type, - impls: &mut Vec, + impls: &mut [syn::ImplItem], methods_type: PyClassMethodsType, ) -> syn::Result { let mut trait_impls = Vec::new(); diff --git a/pyo3-macros-backend/src/pymethod.rs b/pyo3-macros-backend/src/pymethod.rs index 13a49f92734..8cdb2e06dc2 100644 --- a/pyo3-macros-backend/src/pymethod.rs +++ b/pyo3-macros-backend/src/pymethod.rs @@ -138,9 +138,7 @@ pub fn gen_py_method( cls, PropertyType::Function { self_type, spec }, )?), - (_, FnType::FnModule) => { - unreachable!("methods cannot be FnModule") - } + (_, FnType::FnModule) => unreachable!("methods cannot be FnModule"), }) } diff --git a/pyo3-macros-backend/src/pyproto.rs b/pyo3-macros-backend/src/pyproto.rs index 23e5d8a5d5d..6c02fb57367 100644 --- a/pyo3-macros-backend/src/pyproto.rs +++ b/pyo3-macros-backend/src/pyproto.rs @@ -45,7 +45,7 @@ pub fn build_py_proto(ast: &mut syn::ItemImpl) -> syn::Result { fn impl_proto_impl( ty: &syn::Type, - impls: &mut Vec, + impls: &mut [syn::ImplItem], proto: &defs::Proto, ) -> syn::Result { let mut trait_impls = TokenStream::new(); diff --git a/pyo3-macros-backend/src/utils.rs b/pyo3-macros-backend/src/utils.rs index b0353cdc16a..28a8b9200df 100644 --- a/pyo3-macros-backend/src/utils.rs +++ b/pyo3-macros-backend/src/utils.rs @@ -79,11 +79,7 @@ pub fn get_doc( syn::token::Bracket(Span::call_site()).surround(&mut tokens, |tokens| { if let Some((python_name, text_signature)) = text_signature { // create special doc string lines to set `__text_signature__` - let signature_lines = format!( - "{}{}\n--\n\n", - python_name.to_string(), - text_signature.lit.value() - ); + let signature_lines = format!("{}{}\n--\n\n", python_name, text_signature.lit.value()); signature_lines.to_tokens(tokens); comma.to_tokens(tokens); } diff --git a/src/buffer.rs b/src/buffer.rs index 07f6321aecd..9a7a404bdcf 100644 --- a/src/buffer.rs +++ b/src/buffer.rs @@ -168,6 +168,10 @@ fn is_matching_endian(c: u8) -> bool { } /// Trait implemented for possible element types of `PyBuffer`. +/// +/// # Safety +/// +/// This trait must only be implemented for types which represent valid elements of Python buffers. pub unsafe trait Element: Copy { /// Gets whether the element specified in the format string is potentially compatible. /// Alignment and size are checked separately from this function. diff --git a/src/conversion.rs b/src/conversion.rs index e1238edc80e..e7cee002337 100644 --- a/src/conversion.rs +++ b/src/conversion.rs @@ -195,7 +195,6 @@ where /// } /// } /// } -/// # fn main() { /// # Python::with_gil(|py| { /// # let v = Value::Integer(73).into_py(py); /// # let v = v.extract::(py).unwrap(); @@ -206,7 +205,6 @@ where /// # let v = Value::None.into_py(py); /// # let v = v.extract::>>(py).unwrap(); /// # }); -/// # } /// ``` /// Python code will see this as any of the `int`, `string` or `None` objects. #[cfg_attr(docsrs, doc(alias = "IntoPyCallbackOutput"))] @@ -463,6 +461,10 @@ impl IntoPy> for () { } /// Raw level conversion between `*mut ffi::PyObject` and PyO3 types. +/// +/// # Safety +/// +/// See safety notes on individual functions. pub unsafe trait FromPyPointer<'p>: Sized { /// Convert from an arbitrary `PyObject`. /// diff --git a/src/derive_utils.rs b/src/derive_utils.rs index 6d79b2c4dad..cfb9a816627 100644 --- a/src/derive_utils.rs +++ b/src/derive_utils.rs @@ -319,6 +319,24 @@ impl ModuleDef { py: Python, initializer: impl Fn(Python, &PyModule) -> PyResult<()>, ) -> PyResult<*mut ffi::PyObject> { + #[cfg(all(PyPy, not(Py_3_8)))] + { + const PYPY_GOOD_VERSION: [u8; 3] = [7, 3, 8]; + let version = py + .import("sys")? + .getattr("implementation")? + .getattr("version")?; + if version.compare(crate::types::PyTuple::new(py, &PYPY_GOOD_VERSION))? + == std::cmp::Ordering::Less + { + let warn = py.import("warnings")?.getattr("warn")?; + warn.call1(( + "PyPy 3.7 versions older than 7.3.8 are known to have binary \ + compatibility issues which may cause segfaults. Please upgrade.", + ))?; + } + } + let module = unsafe { py.from_owned_ptr_or_err::(ffi::PyModule_Create(self.0.get()))? }; initializer(py, module)?; diff --git a/src/err/mod.rs b/src/err/mod.rs index db23c6782c5..c2583372842 100644 --- a/src/err/mod.rs +++ b/src/err/mod.rs @@ -437,7 +437,7 @@ impl PyErr { pub fn cause(&self, py: Python) -> Option { let ptr = unsafe { ffi::PyException_GetCause(self.pvalue(py).as_ptr()) }; let obj = unsafe { py.from_owned_ptr_or_opt::(ptr) }; - obj.map(|x| Self::from_instance(x)) + obj.map(Self::from_instance) } /// Set the cause associated with the exception, pass `None` to clear it. diff --git a/src/ffi/datetime.rs b/src/ffi/datetime.rs index 8510b96edc4..6c23122b846 100644 --- a/src/ffi/datetime.rs +++ b/src/ffi/datetime.rs @@ -361,7 +361,7 @@ pub struct PyDateTime_CAPI { pub TimeType: *mut PyTypeObject, pub DeltaType: *mut PyTypeObject, pub TZInfoType: *mut PyTypeObject, - #[cfg(all(Py_3_7, any(not(PyPy), Py_3_8)))] + #[cfg(Py_3_7)] pub TimeZone_UTC: *mut PyObject, pub Date_FromDate: unsafe extern "C" fn( year: c_int, @@ -395,7 +395,7 @@ pub struct PyDateTime_CAPI { normalize: c_int, cls: *mut PyTypeObject, ) -> *mut PyObject, - #[cfg(all(Py_3_7, any(not(PyPy), Py_3_8)))] + #[cfg(Py_3_7)] pub TimeZone_FromTimeZone: unsafe extern "C" fn(offset: *mut PyObject, name: *mut PyObject) -> *mut PyObject, diff --git a/src/ffi/methodobject.rs b/src/ffi/methodobject.rs index b1b435d7c51..c1bc431f89f 100644 --- a/src/ffi/methodobject.rs +++ b/src/ffi/methodobject.rs @@ -83,6 +83,7 @@ impl Default for PyMethodDef { } } +#[cfg(not(Py_3_9))] extern "C" { #[cfg_attr(PyPy, link_name = "PyPyCFunction_New")] pub fn PyCFunction_New(ml: *mut PyMethodDef, slf: *mut PyObject) -> *mut PyObject; @@ -95,7 +96,32 @@ extern "C" { ) -> *mut PyObject; } -// skipped non-limited / 3.9 PyCMethod_New +#[cfg(Py_3_9)] +#[inline] +pub unsafe fn PyCFunction_New(ml: *mut PyMethodDef, slf: *mut PyObject) -> *mut PyObject { + PyCFunction_NewEx(ml, slf, std::ptr::null_mut()) +} + +#[cfg(Py_3_9)] +#[inline] +pub unsafe fn PyCFunction_NewEx( + ml: *mut PyMethodDef, + slf: *mut PyObject, + module: *mut PyObject, +) -> *mut PyObject { + PyCMethod_New(ml, slf, module, std::ptr::null_mut()) +} + +#[cfg(Py_3_9)] +extern "C" { + #[cfg_attr(PyPy, link_name = "PyPyCMethod_New")] + pub fn PyCMethod_New( + ml: *mut PyMethodDef, + slf: *mut PyObject, + module: *mut PyObject, + cls: *mut PyTypeObject, + ) -> *mut PyObject; +} /* Flag passed to newmethodobject */ pub const METH_VARARGS: c_int = 0x0001; diff --git a/src/ffi/pyhash.rs b/src/ffi/pyhash.rs index 703f6245467..736f115ea54 100644 --- a/src/ffi/pyhash.rs +++ b/src/ffi/pyhash.rs @@ -14,7 +14,7 @@ extern "C" { pub fn _Py_HashBytes(src: *const c_void, len: Py_ssize_t) -> Py_hash_t; } -pub const _PyHASH_MULTIPLIER: c_ulong = 1000003; +pub const _PyHASH_MULTIPLIER: c_ulong = 1_000_003; // skipped _PyHASH_BITS diff --git a/src/ffi/unicodeobject.rs b/src/ffi/unicodeobject.rs index 61a60f8c101..22dd20bfa7e 100644 --- a/src/ffi/unicodeobject.rs +++ b/src/ffi/unicodeobject.rs @@ -78,9 +78,6 @@ extern "C" { #[cfg_attr(PyPy, link_name = "PyPyUnicode_FromObject")] pub fn PyUnicode_FromObject(obj: *mut PyObject) -> *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyUnicode_FromFormat")] - #[cfg_attr(PyPy, link_name = "PyPyUnicode_FromFormatV")] - //pub fn PyUnicode_FromFormatV(format: *const c_char, - // vargs: va_list) -> *mut PyObject; pub fn PyUnicode_FromFormat(format: *const c_char, ...) -> *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyUnicode_InternInPlace")] pub fn PyUnicode_InternInPlace(arg1: *mut *mut PyObject); diff --git a/src/gil.rs b/src/gil.rs index 588d45f1fe6..b0a4bef45a1 100644 --- a/src/gil.rs +++ b/src/gil.rs @@ -709,10 +709,9 @@ mod tests { // Acquiring GIL for the second time should be safe - see #864 let gil = Python::acquire_gil(); let py = gil.python(); - let obj; let gil2 = Python::acquire_gil(); - obj = py.eval("object()", None, None).unwrap(); + let obj = py.eval("object()", None, None).unwrap(); drop(gil2); // After gil2 drops, obj should still have a reference count of one diff --git a/src/instance.rs b/src/instance.rs index 269d22f3107..472f6bef7f8 100644 --- a/src/instance.rs +++ b/src/instance.rs @@ -17,6 +17,10 @@ use std::ptr::NonNull; /// PyO3 is designed in a way that all references to those types are bound /// to the GIL, which is why you can get a token from all references of those /// types. +/// +/// # Safety +/// +/// This trait must only be implemented for types which cannot be accessed without the GIL. pub unsafe trait PyNativeType: Sized { /// Returns a GIL marker constrained to the lifetime of this type. #[inline] @@ -168,7 +172,6 @@ pub unsafe trait PyNativeType: Sized { /// use pyo3::prelude::*; /// use pyo3::types::PyDict; /// -/// # fn main() { /// Python::with_gil(|py| { /// let first: Py = PyDict::new(py).into(); /// @@ -186,7 +189,6 @@ pub unsafe trait PyNativeType: Sized { /// assert_eq!(fourth.as_ptr(), fifth.as_ptr()); /// assert_eq!(second.as_ptr(), fourth.as_ptr()); /// }); -/// # } /// ``` /// /// # Preventing reference cycles @@ -475,7 +477,6 @@ impl Py { /// use pyo3::prelude::*; /// use pyo3::types::PyDict; /// - /// # fn main() { /// Python::with_gil(|py| { /// let first: Py = PyDict::new(py).into(); /// let second = Py::clone_ref(&first, py); @@ -483,7 +484,6 @@ impl Py { /// // Both point to the same object /// assert_eq!(first.as_ptr(), second.as_ptr()); /// }); - /// # } /// ``` #[inline] pub fn clone_ref(&self, py: Python) -> Py { @@ -561,12 +561,10 @@ impl Py { /// This is equivalent to the Python expression `self()`. pub fn call0(&self, py: Python) -> PyResult { cfg_if::cfg_if! { - // TODO: Use PyObject_CallNoArgs instead after https://bugs.python.org/issue42415. - // Once the issue is resolved, we can enable this optimization for limited API. - if #[cfg(all(Py_3_9, not(Py_LIMITED_API)))] { + if #[cfg(all(Py_3_9, not(PyPy)))] { // Optimized path on python 3.9+ unsafe { - PyObject::from_owned_ptr_or_err(py, ffi::_PyObject_CallNoArg(self.as_ptr())) + PyObject::from_owned_ptr_or_err(py, ffi::PyObject_CallNoArgs(self.as_ptr())) } } else { self.call(py, (), None) @@ -616,7 +614,7 @@ impl Py { /// This is equivalent to the Python expression `self.name()`. pub fn call_method0(&self, py: Python, name: &str) -> PyResult { cfg_if::cfg_if! { - if #[cfg(all(Py_3_9, not(Py_LIMITED_API)))] { + if #[cfg(all(Py_3_9, not(any(Py_LIMITED_API, PyPy))))] { // Optimized path on python 3.9+ unsafe { let name = name.into_py(py); @@ -904,7 +902,23 @@ impl PyObject { mod tests { use super::{Py, PyObject}; use crate::types::PyDict; - use crate::Python; + use crate::{Python, ToPyObject}; + + #[test] + fn test_call0() { + Python::with_gil(|py| { + let obj = py.get_type::().to_object(py); + assert_eq!( + obj.call0(py) + .unwrap() + .as_ref(py) + .repr() + .unwrap() + .to_string_lossy(), + "{}" + ); + }) + } #[test] fn test_call_for_non_existing_method() { diff --git a/src/lib.rs b/src/lib.rs index 14bc7466d3e..2c676ad9e32 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -11,7 +11,6 @@ // Deny some lints in doctests. // Use `#[allow(...)]` locally to override. #![doc(test(attr(deny(warnings), allow(unused_variables, unused_assignments))))] - //! Rust bindings to the Python interpreter. //! //! PyO3 can be used to write native Python modules or run Python code and modules from Rust. diff --git a/src/panic.rs b/src/panic.rs index cf1dd3701e3..da612e8ae9e 100644 --- a/src/panic.rs +++ b/src/panic.rs @@ -21,7 +21,7 @@ impl PanicException { if let Some(string) = payload.downcast_ref::() { Self::new_err((string.clone(),)) } else if let Some(s) = payload.downcast_ref::<&str>() { - Self::new_err((s.to_string(),)) + Self::new_err(((*s).to_string(),)) } else { Self::new_err(("panic from Rust code",)) } diff --git a/src/type_object.rs b/src/type_object.rs index 0f16f566f99..588ffb9d3d9 100644 --- a/src/type_object.rs +++ b/src/type_object.rs @@ -16,6 +16,11 @@ use std::thread::{self, ThreadId}; /// is of `PyAny`. /// /// This trait is intended to be used internally. +/// +/// # Safety +/// +/// Implementations must provide an implementation for `type_object_raw` which infallibly produces a +/// non-null pointer to the corresponding Python type object. pub unsafe trait PyLayout {} /// `T: PySizedLayout` represents that `T` is not a instance of @@ -31,6 +36,11 @@ pub trait PySizedLayout: PyLayout + Sized {} /// - the return value of type_object must always point to the same PyTypeObject instance /// /// It is safely implemented by the `pyclass` macro. +/// +/// # Safety +/// +/// Implementations must provide an implementation for `type_object_raw` which infallibly produces a +/// non-null pointer to the corresponding Python type object. pub unsafe trait PyTypeInfo: Sized { /// Class name. const NAME: &'static str; @@ -60,6 +70,11 @@ pub unsafe trait PyTypeInfo: Sized { /// This trait is marked unsafe because not fulfilling the contract for type_object /// leads to UB. /// +/// # Safety +/// +/// This trait is marked unsafe because not fulfilling the contract for type_object +/// leads to UB. +/// /// See also [PyTypeInfo::type_object_raw](trait.PyTypeInfo.html#tymethod.type_object_raw). pub unsafe trait PyTypeObject { /// Returns the safe abstraction over the type object. diff --git a/src/types/any.rs b/src/types/any.rs index 8fc8640d4e3..0eb7d837524 100644 --- a/src/types/any.rs +++ b/src/types/any.rs @@ -333,12 +333,10 @@ impl PyAny { /// This is equivalent to the Python expression `help()`. pub fn call0(&self) -> PyResult<&PyAny> { cfg_if::cfg_if! { - // TODO: Use PyObject_CallNoArgs instead after https://bugs.python.org/issue42415. - // Once the issue is resolved, we can enable this optimization for limited API. - if #[cfg(all(Py_3_9, not(Py_LIMITED_API)))] { + if #[cfg(all(Py_3_9, not(PyPy)))] { // Optimized path on python 3.9+ unsafe { - self.py().from_owned_ptr_or_err(ffi::_PyObject_CallNoArg(self.as_ptr())) + self.py().from_owned_ptr_or_err(ffi::PyObject_CallNoArgs(self.as_ptr())) } } else { self.call((), None) @@ -463,7 +461,7 @@ impl PyAny { /// ``` pub fn call_method0(&self, name: &str) -> PyResult<&PyAny> { cfg_if::cfg_if! { - if #[cfg(all(Py_3_9, not(Py_LIMITED_API)))] { + if #[cfg(all(Py_3_9, not(any(Py_LIMITED_API, PyPy))))] { // Optimized path on python 3.9+ unsafe { let name = name.into_py(self.py()); diff --git a/tests/hygiene/pymethods.rs b/tests/hygiene/pymethods.rs index 37a916f9c6b..c901834ad86 100644 --- a/tests/hygiene/pymethods.rs +++ b/tests/hygiene/pymethods.rs @@ -1,5 +1,6 @@ #![no_implicit_prelude] #![allow(unused_variables)] +#![allow(clippy::needless_option_as_deref)] #[::pyo3::pyclass] pub struct Dummy; diff --git a/tests/test_compile_error.rs b/tests/test_compile_error.rs index 4751f774d0e..1fd0f9c2c99 100644 --- a/tests/test_compile_error.rs +++ b/tests/test_compile_error.rs @@ -19,21 +19,17 @@ fn _test_compile_errors() { t.compile_fail("tests/ui/invalid_need_module_arg_position.rs"); t.compile_fail("tests/ui/invalid_property_args.rs"); t.compile_fail("tests/ui/invalid_pyclass_args.rs"); - t.compile_fail("tests/ui/invalid_pyfunctions.rs"); - 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/reject_generics.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); + tests_rust_1_60(&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)] @@ -48,20 +44,11 @@ fn _test_compile_errors() { #[rustversion::since(1.54)] fn tests_rust_1_54(t: &trybuild::TestCases) { - t.compile_fail("tests/ui/invalid_frompy_derive.rs"); t.compile_fail("tests/ui/static_ref.rs"); } #[rustversion::before(1.54)] fn tests_rust_1_54(_t: &trybuild::TestCases) {} - #[rustversion::since(1.55)] - fn tests_rust_1_55(t: &trybuild::TestCases) { - t.compile_fail("tests/ui/invalid_pymethod_receiver.rs"); - } - - #[rustversion::before(1.55)] - fn tests_rust_1_55(_t: &trybuild::TestCases) {} - #[rustversion::since(1.56)] fn tests_rust_1_56(t: &trybuild::TestCases) { t.compile_fail("tests/ui/invalid_closure.rs"); @@ -74,4 +61,15 @@ fn _test_compile_errors() { #[rustversion::before(1.56)] fn tests_rust_1_56(_t: &trybuild::TestCases) {} + + #[rustversion::since(1.60)] + fn tests_rust_1_60(t: &trybuild::TestCases) { + t.compile_fail("tests/ui/invalid_pymethod_receiver.rs"); + t.compile_fail("tests/ui/invalid_argument_attributes.rs"); + t.compile_fail("tests/ui/invalid_frompy_derive.rs"); + t.compile_fail("tests/ui/missing_clone.rs"); + } + + #[rustversion::before(1.60)] + fn tests_rust_1_60(_t: &trybuild::TestCases) {} } diff --git a/tests/test_frompyobject.rs b/tests/test_frompyobject.rs index 54f6e322e0f..889ed13f0a3 100644 --- a/tests/test_frompyobject.rs +++ b/tests/test_frompyobject.rs @@ -1,3 +1,4 @@ +#![allow(dead_code)] use pyo3::exceptions::PyValueError; use pyo3::prelude::*; use pyo3::types::{PyDict, PyString, PyTuple}; diff --git a/tests/test_mapping.rs b/tests/test_mapping.rs index 42710094e76..194e25562e2 100644 --- a/tests/test_mapping.rs +++ b/tests/test_mapping.rs @@ -1,4 +1,5 @@ #![allow(deprecated)] // for deprecated protocol methods +#![allow(clippy::needless_option_as_deref)] use std::collections::HashMap; diff --git a/tests/test_methods.rs b/tests/test_methods.rs index 1032acf9a0d..0199d9efd8e 100644 --- a/tests/test_methods.rs +++ b/tests/test_methods.rs @@ -1,3 +1,4 @@ +#![allow(clippy::needless_option_as_deref)] use pyo3::prelude::*; use pyo3::py_run; use pyo3::types::{IntoPyDict, PyDict, PyList, PySet, PyString, PyTuple, PyType}; diff --git a/tests/test_sequence.rs b/tests/test_sequence.rs index 0cb467ee648..f32acfe2d60 100644 --- a/tests/test_sequence.rs +++ b/tests/test_sequence.rs @@ -1,3 +1,4 @@ +#![allow(clippy::needless_option_as_deref)] use pyo3::class::PySequenceProtocol; use pyo3::exceptions::{PyIndexError, PyValueError}; use pyo3::prelude::*; diff --git a/tests/ui/abi3_nativetype_inheritance.stderr b/tests/ui/abi3_nativetype_inheritance.stderr index a90d40442f5..762250a4c1f 100644 --- a/tests/ui/abi3_nativetype_inheritance.stderr +++ b/tests/ui/abi3_nativetype_inheritance.stderr @@ -1,22 +1,11 @@ error[E0277]: the trait bound `PyDict: PyClass` is not satisfied - --> tests/ui/abi3_nativetype_inheritance.rs:5:1 - | -5 | #[pyclass(extends=PyDict)] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `PyClass` is not implemented for `PyDict` - | - = note: required because of the requirements on the impl of `PyClassBaseType` for `PyDict` -note: required by a bound in `PyClassBaseType` - --> src/class/impl_.rs:766:1 - | -766 | / pub trait PyClassBaseType: Sized { -767 | | type Dict; -768 | | type WeakRef; -769 | | type LayoutAsBase: PyCellLayout; -... | -772 | | type Initializer: PyObjectInit; -773 | | } - | |_^ required by this bound in `PyClassBaseType` - = note: this error originates in the attribute macro `pyclass` (in Nightly builds, run with -Z macro-backtrace for more info) + --> tests/ui/abi3_nativetype_inheritance.rs:5:1 + | +5 | #[pyclass(extends=PyDict)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `PyClass` is not implemented for `PyDict` + | + = note: required because of the requirements on the impl of `PyClassBaseType` for `PyDict` + = note: this error originates in the attribute macro `pyclass` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0277]: the trait bound `PyDict: PyClass` is not satisfied --> tests/ui/abi3_nativetype_inheritance.rs:5:1 @@ -26,8 +15,8 @@ error[E0277]: the trait bound `PyDict: PyClass` is not satisfied | = note: required because of the requirements on the impl of `PyClassBaseType` for `PyDict` note: required by a bound in `ThreadCheckerInherited` - --> src/class/impl_.rs:753:47 + --> src/class/impl_.rs | -753 | pub struct ThreadCheckerInherited(PhantomData, U::ThreadChecker); + | pub struct ThreadCheckerInherited(PhantomData, U::ThreadChecker); | ^^^^^^^^^^^^^^^ required by this bound in `ThreadCheckerInherited` = note: this error originates in the attribute macro `pyclass` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/tests/ui/invalid_argument_attributes.stderr b/tests/ui/invalid_argument_attributes.stderr index dd830a3ec04..ed6479d5512 100644 --- a/tests/ui/invalid_argument_attributes.stderr +++ b/tests/ui/invalid_argument_attributes.stderr @@ -5,10 +5,10 @@ error: expected `from_py_with` | ^^^ error: expected `=` - --> tests/ui/invalid_argument_attributes.rs:7:32 + --> tests/ui/invalid_argument_attributes.rs:7:45 | 7 | fn from_py_with_no_value(#[pyo3(from_py_with)] param: String) {} - | ^^^^^^^^^^^^^^ + | ^ error: expected `from_py_with` --> tests/ui/invalid_argument_attributes.rs:10:31 diff --git a/tests/ui/invalid_frompy_derive.stderr b/tests/ui/invalid_frompy_derive.stderr index b5cb180dcee..d16c082f9f5 100644 --- a/tests/ui/invalid_frompy_derive.stderr +++ b/tests/ui/invalid_frompy_derive.stderr @@ -109,10 +109,10 @@ error: attribute name cannot be empty | ^^ error: unexpected end of input, expected string literal - --> tests/ui/invalid_frompy_derive.rs:100:21 + --> tests/ui/invalid_frompy_derive.rs:100:22 | 100 | #[pyo3(attribute())] - | ^^ + | ^ error: expected at most one argument: `item` or `item(key)` --> tests/ui/invalid_frompy_derive.rs:106:20 @@ -121,10 +121,10 @@ error: expected at most one argument: `item` or `item(key)` | ^ error: unexpected end of input, expected literal - --> tests/ui/invalid_frompy_derive.rs:112:16 + --> tests/ui/invalid_frompy_derive.rs:112:17 | 112 | #[pyo3(item())] - | ^^ + | ^ error: only one of `attribute` or `item` can be provided --> tests/ui/invalid_frompy_derive.rs:118:5 @@ -171,10 +171,10 @@ error: cannot derive FromPyObject for empty structs and variants = note: this error originates in the derive macro `FromPyObject` (in Nightly builds, run with -Z macro-backtrace for more info) error: expected `=` - --> tests/ui/invalid_frompy_derive.rs:158:11 + --> tests/ui/invalid_frompy_derive.rs:158:24 | 158 | #[pyo3(from_py_with)] - | ^^^^^^^^^^^^^^ + | ^ error: expected string literal --> tests/ui/invalid_frompy_derive.rs:164:27 diff --git a/tests/ui/invalid_pyfunctions.stderr b/tests/ui/invalid_pyfunctions.stderr index 00bf963faed..6587535ac55 100644 --- a/tests/ui/invalid_pyfunctions.stderr +++ b/tests/ui/invalid_pyfunctions.stderr @@ -12,7 +12,7 @@ error: Python functions cannot have `impl Trait` arguments error: `async fn` is not yet supported for Python functions. -Additional crates such as `pyo3-asyncio` can be used to integrate async Rust and Python. For more information, see https://github.com/PyO3/pyo3/issues/1632 + Additional crates such as `pyo3-asyncio` can be used to integrate async Rust and Python. For more information, see https://github.com/PyO3/pyo3/issues/1632 --> tests/ui/invalid_pyfunctions.rs:10:1 | 10 | async fn async_function() {} diff --git a/tests/ui/invalid_pymethod_receiver.stderr b/tests/ui/invalid_pymethod_receiver.stderr index 395c2acd68f..fb3587f5e30 100644 --- a/tests/ui/invalid_pymethod_receiver.stderr +++ b/tests/ui/invalid_pymethod_receiver.stderr @@ -1,19 +1,14 @@ error[E0277]: the trait bound `i32: From<&PyCell>` is not satisfied - --> tests/ui/invalid_pymethod_receiver.rs:8:43 - | -8 | fn method_with_invalid_self_type(slf: i32, py: Python, index: u32) {} - | ^^^ the trait `From<&PyCell>` is not implemented for `i32` - | - = help: the following implementations were found: - > - > - > - > - and 2 others - = note: required because of the requirements on the impl of `Into` for `&PyCell` - = note: required because of the requirements on the impl of `TryFrom<&PyCell>` for `i32` -note: required by `std::convert::TryFrom::try_from` - --> $RUST/core/src/convert/mod.rs - | - | fn try_from(value: T) -> Result; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + --> tests/ui/invalid_pymethod_receiver.rs:8:43 + | +8 | fn method_with_invalid_self_type(slf: i32, py: Python, index: u32) {} + | ^^^ the trait `From<&PyCell>` is not implemented for `i32` + | + = help: the following implementations were found: + > + > + > + > + and 71 others + = note: required because of the requirements on the impl of `Into` for `&PyCell` + = note: required because of the requirements on the impl of `TryFrom<&PyCell>` for `i32` diff --git a/tests/ui/invalid_pymethods.stderr b/tests/ui/invalid_pymethods.stderr index 3556c2722eb..bc11faba722 100644 --- a/tests/ui/invalid_pymethods.stderr +++ b/tests/ui/invalid_pymethods.stderr @@ -90,7 +90,7 @@ error: Python functions cannot have `impl Trait` arguments error: `async fn` is not yet supported for Python functions. -Additional crates such as `pyo3-asyncio` can be used to integrate async Rust and Python. For more information, see https://github.com/PyO3/pyo3/issues/1632 + Additional crates such as `pyo3-asyncio` can be used to integrate async Rust and Python. For more information, see https://github.com/PyO3/pyo3/issues/1632 --> tests/ui/invalid_pymethods.rs:107:5 | 107 | async fn async_method(&self) {} diff --git a/tests/ui/missing_clone.stderr b/tests/ui/missing_clone.stderr index edb82e47b83..59c1f324ff2 100644 --- a/tests/ui/missing_clone.stderr +++ b/tests/ui/missing_clone.stderr @@ -1,7 +1,12 @@ error[E0277]: the trait bound `TestClass: Clone` is not satisfied - --> tests/ui/missing_clone.rs:15:32 - | -15 | let t: TestClass = pyvalue.extract(py).unwrap(); - | ^^^^^^^ the trait `Clone` is not implemented for `TestClass` - | - = note: required because of the requirements on the impl of `pyo3::FromPyObject<'_>` for `TestClass` + --> tests/ui/missing_clone.rs:15:32 + | +15 | let t: TestClass = pyvalue.extract(py).unwrap(); + | ^^^^^^^ the trait `Clone` is not implemented for `TestClass` + | + = note: required because of the requirements on the impl of `pyo3::FromPyObject<'_>` for `TestClass` +note: required by a bound in `pyo3::Py::::extract` + --> src/instance.rs + | + | D: FromPyObject<'p>, + | ^^^^^^^^^^^^^^^^ required by this bound in `pyo3::Py::::extract` diff --git a/tests/ui/static_ref.stderr b/tests/ui/static_ref.stderr index 58fc6a532ec..9165f63a03f 100644 --- a/tests/ui/static_ref.stderr +++ b/tests/ui/static_ref.stderr @@ -4,7 +4,7 @@ error[E0495]: cannot infer an appropriate lifetime for lifetime parameter `'py` 4 | #[pyfunction] | ^^^^^^^^^^^^^ | -note: first, the lifetime cannot outlive the anonymous lifetime #1 defined on the body at 4:1... +note: first, the lifetime cannot outlive the anonymous lifetime #1 defined here... --> tests/ui/static_ref.rs:4:1 | 4 | #[pyfunction] diff --git a/tests/ui/wrong_aspyref_lifetimes.stderr b/tests/ui/wrong_aspyref_lifetimes.stderr index 319f4ee5f3a..0042bc6b9fd 100644 --- a/tests/ui/wrong_aspyref_lifetimes.stderr +++ b/tests/ui/wrong_aspyref_lifetimes.stderr @@ -2,9 +2,9 @@ error[E0505]: cannot move out of `gil` because it is borrowed --> tests/ui/wrong_aspyref_lifetimes.rs:7:10 | 6 | let dict: &PyDict = dict.as_ref(gil.python()); - | --- borrow of `gil` occurs here + | ------------ borrow of `gil` occurs here 7 | drop(gil); | ^^^ move out of `gil` occurs here 8 | 9 | let _py: Python = dict.py(); // Obtain a Python<'p> without GIL. - | ---- borrow later used here + | --------- borrow later used here