Don't assume enums are exhaustive
Closed this issue · 7 comments
We currently have code like this:
#[derive(Debug, Eq, PartialEq, Clone, Deserialize_repr, Serialize_repr)]
#[repr(u8)]
pub enum SignatureHelpTriggerKind {
Invoked = 1,
TriggerCharacter = 2,
ContentChange = 3,
}
the problem is that if the client implements a protocol extension and send 4
, this will completely prevent deserialization, instead of graceful degradation. We should do the following instead
#[serde(transparent)]
pub struct SignatureHelpTriggerKind(u8);
impl SignatureHelpTriggerKind {
pub const Invoked = SignatureHelpTriggerKind(1);
pub const TriggerCharacter = SignatureHelpTriggerKind(2);
pub const ContentChange = SignatureHelpTriggerKind(3);
}
We already do this for some string-based enums:
lsp-types/src/semantic_tokens.rs
Line 18 in 7801115
As reported in rust-lang/rust-analyzer#8729 (comment), I recently explored making rust-analyzer compatible with Visual Studio 2022. These are the smallest changes that I had to do to lsp-types to make it compatible with Visual Studio: Quick and dirty fix to enums that are not compatible with Visual Studio.
There are two issues that arise when using rust-analyzer with VS:
- Visual Studio adds some custom values for existing LSP enums, I worked around that adding a
#[serde(other)]
value to these enums, - Visual Studio uses some negative or very large numbers for internal values of LSP enums, I had to change some of them from
u8
toi32
.
I would like to work on this one. Should I update all the #[repr(u8)]
enums?
That sounds like a good plan.
Short term, I think doing refactor like the one in #213 (comment) is the way to go.
Long-term, someone has to write lsp.d.ts
file along the lines of vscode.d.ts
, so that we can just generate the whole thing instead of manually duplicating the info from markdown files.
I think it needs:
- manual or semi-automated "parsing" of LSP markdown spec into
lsp.d.ts
file - writing some TypeScript code to parse that
d.ts
and to emit some language-independent JSON (more or less serializing TS AST into JSON) - writing some Rust code to convert from JSON to serde structs
Ideally, this should be done in the protocel iteslf, but there was very little motion on this upstream: microsoft/language-server-protocol#67.
So, organizationally, I think it makes most sense for us to do all the work ourselves, and than just ask upstream if they are willing to pull.
One thing I notice is that most "extensible" enums (except DiagnosticTag
?) are missing a corresponding type
export, e.g.:
export namespace CompletionItemKind {
export const Text = 1;
export const Method = 2;
export const Function = 3;
export const Constructor = 4;
export const Field = 5;
export const Variable = 6;
export const Class = 7;
export const Interface = 8;
export const Module = 9;
export const Property = 10;
export const Unit = 11;
export const Value = 12;
export const Enum = 13;
export const Keyword = 14;
export const Snippet = 15;
export const Color = 16;
export const File = 17;
export const Reference = 18;
export const Folder = 19;
export const EnumMember = 20;
export const Constant = 21;
export const Struct = 22;
export const Event = 23;
export const Operator = 24;
export const TypeParameter = 25;
}
// vs.
export namespace InsertTextMode {
export const asIs: 1 = 1;
export const adjustIndentation: 2 = 2;
}
export type InsertTextMode = 1 | 2;
But maybe that's just a coincidence.
Couldn't we have just kept the old enums and tagged them with #[non_exhaustive]
?
@archseer not really. First of all, it would prevent users from defining protocol extensions, because you wouldn't be able to make new enum variants. Second, I don't know if serde
knows can deal with those. What might have worked was something like:
#[repr(u8)]
enum Foo {
Bar = 1,
Baz = 2,
#[serde(other)]
Extension(u8) = 3,
}
but I don't think #[serde(other)]
works like that, and custom discriminants on tuples isn't stable anyway.