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
/*!
Helpers used both for enums and structs.
*/
use proc_macro2::TokenStream;

use quote::quote;
use syn::*;

use crate::error_message::ParentRef;

/// Extract the relevant parts from an [`Item`]'s [`Generics`] so
/// that they can be used inside [`quote::quote`] to form `impl` items.
///
/// The returned parts are:
/// - The list of parameters incl. bounds enclosed in `< .. >`, for use right
///   after the `impl` keyword. If there are no parameters, this part is
///   empty.
/// - The list of parameters without bounds enclosed in `< .. >`, for use when
///   referring to the Item's type. If there are no parameters, this part is
///   empty.
/// - The where clause, if any.
///
/// The results are formed so that they can be used unconditionally, i.e. the
/// parameter lists are completely empty token streams if and only if the
/// [`Generics`] do not contain any parameters.
pub(crate) fn bake_generics(generics: Generics) -> (TokenStream, TokenStream, Option<WhereClause>) {
    let params = generics.params;
    let where_clause = generics.where_clause;
    if params.len() > 0 {
        let mut params_ref = Vec::new();
        for param in params.iter() {
            params_ref.push(match param {
                GenericParam::Lifetime(lt) => GenericArgument::Lifetime(lt.lifetime.clone()),
                GenericParam::Type(ty) => GenericArgument::Type(Type::Path(TypePath {
                    qself: None,
                    path: ty.ident.clone().into(),
                })),
                GenericParam::Const(cst) => GenericArgument::Const(Expr::Path(ExprPath {
                    attrs: Vec::new(),
                    qself: None,
                    path: cst.ident.clone().into(),
                })),
            });
        }
        (
            quote! {
                < #params >
            },
            quote! {
                < #( #params_ref ),* >
            },
            where_clause,
        )
    } else {
        (quote! {}, quote! {}, where_clause)
    }
}

/// Build a statement calling the validator function at `validate`, if any.
///
/// This assumes that the argument for `validate` is called `result`.
pub(crate) fn build_validate(validate: Option<&Path>) -> Stmt {
    syn::parse2(if let Some(validate) = validate {
        quote! {
            #validate(&mut result)?;
        }
    } else {
        quote! {
            { let _ = &mut result; };
        }
    })
    .expect("failed to build validation code")
}

/// Build a statement calling the preparation function at `prepare`, if any.
///
/// The argument passed to `prepare` is `value_ident`.
pub(crate) fn build_prepare(prepare: Option<&Path>, value_ident: &Ident) -> TokenStream {
    if let Some(prepare) = prepare {
        quote! {
            #prepare(&mut #value_ident);
        }
    } else {
        quote! {
            { let _ = &mut #value_ident; };
        }
    }
}

pub trait ItemDef: std::fmt::Debug {
    /// Construct an expression which consumes `residual` and evaluates to
    /// `Result<T, Error>`.
    ///
    /// - `item_name` may contain either the path necessary to construct an
    ///   instance of the item or a nested parent ref. The latter case may not
    ///   be supported by all implementations of `ItemDef`.
    ///
    /// - `residual` must be the identifier of the `minidom::Element` to
    ///    process.
    fn build_try_from_element(
        &self,
        item_name: &ParentRef,
        residual: &Ident,
    ) -> Result<TokenStream>;

    /// Construct an expression which consumes the `T` value at `value_ident`
    /// and returns a `minidom::Element`.
    ///
    /// - `item_name` is used primarily for diagnostic messages.
    ///
    /// - `value_ident` must be the identifier at which the entire struct can
    ///   be reached. It is used during preparation.
    fn build_into_element(&self, item_name: &ParentRef, value_ident: &Ident)
        -> Result<TokenStream>;

    /// Construct a token stream containing the entire body of the
    /// `impl DynNamespace` block.
    ///
    /// Can only be used on `namespace = dyn` items; any other variants will
    /// cause an appropriate compile-time error.
    fn build_dyn_namespace(&self) -> Result<TokenStream>;
}

impl<T: ItemDef + ?Sized> ItemDef for Box<T> {
    fn build_try_from_element(
        &self,
        item_name: &ParentRef,
        residual: &Ident,
    ) -> Result<TokenStream> {
        (**self).build_try_from_element(item_name, residual)
    }

    fn build_into_element(
        &self,
        item_name: &ParentRef,
        value_ident: &Ident,
    ) -> Result<TokenStream> {
        (**self).build_into_element(item_name, value_ident)
    }

    fn build_dyn_namespace(&self) -> Result<TokenStream> {
        (**self).build_dyn_namespace()
    }
}