-
Notifications
You must be signed in to change notification settings - Fork 1.6k
[pyupgrade] Apply UP008 only when the __class__ cell exists
#19424
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
Conversation
|
01d1724 to
a00d546
Compare
|
@ntBre Could you please review a PR? |
|
Will do! Sorry, I'm a bit behind on my review queue, but this has been on the list. |
ntBre
left a comment
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, this looks great! Sorry again for the delay.
I just had a few small suggestions.
Thanks for the docs links too. I skimmed over a few of those, and I think you've captured everything I understood from them and from the issue too :)
crates/ruff_linter/src/rules/pyupgrade/rules/super_call_with_parameters.rs
Outdated
Show resolved
Hide resolved
crates/ruff_linter/src/rules/pyupgrade/rules/super_call_with_parameters.rs
Show resolved
Hide resolved
| let has_load_super = finder | ||
| .names | ||
| .iter() | ||
| .any(|expr| expr.id.as_str() == "super" && expr.ctx.is_load()); | ||
| let has_load_dunder_class = finder | ||
| .names | ||
| .iter() | ||
| .any(|expr| expr.id.as_str() == "__class__" && expr.ctx.is_load()); |
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.
Because these are both any, I think we can just check these conditions as the visitor visits and avoid needing to collect a Vec of expressions. We may even be able to terminate the visitor early in that case.
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.
My intention was to make it simple and not necessarily optimized.
I have reworked it, and now it is implemented according to your recommendation, but I think it is more complex to read. I would appreciate any simplification advice.
BTW, it was intentional to pass conditions due to initialization rather than hardcode them in visit_expr. In my opinion, it provides more flexibility for future changes.
crates/ruff_linter/src/rules/pyupgrade/rules/super_call_with_parameters.rs
Outdated
Show resolved
Hide resolved
crates/ruff_linter/src/rules/pyupgrade/rules/super_call_with_parameters.rs
Show resolved
Hide resolved
db72ff9 to
f1e50af
Compare
|
I just came back to this notification right after reviewing #19783. I somehow hadn't connected these two before, but now I'm wondering if they have the same root cause. I think if we're able to work out a neat, general solution for binding the |
Summary -- This PR aims to resolve (or help to resolve) #18442 and #19357 by encoding the CPython semantics around the `__class__` cell in our semantic model. Namely, > __class__ is an implicit closure reference created by the compiler if any methods in a class body refer to either __class__ or super. from the Python [docs](https://docs.python.org/3/reference/datamodel.html#creating-the-class-object). As noted in the code comment by @AlexWaygood, we don't fully model this behavior, opting always to create the `__class__` cell binding in a new `ScopeKind::ClassCell` around each method definition, without checking if any method in the class body actually refers to `__class__` or `super`. As such, this PR may only help with #18442 and not #19357. Test Plan -- Existing tests, plus (TODO) tests from #19783 and #19424, which tackled the issues above on a per-rule basis. Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
Summary -- This PR aims to resolve (or help to resolve) #18442 and #19357 by encoding the CPython semantics around the `__class__` cell in our semantic model. Namely, > __class__ is an implicit closure reference created by the compiler if any methods in a class body refer to either __class__ or super. from the Python [docs](https://docs.python.org/3/reference/datamodel.html#creating-the-class-object). As noted in the code comment by @AlexWaygood, we don't fully model this behavior, opting always to create the `__class__` cell binding in a new `ScopeKind::ClassCell` around each method definition, without checking if any method in the class body actually refers to `__class__` or `super`. As such, this PR may only help with #18442 and not #19357. Test Plan -- Existing tests, plus (TODO) tests from #19783 and #19424, which tackled the issues above on a per-rule basis. Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
|
We added a new Thanks again for your work here :) |
|
@ntBre Good news! Here are my thoughts after reviewing the new feature.
Since I've already implemented tailored |
f1e50af to
db109c8
Compare
|
Thanks for looking into it and rebasing the PR!
Yes that's right, we didn't try to detect if
Makes sense to me, I'll give this another review soon! |
ntBre
left a comment
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! Just a couple of minor simplification suggestions and one corner case from the docs.
crates/ruff_linter/src/rules/pyupgrade/rules/super_call_with_parameters.rs
Outdated
Show resolved
Hide resolved
crates/ruff_linter/src/rules/pyupgrade/rules/super_call_with_parameters.rs
Outdated
Show resolved
Hide resolved
crates/ruff_linter/src/rules/pyupgrade/rules/super_call_with_parameters.rs
Show resolved
Hide resolved
Skip UP008 diagnostic for `builtins.super(Class, self)` calls when neither `__class__` nor `super` are referenced locally, preventing incorrect fixes. Issue: astral-sh#19357
`has_local_dunder_class_var_ref` checks if `super` or `__class__` expr.ctx is load and `__class__` var in scope didn't override. Issue: astral-sh#19357
renamed FunctionScopeNameFinder to AnyExpressionFinder moved AnyExpressionFinder to super_call_with_parameters.rs updated test cases and snapshots removed duplicated parents Now AnyExpressionFinder finds any expression by given conditions
db109c8 to
27ab45c
Compare
27ab45c to
c394853
Compare
ntBre
left a comment
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!
pyupgrade] Apply UP008 only when the __class__ cell exists (UP008)pyupgrade] Apply UP008 only when the __class__ cell exists (UP008)
pyupgrade] Apply UP008 only when the __class__ cell exists (UP008)pyupgrade] Apply UP008 only when the __class__ cell exists
Summary
Resolves #19357
Skip UP008 diagnostic for
builtins.super(P, self)calls when__class__is not referenced locally, preventing incorrect fixes.Note: I haven't found concrete information about which cases
__class__will be loaded into the scope. Let me know if anyone has references, it would be useful to enhance the implementation. I did a lot of tests to determine when__class__is loaded. Considered sources:As I understand it, Python will inject at runtime into local scope a
__class__variable if it detects references tosuperor__class__. This allows callingsuper()and passing appropriate parameters. However, the compiler doesn't do the same forbuiltins.super, so we need to somehow introduce__class__into the local scope.I figured out
__class__will be in scope with valid value when two conditions are met:superor__class__names have been loaded within function scope__class__is not overridden.I think my solution isn't elegant, so I would be appreciate a detailed review.
Test Plan
Added 19 test cases, updated snapshots.