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

Impl UUID macro #543

Merged
merged 7 commits into from Nov 1, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
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
3 changes: 3 additions & 0 deletions Cargo.toml
Expand Up @@ -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"]
Expand Down
29 changes: 29 additions & 0 deletions 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() {}
125 changes: 125 additions & 0 deletions 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");
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
11 changes: 11 additions & 0 deletions 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() {}
20 changes: 20 additions & 0 deletions 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() {}
9 changes: 9 additions & 0 deletions benches/parse_str.rs
Expand Up @@ -48,3 +48,12 @@ fn parse_invalid_group_len(b: &mut Bencher) {
fn parse_invalid_groups(b: &mut Bencher) {
b.iter(|| Uuid::parse_str("F9168C5E-CEB2-4faa-B6BFF329BF39FA1E4"));
}

#[cfg(feature = "macros")]
#[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");
}
3 changes: 3 additions & 0 deletions macros/Cargo.toml
Expand Up @@ -7,3 +7,6 @@ edition = "2018"
proc-macro = true

[dependencies]
syn = "1.0.80"
quote = "1.0.10"
proc-macro2 = "1.0.29"
80 changes: 72 additions & 8 deletions macros/src/lib.rs
@@ -1,17 +1,81 @@
use std;
use proc_macro::TokenStream;
use proc_macro2::TokenStream as TokenStream2;
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;
Comment on lines +7 to +13
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Required to stop shared/error.rs and shared/parser.rs from complaining. I just copy-pasted it from uuid.


#[path = "../../shared/error.rs"]
#[allow(dead_code)]
mod error;

#[path = "../../shared/parser.rs"]
#[allow(dead_code)]
mod parser;

#[cfg(test)]
mod tests {
#[test]
fn it_works() {
assert_eq!(2 + 2, 4);
#[proc_macro]
pub fn parse_lit(input: TokenStream) -> TokenStream {
build_uuid(input.clone()).unwrap_or_else(|e| {
let msg = e.to_string();
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::<usize>()
+ 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)
})
})
}

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<TokenStream, Error> {
let string = match syn::parse::<syn::Lit>(input) {
Ok(syn::Lit::Str(literal)) => literal.value(),
_ => return Err(Error::NonStringLiteral),
};

let bytes = parser::parse_str(&string).map_err(Error::UuidParse)?;

let tokens = bytes
.iter()
.map(|byte| quote! { #byte, })
.collect::<TokenStream2>();

Ok(quote! {[#tokens]}.into())
}
2 changes: 1 addition & 1 deletion shared/parser.rs
Expand Up @@ -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
Expand Down
6 changes: 4 additions & 2 deletions src/lib.rs
Expand Up @@ -207,8 +207,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;

Expand Down
53 changes: 53 additions & 0 deletions 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))
}};
}