-
Notifications
You must be signed in to change notification settings - Fork 1.2k
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
[pylint
] Fix missing parens in unsafe fix for unnecessary-dunder-call
(PLC2801
)
#15762
[pylint
] Fix missing parens in unsafe fix for unnecessary-dunder-call
(PLC2801
)
#15762
Conversation
|
pylint
] Fix missing parens in unsafe fix for unnecessary-dunder-call
(PLC2801
)
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 overall looks good. Extracting a common OperatorPrecedence
enum into the ast
crate seems like a good idea because this PR now adds the third version of OperatorPrecedence
enum but I suggest that we do this as a follow up PR. For now, I recommend aligning the enum and variant names with the other OperatorPrecedence
enums
crates/ruff_linter/src/rules/pylint/rules/unnecessary_dunder_call.rs
Outdated
Show resolved
Hide resolved
pub(crate) trait Precedence { | ||
/// Returns the precedence level of the expression. | ||
/// | ||
/// Higher values indicate higher precedence. | ||
fn precedence(&self) -> ExprPrecedence; | ||
} |
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'd avoid a custom trait here and instead implement From<&Expr>
for ExprPrecedence, as well as
From,
Fromand
From`
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 about it. The reason I went a custom trait route is that From
either encourages to either use into()
which is less readable outside of IDE (when types are not apparent), or would force writing ExprPrecedence::from(some_node)
which is readable but longer. I expect compiler to emit exact same code for both.
Interestingly enough, both parser and formatter code include the same method for one specific expression too
ruff/crates/ruff_python_parser/src/parser/expression.rs
Lines 2427 to 2435 in 138e70b
/// Returns the [`OperatorPrecedence`] for the given operator token or [None] if the token | |
/// isn't an operator token. | |
fn precedence(&self) -> OperatorPrecedence { | |
match self { | |
BinaryLikeOperator::Boolean(bool_op) => OperatorPrecedence::from(*bool_op), | |
BinaryLikeOperator::Comparison(_) => OperatorPrecedence::ComparisonsMembershipIdentity, | |
BinaryLikeOperator::Binary(bin_op) => OperatorPrecedence::from(*bin_op), | |
} | |
} |
but rely on OperatorPrecedence::from(some_node)
in all other cases.
I am not married to the custom trait idea, if you have a strong opinion here - I can easily change that. Either in this PR, or as a follow-up while moving the whole thing to AST module.
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 removed the trait for now as it isn't needed when we move to the ruff_python_ast
crate (because we can directly implement it on Expr
).
I also renamed the enum to OperatorPrecedence
to better match what we have in other crates.
Min = 0, | ||
Yield = 10, | ||
Assign = 15, | ||
Starred = 20, | ||
Lambda = 25, | ||
IfElse = 30, | ||
BoolOr = 35, | ||
BoolAnd = 40, | ||
BoolNot = 45, | ||
Compare = 50, | ||
BitwiseXorOr = 55, | ||
BitwiseAnd = 60, | ||
BitwiseShifts = 65, | ||
AddSub = 70, | ||
MultDiv = 75, | ||
Unary = 80, | ||
Power = 85, | ||
Await = 90, | ||
CallAttr = 95, | ||
Atomic = 100, |
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.
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.
First of all, I do not think that having even 3 enums is necessary bad. At least two separate ones make sense:
- Formatter enum is indeed an operator precedence one, as it is purpose is to split operators
- The one I've created and the parser one are both expression precedence and I would keep that naming.
- But unifying the parser one would go against @dhruvmanila comment, that explicitly excludes some expressions. I didn't investigate why, but I trust there were good reasons to do so
As for variant names - tbh. I like the shorter naming that aligns with Python docs better (formatter & mine), but since my enum logically closer to parser I changed names to mostly match parser version, where possible.
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.
For the parser, the enum only contains precedence levels for the operators and not for all expressions. The main reason is that the parser utilizes the Pratt parsing algorithm to parse binary expressions which is the main use case for this enum. While, the other expressions are parsed normally and implemented as separate methods. But, that doesn't necessarily mean that it can't be unified to include other variants as well. We'll just have to be careful that the parser starts the binary expression parsing from the correct precedence level.
* main: (66 commits) [red-knot] Use ternary decision diagrams (TDDs) for visibility constraints (#15861) [`pyupgrade`] Rename private type parameters in PEP 695 generics (`UP049`) (#15862) Simplify the `StringFlags` trait (#15944) [`flake8-pyi`] Make `PYI019` autofixable for `.py` files in preview mode as well as stubs (#15889) Docs (`linter.md`): clarify that Python files are always searched for in subdirectories (#15882) [`flake8-pyi`] Make PEP-695 functions with multiple type parameters fixable by PYI019 again (#15938) [red-knot] Use unambiguous invalid-syntax-construct for suppression comment test (#15933) Make `Binding::range()` point to the range of a type parameter's name, not the full type parameter (#15935) Update black deviations (#15928) [red-knot] MDTest: Fix line numbers in error messages (#15932) Preserve triple quotes and prefixes for strings (#15818) [red-knot] Hand-written MDTest parser (#15926) [`pylint`] Fix missing parens in unsafe fix for `unnecessary-dunder-call` (`PLC2801`) (#15762) nit: docs for ignore & select (#15883) [airflow] `BashOperator` has been moved to `airflow.providers.standard.operators.bash.BashOperator` (AIR302) (#15922) [`flake8-logging`] `.exception()` and `exc_info=` outside exception handlers (`LOG004`, `LOG014`) (#15799) [red-knot] Enforce specifying paths for mdtest code blocks in a separate preceding line (#15890) [red-knot] Internal refactoring of visibility constraints API (#15913) [red-knot] Implicit instance attributes (#15811) [`flake8-comprehensions`] Handle extraneous parentheses around list comprehension (`C403`) (#15877) ...
Summary
This fixes #15745
Also, I haven't found a trait/helper to get expression precedence. Even though at least 3 rules check precedence when generating fixes. Generally speaking, unless/until fixes are done via AST generation - many (all?) fixes involving expression rewrites would need to consult precedence to handle parens. Unless I am mistaken.
Thus, I've implemented an enum and a trait. But I didn't dare doing that in semantic/AST crates, although I believe it would be helpful to move them there.
@AlexWaygood @dylwil3 you'll know the codebase better, let me know if you think it should be elevated to some core base crate. If you do, I am happy to implement this, but will need your guidance on where you'd like it to reside.
Test Plan