diff options
Diffstat (limited to 'syn/examples')
24 files changed, 1019 insertions, 0 deletions
| diff --git a/syn/examples/README.md b/syn/examples/README.md new file mode 100644 index 0000000..fdd69d6 --- /dev/null +++ b/syn/examples/README.md @@ -0,0 +1,19 @@ +### [`dump-syntax`](dump-syntax) + +Little utility to parse a Rust source file into a `syn::File` and print out a +debug representation of the syntax tree. + +### [`heapsize`](heapsize) + +An example implementation of a derive macro that generates trait impls. + +### [`lazy-static`](lazy-static) + +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. + +### [`trace-var`](trace-var) + +An attribute procedural macro that uses a syntax tree traversal to transform +certain syntax tree nodes in a function body. diff --git a/syn/examples/dump-syntax/Cargo.toml b/syn/examples/dump-syntax/Cargo.toml new file mode 100644 index 0000000..0bc9f62 --- /dev/null +++ b/syn/examples/dump-syntax/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "dump-syntax" +version = "0.0.0" +authors = ["David Tolnay <dtolnay@gmail.com>"] +edition = "2018" +publish = false + +[dependencies] +colored = "1.7" +proc-macro2 = { version = "1.0", features = ["span-locations"] } + +[dependencies.syn] +path = "../.." +default-features = false +features = ["parsing", "full", "extra-traits"] + +[workspace] diff --git a/syn/examples/dump-syntax/README.md b/syn/examples/dump-syntax/README.md new file mode 100644 index 0000000..37c84d8 --- /dev/null +++ b/syn/examples/dump-syntax/README.md @@ -0,0 +1,28 @@ +Parse a Rust source file into a `syn::File` and print out a debug representation +of the syntax tree. + +Use the following command from this directory to test this program by running it +on its own source code: + +``` +cargo run -- src/main.rs +``` + +The output will begin with: + +``` +File { +    shebang: None, +    attrs: [ +        Attribute { +            pound_token: Pound, +            style: Inner( +                Bang +            ), +            bracket_token: Bracket, +            path: Path { +                leading_colon: None, +                segments: [ +    ... +} +``` diff --git a/syn/examples/dump-syntax/src/main.rs b/syn/examples/dump-syntax/src/main.rs new file mode 100644 index 0000000..240b7a2 --- /dev/null +++ b/syn/examples/dump-syntax/src/main.rs @@ -0,0 +1,149 @@ +//! Parse a Rust source file into a `syn::File` and print out a debug +//! representation of the syntax tree. +//! +//! Use the following command from this directory to test this program by +//! running it on its own source code: +//! +//!     cargo run -- src/main.rs +//! +//! The output will begin with: +//! +//!     File { +//!         shebang: None, +//!         attrs: [ +//!             Attribute { +//!                 pound_token: Pound, +//!                 style: Inner( +//!         ... +//!     } + +use std::borrow::Cow; +use std::env; +use std::ffi::OsStr; +use std::fmt::{self, Display}; +use std::fs; +use std::io::{self, Write}; +use std::path::{Path, PathBuf}; +use std::process; + +use colored::Colorize; + +enum Error { +    IncorrectUsage, +    ReadFile(io::Error), +    ParseFile { +        error: syn::Error, +        filepath: PathBuf, +        source_code: String, +    }, +} + +impl Display for Error { +    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { +        use self::Error::*; + +        match self { +            IncorrectUsage => write!(f, "Usage: dump-syntax path/to/filename.rs"), +            ReadFile(error) => write!(f, "Unable to read file: {}", error), +            ParseFile { +                error, +                filepath, +                source_code, +            } => render_location(f, error, filepath, source_code), +        } +    } +} + +fn main() { +    if let Err(error) = try_main() { +        let _ = writeln!(io::stderr(), "{}", error); +        process::exit(1); +    } +} + +fn try_main() -> Result<(), Error> { +    let mut args = env::args_os(); +    let _ = args.next(); // executable name + +    let filepath = match (args.next(), args.next()) { +        (Some(arg), None) => PathBuf::from(arg), +        _ => return Err(Error::IncorrectUsage), +    }; + +    let code = fs::read_to_string(&filepath).map_err(Error::ReadFile)?; +    let syntax = syn::parse_file(&code).map_err({ +        |error| Error::ParseFile { +            error, +            filepath, +            source_code: code, +        } +    })?; +    println!("{:#?}", syntax); + +    Ok(()) +} + +// Render a rustc-style error message, including colors. +// +//     error: Syn unable to parse file +//       --> main.rs:40:17 +//        | +//     40 |     fn fmt(&self formatter: &mut fmt::Formatter) -> fmt::Result { +//        |                  ^^^^^^^^^ expected `,` +// +fn render_location( +    formatter: &mut fmt::Formatter, +    err: &syn::Error, +    filepath: &Path, +    code: &str, +) -> fmt::Result { +    let start = err.span().start(); +    let mut end = err.span().end(); + +    if start.line == end.line && start.column == end.column { +        return render_fallback(formatter, err); +    } + +    let code_line = match code.lines().nth(start.line - 1) { +        Some(line) => line, +        None => return render_fallback(formatter, err), +    }; + +    if end.line > start.line { +        end.line = start.line; +        end.column = code_line.len(); +    } + +    let filename = filepath +        .file_name() +        .map(OsStr::to_string_lossy) +        .unwrap_or(Cow::Borrowed("main.rs")); + +    write!( +        formatter, +        "\n\ +         {error}{header}\n\ +         {indent}{arrow} {filename}:{linenum}:{colnum}\n\ +         {indent} {pipe}\n\ +         {label} {pipe} {code}\n\ +         {indent} {pipe} {offset}{underline} {message}\n\ +         ", +        error = "error".red().bold(), +        header = ": Syn unable to parse file".bold(), +        indent = " ".repeat(start.line.to_string().len()), +        arrow = "-->".blue().bold(), +        filename = filename, +        linenum = start.line, +        colnum = start.column, +        pipe = "|".blue().bold(), +        label = start.line.to_string().blue().bold(), +        code = code_line.trim_end(), +        offset = " ".repeat(start.column), +        underline = "^".repeat(end.column - start.column).red().bold(), +        message = err.to_string().red(), +    ) +} + +fn render_fallback(formatter: &mut fmt::Formatter, err: &syn::Error) -> fmt::Result { +    write!(formatter, "Unable to parse file: {}", err) +} diff --git a/syn/examples/heapsize/Cargo.toml b/syn/examples/heapsize/Cargo.toml new file mode 100644 index 0000000..9b19214 --- /dev/null +++ b/syn/examples/heapsize/Cargo.toml @@ -0,0 +1,2 @@ +[workspace] +members = ["example", "heapsize", "heapsize_derive"] diff --git a/syn/examples/heapsize/README.md b/syn/examples/heapsize/README.md new file mode 100644 index 0000000..e789559 --- /dev/null +++ b/syn/examples/heapsize/README.md @@ -0,0 +1,72 @@ +A derive macro that generates trait impls. + +- [`heapsize/src/lib.rs`](heapsize/src/lib.rs) +- [`heapsize_derive/src/lib.rs`](heapsize_derive/src/lib.rs) +- [`example/src/main.rs`](example/src/main.rs) + +We are deriving the `HeapSize` trait which computes an estimate of the amount of +heap memory owned by a value. + +```rust +pub trait HeapSize { +    /// Total number of bytes of heap memory owned by `self`. +    fn heap_size_of_children(&self) -> usize; +} +``` + +The derive macro allows users to write `#[derive(HeapSize)]` on data structures +in their program. + +```rust +#[derive(HeapSize)] +struct Demo<'a, T: ?Sized> { +    a: Box<T>, +    b: u8, +    c: &'a str, +    d: String, +} +``` + +The trait impl generated by the derive macro here would look like: + +```rust +impl<'a, T: ?Sized + heapsize::HeapSize> heapsize::HeapSize for Demo<'a, T> { +    fn heap_size_of_children(&self) -> usize { +        0 + heapsize::HeapSize::heap_size_of_children(&self.a) +            + heapsize::HeapSize::heap_size_of_children(&self.b) +            + heapsize::HeapSize::heap_size_of_children(&self.c) +            + heapsize::HeapSize::heap_size_of_children(&self.d) +    } +} +``` + +The implementation of `heapsize_derive` demonstrates some attention to "spans" +of error messages. For each subexpression in the generated code we apply the +span of the input fragment under which we would want to trigger a compiler error +if the subexpression fails to compile. In this example, each recursive call to +`heap_size_of_children` is associated with the span of the corresponding struct +field. Thus we get errors in the right place if any of the field types do not +implement the `HeapSize` trait. + +``` +error[E0277]: the trait bound `std::thread::Thread: HeapSize` is not satisfied + --> src/main.rs:7:5 +  | +7 |     bad: std::thread::Thread, +  |     ^^^ the trait `HeapSize` is not implemented for `std::thread::Thread` +``` + +Some unstable APIs in the `proc-macro2` crate let us improve this further by +joining together the span of the field name and the field type. There is no +difference in our code — everything is as shown in this directory — +but building the example crate with `cargo build` shows errors like the one +above and building with `RUSTFLAGS='--cfg procmacro2_semver_exempt' cargo build` +is able to show errors like the following. + +``` +error[E0277]: the trait bound `std::thread::Thread: HeapSize` is not satisfied + --> src/main.rs:7:5 +  | +7 |     bad: std::thread::Thread, +  |     ^^^^^^^^^^^^^^^^^^^^^^^^ the trait `HeapSize` is not implemented for `std::thread::Thread` +``` diff --git a/syn/examples/heapsize/example/Cargo.toml b/syn/examples/heapsize/example/Cargo.toml new file mode 100644 index 0000000..85c7699 --- /dev/null +++ b/syn/examples/heapsize/example/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "heapsize_example" +version = "0.0.0" +authors = ["David Tolnay <dtolnay@gmail.com>"] +edition = "2018" +publish = false + +[dependencies] +heapsize = { path = "../heapsize" } diff --git a/syn/examples/heapsize/example/src/main.rs b/syn/examples/heapsize/example/src/main.rs new file mode 100644 index 0000000..9332b11 --- /dev/null +++ b/syn/examples/heapsize/example/src/main.rs @@ -0,0 +1,28 @@ +use heapsize::HeapSize; + +#[derive(HeapSize)] +struct Demo<'a, T: ?Sized> { +    a: Box<T>, +    b: u8, +    c: &'a str, +    d: String, +} + +fn main() { +    let demo = Demo { +        a: b"bytestring".to_vec().into_boxed_slice(), +        b: 255, +        c: "&'static str", +        d: "String".to_owned(), +    }; + +    // 10 + 0 + 0 + 6 = 16 +    println!( +        "heap size = {} + {} + {} + {} = {}", +        demo.a.heap_size_of_children(), +        demo.b.heap_size_of_children(), +        demo.c.heap_size_of_children(), +        demo.d.heap_size_of_children(), +        demo.heap_size_of_children() +    ); +} diff --git a/syn/examples/heapsize/heapsize/Cargo.toml b/syn/examples/heapsize/heapsize/Cargo.toml new file mode 100644 index 0000000..27bb954 --- /dev/null +++ b/syn/examples/heapsize/heapsize/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "heapsize" +version = "0.0.0" +authors = ["David Tolnay <dtolnay@gmail.com>"] +edition = "2018" +publish = false + +[dependencies] +heapsize_derive = { path = "../heapsize_derive" } diff --git a/syn/examples/heapsize/heapsize/src/lib.rs b/syn/examples/heapsize/heapsize/src/lib.rs new file mode 100644 index 0000000..30bb6d6 --- /dev/null +++ b/syn/examples/heapsize/heapsize/src/lib.rs @@ -0,0 +1,64 @@ +use std::mem; + +pub use heapsize_derive::*; + +pub trait HeapSize { +    /// Total number of bytes of heap memory owned by `self`. +    /// +    /// Does not include the size of `self` itself, which may or may not be on +    /// the heap. Includes only children of `self`, meaning things pointed to by +    /// `self`. +    fn heap_size_of_children(&self) -> usize; +} + +// +// In a real version of this library there would be lots more impls here, but +// here are some interesting ones. +// + +impl HeapSize for u8 { +    /// A `u8` does not own any heap memory. +    fn heap_size_of_children(&self) -> usize { +        0 +    } +} + +impl HeapSize for String { +    /// A `String` owns enough heap memory to hold its reserved capacity. +    fn heap_size_of_children(&self) -> usize { +        self.capacity() +    } +} + +impl<T> HeapSize for Box<T> +where +    T: ?Sized + HeapSize, +{ +    /// A `Box` owns however much heap memory was allocated to hold the value of +    /// type `T` that we placed on the heap, plus transitively however much `T` +    /// itself owns. +    fn heap_size_of_children(&self) -> usize { +        mem::size_of_val(&**self) + (**self).heap_size_of_children() +    } +} + +impl<T> HeapSize for [T] +where +    T: HeapSize, +{ +    /// Sum of heap memory owned by each element of a dynamically sized slice of +    /// `T`. +    fn heap_size_of_children(&self) -> usize { +        self.iter().map(HeapSize::heap_size_of_children).sum() +    } +} + +impl<'a, T> HeapSize for &'a T +where +    T: ?Sized, +{ +    /// A shared reference does not own heap memory. +    fn heap_size_of_children(&self) -> usize { +        0 +    } +} diff --git a/syn/examples/heapsize/heapsize_derive/Cargo.toml b/syn/examples/heapsize/heapsize_derive/Cargo.toml new file mode 100644 index 0000000..f4357b9 --- /dev/null +++ b/syn/examples/heapsize/heapsize_derive/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "heapsize_derive" +version = "0.0.0" +authors = ["David Tolnay <dtolnay@gmail.com>"] +edition = "2018" +publish = false + +[lib] +proc-macro = true + +[dependencies] +proc-macro2 = "1.0" +quote = "1.0" +syn = { path = "../../.." } diff --git a/syn/examples/heapsize/heapsize_derive/src/lib.rs b/syn/examples/heapsize/heapsize_derive/src/lib.rs new file mode 100644 index 0000000..9176b29 --- /dev/null +++ b/syn/examples/heapsize/heapsize_derive/src/lib.rs @@ -0,0 +1,96 @@ +extern crate proc_macro; + +use proc_macro2::TokenStream; +use quote::{quote, quote_spanned}; +use syn::spanned::Spanned; +use syn::{parse_macro_input, parse_quote, Data, DeriveInput, Fields, GenericParam, Generics, Index}; + +#[proc_macro_derive(HeapSize)] +pub fn derive_heap_size(input: proc_macro::TokenStream) -> proc_macro::TokenStream { +    // Parse the input tokens into a syntax tree. +    let input = parse_macro_input!(input as DeriveInput); + +    // Used in the quasi-quotation below as `#name`. +    let name = input.ident; + +    // Add a bound `T: HeapSize` to every type parameter T. +    let generics = add_trait_bounds(input.generics); +    let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); + +    // Generate an expression to sum up the heap size of each field. +    let sum = heap_size_sum(&input.data); + +    let expanded = quote! { +        // The generated impl. +        impl #impl_generics heapsize::HeapSize for #name #ty_generics #where_clause { +            fn heap_size_of_children(&self) -> usize { +                #sum +            } +        } +    }; + +    // Hand the output tokens back to the compiler. +    proc_macro::TokenStream::from(expanded) +} + +// Add a bound `T: HeapSize` to every type parameter T. +fn add_trait_bounds(mut generics: Generics) -> Generics { +    for param in &mut generics.params { +        if let GenericParam::Type(ref mut type_param) = *param { +            type_param.bounds.push(parse_quote!(heapsize::HeapSize)); +        } +    } +    generics +} + +// Generate an expression to sum up the heap size of each field. +fn heap_size_sum(data: &Data) -> TokenStream { +    match *data { +        Data::Struct(ref data) => { +            match data.fields { +                Fields::Named(ref fields) => { +                    // Expands to an expression like +                    // +                    //     0 + self.x.heap_size() + self.y.heap_size() + self.z.heap_size() +                    // +                    // but using fully qualified function call syntax. +                    // +                    // We take some care to use the span of each `syn::Field` as +                    // the span of the corresponding `heap_size_of_children` +                    // call. This way if one of the field types does not +                    // implement `HeapSize` then the compiler's error message +                    // underlines which field it is. An example is shown in the +                    // readme of the parent directory. +                    let recurse = fields.named.iter().map(|f| { +                        let name = &f.ident; +                        quote_spanned! {f.span()=> +                            heapsize::HeapSize::heap_size_of_children(&self.#name) +                        } +                    }); +                    quote! { +                        0 #(+ #recurse)* +                    } +                } +                Fields::Unnamed(ref fields) => { +                    // Expands to an expression like +                    // +                    //     0 + self.0.heap_size() + self.1.heap_size() + self.2.heap_size() +                    let recurse = fields.unnamed.iter().enumerate().map(|(i, f)| { +                        let index = Index::from(i); +                        quote_spanned! {f.span()=> +                            heapsize::HeapSize::heap_size_of_children(&self.#index) +                        } +                    }); +                    quote! { +                        0 #(+ #recurse)* +                    } +                } +                Fields::Unit => { +                    // Unit structs cannot own more than 0 bytes of heap memory. +                    quote!(0) +                } +            } +        } +        Data::Enum(_) | Data::Union(_) => unimplemented!(), +    } +} 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) +} diff --git a/syn/examples/trace-var/Cargo.toml b/syn/examples/trace-var/Cargo.toml new file mode 100644 index 0000000..b54454d --- /dev/null +++ b/syn/examples/trace-var/Cargo.toml @@ -0,0 +1,2 @@ +[workspace] +members = ["example", "trace-var"] diff --git a/syn/examples/trace-var/README.md b/syn/examples/trace-var/README.md new file mode 100644 index 0000000..b93fae2 --- /dev/null +++ b/syn/examples/trace-var/README.md @@ -0,0 +1,61 @@ +An example of an attribute procedural macro. The `#[trace_var(...)]` attribute +prints the value of the given variables each time they are reassigned. + +- [`trace-var/src/lib.rs`](trace-var/src/lib.rs) +- [`example/src/main.rs`](example/src/main.rs) + +Consider the following factorial implementation. + +```rust +#[trace_var(p, n)] +fn factorial(mut n: u64) -> u64 { +    let mut p = 1; +    while n > 1 { +        p *= n; +        n -= 1; +    } +    p +} +``` + +Invoking this with `factorial(8)` prints all the values of `p` and `n` during +the execution of the function. + +``` +p = 1 +p = 8 +n = 7 +p = 56 +n = 6 +p = 336 +n = 5 +p = 1680 +n = 4 +p = 6720 +n = 3 +p = 20160 +n = 2 +p = 40320 +n = 1 +``` + +The procedural macro uses a syntax tree [`Fold`] to rewrite every `let` +statement and assignment expression in the following way: + +[`Fold`]: https://docs.rs/syn/1.0/syn/fold/trait.Fold.html + +```rust +// Before +let VAR = INIT; + +// After +let VAR = { let VAR = INIT; println!("VAR = {:?}", VAR); VAR }; +``` + +```rust +// Before +VAR = INIT + +// After +{ VAR = INIT; println!("VAR = {:?}", VAR); } +``` diff --git a/syn/examples/trace-var/example/Cargo.toml b/syn/examples/trace-var/example/Cargo.toml new file mode 100644 index 0000000..d2ad650 --- /dev/null +++ b/syn/examples/trace-var/example/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "example" +version = "0.0.0" +authors = ["David Tolnay <dtolnay@gmail.com>"] +edition = "2018" +publish = false + +[dependencies] +trace-var = { path = "../trace-var" } diff --git a/syn/examples/trace-var/example/src/main.rs b/syn/examples/trace-var/example/src/main.rs new file mode 100644 index 0000000..da2c10b --- /dev/null +++ b/syn/examples/trace-var/example/src/main.rs @@ -0,0 +1,15 @@ +use trace_var::trace_var; + +fn main() { +    println!("{}", factorial(8)); +} + +#[trace_var(p, n)] +fn factorial(mut n: u64) -> u64 { +    let mut p = 1; +    while n > 1 { +        p *= n; +        n -= 1; +    } +    p +} diff --git a/syn/examples/trace-var/trace-var/Cargo.toml b/syn/examples/trace-var/trace-var/Cargo.toml new file mode 100644 index 0000000..72f56e9 --- /dev/null +++ b/syn/examples/trace-var/trace-var/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "trace-var" +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", "fold"] } diff --git a/syn/examples/trace-var/trace-var/src/lib.rs b/syn/examples/trace-var/trace-var/src/lib.rs new file mode 100644 index 0000000..0ecfb47 --- /dev/null +++ b/syn/examples/trace-var/trace-var/src/lib.rs @@ -0,0 +1,180 @@ +extern crate proc_macro; +use self::proc_macro::TokenStream; + +use quote::{quote, ToTokens}; +use std::collections::HashSet as Set; +use syn::fold::{self, Fold}; +use syn::parse::{Parse, ParseStream, Result}; +use syn::punctuated::Punctuated; +use syn::{parse_macro_input, parse_quote, Expr, Ident, ItemFn, Local, Pat, Stmt, Token}; + +/// Parses a list of variable names separated by commas. +/// +///     a, b, c +/// +/// This is how the compiler passes in arguments to our attribute -- it is +/// everything inside the delimiters after the attribute name. +/// +///     #[trace_var(a, b, c)] +///                 ^^^^^^^ +struct Args { +    vars: Set<Ident>, +} + +impl Parse for Args { +    fn parse(input: ParseStream) -> Result<Self> { +        let vars = Punctuated::<Ident, Token![,]>::parse_terminated(input)?; +        Ok(Args { +            vars: vars.into_iter().collect(), +        }) +    } +} + +impl Args { +    /// Determines whether the given `Expr` is a path referring to one of the +    /// variables we intend to print. Expressions are used as the left-hand side +    /// of the assignment operator. +    fn should_print_expr(&self, e: &Expr) -> bool { +        match *e { +            Expr::Path(ref e) => { +                if e.path.leading_colon.is_some() { +                    false +                } else if e.path.segments.len() != 1 { +                    false +                } else { +                    let first = e.path.segments.first().unwrap(); +                    self.vars.contains(&first.ident) && first.arguments.is_empty() +                } +            } +            _ => false, +        } +    } + +    /// Determines whether the given `Pat` is an identifier equal to one of the +    /// variables we intend to print. Patterns are used as the left-hand side of +    /// a `let` binding. +    fn should_print_pat(&self, p: &Pat) -> bool { +        match p { +            Pat::Ident(ref p) => self.vars.contains(&p.ident), +            _ => false, +        } +    } + +    /// Produces an expression that assigns the right-hand side to the left-hand +    /// side and then prints the value. +    /// +    ///     // Before +    ///     VAR = INIT +    /// +    ///     // After +    ///     { VAR = INIT; println!("VAR = {:?}", VAR); } +    fn assign_and_print(&mut self, left: Expr, op: &dyn ToTokens, right: Expr) -> Expr { +        let right = fold::fold_expr(self, right); +        parse_quote!({ +            #left #op #right; +            println!(concat!(stringify!(#left), " = {:?}"), #left); +        }) +    } + +    /// Produces a let-binding that assigns the right-hand side to the left-hand +    /// side and then prints the value. +    /// +    ///     // Before +    ///     let VAR = INIT; +    /// +    ///     // After +    ///     let VAR = { let VAR = INIT; println!("VAR = {:?}", VAR); VAR }; +    fn let_and_print(&mut self, local: Local) -> Stmt { +        let Local { pat, init, .. } = local; +        let init = self.fold_expr(*init.unwrap().1); +        let ident = match pat { +            Pat::Ident(ref p) => &p.ident, +            _ => unreachable!(), +        }; +        parse_quote! { +            let #pat = { +                #[allow(unused_mut)] +                let #pat = #init; +                println!(concat!(stringify!(#ident), " = {:?}"), #ident); +                #ident +            }; +        } +    } +} + +/// The `Fold` trait is a way to traverse an owned syntax tree and replace some +/// of its nodes. +/// +/// Syn provides two other syntax tree traversal traits: `Visit` which walks a +/// shared borrow of a syntax tree, and `VisitMut` which walks an exclusive +/// borrow of a syntax tree and can mutate it in place. +/// +/// All three traits have a method corresponding to each type of node in Syn's +/// syntax tree. All of these methods have default no-op implementations that +/// simply recurse on any child nodes. We can override only those methods for +/// which we want non-default behavior. In this case the traversal needs to +/// transform `Expr` and `Stmt` nodes. +impl Fold for Args { +    fn fold_expr(&mut self, e: Expr) -> Expr { +        match e { +            Expr::Assign(e) => { +                if self.should_print_expr(&e.left) { +                    self.assign_and_print(*e.left, &e.eq_token, *e.right) +                } else { +                    Expr::Assign(fold::fold_expr_assign(self, e)) +                } +            } +            Expr::AssignOp(e) => { +                if self.should_print_expr(&e.left) { +                    self.assign_and_print(*e.left, &e.op, *e.right) +                } else { +                    Expr::AssignOp(fold::fold_expr_assign_op(self, e)) +                } +            } +            _ => fold::fold_expr(self, e), +        } +    } + +    fn fold_stmt(&mut self, s: Stmt) -> Stmt { +        match s { +            Stmt::Local(s) => { +                if s.init.is_some() && self.should_print_pat(&s.pat) { +                    self.let_and_print(s) +                } else { +                    Stmt::Local(fold::fold_local(self, s)) +                } +            } +            _ => fold::fold_stmt(self, s), +        } +    } +} + +/// Attribute to print the value of the given variables each time they are +/// reassigned. +/// +/// # Example +/// +/// ``` +/// #[trace_var(p, n)] +/// fn factorial(mut n: u64) -> u64 { +///     let mut p = 1; +///     while n > 1 { +///         p *= n; +///         n -= 1; +///     } +///     p +/// } +/// ``` +#[proc_macro_attribute] +pub fn trace_var(args: TokenStream, input: TokenStream) -> TokenStream { +    let input = parse_macro_input!(input as ItemFn); + +    // Parse the list of variables the user wanted to print. +    let mut args = parse_macro_input!(args as Args); + +    // Use a syntax tree traversal to transform the function body. +    let output = args.fold_item_fn(input); + +    // Hand the resulting function body back to the compiler. +    TokenStream::from(quote!(#output)) +} | 
