1use std::collections::BTreeSet;
2use std::path::Path;
3use std::{env, fs};
4
5use proc_macro2::Span;
6use quote::ToTokens;
7use sha2::{Digest, Sha256};
8use syn::visit::Visit;
9use syn::visit_mut::VisitMut;
10use syn::{parse_quote, UsePath};
11
12struct GenMacroVistor {
13 exported_macros: BTreeSet<(String, String)>,
14 current_mod: syn::Path,
15}
16
17impl<'a> Visit<'a> for GenMacroVistor {
19 fn visit_item_mod(&mut self, i: &'a syn::ItemMod) {
20 let old_mod = self.current_mod.clone();
21 let i_ident = &i.ident;
22 self.current_mod = parse_quote!(#old_mod::#i_ident);
23
24 syn::visit::visit_item_mod(self, i);
25
26 self.current_mod = old_mod;
27 }
28
29 fn visit_item_fn(&mut self, i: &'a syn::ItemFn) {
30 let is_entry = i
31 .attrs
32 .iter()
33 .any(|a| a.path().to_token_stream().to_string() == "stageleft :: entry");
34
35 if is_entry {
36 let cur_path = &self.current_mod;
37 let mut i_cloned = i.clone();
38 i_cloned.attrs = vec![];
39 let contents = i_cloned
40 .to_token_stream()
41 .to_string()
42 .chars()
43 .filter(|c| c.is_alphanumeric())
44 .collect::<String>();
45 let contents_hash = format!("{:X}", Sha256::digest(contents));
46 self.exported_macros
47 .insert((contents_hash, cur_path.to_token_stream().to_string()));
48 }
49 }
50}
51
52pub fn gen_macro(staged_path: &Path, crate_name: &str) {
53 let out_dir = env::var_os("OUT_DIR").unwrap();
54 let dest_path = Path::new(&out_dir).join("lib_macro.rs");
55
56 let flow_lib =
57 syn_inline_mod::parse_and_inline_modules(&staged_path.join("src").join("lib.rs"));
58 let mut visitor = GenMacroVistor {
59 exported_macros: Default::default(),
60 current_mod: parse_quote!(crate),
61 };
62 visitor.visit_file(&flow_lib);
63
64 let staged_path_absolute = fs::canonicalize(staged_path).unwrap();
65
66 let mut out_file: syn::File = parse_quote!();
67
68 for (hash, exported_from) in visitor.exported_macros {
69 let underscored_path = syn::Ident::new(&("macro_".to_string() + &hash), Span::call_site());
70 let underscored_path_impl =
71 syn::Ident::new(&("macro_".to_string() + &hash + "_impl"), Span::call_site());
72 let exported_from_parsed: syn::Path = syn::parse_str(&exported_from).unwrap();
73
74 let proc_macro_wrapper: syn::ItemFn = parse_quote!(
75 #[proc_macro]
76 #[expect(unused_qualifications, non_snake_case, reason = "generated code")]
77 pub fn #underscored_path(input: ::proc_macro::TokenStream) -> ::proc_macro::TokenStream {
78 let input = ::stageleft::internal::TokenStream::from(input);
79 let out = #exported_from_parsed::#underscored_path_impl(input);
80 ::proc_macro::TokenStream::from(out)
81 }
82 );
83
84 out_file.items.push(syn::Item::Fn(proc_macro_wrapper));
85 }
86
87 fs::write(dest_path, out_file.to_token_stream().to_string()).unwrap();
88
89 println!("cargo::rustc-check-cfg=cfg(stageleft_macro)");
90 println!("cargo::rerun-if-changed=build.rs");
91 println!("cargo::rustc-env=STAGELEFT_FINAL_CRATE_NAME={}", crate_name);
92 println!("cargo::rustc-cfg=stageleft_macro");
93
94 println!(
95 "cargo::rerun-if-changed={}",
96 staged_path_absolute.to_string_lossy()
97 );
98}
99
100struct InlineTopLevelMod {}
101
102impl VisitMut for InlineTopLevelMod {
103 fn visit_file_mut(&mut self, i: &mut syn::File) {
104 i.attrs = vec![];
105 i.items.iter_mut().for_each(|i| {
106 if let syn::Item::Macro(e) = i {
107 if e.mac.path.to_token_stream().to_string() == "stageleft :: top_level_mod" {
108 let inner = &e.mac.tokens;
109 *i = parse_quote!(
110 pub mod #inner;
111 );
112 }
113 }
114 });
115 }
116}
117
118struct GenFinalPubVistor {
119 current_mod: Option<syn::Path>,
120 test_mode: bool,
121}
122
123impl VisitMut for GenFinalPubVistor {
124 fn visit_item_enum_mut(&mut self, i: &mut syn::ItemEnum) {
125 i.vis = parse_quote!(pub);
126 syn::visit_mut::visit_item_enum_mut(self, i);
127 }
128
129 fn visit_variant_mut(&mut self, _i: &mut syn::Variant) {
130 }
132
133 fn visit_item_struct_mut(&mut self, i: &mut syn::ItemStruct) {
134 i.vis = parse_quote!(pub);
135 syn::visit_mut::visit_item_struct_mut(self, i);
136 }
137
138 fn visit_field_mut(&mut self, i: &mut syn::Field) {
139 i.vis = parse_quote!(pub);
140 syn::visit_mut::visit_field_mut(self, i);
141 }
142
143 fn visit_item_use_mut(&mut self, i: &mut syn::ItemUse) {
144 i.vis = parse_quote!(pub);
145 syn::visit_mut::visit_item_use_mut(self, i);
146 }
147
148 fn visit_use_path_mut(&mut self, i: &mut UsePath) {
149 if i.ident == "crate" {
150 i.tree = Box::new(syn::UseTree::Path(UsePath {
151 ident: parse_quote!(__staged),
152 colon2_token: Default::default(),
153 tree: i.tree.clone(),
154 }));
155 }
156
157 syn::visit_mut::visit_use_path_mut(self, i);
158 }
159
160 fn visit_item_mod_mut(&mut self, i: &mut syn::ItemMod) {
161 let is_runtime_or_test = i.attrs.iter().any(|a| {
162 a.path().to_token_stream().to_string() == "stageleft :: runtime"
163 || a.to_token_stream().to_string() == "# [test]"
164 || a.to_token_stream().to_string() == "# [tokio::test]"
165 });
166
167 let is_test_mod = i
168 .attrs
169 .iter()
170 .any(|a| a.to_token_stream().to_string() == "# [cfg (test)]");
171
172 if is_runtime_or_test {
173 *i = parse_quote! {
174 #[cfg(stageleft_macro)]
175 #i
176 };
177 } else {
178 if is_test_mod {
179 i.attrs
180 .retain(|a| a.to_token_stream().to_string() != "# [cfg (test)]");
181
182 if !self.test_mode {
183 i.attrs.push(parse_quote!(#[cfg(stageleft_macro)]));
184 }
185 }
186
187 let old_mod = self.current_mod.clone();
188 let i_ident = &i.ident;
189 self.current_mod = self
190 .current_mod
191 .as_ref()
192 .map(|old_mod| parse_quote!(#old_mod::#i_ident));
193
194 i.vis = parse_quote!(pub);
195
196 syn::visit_mut::visit_item_mod_mut(self, i);
197
198 self.current_mod = old_mod;
199 }
200 }
201
202 fn visit_item_fn_mut(&mut self, i: &mut syn::ItemFn) {
203 let is_entry = i
204 .attrs
205 .iter()
206 .any(|a| a.path().to_token_stream().to_string() == "stageleft :: entry");
207
208 if is_entry {
209 *i = parse_quote! {
210 #[cfg(stageleft_macro)]
211 #i
212 }
213 }
214
215 i.vis = parse_quote!(pub);
216
217 syn::visit_mut::visit_item_fn_mut(self, i);
218 }
219
220 fn visit_item_mut(&mut self, i: &mut syn::Item) {
221 if let Some(cur_path) = self.current_mod.as_ref() {
224 if let syn::Item::Struct(s) = i {
225 if matches!(s.vis, syn::Visibility::Public(_)) {
226 let e_name = &s.ident;
227 *i = parse_quote!(pub use #cur_path::#e_name;);
228 return;
229 }
230 } else if let syn::Item::Enum(e) = i {
231 if matches!(e.vis, syn::Visibility::Public(_)) {
232 let e_name = &e.ident;
233 *i = parse_quote!(pub use #cur_path::#e_name;);
234 return;
235 }
236 } else if let syn::Item::Trait(e) = i {
237 if matches!(e.vis, syn::Visibility::Public(_)) {
238 let e_name = &e.ident;
239 *i = parse_quote!(pub use #cur_path::#e_name;);
240 return;
241 }
242 } else if let syn::Item::Impl(e) = i {
243 *i = parse_quote!(
245 #[cfg(stageleft_macro)]
246 #e
247 );
248 } else if let syn::Item::Static(e) = i {
249 if matches!(e.vis, syn::Visibility::Public(_)) {
250 let e_name = &e.ident;
251 *i = parse_quote!(pub use #cur_path::#e_name;);
252 return;
253 }
254 } else if let syn::Item::Const(e) = i {
255 if matches!(e.vis, syn::Visibility::Public(_)) {
256 let e_name = &e.ident;
257 *i = parse_quote!(pub use #cur_path::#e_name;);
258 return;
259 }
260 }
261 }
262
263 syn::visit_mut::visit_item_mut(self, i);
264 }
265
266 fn visit_file_mut(&mut self, i: &mut syn::File) {
267 i.attrs = vec![];
268 i.items.retain(|i| match i {
269 syn::Item::Macro(m) => {
270 m.mac.path.to_token_stream().to_string() != "stageleft :: stageleft_crate"
271 && m.mac.path.to_token_stream().to_string()
272 != "stageleft :: stageleft_no_entry_crate"
273 }
274 _ => true,
275 });
276
277 syn::visit_mut::visit_file_mut(self, i);
278 }
279}
280
281pub fn gen_staged_trybuild(lib_path: &Path, orig_crate_name: String, test_mode: bool) -> syn::File {
282 let mut orig_flow_lib = syn_inline_mod::parse_and_inline_modules(lib_path);
283 InlineTopLevelMod {}.visit_file_mut(&mut orig_flow_lib);
284
285 let mut flow_lib_pub = syn_inline_mod::parse_and_inline_modules(lib_path);
286
287 let orig_crate_ident = syn::Ident::new(&orig_crate_name, Span::call_site());
288 let mut final_pub_visitor = GenFinalPubVistor {
289 current_mod: Some(parse_quote!(#orig_crate_ident)),
290 test_mode,
291 };
292 final_pub_visitor.visit_file_mut(&mut flow_lib_pub);
293
294 flow_lib_pub
295}
296
297pub fn gen_final_helper() {
298 let out_dir = env::var_os("OUT_DIR").unwrap();
299
300 let mut orig_flow_lib = syn_inline_mod::parse_and_inline_modules(Path::new("src/lib.rs"));
301 InlineTopLevelMod {}.visit_file_mut(&mut orig_flow_lib);
302
303 let mut flow_lib_pub = syn_inline_mod::parse_and_inline_modules(Path::new("src/lib.rs"));
304
305 let mut final_pub_visitor = GenFinalPubVistor {
306 current_mod: Some(parse_quote!(crate)),
307 test_mode: false,
308 };
309 final_pub_visitor.visit_file_mut(&mut flow_lib_pub);
310
311 fs::write(
312 Path::new(&out_dir).join("lib_pub.rs"),
313 flow_lib_pub.to_token_stream().to_string(),
314 )
315 .unwrap();
316
317 println!("cargo::rustc-check-cfg=cfg(stageleft_macro)");
318 println!("cargo::rerun-if-changed=build.rs");
319 println!("cargo::rerun-if-changed=src");
320}
321
322#[macro_export]
323macro_rules! gen_final {
324 () => {
325 #[cfg(not(feature = "stageleft_devel"))]
326 $crate::gen_final_helper()
327 };
328}