fix: Preserve the types of annotated async functions wrapped with on_exception or on_predicate - #202
Conversation
edgarrmondragon
left a comment
There was a problem hiding this comment.
Thanks @MeisQuietude, just a couple of comments
There was a problem hiding this comment.
Does this actually exercise something? i.e. does this fail somehow without the decorator typing changes? Otherwise, it's not clear to me what this tests.
There was a problem hiding this comment.
This covers the sync side of the decorator typing change. Pyrefly infers str for these unannotated sync functions, and the decorated calls must preserve that inferred type instead of becoming coroutines. The strict mypy test cannot cover this case because it requires explicit return annotations.
it guards against an alternative overload-based implementation of this fix.
For this sync function:
@backoff.on_exception(backoff.expo, ValueError)
def load_value(value: str):
return value
load_value("hello").upper()The results are:
Callable[[_CallableT], _CallableT]: Pyrefly preserves the inferred sync return type, so this passes.- Async-first + generic-sync overloads: Pyrefly resolves the call as a coroutine and reports
Coroutine has no attribute upper.
This test prevents fixing the async case by accidentally breaking unannotated sync functions.
--
But ofc if you're willing to remove it, just say and I do it. I appeciate your work :)
There was a problem hiding this comment.
Ah, I see. I did consider exploring the @overload option, so it's good to have in case I forget that it won't work 😅.
Thanks!
on_exception or on_predicate
Model async retry implementations as coroutine transformations while keeping the public decorators as identity transformations. This avoids mypy's disallow_any_decorated regression without introducing ambiguous overloads for other type checkers.
9aaa1ea to
0ef2235
Compare
What changed
on_exceptionandon_predicatewhile giving the internal async retry wrappers accurate coroutine contracts.disallow_any_decoratedand unannotated sync functions.Why
Version 2.4.0 reconstructs decorated async callables in a way that exposes
Coroutine[Any, Any, T]to mypy and failsdisallow_any_decorated. An identity transformation preserves the public callable contract without an overlapping async overload that can make pyrefly misclassify unannotated sync functions.Fixes #200