diff --git a/570.patch b/570.patch new file mode 100644 index 0000000..26c290d --- /dev/null +++ b/570.patch @@ -0,0 +1,3802 @@ +From 180f5886407e7c1dda34afa6fc7298b34d2925da Mon Sep 17 00:00:00 2001 +From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> +Date: Fri, 23 May 2025 15:02:33 +0200 +Subject: [PATCH 1/6] Update pyo3 to 0.25.0 + +--- + Cargo.lock | 4 +- + include/pyo3/pyo3-build-config/Cargo.toml | 6 +- + include/pyo3/pyo3-build-config/src/errors.rs | 2 +- + include/pyo3/pyo3-build-config/src/impl_.rs | 41 +- + include/pyo3/pyo3-build-config/src/lib.rs | 63 +-- + include/pyo3/pyo3-ffi/ACKNOWLEDGEMENTS | 2 +- + include/pyo3/pyo3-ffi/Cargo.toml | 10 +- + include/pyo3/pyo3-ffi/build.rs | 74 ++- + include/pyo3/pyo3-ffi/src/abstract_.rs | 41 +- + include/pyo3/pyo3-ffi/src/bytearrayobject.rs | 2 +- + include/pyo3/pyo3-ffi/src/code.rs | 2 +- + include/pyo3/pyo3-ffi/src/compat/mod.rs | 2 + + include/pyo3/pyo3-ffi/src/compat/py_3_10.rs | 26 ++ + include/pyo3/pyo3-ffi/src/compat/py_3_13.rs | 15 + + include/pyo3/pyo3-ffi/src/compat/py_3_14.rs | 26 ++ + .../pyo3/pyo3-ffi/src/cpython/abstract_.rs | 32 +- + .../pyo3/pyo3-ffi/src/cpython/bytesobject.rs | 2 +- + include/pyo3/pyo3-ffi/src/cpython/code.rs | 232 ++-------- + include/pyo3/pyo3-ffi/src/cpython/compile.rs | 63 ++- + .../pyo3-ffi/src/cpython/critical_section.rs | 14 +- + .../pyo3/pyo3-ffi/src/cpython/descrobject.rs | 5 +- + .../pyo3/pyo3-ffi/src/cpython/dictobject.rs | 16 +- + .../pyo3/pyo3-ffi/src/cpython/frameobject.rs | 5 +- + .../pyo3/pyo3-ffi/src/cpython/funcobject.rs | 2 + + .../pyo3/pyo3-ffi/src/cpython/genobject.rs | 14 +- + include/pyo3/pyo3-ffi/src/cpython/import.rs | 10 +- + .../pyo3/pyo3-ffi/src/cpython/initconfig.rs | 10 + + include/pyo3/pyo3-ffi/src/cpython/lock.rs | 9 - + .../pyo3/pyo3-ffi/src/cpython/longobject.rs | 12 - + include/pyo3/pyo3-ffi/src/cpython/mod.rs | 5 +- + include/pyo3/pyo3-ffi/src/cpython/object.rs | 8 +- + include/pyo3/pyo3-ffi/src/cpython/objimpl.rs | 2 +- + include/pyo3/pyo3-ffi/src/cpython/pyerrors.rs | 2 + + include/pyo3/pyo3-ffi/src/cpython/pyframe.rs | 5 +- + include/pyo3/pyo3-ffi/src/cpython/pyhash.rs | 38 ++ + include/pyo3/pyo3-ffi/src/cpython/pystate.rs | 16 +- + .../pyo3/pyo3-ffi/src/cpython/tupleobject.rs | 4 +- + .../pyo3-ffi/src/cpython/unicodeobject.rs | 117 ++++- + .../pyo3-ffi/src/cpython/weakrefobject.rs | 1 + + include/pyo3/pyo3-ffi/src/datetime.rs | 10 +- + include/pyo3/pyo3-ffi/src/dictobject.rs | 2 +- + include/pyo3/pyo3-ffi/src/floatobject.rs | 2 +- + .../pyo3/pyo3-ffi/src/genericaliasobject.rs | 12 + + include/pyo3/pyo3-ffi/src/lib.rs | 23 +- + include/pyo3/pyo3-ffi/src/longobject.rs | 2 +- + include/pyo3/pyo3-ffi/src/memoryobject.rs | 5 +- + include/pyo3/pyo3-ffi/src/methodobject.rs | 8 +- + include/pyo3/pyo3-ffi/src/modsupport.rs | 7 +- + include/pyo3/pyo3-ffi/src/object.rs | 422 ++++-------------- + include/pyo3/pyo3-ffi/src/pyarena.rs | 2 +- + include/pyo3/pyo3-ffi/src/pybuffer.rs | 6 +- + include/pyo3/pyo3-ffi/src/pyerrors.rs | 1 + + include/pyo3/pyo3-ffi/src/pyframe.rs | 2 +- + include/pyo3/pyo3-ffi/src/pyhash.rs | 30 +- + include/pyo3/pyo3-ffi/src/pyport.rs | 9 +- + include/pyo3/pyo3-ffi/src/pystate.rs | 78 +++- + include/pyo3/pyo3-ffi/src/pythonrun.rs | 6 +- + include/pyo3/pyo3-ffi/src/refcount.rs | 369 +++++++++++++++ + include/pyo3/pyo3-ffi/src/setobject.rs | 6 +- + include/pyo3/pyo3-ffi/src/weakrefobject.rs | 8 +- + 60 files changed, 1065 insertions(+), 885 deletions(-) + create mode 100644 include/pyo3/pyo3-ffi/src/compat/py_3_14.rs + create mode 100644 include/pyo3/pyo3-ffi/src/cpython/pyhash.rs + create mode 100644 include/pyo3/pyo3-ffi/src/genericaliasobject.rs + create mode 100644 include/pyo3/pyo3-ffi/src/refcount.rs + +diff --git a/Cargo.lock b/Cargo.lock +index 03fdf346..c4b1eb1c 100644 +--- a/Cargo.lock ++++ b/Cargo.lock +@@ -190,7 +190,7 @@ dependencies = [ + + [[package]] + name = "pyo3-build-config" +-version = "0.23.3" ++version = "0.25.0" + dependencies = [ + "once_cell", + "target-lexicon", +@@ -198,7 +198,7 @@ dependencies = [ + + [[package]] + name = "pyo3-ffi" +-version = "0.23.3" ++version = "0.25.0" + dependencies = [ + "libc", + "pyo3-build-config", +diff --git a/include/pyo3/pyo3-build-config/Cargo.toml b/include/pyo3/pyo3-build-config/Cargo.toml +index 0facef81..54d5cb49 100644 +--- a/include/pyo3/pyo3-build-config/Cargo.toml ++++ b/include/pyo3/pyo3-build-config/Cargo.toml +@@ -1,6 +1,6 @@ + [package] + name = "pyo3-build-config" +-version = "0.23.3" ++version = "0.25.0" + description = "Build configuration for the PyO3 ecosystem" + authors = ["PyO3 Project and Contributors "] + keywords = ["pyo3", "python", "cpython", "ffi"] +@@ -36,7 +36,9 @@ abi3-py38 = ["abi3-py39"] + abi3-py39 = ["abi3-py310"] + abi3-py310 = ["abi3-py311"] + abi3-py311 = ["abi3-py312"] +-abi3-py312 = ["abi3"] ++abi3-py312 = ["abi3-py313"] ++abi3-py313 = ["abi3-py314"] ++abi3-py314 = ["abi3"] + + [package.metadata.docs.rs] + features = ["resolve-config"] +diff --git a/include/pyo3/pyo3-build-config/src/errors.rs b/include/pyo3/pyo3-build-config/src/errors.rs +index 87c59a99..b11d02fd 100644 +--- a/include/pyo3/pyo3-build-config/src/errors.rs ++++ b/include/pyo3/pyo3-build-config/src/errors.rs +@@ -68,7 +68,7 @@ impl std::fmt::Display for ErrorReport<'_> { + writeln!(f, "\ncaused by:")?; + let mut index = 0; + while let Some(some_source) = source { +- writeln!(f, " - {}: {}", index, some_source)?; ++ writeln!(f, " - {index}: {some_source}")?; + source = some_source.source(); + index += 1; + } +diff --git a/include/pyo3/pyo3-build-config/src/impl_.rs b/include/pyo3/pyo3-build-config/src/impl_.rs +index 0f90d573..9485b8a0 100644 +--- a/include/pyo3/pyo3-build-config/src/impl_.rs ++++ b/include/pyo3/pyo3-build-config/src/impl_.rs +@@ -40,7 +40,7 @@ const MINIMUM_SUPPORTED_VERSION_GRAALPY: PythonVersion = PythonVersion { + }; + + /// Maximum Python version that can be used as minimum required Python version with abi3. +-pub(crate) const ABI3_MAX_MINOR: u8 = 12; ++pub(crate) const ABI3_MAX_MINOR: u8 = 13; + + #[cfg(test)] + thread_local! { +@@ -58,7 +58,7 @@ pub fn cargo_env_var(var: &str) -> Option { + /// the variable changes. + pub fn env_var(var: &str) -> Option { + if cfg!(feature = "resolve-config") { +- println!("cargo:rerun-if-env-changed={}", var); ++ println!("cargo:rerun-if-env-changed={var}"); + } + #[cfg(test)] + { +@@ -180,9 +180,8 @@ impl InterpreterConfig { + let mut out = vec![]; + + for i in MINIMUM_SUPPORTED_VERSION.minor..=self.version.minor { +- out.push(format!("cargo:rustc-cfg=Py_3_{}", i)); ++ out.push(format!("cargo:rustc-cfg=Py_3_{i}")); + } +- println!("cargo::rustc-check-cfg=cfg(Py_3_14)"); + + match self.implementation { + PythonImplementation::CPython => {} +@@ -200,7 +199,7 @@ impl InterpreterConfig { + BuildFlag::Py_GIL_DISABLED => { + out.push("cargo:rustc-cfg=Py_GIL_DISABLED".to_owned()) + } +- flag => out.push(format!("cargo:rustc-cfg=py_sys_config=\"{}\"", flag)), ++ flag => out.push(format!("cargo:rustc-cfg=py_sys_config=\"{flag}\"")), + } + } + +@@ -339,7 +338,7 @@ print("gil_disabled", get_config_var("Py_GIL_DISABLED")) + + let lib_dir = if cfg!(windows) { + map.get("base_prefix") +- .map(|base_prefix| format!("{}\\libs", base_prefix)) ++ .map(|base_prefix| format!("{base_prefix}\\libs")) + } else { + map.get("libdir").cloned() + }; +@@ -667,7 +666,7 @@ print("gil_disabled", get_config_var("Py_GIL_DISABLED")) + write_option_line!(python_framework_prefix)?; + write_line!(suppress_build_script_link_lines)?; + for line in &self.extra_build_script_lines { +- writeln!(writer, "extra_build_script_line={}", line) ++ writeln!(writer, "extra_build_script_line={line}") + .context("failed to write extra_build_script_line")?; + } + Ok(()) +@@ -854,7 +853,7 @@ fn is_abi3() -> bool { + /// Must be called from a PyO3 crate build script. + pub fn get_abi3_version() -> Option { + let minor_version = (MINIMUM_SUPPORTED_VERSION.minor..=ABI3_MAX_MINOR) +- .find(|i| cargo_env_var(&format!("CARGO_FEATURE_ABI3_PY3{}", i)).is_some()); ++ .find(|i| cargo_env_var(&format!("CARGO_FEATURE_ABI3_PY3{i}")).is_some()); + minor_version.map(|minor| PythonVersion { major: 3, minor }) + } + +@@ -1122,8 +1121,8 @@ pub enum BuildFlag { + impl Display for BuildFlag { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { +- BuildFlag::Other(flag) => write!(f, "{}", flag), +- _ => write!(f, "{:?}", self), ++ BuildFlag::Other(flag) => write!(f, "{flag}"), ++ _ => write!(f, "{self:?}"), + } + } + } +@@ -1147,7 +1146,7 @@ impl FromStr for BuildFlag { + /// PyO3 will pick these up and pass to rustc via `--cfg=py_sys_config={varname}`; + /// this allows using them conditional cfg attributes in the .rs files, so + /// +-/// ```rust ++/// ```rust,no_run + /// #[cfg(py_sys_config="{varname}")] + /// # struct Foo; + /// ``` +@@ -1203,7 +1202,7 @@ impl BuildFlags { + + for k in &BuildFlags::ALL { + use std::fmt::Write; +- writeln!(&mut script, "print(config.get('{}', '0'))", k).unwrap(); ++ writeln!(&mut script, "print(config.get('{k}', '0'))").unwrap(); + } + + let stdout = run_python_script(interpreter.as_ref(), &script)?; +@@ -1241,7 +1240,7 @@ impl Display for BuildFlags { + } else { + write!(f, ",")?; + } +- write!(f, "{}", flag)?; ++ write!(f, "{flag}")?; + } + Ok(()) + } +@@ -1307,6 +1306,10 @@ pub fn parse_sysconfigdata(sysconfigdata_path: impl AsRef) -> Result Result + + fn is_pypy_lib_dir(path: &str, v: &Option) -> bool { + let pypy_version_pat = if let Some(v) = v { +- format!("pypy{}", v) ++ format!("pypy{v}") + } else { + "pypy3.".into() + }; +@@ -1433,7 +1436,7 @@ fn is_pypy_lib_dir(path: &str, v: &Option) -> bool { + + fn is_graalpy_lib_dir(path: &str, v: &Option) -> bool { + let graalpy_version_pat = if let Some(v) = v { +- format!("graalpy{}", v) ++ format!("graalpy{v}") + } else { + "graalpy2".into() + }; +@@ -1442,7 +1445,7 @@ fn is_graalpy_lib_dir(path: &str, v: &Option) -> bool { + + fn is_cpython_lib_dir(path: &str, v: &Option) -> bool { + let cpython_version_pat = if let Some(v) = v { +- format!("python{}", v) ++ format!("python{v}") + } else { + "python3.".into() + }; +@@ -1756,7 +1759,7 @@ fn default_lib_name_unix( + ) -> Result { + match implementation { + PythonImplementation::CPython => match ld_version { +- Some(ld_version) => Ok(format!("python{}", ld_version)), ++ Some(ld_version) => Ok(format!("python{ld_version}")), + None => { + if version > PythonVersion::PY37 { + // PEP 3149 ABI version tags are finally gone +@@ -1773,7 +1776,7 @@ fn default_lib_name_unix( + } + }, + PythonImplementation::PyPy => match ld_version { +- Some(ld_version) => Ok(format!("pypy{}-c", ld_version)), ++ Some(ld_version) => Ok(format!("pypy{ld_version}-c")), + None => Ok(format!("pypy{}.{}-c", version.major, version.minor)), + }, + +@@ -2002,7 +2005,7 @@ fn unescape(escaped: &str) -> Vec { + } + } + +- bytes.push(unhex(chunk[0]) << 4 | unhex(chunk[1])); ++ bytes.push((unhex(chunk[0]) << 4) | unhex(chunk[1])); + } + + bytes +diff --git a/include/pyo3/pyo3-build-config/src/lib.rs b/include/pyo3/pyo3-build-config/src/lib.rs +index 9070f6d7..f47c16f4 100644 +--- a/include/pyo3/pyo3-build-config/src/lib.rs ++++ b/include/pyo3/pyo3-build-config/src/lib.rs +@@ -46,7 +46,7 @@ use target_lexicon::OperatingSystem; + pub fn use_pyo3_cfgs() { + print_expected_cfgs(); + for cargo_command in get().build_script_outputs() { +- println!("{}", cargo_command) ++ println!("{cargo_command}") + } + } + +@@ -102,12 +102,7 @@ fn _add_python_framework_link_args( + ) { + if matches!(triple.operating_system, OperatingSystem::Darwin(_)) && link_libpython { + if let Some(framework_prefix) = interpreter_config.python_framework_prefix.as_ref() { +- writeln!( +- writer, +- "cargo:rustc-link-arg=-Wl,-rpath,{}", +- framework_prefix +- ) +- .unwrap(); ++ writeln!(writer, "cargo:rustc-link-arg=-Wl,-rpath,{framework_prefix}").unwrap(); + } + } + } +@@ -168,40 +163,36 @@ fn resolve_cross_compile_config_path() -> Option { + }) + } + ++/// Helper to print a feature cfg with a minimum rust version required. ++fn print_feature_cfg(minor_version_required: u32, cfg: &str) { ++ let minor_version = rustc_minor_version().unwrap_or(0); ++ ++ if minor_version >= minor_version_required { ++ println!("cargo:rustc-cfg={cfg}"); ++ } ++ ++ // rustc 1.80.0 stabilized `rustc-check-cfg` feature, don't emit before ++ if minor_version >= 80 { ++ println!("cargo:rustc-check-cfg=cfg({cfg})"); ++ } ++} ++ + /// Use certain features if we detect the compiler being used supports them. + /// + /// Features may be removed or added as MSRV gets bumped or new features become available, + /// so this function is unstable. + #[doc(hidden)] + pub fn print_feature_cfgs() { +- let rustc_minor_version = rustc_minor_version().unwrap_or(0); +- +- if rustc_minor_version >= 70 { +- println!("cargo:rustc-cfg=rustc_has_once_lock"); +- } +- +- // invalid_from_utf8 lint was added in Rust 1.74 +- if rustc_minor_version >= 74 { +- println!("cargo:rustc-cfg=invalid_from_utf8_lint"); +- } +- +- if rustc_minor_version >= 79 { +- println!("cargo:rustc-cfg=c_str_lit"); +- } +- ++ 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 +- if rustc_minor_version >= 79 { +- println!("cargo:rustc-cfg=diagnostic_namespace"); +- } +- +- if rustc_minor_version >= 83 { +- println!("cargo:rustc-cfg=io_error_more"); +- } +- +- if rustc_minor_version >= 85 { +- println!("cargo:rustc-cfg=fn_ptr_eq"); +- } ++ print_feature_cfg(79, "diagnostic_namespace"); ++ print_feature_cfg(83, "io_error_more"); ++ print_feature_cfg(85, "fn_ptr_eq"); + } + + /// Registers `pyo3`s config names as reachable cfg expressions +@@ -220,14 +211,8 @@ pub fn print_expected_cfgs() { + println!("cargo:rustc-check-cfg=cfg(PyPy)"); + println!("cargo:rustc-check-cfg=cfg(GraalPy)"); + println!("cargo:rustc-check-cfg=cfg(py_sys_config, values(\"Py_DEBUG\", \"Py_REF_DEBUG\", \"Py_TRACE_REFS\", \"COUNT_ALLOCS\"))"); +- println!("cargo:rustc-check-cfg=cfg(invalid_from_utf8_lint)"); + println!("cargo:rustc-check-cfg=cfg(pyo3_disable_reference_pool)"); + println!("cargo:rustc-check-cfg=cfg(pyo3_leak_on_drop_without_reference_pool)"); +- println!("cargo:rustc-check-cfg=cfg(diagnostic_namespace)"); +- println!("cargo:rustc-check-cfg=cfg(c_str_lit)"); +- println!("cargo:rustc-check-cfg=cfg(rustc_has_once_lock)"); +- println!("cargo:rustc-check-cfg=cfg(io_error_more)"); +- println!("cargo:rustc-check-cfg=cfg(fn_ptr_eq)"); + + // allow `Py_3_*` cfgs from the minimum supported version up to the + // maximum minor version (+1 for development for the next) +diff --git a/include/pyo3/pyo3-ffi/ACKNOWLEDGEMENTS b/include/pyo3/pyo3-ffi/ACKNOWLEDGEMENTS +index 4502d777..8b20727d 100644 +--- a/include/pyo3/pyo3-ffi/ACKNOWLEDGEMENTS ++++ b/include/pyo3/pyo3-ffi/ACKNOWLEDGEMENTS +@@ -3,4 +3,4 @@ for binary compatibility, with additional metadata to support PyPy. + + For original implementations please see: + - https://github.com/python/cpython +- - https://foss.heptapod.net/pypy/pypy ++ - https://github.com/pypy/pypy +diff --git a/include/pyo3/pyo3-ffi/Cargo.toml b/include/pyo3/pyo3-ffi/Cargo.toml +index 14a4f958..73ea84ab 100644 +--- a/include/pyo3/pyo3-ffi/Cargo.toml ++++ b/include/pyo3/pyo3-ffi/Cargo.toml +@@ -1,6 +1,6 @@ + [package] + name = "pyo3-ffi" +-version = "0.23.3" ++version = "0.25.0" + description = "Python-API bindings for the PyO3 ecosystem" + authors = ["PyO3 Project and Contributors "] + keywords = ["pyo3", "python", "cpython", "ffi"] +@@ -12,7 +12,7 @@ edition = "2021" + links = "python" + + [dependencies] +-libc = "0.2" ++libc = "0.2.62" + + [features] + +@@ -32,7 +32,9 @@ abi3-py38 = ["abi3-py39", "pyo3-build-config/abi3-py38"] + abi3-py39 = ["abi3-py310", "pyo3-build-config/abi3-py39"] + abi3-py310 = ["abi3-py311", "pyo3-build-config/abi3-py310"] + abi3-py311 = ["abi3-py312", "pyo3-build-config/abi3-py311"] +-abi3-py312 = ["abi3", "pyo3-build-config/abi3-py312"] ++abi3-py312 = ["abi3-py313", "pyo3-build-config/abi3-py312"] ++abi3-py313 = ["abi3-py314", "pyo3-build-config/abi3-py313"] ++abi3-py314 = ["abi3", "pyo3-build-config/abi3-py314"] + + # Automatically generates `python3.dll` import libraries for Windows targets. + generate-import-lib = ["pyo3-build-config/python3-dll-a"] +@@ -41,4 +43,4 @@ generate-import-lib = ["pyo3-build-config/python3-dll-a"] + paste = "1" + + [build-dependencies] +-pyo3-build-config = { path = "../pyo3-build-config", features = ["resolve-config"] } ++pyo3-build-config = { path = "../pyo3-build-config", version = "=0.25.0", features = ["resolve-config"] } +diff --git a/include/pyo3/pyo3-ffi/build.rs b/include/pyo3/pyo3-ffi/build.rs +index b0f1c28d..6776cd80 100644 +--- a/include/pyo3/pyo3-ffi/build.rs ++++ b/include/pyo3/pyo3-ffi/build.rs +@@ -4,9 +4,8 @@ use pyo3_build_config::{ + cargo_env_var, env_var, errors::Result, is_linking_libpython, resolve_interpreter_config, + InterpreterConfig, PythonVersion, + }, +- warn, BuildFlag, PythonImplementation, ++ warn, PythonImplementation, + }; +-use std::ops::Not; + + /// Minimum Python version PyO3 supports. + struct SupportedVersions { +@@ -18,15 +17,15 @@ const SUPPORTED_VERSIONS_CPYTHON: SupportedVersions = SupportedVersions { + min: PythonVersion { major: 3, minor: 7 }, + max: PythonVersion { + major: 3, +- minor: 13, ++ minor: 14, + }, + }; + + const SUPPORTED_VERSIONS_PYPY: SupportedVersions = SupportedVersions { +- min: PythonVersion { major: 3, minor: 7 }, ++ min: PythonVersion { major: 3, minor: 9 }, + max: PythonVersion { + major: 3, +- minor: 10, ++ minor: 11, + }, + }; + +@@ -57,15 +56,22 @@ fn ensure_python_version(interpreter_config: &InterpreterConfig) -> Result<()> { + interpreter_config.version, + versions.min, + ); +- ensure!( +- interpreter_config.version <= versions.max || env_var("PYO3_USE_ABI3_FORWARD_COMPATIBILITY").map_or(false, |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", +- interpreter_config.version, +- versions.max, +- std::env::var("CARGO_PKG_VERSION").unwrap(), +- ); ++ if interpreter_config.version > versions.max { ++ ensure!(!interpreter_config.is_free_threaded(), ++ "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: 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"), ++ "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", ++ interpreter_config.version, ++ versions.max, ++ std::env::var("CARGO_PKG_VERSION").unwrap(), ++ ); ++ } + } + PythonImplementation::PyPy => { + let versions = SUPPORTED_VERSIONS_PYPY; +@@ -107,10 +113,16 @@ fn ensure_python_version(interpreter_config: &InterpreterConfig) -> Result<()> { + + if interpreter_config.abi3 { + match interpreter_config.implementation { +- PythonImplementation::CPython => {} ++ PythonImplementation::CPython => { ++ if interpreter_config.is_free_threaded() { ++ warn!( ++ "The free-threaded build of CPython does not yet support abi3 so the build artifacts will be version-specific." ++ ) ++ } ++ } + PythonImplementation::PyPy => warn!( + "PyPy does not yet support abi3 so the build artifacts will be version-specific. \ +- See https://foss.heptapod.net/pypy/pypy/-/issues/3397 for more information." ++ See https://github.com/pypy/pypy/issues/3397 for more information." + ), + PythonImplementation::GraalPy => warn!( + "GraalPy does not support abi3 so the build artifacts will be version-specific." +@@ -121,29 +133,6 @@ fn ensure_python_version(interpreter_config: &InterpreterConfig) -> Result<()> { + Ok(()) + } + +-fn ensure_gil_enabled(interpreter_config: &InterpreterConfig) -> Result<()> { +- let gil_enabled = interpreter_config +- .build_flags +- .0 +- .contains(&BuildFlag::Py_GIL_DISABLED) +- .not(); +- ensure!( +- gil_enabled || std::env::var("UNSAFE_PYO3_BUILD_FREE_THREADED").map_or(false, |os_str| os_str == "1"), +- "the Python interpreter was built with the GIL disabled, which is not yet supported by PyO3\n\ +- = help: see https://github.com/PyO3/pyo3/issues/4265 for more information\n\ +- = help: please check if an updated version of PyO3 is available. Current version: {}\n\ +- = help: set UNSAFE_PYO3_BUILD_FREE_THREADED=1 to suppress this check and build anyway for free-threaded Python", +- std::env::var("CARGO_PKG_VERSION").unwrap() +- ); +- if !gil_enabled && interpreter_config.abi3 { +- warn!( +- "The free-threaded build of CPython does not yet support abi3 so the build artifacts will be version-specific." +- ) +- } +- +- Ok(()) +-} +- + fn ensure_target_pointer_width(interpreter_config: &InterpreterConfig) -> Result<()> { + if let Some(pointer_width) = interpreter_config.pointer_width { + // Try to check whether the target architecture matches the python library +@@ -187,7 +176,7 @@ fn emit_link_config(interpreter_config: &InterpreterConfig) -> Result<()> { + ); + + if let Some(lib_dir) = &interpreter_config.lib_dir { +- println!("cargo:rustc-link-search=native={}", lib_dir); ++ println!("cargo:rustc-link-search=native={lib_dir}"); + } + + Ok(()) +@@ -209,7 +198,6 @@ fn configure_pyo3() -> Result<()> { + + ensure_python_version(&interpreter_config)?; + ensure_target_pointer_width(&interpreter_config)?; +- ensure_gil_enabled(&interpreter_config)?; + + // Serialize the whole interpreter config into DEP_PYTHON_PYO3_CONFIG env var. + interpreter_config.to_cargo_dep_env()?; +@@ -219,12 +207,12 @@ fn configure_pyo3() -> Result<()> { + } + + for cfg in interpreter_config.build_script_outputs() { +- println!("{}", cfg) ++ println!("{cfg}") + } + + // Extra lines come last, to support last write wins. + for line in &interpreter_config.extra_build_script_lines { +- println!("{}", line); ++ println!("{line}"); + } + + // Emit cfgs like `invalid_from_utf8_lint` +diff --git a/include/pyo3/pyo3-ffi/src/abstract_.rs b/include/pyo3/pyo3-ffi/src/abstract_.rs +index ce6c9b94..84fb98a1 100644 +--- a/include/pyo3/pyo3-ffi/src/abstract_.rs ++++ b/include/pyo3/pyo3-ffi/src/abstract_.rs +@@ -1,15 +1,23 @@ + use crate::object::*; + use crate::pyport::Py_ssize_t; ++#[cfg(any(Py_3_12, all(Py_3_8, not(Py_LIMITED_API))))] ++use libc::size_t; + use std::os::raw::{c_char, c_int}; + + #[inline] +-#[cfg(all(not(Py_3_13), not(PyPy)))] // CPython exposed as a function in 3.13, in object.h ++#[cfg(all( ++ not(Py_3_13), // CPython exposed as a function in 3.13, in object.h ++ not(all(PyPy, not(Py_3_11))) // PyPy exposed as a function until PyPy 3.10, macro in 3.11+ ++))] + pub unsafe fn PyObject_DelAttrString(o: *mut PyObject, attr_name: *const c_char) -> c_int { + PyObject_SetAttrString(o, attr_name, std::ptr::null_mut()) + } + + #[inline] +-#[cfg(all(not(Py_3_13), not(PyPy)))] // CPython exposed as a function in 3.13, in object.h ++#[cfg(all( ++ not(Py_3_13), // CPython exposed as a function in 3.13, in object.h ++ not(all(PyPy, not(Py_3_11))) // PyPy exposed as a function until PyPy 3.10, macro in 3.11+ ++))] + pub unsafe fn PyObject_DelAttr(o: *mut PyObject, attr_name: *mut PyObject) -> c_int { + PyObject_SetAttr(o, attr_name, std::ptr::null_mut()) + } +@@ -17,6 +25,7 @@ pub unsafe fn PyObject_DelAttr(o: *mut PyObject, attr_name: *mut PyObject) -> c_ + extern "C" { + #[cfg(all( + not(PyPy), ++ not(GraalPy), + any(Py_3_10, all(not(Py_LIMITED_API), Py_3_9)) // Added to python in 3.9 but to limited API in 3.10 + ))] + #[cfg_attr(PyPy, link_name = "PyPyObject_CallNoArgs")] +@@ -70,6 +79,28 @@ extern "C" { + method: *mut PyObject, + ... + ) -> *mut PyObject; ++} ++#[cfg(any(Py_3_12, all(Py_3_8, not(Py_LIMITED_API))))] ++pub const PY_VECTORCALL_ARGUMENTS_OFFSET: size_t = ++ 1 << (8 * std::mem::size_of::() as size_t - 1); ++ ++extern "C" { ++ #[cfg_attr(PyPy, link_name = "PyPyObject_Vectorcall")] ++ #[cfg(any(Py_3_12, all(Py_3_11, not(Py_LIMITED_API))))] ++ pub fn PyObject_Vectorcall( ++ callable: *mut PyObject, ++ args: *const *mut PyObject, ++ nargsf: size_t, ++ kwnames: *mut PyObject, ++ ) -> *mut PyObject; ++ ++ #[cfg(any(Py_3_12, all(Py_3_9, not(any(Py_LIMITED_API, PyPy, GraalPy)))))] ++ pub fn PyObject_VectorcallMethod( ++ name: *mut PyObject, ++ args: *const *mut PyObject, ++ nargsf: size_t, ++ kwnames: *mut PyObject, ++ ) -> *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyObject_Type")] + pub fn PyObject_Type(o: *mut PyObject) -> *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyObject_Size")] +@@ -119,7 +150,11 @@ extern "C" { + pub fn PyIter_Next(arg1: *mut PyObject) -> *mut PyObject; + #[cfg(all(not(PyPy), Py_3_10))] + #[cfg_attr(PyPy, link_name = "PyPyIter_Send")] +- pub fn PyIter_Send(iter: *mut PyObject, arg: *mut PyObject, presult: *mut *mut PyObject); ++ pub fn PyIter_Send( ++ iter: *mut PyObject, ++ arg: *mut PyObject, ++ presult: *mut *mut PyObject, ++ ) -> PySendResult; + + #[cfg_attr(PyPy, link_name = "PyPyNumber_Check")] + pub fn PyNumber_Check(o: *mut PyObject) -> c_int; +diff --git a/include/pyo3/pyo3-ffi/src/bytearrayobject.rs b/include/pyo3/pyo3-ffi/src/bytearrayobject.rs +index 24a97bcc..d27dfa8b 100644 +--- a/include/pyo3/pyo3-ffi/src/bytearrayobject.rs ++++ b/include/pyo3/pyo3-ffi/src/bytearrayobject.rs +@@ -17,7 +17,7 @@ pub struct PyByteArrayObject { + } + + #[cfg(any(PyPy, GraalPy, Py_LIMITED_API))] +-opaque_struct!(PyByteArrayObject); ++opaque_struct!(pub PyByteArrayObject); + + #[cfg_attr(windows, link(name = "pythonXY"))] + extern "C" { +diff --git a/include/pyo3/pyo3-ffi/src/code.rs b/include/pyo3/pyo3-ffi/src/code.rs +index d28f68cd..296b17f6 100644 +--- a/include/pyo3/pyo3-ffi/src/code.rs ++++ b/include/pyo3/pyo3-ffi/src/code.rs +@@ -1,4 +1,4 @@ + // This header doesn't exist in CPython, but Include/cpython/code.h does. We add + // this here so that PyCodeObject has a definition under the limited API. + +-opaque_struct!(PyCodeObject); ++opaque_struct!(pub PyCodeObject); +diff --git a/include/pyo3/pyo3-ffi/src/compat/mod.rs b/include/pyo3/pyo3-ffi/src/compat/mod.rs +index 11f29128..044ea467 100644 +--- a/include/pyo3/pyo3-ffi/src/compat/mod.rs ++++ b/include/pyo3/pyo3-ffi/src/compat/mod.rs +@@ -52,8 +52,10 @@ macro_rules! compat_function { + + mod py_3_10; + mod py_3_13; ++mod py_3_14; + mod py_3_9; + + pub use self::py_3_10::*; + pub use self::py_3_13::*; ++pub use self::py_3_14::*; + pub use self::py_3_9::*; +diff --git a/include/pyo3/pyo3-ffi/src/compat/py_3_10.rs b/include/pyo3/pyo3-ffi/src/compat/py_3_10.rs +index c6e8c2cb..d962fb3b 100644 +--- a/include/pyo3/pyo3-ffi/src/compat/py_3_10.rs ++++ b/include/pyo3/pyo3-ffi/src/compat/py_3_10.rs +@@ -17,3 +17,29 @@ compat_function!( + obj + } + ); ++ ++compat_function!( ++ originally_defined_for(Py_3_10); ++ ++ #[inline] ++ pub unsafe fn PyModule_AddObjectRef( ++ module: *mut crate::PyObject, ++ name: *const std::os::raw::c_char, ++ value: *mut crate::PyObject, ++ ) -> std::os::raw::c_int { ++ if value.is_null() && crate::PyErr_Occurred().is_null() { ++ crate::PyErr_SetString( ++ crate::PyExc_SystemError, ++ c_str!("PyModule_AddObjectRef() must be called with an exception raised if value is NULL").as_ptr(), ++ ); ++ return -1; ++ } ++ ++ crate::Py_XINCREF(value); ++ let result = crate::PyModule_AddObject(module, name, value); ++ if result < 0 { ++ crate::Py_XDECREF(value); ++ } ++ result ++ } ++); +diff --git a/include/pyo3/pyo3-ffi/src/compat/py_3_13.rs b/include/pyo3/pyo3-ffi/src/compat/py_3_13.rs +index 59289cb7..96c90e7e 100644 +--- a/include/pyo3/pyo3-ffi/src/compat/py_3_13.rs ++++ b/include/pyo3/pyo3-ffi/src/compat/py_3_13.rs +@@ -104,3 +104,18 @@ compat_function!( + crate::PyList_SetSlice(list, 0, crate::PY_SSIZE_T_MAX, std::ptr::null_mut()) + } + ); ++ ++compat_function!( ++ originally_defined_for(Py_3_13); ++ ++ #[inline] ++ pub unsafe fn PyModule_Add( ++ module: *mut crate::PyObject, ++ name: *const std::os::raw::c_char, ++ value: *mut crate::PyObject, ++ ) -> std::os::raw::c_int { ++ let result = crate::compat::PyModule_AddObjectRef(module, name, value); ++ crate::Py_XDECREF(value); ++ result ++ } ++); +diff --git a/include/pyo3/pyo3-ffi/src/compat/py_3_14.rs b/include/pyo3/pyo3-ffi/src/compat/py_3_14.rs +new file mode 100644 +index 00000000..6fdaef17 +--- /dev/null ++++ b/include/pyo3/pyo3-ffi/src/compat/py_3_14.rs +@@ -0,0 +1,26 @@ ++compat_function!( ++ originally_defined_for(all(Py_3_14, not(Py_LIMITED_API))); ++ ++ #[inline] ++ pub unsafe fn Py_HashBuffer( ++ ptr: *const std::ffi::c_void, ++ len: crate::Py_ssize_t, ++ ) -> crate::Py_hash_t { ++ #[cfg(not(any(Py_LIMITED_API, PyPy)))] ++ { ++ crate::_Py_HashBytes(ptr, len) ++ } ++ ++ #[cfg(any(Py_LIMITED_API, PyPy))] ++ { ++ let bytes = crate::PyBytes_FromStringAndSize(ptr as *const std::os::raw::c_char, len); ++ if bytes.is_null() { ++ -1 ++ } else { ++ let result = crate::PyObject_Hash(bytes); ++ crate::Py_DECREF(bytes); ++ result ++ } ++ } ++ } ++); +diff --git a/include/pyo3/pyo3-ffi/src/cpython/abstract_.rs b/include/pyo3/pyo3-ffi/src/cpython/abstract_.rs +index 477ad02b..6ada1a75 100644 +--- a/include/pyo3/pyo3-ffi/src/cpython/abstract_.rs ++++ b/include/pyo3/pyo3-ffi/src/cpython/abstract_.rs +@@ -1,5 +1,5 @@ + use crate::{PyObject, Py_ssize_t}; +-#[cfg(not(all(Py_3_11, GraalPy)))] ++#[cfg(any(all(Py_3_8, not(any(PyPy, GraalPy))), not(Py_3_11)))] + use std::os::raw::c_char; + use std::os::raw::c_int; + +@@ -41,8 +41,8 @@ extern "C" { + ) -> *mut PyObject; + } + +-#[cfg(Py_3_8)] +-pub const PY_VECTORCALL_ARGUMENTS_OFFSET: size_t = ++#[cfg(Py_3_8)] // NB exported as public in abstract.rs from 3.12 ++const PY_VECTORCALL_ARGUMENTS_OFFSET: size_t = + 1 << (8 * std::mem::size_of::() as size_t - 1); + + #[cfg(Py_3_8)] +@@ -91,7 +91,7 @@ pub unsafe fn _PyObject_VectorcallTstate( + } + } + +-#[cfg(all(Py_3_8, not(any(PyPy, GraalPy))))] ++#[cfg(all(Py_3_8, not(any(PyPy, GraalPy, Py_3_11))))] // exported as a function from 3.11, see abstract.rs + #[inline(always)] + pub unsafe fn PyObject_Vectorcall( + callable: *mut PyObject, +@@ -103,16 +103,6 @@ pub unsafe fn PyObject_Vectorcall( + } + + extern "C" { +- #[cfg(all(PyPy, Py_3_8))] +- #[cfg_attr(not(Py_3_9), link_name = "_PyPyObject_Vectorcall")] +- #[cfg_attr(Py_3_9, link_name = "PyPyObject_Vectorcall")] +- pub fn PyObject_Vectorcall( +- callable: *mut PyObject, +- args: *const *mut PyObject, +- nargsf: size_t, +- kwnames: *mut PyObject, +- ) -> *mut PyObject; +- + #[cfg(Py_3_8)] + #[cfg_attr( + all(not(any(PyPy, GraalPy)), not(Py_3_9)), +@@ -187,23 +177,13 @@ pub unsafe fn PyObject_CallOneArg(func: *mut PyObject, arg: *mut PyObject) -> *m + _PyObject_VectorcallTstate(tstate, func, args, nargsf, std::ptr::null_mut()) + } + +-extern "C" { +- #[cfg(all(Py_3_9, not(any(PyPy, GraalPy))))] +- pub fn PyObject_VectorcallMethod( +- name: *mut PyObject, +- args: *const *mut PyObject, +- nargsf: size_t, +- kwnames: *mut PyObject, +- ) -> *mut PyObject; +-} +- + #[cfg(all(Py_3_9, not(any(PyPy, GraalPy))))] + #[inline(always)] + pub unsafe fn PyObject_CallMethodNoArgs( + self_: *mut PyObject, + name: *mut PyObject, + ) -> *mut PyObject { +- PyObject_VectorcallMethod( ++ crate::PyObject_VectorcallMethod( + name, + &self_, + 1 | PY_VECTORCALL_ARGUMENTS_OFFSET, +@@ -220,7 +200,7 @@ pub unsafe fn PyObject_CallMethodOneArg( + ) -> *mut PyObject { + let args = [self_, arg]; + assert!(!arg.is_null()); +- PyObject_VectorcallMethod( ++ crate::PyObject_VectorcallMethod( + name, + args.as_ptr(), + 2 | PY_VECTORCALL_ARGUMENTS_OFFSET, +diff --git a/include/pyo3/pyo3-ffi/src/cpython/bytesobject.rs b/include/pyo3/pyo3-ffi/src/cpython/bytesobject.rs +index 306702de..dd78e646 100644 +--- a/include/pyo3/pyo3-ffi/src/cpython/bytesobject.rs ++++ b/include/pyo3/pyo3-ffi/src/cpython/bytesobject.rs +@@ -17,7 +17,7 @@ pub struct PyBytesObject { + } + + #[cfg(any(PyPy, GraalPy, Py_LIMITED_API))] +-opaque_struct!(PyBytesObject); ++opaque_struct!(pub PyBytesObject); + + extern "C" { + #[cfg_attr(PyPy, link_name = "_PyPyBytes_Resize")] +diff --git a/include/pyo3/pyo3-ffi/src/cpython/code.rs b/include/pyo3/pyo3-ffi/src/cpython/code.rs +index 230096ca..3d47a1bc 100644 +--- a/include/pyo3/pyo3-ffi/src/cpython/code.rs ++++ b/include/pyo3/pyo3-ffi/src/cpython/code.rs +@@ -1,207 +1,43 @@ + use crate::object::*; + use crate::pyport::Py_ssize_t; + +-#[allow(unused_imports)] +-use std::os::raw::{c_char, c_int, c_short, c_uchar, c_void}; ++#[cfg(not(GraalPy))] ++use std::os::raw::c_char; ++use std::os::raw::{c_int, c_void}; + #[cfg(not(any(PyPy, GraalPy)))] + use std::ptr::addr_of_mut; + +-#[cfg(all(Py_3_8, not(any(PyPy, GraalPy)), not(Py_3_11)))] +-opaque_struct!(_PyOpcache); ++// skipped private _PY_MONITORING_LOCAL_EVENTS ++// skipped private _PY_MONITORING_UNGROUPED_EVENTS ++// skipped private _PY_MONITORING_EVENTS + +-#[cfg(Py_3_12)] +-pub const _PY_MONITORING_LOCAL_EVENTS: usize = 10; +-#[cfg(Py_3_12)] +-pub const _PY_MONITORING_UNGROUPED_EVENTS: usize = 15; +-#[cfg(Py_3_12)] +-pub const _PY_MONITORING_EVENTS: usize = 17; ++// skipped private _PyLocalMonitors ++// skipped private _Py_GlobalMonitors + +-#[cfg(Py_3_12)] +-#[repr(C)] +-#[derive(Clone, Copy)] +-pub struct _Py_LocalMonitors { +- pub tools: [u8; if cfg!(Py_3_13) { +- _PY_MONITORING_LOCAL_EVENTS +- } else { +- _PY_MONITORING_UNGROUPED_EVENTS +- }], +-} +- +-#[cfg(Py_3_12)] +-#[repr(C)] +-#[derive(Clone, Copy)] +-pub struct _Py_GlobalMonitors { +- pub tools: [u8; _PY_MONITORING_UNGROUPED_EVENTS], +-} +- +-// skipped _Py_CODEUNIT ++// skipped private _Py_CODEUNIT + +-// skipped _Py_OPCODE +-// skipped _Py_OPARG ++// skipped private _Py_OPCODE ++// skipped private _Py_OPARG + +-// skipped _py_make_codeunit ++// skipped private _py_make_codeunit + +-// skipped _py_set_opcode ++// skipped private _py_set_opcode + +-// skipped _Py_MAKE_CODEUNIT +-// skipped _Py_SET_OPCODE +- +-#[cfg(Py_3_12)] +-#[repr(C)] +-#[derive(Copy, Clone)] +-pub struct _PyCoCached { +- pub _co_code: *mut PyObject, +- pub _co_varnames: *mut PyObject, +- pub _co_cellvars: *mut PyObject, +- pub _co_freevars: *mut PyObject, +-} +- +-#[cfg(Py_3_12)] +-#[repr(C)] +-#[derive(Copy, Clone)] +-pub struct _PyCoLineInstrumentationData { +- pub original_opcode: u8, +- pub line_delta: i8, +-} ++// skipped private _Py_MAKE_CODEUNIT ++// skipped private _Py_SET_OPCODE + +-#[cfg(Py_3_12)] +-#[repr(C)] +-#[derive(Copy, Clone)] +-pub struct _PyCoMonitoringData { +- pub local_monitors: _Py_LocalMonitors, +- pub active_monitors: _Py_LocalMonitors, +- pub tools: *mut u8, +- pub lines: *mut _PyCoLineInstrumentationData, +- pub line_tools: *mut u8, +- pub per_instruction_opcodes: *mut u8, +- pub per_instruction_tools: *mut u8, +-} +- +-#[cfg(all(not(any(PyPy, GraalPy)), not(Py_3_7)))] +-opaque_struct!(PyCodeObject); +- +-#[cfg(all(not(any(PyPy, GraalPy)), Py_3_7, not(Py_3_8)))] +-#[repr(C)] +-pub struct PyCodeObject { +- pub ob_base: PyObject, +- pub co_argcount: c_int, +- pub co_kwonlyargcount: c_int, +- pub co_nlocals: c_int, +- pub co_stacksize: c_int, +- pub co_flags: c_int, +- pub co_firstlineno: c_int, +- pub co_code: *mut PyObject, +- pub co_consts: *mut PyObject, +- pub co_names: *mut PyObject, +- pub co_varnames: *mut PyObject, +- pub co_freevars: *mut PyObject, +- pub co_cellvars: *mut PyObject, +- pub co_cell2arg: *mut Py_ssize_t, +- pub co_filename: *mut PyObject, +- pub co_name: *mut PyObject, +- pub co_lnotab: *mut PyObject, +- pub co_zombieframe: *mut c_void, +- pub co_weakreflist: *mut PyObject, +- 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)] +-pub struct PyCodeObject { +- pub ob_base: PyObject, +- pub co_argcount: c_int, +- pub co_posonlyargcount: c_int, +- pub co_kwonlyargcount: c_int, +- pub co_nlocals: c_int, +- pub co_stacksize: c_int, +- pub co_flags: c_int, +- pub co_firstlineno: c_int, +- pub co_code: *mut PyObject, +- pub co_consts: *mut PyObject, +- pub co_names: *mut PyObject, +- pub co_varnames: *mut PyObject, +- pub co_freevars: *mut PyObject, +- pub co_cellvars: *mut PyObject, +- pub co_cell2arg: *mut Py_ssize_t, +- pub co_filename: *mut PyObject, +- pub co_name: *mut PyObject, +- #[cfg(not(Py_3_10))] +- pub co_lnotab: *mut PyObject, +- #[cfg(Py_3_10)] +- pub co_linetable: *mut PyObject, +- pub co_zombieframe: *mut c_void, +- pub co_weakreflist: *mut PyObject, +- pub co_extra: *mut c_void, +- pub co_opcache_map: *mut c_uchar, +- pub co_opcache: *mut _PyOpcache, +- pub co_opcache_flag: c_int, +- pub co_opcache_size: c_uchar, +-} ++// skipped private _PyCoCached ++// skipped private _PyCoLineInstrumentationData ++// skipped private _PyCoMontoringData + +-#[cfg(all(not(any(PyPy, GraalPy)), Py_3_11))] +-#[repr(C)] +-pub struct PyCodeObject { +- pub ob_base: PyVarObject, +- pub co_consts: *mut PyObject, +- pub co_names: *mut PyObject, +- pub co_exceptiontable: *mut PyObject, +- pub co_flags: c_int, +- #[cfg(not(Py_3_12))] +- pub co_warmup: c_int, ++// skipped private _PyExecutorArray + +- pub co_argcount: c_int, +- pub co_posonlyargcount: c_int, +- pub co_kwonlyargcount: c_int, +- pub co_stacksize: c_int, +- pub co_firstlineno: c_int, +- +- pub co_nlocalsplus: c_int, +- #[cfg(Py_3_12)] +- pub co_framesize: c_int, +- pub co_nlocals: c_int, +- #[cfg(not(Py_3_12))] +- pub co_nplaincellvars: c_int, +- pub co_ncellvars: c_int, +- pub co_nfreevars: c_int, +- #[cfg(Py_3_12)] +- pub co_version: u32, +- +- pub co_localsplusnames: *mut PyObject, +- pub co_localspluskinds: *mut PyObject, +- pub co_filename: *mut PyObject, +- pub co_name: *mut PyObject, +- pub co_qualname: *mut PyObject, +- pub co_linetable: *mut PyObject, +- pub co_weakreflist: *mut PyObject, +- #[cfg(not(Py_3_12))] +- 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)] +- pub _co_instrumentation_version: u64, +- #[cfg(Py_3_12)] +- pub _co_monitoring: *mut _PyCoMonitoringData, +- pub _co_firsttraceable: c_int, +- pub co_extra: *mut c_void, +- pub co_code_adaptive: [c_char; 1], +-} +- +-#[cfg(PyPy)] +-#[repr(C)] +-pub struct PyCodeObject { +- pub ob_base: PyObject, +- pub co_name: *mut PyObject, +- pub co_filename: *mut PyObject, +- pub co_argcount: c_int, +- pub co_flags: c_int, +-} ++opaque_struct!( ++ #[doc = "A Python code object.\n"] ++ #[doc = "\n"] ++ #[doc = "`pyo3-ffi` does not expose the contents of this struct, as it has no stability guarantees."] ++ pub PyCodeObject ++); + + /* Masks for co_flags */ + pub const CO_OPTIMIZED: c_int = 0x0001; +@@ -247,28 +83,14 @@ pub unsafe fn PyCode_Check(op: *mut PyObject) -> c_int { + (Py_TYPE(op) == addr_of_mut!(PyCode_Type)) as c_int + } + +-#[inline] +-#[cfg(all(not(any(PyPy, GraalPy)), Py_3_10, not(Py_3_11)))] +-pub unsafe fn PyCode_GetNumFree(op: *mut PyCodeObject) -> Py_ssize_t { +- crate::PyTuple_GET_SIZE((*op).co_freevars) +-} +- +-#[inline] +-#[cfg(all(not(Py_3_10), Py_3_11, not(any(PyPy, GraalPy))))] +-pub unsafe fn PyCode_GetNumFree(op: *mut PyCodeObject) -> c_int { +- (*op).co_nfreevars +-} +- + extern "C" { + #[cfg(PyPy)] + #[link_name = "PyPyCode_Check"] + pub fn PyCode_Check(op: *mut PyObject) -> c_int; +- +- #[cfg(PyPy)] +- #[link_name = "PyPyCode_GetNumFree"] +- pub fn PyCode_GetNumFree(op: *mut PyCodeObject) -> Py_ssize_t; + } + ++// skipped PyCode_GetNumFree (requires knowledge of code object layout) ++ + extern "C" { + #[cfg(not(GraalPy))] + #[cfg_attr(PyPy, link_name = "PyPyCode_New")] +diff --git a/include/pyo3/pyo3-ffi/src/cpython/compile.rs b/include/pyo3/pyo3-ffi/src/cpython/compile.rs +index 79f06c92..078b5e0d 100644 +--- a/include/pyo3/pyo3-ffi/src/cpython/compile.rs ++++ b/include/pyo3/pyo3-ffi/src/cpython/compile.rs +@@ -6,19 +6,21 @@ use crate::pyarena::*; + use crate::pythonrun::*; + #[cfg(not(any(PyPy, Py_3_10)))] + use crate::PyCodeObject; ++use crate::INT_MAX; + #[cfg(not(any(PyPy, Py_3_10)))] + use std::os::raw::c_char; + use std::os::raw::c_int; + +-// skipped non-limited PyCF_MASK +-// skipped non-limited PyCF_MASK_OBSOLETE +-// skipped non-limited PyCF_SOURCE_IS_UTF8 +-// skipped non-limited PyCF_DONT_IMPLY_DEDENT +-// skipped non-limited PyCF_ONLY_AST +-// skipped non-limited PyCF_IGNORE_COOKIE +-// skipped non-limited PyCF_TYPE_COMMENTS +-// skipped non-limited PyCF_ALLOW_TOP_LEVEL_AWAIT +-// skipped non-limited PyCF_COMPILE_MASK ++// skipped PyCF_MASK ++// skipped PyCF_MASK_OBSOLETE ++// skipped PyCF_SOURCE_IS_UTF8 ++// skipped PyCF_DONT_IMPLY_DEDENT ++// skipped PyCF_ONLY_AST ++// skipped PyCF_IGNORE_COOKIE ++// skipped PyCF_TYPE_COMMENTS ++// skipped PyCF_ALLOW_TOP_LEVEL_AWAIT ++// skipped PyCF_OPTIMIZED_AST ++// skipped PyCF_COMPILE_MASK + + #[repr(C)] + #[derive(Copy, Clone)] +@@ -28,31 +30,23 @@ pub struct PyCompilerFlags { + pub cf_feature_version: c_int, + } + +-// skipped non-limited _PyCompilerFlags_INIT ++// skipped _PyCompilerFlags_INIT + +-#[cfg(all(Py_3_12, not(any(Py_3_13, PyPy, GraalPy))))] +-#[repr(C)] +-#[derive(Copy, Clone)] +-pub struct _PyCompilerSrcLocation { +- pub lineno: c_int, +- pub end_lineno: c_int, +- pub col_offset: c_int, +- pub end_col_offset: c_int, +-} +- +-// skipped SRC_LOCATION_FROM_AST +- +-#[cfg(not(any(PyPy, GraalPy, Py_3_13)))] ++// NB this type technically existed in the header until 3.13, when it was ++// moved to the internal CPython headers. ++// ++// We choose not to expose it in the public API past 3.10, as it is ++// not used in the public API past that point. ++#[cfg(not(any(PyPy, GraalPy, Py_3_10)))] + #[repr(C)] + #[derive(Copy, Clone)] + pub struct PyFutureFeatures { + pub ff_features: c_int, +- #[cfg(not(Py_3_12))] + pub ff_lineno: c_int, +- #[cfg(Py_3_12)] +- pub ff_location: _PyCompilerSrcLocation, + } + ++// FIXME: these constants should probably be &CStr, if they are used at all ++ + pub const FUTURE_NESTED_SCOPES: &str = "nested_scopes"; + pub const FUTURE_GENERATORS: &str = "generators"; + pub const FUTURE_DIVISION: &str = "division"; +@@ -62,13 +56,12 @@ pub const FUTURE_PRINT_FUNCTION: &str = "print_function"; + pub const FUTURE_UNICODE_LITERALS: &str = "unicode_literals"; + pub const FUTURE_BARRY_AS_BDFL: &str = "barry_as_FLUFL"; + pub const FUTURE_GENERATOR_STOP: &str = "generator_stop"; +-// skipped non-limited FUTURE_ANNOTATIONS ++pub const FUTURE_ANNOTATIONS: &str = "annotations"; + ++#[cfg(not(any(PyPy, GraalPy, Py_3_10)))] + extern "C" { +- #[cfg(not(any(PyPy, Py_3_10)))] + pub fn PyNode_Compile(arg1: *mut _node, arg2: *const c_char) -> *mut PyCodeObject; + +- #[cfg(not(any(PyPy, Py_3_10)))] + pub fn PyAST_CompileEx( + _mod: *mut _mod, + filename: *const c_char, +@@ -77,7 +70,6 @@ extern "C" { + arena: *mut PyArena, + ) -> *mut PyCodeObject; + +- #[cfg(not(any(PyPy, Py_3_10)))] + pub fn PyAST_CompileObject( + _mod: *mut _mod, + filename: *mut PyObject, +@@ -86,23 +78,20 @@ extern "C" { + arena: *mut PyArena, + ) -> *mut PyCodeObject; + +- #[cfg(not(any(PyPy, Py_3_10)))] + pub fn PyFuture_FromAST(_mod: *mut _mod, filename: *const c_char) -> *mut PyFutureFeatures; + +- #[cfg(not(any(PyPy, Py_3_10)))] + pub fn PyFuture_FromASTObject( + _mod: *mut _mod, + filename: *mut PyObject, + ) -> *mut PyFutureFeatures; ++} + +- // skipped non-limited _Py_Mangle +- // skipped non-limited PY_INVALID_STACK_EFFECT ++pub const PY_INVALID_STACK_EFFECT: c_int = INT_MAX; ++ ++extern "C" { + + pub fn PyCompile_OpcodeStackEffect(opcode: c_int, oparg: c_int) -> c_int; + + #[cfg(Py_3_8)] + pub fn PyCompile_OpcodeStackEffectWithJump(opcode: c_int, oparg: c_int, jump: c_int) -> c_int; +- +- // skipped non-limited _PyASTOptimizeState +- // skipped non-limited _PyAST_Optimize + } +diff --git a/include/pyo3/pyo3-ffi/src/cpython/critical_section.rs b/include/pyo3/pyo3-ffi/src/cpython/critical_section.rs +index 1fc6591c..808dba87 100644 +--- a/include/pyo3/pyo3-ffi/src/cpython/critical_section.rs ++++ b/include/pyo3/pyo3-ffi/src/cpython/critical_section.rs +@@ -9,16 +9,6 @@ pub struct PyCriticalSection { + _cs_mutex: *mut PyMutex, + } + +-#[cfg(Py_GIL_DISABLED)] +-impl Default for PyCriticalSection { +- fn default() -> Self { +- PyCriticalSection { +- _cs_prev: 0, +- _cs_mutex: core::ptr::null_mut(), +- } +- } +-} +- + #[repr(C)] + #[cfg(Py_GIL_DISABLED)] + pub struct PyCriticalSection2 { +@@ -27,10 +17,10 @@ pub struct PyCriticalSection2 { + } + + #[cfg(not(Py_GIL_DISABLED))] +-opaque_struct!(PyCriticalSection); ++opaque_struct!(pub PyCriticalSection); + + #[cfg(not(Py_GIL_DISABLED))] +-opaque_struct!(PyCriticalSection2); ++opaque_struct!(pub PyCriticalSection2); + + extern "C" { + pub fn PyCriticalSection_Begin(c: *mut PyCriticalSection, op: *mut PyObject); +diff --git a/include/pyo3/pyo3-ffi/src/cpython/descrobject.rs b/include/pyo3/pyo3-ffi/src/cpython/descrobject.rs +index 1b5ee466..7cef9bdb 100644 +--- a/include/pyo3/pyo3-ffi/src/cpython/descrobject.rs ++++ b/include/pyo3/pyo3-ffi/src/cpython/descrobject.rs +@@ -69,10 +69,7 @@ pub struct PyWrapperDescrObject { + pub d_wrapped: *mut c_void, + } + +-#[cfg_attr(windows, link(name = "pythonXY"))] +-extern "C" { +- pub static mut _PyMethodWrapper_Type: PyTypeObject; +-} ++// skipped _PyMethodWrapper_Type + + // skipped non-limited PyDescr_NewWrapper + // skipped non-limited PyDescr_IsData +diff --git a/include/pyo3/pyo3-ffi/src/cpython/dictobject.rs b/include/pyo3/pyo3-ffi/src/cpython/dictobject.rs +index f67a7725..34b66a96 100644 +--- a/include/pyo3/pyo3-ffi/src/cpython/dictobject.rs ++++ b/include/pyo3/pyo3-ffi/src/cpython/dictobject.rs +@@ -2,10 +2,10 @@ use crate::object::*; + use crate::pyport::Py_ssize_t; + use std::os::raw::c_int; + +-opaque_struct!(PyDictKeysObject); ++opaque_struct!(pub PyDictKeysObject); + + #[cfg(Py_3_11)] +-opaque_struct!(PyDictValues); ++opaque_struct!(pub PyDictValues); + + #[cfg(not(GraalPy))] + #[repr(C)] +@@ -17,7 +17,10 @@ pub struct PyDictObject { + Py_3_12, + deprecated(note = "Deprecated in Python 3.12 and will be removed in the future.") + )] ++ #[cfg(not(Py_3_14))] + pub ma_version_tag: u64, ++ #[cfg(Py_3_14)] ++ _ma_watcher_tag: u64, + pub ma_keys: *mut PyDictKeysObject, + #[cfg(not(Py_3_11))] + pub ma_values: *mut *mut PyObject, +@@ -36,15 +39,6 @@ extern "C" { + item: *mut PyObject, + hash: crate::Py_hash_t, + ) -> c_int; +- +- #[cfg(Py_3_13)] +- pub fn _PyDict_SetItem_KnownHash_LockHeld( +- mp: *mut PyDictObject, +- name: *mut PyObject, +- value: *mut PyObject, +- hash: crate::Py_hash_t, +- ) -> c_int; +- + // skipped _PyDict_DelItem_KnownHash + // skipped _PyDict_DelItemIf + // skipped _PyDict_NewKeysForClass +diff --git a/include/pyo3/pyo3-ffi/src/cpython/frameobject.rs b/include/pyo3/pyo3-ffi/src/cpython/frameobject.rs +index 6d2346f9..993e93c8 100644 +--- a/include/pyo3/pyo3-ffi/src/cpython/frameobject.rs ++++ b/include/pyo3/pyo3-ffi/src/cpython/frameobject.rs +@@ -53,7 +53,7 @@ pub struct PyFrameObject { + } + + #[cfg(any(PyPy, GraalPy, Py_3_11))] +-opaque_struct!(PyFrameObject); ++opaque_struct!(pub PyFrameObject); + + // skipped _PyFrame_IsRunnable + // skipped _PyFrame_IsExecuting +@@ -89,7 +89,8 @@ extern "C" { + pub fn PyFrame_FastToLocals(f: *mut PyFrameObject); + + // skipped _PyFrame_DebugMallocStats +- // skipped PyFrame_GetBack ++ #[cfg(all(Py_3_9, not(PyPy)))] ++ pub fn PyFrame_GetBack(f: *mut PyFrameObject) -> *mut PyFrameObject; + + #[cfg(not(Py_3_9))] + pub fn PyFrame_ClearFreeList() -> c_int; +diff --git a/include/pyo3/pyo3-ffi/src/cpython/funcobject.rs b/include/pyo3/pyo3-ffi/src/cpython/funcobject.rs +index 25de30d5..cd2052de 100644 +--- a/include/pyo3/pyo3-ffi/src/cpython/funcobject.rs ++++ b/include/pyo3/pyo3-ffi/src/cpython/funcobject.rs +@@ -41,6 +41,8 @@ pub struct PyFunctionObject { + pub func_weakreflist: *mut PyObject, + pub func_module: *mut PyObject, + pub func_annotations: *mut PyObject, ++ #[cfg(Py_3_14)] ++ pub func_annotate: *mut PyObject, + #[cfg(Py_3_12)] + pub func_typeparams: *mut PyObject, + pub vectorcall: Option, +diff --git a/include/pyo3/pyo3-ffi/src/cpython/genobject.rs b/include/pyo3/pyo3-ffi/src/cpython/genobject.rs +index 4be310a8..92f14d59 100644 +--- a/include/pyo3/pyo3-ffi/src/cpython/genobject.rs ++++ b/include/pyo3/pyo3-ffi/src/cpython/genobject.rs +@@ -1,13 +1,11 @@ + use crate::object::*; + use crate::PyFrameObject; +-#[cfg(not(any(PyPy, GraalPy)))] +-use crate::_PyErr_StackItem; +-#[cfg(all(Py_3_11, not(GraalPy)))] ++#[cfg(all(Py_3_11, not(any(PyPy, GraalPy, Py_3_14))))] + use std::os::raw::c_char; + use std::os::raw::c_int; + use std::ptr::addr_of_mut; + +-#[cfg(not(any(PyPy, GraalPy)))] ++#[cfg(not(any(PyPy, GraalPy, Py_3_14)))] + #[repr(C)] + pub struct PyGenObject { + pub ob_base: PyObject, +@@ -20,7 +18,7 @@ pub struct PyGenObject { + pub gi_weakreflist: *mut PyObject, + pub gi_name: *mut PyObject, + pub gi_qualname: *mut PyObject, +- pub gi_exc_state: _PyErr_StackItem, ++ pub gi_exc_state: crate::cpython::pystate::_PyErr_StackItem, + #[cfg(Py_3_11)] + pub gi_origin_or_finalizer: *mut PyObject, + #[cfg(Py_3_11)] +@@ -35,6 +33,9 @@ pub struct PyGenObject { + pub gi_iframe: [*mut PyObject; 1], + } + ++#[cfg(all(Py_3_14, not(any(PyPy, GraalPy))))] ++opaque_struct!(pub PyGenObject); ++ + #[cfg_attr(windows, link(name = "pythonXY"))] + extern "C" { + pub static mut PyGen_Type: PyTypeObject; +@@ -67,9 +68,10 @@ extern "C" { + #[cfg_attr(windows, link(name = "pythonXY"))] + extern "C" { + pub static mut PyCoro_Type: PyTypeObject; +- pub static mut _PyCoroWrapper_Type: PyTypeObject; + } + ++// skipped _PyCoroWrapper_Type ++ + #[inline] + pub unsafe fn PyCoro_CheckExact(op: *mut PyObject) -> c_int { + PyObject_TypeCheck(op, addr_of_mut!(PyCoro_Type)) +diff --git a/include/pyo3/pyo3-ffi/src/cpython/import.rs b/include/pyo3/pyo3-ffi/src/cpython/import.rs +index 697d68a4..c8ef5ab4 100644 +--- a/include/pyo3/pyo3-ffi/src/cpython/import.rs ++++ b/include/pyo3/pyo3-ffi/src/cpython/import.rs +@@ -65,10 +65,8 @@ pub struct _frozen { + extern "C" { + #[cfg(not(PyPy))] + pub static mut PyImport_FrozenModules: *const _frozen; +- #[cfg(all(not(PyPy), Py_3_11))] +- pub static mut _PyImport_FrozenBootstrap: *const _frozen; +- #[cfg(all(not(PyPy), Py_3_11))] +- pub static mut _PyImport_FrozenStdlib: *const _frozen; +- #[cfg(all(not(PyPy), Py_3_11))] +- pub static mut _PyImport_FrozenTest: *const _frozen; + } ++ ++// skipped _PyImport_FrozenBootstrap ++// skipped _PyImport_FrozenStdlib ++// skipped _PyImport_FrozenTest +diff --git a/include/pyo3/pyo3-ffi/src/cpython/initconfig.rs b/include/pyo3/pyo3-ffi/src/cpython/initconfig.rs +index 321d200e..076981f1 100644 +--- a/include/pyo3/pyo3-ffi/src/cpython/initconfig.rs ++++ b/include/pyo3/pyo3-ffi/src/cpython/initconfig.rs +@@ -93,6 +93,8 @@ pub struct PyConfig { + pub tracemalloc: c_int, + #[cfg(Py_3_12)] + pub perf_profiling: c_int, ++ #[cfg(Py_3_14)] ++ pub remote_debug: c_int, + pub import_time: c_int, + #[cfg(Py_3_11)] + pub code_debug_ranges: c_int, +@@ -141,10 +143,18 @@ pub struct PyConfig { + pub safe_path: c_int, + #[cfg(Py_3_12)] + pub int_max_str_digits: c_int, ++ #[cfg(Py_3_14)] ++ pub thread_inherit_context: c_int, ++ #[cfg(Py_3_14)] ++ pub context_aware_warnings: c_int, ++ #[cfg(all(Py_3_14, target_os = "macos"))] ++ pub use_system_logger: c_int, + #[cfg(Py_3_13)] + pub cpu_count: c_int, + #[cfg(Py_GIL_DISABLED)] + pub enable_gil: c_int, ++ #[cfg(all(Py_3_14, Py_GIL_DISABLED))] ++ pub tlbc_enabled: c_int, + pub pathconfig_warnings: c_int, + #[cfg(Py_3_10)] + pub program_name: *mut wchar_t, +diff --git a/include/pyo3/pyo3-ffi/src/cpython/lock.rs b/include/pyo3/pyo3-ffi/src/cpython/lock.rs +index c451666e..6c80b00d 100644 +--- a/include/pyo3/pyo3-ffi/src/cpython/lock.rs ++++ b/include/pyo3/pyo3-ffi/src/cpython/lock.rs +@@ -8,15 +8,6 @@ pub struct PyMutex { + pub(crate) _pin: PhantomPinned, + } + +-impl PyMutex { +- pub const fn new() -> PyMutex { +- PyMutex { +- _bits: AtomicU8::new(0), +- _pin: PhantomPinned, +- } +- } +-} +- + extern "C" { + pub fn PyMutex_Lock(m: *mut PyMutex); + pub fn PyMutex_Unlock(m: *mut PyMutex); +diff --git a/include/pyo3/pyo3-ffi/src/cpython/longobject.rs b/include/pyo3/pyo3-ffi/src/cpython/longobject.rs +index 6063575e..45acaae5 100644 +--- a/include/pyo3/pyo3-ffi/src/cpython/longobject.rs ++++ b/include/pyo3/pyo3-ffi/src/cpython/longobject.rs +@@ -61,18 +61,6 @@ extern "C" { + is_signed: c_int, + ) -> *mut PyObject; + +- #[cfg(Py_3_13)] +- #[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, +- with_exceptions: c_int, +- ) -> c_int; +- +- #[cfg(not(Py_3_13))] + #[cfg_attr(PyPy, link_name = "_PyPyLong_AsByteArrayO")] + pub fn _PyLong_AsByteArray( + v: *mut PyLongObject, +diff --git a/include/pyo3/pyo3-ffi/src/cpython/mod.rs b/include/pyo3/pyo3-ffi/src/cpython/mod.rs +index fe909f0c..adaf8bc8 100644 +--- a/include/pyo3/pyo3-ffi/src/cpython/mod.rs ++++ b/include/pyo3/pyo3-ffi/src/cpython/mod.rs +@@ -38,6 +38,7 @@ pub(crate) mod pythonrun; + // skipped sysmodule.h + pub(crate) mod floatobject; + pub(crate) mod pyframe; ++pub(crate) mod pyhash; + pub(crate) mod tupleobject; + pub(crate) mod unicodeobject; + pub(crate) mod weakrefobject; +@@ -71,8 +72,10 @@ pub use self::object::*; + pub use self::objimpl::*; + pub use self::pydebug::*; + pub use self::pyerrors::*; +-#[cfg(Py_3_11)] ++#[cfg(all(Py_3_11, not(PyPy)))] + pub use self::pyframe::*; ++#[cfg(any(not(PyPy), Py_3_13))] ++pub use self::pyhash::*; + #[cfg(all(Py_3_8, not(PyPy)))] + pub use self::pylifecycle::*; + pub use self::pymem::*; +diff --git a/include/pyo3/pyo3-ffi/src/cpython/object.rs b/include/pyo3/pyo3-ffi/src/cpython/object.rs +index 75eef11a..26ef784d 100644 +--- a/include/pyo3/pyo3-ffi/src/cpython/object.rs ++++ b/include/pyo3/pyo3-ffi/src/cpython/object.rs +@@ -310,10 +310,14 @@ pub struct PyHeapTypeObject { + pub ht_cached_keys: *mut c_void, + #[cfg(Py_3_9)] + pub ht_module: *mut object::PyObject, +- #[cfg(Py_3_11)] ++ #[cfg(all(Py_3_11, not(PyPy)))] + _ht_tpname: *mut c_char, +- #[cfg(Py_3_11)] ++ #[cfg(Py_3_14)] ++ pub ht_token: *mut c_void, ++ #[cfg(all(Py_3_11, not(PyPy)))] + _spec_cache: _specialization_cache, ++ #[cfg(all(Py_GIL_DISABLED, Py_3_14))] ++ pub unique_id: Py_ssize_t, + } + + impl Default for PyHeapTypeObject { +diff --git a/include/pyo3/pyo3-ffi/src/cpython/objimpl.rs b/include/pyo3/pyo3-ffi/src/cpython/objimpl.rs +index 98a19abe..14f7121a 100644 +--- a/include/pyo3/pyo3-ffi/src/cpython/objimpl.rs ++++ b/include/pyo3/pyo3-ffi/src/cpython/objimpl.rs +@@ -1,4 +1,4 @@ +-#[cfg(not(all(Py_3_11, GraalPy)))] ++#[cfg(not(all(Py_3_11, any(PyPy, GraalPy))))] + use libc::size_t; + use std::os::raw::c_int; + +diff --git a/include/pyo3/pyo3-ffi/src/cpython/pyerrors.rs b/include/pyo3/pyo3-ffi/src/cpython/pyerrors.rs +index c6e10e5f..c9831669 100644 +--- a/include/pyo3/pyo3-ffi/src/cpython/pyerrors.rs ++++ b/include/pyo3/pyo3-ffi/src/cpython/pyerrors.rs +@@ -46,6 +46,8 @@ pub struct PySyntaxErrorObject { + pub end_offset: *mut PyObject, + pub text: *mut PyObject, + pub print_file_and_line: *mut PyObject, ++ #[cfg(Py_3_14)] ++ pub metadata: *mut PyObject, + } + + #[cfg(not(any(PyPy, GraalPy)))] +diff --git a/include/pyo3/pyo3-ffi/src/cpython/pyframe.rs b/include/pyo3/pyo3-ffi/src/cpython/pyframe.rs +index d0cfa0a2..f0c38be4 100644 +--- a/include/pyo3/pyo3-ffi/src/cpython/pyframe.rs ++++ b/include/pyo3/pyo3-ffi/src/cpython/pyframe.rs +@@ -1,2 +1,3 @@ +-#[cfg(Py_3_11)] +-opaque_struct!(_PyInterpreterFrame); ++// NB used in `_PyEval_EvalFrameDefault`, maybe we remove this too. ++#[cfg(all(Py_3_11, not(PyPy)))] ++opaque_struct!(pub _PyInterpreterFrame); +diff --git a/include/pyo3/pyo3-ffi/src/cpython/pyhash.rs b/include/pyo3/pyo3-ffi/src/cpython/pyhash.rs +new file mode 100644 +index 00000000..b746018a +--- /dev/null ++++ b/include/pyo3/pyo3-ffi/src/cpython/pyhash.rs +@@ -0,0 +1,38 @@ ++#[cfg(Py_3_14)] ++use crate::Py_ssize_t; ++#[cfg(Py_3_13)] ++use crate::{PyObject, Py_hash_t}; ++#[cfg(any(Py_3_13, not(PyPy)))] ++use std::os::raw::c_void; ++#[cfg(not(PyPy))] ++use std::os::raw::{c_char, c_int}; ++ ++#[cfg(not(PyPy))] ++#[repr(C)] ++#[derive(Copy, Clone)] ++pub struct PyHash_FuncDef { ++ pub hash: ++ Option crate::Py_hash_t>, ++ pub name: *const c_char, ++ pub hash_bits: c_int, ++ pub seed_bits: c_int, ++} ++ ++#[cfg(not(PyPy))] ++impl Default for PyHash_FuncDef { ++ #[inline] ++ fn default() -> Self { ++ unsafe { std::mem::zeroed() } ++ } ++} ++ ++extern "C" { ++ #[cfg(not(PyPy))] ++ pub fn PyHash_GetFuncDef() -> *mut PyHash_FuncDef; ++ #[cfg(Py_3_13)] ++ pub fn Py_HashPointer(ptr: *const c_void) -> Py_hash_t; ++ #[cfg(Py_3_13)] ++ pub fn PyObject_GenericHash(obj: *mut PyObject) -> Py_hash_t; ++ #[cfg(Py_3_14)] ++ pub fn Py_HashBuffer(ptr: *const c_void, len: Py_ssize_t) -> Py_hash_t; ++} +diff --git a/include/pyo3/pyo3-ffi/src/cpython/pystate.rs b/include/pyo3/pyo3-ffi/src/cpython/pystate.rs +index 5481265b..b8f6fd66 100644 +--- a/include/pyo3/pyo3-ffi/src/cpython/pystate.rs ++++ b/include/pyo3/pyo3-ffi/src/cpython/pystate.rs +@@ -27,16 +27,18 @@ pub const PyTrace_OPCODE: c_int = 7; + // skipped PyTraceInfo + // skipped CFrame + ++/// Private structure used inline in `PyGenObject` + #[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 { + #[cfg(not(Py_3_11))] +- pub exc_type: *mut PyObject, +- pub exc_value: *mut PyObject, ++ exc_type: *mut PyObject, ++ exc_value: *mut PyObject, + #[cfg(not(Py_3_11))] +- pub exc_traceback: *mut PyObject, +- pub previous_item: *mut _PyErr_StackItem, ++ exc_traceback: *mut PyObject, ++ previous_item: *mut _PyErr_StackItem, + } + + // skipped _PyStackChunk +@@ -69,21 +71,21 @@ extern "C" { + pub fn PyThreadState_DeleteCurrent(); + } + +-#[cfg(all(Py_3_9, not(Py_3_11)))] ++#[cfg(all(Py_3_9, not(any(Py_3_11, PyPy))))] + pub type _PyFrameEvalFunction = extern "C" fn( + *mut crate::PyThreadState, + *mut crate::PyFrameObject, + c_int, + ) -> *mut crate::object::PyObject; + +-#[cfg(Py_3_11)] ++#[cfg(all(Py_3_11, not(PyPy)))] + pub type _PyFrameEvalFunction = extern "C" fn( + *mut crate::PyThreadState, + *mut crate::_PyInterpreterFrame, + c_int, + ) -> *mut crate::object::PyObject; + +-#[cfg(Py_3_9)] ++#[cfg(all(Py_3_9, not(PyPy)))] + extern "C" { + /// Get the frame evaluation function. + pub fn _PyInterpreterState_GetEvalFrameFunc( +diff --git a/include/pyo3/pyo3-ffi/src/cpython/tupleobject.rs b/include/pyo3/pyo3-ffi/src/cpython/tupleobject.rs +index c06609fb..dc1bf8e4 100644 +--- a/include/pyo3/pyo3-ffi/src/cpython/tupleobject.rs ++++ b/include/pyo3/pyo3-ffi/src/cpython/tupleobject.rs +@@ -1,4 +1,6 @@ + use crate::object::*; ++#[cfg(Py_3_14)] ++use crate::pyport::Py_hash_t; + #[cfg(not(PyPy))] + use crate::pyport::Py_ssize_t; + +@@ -6,7 +8,7 @@ use crate::pyport::Py_ssize_t; + pub struct PyTupleObject { + pub ob_base: PyVarObject, + #[cfg(Py_3_14)] +- pub ob_hash: crate::Py_hash_t, ++ pub ob_hash: Py_hash_t, + pub ob_item: [*mut PyObject; 1], + } + +diff --git a/include/pyo3/pyo3-ffi/src/cpython/unicodeobject.rs b/include/pyo3/pyo3-ffi/src/cpython/unicodeobject.rs +index 72da46c1..452c82e4 100644 +--- a/include/pyo3/pyo3-ffi/src/cpython/unicodeobject.rs ++++ b/include/pyo3/pyo3-ffi/src/cpython/unicodeobject.rs +@@ -1,4 +1,4 @@ +-#[cfg(not(PyPy))] ++#[cfg(any(Py_3_11, not(PyPy)))] + use crate::Py_hash_t; + use crate::{PyObject, Py_UCS1, Py_UCS2, Py_UCS4, Py_ssize_t}; + use libc::wchar_t; +@@ -31,11 +31,13 @@ use std::os::raw::{c_char, c_int, c_uint, c_void}; + // skipped Py_UNICODE_LOW_SURROGATE + + // generated by bindgen v0.63.0 (with small adaptations) ++#[cfg(not(Py_3_14))] + #[repr(C)] + struct BitfieldUnit { + storage: Storage, + } + ++#[cfg(not(Py_3_14))] + impl BitfieldUnit { + #[inline] + pub const fn new(storage: Storage) -> Self { +@@ -43,7 +45,7 @@ impl BitfieldUnit { + } + } + +-#[cfg(not(GraalPy))] ++#[cfg(not(any(GraalPy, Py_3_14)))] + impl BitfieldUnit + where + Storage: AsRef<[u8]> + AsMut<[u8]>, +@@ -117,30 +119,33 @@ where + } + } + +-#[cfg(not(GraalPy))] ++#[cfg(not(any(GraalPy, Py_3_14)))] + const STATE_INTERNED_INDEX: usize = 0; +-#[cfg(all(not(GraalPy), not(Py_3_14)))] ++#[cfg(not(any(GraalPy, Py_3_14)))] + const STATE_INTERNED_WIDTH: u8 = 2; + +-#[cfg(all(not(GraalPy), Py_3_14))] +-const STATE_INTERNED_WIDTH: u8 = 16; +- +-#[cfg(not(GraalPy))] ++#[cfg(not(any(GraalPy, Py_3_14)))] + const STATE_KIND_INDEX: usize = STATE_INTERNED_WIDTH as usize; +-#[cfg(not(GraalPy))] ++#[cfg(not(any(GraalPy, Py_3_14)))] + const STATE_KIND_WIDTH: u8 = 3; + +-#[cfg(not(GraalPy))] ++#[cfg(not(any(GraalPy, Py_3_14)))] + const STATE_COMPACT_INDEX: usize = (STATE_INTERNED_WIDTH + STATE_KIND_WIDTH) as usize; +-#[cfg(not(GraalPy))] ++#[cfg(not(any(GraalPy, Py_3_14)))] + const STATE_COMPACT_WIDTH: u8 = 1; + +-#[cfg(not(GraalPy))] ++#[cfg(not(any(GraalPy, Py_3_14)))] + const STATE_ASCII_INDEX: usize = + (STATE_INTERNED_WIDTH + STATE_KIND_WIDTH + STATE_COMPACT_WIDTH) as usize; +-#[cfg(not(GraalPy))] ++#[cfg(not(any(GraalPy, Py_3_14)))] + const STATE_ASCII_WIDTH: u8 = 1; + ++#[cfg(all(not(any(GraalPy, Py_3_14)), Py_3_12))] ++const STATE_STATICALLY_ALLOCATED_INDEX: usize = ++ (STATE_INTERNED_WIDTH + STATE_KIND_WIDTH + STATE_COMPACT_WIDTH + STATE_ASCII_WIDTH) as usize; ++#[cfg(all(not(any(GraalPy, Py_3_14)), Py_3_12))] ++const STATE_STATICALLY_ALLOCATED_WIDTH: u8 = 1; ++ + #[cfg(not(any(Py_3_12, GraalPy)))] + const STATE_READY_INDEX: usize = + (STATE_INTERNED_WIDTH + STATE_KIND_WIDTH + STATE_COMPACT_WIDTH + STATE_ASCII_WIDTH) as usize; +@@ -156,15 +161,15 @@ const STATE_READY_WIDTH: u8 = 1; + /// + /// Memory layout of C bitfields is implementation defined, so these functions are still + /// unsafe. Users must verify that they work as expected on the architectures they target. ++#[cfg(not(Py_3_14))] + #[repr(C)] +-#[repr(align(4))] + struct PyASCIIObjectState { + bitfield_align: [u8; 0], + bitfield: BitfieldUnit<[u8; 4usize]>, + } + + // c_uint and u32 are not necessarily the same type on all targets / architectures +-#[cfg(not(GraalPy))] ++#[cfg(not(any(GraalPy, Py_3_14)))] + #[allow(clippy::useless_transmute)] + impl PyASCIIObjectState { + #[inline] +@@ -218,6 +223,26 @@ impl PyASCIIObjectState { + .set(STATE_ASCII_INDEX, STATE_ASCII_WIDTH, val as u64) + } + ++ #[cfg(Py_3_12)] ++ #[inline] ++ unsafe fn statically_allocated(&self) -> c_uint { ++ std::mem::transmute(self.bitfield.get( ++ STATE_STATICALLY_ALLOCATED_INDEX, ++ STATE_STATICALLY_ALLOCATED_WIDTH, ++ ) as u32) ++ } ++ ++ #[cfg(Py_3_12)] ++ #[inline] ++ unsafe fn set_statically_allocated(&mut self, val: c_uint) { ++ let val: u32 = std::mem::transmute(val); ++ self.bitfield.set( ++ STATE_STATICALLY_ALLOCATED_INDEX, ++ STATE_STATICALLY_ALLOCATED_WIDTH, ++ val as u64, ++ ) ++ } ++ + #[cfg(not(Py_3_12))] + #[inline] + unsafe fn ready(&self) -> c_uint { +@@ -233,6 +258,7 @@ impl PyASCIIObjectState { + } + } + ++#[cfg(not(Py_3_14))] + impl From for PyASCIIObjectState { + #[inline] + fn from(value: u32) -> Self { +@@ -243,6 +269,7 @@ impl From for PyASCIIObjectState { + } + } + ++#[cfg(not(Py_3_14))] + impl From for u32 { + #[inline] + fn from(value: PyASCIIObjectState) -> Self { +@@ -254,26 +281,36 @@ impl From for u32 { + pub struct PyASCIIObject { + pub ob_base: PyObject, + pub length: Py_ssize_t, +- #[cfg(not(PyPy))] ++ #[cfg(any(Py_3_11, not(PyPy)))] + pub hash: Py_hash_t, + /// A bit field with various properties. + /// + /// Rust doesn't expose bitfields. So we have accessor functions for + /// retrieving values. + /// ++ /// Before 3.12: + /// unsigned int interned:2; // SSTATE_* constants. + /// unsigned int kind:3; // PyUnicode_*_KIND constants. + /// unsigned int compact:1; + /// unsigned int ascii:1; + /// unsigned int ready:1; + /// unsigned int :24; ++ /// ++ /// 3.12, and 3.13 ++ /// unsigned int interned:2; // SSTATE_* constants. ++ /// unsigned int kind:3; // PyUnicode_*_KIND constants. ++ /// unsigned int compact:1; ++ /// unsigned int ascii:1; ++ /// unsigned int statically_allocated:1; ++ /// unsigned int :24; ++ /// on 3.14 and higher PyO3 doesn't access the internal state + pub state: u32, + #[cfg(not(Py_3_12))] + pub wstr: *mut wchar_t, + } + + /// Interacting with the bitfield is not actually well-defined, so we mark these APIs unsafe. +-#[cfg(not(GraalPy))] ++#[cfg(not(any(GraalPy, Py_3_14)))] + impl PyASCIIObject { + #[cfg_attr(not(Py_3_12), allow(rustdoc::broken_intra_doc_links))] // SSTATE_INTERNED_IMMORTAL_STATIC requires 3.12 + /// Get the `interned` field of the [`PyASCIIObject`] state bitfield. +@@ -350,6 +387,7 @@ impl PyASCIIObject { + /// + /// Calling this function with an argument that is neither `0` nor `1` is invalid. + #[inline] ++ #[cfg(not(all(Py_3_14, Py_GIL_DISABLED)))] + pub unsafe fn set_ascii(&mut self, val: c_uint) { + let mut state = PyASCIIObjectState::from(self.state); + state.set_ascii(val); +@@ -375,6 +413,26 @@ impl PyASCIIObject { + state.set_ready(val); + self.state = u32::from(state); + } ++ ++ /// Get the `statically_allocated` field of the [`PyASCIIObject`] state bitfield. ++ /// ++ /// Returns either `0` or `1`. ++ #[inline] ++ #[cfg(Py_3_12)] ++ pub unsafe fn statically_allocated(&self) -> c_uint { ++ PyASCIIObjectState::from(self.state).statically_allocated() ++ } ++ ++ /// Set the `statically_allocated` flag of the [`PyASCIIObject`] state bitfield. ++ /// ++ /// Calling this function with an argument that is neither `0` nor `1` is invalid. ++ #[inline] ++ #[cfg(Py_3_12)] ++ pub unsafe fn set_statically_allocated(&mut self, val: c_uint) { ++ let mut state = PyASCIIObjectState::from(self.state); ++ state.set_statically_allocated(val); ++ self.state = u32::from(state); ++ } + } + + #[repr(C)] +@@ -416,7 +474,7 @@ pub const SSTATE_INTERNED_IMMORTAL: c_uint = 2; + #[cfg(Py_3_12)] + pub const SSTATE_INTERNED_IMMORTAL_STATIC: c_uint = 3; + +-#[cfg(not(GraalPy))] ++#[cfg(not(any(GraalPy, Py_3_14)))] + #[inline] + pub unsafe fn PyUnicode_IS_ASCII(op: *mut PyObject) -> c_uint { + debug_assert!(crate::PyUnicode_Check(op) != 0); +@@ -426,13 +484,13 @@ pub unsafe fn PyUnicode_IS_ASCII(op: *mut PyObject) -> c_uint { + (*(op as *mut PyASCIIObject)).ascii() + } + +-#[cfg(not(GraalPy))] ++#[cfg(not(any(GraalPy, Py_3_14)))] + #[inline] + pub unsafe fn PyUnicode_IS_COMPACT(op: *mut PyObject) -> c_uint { + (*(op as *mut PyASCIIObject)).compact() + } + +-#[cfg(not(GraalPy))] ++#[cfg(not(any(GraalPy, Py_3_14)))] + #[inline] + pub unsafe fn PyUnicode_IS_COMPACT_ASCII(op: *mut PyObject) -> c_uint { + ((*(op as *mut PyASCIIObject)).ascii() != 0 && PyUnicode_IS_COMPACT(op) != 0).into() +@@ -464,7 +522,13 @@ pub unsafe fn PyUnicode_4BYTE_DATA(op: *mut PyObject) -> *mut Py_UCS4 { + PyUnicode_DATA(op) as *mut Py_UCS4 + } + +-#[cfg(not(GraalPy))] ++#[cfg(all(not(GraalPy), Py_3_14))] ++extern "C" { ++ #[cfg_attr(PyPy, link_name = "PyPyUnicode_KIND")] ++ pub fn PyUnicode_KIND(op: *mut PyObject) -> c_uint; ++} ++ ++#[cfg(all(not(GraalPy), not(Py_3_14)))] + #[inline] + pub unsafe fn PyUnicode_KIND(op: *mut PyObject) -> c_uint { + debug_assert!(crate::PyUnicode_Check(op) != 0); +@@ -474,7 +538,7 @@ pub unsafe fn PyUnicode_KIND(op: *mut PyObject) -> c_uint { + (*(op as *mut PyASCIIObject)).kind() + } + +-#[cfg(not(GraalPy))] ++#[cfg(not(any(GraalPy, Py_3_14)))] + #[inline] + pub unsafe fn _PyUnicode_COMPACT_DATA(op: *mut PyObject) -> *mut c_void { + if PyUnicode_IS_ASCII(op) != 0 { +@@ -492,7 +556,7 @@ pub unsafe fn _PyUnicode_NONCOMPACT_DATA(op: *mut PyObject) -> *mut c_void { + (*(op as *mut PyUnicodeObject)).data.any + } + +-#[cfg(not(any(GraalPy, PyPy)))] ++#[cfg(not(any(GraalPy, PyPy, Py_3_14)))] + #[inline] + pub unsafe fn PyUnicode_DATA(op: *mut PyObject) -> *mut c_void { + debug_assert!(crate::PyUnicode_Check(op) != 0); +@@ -504,6 +568,13 @@ pub unsafe fn PyUnicode_DATA(op: *mut PyObject) -> *mut c_void { + } + } + ++#[cfg(Py_3_14)] ++#[cfg(all(not(GraalPy), Py_3_14))] ++extern "C" { ++ #[cfg_attr(PyPy, link_name = "PyPyUnicode_DATA")] ++ pub fn PyUnicode_DATA(op: *mut PyObject) -> *mut c_void; ++} ++ + // skipped PyUnicode_WRITE + // skipped PyUnicode_READ + // skipped PyUnicode_READ_CHAR +diff --git a/include/pyo3/pyo3-ffi/src/cpython/weakrefobject.rs b/include/pyo3/pyo3-ffi/src/cpython/weakrefobject.rs +index 88bb501b..1c50c7a7 100644 +--- a/include/pyo3/pyo3-ffi/src/cpython/weakrefobject.rs ++++ b/include/pyo3/pyo3-ffi/src/cpython/weakrefobject.rs +@@ -1,3 +1,4 @@ ++// NB publicly re-exported in `src/weakrefobject.rs` + #[cfg(not(any(PyPy, GraalPy)))] + pub struct _PyWeakReference { + pub ob_base: crate::PyObject, +diff --git a/include/pyo3/pyo3-ffi/src/datetime.rs b/include/pyo3/pyo3-ffi/src/datetime.rs +index e529e0fc..4db1bdd1 100644 +--- a/include/pyo3/pyo3-ffi/src/datetime.rs ++++ b/include/pyo3/pyo3-ffi/src/datetime.rs +@@ -119,7 +119,7 @@ pub struct PyDateTime_DateTime { + pub unsafe fn PyDateTime_GET_YEAR(o: *mut PyObject) -> c_int { + // This should work for Date or DateTime + let data = (*(o as *mut PyDateTime_Date)).data; +- c_int::from(data[0]) << 8 | c_int::from(data[1]) ++ (c_int::from(data[0]) << 8) | c_int::from(data[1]) + } + + #[inline] +@@ -487,7 +487,9 @@ extern "C" { + pub fn PyDateTime_DATE_GET_MICROSECOND(o: *mut PyObject) -> c_int; + #[link_name = "PyPyDateTime_GET_FOLD"] + pub fn PyDateTime_DATE_GET_FOLD(o: *mut PyObject) -> c_int; +- // skipped PyDateTime_DATE_GET_TZINFO (not in PyPy) ++ #[link_name = "PyPyDateTime_DATE_GET_TZINFO"] ++ #[cfg(Py_3_10)] ++ pub fn PyDateTime_DATE_GET_TZINFO(o: *mut PyObject) -> *mut PyObject; + + #[link_name = "PyPyDateTime_TIME_GET_HOUR"] + pub fn PyDateTime_TIME_GET_HOUR(o: *mut PyObject) -> c_int; +@@ -499,7 +501,9 @@ extern "C" { + pub fn PyDateTime_TIME_GET_MICROSECOND(o: *mut PyObject) -> c_int; + #[link_name = "PyPyDateTime_TIME_GET_FOLD"] + pub fn PyDateTime_TIME_GET_FOLD(o: *mut PyObject) -> c_int; +- // skipped PyDateTime_TIME_GET_TZINFO (not in PyPy) ++ #[link_name = "PyPyDateTime_TIME_GET_TZINFO"] ++ #[cfg(Py_3_10)] ++ pub fn PyDateTime_TIME_GET_TZINFO(o: *mut PyObject) -> *mut PyObject; + + #[link_name = "PyPyDateTime_DELTA_GET_DAYS"] + pub fn PyDateTime_DELTA_GET_DAYS(o: *mut PyObject) -> c_int; +diff --git a/include/pyo3/pyo3-ffi/src/dictobject.rs b/include/pyo3/pyo3-ffi/src/dictobject.rs +index 710be802..e609352a 100644 +--- a/include/pyo3/pyo3-ffi/src/dictobject.rs ++++ b/include/pyo3/pyo3-ffi/src/dictobject.rs +@@ -118,4 +118,4 @@ extern "C" { + + #[cfg(any(PyPy, GraalPy, Py_LIMITED_API))] + // TODO: remove (see https://github.com/PyO3/pyo3/pull/1341#issuecomment-751515985) +-opaque_struct!(PyDictObject); ++opaque_struct!(pub PyDictObject); +diff --git a/include/pyo3/pyo3-ffi/src/floatobject.rs b/include/pyo3/pyo3-ffi/src/floatobject.rs +index 65fc1d4c..4e1d6476 100644 +--- a/include/pyo3/pyo3-ffi/src/floatobject.rs ++++ b/include/pyo3/pyo3-ffi/src/floatobject.rs +@@ -4,7 +4,7 @@ use std::ptr::addr_of_mut; + + #[cfg(Py_LIMITED_API)] + // TODO: remove (see https://github.com/PyO3/pyo3/pull/1341#issuecomment-751515985) +-opaque_struct!(PyFloatObject); ++opaque_struct!(pub PyFloatObject); + + #[cfg_attr(windows, link(name = "pythonXY"))] + extern "C" { +diff --git a/include/pyo3/pyo3-ffi/src/genericaliasobject.rs b/include/pyo3/pyo3-ffi/src/genericaliasobject.rs +new file mode 100644 +index 00000000..7979d7d8 +--- /dev/null ++++ b/include/pyo3/pyo3-ffi/src/genericaliasobject.rs +@@ -0,0 +1,12 @@ ++#[cfg(Py_3_9)] ++use crate::object::{PyObject, PyTypeObject}; ++ ++#[cfg_attr(windows, link(name = "pythonXY"))] ++extern "C" { ++ #[cfg(Py_3_9)] ++ #[cfg_attr(PyPy, link_name = "PyPy_GenericAlias")] ++ pub fn Py_GenericAlias(origin: *mut PyObject, args: *mut PyObject) -> *mut PyObject; ++ ++ #[cfg(Py_3_9)] ++ pub static mut Py_GenericAliasType: PyTypeObject; ++} +diff --git a/include/pyo3/pyo3-ffi/src/lib.rs b/include/pyo3/pyo3-ffi/src/lib.rs +index 7bdba117..f78c918a 100644 +--- a/include/pyo3/pyo3-ffi/src/lib.rs ++++ b/include/pyo3/pyo3-ffi/src/lib.rs +@@ -129,7 +129,7 @@ + //! ``` + //! + //! **`src/lib.rs`** +-//! ```rust ++//! ```rust,no_run + //! use std::os::raw::{c_char, c_long}; + //! use std::ptr; + //! +@@ -327,17 +327,24 @@ + non_snake_case, + non_upper_case_globals, + clippy::upper_case_acronyms, +- clippy::missing_safety_doc ++ clippy::missing_safety_doc, ++ clippy::ptr_eq + )] + #![warn(elided_lifetimes_in_paths, unused_lifetimes)] ++// This crate is a hand-maintained translation of CPython's headers, so requiring "unsafe" ++// blocks within those translations increases maintenance burden without providing any ++// additional safety. The safety of the functions in this crate is determined by the ++// original CPython headers ++#![allow(unsafe_op_in_unsafe_fn)] + + // Until `extern type` is stabilized, use the recommended approach to + // model opaque types: + // https://doc.rust-lang.org/nomicon/ffi.html#representing-opaque-structs + macro_rules! opaque_struct { +- ($name:ident) => { ++ ($(#[$attrs:meta])* $pub:vis $name:ident) => { ++ $(#[$attrs])* + #[repr(C)] +- pub struct $name([u8; 0]); ++ $pub struct $name([u8; 0]); + }; + } + +@@ -351,7 +358,7 @@ macro_rules! opaque_struct { + /// + /// Examples: + /// +-/// ```rust ++/// ```rust,no_run + /// use std::ffi::CStr; + /// + /// const HELLO: &CStr = pyo3_ffi::c_str!("hello"); +@@ -410,6 +417,8 @@ pub use self::enumobject::*; + pub use self::fileobject::*; + pub use self::fileutils::*; + pub use self::floatobject::*; ++#[cfg(Py_3_9)] ++pub use self::genericaliasobject::*; + pub use self::import::*; + pub use self::intrcheck::*; + pub use self::iterobject::*; +@@ -439,6 +448,7 @@ pub use self::pystate::*; + pub use self::pystrtod::*; + pub use self::pythonrun::*; + pub use self::rangeobject::*; ++pub use self::refcount::*; + pub use self::setobject::*; + pub use self::sliceobject::*; + pub use self::structseq::*; +@@ -479,7 +489,7 @@ mod fileobject; + mod fileutils; + mod floatobject; + // skipped empty frameobject.h +-// skipped genericaliasobject.h ++mod genericaliasobject; + mod import; + // skipped interpreteridobject.h + mod intrcheck; +@@ -531,6 +541,7 @@ mod pystrtod; + // skipped pythread.h + // skipped pytime.h + mod rangeobject; ++mod refcount; + mod setobject; + mod sliceobject; + mod structseq; +diff --git a/include/pyo3/pyo3-ffi/src/longobject.rs b/include/pyo3/pyo3-ffi/src/longobject.rs +index 68b4ecba..eca0af3d 100644 +--- a/include/pyo3/pyo3-ffi/src/longobject.rs ++++ b/include/pyo3/pyo3-ffi/src/longobject.rs +@@ -4,7 +4,7 @@ use libc::size_t; + 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; + +-opaque_struct!(PyLongObject); ++opaque_struct!(pub PyLongObject); + + #[inline] + pub unsafe fn PyLong_Check(op: *mut PyObject) -> c_int { +diff --git a/include/pyo3/pyo3-ffi/src/memoryobject.rs b/include/pyo3/pyo3-ffi/src/memoryobject.rs +index b7ef9e2e..4e1e50c6 100644 +--- a/include/pyo3/pyo3-ffi/src/memoryobject.rs ++++ b/include/pyo3/pyo3-ffi/src/memoryobject.rs +@@ -3,11 +3,10 @@ use crate::pyport::Py_ssize_t; + use std::os::raw::{c_char, c_int}; + use std::ptr::addr_of_mut; + ++// skipped _PyManagedBuffer_Type ++ + #[cfg_attr(windows, link(name = "pythonXY"))] + extern "C" { +- #[cfg(not(Py_LIMITED_API))] +- pub static mut _PyManagedBuffer_Type: PyTypeObject; +- + #[cfg_attr(PyPy, link_name = "PyPyMemoryView_Type")] + pub static mut PyMemoryView_Type: PyTypeObject; + } +diff --git a/include/pyo3/pyo3-ffi/src/methodobject.rs b/include/pyo3/pyo3-ffi/src/methodobject.rs +index 37e1e206..3dfbbb5a 100644 +--- a/include/pyo3/pyo3-ffi/src/methodobject.rs ++++ b/include/pyo3/pyo3-ffi/src/methodobject.rs +@@ -50,7 +50,7 @@ pub type PyCFunctionFast = unsafe extern "C" fn( + ) -> *mut PyObject; + + #[cfg(any(Py_3_10, not(Py_LIMITED_API)))] +-#[cfg_attr(Py_3_10, deprecated(note = "renamed to `PyCFunctionFast`"))] ++#[deprecated(note = "renamed to `PyCFunctionFast`")] + pub type _PyCFunctionFast = PyCFunctionFast; + + pub type PyCFunctionWithKeywords = unsafe extern "C" fn( +@@ -68,7 +68,7 @@ pub type PyCFunctionFastWithKeywords = unsafe extern "C" fn( + ) -> *mut PyObject; + + #[cfg(any(Py_3_10, not(Py_LIMITED_API)))] +-#[cfg_attr(Py_3_10, deprecated(note = "renamed to `PyCFunctionFastWithKeywords`"))] ++#[deprecated(note = "renamed to `PyCFunctionFastWithKeywords`")] + pub type _PyCFunctionFastWithKeywords = PyCFunctionFastWithKeywords; + + #[cfg(all(Py_3_9, not(Py_LIMITED_API)))] +@@ -153,7 +153,7 @@ pub union PyMethodDefPointer { + + /// This variant corresponds with [`METH_FASTCALL`]. + #[cfg(any(Py_3_10, not(Py_LIMITED_API)))] +- #[cfg_attr(Py_3_10, deprecated(note = "renamed to `PyCFunctionFast`"))] ++ #[deprecated(note = "renamed to `PyCFunctionFast`")] + pub _PyCFunctionFast: PyCFunctionFast, + + /// This variant corresponds with [`METH_FASTCALL`]. +@@ -162,7 +162,7 @@ pub union PyMethodDefPointer { + + /// This variant corresponds with [`METH_FASTCALL`] | [`METH_KEYWORDS`]. + #[cfg(any(Py_3_10, not(Py_LIMITED_API)))] +- #[cfg_attr(Py_3_10, deprecated(note = "renamed to `PyCFunctionFastWithKeywords`"))] ++ #[deprecated(note = "renamed to `PyCFunctionFastWithKeywords`")] + pub _PyCFunctionFastWithKeywords: PyCFunctionFastWithKeywords, + + /// This variant corresponds with [`METH_FASTCALL`] | [`METH_KEYWORDS`]. +diff --git a/include/pyo3/pyo3-ffi/src/modsupport.rs b/include/pyo3/pyo3-ffi/src/modsupport.rs +index 6da2795b..56a68fe2 100644 +--- a/include/pyo3/pyo3-ffi/src/modsupport.rs ++++ b/include/pyo3/pyo3-ffi/src/modsupport.rs +@@ -37,12 +37,7 @@ extern "C" { + // skipped Py_VaBuildValue + + #[cfg(Py_3_13)] +- pub fn PyModule_Add( +- module: *mut PyObject, +- name: *const c_char, +- value: *mut PyObject, +- ) -> core::ffi::c_int; +- ++ pub fn PyModule_Add(module: *mut PyObject, name: *const c_char, value: *mut PyObject) -> c_int; + #[cfg(Py_3_10)] + #[cfg_attr(PyPy, link_name = "PyPyModule_AddObjectRef")] + pub fn PyModule_AddObjectRef( +diff --git a/include/pyo3/pyo3-ffi/src/object.rs b/include/pyo3/pyo3-ffi/src/object.rs +index 51083a8e..5fbf45db 100644 +--- a/include/pyo3/pyo3-ffi/src/object.rs ++++ b/include/pyo3/pyo3-ffi/src/object.rs +@@ -1,5 +1,7 @@ + use crate::pyport::{Py_hash_t, Py_ssize_t}; + #[cfg(Py_GIL_DISABLED)] ++use crate::refcount; ++#[cfg(Py_GIL_DISABLED)] + use crate::PyMutex; + #[cfg(Py_GIL_DISABLED)] + use std::marker::PhantomPinned; +@@ -7,81 +9,50 @@ use std::mem; + use std::os::raw::{c_char, c_int, c_uint, c_ulong, c_void}; + use std::ptr; + #[cfg(Py_GIL_DISABLED)] +-use std::sync::atomic::{AtomicIsize, AtomicU32, AtomicU8, Ordering::Relaxed}; ++use std::sync::atomic::{AtomicIsize, AtomicU32, AtomicU8}; + + #[cfg(Py_LIMITED_API)] +-opaque_struct!(PyTypeObject); ++opaque_struct!(pub PyTypeObject); + + #[cfg(not(Py_LIMITED_API))] + pub use crate::cpython::object::PyTypeObject; + +-#[cfg(Py_3_12)] +-const _Py_IMMORTAL_REFCNT: Py_ssize_t = { +- if cfg!(target_pointer_width = "64") { +- c_uint::MAX as Py_ssize_t +- } else { +- // for 32-bit systems, use the lower 30 bits (see comment in CPython's object.h) +- (c_uint::MAX >> 2) as Py_ssize_t +- } +-}; +- +-#[cfg(Py_GIL_DISABLED)] +-const _Py_IMMORTAL_REFCNT_LOCAL: u32 = u32::MAX; +- +-#[allow(clippy::declare_interior_mutable_const)] +-pub const PyObject_HEAD_INIT: PyObject = PyObject { +- #[cfg(py_sys_config = "Py_TRACE_REFS")] +- _ob_next: std::ptr::null_mut(), +- #[cfg(py_sys_config = "Py_TRACE_REFS")] +- _ob_prev: std::ptr::null_mut(), +- #[cfg(Py_GIL_DISABLED)] +- ob_tid: 0, +- #[cfg(Py_GIL_DISABLED)] +- _padding: 0, +- #[cfg(Py_GIL_DISABLED)] +- ob_mutex: PyMutex { +- _bits: AtomicU8::new(0), +- _pin: PhantomPinned, +- }, +- #[cfg(Py_GIL_DISABLED)] +- ob_gc_bits: 0, +- #[cfg(Py_GIL_DISABLED)] +- ob_ref_local: AtomicU32::new(_Py_IMMORTAL_REFCNT_LOCAL), +- #[cfg(Py_GIL_DISABLED)] +- ob_ref_shared: AtomicIsize::new(0), +- #[cfg(all(not(Py_GIL_DISABLED), Py_3_12))] +- ob_refcnt: PyObjectObRefcnt { ob_refcnt: 1 }, +- #[cfg(not(Py_3_12))] +- ob_refcnt: 1, +- #[cfg(PyPy)] +- ob_pypy_link: 0, +- ob_type: std::ptr::null_mut(), +-}; +- +-// skipped PyObject_VAR_HEAD +-// skipped Py_INVALID_SIZE +- +-// skipped private _Py_UNOWNED_TID ++// skip PyObject_HEAD + +-#[cfg(Py_GIL_DISABLED)] +-const _Py_REF_SHARED_SHIFT: isize = 2; +-// skipped private _Py_REF_SHARED_FLAG_MASK +- +-// skipped private _Py_REF_SHARED_INIT +-// skipped private _Py_REF_MAYBE_WEAKREF +-// skipped private _Py_REF_QUEUED +-// skipped private _Py_REF_MERGED ++#[repr(C)] ++#[derive(Copy, Clone)] ++#[cfg(all(Py_3_14, not(Py_GIL_DISABLED), target_endian = "big"))] ++/// This struct is anonymous in CPython, so the name was given by PyO3 because ++/// Rust structs need a name. ++pub struct PyObjectObFlagsAndRefcnt { ++ pub ob_flags: u16, ++ pub ob_overflow: u16, ++ pub ob_refcnt: u32, ++} + +-// skipped private _Py_REF_SHARED ++#[repr(C)] ++#[derive(Copy, Clone)] ++#[cfg(all(Py_3_14, not(Py_GIL_DISABLED), target_endian = "little"))] ++/// This struct is anonymous in CPython, so the name was given by PyO3 because ++/// Rust structs need a name. ++pub struct PyObjectObFlagsAndRefcnt { ++ pub ob_refcnt: u32, ++ pub ob_overflow: u16, ++ pub ob_flags: u16, ++} + + #[repr(C)] + #[derive(Copy, Clone)] + #[cfg(all(Py_3_12, not(Py_GIL_DISABLED)))] + /// This union is anonymous in CPython, so the name was given by PyO3 because +-/// Rust unions need a name. ++/// Rust union need a name. + pub union PyObjectObRefcnt { ++ #[cfg(all(target_pointer_width = "64", Py_3_14))] ++ pub ob_refcnt_full: crate::PY_INT64_T, ++ #[cfg(Py_3_14)] ++ pub refcnt_and_flags: PyObjectObFlagsAndRefcnt, + pub ob_refcnt: Py_ssize_t, +- #[cfg(target_pointer_width = "64")] ++ #[cfg(all(target_pointer_width = "64", not(Py_3_14)))] + pub ob_refcnt_split: [crate::PY_UINT32_T; 2], + } + +@@ -95,6 +66,9 @@ impl std::fmt::Debug for PyObjectObRefcnt { + #[cfg(all(not(Py_3_12), not(Py_GIL_DISABLED)))] + pub type PyObjectObRefcnt = Py_ssize_t; + ++// PyObject_HEAD_INIT comes before the PyObject definition in object.h ++// but we put it after PyObject because HEAD_INIT uses PyObject ++ + #[repr(C)] + #[derive(Debug)] + pub struct PyObject { +@@ -104,8 +78,10 @@ pub struct PyObject { + pub _ob_prev: *mut PyObject, + #[cfg(Py_GIL_DISABLED)] + pub ob_tid: libc::uintptr_t, +- #[cfg(Py_GIL_DISABLED)] ++ #[cfg(all(Py_GIL_DISABLED, not(Py_3_14)))] + pub _padding: u16, ++ #[cfg(all(Py_GIL_DISABLED, Py_3_14))] ++ pub ob_flags: u16, + #[cfg(Py_GIL_DISABLED)] + pub ob_mutex: PyMutex, // per-object lock + #[cfg(Py_GIL_DISABLED)] +@@ -121,7 +97,41 @@ pub struct PyObject { + pub ob_type: *mut PyTypeObject, + } + +-// skipped private _PyObject_CAST ++#[allow(clippy::declare_interior_mutable_const)] ++pub const PyObject_HEAD_INIT: PyObject = PyObject { ++ #[cfg(py_sys_config = "Py_TRACE_REFS")] ++ _ob_next: std::ptr::null_mut(), ++ #[cfg(py_sys_config = "Py_TRACE_REFS")] ++ _ob_prev: std::ptr::null_mut(), ++ #[cfg(Py_GIL_DISABLED)] ++ ob_tid: 0, ++ #[cfg(all(Py_GIL_DISABLED, Py_3_14))] ++ ob_flags: 0, ++ #[cfg(all(Py_GIL_DISABLED, not(Py_3_14)))] ++ _padding: 0, ++ #[cfg(Py_GIL_DISABLED)] ++ ob_mutex: PyMutex { ++ _bits: AtomicU8::new(0), ++ _pin: PhantomPinned, ++ }, ++ #[cfg(Py_GIL_DISABLED)] ++ ob_gc_bits: 0, ++ #[cfg(Py_GIL_DISABLED)] ++ ob_ref_local: AtomicU32::new(refcount::_Py_IMMORTAL_REFCNT_LOCAL), ++ #[cfg(Py_GIL_DISABLED)] ++ ob_ref_shared: AtomicIsize::new(0), ++ #[cfg(all(not(Py_GIL_DISABLED), Py_3_12))] ++ ob_refcnt: PyObjectObRefcnt { ob_refcnt: 1 }, ++ #[cfg(not(Py_3_12))] ++ ob_refcnt: 1, ++ #[cfg(PyPy)] ++ ob_pypy_link: 0, ++ ob_type: std::ptr::null_mut(), ++}; ++ ++// skipped _Py_UNOWNED_TID ++ ++// skipped _PyObject_CAST + + #[repr(C)] + #[derive(Debug)] +@@ -150,41 +160,23 @@ extern "C" { + pub fn Py_Is(x: *mut PyObject, y: *mut PyObject) -> c_int; + } + +-// skipped private _Py_GetThreadLocal_Addr ++// skipped _Py_GetThreadLocal_Addr + +-// skipped private _Py_ThreadId ++// skipped _Py_ThreadID + +-// skipped private _Py_IsOwnedByCurrentThread ++// skipped _Py_IsOwnedByCurrentThread + +-#[inline] +-pub unsafe fn Py_REFCNT(ob: *mut PyObject) -> Py_ssize_t { +- #[cfg(Py_GIL_DISABLED)] +- { +- let local = (*ob).ob_ref_local.load(Relaxed); +- if local == _Py_IMMORTAL_REFCNT_LOCAL { +- return _Py_IMMORTAL_REFCNT; +- } +- let shared = (*ob).ob_ref_shared.load(Relaxed); +- local as Py_ssize_t + Py_ssize_t::from(shared >> _Py_REF_SHARED_SHIFT) +- } +- +- #[cfg(all(not(Py_GIL_DISABLED), Py_3_12))] +- { +- (*ob).ob_refcnt.ob_refcnt +- } +- +- #[cfg(all(not(Py_GIL_DISABLED), not(Py_3_12), not(GraalPy)))] +- { +- (*ob).ob_refcnt +- } ++#[cfg(GraalPy)] ++extern "C" { ++ #[cfg(GraalPy)] ++ fn _Py_TYPE(arg1: *const PyObject) -> *mut PyTypeObject; + +- #[cfg(all(not(Py_GIL_DISABLED), not(Py_3_12), GraalPy))] +- { +- _Py_REFCNT(ob) +- } ++ #[cfg(GraalPy)] ++ fn _Py_SIZE(arg1: *const PyObject) -> Py_ssize_t; + } + + #[inline] ++#[cfg(not(Py_3_14))] + pub unsafe fn Py_TYPE(ob: *mut PyObject) -> *mut PyTypeObject { + #[cfg(not(GraalPy))] + return (*ob).ob_type; +@@ -192,6 +184,15 @@ pub unsafe fn Py_TYPE(ob: *mut PyObject) -> *mut PyTypeObject { + return _Py_TYPE(ob); + } + ++#[cfg_attr(windows, link(name = "pythonXY"))] ++#[cfg(Py_3_14)] ++extern "C" { ++ #[cfg_attr(PyPy, link_name = "PyPy_TYPE")] ++ pub fn Py_TYPE(ob: *mut PyObject) -> *mut PyTypeObject; ++} ++ ++// skip _Py_TYPE compat shim ++ + #[cfg_attr(windows, link(name = "pythonXY"))] + extern "C" { + #[cfg_attr(PyPy, link_name = "PyPyLong_Type")] +@@ -212,29 +213,11 @@ pub unsafe fn Py_SIZE(ob: *mut PyObject) -> Py_ssize_t { + _Py_SIZE(ob) + } + +-#[inline(always)] +-#[cfg(all(Py_3_12, not(Py_GIL_DISABLED)))] +-pub unsafe fn _Py_IsImmortal(op: *mut PyObject) -> c_int { +- #[cfg(target_pointer_width = "64")] +- { +- (((*op).ob_refcnt.ob_refcnt as crate::PY_INT32_T) < 0) as c_int +- } +- +- #[cfg(target_pointer_width = "32")] +- { +- ((*op).ob_refcnt.ob_refcnt == _Py_IMMORTAL_REFCNT) as c_int +- } +-} +- + #[inline] + pub unsafe fn Py_IS_TYPE(ob: *mut PyObject, tp: *mut PyTypeObject) -> c_int { + (Py_TYPE(ob) == tp) as c_int + } + +-// skipped _Py_SetRefCnt +- +-// skipped Py_SET_REFCNT +- + // skipped Py_SET_TYPE + + // skipped Py_SET_SIZE +@@ -436,7 +419,7 @@ extern "C" { + arg2: *const c_char, + arg3: *mut PyObject, + ) -> c_int; +- #[cfg(any(Py_3_13, PyPy))] // CPython defined in 3.12 as an inline function in abstract.h ++ #[cfg(any(Py_3_13, all(PyPy, not(Py_3_11))))] // CPython defined in 3.12 as an inline function in abstract.h + #[cfg_attr(PyPy, link_name = "PyPyObject_DelAttrString")] + pub fn PyObject_DelAttrString(arg1: *mut PyObject, arg2: *const c_char) -> c_int; + #[cfg_attr(PyPy, link_name = "PyPyObject_HasAttrString")] +@@ -460,7 +443,7 @@ extern "C" { + #[cfg_attr(PyPy, link_name = "PyPyObject_SetAttr")] + pub fn PyObject_SetAttr(arg1: *mut PyObject, arg2: *mut PyObject, arg3: *mut PyObject) + -> c_int; +- #[cfg(any(Py_3_13, PyPy))] // CPython defined in 3.12 as an inline function in abstract.h ++ #[cfg(any(Py_3_13, all(PyPy, not(Py_3_11))))] // CPython defined in 3.12 as an inline function in abstract.h + #[cfg_attr(PyPy, link_name = "PyPyObject_DelAttr")] + pub fn PyObject_DelAttr(arg1: *mut PyObject, arg2: *mut PyObject) -> c_int; + #[cfg_attr(PyPy, link_name = "PyPyObject_HasAttr")] +@@ -586,222 +569,6 @@ pub const Py_TPFLAGS_DEFAULT: c_ulong = if cfg!(Py_3_10) { + pub const Py_TPFLAGS_HAVE_FINALIZE: c_ulong = 1; + pub const Py_TPFLAGS_HAVE_VERSION_TAG: c_ulong = 1 << 18; + +-extern "C" { +- #[cfg(all(py_sys_config = "Py_REF_DEBUG", not(Py_LIMITED_API)))] +- fn _Py_NegativeRefcount(filename: *const c_char, lineno: c_int, op: *mut PyObject); +- #[cfg(all(Py_3_12, py_sys_config = "Py_REF_DEBUG", not(Py_LIMITED_API)))] +- fn _Py_INCREF_IncRefTotal(); +- #[cfg(all(Py_3_12, py_sys_config = "Py_REF_DEBUG", not(Py_LIMITED_API)))] +- fn _Py_DECREF_DecRefTotal(); +- +- #[cfg_attr(PyPy, link_name = "_PyPy_Dealloc")] +- fn _Py_Dealloc(arg1: *mut PyObject); +- +- #[cfg_attr(PyPy, link_name = "PyPy_IncRef")] +- #[cfg_attr(GraalPy, link_name = "_Py_IncRef")] +- pub fn Py_IncRef(o: *mut PyObject); +- #[cfg_attr(PyPy, link_name = "PyPy_DecRef")] +- #[cfg_attr(GraalPy, link_name = "_Py_DecRef")] +- pub fn Py_DecRef(o: *mut PyObject); +- +- #[cfg(all(Py_3_10, not(PyPy)))] +- fn _Py_IncRef(o: *mut PyObject); +- #[cfg(all(Py_3_10, not(PyPy)))] +- fn _Py_DecRef(o: *mut PyObject); +- +- #[cfg(GraalPy)] +- fn _Py_REFCNT(arg1: *const PyObject) -> Py_ssize_t; +- +- #[cfg(GraalPy)] +- fn _Py_TYPE(arg1: *const PyObject) -> *mut PyTypeObject; +- +- #[cfg(GraalPy)] +- fn _Py_SIZE(arg1: *const PyObject) -> Py_ssize_t; +-} +- +-#[inline(always)] +-pub unsafe fn Py_INCREF(op: *mut PyObject) { +- // On limited API, the free-threaded build, or with refcount debugging, let the interpreter do refcounting +- // TODO: reimplement the logic in the header in the free-threaded build, for a little bit of performance. +- #[cfg(any( +- Py_GIL_DISABLED, +- Py_LIMITED_API, +- py_sys_config = "Py_REF_DEBUG", +- GraalPy +- ))] +- { +- // _Py_IncRef was added to the ABI in 3.10; skips null checks +- #[cfg(all(Py_3_10, not(PyPy)))] +- { +- _Py_IncRef(op); +- } +- +- #[cfg(any(not(Py_3_10), PyPy))] +- { +- Py_IncRef(op); +- } +- } +- +- // version-specific builds are allowed to directly manipulate the reference count +- #[cfg(not(any( +- Py_GIL_DISABLED, +- Py_LIMITED_API, +- py_sys_config = "Py_REF_DEBUG", +- GraalPy +- )))] +- { +- #[cfg(all(Py_3_12, target_pointer_width = "64"))] +- { +- let cur_refcnt = (*op).ob_refcnt.ob_refcnt_split[crate::PY_BIG_ENDIAN]; +- let new_refcnt = cur_refcnt.wrapping_add(1); +- if new_refcnt == 0 { +- return; +- } +- (*op).ob_refcnt.ob_refcnt_split[crate::PY_BIG_ENDIAN] = new_refcnt; +- } +- +- #[cfg(all(Py_3_12, target_pointer_width = "32"))] +- { +- if _Py_IsImmortal(op) != 0 { +- return; +- } +- (*op).ob_refcnt.ob_refcnt += 1 +- } +- +- #[cfg(not(Py_3_12))] +- { +- (*op).ob_refcnt += 1 +- } +- +- // Skipped _Py_INCREF_STAT_INC - if anyone wants this, please file an issue +- // or submit a PR supporting Py_STATS build option and pystats.h +- } +-} +- +-#[inline(always)] +-#[cfg_attr( +- all(py_sys_config = "Py_REF_DEBUG", Py_3_12, not(Py_LIMITED_API)), +- track_caller +-)] +-pub unsafe fn Py_DECREF(op: *mut PyObject) { +- // On limited API, the free-threaded build, or with refcount debugging, let the interpreter do refcounting +- // On 3.12+ we implement refcount debugging to get better assertion locations on negative refcounts +- // TODO: reimplement the logic in the header in the free-threaded build, for a little bit of performance. +- #[cfg(any( +- Py_GIL_DISABLED, +- Py_LIMITED_API, +- all(py_sys_config = "Py_REF_DEBUG", not(Py_3_12)), +- GraalPy +- ))] +- { +- // _Py_DecRef was added to the ABI in 3.10; skips null checks +- #[cfg(all(Py_3_10, not(PyPy)))] +- { +- _Py_DecRef(op); +- } +- +- #[cfg(any(not(Py_3_10), PyPy))] +- { +- Py_DecRef(op); +- } +- } +- +- #[cfg(not(any( +- Py_GIL_DISABLED, +- Py_LIMITED_API, +- all(py_sys_config = "Py_REF_DEBUG", not(Py_3_12)), +- GraalPy +- )))] +- { +- #[cfg(Py_3_12)] +- if _Py_IsImmortal(op) != 0 { +- return; +- } +- +- // Skipped _Py_DECREF_STAT_INC - if anyone needs this, please file an issue +- // or submit a PR supporting Py_STATS build option and pystats.h +- +- #[cfg(py_sys_config = "Py_REF_DEBUG")] +- _Py_DECREF_DecRefTotal(); +- +- #[cfg(Py_3_12)] +- { +- (*op).ob_refcnt.ob_refcnt -= 1; +- +- #[cfg(py_sys_config = "Py_REF_DEBUG")] +- if (*op).ob_refcnt.ob_refcnt < 0 { +- let location = std::panic::Location::caller(); +- let filename = std::ffi::CString::new(location.file()).unwrap(); +- _Py_NegativeRefcount(filename.as_ptr(), location.line() as i32, op); +- } +- +- if (*op).ob_refcnt.ob_refcnt == 0 { +- _Py_Dealloc(op); +- } +- } +- +- #[cfg(not(Py_3_12))] +- { +- (*op).ob_refcnt -= 1; +- +- if (*op).ob_refcnt == 0 { +- _Py_Dealloc(op); +- } +- } +- } +-} +- +-#[inline] +-pub unsafe fn Py_CLEAR(op: *mut *mut PyObject) { +- let tmp = *op; +- if !tmp.is_null() { +- *op = ptr::null_mut(); +- Py_DECREF(tmp); +- } +-} +- +-#[inline] +-pub unsafe fn Py_XINCREF(op: *mut PyObject) { +- if !op.is_null() { +- Py_INCREF(op) +- } +-} +- +-#[inline] +-pub unsafe fn Py_XDECREF(op: *mut PyObject) { +- if !op.is_null() { +- Py_DECREF(op) +- } +-} +- +-extern "C" { +- #[cfg(all(Py_3_10, Py_LIMITED_API, not(PyPy)))] +- #[cfg_attr(docsrs, doc(cfg(Py_3_10)))] +- pub fn Py_NewRef(obj: *mut PyObject) -> *mut PyObject; +- #[cfg(all(Py_3_10, Py_LIMITED_API, not(PyPy)))] +- #[cfg_attr(docsrs, doc(cfg(Py_3_10)))] +- pub fn Py_XNewRef(obj: *mut PyObject) -> *mut PyObject; +-} +- +-// macro _Py_NewRef not public; reimplemented directly inside Py_NewRef here +-// macro _Py_XNewRef not public; reimplemented directly inside Py_XNewRef here +- +-#[cfg(all(Py_3_10, any(not(Py_LIMITED_API), PyPy)))] +-#[cfg_attr(docsrs, doc(cfg(Py_3_10)))] +-#[inline] +-pub unsafe fn Py_NewRef(obj: *mut PyObject) -> *mut PyObject { +- Py_INCREF(obj); +- obj +-} +- +-#[cfg(all(Py_3_10, any(not(Py_LIMITED_API), PyPy)))] +-#[cfg_attr(docsrs, doc(cfg(Py_3_10)))] +-#[inline] +-pub unsafe fn Py_XNewRef(obj: *mut PyObject) -> *mut PyObject { +- Py_XINCREF(obj); +- obj +-} +- + #[cfg(Py_3_13)] + pub const Py_CONSTANT_NONE: c_uint = 0; + #[cfg(Py_3_13)] +@@ -942,4 +709,7 @@ extern "C" { + arg1: *mut crate::PyTypeObject, + arg2: *mut crate::PyModuleDef, + ) -> *mut PyObject; ++ ++ #[cfg(Py_3_14)] ++ pub fn PyType_Freeze(tp: *mut crate::PyTypeObject) -> c_int; + } +diff --git a/include/pyo3/pyo3-ffi/src/pyarena.rs b/include/pyo3/pyo3-ffi/src/pyarena.rs +index 87d5f28a..1200de3d 100644 +--- a/include/pyo3/pyo3-ffi/src/pyarena.rs ++++ b/include/pyo3/pyo3-ffi/src/pyarena.rs +@@ -1 +1 @@ +-opaque_struct!(PyArena); ++opaque_struct!(pub PyArena); +diff --git a/include/pyo3/pyo3-ffi/src/pybuffer.rs b/include/pyo3/pyo3-ffi/src/pybuffer.rs +index 50bf4e61..de706759 100644 +--- a/include/pyo3/pyo3-ffi/src/pybuffer.rs ++++ b/include/pyo3/pyo3-ffi/src/pybuffer.rs +@@ -103,7 +103,11 @@ extern "C" { + } + + /// Maximum number of dimensions +-pub const PyBUF_MAX_NDIM: c_int = if cfg!(PyPy) { 36 } else { 64 }; ++pub const PyBUF_MAX_NDIM: usize = if cfg!(all(PyPy, not(Py_3_11))) { ++ 36 ++} else { ++ 64 ++}; + + /* Flags for getting buffers */ + pub const PyBUF_SIMPLE: c_int = 0; +diff --git a/include/pyo3/pyo3-ffi/src/pyerrors.rs b/include/pyo3/pyo3-ffi/src/pyerrors.rs +index 6c9313c4..d341239a 100644 +--- a/include/pyo3/pyo3-ffi/src/pyerrors.rs ++++ b/include/pyo3/pyo3-ffi/src/pyerrors.rs +@@ -116,6 +116,7 @@ extern "C" { + #[cfg_attr(PyPy, link_name = "PyPyExc_BaseException")] + pub static mut PyExc_BaseException: *mut PyObject; + #[cfg(Py_3_11)] ++ #[cfg_attr(PyPy, link_name = "PyPyExc_BaseExceptionGroup")] + pub static mut PyExc_BaseExceptionGroup: *mut PyObject; + #[cfg_attr(PyPy, link_name = "PyPyExc_Exception")] + pub static mut PyExc_Exception: *mut PyObject; +diff --git a/include/pyo3/pyo3-ffi/src/pyframe.rs b/include/pyo3/pyo3-ffi/src/pyframe.rs +index 4dd3d2b3..1693b20b 100644 +--- a/include/pyo3/pyo3-ffi/src/pyframe.rs ++++ b/include/pyo3/pyo3-ffi/src/pyframe.rs +@@ -6,7 +6,7 @@ use crate::PyFrameObject; + use std::os::raw::c_int; + + #[cfg(Py_LIMITED_API)] +-opaque_struct!(PyFrameObject); ++opaque_struct!(pub PyFrameObject); + + extern "C" { + pub fn PyFrame_GetLineNumber(f: *mut PyFrameObject) -> c_int; +diff --git a/include/pyo3/pyo3-ffi/src/pyhash.rs b/include/pyo3/pyo3-ffi/src/pyhash.rs +index 8b0b6771..074278dd 100644 +--- a/include/pyo3/pyo3-ffi/src/pyhash.rs ++++ b/include/pyo3/pyo3-ffi/src/pyhash.rs +@@ -1,7 +1,5 @@ + #[cfg(not(any(Py_LIMITED_API, PyPy)))] + use crate::pyport::{Py_hash_t, Py_ssize_t}; +-#[cfg(not(any(Py_LIMITED_API, PyPy, GraalPy)))] +-use std::os::raw::c_char; + #[cfg(not(any(Py_LIMITED_API, PyPy)))] + use std::os::raw::c_void; + +@@ -12,11 +10,8 @@ extern "C" { + // skipped non-limited _Py_HashPointer + // skipped non-limited _Py_HashPointerRaw + +- #[cfg(not(all(Py_3_14, any(Py_LIMITED_API, PyPy))))] ++ #[cfg(not(any(Py_LIMITED_API, PyPy)))] + pub fn _Py_HashBytes(src: *const c_void, len: Py_ssize_t) -> Py_hash_t; +- +- #[cfg(Py_3_14)] +- pub fn Py_HashBuffer(ptr: *const c_void, len: Py_ssize_t) -> Py_hash_t; + } + + pub const _PyHASH_MULTIPLIER: c_ulong = 1000003; +@@ -25,29 +20,6 @@ pub const _PyHASH_MULTIPLIER: c_ulong = 1000003; + + // skipped non-limited _Py_HashSecret_t + +-#[cfg(not(any(Py_LIMITED_API, PyPy, GraalPy)))] +-#[repr(C)] +-#[derive(Copy, Clone)] +-pub struct PyHash_FuncDef { +- pub hash: Option Py_hash_t>, +- pub name: *const c_char, +- pub hash_bits: c_int, +- pub seed_bits: c_int, +-} +- +-#[cfg(not(any(Py_LIMITED_API, PyPy, GraalPy)))] +-impl Default for PyHash_FuncDef { +- #[inline] +- fn default() -> Self { +- unsafe { std::mem::zeroed() } +- } +-} +- +-extern "C" { +- #[cfg(not(any(Py_LIMITED_API, PyPy, GraalPy)))] +- pub fn PyHash_GetFuncDef() -> *mut PyHash_FuncDef; +-} +- + // skipped Py_HASH_CUTOFF + + pub const Py_HASH_EXTERNAL: c_int = 0; +diff --git a/include/pyo3/pyo3-ffi/src/pyport.rs b/include/pyo3/pyo3-ffi/src/pyport.rs +index a144c67f..e524831d 100644 +--- a/include/pyo3/pyo3-ffi/src/pyport.rs ++++ b/include/pyo3/pyo3-ffi/src/pyport.rs +@@ -1,3 +1,8 @@ ++// NB libc does not define this constant on all platforms, so we hard code it ++// like CPython does. ++// https://github.com/python/cpython/blob/d8b9011702443bb57579f8834f3effe58e290dfc/Include/pyport.h#L372 ++pub const INT_MAX: std::os::raw::c_int = 2147483647; ++ + pub type PY_UINT32_T = u32; + pub type PY_UINT64_T = u64; + +@@ -11,8 +16,8 @@ pub type Py_ssize_t = ::libc::ssize_t; + pub type Py_hash_t = Py_ssize_t; + pub type Py_uhash_t = ::libc::size_t; + +-pub const PY_SSIZE_T_MIN: Py_ssize_t = isize::MIN as Py_ssize_t; +-pub const PY_SSIZE_T_MAX: Py_ssize_t = isize::MAX as Py_ssize_t; ++pub const PY_SSIZE_T_MIN: Py_ssize_t = Py_ssize_t::MIN; ++pub const PY_SSIZE_T_MAX: Py_ssize_t = Py_ssize_t::MAX; + + #[cfg(target_endian = "big")] + pub const PY_BIG_ENDIAN: usize = 1; +diff --git a/include/pyo3/pyo3-ffi/src/pystate.rs b/include/pyo3/pyo3-ffi/src/pystate.rs +index 23aeea3a..cc16e554 100644 +--- a/include/pyo3/pyo3-ffi/src/pystate.rs ++++ b/include/pyo3/pyo3-ffi/src/pystate.rs +@@ -1,3 +1,5 @@ ++#[cfg(all(Py_3_10, not(PyPy), not(Py_LIMITED_API)))] ++use crate::frameobject::PyFrameObject; + use crate::moduleobject::PyModuleDef; + use crate::object::PyObject; + use std::os::raw::c_int; +@@ -7,8 +9,8 @@ use std::os::raw::c_long; + + pub const MAX_CO_EXTRA_USERS: c_int = 255; + +-opaque_struct!(PyThreadState); +-opaque_struct!(PyInterpreterState); ++opaque_struct!(pub PyThreadState); ++opaque_struct!(pub PyInterpreterState); + + extern "C" { + #[cfg(not(PyPy))] +@@ -63,9 +65,14 @@ extern "C" { + } + + // skipped non-limited / 3.9 PyThreadState_GetInterpreter +-// skipped non-limited / 3.9 PyThreadState_GetFrame + // skipped non-limited / 3.9 PyThreadState_GetID + ++extern "C" { ++ // PyThreadState_GetFrame ++ #[cfg(all(Py_3_10, not(PyPy), not(Py_LIMITED_API)))] ++ pub fn PyThreadState_GetFrame(arg1: *mut PyThreadState) -> *mut PyFrameObject; ++} ++ + #[repr(C)] + #[derive(Copy, Clone, Debug, PartialEq, Eq)] + pub enum PyGILState_STATE { +@@ -73,9 +80,70 @@ pub enum PyGILState_STATE { + PyGILState_UNLOCKED, + } + ++#[cfg(not(Py_3_14))] ++struct HangThread; ++ ++#[cfg(not(Py_3_14))] ++impl Drop for HangThread { ++ fn drop(&mut self) { ++ loop { ++ std::thread::park(); // Block forever. ++ } ++ } ++} ++ ++// The PyGILState_Ensure function will call pthread_exit during interpreter shutdown, ++// which causes undefined behavior. Redirect to the "safe" version that hangs instead, ++// as Python 3.14 does. ++// ++// See https://github.com/rust-lang/rust/issues/135929 ++ ++// 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))] ++ 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)))] ++ extern "C" { ++ #[cfg_attr(PyPy, link_name = "PyPyGILState_Ensure")] ++ pub fn PyGILState_Ensure() -> super::PyGILState_STATE; ++ } ++} ++ ++#[cfg(not(Py_3_14))] ++pub unsafe extern "C" fn PyGILState_Ensure() -> PyGILState_STATE { ++ let guard = HangThread; ++ // If `PyGILState_Ensure` calls `pthread_exit`, which it does on Python < 3.14 ++ // when the interpreter is shutting down, this will cause a forced unwind. ++ // doing a forced unwind through a function with a Rust destructor is unspecified ++ // behavior. ++ // ++ // However, currently it runs the destructor, which will cause the thread to ++ // hang as it should. ++ // ++ // And if we don't catch the unwinding here, then one of our callers probably has a destructor, ++ // so it's unspecified behavior anyway, and on many configurations causes the process to abort. ++ // ++ // The alternative is for pyo3 to contain custom C or C++ code that catches the `pthread_exit`, ++ // but that's also annoying from a portability point of view. ++ // ++ // On Windows, `PyGILState_Ensure` calls `_endthreadex` instead, which AFAICT can't be caught ++ // and therefore will cause unsafety if there are pinned objects on the stack. AFAICT there's ++ // nothing we can do it other than waiting for Python 3.14 or not using Windows. At least, ++ // if there is nothing pinned on the stack, it won't cause the process to crash. ++ let ret: PyGILState_STATE = raw::PyGILState_Ensure(); ++ std::mem::forget(guard); ++ ret ++} ++ ++#[cfg(Py_3_14)] ++pub use self::raw::PyGILState_Ensure; ++ + extern "C" { +- #[cfg_attr(PyPy, link_name = "PyPyGILState_Ensure")] +- pub fn PyGILState_Ensure() -> PyGILState_STATE; + #[cfg_attr(PyPy, link_name = "PyPyGILState_Release")] + pub fn PyGILState_Release(arg1: PyGILState_STATE); + #[cfg(not(PyPy))] +diff --git a/include/pyo3/pyo3-ffi/src/pythonrun.rs b/include/pyo3/pyo3-ffi/src/pythonrun.rs +index e7ea2d2e..80209b58 100644 +--- a/include/pyo3/pyo3-ffi/src/pythonrun.rs ++++ b/include/pyo3/pyo3-ffi/src/pythonrun.rs +@@ -49,12 +49,12 @@ pub const PYOS_STACK_MARGIN: c_int = 2048; + // skipped PyOS_CheckStack under Microsoft C + + #[cfg(not(any(PyPy, Py_LIMITED_API, Py_3_10)))] +-opaque_struct!(_mod); ++opaque_struct!(pub _mod); + + #[cfg(not(any(PyPy, Py_3_10)))] +-opaque_struct!(symtable); ++opaque_struct!(pub symtable); + #[cfg(not(any(PyPy, Py_3_10)))] +-opaque_struct!(_node); ++opaque_struct!(pub _node); + + #[cfg(not(any(PyPy, Py_LIMITED_API, Py_3_10)))] + #[cfg_attr(Py_3_9, deprecated(note = "Python 3.9"))] +diff --git a/include/pyo3/pyo3-ffi/src/refcount.rs b/include/pyo3/pyo3-ffi/src/refcount.rs +new file mode 100644 +index 00000000..fcb5f45b +--- /dev/null ++++ b/include/pyo3/pyo3-ffi/src/refcount.rs +@@ -0,0 +1,369 @@ ++use crate::pyport::Py_ssize_t; ++use crate::PyObject; ++#[cfg(py_sys_config = "Py_REF_DEBUG")] ++use std::os::raw::c_char; ++#[cfg(Py_3_12)] ++use std::os::raw::c_int; ++#[cfg(all(Py_3_14, any(not(Py_GIL_DISABLED), target_pointer_width = "32")))] ++use std::os::raw::c_long; ++#[cfg(any(Py_GIL_DISABLED, all(Py_3_12, not(Py_3_14))))] ++use std::os::raw::c_uint; ++#[cfg(all(Py_3_14, not(Py_GIL_DISABLED)))] ++use std::os::raw::c_ulong; ++use std::ptr; ++#[cfg(Py_GIL_DISABLED)] ++use std::sync::atomic::Ordering::Relaxed; ++ ++#[cfg(Py_3_14)] ++const _Py_STATICALLY_ALLOCATED_FLAG: c_int = 1 << 7; ++ ++#[cfg(all(Py_3_12, not(Py_3_14)))] ++const _Py_IMMORTAL_REFCNT: Py_ssize_t = { ++ if cfg!(target_pointer_width = "64") { ++ c_uint::MAX as Py_ssize_t ++ } else { ++ // for 32-bit systems, use the lower 30 bits (see comment in CPython's object.h) ++ (c_uint::MAX >> 2) as Py_ssize_t ++ } ++}; ++ ++// comments in Python.h about the choices for these constants ++ ++#[cfg(all(Py_3_14, not(Py_GIL_DISABLED)))] ++const _Py_IMMORTAL_INITIAL_REFCNT: Py_ssize_t = { ++ if cfg!(target_pointer_width = "64") { ++ ((3 as c_ulong) << (30 as c_ulong)) as Py_ssize_t ++ } else { ++ ((5 as c_long) << (28 as c_long)) as Py_ssize_t ++ } ++}; ++ ++#[cfg(all(Py_3_14, not(Py_GIL_DISABLED)))] ++const _Py_STATIC_IMMORTAL_INITIAL_REFCNT: Py_ssize_t = { ++ if cfg!(target_pointer_width = "64") { ++ _Py_IMMORTAL_INITIAL_REFCNT ++ | ((_Py_STATICALLY_ALLOCATED_FLAG as Py_ssize_t) << (32 as Py_ssize_t)) ++ } else { ++ ((7 as c_long) << (28 as c_long)) as Py_ssize_t ++ } ++}; ++ ++#[cfg(all(Py_3_14, target_pointer_width = "32"))] ++const _Py_IMMORTAL_MINIMUM_REFCNT: Py_ssize_t = ((1 as c_long) << (30 as c_long)) as Py_ssize_t; ++ ++#[cfg(all(Py_3_14, target_pointer_width = "32"))] ++const _Py_STATIC_IMMORTAL_MINIMUM_REFCNT: Py_ssize_t = ++ ((6 as c_long) << (28 as c_long)) as Py_ssize_t; ++ ++#[cfg(all(Py_3_14, Py_GIL_DISABLED))] ++const _Py_IMMORTAL_INITIAL_REFCNT: Py_ssize_t = c_uint::MAX as Py_ssize_t; ++ ++#[cfg(Py_GIL_DISABLED)] ++pub(crate) const _Py_IMMORTAL_REFCNT_LOCAL: u32 = u32::MAX; ++ ++#[cfg(Py_GIL_DISABLED)] ++const _Py_REF_SHARED_SHIFT: isize = 2; ++// skipped private _Py_REF_SHARED_FLAG_MASK ++ ++// skipped private _Py_REF_SHARED_INIT ++// skipped private _Py_REF_MAYBE_WEAKREF ++// skipped private _Py_REF_QUEUED ++// skipped private _Py_REF_MERGED ++ ++// skipped private _Py_REF_SHARED ++ ++extern "C" { ++ #[cfg(all(Py_3_14, Py_LIMITED_API))] ++ pub fn Py_REFCNT(ob: *mut PyObject) -> Py_ssize_t; ++} ++ ++#[cfg(not(all(Py_3_14, Py_LIMITED_API)))] ++#[inline] ++pub unsafe fn Py_REFCNT(ob: *mut PyObject) -> Py_ssize_t { ++ #[cfg(Py_GIL_DISABLED)] ++ { ++ let local = (*ob).ob_ref_local.load(Relaxed); ++ if local == _Py_IMMORTAL_REFCNT_LOCAL { ++ #[cfg(not(Py_3_14))] ++ return _Py_IMMORTAL_REFCNT; ++ #[cfg(Py_3_14)] ++ return _Py_IMMORTAL_INITIAL_REFCNT; ++ } ++ let shared = (*ob).ob_ref_shared.load(Relaxed); ++ local as Py_ssize_t + Py_ssize_t::from(shared >> _Py_REF_SHARED_SHIFT) ++ } ++ ++ #[cfg(all(Py_LIMITED_API, Py_3_14))] ++ { ++ Py_REFCNT(ob) ++ } ++ ++ #[cfg(all(not(Py_GIL_DISABLED), not(all(Py_LIMITED_API, Py_3_14)), Py_3_12))] ++ { ++ (*ob).ob_refcnt.ob_refcnt ++ } ++ ++ #[cfg(all(not(Py_GIL_DISABLED), not(Py_3_12), not(GraalPy)))] ++ { ++ (*ob).ob_refcnt ++ } ++ ++ #[cfg(all(not(Py_GIL_DISABLED), not(Py_3_12), GraalPy))] ++ { ++ _Py_REFCNT(ob) ++ } ++} ++ ++#[cfg(Py_3_12)] ++#[inline(always)] ++unsafe fn _Py_IsImmortal(op: *mut PyObject) -> c_int { ++ #[cfg(all(target_pointer_width = "64", not(Py_GIL_DISABLED)))] ++ { ++ (((*op).ob_refcnt.ob_refcnt as crate::PY_INT32_T) < 0) as c_int ++ } ++ ++ #[cfg(all(target_pointer_width = "32", not(Py_GIL_DISABLED)))] ++ { ++ #[cfg(not(Py_3_14))] ++ { ++ ((*op).ob_refcnt.ob_refcnt == _Py_IMMORTAL_REFCNT) as c_int ++ } ++ ++ #[cfg(Py_3_14)] ++ { ++ ((*op).ob_refcnt.ob_refcnt >= _Py_IMMORTAL_MINIMUM_REFCNT) as c_int ++ } ++ } ++ ++ #[cfg(Py_GIL_DISABLED)] ++ { ++ ((*op).ob_ref_local.load(Relaxed) == _Py_IMMORTAL_REFCNT_LOCAL) as c_int ++ } ++} ++ ++// skipped _Py_IsStaticImmortal ++ ++// TODO: Py_SET_REFCNT ++ ++extern "C" { ++ #[cfg(all(py_sys_config = "Py_REF_DEBUG", not(Py_LIMITED_API)))] ++ fn _Py_NegativeRefcount(filename: *const c_char, lineno: c_int, op: *mut PyObject); ++ #[cfg(all(Py_3_12, py_sys_config = "Py_REF_DEBUG", not(Py_LIMITED_API)))] ++ fn _Py_INCREF_IncRefTotal(); ++ #[cfg(all(Py_3_12, py_sys_config = "Py_REF_DEBUG", not(Py_LIMITED_API)))] ++ fn _Py_DECREF_DecRefTotal(); ++ ++ #[cfg_attr(PyPy, link_name = "_PyPy_Dealloc")] ++ fn _Py_Dealloc(arg1: *mut PyObject); ++ ++ #[cfg_attr(PyPy, link_name = "PyPy_IncRef")] ++ #[cfg_attr(GraalPy, link_name = "_Py_IncRef")] ++ pub fn Py_IncRef(o: *mut PyObject); ++ #[cfg_attr(PyPy, link_name = "PyPy_DecRef")] ++ #[cfg_attr(GraalPy, link_name = "_Py_DecRef")] ++ pub fn Py_DecRef(o: *mut PyObject); ++ ++ #[cfg(all(Py_3_10, not(PyPy)))] ++ fn _Py_IncRef(o: *mut PyObject); ++ #[cfg(all(Py_3_10, not(PyPy)))] ++ fn _Py_DecRef(o: *mut PyObject); ++ ++ #[cfg(GraalPy)] ++ fn _Py_REFCNT(arg1: *const PyObject) -> Py_ssize_t; ++} ++ ++#[inline(always)] ++pub unsafe fn Py_INCREF(op: *mut PyObject) { ++ // On limited API, the free-threaded build, or with refcount debugging, let the interpreter do refcounting ++ // TODO: reimplement the logic in the header in the free-threaded build, for a little bit of performance. ++ #[cfg(any( ++ Py_GIL_DISABLED, ++ Py_LIMITED_API, ++ py_sys_config = "Py_REF_DEBUG", ++ GraalPy ++ ))] ++ { ++ // _Py_IncRef was added to the ABI in 3.10; skips null checks ++ #[cfg(all(Py_3_10, not(PyPy)))] ++ { ++ _Py_IncRef(op); ++ } ++ ++ #[cfg(any(not(Py_3_10), PyPy))] ++ { ++ Py_IncRef(op); ++ } ++ } ++ ++ // version-specific builds are allowed to directly manipulate the reference count ++ #[cfg(not(any( ++ Py_GIL_DISABLED, ++ Py_LIMITED_API, ++ py_sys_config = "Py_REF_DEBUG", ++ GraalPy ++ )))] ++ { ++ #[cfg(all(Py_3_14, target_pointer_width = "64"))] ++ { ++ let cur_refcnt = (*op).ob_refcnt.ob_refcnt; ++ if (cur_refcnt as i32) < 0 { ++ return; ++ } ++ (*op).ob_refcnt.ob_refcnt = cur_refcnt.wrapping_add(1); ++ } ++ ++ #[cfg(all(Py_3_12, not(Py_3_14), target_pointer_width = "64"))] ++ { ++ let cur_refcnt = (*op).ob_refcnt.ob_refcnt_split[crate::PY_BIG_ENDIAN]; ++ let new_refcnt = cur_refcnt.wrapping_add(1); ++ if new_refcnt == 0 { ++ return; ++ } ++ (*op).ob_refcnt.ob_refcnt_split[crate::PY_BIG_ENDIAN] = new_refcnt; ++ } ++ ++ #[cfg(all(Py_3_12, target_pointer_width = "32"))] ++ { ++ if _Py_IsImmortal(op) != 0 { ++ return; ++ } ++ (*op).ob_refcnt.ob_refcnt += 1 ++ } ++ ++ #[cfg(not(Py_3_12))] ++ { ++ (*op).ob_refcnt += 1 ++ } ++ ++ // Skipped _Py_INCREF_STAT_INC - if anyone wants this, please file an issue ++ // or submit a PR supporting Py_STATS build option and pystats.h ++ } ++} ++ ++// skipped _Py_DecRefShared ++// skipped _Py_DecRefSharedDebug ++// skipped _Py_MergeZeroLocalRefcount ++ ++#[inline(always)] ++#[cfg_attr( ++ all(py_sys_config = "Py_REF_DEBUG", Py_3_12, not(Py_LIMITED_API)), ++ track_caller ++)] ++pub unsafe fn Py_DECREF(op: *mut PyObject) { ++ // On limited API, the free-threaded build, or with refcount debugging, let the interpreter do refcounting ++ // On 3.12+ we implement refcount debugging to get better assertion locations on negative refcounts ++ // TODO: reimplement the logic in the header in the free-threaded build, for a little bit of performance. ++ #[cfg(any( ++ Py_GIL_DISABLED, ++ Py_LIMITED_API, ++ all(py_sys_config = "Py_REF_DEBUG", not(Py_3_12)), ++ GraalPy ++ ))] ++ { ++ // _Py_DecRef was added to the ABI in 3.10; skips null checks ++ #[cfg(all(Py_3_10, not(PyPy)))] ++ { ++ _Py_DecRef(op); ++ } ++ ++ #[cfg(any(not(Py_3_10), PyPy))] ++ { ++ Py_DecRef(op); ++ } ++ } ++ ++ #[cfg(not(any( ++ Py_GIL_DISABLED, ++ Py_LIMITED_API, ++ all(py_sys_config = "Py_REF_DEBUG", not(Py_3_12)), ++ GraalPy ++ )))] ++ { ++ #[cfg(Py_3_12)] ++ if _Py_IsImmortal(op) != 0 { ++ return; ++ } ++ ++ // Skipped _Py_DECREF_STAT_INC - if anyone needs this, please file an issue ++ // or submit a PR supporting Py_STATS build option and pystats.h ++ ++ #[cfg(py_sys_config = "Py_REF_DEBUG")] ++ _Py_DECREF_DecRefTotal(); ++ ++ #[cfg(Py_3_12)] ++ { ++ (*op).ob_refcnt.ob_refcnt -= 1; ++ ++ #[cfg(py_sys_config = "Py_REF_DEBUG")] ++ if (*op).ob_refcnt.ob_refcnt < 0 { ++ let location = std::panic::Location::caller(); ++ let filename = std::ffi::CString::new(location.file()).unwrap(); ++ _Py_NegativeRefcount(filename.as_ptr(), location.line() as i32, op); ++ } ++ ++ if (*op).ob_refcnt.ob_refcnt == 0 { ++ _Py_Dealloc(op); ++ } ++ } ++ ++ #[cfg(not(Py_3_12))] ++ { ++ (*op).ob_refcnt -= 1; ++ ++ if (*op).ob_refcnt == 0 { ++ _Py_Dealloc(op); ++ } ++ } ++ } ++} ++ ++#[inline] ++pub unsafe fn Py_CLEAR(op: *mut *mut PyObject) { ++ let tmp = *op; ++ if !tmp.is_null() { ++ *op = ptr::null_mut(); ++ Py_DECREF(tmp); ++ } ++} ++ ++#[inline] ++pub unsafe fn Py_XINCREF(op: *mut PyObject) { ++ if !op.is_null() { ++ Py_INCREF(op) ++ } ++} ++ ++#[inline] ++pub unsafe fn Py_XDECREF(op: *mut PyObject) { ++ if !op.is_null() { ++ Py_DECREF(op) ++ } ++} ++ ++extern "C" { ++ #[cfg(all(Py_3_10, Py_LIMITED_API, not(PyPy)))] ++ #[cfg_attr(docsrs, doc(cfg(Py_3_10)))] ++ pub fn Py_NewRef(obj: *mut PyObject) -> *mut PyObject; ++ #[cfg(all(Py_3_10, Py_LIMITED_API, not(PyPy)))] ++ #[cfg_attr(docsrs, doc(cfg(Py_3_10)))] ++ pub fn Py_XNewRef(obj: *mut PyObject) -> *mut PyObject; ++} ++ ++// macro _Py_NewRef not public; reimplemented directly inside Py_NewRef here ++// macro _Py_XNewRef not public; reimplemented directly inside Py_XNewRef here ++ ++#[cfg(all(Py_3_10, any(not(Py_LIMITED_API), PyPy)))] ++#[cfg_attr(docsrs, doc(cfg(Py_3_10)))] ++#[inline] ++pub unsafe fn Py_NewRef(obj: *mut PyObject) -> *mut PyObject { ++ Py_INCREF(obj); ++ obj ++} ++ ++#[cfg(all(Py_3_10, any(not(Py_LIMITED_API), PyPy)))] ++#[cfg_attr(docsrs, doc(cfg(Py_3_10)))] ++#[inline] ++pub unsafe fn Py_XNewRef(obj: *mut PyObject) -> *mut PyObject { ++ Py_XINCREF(obj); ++ obj ++} +diff --git a/include/pyo3/pyo3-ffi/src/setobject.rs b/include/pyo3/pyo3-ffi/src/setobject.rs +index 9d5351fc..87e33e80 100644 +--- a/include/pyo3/pyo3-ffi/src/setobject.rs ++++ b/include/pyo3/pyo3-ffi/src/setobject.rs +@@ -39,11 +39,7 @@ pub unsafe fn PySet_GET_SIZE(so: *mut PyObject) -> Py_ssize_t { + (*so).used + } + +-#[cfg(not(Py_LIMITED_API))] +-#[cfg_attr(windows, link(name = "pythonXY"))] +-extern "C" { +- pub static mut _PySet_Dummy: *mut PyObject; +-} ++// skipped _PySet_Dummy + + extern "C" { + #[cfg(not(Py_LIMITED_API))] +diff --git a/include/pyo3/pyo3-ffi/src/weakrefobject.rs b/include/pyo3/pyo3-ffi/src/weakrefobject.rs +index 305dc290..88a1bf90 100644 +--- a/include/pyo3/pyo3-ffi/src/weakrefobject.rs ++++ b/include/pyo3/pyo3-ffi/src/weakrefobject.rs +@@ -4,16 +4,18 @@ use std::os::raw::c_int; + use std::ptr::addr_of_mut; + + #[cfg(all(not(PyPy), Py_LIMITED_API, not(GraalPy)))] +-opaque_struct!(PyWeakReference); ++opaque_struct!(pub PyWeakReference); + + #[cfg(all(not(PyPy), not(Py_LIMITED_API), not(GraalPy)))] + pub use crate::_PyWeakReference as PyWeakReference; + + #[cfg_attr(windows, link(name = "pythonXY"))] + extern "C" { ++ // TODO: PyO3 is depending on this symbol in `reference.rs`, we should change this and ++ // remove the export as this is a private symbol. + pub static mut _PyWeakref_RefType: PyTypeObject; +- pub static mut _PyWeakref_ProxyType: PyTypeObject; +- pub static mut _PyWeakref_CallableProxyType: PyTypeObject; ++ static mut _PyWeakref_ProxyType: PyTypeObject; ++ static mut _PyWeakref_CallableProxyType: PyTypeObject; + + #[cfg(PyPy)] + #[link_name = "PyPyWeakref_CheckRef"] + +From d2570d2f25eba771468f41020a2ad69e0cbdbbc7 Mon Sep 17 00:00:00 2001 +From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> +Date: Fri, 23 May 2025 17:00:34 +0200 +Subject: [PATCH 2/6] Add pyo3-ffi patch for _PyLong_AsByteArray with + exceptions + +Co-authored-by: ijl +--- + ...i-pylong-asbytearray-with-exceptions.patch | 23 +++++++++++++++++++ + 1 file changed, 23 insertions(+) + create mode 100644 include/pyo3-ffi-pylong-asbytearray-with-exceptions.patch + +diff --git a/include/pyo3-ffi-pylong-asbytearray-with-exceptions.patch b/include/pyo3-ffi-pylong-asbytearray-with-exceptions.patch +new file mode 100644 +index 00000000..20e5d7a3 +--- /dev/null ++++ b/include/pyo3-ffi-pylong-asbytearray-with-exceptions.patch +@@ -0,0 +1,23 @@ ++diff --git a/include/pyo3/pyo3-ffi/src/cpython/longobject.rs b/include/pyo3/pyo3-ffi/src/cpython/longobject.rs ++index 45acaae..6063575 100644 ++--- a/include/pyo3/pyo3-ffi/src/cpython/longobject.rs +++++ b/include/pyo3/pyo3-ffi/src/cpython/longobject.rs ++@@ -61,6 +61,18 @@ extern "C" { ++ is_signed: c_int, ++ ) -> *mut PyObject; ++ +++ #[cfg(Py_3_13)] +++ #[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, +++ with_exceptions: c_int, +++ ) -> c_int; +++ +++ #[cfg(not(Py_3_13))] ++ #[cfg_attr(PyPy, link_name = "_PyPyLong_AsByteArrayO")] ++ pub fn _PyLong_AsByteArray( ++ v: *mut PyLongObject, + +From d01aea4541800dca412e3f4c3068e974f18c3218 Mon Sep 17 00:00:00 2001 +From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> +Date: Fri, 23 May 2025 18:26:19 +0200 +Subject: [PATCH 3/6] Apply pyo3-ffi patch + +--- + include/pyo3/pyo3-ffi/src/cpython/longobject.rs | 12 ++++++++++++ + 1 file changed, 12 insertions(+) + +diff --git a/include/pyo3/pyo3-ffi/src/cpython/longobject.rs b/include/pyo3/pyo3-ffi/src/cpython/longobject.rs +index 45acaae5..6063575e 100644 +--- a/include/pyo3/pyo3-ffi/src/cpython/longobject.rs ++++ b/include/pyo3/pyo3-ffi/src/cpython/longobject.rs +@@ -61,6 +61,18 @@ extern "C" { + is_signed: c_int, + ) -> *mut PyObject; + ++ #[cfg(Py_3_13)] ++ #[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, ++ with_exceptions: c_int, ++ ) -> c_int; ++ ++ #[cfg(not(Py_3_13))] + #[cfg_attr(PyPy, link_name = "_PyPyLong_AsByteArrayO")] + pub fn _PyLong_AsByteArray( + v: *mut PyLongObject, + +From 6427dba13406b499bc2edc033c0eeeaafdfa4ad4 Mon Sep 17 00:00:00 2001 +From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> +Date: Fri, 23 May 2025 15:16:24 +0200 +Subject: [PATCH 4/6] Fix _PyDict_SetItem_KnownHash_LockHeld + +--- + src/util.rs | 10 ---------- + 1 file changed, 10 deletions(-) + +diff --git a/src/util.rs b/src/util.rs +index ccf425ce..0a255c3f 100644 +--- a/src/util.rs ++++ b/src/util.rs +@@ -258,19 +258,9 @@ macro_rules! pydict_setitem { + ($dict:expr, $pykey:expr, $pyval:expr) => { + debug_assert!(ffi!(Py_REFCNT($dict)) == 1); + debug_assert!(str_hash!($pykey) != -1); +- #[cfg(not(Py_3_13))] + unsafe { + let _ = pyo3_ffi::_PyDict_SetItem_KnownHash($dict, $pykey, $pyval, str_hash!($pykey)); + } +- #[cfg(Py_3_13)] +- unsafe { +- let _ = pyo3_ffi::_PyDict_SetItem_KnownHash_LockHeld( +- $dict.cast::(), +- $pykey, +- $pyval, +- str_hash!($pykey), +- ); +- } + reverse_pydict_incref!($pykey); + reverse_pydict_incref!($pyval); + }; + +From 80779519c9363a81884e77358e62fb443a584039 Mon Sep 17 00:00:00 2001 +From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> +Date: Fri, 23 May 2025 15:16:40 +0200 +Subject: [PATCH 5/6] Fix _Py_IsImmortal + +--- + src/util.rs | 6 ++---- + 1 file changed, 2 insertions(+), 4 deletions(-) + +diff --git a/src/util.rs b/src/util.rs +index 0a255c3f..bbfb72ed 100644 +--- a/src/util.rs ++++ b/src/util.rs +@@ -120,10 +120,8 @@ macro_rules! str_from_slice { + macro_rules! reverse_pydict_incref { + ($op:expr) => { + unsafe { +- if pyo3_ffi::_Py_IsImmortal($op) == 0 { +- debug_assert!(ffi!(Py_REFCNT($op)) >= 2); +- (*$op).ob_refcnt.ob_refcnt -= 1; +- } ++ debug_assert!(ffi!(Py_REFCNT($op)) >= 2); ++ (*$op).ob_refcnt.ob_refcnt -= 1; + } + }; + } + +From b90995f44f80176d403b720d706dfe5a027855ff Mon Sep 17 00:00:00 2001 +From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> +Date: Fri, 23 May 2025 17:12:44 +0200 +Subject: [PATCH 6/6] Use PyUnicode API + +--- + src/serialize/per_type/dict.rs | 4 ++-- + src/serialize/per_type/unicode.rs | 4 ++-- + src/str/ffi.rs | 29 +++++++++++++++-------------- + src/str/pyunicode_new.rs | 3 +++ + 4 files changed, 22 insertions(+), 18 deletions(-) + +diff --git a/src/serialize/per_type/dict.rs b/src/serialize/per_type/dict.rs +index e2e04f62..cf2c3a4b 100644 +--- a/src/serialize/per_type/dict.rs ++++ b/src/serialize/per_type/dict.rs +@@ -13,7 +13,7 @@ use crate::serialize::per_type::{ + }; + use crate::serialize::serializer::PyObjectSerializer; + use crate::serialize::state::SerializerState; +-use crate::str::{unicode_to_str, unicode_to_str_via_ffi}; ++use crate::str::unicode_to_str; + use crate::typeref::{STR_TYPE, TRUE, VALUE_STR}; + use crate::util::isize_to_usize; + use compact_str::CompactString; +@@ -321,7 +321,7 @@ fn non_str_str(key: *mut pyo3_ffi::PyObject) -> Result Result { +- let uni = unicode_to_str_via_ffi(key); ++ let uni = unicode_to_str(key); + if unlikely!(uni.is_none()) { + Err(SerializeError::InvalidStr) + } else { +diff --git a/src/serialize/per_type/unicode.rs b/src/serialize/per_type/unicode.rs +index 23270916..7267b454 100644 +--- a/src/serialize/per_type/unicode.rs ++++ b/src/serialize/per_type/unicode.rs +@@ -1,7 +1,7 @@ + // SPDX-License-Identifier: (Apache-2.0 OR MIT) + + use crate::serialize::error::SerializeError; +-use crate::str::{unicode_to_str, unicode_to_str_via_ffi}; ++use crate::str::unicode_to_str; + + use serde::ser::{Serialize, Serializer}; + +@@ -50,7 +50,7 @@ impl Serialize for StrSubclassSerializer { + where + S: Serializer, + { +- let uni = unicode_to_str_via_ffi(self.ptr); ++ let uni = unicode_to_str(self.ptr); + if unlikely!(uni.is_none()) { + err!(SerializeError::InvalidStr) + } +diff --git a/src/str/ffi.rs b/src/str/ffi.rs +index 4f1c76bd..6b57e7ae 100644 +--- a/src/str/ffi.rs ++++ b/src/str/ffi.rs +@@ -1,25 +1,19 @@ + // SPDX-License-Identifier: (Apache-2.0 OR MIT) + + use crate::util::isize_to_usize; +-use core::ffi::c_void; +-use pyo3_ffi::{PyASCIIObject, PyCompactUnicodeObject, PyObject, Py_hash_t}; ++#[cfg(not(Py_3_14))] ++use pyo3_ffi::PyCompactUnicodeObject; ++use pyo3_ffi::{ ++ PyASCIIObject, PyObject, PyUnicode_DATA, PyUnicode_GET_LENGTH, PyUnicode_KIND, Py_hash_t, ++}; + + // see unicodeobject.h for documentation + + #[inline] + pub fn hash_str(op: *mut PyObject) -> Py_hash_t { + unsafe { +- let data_ptr: *mut c_void = if (*op.cast::()).compact() == 1 +- && (*op.cast::()).ascii() == 1 +- { +- op.cast::().offset(1).cast::() +- } else { +- op.cast::() +- .offset(1) +- .cast::() +- }; +- let num_bytes = +- (*op.cast::()).length * ((*op.cast::()).kind()) as isize; ++ let data_ptr = PyUnicode_DATA(op); ++ let num_bytes = PyUnicode_GET_LENGTH(op) * PyUnicode_KIND(op) as isize; + #[cfg(Py_3_14)] + let hash = pyo3_ffi::Py_HashBuffer(data_ptr, num_bytes); + #[cfg(not(Py_3_14))] +@@ -30,7 +24,7 @@ pub fn hash_str(op: *mut PyObject) -> Py_hash_t { + } + + #[inline(never)] +-pub fn unicode_to_str_via_ffi(op: *mut PyObject) -> Option<&'static str> { ++fn unicode_to_str_via_ffi(op: *mut PyObject) -> Option<&'static str> { + let mut str_size: pyo3_ffi::Py_ssize_t = 0; + let ptr = ffi!(PyUnicode_AsUTF8AndSize(op, &mut str_size)).cast::(); + if unlikely!(ptr.is_null()) { +@@ -40,6 +34,7 @@ pub fn unicode_to_str_via_ffi(op: *mut PyObject) -> Option<&'static str> { + } + } + ++#[cfg(not(Py_3_14))] + #[inline] + pub fn unicode_to_str(op: *mut PyObject) -> Option<&'static str> { + unsafe { +@@ -58,3 +53,9 @@ pub fn unicode_to_str(op: *mut PyObject) -> Option<&'static str> { + } + } + } ++ ++#[cfg(Py_3_14)] ++#[inline] ++pub fn unicode_to_str(op: *mut PyObject) -> Option<&'static str> { ++ unicode_to_str_via_ffi(op) ++} +diff --git a/src/str/pyunicode_new.rs b/src/str/pyunicode_new.rs +index 7b1c2df2..738b8489 100644 +--- a/src/str/pyunicode_new.rs ++++ b/src/str/pyunicode_new.rs +@@ -8,7 +8,9 @@ macro_rules! validate_str { + #[cfg(not(Py_3_12))] + debug_assert!((*($ptr.cast::())).ready() == 1); + ++ #[cfg(not(Py_3_14))] + debug_assert!((*($ptr.cast::())).compact() == 1); ++ #[cfg(not(Py_3_14))] + debug_assert!((*($ptr.cast::())).interned() == 0); + + debug_assert!(ffi!(_PyUnicode_CheckConsistency($ptr.cast::(), 1)) == 1); +@@ -22,6 +24,7 @@ pub fn pyunicode_ascii(buf: *const u8, num_chars: usize) -> *mut pyo3_ffi::PyObj + let data_ptr = ptr.cast::().offset(1).cast::(); + core::ptr::copy_nonoverlapping(buf, data_ptr, num_chars); + core::ptr::write(data_ptr.add(num_chars), 0); ++ #[cfg(not(Py_3_14))] + debug_assert!((*(ptr.cast::())).ascii() == 1); + validate_str!(ptr); + ptr.cast::() diff --git a/python-orjson.spec b/python-orjson.spec index 6fe904a..ab35204 100644 --- a/python-orjson.spec +++ b/python-orjson.spec @@ -17,6 +17,9 @@ Summary: Fast, correct Python JSON library License: Apache-2.0 OR MIT URL: https://github.com/ijl/orjson Source: %{pypi_source orjson} +# Support Python 3.14 +# Updates bundled/forked pyo3-build-config/pyo3-ffi to 0.25.0 +Patch: https://github.com/ijl/orjson/pull/570.patch BuildRequires: tomcli BuildRequires: python3-devel @@ -71,8 +74,8 @@ License: %{shrink: # # Note that these crates are actually forked, not only bundled/vendored; see # https://github.com/ijl/orjson/issues/524#issuecomment-2424170405 for details. -Provides: bundled(crate(pyo3-build-config)) = 0.23.3 -Provides: bundled(crate(pyo3-ffi)) = 0.23.3 +Provides: bundled(crate(pyo3-build-config)) = 0.25.0 +Provides: bundled(crate(pyo3-ffi)) = 0.25.0 %description -n python3-orjson %{_description}