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 crate::{
    attribute::{self, field::FieldKind},
    utils,
};

pub fn parse(input: syn::DeriveInput) -> syn::Result<TokenStream> {
    let helper_info = attribute::item::MaybeFatal::parse_from_attrs(&input.attrs)?;

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

    let syn::DataStruct { fields, .. } = utils::try_into_data_struct(
        input.data,
        || "cannot derive `Diagnose` for `enum`s",
        || "cannot derive `Diagnose` for `union`s",
    )?;

    let mut info_member = None::<syn::Member>;
    let mut span_member = None::<syn::Member>;

    for (field, member) in zip(fields.iter(), fields.members()) {
        let field_info = attribute::field::MaybeFatal::parse_from_attrs(&field.attrs)?;
        match field_info.kind {
            Some(FieldKind::Info) if info_member.is_none() => info_member = Some(member),
            Some(FieldKind::Info) => {
                return Err(syn::Error::new_spanned(
                    field,
                    "multiple fields marked with `info` meta attribute",
                ));
            }
            Some(FieldKind::Span) if span_member.is_none() => span_member = Some(member),
            Some(FieldKind::Span) => {
                return Err(syn::Error::new_spanned(
                    field,
                    "multiple fields marked with `span` meta attribute",
                ));
            }
            None => {}
        }
    }

    let Some(info_member) = info_member else {
        return Err(syn::Error::new(
            input.ident.span(),
            "missing field marked with `info` meta attribute",
        ));
    };

    let Some(span_member) = span_member else {
        return Err(syn::Error::new(
            input.ident.span(),
            "missing field marked with `span` meta attribute",
        ));
    };

    let name = &input.ident;
    let (impl_generics, ty_generics, where_clause) = input.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::Diagnose<#span_type> for #name #ty_generics
        #where_clause
        {
            fn diagnose(
                self,
                colors: &::maybe_fatal::ColorPalette,
            ) -> ::maybe_fatal::Diagnostic<#span_type> {
                self.#info_member.into_diagnostic(self.#span_member, colors)
            }
        }
    })
}