use crate::{def_site, is_attr_name, syn_ext::PairExt};
use pmutil::prelude::*;
use proc_macro2::Span;
use quote::{ToTokens, __rt::TokenStream};
use syn::{
punctuated::Pair,
token::{Mut, Ref},
*,
};
#[derive(Debug, Clone)]
pub struct Binder<'a> {
ident: &'a Ident,
body: &'a Data,
attrs: &'a [Attribute],
}
impl<'a> Binder<'a> {
pub const fn new(ident: &'a Ident, body: &'a Data, attrs: &'a [Attribute]) -> Self {
Binder { ident, body, attrs }
}
pub fn new_from(input: &'a DeriveInput) -> Self {
Self::new(&input.ident, &input.data, &input.attrs)
}
pub fn variants(&self) -> Vec<VariantBinder<'a>> {
match *self.body {
Data::Enum(DataEnum { ref variants, .. }) => {
let enum_name = &self.ident;
variants
.iter()
.map(|v| VariantBinder::new(Some(enum_name), &v.ident, &v.fields, &v.attrs))
.collect()
}
Data::Struct(DataStruct { ref fields, .. }) => {
vec![VariantBinder::new(None, &self.ident, fields, self.attrs)]
}
Data::Union(_) => unimplemented!("Binder for union type"),
}
}
}
#[derive(Debug, Clone)]
pub struct VariantBinder<'a> {
enum_name: Option<&'a Ident>,
name: &'a Ident,
data: &'a Fields,
attrs: &'a [Attribute],
}
impl<'a> VariantBinder<'a> {
pub const fn new(
enum_name: Option<&'a Ident>,
name: &'a Ident,
data: &'a Fields,
attrs: &'a [Attribute],
) -> Self {
VariantBinder {
enum_name,
name,
data,
attrs,
}
}
pub const fn variant_name(&self) -> &Ident {
self.name
}
pub const fn data(&self) -> &Fields {
self.data
}
pub const fn attrs(&self) -> &[Attribute] {
self.attrs
}
pub fn qual_path(&self) -> Path {
match self.enum_name {
Some(enum_name) => Quote::new(def_site::<Span>())
.quote_with(smart_quote!(
Vars {
EnumName: enum_name,
VariantName: self.name,
},
{ EnumName::VariantName }
))
.parse(),
None => self.name.clone().into(),
}
}
pub fn bind(
&self,
prefix: &str,
by_ref: Option<Ref>,
mutability: Option<Mut>,
) -> (Pat, Vec<BindedField<'a>>) {
let path = self.qual_path();
let (pat, bindings) = match *self.data {
Fields::Unit => {
let pat = Pat::Path(PatPath {
qself: None,
path,
attrs: Default::default(),
});
(pat, vec![])
}
Fields::Named(FieldsNamed {
named: ref fields,
brace_token,
}) => {
let mut bindings = vec![];
let fields = fields
.pairs()
.map(|e| {
let (t, p) = e.into_tuple();
Pair::new(t, p.cloned())
})
.enumerate()
.map(|(idx, f)| {
f.map_item(|f| {
let ident = f
.ident
.clone()
.expect("field of struct-like variants should have name");
let binded_ident = ident.new_ident_with(|s| format!("{}{}", prefix, s));
bindings.push(BindedField {
idx,
binded_ident: binded_ident.clone(),
field: f,
});
FieldPat {
attrs: f
.attrs
.iter()
.filter(|attr| is_attr_name(attr, "cfg"))
.cloned()
.collect(),
colon_token: f.colon_token,
member: Member::Named(ident),
pat: Box::new(Pat::Ident(PatIdent {
by_ref,
mutability,
ident: binded_ident,
subpat: None,
attrs: Default::default(),
})),
}
})
})
.collect();
let pat = Pat::Struct(PatStruct {
path,
fields,
brace_token,
dot2_token: None,
attrs: Default::default(),
});
(pat, bindings)
}
Fields::Unnamed(FieldsUnnamed {
unnamed: ref fields,
paren_token,
}) => {
let mut bindings = vec![];
let pats = fields
.pairs()
.map(|e| {
let (t, p) = e.into_tuple();
Pair::new(t, p.cloned())
})
.enumerate()
.map(|(idx, f)| {
f.map_item(|f| {
let binded_ident =
def_site::<Span>().new_ident(format!("{}{}", prefix, idx));
bindings.push(BindedField {
idx,
binded_ident: binded_ident.clone(),
field: f,
});
Pat::Ident(PatIdent {
by_ref,
mutability,
ident: binded_ident,
subpat: None,
attrs: Default::default(),
})
})
})
.collect();
let pat = Pat::TupleStruct(PatTupleStruct {
path,
pat: PatTuple {
elems: pats,
paren_token,
attrs: Default::default(),
},
attrs: Default::default(),
});
(pat, bindings)
}
};
(pat, bindings)
}
}
#[derive(Debug, Clone)]
pub struct BindedField<'a> {
binded_ident: Ident,
idx: usize,
field: &'a Field,
}
impl<'a> BindedField<'a> {
pub const fn idx(&self) -> usize {
self.idx
}
pub const fn name(&self) -> &Ident {
&self.binded_ident
}
pub const fn field(&self) -> &Field {
self.field
}
}
impl<'a> ToTokens for BindedField<'a> {
fn to_tokens(&self, t: &mut TokenStream) {
self.binded_ident.to_tokens(t)
}
}