tetsy_scale_info_derive/
lib.rs1#![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 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("e!(#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 .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 variants.iter().any(|v| v.discriminant.is_some()) ||
220 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}