use std::fmt::Debug;
use quote::{quote, ToTokens};
use syn::spanned::Spanned;
use crate::traits::{ToDocInfo, ToMacroPattern};
use super::{ParamAttr, PermutedItem};
#[derive(Clone)]
pub struct FunctionParams {
receiver: FnReceiver,
pub params: Vec<FunctionParam>,
}
#[derive(Clone)]
pub struct FunctionParam {
pat: syn::Pat,
ty: syn::Type,
attrs: Vec<syn::Attribute>,
pub default_value: ParamAttr,
}
#[derive(Clone)]
pub enum FnReceiver {
None,
Slf {
ty: syn::Type,
token: syn::Token![self],
mutable: bool,
reference: bool,
lifetime: Option<syn::Lifetime>,
colon_token: Option<syn::Token![:]>,
},
}
impl Debug for FunctionParam {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("FunctionParam")
.field("pat", &self.pat.to_token_stream().to_string())
.field("ty", &self.ty.to_token_stream().to_string())
.field("default_value", &self.default_value)
.finish()
}
}
impl Debug for ParamAttr {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::None => write!(f, "None"),
Self::Default => write!(f, "Default"),
Self::Value(arg0) => write!(f, "Value({})", arg0.to_token_stream()),
}
}
}
impl PartialEq for FunctionParam {
fn eq(&self, other: &Self) -> bool {
self.pat.to_token_stream().to_string() == other.pat.to_token_stream().to_string()
}
}
impl ToMacroPattern for PermutedItem<FunctionParam> {
fn to_macro_pattern(&self) -> Option<proc_macro2::TokenStream> {
match self {
PermutedItem::Positional(FunctionParam { pat, .. }) => {
let val = syn::Ident::new(&format!("{}_val", pat.to_token_stream()), pat.span());
Some(quote! {$#val: expr})
}
PermutedItem::Named(FunctionParam { pat, .. }) => {
let val = syn::Ident::new(&format!("{}_val", pat.to_token_stream()), pat.span());
Some(quote! {#pat = $#val: expr})
}
PermutedItem::Default(_) => None,
}
}
fn to_func_call_pattern(&self) -> proc_macro2::TokenStream {
match self {
PermutedItem::Positional(FunctionParam { pat, .. })
| PermutedItem::Named(FunctionParam { pat, .. }) => {
let val = syn::Ident::new(&format!("{}_val", pat.to_token_stream()), pat.span());
quote! {$#val}
}
PermutedItem::Default(FunctionParam { default_value, .. }) => {
match default_value {
ParamAttr::None => unimplemented!("default value must be present"),
ParamAttr::Default => quote! {core::default::Default::default()},
ParamAttr::Value(v) => quote! {#v},
}
}
}
}
}
impl ToDocInfo for FunctionParam {
fn to_doc_info(&self) -> crate::traits::DocInfo {
crate::traits::DocInfo {
ident: self.pat.to_token_stream().to_string(),
ty: self.ty.to_token_stream().to_string(),
default_value: match &self.default_value {
ParamAttr::None => None,
ParamAttr::Default => Some("Default::default()".to_string()),
ParamAttr::Value(expr) => Some(expr.to_token_stream().to_string()),
},
}
}
}
impl FunctionParams {
pub fn from_punctuated(
punctuated: syn::punctuated::Punctuated<syn::FnArg, syn::token::Comma>,
) -> Result<Self, syn::Error> {
let mut s = Self {
receiver: FnReceiver::None,
params: Vec::new(),
};
let mut has_receiver = false;
for punct in punctuated {
match punct {
syn::FnArg::Receiver(recv) => {
if !has_receiver {
has_receiver = true;
} else {
panic!("Function cannot accept multiple receivers");
}
let receiver = match (&recv.reference, &recv.mutability) {
(None, None) => FnReceiver::Slf {
ty: *recv.ty.clone(),
token: recv.self_token,
mutable: false,
reference: false,
lifetime: recv.lifetime().cloned(),
colon_token: recv.colon_token,
},
(None, Some(_)) => FnReceiver::Slf {
ty: *recv.ty.clone(),
token: recv.self_token,
mutable: true,
reference: false,
lifetime: recv.lifetime().cloned(),
colon_token: recv.colon_token,
},
(Some(_), None) => FnReceiver::Slf {
ty: *recv.ty.clone(),
token: recv.self_token,
mutable: false,
reference: true,
lifetime: recv.lifetime().cloned(),
colon_token: recv.colon_token,
},
(Some(_), Some(_)) => FnReceiver::Slf {
ty: *recv.ty.clone(),
token: recv.self_token,
mutable: true,
reference: true,
lifetime: recv.lifetime().cloned(),
colon_token: recv.colon_token,
},
};
s.receiver = receiver;
}
syn::FnArg::Typed(t) => {
let param = FunctionParam::from_pat_type(t)?;
s.params.push(param);
}
}
}
Ok(s)
}
pub fn to_punctuated(&self) -> syn::punctuated::Punctuated<syn::FnArg, syn::token::Comma> {
let mut res = Vec::<syn::FnArg>::new();
match &self.receiver {
FnReceiver::None => (),
FnReceiver::Slf {
ty,
token,
mutable,
reference,
lifetime,
colon_token,
} => {
res.push(syn::FnArg::Receiver(syn::Receiver {
attrs: Vec::new(),
reference: if *reference {
Some((Default::default(), lifetime.clone()))
} else {
None
},
mutability: if *mutable {
Some(Default::default())
} else {
None
},
self_token: *token,
colon_token: *colon_token,
ty: Box::new(ty.clone()),
}));
}
}
for param in &self.params {
let pat = param.pat.to_owned();
let ty = param.ty.to_owned();
let s_attrs = param
.attrs
.iter()
.filter(|a| !a.path().is_ident(crate::DEFAULT_HELPER_ATTR))
.cloned()
.collect::<Vec<_>>();
let arg = syn::FnArg::Typed(syn::PatType {
attrs: s_attrs,
pat: Box::new(pat),
colon_token: Default::default(),
ty: Box::new(ty),
});
res.push(arg);
}
res.into_iter().collect()
}
pub fn first_invalid_param(&self) -> Option<&FunctionParam> {
let mut iter = self
.params
.iter()
.skip_while(|p| matches!(p.default_value, ParamAttr::None));
iter.find(|f| matches!(f.default_value, ParamAttr::None))
}
}
impl FunctionParam {
pub fn from_pat_type(punct: syn::PatType) -> Result<Self, syn::Error> {
let pat = &punct.pat;
let ty = &punct.ty;
let mut default_value = ParamAttr::None;
if !punct.attrs.is_empty() {
for attr in &punct.attrs {
if attr.path().is_ident(crate::DEFAULT_HELPER_ATTR) {
let meta = attr.meta.clone();
match meta {
syn::Meta::Path(_) => default_value = ParamAttr::Default,
syn::Meta::List(l) => {
let l_span = l.span();
let first_item = l.tokens.into_iter().next().ok_or(syn::Error::new(
l_span,
"expected at least 1 item in metalist",
))?;
let e: syn::Expr = syn::parse2(first_item.to_token_stream())?;
default_value = ParamAttr::Value(e);
}
syn::Meta::NameValue(nv) => {
let e = syn::Error::new(
nv.span(),
format!("name-values are not supported. Use #[{}] or #[{}(CONST_EXPRESSION)] instead.",
crate::DEFAULT_HELPER_ATTR,
crate::DEFAULT_HELPER_ATTR
),
);
return Err(e);
}
}
break;
}
}
}
Ok(Self {
pat: *pat.clone(),
ty: *ty.clone(),
attrs: punct.attrs,
default_value,
})
}
pub fn inner_span(&self) -> proc_macro2::Span {
self.pat.span()
}
}
#[cfg(test)]
mod tests {
use super::*;
use proc_macro2::Span;
use quote::quote;
use syn::{punctuated::Punctuated, token::Comma, FnArg};
#[test]
fn test_init_params() {
let default_ident =
syn::Ident::new(crate::DEFAULT_HELPER_ATTR, proc_macro2::Span::call_site());
let tokens = vec![
quote! { a: i32 },
quote! { b: u8 },
quote! { #[#default_ident] c: usize },
quote! { #[#default_ident(42)] d: i64 },
];
let punct: Punctuated<FnArg, Comma> = tokens
.into_iter()
.map(|t| syn::parse2::<FnArg>(t).unwrap())
.collect();
let params = FunctionParams::from_punctuated(punct).unwrap();
assert_eq!(params.params.len(), 4);
}
#[test]
fn test_first_invalid_param() {
let default_attr = syn::Ident::new(crate::DEFAULT_HELPER_ATTR, Span::call_site());
let item_struct = quote! {
fn item(
x: i32,
#[#default_attr] y: i32,
z: i32
) {
}
};
let item_fn: syn::ItemFn = syn::parse2(item_struct).unwrap();
let fields = FunctionParams::from_punctuated(item_fn.sig.inputs).unwrap();
let first_invalid = fields.first_invalid_param();
println!("first invalid: {:?}", first_invalid);
match first_invalid {
Some(iv) => {
assert_eq!(*iv, fields.params[2]);
}
None => panic!("last field must be invalid"),
}
}
}