use proc_macro2::{Span, TokenStream};
use quote::{ToTokens, TokenStreamExt};
use syn;
use BuilderPattern;
use DEFAULT_STRUCT_NAME;
use crate::{change_span, BlockContents, DefaultExpression};
#[derive(Debug, Clone)]
pub struct Initializer<'a> {
pub crate_root: &'a syn::Path,
pub field_ident: &'a syn::Ident,
pub field_enabled: bool,
pub builder_pattern: BuilderPattern,
pub default_value: Option<&'a DefaultExpression>,
pub use_default_struct: bool,
pub custom_error_type_span: Option<Span>,
pub conversion: FieldConversion<'a>,
}
impl<'a> ToTokens for Initializer<'a> {
fn to_tokens(&self, tokens: &mut TokenStream) {
let struct_field = &self.field_ident;
let builder_field = struct_field;
let append_rhs = |tokens: &mut TokenStream| {
if !self.field_enabled {
let default = self.default();
tokens.append_all(quote!(
#default
));
} else {
match &self.conversion {
FieldConversion::Block(conv) => {
conv.to_tokens(tokens);
}
FieldConversion::Move => tokens.append_all(quote!( self.#builder_field )),
FieldConversion::OptionOrDefault => {
let match_some = self.match_some();
let match_none = self.match_none();
tokens.append_all(quote!(
match self.#builder_field {
#match_some,
#match_none,
}
));
}
}
}
};
tokens.append_all(quote!(#struct_field:));
append_rhs(tokens);
tokens.append_all(quote!(,));
}
}
impl<'a> Initializer<'a> {
fn match_some(&'a self) -> MatchSome {
match self.builder_pattern {
BuilderPattern::Owned => MatchSome::Move,
BuilderPattern::Mutable | BuilderPattern::Immutable => MatchSome::Clone {
crate_root: self.crate_root,
},
}
}
fn match_none(&'a self) -> MatchNone<'a> {
match self.default_value {
Some(expr) => MatchNone::DefaultTo {
expr,
crate_root: self.crate_root,
},
None => {
if self.use_default_struct {
MatchNone::UseDefaultStructField(self.field_ident)
} else {
MatchNone::ReturnError {
crate_root: self.crate_root,
field_name: self.field_ident.to_string(),
span: self.custom_error_type_span,
}
}
}
}
}
fn default(&'a self) -> TokenStream {
let crate_root = self.crate_root;
match self.default_value {
Some(expr) => expr.with_crate_root(crate_root).into_token_stream(),
None if self.use_default_struct => {
let struct_ident = syn::Ident::new(DEFAULT_STRUCT_NAME, Span::call_site());
let field_ident = self.field_ident;
quote!(#struct_ident.#field_ident)
}
None => {
quote!(#crate_root::export::core::default::Default::default())
}
}
}
}
#[derive(Debug, Clone)]
pub enum FieldConversion<'a> {
OptionOrDefault,
Block(&'a BlockContents),
Move,
}
enum MatchNone<'a> {
DefaultTo {
expr: &'a DefaultExpression,
crate_root: &'a syn::Path,
},
UseDefaultStructField(&'a syn::Ident),
ReturnError {
crate_root: &'a syn::Path,
field_name: String,
span: Option<Span>,
},
}
impl<'a> ToTokens for MatchNone<'a> {
fn to_tokens(&self, tokens: &mut TokenStream) {
match *self {
MatchNone::DefaultTo { expr, crate_root } => {
let expr = expr.with_crate_root(crate_root);
tokens.append_all(quote!(None => #expr));
}
MatchNone::UseDefaultStructField(field_ident) => {
let struct_ident = syn::Ident::new(DEFAULT_STRUCT_NAME, Span::call_site());
tokens.append_all(quote!(
None => #struct_ident.#field_ident
))
}
MatchNone::ReturnError {
ref field_name,
ref span,
crate_root,
} => {
let conv_span = span.unwrap_or_else(Span::call_site);
let crate_root = change_span(crate_root.into_token_stream(), conv_span);
let err_conv = quote_spanned!(conv_span => #crate_root::export::core::convert::Into::into(
#crate_root::UninitializedFieldError::from(#field_name)
));
tokens.append_all(quote!(
None => return #crate_root::export::core::result::Result::Err(#err_conv)
));
}
}
}
}
enum MatchSome<'a> {
Move,
Clone { crate_root: &'a syn::Path },
}
impl ToTokens for MatchSome<'_> {
fn to_tokens(&self, tokens: &mut TokenStream) {
match *self {
Self::Move => tokens.append_all(quote!(
Some(value) => value
)),
Self::Clone { crate_root } => tokens.append_all(quote!(
Some(ref value) => #crate_root::export::core::clone::Clone::clone(value)
)),
}
}
}
#[doc(hidden)]
#[macro_export]
macro_rules! default_initializer {
() => {
Initializer {
crate_root: &parse_quote!(::db),
field_ident: &syn::Ident::new("foo", ::proc_macro2::Span::call_site()),
field_enabled: true,
builder_pattern: BuilderPattern::Mutable,
default_value: None,
use_default_struct: false,
conversion: FieldConversion::OptionOrDefault,
custom_error_type_span: None,
}
};
}
#[cfg(test)]
mod tests {
#[allow(unused_imports)]
use super::*;
#[test]
fn immutable() {
let mut initializer = default_initializer!();
initializer.builder_pattern = BuilderPattern::Immutable;
assert_eq!(
quote!(#initializer).to_string(),
quote!(
foo: match self.foo {
Some(ref value) => ::db::export::core::clone::Clone::clone(value),
None => return ::db::export::core::result::Result::Err(::db::export::core::convert::Into::into(
::db::UninitializedFieldError::from("foo")
)),
},
)
.to_string()
);
}
#[test]
fn mutable() {
let mut initializer = default_initializer!();
initializer.builder_pattern = BuilderPattern::Mutable;
assert_eq!(
quote!(#initializer).to_string(),
quote!(
foo: match self.foo {
Some(ref value) => ::db::export::core::clone::Clone::clone(value),
None => return ::db::export::core::result::Result::Err(::db::export::core::convert::Into::into(
::db::UninitializedFieldError::from("foo")
)),
},
)
.to_string()
);
}
#[test]
fn owned() {
let mut initializer = default_initializer!();
initializer.builder_pattern = BuilderPattern::Owned;
assert_eq!(
quote!(#initializer).to_string(),
quote!(
foo: match self.foo {
Some(value) => value,
None => return ::db::export::core::result::Result::Err(::db::export::core::convert::Into::into(
::db::UninitializedFieldError::from("foo")
)),
},
)
.to_string()
);
}
#[test]
fn default_value() {
let mut initializer = default_initializer!();
let default_value = DefaultExpression::explicit::<syn::Expr>(parse_quote!(42));
initializer.default_value = Some(&default_value);
assert_eq!(
quote!(#initializer).to_string(),
quote!(
foo: match self.foo {
Some(ref value) => ::db::export::core::clone::Clone::clone(value),
None => { 42 },
},
)
.to_string()
);
}
#[test]
fn default_struct() {
let mut initializer = default_initializer!();
initializer.use_default_struct = true;
assert_eq!(
quote!(#initializer).to_string(),
quote!(
foo: match self.foo {
Some(ref value) => ::db::export::core::clone::Clone::clone(value),
None => __default.foo,
},
)
.to_string()
);
}
#[test]
fn setter_disabled() {
let mut initializer = default_initializer!();
initializer.field_enabled = false;
assert_eq!(
quote!(#initializer).to_string(),
quote!(foo: ::db::export::core::default::Default::default(),).to_string()
);
}
#[test]
fn no_std() {
let initializer = default_initializer!();
assert_eq!(
quote!(#initializer).to_string(),
quote!(
foo: match self.foo {
Some(ref value) => ::db::export::core::clone::Clone::clone(value),
None => return ::db::export::core::result::Result::Err(::db::export::core::convert::Into::into(
::db::UninitializedFieldError::from("foo")
)),
},
)
.to_string()
);
}
}