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) +} | 
