use std::{slice, vec};
use proc_macro2::{Span, TokenStream};
use quote::{quote, quote_spanned, ToTokens};
use syn::ext::IdentExt;
use syn::parse::Parser;
use syn::spanned::Spanned;
use syn::Token;
use crate::usage::{
self, IdentRefSet, IdentSet, LifetimeRefSet, LifetimeSet, UsesLifetimes, UsesTypeParams,
};
use crate::{Error, FromField, FromVariant, Result};
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Data<V, F> {
Enum(Vec<V>),
Struct(Fields<F>),
}
impl<V, F> Data<V, F> {
pub fn empty_from(src: &syn::Data) -> Self {
match *src {
syn::Data::Enum(_) => Data::Enum(vec![]),
syn::Data::Struct(ref vd) => Data::Struct(Fields::empty_from(&vd.fields)),
syn::Data::Union(_) => panic!("Unions are not supported"),
}
}
pub fn try_empty_from(src: &syn::Data) -> Result<Self> {
match *src {
syn::Data::Enum(_) => Ok(Data::Enum(vec![])),
syn::Data::Struct(ref vd) => Ok(Data::Struct(Fields::empty_from(&vd.fields))),
syn::Data::Union(_) => Err(Error::custom("Unions are not supported")),
}
}
pub fn as_ref(&self) -> Data<&V, &F> {
match *self {
Data::Enum(ref variants) => Data::Enum(variants.iter().collect()),
Data::Struct(ref data) => Data::Struct(data.as_ref()),
}
}
pub fn map_enum_variants<T, U>(self, map: T) -> Data<U, F>
where
T: FnMut(V) -> U,
{
match self {
Data::Enum(v) => Data::Enum(v.into_iter().map(map).collect()),
Data::Struct(f) => Data::Struct(f),
}
}
pub fn map_struct_fields<T, U>(self, map: T) -> Data<V, U>
where
T: FnMut(F) -> U,
{
match self {
Data::Enum(v) => Data::Enum(v),
Data::Struct(f) => Data::Struct(f.map(map)),
}
}
pub fn map_struct<T, U>(self, mut map: T) -> Data<V, U>
where
T: FnMut(Fields<F>) -> Fields<U>,
{
match self {
Data::Enum(v) => Data::Enum(v),
Data::Struct(f) => Data::Struct(map(f)),
}
}
pub fn take_struct(self) -> Option<Fields<F>> {
match self {
Data::Enum(_) => None,
Data::Struct(f) => Some(f),
}
}
pub fn take_enum(self) -> Option<Vec<V>> {
match self {
Data::Enum(v) => Some(v),
Data::Struct(_) => None,
}
}
pub fn is_enum(&self) -> bool {
match *self {
Data::Enum(_) => true,
Data::Struct(_) => false,
}
}
pub fn is_struct(&self) -> bool {
!self.is_enum()
}
}
impl<V: FromVariant, F: FromField> Data<V, F> {
pub fn try_from(body: &syn::Data) -> Result<Self> {
match *body {
syn::Data::Enum(ref data) => {
let mut errors = Error::accumulator();
let items = data
.variants
.iter()
.filter_map(|v| errors.handle(FromVariant::from_variant(v)))
.collect();
errors.finish_with(Data::Enum(items))
}
syn::Data::Struct(ref data) => Ok(Data::Struct(Fields::try_from(&data.fields)?)),
syn::Data::Union(_) => Err(Error::custom("Unions are not supported")),
}
}
}
impl<V: UsesTypeParams, F: UsesTypeParams> UsesTypeParams for Data<V, F> {
fn uses_type_params<'a>(
&self,
options: &usage::Options,
type_set: &'a IdentSet,
) -> IdentRefSet<'a> {
match *self {
Data::Struct(ref v) => v.uses_type_params(options, type_set),
Data::Enum(ref v) => v.uses_type_params(options, type_set),
}
}
}
impl<V: UsesLifetimes, F: UsesLifetimes> UsesLifetimes for Data<V, F> {
fn uses_lifetimes<'a>(
&self,
options: &usage::Options,
lifetimes: &'a LifetimeSet,
) -> LifetimeRefSet<'a> {
match *self {
Data::Struct(ref v) => v.uses_lifetimes(options, lifetimes),
Data::Enum(ref v) => v.uses_lifetimes(options, lifetimes),
}
}
}
#[derive(Debug, Clone)]
pub struct Fields<T> {
pub style: Style,
pub fields: Vec<T>,
span: Option<Span>,
__nonexhaustive: (),
}
impl<T> Fields<T> {
pub fn new(style: Style, fields: Vec<T>) -> Self {
Self {
style,
fields,
span: None,
__nonexhaustive: (),
}
}
pub fn with_span(mut self, span: Span) -> Self {
if self.span.is_none() {
self.span = Some(span);
}
self
}
pub fn empty_from(vd: &syn::Fields) -> Self {
Self::new(vd.into(), Vec::new())
}
pub fn split(self) -> (Style, Vec<T>) {
(self.style, self.fields)
}
pub fn is_newtype(&self) -> bool {
self.style == Style::Tuple && self.len() == 1
}
pub fn is_unit(&self) -> bool {
self.style.is_unit()
}
pub fn is_tuple(&self) -> bool {
self.style.is_tuple()
}
pub fn is_struct(&self) -> bool {
self.style.is_struct()
}
pub fn as_ref(&self) -> Fields<&T> {
Fields {
style: self.style,
fields: self.fields.iter().collect(),
span: self.span,
__nonexhaustive: (),
}
}
pub fn map<F, U>(self, map: F) -> Fields<U>
where
F: FnMut(T) -> U,
{
Fields {
style: self.style,
fields: self.fields.into_iter().map(map).collect(),
span: self.span,
__nonexhaustive: (),
}
}
pub fn iter(&self) -> slice::Iter<'_, T> {
self.fields.iter()
}
pub fn len(&self) -> usize {
self.fields.len()
}
pub fn is_empty(&self) -> bool {
self.fields.is_empty()
}
}
impl<F: FromField> Fields<F> {
pub fn try_from(fields: &syn::Fields) -> Result<Self> {
let mut errors = Error::accumulator();
let items = {
match &fields {
syn::Fields::Named(fields) => fields
.named
.iter()
.filter_map(|field| {
errors.handle(FromField::from_field(field).map_err(|err| {
if let Some(ident) = &field.ident {
err.at(ident)
} else {
err
}
}))
})
.collect(),
syn::Fields::Unnamed(fields) => fields
.unnamed
.iter()
.filter_map(|field| errors.handle(FromField::from_field(field)))
.collect(),
syn::Fields::Unit => vec![],
}
};
errors.finish()?;
Ok(Self::new(fields.into(), items).with_span(fields.span()))
}
}
impl<T: ToTokens> ToTokens for Fields<T> {
fn to_tokens(&self, tokens: &mut TokenStream) {
let fields = &self.fields;
let span = self.span.unwrap_or_else(Span::call_site);
match self.style {
Style::Struct => {
let trailing_comma = {
if fields.is_empty() {
quote!()
} else {
quote!(,)
}
};
tokens.extend(quote_spanned![span => { #(#fields),* #trailing_comma }]);
}
Style::Tuple => {
tokens.extend(quote_spanned![span => ( #(#fields),* )]);
}
Style::Unit => {}
}
}
}
impl<T: PartialEq> PartialEq for Fields<T> {
fn eq(&self, other: &Self) -> bool {
self.style == other.style && self.fields == other.fields
}
}
impl<T: Eq> Eq for Fields<T> {}
impl<T> IntoIterator for Fields<T> {
type Item = T;
type IntoIter = vec::IntoIter<T>;
fn into_iter(self) -> Self::IntoIter {
self.fields.into_iter()
}
}
impl<T> From<Style> for Fields<T> {
fn from(style: Style) -> Self {
Self::new(style, Vec::new())
}
}
impl<T, U: Into<Vec<T>>> From<(Style, U)> for Fields<T> {
fn from((style, fields): (Style, U)) -> Self {
style.with_fields(fields)
}
}
impl<T: UsesTypeParams> UsesTypeParams for Fields<T> {
fn uses_type_params<'a>(
&self,
options: &usage::Options,
type_set: &'a IdentSet,
) -> IdentRefSet<'a> {
self.fields.uses_type_params(options, type_set)
}
}
impl<T: UsesLifetimes> UsesLifetimes for Fields<T> {
fn uses_lifetimes<'a>(
&self,
options: &usage::Options,
lifetimes: &'a LifetimeSet,
) -> LifetimeRefSet<'a> {
self.fields.uses_lifetimes(options, lifetimes)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Style {
Tuple,
Struct,
Unit,
}
impl Style {
pub fn is_unit(self) -> bool {
self == Style::Unit
}
pub fn is_tuple(self) -> bool {
self == Style::Tuple
}
pub fn is_struct(self) -> bool {
self == Style::Struct
}
fn with_fields<T, U: Into<Vec<T>>>(self, fields: U) -> Fields<T> {
Fields::new(self, fields.into())
}
}
impl From<syn::Fields> for Style {
fn from(vd: syn::Fields) -> Self {
(&vd).into()
}
}
impl<'a> From<&'a syn::Fields> for Style {
fn from(vd: &syn::Fields) -> Self {
match *vd {
syn::Fields::Named(_) => Style::Struct,
syn::Fields::Unnamed(_) => Style::Tuple,
syn::Fields::Unit => Style::Unit,
}
}
}
#[derive(Debug, Clone)]
pub enum NestedMeta {
Meta(syn::Meta),
Lit(syn::Lit),
}
impl NestedMeta {
pub fn parse_meta_list(tokens: TokenStream) -> syn::Result<Vec<Self>> {
syn::punctuated::Punctuated::<NestedMeta, Token![,]>::parse_terminated
.parse2(tokens)
.map(|punctuated| punctuated.into_iter().collect())
}
}
impl syn::parse::Parse for NestedMeta {
fn parse(input: syn::parse::ParseStream<'_>) -> syn::Result<Self> {
if input.peek(syn::Lit) && !(input.peek(syn::LitBool) && input.peek2(Token![=])) {
input.parse().map(NestedMeta::Lit)
} else if input.peek(syn::Ident::peek_any)
|| input.peek(Token![::]) && input.peek3(syn::Ident::peek_any)
{
input.parse().map(NestedMeta::Meta)
} else {
Err(input.error("expected identifier or literal"))
}
}
}
impl ToTokens for NestedMeta {
fn to_tokens(&self, tokens: &mut TokenStream) {
match self {
NestedMeta::Meta(meta) => meta.to_tokens(tokens),
NestedMeta::Lit(lit) => lit.to_tokens(tokens),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn token_stream_to_fields(input: TokenStream) -> Fields<syn::Field> {
Fields::try_from(&{
if let syn::Data::Struct(s) = syn::parse2::<syn::DeriveInput>(input).unwrap().data {
s.fields
} else {
panic!();
}
})
.unwrap()
}
#[test]
fn test_style_eq() {
struct _AssertEq
where
Style: Eq;
}
#[test]
fn test_fields_to_tokens_struct() {
let reference = quote!(
{
executable: String,
args: Vec<String>,
env: Vec<String>,
index: usize,
optional: Option<String>,
current_dir: String,
}
);
let input = quote!(
struct ExampleTest #reference
);
let fields = token_stream_to_fields(input);
let mut result = quote!();
fields.to_tokens(&mut result);
assert_eq!(result.to_string(), reference.to_string());
}
#[test]
fn test_fields_to_tokens_tuple() {
let reference = quote!((u64, usize, &'a T));
let input = quote!(
struct ExampleTest #reference;
);
let fields = token_stream_to_fields(input);
let mut result = quote!();
fields.to_tokens(&mut result);
assert_eq!(result.to_string(), reference.to_string());
}
}