1use proc_macro::TokenStream;
2use proc_macro_error::proc_macro_error;
3use quote::quote;
4use syn::{
5 FnArg, ItemFn, Pat, ReturnType, Token, Type,
6 parse::{Parse, ParseStream},
7 parse_macro_input,
8 punctuated::Punctuated,
9};
10
11#[proc_macro_attribute]
36#[proc_macro_error]
37pub fn stage(attr: TokenStream, item: TokenStream) -> TokenStream {
38 let input_fn = parse_macro_input!(item as ItemFn);
39 let meta_args = parse_macro_input!(attr as StageArgs);
40 generate_stage_impl(StageConfig::from_args(&input_fn, &meta_args).unwrap()).into()
41}
42
43struct StageConfig {
45 original_fn: ItemFn,
46 stage_name: syn::Ident,
47 is_lazy: bool,
48 cache_strategy: CacheStrategy,
49 outputs: Vec<(String, Type)>,
50 inputs: Vec<InputParam>,
51 state_type: proc_macro2::TokenStream
52}
53
54enum RefType {
55 Owned,
56 Borrowed,
57 BorrowedMut,
58}
59
60impl RefType {
61 fn quoted(&self) -> proc_macro2::TokenStream {
62 match self {
63 RefType::Owned => quote! { directed::RefType::Owned },
64 RefType::Borrowed => quote! { directed::RefType::Borrowed },
65 RefType::BorrowedMut => quote! { directed::RefType::BorrowedMut },
66 }
67 }
68}
69
70struct InputParam {
71 name: syn::Ident,
72 type_: Type,
73 ref_type: RefType,
74 clean_name: String,
75}
76
77#[derive(Clone)]
78struct Outputs(Punctuated<Output, Token![,]>);
79
80impl Parse for Outputs {
81 fn parse(input: ParseStream) -> syn::Result<Self> {
82 Punctuated::parse_terminated(input).map(Self)
83 }
84}
85
86#[derive(Clone)]
87struct Output {
88 name: syn::Ident,
89 ty: Type,
90}
91
92impl Parse for Output {
93 fn parse(input: ParseStream) -> syn::Result<Self> {
94 let name = input.parse()?;
95 let _colon_token: Token![:] = input.parse()?;
96 let ty = input.parse()?;
97 Ok(Output { name, ty })
98 }
99}
100
101enum StageArg {
102 Flag(syn::Ident),
103 Output(Outputs),
104 State(syn::Type)
105}
106
107impl Parse for StageArg {
108 fn parse(input: ParseStream) -> syn::Result<Self> {
109 let lookahead = input.lookahead1();
110
111 if lookahead.peek(syn::Ident) {
112 let ident: syn::Ident = input.parse()?;
113
114 if ident == "out" {
115 let content;
116 let _paren_token = syn::parenthesized!(content in input);
117 return Ok(StageArg::Output(content.parse()?));
118 } else if ident == "state" {
119 let content;
120 let _paren_token = syn::parenthesized!(content in input);
121 return Ok(StageArg::State(content.parse()?));
122 } else {
123 return Ok(StageArg::Flag(ident));
124 }
125 }
126
127 Err(lookahead.error())
128 }
129}
130
131struct StageArgs {
132 args: Punctuated<StageArg, Token![,]>,
133}
134
135impl Parse for StageArgs {
136 fn parse(input: ParseStream) -> syn::Result<Self> {
137 Ok(StageArgs {
138 args: Punctuated::parse_terminated(input)?,
139 })
140 }
141}
142
143#[derive(PartialEq)]
144enum CacheStrategy {
145 None,
146 Last,
147 All,
148}
149
150impl StageConfig {
151 fn from_args(input_fn: &ItemFn, meta_args: &StageArgs) -> syn::Result<Self> {
152 let stage_name = input_fn.sig.ident.clone();
153
154 let mut is_lazy = false;
155 let mut cache_strategy = CacheStrategy::None;
156 let mut outputs = Vec::new();
157 let mut state_type = quote!(());
158
159 for arg in meta_args.args.iter() {
161 match arg {
162 StageArg::Flag(ident) => match ident.to_string().as_str() {
163 "lazy" => is_lazy = true,
164 "cache_last" => cache_strategy = CacheStrategy::Last,
165 "cache_all" => cache_strategy = CacheStrategy::All,
166 unknown => {
167 return Err(syn::Error::new(
168 ident.span(),
169 format!("Unrecognized attribute: {}", unknown),
170 ));
171 }
172 },
173 StageArg::Output(output_defs) => {
174 for output in &output_defs.0 {
175 outputs.push((output.name.to_string(), output.ty.clone()));
176 }
177 },
178 StageArg::State(ty) => {
179 state_type = quote!(#ty);
180 }
181 }
182 }
183
184 let inputs = Self::extract_input_params(&input_fn.sig.inputs)?;
186
187 if outputs.is_empty() {
189 outputs = Self::extract_outputs_from_return_type(&input_fn.sig.output)?;
190 }
191
192 Ok(StageConfig {
193 original_fn: input_fn.clone(),
194 stage_name,
195 is_lazy,
196 cache_strategy,
197 outputs,
198 inputs,
199 state_type
200 })
201 }
202
203 fn extract_input_params(
204 inputs: &syn::punctuated::Punctuated<FnArg, Token![,]>,
205 ) -> syn::Result<Vec<InputParam>> {
206 let mut result = Vec::new();
207
208 for arg in inputs.iter() {
209 if let FnArg::Typed(pat_type) = arg {
210 if let Pat::Ident(pat_ident) = &*pat_type.pat {
211 let arg_name = &pat_ident.ident;
212 let arg_type = &pat_type.ty;
213 let arg_name_str = arg_name.to_string();
214
215 let is_unused = arg_name_str.starts_with('_');
216 let clean_name = if is_unused {
217 arg_name_str[1..].to_string()
218 } else {
219 arg_name_str.clone()
220 };
221
222 let ref_type = match &**arg_type {
223 Type::Reference(type_reference) if type_reference.mutability.is_some() => {
224 RefType::BorrowedMut
225 }
226 Type::Reference(_) => RefType::Borrowed,
227 _ => RefType::Owned,
228 };
229
230 result.push(InputParam {
231 name: arg_name.clone(),
232 type_: *arg_type.clone(),
233 ref_type,
234 clean_name,
235 });
236 }
237 }
238 }
239
240 Ok(result)
241 }
242
243 fn extract_outputs_from_return_type(
244 return_type: &ReturnType,
245 ) -> syn::Result<Vec<(String, Type)>> {
246 match return_type {
247 ReturnType::Type(_, ty) => {
248 if let Type::Path(type_path) = &**ty {
250 if let Some(segment) = type_path.path.segments.last() {
251 if segment.ident == "NodeOutput" {
253 return Ok(Vec::new());
255 }
256 }
257 }
258
259 Ok(vec![("_".to_string(), (**ty).clone())])
261 }
262 ReturnType::Default => {
263 Ok(vec![(
265 "_".to_string(),
266 Type::Tuple(syn::TypeTuple {
267 paren_token: syn::token::Paren::default(),
268 elems: Punctuated::new(),
269 }),
270 )])
271 }
272 }
273 }
274
275 fn is_multi_output(&self) -> bool {
276 if let ReturnType::Type(_, ty) = &self.original_fn.sig.output {
277 if let Type::Path(type_path) = &**ty {
278 if let Some(segment) = type_path.path.segments.last() {
279 return segment.ident == "NodeOutput";
281 }
282 }
283 }
284 false
285 }
286}
287
288fn generate_input_registrations(inputs: &[InputParam]) -> Vec<proc_macro2::TokenStream> {
292 inputs.iter().map(|input| {
293 let arg_name = &input.clean_name;
294 let arg_type = &input.type_;
295 let ref_type = input.ref_type.quoted();
296
297 quote! {
298 inputs.insert(directed::DataLabel::new(#arg_name), (std::any::TypeId::of::<#arg_type>(), #ref_type));
299 }
300 }).collect()
301}
302
303fn generate_output_registrations(outputs: &[(String, Type)]) -> Vec<proc_macro2::TokenStream> {
310 outputs
311 .iter()
312 .map(|(name, ty)| {
313 quote! {
314 outputs.insert(directed::DataLabel::new(#name), std::any::TypeId::of::<#ty>());
315 }
316 })
317 .collect()
318}
319
320fn true_type(ty: &syn::Type) -> &syn::Type {
322 if let syn::Type::Reference(ty) = ty {
323 &*ty.elem
324 } else {
325 ty
326 }
327}
328
329fn generate_extraction_code_move(inputs: &[InputParam]) -> Vec<proc_macro2::TokenStream> {
333 inputs.iter().map(|input| {
334 let arg_name = &input.name;
335 let arg_type = true_type(&input.type_);
336 let clean_arg_name = &input.clean_name;
337 let reeval_name = quote::format_ident!("{}_reevaluation_rule", clean_arg_name);
338
339 quote! {
340 let (#arg_name, #reeval_name): (std::sync::Arc<#arg_type>, directed::ReevaluationRule) = if let Some((input, reeval_rule)) = inputs.remove(&directed::DataLabel::new(#clean_arg_name)) {
342 let dc = std::sync::Arc::downcast::<#arg_type>(input);
343 match dc {
344 Ok(val) => (val, reeval_rule),
345 Err(e) => return Err(anyhow::anyhow!("Type mismatch for input [{}: {:?}], expected: [{}: {:?}] (move). ",
346 #clean_arg_name, e.type_id(), stringify!(#arg_type), std::any::TypeId::of::<#arg_type>()))
347 }
348 } else {
349 return Err(anyhow::anyhow!("Missing required input: {}", #clean_arg_name));
350 };
351 }
352 }).collect()
353}
354
355fn generate_extraction_code_cache_last(inputs: &[InputParam]) -> Vec<proc_macro2::TokenStream> {
356 inputs.iter().map(|input| {
357 let arg_name = &input.name;
358 let arg_type = true_type(&input.type_);
359 let clean_arg_name = &input.clean_name;
360 let reeval_name = quote::format_ident!("{}_reevaluation_rule", clean_arg_name);
361
362 quote! {
363 let (#arg_name, #reeval_name): (std::sync::Arc<#arg_type>, directed::ReevaluationRule) = if let Some((input, reeval_rule)) = inputs.get(&directed::DataLabel::new(#clean_arg_name)) {
364 match std::sync::Arc::downcast::<#arg_type>(input.clone()) {
365 Ok(val) => (val, *reeval_rule),
366 Err(_) => return Err(anyhow::anyhow!("Type mismatch for input {}, expected: {} (cache_last)",
367 #clean_arg_name, stringify!(#arg_type)))
368 }
369 } else {
370 return Err(anyhow::anyhow!("Missing required input: {}", #clean_arg_name));
371 };
372 }
373 }).collect()
374}
375
376fn inject_opaque_out(inputs: &[InputParam]) -> Vec<proc_macro2::TokenStream> {
379 let mut code = inputs.iter().map(|input| {
380 let clean_arg_name = &input.clean_name;
381 let arg_type = &input.type_;
382
383 quote! {
384 #clean_arg_name => {
385 let input_changed = node.input_changed();
386 let output_val = parent.outputs_mut()
387 .remove(&output)
388 .ok_or_else(|| anyhow::anyhow!("Output '{output:?}' not found"))?;
389 let output_val = std::sync::Arc::downcast::<#arg_type>(output_val)
390 .map_err(|_| anyhow::anyhow!("Type mismatch for output (inject_opaque_out)"))?;
391 node.inputs_mut().insert(input, (output_val, directed::ReevaluationRule::Move));
392 Ok(())
393 }
394 }
395 }).collect::<Vec<_>>();
396
397 code.push(quote! {
399 name => Err(anyhow::anyhow!("Unexpected node name: {}", name))
400 });
401
402 code
403}
404
405fn inject_transparent_out_to_owned_in(inputs: &[InputParam]) -> Vec<proc_macro2::TokenStream> {
409 let mut code = inputs.iter().map(|input| {
410 let clean_arg_name = &input.clean_name;
411 let arg_type = true_type(&input.type_);
412
413 quote! {
414 #clean_arg_name => {
415 let input_changed = node.input_changed();
416 let output_val = parent.outputs_mut()
417 .get(&output)
418 .ok_or_else(|| anyhow::anyhow!("Output '{output:?}' not found"))?
419 .clone(); let output_val = std::sync::Arc::downcast::<#arg_type>(output_val)
421 .map_err(|_| anyhow::anyhow!("Type mismatch for output (inject_transparent_out_to_owned_in)"))?;
422
423 match node.inputs_mut().get(&input) {
424 Some((input_val, _)) => {
425 let input_val = input_val
426 .downcast_ref::<#arg_type>()
427 .ok_or_else(|| anyhow::anyhow!("Type mismatch for input"))?;
428 if !input_changed && output_val.as_ref() != input_val {
429 node.set_input_changed(true);
430 }
431 },
432 None => {
433 node.set_input_changed(true);
434 }
435 }
436
437 node.inputs_mut().insert(input, (output_val, directed::ReevaluationRule::CacheLast));
438 Ok(())
439 }
440 }
441 }).collect::<Vec<_>>();
442
443 code.push(quote! {
445 name => Err(anyhow::anyhow!("Unexpected node name: {}", name))
446 });
447
448 code
449}
450
451fn inject_transparent_out_to_opaque_ref_in(inputs: &[InputParam]) -> Vec<proc_macro2::TokenStream> {
452 let mut code = inputs.iter().map(|input| {
453 let clean_arg_name = &input.clean_name;
454 let arg_type = true_type(&input.type_);
455
456 quote! {
457 #clean_arg_name => {
458 let input_changed = node.input_changed();
459 let output_val_arc = parent.outputs_mut()
460 .get(&output)
461 .ok_or_else(|| anyhow::anyhow!("Output '{output:?}' not found"))?;
462 let output_val_ref = std::sync::Arc::downcast::<#arg_type>(output_val_arc.clone())
463 .map_err(|_| anyhow::anyhow!("Type mismatch for output (inject_transparent_out_to_opaque_ref_in)"))?;
464
465 match node.inputs_mut().get(&input) {
466 Some((input_val, _)) => {
467 let input_val = input_val
468 .downcast_ref::<#arg_type>()
469 .ok_or_else(|| anyhow::anyhow!("Type mismatch for input"))?;
470 if !input_changed && input_val != &*output_val_ref {
471 node.set_input_changed(true);
472 }
473 },
474 None => {
475 node.set_input_changed(true);
476 }
477 }
478
479 node.inputs_mut().insert(input, (output_val_ref, directed::ReevaluationRule::CacheLast));
480 Ok(())
481 }
482 }
483 }).collect::<Vec<_>>();
484
485 code.push(quote! {
487 name => Err(anyhow::anyhow!("Unexpected node name: {}", name))
488 });
489
490 code
491}
492
493fn generate_output_handling(config: &StageConfig) -> proc_macro2::TokenStream {
497 let arg_names = config.inputs.iter().map(|input| &input.name);
498 if config.is_multi_output() {
499 quote! {
500 Ok(Self::get_fn()(state, #(#arg_names),*))
501 }
502 } else {
503 quote! {
504 Ok(directed::NodeOutput::new_simple(Self::get_fn()(state, #(#arg_names),*)))
505 }
506 }
507}
508
509fn prepare_input_types(config: &StageConfig) -> Vec<proc_macro2::TokenStream> {
510 let args = config
511 .inputs
512 .iter()
513 .map(|input| (&input.name, &input.clean_name, &input.ref_type));
514 let mut output = Vec::new();
515 for (arg_name, clean_name, ref_type) in args {
516 let reeval_name = quote::format_ident!("{}_reevaluation_rule", clean_name);
517 match ref_type {
518 RefType::Owned => {
519 output.push(quote!{
520 let #arg_name = match #reeval_name {
521 directed::ReevaluationRule::Move => {
522 match std::sync::Arc::into_inner(#arg_name) {
524 Some(arg) => arg,
525 None => {return Err(anyhow::anyhow!("Unexpected references alive for: {}", stringify!(#arg_name)))}
526 }
527 },
528 directed::ReevaluationRule::CacheLast => {
529 (*#arg_name).clone()
531 },
532 directed::ReevaluationRule::CacheAll => panic!("CacheAll is not yet implemented"),
533 };
534 });
535 }
536 RefType::Borrowed => {
537 output.push(quote! {
538 let #arg_name = #arg_name.as_ref();
540 });
541 }
542 RefType::BorrowedMut => panic!("Mutable refs are not yet supported"),
543 }
544 }
545 output
546}
547
548fn generate_stage_impl(config: StageConfig) -> proc_macro2::TokenStream {
550 let original_fn = &config.original_fn;
551 let stage_name = &config.stage_name;
552 let state_type = &config.state_type;
553 let fn_attrs = &original_fn.attrs;
554 let fn_vis = &original_fn.vis;
555 let original_args = &original_fn.sig.inputs;
556 let fn_return_type = &original_fn.sig.output;
557 let original_body = &original_fn.block;
558
559 let input_registrations = generate_input_registrations(&config.inputs);
561 let output_registrations = generate_output_registrations(&config.outputs);
562 let extraction_code = match config.cache_strategy {
563 CacheStrategy::None => generate_extraction_code_move(&config.inputs),
564 CacheStrategy::Last => generate_extraction_code_cache_last(&config.inputs),
565 CacheStrategy::All => todo!(), };
567 let inject_opaque_out_code = inject_opaque_out(&config.inputs);
568 let inject_transparent_out_to_owned_in_code =
569 inject_transparent_out_to_owned_in(&config.inputs);
570 let inject_transparent_out_to_opaque_ref_in_code =
571 inject_transparent_out_to_opaque_ref_in(&config.inputs);
572 let prepare_input_types_code = prepare_input_types(&config);
573 let output_handling = generate_output_handling(&config);
574
575 let eval_strategy = if config.is_lazy {
577 quote! { directed::EvalStrategy::Lazy }
578 } else {
579 quote! { directed::EvalStrategy::Urgent }
580 };
581
582 let reevaluation_rule = match config.cache_strategy {
583 CacheStrategy::None => quote! { directed::ReevaluationRule::Move },
584 CacheStrategy::Last => quote! { directed::ReevaluationRule::CacheLast },
585 CacheStrategy::All => todo!(), };
587
588 quote! {
590 #[derive(Clone)]
592 #fn_vis struct #stage_name {
593 inputs: std::collections::HashMap<directed::DataLabel, (std::any::TypeId, directed::RefType)>,
594 outputs: std::collections::HashMap<directed::DataLabel, std::any::TypeId>,
595 }
596
597 impl #stage_name {
598 pub fn new() -> Self {
599 let mut inputs = std::collections::HashMap::new();
600 let mut outputs = std::collections::HashMap::new();
601 #(#input_registrations)*
602 #(#output_registrations)*
603 Self { inputs, outputs }
604 }
605 }
606
607 impl directed::Stage for #stage_name {
608 type State = #state_type;
609 type BaseFn = fn(state: &mut #state_type, #original_args) #fn_return_type;
610
611 fn inputs(&self) -> &std::collections::HashMap<directed::DataLabel, (std::any::TypeId, directed::RefType)> {
612 &self.inputs
613 }
614
615 fn outputs(&self) -> &std::collections::HashMap<directed::DataLabel, std::any::TypeId> {
616 &self.outputs
617 }
618
619 fn evaluate(&self, state: &mut Self::State, inputs: &mut std::collections::HashMap<directed::DataLabel, (std::sync::Arc<dyn std::any::Any + Send + Sync>, directed::ReevaluationRule)>) -> anyhow::Result<directed::NodeOutput> {
620 #(#extraction_code)*
621 #(#prepare_input_types_code)*
622 #output_handling
623 }
624
625 fn eval_strategy(&self) -> directed::EvalStrategy {
626 #eval_strategy
627 }
628
629 fn reeval_rule(&self) -> directed::ReevaluationRule {
630 #reevaluation_rule
631 }
632
633 fn inject_input(&self, node: &mut directed::Node<Self>, parent: &mut Box<dyn directed::AnyNode>, output: directed::DataLabel, input: directed::DataLabel) -> anyhow::Result<()> {
635 fn inject_opaque_out(node: &mut dyn directed::AnyNode, parent: &mut Box<dyn directed::AnyNode>, output: directed::DataLabel, input: directed::DataLabel) -> anyhow::Result<()> {
636 match input.inner() {
637 #(#inject_opaque_out_code)*
638 }
639 }
640 fn inject_transparent_out_to_owned_in(node: &mut dyn directed::AnyNode, parent: &mut Box<dyn directed::AnyNode>, output: directed::DataLabel, input: directed::DataLabel) -> anyhow::Result<()> {
641 match input.inner() {
642 #(#inject_transparent_out_to_owned_in_code)*
643 }
644 }
645 fn inject_transparent_out_to_opaque_ref_in(node: &mut dyn directed::AnyNode, parent: &mut Box<dyn directed::AnyNode>, output: directed::DataLabel, input: directed::DataLabel) -> anyhow::Result<()> {
646 match input.inner() {
647 #(#inject_transparent_out_to_opaque_ref_in_code)*
648 }
649 }
650
651 if parent.reeval_rule() == directed::ReevaluationRule::Move {
652 if node.reeval_rule() == directed::ReevaluationRule::Move && node.input_reftype(&input) != Some(directed::RefType::Owned) {
653 inject_transparent_out_to_opaque_ref_in(node, parent, output, input)
654 } else {
655 inject_opaque_out(node, parent, output, input)
656 }
657 } else {
658 inject_transparent_out_to_owned_in(node, parent, output, input)
659 }
660 }
661
662 fn get_fn() -> Self::BaseFn {
663 #(#fn_attrs)*
664 fn original_fn(state: &mut #state_type, #original_args) #fn_return_type #original_body
665 return original_fn;
666 }
667 }
668 }
669}