1
use proc_macro2::{Ident, Span};
2
use syn::spanned::Spanned;
3
use syn::{Expr, GenericArgument, PathArguments, PathSegment, Type};
4

            
5
/// Represents an error produced during parsing a type argument (e.g. `::<T>`)
6
pub enum ParseTypeArgumentError {
7
    NoTypeArguments,
8
    EmptyTypeArguments,
9
    MultipleTypeArguments,
10
    TypeArgumentNotAType,
11
    TypeArgumentValueNotPath,
12
    TypeArgumentEmptyPath,
13
}
14

            
15
/// Represents a field in a tree-like structure. Used for deriving the uniplate implementation.
16
#[derive(Debug)]
17
pub enum UniplateField {
18
    /// Any other valid identifier
19
    Identifier(Ident),
20
    /// A field consisting of a Box<T>
21
    Box(Span, Box<UniplateField>),
22
    /// A field consisting of a Vec<T>
23
    Vector(Span, Box<UniplateField>),
24
    /// A tuple of multiple fields (e.g. `(Box<T>, i32)`)
25
    Tuple(Span, Vec<UniplateField>),
26
    /// An array field. ToDo: currently not supported.
27
    Array(Span, Box<UniplateField>, Expr),
28
    /// A field that could not be parsed
29
    Unknown(Span),
30
}
31

            
32
impl UniplateField {
33
    /// Get the span corresponding to this field
34
5
    pub fn span(&self) -> Span {
35
5
        match self {
36
4
            UniplateField::Identifier(idnt) => idnt.span(),
37
            UniplateField::Box(spn, _) => *spn,
38
1
            UniplateField::Vector(spn, _) => *spn,
39
            UniplateField::Tuple(spn, _) => *spn,
40
            UniplateField::Array(spn, _, _) => *spn,
41
            UniplateField::Unknown(spn) => *spn,
42
        }
43
5
    }
44
}
45

            
46
/// Parse a type argument from a path segment (e.g. `T` from `Box<T>`)
47
26
fn parse_type_argument(seg_args: &PathArguments) -> Result<&PathSegment, ParseTypeArgumentError> {
48
26
    match seg_args {
49
26
        PathArguments::AngleBracketed(type_args) => {
50
26
            if type_args.args.len() > 1 {
51
                // ToDo: discuss - can and should we support multiple type arguments?
52
                return Err(ParseTypeArgumentError::MultipleTypeArguments);
53
26
            }
54
26

            
55
26
            match type_args.args.last() {
56
                None => Err(ParseTypeArgumentError::EmptyTypeArguments),
57
26
                Some(arg) => match arg {
58
26
                    GenericArgument::Type(tp) => match tp {
59
26
                        Type::Path(pth) => match pth.path.segments.last() {
60
26
                            Some(seg) => Ok(seg),
61
                            None => Err(ParseTypeArgumentError::TypeArgumentEmptyPath),
62
                        },
63
                        _ => Err(ParseTypeArgumentError::TypeArgumentValueNotPath),
64
                    },
65
                    _ => Err(ParseTypeArgumentError::TypeArgumentNotAType),
66
                },
67
            }
68
        }
69
        _ => Err(ParseTypeArgumentError::NoTypeArguments),
70
    }
71
26
}
72

            
73
/// Parse a field type into a `UniplateField`
74
38
pub fn parse_field_type(field_type: &Type) -> UniplateField {
75
    /// Helper function to parse a path segment into a `UniplateField`
76
58
    fn parse_type(seg: &PathSegment) -> UniplateField {
77
58
        let ident = &seg.ident;
78
58
        let span = ident.span();
79
58
        let args = &seg.arguments;
80
58

            
81
58
        let box_ident = &Ident::new("Box", span);
82
58
        let vec_ident = &Ident::new("Vec", span); // ToDo: support other collection types
83
58

            
84
58
        if ident.eq(box_ident) {
85
16
            match parse_type_argument(args) {
86
16
                Ok(inner_seg) => UniplateField::Box(seg.span(), Box::new(parse_type(inner_seg))),
87
                Err(_) => UniplateField::Unknown(ident.span()),
88
            }
89
42
        } else if ident.eq(vec_ident) {
90
10
            match parse_type_argument(args) {
91
10
                Ok(inner_seg) => UniplateField::Vector(seg.span(), Box::new(parse_type(inner_seg))),
92
                Err(_) => UniplateField::Unknown(ident.span()),
93
            }
94
        } else {
95
32
            UniplateField::Identifier(ident.clone())
96
        }
97
58
    }
98

            
99
38
    match field_type {
100
32
        Type::Path(path) => match path.path.segments.last() {
101
            None => UniplateField::Unknown(path.span()),
102
32
            Some(seg) => parse_type(seg),
103
        },
104
6
        Type::Tuple(tpl) => {
105
6
            UniplateField::Tuple(tpl.span(), tpl.elems.iter().map(parse_field_type).collect())
106
        }
107
        Type::Array(arr) => UniplateField::Array(
108
            arr.span(),
109
            Box::new(parse_field_type(arr.elem.as_ref())),
110
            arr.len.clone(),
111
        ),
112
        _ => UniplateField::Unknown(field_type.span()), // ToDo discuss - Can we support any of: BareFn, Group, ImplTrait, Infer, Macro, Never, Paren, Ptr, Reference, TraitObject, Verbatim
113
    }
114
38
}
115

            
116
/// Check if a field type is equal to a given identifier. Used to check if a field is an instance of the root type.
117
104
pub fn check_field_type(ft: &UniplateField, root_ident: &Ident) -> bool {
118
104
    match ft {
119
64
        UniplateField::Identifier(ident) => ident.eq(root_ident),
120
22
        UniplateField::Box(_, subfield) => check_field_type(subfield.as_ref(), root_ident),
121
12
        UniplateField::Vector(_, subfield) => check_field_type(subfield.as_ref(), root_ident),
122
6
        UniplateField::Tuple(_, subfields) => {
123
6
            for sft in subfields {
124
6
                if check_field_type(sft, root_ident) {
125
6
                    return true;
126
                }
127
            }
128
            false
129
        }
130
        UniplateField::Array(_, arr_type, _) => check_field_type(arr_type.as_ref(), root_ident),
131
        UniplateField::Unknown(_) => false,
132
    }
133
104
}