scale_info_derive/
lib.rs

1// Copyright 2019-2022 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
15extern crate alloc;
16extern crate proc_macro;
17
18mod attr;
19mod trait_bounds;
20mod utils;
21
22use self::attr::{Attributes, CaptureDocsAttr, CratePathAttr};
23use proc_macro::TokenStream;
24use proc_macro2::{Span, TokenStream as TokenStream2};
25use quote::quote;
26use syn::{
27    parse::{Error, Result},
28    parse_quote,
29    punctuated::Punctuated,
30    token::Comma,
31    visit_mut::VisitMut,
32    Data, DataEnum, DataStruct, DeriveInput, Field, Fields, Ident, Lifetime,
33};
34
35#[proc_macro_derive(TypeInfo, attributes(scale_info, codec))]
36pub fn type_info(input: TokenStream) -> TokenStream {
37    match generate(input.into()) {
38        Ok(output) => output.into(),
39        Err(err) => err.to_compile_error().into(),
40    }
41}
42
43fn generate(input: TokenStream2) -> Result<TokenStream2> {
44    let type_info_impl = TypeInfoImpl::parse(input)?;
45    let type_info_impl_toks = type_info_impl.expand()?;
46    Ok(quote! {
47        #[allow(non_upper_case_globals, deprecated, unused_attributes, unused_qualifications)]
48        const _: () = {
49            #type_info_impl_toks;
50        };
51    })
52}
53
54struct TypeInfoImpl {
55    ast: DeriveInput,
56    attrs: Attributes,
57}
58
59impl TypeInfoImpl {
60    fn parse(input: TokenStream2) -> Result<Self> {
61        let ast: DeriveInput = syn::parse2(input)?;
62        let attrs = attr::Attributes::from_ast(&ast)?;
63
64        Ok(Self { ast, attrs })
65    }
66
67    fn expand(&self) -> Result<TokenStream2> {
68        let ident = &self.ast.ident;
69        let ident_str = ident.to_string();
70        let scale_info = crate_path(self.attrs.crate_path())?;
71
72        let where_clause = trait_bounds::make_where_clause(
73            &self.attrs,
74            ident,
75            &self.ast.generics,
76            &self.ast.data,
77            &scale_info,
78        )?;
79
80        let (impl_generics, ty_generics, _) = self.ast.generics.split_for_impl();
81
82        let type_params = self.ast.generics.type_params().map(|tp| {
83            let ty_ident = &tp.ident;
84            let ty = if self
85                .attrs
86                .skip_type_params()
87                .map_or(true, |skip| !skip.skip(tp))
88            {
89                quote! { ::core::option::Option::Some(#scale_info::meta_type::<#ty_ident>()) }
90            } else {
91                quote! { ::core::option::Option::None }
92            };
93            quote! {
94                #scale_info::TypeParameter::new(::core::stringify!(#ty_ident), #ty)
95            }
96        });
97
98        let build_type = match &self.ast.data {
99            Data::Struct(ref s) => self.generate_composite_type(s, &scale_info),
100            Data::Enum(ref e) => self.generate_variant_type(e, &scale_info),
101            Data::Union(_) => return Err(Error::new_spanned(&self.ast, "Unions not supported")),
102        };
103        let docs = self.generate_docs(&self.ast.attrs);
104
105        let replaces = self.attrs.replace_segments().map(|r| {
106            let search = r.search();
107            let replace = r.replace();
108
109            quote!(( #search, #replace ))
110        });
111
112        Ok(quote! {
113            impl #impl_generics #scale_info::TypeInfo for #ident #ty_generics #where_clause {
114                type Identity = Self;
115                fn type_info() -> #scale_info::Type {
116                    #scale_info::Type::builder()
117                        .path(#scale_info::Path::new_with_replace(
118                            #ident_str,
119                            ::core::module_path!(),
120                            &[ #( #replaces ),* ]
121                        ))
122                        .type_params(#scale_info::prelude::vec![ #( #type_params ),* ])
123                        #docs
124                        .#build_type
125                }
126            }
127        })
128    }
129
130    fn generate_composite_type(
131        &self,
132        data_struct: &DataStruct,
133        scale_info: &syn::Path,
134    ) -> TokenStream2 {
135        let fields = match data_struct.fields {
136            Fields::Named(ref fs) => {
137                let fields = self.generate_fields(&fs.named);
138                quote! { named()#( #fields )* }
139            }
140            Fields::Unnamed(ref fs) => {
141                let fields = self.generate_fields(&fs.unnamed);
142                quote! { unnamed()#( #fields )* }
143            }
144            Fields::Unit => {
145                quote! {
146                    unit()
147                }
148            }
149        };
150
151        quote! {
152            composite(#scale_info::build::Fields::#fields)
153        }
154    }
155
156    fn generate_fields(&self, fields: &Punctuated<Field, Comma>) -> Vec<TokenStream2> {
157        fields
158            .iter()
159            .filter(|f| !utils::should_skip(&f.attrs))
160            .map(|f| {
161                let (ty, ident) = (&f.ty, &f.ident);
162                // Replace any field lifetime params with `static to prevent "unnecessary lifetime parameter"
163                // warning. Any lifetime parameters are specified as 'static in the type of the impl.
164                struct StaticLifetimesReplace;
165                impl VisitMut for StaticLifetimesReplace {
166                    fn visit_lifetime_mut(&mut self, lifetime: &mut Lifetime) {
167                        *lifetime = parse_quote!('static)
168                    }
169                }
170                let mut ty = match ty {
171                    // When a type is specified as part of a `macro_rules!`, the tokens passed to
172                    // the `TypeInfo` derive macro are a type `Group`, which is pretty printed with
173                    // invisible delimiters e.g. /*«*/ bool /*»*/. To avoid printing the delimiters
174                    // the inner type element is extracted.
175                    syn::Type::Group(group) => (*group.elem).clone(),
176                    _ => ty.clone(),
177                };
178                StaticLifetimesReplace.visit_type_mut(&mut ty);
179
180                let type_name = clean_type_string(&quote!(#ty).to_string());
181                let docs = self.generate_docs(&f.attrs);
182                let type_of_method = if utils::is_compact(f) {
183                    quote!(compact)
184                } else {
185                    quote!(ty)
186                };
187                let name = if let Some(ident) = ident {
188                    quote!(.name(::core::stringify!(#ident)))
189                } else {
190                    quote!()
191                };
192                quote!(
193                    .field(|f| f
194                        .#type_of_method::<#ty>()
195                        #name
196                        .type_name(#type_name)
197                        #docs
198                    )
199                )
200            })
201            .collect()
202    }
203
204    fn generate_variant_type(&self, data_enum: &DataEnum, scale_info: &syn::Path) -> TokenStream2 {
205        let variants = &data_enum.variants;
206
207        let variants = variants
208            .into_iter()
209            .filter(|v| !utils::should_skip(&v.attrs))
210            .enumerate()
211            .map(|(i, v)| {
212                let ident = &v.ident;
213                let v_name = quote! {::core::stringify!(#ident) };
214                let docs = self.generate_docs(&v.attrs);
215                let index = utils::variant_index(v, i);
216
217                let fields = match v.fields {
218                    Fields::Named(ref fs) => {
219                        let fields = self.generate_fields(&fs.named);
220                        Some(quote! {
221                            .fields(#scale_info::build::Fields::named()
222                                #( #fields )*
223                            )
224                        })
225                    }
226                    Fields::Unnamed(ref fs) => {
227                        let fields = self.generate_fields(&fs.unnamed);
228                        Some(quote! {
229                            .fields(#scale_info::build::Fields::unnamed()
230                                #( #fields )*
231                            )
232                        })
233                    }
234                    Fields::Unit => None,
235                };
236
237                quote! {
238                    .variant(#v_name, |v|
239                        v
240                            .index(#index as ::core::primitive::u8)
241                            #fields
242                            #docs
243                    )
244                }
245            });
246        quote! {
247            variant(
248                #scale_info::build::Variants::new()
249                    #( #variants )*
250            )
251        }
252    }
253
254    fn generate_docs(&self, attrs: &[syn::Attribute]) -> Option<TokenStream2> {
255        let docs_builder_fn = match self.attrs.capture_docs() {
256            CaptureDocsAttr::Never => None, // early return if we never capture docs.
257            CaptureDocsAttr::Default => Some(quote!(docs)),
258            CaptureDocsAttr::Always => Some(quote!(docs_always)),
259        }?;
260
261        let docs = attrs
262            .iter()
263            .filter_map(|attr| {
264                if !attr.path().is_ident("doc") {
265                    return None;
266                }
267                let syn::Meta::NameValue(nv) = &attr.meta else {
268                    return None;
269                };
270                let syn::Expr::Lit(syn::ExprLit {
271                    lit: syn::Lit::Str(s),
272                    ..
273                }) = &nv.value
274                else {
275                    return None;
276                };
277
278                let lit_value = s.value();
279                let stripped = lit_value.strip_prefix(' ').unwrap_or(&lit_value);
280                let lit: syn::Lit = parse_quote!(#stripped);
281                Some(lit)
282            })
283            .collect::<Vec<_>>();
284
285        if !docs.is_empty() {
286            Some(quote! {
287                .#docs_builder_fn(&[ #( #docs ),* ])
288            })
289        } else {
290            None
291        }
292    }
293}
294
295/// Get the name of a crate, to be robust against renamed dependencies.
296fn crate_name_path(name: &str) -> Result<syn::Path> {
297    proc_macro_crate::crate_name(name)
298        .map(|crate_name| {
299            use proc_macro_crate::FoundCrate::*;
300            match crate_name {
301                Itself => Ident::new("self", Span::call_site()).into(),
302                Name(name) => {
303                    let crate_ident = Ident::new(&name, Span::call_site());
304                    parse_quote!( ::#crate_ident )
305                }
306            }
307        })
308        .map_err(|e| syn::Error::new(Span::call_site(), &e))
309}
310
311fn crate_path(crate_path_attr: Option<&CratePathAttr>) -> Result<syn::Path> {
312    crate_path_attr
313        .map(|path_attr| Ok(path_attr.path().clone()))
314        .unwrap_or_else(|| crate_name_path("scale-info"))
315}
316
317fn clean_type_string(input: &str) -> String {
318    input
319        .replace(" ::", "::")
320        .replace(":: ", "::")
321        .replace(" ,", ",")
322        .replace(" ;", ";")
323        .replace(" [", "[")
324        .replace("[ ", "[")
325        .replace(" ]", "]")
326        .replace(" (", "(")
327        // put back a space so that `a: (u8, (bool, u8))` isn't turned into `a: (u8,(bool, u8))`
328        .replace(",(", ", (")
329        .replace("( ", "(")
330        .replace(" )", ")")
331        .replace(" <", "<")
332        .replace("< ", "<")
333        .replace(" >", ">")
334        .replace("& \'", "&'")
335}