-
Notifications
You must be signed in to change notification settings - Fork 763
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
verify py method args count #2083
Conversation
48b3dd0
to
59c3740
Compare
trying to fix #2010 |
59c3740
to
77d3491
Compare
77d3491
to
9ae339d
Compare
Ah! Found it https://bugs.python.org/issue36379 |
9ae339d
to
5f596e0
Compare
I'm not sure why the |
204e6ff
to
8f271fd
Compare
8f271fd
to
adcaafb
Compare
Ready for review @davidhewitt |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wow, thanks for fixing this up, and finally getting to the bottom of the issue with __ipow__
is awesome 👍
As an FYI my laptop keyboard is currently broken so I'm not sure how well I can check in on PRs for the next couple of weeks. Writing longer comments is hard on mobile so I may be a bit slow replying sometimes.
I think the build-dependency
on pyo3-build-config
for pyo3-macros-backend
is unfortunately not correct, however there are alternative ways to resolve it. See other comments.
As a bit of bikeshedding, I also wonder if we should always allow users to write three-argument __ipow__
and then just pass None
always on 3.7. If we document this behaviour it seems like the easiest API for users to implement, rather than forcing them to use #[cfg]
.
pyo3-macros-backend/build.rs
Outdated
use pyo3_build_config::pyo3_build_script_impl::{errors::Result, resolve_interpreter_config}; | ||
|
||
fn configure_pyo3() -> Result<()> { | ||
let interpreter_config = resolve_interpreter_config()?; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If I remember correctly, using resolve_interpreter_config
from a proc-macro crate won't work correctly when cross compiling, because the environment variables provided by cargo are for the host system, not the cross-compile target.
So I don't think we can add this build script and use #[cfg]
in pyo3_build_config
. Instead in pyo3_build_config
we should use the runtime APIs, like in
pyo3/pyo3-macros-backend/src/method.rs
Lines 210 to 215 in 2503a2d
fn can_use_fastcall() -> bool { | |
const PY37: pyo3_build_config::PythonVersion = | |
pyo3_build_config::PythonVersion { major: 3, minor: 7 }; | |
let config = pyo3_build_config::get(); | |
config.version >= PY37 && !config.abi3 | |
} |
Sorry that I did not document this 😭
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I thought of using it but I was pretty sure I can't do ifs and logic when defining globals? I am still pretty confident?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed, I think you'll need to define both globals and then check which one to use based on the Python version later in the macro code.
E.g. __IPOW__
and __IPOW_PY_3_7__
, and then check which one to use at runtime.
This approach might not be so easy for #[pyproto]
, in which case I'd be fine with leaving #[pyproto]
broken. We're deprecating it anyway 😁
tests/test_arithmetics.rs
Outdated
#[cfg(Py_3_8)] | ||
#[pymethods] | ||
impl RichComparisonToSelf { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To avoid passing this #[cfg]
pain onto users, do you think we can make it so that users always write three-argument __ipow__
, and then on Python 3.7 we just always fill in None
as the third argument?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure how to implement this approach and not sure if it's correct. How will the user know he wont ever receive values in Py 3.7 (besides doc?) I think compile error and strict adjustment is better...?
The issue happens where the extract layer of PyO3 tries to convert Option when it's actually an invalid value... The interface would be then OptionInvalid where it will check for python version and do it based on that..?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed that compiler errors are more explicit.
I was thinking that given this is such an edge case anyway, and Python code using =**
will always pass None
, it's only if users explicitly invoke __ipow__
that there's ever a non-none value. So it doesn't seem so bad if we say "on CPython 3.7, due to an interpreter bug, you will always get None".
(Actually this makes me wonder, does it work with PyPy?)
If you're willing to give me a few days I'll write a PR which attempts to implement this always-pass-none case, to see if we like it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay. Would we need the globals in that case though? I think not? so I'll wait with further changes until you do it.
If you want you can give me some pointers on how to get it done. I thought maybe setting the Ty::Object
to be Ty::ObjectOrInvalid
?
guide/src/class/protocols.md
Outdated
@@ -288,7 +288,7 @@ This trait also has support the augmented arithmetic assignments (`+=`, `-=`, | |||
* `fn __itruediv__(&'p mut self, other: impl FromPyObject) -> PyResult<()>` | |||
* `fn __ifloordiv__(&'p mut self, other: impl FromPyObject) -> PyResult<()>` | |||
* `fn __imod__(&'p mut self, other: impl FromPyObject) -> PyResult<()>` | |||
* `fn __ipow__(&'p mut self, other: impl FromPyObject) -> PyResult<()>` | |||
* `fn __ipow__(&'p mut self, other: impl FromPyObject, _modulo: impl FromPyObject) -> PyResult<()>` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
* `fn __ipow__(&'p mut self, other: impl FromPyObject, _modulo: impl FromPyObject) -> PyResult<()>` | |
* `fn __ipow__(&'p mut self, other: impl FromPyObject, modulo: impl FromPyObject) -> PyResult<()>` |
We should also document the special case for 3.7 (whether it's to have two arguments, or always pass None
).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changed the doc.. A suggestion for formatting would be welcome (couldn't think of anything pretty)
error: Expected 1 arguments, got 0 | ||
--> tests/ui/invalid_pymethod_proto_args.rs:8:8 | ||
| | ||
8 | fn __truediv__(&self) -> PyResult<()> { | ||
| ^^^^^^^^^^^ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you think we should include the receiver in these counts? E.g. the above becomes Expected 2 arguments, got 1
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, I was thinking about it but it might be a bit of a burden because we can also have "py" argument and take it into account which might be confusing?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah I see. Can we add a test with py
to confirm the behavior is correct in that situation too?
Other than that, I'm happy for us to leave the error message like this for now. We can always make adjustments later after we get user feedback 👍
`pyo3-macros-backend` is now compiled with PyO3 cfgs to enable different magic method definitions based on version. Add check for correct number of arguments on magic methods.
adcaafb
to
50659b6
Compare
c1c25d8
to
a9b98b7
Compare
I've pushed a commit which implements the strategy to always pass |
The changes look great. I'm still a bit concerned about the user getting confused why |
Hmm, I take your point. It's not possible for us to emit a compiler warning (unless on nightly). Maybe instead of always returning At least that way users will only need to have the If you agree with that idea, I'll pass this PR back into your hands to finish off (at your leisure) if that's ok? I'm a bit tight on time for the next few days, so I'll end up being a blocker most likely 😬 |
I'm not sure how you mean to achieve that without blocking users the usage of |
If tests pass now I'm okay with merging as is. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍 thanks very much again for fixing this!
This fixes #2010. During the change I found out that
__ipow__
didn't receive the correct number of arguments.While fixing it, I encountered a CPython 3.7 bug that passes invalid 3rd parameter in 3.7, so I made the parameters version dependant (3.7>= use 2 params, 3.8>= 3 params)