From 6290874d615e849588058f6d3a5df4408894f991 Mon Sep 17 00:00:00 2001 From: Quinn Okabayashi Date: Sun, 31 Oct 2021 16:54:01 -0400 Subject: [PATCH 1/6] Implemented uuid!("") --- benches/valid_parse_str.rs | 23 +++++++++ macros/Cargo.toml | 3 ++ macros/src/lib.rs | 95 +++++++++++++++++++++++++++++++++++--- 3 files changed, 115 insertions(+), 6 deletions(-) diff --git a/benches/valid_parse_str.rs b/benches/valid_parse_str.rs index f20d6e320..0ae1b5940 100644 --- a/benches/valid_parse_str.rs +++ b/benches/valid_parse_str.rs @@ -37,3 +37,26 @@ fn bench_valid_short(b: &mut Bencher) { let _ = Uuid::parse_str("67e5504410b1426f9247bb680e5fe0c8"); }); } + + +#[cfg(feature = "macros")] +mod macro_tests { + use uuid_macros::uuid; + const _: Uuid = uuid!("00000000000000000000000000000000"); + const _: Uuid = uuid!("67e55044-10b1-426f-9247-bb680e5fe0c8"); + const _: Uuid = uuid!("67e55044-10b1-426f-9247-bb680e5fe0c8"); + const _: Uuid = uuid!("F9168C5E-CEB2-4faa-B6BF-329BF39FA1E4"); + const _: Uuid = uuid!("67e5504410b1426f9247bb680e5fe0c8"); + const _: Uuid = uuid!("01020304-1112-2122-3132-414243444546"); + const _: Uuid = + uuid!("urn:uuid:67e55044-10b1-426f-9247-bb680e5fe0c8"); + + // Nil + const _: Uuid = uuid!("00000000000000000000000000000000"); + const _: Uuid = uuid!("00000000-0000-0000-0000-000000000000"); + + // valid hyphenated + const _: Uuid = uuid!("67e55044-10b1-426f-9247-bb680e5fe0c8"); + // valid short + const _: Uuid = uuid!("67e5504410b1426f9247bb680e5fe0c8"); +} \ No newline at end of file diff --git a/macros/Cargo.toml b/macros/Cargo.toml index 75f041194..d0c03c7b8 100644 --- a/macros/Cargo.toml +++ b/macros/Cargo.toml @@ -7,3 +7,6 @@ edition = "2018" proc-macro = true [dependencies] +syn = "1.0.80" +quote = "1.0.10" +proc-macro2 = "1.0.29" diff --git a/macros/src/lib.rs b/macros/src/lib.rs index 396a74a42..957e0a929 100644 --- a/macros/src/lib.rs +++ b/macros/src/lib.rs @@ -1,4 +1,8 @@ -use std; +use proc_macro::TokenStream; +use proc_macro2::TokenStream as TokenStream2; +use quote::{quote, quote_spanned}; +use std::fmt; +use syn::spanned::Spanned; #[path = "../../shared/error.rs"] #[allow(dead_code)] @@ -8,10 +12,89 @@ mod error; #[allow(dead_code)] mod parser; -#[cfg(test)] -mod tests { - #[test] - fn it_works() { - assert_eq!(2 + 2, 4); +/// Parse [`Uuid`][uuid::Uuid]s from string literals at compile time. +/// ## Usage +/// This macro transforms the string literal representation of a [`Uuid`][uuid::Uuid] into the bytes representation, +/// raising a compilation error if it cannot properly be parsed. +/// +/// ## Examples +/// Setting a global constant: +/// ``` +/// # use uuid::{uuid, Uuid}; +/// pub const SCHEMA_ATTR_CLASS: Uuid = uuid!("00000000-0000-0000-0000-ffff00000000"); +/// pub const SCHEMA_ATTR_UUID: Uuid = uuid!("00000000-0000-0000-0000-ffff00000001"); +/// pub const SCHEMA_ATTR_NAME: Uuid = uuid!("00000000-0000-0000-0000-ffff00000002"); +/// ``` +/// Defining a local variable: +/// ``` +/// # use uuid::{uuid, Uuid}; +/// let uuid: Uuid = uuid!("urn:uuid:F9168C5E-CEB2-4faa-B6BF-329BF39FA1E4"); +/// ``` +/// ## Compilation Failures +/// Invalid UUIDs are rejected: +/// ```ignore +/// # use uuid::{uuid, Uuid}; +/// let uuid: Uuid = uuid!("F9168C5E-ZEB2-4FAA-B6BF-329BF39FA1E4"); +/// ``` +/// Provides the following compilation error: +/// ```txt +/// error: invalid character: expected an optional prefix of `urn:uuid:` followed by 0123456789abcdefABCDEF-, found Z at 9 +/// | +/// | let id: Uuid = uuid!("F9168C5E-ZEB2-4FAA-B6BF-329BF39FA1E4"); +/// | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +/// ``` +/// Tokens that aren't string literals are also rejected: +/// ```ignore +/// # use uuid::{uuid, Uuid}; +/// let uuid_str: &str = "550e8400e29b41d4a716446655440000"; +/// let uuid: Uuid = uuid!(uuid_str); +/// ``` +/// Provides the following compilation error: +/// ```txt +/// error: expected string literal +/// | +/// | let uuid: Uuid = uuid!(uuid_str); +/// | ^^^^^^^^ +/// ``` +/// +/// [uuid::Uuid]: https://docs.rs/uuid/*/uuid/struct.Uuid.html +#[proc_macro] +pub fn uuid(input: TokenStream) -> TokenStream { + build_uuid(input.clone()).unwrap_or_else(|e| { + let msg = e.to_string(); + TokenStream::from(quote_spanned! { + TokenStream2::from(input).span() => + compile_error!(#msg) + }) + }) +} + +enum Error { + NonStringLiteral, + UuidParse(error::Error), +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + Error::NonStringLiteral => f.write_str("expected string literal"), + Error::UuidParse(ref e) => write!(f, "{}", e), + } } } + +fn build_uuid(input: TokenStream) -> Result { + let uuid_str = match syn::parse::(input) { + Ok(syn::Lit::Str(ref literal)) => literal.value(), + _ => return Err(Error::NonStringLiteral), + }; + + let bytes = parser::parse_str(&uuid_str).map_err(Error::UuidParse)?; + + let tokens = bytes + .iter() + .map(|byte| quote! { #byte, }) + .collect::(); + + Ok(quote! {::uuid::Uuid::from_bytes([#tokens])}.into()) +} From 622806c099276d0b09412066875ce25c660f4f4b Mon Sep 17 00:00:00 2001 From: Quinn Okabayashi Date: Sun, 31 Oct 2021 17:12:06 -0400 Subject: [PATCH 2/6] Fixed undetected `crate::std` errors --- benches/valid_parse_str.rs | 2 +- macros/src/lib.rs | 10 ++++++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/benches/valid_parse_str.rs b/benches/valid_parse_str.rs index 0ae1b5940..6cc55a7f5 100644 --- a/benches/valid_parse_str.rs +++ b/benches/valid_parse_str.rs @@ -41,7 +41,7 @@ fn bench_valid_short(b: &mut Bencher) { #[cfg(feature = "macros")] mod macro_tests { - use uuid_macros::uuid; + use uuid::{uuid, Uuid}; const _: Uuid = uuid!("00000000000000000000000000000000"); const _: Uuid = uuid!("67e55044-10b1-426f-9247-bb680e5fe0c8"); const _: Uuid = uuid!("67e55044-10b1-426f-9247-bb680e5fe0c8"); diff --git a/macros/src/lib.rs b/macros/src/lib.rs index 957e0a929..f5e3b0d99 100644 --- a/macros/src/lib.rs +++ b/macros/src/lib.rs @@ -4,12 +4,18 @@ use quote::{quote, quote_spanned}; use std::fmt; use syn::spanned::Spanned; +#[cfg(any(feature = "std", test))] +#[macro_use] +extern crate std; + +#[cfg(all(not(feature = "std"), not(test)))] +#[macro_use] +extern crate core as std; + #[path = "../../shared/error.rs"] -#[allow(dead_code)] mod error; #[path = "../../shared/parser.rs"] -#[allow(dead_code)] mod parser; /// Parse [`Uuid`][uuid::Uuid]s from string literals at compile time. From 860818ae38e11680914a9dc35f4ed26cbe904d80 Mon Sep 17 00:00:00 2001 From: Quinn Okabayashi Date: Sun, 31 Oct 2021 22:13:08 -0400 Subject: [PATCH 3/6] Improved testing --- Cargo.toml | 3 + benches/macros/invalid_parse.rs | 29 +++++++ benches/macros/invalid_parse.stderr | 125 ++++++++++++++++++++++++++++ benches/macros/renamed.rs | 11 +++ benches/macros/valid_parse.rs | 20 +++++ benches/valid_parse_str.rs | 28 ++----- 6 files changed, 195 insertions(+), 21 deletions(-) create mode 100644 benches/macros/invalid_parse.rs create mode 100644 benches/macros/invalid_parse.stderr create mode 100644 benches/macros/renamed.rs create mode 100644 benches/macros/valid_parse.rs diff --git a/Cargo.toml b/Cargo.toml index 18ffc8770..fde23a9f9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -133,6 +133,9 @@ version = "0.2" [dev-dependencies.wasm-bindgen-test] version = "0.3" +[dev-dependencies.trybuild] +version = "1.0.52" + [target.'cfg(windows)'.dev-dependencies.winapi] version = "0.3" features = ["combaseapi"] diff --git a/benches/macros/invalid_parse.rs b/benches/macros/invalid_parse.rs new file mode 100644 index 000000000..1f4330f09 --- /dev/null +++ b/benches/macros/invalid_parse.rs @@ -0,0 +1,29 @@ +use uuid::{uuid, Uuid}; + +const _: Uuid = uuid!(""); +const _: Uuid = uuid!("!"); +const _: Uuid = uuid!("F9168C5E-CEB2-4faa-B6BF-329BF39FA1E45"); +const _: Uuid = uuid!("F9168C5E-CEB2-4faa-BBF-329BF39FA1E4"); +const _: Uuid = uuid!("F9168C5E-CEB2-4faa-BGBF-329BF39FA1E4"); +const _: Uuid = uuid!("F9168C5E-CEB2-4faa-B6BFF329BF39FA1E4"); +const _: Uuid = uuid!("F9168C5E-CEB2-4faa"); +const _: Uuid = uuid!("F9168C5E-CEB2-4faaXB6BFF329BF39FA1E4"); +const _: Uuid = uuid!("F9168C5E-CEB-24fa-eB6BFF32-BF39FA1E4"); +const _: Uuid = uuid!("01020304-1112-2122-3132-41424344"); +const _: Uuid = uuid!("67e5504410b1426f9247bb680e5fe0c88"); +const _: Uuid = uuid!("67e5504410b1426f9247bb680e5fe0cg8"); +const _: Uuid = uuid!("67e5504410b1426%9247bb680e5fe0c8"); + +// Test error reporting +const _: Uuid = uuid!("67e5504410b1426f9247bb680e5fe0c"); +const _: Uuid = uuid!("67e550X410b1426f9247bb680e5fe0cd"); +const _: Uuid = uuid!("67e550-4105b1426f9247bb680e5fe0c"); +const _: Uuid = uuid!("F9168C5E-CEB2-4faa-B6BF1-02BF39FA1E4"); + + +const _: Uuid = uuid!("F9168C5E-CEB2-4faa-BBF-329BF39FA1E4"); +const _: Uuid = uuid!("F9168C5E-CEB2-4faa-BGBF-329BF39FA1E4"); +const _: Uuid = uuid!("01020304-1112-2122-3132-41424344"); +const _: Uuid = uuid!("F9168C5E-CEB2-4faa-B6BFF329BF39FA1E4"); + +fn main() {} \ No newline at end of file diff --git a/benches/macros/invalid_parse.stderr b/benches/macros/invalid_parse.stderr new file mode 100644 index 000000000..563d2aa43 --- /dev/null +++ b/benches/macros/invalid_parse.stderr @@ -0,0 +1,125 @@ +error: invalid length: expected one of [36, 32], found 0 + --> benches/macros/invalid_parse.rs:3:23 + | +3 | const _: Uuid = uuid!(""); + | ^^ + +error: invalid length: expected one of [36, 32], found 1 + --> benches/macros/invalid_parse.rs:4:23 + | +4 | const _: Uuid = uuid!("!"); + | ^^^ + +error: invalid length: expected one of [36, 32], found 37 + --> benches/macros/invalid_parse.rs:5:23 + | +5 | const _: Uuid = uuid!("F9168C5E-CEB2-4faa-B6BF-329BF39FA1E45"); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: invalid length: expected one of [36, 32], found 35 + --> benches/macros/invalid_parse.rs:6:23 + | +6 | const _: Uuid = uuid!("F9168C5E-CEB2-4faa-BBF-329BF39FA1E4"); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: invalid character: expected an optional prefix of `urn:uuid:` followed by 0123456789abcdefABCDEF-, found G at 20 + --> benches/macros/invalid_parse.rs:7:44 + | +7 | const _: Uuid = uuid!("F9168C5E-CEB2-4faa-BGBF-329BF39FA1E4"); + | ^ + +error: invalid number of groups: expected one of [1, 5], found 4 + --> benches/macros/invalid_parse.rs:8:23 + | +8 | const _: Uuid = uuid!("F9168C5E-CEB2-4faa-B6BFF329BF39FA1E4"); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: invalid length: expected one of [36, 32], found 18 + --> benches/macros/invalid_parse.rs:9:23 + | +9 | const _: Uuid = uuid!("F9168C5E-CEB2-4faa"); + | ^^^^^^^^^^^^^^^^^^^^ + +error: invalid character: expected an optional prefix of `urn:uuid:` followed by 0123456789abcdefABCDEF-, found X at 18 + --> benches/macros/invalid_parse.rs:10:42 + | +10 | const _: Uuid = uuid!("F9168C5E-CEB2-4faaXB6BFF329BF39FA1E4"); + | ^ + +error: invalid group length: expected 4, found 3 in group 1 + --> benches/macros/invalid_parse.rs:11:33 + | +11 | const _: Uuid = uuid!("F9168C5E-CEB-24fa-eB6BFF32-BF39FA1E4"); + | ^^^ + +error: invalid group length: expected 12, found 8 in group 4 + --> benches/macros/invalid_parse.rs:12:48 + | +12 | const _: Uuid = uuid!("01020304-1112-2122-3132-41424344"); + | ^^^^^^^^ + +error: invalid length: expected one of [36, 32], found 33 + --> benches/macros/invalid_parse.rs:13:23 + | +13 | const _: Uuid = uuid!("67e5504410b1426f9247bb680e5fe0c88"); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: invalid length: expected one of [36, 32], found 33 + --> benches/macros/invalid_parse.rs:14:23 + | +14 | const _: Uuid = uuid!("67e5504410b1426f9247bb680e5fe0cg8"); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: invalid character: expected an optional prefix of `urn:uuid:` followed by 0123456789abcdefABCDEF-, found % at 15 + --> benches/macros/invalid_parse.rs:15:39 + | +15 | const _: Uuid = uuid!("67e5504410b1426%9247bb680e5fe0c8"); + | ^ + +error: invalid length: expected one of [36, 32], found 31 + --> benches/macros/invalid_parse.rs:18:23 + | +18 | const _: Uuid = uuid!("67e5504410b1426f9247bb680e5fe0c"); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: invalid character: expected an optional prefix of `urn:uuid:` followed by 0123456789abcdefABCDEF-, found X at 6 + --> benches/macros/invalid_parse.rs:19:30 + | +19 | const _: Uuid = uuid!("67e550X410b1426f9247bb680e5fe0cd"); + | ^ + +error: invalid group length: expected 8, found 6 in group 0 + --> benches/macros/invalid_parse.rs:20:24 + | +20 | const _: Uuid = uuid!("67e550-4105b1426f9247bb680e5fe0c"); + | ^^^^^^ + +error: invalid group length: expected 4, found 5 in group 3 + --> benches/macros/invalid_parse.rs:21:43 + | +21 | const _: Uuid = uuid!("F9168C5E-CEB2-4faa-B6BF1-02BF39FA1E4"); + | ^^^^^ + +error: invalid length: expected one of [36, 32], found 35 + --> benches/macros/invalid_parse.rs:24:23 + | +24 | const _: Uuid = uuid!("F9168C5E-CEB2-4faa-BBF-329BF39FA1E4"); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: invalid character: expected an optional prefix of `urn:uuid:` followed by 0123456789abcdefABCDEF-, found G at 20 + --> benches/macros/invalid_parse.rs:25:44 + | +25 | const _: Uuid = uuid!("F9168C5E-CEB2-4faa-BGBF-329BF39FA1E4"); + | ^ + +error: invalid group length: expected 12, found 8 in group 4 + --> benches/macros/invalid_parse.rs:26:48 + | +26 | const _: Uuid = uuid!("01020304-1112-2122-3132-41424344"); + | ^^^^^^^^ + +error: invalid number of groups: expected one of [1, 5], found 4 + --> benches/macros/invalid_parse.rs:27:23 + | +27 | const _: Uuid = uuid!("F9168C5E-CEB2-4faa-B6BFF329BF39FA1E4"); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/benches/macros/renamed.rs b/benches/macros/renamed.rs new file mode 100644 index 000000000..87a5dd619 --- /dev/null +++ b/benches/macros/renamed.rs @@ -0,0 +1,11 @@ +use ::uuid::{uuid as id, Uuid as Id}; + +mod uuid { + struct MyType; +} + +struct Uuid; + +const _: Id = id!("67e55044-10b1-426f-9247-bb680e5fe0c8"); + +fn main() {} \ No newline at end of file diff --git a/benches/macros/valid_parse.rs b/benches/macros/valid_parse.rs new file mode 100644 index 000000000..31ab620cc --- /dev/null +++ b/benches/macros/valid_parse.rs @@ -0,0 +1,20 @@ +use uuid::{uuid, Uuid}; + +const _: Uuid = uuid!("00000000000000000000000000000000"); +const _: Uuid = uuid!("67e55044-10b1-426f-9247-bb680e5fe0c8"); +const _: Uuid = uuid!("67e55044-10b1-426f-9247-bb680e5fe0c8"); +const _: Uuid = uuid!("F9168C5E-CEB2-4faa-B6BF-329BF39FA1E4"); +const _: Uuid = uuid!("67e5504410b1426f9247bb680e5fe0c8"); +const _: Uuid = uuid!("01020304-1112-2122-3132-414243444546"); +const _: Uuid = uuid!("urn:uuid:67e55044-10b1-426f-9247-bb680e5fe0c8"); + +// Nil +const _: Uuid = uuid!("00000000000000000000000000000000"); +const _: Uuid = uuid!("00000000-0000-0000-0000-000000000000"); + +// valid hyphenated +const _: Uuid = uuid!("67e55044-10b1-426f-9247-bb680e5fe0c8"); +// valid short +const _: Uuid = uuid!("67e5504410b1426f9247bb680e5fe0c8"); + +fn main() {} \ No newline at end of file diff --git a/benches/valid_parse_str.rs b/benches/valid_parse_str.rs index 6cc55a7f5..36fee8326 100644 --- a/benches/valid_parse_str.rs +++ b/benches/valid_parse_str.rs @@ -38,25 +38,11 @@ fn bench_valid_short(b: &mut Bencher) { }); } - #[cfg(feature = "macros")] -mod macro_tests { - use uuid::{uuid, Uuid}; - const _: Uuid = uuid!("00000000000000000000000000000000"); - const _: Uuid = uuid!("67e55044-10b1-426f-9247-bb680e5fe0c8"); - const _: Uuid = uuid!("67e55044-10b1-426f-9247-bb680e5fe0c8"); - const _: Uuid = uuid!("F9168C5E-CEB2-4faa-B6BF-329BF39FA1E4"); - const _: Uuid = uuid!("67e5504410b1426f9247bb680e5fe0c8"); - const _: Uuid = uuid!("01020304-1112-2122-3132-414243444546"); - const _: Uuid = - uuid!("urn:uuid:67e55044-10b1-426f-9247-bb680e5fe0c8"); - - // Nil - const _: Uuid = uuid!("00000000000000000000000000000000"); - const _: Uuid = uuid!("00000000-0000-0000-0000-000000000000"); - - // valid hyphenated - const _: Uuid = uuid!("67e55044-10b1-426f-9247-bb680e5fe0c8"); - // valid short - const _: Uuid = uuid!("67e5504410b1426f9247bb680e5fe0c8"); -} \ No newline at end of file +#[test] +fn test_valid_macro() { + let t = trybuild::TestCases::new(); + t.pass("benches/macros/valid_parse.rs"); + t.pass("benches/macros/renamed.rs"); + t.compile_fail("benches/macros/invalid_parse.rs"); +} From 8868cd526c3d976ed2ac53ba3628fe039704dd44 Mon Sep 17 00:00:00 2001 From: Quinn Okabayashi Date: Sun, 31 Oct 2021 22:13:24 -0400 Subject: [PATCH 4/6] Improved diagnostics --- macros/src/lib.rs | 88 +++++++++++++++++++---------------------------- shared/parser.rs | 2 +- 2 files changed, 36 insertions(+), 54 deletions(-) diff --git a/macros/src/lib.rs b/macros/src/lib.rs index f5e3b0d99..a382f9213 100644 --- a/macros/src/lib.rs +++ b/macros/src/lib.rs @@ -18,58 +18,33 @@ mod error; #[path = "../../shared/parser.rs"] mod parser; -/// Parse [`Uuid`][uuid::Uuid]s from string literals at compile time. -/// ## Usage -/// This macro transforms the string literal representation of a [`Uuid`][uuid::Uuid] into the bytes representation, -/// raising a compilation error if it cannot properly be parsed. -/// -/// ## Examples -/// Setting a global constant: -/// ``` -/// # use uuid::{uuid, Uuid}; -/// pub const SCHEMA_ATTR_CLASS: Uuid = uuid!("00000000-0000-0000-0000-ffff00000000"); -/// pub const SCHEMA_ATTR_UUID: Uuid = uuid!("00000000-0000-0000-0000-ffff00000001"); -/// pub const SCHEMA_ATTR_NAME: Uuid = uuid!("00000000-0000-0000-0000-ffff00000002"); -/// ``` -/// Defining a local variable: -/// ``` -/// # use uuid::{uuid, Uuid}; -/// let uuid: Uuid = uuid!("urn:uuid:F9168C5E-CEB2-4faa-B6BF-329BF39FA1E4"); -/// ``` -/// ## Compilation Failures -/// Invalid UUIDs are rejected: -/// ```ignore -/// # use uuid::{uuid, Uuid}; -/// let uuid: Uuid = uuid!("F9168C5E-ZEB2-4FAA-B6BF-329BF39FA1E4"); -/// ``` -/// Provides the following compilation error: -/// ```txt -/// error: invalid character: expected an optional prefix of `urn:uuid:` followed by 0123456789abcdefABCDEF-, found Z at 9 -/// | -/// | let id: Uuid = uuid!("F9168C5E-ZEB2-4FAA-B6BF-329BF39FA1E4"); -/// | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -/// ``` -/// Tokens that aren't string literals are also rejected: -/// ```ignore -/// # use uuid::{uuid, Uuid}; -/// let uuid_str: &str = "550e8400e29b41d4a716446655440000"; -/// let uuid: Uuid = uuid!(uuid_str); -/// ``` -/// Provides the following compilation error: -/// ```txt -/// error: expected string literal -/// | -/// | let uuid: Uuid = uuid!(uuid_str); -/// | ^^^^^^^^ -/// ``` -/// -/// [uuid::Uuid]: https://docs.rs/uuid/*/uuid/struct.Uuid.html #[proc_macro] -pub fn uuid(input: TokenStream) -> TokenStream { +pub fn parse_lit(input: TokenStream) -> TokenStream { build_uuid(input.clone()).unwrap_or_else(|e| { let msg = e.to_string(); - TokenStream::from(quote_spanned! { - TokenStream2::from(input).span() => + let ts = TokenStream2::from(input); + let span = match e { + Error::UuidParse(error::Error( + error::ErrorKind::InvalidCharacter { index, .. }, + )) => { + let mut s = proc_macro2::Literal::string(""); + s.set_span(ts.span()); + s.subspan(index + 1..=index + 1).unwrap() + } + Error::UuidParse(error::Error( + error::ErrorKind::InvalidGroupLength { found, group, .. }, + )) => { + let start = + parser::GROUP_LENS.iter().take(group).sum::() + + group + + 1; + let mut s = proc_macro2::Literal::string(""); + s.set_span(ts.span()); + s.subspan(start..start + found).unwrap() + } + _ => ts.span(), + }; + TokenStream::from(quote_spanned! {span=> compile_error!(#msg) }) }) @@ -90,17 +65,24 @@ impl fmt::Display for Error { } fn build_uuid(input: TokenStream) -> Result { - let uuid_str = match syn::parse::(input) { - Ok(syn::Lit::Str(ref literal)) => literal.value(), + let literal = match syn::parse::(input) { + Ok(syn::Lit::Str(literal)) => literal, _ => return Err(Error::NonStringLiteral), }; - let bytes = parser::parse_str(&uuid_str).map_err(Error::UuidParse)?; + // steps: + // get Literal + // do Literal::subspan + // pass that into quote_spanned + + let literal = literal.value(); + + let bytes = parser::parse_str(&literal).map_err(Error::UuidParse)?; let tokens = bytes .iter() .map(|byte| quote! { #byte, }) .collect::(); - Ok(quote! {::uuid::Uuid::from_bytes([#tokens])}.into()) + Ok(quote! {[#tokens]}.into()) } diff --git a/shared/parser.rs b/shared/parser.rs index 0eb270175..396a37af8 100644 --- a/shared/parser.rs +++ b/shared/parser.rs @@ -26,7 +26,7 @@ fn len_matches_any(len: usize, crits: &[usize]) -> bool { const ACC_GROUP_LENS: [usize; 5] = [8, 12, 16, 20, 32]; // Length of each hyphenated group in hex digits. -const GROUP_LENS: [usize; 5] = [8, 4, 4, 4, 12]; +pub(super) const GROUP_LENS: [usize; 5] = [8, 4, 4, 4, 12]; pub fn parse_str(mut input: &str) -> Result<[u8; 16], Error> { // Ensure length is valid for any of the supported formats From 733cf3838f311d8628722bb18a89339f78a65aeb Mon Sep 17 00:00:00 2001 From: Quinn Okabayashi Date: Sun, 31 Oct 2021 22:13:42 -0400 Subject: [PATCH 5/6] Made `macros` module in `uuid` --- src/lib.rs | 6 ++++-- src/macros.rs | 53 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 57 insertions(+), 2 deletions(-) create mode 100644 src/macros.rs diff --git a/src/lib.rs b/src/lib.rs index ff41f7181..ae6ca6e82 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -233,8 +233,10 @@ mod v5; mod winapi_support; #[cfg(feature = "macros")] -#[doc(inline)] -pub use uuid_macros::*; +#[macro_use] +mod macros; +#[cfg(feature = "macros")] +pub extern crate uuid_macros; use crate::std::convert; diff --git a/src/macros.rs b/src/macros.rs new file mode 100644 index 000000000..f4380d47e --- /dev/null +++ b/src/macros.rs @@ -0,0 +1,53 @@ +/// Parse [`Uuid`][uuid::Uuid]s from string literals at compile time. +/// ## Usage +/// This macro transforms the string literal representation of a +/// [`Uuid`][uuid::Uuid] into the bytes representation, raising a compilation +/// error if it cannot properly be parsed. +/// +/// ## Examples +/// Setting a global constant: +/// ``` +/// # use uuid::{uuid, Uuid}; +/// pub const SCHEMA_ATTR_CLASS: Uuid = uuid!("00000000-0000-0000-0000-ffff00000000"); +/// pub const SCHEMA_ATTR_UUID: Uuid = uuid!("00000000-0000-0000-0000-ffff00000001"); +/// pub const SCHEMA_ATTR_NAME: Uuid = uuid!("00000000-0000-0000-0000-ffff00000002"); +/// ``` +/// Defining a local variable: +/// ``` +/// # use uuid::{uuid, Uuid}; +/// let uuid: Uuid = uuid!("urn:uuid:F9168C5E-CEB2-4faa-B6BF-329BF39FA1E4"); +/// ``` +/// ## Compilation Failures +/// Invalid UUIDs are rejected: +/// ```ignore +/// # use uuid::{uuid, Uuid}; +/// let uuid: Uuid = uuid!("F9168C5E-ZEB2-4FAA-B6BF-329BF39FA1E4"); +/// ``` +/// Provides the following compilation error: +/// ```txt +/// error: invalid character: expected an optional prefix of `urn:uuid:` followed by 0123456789abcdefABCDEF-, found Z at 9 +/// | +/// | let id: Uuid = uuid!("F9168C5E-ZEB2-4FAA-B6BF-329BF39FA1E4"); +/// | ^ +/// ``` +/// Tokens that aren't string literals are also rejected: +/// ```ignore +/// # use uuid::{uuid, Uuid}; +/// let uuid_str: &str = "550e8400e29b41d4a716446655440000"; +/// let uuid: Uuid = uuid!(uuid_str); +/// ``` +/// Provides the following compilation error: +/// ```txt +/// error: expected string literal +/// | +/// | let uuid: Uuid = uuid!(uuid_str); +/// | ^^^^^^^^ +/// ``` +/// +/// [uuid::Uuid]: https://docs.rs/uuid/*/uuid/struct.Uuid.html +#[macro_export] +macro_rules! uuid { + ($uuid:tt) => {{ + $crate::Uuid::from_bytes($crate::uuid_macros::parse_lit!($uuid)) + }}; +} From 96ef40cfaf6834151666f521dbc6f28cbb65dcef Mon Sep 17 00:00:00 2001 From: Quinn Okabayashi Date: Sun, 31 Oct 2021 23:12:36 -0400 Subject: [PATCH 6/6] Micro refactor --- macros/src/lib.rs | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/macros/src/lib.rs b/macros/src/lib.rs index a382f9213..1ff05ffd3 100644 --- a/macros/src/lib.rs +++ b/macros/src/lib.rs @@ -65,19 +65,12 @@ impl fmt::Display for Error { } fn build_uuid(input: TokenStream) -> Result { - let literal = match syn::parse::(input) { - Ok(syn::Lit::Str(literal)) => literal, + let string = match syn::parse::(input) { + Ok(syn::Lit::Str(literal)) => literal.value(), _ => return Err(Error::NonStringLiteral), }; - // steps: - // get Literal - // do Literal::subspan - // pass that into quote_spanned - - let literal = literal.value(); - - let bytes = parser::parse_str(&literal).map_err(Error::UuidParse)?; + let bytes = parser::parse_str(&string).map_err(Error::UuidParse)?; let tokens = bytes .iter()