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

Fix used-before-assignment false positive for TYPE_CHECKING elif branch imports #8441

Merged
merged 38 commits into from
Mar 30, 2023
Merged

Fix used-before-assignment false positive for TYPE_CHECKING elif branch imports #8441

merged 38 commits into from
Mar 30, 2023

Conversation

zenlyj
Copy link
Contributor

@zenlyj zenlyj commented Mar 12, 2023

Type of Changes

Type
βœ“ πŸ› Bug fix
✨ New feature
βœ“ πŸ”¨ Refactoring
πŸ“œ Docs

Description

This PR resolves false positives when imports are made in the TYPE_CHECKING else if branch.

Update:
As suggested by @jacobtylerwalls, some refactoring is done to change how TYPE_CHECKING imports are checked for used-before-assignment. Instead of relying on is_variable_violation(), it now integrates with existing logic in uncertain_nodes_in_false_tests involving the filtering of define nodes that are guarded under always false conditions.

The issue lies in is_variable_violation(). The logic is supposed to rely on the usage of the TYPE_CHECKING if statement node, but it is relying on the elif statement node for such cases.

This solution searches the import node ancestors for the TYPE_CHECKING base branch.

Additionally, a new static function _is_variable_annotation_in_function() is introduced to refactor the related logic and reduce nesting levels.

Closes #8437

@codecov
Copy link

codecov bot commented Mar 12, 2023

Codecov Report

Merging #8441 (36da654) into main (07127ee) will decrease coverage by 0.01%.
The diff coverage is 100.00%.

Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main    #8441      +/-   ##
==========================================
- Coverage   95.81%   95.80%   -0.01%     
==========================================
  Files         174      174              
  Lines       18334    18320      -14     
==========================================
- Hits        17567    17552      -15     
- Misses        767      768       +1     
Impacted Files Coverage Ξ”
pylint/checkers/utils.py 95.98% <ΓΈ> (-0.06%) ⬇️
pylint/checkers/variables.py 97.28% <100.00%> (-0.08%) ⬇️

@Pierre-Sassoulas Pierre-Sassoulas added False Positive 🦟 A message is emitted but nothing is wrong with the code backport maintenance/3.3.x labels Mar 12, 2023
@Pierre-Sassoulas Pierre-Sassoulas added this to the 2.17.1 milestone Mar 12, 2023
zenlyj and others added 2 commits March 12, 2023 22:26
Co-authored-by: Pierre Sassoulas <pierre.sassoulas@gmail.com>
@github-actions

This comment has been minimized.

Copy link
Member

@jacobtylerwalls jacobtylerwalls left a comment

Choose a reason for hiding this comment

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

Hey, thanks for this. I'm starting to worry we're putting more effort into a flawed implementation. For instance, with these changes, we now have a false negative for this case:

from typing import TYPE_CHECKING

if TYPE_CHECKING:
    pass
elif TYPE_CHECKING:
    import dbm

print(dbm)

No pressure, but I'm wondering if you explored whether #8431 (comment) was helpful in any way?

@zenlyj
Copy link
Contributor Author

zenlyj commented Mar 12, 2023

@jacobtylerwalls thanks, just saw your comment #8431 (comment). That does seem like a more fitting way for these checks, by filtering out the always false nodes. Let me look into that and in the meantime, I will mark this as draft.

@zenlyj zenlyj marked this pull request as draft March 12, 2023 17:21
@zenlyj zenlyj changed the title Fix used-before-assignment false positive for TYPE_CHECKING elif branch imports WIP: Fix used-before-assignment false positive for TYPE_CHECKING elif branch imports Mar 12, 2023
@ollie-iterators
Copy link
Contributor

ollie-iterators commented Mar 12, 2023

Another similar false positive to fix could be multiple-imports. Because an example of that can be seen in the file: "tests/functional/u/used/used_before_assignment_typing.py" on line 29

@zenlyj
Copy link
Contributor Author

zenlyj commented Mar 12, 2023

@ollie-iterators line 29 is supposed to raise multiple-imports, since it has multiple imports on a single line.

@ollie-iterators
Copy link
Contributor

Oops, I thought multiple imports was because calendar was imported in the if statement and the elif statement.

@github-actions

This comment has been minimized.

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 false positives being resolved (useless-suppression now emitted in pandas !) are very encouraging, I think it show the new approach is the right approach. It also read like a cleanup, which is nice ! I'll let Jacob merge :)

@codecov-commenter
Copy link

codecov-commenter commented Mar 29, 2023

Codecov Report

Merging #8441 (05c7f78) into main (9f2de91) will decrease coverage by 0.01%.
The diff coverage is 100.00%.

Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main    #8441      +/-   ##
==========================================
- Coverage   95.91%   95.90%   -0.01%     
==========================================
  Files         174      174              
  Lines       18362    18357       -5     
==========================================
- Hits        17611    17605       -6     
- Misses        751      752       +1     
Impacted Files Coverage Ξ”
pylint/checkers/utils.py 95.98% <ΓΈ> (-0.06%) ⬇️
pylint/checkers/variables.py 97.15% <100.00%> (-0.06%) ⬇️

@github-actions

This comment has been minimized.

@Pierre-Sassoulas
Copy link
Member

Pierre-Sassoulas commented Mar 29, 2023

Seeing music 21 the following test case should be added:

import typing as t

if t.TYPE_CHECKING:
    from music 21 import Stream

class StreamExample:
    def my_func(self):
        if t.TYPE_CHECKING:
           assert isinstance(self, Stream)

The panda one also seem genuine as we return in the try then we raise in the final except, so fixturedef is always going to be defined:

    def _get_active_fixturedef(
        self, argname: str
    ) -> Union["FixtureDef[object]", PseudoFixtureDef[object]]:
        try:
            return self._fixture_defs[argname]
        except KeyError:
            try:
                fixturedef = self._getnextfixturedef(argname)
            except FixtureLookupError:
                if argname == "request":
                    cached_result = (self, [0], None)
                    return PseudoFixtureDef(cached_result, Scope.Function)
                raise
        # Remove indent to prevent the python3 exception
        # from leaking into the call.
        self._compute_fixture_value(fixturedef)

@zenlyj
Copy link
Contributor Author

zenlyj commented Mar 29, 2023

@Pierre-Sassoulas The music21 case is interesting, because Stream is used under a TYPE_CHECKING branch. Since TYPE_CHECKING is False at runtime, code under it should not execute at all. So, I am not sure if we should raise an error for it. What do you think?

@jacobtylerwalls
Copy link
Member

jacobtylerwalls commented Mar 29, 2023

The music21 assert is just there to give a hint to mypy. (It would be good to not emit a message.)

@Pierre-Sassoulas
Copy link
Member

Pierre-Sassoulas commented Mar 29, 2023

I agree we should not raise if both are guarded in TYPE_CHECKING, but what about the following:

import typing as t

if t.TYPE_CHECKING:
    from music21 import Stream


class StreamExample(MusicStream): # Definitely used-before-assignment
    def my_func(self):
        if t.TYPE_CHECKING:
           assert isinstance(self, Stream) # No used-before-assignment
           assert isinstance(self, MusicStream) # used-before-assignment imo

@jacobtylerwalls
Copy link
Member

The mypy-hint false positive we're discussing is essentially the same as #8167, where I mentioned we'll eventually want to be smart enough to know it was the very same test that was guarding both the declaration and the use of the variable.

If this PR is only driving a small amount of traffic into that existing false positive, we may not need to solve it right now on this PR.

@jacobtylerwalls
Copy link
Member

The panda one also seem genuine as we return in the try then we raise in the final except, so fixturedef is always going to be defined:

Actually, I think the pandas example should (probably) warn. If self._getnextfixturedef(argname) divides by zero, none of the excepts will catch it, and fixturedef will blow up. It's reasonable for pandas not to code around it if unlikely, but pylint should be warning.

That said, we did choose in #5402 to assume that try blocks succeed, and only warn when definitions are made in an except. Someone could make the argument that's that's what's happening here, just with a nested try. Not that I would be all that convinced :-)

Copy link
Member

@jacobtylerwalls jacobtylerwalls 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 it would be good to add a test (in a separate file) for the failed case from the last review, but pending that, adding a green check!

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, let's just add the test case the new code is handling, it's easy to forget about it otherwise.

@github-actions
Copy link
Contributor

πŸ€– Effect of this PR on checked open source code: πŸ€–

Effect on music21:
The following messages are now emitted:

  1. used-before-assignment:
    Using variable 'music21' before assignment
    https://github.com/cuthbertLab/music21/blob/75420e1271478ed5bdef0cf0583006bb4339677c/music21/humdrum/questions.py#L225
  2. used-before-assignment:
    Using variable 'Stream' before assignment
    https://github.com/cuthbertLab/music21/blob/75420e1271478ed5bdef0cf0583006bb4339677c/music21/stream/core.py#L552

Effect on pandas:
The following messages are now emitted:

  1. useless-suppression:
    Useless suppression of 'used-before-assignment'
    https://github.com/pandas-dev/pandas/blob/d95fc0b17d32b490000277e0f9a10e2de2c63a8d/pandas/core/tools/datetimes.py#L1036
  2. suppressed-message:
    Suppressed 'used-before-assignment' (from line 117)
    https://github.com/pandas-dev/pandas/blob/d95fc0b17d32b490000277e0f9a10e2de2c63a8d/pandas/core/groupby/indexing.py#L117

The following messages are no longer emitted:

  1. suppressed-message:
    Suppressed 'used-before-assignment' (from line 1036)
    https://github.com/pandas-dev/pandas/blob/d95fc0b17d32b490000277e0f9a10e2de2c63a8d/pandas/core/tools/datetimes.py#L1036
  2. redefined-variable-type:
    Redefinition of indexer type from .ndarray to slice
    https://github.com/pandas-dev/pandas/blob/d95fc0b17d32b490000277e0f9a10e2de2c63a8d/pandas/core/indexes/multi.py#L3077
  3. useless-suppression:
    Useless suppression of 'used-before-assignment'
    https://github.com/pandas-dev/pandas/blob/d95fc0b17d32b490000277e0f9a10e2de2c63a8d/pandas/core/groupby/indexing.py#L117

Effect on pytest:
The following messages are no longer emitted:

  1. used-before-assignment:
    Using variable 'fixturedef' before assignment
    https://github.com/pytest-dev/pytest/blob/a3b39069bc44774206c7bd682e0ca253d5f74efc/src/_pytest/fixtures.py#L592

This comment was generated for commit 05c7f78

@Pierre-Sassoulas Pierre-Sassoulas added False Negative πŸ¦‹ No message is emitted but something is wrong with the code and removed backport maintenance/3.3.x labels Mar 30, 2023
@Pierre-Sassoulas Pierre-Sassoulas modified the milestones: 2.17.2, 3.0.0, 3.0.0a6 Mar 30, 2023
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.

New messages are emitted so I removed the backport.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
False Negative πŸ¦‹ No message is emitted but something is wrong with the code False Positive 🦟 A message is emitted but nothing is wrong with the code
Projects
None yet
Development

Successfully merging this pull request may close these issues.

used-before-assignment false positive for import nodes under elif after TYPE_CHECKING
5 participants