use crate::extensions::item::ItemFnStructEnum;
use crate::extensions::path::PathExt;
use crate::macro_api::*;
use crate::macros::ok_or_compiler_error;
use crate::merge_attr::merge_attr;
use darling::FromMeta;
use proc_macro2::TokenStream;
use quote::{ToTokens, quote};
use std::ops::Deref;
use syn::*;
#[derive(FromMeta, Default, Clone)]
#[darling(default, derive_syn_parse)]
pub(super) struct MergeArgs {
pub targets: darling::util::PathList,
}
impl MergeArgs {
pub fn from_path_vec(targets: Vec<Path>) -> Self {
Self {
targets: darling::util::PathList::new(targets),
}
}
pub fn extend_targets(&mut self, mut targets: Vec<Path>) {
let mut current_targets = self.targets.deref().clone();
targets.retain(|target| !current_targets.contains(target));
current_targets.extend(targets);
self.targets = darling::util::PathList::new(current_targets);
}
}
impl ToTokens for MergeArgs {
fn to_tokens(&self, tokens: &mut TokenStream) {
if self.targets.is_empty() {
return;
}
let targets = &self.targets;
tokens.extend(quote! { targets(#(#targets),*) });
}
}
pub fn merge_attr_outer(
attr: impl Into<TokenStream>,
input: impl Into<TokenStream>,
api_paths: impl Into<ApiPaths>,
) -> TokenStream {
ok_or_compiler_error!(merge_attr_inner(attr.into(), input.into(), api_paths))
}
fn merge_attr_inner(
attr: TokenStream,
input: TokenStream,
api_paths: impl Into<ApiPaths>,
) -> Result<TokenStream> {
let item: Item = parse2::<Item>(input)?;
merge_attr_item(attr, item, api_paths)
}
fn merge_attr_item(
attr: TokenStream,
item: Item,
api_paths: impl Into<ApiPaths>,
) -> Result<TokenStream> {
let args: MergeArgs = parse2::<MergeArgs>(attr)?;
Ok(merge(args, item, api_paths))
}
fn merge(args: MergeArgs, mut item: Item, api_paths: impl Into<ApiPaths>) -> TokenStream {
let api_paths = api_paths.into();
let attr_merge_source_start = api_paths.merge_source_start().to_string();
let attr_merge_source_end = api_paths.merge_source_end().to_string();
let mut item = ok_or_compiler_error!(ItemFnStructEnum::try_from(&mut item));
let item_attrs = item.take_attrs();
enum AttrTarget {
Target(Attribute),
Skip(Attribute),
}
impl From<AttrTarget> for Attribute {
fn from(value: AttrTarget) -> Self {
match value {
AttrTarget::Target(attr) => attr,
AttrTarget::Skip(attr) => attr,
}
}
}
let mut source_attrs: Vec<Attribute> = vec![];
let mut target_attrs: Vec<AttrTarget> = vec![];
let mut attr_merge_source_end_found = false;
let mut is_filling_targets = false;
for attr in item_attrs {
if attr.path().eq_str(&attr_merge_source_start) {
if !attr_merge_source_end_found {
panic!(
"unexpected start of source attributes without a preceding {attr_merge_source_end}"
)
}
continue;
}
if !attr_merge_source_end_found && attr.path().eq_str(&attr_merge_source_end) {
attr_merge_source_end_found = true;
is_filling_targets = true;
continue;
}
if is_filling_targets {
if !args.targets.contains(attr.path()) {
target_attrs.push(AttrTarget::Skip(attr))
} else {
target_attrs.push(AttrTarget::Target(attr))
}
} else {
source_attrs.push(attr);
}
}
if !attr_merge_source_end_found {
panic!("unexpected end of attributes without {attr_merge_source_end}")
}
for source_attr in source_attrs {
for target_attr in target_attrs.iter_mut() {
match target_attr {
AttrTarget::Target(inner) => {
if inner.path() == source_attr.path() {
if let Err(err) = merge_attr(inner, &source_attr) {
panic!("failed to merge attributes: {err}");
}
} else {
continue;
}
}
AttrTarget::Skip(_) => continue,
}
}
}
let target_attrs = target_attrs
.into_iter()
.map(Attribute::from)
.collect::<Vec<_>>();
item.put_attrs(target_attrs);
quote! {
#item
}
}
pub(super) fn macro_rules_merge_inner_tokens(
args: MergeArgs,
item_attrs: &Vec<Attribute>,
api_paths: &ApiPaths,
) -> TokenStream {
let attr_start = api_paths.merge_source_start();
let attr_end = api_paths.merge_source_end();
quote! {
#[#attr_start(#args)]
#(#item_attrs)*
#[#attr_end]
$($tokens)*
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::assert_eq_ts_pretty_print;
use crate::macro_api::test_paths::*;
use quote::quote;
#[test]
fn test_merge() -> Result<()> {
let merge_attr: Attribute = parse_quote! {
#[#ATTR_MERGE_SOURCE_START(targets(derive))]
};
let item = parse_quote! {
#[derive(PartialEq)]
#[#ATTR_MERGE_SOURCE_END]
#[derive(Debug)]
struct Foo;
};
let result_tokens = merge_attr_item(
merge_attr.meta.require_list()?.tokens.clone(),
item,
API_PATHS,
)?;
assert_eq_ts_pretty_print!(
result_tokens,
quote! {
#[derive(Debug, PartialEq)]
struct Foo;
},
);
Ok(())
}
}