1use proc_macro::TokenStream;
20use quote::{quote, ToTokens};
21use std::{borrow::Borrow, fmt::Display, iter};
22use syn::{
23 parse_macro_input, spanned::Spanned, Attribute, Error, FnArg, Item, ItemMod, Pat, ReturnType,
24 Type, TypePath, Visibility,
25};
26
27static MODULE_NAME: &str = "metafns";
28
29fn error<T>(spanned: impl Spanned, message: impl Display) -> Result<T, Error> {
30 Err(Error::new(spanned.span(), message))
31}
32
33macro_rules! return_error_if_some {
34 ($option:expr, $message:expr) => {
35 if let Some(spanned) = $option {
36 return error(spanned, $message);
37 }
38 };
39}
40
41fn validate_if_private(spanned: impl Spanned, visibility: &Visibility) -> Result<(), Error> {
42 match visibility {
43 Visibility::Public(_) => Ok(()),
44 other => match other {
45 Visibility::Inherited => {
46 error(spanned, "visibility must be public, add the `pub` keyword")
47 }
48 _ => error(
49 other,
50 "visibility mustn't be restricted, use the `pub` keyword alone",
51 ),
52 },
53 }
54}
55
56fn validate_if_has_no_attributes(
57 attributes: impl IntoIterator<Item = impl Borrow<Attribute>>,
58 message: impl Display,
59) -> Result<(), Error> {
60 let mut attributes = attributes.into_iter();
61
62 if let Some(attribute) = attributes.next() {
63 let span = attributes.fold(attribute.borrow().span(), |span, attribute| {
64 span.join(attribute.borrow().span()).unwrap_or(span)
65 });
66
67 error(span, message)
68 } else {
69 Ok(())
70 }
71}
72
73#[proc_macro_attribute]
171pub fn metawasm(_: TokenStream, item: TokenStream) -> TokenStream {
172 process(parse_macro_input!(item)).unwrap_or_else(|error| error.into_compile_error().into())
173}
174
175fn process(module: ItemMod) -> Result<TokenStream, Error> {
176 let module_span = module.span();
177
178 validate_if_has_no_attributes(
179 module.attrs,
180 "module with #[metawasm] mustn't have attributes",
181 )?;
182 validate_if_private(module_span, &module.vis)?;
183
184 if module.ident != MODULE_NAME {
185 return error(
186 module.ident,
187 format_args!("name of a module with #[metawasm] must be `{MODULE_NAME}`"),
188 );
189 }
190
191 let Some((_, items)) = module.content else {
192 return error(
193 module_span,
194 "`#[metawasm]` doesn't work with modules without a body",
195 );
196 };
197
198 if items.is_empty() {
199 return Ok(Default::default());
200 }
201
202 let mut items = items.into_iter();
203 let two_first_items = (items.next(), items.next());
204
205 let (potential_type_item, potential_functions) =
206 if let (Some(first), Some(second)) = two_first_items {
207 (first, iter::once(second).chain(items))
208 } else {
209 return error(
210 module_span,
211 "module with #[metawasm] must contain the `State` type alias & at least 1 function",
212 );
213 };
214
215 let Item::Type(type_item) = potential_type_item else {
218 return error(
219 potential_type_item,
220 "first item of a module with `#[metawasm]` must be a type alias to a state type (e.g. `type State = StateType;`)",
221 );
222 };
223 let type_item_attributes = &type_item.attrs;
224
225 let (state_type, state_type_inner) = if type_item.ident == "State" {
226 validate_if_private(&type_item, &type_item.vis)?;
227
228 if type_item.generics.params.is_empty() {
229 (
230 TypePath {
231 qself: None,
232 path: type_item.ident.into(),
233 }
234 .into(),
235 *type_item.ty,
236 )
237 } else {
238 return error(type_item.generics, "must be without generics");
239 }
240 } else {
241 return error(
242 type_item.ident,
243 "identifier of the state type must be `State`",
244 );
245 };
246
247 let mut functions = vec![];
250
251 for potential_function in potential_functions {
252 let Item::Fn(function) = potential_function else {
253 return error(
254 potential_function,
255 "rest of items in a module with `#[metawasm]` must be functions",
256 );
257 };
258
259 validate_if_private(&function, &function.vis)?;
260
261 let signature = function.sig;
262
263 return_error_if_some!(signature.constness, "mustn't be constant");
264 return_error_if_some!(signature.asyncness, "mustn't be asynchronous");
265 return_error_if_some!(signature.unsafety, "mustn't be unsafe");
266 return_error_if_some!(signature.abi, "mustn't have a binary interface");
267 return_error_if_some!(signature.variadic, "mustn't have the variadic argument");
268
269 if !signature.generics.params.is_empty() {
270 return error(signature.generics, "mustn't have generics");
271 }
272
273 if signature.inputs.len() > 19 {
274 return error(
275 signature.inputs,
276 "too many arguments, no more 19 arguments must be here due restrictions of the SCALE codec",
277 );
278 }
279
280 let signature_span = signature.span();
281 let mut inputs = signature.inputs.into_iter();
282
283 let first = if let Some(first) = inputs.next() {
286 if let FnArg::Typed(first) = first {
287 validate_if_has_no_attributes(&first.attrs, "mustn't have attributes")?;
288
289 first
290 } else {
291 return error(first, "mustn't be `self`");
292 }
293 } else {
294 return error(
295 signature.paren_token.span,
296 "mustn't be empty, add `state: State` or `_: State`",
297 );
298 };
299
300 if let Pat::Ident(ref pat_ident) = *first.pat {
303 if pat_ident.ident != "state" && pat_ident.ident != "_state" {
304 return error(&pat_ident.ident, "must be `state` or `_state`");
305 }
306 }
307
308 match *first.ty {
311 Type::Reference(reference) if *reference.elem == state_type => {
312 let lifetime_span = reference.lifetime.map(|lifetime| lifetime.span());
313 let mutability_span = reference.mutability.map(|mutability| mutability.span());
314
315 let lifetime_mutability = lifetime_span.map_or(mutability_span, |lifetime_span| {
316 Some(
317 mutability_span
318 .and_then(|mutability_span| mutability_span.join(lifetime_span))
319 .unwrap_or(lifetime_span),
320 )
321 });
322
323 let span = lifetime_mutability
324 .and_then(|lifetime_mutability| {
325 lifetime_mutability.join(reference.and_token.span)
326 })
327 .unwrap_or(reference.and_token.span);
328
329 return error(span, "mustn't take a reference");
330 }
331 first_type => {
332 if first_type != state_type {
333 return error(first_type, "first argument's type must be `State`");
334 }
335 }
336 }
337
338 let mut arguments = vec![];
341
342 for argument in inputs {
343 if let FnArg::Typed(argument) = argument {
344 validate_if_has_no_attributes(&argument.attrs, "mustn't have attributes")?;
345
346 arguments.push((argument.pat, argument.ty));
347 } else {
348 unreachable!("unexpected `self` argument");
351 }
352 }
353
354 let return_type = match signature.output {
357 ReturnType::Default => {
358 return error(signature_span, "return type must be specified");
359 }
360 ReturnType::Type(_, return_type) => {
361 if let Type::Tuple(ref tuple) = *return_type {
362 if tuple.elems.is_empty() {
363 return error(tuple, "return type mustn't be `()`");
364 }
365 }
366
367 return_type
368 }
369 };
370
371 functions.push((
372 function.attrs,
373 signature.ident,
374 first.pat,
375 arguments,
376 return_type,
377 function.block,
378 ));
379 }
380
381 let mut type_registrations = Vec::with_capacity(functions.len());
384 let (mut extern_functions, mut public_functions) =
385 (type_registrations.clone(), type_registrations.clone());
386
387 for (attributes, function_identifier, state_pattern, arguments, return_type, block) in functions
388 {
389 let CodeGenItems {
390 input_type,
391 variables,
392 variables_types,
393 variables_wo_parentheses,
394 arguments,
395 } = process_arguments(arguments, state_pattern);
396
397 let stringed_fn_ident = function_identifier.to_string();
398 let output = register_type(&return_type);
399
400 type_registrations.push(quote! {
401 funcs.insert(#stringed_fn_ident.into(), ::gmeta::TypesRepr { input: #input_type, output: #output });
402 });
403
404 extern_functions.push(quote! {
405 #[unsafe(no_mangle)]
406 extern "C" fn #function_identifier() {
407 let #variables: #variables_types = ::gstd::msg::load()
408 .expect("Failed to load or decode a payload");
409
410 ::gstd::msg::reply(super::#function_identifier(#variables_wo_parentheses), 0)
411 .expect("Failed to encode or reply with a result from a metawasm function");
412 }
413 });
414
415 public_functions.push(quote! {
416 #(#attributes)*
417 pub fn #function_identifier(#arguments) -> #return_type #block
418 });
419 }
420
421 let module_ident = quote::format_ident!("{MODULE_NAME}");
422
423 Ok(quote! {
424 pub mod #module_ident {
425 use super::*;
426
427 mod r#extern {
428 use super::*;
429
430 #[unsafe(no_mangle)]
431 extern "C" fn metadata() {
432 let mut funcs = ::gstd::collections::BTreeMap::new();
433 let mut registry = ::gmeta::Registry::new();
434
435 #(#type_registrations)*
436
437 let metawasm_data = ::gmeta::MetawasmData {
438 funcs,
439 registry: ::gstd::Encode::encode(&::gmeta::PortableRegistry::from(registry)),
440 };
441
442 ::gstd::msg::reply(metawasm_data, 0).expect("Failed to encode or reply with metawasm data");
443 }
444
445 #(#extern_functions)*
446 }
447
448 #(#type_item_attributes)*
449 pub type #state_type = #state_type_inner;
450
451 #(#public_functions)*
452 }
453 }.into())
454}
455
456struct CodeGenItems {
457 input_type: proc_macro2::TokenStream,
458 variables: proc_macro2::TokenStream,
459 variables_types: proc_macro2::TokenStream,
460 variables_wo_parentheses: proc_macro2::TokenStream,
461 arguments: proc_macro2::TokenStream,
462}
463
464fn process_arguments(
465 arguments: Vec<(Box<Pat>, Box<Type>)>,
466 state_pattern: Box<Pat>,
467) -> CodeGenItems {
468 if arguments.is_empty() {
469 let variables = quote!(state);
470
471 CodeGenItems {
472 input_type: quote!(None),
473 variables: variables.clone(),
474 variables_types: quote!(State),
475 variables_wo_parentheses: variables,
476 arguments: quote!(#state_pattern: State),
477 }
478 } else {
479 let arguments_types = arguments.iter().map(|argument| &argument.1);
480 let variables_types_wo_parentheses = quote!(#(#arguments_types),*);
481
482 let (variables_wo_parentheses, variables, variables_types) = if arguments.len() > 1 {
483 let variables_wo_parentheses =
484 (0..arguments.len()).map(|index| quote::format_ident!("arg{}", index));
485 let variables_wo_parentheses = quote!(#(#variables_wo_parentheses),*);
486
487 let variables_with_parentheses = quote!((#variables_wo_parentheses));
488
489 (
490 variables_wo_parentheses,
491 variables_with_parentheses,
492 quote!((#variables_types_wo_parentheses)),
493 )
494 } else {
495 let variables_wo_parentheses = quote!(arg);
496
497 (
498 variables_wo_parentheses.clone(),
499 variables_wo_parentheses,
500 variables_types_wo_parentheses,
501 )
502 };
503
504 let input_type = register_type(variables_types.clone());
505
506 let arguments = arguments
507 .into_iter()
508 .map(|(pattern, ty)| quote!(#pattern: #ty));
509
510 CodeGenItems {
511 input_type,
512 variables: quote!((#variables, state)),
513 variables_types: quote!((#variables_types, State)),
514 variables_wo_parentheses: quote!(state, #variables_wo_parentheses),
515 arguments: quote!(#state_pattern: State, #(#arguments),*),
516 }
517 }
518}
519
520fn register_type(ty: impl ToTokens) -> proc_macro2::TokenStream {
521 let ty = ty.to_token_stream();
522
523 quote! {
524 Some(registry.register_type(&::gmeta::MetaType::new::<#ty>()).id)
525 }
526}