pl_hlist_derive/
lib.rs

1//
2// Copyright (c) 2015-2019 Plausible Labs Cooperative, Inc.
3// All rights reserved.
4//
5
6extern crate proc_macro;
7
8use proc_macro::TokenStream;
9use quote::{quote, ToTokens};
10use syn::{parse_macro_input, Data, DataStruct, DeriveInput, Field, Fields};
11
12#[proc_macro_derive(HListSupport)]
13pub fn hlist_support_derive(input: TokenStream) -> TokenStream {
14    // Parse the input tokens into a syntax tree
15    let input = parse_macro_input!(input as DeriveInput);
16
17    // Check that the input type is a struct
18    let data_struct: DataStruct;
19    if let Data::Struct(s) = input.data {
20        data_struct = s
21    } else {
22        panic!("`HListSupport` may only be applied to structs")
23    }
24
25    // Check that the struct has named fields, since that's the only
26    // type we support at the moment
27    let fields: Fields;
28    if let Fields::Named(_) = data_struct.fields {
29        fields = data_struct.fields
30    } else {
31        panic!("`HListSupport` may only be applied to structs with named fields")
32    }
33
34    // Extract the struct name
35    let struct_name = &input.ident;
36
37    // Build the HList type
38    let hlist_type = hlist_type(fields.iter());
39
40    // Build the HList pattern
41    let hlist_pat = hlist_pattern(fields.iter());
42
43    // Build the struct initializer
44    let struct_field_init = struct_field_init(fields.iter());
45
46    // Build the HList initializer for ToHList
47    let hlist_cloned_init = hlist_cloned_init(fields.iter());
48
49    // Build the HList initializer for IntoHList
50    let hlist_init = hlist_init(fields.iter());
51
52    // Build the output
53    let expanded = quote! {
54        // Include the FromHList impl
55        #[allow(dead_code)]
56        impl FromHList<#hlist_type> for #struct_name {
57            fn from_hlist(hlist: #hlist_type) -> Self {
58                match hlist {
59                    #hlist_pat => #struct_name { #struct_field_init }
60                }
61            }
62        }
63
64        // Include the ToHList impl
65        #[allow(dead_code)]
66        impl ToHList<#hlist_type> for #struct_name {
67            fn to_hlist(&self) -> #hlist_type {
68                #hlist_cloned_init
69            }
70        }
71
72        // Include the IntoHList impl
73        #[allow(dead_code)]
74        impl IntoHList<#hlist_type> for #struct_name {
75            fn into_hlist(self) -> #hlist_type {
76                #hlist_init
77            }
78        }
79    };
80
81    // Hand the output tokens back to the compiler
82    TokenStream::from(expanded)
83}
84
85/// Recursive function that builds up an HList type using the types from a
86/// series of Fields.
87fn hlist_type(mut fields: syn::punctuated::Iter<Field>) -> proc_macro2::TokenStream {
88    match fields.next() {
89        Some(field) => {
90            let lhs = field.ty.to_token_stream();
91            let rhs = hlist_type(fields);
92            quote!(HCons<#lhs, #rhs>)
93        }
94        None => quote!(HNil),
95    }
96}
97
98/// Recursive function that builds up an HList pattern using the names from a
99/// series of Fields.
100fn hlist_pattern(mut fields: syn::punctuated::Iter<Field>) -> proc_macro2::TokenStream {
101    match fields.next() {
102        Some(field) => {
103            let lhs = field.ident.as_ref();
104            let rhs = hlist_pattern(fields);
105            quote!(HCons(#lhs, #rhs))
106        }
107        None => quote!(HNil),
108    }
109}
110
111/// Recursive function that builds up an HList initializer using the names from a
112/// series of Fields.
113fn hlist_init(mut fields: syn::punctuated::Iter<Field>) -> proc_macro2::TokenStream {
114    match fields.next() {
115        Some(field) => {
116            let lhs = field.ident.as_ref();
117            let rhs = hlist_init(fields);
118            quote!(HCons(self.#lhs, #rhs))
119        }
120        None => quote!(HNil),
121    }
122}
123
124/// Recursive function that builds up an HList initializer using the names from a
125/// series of Fields.
126fn hlist_cloned_init(mut fields: syn::punctuated::Iter<Field>) -> proc_macro2::TokenStream {
127    match fields.next() {
128        Some(field) => {
129            let lhs = field.ident.as_ref();
130            let rhs = hlist_cloned_init(fields);
131            quote!(HCons(self.#lhs.clone(), #rhs))
132        }
133        None => quote!(HNil),
134    }
135}
136
137/// Builds up a struct initializer list using the names from a series of Fields.
138fn struct_field_init(fields: syn::punctuated::Iter<Field>) -> proc_macro2::TokenStream {
139    let field_names = fields.map(|f| f.ident.as_ref());
140    quote!(#(#field_names),*)
141}