1extern 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 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 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("e!(#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, 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
295fn 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 .replace(",(", ", (")
329 .replace("( ", "(")
330 .replace(" )", ")")
331 .replace(" <", "<")
332 .replace("< ", "<")
333 .replace(" >", ">")
334 .replace("& \'", "&'")
335}