Skip to content

Commit 2cee7fe

Browse files
authored
Merge pull request #2083 from aviramha/magic_methods
verify py method args count
2 parents e01add5 + cbfb5ac commit 2cee7fe

16 files changed

+520
-18
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2626
- Expose `pyo3-build-config` APIs for cross-compiling and Python configuration discovery for use in other projects. [#1996](https://github.com/PyO3/pyo3/pull/1996)
2727
- Add buffer magic methods `__getbuffer__` and `__releasebuffer__` to `#[pymethods]`. [#2067](https://github.com/PyO3/pyo3/pull/2067)
2828
- Accept paths in `wrap_pyfunction` and `wrap_pymodule`. [#2081](https://github.com/PyO3/pyo3/pull/2081)
29+
- Add check for correct number of arguments on magic methods. [#2083](https://github.com/PyO3/pyo3/pull/2083)
2930

3031
### Changed
3132

@@ -46,6 +47,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
4647
- internal `handle_panic` helper [#2074](https://github.com/PyO3/pyo3/pull/2074)
4748
- `#[pyfunction]` and `#[pymethods]` argument extraction [#2075](https://github.com/PyO3/pyo3/pull/2075) [#2085](https://github.com/PyO3/pyo3/pull/2085)
4849
- `#[pyclass]` type object creation [#2076](https://github.com/PyO3/pyo3/pull/2076) [#2081](https://github.com/PyO3/pyo3/pull/2081)
50+
- `__ipow__` now supports modulo argument on Python 3.8+. [#2083](https://github.com/PyO3/pyo3/pull/2083)
51+
- `pyo3-macros-backend` is now compiled with PyO3 cfgs to enable different magic method definitions based on version. [#2083](https://github.com/PyO3/pyo3/pull/2083)
4952

5053
### Removed
5154

guide/src/class/protocols.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -289,13 +289,15 @@ This trait also has support the augmented arithmetic assignments (`+=`, `-=`,
289289
* `fn __itruediv__(&'p mut self, other: impl FromPyObject) -> PyResult<()>`
290290
* `fn __ifloordiv__(&'p mut self, other: impl FromPyObject) -> PyResult<()>`
291291
* `fn __imod__(&'p mut self, other: impl FromPyObject) -> PyResult<()>`
292-
* `fn __ipow__(&'p mut self, other: impl FromPyObject) -> PyResult<()>`
292+
* `fn __ipow__(&'p mut self, other: impl FromPyObject, modulo: impl FromPyObject) -> PyResult<()>` on Python 3.8^
293+
* `fn __ipow__(&'p mut self, other: impl FromPyObject) -> PyResult<()>` on Python 3.7 see https://bugs.python.org/issue36379
293294
* `fn __ilshift__(&'p mut self, other: impl FromPyObject) -> PyResult<()>`
294295
* `fn __irshift__(&'p mut self, other: impl FromPyObject) -> PyResult<()>`
295296
* `fn __iand__(&'p mut self, other: impl FromPyObject) -> PyResult<()>`
296297
* `fn __ior__(&'p mut self, other: impl FromPyObject) -> PyResult<()>`
297298
* `fn __ixor__(&'p mut self, other: impl FromPyObject) -> PyResult<()>`
298299

300+
299301
The following methods implement the unary arithmetic operations (`-`, `+`, `abs()` and `~`):
300302

301303
* `fn __neg__(&'p self) -> PyResult<impl ToPyObject>`

pyo3-macros-backend/src/defs.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -421,7 +421,7 @@ pub const NUM: Proto = Proto {
421421
.args(&["Other"])
422422
.has_self(),
423423
MethodProto::new("__ipow__", "PyNumberIPowProtocol")
424-
.args(&["Other"])
424+
.args(&["Other", "Modulo"])
425425
.has_self(),
426426
MethodProto::new("__ilshift__", "PyNumberILShiftProtocol")
427427
.args(&["Other"])

pyo3-macros-backend/src/pymethod.rs

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -532,8 +532,8 @@ const __IMOD__: SlotDef = SlotDef::new("Py_nb_inplace_remainder", "binaryfunc")
532532
.arguments(&[Ty::Object])
533533
.extract_error_mode(ExtractErrorMode::NotImplemented)
534534
.return_self();
535-
const __IPOW__: SlotDef = SlotDef::new("Py_nb_inplace_power", "ternaryfunc")
536-
.arguments(&[Ty::Object, Ty::Object])
535+
const __IPOW__: SlotDef = SlotDef::new("Py_nb_inplace_power", "ipowfunc")
536+
.arguments(&[Ty::Object, Ty::IPowModulo])
537537
.extract_error_mode(ExtractErrorMode::NotImplemented)
538538
.return_self();
539539
const __ILSHIFT__: SlotDef = SlotDef::new("Py_nb_inplace_lshift", "binaryfunc")
@@ -613,6 +613,7 @@ enum Ty {
613613
Object,
614614
MaybeNullObject,
615615
NonNullObject,
616+
IPowModulo,
616617
CompareOp,
617618
Int,
618619
PyHashT,
@@ -626,6 +627,7 @@ impl Ty {
626627
match self {
627628
Ty::Object | Ty::MaybeNullObject => quote! { *mut _pyo3::ffi::PyObject },
628629
Ty::NonNullObject => quote! { ::std::ptr::NonNull<_pyo3::ffi::PyObject> },
630+
Ty::IPowModulo => quote! { _pyo3::impl_::pymethods::IPowModulo },
629631
Ty::Int | Ty::CompareOp => quote! { ::std::os::raw::c_int },
630632
Ty::PyHashT => quote! { _pyo3::ffi::Py_hash_t },
631633
Ty::PySsizeT => quote! { _pyo3::ffi::Py_ssize_t },
@@ -679,6 +681,16 @@ impl Ty {
679681
);
680682
extract_object(cls, arg.ty, ident, extract)
681683
}
684+
Ty::IPowModulo => {
685+
let extract = handle_error(
686+
extract_error_mode,
687+
py,
688+
quote! {
689+
#ident.extract(#py)
690+
},
691+
);
692+
extract_object(cls, arg.ty, ident, extract)
693+
}
682694
Ty::CompareOp => {
683695
let extract = handle_error(
684696
extract_error_mode,
@@ -888,8 +900,11 @@ fn generate_method_body(
888900
) -> Result<TokenStream> {
889901
let self_conversion = spec.tp.self_conversion(Some(cls), extract_error_mode);
890902
let rust_name = spec.name;
891-
let (arg_idents, conversions) =
903+
let (arg_idents, arg_count, conversions) =
892904
extract_proto_arguments(cls, py, &spec.args, arguments, extract_error_mode)?;
905+
if arg_count != arguments.len() {
906+
bail_spanned!(spec.name.span() => format!("Expected {} arguments, got {}", arguments.len(), arg_count));
907+
}
893908
let call = quote! { _pyo3::callback::convert(#py, #cls::#rust_name(_slf, #(#arg_idents),*)) };
894909
let body = if let Some(return_mode) = return_mode {
895910
return_mode.return_call_output(py, call)
@@ -1058,7 +1073,7 @@ fn extract_proto_arguments(
10581073
method_args: &[FnArg],
10591074
proto_args: &[Ty],
10601075
extract_error_mode: ExtractErrorMode,
1061-
) -> Result<(Vec<Ident>, TokenStream)> {
1076+
) -> Result<(Vec<Ident>, usize, TokenStream)> {
10621077
let mut arg_idents = Vec::with_capacity(method_args.len());
10631078
let mut non_python_args = 0;
10641079

@@ -1077,9 +1092,8 @@ fn extract_proto_arguments(
10771092
arg_idents.push(ident);
10781093
}
10791094
}
1080-
10811095
let conversions = quote!(#(#args_conversions)*);
1082-
Ok((arg_idents, conversions))
1096+
Ok((arg_idents, non_python_args, conversions))
10831097
}
10841098

10851099
struct StaticIdent(&'static str);

src/class/number.rs

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -221,7 +221,7 @@ pub trait PyNumberProtocol<'p>: PyClass {
221221
{
222222
unimplemented!()
223223
}
224-
fn __ipow__(&'p mut self, other: Self::Other) -> Self::Result
224+
fn __ipow__(&'p mut self, other: Self::Other, modulo: Option<Self::Modulo>) -> Self::Result
225225
where
226226
Self: PyNumberIPowProtocol<'p>,
227227
{
@@ -504,6 +504,8 @@ pub trait PyNumberIDivmodProtocol<'p>: PyNumberProtocol<'p> {
504504
pub trait PyNumberIPowProtocol<'p>: PyNumberProtocol<'p> {
505505
type Other: FromPyObject<'p>;
506506
type Result: IntoPyCallbackOutput<()>;
507+
// See https://bugs.python.org/issue36379
508+
type Modulo: FromPyObject<'p>;
507509
}
508510

509511
#[allow(clippy::upper_case_acronyms)]
@@ -718,17 +720,28 @@ py_binary_self_func!(imod, PyNumberIModProtocol, T::__imod__);
718720
pub unsafe extern "C" fn ipow<T>(
719721
slf: *mut ffi::PyObject,
720722
other: *mut ffi::PyObject,
721-
_modulo: *mut ffi::PyObject,
723+
modulo: crate::impl_::pymethods::IPowModulo,
722724
) -> *mut ffi::PyObject
723725
where
724726
T: for<'p> PyNumberIPowProtocol<'p>,
725727
{
726-
// NOTE: Somehow __ipow__ causes SIGSEGV in Python < 3.8 when we extract,
727-
// so we ignore it. It's the same as what CPython does.
728728
crate::callback_body!(py, {
729729
let slf_cell = py.from_borrowed_ptr::<crate::PyCell<T>>(slf);
730730
let other = py.from_borrowed_ptr::<crate::PyAny>(other);
731-
call_operator_mut!(py, slf_cell, __ipow__, other).convert(py)?;
731+
slf_cell
732+
.try_borrow_mut()?
733+
.__ipow__(
734+
extract_or_return_not_implemented!(other),
735+
match modulo.extract(py) {
736+
Ok(value) => value,
737+
Err(_) => {
738+
let res = crate::ffi::Py_NotImplemented();
739+
crate::ffi::Py_INCREF(res);
740+
return Ok(res);
741+
}
742+
},
743+
)
744+
.convert(py)?;
732745
ffi::Py_INCREF(slf);
733746
Ok::<_, PyErr>(slf)
734747
})

src/ffi/mod.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,3 +204,7 @@ mod cpython;
204204

205205
#[cfg(not(Py_LIMITED_API))]
206206
pub use self::cpython::*;
207+
208+
/// Helper to enable #\[pymethods\] to see the workaround for __ipow__ on Python 3.7
209+
#[doc(hidden)]
210+
pub use crate::impl_::pymethods::ipowfunc;

src/impl_.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,6 @@ pub mod frompyobject;
1212
pub(crate) mod not_send;
1313
#[doc(hidden)]
1414
pub mod pyclass;
15+
#[doc(hidden)]
16+
pub mod pymethods;
1517
pub mod pymodule;

src/impl_/pymethods.rs

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
use crate::{ffi, FromPyObject, PyAny, PyResult, Python};
2+
3+
/// Python 3.8 and up - __ipow__ has modulo argument correctly populated.
4+
#[cfg(Py_3_8)]
5+
#[repr(transparent)]
6+
pub struct IPowModulo(*mut ffi::PyObject);
7+
8+
/// Python 3.7 and older - __ipow__ does not have modulo argument correctly populated.
9+
#[cfg(not(Py_3_8))]
10+
#[repr(transparent)]
11+
pub struct IPowModulo(std::mem::MaybeUninit<*mut ffi::PyObject>);
12+
13+
/// Helper to use as pymethod ffi definition
14+
#[allow(non_camel_case_types)]
15+
pub type ipowfunc = unsafe extern "C" fn(
16+
arg1: *mut ffi::PyObject,
17+
arg2: *mut ffi::PyObject,
18+
arg3: IPowModulo,
19+
) -> *mut ffi::PyObject;
20+
21+
impl IPowModulo {
22+
#[cfg(Py_3_8)]
23+
#[inline]
24+
pub fn extract<'a, T: FromPyObject<'a>>(self, py: Python<'a>) -> PyResult<T> {
25+
unsafe { py.from_borrowed_ptr::<PyAny>(self.0) }.extract()
26+
}
27+
28+
#[cfg(not(Py_3_8))]
29+
#[inline]
30+
pub fn extract<'a, T: FromPyObject<'a>>(self, py: Python<'a>) -> PyResult<T> {
31+
unsafe { py.from_borrowed_ptr::<PyAny>(ffi::Py_None()) }.extract()
32+
}
33+
}

0 commit comments

Comments
 (0)