Skip to content

Commit

Permalink
add descriptive error msg for __traverse__ receivers other than `&s…
Browse files Browse the repository at this point in the history
…elf` (#4045)

* add descriptive error msg for `__traverse__` receivers other than `self`

* add newsfragment

* improve error message
  • Loading branch information
Icxolu authored Apr 4, 2024
1 parent a4aea23 commit 7a00b4d
Show file tree
Hide file tree
Showing 4 changed files with 88 additions and 26 deletions.
1 change: 1 addition & 0 deletions newsfragments/4045.fixed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add better error message on wrong receiver extraction in `__traverse__`.
21 changes: 18 additions & 3 deletions pyo3-macros-backend/src/pymethod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -434,9 +434,24 @@ fn impl_traverse_slot(
let Ctx { pyo3_path } = ctx;
if let (Some(py_arg), _) = split_off_python_arg(&spec.signature.arguments) {
return Err(syn::Error::new_spanned(py_arg.ty, "__traverse__ may not take `Python`. \
Usually, an implementation of `__traverse__` should do nothing but calls to `visit.call`. \
Most importantly, safe access to the GIL is prohibited inside implementations of `__traverse__`, \
i.e. `Python::with_gil` will panic."));
Usually, an implementation of `__traverse__(&self, visit: PyVisit<'_>) -> Result<(), PyTraverseError>` \
should do nothing but calls to `visit.call`. Most importantly, safe access to the GIL is prohibited \
inside implementations of `__traverse__`, i.e. `Python::with_gil` will panic."));
}

// check that the receiver does not try to smuggle an (implicit) `Python` token into here
if let FnType::Fn(SelfType::TryFromBoundRef(span))
| FnType::Fn(SelfType::Receiver {
mutable: true,
span,
}) = spec.tp
{
bail_spanned! { span =>
"__traverse__ may not take a receiver other than `&self`. Usually, an implementation of \
`__traverse__(&self, visit: PyVisit<'_>) -> Result<(), PyTraverseError>` \
should do nothing but calls to `visit.call`. Most importantly, safe access to the GIL is prohibited \
inside implementations of `__traverse__`, i.e. `Python::with_gil` will panic."
}
}

let rust_fn_ident = spec.name;
Expand Down
50 changes: 45 additions & 5 deletions tests/ui/traverse.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,55 @@
use pyo3::prelude::*;
use pyo3::PyVisit;
use pyo3::PyTraverseError;
use pyo3::PyVisit;

#[pyclass]
struct TraverseTriesToTakePyRef {}

#[pymethods]
impl TraverseTriesToTakePyRef {
fn __traverse__(slf: PyRef<Self>, visit: PyVisit) {}
fn __traverse__(slf: PyRef<Self>, visit: PyVisit) -> Result<(), PyTraverseError> {
Ok(())
}
}

#[pyclass]
struct TraverseTriesToTakePyRefMut {}

#[pymethods]
impl TraverseTriesToTakePyRefMut {
fn __traverse__(slf: PyRefMut<Self>, visit: PyVisit) -> Result<(), PyTraverseError> {
Ok(())
}
}

#[pyclass]
struct TraverseTriesToTakeBound {}

#[pymethods]
impl TraverseTriesToTakeBound {
fn __traverse__(slf: Bound<'_, Self>, visit: PyVisit) -> Result<(), PyTraverseError> {
Ok(())
}
}

#[pyclass]
struct TraverseTriesToTakeMutSelf {}

#[pymethods]
impl TraverseTriesToTakeMutSelf {
fn __traverse__(&mut self, visit: PyVisit) -> Result<(), PyTraverseError> {
Ok(())
}
}

#[pyclass]
struct TraverseTriesToTakeSelf {}

#[pymethods]
impl TraverseTriesToTakeSelf {
fn __traverse__(&self, visit: PyVisit) -> Result<(), PyTraverseError> {
Ok(())
}
}

#[pyclass]
Expand All @@ -19,9 +61,7 @@ impl Class {
Ok(())
}

fn __clear__(&mut self) {
}
fn __clear__(&mut self) {}
}


fn main() {}
42 changes: 24 additions & 18 deletions tests/ui/traverse.stderr
Original file line number Diff line number Diff line change
@@ -1,23 +1,29 @@
error: __traverse__ may not take `Python`. Usually, an implementation of `__traverse__` should do nothing but calls to `visit.call`. Most importantly, safe access to the GIL is prohibited inside implementations of `__traverse__`, i.e. `Python::with_gil` will panic.
--> tests/ui/traverse.rs:18:32
error: __traverse__ may not take a receiver other than `&self`. Usually, an implementation of `__traverse__(&self, visit: PyVisit<'_>) -> Result<(), PyTraverseError>` should do nothing but calls to `visit.call`. Most importantly, safe access to the GIL is prohibited inside implementations of `__traverse__`, i.e. `Python::with_gil` will panic.
--> tests/ui/traverse.rs:10:26
|
18 | fn __traverse__(&self, py: Python<'_>, visit: PyVisit<'_>) -> Result<(), PyTraverseError> {
| ^^^^^^^^^^
10 | fn __traverse__(slf: PyRef<Self>, visit: PyVisit) -> Result<(), PyTraverseError> {
| ^^^^^

error: __traverse__ may not take a receiver other than `&self`. Usually, an implementation of `__traverse__(&self, visit: PyVisit<'_>) -> Result<(), PyTraverseError>` should do nothing but calls to `visit.call`. Most importantly, safe access to the GIL is prohibited inside implementations of `__traverse__`, i.e. `Python::with_gil` will panic.
--> tests/ui/traverse.rs:20:26
|
20 | fn __traverse__(slf: PyRefMut<Self>, visit: PyVisit) -> Result<(), PyTraverseError> {
| ^^^^^^^^

error[E0308]: mismatched types
--> tests/ui/traverse.rs:9:6
error: __traverse__ may not take a receiver other than `&self`. Usually, an implementation of `__traverse__(&self, visit: PyVisit<'_>) -> Result<(), PyTraverseError>` should do nothing but calls to `visit.call`. Most importantly, safe access to the GIL is prohibited inside implementations of `__traverse__`, i.e. `Python::with_gil` will panic.
--> tests/ui/traverse.rs:30:26
|
8 | #[pymethods]
| ------------ arguments to this function are incorrect
9 | impl TraverseTriesToTakePyRef {
| ______^
10 | | fn __traverse__(slf: PyRef<Self>, visit: PyVisit) {}
| |___________________^ expected fn pointer, found fn item
30 | fn __traverse__(slf: Bound<'_, Self>, visit: PyVisit) -> Result<(), PyTraverseError> {
| ^^^^^

error: __traverse__ may not take a receiver other than `&self`. Usually, an implementation of `__traverse__(&self, visit: PyVisit<'_>) -> Result<(), PyTraverseError>` should do nothing but calls to `visit.call`. Most importantly, safe access to the GIL is prohibited inside implementations of `__traverse__`, i.e. `Python::with_gil` will panic.
--> tests/ui/traverse.rs:40:21
|
= note: expected fn pointer `for<'a, 'b> fn(&'a TraverseTriesToTakePyRef, PyVisit<'b>) -> Result<(), PyTraverseError>`
found fn item `for<'a, 'b> fn(pyo3::PyRef<'a, TraverseTriesToTakePyRef, >, PyVisit<'b>) {TraverseTriesToTakePyRef::__traverse__}`
note: function defined here
--> src/impl_/pymethods.rs
40 | fn __traverse__(&mut self, visit: PyVisit) -> Result<(), PyTraverseError> {
| ^

error: __traverse__ may not take `Python`. Usually, an implementation of `__traverse__(&self, visit: PyVisit<'_>) -> Result<(), PyTraverseError>` should do nothing but calls to `visit.call`. Most importantly, safe access to the GIL is prohibited inside implementations of `__traverse__`, i.e. `Python::with_gil` will panic.
--> tests/ui/traverse.rs:60:32
|
| pub unsafe fn _call_traverse<T>(
| ^^^^^^^^^^^^^^
60 | fn __traverse__(&self, py: Python<'_>, visit: PyVisit<'_>) -> Result<(), PyTraverseError> {
| ^^^^^^^^^^

0 comments on commit 7a00b4d

Please sign in to comment.