1use inflector::Inflector;
20use proc_macro2::TokenStream as TokenStream2;
21use quote::{format_ident, quote};
22use syn::{
23 Data, DataEnum, DeriveInput, Error, Expr, ExprLit, Fields, Ident, Lit, Meta, MetaNameValue,
24 Result, Variant,
25};
26
27pub fn derive(input: DeriveInput) -> Result<TokenStream2> {
28 let data_enum = match &input.data {
29 Data::Enum(data_enum) => data_enum,
30 _ => return Err(Error::new_spanned(&input, "Expected the `Instruction` enum")),
31 };
32 let builder_raw_impl = generate_builder_raw_impl(&input.ident, data_enum);
33 let builder_impl = generate_builder_impl(&input.ident, data_enum)?;
34 let builder_unpaid_impl = generate_builder_unpaid_impl(&input.ident, data_enum)?;
35 let output = quote! {
36 pub trait XcmBuilderState {}
38
39 pub enum AnythingGoes {}
41 pub enum PaymentRequired {}
43 pub enum LoadedHolding {}
45 pub enum ExplicitUnpaidRequired {}
47
48 impl XcmBuilderState for AnythingGoes {}
49 impl XcmBuilderState for PaymentRequired {}
50 impl XcmBuilderState for LoadedHolding {}
51 impl XcmBuilderState for ExplicitUnpaidRequired {}
52
53 pub struct XcmBuilder<Call, S: XcmBuilderState> {
55 pub(crate) instructions: Vec<Instruction<Call>>,
56 pub state: core::marker::PhantomData<S>,
57 }
58
59 impl<Call> Xcm<Call> {
60 pub fn builder() -> XcmBuilder<Call, PaymentRequired> {
61 XcmBuilder::<Call, PaymentRequired> {
62 instructions: Vec::new(),
63 state: core::marker::PhantomData,
64 }
65 }
66 pub fn builder_unpaid() -> XcmBuilder<Call, ExplicitUnpaidRequired> {
67 XcmBuilder::<Call, ExplicitUnpaidRequired> {
68 instructions: Vec::new(),
69 state: core::marker::PhantomData,
70 }
71 }
72 pub fn builder_unsafe() -> XcmBuilder<Call, AnythingGoes> {
73 XcmBuilder::<Call, AnythingGoes> {
74 instructions: Vec::new(),
75 state: core::marker::PhantomData,
76 }
77 }
78 }
79 #builder_impl
80 #builder_unpaid_impl
81 #builder_raw_impl
82 };
83 Ok(output)
84}
85
86fn generate_builder_raw_impl(name: &Ident, data_enum: &DataEnum) -> TokenStream2 {
87 let methods = data_enum.variants.iter().map(|variant| {
88 let variant_name = &variant.ident;
89 let method_name_string = &variant_name.to_string().to_snake_case();
90 let method_name = syn::Ident::new(method_name_string, variant_name.span());
91 let docs = get_doc_comments(variant);
92 let method = match &variant.fields {
93 Fields::Unit => {
94 quote! {
95 pub fn #method_name(mut self) -> Self {
96 self.instructions.push(#name::<Call>::#variant_name);
97 self
98 }
99 }
100 },
101 Fields::Unnamed(fields) => {
102 let arg_names: Vec<_> = fields
103 .unnamed
104 .iter()
105 .enumerate()
106 .map(|(index, _)| format_ident!("arg{}", index))
107 .collect();
108 let arg_types: Vec<_> = fields.unnamed.iter().map(|field| &field.ty).collect();
109 quote! {
110 pub fn #method_name(mut self, #(#arg_names: impl Into<#arg_types>),*) -> Self {
111 #(let #arg_names = #arg_names.into();)*
112 self.instructions.push(#name::<Call>::#variant_name(#(#arg_names),*));
113 self
114 }
115 }
116 },
117 Fields::Named(fields) => {
118 let arg_names: Vec<_> = fields.named.iter().map(|field| &field.ident).collect();
119 let arg_types: Vec<_> = fields.named.iter().map(|field| &field.ty).collect();
120 quote! {
121 pub fn #method_name(mut self, #(#arg_names: impl Into<#arg_types>),*) -> Self {
122 #(let #arg_names = #arg_names.into();)*
123 self.instructions.push(#name::<Call>::#variant_name { #(#arg_names),* });
124 self
125 }
126 }
127 },
128 };
129 quote! {
130 #(#docs)*
131 #method
132 }
133 });
134 let output = quote! {
135 impl<Call> XcmBuilder<Call, AnythingGoes> {
136 #(#methods)*
137
138 pub fn build(self) -> Xcm<Call> {
139 Xcm(self.instructions)
140 }
141 }
142 };
143 output
144}
145
146fn generate_builder_impl(name: &Ident, data_enum: &DataEnum) -> Result<TokenStream2> {
147 let load_holding_variants = data_enum
149 .variants
150 .iter()
151 .map(|variant| {
152 let maybe_builder_attr = variant.attrs.iter().find(|attr| match attr.meta {
153 Meta::List(ref list) => list.path.is_ident("builder"),
154 _ => false,
155 });
156 let builder_attr = match maybe_builder_attr {
157 Some(builder) => builder.clone(),
158 None => return Ok(None), };
161 let Meta::List(ref list) = builder_attr.meta else { unreachable!("We checked before") };
162 let inner_ident: Ident = syn::parse2(list.tokens.clone()).map_err(|_| {
163 Error::new_spanned(&builder_attr, "Expected `builder(loads_holding)`")
164 })?;
165 let ident_to_match: Ident = syn::parse_quote!(loads_holding);
166 if inner_ident == ident_to_match {
167 Ok(Some(variant))
168 } else {
169 Err(Error::new_spanned(&builder_attr, "Expected `builder(loads_holding)`"))
170 }
171 })
172 .collect::<Result<Vec<_>>>()?;
173
174 let load_holding_methods = load_holding_variants
175 .into_iter()
176 .flatten()
177 .map(|variant| {
178 let variant_name = &variant.ident;
179 let method_name_string = &variant_name.to_string().to_snake_case();
180 let method_name = syn::Ident::new(method_name_string, variant_name.span());
181 let docs = get_doc_comments(variant);
182 let method = match &variant.fields {
183 Fields::Unnamed(fields) => {
184 let arg_names: Vec<_> = fields
185 .unnamed
186 .iter()
187 .enumerate()
188 .map(|(index, _)| format_ident!("arg{}", index))
189 .collect();
190 let arg_types: Vec<_> = fields.unnamed.iter().map(|field| &field.ty).collect();
191 quote! {
192 #(#docs)*
193 pub fn #method_name(self, #(#arg_names: impl Into<#arg_types>),*) -> XcmBuilder<Call, LoadedHolding> {
194 let mut new_instructions = self.instructions;
195 #(let #arg_names = #arg_names.into();)*
196 new_instructions.push(#name::<Call>::#variant_name(#(#arg_names),*));
197 XcmBuilder {
198 instructions: new_instructions,
199 state: core::marker::PhantomData,
200 }
201 }
202 }
203 },
204 Fields::Named(fields) => {
205 let arg_names: Vec<_> = fields.named.iter().map(|field| &field.ident).collect();
206 let arg_types: Vec<_> = fields.named.iter().map(|field| &field.ty).collect();
207 quote! {
208 #(#docs)*
209 pub fn #method_name(self, #(#arg_names: impl Into<#arg_types>),*) -> XcmBuilder<Call, LoadedHolding> {
210 let mut new_instructions = self.instructions;
211 #(let #arg_names = #arg_names.into();)*
212 new_instructions.push(#name::<Call>::#variant_name { #(#arg_names),* });
213 XcmBuilder {
214 instructions: new_instructions,
215 state: core::marker::PhantomData,
216 }
217 }
218 }
219 },
220 _ =>
221 return Err(Error::new_spanned(
222 variant,
223 "Instructions that load the holding register should take operands",
224 )),
225 };
226 Ok(method)
227 })
228 .collect::<std::result::Result<Vec<_>, _>>()?;
229
230 let first_impl = quote! {
231 impl<Call> XcmBuilder<Call, PaymentRequired> {
232 #(#load_holding_methods)*
233 }
234 };
235
236 let allowed_after_load_holding_methods: Vec<TokenStream2> = data_enum
238 .variants
239 .iter()
240 .filter(|variant| variant.ident == "ClearOrigin")
241 .map(|variant| {
242 let variant_name = &variant.ident;
243 let method_name_string = &variant_name.to_string().to_snake_case();
244 let method_name = syn::Ident::new(method_name_string, variant_name.span());
245 let docs = get_doc_comments(variant);
246 let method = match &variant.fields {
247 Fields::Unit => {
248 quote! {
249 #(#docs)*
250 pub fn #method_name(mut self) -> XcmBuilder<Call, LoadedHolding> {
251 self.instructions.push(#name::<Call>::#variant_name);
252 self
253 }
254 }
255 },
256 _ => return Err(Error::new_spanned(variant, "ClearOrigin should have no fields")),
257 };
258 Ok(method)
259 })
260 .collect::<std::result::Result<Vec<_>, _>>()?;
261
262 let buy_execution_method = data_enum
264 .variants
265 .iter()
266 .find(|variant| variant.ident == "BuyExecution")
267 .map_or(
268 Err(Error::new_spanned(&data_enum.variants, "No BuyExecution instruction")),
269 |variant| {
270 let variant_name = &variant.ident;
271 let method_name_string = &variant_name.to_string().to_snake_case();
272 let method_name = syn::Ident::new(method_name_string, variant_name.span());
273 let docs = get_doc_comments(variant);
274 let fields = match &variant.fields {
275 Fields::Named(fields) => {
276 let arg_names: Vec<_> =
277 fields.named.iter().map(|field| &field.ident).collect();
278 let arg_types: Vec<_> =
279 fields.named.iter().map(|field| &field.ty).collect();
280 quote! {
281 #(#docs)*
282 pub fn #method_name(self, #(#arg_names: impl Into<#arg_types>),*) -> XcmBuilder<Call, AnythingGoes> {
283 let mut new_instructions = self.instructions;
284 #(let #arg_names = #arg_names.into();)*
285 new_instructions.push(#name::<Call>::#variant_name { #(#arg_names),* });
286 XcmBuilder {
287 instructions: new_instructions,
288 state: core::marker::PhantomData,
289 }
290 }
291 }
292 },
293 _ =>
294 return Err(Error::new_spanned(
295 variant,
296 "BuyExecution should have named fields",
297 )),
298 };
299 Ok(fields)
300 },
301 )?;
302
303 let second_impl = quote! {
304 impl<Call> XcmBuilder<Call, LoadedHolding> {
305 #(#allowed_after_load_holding_methods)*
306 #buy_execution_method
307 }
308 };
309
310 let output = quote! {
311 #first_impl
312 #second_impl
313 };
314
315 Ok(output)
316}
317
318fn generate_builder_unpaid_impl(name: &Ident, data_enum: &DataEnum) -> Result<TokenStream2> {
319 let unpaid_execution_variant = data_enum
320 .variants
321 .iter()
322 .find(|variant| variant.ident == "UnpaidExecution")
323 .ok_or(Error::new_spanned(&data_enum.variants, "No UnpaidExecution instruction"))?;
324 let unpaid_execution_ident = &unpaid_execution_variant.ident;
325 let unpaid_execution_method_name = Ident::new(
326 &unpaid_execution_ident.to_string().to_snake_case(),
327 unpaid_execution_ident.span(),
328 );
329 let docs = get_doc_comments(unpaid_execution_variant);
330 let fields = match &unpaid_execution_variant.fields {
331 Fields::Named(fields) => fields,
332 _ =>
333 return Err(Error::new_spanned(
334 unpaid_execution_variant,
335 "UnpaidExecution should have named fields",
336 )),
337 };
338 let arg_names: Vec<_> = fields.named.iter().map(|field| &field.ident).collect();
339 let arg_types: Vec<_> = fields.named.iter().map(|field| &field.ty).collect();
340 Ok(quote! {
341 impl<Call> XcmBuilder<Call, ExplicitUnpaidRequired> {
342 #(#docs)*
343 pub fn #unpaid_execution_method_name(self, #(#arg_names: impl Into<#arg_types>),*) -> XcmBuilder<Call, AnythingGoes> {
344 let mut new_instructions = self.instructions;
345 #(let #arg_names = #arg_names.into();)*
346 new_instructions.push(#name::<Call>::#unpaid_execution_ident { #(#arg_names),* });
347 XcmBuilder {
348 instructions: new_instructions,
349 state: core::marker::PhantomData,
350 }
351 }
352 }
353 })
354}
355
356fn get_doc_comments(variant: &Variant) -> Vec<TokenStream2> {
357 variant
358 .attrs
359 .iter()
360 .filter_map(|attr| match &attr.meta {
361 Meta::NameValue(MetaNameValue {
362 value: Expr::Lit(ExprLit { lit: Lit::Str(literal), .. }),
363 ..
364 }) if attr.path().is_ident("doc") => Some(literal.value()),
365 _ => None,
366 })
367 .map(|doc| syn::parse_str::<TokenStream2>(&format!("/// {}", doc)).unwrap())
368 .collect()
369}