Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

rust宏 #11

Open
wuyuedefeng opened this issue Jul 8, 2021 · 2 comments
Open

rust宏 #11

wuyuedefeng opened this issue Jul 8, 2021 · 2 comments

Comments

@wuyuedefeng
Copy link
Member

wuyuedefeng commented Jul 8, 2021

属性式过程宏

参考资料: https://blog.ideawand.com/2021/02/27/rust_procedural_macro/rust_proc_marco_workshop_guide-01/

初始化项目

cargo new my_demo --bin
cd my_demo
cargo new proc_macro_first --lib
  • 编辑my_demo/proc_macro_first/Cargo.toml, 添加:
[lib]
proc-macro = true
  • 编辑my_demo/proc_macro_first/src/lib.rs, 添加:
use proc_macro::TokenStream;

// 告诉编译器定义的是 属性式 的过程宏; 函数式过程宏使用:#[proc_macro], 派生式过程宏使用:#[proc_macro_derive]
#[proc_macro_attribute] 
// attr 为参数,  item为标记对象
pub fn my_test_proc_macro_attribute(attr: TokenStream, item: TokenStream) -> TokenStream {
    eprintln!("attr -> {:#?}", attr);
    eprintln!("item -> {:#?}", item);
    item
}
  • 编辑my_demo/Cargo.toml, 添加:
[dependencies]
proc_macro_first = { path = "./proc_macro_first"}
  • 编辑my_demo/src/main.rs, 添加:
use proc_macro_first::my_test_proc_macro_attribute;

#[my_test_proc_macro_attribute(blog(::hello::world))]
pub fn foo(i: i32) {
    println!("Hello foo! {}", i);
}
fn main() {
    println!("Hello, world!");
}
执行`cargo check`
 
attr -> TokenStream [
    Ident {
        ident: "blog",
        span: #0 bytes(84..88),
    },
    Group {
        delimiter: Parenthesis,
        stream: TokenStream [
            Punct {
                ch: ':',
                spacing: Joint,
                span: #0 bytes(89..91),
            },
            Punct {
                ch: ':',
                spacing: Alone,
                span: #0 bytes(89..91),
            },
            Ident {
                ident: "hello",
                span: #0 bytes(91..96),
            },
            Punct {
                ch: ':',
                spacing: Joint,
                span: #0 bytes(96..98),
            },
            Punct {
                ch: ':',
                spacing: Alone,
                span: #0 bytes(96..98),
            },
            Ident {
                ident: "world",
                span: #0 bytes(98..103),
            },
        ],
        span: #0 bytes(88..104),
    },
]
item -> TokenStream [
    Ident {
        ident: "pub",
        span: #0 bytes(107..110),
    },
    Ident {
        ident: "fn",
        span: #0 bytes(111..113),
    },
    Ident {
        ident: "foo",
        span: #0 bytes(114..117),
    },
    Group {
        delimiter: Parenthesis,
        stream: TokenStream [
            Ident {
                ident: "i",
                span: #0 bytes(118..119),
            },
            Punct {
                ch: ':',
                spacing: Alone,
                span: #0 bytes(119..120),
            },
            Ident {
                ident: "i32",
                span: #0 bytes(121..124),
            },
        ],
        span: #0 bytes(117..125),
    },
    Group {
        delimiter: Brace,
        stream: TokenStream [
            Ident {
                ident: "println",
                span: #0 bytes(132..139),
            },
            Punct {
                ch: '!',
                spacing: Alone,
                span: #0 bytes(139..140),
            },
            Group {
                delimiter: Parenthesis,
                stream: TokenStream [
                    Literal {
                        kind: Str,
                        symbol: "Hello foo! {}",
                        suffix: None,
                        span: #0 bytes(141..156),
                    },
                    Punct {
                        ch: ',',
                        spacing: Alone,
                        span: #0 bytes(156..157),
                    },
                    Ident {
                        ident: "i",
                        span: #0 bytes(158..159),
                    },
                ],
                span: #0 bytes(140..160),
            },
            Punct {
                ch: ';',
                spacing: Alone,
                span: #0 bytes(160..161),
            },
        ],
        span: #0 bytes(126..163),
    },
]

* 使用synTokenStream转换成更高级的TokenStream, quoto!可以将高级的TokenStream转换回函数支持的返回值类型的TokenStream进行返回

  • 编辑my_demo/proc_macro_first/Cargo.toml, 添加:
[dependencies]
quote = "1"
syn = { version = "*", features = ["full", "extra-traits"] }
  • 编辑my_demo/proc_macro_first/src/lib.rs, 添加:
use proc_macro::TokenStream;
use syn::{parse_macro_input, AttributeArgs, Item};
use quote::quote;

// 告诉编译器定义的是 属性式 的过程宏; 函数式过程宏使用:#[proc_macro], 派生式过程宏使用:#[proc_macro_derive]
#[proc_macro_attribute]
// attr 为参数
pub fn my_test_proc_macro_attribute(attr: TokenStream, item: TokenStream) -> TokenStream {
    eprintln!("attr -> {:#?}", parse_macro_input!(attr as AttributeArgs));
    let body_ast = parse_macro_input!(item as Item);
    eprintln!("item -> {:#?}", body_ast);
    // quote!返回的是quote内置依赖的proc_macro2::TokenStream,调用into方法进一步转换为系统的proc_macro::TokenStream
    quote!(#body_ast).into()
}
执行`cargo check`
 
attr -> [
    Meta(
        List(
            MetaList {
                path: Path {
                    leading_colon: None,
                    segments: [
                        PathSegment {
                            ident: Ident {
                                ident: "blog",
                                span: #0 bytes(84..88),
                            },
                            arguments: None,
                        },
                    ],
                },
                paren_token: Paren,
                nested: [
                    Meta(
                        Path(
                            Path {
                                leading_colon: Some(
                                    Colon2,
                                ),
                                segments: [
                                    PathSegment {
                                        ident: Ident {
                                            ident: "hello",
                                            span: #0 bytes(91..96),
                                        },
                                        arguments: None,
                                    },
                                    Colon2,
                                    PathSegment {
                                        ident: Ident {
                                            ident: "world",
                                            span: #0 bytes(98..103),
                                        },
                                        arguments: None,
                                    },
                                ],
                            },
                        ),
                    ),
                ],
            },
        ),
    ),
]
item -> Fn(
    ItemFn {
        attrs: [],
        vis: Public(
            VisPublic {
                pub_token: Pub,
            },
        ),
        sig: Signature {
            constness: None,
            asyncness: None,
            unsafety: None,
            abi: None,
            fn_token: Fn,
            ident: Ident {
                ident: "foo",
                span: #0 bytes(114..117),
            },
            generics: Generics {
                lt_token: None,
                params: [],
                gt_token: None,
                where_clause: None,
            },
            paren_token: Paren,
            inputs: [
                Typed(
                    PatType {
                        attrs: [],
                        pat: Ident(
                            PatIdent {
                                attrs: [],
                                by_ref: None,
                                mutability: None,
                                ident: Ident {
                                    ident: "i",
                                    span: #0 bytes(118..119),
                                },
                                subpat: None,
                            },
                        ),
                        colon_token: Colon,
                        ty: Path(
                            TypePath {
                                qself: None,
                                path: Path {
                                    leading_colon: None,
                                    segments: [
                                        PathSegment {
                                            ident: Ident {
                                                ident: "i32",
                                                span: #0 bytes(121..124),
                                            },
                                            arguments: None,
                                        },
                                    ],
                                },
                            },
                        ),
                    },
                ),
            ],
            variadic: None,
            output: Default,
        },
        block: Block {
            brace_token: Brace,
            stmts: [
                Semi(
                    Macro(
                        ExprMacro {
                            attrs: [],
                            mac: Macro {
                                path: Path {
                                    leading_colon: None,
                                    segments: [
                                        PathSegment {
                                            ident: Ident {
                                                ident: "println",
                                                span: #0 bytes(132..139),
                                            },
                                            arguments: None,
                                        },
                                    ],
                                },
                                bang_token: Bang,
                                delimiter: Paren(
                                    Paren,
                                ),
                                tokens: TokenStream [
                                    Literal {
                                        kind: Str,
                                        symbol: "Hello foo! {}",
                                        suffix: None,
                                        span: #0 bytes(141..156),
                                    },
                                    Punct {
                                        ch: ',',
                                        spacing: Alone,
                                        span: #0 bytes(156..157),
                                    },
                                    Ident {
                                        ident: "i",
                                        span: #0 bytes(158..159),
                                    },
                                ],
                            },
                        },
                    ),
                    Semi,
                ),
            ],
        },
    },
)

派生式过程宏derive

可以把派生式过程宏认为是一种特殊的属性式过程宏,把属性式过程宏看成是派生式过程宏的拓展版本,能用派生式过程宏实现的,用属性式过程宏也可以实现。**区别: 派生式过程宏不能修改用户的代码,只能在修饰的代码后面追加新代码

参考资料

* 第一关

  • 编辑my_demo/proc_macro_first/src/lib.rs, 写入:
use proc_macro::TokenStream;
use syn;

#[proc_macro_derive(Builder)] //这里的Builder及derive宏的名称
pub fn derive_builder(input: TokenStream) -> TokenStream { // 合理函数名称无所谓
    let st = syn::parse_macro_input!(input as syn::DeriveInput);
    eprintln!("{:#?}", st);
    // 和属性式过程宏不同,这里返回空的TokenStream代表不对用户代码追加任何代码(不修改用户代码)
    TokenStream::new()
}
  • 编辑my_demo/src/main.rs, 写入:
use proc_macro_first::Builder;

#[derive(Builder)]
pub struct Command {
    executable: String,
    args: Vec<String>,
    env: Vec<String>,
    current_dir: String,
}

fn main() {
    println!("Hello, world!");
}
执行`cargo check`
 
DeriveInput {
    attrs: [],
    vis: Public(
        VisPublic {
            pub_token: Pub,
        },
    ),
    ident: Ident {
        ident: "Command",
        span: #0 bytes(62..69),
    },
    generics: Generics {
        lt_token: None,
        params: [],
        gt_token: None,
        where_clause: None,
    },
    data: Struct(
        DataStruct {
            struct_token: Struct,
            fields: Named(
                FieldsNamed {
                    brace_token: Brace,
                    named: [
                        Field {
                            attrs: [],
                            vis: Inherited,
                            ident: Some(
                                Ident {
                                    ident: "executable",
                                    span: #0 bytes(76..86),
                                },
                            ),
                            colon_token: Some(
                                Colon,
                            ),
                            ty: Path(
                                TypePath {
                                    qself: None,
                                    path: Path {
                                        leading_colon: None,
                                        segments: [
                                            PathSegment {
                                                ident: Ident {
                                                    ident: "String",
                                                    span: #0 bytes(88..94),
                                                },
                                                arguments: None,
                                            },
                                        ],
                                    },
                                },
                            ),
                        },
                        Comma,
                        Field {
                            attrs: [],
                            vis: Inherited,
                            ident: Some(
                                Ident {
                                    ident: "args",
                                    span: #0 bytes(100..104),
                                },
                            ),
                            colon_token: Some(
                                Colon,
                            ),
                            ty: Path(
                                TypePath {
                                    qself: None,
                                    path: Path {
                                        leading_colon: None,
                                        segments: [
                                            PathSegment {
                                                ident: Ident {
                                                    ident: "Vec",
                                                    span: #0 bytes(106..109),
                                                },
                                                arguments: AngleBracketed(
                                                    AngleBracketedGenericArguments {
                                                        colon2_token: None,
                                                        lt_token: Lt,
                                                        args: [
                                                            Type(
                                                                Path(
                                                                    TypePath {
                                                                        qself: None,
                                                                        path: Path {
                                                                            leading_colon: None,
                                                                            segments: [
                                                                                PathSegment {
                                                                                    ident: Ident {
                                                                                        ident: "String",
                                                                                        span: #0 bytes(110..116),
                                                                                    },
                                                                                    arguments: None,
                                                                                },
                                                                            ],
                                                                        },
                                                                    },
                                                                ),
                                                            ),
                                                        ],
                                                        gt_token: Gt,
                                                    },
                                                ),
                                            },
                                        ],
                                    },
                                },
                            ),
                        },
                        Comma,
                        Field {
                            attrs: [],
                            vis: Inherited,
                            ident: Some(
                                Ident {
                                    ident: "env",
                                    span: #0 bytes(123..126),
                                },
                            ),
                            colon_token: Some(
                                Colon,
                            ),
                            ty: Path(
                                TypePath {
                                    qself: None,
                                    path: Path {
                                        leading_colon: None,
                                        segments: [
                                            PathSegment {
                                                ident: Ident {
                                                    ident: "Vec",
                                                    span: #0 bytes(128..131),
                                                },
                                                arguments: AngleBracketed(
                                                    AngleBracketedGenericArguments {
                                                        colon2_token: None,
                                                        lt_token: Lt,
                                                        args: [
                                                            Type(
                                                                Path(
                                                                    TypePath {
                                                                        qself: None,
                                                                        path: Path {
                                                                            leading_colon: None,
                                                                            segments: [
                                                                                PathSegment {
                                                                                    ident: Ident {
                                                                                        ident: "String",
                                                                                        span: #0 bytes(132..138),
                                                                                    },
                                                                                    arguments: None,
                                                                                },
                                                                            ],
                                                                        },
                                                                    },
                                                                ),
                                                            ),
                                                        ],
                                                        gt_token: Gt,
                                                    },
                                                ),
                                            },
                                        ],
                                    },
                                },
                            ),
                        },
                        Comma,
                        Field {
                            attrs: [],
                            vis: Inherited,
                            ident: Some(
                                Ident {
                                    ident: "current_dir",
                                    span: #0 bytes(145..156),
                                },
                            ),
                            colon_token: Some(
                                Colon,
                            ),
                            ty: Path(
                                TypePath {
                                    qself: None,
                                    path: Path {
                                        leading_colon: None,
                                        segments: [
                                            PathSegment {
                                                ident: Ident {
                                                    ident: "String",
                                                    span: #0 bytes(158..164),
                                                },
                                                arguments: None,
                                            },
                                        ],
                                    },
                                },
                            ),
                        },
                        Comma,
                    ],
                },
            ),
            semi_token: None,
        },
    ),
}

* 其他关卡,完整代码

参考资料:

  • 编辑my_demo/proc_macro_first/Cargo.toml, 添加:
[dependencies]
quote = "1"
syn = { version = "*", features = ["full", "extra-traits"] }
# 新增
proc-macro2 = "1"
  • 编辑my_demo/proc_macro_first/src/lib.rs, 更改为:
extern crate proc_macro;

use proc_macro::TokenStream;
use quote::{format_ident, quote};
use syn::{self, parse_macro_input, Data, DataStruct, DeriveInput, Fields, FieldsNamed, Type};

#[proc_macro_derive(Builder)]
pub fn derive(input: TokenStream) -> 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;

    let builder = format_ident!("{}Builder", name);

    let data: FieldsNamed = match input.data {
        Data::Struct(DataStruct {
            fields: Fields::Named(n),
            ..
        }) => n,
        other => unimplemented!("{:?}", other),
    };

    let fields = data.named.iter().filter_map(|field| {
        let ty = &field.ty;
        match &field.ident {
            Some(ident) => Some((ident, ty, inner_for_option(ty))),
            _ => None,
        }
    });

    let names = data.named.iter().filter_map(|field| match &field.ident {
        None => None,
        Some(ident) => Some((ident, inner_for_option(&field.ty))),
    });

    let initialize = names.clone().map(|(name, _)| quote! { #name: None });

    let extract = names.clone().map(|(name, option)| match option {
        None => quote! { #name: self.#name.clone()? },
        Some(_) => quote! { #name: self.#name.clone() },
    });

    let quoted_fields = fields.clone().map(|(name, ty, option)| match option {
        None => quote! { #name: Option<#ty> },
        Some(ty) => quote! { #name: Option<#ty> },
    });

    let methods = fields.clone().map(|(name, ty, option)| match option {
        None => quote! {
            pub fn #name(&mut self, value: #ty) -> &mut Self {
                self.#name = Some(value);
                self
            }
        },

        Some(ty) => quote! {
            pub fn #name(&mut self, value: #ty) -> &mut Self {
                self.#name = Some(value);
                self
            }
        },
    });

    let expanded = quote! {
        impl #name {
            fn builder() -> #builder {
                #builder {
                    #(
                        #initialize,
                    )*
                }
            }
        }

        struct #builder {
            #(
                #quoted_fields,
            )*
        }

        impl #builder {
            pub fn build(&self) -> Option<#name> {
                Some(#name {
                    #(
                        #extract,
                    )*
                })
            }

            #(
                #methods
            )*
        }
    };

    TokenStream::from(expanded)
}

fn inner_for_option(ty: &Type) -> Option<Type> {
    match ty {
        Type::Path(syn::TypePath {
            path: syn::Path { segments, .. },
            ..
        }) if segments[0].ident == "Option" => {
            let segment = &segments[0];

            match &segment.arguments {
                syn::PathArguments::AngleBracketed(generic) => {
                    match generic.args.first().unwrap() {
                        syn::GenericArgument::Type(ty) => Some(ty.clone()),
                        _ => None,
                    }
                }
                _ => None,
            }
        }

        _ => None,
    }
}
  • 查看代码生成结果:
# cargo install cargo-expand
cargo expand

生成结果打印如下:

#![feature(prelude_import)]
#[prelude_import]
use std::prelude::rust_2018::*;
#[macro_use]
extern crate std;
use proc_macro_first::Builder;
pub struct Command {
    executable: String,
    args: Vec<String>,
    env: Vec<String>,
    current_dir: String,
}
impl Command {
    fn builder() -> CommandBuilder {
        CommandBuilder {
            executable: None,
            args: None,
            env: None,
            current_dir: None,
        }
    }
}
struct CommandBuilder {
    executable: Option<String>,
    args: Option<Vec<String>>,
    env: Option<Vec<String>>,
    current_dir: Option<String>,
}
impl CommandBuilder {
    pub fn build(&self) -> Option<Command> {
        Some(Command {
            executable: self.executable.clone()?,
            args: self.args.clone()?,
            env: self.env.clone()?,
            current_dir: self.current_dir.clone()?,
        })
    }
    pub fn executable(&mut self, value: String) -> &mut Self {
        self.executable = Some(value);
        self
    }
    pub fn args(&mut self, value: Vec<String>) -> &mut Self {
        self.args = Some(value);
        self
    }
    pub fn env(&mut self, value: Vec<String>) -> &mut Self {
        self.env = Some(value);
        self
    }
    pub fn current_dir(&mut self, value: String) -> &mut Self {
        self.current_dir = Some(value);
        self
    }
}
fn main() {
    {
        ::std::io::_print(::core::fmt::Arguments::new_v1(
            &["Hello, world!\n"],
            &match () {
                () => [],
            },
        ));
    };
}
@wuyuedefeng wuyuedefeng changed the title rust过程宏 rust宏 Jul 8, 2021
@wuyuedefeng
Copy link
Member Author

wuyuedefeng commented Dec 13, 2021

struct 派生宏 个人实现版本 参考

use proc_macro::TokenStream;
use syn::{spanned::Spanned};
// use syn::{parse_quote};
use quote;

pub fn derive(input: TokenStream) -> TokenStream {
    let st = syn::parse_macro_input!(input as syn::DeriveInput);
    // TokenStream::new()
    match do_expand(&st) {
        Ok(token_stream) => token_stream.into(),
        Err(e) => e.to_compile_error().into(),
    }
}

type StructFields = syn::punctuated::Punctuated<syn::Field, syn::Token![,]>;

fn get_fields_from_derive_input(st: &syn::DeriveInput) -> syn::Result<&StructFields> {
    if let syn::Data::Struct(
        syn::DataStruct {
            fields: syn::Fields::Named(
                syn::FieldsNamed {
                    ref named,
                    ..
                }
            ),
            ..
        }
    ) = st.data {
        Ok(named)
    } else {
        Err(syn::Error::new_spanned(st, "Must Define On Struct, Not Allow On Enum Or Union".to_string()))
    }
}

fn get_generic_inner_type<'a>(t: &'a syn::Type, out_ident_name: &str) -> Option<&'a syn::Type> {
    if let syn::Type::Path(
        syn::TypePath {
            path: syn::Path {
                segments,
                ..
            },
            ..
        }
    ) = t {
        if let Some(seg) = segments.last() {
            if seg.ident.to_string() == out_ident_name {
                if let syn::PathArguments::AngleBracketed(
                    syn::AngleBracketedGenericArguments {
                        args,
                        ..
                    }
                ) = &seg.arguments {
                    if let Some(syn::GenericArgument::Type(inner_type)) = args.first() {
                        return Some(inner_type);
                    }
                }
            }
        }
    }
    None
}


enum AttrType {
    Struct,
    // Field,
}
fn get_attr(attrs: &Vec<syn::Attribute>, attr_name: &str, attr_type: AttrType) -> syn::Result<Option<syn::Ident>> {
    // #[arel="users"]
    // for attr in attrs {
    //     if let Ok(
    //         syn::Meta::NameValue(
    //             syn::MetaNameValue {
    //               ref path,
    //               ref lit,
    //               ..
    //             }
    //         )
    //     ) = attr.parse_meta() {
    //         if path.is_ident("arel") {
    //             if let syn::Lit::Str(ref ident_str) = lit {
    //                 return Ok(Some(syn::Ident::new(ident_str.value().as_str(), attr.span())));
    //             }
    //         }
    //     }
    // }
    // #[arel(table_name="users")]
    for attr in attrs {
        if let Ok(
            syn::Meta::List(
                syn::MetaList {
                    ref path,
                    ref nested,
                    ..
                }
            )
        ) = attr.parse_meta() {
            for segment in path.segments.iter() {
                if segment.ident == "arel" {
                    for nested_item in nested.iter() {
                        if let syn::NestedMeta::Meta(syn::Meta::NameValue(kv)) = nested_item {
                            if let syn::Lit::Str(ref ident_str) = kv.lit {
                                if kv.path.is_ident(attr_name) {
                                    return Ok(Some(syn::Ident::new(ident_str.value().as_str(), attr.span())));
                                } else { // 校验
                                    match attr_type {
                                        AttrType::Struct => {
                                            let support_attr_names = vec!["table_name"];
                                            for attr_name in &support_attr_names {
                                                if !kv.path.is_ident(attr_name) {
                                                    return Err(syn::Error::new_spanned(&kv.path, format!("Struct Only Support Attrs: {:?}", &support_attr_names)));
                                                }
                                            }
                                        },
                                        // AttrType::Field => {
                                        //     let support_attr_names = vec!["table_column_name"];
                                        //     for attr_name in &support_attr_names {
                                        //         if !kv.path.is_ident(attr_name) {
                                        //             return Err(syn::Error::new_spanned(&kv.path, format!("Field Only Support Attrs: {:?}", &support_attr_names)));
                                        //         }
                                        //     }
                                        // }
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }
    Ok(None)
}

fn builde_struct_fields(st: &syn::DeriveInput) -> syn::Result<(proc_macro2::TokenStream, proc_macro2::TokenStream, proc_macro2::TokenStream, proc_macro2::TokenStream)> {
    let fields = get_fields_from_derive_input(st)?;

    let idents: Vec<_> = fields.iter().map(|f| &f.ident).collect();
    let types: Vec<_> = fields.iter().map(|f| &f.ty).collect();

    Ok(
        (
            // first
            {
                let types: Vec<_> = fields.iter().map(|f| {
                    // &f.ty
                    if let Some(ty_inner_type) = get_generic_inner_type(&f.ty, "Option") {
                        quote::quote! {
                            std::option::Option<#ty_inner_type>
                        }
                    } else if let Some(_) = get_generic_inner_type(&f.ty, "Vec") {
                        let origin_type = &f.ty;
                        quote::quote! {
                            #origin_type
                        }
                    } else {
                        let origin_type = &f.ty;
                        quote::quote! {
                            std::option::Option<#origin_type>
                        }
                    }
                }).collect();
                quote::quote! {
                    #(#idents: #types),*
                }
            },
            // second
            // quote::quote! {
            //     #(#idents: std::option::Option::None),*
            // },
            {
                let mut final_token_stream = proc_macro2::TokenStream::new();
                for (ident, r#type) in idents.iter().zip(types.iter()) {
                    let token_stream_piece;
                    if let Some(_) = get_generic_inner_type(r#type, "Vec") {
                        token_stream_piece = quote::quote! {
                            #ident: std::vec::Vec::new(),
                        };
                    } else {
                        token_stream_piece = quote::quote! {
                            #ident: std::option::Option::None,
                        };
                    }
                    final_token_stream.extend(token_stream_piece);
                }
                final_token_stream
            },
            // third
            // quote::quote! {
            //     #(pub fn #idents(&mut self, #idents: #types) -> &mut Self {
            //         self.#idents = std::option::Option::Some(#idents);
            //         self
            //     })*
            // },
            {
                let mut final_token_stream = proc_macro2::TokenStream::new();
                for (ident, r#type) in idents.iter().zip(types.iter()) {
                    let token_stream_piece;
                    if let Some(_) = get_generic_inner_type(r#type, "Vec") {
                        token_stream_piece = quote::quote! {
                            fn #ident(&mut self, #ident: #r#type) -> &mut Self {
                                self.#ident = #ident;
                                self
                            }
                        };
                    }  else {
                        // T -> T, Option<T> -> T
                        let r#type = if let Some(inner_type) = get_generic_inner_type(r#type, "Option") { inner_type } else { r#type };
                        token_stream_piece = quote::quote! {
                            fn #ident(&mut self, #ident: #r#type) -> &mut Self {
                                self.#ident = std::option::Option::Some(#ident);
                                self
                            }
                        };
                    }
                    final_token_stream.extend(token_stream_piece);
                }
                final_token_stream
            },
            // fourth
            {
                let segments: Vec<_> = fields.iter().filter(|f| {
                    get_generic_inner_type(&f.ty, "Option").is_none() && get_generic_inner_type(&f.ty, "Vec").is_none()
                }).map(|f| {
                    let ident = &f.ident;
                    quote::quote! {
                        if self.#ident.is_none() {
                            return Err(anyhow::anyhow!("{} Not Allow None", stringify!(#ident)));
                        }
                    }
                }).collect();
                quote::quote! {
                    pub fn validate(&self) -> anyhow::Result<()> {
                        #(#segments)*
                        Ok(())
                    }
                }
            }
        )
    )
}

fn do_expand(st: &syn::DeriveInput) -> syn::Result<proc_macro2::TokenStream> {
    let struct_ident = &st.ident;
    let struct_name_literal = struct_ident.to_string();
    let arel_struct_name_literal = format!("{}Arel", struct_name_literal);
    let arel_struct_ident = &syn::Ident::new(&arel_struct_name_literal, st.span());

    let (
        builder_fields_def,
        builder_fields_init_clauses,
        builder_setter_functions,
        validate_function,
    ) = builde_struct_fields(st)?;

    // table_name_function
    let table_name_function = {
        if let Some(table_name_ident) = get_attr(&st.attrs, "table_name", AttrType::Struct)? {
            quote::quote! {
                fn table_name() -> String {
                    stringify!(#table_name_ident).to_string()
                }
            }
        } else {
            proc_macro2::TokenStream::new()
        }
    };

    // trait限定 使用eg: impl #impl_generics #arel_struct_ident #type_generics #where_clause
    // let mut generics = st.generics.clone();
    // let (impl_generics, type_generics, where_clause) = {
    //     // for param in generics.params.iter_mut() {
    //     //     if let syn::GenericParam::Type(t) = param {
    //     //         // 对每个trait 额外增加Debug限定 eg: struct User<T: Clone> => struct User<T: Clone + Debug>
    //     //         t.bounds.push(parse_quote!(std::fmt::Debug))
    //     //     }
    //     // }
    //     generics.split_for_impl()
    // };

    let ret = quote::quote! {
        // User
        impl #struct_ident {
            fn table() -> arel::table::Table<#arel_struct_ident> { #arel_struct_ident::table() }
            fn query() -> arel::table::Table<#arel_struct_ident> { #arel_struct_ident::query() }
            fn lock() -> arel::table::Table<#arel_struct_ident> { #arel_struct_ident::lock() }
            fn create(condition: serde_json::Value) -> arel::table::Table<#arel_struct_ident> { #arel_struct_ident::create(condition) }
            fn update_all(condition: serde_json::Value) -> arel::table::Table<#arel_struct_ident> { #arel_struct_ident::update_all(condition) }
            fn delete_all(condition: serde_json::Value) -> arel::table::Table<#arel_struct_ident> { #arel_struct_ident::delete_all(condition) }
        }

        // UserArel
        #[derive(::core::clone::Clone, ::core::fmt::Debug)]
        pub struct #arel_struct_ident {
            #builder_fields_def
        }

        impl arel::ArelAble for #arel_struct_ident {
            #table_name_function
            fn primary_key() -> &'static str { "id" }
        }

        impl #arel_struct_ident {
            pub fn new() -> Self {
                Self {
                    #builder_fields_init_clauses
                }
            }
            #builder_setter_functions
            #validate_function
        }
    };
    Ok(ret)
}

@wuyuedefeng
Copy link
Member Author

其他rust宏解析器

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant