roto_macros/
lib.rs

1use proc_macro::TokenStream;
2use quote::{quote, quote_spanned};
3use syn::{Error, Token, parse::Parse, parse_macro_input, spanned::Spanned};
4
5#[proc_macro_derive(Context)]
6pub fn roto_context(item: TokenStream) -> TokenStream {
7    let item = parse_macro_input!(item as syn::DeriveInput);
8
9    let struct_name = &item.ident;
10
11    let syn::Data::Struct(s) = &item.data else {
12        panic!("Only structs can be used as context");
13    };
14
15    let syn::Fields::Named(fields) = &s.fields else {
16        panic!("Fields must be named");
17    };
18
19    let fields: Vec<_> = fields
20        .named
21        .iter()
22        .map(|f| {
23            if !matches!(f.vis, syn::Visibility::Public(_)) {
24                panic!("All fields must be marked pub")
25            }
26
27            let field_name = f.ident.as_ref().unwrap();
28            let field_ty = &f.ty;
29            let offset = quote!(std::mem::offset_of!(Self, #field_name));
30            let type_name = quote!(std::any::type_name::<#field_ty>());
31            let type_id = quote!(std::any::TypeId::of::<#field_ty>());
32            let docstring = gather_docstring(&f.attrs);
33
34            quote!(
35                roto::__internal::ContextField {
36                    name: stringify!(#field_name),
37                    offset: #offset,
38                    type_name: #type_name,
39                    type_id: #type_id,
40                    docstring: #docstring,
41                }
42            )
43        })
44        .collect();
45
46    let expanded = quote!(
47        unsafe impl Context for #struct_name {
48            fn fields() -> Vec<roto::__internal::ContextField> {
49                vec![
50                    #(#fields),*
51                ]
52            }
53        }
54    );
55
56    TokenStream::from(expanded)
57}
58
59struct ItemList {
60    items: Vec<ItemWithDocs>,
61}
62
63struct ItemWithDocs {
64    doc: proc_macro2::TokenStream,
65    item: Item,
66}
67
68enum Item {
69    Type(syn::ItemType),
70    Let(syn::ExprLet),
71    Fn(syn::ItemFn),
72    Mod(syn::Ident, ItemList),
73    Impl(proc_macro2::Span, syn::Type, ItemList),
74    Const(syn::ItemConst),
75    Include(proc_macro2::TokenStream),
76    Use(syn::ItemUse),
77}
78
79mod kw {
80    syn::custom_keyword!(include);
81}
82
83impl Parse for ItemList {
84    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
85        let mut items = Vec::new();
86
87        while !input.is_empty() {
88            // We need to parse the docstring and other attributes first, so
89            // that we can then branch on the next keyword. Afterwards, we can
90            // then assign the attributes to the item we parsed.
91            let attributes = syn::Attribute::parse_outer(input)?;
92            let doc = gather_docstring(&attributes);
93
94            let look = input.lookahead1();
95            let item = match () {
96                // Case 1: A normal function
97                _ if look.peek(Token![fn]) => {
98                    let mut item: syn::ItemFn = input.parse()?;
99                    item.attrs = attributes;
100                    Item::Fn(item)
101                }
102                // Case 2: A module
103                _ if look.peek(Token![mod]) => {
104                    input.parse::<Token![mod]>()?;
105                    let ident: syn::Ident = input.parse()?;
106                    let content;
107                    syn::braced!(content in input);
108                    let item_list: ItemList = content.parse()?;
109                    Item::Mod(ident, item_list)
110                }
111                // Case 3: A let binding with a closure
112                _ if look.peek(Token![let]) => {
113                    let mut item: syn::ExprLet = input.parse()?;
114                    input.parse::<Token![;]>()?;
115                    item.attrs = attributes;
116                    Item::Let(item)
117                }
118                // Case 4: An impl block
119                _ if look.peek(Token![impl]) => {
120                    let tok = input.parse::<Token![impl]>()?;
121                    let ty: syn::Type = input.parse()?;
122
123                    let content;
124                    syn::braced!(content in input);
125                    let item_list: ItemList = content.parse()?;
126                    Item::Impl(tok.span, ty, item_list)
127                }
128                // Case 5: A constant
129                _ if look.peek(Token![const]) => {
130                    let mut item: syn::ItemConst = input.parse()?;
131                    item.attrs = attributes;
132                    Item::Const(item)
133                }
134                // Case 6: A type
135                _ if look.peek(Token![type]) => {
136                    let mut item: syn::ItemType = input.parse()?;
137                    item.attrs = attributes;
138                    Item::Type(item)
139                }
140                _ if look.peek(Token![use]) => {
141                    let item = input.parse()?;
142                    Item::Use(item)
143                }
144                _ if look.peek(kw::include) => {
145                    let m: syn::Macro = input.parse()?;
146                    input.parse::<Token![;]>()?;
147                    Item::Include(m.tokens)
148                }
149                _ => return Err(look.error()),
150            };
151
152            items.push(ItemWithDocs { doc, item });
153        }
154
155        Ok(ItemList { items })
156    }
157}
158
159#[proc_macro]
160pub fn library(input: TokenStream) -> TokenStream {
161    let parsed_items: ItemList = syn::parse_macro_input!(input);
162
163    let expanded = to_tokens(parsed_items, None)
164        .unwrap_or_else(Error::into_compile_error);
165
166    TokenStream::from(expanded)
167}
168
169fn location(s: proc_macro2::Span) -> proc_macro2::TokenStream {
170    let start = s.end();
171    let line = start.line as u32;
172    let column = start.column as u32;
173    quote! {
174        roto::Location {
175            file: file!(),
176            line: #line,
177            column: #column,
178        }
179    }
180}
181
182fn to_tokens(
183    item_list: ItemList,
184    ty: Option<&syn::Type>,
185) -> syn::Result<proc_macro2::TokenStream> {
186    let mut items = Vec::new();
187    for ItemWithDocs { doc, item } in item_list.items {
188        let new = match item {
189            Item::Type(item) => {
190                let ident = &item.ident;
191                let location = location(ident.span());
192                let ident_str = ident.to_string();
193                let ty = &item.ty;
194                let movability = get_movability(item.span(), &item.attrs)?;
195
196                quote! {
197                    roto::Type::#movability::<#ty>(
198                        #ident_str,
199                        #doc,
200                        #location,
201                    ).unwrap()
202                }
203            }
204            Item::Let(mut item) => {
205                let pat = item.pat;
206
207                let syn::Pat::Ident(ident) = &*pat else {
208                    return Err(syn::Error::new(
209                        pat.span(),
210                        "pattern must be an identifier",
211                    ));
212                };
213                let location = location(ident.ident.span());
214                let ident_str = ident.ident.to_string();
215
216                let expr = item.expr;
217                let syn::Expr::Closure(closure) = &*expr else {
218                    return Err(syn::Error::new(
219                        expr.span(),
220                        "expressions must be a closure",
221                    ));
222                };
223
224                let params: Vec<_> = closure
225                    .inputs
226                    .iter()
227                    .map(|p| param_name(p).unwrap())
228                    .collect();
229
230                let roto_sig = get_sig(&item.attrs)?;
231                let vtables = get_vtables(&item.attrs)?;
232
233                // We have to remove the attributes that we define, because the generated
234                // code won't recognize them.
235                item.attrs.retain(|a| {
236                    let path = a.meta.path();
237                    let our_attrs =
238                        path.is_ident("sig") || path.is_ident("vtables");
239                    !our_attrs
240                });
241
242                if let Some(roto_sig) = roto_sig {
243                    quote! {
244                        unsafe { roto::Function::new_generic(
245                            #ident_str,
246                            #doc,
247                            { let x: Vec<&'static str> = vec![#(#params),*]; x },
248                            #expr,
249                            #roto_sig,
250                            vec![#(#vtables),*],
251                            #location,
252                        )}.unwrap()
253                    }
254                } else {
255                    quote! {
256                        roto::Function::new(
257                            #ident_str,
258                            #doc,
259                            { let x: Vec<&'static str> = vec![#(#params),*]; x },
260                            #expr,
261                            #location,
262                        ).unwrap()
263                    }
264                }
265            }
266            Item::Fn(mut item) => {
267                let sig = &item.sig;
268                let ident = &sig.ident;
269                let location = location(ident.span());
270                let ident_str = ident.to_string();
271
272                let params: Vec<_> = item
273                    .sig
274                    .inputs
275                    .iter()
276                    .map(|arg| match arg {
277                        syn::FnArg::Receiver(_) => "self".into(),
278                        syn::FnArg::Typed(pat) => {
279                            param_name(&pat.pat).unwrap()
280                        }
281                    })
282                    .collect();
283
284                let value_checks: proc_macro2::TokenStream = item
285                    .sig
286                    .inputs
287                    .iter()
288                    .map(|arg| {
289                        let ty = match arg {
290                            syn::FnArg::Receiver(receiver) => {
291                                let span = receiver.span();
292                                let ident = syn::Ident::new("Self", span);
293                                quote!(#ident)
294                            }
295                            syn::FnArg::Typed(pat_type) => {
296                                let ty = &pat_type.ty;
297
298                                // OutPtr is special and does not implement Value.
299                                // We do not need to be super precise in skipping it, because
300                                // these checks are only here for better diagnostics.
301                                if let syn::Type::Path(type_path) = &**ty
302                                    && let Some(segment) = type_path.path.segments.last()
303                                    && segment.ident == "OutPtr" {
304                                    return quote!();
305                                }
306
307                                quote!(#ty)
308                            }
309                        };
310                        let span = ty.span();
311                        quote_spanned!(span=> roto::__internal::implements_value::<#ty>();)
312                    })
313                    .chain(match &item.sig.output {
314                        syn::ReturnType::Default => None,
315                        syn::ReturnType::Type(_, ty) => {
316                            let span = ty.span();
317                            Some(quote_spanned!(span=>
318                                roto::__internal::implements_value::<#ty>();
319                            ))
320                        }
321                    })
322                    .collect();
323
324                let roto_sig = get_sig(&item.attrs)?;
325                let vtables = get_vtables(&item.attrs)?;
326
327                // We have to remove the attributes that we define, because the generated
328                // code won't recognize them.
329                item.attrs.retain(|a| {
330                    let path = a.meta.path();
331                    let our_attrs =
332                        path.is_ident("sig") || path.is_ident("vtables");
333                    !our_attrs
334                });
335
336                let expr = if let Some(ty) = ty {
337                    // This is a trick to allow method syntax:
338                    //  - We define a private extension trait in a const block.
339                    //  - Then we implement that trait, which won't conflict
340                    //    with anything.
341                    //  - Then we export that method as a free function.
342                    //
343                    // We do need to map each pattern to `_` because patterns
344                    // are not allowed to appear in trait definitions.
345                    let new_inputs = sig
346                        .inputs
347                        .iter()
348                        .map(|arg| match arg {
349                            syn::FnArg::Receiver(rec) => {
350                                syn::FnArg::Receiver(syn::Receiver {
351                                    mutability: None,
352                                    ..rec.clone()
353                                })
354                            }
355                            syn::FnArg::Typed(pat_type) => {
356                                syn::FnArg::Typed(syn::PatType {
357                                    pat: Box::new(syn::Pat::Wild(
358                                        syn::PatWild {
359                                            attrs: Vec::new(),
360                                            underscore_token:
361                                                syn::token::Underscore {
362                                                    spans: [pat_type.span()],
363                                                },
364                                        },
365                                    )),
366                                    ..pat_type.clone()
367                                })
368                            }
369                        })
370                        .collect();
371
372                    let new_sig = syn::Signature {
373                        inputs: new_inputs,
374                        ident: syn::Ident::new("__ext__", sig.ident.span()),
375                        ..sig.clone()
376                    };
377
378                    let mut new_item = item.clone();
379                    new_item.sig.ident =
380                        syn::Ident::new("__ext__", sig.ident.span());
381
382                    quote!(
383                        const {
384                            trait Ext {
385                                #new_sig;
386                                fn value_checks();
387                            }
388
389                            impl Ext for #ty {
390                                #new_item
391
392                                fn value_checks() {
393                                    #value_checks
394                                }
395                            }
396
397                            <#ty as Ext>::__ext__
398                        }
399                    )
400                } else {
401                    quote!({ #value_checks #item #ident })
402                };
403
404                let span = ident.span();
405                if let Some(roto_sig) = roto_sig {
406                    quote_spanned! {span=> {
407                        let #ident = #expr;
408                        unsafe { roto::Function::new_generic(
409                            #ident_str,
410                            #doc,
411                            { let x: Vec<&'static str> = vec![#(#params),*]; x },
412                            #ident,
413                            #roto_sig,
414                            vec![#(#vtables),*],
415                            #location,
416                        )}.unwrap()
417                    }}
418                } else {
419                    quote_spanned! {span=> {
420                        let #ident = #expr;
421
422                         roto::Function::new(
423                            #ident_str,
424                            #doc,
425                            { let x: Vec<&'static str> = vec![#(#params),*]; x },
426                            #ident,
427                            #location,
428                         ).unwrap()
429                    }}
430                }
431            }
432            Item::Mod(ident, items) => {
433                let ident_str = ident.to_string();
434                let location = location(ident.span());
435                let items = to_tokens(items, None)?;
436                quote! {{
437                    let mut module = roto::Module::new(
438                        #ident_str,
439                        #doc,
440                        #location,
441                    ).unwrap();
442                    module.add(#items);
443                    module
444                }}
445            }
446            Item::Impl(span, ty, items) => {
447                let items = to_tokens(items, Some(&ty))?;
448                let location = location(span);
449                quote! {{
450                    let mut impl_block = roto::Impl::new::<#ty>(#location);
451                    impl_block.add(#items);
452                    impl_block
453                }}
454            }
455            Item::Const(item) => {
456                let ident = item.ident;
457                let location = location(ident.span());
458                let ident_str = ident.to_string();
459                let ty = item.ty;
460                let expr = item.expr;
461                quote! {
462                    roto::Constant::new::<#ty>(
463                        #ident_str,
464                        #doc,
465                        #expr,
466                        #location,
467                    ).unwrap()
468                }
469            }
470            Item::Include(item) => {
471                quote! { #item }
472            }
473            Item::Use(item) => {
474                let imports = flatten_use_tree(&item.tree);
475                quote! {
476                    roto::Use::new(
477                        vec![#(vec![#(#imports.to_string()),*]),*],
478                        roto::location!(),
479                    )
480                }
481            }
482        };
483
484        items.push(quote! { #new });
485    }
486
487    Ok(quote! {{
488        let mut lib = roto::Library::new();
489        #(roto::Registerable::add_to_lib(#items, &mut lib);)*
490        lib
491    }})
492}
493
494fn get_sig(attrs: &[syn::Attribute]) -> syn::Result<Option<String>> {
495    let mut sig = None;
496    for attr in attrs {
497        if attr.meta.path().is_ident("sig") {
498            let syn::Meta::NameValue(syn::MetaNameValue {
499                value:
500                    syn::Expr::Lit(syn::ExprLit {
501                        lit: syn::Lit::Str(lit_str),
502                        ..
503                    }),
504                ..
505            }) = &attr.meta
506            else {
507                return Err(syn::Error::new(
508                    attr.meta.span(),
509                    "sig attribute must contain an equals sign followed by a string literal",
510                ));
511            };
512            let value = lit_str.value();
513            if sig.is_some() {
514                return Err(syn::Error::new(
515                    attr.span(),
516                    "duplicate sig attribute found, only 1 sig attribute is allowed",
517                ));
518            }
519            sig = Some(value);
520        }
521    }
522
523    Ok(sig)
524}
525
526fn get_vtables(attrs: &[syn::Attribute]) -> syn::Result<Vec<String>> {
527    let mut vtables = Vec::new();
528    for attr in attrs {
529        if attr.meta.path().is_ident("vtables") {
530            attr.parse_nested_meta(|meta| {
531                let Some(ident) = meta.path.get_ident() else {
532                    return Err(meta.error(
533                        "argument of vtables attribute must be an identifier",
534                    ));
535                };
536                if !meta.input.is_empty() {
537                    return Err(meta.error(
538                        "argument of vtables attribute must be an identifier",
539                    ));
540                }
541                vtables.push(ident.to_string());
542                Ok(())
543            })?;
544        }
545    }
546
547    Ok(vtables)
548}
549
550fn get_movability(
551    span: proc_macro2::Span,
552    attrs: &[syn::Attribute],
553) -> syn::Result<syn::Ident> {
554    let mut clone = 0;
555    let mut copy = 0;
556    let mut value = 0;
557    let mut ident_span = None;
558
559    for attr in attrs {
560        if let syn::Meta::Path(p) = &attr.meta {
561            if p.is_ident("clone") {
562                clone += 1;
563                ident_span = Some(p.span());
564            } else if p.is_ident("copy") {
565                copy += 1;
566                ident_span = Some(p.span());
567            } else if p.is_ident("value") {
568                value += 1;
569                ident_span = Some(p.span());
570            }
571        }
572    }
573
574    let s = match (clone, copy, value) {
575        (1, 0, 0) => "clone",
576        (0, 1, 0) => "copy",
577        (0, 0, 1) => "value",
578        _ => {
579            return Err(syn::Error::new(
580                span,
581                "specify exactly 1 of `#[clone]`, `#[copy]` or `#[value]`",
582            ));
583        }
584    };
585
586    Ok(syn::Ident::new(s, ident_span.unwrap()))
587}
588
589fn flatten_use_tree(tree: &syn::UseTree) -> Vec<Vec<String>> {
590    match tree {
591        syn::UseTree::Path(p) => {
592            let recursive = flatten_use_tree(&p.tree);
593            recursive
594                .into_iter()
595                .map(|v| {
596                    let mut new_v = vec![p.ident.to_string()];
597                    new_v.extend(v);
598                    new_v
599                })
600                .collect()
601        }
602        syn::UseTree::Name(name) => {
603            vec![vec![name.ident.to_string()]]
604        }
605        syn::UseTree::Group(g) => {
606            g.items.iter().flat_map(flatten_use_tree).collect()
607        }
608        syn::UseTree::Rename(_) => panic!(),
609        syn::UseTree::Glob(_) => panic!(),
610    }
611}
612
613fn param_name(pat: &syn::Pat) -> Option<String> {
614    match pat {
615        syn::Pat::Ident(ident) => {
616            let s = ident.ident.to_string();
617            Some(match s.strip_suffix('_') {
618                Some(s) => s.to_string(),
619                None => s,
620            })
621        }
622        syn::Pat::Paren(paren) => param_name(&paren.pat),
623        syn::Pat::Reference(reference) => param_name(&reference.pat),
624        syn::Pat::TupleStruct(pat) => {
625            let elems: Vec<_> = pat.elems.iter().collect();
626            let [elem] = &*elems else { return None };
627            param_name(elem)
628        }
629        syn::Pat::Type(p) => param_name(&p.pat),
630        syn::Pat::Wild(_) => Some("_".to_string()),
631
632        // Rust will ensure that any name bound in any or the or pattern cases
633        // will also appear in the other cases and error out otherwise.
634        // Therefore, we can just look at the first case to extract the name.
635        syn::Pat::Or(p) => param_name(p.cases.first()?),
636
637        // ---
638        syn::Pat::Verbatim(_) => None,
639        syn::Pat::Tuple(_) => None,
640        syn::Pat::Struct(_) => None,
641        syn::Pat::Slice(_) => None,
642        syn::Pat::Rest(_) => None,
643        syn::Pat::Range(_) => None,
644        syn::Pat::Path(_) => None,
645        syn::Pat::Const(_) => None,
646        syn::Pat::Lit(_) => None,
647        syn::Pat::Macro(_) => None,
648        _ => None,
649    }
650}
651
652struct Intermediate {
653    function: proc_macro2::TokenStream,
654    ident: syn::Ident,
655    docstring: proc_macro2::TokenStream,
656    parameter_names: proc_macro2::TokenStream,
657}
658
659struct FunctionArgs {
660    runtime_ident: syn::Ident,
661    name: Option<syn::Ident>,
662}
663
664impl syn::parse::Parse for FunctionArgs {
665    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
666        let runtime_ident = input.parse()?;
667
668        let mut name = None;
669        if input.peek(Token![,]) {
670            input.parse::<Token![,]>()?;
671            if input.peek(syn::Ident) {
672                name = input.parse()?;
673            }
674        }
675
676        Ok(Self {
677            runtime_ident,
678            name,
679        })
680    }
681}
682
683#[proc_macro_attribute]
684pub fn roto_function(attr: TokenStream, item: TokenStream) -> TokenStream {
685    let item = parse_macro_input!(item as syn::ItemFn);
686    let Intermediate {
687        function,
688        ident,
689        docstring,
690        parameter_names,
691    } = generate_function(item);
692
693    let FunctionArgs {
694        runtime_ident,
695        name,
696    } = syn::parse(attr).unwrap();
697
698    let name = name.unwrap_or(ident.clone());
699
700    let expanded = quote! {
701        #function
702
703        #runtime_ident.register_fn(
704            stringify!(#name),
705            #docstring,
706            #parameter_names,
707            #ident,
708        ).unwrap();
709    };
710
711    TokenStream::from(expanded)
712}
713
714struct MethodArgs {
715    runtime_ident: syn::Ident,
716    ty: syn::Type,
717    name: Option<syn::Ident>,
718}
719
720impl syn::parse::Parse for MethodArgs {
721    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
722        let runtime_ident = input.parse()?;
723        input.parse::<Token![,]>()?;
724        let ty = input.parse()?;
725
726        let mut name = None;
727        if input.peek(Token![,]) {
728            input.parse::<Token![,]>()?;
729            if input.peek(syn::Ident) {
730                name = input.parse()?;
731            }
732        }
733        Ok(Self {
734            runtime_ident,
735            ty,
736            name,
737        })
738    }
739}
740
741#[proc_macro_attribute]
742pub fn roto_method(attr: TokenStream, item: TokenStream) -> TokenStream {
743    let item = parse_macro_input!(item as syn::ItemFn);
744    let Intermediate {
745        function,
746        ident,
747        docstring,
748        parameter_names,
749    } = generate_function(item);
750
751    let MethodArgs {
752        runtime_ident,
753        ty,
754        name,
755    } = parse_macro_input!(attr as MethodArgs);
756
757    let name = name.unwrap_or(ident.clone());
758
759    let expanded = quote! {
760        #function
761
762        #runtime_ident.register_method::<#ty, _, _>(
763            stringify!(#name),
764            #docstring,
765            #parameter_names,
766            #ident
767        ).unwrap();
768    };
769
770    TokenStream::from(expanded)
771}
772
773#[proc_macro_attribute]
774pub fn roto_static_method(
775    attr: TokenStream,
776    item: TokenStream,
777) -> TokenStream {
778    let item = parse_macro_input!(item as syn::ItemFn);
779    let Intermediate {
780        function,
781        ident,
782        docstring,
783        parameter_names,
784    } = generate_function(item);
785
786    let MethodArgs {
787        runtime_ident,
788        ty,
789        name,
790    } = parse_macro_input!(attr as MethodArgs);
791
792    let name = name.unwrap_or(ident.clone());
793
794    let expanded = quote! {
795        #function
796
797        #runtime_ident.register_static_method::<#ty, _, _>(
798            stringify!(#name),
799            #docstring.to_string(),
800            #parameter_names,
801            #ident
802        ).unwrap();
803    };
804
805    TokenStream::from(expanded)
806}
807
808fn gather_docstring(attrs: &[syn::Attribute]) -> proc_macro2::TokenStream {
809    let mut docstring = Vec::new();
810
811    for attr in attrs {
812        if attr.path().is_ident("doc") {
813            let value = match &attr.meta {
814                syn::Meta::NameValue(name_value) => &name_value.value,
815                _ => panic!("doc attribute must be a name and a value"),
816            };
817            docstring.push(value);
818        }
819    }
820
821    quote! {{
822        let x: Vec<String> = vec![#({
823            let s: String = #docstring.to_string();
824            s.strip_prefix(" ").unwrap_or(&s).to_string()
825        }),*];
826        x.join("\n")
827    }}
828}
829
830fn generate_function(item: syn::ItemFn) -> Intermediate {
831    let syn::ItemFn {
832        attrs,
833        vis: _,
834        sig,
835        block: _,
836    } = item.clone();
837
838    let docstring = gather_docstring(&attrs);
839
840    assert!(sig.unsafety.is_none());
841    assert!(sig.variadic.is_none());
842
843    let ident = sig.ident;
844    let args: Vec<_> = sig
845        .inputs
846        .iter()
847        .map(|i| {
848            let syn::FnArg::Typed(syn::PatType { pat, .. }) = i else {
849                panic!()
850            };
851            pat
852        })
853        .collect();
854
855    let parameter_names = quote!( [#(stringify!(#args)),*] );
856
857    Intermediate {
858        function: quote!(#item),
859        ident,
860        docstring,
861        parameter_names,
862    }
863}