use syn::{Field, Fields, GenericParam, ItemMod, ItemStruct, Lifetime, Type};
use quote::quote;
use proc_macro2::Span;
use crate::{TusksModule, models::TusksParameters};
impl TusksModule {
pub fn supplement_parameters(
&mut self,
module: &mut ItemMod,
is_tusks_root: bool,
derive_debug: bool,
) -> syn::Result<()> {
let lifetime = if let Some(ref params) = self.parameters {
Self::extract_lifetime(¶ms.pstruct)?
} else {
self.add_parameters_struct(module, derive_debug)?
};
let mut parameters_struct = Self::find_parameters_struct_mut(module)?;
if !is_tusks_root {
self.add_super_field_to_parameters_struct(
&mut parameters_struct,
&lifetime)?;
}
Self::add_phantom_field_to_struct(&mut parameters_struct, &lifetime)?;
if let Some(ref mut params) = self.parameters {
params.pstruct = parameters_struct.clone();
}
if let Some((_, ref mut items)) = module.content {
for submodule_data in &mut self.submodules {
if let Some(item_mod) = items.iter_mut().find_map(|item| {
if let syn::Item::Mod(m) = item {
if m.ident == submodule_data.name {
return Some(m);
}
}
None
}) {
submodule_data.supplement_parameters(
item_mod,
false,
derive_debug
)?;
}
}
}
Ok(())
}
pub fn extract_lifetime(item_struct: &ItemStruct) -> syn::Result<Lifetime> {
for param in &item_struct.generics.params {
if let GenericParam::Lifetime(lifetime_param) = param {
return Ok(lifetime_param.lifetime.clone());
}
}
Err(syn::Error::new_spanned(
&item_struct.ident,
"Parameters struct must have a lifetime parameter"
))
}
fn add_parameters_struct(
&mut self,
module: &mut ItemMod,
derive_debug: bool
) -> syn::Result<Lifetime> {
let lifetime = Lifetime::new("'a", Span::call_site());
let lifetime_param = { quote! {<#lifetime>} };
let derive_attr =
if derive_debug { quote! { #[derive(Debug)] } }
else { quote! {} };
let tokens = quote! {
#derive_attr
pub struct Parameters #lifetime_param {
}
};
let params_struct: ItemStruct = syn::parse2(tokens).map_err(|e| {
syn::Error::new(Span::call_site(), format!("Failed to create Parameters struct: {}", e))
})?;
Self::add_struct_to_module(module, params_struct.clone())?;
self.parameters = Some(TusksParameters{ pstruct: params_struct });
Ok(lifetime)
}
fn add_struct_to_module(module: &mut ItemMod, item_struct: ItemStruct) -> syn::Result<()> {
if let Some((_, ref mut items)) = module.content {
items.insert(0, syn::Item::Struct(item_struct));
Ok(())
} else {
Err(syn::Error::new_spanned(
&module.ident,
"Module has no content"
))
}
}
fn find_parameters_struct_mut(module: &mut ItemMod) -> syn::Result<&mut ItemStruct> {
if let Some((_, ref mut items)) = module.content {
for item in items.iter_mut() {
if let syn::Item::Struct(s) = item {
if s.ident == "Parameters" {
return Ok(s);
}
}
}
}
Err(syn::Error::new_spanned(
&module.ident,
"Parameters struct not found in module"
))
}
fn add_super_field_to_parameters_struct(
&mut self,
parameters_struct: &mut ItemStruct,
lifetime: &Lifetime
) -> syn::Result<()> {
let super_type = if self.external_parent.is_some() {
quote! { &#lifetime parent_::Parameters<#lifetime> }
} else {
quote! { &#lifetime super::Parameters<#lifetime> }
};
let super_field_type: Type = syn::parse2(super_type).map_err(|e| {
syn::Error::new(Span::call_site(), format!("Failed to parse super_ type: {}", e))
})?;
let super_field = Field {
attrs: vec![],
vis: syn::Visibility::Public(syn::token::Pub::default()),
mutability: syn::FieldMutability::None,
ident: Some(syn::Ident::new("super_", Span::call_site())),
colon_token: Some(syn::token::Colon::default()),
ty: super_field_type,
};
if let Fields::Named(ref mut fields) = parameters_struct.fields {
fields.named.push(super_field);
} else {
return Err(syn::Error::new_spanned(
¶meters_struct.ident,
"Parameters struct must have named fields"
));
}
Ok(())
}
fn add_phantom_field_to_struct(
item_struct: &mut ItemStruct,
lifetime: &Lifetime
) -> syn::Result<()> {
use syn::{Field, Type, Ident};
let phantom_type: Type = syn::parse2(quote! {
::std::marker::PhantomData<&#lifetime ()>
})?;
let phantom_field = Field {
attrs: vec![],
vis: syn::Visibility::Public(syn::token::Pub::default()),
mutability: syn::FieldMutability::None,
ident: Some(Ident::new("_phantom_lifetime_marker", Span::call_site())),
colon_token: Some(Default::default()),
ty: phantom_type,
};
match &mut item_struct.fields {
syn::Fields::Named(fields) => {
fields.named.push(phantom_field);
}
syn::Fields::Unnamed(_) => {
return Err(syn::Error::new_spanned(
item_struct,
"Cannot add named field to tuple struct"
));
}
syn::Fields::Unit => {
item_struct.fields = syn::Fields::Named(syn::FieldsNamed {
brace_token: Default::default(),
named: {
let mut fields = syn::punctuated::Punctuated::new();
fields.push(phantom_field);
fields
}
});
}
}
Ok(())
}
}