extern crate proc_macro;
use proc_macro::TokenStream;
use proc_macro2::{Span, TokenTree};
use proc_macro_error::{abort_call_site, proc_macro_error};
use quote::{format_ident, quote};
use std::{iter::FromIterator, str::FromStr};
use syn::{
parse_macro_input, punctuated::Punctuated, Attribute, Data, DeriveInput, Fields, Ident, Lit,
Meta, MetaList, MetaNameValue, NestedMeta, Path, PredicateType, Token, TraitBound,
TraitBoundModifier, Type, TypeParamBound, WherePredicate,
};
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
enum FieldType {
Ordered,
Unordered,
UnorderedDelta,
Scalar,
Delta,
}
const VALID_FIELD_TYPES: &str =
"\"ordered\", \"unordered\", \"unordered-delta\", \"delta\", or \"scalar\"";
type Field = (String, Type, FieldType, String);
type ParsedField = (String, Type, ParsedAttrs);
type ParsedAttrs = Result<(Option<FieldType>, String), FieldTypeError>;
#[proc_macro_derive(Delta, attributes(delta_struct))]
#[proc_macro_error]
pub fn derive_delta(input: TokenStream) -> TokenStream {
let DeriveInput {
attrs,
vis,
ident,
mut generics,
data,
} = parse_macro_input!(input as DeriveInput);
let (default_field_type, delta_leader) =
match get_fieldtype_from_attrs(attrs.into_iter(), "default") {
Ok((v, delta_leader)) => (v.unwrap_or(FieldType::Scalar), delta_leader),
Err(_) => {
abort_call_site!(
"delta_struct(default = ...) for {} is not an accepted value, expected {}.",
ident,
VALID_FIELD_TYPES
);
}
};
let (named, fields) = match data {
Data::Struct(strukt) => match strukt.fields {
Fields::Named(named) => (
true,
collect_results(
named.named.into_iter().map(|field| {
(
field.ident.unwrap().to_string(),
field.ty,
get_fieldtype_from_attrs(field.attrs.into_iter(), "field_type"),
)
}),
default_field_type,
),
),
Fields::Unnamed(unnamed) => (
false,
collect_results(
unnamed.unnamed.into_iter().enumerate().map(|(i, field)| {
(
i.to_string(),
field.ty,
get_fieldtype_from_attrs(field.attrs.into_iter(), "field_type"),
)
}),
default_field_type,
),
),
Fields::Unit => (false, Ok(vec![])),
},
_ => {
abort_call_site!(
"delta_struct::Delta may only be derived for struct types currently. {} is not a struct type."
, ident)
}
};
let fields = match fields {
Ok(fields) => fields,
Err(bad_fields) => {
let bad_fields = format!("{:?}", bad_fields);
abort_call_site!(
"delta_struct(field_type = ...) for fields in {}: {} are not valid values. Expected {}.",
ident,
bad_fields,
VALID_FIELD_TYPES
)
}
};
let delta_leader = match proc_macro2::TokenStream::from_str(&delta_leader) {
Ok(v) => v,
Err(e) => {
abort_call_site!("error parsing delta leader as token stream {}", e);
}
};
let delta_ident = format_ident!("{}Delta", ident);
let delta_fields = delta_fields(named, fields.iter().cloned());
let og_where_clause = generics.where_clause.clone();
let (delta_compute_let, delta_compute_fields) =
delta_compute_fields(named, fields.iter().cloned());
let (delta_apply_let, delta_apply_actions) = delta_apply_fields(named, fields.into_iter());
let (delta_struct, delta_compute_init, delta_apply_pattern) = if named {
(
quote! {
#delta_leader
#vis struct #delta_ident #generics #og_where_clause {
#delta_fields
}
},
quote!(Self::Output { #delta_compute_fields }),
quote!(Self::Output { #delta_apply_let }),
)
} else {
(
quote! {
#delta_leader
#vis struct #delta_ident #generics (#delta_fields) #og_where_clause;
},
quote!(#delta_ident(#delta_compute_fields)),
quote!(#delta_ident(#delta_apply_let)),
)
};
let partial_eq_types = generics
.type_params()
.map(|t| t.ident.clone())
.collect::<Vec<_>>();
let where_clause = generics.make_where_clause();
for ty in partial_eq_types {
let mut bounds = Punctuated::new();
let mut segments = Punctuated::new();
segments.push(Ident::new("std", Span::call_site()).into());
segments.push(Ident::new("cmp", Span::call_site()).into());
segments.push(Ident::new("PartialEq", Span::call_site()).into());
bounds.push(TypeParamBound::Trait(TraitBound {
paren_token: None,
modifier: TraitBoundModifier::None,
lifetimes: None,
path: Path {
leading_colon: Some(Token!(::)(Span::call_site())),
segments,
},
}));
where_clause
.predicates
.push(WherePredicate::Type(PredicateType {
lifetimes: None,
bounded_ty: Type::Verbatim(<Ident as Into<TokenTree>>::into(ty).into()),
colon_token: Token!(:)(Span::call_site()),
bounds,
}));
}
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
let delta_impl = quote! {
impl #impl_generics Delta for #ident #ty_generics #where_clause {
type Output = #delta_ident #ty_generics;
fn delta(old: Self, new: Self) -> Option<Self::Output> {
let mut delta_is_some = false;
#delta_compute_let
if delta_is_some {
Some(#delta_compute_init)
} else {
None
}
}
fn apply_delta(&mut self, delta: Self::Output) {
let #delta_apply_pattern = delta;
#delta_apply_actions
}
}
};
let output = quote! {
#delta_struct
#delta_impl
};
TokenStream::from(output)
}
fn delta_fields(named: bool, iter: impl Iterator<Item = Field>) -> proc_macro2::TokenStream {
FromIterator::from_iter(iter.map(|(ident, ty, field_ty, field_leader)| {
let field_leader = proc_macro2::TokenStream::from_str(&field_leader).unwrap();
let declared_ty = match field_ty {
FieldType::Ordered => {
quote!(::delta_struct::SeqDelta<<#ty as ::std::iter::IntoIterator>::Item>)
}
FieldType::Unordered => {
quote!(::delta_struct::BagDelta<<#ty as ::std::iter::IntoIterator>::Item>)
}
FieldType::UnorderedDelta => {
let entry = quote!(<#ty as ::std::iter::IntoIterator>::Item);
let key = quote!(<#entry as ::delta_struct::MapEntry>::Key);
let value = quote!(<#entry as ::delta_struct::MapEntry>::Value);
quote!(::delta_struct::MapDelta<#key, #value, <#value as Delta>::Output>)
}
FieldType::Scalar => quote!(::std::option::Option<#ty>),
FieldType::Delta => quote!(::std::option::Option<<#ty as Delta>::Output>),
};
if named {
let ident = format_ident!("{}", ident);
quote! {
#field_leader
pub #ident: #declared_ty,
}
} else {
quote! {
#field_leader
pub #declared_ty,
}
}
}))
}
fn delta_compute_fields(
named: bool,
iter: impl Iterator<Item = Field>,
) -> (proc_macro2::TokenStream, proc_macro2::TokenStream) {
iter.map(|(og_ident, _ty, field_ty, _field_leader)| {
let ident = if named {
format_ident!("{}", og_ident)
} else {
format_ident!("field_{}", og_ident)
};
let og_ident: proc_macro2::TokenStream = FromStr::from_str(&og_ident).unwrap();
let statements = match field_ty {
FieldType::Ordered | FieldType::Unordered | FieldType::UnorderedDelta => {
let module = collection_module(field_ty);
quote! {
let #ident = ::delta_struct::#module::diff(old.#og_ident, new.#og_ident);
delta_is_some = delta_is_some || !#ident.is_empty();
}
}
FieldType::Scalar => quote! {
let #ident = if old.#og_ident != new.#og_ident {
delta_is_some = true;
Some(new.#og_ident)
} else {
None
};
},
FieldType::Delta => quote! {
let #ident = Delta::delta(old.#og_ident, new.#og_ident);
delta_is_some = delta_is_some || #ident.is_some();
},
};
(statements, quote!(#ident,))
})
.unzip()
}
fn delta_apply_fields(
named: bool,
iter: impl Iterator<Item = Field>,
) -> (proc_macro2::TokenStream, proc_macro2::TokenStream) {
iter.map(|(og_ident, _ty, field_ty, _field_leader)| {
let ident = if named {
format_ident!("{}", og_ident)
} else {
format_ident!("field_{}", og_ident)
};
let og_ident: proc_macro2::TokenStream = FromStr::from_str(&og_ident).unwrap();
let statements = match field_ty {
FieldType::Ordered | FieldType::Unordered | FieldType::UnorderedDelta => {
let module = collection_module(field_ty);
quote! {
::delta_struct::#module::apply(&mut self.#og_ident, #ident);
}
}
FieldType::Scalar => quote! {
if let Some(v) = #ident {
self.#og_ident = v;
}
},
FieldType::Delta => quote! {
if let Some(v) = #ident {
self.#og_ident.apply_delta(v);
}
},
};
(quote!(#ident,), statements)
})
.unzip()
}
fn collection_module(field_ty: FieldType) -> Ident {
match field_ty {
FieldType::Ordered => format_ident!("seq"),
FieldType::Unordered => format_ident!("bag"),
FieldType::UnorderedDelta => format_ident!("map"),
FieldType::Scalar | FieldType::Delta => {
unreachable!("{:?} is not a collection field type", field_ty)
}
}
}
#[allow(clippy::manual_try_fold)] fn collect_results(
iter: impl Iterator<Item = ParsedField>,
default_field_type: FieldType,
) -> Result<Vec<Field>, Vec<String>> {
iter.fold(Ok(vec![]), |v, i| match (v, i) {
(Ok(mut v), (ident, b, Ok((c, d)))) => {
v.push((ident, b, c.unwrap_or(default_field_type), d));
Ok(v)
}
(Ok(_), (ident, _, Err(_))) => Err(vec![ident]),
(Err(mut v), (ident, _, Err(_))) => {
v.push(ident);
Err(v)
}
(v @ Err(_), _) => v,
})
}
enum FieldTypeError {
UnrecognizedJunkFound,
}
#[allow(clippy::manual_try_fold)] fn get_fieldtype_from_attrs(iter: impl Iterator<Item = Attribute>, attr_name: &str) -> ParsedAttrs {
for attr in iter {
if let Ok(Meta::List(MetaList { path, nested, .. })) = attr.parse_meta() {
let Path { segments, .. } = path;
if segments
.iter()
.map(|p| &p.ident)
.eq(["delta_struct"].iter().cloned())
{
let values: Result<Vec<_>, Vec<NestedMeta>> = nested
.iter()
.map(|nested_meta| match nested_meta {
NestedMeta::Meta(Meta::NameValue(MetaNameValue {
path,
lit: Lit::Str(s),
..
})) => Ok((path.get_ident().map(|i| i.to_string()), s.value())),
e => Err(e),
})
.fold(Ok(vec![]), |v, i| match (v, i) {
(Ok(mut v), Ok(i)) => {
v.push(i);
Ok(v)
}
(Ok(_), Err(e)) => Err(vec![e.clone()]),
(Err(mut v), Err(e)) => {
v.push(e.clone());
Err(v)
}
(v @ Err(_), _) => v,
});
return match values {
Ok(v) => {
let mut field_type = None;
let mut delta_leader = String::new();
for i in v {
match i.0.as_deref() {
Some("delta_leader") => {
delta_leader = i.1;
}
a if Some(attr_name) == a => {
field_type = string_to_fieldtype(&i.1);
}
a => {
abort_call_site!("Unrecognized value {:?}", a);
}
}
}
Ok((field_type, delta_leader))
}
Err(_) => Err(FieldTypeError::UnrecognizedJunkFound),
};
}
}
}
Ok((None, String::new()))
}
fn string_to_fieldtype(s: &str) -> Option<FieldType> {
match s {
"ordered" => Some(FieldType::Ordered),
"unordered" => Some(FieldType::Unordered),
"unordered-delta" => Some(FieldType::UnorderedDelta),
"scalar" => Some(FieldType::Scalar),
"delta" => Some(FieldType::Delta),
_ => None,
}
}
#[proc_macro_derive(Fingerprint)]
#[proc_macro_error]
pub fn derive_fingerprint(input: TokenStream) -> TokenStream {
let DeriveInput {
ident,
mut generics,
data,
..
} = parse_macro_input!(input as DeriveInput);
let body = match data {
Data::Struct(strukt) => {
fingerprint_calls(strukt.fields.iter().enumerate().map(|(i, field)| {
match &field.ident {
Some(ident) => quote!(self.#ident),
None => {
let index = syn::Index::from(i);
quote!(self.#index)
}
}
}))
}
Data::Enum(enom) => {
let arms = enom.variants.into_iter().enumerate().map(|(index, variant)| {
let variant_ident = variant.ident;
let bindings = binding_idents(&variant.fields);
let pattern = match &variant.fields {
Fields::Named(_) => quote!(Self::#variant_ident { #(#bindings),* }),
Fields::Unnamed(_) => quote!(Self::#variant_ident( #(#bindings),* )),
Fields::Unit => quote!(Self::#variant_ident),
};
let fields = fingerprint_calls(bindings.iter().map(|b| quote!(#b)));
let index = index as u32;
quote! {
#pattern => {
::delta_struct::Fingerprint::fingerprint(&#index, hasher);
#fields
}
}
});
quote! {
match self {
#(#arms)*
}
}
}
_ => abort_call_site!(
"delta_struct::Fingerprint may only be derived for struct and enum types. {} is neither.",
ident
),
};
let fingerprint_types = generics
.type_params()
.map(|t| t.ident.clone())
.collect::<Vec<_>>();
let where_clause = generics.make_where_clause();
for ty in fingerprint_types {
where_clause
.predicates
.push(syn::parse_quote!(#ty: ::delta_struct::Fingerprint));
}
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
TokenStream::from(quote! {
impl #impl_generics ::delta_struct::Fingerprint for #ident #ty_generics #where_clause {
fn fingerprint(&self, hasher: &mut ::delta_struct::fingerprint::Hasher) {
#body
}
}
})
}
fn binding_idents(fields: &Fields) -> Vec<Ident> {
fields
.iter()
.enumerate()
.map(|(i, field)| match &field.ident {
Some(ident) => ident.clone(),
None => format_ident!("field_{}", i),
})
.collect()
}
fn fingerprint_calls(
exprs: impl Iterator<Item = proc_macro2::TokenStream>,
) -> proc_macro2::TokenStream {
let calls = exprs.map(|expr| quote!(::delta_struct::Fingerprint::fingerprint(&#expr, hasher);));
quote!(#(#calls)*)
}