use crate::extensions::item::ItemFnStructEnum;
use crate::flag_or_meta::FlagOrMeta;
use crate::macro_api::ApiPaths;
use crate::macro_api::copy::{CopyArgs, macro_rules_copy_inner_tokens};
use crate::macro_api::merge::{MergeArgs, macro_rules_merge_inner_tokens};
use crate::macros::ok_or_compiler_error;
use darling::FromMeta;
use proc_macro2::TokenStream;
use quote::{ToTokens, format_ident, quote};
use syn::spanned::Spanned;
use syn::*;
#[derive(FromMeta, Default)]
#[darling(default, derive_syn_parse)]
struct ExportArgs {
name: Option<Ident>,
copy: FlagOrMeta<CopyArgs>,
merge: FlagOrMeta<MergeArgs>,
blacklist: darling::util::Flag,
filter: darling::util::PathList,
export: darling::util::Flag,
pub_crate_use: darling::util::Flag,
#[cfg(test)]
test: darling::util::Flag,
}
impl ExportArgs {
fn resolve_macro_rules_ident(&self, prefix: &str, item: &ItemFnStructEnum) -> Ident {
let ident = self.name.as_ref().unwrap_or_else(|| item.ident());
format_ident!("{prefix}{ident}")
}
}
impl ToTokens for ExportArgs {
fn to_tokens(&self, tokens: &mut TokenStream) {
let mut items = vec![];
if self.blacklist.is_present() {
items.push(quote! { blacklist });
}
if !self.filter.is_empty() {
for filter_path in self.filter.iter() {
items.push(quote! { filter(#filter_path) })
}
}
tokens.extend(quote! { #(#items),* });
}
}
pub fn export_attr_outer(
attr: impl Into<TokenStream>,
input: impl Into<TokenStream>,
api_paths: impl Into<ApiPaths>,
) -> TokenStream {
ok_or_compiler_error!(export_attr_inner(attr.into(), input.into(), api_paths))
}
fn export_attr_inner(
attr: TokenStream,
input: TokenStream,
api_paths: impl Into<ApiPaths>,
) -> Result<TokenStream> {
let item = parse2::<Item>(input)?;
export_attr_inner_item(attr, item, api_paths)
}
fn export_attr_inner_item(
attr: TokenStream,
item: Item,
api_paths: impl Into<ApiPaths>,
) -> Result<TokenStream> {
let args: ExportArgs = parse2::<ExportArgs>(attr)?;
Ok(export(args, item, api_paths))
}
fn export(args: ExportArgs, mut item: Item, api_paths: impl Into<ApiPaths>) -> TokenStream {
let api_paths = api_paths.into();
let item = ok_or_compiler_error!(ItemFnStructEnum::try_from(&mut item));
let mut item_attrs = item.clone_attrs();
if args.blacklist.is_present() {
item_attrs.retain(|attr| !args.filter.contains(attr.path()));
} else if !args.filter.is_empty() {
item_attrs.retain(|attr| args.filter.contains(attr.path()));
}
let target_idents = item_attrs
.iter()
.map(|attr| attr.path())
.collect::<Vec<_>>();
let macro_export_tokens = if args.export.is_present() {
quote! {
#[macro_export]
}
} else {
quote! {}
};
let get_target_idents = move || {
target_idents
.clone()
.into_iter()
.cloned()
.collect::<Vec<_>>()
};
let mut exports = vec![];
if args.copy.is_present() {
let mut copy_args = args
.copy
.inner_meta
.as_ref()
.cloned()
.unwrap_or_else(|| CopyArgs::from_path_vec(vec![]));
copy_args.extend_targets(get_target_idents());
exports.push((
args.resolve_macro_rules_ident("Copy", &item),
macro_rules_copy_inner_tokens(copy_args, &item_attrs, &api_paths),
));
}
if args.merge.is_present() {
let mut merge_args = args
.merge
.inner_meta
.as_ref()
.cloned()
.unwrap_or_else(|| MergeArgs::from_path_vec(vec![]));
merge_args.extend_targets(get_target_idents());
exports.push((
args.resolve_macro_rules_ident("Merge", &item),
macro_rules_merge_inner_tokens(merge_args, &item_attrs, &api_paths),
));
}
let mut macro_rules_tokens = quote! {};
for (macro_rules_ident, inner_macro) in exports {
let pub_crate_use_tokens = if args.pub_crate_use.is_present() {
quote! {
pub(crate) use #macro_rules_ident;
}
} else {
quote! {}
};
#[cfg(test)]
let inner_macro = if args.test.is_present() {
quote!(::qoute::quote! { #inner_macro })
} else {
inner_macro
};
macro_rules_tokens.extend(quote! {
#macro_export_tokens
#[allow(non_snake_case)]
macro_rules! #macro_rules_ident {
($($tokens:tt)*) => {
#inner_macro
}
}
#pub_crate_use_tokens
});
}
if macro_rules_tokens.is_empty() {
return Error::new(
args.span(),
"no macros exported, specify `copy` and/or `merge`",
)
.to_compile_error();
}
quote! {
#item
#macro_rules_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_export_copy_merge_name() -> Result<()> {
let export_attr: Attribute = parse_quote! {
#[#ATTR_EXPORT(pub_crate_use, copy, merge, name = FooBar)]
};
let item = parse_quote! {
#[derive(Debug)]
struct Foo;
};
let result_tokens = export_attr_inner_item(
export_attr.meta.require_list()?.tokens.clone(),
item,
API_PATHS,
)?;
assert_eq_ts_pretty_print!(
result_tokens,
quote! {
#[derive(Debug)]
struct Foo;
#[allow(non_snake_case)]
macro_rules! CopyFooBar {
($($tokens:tt)*) => {
#[#ATTR_COPY_SOURCE_START(targets(derive))]
#[derive(Debug)]
#[#ATTR_COPY_SOURCE_END]
$($tokens)*
}
}
pub(crate) use CopyFooBar;
#[allow(non_snake_case)]
macro_rules! MergeFooBar {
($($tokens:tt)*) => {
#[#ATTR_MERGE_SOURCE_START(targets(derive))]
#[derive(Debug)]
#[#ATTR_MERGE_SOURCE_END]
$($tokens)*
}
}
pub(crate) use MergeFooBar;
}
);
Ok(())
}
#[test]
fn test_export_copy() -> Result<()> {
let export_attr: Attribute = parse_quote! {
#[#ATTR_EXPORT(pub_crate_use, copy)]
};
let item = parse_quote! {
#[derive(Debug)]
struct Foo;
};
let result_tokens = export_attr_inner_item(
export_attr.meta.require_list()?.tokens.clone(),
item,
API_PATHS,
)?;
assert_eq_ts_pretty_print!(
result_tokens,
quote! {
#[derive(Debug)]
struct Foo;
#[allow(non_snake_case)]
macro_rules! CopyFoo {
($($tokens:tt)*) => {
#[#ATTR_COPY_SOURCE_START(targets(derive))]
#[derive(Debug)]
#[#ATTR_COPY_SOURCE_END]
$($tokens)*
}
}
pub(crate) use CopyFoo;
}
);
Ok(())
}
#[test]
fn test_export_merge() -> Result<()> {
let export_attr: Attribute = parse_quote! {
#[#ATTR_EXPORT(pub_crate_use, merge)]
};
let item = parse_quote! {
#[derive(Debug)]
struct Foo;
};
let result_tokens = export_attr_inner_item(
export_attr.meta.require_list()?.tokens.clone(),
item,
API_PATHS,
)?;
assert_eq_ts_pretty_print!(
result_tokens,
quote! {
#[derive(Debug)]
struct Foo;
#[allow(non_snake_case)]
macro_rules! MergeFoo {
($($tokens:tt)*) => {
#[#ATTR_MERGE_SOURCE_START(targets(derive))]
#[derive(Debug)]
#[#ATTR_MERGE_SOURCE_END]
$($tokens)*
}
}
pub(crate) use MergeFoo;
}
);
Ok(())
}
}