From 710d90e8284fd6566223a1ad9c4df06b72c7392b Mon Sep 17 00:00:00 2001
From: David Hewitt <mail@davidhewitt.dev>
Date: Fri, 15 Nov 2024 16:44:02 +0000
Subject: [PATCH] fixup docs for packaging

---
 README.md                                     |   4 +-
 examples/decorator/.template/pre-script.rhai  |   2 +-
 .../maturin-starter/.template/pre-script.rhai |   2 +-
 examples/plugin/.template/pre-script.rhai     |   2 +-
 .../.template/pre-script.rhai                 |   2 +-
 examples/word-count/.template/pre-script.rhai |   2 +-
 pyo3-ffi/README.md                            | 128 ++++++++++++-----
 pyo3-ffi/src/lib.rs                           | 134 +++++++++++++++++-
 8 files changed, 234 insertions(+), 42 deletions(-)

diff --git a/README.md b/README.md
index a131a823f0e..b086e82cae5 100644
--- a/README.md
+++ b/README.md
@@ -71,7 +71,7 @@ name = "string_sum"
 crate-type = ["cdylib"]
 
 [dependencies]
-pyo3 = { version = "0.22.5", features = ["extension-module"] }
+pyo3 = { version = "0.23.0", features = ["extension-module"] }
 ```
 
 **`src/lib.rs`**
@@ -140,7 +140,7 @@ Start a new project with `cargo new` and add  `pyo3` to the `Cargo.toml` like th
 
 ```toml
 [dependencies.pyo3]
-version = "0.22.5"
+version = "0.23.0"
 features = ["auto-initialize"]
 ```
 
diff --git a/examples/decorator/.template/pre-script.rhai b/examples/decorator/.template/pre-script.rhai
index 3f9199c0f2f..d06e0e70994 100644
--- a/examples/decorator/.template/pre-script.rhai
+++ b/examples/decorator/.template/pre-script.rhai
@@ -1,4 +1,4 @@
-variable::set("PYO3_VERSION", "0.22.5");
+variable::set("PYO3_VERSION", "0.23.0");
 file::rename(".template/Cargo.toml", "Cargo.toml");
 file::rename(".template/pyproject.toml", "pyproject.toml");
 file::delete(".template");
diff --git a/examples/maturin-starter/.template/pre-script.rhai b/examples/maturin-starter/.template/pre-script.rhai
index 3f9199c0f2f..d06e0e70994 100644
--- a/examples/maturin-starter/.template/pre-script.rhai
+++ b/examples/maturin-starter/.template/pre-script.rhai
@@ -1,4 +1,4 @@
-variable::set("PYO3_VERSION", "0.22.5");
+variable::set("PYO3_VERSION", "0.23.0");
 file::rename(".template/Cargo.toml", "Cargo.toml");
 file::rename(".template/pyproject.toml", "pyproject.toml");
 file::delete(".template");
diff --git a/examples/plugin/.template/pre-script.rhai b/examples/plugin/.template/pre-script.rhai
index 1d2e4657033..19694cc1f8c 100644
--- a/examples/plugin/.template/pre-script.rhai
+++ b/examples/plugin/.template/pre-script.rhai
@@ -1,4 +1,4 @@
-variable::set("PYO3_VERSION", "0.22.5");
+variable::set("PYO3_VERSION", "0.23.0");
 file::rename(".template/Cargo.toml", "Cargo.toml");
 file::rename(".template/plugin_api/Cargo.toml", "plugin_api/Cargo.toml");
 file::delete(".template");
diff --git a/examples/setuptools-rust-starter/.template/pre-script.rhai b/examples/setuptools-rust-starter/.template/pre-script.rhai
index 9a92b25c613..d3cf83d3a4d 100644
--- a/examples/setuptools-rust-starter/.template/pre-script.rhai
+++ b/examples/setuptools-rust-starter/.template/pre-script.rhai
@@ -1,4 +1,4 @@
-variable::set("PYO3_VERSION", "0.22.5");
+variable::set("PYO3_VERSION", "0.23.0");
 file::rename(".template/Cargo.toml", "Cargo.toml");
 file::rename(".template/setup.cfg", "setup.cfg");
 file::delete(".template");
diff --git a/examples/word-count/.template/pre-script.rhai b/examples/word-count/.template/pre-script.rhai
index 3f9199c0f2f..d06e0e70994 100644
--- a/examples/word-count/.template/pre-script.rhai
+++ b/examples/word-count/.template/pre-script.rhai
@@ -1,4 +1,4 @@
-variable::set("PYO3_VERSION", "0.22.5");
+variable::set("PYO3_VERSION", "0.23.0");
 file::rename(".template/Cargo.toml", "Cargo.toml");
 file::rename(".template/pyproject.toml", "pyproject.toml");
 file::delete(".template");
diff --git a/pyo3-ffi/README.md b/pyo3-ffi/README.md
index c5acc96ed3b..d9931b3cf48 100644
--- a/pyo3-ffi/README.md
+++ b/pyo3-ffi/README.md
@@ -41,13 +41,32 @@ name = "string_sum"
 crate-type = ["cdylib"]
 
 [dependencies.pyo3-ffi]
-version = "*"
+version = "0.23.0"
 features = ["extension-module"]
+
+[build-dependencies]
+# This is only necessary if you need to configure your build based on
+# the Python version or the compile-time configuration for the interpreter.
+pyo3_build_config = "0.23.0"
+```
+
+If you need to use conditional compilation based on Python version or how
+Python was compiled, you need to add `pyo3-build-config` as a
+`build-dependency` in your `Cargo.toml` as in the example above and either
+create a new `build.rs` file or modify an existing one so that
+`pyo3_build_config::use_pyo3_cfgs()` gets called at build time:
+
+**`build.rs`**
+
+```rust,ignore
+fn main() {
+    pyo3_build_config::use_pyo3_cfgs()
+}
 ```
 
 **`src/lib.rs`**
 ```rust
-use std::os::raw::c_char;
+use std::os::raw::{c_char, c_long};
 use std::ptr;
 
 use pyo3_ffi::*;
@@ -57,14 +76,14 @@ static mut MODULE_DEF: PyModuleDef = PyModuleDef {
     m_name: c_str!("string_sum").as_ptr(),
     m_doc: c_str!("A Python module written in Rust.").as_ptr(),
     m_size: 0,
-    m_methods: unsafe { METHODS.as_mut_ptr().cast() },
+    m_methods: unsafe { METHODS as *const [PyMethodDef] as *mut PyMethodDef },
     m_slots: std::ptr::null_mut(),
     m_traverse: None,
     m_clear: None,
     m_free: None,
 };
 
-static mut METHODS: [PyMethodDef; 2] = [
+static mut METHODS: &[PyMethodDef] = &[
     PyMethodDef {
         ml_name: c_str!("sum_as_string").as_ptr(),
         ml_meth: PyMethodDefPointer {
@@ -74,58 +93,99 @@ static mut METHODS: [PyMethodDef; 2] = [
         ml_doc: c_str!("returns the sum of two integers as a string").as_ptr(),
     },
     // A zeroed PyMethodDef to mark the end of the array.
-    PyMethodDef::zeroed()
+    PyMethodDef::zeroed(),
 ];
 
 // The module initialization function, which must be named `PyInit_<your_module>`.
 #[allow(non_snake_case)]
 #[no_mangle]
 pub unsafe extern "C" fn PyInit_string_sum() -> *mut PyObject {
-    PyModule_Create(ptr::addr_of_mut!(MODULE_DEF))
+    let module = PyModule_Create(ptr::addr_of_mut!(MODULE_DEF));
+    if module.is_null() {
+        return module;
+    }
+    #[cfg(Py_GIL_DISABLED)]
+    {
+        if PyUnstable_Module_SetGIL(module, Py_MOD_GIL_NOT_USED) < 0 {
+            Py_DECREF(module);
+            return std::ptr::null_mut();
+        }
+    }
+    module
 }
 
-pub unsafe extern "C" fn sum_as_string(
-    _self: *mut PyObject,
-    args: *mut *mut PyObject,
-    nargs: Py_ssize_t,
-) -> *mut PyObject {
-    if nargs != 2 {
-        PyErr_SetString(
-            PyExc_TypeError,
-            c_str!("sum_as_string() expected 2 positional arguments").as_ptr(),
+/// A helper to parse function arguments
+/// If we used PyO3's proc macros they'd handle all of this boilerplate for us :)
+unsafe fn parse_arg_as_i32(obj: *mut PyObject, n_arg: usize) -> Option<i32> {
+    if PyLong_Check(obj) == 0 {
+        let msg = format!(
+            "sum_as_string expected an int for positional argument {}\0",
+            n_arg
         );
-        return std::ptr::null_mut();
+        PyErr_SetString(PyExc_TypeError, msg.as_ptr().cast::<c_char>());
+        return None;
     }
 
-    let arg1 = *args;
-    if PyLong_Check(arg1) == 0 {
-        PyErr_SetString(
-            PyExc_TypeError,
-            c_str!("sum_as_string() expected an int for positional argument 1").as_ptr(),
-        );
-        return std::ptr::null_mut();
+    // Let's keep the behaviour consistent on platforms where `c_long` is bigger than 32 bits.
+    // In particular, it is an i32 on Windows but i64 on most Linux systems
+    let mut overflow = 0;
+    let i_long: c_long = PyLong_AsLongAndOverflow(obj, &mut overflow);
+
+    #[allow(irrefutable_let_patterns)] // some platforms have c_long equal to i32
+    if overflow != 0 {
+        raise_overflowerror(obj);
+        None
+    } else if let Ok(i) = i_long.try_into() {
+        Some(i)
+    } else {
+        raise_overflowerror(obj);
+        None
     }
+}
 
-    let arg1 = PyLong_AsLong(arg1);
-    if !PyErr_Occurred().is_null() {
-        return ptr::null_mut();
+unsafe fn raise_overflowerror(obj: *mut PyObject) {
+    let obj_repr = PyObject_Str(obj);
+    if !obj_repr.is_null() {
+        let mut size = 0;
+        let p = PyUnicode_AsUTF8AndSize(obj_repr, &mut size);
+        if !p.is_null() {
+            let s = std::str::from_utf8_unchecked(std::slice::from_raw_parts(
+                p.cast::<u8>(),
+                size as usize,
+            ));
+            let msg = format!("cannot fit {} in 32 bits\0", s);
+
+            PyErr_SetString(PyExc_OverflowError, msg.as_ptr().cast::<c_char>());
+        }
+        Py_DECREF(obj_repr);
     }
+}
 
-    let arg2 = *args.add(1);
-    if PyLong_Check(arg2) == 0 {
+pub unsafe extern "C" fn sum_as_string(
+    _self: *mut PyObject,
+    args: *mut *mut PyObject,
+    nargs: Py_ssize_t,
+) -> *mut PyObject {
+    if nargs != 2 {
         PyErr_SetString(
             PyExc_TypeError,
-            c_str!("sum_as_string() expected an int for positional argument 2").as_ptr(),
+            c_str!("sum_as_string expected 2 positional arguments").as_ptr(),
         );
         return std::ptr::null_mut();
     }
 
-    let arg2 = PyLong_AsLong(arg2);
-    if !PyErr_Occurred().is_null() {
-        return ptr::null_mut();
-    }
+    let (first, second) = (*args, *args.add(1));
+
+    let first = match parse_arg_as_i32(first, 1) {
+        Some(x) => x,
+        None => return std::ptr::null_mut(),
+    };
+    let second = match parse_arg_as_i32(second, 2) {
+        Some(x) => x,
+        None => return std::ptr::null_mut(),
+    };
 
-    match arg1.checked_add(arg2) {
+    match first.checked_add(second) {
         Some(sum) => {
             let string = sum.to_string();
             PyUnicode_FromStringAndSize(string.as_ptr().cast::<c_char>(), string.len() as isize)
diff --git a/pyo3-ffi/src/lib.rs b/pyo3-ffi/src/lib.rs
index 23a5e0000b1..7bdba1173d6 100644
--- a/pyo3-ffi/src/lib.rs
+++ b/pyo3-ffi/src/lib.rs
@@ -130,7 +130,139 @@
 //!
 //! **`src/lib.rs`**
 //! ```rust
-#![doc = include_str!("../examples/string-sum/src/lib.rs")]
+//! use std::os::raw::{c_char, c_long};
+//! use std::ptr;
+//!
+//! use pyo3_ffi::*;
+//!
+//! static mut MODULE_DEF: PyModuleDef = PyModuleDef {
+//!     m_base: PyModuleDef_HEAD_INIT,
+//!     m_name: c_str!("string_sum").as_ptr(),
+//!     m_doc: c_str!("A Python module written in Rust.").as_ptr(),
+//!     m_size: 0,
+//!     m_methods: unsafe { METHODS as *const [PyMethodDef] as *mut PyMethodDef },
+//!     m_slots: std::ptr::null_mut(),
+//!     m_traverse: None,
+//!     m_clear: None,
+//!     m_free: None,
+//! };
+//!
+//! static mut METHODS: &[PyMethodDef] = &[
+//!     PyMethodDef {
+//!         ml_name: c_str!("sum_as_string").as_ptr(),
+//!         ml_meth: PyMethodDefPointer {
+//!             PyCFunctionFast: sum_as_string,
+//!         },
+//!         ml_flags: METH_FASTCALL,
+//!         ml_doc: c_str!("returns the sum of two integers as a string").as_ptr(),
+//!     },
+//!     // A zeroed PyMethodDef to mark the end of the array.
+//!     PyMethodDef::zeroed(),
+//! ];
+//!
+//! // The module initialization function, which must be named `PyInit_<your_module>`.
+//! #[allow(non_snake_case)]
+//! #[no_mangle]
+//! pub unsafe extern "C" fn PyInit_string_sum() -> *mut PyObject {
+//!     let module = PyModule_Create(ptr::addr_of_mut!(MODULE_DEF));
+//!     if module.is_null() {
+//!         return module;
+//!     }
+//!     #[cfg(Py_GIL_DISABLED)]
+//!     {
+//!         if PyUnstable_Module_SetGIL(module, Py_MOD_GIL_NOT_USED) < 0 {
+//!             Py_DECREF(module);
+//!             return std::ptr::null_mut();
+//!         }
+//!     }
+//!     module
+//! }
+//!
+//! /// A helper to parse function arguments
+//! /// If we used PyO3's proc macros they'd handle all of this boilerplate for us :)
+//! unsafe fn parse_arg_as_i32(obj: *mut PyObject, n_arg: usize) -> Option<i32> {
+//!     if PyLong_Check(obj) == 0 {
+//!         let msg = format!(
+//!             "sum_as_string expected an int for positional argument {}\0",
+//!             n_arg
+//!         );
+//!         PyErr_SetString(PyExc_TypeError, msg.as_ptr().cast::<c_char>());
+//!         return None;
+//!     }
+//!
+//!     // Let's keep the behaviour consistent on platforms where `c_long` is bigger than 32 bits.
+//!     // In particular, it is an i32 on Windows but i64 on most Linux systems
+//!     let mut overflow = 0;
+//!     let i_long: c_long = PyLong_AsLongAndOverflow(obj, &mut overflow);
+//!
+//!     #[allow(irrefutable_let_patterns)] // some platforms have c_long equal to i32
+//!     if overflow != 0 {
+//!         raise_overflowerror(obj);
+//!         None
+//!     } else if let Ok(i) = i_long.try_into() {
+//!         Some(i)
+//!     } else {
+//!         raise_overflowerror(obj);
+//!         None
+//!     }
+//! }
+//!
+//! unsafe fn raise_overflowerror(obj: *mut PyObject) {
+//!     let obj_repr = PyObject_Str(obj);
+//!     if !obj_repr.is_null() {
+//!         let mut size = 0;
+//!         let p = PyUnicode_AsUTF8AndSize(obj_repr, &mut size);
+//!         if !p.is_null() {
+//!             let s = std::str::from_utf8_unchecked(std::slice::from_raw_parts(
+//!                 p.cast::<u8>(),
+//!                 size as usize,
+//!             ));
+//!             let msg = format!("cannot fit {} in 32 bits\0", s);
+//!
+//!             PyErr_SetString(PyExc_OverflowError, msg.as_ptr().cast::<c_char>());
+//!         }
+//!         Py_DECREF(obj_repr);
+//!     }
+//! }
+//!
+//! pub unsafe extern "C" fn sum_as_string(
+//!     _self: *mut PyObject,
+//!     args: *mut *mut PyObject,
+//!     nargs: Py_ssize_t,
+//! ) -> *mut PyObject {
+//!     if nargs != 2 {
+//!         PyErr_SetString(
+//!             PyExc_TypeError,
+//!             c_str!("sum_as_string expected 2 positional arguments").as_ptr(),
+//!         );
+//!         return std::ptr::null_mut();
+//!     }
+//!
+//!     let (first, second) = (*args, *args.add(1));
+//!
+//!     let first = match parse_arg_as_i32(first, 1) {
+//!         Some(x) => x,
+//!         None => return std::ptr::null_mut(),
+//!     };
+//!     let second = match parse_arg_as_i32(second, 2) {
+//!         Some(x) => x,
+//!         None => return std::ptr::null_mut(),
+//!     };
+//!
+//!     match first.checked_add(second) {
+//!         Some(sum) => {
+//!             let string = sum.to_string();
+//!             PyUnicode_FromStringAndSize(string.as_ptr().cast::<c_char>(), string.len() as isize)
+//!         }
+//!         None => {
+//!             PyErr_SetString(
+//!                 PyExc_OverflowError,
+//!                 c_str!("arguments too large to add").as_ptr(),
+//!             );
+//!             std::ptr::null_mut()
+//!         }
+//!     }
+//! }
 //! ```
 //!
 //! With those two files in place, now `maturin` needs to be installed. This can be done using