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

Add comparison-of-constants checker #6413

Merged
merged 13 commits into from May 4, 2022

Conversation

omarandlorraine
Copy link
Contributor

Type of Changes

Type
βœ“ πŸ› Bug fix

Description

checker for catching pointless comparisons between two literals

Closes #6076

The Issue mentions tautological and logical errors. But this check is about comparisons between literals, which is a subset of all possible logical errors.

@omarandlorraine
Copy link
Contributor Author

omarandlorraine commented Apr 20, 2022

There are so many stupid commits saying "Merge branch 'PyCQA:main' into main". Does github automagically squash these away when/if the branch eventually gets merged?

@omarandlorraine
Copy link
Contributor Author

I notice that my check only fires once for lines like if "abc" == "abc" != None == True:. Is this a problem?

@coveralls
Copy link

coveralls commented Apr 20, 2022

Pull Request Test Coverage Report for Build 2272482650

  • 11 of 11 (100.0%) changed or added relevant lines in 1 file are covered.
  • 10 unchanged lines in 1 file lost coverage.
  • Overall coverage increased (+0.01%) to 95.188%

Files with Coverage Reduction New Missed Lines %
pylint/checkers/stdlib.py 10 94.4%
Totals Coverage Status
Change from base Build 2266524332: 0.01%
Covered Lines: 15866
Relevant Lines: 16668

πŸ’› - Coveralls

@Pierre-Sassoulas
Copy link
Member

There are so many stupid commits saying "Merge branch 'PyCQA:main' into main". Does github automagically squash these away when/if the branch eventually gets merged?

Well yes, we can squash all the commit when we merge. But you can also rebase on the latest origin/main to remove the merge commit and cleanup your commits (it's better to not do that once there was a review so reviewer can check the latest commit only). pylint is a very active code base so new commits land on the main branch very often.

@Pierre-Sassoulas Pierre-Sassoulas added this to the 2.15.0 milestone Apr 20, 2022
@omarandlorraine
Copy link
Contributor Author

The remaining failing tests appear to be because of this kind of thing:

 OutputLine(symbol='comparison-with-itself', lineno=6, column=0, end_lineno=None, end_column=None, object='foo', msg='Redundant comparison - arg == arg', confidence='UNDEFINED')

In particular, I'm concerned about end_line=None and end_column=None. Maybe I broke something, because this affects existing tests as well.

Copy link
Member

@Pierre-Sassoulas Pierre-Sassoulas left a comment

Choose a reason for hiding this comment

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

I think this need some functional tests, this could be by removing disable in existing "comparison" functional files, but creating a new file would be nice too.

@Pierre-Sassoulas
Copy link
Member

The test failing right now needs to add the expected result in functional py file with # [comparison-of-two-literals] on the line where the message should be raised.

@Pierre-Sassoulas Pierre-Sassoulas added Enhancement ✨ Improvement to a component Checkers Related to a checker labels Apr 20, 2022
@omarandlorraine
Copy link
Contributor Author

I think this need some functional tests, this could be by removing disable in existing "comparison" functional files, but creating a new file would be nice too.

My opinion is it's covered by a3f3e61, what do you think? Is there something missing?

@DanielNoord
Copy link
Collaborator

DanielNoord commented Apr 21, 2022

I think this need some functional tests, this could be by removing disable in existing "comparison" functional files, but creating a new file would be nice too.

My opinion is it's covered by a3f3e61, what do you think? Is there something missing?

It would be good to have a separate file as well. That makes it easy to see what a message is testing by quickly looking up the message name in the test directory.

We will also need some documentation as discussed in #5953.

Copy link
Member

@Pierre-Sassoulas Pierre-Sassoulas left a comment

Choose a reason for hiding this comment

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

Looks good already, it's a very nice first step toward catching all constant comparison !

pylint/checkers/base/comparison_checker.py Outdated Show resolved Hide resolved
pylint/checkers/base/comparison_checker.py Outdated Show resolved Hide resolved
pylint/checkers/base/comparison_checker.py Outdated Show resolved Hide resolved
pylint/checkers/base/comparison_checker.py Outdated Show resolved Hide resolved
pylint/checkers/base/comparison_checker.py Outdated Show resolved Hide resolved
tests/functional/a/access/access_to_protected_members.py Outdated Show resolved Hide resolved
tests/functional/c/comparison_of_two_literals.py Outdated Show resolved Hide resolved
@omarandlorraine
Copy link
Contributor Author

As for commit 864c1c1.

The tests are failing because the literal-comparison lint is no longer firing on certain lines in tests/functional/c/comparison_of_constants.py. So I have adjusted the [symbolic-name-for-lint] annotations for this test, but I don't understand why. Does anyone know why this is?

@omarandlorraine
Copy link
Contributor Author

Can anyone here see why "Checks / documentation" has failed?

@Pierre-Sassoulas
Copy link
Member

@omarandlorraine the bad.py file in the documentation does not raise comparison-of-two-literals

@jacobtylerwalls
Copy link
Member

Was there any thought given to reusing using-constant-test for this?

@omarandlorraine
Copy link
Contributor Author

Was there any thought given to reusing using-constant-test for this?

No, I wasn't even aware of it.

@omarandlorraine
Copy link
Contributor Author

Okay, the tests have passed, and I think it is ready to be merged in. Do you agree?

@Pierre-Sassoulas
Copy link
Member

I'm sorry @omarandlorraine I did not remember that we already had a very close warnings for that kind of problem. Thank you Jacob for noticing.

I think creating a new message makes sense because the old one is a constant as in if 1: and the new one is a little different with 1 == 2 with two constants. We could merge them or normalize the name, maybe using-constant-comparison-tests ? We have to keep in mind that the idea is to maybe extends the check later to infer that a check is between two constants so we need to anticipate that. What do you think @DanielNoord @jacobtylerwalls ?

@jacobtylerwalls
Copy link
Member

I was just wondering, but now after thinking a bit, I take it back. I see now that using-constant-test doesn't inspect a comparison, and if it did someday, it would need to do exactly what this check is doing, so maybe the current approach is fine.

Copy link
Member

@Pierre-Sassoulas Pierre-Sassoulas left a comment

Choose a reason for hiding this comment

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

Looking good. Sorry for the message change, it will require an upgrade of the result of the functional tests. Let's hear about what other think of it before changing it again. This is user facing, the clearer it is the better.

pylint/checkers/base/comparison_checker.py Outdated Show resolved Hide resolved
@Pierre-Sassoulas Pierre-Sassoulas modified the milestones: 2.15.0, 2.14.0 Apr 27, 2022
@jacobtylerwalls jacobtylerwalls changed the title Logic checks Add comparison-of-constants checker Apr 27, 2022
@Pierre-Sassoulas
Copy link
Member

what can I do to unconflict?

Generally in the changelog you should keep both entry (inserting your own changelog randomly help, as conflicts happens a lot more for the first entry and last entry). Regarding the other conflict it's easier to resolve with a global vision of the other MR so I did it :) The change came from #6492

@Pierre-Sassoulas
Copy link
Member

Pierre-Sassoulas commented May 3, 2022

Generally to avoid the problem you could use git fetch -a to have the latest main branch from upstream after your rebase, and do the rebase again (it's easier once you fixed the conflict for the first rebase).

Copy link
Member

@Pierre-Sassoulas Pierre-Sassoulas left a comment

Choose a reason for hiding this comment

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

The code itself looks good,great even, thank you for your contribution @omarandlorraine.

Regarding the message description and name and its consistency with other pylint message, I think there's two possible solutions:

  • We suppress comparison-of-constants if a less generic one would be raised πŸš€
  • We raise two messages, after all one fix will remove both... πŸ‘€

I think suppression message in some context would bring complexity. What happens if the less generic message is disabled for example, should we still suppress comparison-of-constants ?

Let me know what you think of the two options and if something else could be done, then I think we should vote on the best solution to merge this before yet another change in main create a conflict πŸ˜„

tests/functional/c/comparison_of_constants.py Outdated Show resolved Hide resolved
tests/functional/c/comparison_of_constants.py Outdated Show resolved Hide resolved
Copy link
Collaborator

@DanielNoord DanielNoord left a comment

Choose a reason for hiding this comment

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

Thanks for fixing the merge @omarandlorraine!

Sorry if my comments seem a bit much, that sort of thing happens naturally with larger PRs like these. (152 lines is quite a lot for our project).

To explain two seemingly nitpicky recurring comments:

  1. Adding disables at the top of the file is helpful for later contributors as they don't need to add in-line disables for the same problem. Besides, they tend to keep the testable code clean.
  2. Adding double spaces between comments and codes helps contributors who have set up their IDE to auto-format on save. My IDE auto-formats 95% of the code to this project's style. Normally such deviations would be caught by pre-commit, but some of the test files are excluded. However, when I (or somebody else) ever revisits these tests they won't be able to easily save without reformatting the complete file. Sticking to the project's style in tests even without pre-commit is therefore useful for many contributors/reviewers and increases their productivity. It's a bit nitpicky now, but would be much appreciated πŸ˜„

doc/whatsnew/2.14.rst Outdated Show resolved Hide resolved
ChangeLog Outdated Show resolved Hide resolved
@@ -58,6 +59,13 @@ class ComparisonChecker(_BasicChecker):
"comparison-with-itself",
"Used when something is compared against itself.",
),
"R0125": (
"Comparison between constants: '%s %s %s' is also a constant, remove the comparison and consider a refactor",
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
"Comparison between constants: '%s %s %s' is also a constant, remove the comparison and consider a refactor",
"Comparison between constants: '%s %s %s' has a constant value",

The R in R0125 implies that the code should be refactored so I think we can leave that out of the message here.

Comment on lines 65 to 67
"When two literals are compared with each other the result is a constant, "
"and using the constant directly is both easier to read and more performant."
" Initializing 'True' and 'False' this way is not required since python 2.3",
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
"When two literals are compared with each other the result is a constant, "
"and using the constant directly is both easier to read and more performant."
" Initializing 'True' and 'False' this way is not required since python 2.3",
"When two literals are compared with each other the result is a constant. "
"Using the constant directly is both easier to read and more performant. "
"Initializing 'True' and 'False' this way is not required since Python 2.3.",

pylint/checkers/base/comparison_checker.py Outdated Show resolved Hide resolved
@@ -11,21 +11,21 @@ def foo():
return True
elif arg <= arg: # [comparison-with-itself]
return True
elif None == None: # [comparison-with-itself]
elif None == None: # [comparison-of-constants, comparison-with-itself]
Copy link
Collaborator

Choose a reason for hiding this comment

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

As we're updating these comments as well, could you add the double spaces here?

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'll try using the autoformatter locally on my machine.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Did this work?

tests/functional/s/superfluous_parens.py Outdated Show resolved Hide resolved
tests/functional/s/superfluous_parens.py Outdated Show resolved Hide resolved
tests/functional/u/use/use_implicit_booleaness_not_len.py Outdated Show resolved Hide resolved
tests/functional/u/using_constant_test.py Outdated Show resolved Hide resolved
Co-authored-by: DaniΓ«l van Noord <13665637+DanielNoord@users.noreply.github.com>
@omarandlorraine
Copy link
Contributor Author

Thanks for fixing the merge @omarandlorraine!

No problem, thanks also to you @DanielNoord for the technical help with git. Thanks also to @Pierre-Sassoulas for cleaning up the fiddly bits

@omarandlorraine
Copy link
Contributor Author

omarandlorraine commented May 4, 2022

The code itself looks good,great even, thank you for your contribution @omarandlorraine.

thanks for your kind words.

* We raise two messages, after all one fix will remove both... πŸ‘€

Let me know what you think of the two options and if something else could be done, then I think we should vote on the best solution to merge this before yet another change in main create a conflict πŸ˜„

I voted eyeballs because I don't see a problem with raising two messages for one bad line of code. But I might change my vote if someone can tell my why it could be a problem (maybe it could be a UX concern, I don't know?)

omarandlorraine and others added 2 commits May 4, 2022 08:44
Co-authored-by: DaniΓ«l van Noord <13665637+DanielNoord@users.noreply.github.com>
Co-authored-by: DaniΓ«l van Noord <13665637+DanielNoord@users.noreply.github.com>
@omarandlorraine
Copy link
Contributor Author

Oh pants! You see we've got two commits each called "Whitespace" and "period at the end of changelog entry". I tried to squash those into 31327ef and
8a363d7.

But it seems to have gone wrong

Co-authored-by: DaniΓ«l van Noord <13665637+DanielNoord@users.noreply.github.com>
@DanielNoord
Copy link
Collaborator

Oh pants! You see we've got two commits each called "Whitespace" and "period at the end of changelog entry". I tried to squash those into 31327ef and 8a363d7.

But it seems to have gone wrong

What you might want to do is commit batch. If you go to the Files Changes tab you can add all comments with suggestions to one batch and then commit at once. Since the comments are made by reviewers I doesn't matter too much if the commit name is not descriptive. We'll likely squash merge anyway.
Another benefit is that this will only run the CI once instead of for each commit which will save us some valuable CI runner time πŸ˜„

omarandlorraine and others added 3 commits May 4, 2022 09:11
Co-authored-by: DaniΓ«l van Noord <13665637+DanielNoord@users.noreply.github.com>
Co-authored-by: DaniΓ«l van Noord <13665637+DanielNoord@users.noreply.github.com>
@omarandlorraine
Copy link
Contributor Author

I've stuffed up again 😱

Now some tests are failing in CI, even though (some of) these pass locally.

I don't think whatever code the CI task runs is always the same as what I've got.

@Pierre-Sassoulas
Copy link
Member

Pierre-Sassoulas commented May 4, 2022

Now some tests are failing in CI, even though (some of) these pass locally.

The information for the line/column in functional tests come from the AST module that we do not control. We're also testing for python 3.8 to 3.10 (The AST got more stable in 3.8). If there's a discrepancy between interpreter for the line/column information coming from the AST we need to examine closely what interpreter changed and take a decision to exclude some interpreter from the check in a generic file or to separate the check by interpreter.

@DanielNoord
Copy link
Collaborator

It seems the expected output for the comparison_of_constants test is incorrect. That should be easily fixable!

Copy link
Collaborator

@DanielNoord DanielNoord left a comment

Choose a reason for hiding this comment

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

Some last comments about spacing, most have now been fixed. Note that there are also some open comments in pylint/checkers/base/comparison_checker.py

doc/data/messages/c/comparison-of-constants/bad.py Outdated Show resolved Hide resolved
@@ -1,6 +1,6 @@
'''Assert check example'''

# pylint: disable=comparison-with-itself
# pylint: disable=comparison-with-itself comparison-of-constants
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
# pylint: disable=comparison-with-itself comparison-of-constants
# pylint: disable=comparison-with-itself, comparison-of-constants

# pylint: disable=missing-docstring, comparison-with-itself


if 2 is 2: # [literal-comparison, comparison-of-constants]
Copy link
Collaborator

Choose a reason for hiding this comment

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

Note that this file still needs to be updated for spaces.

@@ -11,21 +11,21 @@ def foo():
return True
elif arg <= arg: # [comparison-with-itself]
return True
elif None == None: # [comparison-with-itself]
elif None == None: # [comparison-of-constants, comparison-with-itself]
Copy link
Collaborator

Choose a reason for hiding this comment

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

Did this work?

@@ -62,9 +62,9 @@ def test_break_in_orelse_deep():
"""no false positive for break in else deeply nested
"""
for _ in range(10):
if 1 < 2:
if 1 < 2: # pylint: disable=comparison-of-constants
Copy link
Collaborator

Choose a reason for hiding this comment

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

This file is also missing double spacing.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

black doesn't seem to mind about this issue; even if I do pre-commit run --all-files, the double space doesn't get put in. Does this depend on some kind of configuration, or?

Copy link
Collaborator

Choose a reason for hiding this comment

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

No, pre-commit sadly won't fix this for us. We explicitly exclude these test files from pre-commit: for example, sometimes we want to test that even if there are mistakes our syntactic errors in code pylint exits gracefully instead of crashing. We don't want pre-commit to warn us about such "mistakes".

Thus, keeping style in these files is a little harder. Many of the contributors and reviewers have set up their IDE to also run some formatters whenever they save a file. For example, I run black on save together with an import sorter. This helps keep the style in our test files somewhat similar. However, when people with such "on-save-formatters" look at these test files and make a change their formatters will also change stuff unrelated to the issue they are working on, as the formatter might start sorting imports or formatting lines. So, keeping the general style in our test files not only is a stylistic choice but also helps keep some of our contributors more productive. It's a bit annoying that we need to do this by hand, especially for larger PRs such as this one, but in the long run it tends to make us all more productive πŸ˜„

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Oh I get it now. pre-commit won't do this, by deliberate design. But of course we I could invoke black on those files ourselves.

Anyway I've done it manually

Copy link
Member

Choose a reason for hiding this comment

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

Those file are sometime not black formatted by design, if we run black everywhere we won't have any tests for single quote strings for example. I think we should go easy on the normalization, we want to also catch bugs for users that do not use black or isort or do not respect pep8 to the letter, it's a good thing if the functional tests are somewhat messy.

Copy link
Member

@Pierre-Sassoulas Pierre-Sassoulas left a comment

Choose a reason for hiding this comment

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

LGTM, once the point raised by Daniel are resolved ! Thank you for taking a lof of remarks into account already @omarandlorraine.

@DanielNoord DanielNoord merged commit 6486514 into pylint-dev:main May 4, 2022
@DanielNoord
Copy link
Collaborator

I added the last changes myself. Thanks for your work here @omarandlorraine and congratulations on writing your first new checker! πŸ˜„

@jacobtylerwalls
Copy link
Member

Yes thanks very much @omarandlorraine !

@omarandlorraine
Copy link
Contributor Author

Oh cool!

Thanks also to everyone for making this a good experience for me.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Checkers Related to a checker Enhancement ✨ Improvement to a component
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Report logical contradictions
5 participants