use proc_macro2::Span;
use quote::quote;
use syn::{
parse, parse_quote, punctuated::Punctuated, spanned::Spanned, Arm, BinOp, Block, Data,
DeriveInput, Expr, ExprBinary, ExprBlock, Field, FieldPat, Fields, Ident, Index, Member, Pat,
PatIdent, PatRest, PatStruct, PatTuple, Path, Stmt, Token,
};
#[proc_macro_derive(TypeEq, attributes(not_type, use_eq, use_eq_ignore_span))]
pub fn derive_type_eq(item: proc_macro::TokenStream) -> proc_macro::TokenStream {
Deriver {
trait_name: Ident::new("TypeEq", Span::call_site()),
method_name: Ident::new("type_eq", Span::call_site()),
ignore_field: Box::new(|field| {
for attr in &field.attrs {
if attr.path().is_ident("not_type") {
return true;
}
}
false
}),
}
.derive(item)
}
#[proc_macro_derive(EqIgnoreSpan, attributes(not_spanned, use_eq))]
pub fn derive_eq_ignore_span(item: proc_macro::TokenStream) -> proc_macro::TokenStream {
Deriver {
trait_name: Ident::new("EqIgnoreSpan", Span::call_site()),
method_name: Ident::new("eq_ignore_span", Span::call_site()),
ignore_field: Box::new(|_field| {
false
}),
}
.derive(item)
}
struct Deriver {
trait_name: Ident,
method_name: Ident,
ignore_field: Box<dyn Fn(&Field) -> bool>,
}
impl Deriver {
fn derive(&self, item: proc_macro::TokenStream) -> proc_macro::TokenStream {
let input: DeriveInput = parse(item).unwrap();
let body = self.make_body(&input.data);
let trait_name = &self.trait_name;
let ty = &input.ident;
let method_name = &self.method_name;
quote!(
#[automatically_derived]
impl ::swc_common::#trait_name for #ty {
#[allow(non_snake_case)]
fn #method_name(&self, other: &Self) -> bool {
#body
}
}
)
.into()
}
fn make_body(&self, data: &Data) -> Expr {
match data {
Data::Struct(s) => {
let arm = self.make_arm_from_fields(parse_quote!(Self), &s.fields);
parse_quote!(match (self, other) { #arm })
}
Data::Enum(e) => {
let mut arms = Punctuated::<_, Token![,]>::default();
for v in &e.variants {
let vi = &v.ident;
let arm = self.make_arm_from_fields(parse_quote!(Self::#vi), &v.fields);
arms.push(arm);
}
arms.push(parse_quote!(_ => false));
parse_quote!(match (self, other) { #arms })
}
Data::Union(_) => unimplemented!("union"),
}
}
fn make_arm_from_fields(&self, pat_path: Path, fields: &Fields) -> Arm {
let mut l_pat_fields = Punctuated::<_, Token![,]>::default();
let mut r_pat_fields = Punctuated::<_, Token![,]>::default();
let mut exprs = Vec::new();
for (i, field) in fields
.iter()
.enumerate()
.filter(|(_, f)| !(self.ignore_field)(f))
{
let method_name =
if field.attrs.iter().any(|attr| {
attr.path().is_ident("not_spanned") || attr.path().is_ident("use_eq")
}) {
Ident::new("eq", Span::call_site())
} else if field
.attrs
.iter()
.any(|attr| attr.path().is_ident("use_eq_ignore_span"))
{
Ident::new("eq_ignore_span", Span::call_site())
} else {
self.method_name.clone()
};
let base = field
.ident
.clone()
.unwrap_or_else(|| Ident::new(&format!("_{}", i), field.ty.span()));
let l_binding_ident = Ident::new(&format!("_l_{}", base), base.span());
let r_binding_ident = Ident::new(&format!("_r_{}", base), base.span());
let make_pat_field = |ident: &Ident| FieldPat {
attrs: Default::default(),
member: match &field.ident {
Some(v) => Member::Named(v.clone()),
None => Member::Unnamed(Index {
index: i as _,
span: field.ty.span(),
}),
},
colon_token: Some(Token![:](ident.span())),
pat: Box::new(Pat::Ident(PatIdent {
attrs: Default::default(),
by_ref: Some(Token![ref](ident.span())),
mutability: None,
ident: ident.clone(),
subpat: None,
})),
};
l_pat_fields.push(make_pat_field(&l_binding_ident));
r_pat_fields.push(make_pat_field(&r_binding_ident));
exprs.push(parse_quote!(#l_binding_ident.#method_name(#r_binding_ident)));
}
let mut expr: Expr = parse_quote!(true);
for expr_el in exprs {
expr = Expr::Binary(ExprBinary {
attrs: Default::default(),
left: Box::new(expr),
op: BinOp::And(Token![&&](Span::call_site())),
right: Box::new(expr_el),
});
}
Arm {
attrs: Default::default(),
pat: Pat::Tuple(PatTuple {
attrs: Default::default(),
paren_token: Default::default(),
elems: {
let mut elems = Punctuated::default();
elems.push(Pat::Struct(PatStruct {
attrs: Default::default(),
qself: None,
path: pat_path.clone(),
brace_token: Default::default(),
fields: l_pat_fields,
rest: Some(PatRest {
attrs: Default::default(),
dot2_token: Token![..](Span::call_site()),
}),
}));
elems.push(Pat::Struct(PatStruct {
attrs: Default::default(),
qself: None,
path: pat_path,
brace_token: Default::default(),
fields: r_pat_fields,
rest: Some(PatRest {
attrs: Default::default(),
dot2_token: Token![..](Span::call_site()),
}),
}));
elems
},
}),
guard: Default::default(),
fat_arrow_token: Token![=>](Span::call_site()),
body: Box::new(Expr::Block(ExprBlock {
attrs: Default::default(),
label: Default::default(),
block: Block {
brace_token: Default::default(),
stmts: vec![Stmt::Expr(expr, None)],
},
})),
comma: Default::default(),
}
}
}