use std::collections::{HashMap, HashSet};
use darling::{
ast::{Data, Fields},
util::{Override, SpannedValue},
FromDeriveInput,
};
use heck::ToSnakeCase;
use macro_field_utils::{FieldInfo, FieldsCollector, FieldsHelper, VariantsHelper};
use proc_macro2::TokenStream;
use proc_macro_error2::abort_if_dirty;
use quote::{format_ident, quote, ToTokens};
use syn::{fold::Fold, parse_quote, visit::Visit};
use crate::{input::*, type_path_ext::*};
pub(crate) fn r#impl(input: syn::DeriveInput) -> TokenStream {
let opts = match MapperOpts::from_derive_input(&input) {
Ok(o) => o,
Err(e) => {
return e.write_errors();
}
};
let ident = &opts.ident;
let derive_items = opts.items();
derive_items
.iter()
.for_each(|i| ItemInput::validate(i, opts.data.is_enum()));
abort_if_dirty();
match &opts.data {
Data::Struct(s) => {
s.iter().for_each(|f| f.validate(&derive_items));
}
Data::Enum(e) => {
e.iter().for_each(|v| v.validate(&derive_items));
}
}
abort_if_dirty();
let mut output = TokenStream::new();
for derive in derive_items {
match &opts.data {
Data::Struct(struct_fields) => {
derive_struct(ident, &opts.generics, derive, struct_fields).to_tokens(&mut output);
}
Data::Enum(enum_variants) => {
derive_enum(ident, &opts.generics, derive, enum_variants).to_tokens(&mut output);
}
}
}
output
}
fn derive_struct(
ident: &syn::Ident,
generics: &syn::Generics,
derive: ItemInput,
struct_fields: &Fields<FieldReceiver>,
) -> TokenStream {
let mut output = TokenStream::new();
if let Some(from) = derive.from.as_ref() {
derive_struct_from(from, ident, generics, &derive, struct_fields, false).to_tokens(&mut output);
}
if let Some(into) = derive.into.as_ref() {
derive_struct_into(into, ident, generics, &derive, struct_fields, false).to_tokens(&mut output);
}
if let Some(try_from) = derive.try_from.as_ref() {
derive_struct_from(try_from, ident, generics, &derive, struct_fields, true).to_tokens(&mut output);
}
if let Some(try_into) = derive.try_into.as_ref() {
derive_struct_into(try_into, ident, generics, &derive, struct_fields, true).to_tokens(&mut output);
}
output
}
fn derive_enum(
ident: &syn::Ident,
generics: &syn::Generics,
derive: ItemInput,
enum_variants: &[VariantReceiver],
) -> TokenStream {
let mut output = TokenStream::new();
if let Some(from) = derive.from.as_ref() {
derive_enum_from(from, ident, generics, &derive, enum_variants, false).to_tokens(&mut output);
}
if let Some(into) = derive.into.as_ref() {
derive_enum_into(into, ident, generics, &derive, enum_variants, false).to_tokens(&mut output);
}
if let Some(try_from) = derive.try_from.as_ref() {
derive_enum_from(try_from, ident, generics, &derive, enum_variants, true).to_tokens(&mut output);
}
if let Some(try_into) = derive.try_into.as_ref() {
derive_enum_into(try_into, ident, generics, &derive, enum_variants, true).to_tokens(&mut output);
}
output
}
fn derive_struct_from(
from: &SpannedValue<Override<DeriveInput>>,
ident: &syn::Ident,
generics: &syn::Generics,
derive: &ItemInput,
struct_fields: &Fields<FieldReceiver>,
is_try: bool,
) -> TokenStream {
let original_from_ty = derive.path.as_ref();
let into_ty = ident;
let (_, into_ty_generics, _) = generics.split_for_impl();
let (mut all_generics, from_ty_with_generics, generics_rename_map) =
process_generics(generics, original_from_ty, true);
struct_fields.iter().for_each(|f| {
if let Some(other_ty) = f.other_ty_for(original_from_ty) {
let ty_ident = other_ty;
let resolved_ident = generics_rename_map.get(ty_ident).unwrap_or(ty_ident);
let field_ty = &f.ty;
let where_clause = all_generics.make_where_clause();
where_clause
.predicates
.push(parse_quote!(#resolved_ident: Into<#field_ty>));
}
});
let (impl_generics, _, where_clause) = all_generics.split_for_impl();
let from_ty = strip_generics(&from_ty_with_generics);
let from_ty_fields_helper = FieldsHelper::new(struct_fields)
.filtering(|_ix, f| f.skip_for(original_from_ty).is_none())
.extra_fields(derive.add.iter().map(|f| f.field.as_ref()))
.ignore_all_extra(derive.ignore_extra.is_present());
let into_ty_fields_helper = FieldsHelper::new(struct_fields)
.filtering(|_ix, f| f.skip_for(original_from_ty).is_none())
.extra_fields_with(
struct_fields
.iter()
.filter_map(|f| f.skip_for(original_from_ty).map(|skip| (f, skip)))
.filter_map(|(f, skip)| {
f.ident.as_ref().map(|field| {
(
field,
skip.as_ref()
.explicit()
.and_then(|s| s.default.as_deref())
.map(|d|
d.clone()
.explicit()
.map(|e| e.value)
.unwrap_or_else(|| parse_quote!(Default::default()))
)
.unwrap_or_else(|| {
let ident = if let Some(rename) = f.rename_for(original_from_ty) {
rename
} else {
field
};
parse_quote!(#ident)
}),
)
})
}),
);
let deconstructed_from = from_ty_fields_helper
.left_collector(|ix, f| {
let ident = if let Some(rename) = f.rename_for(original_from_ty) {
rename.clone()
} else {
f.as_ident(ix)
};
quote!(#ident)
})
.right_collector(FieldsCollector::ident)
.collect();
let into_body = into_ty_fields_helper
.right_collector(|ix, f| {
let ident = f.as_ident(ix);
if is_try {
quote!(#ident)
} else {
f.build_into_for(true, is_try, &ident, original_from_ty)
}
})
.collect();
if let Some(custom) = Override::as_ref(from).explicit().and_then(|e| e.custom.as_deref()) {
let external_fields = struct_fields
.iter()
.filter(|f| {
f.skip_for(original_from_ty)
.filter(|map| map.as_ref().explicit().map(|s| s.default.is_none()).unwrap_or(true))
.is_some()
})
.filter_map(|f| {
let ty = &f.ty;
f.ident.as_ref().map(|i| {
let ident = if let Some(rename) = f.rename_for(original_from_ty) {
rename
} else {
i
};
quote!(#ident: #ty)
})
})
.collect::<Vec<_>>();
let fn_name = custom.clone().explicit().unwrap_or_else(|| {
format_ident!(
"{}_{}",
if is_try { "try_from" } else { "from" },
original_from_ty.to_token_stream().to_string().to_snake_case()
)
});
let doc = format!(
"{} a new [{into_ty}] from a [{}]",
if is_try { "Tries to build" } else { "Builds" },
original_from_ty.to_token_stream().to_string().replace(' ', "")
);
if is_try {
let (trait_error_ty, is_accumulate, custom_accumulator, err_ty) = get_error_and_accumulate_info(Override::as_ref(from).explicit());
let try_body = build_try_body(
true,
original_from_ty,
struct_fields,
quote!(Self #into_body),
is_accumulate,
&custom_accumulator,
&err_ty,
);
quote!(
#[automatically_derived]
#[allow(non_shorthand_field_patterns)]
impl #impl_generics #into_ty #into_ty_generics #where_clause {
#[doc = #doc]
#[allow(clippy::too_many_arguments)]
pub fn #fn_name(from: #from_ty_with_generics, #( #external_fields ),*)
-> ::std::result::Result<Self, #trait_error_ty> {
let #from_ty #deconstructed_from = from;
#try_body
}
}
)
} else {
quote!(
#[automatically_derived]
#[allow(non_shorthand_field_patterns)]
impl #impl_generics #into_ty #into_ty_generics #where_clause {
#[doc = #doc]
#[allow(clippy::too_many_arguments)]
pub fn #fn_name(from: #from_ty_with_generics, #( #external_fields ),*) -> Self {
let #from_ty #deconstructed_from = from;
Self #into_body
}
}
)
}
} else if is_try {
let (trait_error_ty, is_accumulate, custom_accumulator, err_ty) = get_error_and_accumulate_info(Override::as_ref(from).explicit());
let try_body = build_try_body(
true,
original_from_ty,
struct_fields,
quote!(Self #into_body),
is_accumulate,
&custom_accumulator,
&err_ty,
);
quote!(
#[automatically_derived]
#[allow(non_shorthand_field_patterns)]
impl #impl_generics TryFrom<#from_ty_with_generics> for #into_ty #into_ty_generics #where_clause {
type Error = #trait_error_ty;
fn try_from(from: #from_ty_with_generics)
-> ::std::result::Result<Self, <Self as TryFrom<#from_ty_with_generics>>::Error> {
let #from_ty #deconstructed_from = from;
#try_body
}
}
)
} else {
quote!(
#[automatically_derived]
#[allow(non_shorthand_field_patterns)]
impl #impl_generics From<#from_ty_with_generics> for #into_ty #into_ty_generics #where_clause {
fn from(from: #from_ty_with_generics) -> Self {
let #from_ty #deconstructed_from = from;
Self #into_body
}
}
)
}
}
fn derive_struct_into(
into: &SpannedValue<Override<DeriveInput>>,
ident: &syn::Ident,
generics: &syn::Generics,
derive: &ItemInput,
struct_fields: &Fields<FieldReceiver>,
is_try: bool,
) -> TokenStream {
let from_ty = ident;
let original_into_ty = derive.path.as_ref();
let (_, from_ty_generics, _) = generics.split_for_impl();
let (mut all_generics, into_ty_with_generics, generics_rename_map) =
process_generics(generics, original_into_ty, false);
struct_fields.iter().for_each(|f| {
if let Some(other_ty) = f.other_ty_for(original_into_ty) {
let ty_ident = other_ty;
let resolved_ident = generics_rename_map.get(ty_ident).unwrap_or(ty_ident);
let field_ty = &f.ty;
let where_clause = all_generics.make_where_clause();
where_clause
.predicates
.push(parse_quote!(#field_ty: Into<#resolved_ident>));
}
});
let (impl_generics, _, where_clause) = all_generics.split_for_impl();
let into_ty = strip_generics(&into_ty_with_generics);
let from_ty_fields_helper = FieldsHelper::new(struct_fields);
let into_ty_fields_helper = FieldsHelper::new(struct_fields)
.filtering(|_ix, f| f.skip_for(original_into_ty).is_none())
.extra_fields_with(derive.add.iter().map(|i| {
let field = i.field.as_ref();
(
field,
i.default
.as_deref()
.map(|d| d
.clone()
.explicit()
.map(|d| d.value)
.unwrap_or_else(|| parse_quote!(Default::default()))
)
.unwrap_or_else(|| parse_quote!(#field)),
)
}))
.include_all_default(derive.ignore_extra.is_present());
let deconstructed_from = from_ty_fields_helper
.right_collector(|ix, f| {
let ident = if let Some(rename) = f.rename_for(original_into_ty) {
rename.clone()
} else {
f.as_ident(ix)
};
quote!(#ident)
})
.collect();
let into_body = into_ty_fields_helper
.left_collector(|ix, f| {
let ident = if let Some(rename) = f.rename_for(original_into_ty) {
rename.clone()
} else {
f.as_ident(ix)
};
quote!(#ident)
})
.right_collector(|ix, f| {
let ident = if let Some(rename) = f.rename_for(original_into_ty) {
rename.clone()
} else {
f.as_ident(ix)
};
if is_try {
quote!(#ident)
} else {
f.build_into_for(false, is_try, &ident, original_into_ty)
}
})
.collect();
if let Some(custom) = Override::as_ref(into).explicit().and_then(|e| e.custom.as_deref()) {
let external_fields = derive
.add
.iter()
.filter(|a| a.default.is_none())
.map(|f| {
let ident = f.field.as_ref();
let ty = f.ty.as_ref().expect("'ty' must be provided").as_ref();
quote!(#ident: #ty)
})
.collect::<Vec<_>>();
let fn_name = custom.clone().explicit().unwrap_or_else(|| {
format_ident!(
"{}_{}",
if is_try { "try_into" } else { "into" },
original_into_ty.to_token_stream().to_string().to_snake_case()
)
});
let doc = format!(
"{} a new [{}] from a [{from_ty}]",
if is_try { "Tries to build" } else { "Builds" },
original_into_ty.to_token_stream().to_string().replace(' ', "")
);
if is_try {
let (trait_error_ty, is_accumulate, custom_accumulator, err_ty) = get_error_and_accumulate_info(Override::as_ref(into).explicit());
let try_body = build_try_body(
false,
original_into_ty,
struct_fields,
quote!(#into_ty #into_body),
is_accumulate,
&custom_accumulator,
&err_ty,
);
quote!(
#[automatically_derived]
#[allow(non_shorthand_field_patterns)]
impl #impl_generics #from_ty #from_ty_generics #where_clause {
#[doc = #doc]
#[allow(clippy::too_many_arguments)]
pub fn #fn_name(self, #( #external_fields ),*)
-> ::std::result::Result<#into_ty_with_generics, #trait_error_ty> {
let #from_ty #deconstructed_from = self;
#try_body
}
}
)
} else {
quote!(
#[automatically_derived]
#[allow(non_shorthand_field_patterns)]
impl #impl_generics #from_ty #from_ty_generics #where_clause {
#[doc = #doc]
#[allow(clippy::too_many_arguments)]
pub fn #fn_name(self, #( #external_fields ),*) -> #into_ty_with_generics {
let #from_ty #deconstructed_from = self;
#into_ty #into_body
}
}
)
}
} else if is_try {
let (trait_error_ty, is_accumulate, custom_accumulator, err_ty) = get_error_and_accumulate_info(Override::as_ref(into).explicit());
let try_body = build_try_body(
false,
original_into_ty,
struct_fields,
quote!(Self #into_body),
is_accumulate,
&custom_accumulator,
&err_ty,
);
quote!(
#[automatically_derived]
#[allow(non_shorthand_field_patterns)]
impl #impl_generics TryFrom<#from_ty #from_ty_generics> for #into_ty_with_generics #where_clause {
type Error = #trait_error_ty;
fn try_from(from: #from_ty #from_ty_generics)
-> ::std::result::Result<Self, <Self as TryFrom<#from_ty #from_ty_generics>>::Error> {
let #from_ty #deconstructed_from = from;
#try_body
}
}
)
} else {
quote!(
#[automatically_derived]
#[allow(non_shorthand_field_patterns)]
impl #impl_generics From<#from_ty #from_ty_generics> for #into_ty_with_generics #where_clause {
fn from(from: #from_ty #from_ty_generics) -> Self {
let #from_ty #deconstructed_from = from;
Self #into_body
}
}
)
}
}
fn derive_enum_from(
from: &SpannedValue<Override<DeriveInput>>,
ident: &syn::Ident,
generics: &syn::Generics,
derive: &ItemInput,
enum_variants: &[VariantReceiver],
is_try: bool,
) -> TokenStream {
let original_from_ty = derive.path.as_ref();
let into_ty = ident;
let (_, into_ty_generics, _) = generics.split_for_impl();
let (mut all_generics, from_ty_with_generics, generics_rename_map) =
process_generics(generics, original_from_ty, true);
enum_variants.iter().for_each(|v| {
v.fields.iter().for_each(|f| {
if let Some(other_ty) = f.other_ty_for(original_from_ty) {
let ty_ident = other_ty;
let resolved_ident = generics_rename_map.get(ty_ident).unwrap_or(ty_ident);
let field_ty = &f.ty;
let where_clause = all_generics.make_where_clause();
where_clause
.predicates
.push(parse_quote!(#resolved_ident: Into<#field_ty>));
}
});
});
let (impl_generics, _, where_clause) = all_generics.split_for_impl();
let from_ty = strip_generics(&from_ty_with_generics);
let (_, is_acc, custom_acc, acc_err_ty) = get_error_and_accumulate_info(Override::as_ref(from).explicit());
let match_body = VariantsHelper::new(enum_variants)
.filtering_variants(|v| v.skip_for(original_from_ty).is_none())
.include_extra_variants(derive.add.iter().map(|i| {
let field = i.field.as_ref();
let val: syn::Expr = i.default
.as_deref()
.expect("'default' must be provided")
.clone()
.explicit()
.map(|d| d.value)
.unwrap_or_else(|| parse_quote!(Default::default()));
let rhs = if is_try {
quote!(::std::result::Result::Ok(#val))
} else {
quote!(#val)
};
(
quote!(#from_ty::#field { .. }),
Some(rhs),
)
}))
.ignore_all_extra_variants(if derive.ignore_extra.is_present(){
if is_try {
Some(quote!(::std::result::Result::Ok(Default::default())))
} else {
Some(quote!(Default::default()))
}
} else {
None
})
.left_collector(|v, fields| {
let ident = if let Some(rename) = v.rename_for(original_from_ty) {
rename
} else {
&v.ident
};
let from_fields = fields
.filtering(|_ix, f| f.skip_for(original_from_ty).is_none())
.extra_fields(v
.additional_for(original_from_ty)
.map(|i| i.iter().map(|i| i.field.as_ref()).collect::<Vec<_>>())
.unwrap_or_default())
.ignore_all_extra(v.ignore_extra_for(original_from_ty))
.left_collector(|ix, f| {
let ident = if let Some(rename) = f.rename_for(original_from_ty) {
rename.clone()
} else {
f.as_ident(ix)
};
quote!(#ident)
})
.right_collector(FieldsCollector::ident)
.collect();
quote!( #from_ty::#ident #from_fields )
})
.right_collector(|v, fields| {
let ident = &v.ident;
let into_fields = fields
.filtering(|_ix, f| f.skip_for(original_from_ty).is_none())
.extra_fields_with(
v.fields
.iter()
.filter_map(|f| f.skip_for(original_from_ty).map(|skip| (f, skip)))
.filter_map(|(f, skip)| {
f.ident.as_ref().map(|field| {
(
field,
skip.as_ref()
.explicit()
.and_then(|s| s.default.as_deref())
.map(|d| d
.clone()
.explicit()
.map(|e| e.value)
.unwrap_or_else(|| parse_quote!(Default::default())))
.unwrap_or_else(|| {
let ident = if let Some(rename) = f.rename_for(original_from_ty) {
rename
} else {
field
};
let field_provider = format_ident!("{ident}_provider");
parse_quote!(#field_provider())
}),
)
})
}),
)
.right_collector(|ix, f| {
let ident = f.as_ident(ix);
if is_try {
quote!(#ident)
} else {
f.build_into_for(true, is_try, &ident, original_from_ty)
}
})
.collect();
let variant_construction = quote!( #into_ty::#ident #into_fields );
if is_try {
build_try_body(true, original_from_ty, &v.fields, variant_construction, is_acc, &custom_acc, &acc_err_ty)
} else {
variant_construction
}
})
.collect();
if let Some(custom) = Override::as_ref(from).explicit().and_then(|e| e.custom.as_deref()) {
let external_fields = enum_variants
.iter()
.flat_map(|v| &v.fields.fields)
.filter(|f| {
f.skip_for(original_from_ty)
.filter(|map| map.as_ref().explicit().map(|s| s.default.is_none()).unwrap_or(true))
.is_some()
})
.filter_map(|f| {
let ty = &f.ty;
f.ident.as_ref().map(|i| {
let ident = if let Some(rename) = f.rename_for(original_from_ty) {
rename
} else {
i
};
let field_provider = format_ident!("{ident}_provider");
quote!(#field_provider: impl FnOnce() -> #ty)
})
})
.collect::<Vec<_>>();
let fn_name = custom.clone().explicit().unwrap_or_else(|| {
format_ident!(
"{}_{}",
if is_try { "try_from" } else { "from" },
original_from_ty.to_token_stream().to_string().to_snake_case()
)
});
let doc = format!(
"{} a new [{into_ty}] from a [{}]",
if is_try { "Tries to build" } else { "Builds" },
original_from_ty.to_token_stream().to_string().replace(' ', "")
);
if is_try {
let (trait_error_ty, _, _, _) = get_error_and_accumulate_info(Override::as_ref(from).explicit());
quote!(
#[automatically_derived]
#[allow(non_shorthand_field_patterns)]
impl #impl_generics #into_ty #into_ty_generics #where_clause {
#[doc = #doc]
#[allow(clippy::too_many_arguments)]
pub fn #fn_name(from: #from_ty_with_generics, #( #external_fields ),*)
-> ::std::result::Result<Self, #trait_error_ty> {
match from #match_body
}
}
)
} else {
quote!(
#[automatically_derived]
#[allow(non_shorthand_field_patterns)]
impl #impl_generics #into_ty #into_ty_generics #where_clause {
#[doc = #doc]
#[allow(clippy::too_many_arguments)]
fn #fn_name(from: #from_ty_with_generics, #( #external_fields ),*) -> Self {
match from #match_body
}
}
)
}
} else if is_try {
let (trait_error_ty, _, _, _) = get_error_and_accumulate_info(Override::as_ref(from).explicit());
quote!(
#[automatically_derived]
#[allow(non_shorthand_field_patterns)]
impl #impl_generics TryFrom<#from_ty_with_generics> for #into_ty #into_ty_generics #where_clause {
type Error = #trait_error_ty;
fn try_from(other: #from_ty_with_generics)
-> ::std::result::Result<Self, <Self as TryFrom<#from_ty_with_generics>>::Error> {
match other #match_body
}
}
)
} else {
quote!(
#[automatically_derived]
#[allow(non_shorthand_field_patterns)]
impl #impl_generics From<#from_ty_with_generics> for #into_ty #into_ty_generics #where_clause {
fn from(other: #from_ty_with_generics) -> Self {
match other #match_body
}
}
)
}
}
fn derive_enum_into(
into: &SpannedValue<Override<DeriveInput>>,
ident: &syn::Ident,
generics: &syn::Generics,
derive: &ItemInput,
enum_variants: &[VariantReceiver],
is_try: bool,
) -> TokenStream {
let from_ty = ident;
let original_into_ty = derive.path.as_ref();
let (_, from_ty_generics, _) = generics.split_for_impl();
let (mut all_generics, into_ty_with_generics, generics_rename_map) =
process_generics(generics, original_into_ty, false);
enum_variants.iter().for_each(|v| {
v.fields.iter().for_each(|f| {
if let Some(other_ty) = f.other_ty_for(original_into_ty) {
let ty_ident = other_ty;
let resolved_ident = generics_rename_map.get(ty_ident).unwrap_or(ty_ident);
let field_ty = &f.ty;
let where_clause = all_generics.make_where_clause();
where_clause
.predicates
.push(parse_quote!(#field_ty: Into<#resolved_ident>));
}
});
});
let (impl_generics, _, where_clause) = all_generics.split_for_impl();
let into_ty = strip_generics(&into_ty_with_generics);
let (_, is_acc, custom_acc, acc_err_ty) = get_error_and_accumulate_info(Override::as_ref(into).explicit());
let match_body = VariantsHelper::new(enum_variants)
.filtering_variants(|v| v.skip_for(original_into_ty).is_none())
.include_extra_variants(
enum_variants
.iter()
.filter_map(|v| v.skip_for(original_into_ty).map(|skip| (v, skip)))
.map(|(v, skip)| {
let variant = &v.ident;
let val: syn::Expr = skip.as_ref()
.explicit()
.and_then(|s| s.default.as_deref())
.map(|d| d
.clone()
.explicit()
.map(|e| e.value)
.unwrap_or_else(|| parse_quote!(Default::default())))
.expect("'default' is required");
let rhs = if is_try {
quote!(::std::result::Result::Ok(#val))
} else {
quote!(#val)
};
(
quote!(#from_ty::#variant { .. }),
Some(rhs),
)
}),
)
.left_collector(|v, fields| {
let ident = &v.ident;
let from_fields = fields
.right_collector(|ix, f| {
let ident = if let Some(rename) = f.rename_for(original_into_ty) {
rename.clone()
} else {
f.as_ident(ix)
};
quote!(#ident)
})
.collect();
quote!( #from_ty::#ident #from_fields )
})
.right_collector(|v, fields| {
let ident = if let Some(rename) = v.rename_for(original_into_ty) {
rename
} else {
&v.ident
};
let into_fields = fields
.filtering(|_ix, f| f.skip_for(original_into_ty).is_none())
.extra_fields_with(
v.additional_for(original_into_ty)
.map(|i| {
i.iter()
.map(|i| {
let field = i.field.as_ref();
(
field,
i.default
.as_deref()
.map(|d| d
.clone()
.explicit()
.map(|d| d.value)
.unwrap_or_else(|| parse_quote!(Default::default()))
)
.unwrap_or_else(|| {
let field_provider = format_ident!("{field}_provider");
parse_quote!(#field_provider())
}),
)
})
.collect::<Vec<_>>()
})
.unwrap_or_default(),
)
.left_collector(|ix, f| {
let ident = if let Some(rename) = f.rename_for(original_into_ty) {
rename.clone()
} else {
f.as_ident(ix)
};
quote!(#ident)
})
.right_collector(|ix, f| {
let ident = if let Some(rename) = f.rename_for(original_into_ty) {
rename.clone()
} else {
f.as_ident(ix)
};
if is_try {
quote!(#ident)
} else {
f.build_into_for(false, is_try, &ident, original_into_ty)
}
})
.collect();
let variant_construction = quote!( #into_ty::#ident #into_fields );
if is_try {
build_try_body(false, original_into_ty, &v.fields, variant_construction, is_acc, &custom_acc, &acc_err_ty)
} else {
variant_construction
}
})
.collect();
if let Some(custom) = Override::as_ref(into).explicit().and_then(|e| e.custom.as_deref()) {
let external_fields = enum_variants
.iter()
.filter_map(|v| v.additional_for(original_into_ty))
.flatten()
.filter(|a| a.default.is_none())
.map(|f| {
let ident = f.field.as_ref();
let field_provider = format_ident!("{ident}_provider");
let ty = f.ty.as_ref().expect("'ty' must be provided").as_ref();
quote!(#field_provider: impl FnOnce() -> #ty)
})
.collect::<Vec<_>>();
let fn_name = custom.clone().explicit().unwrap_or_else(|| {
format_ident!(
"{}_{}",
if is_try { "try_into" } else { "into" },
original_into_ty.to_token_stream().to_string().to_snake_case()
)
});
let doc = format!(
"{} a new [{}] from a [{from_ty}]",
if is_try { "Tries to build" } else { "Builds" },
original_into_ty.to_token_stream().to_string().replace(' ', "")
);
if is_try {
let (trait_error_ty, _, _, _) = get_error_and_accumulate_info(Override::as_ref(into).explicit());
quote!(
#[automatically_derived]
#[allow(non_shorthand_field_patterns)]
impl #impl_generics #from_ty #from_ty_generics #where_clause {
#[doc = #doc]
#[allow(clippy::too_many_arguments)]
pub fn #fn_name(self, #( #external_fields ),*)
-> ::std::result::Result<#into_ty_with_generics, #trait_error_ty> {
match self #match_body
}
}
)
} else {
quote!(
#[automatically_derived]
#[allow(non_shorthand_field_patterns)]
impl #impl_generics #from_ty #from_ty_generics #where_clause {
#[doc = #doc]
#[allow(clippy::too_many_arguments)]
pub fn #fn_name(self, #( #external_fields ),*) -> #into_ty_with_generics {
match self #match_body
}
}
)
}
} else if is_try {
let (trait_error_ty, _, _, _) = get_error_and_accumulate_info(Override::as_ref(into).explicit());
quote!(
#[automatically_derived]
#[allow(non_shorthand_field_patterns)]
impl #impl_generics TryFrom<#from_ty #from_ty_generics> for #into_ty_with_generics #where_clause {
type Error = #trait_error_ty;
fn try_from(other: #from_ty #from_ty_generics)
-> ::std::result::Result<Self, <Self as TryFrom<#from_ty #from_ty_generics>>::Error> {
match other #match_body
}
}
)
} else {
quote!(
#[automatically_derived]
#[allow(non_shorthand_field_patterns)]
impl #impl_generics From<#from_ty #from_ty_generics> for #into_ty_with_generics #where_clause {
fn from(other: #from_ty #from_ty_generics) -> Self {
match other #match_body
}
}
)
}
}
fn process_generics(
target_generics: &syn::Generics,
source_ty: &syn::TypePath,
is_from_direction: bool,
) -> (syn::Generics, syn::TypePath, HashMap<syn::Ident, syn::Ident>) {
let mut all_generics = target_generics.clone();
let target_params: HashSet<syn::Ident> = target_generics
.params
.iter()
.filter_map(|p| {
if let syn::GenericParam::Type(t) = p {
Some(t.ident.clone())
} else {
None
}
})
.collect();
let mut collector = TypePathCollector { idents: HashSet::new() };
collector.visit_type_path(source_ty);
let original_source_idents = collector.idents;
let mut generics_rename_map = HashMap::new();
let mut new_params = Vec::new();
let mut new_predicates: Vec<syn::WherePredicate> = Vec::new();
for ident in &original_source_idents {
if target_params.contains(ident) {
let new_ident = format_ident!("{}Src", ident);
generics_rename_map.insert(ident.clone(), new_ident.clone());
new_params.push(syn::GenericParam::Type(parse_quote!(#new_ident)));
if is_from_direction {
new_predicates.push(parse_quote!(#new_ident: Into<#ident>));
} else {
new_predicates.push(parse_quote!(#ident: Into<#new_ident>));
}
} else {
let is_concrete_type = if source_ty.path.segments.len() == 1 {
let seg = &source_ty.path.segments[0];
seg.ident == *ident && seg.arguments.is_empty()
} else {
false
};
if !is_concrete_type {
new_params.push(syn::GenericParam::Type(parse_quote!(#ident)));
}
}
}
all_generics.params.extend(new_params);
if !new_predicates.is_empty() {
let where_clause = all_generics.make_where_clause();
where_clause.predicates.extend(new_predicates);
}
let mut replacer = TypePathReplacer {
map: &generics_rename_map,
};
let source_ty_with_generics = replacer.fold_type_path(source_ty.clone());
(all_generics, source_ty_with_generics, generics_rename_map)
}
fn strip_generics(ty: &syn::TypePath) -> syn::TypePath {
let mut new_ty = ty.clone();
for segment in &mut new_ty.path.segments {
segment.arguments = syn::PathArguments::None;
}
new_ty
}
fn get_error_and_accumulate_info(
derive_input: Option<&DeriveInput>,
) -> (syn::Type, bool, Option<syn::Type>, syn::Type) {
let err_ty: syn::Type = derive_input
.and_then(|i| i.err.clone())
.map(|w| syn::Type::Path(w.0))
.unwrap_or_else(|| parse_quote!(::anyhow::Error));
let mut is_accumulate = false;
let mut custom_accumulator = None;
if let Some(input) = derive_input {
if let Some(acc) = &input.accumulate {
is_accumulate = true;
match acc.as_ref() {
Override::Explicit(ty) => {
custom_accumulator = Some(syn::Type::Path(ty.0.clone()));
}
Override::Inherit => {}
}
}
}
let trait_error_ty = if is_accumulate {
if let Some(custom) = &custom_accumulator {
custom.clone()
} else {
parse_quote!(::std::vec::Vec<#err_ty>)
}
} else {
err_ty.clone()
};
(trait_error_ty, is_accumulate, custom_accumulator, err_ty)
}
fn build_try_body(
from: bool,
original_path: &syn::TypePath,
fields: &Fields<FieldReceiver>,
construction_expr: TokenStream,
is_accumulate: bool,
custom_accumulator: &Option<syn::Type>,
err_ty: &syn::Type,
) -> TokenStream {
if is_accumulate {
let mut field_conversions = TokenStream::new();
let mut field_unwraps = TokenStream::new();
for (ix, f) in fields.iter().enumerate() {
if f.skip_for(original_path).is_none() {
let ident = if from {
f.as_ident(ix)
} else if let Some(rename) = f.rename_for(original_path) {
rename.clone()
} else {
f.as_ident(ix)
};
let raw_expr = f.build_into_for(from, true, &ident, original_path);
let map_err = if let Some(err_expr) = f.err_for(original_path) {
quote!(|_| #err_expr)
} else if let Some(err_with_expr) = f.err_with_for(original_path) {
quote!(#err_with_expr)
} else {
quote!(<#err_ty as ::std::convert::From<_>>::from)
};
field_conversions.extend(quote!(
let #ident = match #raw_expr {
::std::result::Result::Ok(val) => ::std::option::Option::Some(val),
::std::result::Result::Err(err) => {
let mapped_err = (#map_err)(err);
errors.push(mapped_err);
::std::option::Option::None
}
};
));
field_unwraps.extend(quote!(
let #ident = #ident.expect("field conversion succeeded but value missing");
));
}
}
let err_return = if let Some(custom) = custom_accumulator {
quote!(::std::result::Result::Err(errors.into_iter().collect::<#custom>()))
} else {
quote!(::std::result::Result::Err(errors))
};
quote!({
let mut errors = ::std::vec::Vec::new();
#field_conversions
if errors.is_empty() {
#field_unwraps
::std::result::Result::Ok(#construction_expr)
} else {
#err_return
}
})
} else {
let mut field_conversions = TokenStream::new();
for (ix, f) in fields.iter().enumerate() {
if f.skip_for(original_path).is_none() {
let ident = if from {
f.as_ident(ix)
} else if let Some(rename) = f.rename_for(original_path) {
rename.clone()
} else {
f.as_ident(ix)
};
let raw_expr = f.build_into_for(from, true, &ident, original_path);
let stmt = if let Some(err_expr) = f.err_for(original_path) {
quote!(let #ident = #raw_expr.map_err(|_| #err_expr)?;)
} else if let Some(err_with_expr) = f.err_with_for(original_path) {
quote!(let #ident = #raw_expr.map_err(#err_with_expr)?;)
} else {
quote!(let #ident = #raw_expr ?;)
};
field_conversions.extend(stmt);
}
}
quote!({
#field_conversions
::std::result::Result::Ok(#construction_expr)
})
}
}