diff options
Diffstat (limited to 'syn/examples/lazy-static')
-rw-r--r-- | syn/examples/lazy-static/Cargo.toml | 2 | ||||
-rw-r--r-- | syn/examples/lazy-static/README.md | 42 | ||||
-rw-r--r-- | syn/examples/lazy-static/example/Cargo.toml | 10 | ||||
-rw-r--r-- | syn/examples/lazy-static/example/src/main.rs | 20 | ||||
-rw-r--r-- | syn/examples/lazy-static/lazy-static/Cargo.toml | 14 | ||||
-rw-r--r-- | syn/examples/lazy-static/lazy-static/src/lib.rs | 143 |
6 files changed, 231 insertions, 0 deletions
diff --git a/syn/examples/lazy-static/Cargo.toml b/syn/examples/lazy-static/Cargo.toml new file mode 100644 index 0000000..586e547 --- /dev/null +++ b/syn/examples/lazy-static/Cargo.toml @@ -0,0 +1,2 @@ +[workspace] +members = ["example", "lazy-static"] diff --git a/syn/examples/lazy-static/README.md b/syn/examples/lazy-static/README.md new file mode 100644 index 0000000..bc64585 --- /dev/null +++ b/syn/examples/lazy-static/README.md @@ -0,0 +1,42 @@ +An example of parsing a custom syntax within a `functionlike!(...)` procedural +macro. Demonstrates how to trigger custom warnings and error messages on +individual tokens of the input. + +- [`lazy-static/src/lib.rs`](lazy-static/src/lib.rs) +- [`example/src/main.rs`](example/src/main.rs) + +The library implements a `lazy_static!` macro similar to the one from the real +[`lazy_static`](https://docs.rs/lazy_static/1.0.0/lazy_static/) crate on +crates.io. + +```rust +lazy_static! { + static ref USERNAME: Regex = Regex::new("^[a-z0-9_-]{3,16}$").unwrap(); +} +``` + +Compile and run the example by doing `cargo run` in the directory of the +`example` crate. + +The implementation shows how to trigger custom warnings and error messages on +the macro input. For example if you try adding an uncreatively named `FOO` lazy +static, the macro will scold you with the following warning. + +``` +warning: come on, pick a more creative name + --> src/main.rs:10:16 + | +10 | static ref FOO: String = "lazy_static".to_owned(); + | ^^^ +``` + +And if you try to lazily initialize `() = ()`, the macro will outright refuse to +compile it for you. + +``` +error: I can't think of a legitimate use for lazily initializing the value `()` + --> src/main.rs:10:27 + | +10 | static ref UNIT: () = (); + | ^^ +``` diff --git a/syn/examples/lazy-static/example/Cargo.toml b/syn/examples/lazy-static/example/Cargo.toml new file mode 100644 index 0000000..716b08c --- /dev/null +++ b/syn/examples/lazy-static/example/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "example" +version = "0.0.0" +authors = ["David Tolnay <dtolnay@gmail.com>"] +edition = "2018" +publish = false + +[dependencies] +lazy_static = { path = "../lazy-static" } +regex = "0.2" diff --git a/syn/examples/lazy-static/example/src/main.rs b/syn/examples/lazy-static/example/src/main.rs new file mode 100644 index 0000000..c4f64af --- /dev/null +++ b/syn/examples/lazy-static/example/src/main.rs @@ -0,0 +1,20 @@ +use lazy_static::lazy_static; +use regex::Regex; + +lazy_static! { + static ref USERNAME: Regex = { + println!("Compiling username regex..."); + Regex::new("^[a-z0-9_-]{3,16}$").unwrap() + }; +} + +fn main() { + println!("Let's validate some usernames."); + validate("fergie"); + validate("will.i.am"); +} + +fn validate(name: &str) { + // The USERNAME regex is compiled lazily the first time its value is accessed. + println!("is_match({:?}): {}", name, USERNAME.is_match(name)); +} diff --git a/syn/examples/lazy-static/lazy-static/Cargo.toml b/syn/examples/lazy-static/lazy-static/Cargo.toml new file mode 100644 index 0000000..bf65787 --- /dev/null +++ b/syn/examples/lazy-static/lazy-static/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "lazy_static" +version = "0.0.0" +authors = ["David Tolnay <dtolnay@gmail.com>"] +edition = "2018" +publish = false + +[lib] +proc-macro = true + +[dependencies] +proc-macro2 = { version = "1.0", features = ["nightly"] } +quote = "1.0" +syn = { path = "../../../", features = ["full"] } diff --git a/syn/examples/lazy-static/lazy-static/src/lib.rs b/syn/examples/lazy-static/lazy-static/src/lib.rs new file mode 100644 index 0000000..254ca72 --- /dev/null +++ b/syn/examples/lazy-static/lazy-static/src/lib.rs @@ -0,0 +1,143 @@ +#![recursion_limit = "128"] +#![feature(proc_macro_diagnostic)] + +extern crate proc_macro; +use self::proc_macro::TokenStream; + +use quote::{quote, quote_spanned}; +use syn::parse::{Parse, ParseStream, Result}; +use syn::spanned::Spanned; +use syn::{parse_macro_input, Expr, Ident, Token, Type, Visibility}; + +/// Parses the following syntax, which aligns with the input of the real +/// `lazy_static` crate. +/// +/// lazy_static! { +/// $VISIBILITY static ref $NAME: $TYPE = $EXPR; +/// } +/// +/// For example: +/// +/// lazy_static! { +/// static ref USERNAME: Regex = Regex::new("^[a-z0-9_-]{3,16}$").unwrap(); +/// } +struct LazyStatic { + visibility: Visibility, + name: Ident, + ty: Type, + init: Expr, +} + +impl Parse for LazyStatic { + fn parse(input: ParseStream) -> Result<Self> { + let visibility: Visibility = input.parse()?; + input.parse::<Token![static]>()?; + input.parse::<Token![ref]>()?; + let name: Ident = input.parse()?; + input.parse::<Token![:]>()?; + let ty: Type = input.parse()?; + input.parse::<Token![=]>()?; + let init: Expr = input.parse()?; + input.parse::<Token![;]>()?; + Ok(LazyStatic { + visibility, + name, + ty, + init, + }) + } +} + +#[proc_macro] +pub fn lazy_static(input: TokenStream) -> TokenStream { + let LazyStatic { + visibility, + name, + ty, + init, + } = parse_macro_input!(input as LazyStatic); + + // The warning looks like this. + // + // warning: come on, pick a more creative name + // --> src/main.rs:10:16 + // | + // 10 | static ref FOO: String = "lazy_static".to_owned(); + // | ^^^ + if name == "FOO" { + name.span() + .unwrap() + .warning("come on, pick a more creative name") + .emit(); + } + + // The error looks like this. + // + // error: I can't think of a legitimate use for lazily initializing the value `()` + // --> src/main.rs:10:27 + // | + // 10 | static ref UNIT: () = (); + // | ^^ + if let Expr::Tuple(ref init) = init { + if init.elems.is_empty() { + init.span() + .unwrap() + .error("I can't think of a legitimate use for lazily initializing the value `()`") + .emit(); + return TokenStream::new(); + } + } + + // Assert that the static type implements Sync. If not, user sees an error + // message like the following. We span this assertion with the field type's + // line/column so that the error message appears in the correct place. + // + // error[E0277]: the trait bound `*const (): std::marker::Sync` is not satisfied + // --> src/main.rs:10:21 + // | + // 10 | static ref PTR: *const () = &(); + // | ^^^^^^^^^ `*const ()` cannot be shared between threads safely + let assert_sync = quote_spanned! {ty.span()=> + struct _AssertSync where #ty: std::marker::Sync; + }; + + // Check for Sized. Not vital to check here, but the error message is less + // confusing this way than if they get a Sized error in one of our + // implementation details where it assumes Sized. + // + // error[E0277]: the trait bound `str: std::marker::Sized` is not satisfied + // --> src/main.rs:10:19 + // | + // 10 | static ref A: str = ""; + // | ^^^ `str` does not have a constant size known at compile-time + let assert_sized = quote_spanned! {ty.span()=> + struct _AssertSized where #ty: std::marker::Sized; + }; + + let init_ptr = quote_spanned! {init.span()=> + Box::into_raw(Box::new(#init)) + }; + + let expanded = quote! { + #visibility struct #name; + + impl std::ops::Deref for #name { + type Target = #ty; + + fn deref(&self) -> &#ty { + #assert_sync + #assert_sized + + static ONCE: std::sync::Once = std::sync::Once::new(); + static mut VALUE: *mut #ty = 0 as *mut #ty; + + unsafe { + ONCE.call_once(|| VALUE = #init_ptr); + &*VALUE + } + } + } + }; + + TokenStream::from(expanded) +} |