diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0c245f33483..5bf2b7ea1e8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -224,6 +224,7 @@ jobs: "3.10", "3.11", "3.12", + "3.13-dev", "pypy3.7", "pypy3.8", "pypy3.9", diff --git a/newsfragments/4184.packaging.md b/newsfragments/4184.packaging.md new file mode 100644 index 00000000000..c12302a7029 --- /dev/null +++ b/newsfragments/4184.packaging.md @@ -0,0 +1 @@ +Support Python 3.13. diff --git a/noxfile.py b/noxfile.py index 84676b1ff0c..2383e2f865f 100644 --- a/noxfile.py +++ b/noxfile.py @@ -30,7 +30,7 @@ PYO3_GUIDE_SRC = PYO3_DIR / "guide" / "src" PYO3_GUIDE_TARGET = PYO3_TARGET / "guide" PYO3_DOCS_TARGET = PYO3_TARGET / "doc" -PY_VERSIONS = ("3.7", "3.8", "3.9", "3.10", "3.11", "3.12") +PY_VERSIONS = ("3.7", "3.8", "3.9", "3.10", "3.11", "3.12", "3.13") PYPY_VERSIONS = ("3.7", "3.8", "3.9", "3.10") @@ -631,11 +631,11 @@ def test_version_limits(session: nox.Session): config_file.set("CPython", "3.6") _run_cargo(session, "check", env=env, expect_error=True) - assert "3.13" not in PY_VERSIONS - config_file.set("CPython", "3.13") + assert "3.14" not in PY_VERSIONS + config_file.set("CPython", "3.14") _run_cargo(session, "check", env=env, expect_error=True) - # 3.13 CPython should build with forward compatibility + # 3.14 CPython should build with forward compatibility env["PYO3_USE_ABI3_FORWARD_COMPATIBILITY"] = "1" _run_cargo(session, "check", env=env) @@ -734,7 +734,9 @@ def update_ui_tests(session: nox.Session): def _build_docs_for_ffi_check(session: nox.Session) -> None: # pyo3-ffi-check needs to scrape docs of pyo3-ffi - _run_cargo(session, "doc", _FFI_CHECK, "-p", "pyo3-ffi", "--no-deps") + env = os.environ.copy() + env["PYO3_PYTHON"] = sys.executable + _run_cargo(session, "doc", _FFI_CHECK, "-p", "pyo3-ffi", "--no-deps", env=env) @lru_cache() diff --git a/pyo3-ffi/build.rs b/pyo3-ffi/build.rs index 23f03f1a636..0f4931d6dc7 100644 --- a/pyo3-ffi/build.rs +++ b/pyo3-ffi/build.rs @@ -17,7 +17,7 @@ const SUPPORTED_VERSIONS_CPYTHON: SupportedVersions = SupportedVersions { min: PythonVersion { major: 3, minor: 7 }, max: PythonVersion { major: 3, - minor: 12, + minor: 13, }, }; diff --git a/pyo3-ffi/src/cpython/code.rs b/pyo3-ffi/src/cpython/code.rs index 74586eac595..86e862f21ab 100644 --- a/pyo3-ffi/src/cpython/code.rs +++ b/pyo3-ffi/src/cpython/code.rs @@ -20,7 +20,11 @@ pub const _PY_MONITORING_EVENTS: usize = 17; #[repr(C)] #[derive(Clone, Copy)] pub struct _Py_LocalMonitors { - pub tools: [u8; _PY_MONITORING_UNGROUPED_EVENTS], + pub tools: [u8; if cfg!(Py_3_13) { + _PY_MONITORING_LOCAL_EVENTS + } else { + _PY_MONITORING_UNGROUPED_EVENTS + }], } #[cfg(Py_3_12)] @@ -102,6 +106,9 @@ pub struct PyCodeObject { pub co_extra: *mut c_void, } +#[cfg(Py_3_13)] +opaque_struct!(_PyExecutorArray); + #[cfg(all(not(any(PyPy, GraalPy)), Py_3_8, not(Py_3_11)))] #[repr(C)] #[derive(Copy, Clone)] @@ -176,6 +183,8 @@ pub struct PyCodeObject { pub _co_code: *mut PyObject, #[cfg(not(Py_3_12))] pub _co_linearray: *mut c_char, + #[cfg(Py_3_13)] + pub co_executors: *mut _PyExecutorArray, #[cfg(Py_3_12)] pub _co_cached: *mut _PyCoCached, #[cfg(Py_3_12)] diff --git a/pyo3-ffi/src/cpython/compile.rs b/pyo3-ffi/src/cpython/compile.rs index 8bce9dacb3b..79f06c92003 100644 --- a/pyo3-ffi/src/cpython/compile.rs +++ b/pyo3-ffi/src/cpython/compile.rs @@ -30,7 +30,7 @@ pub struct PyCompilerFlags { // skipped non-limited _PyCompilerFlags_INIT -#[cfg(all(Py_3_12, not(any(PyPy, GraalPy))))] +#[cfg(all(Py_3_12, not(any(Py_3_13, PyPy, GraalPy))))] #[repr(C)] #[derive(Copy, Clone)] pub struct _PyCompilerSrcLocation { @@ -42,7 +42,7 @@ pub struct _PyCompilerSrcLocation { // skipped SRC_LOCATION_FROM_AST -#[cfg(not(any(PyPy, GraalPy)))] +#[cfg(not(any(PyPy, GraalPy, Py_3_13)))] #[repr(C)] #[derive(Copy, Clone)] pub struct PyFutureFeatures { diff --git a/pyo3-ffi/src/cpython/import.rs b/pyo3-ffi/src/cpython/import.rs index aafd71a8355..697d68a419c 100644 --- a/pyo3-ffi/src/cpython/import.rs +++ b/pyo3-ffi/src/cpython/import.rs @@ -57,7 +57,7 @@ pub struct _frozen { pub size: c_int, #[cfg(Py_3_11)] pub is_package: c_int, - #[cfg(Py_3_11)] + #[cfg(all(Py_3_11, not(Py_3_13)))] pub get_code: Option *mut PyObject>, } diff --git a/pyo3-ffi/src/cpython/initconfig.rs b/pyo3-ffi/src/cpython/initconfig.rs index 17fe7559e1b..32931415888 100644 --- a/pyo3-ffi/src/cpython/initconfig.rs +++ b/pyo3-ffi/src/cpython/initconfig.rs @@ -141,6 +141,8 @@ pub struct PyConfig { pub safe_path: c_int, #[cfg(Py_3_12)] pub int_max_str_digits: c_int, + #[cfg(Py_3_13)] + pub cpu_count: c_int, pub pathconfig_warnings: c_int, #[cfg(Py_3_10)] pub program_name: *mut wchar_t, @@ -165,6 +167,8 @@ pub struct PyConfig { pub run_command: *mut wchar_t, pub run_module: *mut wchar_t, pub run_filename: *mut wchar_t, + #[cfg(Py_3_13)] + pub sys_path_0: *mut wchar_t, pub _install_importlib: c_int, pub _init_main: c_int, #[cfg(all(Py_3_9, not(Py_3_12)))] diff --git a/pyo3-ffi/src/cpython/longobject.rs b/pyo3-ffi/src/cpython/longobject.rs new file mode 100644 index 00000000000..45acaae577d --- /dev/null +++ b/pyo3-ffi/src/cpython/longobject.rs @@ -0,0 +1,74 @@ +use crate::longobject::*; +use crate::object::*; +#[cfg(Py_3_13)] +use crate::pyport::Py_ssize_t; +use libc::size_t; +#[cfg(Py_3_13)] +use std::os::raw::c_void; +use std::os::raw::{c_int, c_uchar}; + +#[cfg(Py_3_13)] +extern "C" { + pub fn PyLong_FromUnicodeObject(u: *mut PyObject, base: c_int) -> *mut PyObject; +} + +#[cfg(Py_3_13)] +pub const Py_ASNATIVEBYTES_DEFAULTS: c_int = -1; +#[cfg(Py_3_13)] +pub const Py_ASNATIVEBYTES_BIG_ENDIAN: c_int = 0; +#[cfg(Py_3_13)] +pub const Py_ASNATIVEBYTES_LITTLE_ENDIAN: c_int = 1; +#[cfg(Py_3_13)] +pub const Py_ASNATIVEBYTES_NATIVE_ENDIAN: c_int = 3; +#[cfg(Py_3_13)] +pub const Py_ASNATIVEBYTES_UNSIGNED_BUFFER: c_int = 4; +#[cfg(Py_3_13)] +pub const Py_ASNATIVEBYTES_REJECT_NEGATIVE: c_int = 8; + +extern "C" { + // skipped _PyLong_Sign + + #[cfg(Py_3_13)] + pub fn PyLong_AsNativeBytes( + v: *mut PyObject, + buffer: *mut c_void, + n_bytes: Py_ssize_t, + flags: c_int, + ) -> Py_ssize_t; + + #[cfg(Py_3_13)] + pub fn PyLong_FromNativeBytes( + buffer: *const c_void, + n_bytes: size_t, + flags: c_int, + ) -> *mut PyObject; + + #[cfg(Py_3_13)] + pub fn PyLong_FromUnsignedNativeBytes( + buffer: *const c_void, + n_bytes: size_t, + flags: c_int, + ) -> *mut PyObject; + + // skipped PyUnstable_Long_IsCompact + // skipped PyUnstable_Long_CompactValue + + #[cfg_attr(PyPy, link_name = "_PyPyLong_FromByteArray")] + pub fn _PyLong_FromByteArray( + bytes: *const c_uchar, + n: size_t, + little_endian: c_int, + is_signed: c_int, + ) -> *mut PyObject; + + #[cfg_attr(PyPy, link_name = "_PyPyLong_AsByteArrayO")] + pub fn _PyLong_AsByteArray( + v: *mut PyLongObject, + bytes: *mut c_uchar, + n: size_t, + little_endian: c_int, + is_signed: c_int, + ) -> c_int; + + // skipped _PyLong_GCD +} diff --git a/pyo3-ffi/src/cpython/mod.rs b/pyo3-ffi/src/cpython/mod.rs index 1ab0e3c893f..1710dbc4122 100644 --- a/pyo3-ffi/src/cpython/mod.rs +++ b/pyo3-ffi/src/cpython/mod.rs @@ -18,6 +18,7 @@ pub(crate) mod import; pub(crate) mod initconfig; // skipped interpreteridobject.h pub(crate) mod listobject; +pub(crate) mod longobject; #[cfg(all(Py_3_9, not(PyPy)))] pub(crate) mod methodobject; pub(crate) mod object; @@ -53,6 +54,7 @@ pub use self::import::*; #[cfg(all(Py_3_8, not(PyPy)))] pub use self::initconfig::*; pub use self::listobject::*; +pub use self::longobject::*; #[cfg(all(Py_3_9, not(PyPy)))] pub use self::methodobject::*; pub use self::object::*; diff --git a/pyo3-ffi/src/cpython/object.rs b/pyo3-ffi/src/cpython/object.rs index 0f1778f6a3d..b4f6ce5a878 100644 --- a/pyo3-ffi/src/cpython/object.rs +++ b/pyo3-ffi/src/cpython/object.rs @@ -296,6 +296,8 @@ pub struct _specialization_cache { pub getitem: *mut PyObject, #[cfg(Py_3_12)] pub getitem_version: u32, + #[cfg(Py_3_13)] + pub init: *mut PyObject, } #[repr(C)] diff --git a/pyo3-ffi/src/longobject.rs b/pyo3-ffi/src/longobject.rs index 55ea8fa1462..35a2bc1b0ff 100644 --- a/pyo3-ffi/src/longobject.rs +++ b/pyo3-ffi/src/longobject.rs @@ -1,8 +1,6 @@ use crate::object::*; use crate::pyport::Py_ssize_t; use libc::size_t; -#[cfg(not(Py_LIMITED_API))] -use std::os::raw::c_uchar; use std::os::raw::{c_char, c_double, c_int, c_long, c_longlong, c_ulong, c_ulonglong, c_void}; use std::ptr::addr_of_mut; @@ -90,34 +88,12 @@ extern "C" { arg3: c_int, ) -> *mut PyObject; } -// skipped non-limited PyLong_FromUnicodeObject -// skipped non-limited _PyLong_FromBytes #[cfg(not(Py_LIMITED_API))] extern "C" { - // skipped non-limited _PyLong_Sign - #[cfg_attr(PyPy, link_name = "_PyPyLong_NumBits")] + #[cfg(not(Py_3_13))] pub fn _PyLong_NumBits(obj: *mut PyObject) -> size_t; - - // skipped _PyLong_DivmodNear - - #[cfg_attr(PyPy, link_name = "_PyPyLong_FromByteArray")] - pub fn _PyLong_FromByteArray( - bytes: *const c_uchar, - n: size_t, - little_endian: c_int, - is_signed: c_int, - ) -> *mut PyObject; - - #[cfg_attr(PyPy, link_name = "_PyPyLong_AsByteArrayO")] - pub fn _PyLong_AsByteArray( - v: *mut PyLongObject, - bytes: *mut c_uchar, - n: size_t, - little_endian: c_int, - is_signed: c_int, - ) -> c_int; } // skipped non-limited _PyLong_Format @@ -130,6 +106,5 @@ extern "C" { pub fn PyOS_strtol(arg1: *const c_char, arg2: *mut *mut c_char, arg3: c_int) -> c_long; } -// skipped non-limited _PyLong_GCD // skipped non-limited _PyLong_Rshift // skipped non-limited _PyLong_Lshift diff --git a/pytests/noxfile.py b/pytests/noxfile.py index 7c681ab1aa8..af2eb0d3a75 100644 --- a/pytests/noxfile.py +++ b/pytests/noxfile.py @@ -9,11 +9,16 @@ def test(session: nox.Session): session.env["MATURIN_PEP517_ARGS"] = "--profile=dev" session.run_always("python", "-m", "pip", "install", "-v", ".[dev]") - try: - session.install("--only-binary=numpy", "numpy>=1.16") - except CommandFailed: - # No binary wheel for numpy available on this platform - pass + + def try_install_binary(package: str, constraint: str): + try: + session.install(f"--only-binary={package}", f"{package}{constraint}") + except CommandFailed: + # No binary wheel available on this platform + pass + + try_install_binary("numpy", ">=1.16") + try_install_binary("gevent", ">=22.10.2") ignored_paths = [] if sys.version_info < (3, 10): # Match syntax is only available in Python >= 3.10 diff --git a/pytests/pyproject.toml b/pytests/pyproject.toml index aace57dd4d4..5f78a573124 100644 --- a/pytests/pyproject.toml +++ b/pytests/pyproject.toml @@ -20,7 +20,6 @@ classifiers = [ [project.optional-dependencies] dev = [ - "gevent>=22.10.2; implementation_name == 'cpython'", "hypothesis>=3.55", "pytest-asyncio>=0.21", "pytest-benchmark>=3.4", diff --git a/pytests/tests/test_misc.py b/pytests/tests/test_misc.py index de75f1c8a80..88af735e861 100644 --- a/pytests/tests/test_misc.py +++ b/pytests/tests/test_misc.py @@ -5,6 +5,11 @@ import pyo3_pytests.misc import pytest +if sys.version_info >= (3, 13): + subinterpreters = pytest.importorskip("subinterpreters") +else: + subinterpreters = pytest.importorskip("_xxsubinterpreters") + def test_issue_219(): # Should not deadlock @@ -31,23 +36,19 @@ def test_multiple_imports_same_interpreter_ok(): reason="PyPy and GraalPy do not support subinterpreters", ) def test_import_in_subinterpreter_forbidden(): - import _xxsubinterpreters - if sys.version_info < (3, 12): expected_error = "PyO3 modules do not yet support subinterpreters, see https://github.com/PyO3/pyo3/issues/576" else: expected_error = "module pyo3_pytests.pyo3_pytests does not support loading in subinterpreters" - sub_interpreter = _xxsubinterpreters.create() + sub_interpreter = subinterpreters.create() with pytest.raises( - _xxsubinterpreters.RunFailedError, + subinterpreters.RunFailedError, match=expected_error, ): - _xxsubinterpreters.run_string( - sub_interpreter, "import pyo3_pytests.pyo3_pytests" - ) + subinterpreters.run_string(sub_interpreter, "import pyo3_pytests.pyo3_pytests") - _xxsubinterpreters.destroy(sub_interpreter) + subinterpreters.destroy(sub_interpreter) def test_type_full_name_includes_module(): diff --git a/src/conversions/num_bigint.rs b/src/conversions/num_bigint.rs index 743df8a9923..196ae28e788 100644 --- a/src/conversions/num_bigint.rs +++ b/src/conversions/num_bigint.rs @@ -47,6 +47,8 @@ //! assert n + 1 == value //! ``` +#[cfg(not(Py_LIMITED_API))] +use crate::ffi_ptr_ext::FfiPtrExt; #[cfg(Py_LIMITED_API)] use crate::types::{bytes::PyBytesMethods, PyBytes}; use crate::{ @@ -63,20 +65,47 @@ use num_bigint::Sign; // for identical functionality between BigInt and BigUint macro_rules! bigint_conversion { - ($rust_ty: ty, $is_signed: expr, $to_bytes: path) => { + ($rust_ty: ty, $is_signed: literal, $to_bytes: path) => { #[cfg_attr(docsrs, doc(cfg(feature = "num-bigint")))] impl ToPyObject for $rust_ty { #[cfg(not(Py_LIMITED_API))] fn to_object(&self, py: Python<'_>) -> PyObject { let bytes = $to_bytes(self); - unsafe { - let obj = ffi::_PyLong_FromByteArray( - bytes.as_ptr().cast(), - bytes.len(), - 1, - $is_signed, - ); - PyObject::from_owned_ptr(py, obj) + #[cfg(not(Py_3_13))] + { + unsafe { + ffi::_PyLong_FromByteArray( + bytes.as_ptr().cast(), + bytes.len(), + 1, + $is_signed.into(), + ) + .assume_owned(py) + .unbind() + } + } + #[cfg(Py_3_13)] + { + if $is_signed { + unsafe { + ffi::PyLong_FromNativeBytes( + bytes.as_ptr().cast(), + bytes.len(), + ffi::Py_ASNATIVEBYTES_LITTLE_ENDIAN, + ) + .assume_owned(py) + } + } else { + unsafe { + ffi::PyLong_FromUnsignedNativeBytes( + bytes.as_ptr().cast(), + bytes.len(), + ffi::Py_ASNATIVEBYTES_LITTLE_ENDIAN, + ) + .assume_owned(py) + } + } + .unbind() } } @@ -84,7 +113,7 @@ macro_rules! bigint_conversion { fn to_object(&self, py: Python<'_>) -> PyObject { let bytes = $to_bytes(self); let bytes_obj = PyBytes::new_bound(py, &bytes); - let kwargs = if $is_signed > 0 { + let kwargs = if $is_signed { let kwargs = crate::types::PyDict::new_bound(py); kwargs.set_item(crate::intern!(py, "signed"), true).unwrap(); Some(kwargs) @@ -107,8 +136,8 @@ macro_rules! bigint_conversion { }; } -bigint_conversion!(BigUint, 0, BigUint::to_bytes_le); -bigint_conversion!(BigInt, 1, BigInt::to_signed_bytes_le); +bigint_conversion!(BigUint, false, BigUint::to_bytes_le); +bigint_conversion!(BigInt, true, BigInt::to_signed_bytes_le); #[cfg_attr(docsrs, doc(cfg(feature = "num-bigint")))] impl<'py> FromPyObject<'py> for BigInt { @@ -122,13 +151,9 @@ impl<'py> FromPyObject<'py> for BigInt { num_owned = unsafe { Py::from_owned_ptr_or_err(py, ffi::PyNumber_Index(ob.as_ptr()))? }; num_owned.bind(py) }; - let n_bits = int_n_bits(num)?; - if n_bits == 0 { - return Ok(BigInt::from(0isize)); - } #[cfg(not(Py_LIMITED_API))] { - let mut buffer = int_to_u32_vec(num, (n_bits + 32) / 32, true)?; + let mut buffer = int_to_u32_vec::(num)?; let sign = if buffer.last().copied().map_or(false, |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) @@ -152,6 +177,10 @@ impl<'py> FromPyObject<'py> for BigInt { } #[cfg(Py_LIMITED_API)] { + let n_bits = int_n_bits(num)?; + if n_bits == 0 { + return Ok(BigInt::from(0isize)); + } let bytes = int_to_py_bytes(num, (n_bits + 8) / 8, true)?; Ok(BigInt::from_signed_bytes_le(bytes.as_bytes())) } @@ -170,31 +199,37 @@ impl<'py> FromPyObject<'py> for BigUint { num_owned = unsafe { Py::from_owned_ptr_or_err(py, ffi::PyNumber_Index(ob.as_ptr()))? }; num_owned.bind(py) }; - let n_bits = int_n_bits(num)?; - if n_bits == 0 { - return Ok(BigUint::from(0usize)); - } #[cfg(not(Py_LIMITED_API))] { - let buffer = int_to_u32_vec(num, (n_bits + 31) / 32, false)?; + let buffer = int_to_u32_vec::(num)?; Ok(BigUint::new(buffer)) } #[cfg(Py_LIMITED_API)] { + let n_bits = int_n_bits(num)?; + if n_bits == 0 { + return Ok(BigUint::from(0usize)); + } let bytes = int_to_py_bytes(num, (n_bits + 7) / 8, false)?; Ok(BigUint::from_bytes_le(bytes.as_bytes())) } } } -#[cfg(not(Py_LIMITED_API))] +#[cfg(not(any(Py_LIMITED_API, Py_3_13)))] #[inline] -fn int_to_u32_vec( - long: &Bound<'_, PyLong>, - n_digits: usize, - is_signed: bool, -) -> PyResult> { - let mut buffer = Vec::with_capacity(n_digits); +fn int_to_u32_vec(long: &Bound<'_, PyLong>) -> PyResult> { + let mut buffer = Vec::new(); + let n_bits = int_n_bits(long)?; + if n_bits == 0 { + return Ok(buffer); + } + let n_digits = if SIGNED { + (n_bits + 32) / 32 + } else { + (n_bits + 31) / 32 + }; + buffer.reserve_exact(n_digits); unsafe { crate::err::error_on_minusone( long.py(), @@ -203,7 +238,7 @@ fn int_to_u32_vec( buffer.as_mut_ptr() as *mut u8, n_digits * 4, 1, - is_signed.into(), + SIGNED.into(), ), )?; buffer.set_len(n_digits) @@ -215,6 +250,44 @@ fn int_to_u32_vec( Ok(buffer) } +#[cfg(all(not(Py_LIMITED_API), Py_3_13))] +#[inline] +fn int_to_u32_vec(long: &Bound<'_, PyLong>) -> PyResult> { + let mut buffer = Vec::new(); + let mut flags = ffi::Py_ASNATIVEBYTES_LITTLE_ENDIAN; + if !SIGNED { + flags |= ffi::Py_ASNATIVEBYTES_UNSIGNED_BUFFER | ffi::Py_ASNATIVEBYTES_REJECT_NEGATIVE; + } + let n_bytes = + unsafe { ffi::PyLong_AsNativeBytes(long.as_ptr().cast(), std::ptr::null_mut(), 0, flags) }; + let n_bytes_unsigned: usize = n_bytes + .try_into() + .map_err(|_| crate::PyErr::fetch(long.py()))?; + if n_bytes == 0 { + return Ok(buffer); + } + // TODO: use div_ceil when MSRV >= 1.73 + let n_digits = { + let adjust = if n_bytes % 4 == 0 { 0 } else { 1 }; + (n_bytes_unsigned / 4) + adjust + }; + buffer.reserve_exact(n_digits); + unsafe { + ffi::PyLong_AsNativeBytes( + long.as_ptr().cast(), + buffer.as_mut_ptr().cast(), + (n_digits * 4).try_into().unwrap(), + flags, + ); + buffer.set_len(n_digits); + }; + buffer + .iter_mut() + .for_each(|chunk| *chunk = u32::from_le(*chunk)); + + Ok(buffer) +} + #[cfg(Py_LIMITED_API)] fn int_to_py_bytes<'py>( long: &Bound<'py, PyLong>, @@ -239,6 +312,7 @@ fn int_to_py_bytes<'py>( } #[inline] +#[cfg(any(not(Py_3_13), Py_LIMITED_API))] fn int_n_bits(long: &Bound<'_, PyLong>) -> PyResult { let py = long.py(); #[cfg(not(Py_LIMITED_API))] diff --git a/src/conversions/std/num.rs b/src/conversions/std/num.rs index e2072d210e0..effe7c7c062 100644 --- a/src/conversions/std/num.rs +++ b/src/conversions/std/num.rs @@ -1,3 +1,4 @@ +use crate::ffi_ptr_ext::FfiPtrExt; #[cfg(feature = "experimental-inspect")] use crate::inspect::types::TypeInfo; use crate::types::any::PyAnyMethods; @@ -63,14 +64,8 @@ macro_rules! extract_int { err_if_invalid_value($obj.py(), $error_val, unsafe { $pylong_as(long.as_ptr()) }) } else { unsafe { - let num = ffi::PyNumber_Index($obj.as_ptr()); - if num.is_null() { - Err(PyErr::fetch($obj.py())) - } else { - let result = err_if_invalid_value($obj.py(), $error_val, $pylong_as(num)); - ffi::Py_DECREF(num); - result - } + let num = ffi::PyNumber_Index($obj.as_ptr()).assume_owned_or_err($obj.py())?; + err_if_invalid_value($obj.py(), $error_val, $pylong_as(num.as_ptr())) } } }; @@ -181,7 +176,7 @@ mod fast_128bit_int_conversion { // for 128bit Integers macro_rules! int_convert_128 { - ($rust_type: ty, $is_signed: expr) => { + ($rust_type: ty, $is_signed: literal) => { impl ToPyObject for $rust_type { #[inline] fn to_object(&self, py: Python<'_>) -> PyObject { @@ -190,18 +185,44 @@ mod fast_128bit_int_conversion { } impl IntoPy for $rust_type { fn into_py(self, py: Python<'_>) -> PyObject { - // Always use little endian - let bytes = self.to_le_bytes(); - unsafe { - PyObject::from_owned_ptr( - py, + #[cfg(not(Py_3_13))] + { + let bytes = self.to_le_bytes(); + unsafe { ffi::_PyLong_FromByteArray( - bytes.as_ptr() as *const std::os::raw::c_uchar, + bytes.as_ptr().cast(), bytes.len(), 1, - $is_signed, - ), - ) + $is_signed.into(), + ) + .assume_owned(py) + .unbind() + } + } + #[cfg(Py_3_13)] + { + let bytes = self.to_ne_bytes(); + + if $is_signed { + unsafe { + ffi::PyLong_FromNativeBytes( + bytes.as_ptr().cast(), + bytes.len(), + ffi::Py_ASNATIVEBYTES_NATIVE_ENDIAN, + ) + .assume_owned(py) + } + } else { + unsafe { + ffi::PyLong_FromUnsignedNativeBytes( + bytes.as_ptr().cast(), + bytes.len(), + ffi::Py_ASNATIVEBYTES_NATIVE_ENDIAN, + ) + .assume_owned(py) + } + } + .unbind() } } @@ -213,20 +234,46 @@ mod fast_128bit_int_conversion { impl FromPyObject<'_> for $rust_type { fn extract_bound(ob: &Bound<'_, PyAny>) -> PyResult<$rust_type> { - let num = unsafe { - PyObject::from_owned_ptr_or_err(ob.py(), ffi::PyNumber_Index(ob.as_ptr()))? - }; - let mut buffer = [0; std::mem::size_of::<$rust_type>()]; + let num = + unsafe { ffi::PyNumber_Index(ob.as_ptr()).assume_owned_or_err(ob.py())? }; + let mut buffer = [0u8; std::mem::size_of::<$rust_type>()]; + #[cfg(not(Py_3_13))] crate::err::error_on_minusone(ob.py(), unsafe { ffi::_PyLong_AsByteArray( num.as_ptr() as *mut ffi::PyLongObject, buffer.as_mut_ptr(), buffer.len(), 1, - $is_signed, + $is_signed.into(), ) })?; - Ok(<$rust_type>::from_le_bytes(buffer)) + #[cfg(Py_3_13)] + { + let mut flags = ffi::Py_ASNATIVEBYTES_NATIVE_ENDIAN; + if !$is_signed { + flags |= ffi::Py_ASNATIVEBYTES_UNSIGNED_BUFFER + | ffi::Py_ASNATIVEBYTES_REJECT_NEGATIVE; + } + let actual_size: usize = unsafe { + ffi::PyLong_AsNativeBytes( + num.as_ptr(), + buffer.as_mut_ptr().cast(), + buffer + .len() + .try_into() + .expect("length of buffer fits in Py_ssize_t"), + flags, + ) + } + .try_into() + .map_err(|_| PyErr::fetch(ob.py()))?; + if actual_size as usize > buffer.len() { + return Err(crate::exceptions::PyOverflowError::new_err( + "Python int larger than 128 bits", + )); + } + } + Ok(<$rust_type>::from_ne_bytes(buffer)) } #[cfg(feature = "experimental-inspect")] @@ -237,8 +284,8 @@ mod fast_128bit_int_conversion { }; } - int_convert_128!(i128, 1); - int_convert_128!(u128, 0); + int_convert_128!(i128, true); + int_convert_128!(u128, false); } // For ABI3 we implement the conversion manually.