directed_stage_macro/
lib.rs

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/// A macro that wraps a function with the standardized interface:
12/// fn fn_name(&mut DataMap, &DataMap) -> anyhow::Result<DataMap>
13///
14/// Example usage:
15///
16/// ```
17/// #[stage(lazy, transparent)]
18/// fn add_numbers(a: i32, b: i32) -> i32 {
19///     a + b
20/// }
21/// ```
22///
23/// Multiple outputs are also supported with this syntax:
24/// #[stage(out(arg1_name: String, arg2_name: Vec<u8>))]
25/// fn output_things() -> directed::NodeOutput {
26///    let some_string = String::from("Hello Graph!");
27///    let some_vec = vec![1, 2, 3, 4, 5];
28///
29///    // This builds an output type
30///    directed::output!{
31///        arg1_name: some_string,
32///        arg2_name: some_vec
33///    }
34/// }
35#[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
43// Configuration structs
44struct 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        // Process stage attribute arguments
160        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        // Process function arguments to create input definitions
185        let inputs = Self::extract_input_params(&input_fn.sig.inputs)?;
186
187        // If no outputs specified, process return type
188        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                // Check if the return type is NodeOutput
249                if let Type::Path(type_path) = &**ty {
250                    if let Some(segment) = type_path.path.segments.last() {
251                        // TODO: This is a hack, just properly check if any out attributes exist
252                        if segment.ident == "NodeOutput" {
253                            // NodeOutput will be handled elsewhere
254                            return Ok(Vec::new());
255                        }
256                    }
257                }
258
259                // Single output with default name
260                Ok(vec![("_".to_string(), (**ty).clone())])
261            }
262            ReturnType::Default => {
263                // Return type is (), use default name
264                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                    // TODO: This is a hack, just check if any out attributes exist
280                    return segment.ident == "NodeOutput";
281                }
282            }
283        }
284        false
285    }
286}
287
288/// This associates the names of function parameters with the TypeId of their type.
289///
290/// Used to build and validate I/O-based connections
291fn 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
303/// This associates the names of function outputs with the TypeId of their type.
304/// When a function returns a NodeOutput type, this will associate meaningful
305/// names to each output. When a function returns any other type, this will
306/// simply associate that one type with the name '_'.
307///
308/// Used to build and validate I/O-based connections
309fn 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
320/// Get a type, squash the &
321fn 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
329/// This code is used by the wrapper function - it downcasts type-erased
330/// function parameters so that the user-facing function can be called with
331/// concrete types.
332fn 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            // Non-transparent functions never clone, always move
341            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
376/// This generates the code that uses the output of a parent node to set the
377/// input of a child node. This function is for moves only.
378fn 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    // Add the default case
398    code.push(quote! {
399        name => Err(anyhow::anyhow!("Unexpected node name: {}", name))
400    });
401
402    code
403}
404
405/// This generates the code that uses the output of a parent node to set the
406/// input of a child node. An equality comparison will be done between new
407/// output and the previous input, and a flag is raised if they don't match.
408fn 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(); // Clone the Arc
420                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    // Add the default case
444    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    // Add the default case
486    code.push(quote! {
487        name => Err(anyhow::anyhow!("Unexpected node name: {}", name))
488    });
489
490    code
491}
492
493/// Functions that return a NodeOutput are used as-is, where as functions
494/// that return anything else are wrapped in a simple MultOutput (simple
495/// in that it contains only 1 output named '_')
496fn 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                            // Parent is opaque, use Arc::into_inner
523                            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                            // Parent is transparent, clone the value
530                            (*#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                    // TODO: if node is transparent (config.cache_strategy != None), error with a graceful message (rather than letting clone fail)
539                    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
548/// The core trait that defines a stage - the culimnation of this macro
549fn 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    // Generate code sections
560    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!(), // TODO: Handle CacheAll extraction
566    };
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    // Determine evaluation strategy and reevaluation rule
576    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!(), //quote! { directed::ReevaluationRule::CacheAll },
586    };
587
588    // The coup de grace
589    quote! {
590        // Create a struct implementing the Stage trait
591        #[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            // TODO: This can be simplified to be a bit less unruly
634            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}