use proc_macro2::TokenStream;
use quote::quote;
use syn::Ident;
use crate::parsing::StructInfo;
fn generate_fields_trait_parts(field_names: &[&Ident]) -> Vec<TokenStream> {
field_names
.iter()
.map(|name| {
quote! { type #name: Presence; }
})
.collect()
}
fn generate_fieldset_parts(
field_names: &[&Ident],
) -> (Vec<TokenStream>, Vec<TokenStream>, Vec<TokenStream>) {
let phantom_types = field_names
.iter()
.map(|name| {
quote! { #name }
})
.collect();
let params = field_names
.iter()
.map(|name| {
quote! { #name: Presence }
})
.collect();
let assocs = field_names
.iter()
.map(|name| {
quote! { type #name = #name; }
})
.collect();
(phantom_types, params, assocs)
}
fn generate_merge_fields(field_names: &[&Ident]) -> Vec<TokenStream> {
field_names
.iter()
.map(|name| {
quote! {
<<F2 as Fields>::#name as Presence>::Or<<F1 as Fields>::#name>
}
})
.collect()
}
fn generate_omit_fields(field_names: &[&Ident]) -> Vec<TokenStream> {
field_names
.iter()
.map(|name| {
quote! {
<<F2 as Fields>::#name as ::structural_typing::presence::Presence>::RemainderFrom<<F1 as Fields>::#name>
}
})
.collect()
}
fn generate_with_modules(field_names: &[&Ident]) -> TokenStream {
let has_multiple_fields = field_names.len() > 1;
let all_absent_default: Vec<_> = std::iter::repeat_n(
quote! { ::structural_typing::presence::Absent },
field_names.len(),
)
.collect();
let field_modules: Vec<_> = field_names
.iter()
.enumerate()
.map(|(current_idx, field_name)| {
let present_types: Vec<_> = field_names
.iter()
.enumerate()
.map(|(idx, name)| {
if idx == current_idx {
quote! { ::structural_typing::presence::Present }
} else {
quote! { F::#name }
}
})
.collect();
let optional_types: Vec<_> = field_names
.iter()
.enumerate()
.map(|(idx, name)| {
if idx == current_idx {
quote! { ::structural_typing::presence::Optional }
} else {
quote! { F::#name }
}
})
.collect();
let absent_types: Vec<_> = field_names
.iter()
.enumerate()
.map(|(idx, name)| {
if idx == current_idx {
quote! { ::structural_typing::presence::Absent }
} else {
quote! { F::#name }
}
})
.collect();
if has_multiple_fields {
quote! {
pub mod #field_name {
use super::*;
pub type Present<F: Fields = FieldSet<#(#all_absent_default),*>> = FieldSet<#(#present_types),*>;
pub type Optional<F: Fields = FieldSet<#(#all_absent_default),*>> = FieldSet<#(#optional_types),*>;
pub type Absent<F: Fields = FieldSet<#(#all_absent_default),*>> = FieldSet<#(#absent_types),*>;
}
}
} else {
quote! {
pub mod #field_name {
use super::*;
pub type Present = FieldSet<#(#present_types),*>;
pub type Optional = FieldSet<#(#optional_types),*>;
pub type Absent = FieldSet<#(#absent_types),*>;
}
}
}
})
.collect();
let all_present: Vec<_> = std::iter::repeat_n(
quote! { ::structural_typing::presence::Present },
field_names.len(),
)
.collect();
let all_optional: Vec<_> = std::iter::repeat_n(
quote! { ::structural_typing::presence::Optional },
field_names.len(),
)
.collect();
let all_absent: Vec<_> = std::iter::repeat_n(
quote! { ::structural_typing::presence::Absent },
field_names.len(),
)
.collect();
quote! {
pub mod with {
use super::*;
#(#field_modules)*
pub mod all {
use super::*;
pub type Present = FieldSet<#(#all_present),*>;
pub type Optional = FieldSet<#(#all_optional),*>;
pub type Absent = FieldSet<#(#all_absent),*>;
}
}
}
}
pub fn generate(info: &StructInfo) -> TokenStream {
let module_name = &info.module_name;
let vis = &info.vis;
let field_names: Vec<_> = info.fields.iter().map(|f| &f.name).collect();
let field_type_assocs = generate_fields_trait_parts(&field_names);
let (fieldset_phantom_types, fieldset_params, fieldset_assocs) =
generate_fieldset_parts(&field_names);
let merge_fields = generate_merge_fields(&field_names);
let omit_fields = generate_omit_fields(&field_names);
let with_modules = generate_with_modules(&field_names);
quote! {
#vis mod #module_name {
use super::*;
use ::core::marker::PhantomData;
use ::structural_typing::presence::{Presence, Present, Optional, Absent};
mod sealed {
pub trait Sealed {}
}
#[allow(non_camel_case_types)]
pub trait Fields: sealed::Sealed {
#(#field_type_assocs)*
}
#[allow(non_camel_case_types)]
pub struct FieldSet<#(#fieldset_params),*>(
PhantomData<(#(#fieldset_phantom_types),*)>,
);
#[allow(non_camel_case_types)]
impl<#(#fieldset_params),*> sealed::Sealed for FieldSet<#(#field_names),*> {}
#[allow(non_camel_case_types)]
impl<#(#fieldset_params),*> Fields for FieldSet<#(#field_names),*> {
#(#fieldset_assocs)*
}
pub type Merge<F1, F2> = FieldSet<
#(#merge_fields),*
>;
pub type Omit<F1, F2> = FieldSet<
#(#omit_fields),*
>;
#with_modules
}
}
}