use proc_macro2::TokenStream;
use quote::ToTokens;
use syn::spanned::Spanned;
use syn::{Fields, GenericArgument, PathArguments, Type};
use crate::{Mode, Parameters};
macro_rules! equal_type_or_continue {
($src_type:ident, $target_type:ident, $type:expr, $correct_macro:expr) => {
if !is_equal_type(&$src_type, &$target_type) {
err!(
$src_type,
"{}Type '{} cannot be merged into field of type '{}'.",
$type,
$src_type.to_token_stream(),
$target_type.to_token_stream()
)
} else {
$correct_macro
}
};
}
mod borrowed;
mod owned;
pub(crate) fn generate_impl(mode: &Mode, params: Parameters) -> Result<TokenStream, TokenStream> {
let target_fields = match params.target_struct.fields.clone() {
Fields::Named(fields) => fields,
_ => {
return Err(err!(
params.target_struct,
"struct_merge only works on structs with named fields."
));
}
};
let src_fields = match params.src_struct.fields.clone() {
Fields::Named(fields) => fields,
_ => {
return Err(err!(
params.target_struct,
"struct_merge only works on structs with named fields."
));
}
};
let mut similar_fields = Vec::new();
for src_field in src_fields.named {
for target_field in target_fields.named.clone() {
if src_field.ident == target_field.ident {
similar_fields.push((src_field.clone(), target_field));
}
}
}
match *mode {
Mode::Owned => Ok(owned::impl_owned(¶ms, similar_fields)),
Mode::Borrowed => Ok(borrowed::impl_borrowed(¶ms, similar_fields)),
}
}
fn is_equal_type(src_type: &Type, target_type: &Type) -> bool {
if src_type.to_token_stream().to_string() != target_type.to_token_stream().to_string() {
return false;
}
true
}
#[allow(clippy::large_enum_variant)]
enum FieldType {
Normal(Type),
Optional { inner: Type, outer: Type },
Invalid,
}
fn determine_field_type(ty: Type) -> FieldType {
match ty.clone() {
Type::Path(type_path) => {
if type_path.qself.is_some() {
return FieldType::Normal(ty);
}
let path = type_path.path;
if path.leading_colon.is_some() || path.segments.len() > 1 {
return FieldType::Normal(ty);
}
let segment = if let Some(segment) = path.segments.iter().next() {
segment
} else {
return FieldType::Normal(ty);
};
if segment.ident != "Option" {
return FieldType::Normal(ty);
}
let generic_arg = match &segment.arguments {
PathArguments::AngleBracketed(params) => {
if let Some(arg) = params.args.iter().next() {
arg
} else {
err!(ty, "Option doesn't have a type parameter..");
return FieldType::Invalid;
}
}
_ => {
err!(
ty,
"Unknown path arguments behind Option. Please report this."
);
return FieldType::Invalid;
}
};
match generic_arg {
GenericArgument::Type(inner_type) => FieldType::Optional {
inner: inner_type.clone(),
outer: ty,
},
_ => {
err!(ty, "Option path argument isn't a type.");
FieldType::Invalid
}
}
}
_ => {
err!(
ty,
"Found a non-path type. This isn't supported in struct-merge yet."
);
FieldType::Invalid
}
}
}