disnake icon indicating copy to clipboard operation
disnake copied to clipboard

feat: auto-generate overloads for `Client.wait_for`

Open shiftinv opened this issue 2 years ago • 8 comments

Summary

Part 1/? of event QoL work, adds just a few overloads (106, to be exact) to Client.wait_for, one for each event. This allows for correct typing of the check parameter and the return type, particularly when used with the Event enum (see below). These are being auto-generated through yet another custom libcst module. While certainly not ideal, there isn't a better way to do this with Python's current typing system due to several reasons.

We may want to consider merging overloads with the same return types, I'm not sure if that affects pyright performance in any significant way. That one big lookup table is a separate module as part of the library, since it'll be reused and expanded in future PRs with runtime usage.

Some other things:

Fallback for str

One of the overloads takes an unspecific str event. This is meant to be the fallback overload for anyone using the existing event infrastructure in the lib for custom events, since doing that would otherwise run into typing issues: https://github.com/DisnakeDev/disnake/blob/a52790d008139633723d128c6de9a5d71c367ba4/disnake/client.py#L2742-L2751

This, however, means that incorrectly typing a library event handler also falls back to this, since it's the most generic one - i.e. passing a wrong check type for .wait_for("message") will not show any errors. This is not the case with Event.message, which will correctly show a typing error if necessary.

But what about the other event methods?

No :) Well, maybe. I partially implemented this, but decided to remove it again; it may or may not affect the performance of pyright(/mypy), and the library typing is already complex enough. Reason for choosing wait_for in particular is that it's usually used together with check=lambda x, y, z: ..., and you can't add type annotations to inline lambdas.

Somewhat depends on #1016 due to tests.

Checklist

  • [x] If code changes were made, then they have been tested
    • [ ] I have updated the documentation to reflect the changes
    • [x] I have formatted the code properly by running pdm lint
    • [x] I have type-checked the code by running pdm pyright
  • [ ] This PR fixes an issue
  • [x] This PR adds something new (e.g. new method or parameters)
  • [ ] This PR is a breaking change (e.g. methods or parameters removed/renamed)
  • [ ] This PR is not a code change (e.g. documentation, README, ...)

shiftinv avatar Apr 28 '23 14:04 shiftinv

@shiftinv would you please solve conflicts?

onerandomusername avatar Jun 12 '23 19:06 onerandomusername

@shiftinv would you please solve conflicts?

done, rebased away the commits from the base PR and updated to master :+1:

shiftinv avatar Jun 13 '23 13:06 shiftinv

I hate the concept of putting a ton of overloads in the main client.py file. It makes it way more bloated than it needs to be. Can't wait_for and its overloads be put in a mixin in a separate file?

EmmmaTech avatar Sep 19 '23 00:09 EmmmaTech

I hate the concept of putting a ton of overloads in the main client.py file. It makes it way more bloated than it needs to be. Can't wait_for and its overloads be put in a mixin in a separate file?

It's not ideal either way, yeah. This still seems to be the ~~least awful~~ best solution, given we unfortunately can't just have a wait_for<T extends keyof typeof Event>(event: T, check: (...args: EventData[T]) => boolean) or something else that would avoid all these overloads in the first place :/ I'd like to avoid mixins as much as possible due to them being mostly incompatible with type checking, moving wait_for into a separate mixin/file would require additional # type: ignores.

shiftinv avatar Sep 20 '23 21:09 shiftinv

I'd like to avoid mixins as much as possible due to them being mostly incompatible with type checking, moving wait_for into a separate mixin/file would require additional # type: ignores.

How come?

EmmmaTech avatar Sep 21 '23 01:09 EmmmaTech

I hate the concept of putting a ton of overloads in the main client.py file. It makes it way more bloated than it needs to be. Can't wait_for and its overloads be put in a mixin in a separate file?

Instead of mixins maybe .pyi files could be used to achieve this? I don't know much about those though

EQUENOS avatar Sep 21 '23 05:09 EQUENOS

I hate the concept of putting a ton of overloads in the main client.py file. It makes it way more bloated than it needs to be. Can't wait_for and its overloads be put in a mixin in a separate file?

Instead of mixins maybe .pyi files could be used to achieve this? I don't know much about those though

Not an expert on the topic either, but i heard that .pyi files are preferred over source code by type checkers, which means if we're going to supply a .pyi file we'll have to type hint the entirety of client.py there (since for TCs can't consume both stubs and source hints), which sounds like another PR to drop source hints altogether.

(mypy reference: https://mypy.readthedocs.io/en/stable/stubs.html#creating-a-stub - "If a directory contains both a .py and a .pyi file for the same module, the .pyi file takes precedence.")

EDIT: Forget what i said, it seems like in theory there is a way to make "partial" stubs: https://peps.python.org/pep-0561/#partial-stub-packages

elenakrittik avatar Sep 21 '23 05:09 elenakrittik

which means if we're going to supply a .pyi file we'll have to type hint the entirety of client.py there

Yea, not something we want to do. Creating "partial" stubs like you mentioned would be cool, but that mechanism only applies at the file/module level and not the class/func level, which would be needed here.

shiftinv avatar Sep 21 '23 19:09 shiftinv