Skip to content
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

Merged

Conversation

mishamsk
Copy link
Contributor

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

  • Updated test case with 2 examples from the issue + a couple of mine, including one that would generate invalid python program before this fix
  • cargo nextest run (check updated snapshot)

Copy link
Contributor

github-actions bot commented Jan 27, 2025

ruff-ecosystem results

Linter (stable)

✅ ecosystem check detected no linter changes.

Linter (preview)

✅ ecosystem check detected no linter changes.

@dylwil3 dylwil3 self-assigned this Jan 29, 2025
@dylwil3 dylwil3 changed the title [pylint] Fix missing parens in unsafe fix (PLC2801) [pylint] Fix missing parens in unsafe fix for unnecessary-dunder-call (PLC2801) Jan 30, 2025
Copy link
Member

@MichaReiser MichaReiser left a 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

Comment on lines 605 to 610
pub(crate) trait Precedence {
/// Returns the precedence level of the expression.
///
/// Higher values indicate higher precedence.
fn precedence(&self) -> ExprPrecedence;
}
Copy link
Member

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, FromandFrom`

Copy link
Contributor Author

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

/// 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.

Copy link
Member

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.

Comment on lines 580 to 599
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,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we align the names (enum and variants) with what we have in the formatter and parser. It should simplify unifying the enums long-term

Copy link
Contributor Author

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.

Copy link
Member

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.

@dhruvmanila dhruvmanila added bug Something isn't working preview Related to preview mode features labels Feb 4, 2025
@MichaReiser MichaReiser enabled auto-merge (squash) February 4, 2025 09:49
@MichaReiser MichaReiser merged commit 15dd3b5 into astral-sh:main Feb 4, 2025
21 checks passed
dcreager added a commit that referenced this pull request Feb 4, 2025
* 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)
  ...
@mishamsk mishamsk deleted the 15745-fix-PLC2801-missing-parens branch February 4, 2025 23:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working preview Related to preview mode features
Projects
None yet
Development

Successfully merging this pull request may close these issues.

PLC2801 fix needs parentheses before subscript or attribute access
4 participants