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

feat: Add a TryFrom<&str> implementation for enumerations #224

Merged
merged 2 commits into from Feb 7, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
10 changes: 10 additions & 0 deletions src/completion.rs
Expand Up @@ -587,4 +587,14 @@ mod tests {
"TypeParameter"
);
}

#[test]
fn test_try_from_enum() {
use std::convert::TryInto;
assert_eq!("Text".try_into(), Ok(CompletionItemKind::TEXT));
assert_eq!(
"TypeParameter".try_into(),
Ok(CompletionItemKind::TYPE_PARAMETER)
);
}
}
44 changes: 41 additions & 3 deletions src/lib.rs
Expand Up @@ -15,8 +15,7 @@ able to parse any URI, such as `urn:isbn:0451450523`.

*/
#![allow(non_upper_case_globals)]
#![forbid(unsafe_code)]

#[forbid(unsafe_code)]
#[macro_use]
extern crate bitflags;

Expand All @@ -30,6 +29,32 @@ use serde::de;
use serde::de::Error as Error_;
use serde_json::Value;

const fn fmt_pascal_case_const(name: &str) -> ([u8; 128], usize) {
let mut buf = [0; 128];
let mut buf_i = 0;
let mut name_i = 0;
let name = name.as_bytes();
while name_i < name.len() {
let first = name[name_i];
name_i += 1;

buf[buf_i] = first;
buf_i += 1;

while name_i < name.len() {
let rest = name[name_i];
name_i += 1;
if rest == b'_' {
break;
}

buf[buf_i] = rest.to_ascii_lowercase();
buf_i += 1;
}
}
(buf, buf_i)
}

fn fmt_pascal_case(f: &mut std::fmt::Formatter<'_>, name: &str) -> std::fmt::Result {
for word in name.split('_') {
let mut chars = word.chars();
Expand All @@ -43,7 +68,7 @@ fn fmt_pascal_case(f: &mut std::fmt::Formatter<'_>, name: &str) -> std::fmt::Res
}

macro_rules! lsp_enum {
(impl $typ: ty { $( $(#[$attr:meta])* pub const $name: ident : $enum_type: ty = $value: expr; )* }) => {
(impl $typ: ident { $( $(#[$attr:meta])* pub const $name: ident : $enum_type: ty = $value: expr; )* }) => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not sure why, also the other : ty is still here

impl $typ {
$(
$(#[$attr])*
Expand All @@ -61,6 +86,19 @@ macro_rules! lsp_enum {
}
}
}

impl std::convert::TryFrom<&str> for $typ {
type Error = &'static str;
fn try_from(value: &str) -> Result<Self, Self::Error> {
match value {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess we can also write match ()

$(
_ if { let (buf, len) = crate::fmt_pascal_case_const(stringify!($name)); &buf[..len] == value.as_bytes() } => Ok(Self::$name),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

great idea. The only weird thing is that when I change the max length from 128 to 5 we will error at runtime (not at compile time!) if the value parameter is only known at runtime -- even though $name is known at compile time.
Assertions probably won't help here either (though they should be allowed in stable Rust soon)

So we might save allocations but we still run the code at runtime and don't generate the neat match-statement a human would write, which is "TypeParameter" => Ok(Self::TYPE_PARAMETER)

Anyway, that's not so important.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So at this point it seems better to write
match pascal_case_to_screaming_snake_case(value) { "TEXT" => Ok(Self::Text), .. }

)*
_ => Err("unknown enum variant"),
}
}
}

}
}

Expand Down