maybe-fatal-derive 0.1.0-beta.4

Code generation for the maybe-fatal crate
Documentation
use core::iter::zip;

use proc_macro2::TokenStream;
use quote::quote;
use syn::parse_quote;

use crate::{attribute, utils};

pub fn parse(input: syn::DeriveInput) -> syn::Result<TokenStream> {
    let syn::DeriveInput {
        attrs,
        ident,
        generics,
        data,
        ..
    } = input;

    let helper_info = attribute::item::MaybeFatal::parse_from_attrs(&attrs)?;

    let Some(span_type) = helper_info.span_type else {
        return Err(syn::Error::new_spanned(
            ident,
            "missing `span_type` meta attribute",
        ));
    };

    let discriminant_type = helper_info
        .group
        .and_then(|group| group.discriminant_type)
        .unwrap_or_else(|| parse_quote!(::maybe_fatal::code::DefaultDiscriminant));

    let method_body = utils::process_data(
        data,
        |data| parse_struct(quote! { self. }, &attrs, &data.fields),
        parse_enum,
        utils::deny_data!("cannot derive `PartialDiagnose` for `union`s"),
    )?;

    let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
    let where_clause: syn::WhereClause = match where_clause {
        Some(syn::WhereClause {
            where_token,
            predicates,
        }) => {
            let predicates = predicates.iter();
            syn::parse_quote! { #where_token #(#predicates),* #span_type: ::ariadne::Span }
        }
        None => syn::parse_quote! { where #span_type: ::ariadne::Span },
    };

    Ok(quote! {
        impl #impl_generics ::maybe_fatal::traits::PartialDiagnose<#span_type, #discriminant_type>
            for #ident #ty_generics
        #where_clause
        {
            fn partial_diagnose(
                self,
                mut diagnostic: ::maybe_fatal::Diagnostic<#span_type, #discriminant_type>,
                colors: &::maybe_fatal::ColorPalette,
            ) -> ::maybe_fatal::Diagnostic<#span_type, #discriminant_type> {
                #![allow(unused)]
                #method_body

                diagnostic
            }
        }
    })
}

fn parse_struct(
    self_dot: TokenStream,
    attrs: &[syn::Attribute],
    fields: &syn::Fields,
) -> syn::Result<TokenStream> {
    let mut body = TokenStream::new();

    let helper_info = attribute::variant::MaybeFatal::parse_from_attrs(attrs)?;
    if let Some(label_meta) = helper_info.label {
        body.extend(quote! {
            diagnostic.label(::ariadne::Label::new(
                <_ as ::core::clone::Clone>::clone(&diagnostic.span)
            ) #label_meta);
        });
    }

    for note in helper_info.notes {
        body.extend(quote! { diagnostic.note(#note); });
    }

    for help in helper_info.help_messages {
        body.extend(quote! { diagnostic.help(#help); });
    }

    for (field, member) in zip(fields.iter(), fields.members()) {
        let field_info = attribute::field::MaybeFatal::parse_from_attrs(&field.attrs)?;
        if let Some(label_meta) = field_info.label {
            body.extend(quote! {
                diagnostic.label(::ariadne::Label::new(#self_dot #member) #label_meta);
            });
        }
    }

    Ok(body)
}

fn parse_enum(data: syn::DataEnum) -> syn::Result<TokenStream> {
    let mut match_body = TokenStream::new();
    for variant in data.variants {
        let ident = variant.ident;
        let arm_body = parse_struct(TokenStream::new(), &variant.attrs, &variant.fields)?;
        let members = variant.fields.members();
        match_body.extend(quote! { Self::#ident { #(#members),* } => { #arm_body } })
    }

    Ok(quote! { match self { #match_body } })
}