meta_merge_core 0.1.0

Core logic for meta_merge
Documentation
#![allow(dead_code)]
use proc_macro2::TokenStream;
use quote::{ToTokens, quote};
use std::fmt::Display;
use std::marker::PhantomData;
use syn::parse::Parse;

/// A wrapper around a string that implements `ToTokens` to produce a `Literal` token. (without quotes)
#[derive(Debug, Copy, Clone)]
pub struct TokenStreamFromStr<'a, T> {
    token_str: &'a str,
    _marker: PhantomData<T>,
}

impl<'a, T> TokenStreamFromStr<'a, T> {
    pub const fn new(token_str: &'a str) -> Self {
        Self {
            token_str,
            _marker: PhantomData,
        }
    }
    pub const fn as_str(&self) -> &str {
        self.token_str
    }
}

pub type PathTokenStreamFromStr<'a> = TokenStreamFromStr<'a, syn::Path>;
pub type IdentTokenStreamFromStr<'a> = TokenStreamFromStr<'a, syn::Ident>;

impl<'a, T> Display for TokenStreamFromStr<'a, T> {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", self.as_str())
    }
}

impl<'a, T> From<TokenStreamFromStr<'a, T>> for &'a str {
    fn from(ts: TokenStreamFromStr<'a, T>) -> Self {
        ts.token_str
    }
}

impl<T: Parse + ToTokens + 'static> ToTokens for TokenStreamFromStr<'_, T> {
    fn to_tokens(&self, tokens: &mut TokenStream) {
        let Ok(item) = syn::parse_str::<T>(self.token_str) else {
            panic!(
                "failed to parse {} from: {:?}",
                std::any::type_name::<T>(),
                self.token_str,
            );
        };
        tokens.extend(quote!(#item));
    }
}

pub type PathTokenStreamFromStrCollection<'a, const N: usize> =
    TokenStreamFromStrCollection<'a, syn::Path, N>;
pub type IdentTokenStreamFromStrCollection<'a, const N: usize> =
    TokenStreamFromStrCollection<'a, syn::Ident, N>;

#[derive(Debug, Copy, Clone)]
pub struct TokenStreamFromStrCollection<'a, T, const N: usize> {
    token_strs: [TokenStreamFromStr<'a, T>; N],
    _marker: PhantomData<T>,
}

impl<'a, T, const N: usize> TokenStreamFromStrCollection<'a, T, N> {
    pub const fn new(token_strs: [TokenStreamFromStr<'a, T>; N]) -> Self {
        Self {
            token_strs,
            _marker: PhantomData,
        }
    }
}

impl<'a, T, const N: usize> Display for TokenStreamFromStrCollection<'a, T, N> {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", String::from(self))
    }
}

impl<'a, T, const N: usize> From<TokenStreamFromStrCollection<'a, T, N>> for String {
    fn from(ts: TokenStreamFromStrCollection<'a, T, N>) -> Self {
        String::from(&ts)
    }
}

impl<'a, T, const N: usize> From<&TokenStreamFromStrCollection<'a, T, N>> for String {
    fn from(ts: &TokenStreamFromStrCollection<'a, T, N>) -> Self {
        ts.token_strs
            .iter()
            .map(|x| format!("::{x}"))
            .collect::<String>()
    }
}

impl<T: Parse + ToTokens + 'static, const N: usize> ToTokens
    for TokenStreamFromStrCollection<'_, T, N>
{
    fn to_tokens(&self, tokens: &mut TokenStream) {
        let path = self.to_string();
        let Ok(item) = syn::parse_str::<T>(&path) else {
            panic!(
                "failed to parse {} from: {:?}",
                std::any::type_name::<T>(),
                path
            );
        };
        tokens.extend(quote!(#item));
    }
}

pub trait TokenStreamExt {
    fn to_pretty_print_string(self) -> syn::Result<String>;
}

impl TokenStreamExt for TokenStream {
    fn to_pretty_print_string(self) -> syn::Result<String> {
        let abstract_file: syn::File = syn::parse2(self)?;
        let pretty_rust = prettyplease::unparse(&abstract_file);
        Ok(pretty_rust)
    }
}

#[cfg(test)]
#[macro_export]
macro_rules! assert_eq_ts_pretty_print {
    ($left:expr, $right:expr $(,)?) => {{
        use ::proc_macro2::TokenStream;
        use $crate::extensions::token_stream::TokenStreamExt;
        let left = <TokenStream as TokenStreamExt>::to_pretty_print_string($left)?;
        let right = <TokenStream as TokenStreamExt>::to_pretty_print_string($right)?;
        ::core::assert_eq!(
            left,
            right,
            "tokens don't match\n\n/* left */\n\n{left}\n/* right */\n\n{right}\n/* end */"
        );
    }};
}

#[cfg(test)]
mod tests {
    use super::*;
    use quote::quote;

    #[test]
    fn test_to_tokens() {
        assert_eq!(
            PathTokenStreamFromStr::new("foo")
                .to_token_stream()
                .to_string(),
            quote!(foo).to_string()
        );
    }
}