use crate::{
ident::{CRATE_IDENT, TRAIT_IDENT},
ident_collector::IdentCollector,
opts::{self, FuncMapOpts, Param},
result::{self, Error, IteratorExt, ResultExt},
syn_ext::{IsTypish, ToNonEmptyTokens},
};
use std::{collections::HashSet, iter};
use proc_macro2::Ident;
use quote::ToTokens;
use syn::{
visit::Visit, Data, DataEnum, DataStruct, DataUnion, DeriveInput, Field, GenericParam,
Generics, Path, Token, Type, TypeParam, Variant,
};
#[derive(Debug)]
pub(crate) struct FuncMapInput {
pub(crate) meta: FuncMapMeta,
pub(crate) ident: Ident,
pub(crate) generics: Generics,
pub(crate) mapped_type_params: Vec<MappedTypeParam>,
pub(crate) variants: Vec<Structish>,
}
#[derive(Debug)]
pub(crate) struct FuncMapMeta {
pub(crate) crate_path: Path,
pub(crate) ident_collector: IdentCollector,
}
#[derive(Debug)]
pub(crate) struct MappedTypeParam {
pub(crate) param_idx: usize,
pub(crate) marker_idx: usize,
pub(crate) type_param: TypeParam,
}
#[derive(Debug)]
pub(crate) struct Structish {
pub(crate) variant_ident: Option<Ident>,
pub(crate) fields: Vec<Fieldish>,
}
#[derive(Debug)]
pub(crate) struct Fieldish {
pub(crate) ident: Option<Ident>,
pub(crate) ty: Type,
}
impl TryFrom<DeriveInput> for FuncMapInput {
type Error = Error;
fn try_from(derive_input: DeriveInput) -> Result<Self, Self::Error> {
let ident_collector = {
let mut ident_collector = IdentCollector::new_visiting();
ident_collector.visit_derive_input(&derive_input);
ident_collector.into_reserved()
};
let opts: FuncMapOpts = derive_input.attrs.try_into()?;
let meta = FuncMapMeta {
crate_path: opts.crate_path.unwrap_or_else(|| {
let path = CRATE_IDENT.into();
Path {
leading_colon: Some(<Token![::]>::default()),
..path
}
}),
ident_collector,
};
let mut mapped_type_param_idents = HashSet::new();
let mut result_builder = result::Builder::new();
for param in opts.params {
match (
derive_input.generics.params.iter().find(|p| &¶m == p),
param,
) {
(Some(GenericParam::Type(..)), Param::TypeOrConst(ident)) => {
mapped_type_param_idents.insert(ident);
}
(Some(GenericParam::Lifetime(..)), param) => {
result_builder.add_err(syn::Error::new_spanned(
param,
format!("cannot implement {} over lifetime parameter", TRAIT_IDENT),
));
}
(Some(GenericParam::Const(..)), param) => {
result_builder.add_err(syn::Error::new_spanned(
param,
format!("cannot implement {} over const generic", TRAIT_IDENT),
));
}
(_, param) => {
result_builder
.add_err(syn::Error::new_spanned(param, "unknown generic parameter"));
}
}
}
let mapped_type_params: Vec<_> = derive_input
.generics
.params
.iter()
.enumerate()
.filter(|(_, param)| param.is_typish())
.enumerate()
.filter_map(|(marker_idx, (param_idx, param))| match param {
GenericParam::Type(type_param)
if mapped_type_param_idents.is_empty()
|| mapped_type_param_idents.contains(&type_param.ident) =>
{
Some(MappedTypeParam {
param_idx,
marker_idx,
type_param: type_param.clone(),
})
}
_ => None,
})
.collect();
if mapped_type_params.is_empty() {
result_builder.add_err(syn::Error::new_spanned(
derive_input
.generics
.to_non_empty_token_stream()
.unwrap_or_else(|| derive_input.ident.to_token_stream()),
"expected at least one type parameter, found none",
));
}
let variants = match derive_input.data {
Data::Struct(data_struct) => iter::once(data_struct.try_into()).collect_with_errors(),
Data::Enum(DataEnum { variants, .. }) => variants
.into_iter()
.map(TryInto::try_into)
.collect_with_errors(),
Data::Union(DataUnion { union_token, .. }) => iter::once(Err(syn::Error::new_spanned(
union_token,
"expected a struct or an enum, found a union",
)))
.collect_with_errors(),
}
.with_error_from(result_builder)?;
Ok(Self {
meta,
ident: derive_input.ident,
generics: derive_input.generics,
mapped_type_params,
variants,
})
}
}
impl TryFrom<DataStruct> for Structish {
type Error = Error;
fn try_from(data_struct: DataStruct) -> Result<Self, Self::Error> {
Ok(Self {
variant_ident: None,
fields: data_struct
.fields
.into_iter()
.map(TryInto::try_into)
.collect_with_errors()?,
})
}
}
impl TryFrom<Variant> for Structish {
type Error = Error;
fn try_from(variant: Variant) -> Result<Self, Self::Error> {
let mut result_builder = result::Builder::new();
opts::assert_absent(&variant.attrs, "variants").add_err_to(&mut result_builder);
Ok(Self {
variant_ident: Some(variant.ident),
fields: variant
.fields
.into_iter()
.map(TryInto::try_into)
.collect_with_errors()
.with_error_from(result_builder)?,
})
}
}
impl TryFrom<Field> for Fieldish {
type Error = Error;
fn try_from(field: Field) -> Result<Self, Self::Error> {
opts::assert_absent(&field.attrs, "fields")?;
Ok(Self {
ident: field.ident,
ty: field.ty,
})
}
}