altv_internal_resource_main_macro/
lib.rs

1use proc_macro::TokenStream;
2use quote::quote;
3use syn::{parse::Parser, spanned::Spanned, ItemFn};
4
5/// Defines entry point of your alt:V Rust resource.
6///
7/// ## Example
8/// ```rust,ignore
9/// #[altv::main]
10/// fn main() -> impl altv::IntoVoidResult {
11///   altv::log!("hello world");
12/// }
13/// ```
14///
15/// ## `crate_name`
16/// This attribute can be used if `altv` crate is renamed in Cargo.toml using "package" option.
17///
18/// Cargo.toml
19/// ```toml
20/// [dependencies]
21/// my_custom_name = { version = "...", package = "altv" }
22/// ```
23///
24/// src/lib.rs
25/// ```rust,ignore
26/// #[my_custom_name::main(crate_name = "my_custom_name")]
27/// fn main() -> impl my_custom_name::IntoVoidResult {
28///   my_custom_name::log!("hello world");
29/// }
30/// ```
31#[proc_macro_attribute]
32pub fn resource_main_func(params: TokenStream, input: TokenStream) -> TokenStream {
33  let fn_item = {
34    let input = input.clone();
35    syn::parse_macro_input!(input as ItemFn)
36  };
37  let syn::ItemFn {
38    sig: fn_sig,
39    block: fn_block,
40    ..
41  } = fn_item;
42  let fn_ident = fn_sig.ident;
43
44  if fn_ident != "main" {
45    return compile_error(fn_ident, "main function must be named \"main\"");
46  }
47  if !fn_sig.inputs.is_empty() {
48    return compile_error(fn_sig.inputs, "main function can't have any arguments");
49  }
50
51  let crate_name = parse_crate_name_from_params(params);
52
53  let wrapped_fn = quote! {
54    fn #fn_ident() -> bool {
55      use #crate_name::IntoVoidResult;
56      fn user_code() -> impl IntoVoidResult #fn_block
57
58      match user_code().into_void_result() {
59        Ok(()) => {
60          true
61        }
62        Err(err) => {
63          #crate_name::__internal::on_main_error(err);
64          false
65        }
66      }
67    }
68  };
69
70  let exportified_fn = relib_exportify::exportify(wrapped_fn);
71
72  quote! {
73    // NOTE ⚠️: relib_module must be imported because it exports internal symbols that are required for relib_host crate
74    use #crate_name::__internal::relib_module as _;
75
76    #exportified_fn
77  }
78  .into()
79}
80
81fn compile_error(spanned: impl Spanned, message: &str) -> TokenStream {
82  syn::Error::new(spanned.span(), message)
83    .to_compile_error()
84    .into()
85}
86
87fn parse_crate_name_from_params(params: TokenStream) -> syn::Ident {
88  let mut crate_name = "altv".to_owned();
89
90  let parser = syn::meta::parser(|meta| {
91    assert!(
92      meta.path.is_ident("crate_name"),
93      "expected crate_name parameter"
94    );
95    let literal: syn::LitStr = meta.value()?.parse()?;
96    crate_name = literal.value();
97    Ok(())
98  });
99
100  parser
101    .parse(params)
102    .expect("Failed to parse altv::main parameters");
103
104  quote::format_ident!("{crate_name}")
105}