use darling::{Error, FromMeta};
use proc_macro2::{Span, TokenStream};
use quote::ToTokens;
use syn::punctuated::{Pair, Punctuated};
use syn::token::Bracket;
use syn::{
AttrStyle, Attribute, BoundLifetimes, Expr, ExprLit, GenericArgument, GenericParam, Ident, Lit,
LitBool, LitStr, Meta, MetaNameValue, Pat, PathArguments, ReturnType, Token, Type, TypePath,
WhereClause,
};
use crate::model::FieldTransform;
#[derive(Debug, Clone, Copy)]
pub struct ImplGenerics<'a>(pub &'a Punctuated<GenericParam, Token![,]>);
#[derive(Debug, Clone, Copy)]
pub struct TypeGenerics<'a>(pub &'a Punctuated<GenericParam, Token![,]>);
#[derive(Debug, Clone, Copy)]
pub struct StructGenerics<'a>(pub &'a Punctuated<GenericParam, Token![,]>);
impl ToTokens for ImplGenerics<'_> {
fn to_tokens(&self, tokens: &mut TokenStream) {
for generic in self.0.pairs() {
match generic.into_value() {
GenericParam::Lifetime(param) => param.to_tokens(tokens),
GenericParam::Type(param) => {
param.ident.to_tokens(tokens);
param.colon_token.to_tokens(tokens);
param.bounds.to_tokens(tokens);
},
GenericParam::Const(param) => {
param.const_token.to_tokens(tokens);
param.ident.to_tokens(tokens);
param.colon_token.to_tokens(tokens);
param.ty.to_tokens(tokens);
},
}
<Token![,]>::default().to_tokens(tokens);
}
}
}
impl ToTokens for TypeGenerics<'_> {
fn to_tokens(&self, tokens: &mut TokenStream) {
for generic in self.0.pairs() {
match generic.into_value() {
GenericParam::Lifetime(param) => param.lifetime.to_tokens(tokens),
GenericParam::Type(param) => param.ident.to_tokens(tokens),
GenericParam::Const(param) => param.ident.to_tokens(tokens),
}
<Token![,]>::default().to_tokens(tokens);
}
}
}
impl ToTokens for StructGenerics<'_> {
fn to_tokens(&self, tokens: &mut TokenStream) {
for generic in self.0.pairs() {
generic.into_value().to_tokens(tokens);
<Token![,]>::default().to_tokens(tokens);
}
}
}
#[derive(Debug)]
pub struct AngleBracketedGenerics {
lt_token: Token![<],
params: Punctuated<GenericParam, Token![,]>,
gt_token: Token![>],
}
impl ToTokens for AngleBracketedGenerics {
fn to_tokens(&self, tokens: &mut TokenStream) {
self.lt_token.to_tokens(tokens);
self.params.to_tokens(tokens);
self.gt_token.to_tokens(tokens);
}
}
impl From<BoundLifetimes> for AngleBracketedGenerics {
fn from(value: BoundLifetimes) -> Self {
Self {
lt_token: value.lt_token,
params: value.lifetimes,
gt_token: value.gt_token,
}
}
}
#[derive(Debug)]
pub enum BoolOr<T> {
Bool(bool),
Value(T),
}
impl<T: FromMeta> FromMeta for BoolOr<T> {
fn from_expr(expr: &Expr) -> darling::Result<Self> {
if let Expr::Lit(ExprLit {
lit: Lit::Bool(LitBool { value, .. }),
..
}) = *expr
{
return Ok(BoolOr::Bool(value));
}
T::from_expr(expr).map(BoolOr::Value)
}
}
#[derive(Default, Debug)]
pub struct AnyItem(());
impl FromMeta for AnyItem {
fn from_meta(_item: &Meta) -> darling::Result<Self> {
Ok(Self(()))
}
}
pub fn into_write_errors(acc: darling::error::Accumulator) -> TokenStream {
Error::multiple(acc.into_inner()).write_errors()
}
pub fn iter_doc_attrs(attrs: &[Attribute]) -> impl Iterator<Item = &Attribute> {
attrs.iter().filter(|f| f.path().is_ident("doc"))
}
pub fn doc_str_attr(lit: &str) -> Attribute {
Attribute {
pound_token: <Token![#]>::default(),
style: AttrStyle::Outer,
bracket_token: Bracket::default(),
meta: Meta::NameValue(MetaNameValue {
path: simple_ident("doc").into(),
eq_token: <Token![=]>::default(),
value: lit_str_expr(lit),
}),
}
}
pub fn find_deprecated(attrs: &[Attribute]) -> Option<&Attribute> {
attrs.iter().find(|a| a.path().is_ident("deprecated"))
}
pub fn allow_deprecated(attr: Option<&Attribute>) -> Option<TokenStream> {
attr.map(|_| quote::quote! { #[allow(deprecated)] })
}
pub fn simple_ident(ident: &str) -> Ident {
Ident::new(ident, Span::call_site())
}
fn first_punct<T, P>(p: &Punctuated<T, P>) -> Option<&T> {
p.pairs().next().map(|p| p.into_value())
}
fn last_punct<T, P>(p: &Punctuated<T, P>) -> Option<&T> {
p.pairs().next_back().map(|p| p.into_value())
}
pub fn first_generic_arg(mut ty: &Type) -> Option<&Type> {
fn inner(ty: &TypePath) -> Option<&Type> {
let seg = last_punct(&ty.path.segments)?;
if let PathArguments::AngleBracketed(a) = &seg.arguments
&& let GenericArgument::Type(ty) = first_punct(&a.args)?
{
Some(ty)
} else {
None
}
}
loop {
match ty {
Type::Group(t) => ty = &t.elem,
Type::Paren(t) => ty = &t.elem,
Type::Path(t) => break inner(t),
_ => break None,
}
}
}
pub fn empty_where_clause() -> WhereClause {
WhereClause {
where_token: <Token![where]>::default(),
predicates: Punctuated::new(),
}
}
pub fn lit_str_expr(lit: &str) -> Expr {
Expr::Lit(ExprLit {
lit: Lit::Str(LitStr::new(lit, Span::call_site())),
attrs: Vec::new(),
})
}
pub fn peel_boxed_expr(mut expr: Box<Expr>) -> Box<Expr> {
loop {
match *expr {
Expr::Group(g) => expr = g.expr,
Expr::Paren(g) => expr = g.expr,
_ => break expr,
}
}
}
pub fn to_field_transform(
value: Box<Expr>,
acc: &mut darling::error::Accumulator,
) -> Box<FieldTransform> {
let value = peel_boxed_expr(value);
let Expr::Closure(value) = *value else {
let transform = FieldTransform {
lifetimes: None,
inputs: syn::parse_quote!(invalid_setter: _),
body: syn::parse_quote!(invalid_setter),
};
acc.push(Error::custom("expected closure").with_span(&*value));
return Box::new(transform);
};
for attr in &value.attrs {
let err = Error::custom("closure must not have attributes");
acc.push(err.with_span(attr));
}
if let Some(constness) = &value.constness {
let err = Error::custom("closure must not have constness, it is implicitly const here");
acc.push(err.with_span(constness));
}
if let Some(movability) = &value.movability {
let err = Error::custom("closure must not be static");
acc.push(err.with_span(movability));
}
if let Some(asyncness) = &value.asyncness {
let err = Error::custom("closure must not be async");
acc.push(err.with_span(asyncness));
}
if let Some(capture) = &value.capture {
let err = Error::custom("closure cannot have captures");
acc.push(err.with_span(capture));
}
if !matches!(value.output, ReturnType::Default) {
let err = Error::custom("closure must not have an explicit return type");
acc.push(err.with_span(&value.output));
}
let inputs = value
.inputs
.into_pairs()
.map(|pat| {
let (pat, punct) = pat.into_tuple();
let pat = match pat {
Pat::Type(pat_type) => pat_type,
pat => {
let err = Error::custom("closure inputs must all have an explicit type");
acc.push(err.with_span(&pat));
syn::parse_quote!(#pat: _)
},
};
Pair::new(pat, punct)
})
.collect();
let lifetimes = value.lifetimes.map(AngleBracketedGenerics::from);
let body = value.body;
Box::new(FieldTransform {
lifetimes,
inputs,
body,
})
}