use crate::{
add_trait_bounds,
context::{Context, Tokens},
};
use proc_macro2::{Span, TokenStream};
use quote::{format_ident, quote, ToTokens};
use syn::Token;
pub struct Derive {
input: syn::DeriveInput,
}
impl syn::parse::Parse for Derive {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
Ok(Self {
input: input.parse()?,
})
}
}
impl Derive {
pub(super) fn expand(self, cx: &Context, is_option_spanned: bool) -> Result<TokenStream, ()> {
let attr = cx.type_attrs(&self.input.attrs);
let tokens = cx.tokens_with_module(attr.module.as_ref());
let mut expander = Expander { cx, tokens };
let inner = match &self.input.data {
syn::Data::Struct(st) => expander.expand_struct_fields(
&st.fields,
|member| quote!(&self.#member),
is_option_spanned,
)?,
syn::Data::Enum(enum_) => expander.expand_enum(enum_, is_option_spanned)?,
syn::Data::Union(un) => {
expander.cx.error(syn::Error::new_spanned(
un.union_token,
"not supported on unions",
));
return Err(());
}
};
let ident = &self.input.ident;
let Tokens {
spanned,
option_spanned,
span,
option,
..
} = &expander.tokens;
let mut generics = self.input.generics.clone();
let (trait_t, ret) = if is_option_spanned {
add_trait_bounds(&mut generics, option_spanned);
(option_spanned, quote!(#option<#span>))
} else {
add_trait_bounds(&mut generics, spanned);
(spanned, quote!(#span))
};
let (impl_gen, type_gen, where_gen) = generics.split_for_impl();
let name = if is_option_spanned {
syn::Ident::new("option_span", Span::call_site())
} else {
syn::Ident::new("span", Span::call_site())
};
let implementation = quote! {
#[automatically_derived]
impl #impl_gen #trait_t for #ident #type_gen #where_gen {
fn #name(&self) -> #ret {
#inner
}
}
};
let option_spanned = (!is_option_spanned).then(|| {
quote! {
#[automatically_derived]
impl #impl_gen #option_spanned for #ident #type_gen #where_gen {
fn option_span(&self) -> #option<#span> {
#option::Some(#spanned::span(self))
}
}
}
});
Ok(quote! {
#implementation
#option_spanned
})
}
}
struct Expander<'cx> {
cx: &'cx Context,
tokens: Tokens,
}
impl Expander<'_> {
fn expand_enum(
&mut self,
enum_: &syn::DataEnum,
is_option_spanned: bool,
) -> Result<TokenStream, ()> {
let mut variants = Vec::new();
for variant in &enum_.variants {
let ident = &variant.ident;
if matches!(&variant.fields, syn::Fields::Unit if !is_option_spanned) {
self.cx.error(syn::Error::new_spanned(
variant,
"Spanned cannot be implemented for unit variants",
));
continue;
}
let mut assign = Vec::new();
for (index, field) in variant.fields.iter().enumerate() {
let member = match &field.ident {
Some(ident) => syn::Member::Named(ident.clone()),
None => syn::Member::Unnamed(syn::Index::from(index)),
};
let to = match &field.ident {
Some(ident) => ident.clone(),
None => format_ident!("_{}", index),
};
assign.push(syn::FieldValue {
attrs: Vec::new(),
member,
colon_token: Some(<Token![:]>::default()),
expr: syn::Expr::Path(syn::ExprPath {
attrs: Vec::new(),
qself: None,
path: syn::Path::from(to),
}),
});
}
if let Ok(body) = self.expand_struct_fields(
&variant.fields,
|member| match member {
syn::Member::Named(field) => quote!(#field),
syn::Member::Unnamed(index) => format_ident!("_{}", index).into_token_stream(),
},
is_option_spanned,
) {
variants.push(quote! {
Self::#ident { #(#assign),* } => { #body }
});
}
}
if self.cx.has_errors() {
return Err(());
}
Ok(quote! {
match self {
#(#variants,)*
}
})
}
fn expand_struct_fields(
&mut self,
fields: &syn::Fields,
access_member: fn(&syn::Member) -> TokenStream,
is_option_spanned: bool,
) -> Result<TokenStream, ()> {
let mut explicit_span = None;
let Tokens {
spanned,
into_iterator,
span,
option,
option_spanned,
iterator,
double_ended_iterator,
..
} = &self.tokens;
let mut out = None;
let mut definite_span = false;
for (index, field) in fields.iter().enumerate() {
let attr = self.cx.field_attrs(&field.attrs);
if attr.id.is_some() || attr.skip.is_some() {
continue;
}
let member = match &field.ident {
Some(ident) => syn::Member::Named(ident.clone()),
None => syn::Member::Unnamed(syn::Index::from(index)),
};
if let Some(span) = attr.span {
if explicit_span.is_some() {
self.cx.error(syn::Error::new(
span,
"Only one field can be marked `#[rune(span)]`",
));
return Err(());
}
explicit_span = Some(member.clone());
}
let access = access_member(&member);
let next = if attr.iter.is_some() {
quote! {
#iterator::map(#into_iterator::into_iter(#access), #spanned::span)
}
} else if attr.option.is_some() {
quote! {
#iterator::flat_map(#into_iterator::into_iter([#access]), #option_spanned::option_span)
}
} else {
definite_span = true;
quote! {
#iterator::map(#into_iterator::into_iter([#access]), #spanned::span)
}
};
out = Some(match out.take() {
Some(out) => quote!(#iterator::chain(#out, #next)),
None => next,
});
}
if let Some(explicit_span) = explicit_span {
let access = access_member(&explicit_span);
if is_option_spanned {
return Ok(quote!(#option::Some(#spanned::span(#access))));
} else {
return Ok(quote!(#spanned::span(#access)));
}
}
let match_head_back = if is_option_spanned {
quote! {
match (head, back) {
(#option::Some(head), #option::Some(back)) => #option::Some(#span::join(head, back)),
(#option::Some(head), #option::None) => #option::Some(head),
(#option::None, #option::Some(back)) => #option::Some(back),
_ => None,
}
}
} else {
if !definite_span {
self.cx.error(syn::Error::new_spanned(
fields,
"No field available that can definitely produce a `Span` from",
));
return Err(());
}
quote! {
match (head, back) {
(#option::Some(head), #option::Some(back)) => #span::join(head, back),
(#option::Some(head), #option::None) => head,
(#option::None, #option::Some(back)) => back,
_ => unreachable!(),
}
}
};
let Some(out) = out else {
return Ok(quote!(#option::None));
};
Ok(quote! {
let mut iter = #out;
let head: #option<#span> = #iterator::next(&mut iter);
let back: #option<#span> = #double_ended_iterator::next_back(&mut iter);
#match_head_back
})
}
}