-
-
Notifications
You must be signed in to change notification settings - Fork 1.9k
Allow to extend return types of functions decorated by ContextDecorator
with None
#14439
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
base: main
Are you sure you want to change the base?
Conversation
7acbe00
to
fff6ca5
Compare
I think we can rename the new typevar to |
This PR so far has a significant drawback, as it assumes all Therefore for from collections.abc import Generator
@contextmanager
def not_suppressing() -> Generator[None]:
yield
@not_suppressing()
def foo(x: int, y: int) -> int:
return x // y it causes Ideally, whether suppression happens or not could be inferred by the type checker. |
This comment has been minimized.
This comment has been minimized.
jax errorIt's because I believe that in this sense prefect error
openlibrary errorSimilarly for openlibrary, |
This PR is blocked until I figure out a way to make this edge case an opt-in. That might either be via type checker special casing: def example() -> int:
with suppress():
return 1 for both mypy and pyright, type checking this code errors out (although type checkers could be smarter in this exact case :-)): from contextlib import suppress
def example() -> int:
# [pyright] error: Function with declared return type "int" must return value on all code paths
# [mypy] error: Missing return statement [return]
with suppress():
return 1 and this doesn't: from contextlib import nullcontext
def example() -> int:
with nullcontext():
return 1 So it definitely is possible to infer what @contextmanager
def example() -> Generator[None]:
# type checker should flag this as suppressing context manager
# therefore `example` becomes a `Callable[_P, _GeneratorContextManager[..., None]]`
with suppress():
yield @contextmanager
def example() -> Generator[None]:
# normal path, this becomes a `Callable[_P, _GeneratorContextManager[..., Never]]`
with nullcontext():
yield |
Oh, although that doesn't solve the problem with jax. That shouldn't really be a huge issue if the annotation can be corrected downstream, question is how. |
I exposed |
Diff from mypy_primer, showing the effect of this PR on open source code: cki-lib (https://gitlab.com/cki-project/cki-lib)
- tests/test_metrics.py:75: error: Call to untyped function "dummy_function" in typed context [no-untyped-call]
jax (https://github.com/google/jax)
+ jax/_src/api.py:2882: error: Incompatible return value type (got "Callable[[VarArg(Any), KwArg(Any)], Any]", expected "F") [return-value]
|
OK, this looks good. |
None
to [Async]ContextDecorator.__call__
return type when context managers suppress exceptionsContextDecorator
with None
ContextDecorator
with None
ContextDecorator
with None
This would not solve a related problem though: from contextlib import contextmanager, suppress
from collections.abc import Generator
@contextmanager
def ignore() -> Generator[None]:
with suppress(ZeroDivisionError):
yield
def div(a: int, b: int) -> float:
with ignore():
return a / b That should cause in the same error as in from contextlib import suppress
def div(a: int, b: int) -> float:
with suppress(ZeroDivisionError):
return a / b Which signalizes that |
This led me to seeing a more general problem consisting of at least these 3 issues with how type checkers (don't) handle generator-based context managers:
In my understanding, there are only two correct bindings of
Their individual upper bounds would remain the same, but there should be some way to enforce that logic. This sheds new light on this issue as a much more general problem that needs discussion and new sections in the typing specification. |
Closes #13512