use crate::generics::{clause_to_toks, WhereClause};
use crate::{ForDeref, SimplePath};
use proc_macro2::{Span, TokenStream as Toks};
use proc_macro_error::emit_error;
use quote::{quote, TokenStreamExt};
use syn::spanned::Spanned;
use syn::token::Comma;
use syn::{
parse2, Field, Fields, Ident, Index, Item, ItemStruct, Member, Path, PathArguments, Token,
};
mod impl_misc;
mod impl_using;
pub use impl_misc::*;
pub use impl_using::*;
pub const STD_IMPLS: &[&dyn ImplTrait] = &[
&ImplClone,
&ImplCopy,
&ImplDebug,
&ImplDefault,
&ImplPartialEq,
&ImplEq,
&ImplPartialOrd,
&ImplOrd,
&ImplHash,
&ImplBorrow,
&ImplBorrowMut,
&ImplAsRef,
&ImplAsMut,
&ImplDeref,
&ImplDerefMut,
];
pub trait ImplTrait {
fn path(&self) -> SimplePath;
fn support_path_arguments(&self) -> bool {
false
}
fn support_ignore(&self) -> bool {
false
}
fn allow_ignore_with(&self) -> Option<SimplePath> {
None
}
fn support_using(&self) -> bool {
false
}
fn struct_impl(&self, item: &ItemStruct, args: &ImplArgs) -> Result<Toks> {
let type_ident = &item.ident;
let (impl_generics, ty_generics, item_wc) = item.generics.split_for_impl();
let (path, items) = self.struct_items(item, args)?;
let wc = clause_to_toks(&args.clause, item_wc, &path);
Ok(quote! {
#[automatically_derived]
impl #impl_generics #path for #type_ident #ty_generics #wc {
#items
}
})
}
fn struct_items(&self, item: &ItemStruct, args: &ImplArgs) -> Result<(Toks, Toks)>;
}
#[allow(non_camel_case_types)]
mod kw {
use syn::custom_keyword;
custom_keyword!(ignore);
custom_keyword!(using);
}
pub enum Attr {
ForDeref(ForDeref),
ImplTraits(ImplTraits),
}
pub struct ImplTraits {
targets: Vec<Path>,
args: ImplArgs,
}
pub enum Error {
RequireUsing,
CallSite(&'static str),
WithSpan(Span, &'static str),
PathArguments(&'static str),
}
pub type Result<T> = std::result::Result<T, Error>;
mod parsing {
use super::*;
use syn::parse::{Parse, ParseStream, Result};
impl Parse for Attr {
fn parse(input: ParseStream) -> Result<Self> {
let mut empty_or_trailing = true;
let mut lookahead = input.lookahead1();
if lookahead.peek(Token![for]) {
return input.call(ForDeref::parse).map(Attr::ForDeref);
}
let mut targets = Vec::new();
let mut using = None;
let mut ignores = Vec::new();
let mut clause = None;
while !input.is_empty() {
if lookahead.peek(Token![where])
|| lookahead.peek(kw::using)
|| lookahead.peek(kw::ignore)
{
break;
}
if empty_or_trailing {
if lookahead.peek(Ident) {
targets.push(input.parse()?);
empty_or_trailing = false;
lookahead = input.lookahead1();
continue;
}
} else if input.peek(Comma) {
let _ = input.parse::<Comma>()?;
empty_or_trailing = true;
lookahead = input.lookahead1();
continue;
}
return Err(lookahead.error());
}
while !input.is_empty() {
lookahead = input.lookahead1();
if clause.is_none() && using.is_none() && lookahead.peek(kw::using) {
let _: kw::using = input.parse()?;
let _ = input.parse::<Token![self]>()?;
let _ = input.parse::<Token![.]>()?;
using = Some(input.parse()?);
} else if clause.is_none() && ignores.is_empty() && lookahead.peek(kw::ignore) {
let _: kw::ignore = input.parse()?;
let _ = input.parse::<Token![self]>()?;
let _ = input.parse::<Token![.]>()?;
ignores.push(input.parse()?);
while input.peek(Comma) {
let _ = input.parse::<Comma>()?;
if input.peek(Token![self]) {
let _ = input.parse::<Token![self]>()?;
let _ = input.parse::<Token![.]>()?;
ignores.push(input.parse()?);
continue;
}
break;
}
} else if lookahead.peek(Token![where]) {
clause = Some(input.parse()?);
} else {
return Err(lookahead.error());
}
}
let args = ImplArgs {
path_arguments: PathArguments::None,
ignores,
using,
clause,
};
Ok(Attr::ImplTraits(ImplTraits { targets, args }))
}
}
}
impl ImplTraits {
pub fn expand(
self,
item: Toks,
find_impl: impl Fn(&Path) -> Option<&'static dyn ImplTrait>,
) -> Toks {
let ImplTraits {
mut targets,
mut args,
} = self;
let item = match parse2::<Item>(item) {
Ok(Item::Struct(item)) => item,
Ok(item) => {
emit_error!(item, "expected struct");
return Toks::new();
}
Err(err) => {
emit_error!(err);
return Toks::new();
}
};
let mut not_supporting_ignore = vec![];
let mut not_supporting_using = vec![];
let mut impl_targets: Vec<(Span, _, _)> = Vec::with_capacity(targets.len());
for mut target in targets.drain(..) {
let target_span = target.span();
let path_args = target
.segments
.last_mut()
.map(|seg| std::mem::take(&mut seg.arguments))
.unwrap_or(PathArguments::None);
let target_impl = match find_impl(&target) {
Some(impl_) => impl_,
None => {
emit_error!(target, "unsupported trait");
return Toks::new();
}
};
if !target_impl.support_ignore() {
let except_with = target_impl.allow_ignore_with();
not_supporting_ignore.push((target.clone(), except_with));
}
if !target_impl.support_using() {
not_supporting_using.push(target.clone());
}
if !(path_args.is_empty() || target_impl.support_path_arguments()) {
emit_error!(
target_span,
"target {} does not support path arguments",
target_impl.path()
);
}
impl_targets.push((target.span(), target_impl, path_args));
}
if !args.ignores.is_empty() {
for (target, except_with) in not_supporting_ignore.into_iter() {
if let Some(path) = except_with {
if impl_targets
.iter()
.any(|(_, target_impl, _)| path == target_impl.path())
{
continue;
}
}
emit_error!(target, "target does not support `ignore`",);
}
}
if args.using.is_some() {
for target in not_supporting_using.into_iter() {
emit_error!(target, "target does not support `using`",);
}
}
fn check_is_field(mem: &Member, fields: &Fields) {
match (fields, mem) {
(Fields::Named(fields), Member::Named(ref ident)) => {
if fields
.named
.iter()
.any(|field| field.ident.as_ref() == Some(ident))
{
return;
}
}
(Fields::Unnamed(fields), Member::Unnamed(index)) => {
if (index.index as usize) < fields.unnamed.len() {
return;
}
}
_ => (),
}
emit_error!(mem, "not a struct field");
}
let mut toks = Toks::new();
for mem in &args.ignores {
check_is_field(mem, &item.fields);
}
if let Some(mem) = args.using_member() {
check_is_field(mem, &item.fields);
}
for (span, target, path_args) in impl_targets.drain(..) {
let path_args_span = path_args.span();
args.path_arguments = path_args;
match target.struct_impl(&item, &args) {
Ok(items) => toks.append_all(items),
Err(error) => match error {
Error::RequireUsing => {
emit_error!(span, "target requires argument `using self.FIELD`")
}
Error::CallSite(msg) => emit_error!(span, msg),
Error::WithSpan(span, msg) => emit_error!(span, msg),
Error::PathArguments(msg) => emit_error!(path_args_span, msg),
},
}
}
toks
}
}
pub struct ImplArgs {
pub path_arguments: PathArguments,
pub ignores: Vec<Member>,
pub using: Option<Member>,
pub clause: Option<WhereClause>,
}
impl ImplArgs {
pub fn ignore(&self, member: &Member) -> bool {
self.ignores.iter().any(|ig| *ig == *member)
}
pub fn ignore_named(&self, ident: &Ident) -> bool {
self.ignores.iter().any(|ig| match ig {
Member::Named(m) => m == ident,
_ => false,
})
}
pub fn ignore_unnamed(&self, index: &Index) -> bool {
self.ignores.iter().any(|ig| match ig {
Member::Unnamed(m) => m == index,
_ => false,
})
}
pub fn using_member(&self) -> Option<&Member> {
self.using.as_ref()
}
pub fn using_field<'b>(&self, fields: &'b Fields) -> Option<&'b Field> {
match fields {
Fields::Named(fields) => fields.named.iter().find(|field| match self.using {
Some(Member::Named(ref ident)) => ident == field.ident.as_ref().unwrap(),
_ => false,
}),
Fields::Unnamed(fields) => {
fields
.unnamed
.iter()
.enumerate()
.find_map(|(i, field)| match self.using {
Some(Member::Unnamed(ref index)) => {
(*index == Index::from(i)).then(|| field)
}
_ => None,
})
}
Fields::Unit => None,
}
}
pub fn for_fields<'f>(&self, fields: &'f Fields, f: impl FnMut(Member, &'f Field)) {
self.for_fields_iter(fields.iter().enumerate(), f);
}
pub fn for_fields_iter<'f>(
&self,
fields: impl Iterator<Item = (usize, &'f Field)>,
mut f: impl FnMut(Member, &'f Field),
) {
for (i, field) in fields {
let member = match field.ident.clone() {
Some(ident) => Member::Named(ident),
None => Member::Unnamed(Index::from(i)),
};
if !self.ignore(&member) {
f(member, field);
}
}
}
}