meta_merge_core 0.1.0

Core logic for meta_merge
Documentation
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 {
    /// attribute macros to affect
    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(())
    }
}