-
Notifications
You must be signed in to change notification settings - Fork 47
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
Use ParamSpec in .asyncio() #143
Conversation
self, | ||
*args: OriginalFunctionParams.args, | ||
**kwargs: OriginalFunctionParams.kwargs, | ||
) -> _Coroutine: ... |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This loses the return type, use Coroutine[Any, Any, _T]
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
type checkers think _T is Generator because there is yield
ret1: int = await f.asyncio(1, "2")
asynq/test.py:18:17 - error: Expression of type "Generator[AsyncTask[int], Unknown, Unknown]" is incompatible with declared type "int"
"Generator[AsyncTask[int], Unknown, Unknown]" is incompatible with "int" (reportAssignmentType)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see. This is perhaps fixable with overloads: if the decorator is passed something returning a Generator[Any, Any, T]
, then this returns T
, else it returns the return type directly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems important to fix because otherwise we lose type checking for the object returned from the asynq function.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I need to think more about edge cases. Can we merge this PR and make the return type a separate discussion?
@asynq()
def f1() -> Generator[Any, Any, int]:
yield op1
yield op2
return 100
@asynq()
def f2() -> Generator[Any, Any, int]:
return normal_generator_that_returns_int
(await f1.asyncio()) -> int
(await f2.asyncio()) -> Generator[Any, Any, int]
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- Case: the function body has yield (inspect.isgeneratorfunction = True)
fn: Callable[..., Generator[?, ?, T]] -> return: T
- Case: the function body no yield (inspect.isgeneratorfunction = False)
fn: Callable[..., T] -> return: T
inspect.isgeneratorfunction
cannot be expressed in type annotations.
Not sure if this kind of dynamic type checking would work:
return_type = TypeVar('T')
if TYPE_CHECKING:
if inspect.isgeneratorfunction(fn):
orig_fn: Callable[..., Generator[Any, Any, T]] = fn
new_fn: Callable[... T] =...
return new_fn
else:
orig_fn: Callable[..., T] = fn
new_fn: Callable[... T] =...
return new_fn
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, we can leave this for another PR. What you're suggesting won't work in a stub; I think we need to somehow overload __init__
.
pyright test.py
reports