#![deny(unsafe_code)]
#![cfg_attr(feature = "nightly", feature(allow_internal_unstable))]
#![allow(clippy::tabs_in_doc_comments)]
#![warn(clippy::cargo, clippy::missing_docs_in_private_items)]
#![cfg_attr(doc, warn(rustdoc::all), allow(rustdoc::missing_doc_code_examples))]
mod attr;
mod data;
mod error;
mod input;
mod item;
#[cfg(test)]
mod test;
mod trait_;
mod util;
use std::{borrow::Cow, iter};
use proc_macro2::TokenStream;
use quote::{quote, quote_spanned, ToTokens};
use syn::{
spanned::Spanned, Attribute, DataEnum, DataStruct, DataUnion, DeriveInput, Fields, FieldsNamed,
FieldsUnnamed, Generics, Lit, Meta, NestedMeta, Path, Variant,
};
#[cfg(feature = "zeroize")]
use self::attr::ZeroizeFqs;
use self::{
attr::{Default, DeriveTrait, DeriveWhere, FieldAttr, ItemAttr, Skip, SkipGroup, VariantAttr},
data::{Data, DataType, Field, SimpleType},
error::Error,
input::Input,
item::Item,
trait_::{Trait, TraitImpl},
util::Either,
};
const DERIVE_WHERE: &str = "derive_where";
const DERIVE_WHERE_FORWARD: &str = "DeriveWhere";
const DERIVE_WHERE_VISITED: &str = "derive_where_visited";
#[proc_macro_attribute]
pub fn derive_where(
attr: proc_macro::TokenStream,
original_input: proc_macro::TokenStream,
) -> proc_macro::TokenStream {
let attr = TokenStream::from(attr);
let mut original_input = TokenStream::from(original_input);
let mut input = quote_spanned! { attr.span()=> #[derive_where(#attr)] };
input.extend(original_input.clone());
match syn::parse2::<DeriveInput>(input) {
Ok(input) => match derive_where_internal(input.clone()) {
Ok(item) => item.into(),
Err(error) => {
let mut clean_input =
input_without_derive_where_attributes(input).into_token_stream();
clean_input.extend(error.into_compile_error());
clean_input.into()
}
},
Err(error) => {
original_input.extend(error.into_compile_error());
original_input.into()
}
}
}
fn derive_where_internal(mut item: DeriveInput) -> Result<TokenStream, syn::Error> {
let mut crate_ = None;
for attr in &item.attrs {
if attr.path.is_ident(DERIVE_WHERE) {
if let Ok(Meta::List(list)) = attr.parse_meta() {
if list.nested.len() == 1 {
if let NestedMeta::Meta(meta) = list
.nested
.into_iter()
.next()
.expect("unexpected empty list")
{
if meta.path().is_ident("crate") {
if let Meta::NameValue(name_value) = meta {
if let Lit::Str(lit_str) = &name_value.lit {
match lit_str.parse::<Path>() {
Ok(path) => {
if path == util::path_from_strs(&[DERIVE_WHERE]) {
return Err(Error::path_unnecessary(
path.span(),
&format!("::{}", DERIVE_WHERE),
));
}
match crate_ {
Some(_) => {
return Err(Error::option_duplicate(
name_value.span(),
"crate",
))
}
None => crate_ = Some(path),
}
}
Err(error) => {
return Err(Error::path(lit_str.span(), error))
}
}
} else {
return Err(Error::option_syntax(name_value.lit.span()));
}
} else {
return Err(Error::option_syntax(meta.span()));
}
}
}
}
}
}
}
let crate_ = crate_.unwrap_or_else(|| util::path_from_strs(&[DERIVE_WHERE]));
let derive_where_visited =
util::path_from_root_and_strs(crate_.clone(), &[DERIVE_WHERE_VISITED]);
for attr in &item.attrs {
if attr.path == derive_where_visited {
return Err(Error::visited(attr.span()));
}
}
item.attrs
.push(syn::parse_quote! { #[#derive_where_visited] });
let derive_where = util::path_from_root_and_strs(crate_, &[DERIVE_WHERE_FORWARD]);
let mut output = quote! { #[derive(#derive_where)] };
output.extend(item.into_token_stream());
Ok(output)
}
#[doc(hidden)]
#[proc_macro_derive(DeriveWhere, attributes(derive_where))]
#[cfg_attr(feature = "nightly", allow_internal_unstable(core_intrinsics))]
pub fn derive_where_actual(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let input = TokenStream::from(input);
let item = match syn::parse2::<DeriveInput>(input) {
Ok(item) => item,
Err(error) => {
return error.into_compile_error().into();
}
};
let span = {
let clean_item = DeriveInput {
attrs: Vec::new(),
vis: item.vis.clone(),
ident: item.ident.clone(),
generics: item.generics.clone(),
data: item.data.clone(),
};
clean_item.span()
};
match { Input::from_input(span, &item) } {
Ok(Input {
derive_wheres,
generics,
item,
}) => derive_wheres
.iter()
.flat_map(|derive_where| iter::repeat(derive_where).zip(&derive_where.traits))
.map(|(derive_where, trait_)| generate_impl(derive_where, trait_, &item, generics))
.collect::<TokenStream>()
.into(),
Err(error) => error.into_compile_error().into(),
}
}
#[doc(hidden)]
#[proc_macro_attribute]
pub fn derive_where_visited(
_attr: proc_macro::TokenStream,
input: proc_macro::TokenStream,
) -> proc_macro::TokenStream {
input
}
fn generate_impl(
derive_where: &DeriveWhere,
trait_: &DeriveTrait,
item: &Item,
generics: &Generics,
) -> TokenStream {
let (impl_generics, type_generics, where_clause) = generics.split_for_impl();
let mut where_clause = where_clause.map(Cow::Borrowed);
derive_where.where_clause(&mut where_clause, trait_, item);
let body = generate_body(&derive_where.traits, trait_, item);
let ident = item.ident();
let path = trait_.impl_path(trait_);
let mut output = quote! {
impl #impl_generics #path for #ident #type_generics
#where_clause
{
#body
}
};
if let Some((path, body)) = trait_.additional_impl(trait_) {
output.extend(quote! {
impl #impl_generics #path for #ident #type_generics
#where_clause
{
#body
}
})
}
output
}
fn generate_body(traits: &[DeriveTrait], trait_: &DeriveTrait, item: &Item) -> TokenStream {
match &item {
Item::Item(data) => {
let body = trait_.build_body(traits, trait_, data);
trait_.build_signature(item, traits, trait_, &body)
}
Item::Enum { variants, .. } => {
let body: TokenStream = variants
.iter()
.map(|data| trait_.build_body(traits, trait_, data))
.collect();
trait_.build_signature(item, traits, trait_, &body)
}
}
}
fn input_without_derive_where_attributes(mut input: DeriveInput) -> DeriveInput {
use syn::Data;
let DeriveInput { data, attrs, .. } = &mut input;
fn remove_derive_where(attrs: &mut Vec<Attribute>) {
attrs.retain(|attr| !attr.path.is_ident(DERIVE_WHERE))
}
fn remove_derive_where_from_fields_named(fields: &mut FieldsNamed) {
let FieldsNamed { named, .. } = fields;
named
.iter_mut()
.for_each(|field| remove_derive_where(&mut field.attrs))
}
fn remove_derive_where_from_fields(fields: &mut Fields) {
match fields {
Fields::Named(fields) => remove_derive_where_from_fields_named(fields),
Fields::Unnamed(FieldsUnnamed { unnamed, .. }) => unnamed
.iter_mut()
.for_each(|field| remove_derive_where(&mut field.attrs)),
Fields::Unit => (),
}
}
remove_derive_where(attrs);
match data {
Data::Struct(DataStruct { fields, .. }) => remove_derive_where_from_fields(fields),
Data::Enum(DataEnum { variants, .. }) => {
variants
.iter_mut()
.for_each(|Variant { attrs, fields, .. }| {
remove_derive_where(attrs);
remove_derive_where_from_fields(fields)
})
}
Data::Union(DataUnion { fields, .. }) => remove_derive_where_from_fields_named(fields),
}
input
}