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
//
// Copyright (c) Dell Inc., or its subsidiaries. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//

use proc_macro2::TokenStream;
use quote::{quote, quote_spanned};
use syn::spanned::Spanned;
use syn::{parse_macro_input, parse_quote, Data, DeriveInput, Fields, GenericParam, Generics};

/// A derive marco that generates trait impls.
///
/// This derive macro implements Fields trait for struct. The Fields trait has a single
/// method that outputs a list of key value pairs. The key is the struct field name and the value
/// is the corresponding field value. The value has to be u64 type.
#[proc_macro_derive(Fields)]
pub fn derive_fields(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
    // Parse the input tokens into a syntax tree.
    let input = parse_macro_input!(input as DeriveInput);

    // Used in the quasi-quotation below as `#name`.
    let name = input.ident;

    // Add a bound `T: Fields` to every type parameter T.
    let generics = add_trait_bounds(input.generics);
    let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();

    // Generate an expression to get the key value pairs of the struct field.
    let expr = key_value_pairs(&input.data);
    let expanded = quote! {
        // The generated impl.
        impl #impl_generics pravega_client::index::Fields for #name #ty_generics #where_clause {
            fn get_field_values(&self) -> Vec<(&'static str, u64)> {
                vec!{#expr}
            }
        }
    };

    // Hand the output tokens back to the compiler.
    proc_macro::TokenStream::from(expanded)
}

// Add a bound `T: Fields` to every type parameter T.
fn add_trait_bounds(mut generics: Generics) -> Generics {
    for param in &mut generics.params {
        if let GenericParam::Type(ref mut type_param) = *param {
            type_param.bounds.push(parse_quote!(Fields));
        }
    }
    generics
}

fn key_value_pairs(data: &Data) -> TokenStream {
    match *data {
        Data::Struct(ref data) => match data.fields {
            Fields::Named(ref fields) => fields
                .named
                .iter()
                .map(|f| {
                    let name = f.ident.as_ref().unwrap();
                    let name_str = format!("{}", name);

                    quote_spanned! {f.span()=>
                        (#name_str, pravega_client::index::Value::value(&self.#name)),
                    }
                })
                .collect(),
            Fields::Unnamed(ref _fields) => {
                quote! {
                    compile_error!("expected named fields");
                }
            }
            Fields::Unit => {
                quote! {
                    compile_error!("expected named fields");
                }
            }
        },
        Data::Enum(_) | Data::Union(_) => unimplemented!(),
    }
}