use proc_macro2::{Span, TokenStream as TokenStream2};
use quote::{quote, ToTokens};
use syn::visit_mut::VisitMut;
use syn::{parse_quote, Error, GenericParam, Ident, Path, Type};
use crate::shape::bare_ident;
use crate::subject::{targets, Conversions, Entry, SelfReplacer, Subject, Target};
pub(crate) fn conversion_impls(
subject: &Subject,
entries: &[Entry],
from: bool,
try_from: bool,
errors: &mut Vec<Error>,
) -> TokenStream2 {
let (impl_generics, ty_generics, where_clause) = subject.generics().split_for_impl();
let enum_ident = subject.ident();
let prefix = Path::from(enum_ident.clone());
let self_path: Path = parse_quote!(Self);
let self_type: Type = parse_quote!(#enum_ident #ty_generics);
let converting: Vec<(&Target, Conversions)> = targets(entries)
.into_iter()
.filter_map(|target| Some((target, target.conversions()?)))
.filter(|(_, conversions)| conversions.from)
.collect();
let overlapping = from && converting.len() > 1;
let mut payload_counts = std::collections::HashMap::<String, usize>::new();
for (target, _) in &converting {
*payload_counts
.entry(target.payload.to_token_stream().to_string())
.or_default() += 1;
}
let value = Ident::new("__value", Span::mixed_site());
let other = Ident::new("__other", Span::mixed_site());
let mut output = TokenStream2::new();
for (target, conversions) in converting {
let Target { payload, cfgs, .. } = target;
if payload_counts[&payload.to_token_stream().to_string()] > 1 {
continue;
}
let built = target.rebuild(&self_path, quote!(#value));
let pattern = target.pattern(&prefix, &value);
let parameter = type_parameter(subject, target);
if let (true, Some(ident)) = (overlapping, parameter) {
errors.push(Error::new_spanned(
payload,
format!("`{ident}` is a type parameter of `{enum_ident}`, so `From<{ident}>` would overlap the other variants' `From` impls; add `from = false` on the enum, or `#[dispatch(from = false)]` on this variant"),
));
} else if from {
output.extend(quote! {
#(#cfgs)*
#[automatically_derived]
impl #impl_generics ::core::convert::From<#payload> for #enum_ident #ty_generics #where_clause {
#[inline]
fn from(#value: #payload) -> Self {
#built
}
}
});
}
if conversions.try_from.unwrap_or(try_from) {
if let Some(ident) = parameter {
errors.push(Error::new_spanned(
payload,
format!("`{ident}` is a type parameter of `{enum_ident}`, so `TryFrom<{enum_ident}> for {ident}` would implement a foreign trait for a type parameter, which the orphan rules forbid; add `#[dispatch(try_from = false)]` on this variant"),
));
continue;
}
let mut outside = payload.clone();
SelfReplacer(self_type.clone()).visit_type_mut(&mut outside);
output.extend(quote! {
#(#cfgs)*
#[automatically_derived]
impl #impl_generics ::core::convert::TryFrom<#enum_ident #ty_generics> for #outside #where_clause {
type Error = #enum_ident #ty_generics;
#[inline]
#[allow(unreachable_patterns)]
fn try_from(#value: #enum_ident #ty_generics) -> ::core::result::Result<Self, Self::Error> {
match #value {
#pattern => ::core::result::Result::Ok(#value),
#other => ::core::result::Result::Err(#other),
}
}
}
});
}
}
output
}
fn type_parameter<'t>(subject: &Subject, target: &'t Target) -> Option<&'t Ident> {
bare_ident(&target.payload).filter(|ident| {
subject
.generics()
.params
.iter()
.any(|param| matches!(param, GenericParam::Type(param) if ¶m.ident == *ident))
})
}