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

False Positive on contextmanager-generator-missing-cleanup / W0135 When Using With Expression in the ContextManager #9625

Closed
ancantus opened this issue May 15, 2024 · 8 comments · Fixed by #9654
Labels
False Positive 🦟 A message is emitted but nothing is wrong with the code Needs PR This issue is accepted, sufficiently specified and now needs an implementation

Comments

@ancantus
Copy link

ancantus commented May 15, 2024

Bug description

The contextmanager-generator-missing-cleanup warning triggers a false positive (by my understanding of the warning) when a yield expression is fully cleaned up by a surrounding with statement (or multiple with statements).

Below is a simplified example of the issue:

import contextlib


@contextlib.contextmanager
def cm():
    with open("/tmp/test", "wb+") as contextvar:
        yield contextvar.fileno()


def genfunc_with_cm():  # [contextmanager-generator-missing-cleanup]
    with cm() as context:
        yield context * 2

The warning can be silenced with the guidance from https://pylint.readthedocs.io/en/latest/user_guide/messages/warning/contextmanager-generator-missing-cleanup.html . But by my understanding this is an unnecessary warning in this case. The with statement in cm() does the 'right thing' when yield throws GeneratorExit and closes the file.

Configuration

No response

Command used

pylint ./test.py

Pylint output

************* Module test
W  test.py:10: The context used in function 'genfunc_with_cm' will not be exited. (contextmanager-generator-missing-cleanup | W0135)

Expected behavior

I would expect the contextmanager-generator-missing-cleanup to only warn when there is extra cleanup required in a contextmanager outside of with statements.

I think you see the intention of these cases being supported in the good_cm_yield_none() example in https://pylint.readthedocs.io/en/latest/user_guide/messages/warning/contextmanager-generator-missing-cleanup.html

If there's nothing to clean up: the warning should not be generated.

Pylint version

pylint 3.2.0
astroid 3.2.0
Python 3.10.12 (main, Nov 20 2023, 15:14:05) [GCC 11.4.0]

OS / Environment

Ubuntu 22.04 LTS

Additional dependencies

No response

@ancantus ancantus added the Needs triage 📥 Just created, needs acknowledgment, triage, and proper labelling label May 15, 2024
@DanielNoord
Copy link
Collaborator

Yes I agree, this seems like a false positive. Opinions @Pierre-Sassoulas @rhyn0?

@rhyn0
Copy link
Contributor

rhyn0 commented May 15, 2024

Yah that seems correct.

Wondering if the way to check for this is by recursively tracing with calls and then we just have a white list of safe context managers such as open. Have to imagine we could have a context manager function using another and so on so forth

Worried about how a poorly written opener could break a whitelist containing open but that is probably a rare edge case compared to the example above.

@jacobtylerwalls jacobtylerwalls added False Positive 🦟 A message is emitted but nothing is wrong with the code Needs PR This issue is accepted, sufficiently specified and now needs an implementation and removed Needs triage 📥 Just created, needs acknowledgment, triage, and proper labelling labels May 15, 2024
@Pierre-Sassoulas
Copy link
Member

Worried about how a poorly written opener could break a whitelist containing open but that is probably a rare edge case compared to the example above.

We have astroid inference to check that it's an actual open and not something else. We generally don't because shadowing builtin is really bad practice in general and the perf impact to everyone to prevent an edge case that should never happen is not negligible.

@cdce8p
Copy link
Member

cdce8p commented May 16, 2024

My current understanding is that as long as there is no additional code after the yield, it's fine. Should be as simple as checking that the yield statement is the last on in the current block and recursively upwards until the current scope is reached.

Am I missing something?

@ancantus
Copy link
Author

ancantus commented May 17, 2024

that as long as there is no additional code after the yield, it's fine

This is my mental model of the situation as well. What orginally triggered this in my code was not the open() call: but a custom written context manager. The example with open() is just the smallest reproducable example I could come up with.

@eli-schwartz
Copy link

eli-schwartz commented May 19, 2024

Similar case: If I have code that is decorated with @contextmanager, creates with TemporaryDirectory() and does some prep work, then exits the function by yielding a file in the temporary directory -- there is no expected code to run after yield but I do want the parent tempdir cleanup.

Explicitly wrapping it in try/finally with cleanup code consisting of "pass", feels wrong because that's just a pinky promise that cleanup is done.

mesonbuild/compilers/compilers.py:858:4: W0135: The context used in function 'cached_compile' will not be exited. (contextmanager-generator-missing-cleanup)

https://github.com/mesonbuild/meson/blob/128f0e828e425793203f3112c23cb31f959b4b3e/mesonbuild/compilers/compilers.py#L862-L874

eli-schwartz added a commit to eli-schwartz/meson that referenced this issue May 19, 2024
It does no control flow analysis, and upgrading to pylint 3.2.0 produced
many incorrect warnings.

Also ignore contextmanager-generator-missing-cleanup for now. It has FP
when there is no code after a yield (and thus no cleanup needs to be
handled), which is what we do. Currently under discussion upstream:
pylint-dev/pylint#9625
@cdce8p
Copy link
Member

cdce8p commented May 19, 2024

Opened #9654 which should at least address the most common false-positive case with a singular yield.

@eli-schwartz I looked at your example. The fix won't work there unfortunately. Checking all if branches would add additional complexity. It's probably simpler to add a pylint: disable in that case.

@eli-schwartz
Copy link

@eli-schwartz I looked at your example. The fix won't work there unfortunately. Checking all if branches would add additional complexity. It's probably simpler to add a pylint: disable in that case.

Thanks for looking. I ended up adding it to pylintrc instead.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
False Positive 🦟 A message is emitted but nothing is wrong with the code Needs PR This issue is accepted, sufficiently specified and now needs an implementation
Projects
None yet
Development

Successfully merging a pull request may close this issue.

7 participants