-
-
Notifications
You must be signed in to change notification settings - Fork 2.9k
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
Bug: false positive with --warn-unreachable #7214
Comments
This seems like a bug in how mypy calculates reachability of with statements. cc @Michael0x2a |
@JukkaL -- I'm not sure if there's a simple solution for this one either. Basically, we could try adjusting mypy so it (correctly) treats all with statements as if they were the body of a try-finally block, which would mean statements after the with would start to be considered reachable. But that ends up causing "Missing return statement" errors for code like the following: def foo() -> int:
with open("foobar.txt", "r") as f:
return 3 More generally, there doesn't seem to be a clean way of distinguishing between context managers that suppress exceptions vs ones that don't. It'd be nice to find some way of encoding this distinction at the type-system level (for example, by taking advantage of the proposed |
You could in theory get this out of the return type of the context manager's One possible pragmatic solution is to assume that contextmanagers don't swallow exceptions, unless they are annotated as returning |
@JelleZijlstra I like your idea! The implementation sounds pretty simple and it seems pretty logical (though it might be a little surprising). We'd probably need to document this in typeshed at least, and perhaps also in mypy docs. @Michael0x2a What do you think about Jelle's proposal? |
This pull request is a follow-up to python/mypy#7214. In short, within that mypy issue, we found it would be helpful to determine between contextmanagers that can "swallow" exceptions vs ones that can't. This helps prevent some false positive when using flags that analyze control flow such as `--warn-unreachable`. To do this, Jelle proposed assuming that only contextmanagers where the `__exit__` returns `bool` are assumed to swallow exceptions. This unfortunately required the following typeshed changes: 1. The typing.IO, threading.Lock, and concurrent.futures.Executor were all modified so `__exit__` returns `Optional[None]` instead of None -- along with all of their subclasses. I believe these three types are meant to be subclassed, so I felt picking the more general type was correct. 2. There were also a few concrete types (e.g. see socketserver, subprocess, ftplib...) that I modified to return `None` -- I checked the source code, and these all seem to return None (and don't appear to be meant to be subclassable). 3. contextlib.suppress was changed to return bool. I also double-checked the unittest modules and modified a subset of those contextmanagers, leaving ones like `_AssertRaisesContext` alone.
This pull request fixes python#7214: it makes mypy treat any contextmanagers where the `__exit__` returns `bool` or `Literal[True]` as ones that can potentially swallow exceptions. Contextmanagers that return `Optional[bool]`, None, or `Literal[False]` continue to be treated as non-exception-swallowing ones. This distinction helps the `--warn-unreachable` flag do the right thing in this example program: ```python from contextlib import suppress def should_warn() -> str: with contextlib.suppress(IndexError): return ["a", "b", "c"][0] def should_not_warn() -> str: with open("foo.txt") as f: return "blah" ``` This pull request needs the typeshed changes I made in python/typeshed#3179. Once that one gets merged, I'll update typeshed and rebase this pull request.
This pull request is a follow-up to python/mypy#7214. In short, within that mypy issue, we found it would be helpful to determine between contextmanagers that can "swallow" exceptions vs ones that can't. This helps prevent some false positive when using flags that analyze control flow such as `--warn-unreachable`. To do this, Jelle proposed assuming that only contextmanagers where the `__exit__` returns `bool` are assumed to swallow exceptions. This unfortunately required the following typeshed changes: 1. The typing.IO, threading.Lock, and concurrent.futures.Executor were all modified so `__exit__` returns `Optional[None]` instead of None -- along with all of their subclasses. I believe these three types are meant to be subclassed, so I felt picking the more general type was correct. 2. There were also a few concrete types (e.g. see socketserver, subprocess, ftplib...) that I modified to return `None` -- I checked the source code, and these all seem to return None (and don't appear to be meant to be subclassable). 3. contextlib.suppress was changed to return bool. I also double-checked the unittest modules and modified a subset of those contextmanagers, leaving ones like `_AssertRaisesContext` alone.
…3179) This pull request is a follow-up to python/mypy#7214. In short, within that mypy issue, we found it would be helpful to determine between contextmanagers that can "swallow" exceptions vs ones that can't. This helps prevent some false positive when using flags that analyze control flow such as `--warn-unreachable`. To do this, Jelle proposed assuming that only contextmanagers where the `__exit__` returns `bool` are assumed to swallow exceptions. This unfortunately required the following typeshed changes: 1. The typing.IO, threading.Lock, and concurrent.futures.Executor were all modified so `__exit__` returns `Optional[None]` instead of None -- along with all of their subclasses. I believe these three types are meant to be subclassed, so I felt picking the more general type was correct. 2. There were also a few concrete types (e.g. see socketserver, subprocess, ftplib...) that I modified to return `None` -- I checked the source code, and these all seem to return None (and don't appear to be meant to be subclassable). 3. contextlib.suppress was changed to return bool. I also double-checked the unittest modules and modified a subset of those contextmanagers, leaving ones like `_AssertRaisesContext` alone.
…#7317) This pull request fixes #7214: it makes mypy treat any context managers where the `__exit__` returns `bool` or `Literal[True]` as ones that can potentially swallow exceptions. Context managers that return `Optional[bool]`, None, or `Literal[False]` continue to be treated as non-exception-swallowing ones. This distinction helps the `--warn-unreachable` flag do the right thing in this example program: ```python from contextlib import suppress def should_warn() -> str: with contextlib.suppress(IndexError): return ["a", "b", "c"][0] def should_not_warn() -> str: with open("foo.txt") as f: return "blah" ``` This behavior is partially disabled when strict-optional is disabled: we can't necessarily distinguish between `Optional[bool]` vs `bool` in that mode, so we conservatively treat the latter in the same way we treat the former.
According to the mypy#7214, mypy-0.740 prefers a return value of __exit__() method which does not swallow exceptions is None instead of bool. mypy#7214: python/mypy#7214
According to the python/mypy#7214, mypy-0.740 prefers a return value of __exit__() method which does not swallow exceptions is None instead of bool. mypy#7214: python/mypy#7214
I hope you don't mind me digging up an old issue, but it seems like this fits in nicely here - unless I'm missing something, there's no way to annotate this if using As an example: import contextlib
from typing import Iterator
@contextlib.contextmanager
def swallow_exceptions() -> Iterator[None]:
try:
yield
except Exception:
# ...
pass
def test() -> int:
with swallow_exceptions():
return 1
return 2 results in:
|
As for the mypy unreachable warning, see: See python/mypy#7214 and python/mypy#8766 Includes cherry-pick of 27ad478
A workaround that you could consider: import contextlib
from typing import TYPE_CHECKING, Iterator
if TYPE_CHECKING:
class swallow_exceptions:
def __init__(self) -> None: ...
def __enter__(self) -> None: ...
def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> bool: ...
else:
@contextlib.contextmanager
def swallow_exceptions() -> Iterator[None]:
try:
yield
except Exception:
# ...
pass |
Please provide more information to help us understand the issue:
Are you reporting a bug, or opening a feature request?
Bug
Please insert below the code you are checking with mypy,
or a mock-up repro if the source is private. We would appreciate
if you try to simplify your case to a minimal repro.
What is the behavior/output you expect?
No error, since line 8 is reachable.
What are the versions of mypy and Python you are using?
--warn-unreachable
Maybe related to #7204 and #7207
The text was updated successfully, but these errors were encountered: