From 2d2c91be102bce013e3c414f4572a7ee5314aab4 Mon Sep 17 00:00:00 2001 From: Atliac Date: Sun, 22 Mar 2026 04:29:04 +0800 Subject: [PATCH 1/3] docs(procedural_macros): clarify explanation of macro development workflow Improve the description of how syn, quote, and proc-macro2 enable testing and debugging. The revised text better explains the decoupling of macro logic from the restricted proc-macro environment and emphasizes the use of standard Rust tooling. --- src/procedural_macros.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/procedural_macros.md b/src/procedural_macros.md index 4d35749..ff391d2 100644 --- a/src/procedural_macros.md +++ b/src/procedural_macros.md @@ -11,7 +11,7 @@ These constraints can make testing and debugging procedural macros challenging, Fortunately, [David Tolnay](https://github.com/dtolnay), a prominent figure in the Rust community, has created several essential crates that simplify this workflow. In this chapter, we will focus on three of them: [`syn`](https://github.com/dtolnay/syn), [`quote`](https://github.com/dtolnay/quote), and [`proc-macro2`](https://github.com/dtolnay/proc-macro2). These are the standard tools used in nearly all professional Rust projects for building procedural macros. -By using these crates, we can write our macro logic in a standard Rust crate where it can be easily tested and debugged. Once the logic is verified, we can then integrate it into a formal `proc-macro` crate. +By using these crates, we can write our macro logic as standard, testable Rust code. This decouples the core logic from the restricted environment of a `proc-macro` crate, making it much easier to verify and debug using standard tools. Instead of jumping straight into the complexities of procedural macro crates, we will begin our journey by learning how to write "code that generates code" within a standard Rust environment. From 6cf2e9ed4b979a5f8322682ecb1a28365b66bc63 Mon Sep 17 00:00:00 2001 From: Atliac Date: Wed, 13 May 2026 03:59:11 +0800 Subject: [PATCH 2/3] docs(procedural_macros): improve syntax tree tutorial examples Print parsed tokens to stdout and use custom `div` keyword in the custom parsing section. This makes the examples more interactive and demonstrates best practices for defining keywords in `syn` macros. --- src/procedural_macros/syntax_tree.md | 48 ++++++++++++++++++---------- 1 file changed, 31 insertions(+), 17 deletions(-) diff --git a/src/procedural_macros/syntax_tree.md b/src/procedural_macros/syntax_tree.md index b3b7b13..36782a3 100644 --- a/src/procedural_macros/syntax_tree.md +++ b/src/procedural_macros/syntax_tree.md @@ -43,21 +43,22 @@ use syn::*; fn main() { // Parse the `pub` keyword let input = quote::quote! {pub}; - let _token: Token![pub] = parse2(input).unwrap(); + let t: Token![pub] = parse2(input).unwrap(); + println!("{t:?}"); // Or use parse_quote! - let _token: Token![pub] = parse_quote! {pub}; - + let t: Token![pub] = parse_quote! {pub}; + println!("{t:?}"); // Parse the `struct` keyword - let _token: Token![struct] = parse_quote! {struct}; - + let t: Token![struct] = parse_quote! {struct}; + println!("{t:?}"); // Parse `+=` - let _token: Token![+=] = parse_quote! {+=}; - + let t: Token![+=] = parse_quote! {+=}; + println!("{t:?}"); // Parse `::` - let _token: Token![::] = parse_quote! {::}; - + let t: Token![::] = parse_quote! {::}; + println!("{t:?}"); // Error: `pub fn main() {}` is not a single token - // let _token: Token![pub] = parse_quote! {pub fn main() {}}; + // let t: Token![pub] = parse_quote! {pub fn main() {}}; } ``` @@ -72,7 +73,8 @@ mod kw{ } fn main() { - let _token: kw::div = parse_quote! {div}; + let t: kw::div = parse_quote! {div}; + println!("{t:?}"); } ``` @@ -82,11 +84,15 @@ fn main() { use syn::*; fn main() { - let _node: ItemFn = parse_quote! {fn main() {println!("Hello, world!")}}; - let _node: ItemStruct = parse_quote! {struct MyStruct {field: i32}}; + let node: ItemFn = parse_quote! {fn main() {println!("Hello, world!")}}; + println!("{node:#?}"); + let node: ItemStruct = parse_quote! {struct MyStruct {field: i32}}; + println!("{node:#?}"); // `syn::DeriveInput` is a syntax tree node that represents any valid input to a derive macro. - let _node: DeriveInput = parse_quote! {#[derive(Debug)] struct MyStruct {field: i32}}; + let node: DeriveInput = parse_quote! {#[derive(Debug)] struct MyStruct {field: i32}}; + println!("{node:#?}"); } + ``` ## Parsing a Custom Syntax Tree Node @@ -104,9 +110,14 @@ use syn::{ parse::{ParseStream, Parser}, *, }; +// We define custom keywords in a `kw` or `keywords` module by convention. +mod kw { + syn::custom_keyword!(div); +} fn main() { let input = quote! { + // Tip: try modifying it to an invalid div element and see the result.
"Hello World"
}; // parse::Parser::parse2(|input: ParseStream| -> Result<()> { todo!() }, input).unwrap(); @@ -118,23 +129,26 @@ fn main() { // `<` input.parse::()?; // `div` - input.parse::()?; + input.parse::()?; // `>` input.parse::]>()?; // `"Hello World"` - input.parse::()?; + let str = input.parse::()?; // `<` input.parse::()?; // `/` input.parse::()?; // `div` - input.parse::()?; + input.parse::()?; // `>` input.parse::]>()?; + println!("{str:?}"); + println!("Done!"); Ok(()) }; parser.parse2(input).unwrap(); } + ``` ### Defining a custom syntax tree node type by implementing the `syn::parse::Parse` trait From 16e9d69987181a583538ae401e2796020bb2acf9 Mon Sep 17 00:00:00 2001 From: Atliac Date: Wed, 13 May 2026 05:44:49 +0800 Subject: [PATCH 3/3] docs(procedural_macros): add Rust syntax tree guide Introduce a new section explaining how to work with `syn`'s syntax tree for concrete Rust code, removing the need for custom nodes. The guide uses a practical `Ok` type extraction example and emphasizes following the "happy path" via debug output. --- src/SUMMARY.md | 1 + .../syntax_tree/rust_syntax_tree.md | 66 +++++++++++++++++++ 2 files changed, 67 insertions(+) create mode 100644 src/procedural_macros/syntax_tree/rust_syntax_tree.md diff --git a/src/SUMMARY.md b/src/SUMMARY.md index c981a1f..66afaef 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -12,6 +12,7 @@ - [TokenStream](./procedural_macros/token_stream.md) - [proc-macro2, syn, quote](./procedural_macros/proc_macro2_syn_quote.md) - [Syntax Tree](./procedural_macros/syntax_tree.md) + - [Rust Syntax Tree](./procedural_macros/syntax_tree/rust_syntax_tree.md) - [Span](./procedural_macros/span.md) - [quote](./procedural_macros/quote.md) - [Procedural Macro Crates](./procedural_macros/proc_macro_crates.md) diff --git a/src/procedural_macros/syntax_tree/rust_syntax_tree.md b/src/procedural_macros/syntax_tree/rust_syntax_tree.md new file mode 100644 index 0000000..bf21aa0 --- /dev/null +++ b/src/procedural_macros/syntax_tree/rust_syntax_tree.md @@ -0,0 +1,66 @@ +# Rust Syntax Tree + +We've learned how to implement our own syntax tree nodes for virtually any DSL. But if we only want to work with valid Rust code using `syn`, we don't need to implement any custom nodes. The good news is that you don't even need to learn anything beyond what this tutorial has already covered about `syn` to handle concrete Rust-code-related tasks. + +Here is a simple example to get you started. + +# Ok Type Extraction + +Let's implement a function that parses a Rust function and extracts the inner success type (Ok type) from its Result return type and prints it to the console. + +> [!TIP] +> +> You don't need to read the following implementation. Just run it and observe the output. + +```rust,editable,compile_fail +use proc_macro2::TokenStream; +use syn::ItemFn; + +fn main() { + ok_type(syn::parse_quote! {fn foo() -> i32 {}}); + ok_type(syn::parse_quote! {fn foo() -> Result {}}); + ok_type(syn::parse_quote! {fn add(a:u32,b:u32) -> Result {}}); +} + +fn ok_type(item_fn: TokenStream) { + println!(r#"fn: "{}""#, item_fn.to_string()); + let item_fn: ItemFn = syn::parse_quote! {#item_fn}; + let syn::ReturnType::Type(_, return_type) = item_fn.sig.output else { + println!("Ok Type Unknown."); + return; + }; + let syn::Type::Path(return_type) = return_type.as_ref() else { + println!("Ok Type Unknown."); + return; + }; + let syn::PathArguments::AngleBracketed(generic_args) = + &return_type.path.segments.first().unwrap().arguments + else { + println!("Ok Type Unknown."); + return; + }; + let syn::GenericArgument::Type(syn::Type::Path(ok_type)) = generic_args.args.first().unwrap() + else { + println!("Ok Type Unknown."); + return; + }; + println!("Ok type is: {}", ok_type.path.get_ident().unwrap()); +} +``` + +The above code uses a few `syn` nodes. How do you know in advance which nodes you need to implement your own `ok_type` function? The answer is: no, you don't need to know anything. + +You just need to start at the very top node — `ItemFn` for this example. Print its debug output, then figure out the "happy path" that leads to your task target. + +For example: + +```rust,editable,compile_fail +use syn::ItemFn; + +fn main() { + let item_fn: ItemFn = syn::parse_quote! {fn foo() -> Result {}}; + println!("{item_fn:#?}"); +} +``` + +Then follow the "happy path" all the way to your target — `i32` in this example. The golden rule is: focus only on the "happy-path".