use crate::utils::*;
use proc_macro::TokenStream;
use proc_macro2::{Group, Ident, Span};
use quote::{ToTokens, quote};
use syn::{
Field, FieldsUnnamed, Token, parse_macro_input, parse_quote, punctuated::Punctuated,
token::Paren,
};
fn add_field_to_item_struct(
item: &mut syn::ItemStruct,
extra_field_name: &str,
extra_field_type: syn::Type,
) -> proc_macro2::TokenStream {
if let fields @ syn::Fields::Unit = &mut item.fields {
let span = Group::new(proc_macro2::Delimiter::Brace, fields.to_token_stream()).delim_span();
*fields = syn::Fields::Unnamed(FieldsUnnamed {
paren_token: Paren { span },
unnamed: Punctuated::default(),
})
}
fn fresh_ident(base: &str, existing: &[Ident]) -> Ident {
let existing: std::collections::HashSet<_> =
existing.iter().map(|id| id.to_string()).collect();
(0..)
.map(|i| {
if i == 0 {
base.to_string()
} else {
format!("{}{}", base, i)
}
})
.find(|name| !existing.contains(name))
.map(|name| Ident::new(&name, Span::call_site()))
.expect("should always find a fresh identifier")
}
let (fields, named) = match &mut item.fields {
syn::Fields::Named(fields_named) => (&mut fields_named.named, true),
syn::Fields::Unnamed(fields_unnamed) => (&mut fields_unnamed.unnamed, false),
syn::Fields::Unit => unreachable!("Unit structs were dealt with."),
};
let existing_names = fields
.iter()
.flat_map(|f| &f.ident)
.cloned()
.collect::<Vec<_>>();
let (extra_field_ident, extra_field_ident_ts) = if named {
let ident = fresh_ident(extra_field_name, &existing_names);
(Some(ident.clone()), ident.to_token_stream())
} else {
(
None,
syn::LitInt::new(&format!("{}", fields.len()), Span::call_site()).to_token_stream(),
)
};
fields.push(Field {
attrs: vec![],
vis: syn::Visibility::Inherited,
mutability: syn::FieldMutability::None,
ident: extra_field_ident,
colon_token: named.then_some(Token)),
ty: extra_field_type,
});
extra_field_ident_ts
}
pub(crate) fn setup_error_handling_struct(_attr: TokenStream, item: TokenStream) -> TokenStream {
let mut item: syn::ItemStruct = parse_macro_input!(item);
let krate = rust_engine_krate_name();
let extra_field_ident_ts = add_field_to_item_struct(
&mut item,
"error_handling_state",
parse_quote! {#krate::ast::visitors::wrappers::ErrorHandlingState},
);
let struct_name = &item.ident;
let generics = &item.generics;
quote! {
#item
impl #generics #krate::ast::HasSpan for #struct_name #generics {
fn span(&self) -> #krate::ast::span::Span {
self.#extra_field_ident_ts.0.clone()
}
fn span_mut(&mut self) -> &mut #krate::ast::span::Span {
&mut self.#extra_field_ident_ts.0
}
}
impl #generics #krate::ast::visitors::wrappers::VisitorWithErrors for #struct_name #generics {
fn error_vault(&mut self) -> &mut #krate::ast::visitors::wrappers::ErrorVault {
&mut self.#extra_field_ident_ts.1
}
}
}
.into()
}
pub(crate) fn setup_printer_struct(_attr: TokenStream, item: TokenStream) -> TokenStream {
let mut item: syn::ItemStruct = parse_macro_input!(item);
let krate = rust_engine_krate_name();
let extra_contextual_span_field_ident_ts = add_field_to_item_struct(
&mut item,
"contextual_span",
parse_quote! {Option<#krate::ast::span::Span>},
);
let extra_linked_item_graph_field_ident_ts = add_field_to_item_struct(
&mut item,
"linked_item_graph",
parse_quote! {::std::rc::Rc<#krate::attributes::LinkedItemGraph>},
);
let struct_name = &item.ident;
let generics = &item.generics;
quote! {
#item
impl #generics #krate::printer::pretty_ast::HasContextualSpan for #struct_name #generics {
fn span(&self) -> Option<#krate::ast::span::Span> {
self.#extra_contextual_span_field_ident_ts.clone()
}
fn with_span(&self, span: #krate::ast::span::Span) -> Self {
let mut printer = self.clone();
printer.#extra_contextual_span_field_ident_ts = Some(span);
printer
}
}
impl #generics #krate::printer::HasLinkedItemGraph for #struct_name #generics {
fn linked_item_graph(&self) -> &#krate::attributes::LinkedItemGraph {
&self.#extra_linked_item_graph_field_ident_ts
}
fn with_linked_item_graph(mut self, graph: ::std::rc::Rc<#krate::attributes::LinkedItemGraph>) -> Self {
self.#extra_linked_item_graph_field_ident_ts = graph;
self
}
}
}
.into()
}