Skip to main content

tetsy_scale_info_derive/
lib.rs

1// Copyright 2019-2021 Parity Technologies (UK) Ltd.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15#![cfg_attr(not(feature = "std"), no_std)]
16
17extern crate alloc;
18extern crate proc_macro;
19
20mod impl_wrapper;
21mod trait_bounds;
22
23use alloc::{
24    string::{
25        String,
26        ToString,
27    },
28    vec::Vec,
29};
30use proc_macro::TokenStream;
31use proc_macro2::TokenStream as TokenStream2;
32use quote::quote;
33use syn::{
34    parse::{
35        Error,
36        Result,
37    },
38    parse_quote,
39    punctuated::Punctuated,
40    token::Comma,
41    visit_mut::VisitMut,
42    Data,
43    DataEnum,
44    DataStruct,
45    DeriveInput,
46    Expr,
47    ExprLit,
48    Field,
49    Fields,
50    Lifetime,
51    Lit,
52    Variant,
53};
54
55#[proc_macro_derive(TypeInfo)]
56pub fn type_info(input: TokenStream) -> TokenStream {
57    match generate(input.into()) {
58        Ok(output) => output.into(),
59        Err(err) => err.to_compile_error().into(),
60    }
61}
62
63fn generate(input: TokenStream2) -> Result<TokenStream2> {
64    let mut tokens = quote! {};
65    tokens.extend(generate_type(input)?);
66    Ok(tokens)
67}
68
69fn generate_type(input: TokenStream2) -> Result<TokenStream2> {
70    let mut ast: DeriveInput = syn::parse2(input.clone())?;
71
72    let ident = &ast.ident;
73
74    ast.generics
75        .lifetimes_mut()
76        .for_each(|l| *l = parse_quote!('static));
77
78    let (_, ty_generics, _) = ast.generics.split_for_impl();
79    let where_clause = trait_bounds::make_where_clause(ident, &ast.generics, &ast.data)?;
80
81    let generic_type_ids = ast.generics.type_params().map(|ty| {
82        let ty_ident = &ty.ident;
83        quote! {
84            ::tetsy_scale_info::meta_type::<#ty_ident>()
85        }
86    });
87
88    let ast: DeriveInput = syn::parse2(input.clone())?;
89    let build_type = match &ast.data {
90        Data::Struct(ref s) => generate_composite_type(s),
91        Data::Enum(ref e) => generate_variant_type(e),
92        Data::Union(_) => return Err(Error::new_spanned(input, "Unions not supported")),
93    };
94    let generic_types = ast.generics.type_params();
95    let type_info_impl = quote! {
96        impl <#( #generic_types ),*> ::tetsy_scale_info::TypeInfo for #ident #ty_generics #where_clause {
97            type Identity = Self;
98            fn type_info() -> ::tetsy_scale_info::Type {
99                ::tetsy_scale_info::Type::builder()
100                    .path(::tetsy_scale_info::Path::new(stringify!(#ident), module_path!()))
101                    .type_params(::tetsy_scale_info::prelude::vec![ #( #generic_type_ids ),* ])
102                    .#build_type
103                    .into()
104            }
105        }
106    };
107
108    Ok(impl_wrapper::wrap(ident, "TYPE_INFO", type_info_impl))
109}
110
111type FieldsList = Punctuated<Field, Comma>;
112
113fn generate_fields(fields: &FieldsList) -> Vec<TokenStream2> {
114    fields
115        .iter()
116        .map(|f| {
117            let (ty, ident) = (&f.ty, &f.ident);
118            // Replace any field lifetime params with `static to prevent "unnecessary lifetime parameter"
119            // warning. Any lifetime parameters are specified as 'static in the type of the impl.
120            struct StaticLifetimesReplace;
121            impl VisitMut for StaticLifetimesReplace {
122                fn visit_lifetime_mut(&mut self, lifetime: &mut Lifetime) {
123                    *lifetime = parse_quote!('static)
124                }
125            }
126            let mut ty = ty.clone();
127            StaticLifetimesReplace.visit_type_mut(&mut ty);
128
129            let type_name = clean_type_string(&quote!(#ty).to_string());
130
131            if let Some(i) = ident {
132                quote! {
133                    .field_of::<#ty>(stringify!(#i), #type_name)
134                }
135            } else {
136                quote! {
137                    .field_of::<#ty>(#type_name)
138                }
139            }
140        })
141        .collect()
142}
143
144fn clean_type_string(input: &str) -> String {
145    input
146        .replace(" ::", "::")
147        .replace(":: ", "::")
148        .replace(" ,", ",")
149        .replace(" ;", ";")
150        .replace(" [", "[")
151        .replace("[ ", "[")
152        .replace(" ]", "]")
153        .replace(" (", "(")
154        // put back a space so that `a: (u8, (bool, u8))` isn't turned into `a: (u8,(bool, u8))`
155        .replace(",(", ", (")
156        .replace("( ", "(")
157        .replace(" )", ")")
158        .replace(" <", "<")
159        .replace("< ", "<")
160        .replace(" >", ">")
161        .replace("& \'", "&'")
162}
163
164fn generate_composite_type(data_struct: &DataStruct) -> TokenStream2 {
165    let fields = match data_struct.fields {
166        Fields::Named(ref fs) => {
167            let fields = generate_fields(&fs.named);
168            quote! { named()#( #fields )* }
169        }
170        Fields::Unnamed(ref fs) => {
171            let fields = generate_fields(&fs.unnamed);
172            quote! { unnamed()#( #fields )* }
173        }
174        Fields::Unit => {
175            quote! {
176                unit()
177            }
178        }
179    };
180    quote! {
181        composite(::tetsy_scale_info::build::Fields::#fields)
182    }
183}
184
185type VariantList = Punctuated<Variant, Comma>;
186
187fn generate_c_like_enum_def(variants: &VariantList) -> TokenStream2 {
188    let variants = variants.into_iter().enumerate().map(|(i, v)| {
189        let name = &v.ident;
190        let discriminant = if let Some((
191            _,
192            Expr::Lit(ExprLit {
193                lit: Lit::Int(lit_int),
194                ..
195            }),
196        )) = &v.discriminant
197        {
198            match lit_int.base10_parse::<u64>() {
199                Ok(i) => i,
200                Err(err) => return err.to_compile_error(),
201            }
202        } else {
203            i as u64
204        };
205        quote! {
206            .variant(stringify!(#name), #discriminant)
207        }
208    });
209    quote! {
210        variant(
211            ::tetsy_scale_info::build::Variants::fieldless()
212                #( #variants )*
213        )
214    }
215}
216
217fn is_c_like_enum(variants: &VariantList) -> bool {
218    // any variant has an explicit discriminant
219    variants.iter().any(|v| v.discriminant.is_some()) ||
220        // all variants are unit
221        variants.iter().all(|v| matches!(v.fields, Fields::Unit))
222}
223
224fn generate_variant_type(data_enum: &DataEnum) -> TokenStream2 {
225    let variants = &data_enum.variants;
226
227    if is_c_like_enum(&variants) {
228        return generate_c_like_enum_def(variants)
229    }
230
231    let variants = variants.into_iter().map(|v| {
232        let ident = &v.ident;
233        let v_name = quote! {stringify!(#ident) };
234        match v.fields {
235            Fields::Named(ref fs) => {
236                let fields = generate_fields(&fs.named);
237                quote! {
238                    .variant(
239                        #v_name,
240                        ::tetsy_scale_info::build::Fields::named()
241                            #( #fields)*
242                    )
243                }
244            }
245            Fields::Unnamed(ref fs) => {
246                let fields = generate_fields(&fs.unnamed);
247                quote! {
248                    .variant(
249                        #v_name,
250                        ::tetsy_scale_info::build::Fields::unnamed()
251                            #( #fields)*
252                    )
253                }
254            }
255            Fields::Unit => {
256                quote! {
257                    .variant_unit(#v_name)
258                }
259            }
260        }
261    });
262    quote! {
263        variant(
264            ::tetsy_scale_info::build::Variants::with_fields()
265                #( #variants)*
266        )
267    }
268}