use proc_macro2::{Span, TokenStream};
use quote::{ToTokens, quote};
use std::collections::BTreeMap;
use syn::{self, AttrStyle, Attribute, Expr, Meta, Path, Token, token};
#[derive(Clone)]
enum MetaTree {
Path(Path),
NameValue(Path, Expr),
List(Path, Vec<MetaTree>),
}
impl MetaTree {
pub fn as_path(&self) -> &Path {
match self {
MetaTree::Path(p) => p,
MetaTree::NameValue(p, _) => p,
MetaTree::List(p, _) => p,
}
}
}
fn path_key(p: &Path) -> String {
p.to_token_stream().to_string()
}
impl MetaTree {
fn from_attr(attr: &Attribute) -> syn::Result<MetaTree> {
match &attr.meta {
Meta::Path(p) => Ok(MetaTree::Path(p.clone())),
Meta::NameValue(nv) => Ok(MetaTree::NameValue(nv.path.clone(), nv.value.clone())),
Meta::List(_) => {
let top_path = attr.path().clone();
let mut items = Vec::new();
attr.parse_nested_meta(|nested| {
let key = nested.path.clone();
if nested.input.peek(Token![=]) {
let _eq: Token![=] = nested.input.parse()?;
let expr: Expr = nested.input.parse()?;
items.push(MetaTree::NameValue(key, expr));
return Ok(());
}
if nested.input.peek(token::Paren) {
let mut inner = Vec::new();
nested.parse_nested_meta(|nn| {
let inner_key = nn.path.clone();
if nn.input.peek(Token![=]) {
let _eq: Token![=] = nn.input.parse()?;
let expr: Expr = nn.input.parse()?;
inner.push(MetaTree::NameValue(inner_key, expr));
return Ok(());
}
if nn.input.peek(token::Paren) {
let mut deeper = Vec::new();
nn.parse_nested_meta(|nnn| {
let deeper_key = nnn.path.clone();
if nnn.input.peek(Token![=]) {
let _eq: Token![=] = nnn.input.parse()?;
let expr: Expr = nnn.input.parse()?;
deeper.push(MetaTree::NameValue(deeper_key, expr));
} else {
deeper.push(MetaTree::Path(deeper_key));
}
Ok(())
})?;
inner.push(MetaTree::List(inner_key, deeper));
return Ok(());
}
inner.push(MetaTree::Path(inner_key));
Ok(())
})?;
items.push(MetaTree::List(key, inner));
return Ok(());
}
items.push(MetaTree::Path(key));
Ok(())
})?;
Ok(MetaTree::List(top_path, items))
}
}
}
fn many_to_token_stream(items: &[MetaTree]) -> TokenStream {
let mut out: Vec<TokenStream> = Vec::new();
for it in items {
match it {
MetaTree::Path(p) => out.push(quote!(#p)),
MetaTree::NameValue(p, expr) => out.push(quote!(#p = #expr)),
MetaTree::List(p, inner) => {
let inner_ts = MetaTree::many_to_token_stream(inner);
out.push(quote!(#p( #inner_ts )));
}
}
}
quote!( #(#out),* )
}
fn into_attribute(self, style: AttrStyle) -> Attribute {
let meta_ts = match self {
MetaTree::Path(p) => quote!(#p),
MetaTree::NameValue(p, expr) => quote!(#p = #expr),
MetaTree::List(p, inner) => {
let inner_ts = MetaTree::many_to_token_stream(&inner);
quote!(#p( #inner_ts ))
}
};
let meta: Meta =
syn::parse2(meta_ts).expect("failed to parse Meta from synthesized tokens");
Attribute {
pound_token: Token),
style,
bracket_token: token::Bracket::default(),
meta,
}
}
}
fn merge_meta_trees(dst: &mut MetaTree, src: &MetaTree) {
match (dst, src) {
(MetaTree::List(dst_path, dst_items), MetaTree::List(src_path, src_items))
if path_key(dst_path) == path_key(src_path) =>
{
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
enum Kind {
Path,
NameValue,
List,
}
impl From<&MetaTree> for Kind {
fn from(m: &MetaTree) -> Self {
match m {
MetaTree::Path(_) => Kind::Path,
MetaTree::NameValue(_, _) => Kind::NameValue,
MetaTree::List(_, _) => Kind::List,
}
}
}
let mut index: BTreeMap<(String, Kind), usize> = BTreeMap::new();
for (ix, meta_tree) in dst_items.iter().enumerate() {
let (path, kind) = (path_key(meta_tree.as_path()), Kind::from(meta_tree));
index.entry((path, kind)).or_insert(ix);
}
for src_meta_tree in src_items {
match src_meta_tree {
MetaTree::Path(p) => {
let k = (path_key(p), Kind::Path);
index.entry(k).or_insert_with(|| {
dst_items.push(MetaTree::Path(p.clone()));
dst_items.len() - 1
});
}
MetaTree::NameValue(p, expr) => {
let k = (path_key(p), Kind::NameValue);
index.entry(k).or_insert_with(|| {
dst_items.push(MetaTree::NameValue(p.clone(), expr.clone()));
dst_items.len() - 1
}); }
MetaTree::List(p, inner) => {
let k = (path_key(p), Kind::List);
if let Some(&idx) = index.get(&k) {
if let MetaTree::List(_, ref mut dst_inner) = dst_items[idx] {
let mut merged = MetaTree::List(p.clone(), dst_inner.clone());
merge_meta_trees(
&mut merged,
&MetaTree::List(p.clone(), inner.clone()),
);
if let MetaTree::List(_, new_inner) = merged {
*dst_inner = new_inner;
}
}
} else {
dst_items.push(MetaTree::List(p.clone(), inner.clone()));
index.insert(k, dst_items.len() - 1);
}
}
}
}
}
_ => { }
}
}
pub fn merge_attr(dst: &mut Attribute, src: &Attribute) -> syn::Result<()> {
let mut dst_tree = MetaTree::from_attr(dst)?;
let src_tree = MetaTree::from_attr(src)?;
merge_meta_trees(&mut dst_tree, &src_tree);
dst.clone_from(&dst_tree.into_attribute(dst.style));
Ok(())
}
#[allow(dead_code)]
pub fn merge_attr_vecs_deep(dst: &mut Vec<Attribute>, src: &[Attribute]) -> syn::Result<()> {
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord)]
struct Key {
path_key: String,
is_inner: bool,
}
let mut idx: BTreeMap<Key, usize> = BTreeMap::new();
for (i, a) in dst.iter().enumerate() {
let key = Key {
path_key: path_key(a.path()),
is_inner: matches!(a.style, AttrStyle::Inner(_)),
};
idx.entry(key).or_insert(i);
}
for s in src {
let k = Key {
path_key: path_key(s.path()),
is_inner: matches!(s.style, AttrStyle::Inner(_)),
};
if let Some(&i) = idx.get(&k) {
let dst_attr = dst[i].clone();
let mut dst_tree = MetaTree::from_attr(&dst_attr)?;
let src_tree = MetaTree::from_attr(s)?;
merge_meta_trees(&mut dst_tree, &src_tree);
dst[i] = dst_tree.into_attribute(dst_attr.style);
} else {
dst.push(s.clone());
idx.insert(k, dst.len() - 1);
}
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use crate::extensions::token_stream::TokenStreamExt;
use quote::quote;
use syn::parse_quote;
fn pretty_print_or_panic(ts: &TokenStream, message: &str) -> String {
match ts.clone().to_pretty_print_string() {
Ok(output) => output,
Err(err) => {
panic!("failed to pretty print {message}:\n{ts}\n\n{err}");
}
}
}
#[inline]
fn assert_eq_attrs(left: &Vec<Attribute>, right: &Vec<Attribute>) {
let dummy = quote! { struct __DummyStruct__; };
let left = quote! {
#(#left)*
#dummy
};
let right = quote! {
#(#right)*
#dummy
};
let left = pretty_print_or_panic(&left, "left");
let right = match right.clone().to_pretty_print_string() {
Ok(output) => output,
Err(err) => {
panic!("failed to pretty print right:\n{right}\n\n{err}");
}
};
let dummy = dummy.to_pretty_print_string().unwrap();
let left = left.replace(&dummy, "");
let right = right.replace(&dummy, "");
let left = left.trim();
let right = right.trim();
assert_eq!(
left, right,
"tokens don't match\n\n/* left */\n\n{left}\n/* right */\n\n{right}\n/* end */"
);
}
#[test]
fn simple_merge() {
let src_attrs = vec![parse_quote!( #[foo(bar)] )];
let mut dst_attrs = vec![parse_quote!( #[foo(bar)] )];
assert_eq!(
merge_attr_vecs_deep(&mut dst_attrs, &src_attrs)
.as_ref()
.map_err(syn::Error::to_string),
Ok(&())
);
assert_eq_attrs(&dst_attrs, &src_attrs);
let src_attrs = vec![parse_quote!( #[foo(fizz)] )];
let mut dst_attrs = vec![parse_quote!( #[foo(bar)] )];
assert_eq!(
merge_attr_vecs_deep(&mut dst_attrs, &src_attrs)
.as_ref()
.map_err(syn::Error::to_string),
Ok(&())
);
assert_eq_attrs(&dst_attrs, &vec![parse_quote!( #[foo(bar, fizz)] )]);
}
#[test]
fn simple_merge_2_params() {
let src_attrs = vec![parse_quote!( #[foo(bar, fizz)] )];
let mut dst_attrs = vec![parse_quote!( #[foo(bar)] )];
assert_eq!(
merge_attr_vecs_deep(&mut dst_attrs, &src_attrs)
.as_ref()
.map_err(syn::Error::to_string),
Ok(&())
);
assert_eq_attrs(&dst_attrs, &src_attrs);
}
#[test]
fn simple_merge_2_attrs() {
let src_attrs = vec![
parse_quote!( #[foo(fizz)] ),
parse_quote!( #[derive(PartialEq)] ),
];
let mut dst_attrs = vec![
parse_quote!( #[foo(bar)] ),
parse_quote!( #[derive(Debug)] ),
];
assert_eq!(
merge_attr_vecs_deep(&mut dst_attrs, &src_attrs)
.as_ref()
.map_err(syn::Error::to_string),
Ok(&())
);
assert_eq_attrs(
&dst_attrs,
&vec![
parse_quote!( #[foo(bar, fizz)] ),
parse_quote!( #[derive(Debug, PartialEq)] ),
],
);
}
#[test]
fn test_merge_attr() {
let src_attr = parse_quote!( #[foo(bar)] );
let mut dst_attr = parse_quote!( #[foo(bar)] );
assert_eq!(
merge_attr(&mut dst_attr, &src_attr)
.as_ref()
.map_err(syn::Error::to_string),
Ok(&())
);
assert_eq_attrs(&vec![dst_attr], &vec![src_attr]);
}
}