runite_proc_macros/
lib.rs1#![deny(missing_docs)]
10
11use proc_macro::TokenStream;
12use proc_macro2::TokenStream as TokenStream2;
13use quote::{format_ident, quote};
14use syn::parse::{Parse, ParseStream};
15use syn::{Error, ItemFn, LitStr, Path, Token, parse_macro_input, parse_quote};
16
17#[proc_macro_attribute]
31pub fn main(attr: TokenStream, item: TokenStream) -> TokenStream {
32 expand(attr, item, EntryKind::Main)
33}
34
35#[proc_macro_attribute]
46pub fn test(attr: TokenStream, item: TokenStream) -> TokenStream {
47 expand(attr, item, EntryKind::Test)
48}
49
50#[derive(Clone, Copy, PartialEq, Eq)]
51enum EntryKind {
52 Main,
53 Test,
54}
55
56impl EntryKind {
57 fn noun(self) -> &'static str {
58 match self {
59 EntryKind::Main => "entry",
60 EntryKind::Test => "test",
61 }
62 }
63}
64
65struct EntryArgs {
68 crate_path: Path,
69}
70
71impl Parse for EntryArgs {
72 fn parse(input: ParseStream) -> syn::Result<Self> {
73 let mut crate_path: Path = parse_quote!(::runite);
74 if input.is_empty() {
75 return Ok(Self { crate_path });
76 }
77
78 if !input.peek(Token![crate]) {
80 return Err(input.error("runite entry attributes accept only `crate = \"...\"`"));
81 }
82 input.parse::<Token![crate]>()?;
83 input.parse::<Token![=]>()?;
84 let value: LitStr = input.parse()?;
85 crate_path = value.parse()?;
86
87 if !input.is_empty() {
88 return Err(input.error("unexpected trailing tokens after `crate = \"...\"`"));
89 }
90 Ok(Self { crate_path })
91 }
92}
93
94fn expand(attr: TokenStream, item: TokenStream, kind: EntryKind) -> TokenStream {
95 let args = parse_macro_input!(attr as EntryArgs);
96 let function = parse_macro_input!(item as ItemFn);
97 match validate(&function, kind) {
98 Ok(()) => generate(function, args.crate_path, kind).into(),
99 Err(error) => error.to_compile_error().into(),
100 }
101}
102
103fn validate(function: &ItemFn, kind: EntryKind) -> syn::Result<()> {
104 let signature = &function.sig;
105
106 if kind == EntryKind::Main && signature.ident != "main" {
107 return Err(Error::new_spanned(
108 &signature.ident,
109 "runite entry attribute must be attached to a function named `main`",
110 ));
111 }
112
113 let noun = kind.noun();
114 if !signature.inputs.is_empty() {
115 return Err(Error::new_spanned(
116 &signature.inputs,
117 format!("runite {noun} functions cannot take parameters"),
118 ));
119 }
120 if !signature.generics.params.is_empty() || signature.generics.where_clause.is_some() {
121 return Err(Error::new_spanned(
122 &signature.generics,
123 format!("runite {noun} functions cannot be generic"),
124 ));
125 }
126 if signature.constness.is_some() {
127 return Err(Error::new_spanned(
128 signature.fn_token,
129 format!("runite {noun} functions cannot be const"),
130 ));
131 }
132 if signature.unsafety.is_some() {
133 return Err(Error::new_spanned(
134 signature.fn_token,
135 format!("runite {noun} functions cannot be unsafe"),
136 ));
137 }
138 if signature.abi.is_some() {
139 return Err(Error::new_spanned(
140 &signature.abi,
141 format!("runite {noun} functions cannot declare an ABI"),
142 ));
143 }
144 if signature.variadic.is_some() {
145 return Err(Error::new_spanned(
146 &signature.variadic,
147 format!("runite {noun} functions cannot be variadic"),
148 ));
149 }
150
151 Ok(())
152}
153
154fn generate(function: ItemFn, crate_path: Path, kind: EntryKind) -> TokenStream2 {
155 let is_async = function.sig.asyncness.is_some();
156 let original_name = function.sig.ident.clone();
157 let output = function.sig.output.clone();
158 let implementation_name = format_ident!("__runite_runtime_internal_{}", original_name);
159
160 let mut implementation = function;
161 implementation.sig.ident = implementation_name.clone();
162
163 let wrapper_attrs = match kind {
167 EntryKind::Test => std::mem::take(&mut implementation.attrs),
168 EntryKind::Main => Vec::new(),
169 };
170
171 let drive = if is_async {
176 quote! { #crate_path::block_on(#implementation_name()) }
177 } else {
178 quote! {
179 let __runite_output = #implementation_name();
180 #crate_path::run();
181 __runite_output
182 }
183 };
184
185 let test_attr = match kind {
186 EntryKind::Test => quote! { #[::core::prelude::v1::test] },
189 EntryKind::Main => quote! {},
190 };
191
192 quote! {
193 #implementation
194
195 #(#wrapper_attrs)*
196 #test_attr
197 fn #original_name() #output {
198 #drive
199 }
200 }
201}