use proc_macro2::{Span, TokenStream};
use quote::quote;
use syn::visit_mut::{self, VisitMut};
use syn::{
Data, DeriveInput, Error, Field, Fields, Ident, Path, Result, Token, Visibility, parse_quote,
};
type Punctuated = syn::punctuated::Punctuated<Field, Token![,]>;
pub fn embed(input: DeriveInput) -> Result<TokenStream> {
let call_site = Span::call_site();
let mut input = input;
let ident = input.ident.clone();
let vis = input.vis.clone();
let generics = input.generics.clone();
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
let self_path: Path = parse_quote!(#ident #ty_generics);
let orig_attrs = input.attrs.clone();
let mut attr_errors = Vec::new();
let readonly_indices;
let all_fields;
let mut doc_fields;
match &mut input.data {
Data::Struct(data) => match &mut data.fields {
Fields::Named(fields) => {
if fields.named.is_empty() {
return Err(Error::new(call_site, "input must have at least one field"));
}
let stripped_readonly_indices =
find_and_strip_readonly_attrs(&mut fields.named, &mut attr_errors);
if stripped_readonly_indices.is_empty() {
return Err(Error::new(
call_site,
"expected at least one #[readonly] field",
));
}
let conflicting_span = conflicting_read_only_span(&fields.named);
if conflicting_span.is_some() {
return Err(Error::new(
conflicting_span.unwrap_or(call_site),
"field name `read_only` conflicts with generated field",
));
}
let extracted_fields: Vec<Field> =
std::mem::take(&mut fields.named).into_iter().collect();
readonly_indices = stripped_readonly_indices;
doc_fields = extracted_fields.clone();
all_fields = extracted_fields;
}
Fields::Unnamed(_) | Fields::Unit => {
return Err(Error::new(call_site, "input must have named fields"));
}
},
Data::Enum(_) | Data::Union(_) => {
return Err(Error::new(call_site, "input must be a struct"));
}
}
let mut readonly_fields = Vec::new();
let mut remaining_fields = Vec::new();
for (i, field) in doc_fields.iter_mut().enumerate() {
if readonly_indices.contains(&i) && matches!(field.vis, Visibility::Inherited) {
field.vis = vis.clone();
}
}
for (i, mut field) in all_fields.into_iter().enumerate() {
if readonly_indices.contains(&i) {
if let Visibility::Inherited = field.vis {
field.vis = vis.clone();
}
ReplaceSelf::new(&self_path).visit_type_mut(&mut field.ty);
field.attrs.clear();
readonly_fields.push(field);
} else {
remaining_fields.push(field);
}
}
let readonly_ident = Ident::new(&format!("ReadOnly{}", ident), call_site);
let read_only_field: Field = parse_quote! {
read_only: #readonly_ident #ty_generics
};
let attr_errors_tokens = attr_errors.iter().map(Error::to_compile_error);
let outer_struct = if cfg!(feature = "docs") {
quote! {
#[cfg(doc)]
#(#orig_attrs)*
#vis struct #ident #impl_generics #where_clause {
#(#doc_fields),*
}
#[cfg(not(doc))]
#(#orig_attrs)*
#vis struct #ident #impl_generics #where_clause {
#read_only_field,
#(#remaining_fields),*
}
}
} else {
quote! {
#(#orig_attrs)*
#vis struct #ident #impl_generics #where_clause {
#read_only_field,
#(#remaining_fields),*
}
}
};
let deref_impl = if cfg!(feature = "docs") {
quote! {
#[cfg(not(doc))]
const _: () = {
impl #impl_generics ::core::ops::Deref for #ident #ty_generics #where_clause {
type Target = #readonly_ident #ty_generics;
fn deref(&self) -> &Self::Target {
&self.read_only
}
}
};
}
} else {
quote! {
const _: () = {
impl #impl_generics ::core::ops::Deref for #ident #ty_generics #where_clause {
type Target = #readonly_ident #ty_generics;
fn deref(&self) -> &Self::Target {
&self.read_only
}
}
};
}
};
Ok(quote! {
#[doc(hidden)]
#vis struct #readonly_ident #impl_generics #where_clause {
#(#readonly_fields),*
}
#outer_struct
#deref_impl
#(#attr_errors_tokens)*
})
}
fn find_and_strip_readonly_attrs(fields: &mut Punctuated, errors: &mut Vec<Error>) -> Vec<usize> {
let mut indices = Vec::new();
for (i, field) in fields.iter_mut().enumerate() {
let mut readonly_attr_index = None;
for (j, attr) in field.attrs.iter().enumerate() {
if attr.path().is_ident("readonly") {
if let Err(err) = attr.meta.require_path_only() {
errors.push(err);
}
readonly_attr_index = Some(j);
break;
}
}
if let Some(readonly_attr_index) = readonly_attr_index {
field.attrs.remove(readonly_attr_index);
indices.push(i);
}
}
indices
}
fn conflicting_read_only_span(fields: &Punctuated) -> Option<Span> {
fields.iter().find_map(|field| {
field
.ident
.as_ref()
.filter(|id| *id == "read_only")
.map(Ident::span)
})
}
struct ReplaceSelf<'a> {
with: &'a Path,
}
impl<'a> ReplaceSelf<'a> {
fn new(with: &'a Path) -> Self {
ReplaceSelf { with }
}
}
impl<'a> VisitMut for ReplaceSelf<'a> {
fn visit_path_mut(&mut self, path: &mut Path) {
if path.is_ident("Self") {
let span = path.segments[0].ident.span();
*path = self.with.clone();
path.segments[0].ident.set_span(span);
} else {
visit_mut::visit_path_mut(self, path);
}
}
}