// Some fields may not always need to be specified. Typically these would be
// represented as Option<T> in the struct being built.
//
// Have your macro identify fields in the macro input whose type is Option and
// make the corresponding builder method optional for the caller. In the test
// case below, current_dir is optional and not passed to one of the builders in
// main.
//
// Be aware that the Rust compiler performs name resolution only after macro
// expansion has finished completely. That means during the evaluation of a
// procedural macro, "types" do not exist yet, only tokens. In general many
// different token representations may end up referring to the same type: for
// example `Option<T>` and `std::option::Option<T>` and `<Vec<Option<T>> as
// IntoIterator>::Item` are all different names for the same type. Conversely,
// a single token representation may end up referring to many different types in
// different places; for example the meaning of `Error` will depend on whether
// the surrounding scope has imported std::error::Error or std::io::Error. As a
// consequence, it isn't possible in general for a macro to compare two token
// representations and tell whether they refer to the same type.
//
// In the context of the current test case, all of this means that there isn't
// some compiler representation of Option that our macro can compare fields
// against to find out whether they refer to the eventual Option type after name
// resolution. Instead all we get to look at are the tokens of how the user has
// described the type in their code. By necessity, the macro will look for
// fields whose type is written literally as Option<...> and will not realize
// when the same type has been written in some different way.
//
// The syntax tree for types parsed from tokens is somewhat complicated because
// there is such a large variety of type syntax in Rust, so here is the nested
// data structure representation that your macro will want to identify:
//
// Type::Path(
// TypePath {
// qself: None,
// path: Path {
// segments: [
// PathSegment {
// ident: "Option",
// arguments: PathArguments::AngleBracketed(
// AngleBracketedGenericArguments {
// args: [
// GenericArgument::Type(
// ...
// ),
// ],
// },
// ),
// },
// ],
// },
// },
// )
use derive_builder::Builder;
#[derive(Builder)]
pub struct Command {
executable: String,
args: Vec<String>,
env: Vec<String>,
current_dir: Option<String>,
}
fn main() {
let command = Command::builder()
.executable("cargo".to_owned())
.args(vec!["build".to_owned(), "--release".to_owned()])
.env(vec![])
.build()
.unwrap();
assert!(command.current_dir.is_none());
let command = Command::builder()
.executable("cargo".to_owned())
.args(vec!["build".to_owned(), "--release".to_owned()])
.env(vec![])
.current_dir("..".to_owned())
.build()
.unwrap();
assert!(command.current_dir.is_some());
}
這一關主要表明的就是所謂的預設
欄位,這一下基本上把前面的實現都已經打亂了
XBuilder-fields
:不能簡單包裹,否則會產生Option<Option<T>>
setter
:設定的是T
,而不是Option<T>
build
:欄位檢查可以為空
由此看來,我們基本上要把之前的方法全部實現一遍,並且要把Option<T>
解析出來。
透過相關的提示,就是透過解析欄位型別的Path
,獲取Option<T>
中的T
。
// common.rs
// 解析X::Y::ty<T>中的T
pub(crate) fn option_type_with_ident<'a>(
ty: &'a syn::Type,
ident: &str,
) -> std::option::Option<&'a syn::Type> {
if let syn::Type::Path(
syn::TypePath {
ref path,
..
}
) = ty {
// 專門匹配路徑的最後一個
if let std::option::Option::Some(seg) = path.segments.last() {
// 特徵符號
if seg.ident == ident {
// <?> 中的?
if let syn::PathArguments::AngleBracketed(
syn::AngleBracketedGenericArguments {
ref args,
..
}
) = seg.arguments{
// 匹配提取T
if let Some(syn::GenericArgument::Type(inner_type)) = args.first() {
return std::option::Option::Some(inner_type);
}
}
}
}
}
std::option::Option::None
}
// 提取指定的XX::YY::Option<T>中的T
pub(crate) fn option_type(ty: &syn::Type) -> std::option::Option<&syn::Type> {
option_type_with_ident(ty, "Option".into())
}
這樣,我們後續就好改寫之前的方法了
按照之前的分類,我們還是分步作答,不過基礎方法都要進行重寫
// solution6.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 refactor_solution2_stream = solution2(fields, builder_ident, origin_ident);
token_stream.extend(refactor_solution2_stream);
// solution35
let refactor_solution35_stream = solution35(fields, builder_ident, origin_ident);
token_stream.extend(refactor_solution35_stream);
// solution4
let refactor_solution4_stream = solution4(fields, builder_ident, origin_ident);
token_stream.extend(refactor_solution4_stream);
return token_stream;
}
// 重寫builder
fn solution2(
fields: &crate::common::FieldsType,
builder_ident: &syn::Ident,
origin_ident: &syn::Ident,
) -> proc_macro2::TokenStream {
let builder_fields_stream_vec: Vec<_> = fields
.iter()
.map(|f| {
let ident = &f.ident;
let ty = &f.ty;
match crate::common::option_type(ty) {
// 如果是包裝型別Option<T>,直接使用T
std::option::Option::Some(inner_type) => {
quote::quote! {
pub #ident: std::option::Option<#inner_type>
}
},
// 否則直接使用T
std::option::Option::None => {
quote::quote! {
pub #ident: std::option::Option<#ty>
}
}
}
})
.collect();
let idents: Vec<_> = fields.iter().map(|f| &f.ident).collect();
quote::quote! {
pub struct #builder_ident {
#(
#builder_fields_stream_vec
),*
}
impl #origin_ident {
pub fn builder() -> #builder_ident {
#builder_ident {
#(
#idents: std::option::Option::None
),*
}
}
}
}
}
// 重寫setter
fn solution35(
fields: &crate::common::FieldsType,
builder_ident: &syn::Ident,
_origin_ident: &syn::Ident,
) -> proc_macro2::TokenStream {
let setter_method_stream_vec: Vec<_> = fields
.iter()
.map(|f| {
let ident = &f.ident;
let ty = &f.ty;
match crate::common::option_type(ty) {
// 如果是Option<T>,使用T
std::option::Option::Some(inner_type) => {
quote::quote! {
pub fn #ident(&mut self, #ident: #inner_type) -> &mut Self {
self.#ident = std::option::Option::Some(#ident);
self
}
}
},
// 直接使用T
std::option::Option::None => {
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_method_stream_vec
)*
}
}
}
// 重寫build:檢查和回傳
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()
.map(|f| {
let ident = &f.ident;
let ty = &f.ty;
// 專門針對非Option<T>進行條件檢查
// 對於預設欄位無需檢查
if let std::option::Option::None = crate::common::option_type(ty) {
return std::option::Option::Some(quote::quote! {
if self.#ident.is_none() {
let err = std::format!("field {} missing", std::stringify!(#ident));
return std::result::Result::Err(err.into());
}
});
}
std::option::Option::None
})
.filter(|e| e.is_some())
.collect();
let construct_instance_stream_vec: Vec<_> = fields
.iter()
.map(|f| {
let ident = &f.ident;
let ty = &f.ty;
// 如果是Option<T>,直接複製即可
match crate::common::option_type(ty) {
std::option::Option::Some(_) => {
quote::quote! {
#ident: self.#ident.clone()
}
},
// 如果是T,需要從builder中的Option<T>解出來進行設定
std::option::Option::None => {
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_instance_stream_vec
),*
};
std::result::Result::Ok(res)
}
}
}
}
其中需要注意的是
#()*
,後面的,
在if
中是不需要的。
// lib.rs
mod solution2;
mod solution35;
mod solution4;
mod common;
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 token_stream = solution6::solution(fields, builder_ident, origin_ident);
proc_macro::TokenStream::from(token_stream)
}
雖然前面的
solution2
solution35
solution4
都已經被重新實現了,完全不需要新增,但是為了清晰的還原解題步驟,還是原樣保留。
本作品採用《CC 協議》,轉載必須註明作者和本文連結