relm_derive/
lib.rs

1/*
2 * Copyright (c) 2017-2019 Boucher, Antoni <bouanto@zoho.com>
3 *
4 * Permission is hereby granted, free of charge, to any person obtaining a copy of
5 * this software and associated documentation files (the "Software"), to deal in
6 * the Software without restriction, including without limitation the rights to
7 * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
8 * the Software, and to permit persons to whom the Software is furnished to do so,
9 * subject to the following conditions:
10 *
11 * The above copyright notice and this permission notice shall be included in all
12 * copies or substantial portions of the Software.
13 *
14 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
16 * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
17 * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
18 * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
19 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
20 */
21
22/*
23 * TODO: does an attribute #[msg] would simplify the implementation instead of #[derive(Msg)]?
24 */
25
26#![recursion_limit="256"]
27
28extern crate proc_macro;
29
30mod gen;
31
32use quote::{quote, quote_spanned};
33use proc_macro2::TokenStream;
34use syn::{
35    GenericParam,
36    Generics,
37    Ident,
38    Item,
39    LifetimeDef,
40    TypeParam,
41    parse,
42};
43use syn::spanned::Spanned;
44
45use gen::{gen_widget, gen_where_clause, parser::dummy_ident};
46
47#[proc_macro_derive(Msg)]
48pub fn msg(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
49    let ast: Item = parse(input).expect("msg > parse failed");
50    let gen = impl_msg(&ast, Ident::new("relm", ast.span()));
51    gen.into()
52}
53
54#[proc_macro_attribute]
55pub fn widget(_attributes: proc_macro::TokenStream, input: proc_macro::TokenStream) -> proc_macro::TokenStream {
56    let ast: Item = parse(input).expect("widget.parse failed");
57    let tokens = quote! {
58        #ast
59    };
60    let expanded = gen_widget(tokens);
61    expanded.into()
62}
63
64fn impl_msg(ast: &Item, krate: Ident) -> TokenStream {
65    let display = derive_display_variant(ast, &krate);
66    let into_option = derive_into_option(ast, &krate);
67
68    quote! {
69        #display
70        #into_option
71    }
72}
73
74fn derive_display_variant(ast: &Item, krate: &Ident) -> TokenStream {
75    if let Item::Enum(ref enum_item) = *ast {
76        let generics = &enum_item.generics;
77        let name = &enum_item.ident;
78        let generics_without_bound = remove_generic_bounds(generics);
79        let typ = quote! {
80            #name #generics_without_bound
81        };
82
83        let variant_patterns = enum_item.variants.iter().map(|variant| {
84            let doc_ident = dummy_ident("doc");
85            let attrs = variant.attrs.iter().filter(|attr| !attr.path.is_ident(&doc_ident));
86            let ident = &variant.ident;
87            quote! {
88                #(#attrs)* #name::#ident { .. }
89            }
90        });
91        let variant_names = enum_item.variants.iter().map(|variant| {
92            variant.ident.to_string()
93        });
94        let where_clause = gen_where_clause(generics);
95
96        quote_spanned! { krate.span() =>
97            impl #generics ::#krate::DisplayVariant for #typ #where_clause {
98                #[allow(unused_qualifications)]
99                fn display_variant(&self) -> &'static str {
100                    match *self {
101                        #(#variant_patterns => #variant_names,)*
102                    }
103                }
104            }
105        }
106    }
107    else {
108        panic!("Expected enum");
109    }
110}
111
112fn derive_into_option(ast: &Item, krate: &Ident) -> TokenStream {
113    if let Item::Enum(ref enum_item) = *ast {
114        let generics = &enum_item.generics;
115        let name = &enum_item.ident;
116        let generics_without_bound = remove_generic_bounds(generics);
117        let typ = quote! {
118            #name #generics_without_bound
119        };
120        let where_clause = gen_where_clause(generics);
121
122        quote_spanned! { krate.span() =>
123            impl #generics ::#krate::IntoOption<#typ> for #typ #where_clause {
124                fn into_option(self) -> Option<#typ> {
125                    Some(self)
126                }
127            }
128        }
129    }
130    else {
131        panic!("Expecting enum");
132    }
133}
134
135fn remove_generic_bounds(generics: &Generics) -> Generics {
136    let mut generics = generics.clone();
137    for param in generics.params.iter_mut() {
138        match *param {
139            GenericParam::Lifetime(LifetimeDef { ref mut bounds, .. }) =>
140                while bounds.pop().is_some() {
141                },
142            GenericParam::Type(TypeParam { ref mut bounds, .. }) =>
143                while bounds.pop().is_some() {
144                },
145            _ => (),
146        }
147    }
148    generics
149}