From 19b85cf4a7b5396a89d364e271c3867eb6c840e4 Mon Sep 17 00:00:00 2001 From: Markus Westerlind Date: Sun, 21 Nov 2021 19:06:41 +0100 Subject: [PATCH 1/2] feat: Add a TryFrom<&str> implementation for enumerations Closes #223 cc @krobelus --- src/completion.rs | 10 ++++++++++ src/lib.rs | 44 +++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 51 insertions(+), 3 deletions(-) diff --git a/src/completion.rs b/src/completion.rs index c2d2c02..5f9277a 100644 --- a/src/completion.rs +++ b/src/completion.rs @@ -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) + ); + } } diff --git a/src/lib.rs b/src/lib.rs index 6f690bb..2528d9d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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; @@ -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(); @@ -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; )* }) => { impl $typ { $( $(#[$attr])* @@ -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 { + match value { + $( + _ if { let (buf, len) = crate::fmt_pascal_case_const(stringify!($name)); &buf[..len] == value.as_bytes() } => Ok(Self::$name), + )* + _ => Err("unknown enum variant"), + } + } + } + } } From 7337535b79000912f859319455cd371af77d1899 Mon Sep 17 00:00:00 2001 From: Markus Westerlind Date: Tue, 23 Nov 2021 13:04:59 +0100 Subject: [PATCH 2/2] fix: Ensure that the pascal_case_name is evaluated at compile time --- src/lib.rs | 31 +++++++++++++++++-------------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 2528d9d..12b22b5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -19,18 +19,16 @@ able to parse any URI, such as `urn:isbn:0451450523`. #[macro_use] extern crate bitflags; -use serde::{Deserialize, Serialize}; -use std::fmt::Debug; -pub use url::Url; - -use std::collections::HashMap; +use std::{collections::HashMap, fmt::Debug}; -use serde::de; -use serde::de::Error as Error_; +use serde::{de, de::Error as Error_, Deserialize, Serialize}; use serde_json::Value; +pub use url::Url; -const fn fmt_pascal_case_const(name: &str) -> ([u8; 128], usize) { - let mut buf = [0; 128]; +// Large enough to contain any enumeration name defined in this crate +type PascalCaseBuf = [u8; 32]; +const fn fmt_pascal_case_const(name: &str) -> (PascalCaseBuf, usize) { + let mut buf = [0; 32]; let mut buf_i = 0; let mut name_i = 0; let name = name.as_bytes(); @@ -90,9 +88,13 @@ macro_rules! lsp_enum { impl std::convert::TryFrom<&str> for $typ { type Error = &'static str; fn try_from(value: &str) -> Result { - match value { + match () { $( - _ if { let (buf, len) = crate::fmt_pascal_case_const(stringify!($name)); &buf[..len] == value.as_bytes() } => Ok(Self::$name), + _ if { + const X: (crate::PascalCaseBuf, usize) = crate::fmt_pascal_case_const(stringify!($name)); + let (buf, len) = X; + &buf[..len] == value.as_bytes() + } => Ok(Self::$name), )* _ => Err("unknown enum variant"), } @@ -706,10 +708,10 @@ pub struct ConfigurationItem { } mod url_map { - use super::*; - use std::fmt; + use super::*; + pub fn deserialize<'de, D>( deserializer: D, ) -> Result>>, D::Error> @@ -2453,9 +2455,10 @@ impl SymbolTag { #[cfg(test)] mod tests { - use super::*; use serde::{Deserialize, Serialize}; + use super::*; + pub(crate) fn test_serialization(ms: &SER, expected: &str) where SER: Serialize + for<'de> Deserialize<'de> + PartialEq + std::fmt::Debug,