ci: start testing on 3.13-dev (#4184)

* ci: start testing on 3.13-dev

* ffi fixes for 3.13 beta 1

* support 3.13

* move gevent to be binary-only

* adjust for div_ceil

* fixup pytests
This commit is contained in:
David Hewitt 2024-05-25 23:41:26 +01:00 committed by GitHub
parent d21045cbc1
commit 388d1760b5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
17 changed files with 302 additions and 106 deletions

View File

@ -224,6 +224,7 @@ jobs:
"3.10", "3.10",
"3.11", "3.11",
"3.12", "3.12",
"3.13-dev",
"pypy3.7", "pypy3.7",
"pypy3.8", "pypy3.8",
"pypy3.9", "pypy3.9",

View File

@ -0,0 +1 @@
Support Python 3.13.

View File

@ -30,7 +30,7 @@ PYO3_TARGET = Path(os.environ.get("CARGO_TARGET_DIR", PYO3_DIR / "target")).abso
PYO3_GUIDE_SRC = PYO3_DIR / "guide" / "src" PYO3_GUIDE_SRC = PYO3_DIR / "guide" / "src"
PYO3_GUIDE_TARGET = PYO3_TARGET / "guide" PYO3_GUIDE_TARGET = PYO3_TARGET / "guide"
PYO3_DOCS_TARGET = PYO3_TARGET / "doc" 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") 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") config_file.set("CPython", "3.6")
_run_cargo(session, "check", env=env, expect_error=True) _run_cargo(session, "check", env=env, expect_error=True)
assert "3.13" not in PY_VERSIONS assert "3.14" not in PY_VERSIONS
config_file.set("CPython", "3.13") config_file.set("CPython", "3.14")
_run_cargo(session, "check", env=env, expect_error=True) _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" env["PYO3_USE_ABI3_FORWARD_COMPATIBILITY"] = "1"
_run_cargo(session, "check", env=env) _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: def _build_docs_for_ffi_check(session: nox.Session) -> None:
# pyo3-ffi-check needs to scrape docs of pyo3-ffi # 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() @lru_cache()

View File

@ -17,7 +17,7 @@ const SUPPORTED_VERSIONS_CPYTHON: SupportedVersions = SupportedVersions {
min: PythonVersion { major: 3, minor: 7 }, min: PythonVersion { major: 3, minor: 7 },
max: PythonVersion { max: PythonVersion {
major: 3, major: 3,
minor: 12, minor: 13,
}, },
}; };

View File

@ -20,7 +20,11 @@ pub const _PY_MONITORING_EVENTS: usize = 17;
#[repr(C)] #[repr(C)]
#[derive(Clone, Copy)] #[derive(Clone, Copy)]
pub struct _Py_LocalMonitors { 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)] #[cfg(Py_3_12)]
@ -102,6 +106,9 @@ pub struct PyCodeObject {
pub co_extra: *mut c_void, 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)))] #[cfg(all(not(any(PyPy, GraalPy)), Py_3_8, not(Py_3_11)))]
#[repr(C)] #[repr(C)]
#[derive(Copy, Clone)] #[derive(Copy, Clone)]
@ -176,6 +183,8 @@ pub struct PyCodeObject {
pub _co_code: *mut PyObject, pub _co_code: *mut PyObject,
#[cfg(not(Py_3_12))] #[cfg(not(Py_3_12))]
pub _co_linearray: *mut c_char, pub _co_linearray: *mut c_char,
#[cfg(Py_3_13)]
pub co_executors: *mut _PyExecutorArray,
#[cfg(Py_3_12)] #[cfg(Py_3_12)]
pub _co_cached: *mut _PyCoCached, pub _co_cached: *mut _PyCoCached,
#[cfg(Py_3_12)] #[cfg(Py_3_12)]

View File

@ -30,7 +30,7 @@ pub struct PyCompilerFlags {
// skipped non-limited _PyCompilerFlags_INIT // 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)] #[repr(C)]
#[derive(Copy, Clone)] #[derive(Copy, Clone)]
pub struct _PyCompilerSrcLocation { pub struct _PyCompilerSrcLocation {
@ -42,7 +42,7 @@ pub struct _PyCompilerSrcLocation {
// skipped SRC_LOCATION_FROM_AST // skipped SRC_LOCATION_FROM_AST
#[cfg(not(any(PyPy, GraalPy)))] #[cfg(not(any(PyPy, GraalPy, Py_3_13)))]
#[repr(C)] #[repr(C)]
#[derive(Copy, Clone)] #[derive(Copy, Clone)]
pub struct PyFutureFeatures { pub struct PyFutureFeatures {

View File

@ -57,7 +57,7 @@ pub struct _frozen {
pub size: c_int, pub size: c_int,
#[cfg(Py_3_11)] #[cfg(Py_3_11)]
pub is_package: c_int, pub is_package: c_int,
#[cfg(Py_3_11)] #[cfg(all(Py_3_11, not(Py_3_13)))]
pub get_code: Option<unsafe extern "C" fn() -> *mut PyObject>, pub get_code: Option<unsafe extern "C" fn() -> *mut PyObject>,
} }

View File

@ -141,6 +141,8 @@ pub struct PyConfig {
pub safe_path: c_int, pub safe_path: c_int,
#[cfg(Py_3_12)] #[cfg(Py_3_12)]
pub int_max_str_digits: c_int, pub int_max_str_digits: c_int,
#[cfg(Py_3_13)]
pub cpu_count: c_int,
pub pathconfig_warnings: c_int, pub pathconfig_warnings: c_int,
#[cfg(Py_3_10)] #[cfg(Py_3_10)]
pub program_name: *mut wchar_t, pub program_name: *mut wchar_t,
@ -165,6 +167,8 @@ pub struct PyConfig {
pub run_command: *mut wchar_t, pub run_command: *mut wchar_t,
pub run_module: *mut wchar_t, pub run_module: *mut wchar_t,
pub run_filename: *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 _install_importlib: c_int,
pub _init_main: c_int, pub _init_main: c_int,
#[cfg(all(Py_3_9, not(Py_3_12)))] #[cfg(all(Py_3_9, not(Py_3_12)))]

View File

@ -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
}

View File

@ -18,6 +18,7 @@ pub(crate) mod import;
pub(crate) mod initconfig; pub(crate) mod initconfig;
// skipped interpreteridobject.h // skipped interpreteridobject.h
pub(crate) mod listobject; pub(crate) mod listobject;
pub(crate) mod longobject;
#[cfg(all(Py_3_9, not(PyPy)))] #[cfg(all(Py_3_9, not(PyPy)))]
pub(crate) mod methodobject; pub(crate) mod methodobject;
pub(crate) mod object; pub(crate) mod object;
@ -53,6 +54,7 @@ pub use self::import::*;
#[cfg(all(Py_3_8, not(PyPy)))] #[cfg(all(Py_3_8, not(PyPy)))]
pub use self::initconfig::*; pub use self::initconfig::*;
pub use self::listobject::*; pub use self::listobject::*;
pub use self::longobject::*;
#[cfg(all(Py_3_9, not(PyPy)))] #[cfg(all(Py_3_9, not(PyPy)))]
pub use self::methodobject::*; pub use self::methodobject::*;
pub use self::object::*; pub use self::object::*;

View File

@ -296,6 +296,8 @@ pub struct _specialization_cache {
pub getitem: *mut PyObject, pub getitem: *mut PyObject,
#[cfg(Py_3_12)] #[cfg(Py_3_12)]
pub getitem_version: u32, pub getitem_version: u32,
#[cfg(Py_3_13)]
pub init: *mut PyObject,
} }
#[repr(C)] #[repr(C)]

View File

@ -1,8 +1,6 @@
use crate::object::*; use crate::object::*;
use crate::pyport::Py_ssize_t; use crate::pyport::Py_ssize_t;
use libc::size_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::os::raw::{c_char, c_double, c_int, c_long, c_longlong, c_ulong, c_ulonglong, c_void};
use std::ptr::addr_of_mut; use std::ptr::addr_of_mut;
@ -90,34 +88,12 @@ extern "C" {
arg3: c_int, arg3: c_int,
) -> *mut PyObject; ) -> *mut PyObject;
} }
// skipped non-limited PyLong_FromUnicodeObject
// skipped non-limited _PyLong_FromBytes
#[cfg(not(Py_LIMITED_API))] #[cfg(not(Py_LIMITED_API))]
extern "C" { extern "C" {
// skipped non-limited _PyLong_Sign
#[cfg_attr(PyPy, link_name = "_PyPyLong_NumBits")] #[cfg_attr(PyPy, link_name = "_PyPyLong_NumBits")]
#[cfg(not(Py_3_13))]
pub fn _PyLong_NumBits(obj: *mut PyObject) -> size_t; 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 // 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; 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_Rshift
// skipped non-limited _PyLong_Lshift // skipped non-limited _PyLong_Lshift

View File

@ -9,11 +9,16 @@ nox.options.sessions = ["test"]
def test(session: nox.Session): def test(session: nox.Session):
session.env["MATURIN_PEP517_ARGS"] = "--profile=dev" session.env["MATURIN_PEP517_ARGS"] = "--profile=dev"
session.run_always("python", "-m", "pip", "install", "-v", ".[dev]") session.run_always("python", "-m", "pip", "install", "-v", ".[dev]")
def try_install_binary(package: str, constraint: str):
try: try:
session.install("--only-binary=numpy", "numpy>=1.16") session.install(f"--only-binary={package}", f"{package}{constraint}")
except CommandFailed: except CommandFailed:
# No binary wheel for numpy available on this platform # No binary wheel available on this platform
pass pass
try_install_binary("numpy", ">=1.16")
try_install_binary("gevent", ">=22.10.2")
ignored_paths = [] ignored_paths = []
if sys.version_info < (3, 10): if sys.version_info < (3, 10):
# Match syntax is only available in Python >= 3.10 # Match syntax is only available in Python >= 3.10

View File

@ -20,7 +20,6 @@ classifiers = [
[project.optional-dependencies] [project.optional-dependencies]
dev = [ dev = [
"gevent>=22.10.2; implementation_name == 'cpython'",
"hypothesis>=3.55", "hypothesis>=3.55",
"pytest-asyncio>=0.21", "pytest-asyncio>=0.21",
"pytest-benchmark>=3.4", "pytest-benchmark>=3.4",

View File

@ -5,6 +5,11 @@ import sys
import pyo3_pytests.misc import pyo3_pytests.misc
import pytest import pytest
if sys.version_info >= (3, 13):
subinterpreters = pytest.importorskip("subinterpreters")
else:
subinterpreters = pytest.importorskip("_xxsubinterpreters")
def test_issue_219(): def test_issue_219():
# Should not deadlock # Should not deadlock
@ -31,23 +36,19 @@ def test_multiple_imports_same_interpreter_ok():
reason="PyPy and GraalPy do not support subinterpreters", reason="PyPy and GraalPy do not support subinterpreters",
) )
def test_import_in_subinterpreter_forbidden(): def test_import_in_subinterpreter_forbidden():
import _xxsubinterpreters
if sys.version_info < (3, 12): if sys.version_info < (3, 12):
expected_error = "PyO3 modules do not yet support subinterpreters, see https://github.com/PyO3/pyo3/issues/576" expected_error = "PyO3 modules do not yet support subinterpreters, see https://github.com/PyO3/pyo3/issues/576"
else: else:
expected_error = "module pyo3_pytests.pyo3_pytests does not support loading in subinterpreters" expected_error = "module pyo3_pytests.pyo3_pytests does not support loading in subinterpreters"
sub_interpreter = _xxsubinterpreters.create() sub_interpreter = subinterpreters.create()
with pytest.raises( with pytest.raises(
_xxsubinterpreters.RunFailedError, subinterpreters.RunFailedError,
match=expected_error, match=expected_error,
): ):
_xxsubinterpreters.run_string( subinterpreters.run_string(sub_interpreter, "import pyo3_pytests.pyo3_pytests")
sub_interpreter, "import pyo3_pytests.pyo3_pytests"
)
_xxsubinterpreters.destroy(sub_interpreter) subinterpreters.destroy(sub_interpreter)
def test_type_full_name_includes_module(): def test_type_full_name_includes_module():

View File

@ -47,6 +47,8 @@
//! assert n + 1 == value //! assert n + 1 == value
//! ``` //! ```
#[cfg(not(Py_LIMITED_API))]
use crate::ffi_ptr_ext::FfiPtrExt;
#[cfg(Py_LIMITED_API)] #[cfg(Py_LIMITED_API)]
use crate::types::{bytes::PyBytesMethods, PyBytes}; use crate::types::{bytes::PyBytesMethods, PyBytes};
use crate::{ use crate::{
@ -63,20 +65,47 @@ use num_bigint::Sign;
// for identical functionality between BigInt and BigUint // for identical functionality between BigInt and BigUint
macro_rules! bigint_conversion { 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")))] #[cfg_attr(docsrs, doc(cfg(feature = "num-bigint")))]
impl ToPyObject for $rust_ty { impl ToPyObject for $rust_ty {
#[cfg(not(Py_LIMITED_API))] #[cfg(not(Py_LIMITED_API))]
fn to_object(&self, py: Python<'_>) -> PyObject { fn to_object(&self, py: Python<'_>) -> PyObject {
let bytes = $to_bytes(self); let bytes = $to_bytes(self);
#[cfg(not(Py_3_13))]
{
unsafe { unsafe {
let obj = ffi::_PyLong_FromByteArray( ffi::_PyLong_FromByteArray(
bytes.as_ptr().cast(), bytes.as_ptr().cast(),
bytes.len(), bytes.len(),
1, 1,
$is_signed, $is_signed.into(),
); )
PyObject::from_owned_ptr(py, obj) .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 { fn to_object(&self, py: Python<'_>) -> PyObject {
let bytes = $to_bytes(self); let bytes = $to_bytes(self);
let bytes_obj = PyBytes::new_bound(py, &bytes); 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); let kwargs = crate::types::PyDict::new_bound(py);
kwargs.set_item(crate::intern!(py, "signed"), true).unwrap(); kwargs.set_item(crate::intern!(py, "signed"), true).unwrap();
Some(kwargs) Some(kwargs)
@ -107,8 +136,8 @@ macro_rules! bigint_conversion {
}; };
} }
bigint_conversion!(BigUint, 0, BigUint::to_bytes_le); bigint_conversion!(BigUint, false, BigUint::to_bytes_le);
bigint_conversion!(BigInt, 1, BigInt::to_signed_bytes_le); bigint_conversion!(BigInt, true, BigInt::to_signed_bytes_le);
#[cfg_attr(docsrs, doc(cfg(feature = "num-bigint")))] #[cfg_attr(docsrs, doc(cfg(feature = "num-bigint")))]
impl<'py> FromPyObject<'py> for 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 = unsafe { Py::from_owned_ptr_or_err(py, ffi::PyNumber_Index(ob.as_ptr()))? };
num_owned.bind(py) 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))] #[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::<true>(num)?;
let sign = if buffer.last().copied().map_or(false, |last| last >> 31 != 0) { 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 // 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) // 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)] #[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)?; let bytes = int_to_py_bytes(num, (n_bits + 8) / 8, true)?;
Ok(BigInt::from_signed_bytes_le(bytes.as_bytes())) 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 = unsafe { Py::from_owned_ptr_or_err(py, ffi::PyNumber_Index(ob.as_ptr()))? };
num_owned.bind(py) 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))] #[cfg(not(Py_LIMITED_API))]
{ {
let buffer = int_to_u32_vec(num, (n_bits + 31) / 32, false)?; let buffer = int_to_u32_vec::<false>(num)?;
Ok(BigUint::new(buffer)) Ok(BigUint::new(buffer))
} }
#[cfg(Py_LIMITED_API)] #[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)?; let bytes = int_to_py_bytes(num, (n_bits + 7) / 8, false)?;
Ok(BigUint::from_bytes_le(bytes.as_bytes())) Ok(BigUint::from_bytes_le(bytes.as_bytes()))
} }
} }
} }
#[cfg(not(Py_LIMITED_API))] #[cfg(not(any(Py_LIMITED_API, Py_3_13)))]
#[inline] #[inline]
fn int_to_u32_vec( fn int_to_u32_vec<const SIGNED: bool>(long: &Bound<'_, PyLong>) -> PyResult<Vec<u32>> {
long: &Bound<'_, PyLong>, let mut buffer = Vec::new();
n_digits: usize, let n_bits = int_n_bits(long)?;
is_signed: bool, if n_bits == 0 {
) -> PyResult<Vec<u32>> { return Ok(buffer);
let mut buffer = Vec::with_capacity(n_digits); }
let n_digits = if SIGNED {
(n_bits + 32) / 32
} else {
(n_bits + 31) / 32
};
buffer.reserve_exact(n_digits);
unsafe { unsafe {
crate::err::error_on_minusone( crate::err::error_on_minusone(
long.py(), long.py(),
@ -203,7 +238,7 @@ fn int_to_u32_vec(
buffer.as_mut_ptr() as *mut u8, buffer.as_mut_ptr() as *mut u8,
n_digits * 4, n_digits * 4,
1, 1,
is_signed.into(), SIGNED.into(),
), ),
)?; )?;
buffer.set_len(n_digits) buffer.set_len(n_digits)
@ -215,6 +250,44 @@ fn int_to_u32_vec(
Ok(buffer) Ok(buffer)
} }
#[cfg(all(not(Py_LIMITED_API), Py_3_13))]
#[inline]
fn int_to_u32_vec<const SIGNED: bool>(long: &Bound<'_, PyLong>) -> PyResult<Vec<u32>> {
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)] #[cfg(Py_LIMITED_API)]
fn int_to_py_bytes<'py>( fn int_to_py_bytes<'py>(
long: &Bound<'py, PyLong>, long: &Bound<'py, PyLong>,
@ -239,6 +312,7 @@ fn int_to_py_bytes<'py>(
} }
#[inline] #[inline]
#[cfg(any(not(Py_3_13), Py_LIMITED_API))]
fn int_n_bits(long: &Bound<'_, PyLong>) -> PyResult<usize> { fn int_n_bits(long: &Bound<'_, PyLong>) -> PyResult<usize> {
let py = long.py(); let py = long.py();
#[cfg(not(Py_LIMITED_API))] #[cfg(not(Py_LIMITED_API))]

View File

@ -1,3 +1,4 @@
use crate::ffi_ptr_ext::FfiPtrExt;
#[cfg(feature = "experimental-inspect")] #[cfg(feature = "experimental-inspect")]
use crate::inspect::types::TypeInfo; use crate::inspect::types::TypeInfo;
use crate::types::any::PyAnyMethods; 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()) }) err_if_invalid_value($obj.py(), $error_val, unsafe { $pylong_as(long.as_ptr()) })
} else { } else {
unsafe { unsafe {
let num = ffi::PyNumber_Index($obj.as_ptr()); let num = ffi::PyNumber_Index($obj.as_ptr()).assume_owned_or_err($obj.py())?;
if num.is_null() { err_if_invalid_value($obj.py(), $error_val, $pylong_as(num.as_ptr()))
Err(PyErr::fetch($obj.py()))
} else {
let result = err_if_invalid_value($obj.py(), $error_val, $pylong_as(num));
ffi::Py_DECREF(num);
result
}
} }
} }
}; };
@ -181,7 +176,7 @@ mod fast_128bit_int_conversion {
// for 128bit Integers // for 128bit Integers
macro_rules! int_convert_128 { macro_rules! int_convert_128 {
($rust_type: ty, $is_signed: expr) => { ($rust_type: ty, $is_signed: literal) => {
impl ToPyObject for $rust_type { impl ToPyObject for $rust_type {
#[inline] #[inline]
fn to_object(&self, py: Python<'_>) -> PyObject { fn to_object(&self, py: Python<'_>) -> PyObject {
@ -190,18 +185,44 @@ mod fast_128bit_int_conversion {
} }
impl IntoPy<PyObject> for $rust_type { impl IntoPy<PyObject> for $rust_type {
fn into_py(self, py: Python<'_>) -> PyObject { fn into_py(self, py: Python<'_>) -> PyObject {
// Always use little endian #[cfg(not(Py_3_13))]
{
let bytes = self.to_le_bytes(); let bytes = self.to_le_bytes();
unsafe { unsafe {
PyObject::from_owned_ptr(
py,
ffi::_PyLong_FromByteArray( ffi::_PyLong_FromByteArray(
bytes.as_ptr() as *const std::os::raw::c_uchar, bytes.as_ptr().cast(),
bytes.len(), bytes.len(),
1, 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 { impl FromPyObject<'_> for $rust_type {
fn extract_bound(ob: &Bound<'_, PyAny>) -> PyResult<$rust_type> { fn extract_bound(ob: &Bound<'_, PyAny>) -> PyResult<$rust_type> {
let num = unsafe { let num =
PyObject::from_owned_ptr_or_err(ob.py(), ffi::PyNumber_Index(ob.as_ptr()))? unsafe { ffi::PyNumber_Index(ob.as_ptr()).assume_owned_or_err(ob.py())? };
}; let mut buffer = [0u8; std::mem::size_of::<$rust_type>()];
let mut buffer = [0; std::mem::size_of::<$rust_type>()]; #[cfg(not(Py_3_13))]
crate::err::error_on_minusone(ob.py(), unsafe { crate::err::error_on_minusone(ob.py(), unsafe {
ffi::_PyLong_AsByteArray( ffi::_PyLong_AsByteArray(
num.as_ptr() as *mut ffi::PyLongObject, num.as_ptr() as *mut ffi::PyLongObject,
buffer.as_mut_ptr(), buffer.as_mut_ptr(),
buffer.len(), buffer.len(),
1, 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")] #[cfg(feature = "experimental-inspect")]
@ -237,8 +284,8 @@ mod fast_128bit_int_conversion {
}; };
} }
int_convert_128!(i128, 1); int_convert_128!(i128, true);
int_convert_128!(u128, 0); int_convert_128!(u128, false);
} }
// For ABI3 we implement the conversion manually. // For ABI3 we implement the conversion manually.