summaryrefslogtreecommitdiffstats
path: root/vendor/derivative/src/ast.rs
blob: cf06995fa9ae2be486a107ec321347ddefafcbc5 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
use attr;
use proc_macro2;
use syn;
use syn::spanned::Spanned as SynSpanned;

#[derive(Debug)]
pub struct Input<'a> {
    pub attrs: attr::Input,
    pub body: Body<'a>,
    pub generics: &'a syn::Generics,
    pub ident: syn::Ident,
    pub span: proc_macro2::Span,
}

#[derive(Debug)]
pub enum Body<'a> {
    Enum(Vec<Variant<'a>>),
    Struct(Style, Vec<Field<'a>>),
}

#[derive(Debug)]
pub struct Variant<'a> {
    pub attrs: attr::Input,
    pub fields: Vec<Field<'a>>,
    pub ident: syn::Ident,
    pub style: Style,
}

#[derive(Debug)]
pub struct Field<'a> {
    pub attrs: attr::Field,
    pub ident: Option<syn::Ident>,
    pub ty: &'a syn::Type,
    pub span: proc_macro2::Span,
}

#[derive(Clone, Copy, Debug)]
pub enum Style {
    Struct,
    Tuple,
    Unit,
}

impl<'a> Input<'a> {
    pub fn from_ast(
        item: &'a syn::DeriveInput,
        errors: &mut proc_macro2::TokenStream,
    ) -> Result<Input<'a>, ()> {
        let attrs = attr::Input::from_ast(&item.attrs, errors)?;

        let body = match item.data {
            syn::Data::Enum(syn::DataEnum { ref variants, .. }) => {
                Body::Enum(enum_from_ast(variants, errors)?)
            }
            syn::Data::Struct(syn::DataStruct { ref fields, .. }) => {
                let (style, fields) = struct_from_ast(fields, errors)?;
                Body::Struct(style, fields)
            }
            syn::Data::Union(..) => {
                errors.extend(
                    syn::Error::new_spanned(item, "derivative does not support unions")
                        .to_compile_error(),
                );
                return Err(());
            }
        };

        Ok(Input {
            attrs,
            body,
            generics: &item.generics,
            ident: item.ident.clone(),
            span: item.span(),
        })
    }

    /// Checks whether this type is an enum with only unit variants.
    pub fn is_trivial_enum(&self) -> bool {
        match &self.body {
            Body::Enum(e) => e.iter().all(|v| v.is_unit()),
            Body::Struct(..) => false,
        }
    }
}

impl<'a> Body<'a> {
    pub fn all_fields(&self) -> Vec<&Field> {
        match *self {
            Body::Enum(ref variants) => variants
                .iter()
                .flat_map(|variant| variant.fields.iter())
                .collect(),
            Body::Struct(_, ref fields) => fields.iter().collect(),
        }
    }

    pub fn is_empty(&self) -> bool {
        match *self {
            Body::Enum(ref variants) => variants.is_empty(),
            Body::Struct(_, ref fields) => fields.is_empty(),
        }
    }
}

impl<'a> Variant<'a> {
    /// Checks whether this variant is a unit variant.
    pub fn is_unit(&self) -> bool {
        self.fields.is_empty()
    }
}

fn enum_from_ast<'a>(
    variants: &'a syn::punctuated::Punctuated<syn::Variant, syn::token::Comma>,
    errors: &mut proc_macro2::TokenStream,
) -> Result<Vec<Variant<'a>>, ()> {
    variants
        .iter()
        .map(|variant| {
            let (style, fields) = struct_from_ast(&variant.fields, errors)?;
            Ok(Variant {
                attrs: attr::Input::from_ast(&variant.attrs, errors)?,
                fields,
                ident: variant.ident.clone(),
                style,
            })
        })
        .collect()
}

fn struct_from_ast<'a>(
    fields: &'a syn::Fields,
    errors: &mut proc_macro2::TokenStream,
) -> Result<(Style, Vec<Field<'a>>), ()> {
    match *fields {
        syn::Fields::Named(ref fields) => {
            Ok((Style::Struct, fields_from_ast(&fields.named, errors)?))
        }
        syn::Fields::Unnamed(ref fields) => {
            Ok((Style::Tuple, fields_from_ast(&fields.unnamed, errors)?))
        }
        syn::Fields::Unit => Ok((Style::Unit, Vec::new())),
    }
}

fn fields_from_ast<'a>(
    fields: &'a syn::punctuated::Punctuated<syn::Field, syn::token::Comma>,
    errors: &mut proc_macro2::TokenStream,
) -> Result<Vec<Field<'a>>, ()> {
    fields
        .iter()
        .map(|field| {
            Ok(Field {
                attrs: attr::Field::from_ast(field, errors)?,
                ident: field.ident.clone(),
                ty: &field.ty,
                span: field.span(),
            })
        })
        .collect()
}