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
单独设置,否则我们还是需要提供一个整体的Vec
的setter
方法。
声明
#[proc_macro_derive(Builder, attributes(builder))]
pub fn derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
solution1(input)
}
除了基础的Builder
,我们还需要声明下属的属性标记,增加attributes(builder)
描述,这样,在Builder
标记的结构体内,才能够正常解析builder
。
方法
如何提取字段上的属性标记呢,可以参考文档中的提示
- docs.rs/syn/1.0/syn/struct.Attribu...
- docs.rs/syn/1.0/syn/enum.Meta.html
这里说明一下几个简单的结构 syn:: Meta::Path
:#[A:: B::C]
syn:: Meta::List
:#[Foo(A,B,C)]
syn:: Meta::NameValue
:#[x = "y"]
其中比较特殊的是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 = "e::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 协议》,转载必须注明作者和本文链接
推荐文章: