use std::collections::HashMap;
use std::convert::TryFrom;
use std::fmt;
use std::fmt::Display;
use proc_macro2::Span;
use proc_macro2::TokenStream;
use quote::quote;
use syn::punctuated::Punctuated;
use syn::spanned::Spanned;
use syn::Error;
use syn::FnArg;
use syn::Ident;
use syn::Index;
use syn::ItemFn;
use syn::Pat;
use syn::PatType;
use syn::Receiver;
use syn::Type;
use syn::TypeTuple;
const SINGLETON_FN_TUPLE_ARG_NAME: &str = "args";
enum SingletonFnError<'e> {
FnHasNoInputs(&'e ItemFn),
FnReceiverNotRef(&'e Receiver),
FnArgNotReceiver(&'e PatType),
FnArgNotTyped(&'e FnArg),
PatTypeNoIdent(&'e PatType),
}
pub(super) enum SingletonFnType<'f> {
NonMut,
NonMutWithArg(SingletonFnArgs<'f>),
Mut,
MutWithArg(SingletonFnArgs<'f>),
}
pub(super) enum SingletonFnArgs<'f> {
Single {
arg_ident: &'f Ident,
arg_type: &'f Type,
},
Multiple {
arg_idents: Vec<&'f Ident>,
tuple_ident: Ident,
tuple_type: TypeTuple,
},
}
impl<'f> SingletonFnArgs<'f> {
pub fn build_impl_fn_call_arg(&self) -> TokenStream {
match self {
SingletonFnArgs::Single { arg_ident, .. } => {
quote! {#arg_ident}
}
SingletonFnArgs::Multiple { arg_idents, .. } => {
quote! { ( #(#arg_idents),*) }
}
}
}
pub fn build_impl_fn_sig_arg(&self) -> syn::Result<FnArg> {
match self {
SingletonFnArgs::Single {
arg_ident,
arg_type,
} => syn::parse2(quote! { #arg_ident: #arg_type }),
SingletonFnArgs::Multiple {
tuple_ident,
tuple_type,
..
} => syn::parse2(quote! { #tuple_ident: #tuple_type }),
}
}
pub fn build_impl_fn_replacement_legend(&self) -> Option<HashMap<String, TokenStream>> {
match self {
SingletonFnArgs::Single { .. } => None,
SingletonFnArgs::Multiple {
arg_idents,
tuple_ident,
..
} => {
let replacement_legend = arg_idents
.iter()
.enumerate()
.map(|(index, arg)| {
let index = Index::from(index);
(
format!("{}", quote! {#arg}),
quote! { #tuple_ident.#index },
)
})
.collect::<HashMap<String, TokenStream>>();
Some(replacement_legend)
}
}
}
}
impl<'f> TryFrom<&'f ItemFn> for SingletonFnType<'f> {
type Error = syn::Error;
fn try_from(base: &'f ItemFn) -> syn::Result<Self> {
if base.sig.inputs.is_empty() {
return Err(SingletonFnError::FnHasNoInputs(base).into());
}
let receiver = fn_arg_as_receiver(base.sig.inputs.first().unwrap())?;
if receiver.reference.is_none() {
return Err(SingletonFnError::FnReceiverNotRef(receiver).into());
}
let args = {
if base.sig.inputs.len() == 1 {
None
} else {
Some(
base.sig
.inputs
.iter()
.filter_map(|arg| {
if let FnArg::Receiver(_) = arg {
None
} else {
Some(fn_arg_as_typed(arg).unwrap())
}
})
.collect::<Vec<&PatType>>(),
)
}
};
if receiver.mutability.is_none() {
if let Some(value) = args {
Ok(Self::NonMutWithArg(SingletonFnArgs::try_from(
value.as_slice(),
)?))
} else {
Ok(Self::NonMut)
}
} else if let Some(value) = args {
Ok(Self::MutWithArg(SingletonFnArgs::try_from(
value.as_slice(),
)?))
} else {
Ok(Self::Mut)
}
}
}
impl<'f> TryFrom<&[&'f PatType]> for SingletonFnArgs<'f> {
type Error = syn::Error;
fn try_from(src: &[&'f PatType]) -> syn::Result<Self> {
if src.is_empty() {
panic!("singleton fn arg: attempted to construct from a function that has no inputs")
} else if src.len() == 1 {
let arg = src.first().unwrap();
Ok(Self::Single {
arg_ident: pat_type_as_ident(*arg)?,
arg_type: &*arg.ty,
})
} else {
let arg_idents = {
let mut arg_idents = Vec::with_capacity(src.len());
for arg in src {
arg_idents.push(pat_type_as_ident(arg)?);
}
arg_idents
};
let tuple_ident = Ident::new(SINGLETON_FN_TUPLE_ARG_NAME, Span::call_site());
let tuple_type = {
let elems = src
.iter()
.map(|arg| *arg.ty.clone())
.collect::<Punctuated<Type, syn::token::Comma>>();
TypeTuple {
paren_token: syn::token::Paren {
span: Span::call_site(),
},
elems,
}
};
Ok(Self::Multiple {
arg_idents,
tuple_ident,
tuple_type,
})
}
}
}
impl<'e> Display for SingletonFnError<'e> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
use SingletonFnError::*;
match self {
FnHasNoInputs(_) => {
write!(f, "attempted to construct a singleton fn from function that has no inputs (must have at least a reference receiver - &self or &mut self)")
}
FnReceiverNotRef(_) => {
write!(
f,
"function receiver must be a reference receiver (&self or &mut self)"
)
}
FnArgNotReceiver(arg) => {
let pat = &arg.pat;
let ty = &arg.ty;
write!(f, "first function argument {} (type: {}) must be a reference receiver (&self or &mut self)",
quote! { #pat },
quote! { #ty },
)
}
FnArgNotTyped(_) => write!(f, "function argument is not typed"),
PatTypeNoIdent(_) => {
write!(f, "function argument must have an identifier")
}
}
}
}
impl<'e> Spanned for SingletonFnError<'e> {
fn span(&self) -> Span {
use SingletonFnError::*;
match self {
FnHasNoInputs(f) => f.sig.ident.span(),
FnReceiverNotRef(recv) => recv.span(),
FnArgNotReceiver(arg) => arg.span(),
FnArgNotTyped(arg) => arg.span(),
PatTypeNoIdent(pat_ty) => pat_ty.span(),
}
}
}
#[allow(clippy::from_over_into)]
impl<'e> Into<Error> for SingletonFnError<'e> {
fn into(self) -> Error {
Error::new(self.span(), format!("{}", self))
}
}
fn fn_arg_as_receiver(src: &FnArg) -> syn::Result<&Receiver> {
match src {
FnArg::Receiver(value) => Ok(value),
FnArg::Typed(bad) => Err(SingletonFnError::FnArgNotReceiver(bad).into()),
}
}
fn fn_arg_as_typed(src: &FnArg) -> syn::Result<&PatType> {
if let FnArg::Typed(value) = src {
Ok(value)
} else {
Err(SingletonFnError::FnArgNotTyped(src).into())
}
}
fn pat_type_as_ident(src: &PatType) -> syn::Result<&Ident> {
if let Pat::Ident(value) = &*src.pat {
Ok(&value.ident)
} else {
Err(SingletonFnError::PatTypeNoIdent(src).into())
}
}