1mod calls;
8mod constants;
9mod custom_values;
10mod errors;
11mod events;
12mod pallet_view_functions;
13mod runtime_apis;
14mod storage;
15
16use scale_typegen::TypeGenerator;
17use scale_typegen::typegen::ir::ToTokensWithSettings;
18use scale_typegen::typegen::ir::type_ir::{CompositeFieldIR, CompositeIR, CompositeIRKind};
19use scale_typegen::typegen::type_params::TypeParameters;
20use scale_typegen::typegen::type_path::TypePath;
21use subxt_metadata::Metadata;
22use syn::{Ident, parse_quote};
23
24use crate::error::CodegenError;
25use crate::subxt_type_gen_settings;
26use crate::{api::custom_values::generate_custom_values, ir};
27
28use heck::{ToSnakeCase as _, ToUpperCamelCase};
29use proc_macro2::TokenStream as TokenStream2;
30use quote::{format_ident, quote};
31
32pub struct RuntimeGenerator {
34 metadata: Metadata,
35}
36
37impl RuntimeGenerator {
38 pub fn new(mut metadata: Metadata) -> Self {
49 scale_typegen::utils::ensure_unique_type_paths(metadata.types_mut())
50 .expect("Duplicate type paths in metadata; this is bug please file an issue.");
51 RuntimeGenerator { metadata }
52 }
53
54 pub fn generate_runtime_types(
64 &self,
65 item_mod: syn::ItemMod,
66 derives: scale_typegen::DerivesRegistry,
67 type_substitutes: scale_typegen::TypeSubstitutes,
68 crate_path: syn::Path,
69 should_gen_docs: bool,
70 ) -> Result<TokenStream2, CodegenError> {
71 let item_mod_attrs = item_mod.attrs.clone();
72 let item_mod_ir = ir::ItemMod::try_from(item_mod)?;
73
74 let settings =
75 subxt_type_gen_settings(derives, type_substitutes, &crate_path, should_gen_docs);
76
77 let type_gen = TypeGenerator::new(self.metadata.types(), &settings);
78 let types_mod = type_gen
79 .generate_types_mod()?
80 .to_token_stream(type_gen.settings());
81 let mod_ident = &item_mod_ir.ident;
82 let rust_items = item_mod_ir.rust_items();
83
84 Ok(quote! {
85 #( #item_mod_attrs )*
86 #[allow(dead_code, unused_imports, non_camel_case_types, unreachable_patterns)]
87 #[allow(clippy::all)]
88 #[allow(rustdoc::broken_intra_doc_links)]
89 pub mod #mod_ident {
90 #( #rust_items ) *
92
93 #[allow(unused_imports)]
96 mod root_mod {
97 pub use super::*;
98 }
99
100 #types_mod
101 }
102 })
103 }
104
105 pub fn generate_runtime(
115 &self,
116 item_mod: syn::ItemMod,
117 derives: scale_typegen::DerivesRegistry,
118 type_substitutes: scale_typegen::TypeSubstitutes,
119 crate_path: syn::Path,
120 should_gen_docs: bool,
121 ) -> Result<TokenStream2, CodegenError> {
122 let item_mod_attrs = item_mod.attrs.clone();
123 let item_mod_ir = ir::ItemMod::try_from(item_mod)?;
124
125 let settings =
126 subxt_type_gen_settings(derives, type_substitutes, &crate_path, should_gen_docs);
127
128 let type_gen = TypeGenerator::new(self.metadata.types(), &settings);
129 let types_mod = type_gen
130 .generate_types_mod()?
131 .to_token_stream(type_gen.settings());
132 let types_mod_ident = type_gen.types_mod_ident();
133 let pallets_with_mod_names = self
134 .metadata
135 .pallets()
136 .map(|pallet| {
137 (
138 pallet,
139 format_ident!("{}", pallet.name().to_string().to_snake_case()),
140 )
141 })
142 .collect::<Vec<_>>();
143
144 let pallet_names: Vec<_> = self
148 .metadata
149 .pallets()
150 .map(|pallet| pallet.name())
151 .collect();
152 let pallet_names_len = pallet_names.len();
153
154 let runtime_api_names: Vec<_> = self
155 .metadata
156 .runtime_api_traits()
157 .map(|api| api.name().to_string())
158 .collect();
159 let runtime_api_names_len = runtime_api_names.len();
160
161 let modules = pallets_with_mod_names
162 .iter()
163 .map(|(pallet, mod_name)| {
164 let calls = calls::generate_calls(&type_gen, pallet, &crate_path)?;
165
166 let event = events::generate_events(&type_gen, pallet, &crate_path)?;
167
168 let storage_mod = storage::generate_storage(&type_gen, pallet, &crate_path)?;
169
170 let constants_mod = constants::generate_constants(&type_gen, pallet, &crate_path)?;
171
172 let errors = errors::generate_error_type_alias(&type_gen, pallet)?;
173
174 let view_functions = pallet_view_functions::generate_pallet_view_functions(
175 &type_gen,
176 pallet,
177 &crate_path,
178 )?;
179
180 Ok(quote! {
181 pub mod #mod_name {
182 use super::root_mod;
183 use super::#types_mod_ident;
184 #errors
185 #calls
186 #view_functions
187 #event
188 #storage_mod
189 #constants_mod
190 }
191 })
192 })
193 .collect::<Result<Vec<_>, CodegenError>>()?;
194
195 let mod_ident = &item_mod_ir.ident;
196 let pallets_with_constants: Vec<_> = pallets_with_mod_names
197 .iter()
198 .filter_map(|(pallet, pallet_mod_name)| {
199 pallet
200 .constants()
201 .next()
202 .is_some()
203 .then_some(pallet_mod_name)
204 })
205 .collect();
206
207 let pallets_with_storage: Vec<_> = pallets_with_mod_names
208 .iter()
209 .filter_map(|(pallet, pallet_mod_name)| pallet.storage().map(|_| pallet_mod_name))
210 .collect();
211
212 let pallets_with_calls: Vec<_> = pallets_with_mod_names
213 .iter()
214 .filter_map(|(pallet, pallet_mod_name)| pallet.call_ty_id().map(|_| pallet_mod_name))
215 .collect();
216
217 let pallets_with_view_functions: Vec<_> = pallets_with_mod_names
218 .iter()
219 .filter(|(pallet, _pallet_mod_name)| pallet.has_view_functions())
220 .map(|(_, pallet_mod_name)| pallet_mod_name)
221 .collect();
222
223 let rust_items = item_mod_ir.rust_items();
224
225 let apis_mod = runtime_apis::generate_runtime_apis(
226 &self.metadata,
227 &type_gen,
228 types_mod_ident,
229 &crate_path,
230 )?;
231
232 let call_path = type_gen
235 .resolve_type_path(self.metadata.outer_enums().call_enum_ty())?
236 .to_token_stream(type_gen.settings());
237 let event_path = type_gen
238 .resolve_type_path(self.metadata.outer_enums().event_enum_ty())?
239 .to_token_stream(type_gen.settings());
240 let error_path = type_gen
241 .resolve_type_path(self.metadata.outer_enums().error_enum_ty())?
242 .to_token_stream(type_gen.settings());
243
244 let metadata_hash = self.metadata.hasher().hash();
245
246 let custom_values = generate_custom_values(&self.metadata, &type_gen, &crate_path);
247
248 Ok(quote! {
249 #( #item_mod_attrs )*
250 #[allow(dead_code, unused_imports, non_camel_case_types, unreachable_patterns)]
251 #[allow(clippy::all)]
252 #[allow(rustdoc::broken_intra_doc_links)]
253 pub mod #mod_ident {
254 #( #rust_items ) *
256
257 #[allow(unused_imports)]
260 mod root_mod {
261 pub use super::*;
262 }
263
264 pub static PALLETS: [&str; #pallet_names_len] = [ #(#pallet_names,)* ];
266
267 pub static RUNTIME_APIS: [&str; #runtime_api_names_len] = [ #(#runtime_api_names,)* ];
269
270 pub type DispatchError = #types_mod_ident::sp_runtime::DispatchError;
272
273 pub type Event = #event_path;
275
276 pub type Call = #call_path;
278
279 pub type Error = #error_path;
281
282 pub fn constants() -> ConstantsApi {
283 ConstantsApi
284 }
285
286 pub fn storage() -> StorageApi {
287 StorageApi
288 }
289
290 pub fn tx() -> TransactionApi {
291 TransactionApi
292 }
293
294 pub fn apis() -> runtime_apis::RuntimeApi {
295 runtime_apis::RuntimeApi
296 }
297
298 #apis_mod
299
300 pub fn view_functions() -> ViewFunctionsApi {
301 ViewFunctionsApi
302 }
303
304 pub fn custom() -> CustomValuesApi {
305 CustomValuesApi
306 }
307
308 #custom_values
309
310 pub struct ConstantsApi;
311 impl ConstantsApi {
312 #(
313 pub fn #pallets_with_constants(&self) -> #pallets_with_constants::constants::ConstantsApi {
314 #pallets_with_constants::constants::ConstantsApi
315 }
316 )*
317 }
318
319 pub struct StorageApi;
320 impl StorageApi {
321 #(
322 pub fn #pallets_with_storage(&self) -> #pallets_with_storage::storage::StorageApi {
323 #pallets_with_storage::storage::StorageApi
324 }
325 )*
326 }
327
328 pub struct TransactionApi;
329 impl TransactionApi {
330 #(
331 pub fn #pallets_with_calls(&self) -> #pallets_with_calls::calls::TransactionApi {
332 #pallets_with_calls::calls::TransactionApi
333 }
334 )*
335 }
336
337 pub struct ViewFunctionsApi;
338 impl ViewFunctionsApi {
339 #(
340 pub fn #pallets_with_view_functions(&self) -> #pallets_with_view_functions::view_functions::ViewFunctionsApi {
341 #pallets_with_view_functions::view_functions::ViewFunctionsApi
342 }
343 )*
344 }
345
346 pub fn is_codegen_valid_for(metadata: &#crate_path::Metadata) -> bool {
348 let runtime_metadata_hash = metadata
349 .hasher()
350 .only_these_pallets(&PALLETS)
351 .only_these_runtime_apis(&RUNTIME_APIS)
352 .hash();
353 runtime_metadata_hash == [ #(#metadata_hash,)* ]
354 }
355
356 #( #modules )*
357 #types_mod
358 }
359 })
360 }
361}
362
363pub fn generate_structs_from_variants<F>(
365 type_gen: &TypeGenerator,
366 type_id: u32,
367 variant_to_struct_name: F,
368 error_message_type_name: &str,
369) -> Result<Vec<StructFromVariant>, CodegenError>
370where
371 F: Fn(&str) -> std::borrow::Cow<str>,
372{
373 let ty = type_gen.resolve_type(type_id)?;
374
375 let scale_info::TypeDef::Variant(variant) = &ty.type_def else {
376 return Err(CodegenError::InvalidType(error_message_type_name.into()));
377 };
378
379 variant
380 .variants
381 .iter()
382 .map(|var| {
383 let mut type_params = TypeParameters::from_scale_info(&[]);
384 let composite_ir_kind =
385 type_gen.create_composite_ir_kind(&var.fields, &mut type_params)?;
386 let struct_name = variant_to_struct_name(&var.name);
387 let mut composite = CompositeIR::new(
388 syn::parse_str(&struct_name).expect("enum variant is a valid ident; qed"),
389 composite_ir_kind,
390 type_gen.docs_from_scale_info(&var.docs),
391 );
392
393 let type_alias_mod = generate_type_alias_mod(&mut composite, type_gen);
394 Ok(StructFromVariant {
395 variant_name: var.name.to_string(),
396 composite,
397 type_alias_mod,
398 })
399 })
400 .collect()
401}
402
403pub struct StructFromVariant {
404 variant_name: String,
405 composite: CompositeIR,
406 type_alias_mod: TokenStream2,
407}
408
409pub fn generate_type_alias_mod(
437 composite: &mut CompositeIR,
438 type_gen: &TypeGenerator,
439) -> TokenStream2 {
440 let mut aliases: Vec<TokenStream2> = vec![];
441 let alias_mod_name: Ident = syn::parse_str(&composite.name.to_string().to_snake_case())
442 .expect("composite name in snake_case should be a valid identifier");
443
444 let mut modify_field_to_be_type_alias = |field: &mut CompositeFieldIR, alias_name: Ident| {
445 let type_path = field.type_path.to_token_stream(type_gen.settings());
446 aliases.push(quote!(pub type #alias_name = #type_path;));
447
448 let type_alias_path: syn::Path = parse_quote!(#alias_mod_name::#alias_name);
449 field.type_path = TypePath::from_syn_path(type_alias_path);
450 };
451
452 match &mut composite.kind {
453 CompositeIRKind::NoFields => {
454 return quote!(); }
456 CompositeIRKind::Named(named) => {
457 for (name, field) in named.iter_mut() {
458 let alias_name = format_ident!("{}", name.to_string().to_upper_camel_case());
459 modify_field_to_be_type_alias(field, alias_name);
460 }
461 }
462 CompositeIRKind::Unnamed(unnamed) => {
463 for (i, field) in unnamed.iter_mut().enumerate() {
464 let alias_name = format_ident!("Field{}", i);
465 modify_field_to_be_type_alias(field, alias_name);
466 }
467 }
468 };
469
470 let types_mod_ident = type_gen.types_mod_ident();
471 quote!(pub mod #alias_mod_name {
472 use super::#types_mod_ident;
473 #( #aliases )*
474 })
475}