-
Notifications
You must be signed in to change notification settings - Fork 1.4k
[pycodestyle
] Auto-fix redundant boolean comparison (E712
)
#17090
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
For review, see the comment in #17014 (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 for working on this! I added some code-style suggestions in-line.
To your question on the issue, I think it does make sense to restrict this to the single-comparison case, especially to start. I think that's what you have here.
More broadly, I'm a bit worried about making these changes in this helpers
file, which is used by multiple rules. The original issue was just about E712. If you can get CI passing here, we can see some results from our ecosystem check and see if any other rules are affected. It could still be an improvement to multiple rules, which would be good, but it does mean it's a larger change.
Hey, Thanks for the code-style suggestions I have refactored the code accordingly. On running the tests it has only failed on the E712 rule-related snapshots ( 2 failed ). So should I move the logic to the rule level ( rules/literal_comparison.rs) or keep it in helpers as it may be helpful for other rules. |
I'm still not sure. Can you fix the test failures? If the tests run successfully, we will get a helpful comment like this on this PR showing changes in ruff output on a set of ecosystem packages. That will help us to decide if we need to restrict the changes to E712 or if we can change the helper, which might be more convenient or useful. If there are many changes, or especially if any of them look incorrect, that would strongly suggest limiting the change to E712. |
The below is an example of the failed testcase. Previous snapshot was if res == True was changed to if res is True, Now its if res instead. Snapshot file: crates/ruff_linter/src/rules/pycodestyle/snapshots/ruff_linter__rules__pycodestyle__tests__E712_E712.py.snap
Snapshot: E712_E712.py
Source: crates/ruff_linter/src/rules/pycodestyle/mod.rs:74
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
-old snapshot
+new results
────────────┬───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
9 9 │
10 10 │ ℹ Unsafe fix
11 11 │ 1 1 | #: E712
12 12 │ 2 |-if res == True:
13 │- 2 |+if res is True:
13 │+ 2 |+if res:
14 14 │ 3 3 | pass
15 15 │ 4 4 | #: E712
16 16 │ 5 5 | if res != False:
17 17 │ I am not sure about snapshots. Should I edit this snapshot and replace it with what is expected? Whats the general approach with snapshots? |
You should use cargo-insta to update the snapshots. See the Prerequisites section of the ruff CONTRIBUTING.md file. |
|
code | total | + violation | - violation | + fix | - fix |
---|---|---|---|---|---|
PERF401 | 14 | 7 | 7 | 0 | 0 |
Linter (preview)
ℹ️ ecosystem check detected linter changes. (+7 -7 violations, +0 -0 fixes in 1 projects; 54 projects unchanged)
apache/superset (+7 -7 violations, +0 -0 fixes)
ruff check --no-cache --exit-zero --ignore RUF9 --no-fix --output-format concise --preview --select ALL
+ tests/integration_tests/charts/api_tests.py:350:13: PERF401 Use `list.extend` to create a transformed list - tests/integration_tests/charts/api_tests.py:350:13: PERF401 Use a list comprehension to create a transformed list + tests/integration_tests/charts/api_tests.py:464:13: PERF401 Use `list.extend` to create a transformed list - tests/integration_tests/charts/api_tests.py:464:13: PERF401 Use a list comprehension to create a transformed list + tests/integration_tests/charts/api_tests.py:515:13: PERF401 Use `list.extend` to create a transformed list - tests/integration_tests/charts/api_tests.py:515:13: PERF401 Use a list comprehension to create a transformed list + tests/integration_tests/dashboards/api_tests.py:1280:13: PERF401 Use `list.extend` to create a transformed list - tests/integration_tests/dashboards/api_tests.py:1280:13: PERF401 Use a list comprehension to create a transformed list + tests/integration_tests/dashboards/api_tests.py:1307:13: PERF401 Use `list.extend` to create a transformed list - tests/integration_tests/dashboards/api_tests.py:1307:13: PERF401 Use a list comprehension to create a transformed list + tests/integration_tests/dashboards/api_tests.py:1441:13: PERF401 Use `list.extend` to create a transformed list - tests/integration_tests/dashboards/api_tests.py:1441:13: PERF401 Use a list comprehension to create a transformed list + tests/integration_tests/dashboards/api_tests.py:1506:13: PERF401 Use `list.extend` to create a transformed list - tests/integration_tests/dashboards/api_tests.py:1506:13: PERF401 Use a list comprehension to create a transformed list
Changes by rule (1 rules affected)
code | total | + violation | - violation | + fix | - fix |
---|---|---|---|---|---|
PERF401 | 14 | 7 | 7 | 0 | 0 |
… this to literal_comparisons from generic helpers crate
Hey, I was looking into the E712.py and there were a few edge cases which I missed (the left is a bool and the right is a comparator ). There was this edge case if(True) == TrueElement or x == TrueElement:
pass If this was generally checked for redundancy and converted it would have been ifTrueElement or x == TrueElement:
pass which is syntactically wrong so I added a special case for this where the compare range and left range don't start on the same index. I enclose the comparator in parentheses. So it becomes if(TrueElement) or x == TrueElement:
pass I tried looking at the ast-parsed tree to find its parentheses or not, but I couldn't find any apart from the range numbers so I have used them to check whether to add parentheses to the comparator or not Can you review the changes and let me know if I have to make any other changes? |
I think you can actually rely on yoda-conditions (SIM300) to handle these edge cases (playground link) and avoid the added complexity in this PR. Fixing the simple case like your first two commits would be plenty for this PR, in my opinion! Edit: to be a bit more explicit, SIM300 will recommend reversing the condition so that your initial changes catch the issue, without having to handle the redundancy on the left-hand side separately. |
So should i revert to my previous commit? |
Yes, that is what I was thinking. Does that sound reasonable to you? |
Yeah, if ruff --fix does SIM300 fix first and then if it does the E712 fix then it is completely fine. i tried running this command cargo run -p ruff check --fix --unsafe-fixes test.py the below was fixed as if (True) == TrueElement or x == TrueElement:
pass if (True) is TrueElement or x == TrueElement:
pass So it seems like E712 is being fixed first and then 'yoda-condition' isn't being raised anymore. So I thought to include the redundancy comparison on the left expression as well. Currently, in the expression.rs The literal comparison is being done first and then the Yoda detection. We can change it as below (but I am not sure why SIM300 fix was placed at the end before. Was this intentional?). if checker.enabled(Rule::YodaConditions) {
flake8_simplify::rules::yoda_conditions(checker, expr, left, ops, comparators);
}
if checker.any_enabled(&[Rule::NoneComparison, Rule::TrueFalseComparison]) {
pycodestyle::rules::literal_comparisons(checker, compare);
} Also the test case on #[test_case(Rule::TrueFalseComparison, Path::new("E712.py"))] 54 54 │ 8 |-if True != res:
55 │- 8 |+if not res:
55 │+ 8 |+if True is not res: |
Thanks for looking into this! I asked internally, and it sounds like my suggestion was actually wrong, and your handling of both sides is the right way to go because we want to avoid these kinds of ordering dependencies between rules. I'll give this another review to see if we can simplify some of the parentheses handling, but this is looking good. I also realized that the parentheses might be an issue on the right side of the comparison. Could you add a test case like this (if there's not one already): if x is (True)or False: ... I didn't realize you could do something like that until your example here, so thank you for catching that! |
Hey, that was a case I hadn’t accounted for earlier. I’ve made the necessary changes along with some code cleanup. I believe this should now handle all relevant cases. To ensure validity, we can either enclose the expression in parentheses or add an extra space. I couldn’t identify any other edge cases beyond the need for parentheses, so I’ve opted to wrap the expression in them for now. |
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.
This looks good. I only had one refactoring suggestion until I noticed a question about parentheses too.
crates/ruff_linter/src/rules/pycodestyle/rules/literal_comparisons.rs
Outdated
Show resolved
Hide resolved
@@ -181,7 +181,7 @@ E712.py:22:4: E712 [*] Avoid equality comparisons to `True`; use `if TrueElement | |||
20 20 | var = 1 if cond == True else -1 if cond == False else cond | |||
21 21 | #: E712 | |||
22 |-if (True) == TrueElement or x == TrueElement: | |||
22 |+if (True) is TrueElement or x == TrueElement: | |||
22 |+if (TrueElement) or x == TrueElement: |
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.
Oh, is the parenthesis detection wrong here? I don't think we need these parens.
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.
According to the logic, the comparison of the start indexes is on the (true) == TrueElement
, and the comparator is true
. So, there is a mismatch, so a bracket was included.
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 guess the point I was trying to make is that I would rather not have these parentheses because the expression is valid without them. I would have expected this output here:
if TrueElement or x == TrueElement:
...
The current logic may not be correct if it preserves unnecessary parentheses.
I tried a couple of approaches to get rid of the range
comparisons in an earlier review and couldn't come up with something better, though, so this doesn't necessarily need to block this PR. It's still an improvement over the old fix.
…sons.rs Co-authored-by: Brent Westbrook <[email protected]>
pycodestyle
] Auto-fix redundant boolean comparison (E712
)
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 again!
…tructor * origin/main: [red-knot] Trust module-level undeclared symbols in stubs (#17577) [`airflow`] Apply auto fixes to cases where the names have changed in Airflow 3 (`AIR301`) (#17355) [`pycodestyle`] Auto-fix redundant boolean comparison (`E712`) (#17090) [red-knot] Detect semantic syntax errors (#17463) Fix stale diagnostics in Ruff playground (#17583) [red-knot] Early return from `project.is_file_open` for vendored files (#17580)
…var-instance * dcreager/generic-constructor: Revert FunctionLiteral type [red-knot] Trust module-level undeclared symbols in stubs (#17577) [`airflow`] Apply auto fixes to cases where the names have changed in Airflow 3 (`AIR301`) (#17355) [`pycodestyle`] Auto-fix redundant boolean comparison (`E712`) (#17090) Clean this up a bit clippy [red-knot] Detect semantic syntax errors (#17463) Fix stale diagnostics in Ruff playground (#17583) [red-knot] Early return from `project.is_file_open` for vendored files (#17580)
This pull request fixes #17014
changes this
to this