segsource-derive 0.2.0

Derive implementations for `segsource`. You should use `segsource` instead.
Documentation
use alloc::rc::Rc;
use pmhelp::{parse::token_stream::parenthesized, util::create_new_lifetimes};
use proc_macro2::TokenStream;
use quote::quote;
use syn::{
    parse::{Parser as _, Result},
    punctuated::Punctuated,
    Data, DataEnum, DataStruct, DeriveInput, Fields, FieldsNamed, FieldsUnnamed, Ident, Token,
    Type,
};
mod attrs;
use attrs::{AlsoNeeds, FromSegField, FromSegInfo};

fn generate_fields_body(
    ident: &Ident,
    fields: Fields,
    also_needs: Rc<AlsoNeeds>,
    generating_try_from: bool,
) -> TokenStream {
    let (tuple_like, fields_iter) = match fields {
        Fields::Named(FieldsNamed { named, .. }) => (false, named.into_iter()),
        Fields::Unnamed(FieldsUnnamed { unnamed, .. }) => (true, unnamed.into_iter()),
        Fields::Unit => panic!("Struct or variant {} has no fields!", ident),
    };
    let fields: Punctuated<FromSegField, Token![;]> = fields_iter
        .map(|f| (f, generating_try_from, Rc::clone(&also_needs)))
        .enumerate()
        .map(FromSegField::from)
        .collect();
    let field_names: Punctuated<Ident, Token![,]> =
        fields.iter().map(FromSegField::tmp_var).collect();
    let create_self_stmt = if tuple_like && generating_try_from {
        quote! {Ok(Self(#field_names))}
    } else if tuple_like {
        quote! {Self(#field_names)}
    } else if generating_try_from {
        quote! {Ok(Self{#field_names})}
    } else {
        quote! {Self{#field_names}}
    };
    quote! {
        #fields
        #create_self_stmt
    }
}

fn generate_body(
    ident: &Ident,
    data: Data,
    also_needs: Rc<AlsoNeeds>,
    generating_try_from: bool,
) -> TokenStream {
    match data {
        Data::Struct(DataStruct { fields, .. }) => {
            generate_fields_body(ident, fields, also_needs, generating_try_from)
        }
        Data::Enum(DataEnum { variants, .. }) => todo!(),
        _ => unimplemented!(),
    }
}

pub fn base_from_segment(input: DeriveInput, generating_try_from: bool) -> Result<TokenStream> {
    let mut maybe_info = None;
    for attr in input.attrs {
        if attr.path.is_ident("from_seg") {
            maybe_info = Some(syn::parse2(parenthesized::<TokenStream>(attr.tokens)?)?);
            break;
        }
    }
    let ([lifetime], generics) = create_new_lifetimes(&input.generics);
    let (impl_g, _, maybe_where) = generics.split_for_impl();
    let FromSegInfo {
        item_type,
        error_type,
        mut also_needs,
    } = maybe_info.unwrap_or_default();
    if generating_try_from && error_type.is_none() {
        panic!("No error type specified!");
    }
    also_needs.set_segment_generics(quote! {#lifetime, #item_type});
    let also_needs = Rc::new(also_needs);
    let error_stmt = error_type
        .map(|etype| quote! {type Error = #etype;})
        .unwrap_or_default();
    let name = input.ident;
    let (_, type_g, _) = input.generics.split_for_impl();
    let segment_type = also_needs.get_type();
    let method_args = also_needs.get_args();
    let (trait_name, method_sig) = if generating_try_from {
        (
            quote! {::core::convert::TryFrom},
            quote! {
                try_from(#method_args: #segment_type)
                    -> ::core::result::Result<Self, Self::Error>
            },
        )
    } else {
        (
            quote! {::core::convert::From},
            quote! {from(segment: #segment_type) -> Self},
        )
    };
    let body = generate_body(&name, input.data, also_needs, generating_try_from);
    Ok(quote! {
        impl #impl_g #trait_name<#segment_type> for #name #type_g #maybe_where {
            #error_stmt
            #[allow(unused_parens)]
            fn #method_sig {
                #body
            }
        }
    })
}

pub(crate) fn derive_from_segment(input: DeriveInput) -> TokenStream {
    base_from_segment(input, false).unwrap()
}

pub(crate) fn derive_try_from_segment(input: DeriveInput) -> TokenStream {
    base_from_segment(input, true).unwrap()
}