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.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 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".