Skip to main content

cindy_macros/
lib.rs

1use proc_macro::TokenStream;
2use quote::{format_ident, quote};
3use syn::{FnArg, Item, ItemFn, Pat, PatIdent, parse_macro_input};
4
5/// Given an argument type, if it's `impl Into<T>` return `Some(T)`; otherwise
6/// `None` (the type is used as-is for the concrete raw fn).
7fn into_target(ty: &syn::Type) -> Option<syn::Type> {
8    let syn::Type::ImplTrait(it) = ty else {
9        return None;
10    };
11    for bound in &it.bounds {
12        let syn::TypeParamBound::Trait(tb) = bound else {
13            continue;
14        };
15        let seg = tb.path.segments.last()?;
16        if seg.ident != "Into" {
17            continue;
18        }
19        if let syn::PathArguments::AngleBracketed(args) = &seg.arguments
20            && let Some(syn::GenericArgument::Type(inner)) = args.args.first()
21        {
22            return Some(inner.clone());
23        }
24    }
25    None
26}
27
28/// Shorthand for deriving `Debug` and `serde::{Deserialize, Serialize}`
29/// without an explicit `serde` dependency.
30///
31/// `Debug` is included because inventory `vars` types need it (so
32/// `cindy inventory` can render them). Don't also list `Debug` in a
33/// manual `#[derive(...)]` on the same item — that's a duplicate impl.
34#[proc_macro_attribute]
35pub fn wire(_args: TokenStream, input: TokenStream) -> TokenStream {
36    let item = parse_macro_input!(input as Item);
37
38    quote! {
39        #[derive(
40            ::std::fmt::Debug,
41            cindy::__reexports::serde::Serialize,
42            cindy::__reexports::serde::Deserialize
43        )]
44        #[serde(crate = "cindy::__reexports::serde")]
45        #item
46    }
47    .into()
48}
49
50/// Specifies an entrypoint.
51///
52/// - On `feature = "orchestrator"`
53///   - Compiles to normal program.
54/// - On `feature = "remote"`
55///   - Runs a RPC loop instead.
56///
57/// The user's `main` may be declared in one of two shapes:
58///
59/// 1. `async fn main() -> Result<()>` - legacy, no host context.
60/// 2. `async fn main(host: cindy::Host<V>) -> Result<()>` - the macro
61///    reads the `CINDY_HOST_CONTEXT` env var (JSON serialized
62///    `cindy::Host<V>` produced by the CLI for this particular target)
63///    and hands it pre-parsed to the body. Any `Serialize +
64///    DeserializeOwned` `V` works; use `cindy::Host<()>` if you only
65///    need `name`/`tags`.
66#[proc_macro_attribute]
67pub fn main(_args: TokenStream, input: TokenStream) -> TokenStream {
68    let input = parse_macro_input!(input as ItemFn);
69
70    let (attrs, vis, sig, block) = (&input.attrs, &input.vis, &input.sig, &input.block);
71    let output = &sig.output;
72    let inputs = &sig.inputs;
73
74    let invoke_user_main = match inputs.len() {
75        0 => quote! {
76            cindy::__reexports::tokio::spawn(__user_main())
77        },
78        1 => {
79            let host_type = match &inputs[0] {
80                FnArg::Typed(pt) => &pt.ty,
81                FnArg::Receiver(_) => {
82                    panic!("`#[cindy::main]` cannot take `self`");
83                }
84            };
85            quote! {
86                {
87                    let __host_json = ::std::env::var("CINDY_HOST_CONTEXT").expect(
88                        "CINDY_HOST_CONTEXT not set!\n\
89                         The orchestrator process is meant to be launched by `cindy` command line tool. \
90                         If you're trying to run the binary directly, set CINDY_HOST_CONTEXT to a \
91                         JSON-serialised `cindy::Host<V>` first."
92                    );
93                    let __host: #host_type =
94                        cindy::__reexports::serde_json::from_str(&__host_json)
95                            .expect("CINDY_HOST_CONTEXT was not valid JSON for the declared `cindy::Host<V>` type");
96                    cindy::__reexports::tokio::spawn(__user_main(__host))
97                }
98            }
99        }
100        _ => panic!(
101            "`#[cindy::main]` accepts at most one parameter (the host context, `cindy::Host<V>`)"
102        ),
103    };
104
105    quote! {
106        #(#attrs)*
107        #[cindy::__reexports::tokio::main(crate = "cindy::__reexports::tokio")]
108        #vis async fn main() {
109            let (rpc_in, rpc_out) = cindy::common::quarantine_stdio();
110
111            #[cfg(feature = "orchestrator")]
112            if ::std::env::var_os("CINDY_DUMP_INVENTORY").is_some() {
113                let entries: ::std::vec::Vec<&cindy::inventory::RegisteredInventory> =
114                    cindy::__reexports::inventory::iter::<cindy::inventory::RegisteredInventory>
115                        .into_iter()
116                        .collect();
117                let dump: cindy::inventory::InventoryDump = match entries.as_slice() {
118                    [one] => (one.dump)().await,
119                    _ => {
120                        ::std::eprintln!(
121                            "There must be exactly 1 `#[cindy::inventory]` registered."
122                        );
123                        ::std::process::exit(2);
124                    }
125                };
126                let bytes = cindy::__reexports::serde_json::to_vec(&dump)
127                    .expect("Failed to serialize inventory dump to JSON");
128                {
129                    use cindy::__reexports::tokio::io::AsyncWriteExt as _;
130                    let mut out = rpc_out;
131                    out.write_all(&bytes).await.expect("Failed to write inventory");
132                    out.flush().await.expect("Failed to flush inventory");
133                }
134                ::std::process::exit(0);
135            }
136
137            // Vault enumeration: print the set of vaults referenced by
138            // `secret!` invocations compiled into this binary (the
139            // "secrets in code" half of the preflight) as a JSON array,
140            // then exit. The CLI runs this once during preflight to
141            // know which DEKs every participating machine must hold.
142            #[cfg(feature = "orchestrator")]
143            if ::std::env::var_os("CINDY_DUMP_VAULTS").is_some() {
144                use cindy::__reexports::tokio::io::AsyncWriteExt as _;
145                let vaults = cindy::secret::registered_vaults();
146                let bytes = cindy::__reexports::serde_json::to_vec(&vaults)
147                    .expect("Failed to serialise vault list");
148                let mut out = rpc_out;
149                out.write_all(&bytes).await.expect("Failed to write vault list");
150                out.flush().await.expect("Failed to flush vault list");
151                ::std::process::exit(0);
152            }
153
154            #[cfg(feature = "orchestrator")]
155            if ::std::env::var_os("CINDY_SEAL_SECRETS").is_some() {
156                use cindy::__reexports::tokio::io::AsyncWriteExt as _;
157                let mut out = rpc_out;
158                let mut failed = false;
159                for pending in cindy::__reexports::inventory::iter::<cindy::secret::PendingSecret>() {
160                    let plaintext = (pending.serialize)();
161                    // Refuse to bootstrap a vault on the fly. If the
162                    // user didn't `cindy secret vault create <name>`
163                    // before running seal, that's almost certainly a
164                    // mistake — they probably meant to copy an
165                    // existing team-shared key file into place. Bail
166                    // with the (already actionable) error from the
167                    // keychain layer instead of generating a fresh
168                    // key whose ciphertext nobody else can decrypt.
169                    let dek = match cindy::secret::keychain::get_dek(pending.vault) {
170                        Ok(d) => d,
171                        Err(e) => {
172                            ::std::eprintln!(
173                                "cindy secret seal: couldn't load DEK for vault `{}` \
174                                 (referenced from {}:{}:{}): {e:#}",
175                                pending.vault, pending.file, pending.line, pending.column,
176                            );
177                            failed = true;
178                            continue;
179                        }
180                    };
181                    let ciphertext = match cindy::secret::crypto::seal(&dek, &plaintext) {
182                        Ok(c) => c,
183                        Err(e) => {
184                            ::std::eprintln!(
185                                "cindy secret seal: encryption failed for {}:{}:{} ({e:#})",
186                                pending.file, pending.line, pending.column,
187                            );
188                            failed = true;
189                            continue;
190                        }
191                    };
192                    use cindy::__reexports::base64::Engine as _;
193                    let b64 = cindy::__reexports::base64::engine::general_purpose::STANDARD
194                        .encode(&ciphertext);
195                    let line = cindy::__reexports::serde_json::json!({
196                        "file":       pending.file,
197                        "line":       pending.line,
198                        "column":     pending.column,
199                        "vault":      pending.vault,
200                        "ciphertext": b64,
201                    });
202                    let mut bytes = cindy::__reexports::serde_json::to_vec(&line)
203                        .expect("Failed to serialise seal record");
204                    bytes.push(b'\n');
205                    out.write_all(&bytes).await.expect("Failed to write seal record");
206                }
207                out.flush().await.expect("Failed to flush seal records");
208                ::std::process::exit(if failed { 2 } else { 0 });
209            }
210
211            // Remote-only builds skip the user's body entirely; the worker
212            // process just runs the RPC dispatch loop. Vault DEKs arrive
213            // over the channel's first frame (the handshake) and the
214            // worker-side vault preflight runs there, inside
215            // `cindy::remote::rpc` — keys never touch this process's
216            // argv/env, so they're invisible in the target's `ps`.
217            #[cfg(all(feature = "remote", not(feature = "orchestrator")))]
218            {
219                cindy::remote::rpc(rpc_in, rpc_out).await;
220                ::std::process::exit(0);
221            }
222
223            // Orchestrator builds (and dual-feature LSP builds) set up the
224            // dispatch channel and then execute the user's main body. Using a
225            // *positive* cfg here — rather than `not(remote)` — keeps the
226            // body in scope when both features are enabled at once, which is
227            // how rust-analyzer typically evaluates this workspace.
228            #[cfg(feature = "orchestrator")]
229            {
230                async fn __user_main(#inputs) #output #block
231
232                // The CLI marshals this run's vault DEKs into
233                // `CINDY_VAULT_KEYS` on *this* (local) process — env is
234                // fine here since it's the operator's own machine, not a
235                // target. We install them locally for orchestrator-side
236                // reveals, and forward the same map to the worker over
237                // the RPC handshake (so they never hit the target's env).
238                let __cindy_vault_keys = match cindy::secret::keychain::decode_env_keys() {
239                    ::std::result::Result::Ok(m) => m.unwrap_or_default(),
240                    ::std::result::Result::Err(e) => {
241                        ::std::eprintln!("\x1b[31m{:?}\x1b[0m", e);
242                        ::std::process::exit(1);
243                    }
244                };
245                if let ::std::result::Result::Err(e) =
246                    cindy::secret::keychain::install_raw_keys(__cindy_vault_keys.clone())
247                {
248                    ::std::eprintln!("\x1b[31m{:?}\x1b[0m", e);
249                    ::std::process::exit(1);
250                }
251
252                // Vault preflight: before any RPC or play work, confirm
253                // this orchestrator can load every vault key it might
254                // need — the union of in-code `secret!` vaults and the
255                // sealed-secret vaults present in this host's context.
256                // Fail loudly and early instead of partway through.
257                if let ::std::result::Result::Err(e) = cindy::secret::preflight(
258                    "the orchestrator",
259                    ::std::env::var("CINDY_HOST_CONTEXT").ok().as_deref(),
260                ) {
261                    ::std::eprintln!("\x1b[31m{:?}\x1b[0m", e);
262                    ::std::process::exit(1);
263                }
264
265                let (tx, rx) = cindy::__reexports::tokio::sync::mpsc::unbounded_channel();
266                cindy::orchestrator::ORCHESTRATOR_TX
267                    .set(tx)
268                    .expect("ORCHESTRATOR_TX already set");
269                cindy::__reexports::tokio::spawn(cindy::orchestrator::rpc(
270                    rx, rpc_in, rpc_out, __cindy_vault_keys,
271                ));
272                match #invoke_user_main.await {
273                    Ok(Ok(_)) => {
274                        ::std::process::exit(0);
275                    }
276                    Ok(Err(e)) => {
277                        ::std::eprintln!("\x1b[31m{:?}\x1b[0m", e);
278                        ::std::process::exit(1);
279                    }
280                    Err(_) => {
281                        ::std::process::exit(1);
282                    }
283                };
284            }
285        }
286    }
287    .into()
288}
289
290/// Registers an inventory function.
291///
292/// Exactly one `#[cindy::inventory]` should exist per binary. The function
293/// may be named in any way, can be sync or async, and may return either
294/// `cindy::Inventory<V>` or `cindy::Result<cindy::Inventory<V>>`.
295///
296/// ```rust,ignore
297/// #[cindy::wire]
298/// struct MyVars {
299///     var_a: u64,
300///     var_b: String,
301/// }
302///
303/// #[cindy::inventory]
304/// async fn inventory() -> cindy::Result<cindy::Inventory<MyVars>> {
305///     Ok(cindy::Inventory {
306///         hosts: vec![
307///             cindy::Host {
308///                 name: "host-01".into(),
309///                 tags: cindy::tags!["env:production", "continent:eu", "router"],
310///                 vars: MyVars { var_a: 42, var_b: "hello".to_string() },
311///             },
312///         ],
313///     })
314/// }
315/// ```
316#[proc_macro_attribute]
317pub fn inventory(_args: TokenStream, input: TokenStream) -> TokenStream {
318    let input = parse_macro_input!(input as ItemFn);
319
320    let (attrs, vis, sig, block) = (&input.attrs, &input.vis, &input.sig, &input.block);
321    let function_ident = &sig.ident;
322    let asyncness = &sig.asyncness;
323    let output = &sig.output;
324    let inputs = &sig.inputs;
325
326    if !inputs.is_empty() {
327        panic!("`#[cindy::inventory]` functions must take no arguments");
328    }
329
330    let invocation = if asyncness.is_some() {
331        quote! { #function_ident().await }
332    } else {
333        quote! {
334            match cindy::__reexports::tokio::task::spawn_blocking(move || #function_ident())
335                .await
336            {
337                Ok(v) => v,
338                Err(je) => ::std::panic::resume_unwind(je.into_panic()),
339            }
340        }
341    };
342
343    quote! {
344        #(#attrs)*
345        #vis #asyncness fn #function_ident () #output #block
346
347        cindy::__reexports::inventory::submit! {
348            cindy::inventory::RegisteredInventory {
349                dump: || ::std::boxed::Box::pin(async move {
350                    cindy::inventory::IntoInventoryDump::into_inventory_dump(#invocation)
351                }),
352            }
353        }
354    }
355    .into()
356}
357
358/// Marks a function as "running on the remote host".
359///
360/// Each `#[cindy::remote] fn foo(args) -> T` expands to a small bundle of
361/// items at the same path:
362///
363/// 1. **`fn foo(args) -> cindy::orchestrator::Future<T>`** — the RPC shim
364///    used by `#[cindy::main]` and other orchestrator-side code. Returns a
365///    future that resolves once the worker has replied. (Lives in the
366///    value namespace.)
367/// 2. **`enum foo {} + impl foo { fn inner(args) -> T { ..user body.. } }`** -
368///    the actual body, exposed at `foo::inner(args)`. Used inside other
369///    `#[remote]` bodies on the worker to call siblings directly without
370///    going through the RPC channel. The enum is uninhabited; it exists
371///    purely so the path syntax `foo::inner` works in the type namespace,
372///    side-by-side with `fn foo` in the value namespace.
373#[proc_macro_attribute]
374pub fn remote(_args: TokenStream, input: TokenStream) -> TokenStream {
375    let input = parse_macro_input!(input as ItemFn);
376
377    let (attrs, vis, sig, block) = (&input.attrs, &input.vis, &input.sig, &input.block);
378
379    if !sig.generics.params.is_empty() {
380        panic!("Generics not allowed. Remote functions cannot be generic.");
381    }
382
383    let function_ident = &sig.ident;
384    let asyncness = &sig.asyncness;
385    let inputs = &sig.inputs;
386    let return_type = match &sig.output {
387        syn::ReturnType::Default => quote! { () },
388        syn::ReturnType::Type(_, ty) => quote! { #ty },
389    };
390
391    let mut arg_idents = vec![];
392    let mut arg_types = vec![];
393    for input_arg in &sig.inputs {
394        match input_arg {
395            FnArg::Receiver(..) => panic!("Argument `self` not allowed"),
396            FnArg::Typed(pat_type) => match &*pat_type.pat {
397                Pat::Ident(PatIdent { ident, .. }) => {
398                    arg_idents.push(ident);
399                    arg_types.push(&pat_type.ty)
400                }
401                other => panic!("Only standard named arguments are supported. Found: {other:?}"),
402            },
403        }
404    }
405
406    let type_signature = arg_types
407        .iter()
408        .map(|ty| quote! { #ty }.to_string().replace(' ', ""))
409        .collect::<Vec<String>>()
410        .join(",");
411    let manifest_dir = std::env::var("CARGO_MANIFEST_DIR").unwrap_or_default();
412    let absolute_span_path = std::path::PathBuf::from(proc_macro::Span::call_site().file());
413    let relative_path = absolute_span_path
414        .strip_prefix(&manifest_dir)
415        .unwrap_or(&absolute_span_path)
416        .to_string_lossy()
417        .to_string();
418    let crate_name = std::env::var("CARGO_PKG_NAME").unwrap_or_default();
419
420    let remote_fn_id = format!(
421        "::{}::{}::{}({})",
422        crate_name, relative_path, function_ident, type_signature
423    );
424
425    let invocation = if asyncness.is_some() {
426        quote! { #function_ident::inner( #(#arg_idents),* ).await }
427    } else {
428        quote! {
429            match cindy::__reexports::tokio::task::spawn_blocking(move || {
430                #function_ident::inner( #(#arg_idents),* )
431            })
432            .await
433            {
434                Ok(v) => v,
435                Err(je) => ::std::panic::resume_unwind(je.into_panic()),
436            }
437        }
438    };
439
440    let outer_docstring = format!(
441        "This function can be called from the orchestrator.
442
443To run the remote-side version of this function (e.g. to call it from another remote function),
444see [`{function_ident}::inner`]."
445    );
446    let inner_docstring = format!(
447        "This function can be called from another remote functions.
448
449For documentation about the actual function, please refer to [`{function_ident}`]."
450    );
451    quote! {
452        #[allow(non_camel_case_types)]
453        #[doc(hidden)]
454        #vis enum #function_ident {}
455
456        #[doc(hidden)]
457        impl #function_ident {
458            #[doc = #inner_docstring]
459            #(#attrs)*
460            pub #asyncness fn inner (#inputs) -> #return_type #block
461        }
462
463        //
464        //
465        #[cfg(feature = "orchestrator")]
466        #[doc = #outer_docstring]
467        #(#attrs)*
468        #vis fn #function_ident (#inputs) -> cindy::orchestrator::Future<#return_type> {
469            let uuid = cindy::__reexports::uuid::Uuid::new_v4();
470            let payload = cindy::common::RemoteFnPayload {
471                uuid,
472                fn_id: #remote_fn_id.to_string(),
473                data: cindy::__reexports::postcard::to_allocvec(&( #(#arg_idents),* ))
474                    .expect("Failed to serialize args"),
475            };
476            let (tx, rx) = cindy::__reexports::tokio::sync::oneshot::channel();
477            cindy::orchestrator::ORCHESTRATOR_TX
478                .get()
479                .expect("ORCHESTRATOR_TX not set")
480                .send(cindy::orchestrator::OutboundRegistration { payload, tx })
481                .expect("Orchestrator channel closed");
482            cindy::orchestrator::Future::new(rx)
483        }
484
485        #[cfg(feature = "remote")]
486        cindy::__reexports::inventory::submit! {
487            cindy::remote::RemoteFn {
488                id: #remote_fn_id,
489                function: |args_bytes| {
490                    let ( #(#arg_idents),* ): ( #(#arg_types),* ) =
491                        cindy::__reexports::postcard::from_bytes(&args_bytes)
492                        .expect("Failed to deserialize args");
493
494                    ::std::boxed::Box::pin(async move {
495                        let result = #invocation;
496                        cindy::__reexports::postcard::to_allocvec(&result)
497                            .expect("Failed to serialize return value")
498                    })
499                },
500            }
501        }
502    }
503    .into()
504}
505
506/// Defines a builtin "action": one author-written function with ergonomic
507/// `impl Into<T>` arguments and a real body, from which the macro generates
508/// both the wire-level `#[remote]` entry point *and* the ergonomic layer.
509///
510/// `#[remote]` requires concrete, postcard-serializable argument types (no
511/// generics), but call sites want `impl Into<T>` so they can pass `&str`,
512/// literals, etc. Those can't be one function, so historically each action
513/// was two hand-written items: a concrete `*_raw` `#[remote]` fn plus a
514/// body-less convenience shim. `#[action]` collapses that to one: you write
515/// the `impl Into` fn with its body once, and the macro emits:
516///
517/// 1. **`<name>_raw(<concrete args>)`** — a `#[remote]` fn whose body is
518///    *your* body (the `impl Into<T>` params rewritten to their concrete `T`).
519///    This is the real, registered remote entry point.
520/// 2. **`<name>(<impl Into args>)`** (orchestrator) — converts each arg with
521///    `.into()` and `.await`s the `<name>_raw` RPC shim (runs on the worker).
522/// 3. **`<name>::inner(<impl Into args>)`** (both features) — converts and
523///    calls `<name>_raw::inner(..)` directly, in-process (no RPC).
524///
525/// Asyncness is taken from the annotated fn — async fns produce an async
526/// `_raw`/`::inner` (the body is `.await`-capable); no marker needed.
527///
528/// ```ignore
529/// #[crate::action]
530/// pub async fn restart(name: impl Into<String>) -> crate::Result<super::Return> {
531///     apply(State { name, runtime: Some(RuntimeAction::Restarted), ..Default::default() }).await
532/// }
533/// ```
534#[proc_macro_attribute]
535pub fn action(_args: TokenStream, input: TokenStream) -> TokenStream {
536    let func = parse_macro_input!(input as ItemFn);
537    let (attrs, vis, sig, block) = (&func.attrs, &func.vis, &func.sig, &func.block);
538
539    if !sig.generics.params.is_empty() {
540        panic!("`#[action]` functions cannot have generic parameters (use `impl Into<T>` args)");
541    }
542
543    let ident = &sig.ident;
544    let raw_ident = format_ident!("{ident}_raw");
545    let is_async = sig.asyncness.is_some();
546    let (maybe_async, maybe_await) = if is_async {
547        (quote! { async }, quote! { .await })
548    } else {
549        (quote! {}, quote! {})
550    };
551    let return_type = match &sig.output {
552        syn::ReturnType::Default => quote! { () },
553        syn::ReturnType::Type(_, ty) => quote! { #ty },
554    };
555
556    // Build, in lockstep:
557    //  - `ergonomic_inputs`: the original `impl Into<T>` params (the shim sig),
558    //  - `raw_inputs`: the same params with each `impl Into<T>` lowered to `T`
559    //    (the concrete `#[remote]` sig),
560    //  - `arg_idents`: param names, forwarded as `ident.into()` from the shims.
561    let mut ergonomic_inputs = vec![];
562    let mut raw_inputs = vec![];
563    let mut arg_idents = vec![];
564    for input_arg in &sig.inputs {
565        let FnArg::Typed(pat_type) = input_arg else {
566            panic!("`#[action]` functions cannot take `self`");
567        };
568        let Pat::Ident(PatIdent { ident, .. }) = &*pat_type.pat else {
569            panic!("only standard named arguments are supported in `#[action]` fns");
570        };
571        arg_idents.push(ident.clone());
572        ergonomic_inputs.push(input_arg.clone());
573
574        let concrete_ty = into_target(&pat_type.ty).unwrap_or_else(|| (*pat_type.ty).clone());
575        raw_inputs.push(quote! { #ident: #concrete_ty });
576    }
577
578    quote! {
579        // (1) The real, wire-level entry point: a `#[remote]` fn with concrete
580        // args, carrying the author's body verbatim.
581        #[doc(hidden)]
582        #[cindy::remote]
583        #vis #maybe_async fn #raw_ident (#(#raw_inputs),*) -> #return_type #block
584
585        // (2) Orchestrator shim: convert args, drive the remote worker.
586        #[cfg(feature = "orchestrator")]
587        #(#attrs)*
588        #vis async fn #ident (#(#ergonomic_inputs),*) -> #return_type {
589            #raw_ident ( #(#arg_idents.into()),* ).await
590        }
591
592        // (3) Local/in-process variant (`#ident::inner`), under BOTH features:
593        // worker builtins call siblings with it, and orchestrator code uses it
594        // to act on the *local* machine without RPC. Lives in the type
595        // namespace so it coexists with the `fn #ident` shim.
596        #[allow(non_camel_case_types)]
597        #vis enum #ident {}
598
599        impl #ident {
600            #(#attrs)*
601            #vis #maybe_async fn inner (#(#ergonomic_inputs),*) -> #return_type {
602                #raw_ident ::inner( #(#arg_idents.into()),* ) #maybe_await
603            }
604        }
605    }
606    .into()
607}