use proc_macro2::TokenStream as TokenStream2;
use quote::{ToTokens, quote};
use syn::{
Attribute, Data, DataEnum, DataStruct, DeriveInput, Fields, Generics, Ident, Index,
spanned::Spanned,
};
use crate::{
attributes::{self, ContainerAttributes, MemberAttributes, ParseAttrs},
case::Case,
names::NameResolver,
};
#[derive(Debug)]
pub struct IntoValue;
type DeriveError = super::error::DeriveError<IntoValue>;
type Result<T = TokenStream2> = std::result::Result<T, DeriveError>;
pub fn derive_into_value(input: TokenStream2) -> Result {
let input: DeriveInput = syn::parse2(input).map_err(DeriveError::Syn)?;
match input.data {
Data::Struct(data_struct) => Ok(struct_into_value(
input.ident,
data_struct,
input.generics,
input.attrs,
)?),
Data::Enum(data_enum) => Ok(enum_into_value(
input.ident,
data_enum,
input.generics,
input.attrs,
)?),
Data::Union(_) => Err(DeriveError::UnsupportedUnions),
}
}
fn struct_into_value(
ident: Ident,
data: DataStruct,
generics: Generics,
attrs: Vec<Attribute>,
) -> Result {
let container_attrs = ContainerAttributes::parse_attrs(attrs.iter())?;
let record = match &data.fields {
Fields::Named(fields) => {
let accessor = fields
.named
.iter()
.map(|field| field.ident.as_ref().expect("named has idents"))
.map(|ident| quote!(self.#ident));
fields_return_value(&data.fields, accessor, &container_attrs)?
}
Fields::Unnamed(fields) => {
let accessor = fields
.unnamed
.iter()
.enumerate()
.map(|(n, _)| Index::from(n))
.map(|index| quote!(self.#index));
fields_return_value(&data.fields, accessor, &container_attrs)?
}
Fields::Unit => quote!(nu_protocol::Value::nothing(span)),
};
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
Ok(quote! {
#[automatically_derived]
impl #impl_generics nu_protocol::IntoValue for #ident #ty_generics #where_clause {
fn into_value(self, span: nu_protocol::Span) -> nu_protocol::Value {
#record
}
}
})
}
fn enum_into_value(
ident: Ident,
data: DataEnum,
generics: Generics,
attrs: Vec<Attribute>,
) -> Result {
let container_attrs = ContainerAttributes::parse_attrs(attrs.iter())?;
let mut name_resolver = NameResolver::new();
let arms: Vec<TokenStream2> = data
.variants
.into_iter()
.map(|variant| {
let member_attrs = MemberAttributes::parse_attrs(variant.attrs.iter())?;
let ident = variant.ident;
let ident_s = name_resolver.resolve_ident(
&ident,
&container_attrs,
&member_attrs,
Case::Snake,
)?;
match &variant.fields {
Fields::Named(fields) => Err(DeriveError::UnsupportedEnums {
fields_span: fields.span(),
}),
Fields::Unnamed(fields) => Err(DeriveError::UnsupportedEnums {
fields_span: fields.span(),
}),
Fields::Unit => {
Ok(quote!(Self::#ident => nu_protocol::Value::string(#ident_s, span)))
}
}
})
.collect::<Result<_>>()?;
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
Ok(quote! {
impl #impl_generics nu_protocol::IntoValue for #ident #ty_generics #where_clause {
fn into_value(self, span: nu_protocol::Span) -> nu_protocol::Value {
match self {
#(#arms,)*
}
}
}
})
}
fn fields_return_value(
fields: &Fields,
accessor: impl Iterator<Item = impl ToTokens>,
container_attrs: &ContainerAttributes,
) -> Result {
match fields {
Fields::Named(fields) => {
let mut name_resolver = NameResolver::new();
let mut items: Vec<TokenStream2> = Vec::with_capacity(fields.named.len());
for (field, accessor) in fields.named.iter().zip(accessor) {
let member_attrs = MemberAttributes::parse_attrs(field.attrs.iter())?;
let ident = field.ident.as_ref().expect("named has idents");
let field =
name_resolver.resolve_ident(ident, container_attrs, &member_attrs, None)?;
items.push(quote!(#field => nu_protocol::IntoValue::into_value(#accessor, span)));
}
Ok(quote! {
nu_protocol::Value::record(nu_protocol::record! {
#(#items),*
}, span)
})
}
f @ Fields::Unnamed(fields) => {
attributes::deny_fields(f)?;
let items =
fields.unnamed.iter().zip(accessor).map(
|(_, accessor)| quote!(nu_protocol::IntoValue::into_value(#accessor, span)),
);
Ok(quote!(nu_protocol::Value::list(
std::vec![#(#items),*],
span
)))
}
Fields::Unit => Ok(quote!(nu_protocol::Value::nothing(span))),
}
}