proc-macro_workshop:builder-7

审题

// The std::process::Command builder handles args in a way that is potentially
// more convenient than passing a full vector of args to the builder all at
// once.
//
// Look for a field attribute #[builder(each = "...")] on each field. The
// generated code may assume that fields with this attribute have the type Vec
// and should use the word given in the string literal as the name for the
// corresponding builder method which accepts one vector element at a time.
//
// In order for the compiler to know that these builder attributes are
// associated with your macro, they must be declared at the entry point of the
// derive macro. Otherwise the compiler will report them as unrecognized
// attributes and refuse to compile the caller's code.
//
//     #[proc_macro_derive(Builder, attributes(builder))]
//
// These are called inert attributes. The word "inert" indicates that these
// attributes do not correspond to a macro invocation on their own; they are
// simply looked at by other macro invocations.
//
// If the new one-at-a-time builder method is given the same name as the field,
// avoid generating an all-at-once builder method for that field because the
// names would conflict.
//
//
// Resources:
//
//   - Relevant syntax tree types:
//     https://docs.rs/syn/1.0/syn/struct.Attribute.html
//     https://docs.rs/syn/1.0/syn/enum.Meta.html

use derive_builder::Builder;

#[derive(Builder)]
pub struct Command {
    executable: String,
    #[builder(each = "arg")]
    args: Vec<String>,
    #[builder(each = "env")]
    env: Vec<String>,
    current_dir: Option<String>,
}

fn main() {
    let command = Command::builder()
        .executable("cargo".to_owned())
        .arg("build".to_owned())
        .arg("--release".to_owned())
        .build()
        .unwrap();

    assert_eq!(command.executable, "cargo");
    assert_eq!(command.args, vec!["build", "--release"]);
}

这道题相较于之前的题目,有了一定的升华。
相较于之前的字段类型检测,这道题的每一个字段,都可能有额外的属性标记。
这是由于rust宏发展造就的派生宏和属性宏的交叉。
算是派生宏下属的过程宏,但是这种属性,只能绑定在派生宏下。

观察题目,其中主要的目的就是通过解析Vec<T>字段上的#[builder(each = "xx")],然后改造具体的setter方法,使得能够单独的添加属性,而不是一次性的设置整个Vec

Option<T>类似,我们要提取Vec<T>中的T,并创建对应的setter方法。
这里需要说明特殊的一点,如果each指定的名称和变量名相同,那就只能通过each单独设置,否则我们还是需要提供一个整体的Vecsetter方法。

声明

#[proc_macro_derive(Builder, attributes(builder))]
pub fn derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
    solution1(input)
}

除了基础的Builder,我们还需要声明下属的属性标记,增加
attributes(builder)描述,这样,在Builder标记的结构体内,才能够正常解析builder

方法

如何提取字段上的属性标记呢,可以参考文档中的提示

其中比较特殊的是syn:: Meta::List,它里面的(A,B,C)作为list中的每一项的结构都是syn:: NestedMeta::Meta,而这个数据结构,有可以针对的解析为上述三种结构。

分析一下#[builder(each = "arg")]不难发现,它其实就是一个syn:: Meta::List内部再解析为syn:: Meta::NameValue
这样,我们就可以提取出指定的方法名称了。

pub(crate) fn each_method(field: &syn::Field) ->  std::option::Option<syn::Ident> {
    for attr in &field.attrs {
        // 匹配MetaList
        if  let  std::result::Result::Ok(
            syn::Meta::List(syn::MetaList {
                ref  path,
                ref  nested,
                ..
            }
        )) =  attr.parse_meta() {
            if  let  Some(p) =  path.segments.first() {
                // 指定builder
                if  p.ident  ==  "builder" {
                    if  let  Some(syn::NestedMeta::Meta(
                        syn::Meta::NameValue(kv)
                    )) =  nested.first() {
                        // 解析each
                        if  kv.path.is_ident("each") {
                            if  let  syn::Lit::Str(ref  ident_str) =  kv.lit {
                                return std::option::Option::Some(
                                    syn::Ident::new(
                                        ident_str.value().as_str(),
                                        attr.span()));
                            }
                        }
                    }
                }
            }
        }
    }
    std::option::Option::None
}

基础方法完成,接下来就是如何作答了。

题解

还是按照之前的分步骤答题,先思考一下我们需要改动哪些地方

  • XBuilder:如果指定了each的,我们直接使用Vec<T>替换Option<T>
  • setter:单独设置
  • build:对于指定each的,直接拷贝Vec<T>就好了

和第六题一样,我们基本都要全部进行实现

// solution7.rs

pub(super) fn solution(
    fields: &crate::common::FieldsType,
    builder_ident: &syn::Ident,
    origin_ident: &syn::Ident,
) -> proc_macro2::TokenStream {
    let mut token_stream = proc_macro2::TokenStream::new();

    // solution2
    let solution2_stream = solution2(fields, builder_ident, origin_ident);
    token_stream.extend(solution2_stream);

    // solution35
    let solution35_stream = solution35(fields, builder_ident, origin_ident);
    token_stream.extend(solution35_stream);

    // solution45
    let solution4_stream = solution4(fields, builder_ident, origin_ident);
    token_stream.extend(solution4_stream);

    token_stream
}

// builder构造
fn solution2(
    fields: &crate::common::FieldsType,
    builder_ident: &syn::Ident,
    origin_ident: &syn::Ident,
) -> proc_macro2::TokenStream {
    let struct_field_stream_vec: Vec<_> = fields
        .iter()
        .map(|f| {
            let ident = &f.ident;
            let ty = &f.ty;
            // option
            if let std::option::Option::Some(_) =
                crate::common::option_type_with_ident(ty, "Option")
            {
                quote::quote! {
                    pub #ident: #ty
                }
            // vec
            } else if let std::option::Option::Some(_) =
                crate::common::option_type_with_ident(ty, "Vec")
            {
                quote::quote! {
                    pub #ident: #ty
                }
            // 其他类型
            } else {
                quote::quote! {
                    pub #ident: std::option::Option<#ty>
                }
            }
        })
        .collect();

    let construct_field_stream_vec: Vec<_> = fields
        .iter()
        .map(|f| {
            let ident = &f.ident;
            let ty = &f.ty;
            // vec
            if let std::option::Option::Some(_) = crate::common::option_type_with_ident(ty, "Vec") {
                quote::quote! {
                    #ident: vec![]
                }
            } else {
                quote::quote! {
                    #ident: std::option::Option::None
                }
            }
        })
        .collect();
    quote::quote! {
        pub struct #builder_ident {
            #(
                #struct_field_stream_vec
            ),*
        }

        impl #origin_ident {
            pub fn builder() -> #builder_ident {
                #builder_ident {
                    #(
                        #construct_field_stream_vec
                    ),*
                }
            }
        }
    }
}

// setter
fn solution35(
    fields: &crate::common::FieldsType,
    builder_ident: &syn::Ident,
    _: &syn::Ident,
) -> proc_macro2::TokenStream {
    let setter_stream_vec: Vec<_> = fields
        .iter()
        .map(|f| {
            let ident = &f.ident;
            let ty = &f.ty;
            // option
            if let std::option::Option::Some(inner_type) =
                crate::common::option_type_with_ident(ty, "Option")
            {
                quote::quote! {
                    pub fn #ident(&mut self, #ident: #inner_type) -> &mut Self {
                        self.#ident = std::option::Option::Some(#ident);
                        self
                    }
                }
            // vec
            } else if let std::option::Option::Some(inner_type) =
                crate::common::option_type_with_ident(ty, "Vec")
            {
                // 指定了each
                if let std::option::Option::Some(ref each_method_ident) =
                    crate::common::each_method(f)
                {
                    let mut each_method_stream = quote::quote! {
                        pub fn #each_method_ident(&mut self, #ident: #inner_type) -> &mut Self {
                            self.#ident.push(#ident);
                            self
                        }
                    };
                    // 不重名添加额外方法
                    if ident.as_ref().unwrap() != each_method_ident {
                        let origin_setter_stream = quote::quote! {
                            pub fn #ident(&mut self, #ident: #ty) -> &mut Self {
                                self.#ident = #ident;
                                self
                            }
                        };
                        each_method_stream.extend(origin_setter_stream);
                    }
                    each_method_stream
                } else {
                    // 没有指定each
                    quote::quote! {
                        pub fn #ident(&mut self, #ident: #ty) -> &mut Self {
                            self.#ident = #ident;
                            self
                        }
                    }
                }
            // 其他
            } else {
                quote::quote! {
                    pub fn #ident(&mut self, #ident: #ty) -> &mut Self {
                        self.#ident = std::option::Option::Some(#ident);
                        self
                    }
                }
            }
        })
        .collect();
    quote::quote! {
        impl #builder_ident {
            #(
                #setter_stream_vec
            )*
        }
    }
}

// builder
fn solution4(
    fields: &crate::common::FieldsType,
    builder_ident: &syn::Ident,
    origin_ident: &syn::Ident,
) -> proc_macro2::TokenStream {
    let construct_if_stream_vec: Vec<_> = fields
        .iter()
        .filter(|f| {
            // option和vec的不检查
            let option_field = crate::common::option_type_with_ident(&f.ty, "Option");
            let vec_field = crate::common::option_type_with_ident(&f.ty, "Vec");
            option_field.is_none() && vec_field.is_none()
        })
        .map(|f| {
            let ident = &f.ident;
            quote::quote! {
                if self.#ident.is_none() {
                    let err = format!("field {} is missing", stringify!(#ident));
                    return std::result::Result::Err(err.into());
                }
            }
        })
        .collect();

    let construct_stream: Vec<_> = fields
        .iter()
        .map(|f| {
            let ident = &f.ident;
            let ty = &f.ty;
            // option直接拷贝
            if let std::option::Option::Some(_) =
                crate::common::option_type_with_ident(ty, "Option")
            {
                quote::quote! {
                    #ident: self.#ident.clone()
                }
            // vec直接拷贝
            } else if let std::option::Option::Some(_) =
                crate::common::option_type_with_ident(ty, "Vec")
            {
                quote::quote! {
                    #ident: self.#ident.clone()
                }
            } else {
                // 其他类型unwrap
                quote::quote! {
                    #ident: self.#ident.clone().unwrap()
                }
            }
        })
        .collect();
    quote::quote! {
        impl #builder_ident {
            pub fn build(&self) -> std::result::Result<#origin_ident, std::boxed::Box<dyn std::error::Error>> {
                #(
                    #construct_if_stream_vec
                )*

                let res = #origin_ident {
                    #(
                        #construct_stream
                    ),*
                };
                std::result::Result::Ok(res)
            }
        }
    }
}

至此,第七题完成。

整体

因为,又重写了一遍,因此前面的全部作废,按照惯例,照旧保留

// lib.rs
mod  common;
mod  solution2;
mod  solution35;
mod  solution4;
mod  solution6;
mod  solution7;

#[proc_macro_derive(Builder, attributes(builder))]
pub fn derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
    solution1(input)
}

fn solution1(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
    let ast = syn::parse_macro_input!(input as syn::DeriveInput);
    let fields = {
        match common::parse_fields(&ast) {
            Ok(f) => f,
            Err(_e) => std::panic!(std::stringify!(_e)),
        }
    };

    let origin_ident = &ast.ident;
    let builder_ident = &quote::format_ident!("{}Builder", origin_ident);
    let mut token_stream = proc_macro2::TokenStream::new();

    // solution2
    let solution2_stream = solution2::solution(fields, origin_ident, builder_ident);
    token_stream.extend(solution2_stream);

    // solution35
    let solution35_stream = solution35::soultion(fields, builder_ident);
    token_stream.extend(solution35_stream);

    // solution4
    let solution4_stream = solution4::solution(fields, builder_ident, origin_ident);
    token_stream.extend(solution4_stream);

    // solution6
    let _ = solution6::solution(fields, builder_ident, origin_ident);

    // solution7
    let token_stream = solution7::solution(fields, builder_ident, origin_ident);

    proc_macro::TokenStream::from(token_stream)
}
本作品采用《CC 协议》,转载必须注明作者和本文链接
讨论数量: 0
(= ̄ω ̄=)··· 暂无内容!

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!