aboutsummaryrefslogtreecommitdiff
path: root/syn/examples
diff options
context:
space:
mode:
Diffstat (limited to 'syn/examples')
-rw-r--r--syn/examples/README.md19
-rw-r--r--syn/examples/dump-syntax/Cargo.toml17
-rw-r--r--syn/examples/dump-syntax/README.md28
-rw-r--r--syn/examples/dump-syntax/src/main.rs149
-rw-r--r--syn/examples/heapsize/Cargo.toml2
-rw-r--r--syn/examples/heapsize/README.md72
-rw-r--r--syn/examples/heapsize/example/Cargo.toml9
-rw-r--r--syn/examples/heapsize/example/src/main.rs28
-rw-r--r--syn/examples/heapsize/heapsize/Cargo.toml9
-rw-r--r--syn/examples/heapsize/heapsize/src/lib.rs64
-rw-r--r--syn/examples/heapsize/heapsize_derive/Cargo.toml14
-rw-r--r--syn/examples/heapsize/heapsize_derive/src/lib.rs96
-rw-r--r--syn/examples/lazy-static/Cargo.toml2
-rw-r--r--syn/examples/lazy-static/README.md42
-rw-r--r--syn/examples/lazy-static/example/Cargo.toml10
-rw-r--r--syn/examples/lazy-static/example/src/main.rs20
-rw-r--r--syn/examples/lazy-static/lazy-static/Cargo.toml14
-rw-r--r--syn/examples/lazy-static/lazy-static/src/lib.rs143
-rw-r--r--syn/examples/trace-var/Cargo.toml2
-rw-r--r--syn/examples/trace-var/README.md61
-rw-r--r--syn/examples/trace-var/example/Cargo.toml9
-rw-r--r--syn/examples/trace-var/example/src/main.rs15
-rw-r--r--syn/examples/trace-var/trace-var/Cargo.toml14
-rw-r--r--syn/examples/trace-var/trace-var/src/lib.rs180
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 &mdash; everything is as shown in this directory &mdash;
+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))
+}