structural_derive 0.4.3

Implementation detail of the structural crate.
Documentation
use crate::{
    arenas::Arenas,
    datastructure::StructOrEnum,
    ident_or_index::IdentOrIndexRef,
    structural_alias_impl_mod::{
        Exhaustiveness, FieldType, IdentType, StructuralAliasParams, StructuralDataType,
        StructuralField, StructuralVariant,
    },
    tokenizers::tstr_tokens,
    write_docs::{self, DocsFor},
};

use as_derive_utils::{
    datastructure::{DataStructure, DataVariant, Field, Struct},
    gen_params_in::{GenParamsIn, InWhat},
    return_spanned_err, return_syn_err,
    utils::expr_from_int,
};

use core_extensions::SelfOps;

use proc_macro2::{Span, TokenStream as TokenStream2};

use quote::{quote, ToTokens, TokenStreamExt};

use syn::{punctuated::Punctuated, DeriveInput, Ident, Visibility};

mod attribute_config;

mod attribute_parsing;

mod delegation;

mod from_structural;

#[cfg(test)]
mod tests;

use self::{attribute_parsing::StructuralOptions, delegation::DelegateTo};

#[cfg(test)]
fn derive_from_str(string: &str) -> Result<TokenStream2, syn::Error> {
    syn::parse_str(string).and_then(derive)
}

const STRUCTURAL_SIZE_LIMIT: usize = 64;

pub fn derive(data: DeriveInput) -> Result<TokenStream2, syn::Error> {
    let ds = &DataStructure::new(&data);

    match ds.data_variant {
        DataVariant::Enum => {}
        DataVariant::Union => {
            return_syn_err!(Span::call_site(), "Cannot derive Structural on an union")
        }
        DataVariant::Struct => {}
    }

    let options = attribute_parsing::parse_attrs_for_structural(ds)?;
    let debug_print = options.debug_print;

    match &options.delegate_to {
        Some(to) => delegating_structural(ds, &options, to),
        None => {
            let arenas = Arenas::default();
            deriving_structural(ds, &options, &arenas)
        }
    }?
    .observe(|tokens| {
        if debug_print {
            panic!("\n\n\n{}\n\n\n", tokens);
        }
    })
    .piped(Ok)
}

fn delegating_structural<'a>(
    ds: &'a DataStructure<'a>,
    options: &'a StructuralOptions<'a>,
    delegate_to: &'a DelegateTo<'a>,
) -> Result<TokenStream2, syn::Error> {
    let DelegateTo {
        field,
        delegation_params,
        bounds,
        mut_bounds,
        move_bounds,
    } = delegate_to;

    let StructuralOptions { drop_params, .. } = options;

    let field = *field;

    use std::fmt::Write;

    let struct_ = &ds.variants[0];

    let non_delegated_to_fields = struct_
        .fields
        .iter()
        .filter(|&f| !std::ptr::eq(field, f))
        .map(|f| &f.ident);

    let (_, ty_generics, where_clause) = ds.generics.split_for_impl();

    let impl_generics = GenParamsIn::new(ds.generics, InWhat::ImplHeader);

    let tyname = ds.name;

    let the_field = &field.ident;
    let fieldty = field.ty;

    let empty_preds = Punctuated::new();
    let where_preds = where_clause
        .as_ref()
        .map_or(&empty_preds, |x| &x.predicates)
        .into_iter();

    let mut docs = format!("`{}` delegates all its accessor trait impls to ", tyname);
    let ty_tokens = field.ty.to_token_stream();
    let _ = match field.vis {
        Visibility::Public { .. } => write!(docs, "the `{}: {}` field", field.ident, ty_tokens),
        _ => write!(docs, "a private `{}` field", ty_tokens),
    };

    let pre_move = drop_params.pre_move.as_ref().into_iter();
    let pre_post_drop = drop_params.pre_post_drop_fields;

    quote!(::structural::unsafe_delegate_structural_with! {
        #[doc=#docs]
        impl[#impl_generics] #tyname #ty_generics
        where[
            #(#where_preds,)*
            #(#bounds,)*
        ]

        self_ident=this;
        #delegation_params
        delegating_to_type= #fieldty;

        GetField { &this.#the_field }


        GetFieldMut
        where[ #(#mut_bounds,)* ]
        { &mut this.#the_field }

        as_delegating_raw{
            &mut (*this).#the_field as *mut #fieldty
        }


        IntoField
        where[ #(#move_bounds,)* ]
        { this.#the_field }
        move_out_field{ &mut this.#the_field }

        DropFields = {
            dropped_fields[ #(#non_delegated_to_fields)* ]
            #( pre_move = #pre_move; )*
            pre_post_drop_fields= #pre_post_drop ;
        }
    })
    .piped(Ok)
}

#[allow(clippy::cognitive_complexity)]
fn deriving_structural<'a>(
    ds: &'a DataStructure<'a>,
    options: &'a StructuralOptions<'a>,
    _arenas: &'a Arenas,
) -> Result<TokenStream2, syn::Error> {
    let StructuralOptions {
        drop_params,
        fields: config_fields,
        with_trait_alias,
        non_exhaustive_attr,
        ..
    } = options;

    let struct_ = &ds.variants[0];

    let vis = ds.vis;

    let tyname = ds.name;

    let struct_or_enum = match ds.data_variant {
        DataVariant::Struct => StructOrEnum::Struct,
        DataVariant::Enum => StructOrEnum::Enum,
        DataVariant::Union => unreachable!(),
    };

    let mut contains_move_field = false;

    let mut make_fields = |variant: &'a Struct<'a>| {
        variant
            .fields
            .iter()
            .filter_map(|field| {
                let config_f = &config_fields[field.index];

                if !config_f.is_pub {
                    return None;
                }

                let ident = match &config_f.renamed {
                    Some(x) => IdentType::Ident(x.borrowed()),
                    None => IdentType::from(&field.ident),
                };

                if config_f.access.has_by_value_access() {
                    contains_move_field = true;
                }

                Some(StructuralField {
                    access: config_f.access,
                    ident,
                    pub_field_rename: if field.is_public() && config_f.renamed.is_some() {
                        Some(IdentOrIndexRef::from(&field.ident))
                    } else {
                        None
                    },
                    ty: match &config_f.is_impl {
                        Some(yes) => FieldType::Impl(yes),
                        None => FieldType::Ty(field.ty),
                    },
                })
            })
            .collect::<Vec<StructuralField<'a>>>()
    };

    let sdt = match struct_or_enum {
        StructOrEnum::Struct => StructuralDataType {
            type_name: Some(ds.name),
            fields: make_fields(struct_),
            variants: Vec::new(),
        },
        StructOrEnum::Enum => StructuralDataType {
            type_name: Some(ds.name),
            fields: Vec::new(),
            variants: ds
                .variants
                .iter()
                .enumerate()
                .map(|(vari, variant)| {
                    let config_v = &options.variants[vari];

                    let name: IdentOrIndexRef<'a> = match &config_v.renamed {
                        Some(x) => x.borrowed(),
                        None => variant.name.into(),
                    };

                    StructuralVariant {
                        name: IdentType::Ident(name),
                        pub_vari_rename: if options.generate_docs && config_v.renamed.is_some() {
                            Some(variant.name.into())
                        } else {
                            None
                        },
                        fields: make_fields(variant),
                        is_newtype: config_v.is_newtype,
                        replace_bounds: config_v.replace_bounds.as_ref(),
                    }
                })
                .collect(),
        },
    };

    {
        if sdt.fields.len() > STRUCTURAL_SIZE_LIMIT {
            return_spanned_err! {
                ds.name,
                "Structs cannot have more than {} fields with accessors",
                STRUCTURAL_SIZE_LIMIT,
            }
        }

        if let Some(i) = sdt
            .variants
            .iter()
            .position(|v| v.fields.len() > STRUCTURAL_SIZE_LIMIT)
        {
            return_spanned_err! {
                ds.variants[i].name,
                "Variants cannot have more than {} fields with accessors",
                STRUCTURAL_SIZE_LIMIT,
            }
        }
    }

    let mut structural_alias_trait = TokenStream2::new();

    if *with_trait_alias {
        let trait_ident = Ident::new(&format!("{}_SI", tyname), Span::call_site());
        let soe_str = match struct_or_enum {
            StructOrEnum::Struct => "struct",
            StructOrEnum::Enum => "enum",
        };

        let exhaustive_ident = Ident::new(&format!("{}_ESI", tyname), Span::call_site());

        let enum_exhaustiveness = match (struct_or_enum, non_exhaustive_attr) {
            (StructOrEnum::Struct, _) => Exhaustiveness::Nonexhaustive,
            (StructOrEnum::Enum, false) => Exhaustiveness::AndExhaustive {
                name: &exhaustive_ident,
            },
            (StructOrEnum::Enum, true) => Exhaustiveness::Nonexhaustive,
        };

        Ident::new(&format!("{}_SI", tyname), Span::call_site());

        let docs = if options.generate_docs {
            Some(format!(
                "A trait aliasing the accessor impls for \
                 [{tyname}](./{soe_str}.{tyname}.html) fields\n\
                 \n\
                 This trait also has all the constraints(where clause and generic parameter bounds)
                 of [the same type](./{soe_str}.{tyname}.html).\n\n\
                 ### Accessor traits\n\
                 These are the accessor traits this aliases:\n\
                ",
                tyname = tyname,
                soe_str = soe_str,
            ))
        } else {
            None
        };

        let struct_variant_trait = match struct_or_enum {
            StructOrEnum::Struct => Some(Ident::new(&format!("{}_VSI", tyname), Span::call_site())),
            StructOrEnum::Enum => None,
        };

        let sop = StructuralAliasParams {
            span: tyname.span(),
            attrs: None::<&Ident>,
            docs,
            vis,
            ident: &trait_ident,
            generics: ds.generics,
            extra_where_preds: &options.bounds,
            supertraits: &Punctuated::new(),
            trait_items: &[],
            variant_trait: struct_variant_trait.as_ref(),
            enum_exhaustiveness,
            datatype: &sdt,
        };

        structural_alias_trait.append_all(sop.tokens()?);
    }

    let impl_generics = GenParamsIn::new(ds.generics, InWhat::ImplHeader);

    let (_, ty_generics, where_clause) = ds.generics.split_for_impl();

    let empty_preds = Punctuated::new();
    let where_preds = where_clause
        .as_ref()
        .map_or(&empty_preds, |x| &x.predicates)
        .into_iter();

    let mut config_variants = options.variants.iter();

    let drop_fields_arg = if contains_move_field {
        let pre_post_drop_fields = if drop_params.pre_post_drop_fields {
            quote!(pre_post_drop)
        } else {
            quote!(just_fields)
        };

        let pre_move = drop_params.pre_move.as_ref().into_iter();
        quote! {
            drop_fields={
                #pre_post_drop_fields,
                #( pre_move = #pre_move, )*
            }
        }
    } else {
        quote!(drop_fields = custom_drop)
    };

    let tuple = match struct_or_enum {
        StructOrEnum::Struct => {
            let fields = struct_
                .fields
                .iter()
                .filter(|&f| config_fields[f].is_pub)
                .collect::<Vec<&Field<'_>>>();

            let getter_trait = sdt
                .fields
                .iter()
                .map(|f| f.access.compute_trait(StructOrEnum::Struct));

            let indices = (0..).map(expr_from_int);

            let not_public_field_names = struct_
                .fields
                .iter()
                .filter(|&f| !config_fields[f].is_pub)
                .map(|f| &f.ident);

            let field_names = fields.iter().map(|f| &f.ident);

            let field_name_tstrs = sdt.fields.iter().map(|f| f.ident.tstr_tokens());

            let field_tys = fields.iter().map(|f| f.ty);

            let renamed_field_names =
                fields
                    .iter()
                    .map(|&field| match &config_fields[field].renamed {
                        Some(x) => x.to_string(),
                        None => field.ident.to_string(),
                    });

            (
                quote!(_private_impl_getters_for_derive_struct),
                quote!(),
                quote!(
                    DropFields{
                        #drop_fields_arg
                        not_public( #(#not_public_field_names)* )
                    }

                    #((
                        #getter_trait<
                            #field_names : #field_tys ,
                            #indices,
                            #field_name_tstrs,
                            #renamed_field_names,
                        >
                    ))*
                ),
            )
        }
        StructOrEnum::Enum => {
            let variants = ds
                .variants
                .iter()
                .zip(&sdt.variants)
                .map(|(variant, sdt_variant)| {
                    let config_v = config_variants.next().unwrap();

                    let variant_kind = if config_v.is_newtype {
                        quote!(newtype)
                    } else {
                        quote!(regular)
                    };

                    let field_tokens = variant
                        .fields
                        .iter()
                        .filter(|&f| config_fields[f].is_pub)
                        .zip(&sdt_variant.fields)
                        .zip((0..).map(expr_from_int))
                        .map(|((field, sdt_field), field_index)| {
                            let access = sdt_field.access.compute_trait(StructOrEnum::Enum);
                            let fname = &field.ident;
                            let fty = field.ty;
                            let field_variable = field.ident();
                            let f_tstr = sdt_field.ident.tstr_tokens();
                            quote!(
                                #access,
                                #fname:#fty,
                                dropping(#field_variable ,#field_index),
                                #f_tstr,
                            )
                        });

                    let not_public = variant
                        .fields
                        .iter()
                        .filter(|&f| !config_fields[f].is_pub)
                        .map(|field| {
                            let ident = &field.ident;
                            let var_ident = field.ident();
                            quote!((#ident = #var_ident))
                        });

                    let variant_name = variant.name;
                    let variant_str = sdt_variant.name.tokens();
                    quote!(
                        #variant_name,
                        #variant_str,
                        kind=#variant_kind,
                        not_public( #(#not_public)* ),
                        fields( #( (#field_tokens) )* )
                    )
                });

            let enum_ = ds.name;
            let variant_count = tstr_tokens(ds.variants.len().to_string(), tyname.span());

            let variant_count_tokens = if options.make_variant_count_alias {
                let variant_count_ident_str = format!("{}_VC", ds.name);
                let variant_count_docs = format!(
                    "\
                        The amount of variants in the [{0} enum](./enum.{0}.html)\n\
                        \n\
                        This is a structural::TStr,\
                        which can be instantiated with {1}::NEW.\n\
                    ",
                    ds.name, variant_count_ident_str,
                );
                let variant_count_type =
                    syn::Ident::new(&variant_count_ident_str, Span::call_site());

                quote!(
                    #[doc=#variant_count_docs]
                    #vis type #variant_count_type=#variant_count;
                )
            } else {
                quote!()
            };

            let variant_count_param = if *non_exhaustive_attr {
                quote!()
            } else {
                quote!(variant_count=#variant_count,)
            };

            (
                quote!(_private_impl_getters_for_derive_enum),
                variant_count_tokens,
                quote! {
                    enum=#enum_
                    #drop_fields_arg
                    #variant_count_param
                    #((#variants))*
                },
            )
        }
    };

    let from_structural_tokens = match &options.from_struc {
        Some(fso) => from_structural::deriving_from_structural(ds, options, fso)?,
        None => TokenStream2::new(),
    };

    let mut impl_docs = String::new();
    if options.generate_docs {
        write_docs::write_datatype_docs(&mut impl_docs, DocsFor::Type, &sdt)?;
    }

    let (which_macro, soe_specific_out, soe_specific_in) = tuple;
    let extra_where_preds = options.bounds.iter();

    quote!(
        #from_structural_tokens

        #structural_alias_trait

        #soe_specific_out

        ::structural::#which_macro!{
            #[doc=#impl_docs]
            impl[#impl_generics] #tyname #ty_generics
            where[
                #(#where_preds,)*
                #(#extra_where_preds,)*
            ]
            {#soe_specific_in}
        }
    )
    .piped(Ok)
}