Skip to content
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

Typed IDs. #1149

Open
elenakrittik opened this issue Jan 9, 2024 · 8 comments
Open

Typed IDs. #1149

elenakrittik opened this issue Jan 9, 2024 · 8 comments
Labels
feature request Request for a new feature

Comments

@elenakrittik
Copy link
Contributor

elenakrittik commented Jan 9, 2024

Summary

Change all ID types from int to *Id newtypes for greater type safety.

What is the feature request for?

The core library

The Problem

Currently, this (~pseudo) code passes type checks, but in reality this will almost certainly raise an exception:

id = inter.channel.id
inter.guild.fetch_member(id)

While the above code is obviously wrong, command bodies can get large enough in practice that the error will not be as easy to spot. Using IDs in incorrect places usually results in an unhandled exception, but in very extreme cases can lead to damage (f.e. as thread ids are the same as the original message ids, one can delete thread instead of the original message or vice versa). The more developer passes IDs around, the more is the chance of making mistakes like this.

The Ideal Solution

Change all raw int IDs to appropriate newtypes. This incurs zero (or close to zero) overhead, but allows to prevent incorrect usage of IDs.

The implementation is very straightforward:

ApplicationId = NewType("ApplicationId", int)
ChannelId = NewType("ChannelId", int)
# and so on for every resource type

The rest is only about updating the library to use Ids instead of raw ints, and maybe updating converters.

The Current Solution

Carefully reviewing, testing or otherwise ensuring that IDs for e.g. channels are not used where e.g. a role Id was expected.

Additional Context

I am willing to work on this.

This was originally inspired by twilight-model's Id.

Compatibility question is not resolved yet.

@elenakrittik elenakrittik added the feature request Request for a new feature label Jan 9, 2024
@shiftinv
Copy link
Member

This is definitely an interesting proposal, thanks!

The cost of using IDs in incorrect places can range from an unhandled exception up to damaging users' servers and, in very rare, but still real cases, introducing security holes (if the ID comes from user input).

I can't think of situations where a mistake like that could cause any damage, given that IDs are unique (barring a few exceptions), but it's rather annoying and difficult to debug nonetheless, yeah.

Change all raw int IDs to appropriate newtypes. This incurs zero (or close to zero) overhead, but allows to prevent incorrect usage of IDs.

Sounds reasonable.
I'm somewhat worried about the additional overhead in very common expressions/hot paths like self.id = MessageId(int(data["id"])). It's less in >=3.11 (python/cpython@96c4cbd), but not fully negligible especially in earlier versions.

Compatibility is still to be estimated.

Compatibility is my main concern here, even if it's just in the type-checking realm, and not at runtime.
As far as I understand, only simple expressions like fetch_channel(channel.id) would continue to work as-is, anything more advanced would likely require replacing annotations with the appropriate *Id types everywhere; while reasonable in many cases, this can be a pretty big ask in larger projects.

The ideal compatible solution would be making methods accept something like ChannelId | int instead of just ChannelId. With the current typing.NewType semantics however, that would still allow passing mismatching ID types.

I don't have any data on how often mix-ups between different ID "types" happen - I imagine it's probably not that frequent to warrant a fundamental change like this, personally.
If this library was still in very early development, adopting typed IDs like this would be really cool. By now, preserving compatibility (when possible) is pretty essential, and there doesn't seem to be an obvious way to accomplish that here (wouldn't mind to be proven wrong about this, though).

@elenakrittik
Copy link
Contributor Author

I'm somewhat worried about the additional overhead in very common expressions/hot paths like self.id = MessageId(int(data["id"])). It's less in >=3.11 (python/cpython@96c4cbd), but not fully negligible especially in earlier versions.

I think we can make an exception for those and # type: ignore them, given that these IDs are coming from Discord and are likely unlikely to be invalid/wrong.

@Enegg
Copy link
Contributor

Enegg commented Jan 12, 2024

Wouldn't the identity function overhead be dwarfed by the int cast anyway?

@OseSem
Copy link
Contributor

OseSem commented Feb 21, 2024

Change all ID types from int to *Id newtypes for greater type safety.

This is cool and all, but if they were to do this it would bring some really weird code. For example: everytime you want to search someone up you would have to ApplicationID(applicationid) which could really flood the code. And if the ID doesn't exist it would already flag.

@OseSem
Copy link
Contributor

OseSem commented Feb 21, 2024

And you can make an error_handler using disnake.ext.commands.UserNotFound for example.

@elenakrittik
Copy link
Contributor Author

elenakrittik commented Feb 21, 2024

This proposal's intention is to allow optional, type-check-time guarantees about validness of ID usage. Whether to use it or not is up to you, and if you prefer catching errors at runtime instead, you will be able to do that.

(if we find a solution for the compatibility problem..)

@OseSem
Copy link
Contributor

OseSem commented Feb 21, 2024

There could be something like bot.get_member() which could be member_id: int | UserID

@Enegg
Copy link
Contributor

Enegg commented Feb 24, 2024

There could be something like bot.get_member() which could be member_id: int | UserID

The union would defeat the purpose of this issue, since NewType(..., int) is a subtype of int.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature request Request for a new feature
Projects
None yet
Development

No branches or pull requests

4 participants