serenity-rs / poise

Discord bot command framework for serenity, with advanced features like edit tracking and flexible argument parsing
MIT License
624 stars 111 forks source link

Fix poise::command when used with generic functions #206

Closed m4tx closed 11 months ago

m4tx commented 11 months ago

This commit also adds basic tests for the macros crate.

This PR enables generic functions to be used with #[poise::command] proc macro, such as:

#[poise::command(slash_command)]
pub async fn example<T: Send + Sync>(_ctx: Context<'_, T, Infallible>) -> Result<(), Infallible> {
    Ok(())
}

The old macro version didn't put generic arguments in the outer function, resulting in a compilation error.

m4tx commented 11 months ago

FYI: the work from this PR is successfully being used in riichi/chombot#192.

kangalio commented 11 months ago

I don't understand this conceptually.. what type is the generic parameter set to, when poise invokes it?

m4tx commented 11 months ago

@kangalio this depends on the type which you pass to FrameworkOptions. You still need to instantiate your command with a concrete Context type, but this PR allows you to build libraries that expose poise::commands which can be used with generic contexts that can store pretty much any actual type inside.

For instance - have a look at the PR I've linked - this defines hand() command generic here, which is later instantiated here and here with different Context types (which in these examples is inferred from the function return type).

In other words, poise invokes it with some specific types which you need to specify when you pass the commands to FrameworkOptions.

kangalio commented 11 months ago

Ohh I see. Interesting, I never considered the possibility of needing this.

How does it happen that your bot needs to support the exact same command with two different user data types?

And what do you think of doing that on current unmodified poise by having two #[poise::command]-annotated functions that both call into the same generic un-annotated function?

m4tx commented 11 months ago

How does it happen that your bot needs to support the exact same command with two different user data types?

The problem is, it's not one bot - I'm building two different bots with similar (but not quite the same) functionalities. Since they have different functionalities, they do not share common Context object, since each of them requires a bit different user data structures (which implement some specific traits, so that I can write generic commands that just use subsets of these user data objects).

And what do you think of doing that on current unmodified poise by having two #[poise::command]-annotated functions that both call into the same generic un-annotated function?

Hmm, yes, that would work, too - however, this requires a little bit of extra boilerplate (especially since the command's description, parameters, and the parameters' description all need to be duplicated), and I thought adding support for generics in poise is easy enough and elegant, too.

kangalio commented 11 months ago

I'm building two different bots with similar (but not quite the same) functionalities

Okay... sounds cursed but I guess let users be users :joy: I'll just quickly integrate the tests into the examples/ folder for consistency