// 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 協議》,轉載必須註明作者和本文連結