cgp-macro-lib 0.7.0

Context-generic programming core component macros implemented as a library.
Documentation
use core::iter;

use quote::ToTokens;
use syn::parse::{Parse, ParseStream};
use syn::punctuated::Punctuated;
use syn::spanned::Spanned;
use syn::token::{Bracket, Comma, Lt, Pound};
use syn::{Attribute, Ident, Type, braced, bracketed, parse2};

use crate::parse::{DelegateMode, DelegateValue, ImplGenerics, SimpleType};

pub struct DelegateAndCheckSpec {
    pub impl_generics: ImplGenerics,
    pub trait_name: Ident,
    pub context_type: Type,
    pub entries: Punctuated<DelegateAndCheckEntry, Comma>,
}

#[derive(Clone)]
pub struct DelegateAndCheckEntry {
    pub keys: Punctuated<DelegateAndCheckKey, Comma>,
    pub mode: DelegateMode,
    pub value: DelegateValue,
}

#[derive(Clone)]
pub struct DelegateAndCheckKey {
    pub component_type: Type,
    pub check_params: Option<Punctuated<Type, Comma>>,
}

impl Parse for DelegateAndCheckSpec {
    fn parse(input: ParseStream) -> syn::Result<Self> {
        let impl_generics = if input.peek(Lt) {
            input.parse()?
        } else {
            Default::default()
        };

        let m_trait_name = parse_check_trait_name(input)?;

        let context_type: Type = input.parse()?;

        let trait_name = match m_trait_name {
            Some(ident) => ident,
            None => {
                let context_type: SimpleType = parse2(context_type.to_token_stream())?;
                Ident::new(
                    &format!("__CanUse{}", context_type.name),
                    context_type.span(),
                )
            }
        };

        let entries = {
            let body;
            braced!(body in input);
            Punctuated::parse_terminated(&body)?
        };

        Ok(Self {
            impl_generics,
            trait_name,
            context_type,
            entries,
        })
    }
}

impl Parse for DelegateAndCheckEntry {
    fn parse(input: ParseStream) -> syn::Result<Self> {
        let check_params = parse_check_params(input)?;

        let mut keys = if input.peek(Bracket) {
            let body;
            bracketed!(body in input);
            Punctuated::parse_terminated(&body)?
        } else {
            let key: DelegateAndCheckKey = input.parse()?;
            Punctuated::from_iter(iter::once(key))
        };

        if let Some(check_params) = check_params {
            for key in &mut keys {
                key.check_params
                    .get_or_insert_default()
                    .extend(check_params.clone());
            }
        }

        let mode = input.parse()?;

        let value = input.parse()?;

        Ok(Self { keys, mode, value })
    }
}

impl Parse for DelegateAndCheckKey {
    fn parse(input: ParseStream) -> syn::Result<Self> {
        let check_params = parse_check_params(input)?;

        let component_type: Type = input.parse()?;
        Ok(Self {
            component_type,
            check_params,
        })
    }
}

pub fn parse_check_trait_name(input: ParseStream) -> syn::Result<Option<Ident>> {
    if input.peek(Pound) {
        let attributes = input.call(Attribute::parse_outer)?;

        let [attribute]: [Attribute; 1] = attributes
            .try_into()
            .map_err(|_| input.error("Expected exactly one attribute for the check trait name"))?;

        if !attribute.path().is_ident("check_trait") {
            return Err(syn::Error::new(
                attribute.span(),
                "Expected `#[check_trait]` attribute for specifying the check trait name",
            ));
        }

        let ident: Ident = attribute.parse_args()?;
        Ok(Some(ident))
    } else {
        Ok(None)
    }
}

pub fn parse_check_params(input: ParseStream) -> syn::Result<Option<Punctuated<Type, Comma>>> {
    if input.peek(Pound) {
        let attributes = input.call(Attribute::parse_outer)?;

        let [attribute]: [Attribute; 1] = attributes
            .try_into()
            .map_err(|_| input.error("Expected exactly one key attribute"))?;

        let check_params = if attribute.path().is_ident("check_params") {
            attribute.parse_args_with(Punctuated::parse_terminated)?
        } else if attribute.path().is_ident("skip_check") {
            Punctuated::new()
        } else {
            return Err(syn::Error::new(
                attribute.span(),
                "Expected either `#[skip_check]` or `#[check_params]` attribute for specifying the check generics",
            ));
        };

        Ok(Some(check_params))
    } else {
        Ok(None)
    }
}