use crate::extensions::item::ItemFnStructEnum;
use crate::extensions::path::PathExt;
use crate::macro_api::*;
use crate::macros::ok_or_compiler_error;
use darling::FromMeta;
use proc_macro2::TokenStream;
use quote::{ToTokens, quote};
use std::collections::{HashSet, VecDeque};
use std::ops::Deref;
use syn::*;
#[derive(FromMeta, Default, Clone)]
#[darling(default, derive_syn_parse)]
pub(super) struct CopyArgs {
pub targets: darling::util::PathList,
pub dedupe: darling::util::Flag,
pub prepend: darling::util::Flag,
}
#[allow(dead_code)]
impl CopyArgs {
pub fn from_path_vec(targets: Vec<Path>) -> Self {
Self {
targets: darling::util::PathList::new(targets),
..Default::default()
}
}
pub fn with_prepend(mut self) -> Self {
self.prepend = darling::util::Flag::present();
self
}
pub fn with_dedupe(mut self) -> Self {
self.dedupe = darling::util::Flag::present();
self
}
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 CopyArgs {
fn to_tokens(&self, tokens: &mut TokenStream) {
if self.targets.is_empty() {
return;
}
let targets = &self.targets;
tokens.extend(quote! { targets(#(#targets),*) });
}
}
pub fn copy_attr_outer(
attr: impl Into<TokenStream>,
input: impl Into<TokenStream>,
api_paths: impl Into<ApiPaths>,
) -> TokenStream {
ok_or_compiler_error!(copy_attr_inner(attr.into(), input.into(), api_paths))
}
fn copy_attr_inner(
attr: TokenStream,
input: TokenStream,
api_paths: impl Into<ApiPaths>,
) -> Result<TokenStream> {
let item: Item = parse2::<Item>(input)?;
copy_attr_item(attr, item, api_paths)
}
fn copy_attr_item(
attr: TokenStream,
item: Item,
api_paths: impl Into<ApiPaths>,
) -> Result<TokenStream> {
let args: CopyArgs = parse2::<CopyArgs>(attr)?;
Ok(copy(args, item, api_paths))
}
fn copy(args: CopyArgs, mut item: Item, api_paths: impl Into<ApiPaths>) -> TokenStream {
let api_paths = api_paths.into();
let attr_copy_source_start = api_paths.copy_source_start().to_string();
let attr_copy_source_end = api_paths.copy_source_end().to_string();
let mut item = ok_or_compiler_error!(ItemFnStructEnum::try_from(&mut item));
let item_attrs = item.take_attrs();
let mut source_attrs: Vec<Attribute> = vec![];
let mut target_attrs: VecDeque<Attribute> = VecDeque::new();
let mut attr_copy_source_end_found = false;
let mut is_filling_targets = false;
let mut sources_to_remove: HashSet<usize> = HashSet::new();
for attr in item_attrs {
if attr.path().eq_str(&attr_copy_source_start) {
if !attr_copy_source_end_found {
panic!(
"unexpected start of source attributes without a preceding {attr_copy_source_end}"
)
}
continue;
}
if !attr_copy_source_end_found && attr.path().eq_str(&attr_copy_source_end) {
attr_copy_source_end_found = true;
is_filling_targets = true;
continue;
}
if is_filling_targets {
if args.dedupe.is_present() && args.targets.contains(attr.path()) {
'source: for (ix, source) in source_attrs.iter().enumerate() {
if source.path() != attr.path() {
continue;
}
if attr.meta == source.meta {
sources_to_remove.insert(ix);
break 'source;
}
}
}
target_attrs.push_back(attr)
} else {
source_attrs.push(attr);
}
}
if !attr_copy_source_end_found {
panic!("unexpected end of attributes without {attr_copy_source_end}")
}
if args.dedupe.is_present() {
let sources_to_remove = {
let mut sources_to_remove = sources_to_remove.into_iter().collect::<Vec<_>>();
sources_to_remove.sort();
sources_to_remove.reverse();
sources_to_remove
};
for ix in sources_to_remove.iter() {
source_attrs.remove(*ix);
}
}
for source in source_attrs {
if args.prepend.is_present() {
target_attrs.push_front(source);
} else {
target_attrs.push_back(source);
}
}
item.put_attrs(target_attrs.into());
quote! {
#item
}
}
pub(super) fn macro_rules_copy_inner_tokens(
args: CopyArgs,
item_attrs: &Vec<Attribute>,
api_paths: &ApiPaths,
) -> TokenStream {
let attr_start = api_paths.copy_source_start();
let attr_end = api_paths.copy_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_copy() -> Result<()> {
let copy_attr: Attribute = parse_quote! {
#[#ATTR_COPY_SOURCE_START(targets(derive))]
};
let item = parse_quote! {
#[derive(PartialEq)]
#[#ATTR_COPY_SOURCE_END]
#[derive(Debug)]
struct Foo;
};
let result_tokens = copy_attr_item(
copy_attr.meta.require_list()?.tokens.clone(),
item,
API_PATHS,
)?;
assert_eq_ts_pretty_print!(
result_tokens,
quote! {
#[derive(Debug)]
#[derive(PartialEq)]
struct Foo;
},
);
Ok(())
}
#[test]
fn test_copy_prepend() -> Result<()> {
let copy_attr: Attribute = parse_quote! {
#[#ATTR_COPY_SOURCE_START(targets(derive), prepend)]
};
let item = parse_quote! {
#[derive(PartialEq)]
#[#ATTR_COPY_SOURCE_END]
#[derive(Debug)]
struct Foo;
};
let result_tokens = copy_attr_item(
copy_attr.meta.require_list()?.tokens.clone(),
item,
API_PATHS,
)?;
assert_eq_ts_pretty_print!(
result_tokens,
quote! {
#[derive(PartialEq)]
#[derive(Debug)]
struct Foo;
},
);
Ok(())
}
#[test]
fn test_copy_dedupe() -> Result<()> {
let copy_attr: Attribute = parse_quote! {
#[#ATTR_COPY_SOURCE_START(targets(derive), dedupe)]
};
let item = parse_quote! {
#[derive(PartialEq)]
#[derive(Debug)]
#[#ATTR_COPY_SOURCE_END]
#[derive(Debug)]
struct Foo;
};
let result_tokens = copy_attr_item(
copy_attr.meta.require_list()?.tokens.clone(),
item,
API_PATHS,
)?;
assert_eq_ts_pretty_print!(
result_tokens,
quote! {
#[derive(Debug)]
#[derive(PartialEq)]
struct Foo;
},
);
Ok(())
}
#[test]
fn test_copy_no_dedupe() -> Result<()> {
let copy_attr: Attribute = parse_quote! {
#[#ATTR_COPY_SOURCE_START(targets(derive))]
};
let item = parse_quote! {
#[derive(PartialEq)]
#[derive(Debug)]
#[#ATTR_COPY_SOURCE_END]
#[derive(Debug)]
struct Foo;
};
let result_tokens = copy_attr_item(
copy_attr.meta.require_list()?.tokens.clone(),
item,
API_PATHS,
)?;
assert_eq_ts_pretty_print!(
result_tokens,
quote! {
#[derive(Debug)]
#[derive(PartialEq)]
#[derive(Debug)]
struct Foo;
},
);
Ok(())
}
}