cgp-macro-lib 0.7.0

Context-generic programming core component macros implemented as a library.
Documentation
use std::collections::BTreeMap;
use std::collections::btree_map::Entry;

use proc_macro2::TokenStream;
use quote::{ToTokens, quote};
use syn::{Ident, ItemTrait, Type, parse_quote, parse2};

use crate::derive_component::{derive_component_with_ast, preprocess_consumer_trait};
use crate::derive_getter::{
    GetterField, derive_use_field_impl, derive_use_fields_impl, derive_with_provider_impl,
    parse_getter_fields,
};
use crate::derive_provider::derive_is_provider_for;
use crate::parse::{ComponentSpec, Entries};

pub fn cgp_getter(attr: TokenStream, body: TokenStream) -> syn::Result<TokenStream> {
    let mut entries = if let Ok(provider_ident) = parse2::<Ident>(attr.clone()) {
        BTreeMap::from([("provider".to_owned(), provider_ident.to_token_stream())])
    } else {
        parse2::<Entries>(attr)?.entries
    };

    let mut consumer_trait: ItemTrait = syn::parse2(body)?;

    preprocess_consumer_trait(&mut consumer_trait)?;

    let provider_entry = entries.entry("provider".to_owned());

    if let Entry::Vacant(entry) = provider_entry {
        let consumer_name = consumer_trait.ident.to_string();
        if let Some(field_name) = consumer_name.strip_prefix("Has")
            && !field_name.is_empty()
        {
            let provider_name =
                Ident::new(&format!("{field_name}Getter"), consumer_trait.ident.span());
            entry.insert(parse2(provider_name.to_token_stream())?);
        }
    }

    let spec = ComponentSpec::from_entries(&entries)?;

    let derived_component = derive_component_with_ast(&spec, consumer_trait.clone())?;

    let (fields, field_assoc_type) = parse_getter_fields(&spec.context_type, &consumer_trait)?;

    let use_fields_impl = derive_use_fields_impl(
        &spec,
        &derived_component.provider_trait,
        &fields,
        &field_assoc_type,
    )?;

    let component_name_type: Type = {
        let component_name = &spec.component_name;
        let component_params = &spec.component_params;
        parse_quote!( #component_name < #component_params > )
    };

    let is_provider_use_fields_impl =
        derive_is_provider_for(&component_name_type, &use_fields_impl)?;

    let m_field: Option<[GetterField; 1]> = fields.try_into().ok();

    let mut derived = quote! {
        #derived_component

        #use_fields_impl

        #is_provider_use_fields_impl
    };

    if let Some([field]) = m_field {
        let use_field_impl = derive_use_field_impl(
            &spec,
            &derived_component.provider_trait,
            &field,
            &field_assoc_type,
        )?;

        let is_provider_use_field_impl =
            derive_is_provider_for(&component_name_type, &use_field_impl)?;

        let use_provider_impl = derive_with_provider_impl(
            &spec,
            &derived_component.provider_trait,
            &field,
            &field_assoc_type,
        )?;

        let is_provider_use_provider_impl =
            derive_is_provider_for(&component_name_type, &use_provider_impl)?;

        derived.extend(quote! {
            #use_field_impl
            #is_provider_use_field_impl

            #use_provider_impl
            #is_provider_use_provider_impl
        });
    }

    Ok(derived)
}