cgp-macro-lib 0.6.1

Context-generic programming core component macros implemented as a library.
Documentation
use proc_macro2::{Span, TokenStream};
use quote::{ToTokens, quote};
use syn::parse::discouraged::Speculative;
use syn::parse::{Parse, ParseStream};
use syn::token::{Colon, For};
use syn::{FnArg, Ident, ImplItem, ItemImpl, Type, parse_quote, parse2};

use crate::derive_provider::{
    derive_component_name_from_provider_impl, derive_is_provider_for, derive_provider_struct,
};
use crate::parse::SimpleType;
use crate::replace_self::{
    replace_self_receiver, replace_self_type, replace_self_var, to_snake_case_ident,
};

pub fn cgp_impl(attr: TokenStream, body: TokenStream) -> syn::Result<TokenStream> {
    let spec: ImplProviderSpec = parse2(attr)?;
    let mut item_impl: ItemImpl = parse2(body)?;

    let provider_impl = match &item_impl.trait_ {
        Some((_, path, _)) => {
            let consumer_trait_path = parse2(path.to_token_stream())?;
            let context_type = item_impl.self_ty.as_ref();
            transform_impl_trait(
                &item_impl,
                &consumer_trait_path,
                &spec.provider_type,
                context_type,
            )?
        }
        None => {
            let consumer_trait_path = parse2(item_impl.self_ty.to_token_stream())?;
            let context_type = parse_quote! { __Context__ };

            item_impl
                .generics
                .params
                .insert(0, parse_quote! { __Context__ });

            transform_impl_trait(
                &item_impl,
                &consumer_trait_path,
                &spec.provider_type,
                &context_type,
            )?
        }
    };

    let component_type = match &spec.component_type {
        Some(component_type) => component_type.clone(),
        None => derive_component_name_from_provider_impl(&provider_impl)?,
    };

    let is_provider_for_impl: ItemImpl = derive_is_provider_for(&component_type, &provider_impl)?;

    let provider_struct = if spec.new_struct {
        Some(derive_provider_struct(&provider_impl)?)
    } else {
        None
    };

    Ok(quote! {
        #provider_struct

        #provider_impl

        #is_provider_for_impl
    })
}

pub struct ImplProviderSpec {
    pub new_struct: bool,
    pub provider_type: Type,
    pub component_type: Option<Type>,
}

impl Parse for ImplProviderSpec {
    fn parse(input: ParseStream) -> syn::Result<Self> {
        let new_struct = {
            let fork = input.fork();
            let new_ident: Option<Ident> = fork.parse().ok();
            match new_ident {
                Some(new_ident) if new_ident == "new" => {
                    input.advance_to(&fork);
                    true
                }
                _ => false,
            }
        };

        let provider_type = input.parse()?;

        let component_type = if let Some(_colon) = input.parse::<Option<Colon>>()? {
            let component_type: Type = input.parse()?;
            Some(component_type)
        } else {
            None
        };

        Ok(ImplProviderSpec {
            new_struct,
            provider_type,
            component_type,
        })
    }
}

pub fn transform_impl_trait(
    item_impl: &ItemImpl,
    consumer_trait_path: &SimpleType,
    provider_type: &Type,
    context_type: &Type,
) -> syn::Result<ItemImpl> {
    let context_var = if let Ok(ident) = parse2::<Ident>(context_type.to_token_stream()) {
        to_snake_case_ident(&ident)
    } else {
        Ident::new("__context__", Span::call_site())
    };

    let local_assoc_types: Vec<Ident> = item_impl
        .items
        .iter()
        .filter_map(|item| {
            if let ImplItem::Type(assoc_type) = item {
                Some(assoc_type.ident.clone())
            } else {
                None
            }
        })
        .collect();

    let raw_out_impl = replace_self_type(
        item_impl.to_token_stream(),
        context_type.to_token_stream(),
        &local_assoc_types,
    );

    let mut out_impl: ItemImpl = parse2(raw_out_impl)?;
    out_impl.self_ty = Box::new(provider_type.clone());

    let mut provider_trait_path: SimpleType = consumer_trait_path.clone();

    match &mut provider_trait_path.generics {
        Some(generics) => {
            generics
                .args
                .insert(0, parse2(context_type.to_token_stream())?);
        }
        None => {
            provider_trait_path.generics = Some(parse2(quote! { < #context_type > })?);
        }
    }

    out_impl.trait_ = Some((
        None,
        parse2(provider_trait_path.to_token_stream())?,
        For(Span::call_site()),
    ));

    for item in out_impl.items.iter_mut() {
        if let ImplItem::Fn(item_fn) = item
            && let Some(arg) = item_fn.sig.inputs.first_mut()
            && let FnArg::Receiver(receiver) = arg
        {
            *arg = replace_self_receiver(receiver, &context_var, context_type.to_token_stream());

            let replaced_block = replace_self_var(item_fn.block.to_token_stream(), &context_var);
            item_fn.block = parse2(replaced_block)?;
        }
    }

    Ok(out_impl)
}