macro-data 0.1.1

Utitlies to transfer data between procedural macros
Documentation
use std::hash::{DefaultHasher, Hash, Hasher};

use proc_macro2::{Ident, Span, TokenStream};
use quote::{ToTokens, TokenStreamExt, quote};
use syn::{Path, parse::Parse};

/// Create the macro to save the data.
///
/// Use `request` and `transfer` to load the data in another procedural macro.
///
/// The name lookup is done with path only,
/// so the complete path to the place where the tokens are inserted is required.
pub fn save(name: &Ident, data: &impl quote::ToTokens) -> TokenStream {
    save_impl(name, data.to_token_stream())
}

fn save_impl(name: &Ident, data: TokenStream) -> TokenStream {
    let unique_name = macro_name(name);

    let mut hasher = DefaultHasher::new();
    // future todo: replace with hash(name + file + line + column) if available on stable
    format!("{:?}", name.span()).hash(&mut hasher);
    let hash = hasher.finish();
    let internal_name = Ident::new(&format!("{}_{:x}", unique_name, hash), name.span());

    quote::quote! {
        #[macro_export]
        #[doc(hidden)]
        macro_rules! #internal_name {
            (
                $target:path, $context:tt,
            ) => {
                $target!(
                    $context,
                    (#data),
                );
            };
        }

        #[doc(hidden)]
        pub use #internal_name as #unique_name;
    }
}

/// Transfer data from this macro and saved data to the target macro.
///
/// The crate must reexport `macro_data::macro_data_transfer` and the target procedural macro.
pub fn transfer(crate_name: &str, target: &str, data: &impl quote::ToTokens) -> TokenStream {
    transfer_impl(crate_name, target, data.to_token_stream())
}

fn transfer_impl(crate_name: &str, target: &str, data: TokenStream) -> TokenStream {
    let crate_name = Ident::new(crate_name, Span::mixed_site());
    let target = Ident::new(target, Span::mixed_site());
    quote::quote! {
        #crate_name::macro_data_transfer!(
            #crate_name::#target,
            (#data),
        );
    }
}

/// The name for the macro to avoid name collisions.
pub fn macro_name(name: &Ident) -> Ident {
    Ident::new(&format!("__macro_data_{}", name), name.span())
}

/// Create a `Transfer` request for this path.
pub fn request<T>(path: &Path) -> Transfer<T, Request> {
    let mut path = path.clone();
    let segment = path.segments.last_mut().expect("path has segments");
    segment.ident = macro_name(&segment.ident);
    Transfer(path)
}

/// See `Transfer`.
pub struct Request;

/// See `Transfer`.
pub struct Load;

/// See `Transfer`
pub trait Storage {
    type Storage<T>;
}

impl Storage for Request {
    type Storage<T> = Path;
}

impl Storage for Load {
    type Storage<T> = T;
}

/// Type to transfer data.
///
/// - With `S: Storage = Request` this contains a path for loading the data and implements `ToTokens`
/// - With `S: Storage = Load` this contains the final data and implements `Parse` and `ToTokens`
pub struct Transfer<T, S: Storage>(pub S::Storage<T>);

impl<T> ToTokens for Transfer<T, Request> {
    fn to_tokens(&self, tokens: &mut TokenStream) {
        let path = &self.0;
        tokens.append_all(quote! {@load(#path)});
    }
}

impl<T: ToTokens> ToTokens for Transfer<T, Load> {
    fn to_tokens(&self, tokens: &mut TokenStream) {
        self.0.to_tokens(tokens);
    }
}

impl<T: Parse> Parse for Transfer<T, Load> {
    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
        T::parse(input).map(Self)
    }
}