tokio_macros/
entry.rs

1use proc_macro2::{Span, TokenStream, TokenTree};
2use quote::{quote, quote_spanned, ToTokens};
3use syn::parse::{Parse, ParseStream, Parser};
4use syn::{braced, Attribute, Ident, Path, Signature, Visibility};
5
6// syn::AttributeArgs does not implement syn::Parse
7type AttributeArgs = syn::punctuated::Punctuated<syn::Meta, syn::Token![,]>;
8
9#[derive(Clone, Copy, PartialEq)]
10enum RuntimeFlavor {
11    CurrentThread,
12    Threaded,
13}
14
15impl RuntimeFlavor {
16    fn from_str(s: &str) -> Result<RuntimeFlavor, String> {
17        match s {
18            "current_thread" => Ok(RuntimeFlavor::CurrentThread),
19            "multi_thread" => Ok(RuntimeFlavor::Threaded),
20            "single_thread" => Err("The single threaded runtime flavor is called `current_thread`.".to_string()),
21            "basic_scheduler" => Err("The `basic_scheduler` runtime flavor has been renamed to `current_thread`.".to_string()),
22            "threaded_scheduler" => Err("The `threaded_scheduler` runtime flavor has been renamed to `multi_thread`.".to_string()),
23            _ => Err(format!("No such runtime flavor `{}`. The runtime flavors are `current_thread` and `multi_thread`.", s)),
24        }
25    }
26}
27
28#[derive(Clone, Copy, PartialEq)]
29enum UnhandledPanic {
30    Ignore,
31    ShutdownRuntime,
32}
33
34impl UnhandledPanic {
35    fn from_str(s: &str) -> Result<UnhandledPanic, String> {
36        match s {
37            "ignore" => Ok(UnhandledPanic::Ignore),
38            "shutdown_runtime" => Ok(UnhandledPanic::ShutdownRuntime),
39            _ => Err(format!("No such unhandled panic behavior `{}`. The unhandled panic behaviors are `ignore` and `shutdown_runtime`.", s)),
40        }
41    }
42
43    fn into_tokens(self, crate_path: &TokenStream) -> TokenStream {
44        match self {
45            UnhandledPanic::Ignore => quote! { #crate_path::runtime::UnhandledPanic::Ignore },
46            UnhandledPanic::ShutdownRuntime => {
47                quote! { #crate_path::runtime::UnhandledPanic::ShutdownRuntime }
48            }
49        }
50    }
51}
52
53struct FinalConfig {
54    flavor: RuntimeFlavor,
55    worker_threads: Option<usize>,
56    start_paused: Option<bool>,
57    crate_name: Option<Path>,
58    unhandled_panic: Option<UnhandledPanic>,
59}
60
61/// Config used in case of the attribute not being able to build a valid config
62const DEFAULT_ERROR_CONFIG: FinalConfig = FinalConfig {
63    flavor: RuntimeFlavor::CurrentThread,
64    worker_threads: None,
65    start_paused: None,
66    crate_name: None,
67    unhandled_panic: None,
68};
69
70struct Configuration {
71    rt_multi_thread_available: bool,
72    default_flavor: RuntimeFlavor,
73    flavor: Option<RuntimeFlavor>,
74    worker_threads: Option<(usize, Span)>,
75    start_paused: Option<(bool, Span)>,
76    is_test: bool,
77    crate_name: Option<Path>,
78    unhandled_panic: Option<(UnhandledPanic, Span)>,
79}
80
81impl Configuration {
82    fn new(is_test: bool, rt_multi_thread: bool) -> Self {
83        Configuration {
84            rt_multi_thread_available: rt_multi_thread,
85            default_flavor: match is_test {
86                true => RuntimeFlavor::CurrentThread,
87                false => RuntimeFlavor::Threaded,
88            },
89            flavor: None,
90            worker_threads: None,
91            start_paused: None,
92            is_test,
93            crate_name: None,
94            unhandled_panic: None,
95        }
96    }
97
98    fn set_flavor(&mut self, runtime: syn::Lit, span: Span) -> Result<(), syn::Error> {
99        if self.flavor.is_some() {
100            return Err(syn::Error::new(span, "`flavor` set multiple times."));
101        }
102
103        let runtime_str = parse_string(runtime, span, "flavor")?;
104        let runtime =
105            RuntimeFlavor::from_str(&runtime_str).map_err(|err| syn::Error::new(span, err))?;
106        self.flavor = Some(runtime);
107        Ok(())
108    }
109
110    fn set_worker_threads(
111        &mut self,
112        worker_threads: syn::Lit,
113        span: Span,
114    ) -> Result<(), syn::Error> {
115        if self.worker_threads.is_some() {
116            return Err(syn::Error::new(
117                span,
118                "`worker_threads` set multiple times.",
119            ));
120        }
121
122        let worker_threads = parse_int(worker_threads, span, "worker_threads")?;
123        if worker_threads == 0 {
124            return Err(syn::Error::new(span, "`worker_threads` may not be 0."));
125        }
126        self.worker_threads = Some((worker_threads, span));
127        Ok(())
128    }
129
130    fn set_start_paused(&mut self, start_paused: syn::Lit, span: Span) -> Result<(), syn::Error> {
131        if self.start_paused.is_some() {
132            return Err(syn::Error::new(span, "`start_paused` set multiple times."));
133        }
134
135        let start_paused = parse_bool(start_paused, span, "start_paused")?;
136        self.start_paused = Some((start_paused, span));
137        Ok(())
138    }
139
140    fn set_crate_name(&mut self, name: syn::Lit, span: Span) -> Result<(), syn::Error> {
141        if self.crate_name.is_some() {
142            return Err(syn::Error::new(span, "`crate` set multiple times."));
143        }
144        let name_path = parse_path(name, span, "crate")?;
145        self.crate_name = Some(name_path);
146        Ok(())
147    }
148
149    fn set_unhandled_panic(
150        &mut self,
151        unhandled_panic: syn::Lit,
152        span: Span,
153    ) -> Result<(), syn::Error> {
154        if self.unhandled_panic.is_some() {
155            return Err(syn::Error::new(
156                span,
157                "`unhandled_panic` set multiple times.",
158            ));
159        }
160
161        let unhandled_panic = parse_string(unhandled_panic, span, "unhandled_panic")?;
162        let unhandled_panic =
163            UnhandledPanic::from_str(&unhandled_panic).map_err(|err| syn::Error::new(span, err))?;
164        self.unhandled_panic = Some((unhandled_panic, span));
165        Ok(())
166    }
167
168    fn macro_name(&self) -> &'static str {
169        if self.is_test {
170            "tokio::test"
171        } else {
172            "tokio::main"
173        }
174    }
175
176    fn build(&self) -> Result<FinalConfig, syn::Error> {
177        use RuntimeFlavor as F;
178
179        let flavor = self.flavor.unwrap_or(self.default_flavor);
180        let worker_threads = match (flavor, self.worker_threads) {
181            (F::CurrentThread, Some((_, worker_threads_span))) => {
182                let msg = format!(
183                    "The `worker_threads` option requires the `multi_thread` runtime flavor. Use `#[{}(flavor = \"multi_thread\")]`",
184                    self.macro_name(),
185                );
186                return Err(syn::Error::new(worker_threads_span, msg));
187            }
188            (F::CurrentThread, None) => None,
189            (F::Threaded, worker_threads) if self.rt_multi_thread_available => {
190                worker_threads.map(|(val, _span)| val)
191            }
192            (F::Threaded, _) => {
193                let msg = if self.flavor.is_none() {
194                    "The default runtime flavor is `multi_thread`, but the `rt-multi-thread` feature is disabled."
195                } else {
196                    "The runtime flavor `multi_thread` requires the `rt-multi-thread` feature."
197                };
198                return Err(syn::Error::new(Span::call_site(), msg));
199            }
200        };
201
202        let start_paused = match (flavor, self.start_paused) {
203            (F::Threaded, Some((_, start_paused_span))) => {
204                let msg = format!(
205                    "The `start_paused` option requires the `current_thread` runtime flavor. Use `#[{}(flavor = \"current_thread\")]`",
206                    self.macro_name(),
207                );
208                return Err(syn::Error::new(start_paused_span, msg));
209            }
210            (F::CurrentThread, Some((start_paused, _))) => Some(start_paused),
211            (_, None) => None,
212        };
213
214        let unhandled_panic = match (flavor, self.unhandled_panic) {
215            (F::Threaded, Some((_, unhandled_panic_span))) => {
216                let msg = format!(
217                    "The `unhandled_panic` option requires the `current_thread` runtime flavor. Use `#[{}(flavor = \"current_thread\")]`",
218                    self.macro_name(),
219                );
220                return Err(syn::Error::new(unhandled_panic_span, msg));
221            }
222            (F::CurrentThread, Some((unhandled_panic, _))) => Some(unhandled_panic),
223            (_, None) => None,
224        };
225
226        Ok(FinalConfig {
227            crate_name: self.crate_name.clone(),
228            flavor,
229            worker_threads,
230            start_paused,
231            unhandled_panic,
232        })
233    }
234}
235
236fn parse_int(int: syn::Lit, span: Span, field: &str) -> Result<usize, syn::Error> {
237    match int {
238        syn::Lit::Int(lit) => match lit.base10_parse::<usize>() {
239            Ok(value) => Ok(value),
240            Err(e) => Err(syn::Error::new(
241                span,
242                format!("Failed to parse value of `{}` as integer: {}", field, e),
243            )),
244        },
245        _ => Err(syn::Error::new(
246            span,
247            format!("Failed to parse value of `{}` as integer.", field),
248        )),
249    }
250}
251
252fn parse_string(int: syn::Lit, span: Span, field: &str) -> Result<String, syn::Error> {
253    match int {
254        syn::Lit::Str(s) => Ok(s.value()),
255        syn::Lit::Verbatim(s) => Ok(s.to_string()),
256        _ => Err(syn::Error::new(
257            span,
258            format!("Failed to parse value of `{}` as string.", field),
259        )),
260    }
261}
262
263fn parse_path(lit: syn::Lit, span: Span, field: &str) -> Result<Path, syn::Error> {
264    match lit {
265        syn::Lit::Str(s) => {
266            let err = syn::Error::new(
267                span,
268                format!(
269                    "Failed to parse value of `{}` as path: \"{}\"",
270                    field,
271                    s.value()
272                ),
273            );
274            s.parse::<syn::Path>().map_err(|_| err.clone())
275        }
276        _ => Err(syn::Error::new(
277            span,
278            format!("Failed to parse value of `{}` as path.", field),
279        )),
280    }
281}
282
283fn parse_bool(bool: syn::Lit, span: Span, field: &str) -> Result<bool, syn::Error> {
284    match bool {
285        syn::Lit::Bool(b) => Ok(b.value),
286        _ => Err(syn::Error::new(
287            span,
288            format!("Failed to parse value of `{}` as bool.", field),
289        )),
290    }
291}
292
293fn build_config(
294    input: &ItemFn,
295    args: AttributeArgs,
296    is_test: bool,
297    rt_multi_thread: bool,
298) -> Result<FinalConfig, syn::Error> {
299    if input.sig.asyncness.is_none() {
300        let msg = "the `async` keyword is missing from the function declaration";
301        return Err(syn::Error::new_spanned(input.sig.fn_token, msg));
302    }
303
304    let mut config = Configuration::new(is_test, rt_multi_thread);
305    let macro_name = config.macro_name();
306
307    for arg in args {
308        match arg {
309            syn::Meta::NameValue(namevalue) => {
310                let ident = namevalue
311                    .path
312                    .get_ident()
313                    .ok_or_else(|| {
314                        syn::Error::new_spanned(&namevalue, "Must have specified ident")
315                    })?
316                    .to_string()
317                    .to_lowercase();
318                let lit = match &namevalue.value {
319                    syn::Expr::Lit(syn::ExprLit { lit, .. }) => lit,
320                    expr => return Err(syn::Error::new_spanned(expr, "Must be a literal")),
321                };
322                match ident.as_str() {
323                    "worker_threads" => {
324                        config.set_worker_threads(lit.clone(), syn::spanned::Spanned::span(lit))?;
325                    }
326                    "flavor" => {
327                        config.set_flavor(lit.clone(), syn::spanned::Spanned::span(lit))?;
328                    }
329                    "start_paused" => {
330                        config.set_start_paused(lit.clone(), syn::spanned::Spanned::span(lit))?;
331                    }
332                    "core_threads" => {
333                        let msg = "Attribute `core_threads` is renamed to `worker_threads`";
334                        return Err(syn::Error::new_spanned(namevalue, msg));
335                    }
336                    "crate" => {
337                        config.set_crate_name(lit.clone(), syn::spanned::Spanned::span(lit))?;
338                    }
339                    "unhandled_panic" => {
340                        config
341                            .set_unhandled_panic(lit.clone(), syn::spanned::Spanned::span(lit))?;
342                    }
343                    name => {
344                        let msg = format!(
345                            "Unknown attribute {} is specified; expected one of: `flavor`, `worker_threads`, `start_paused`, `crate`, `unhandled_panic`",
346                            name,
347                        );
348                        return Err(syn::Error::new_spanned(namevalue, msg));
349                    }
350                }
351            }
352            syn::Meta::Path(path) => {
353                let name = path
354                    .get_ident()
355                    .ok_or_else(|| syn::Error::new_spanned(&path, "Must have specified ident"))?
356                    .to_string()
357                    .to_lowercase();
358                let msg = match name.as_str() {
359                    "threaded_scheduler" | "multi_thread" => {
360                        format!(
361                            "Set the runtime flavor with #[{}(flavor = \"multi_thread\")].",
362                            macro_name
363                        )
364                    }
365                    "basic_scheduler" | "current_thread" | "single_threaded" => {
366                        format!(
367                            "Set the runtime flavor with #[{}(flavor = \"current_thread\")].",
368                            macro_name
369                        )
370                    }
371                    "flavor" | "worker_threads" | "start_paused" | "crate" | "unhandled_panic" => {
372                        format!("The `{}` attribute requires an argument.", name)
373                    }
374                    name => {
375                        format!("Unknown attribute {} is specified; expected one of: `flavor`, `worker_threads`, `start_paused`, `crate`, `unhandled_panic`.", name)
376                    }
377                };
378                return Err(syn::Error::new_spanned(path, msg));
379            }
380            other => {
381                return Err(syn::Error::new_spanned(
382                    other,
383                    "Unknown attribute inside the macro",
384                ));
385            }
386        }
387    }
388
389    config.build()
390}
391
392fn parse_knobs(mut input: ItemFn, is_test: bool, config: FinalConfig) -> TokenStream {
393    input.sig.asyncness = None;
394
395    // If type mismatch occurs, the current rustc points to the last statement.
396    let (last_stmt_start_span, last_stmt_end_span) = {
397        let mut last_stmt = input.stmts.last().cloned().unwrap_or_default().into_iter();
398
399        // `Span` on stable Rust has a limitation that only points to the first
400        // token, not the whole tokens. We can work around this limitation by
401        // using the first/last span of the tokens like
402        // `syn::Error::new_spanned` does.
403        let start = last_stmt.next().map_or_else(Span::call_site, |t| t.span());
404        let end = last_stmt.last().map_or(start, |t| t.span());
405        (start, end)
406    };
407
408    let crate_path = config
409        .crate_name
410        .map(ToTokens::into_token_stream)
411        .unwrap_or_else(|| Ident::new("tokio", last_stmt_start_span).into_token_stream());
412
413    let mut rt = match config.flavor {
414        RuntimeFlavor::CurrentThread => quote_spanned! {last_stmt_start_span=>
415            #crate_path::runtime::Builder::new_current_thread()
416        },
417        RuntimeFlavor::Threaded => quote_spanned! {last_stmt_start_span=>
418            #crate_path::runtime::Builder::new_multi_thread()
419        },
420    };
421    if let Some(v) = config.worker_threads {
422        rt = quote_spanned! {last_stmt_start_span=> #rt.worker_threads(#v) };
423    }
424    if let Some(v) = config.start_paused {
425        rt = quote_spanned! {last_stmt_start_span=> #rt.start_paused(#v) };
426    }
427    if let Some(v) = config.unhandled_panic {
428        let unhandled_panic = v.into_tokens(&crate_path);
429        rt = quote_spanned! {last_stmt_start_span=> #rt.unhandled_panic(#unhandled_panic) };
430    }
431
432    let generated_attrs = if is_test {
433        quote! {
434            #[::core::prelude::v1::test]
435        }
436    } else {
437        quote! {}
438    };
439
440    let body_ident = quote! { body };
441    let last_block = quote_spanned! {last_stmt_end_span=>
442        #[allow(clippy::expect_used, clippy::diverging_sub_expression)]
443        {
444            return #rt
445                .enable_all()
446                .build()
447                .expect("Failed building the Runtime")
448                .block_on(#body_ident);
449        }
450    };
451
452    let body = input.body();
453
454    // For test functions pin the body to the stack and use `Pin<&mut dyn
455    // Future>` to reduce the amount of `Runtime::block_on` (and related
456    // functions) copies we generate during compilation due to the generic
457    // parameter `F` (the future to block on). This could have an impact on
458    // performance, but because it's only for testing it's unlikely to be very
459    // large.
460    //
461    // We don't do this for the main function as it should only be used once so
462    // there will be no benefit.
463    let body = if is_test {
464        let output_type = match &input.sig.output {
465            // For functions with no return value syn doesn't print anything,
466            // but that doesn't work as `Output` for our boxed `Future`, so
467            // default to `()` (the same type as the function output).
468            syn::ReturnType::Default => quote! { () },
469            syn::ReturnType::Type(_, ret_type) => quote! { #ret_type },
470        };
471        quote! {
472            let body = async #body;
473            #crate_path::pin!(body);
474            let body: ::core::pin::Pin<&mut dyn ::core::future::Future<Output = #output_type>> = body;
475        }
476    } else {
477        quote! {
478            let body = async #body;
479        }
480    };
481
482    input.into_tokens(generated_attrs, body, last_block)
483}
484
485fn token_stream_with_error(mut tokens: TokenStream, error: syn::Error) -> TokenStream {
486    tokens.extend(error.into_compile_error());
487    tokens
488}
489
490pub(crate) fn main(args: TokenStream, item: TokenStream, rt_multi_thread: bool) -> TokenStream {
491    // If any of the steps for this macro fail, we still want to expand to an item that is as close
492    // to the expected output as possible. This helps out IDEs such that completions and other
493    // related features keep working.
494    let input: ItemFn = match syn::parse2(item.clone()) {
495        Ok(it) => it,
496        Err(e) => return token_stream_with_error(item, e),
497    };
498
499    let config = if input.sig.ident == "main" && !input.sig.inputs.is_empty() {
500        let msg = "the main function cannot accept arguments";
501        Err(syn::Error::new_spanned(&input.sig.ident, msg))
502    } else {
503        AttributeArgs::parse_terminated
504            .parse2(args)
505            .and_then(|args| build_config(&input, args, false, rt_multi_thread))
506    };
507
508    match config {
509        Ok(config) => parse_knobs(input, false, config),
510        Err(e) => token_stream_with_error(parse_knobs(input, false, DEFAULT_ERROR_CONFIG), e),
511    }
512}
513
514// Check whether given attribute is a test attribute of forms:
515// * `#[test]`
516// * `#[core::prelude::*::test]` or `#[::core::prelude::*::test]`
517// * `#[std::prelude::*::test]` or `#[::std::prelude::*::test]`
518fn is_test_attribute(attr: &Attribute) -> bool {
519    let path = match &attr.meta {
520        syn::Meta::Path(path) => path,
521        _ => return false,
522    };
523    let candidates = [
524        ["core", "prelude", "*", "test"],
525        ["std", "prelude", "*", "test"],
526    ];
527    if path.leading_colon.is_none()
528        && path.segments.len() == 1
529        && path.segments[0].arguments.is_none()
530        && path.segments[0].ident == "test"
531    {
532        return true;
533    } else if path.segments.len() != candidates[0].len() {
534        return false;
535    }
536    candidates.into_iter().any(|segments| {
537        path.segments.iter().zip(segments).all(|(segment, path)| {
538            segment.arguments.is_none() && (path == "*" || segment.ident == path)
539        })
540    })
541}
542
543pub(crate) fn test(args: TokenStream, item: TokenStream, rt_multi_thread: bool) -> TokenStream {
544    // If any of the steps for this macro fail, we still want to expand to an item that is as close
545    // to the expected output as possible. This helps out IDEs such that completions and other
546    // related features keep working.
547    let input: ItemFn = match syn::parse2(item.clone()) {
548        Ok(it) => it,
549        Err(e) => return token_stream_with_error(item, e),
550    };
551    let config = if let Some(attr) = input.attrs().find(|attr| is_test_attribute(attr)) {
552        let msg = "second test attribute is supplied, consider removing or changing the order of your test attributes";
553        Err(syn::Error::new_spanned(attr, msg))
554    } else {
555        AttributeArgs::parse_terminated
556            .parse2(args)
557            .and_then(|args| build_config(&input, args, true, rt_multi_thread))
558    };
559
560    match config {
561        Ok(config) => parse_knobs(input, true, config),
562        Err(e) => token_stream_with_error(parse_knobs(input, true, DEFAULT_ERROR_CONFIG), e),
563    }
564}
565
566struct ItemFn {
567    outer_attrs: Vec<Attribute>,
568    vis: Visibility,
569    sig: Signature,
570    brace_token: syn::token::Brace,
571    inner_attrs: Vec<Attribute>,
572    stmts: Vec<proc_macro2::TokenStream>,
573}
574
575impl ItemFn {
576    /// Access all attributes of the function item.
577    fn attrs(&self) -> impl Iterator<Item = &Attribute> {
578        self.outer_attrs.iter().chain(self.inner_attrs.iter())
579    }
580
581    /// Get the body of the function item in a manner so that it can be
582    /// conveniently used with the `quote!` macro.
583    fn body(&self) -> Body<'_> {
584        Body {
585            brace_token: self.brace_token,
586            stmts: &self.stmts,
587        }
588    }
589
590    /// Convert our local function item into a token stream.
591    fn into_tokens(
592        self,
593        generated_attrs: proc_macro2::TokenStream,
594        body: proc_macro2::TokenStream,
595        last_block: proc_macro2::TokenStream,
596    ) -> TokenStream {
597        let mut tokens = proc_macro2::TokenStream::new();
598        // Outer attributes are simply streamed as-is.
599        for attr in self.outer_attrs {
600            attr.to_tokens(&mut tokens);
601        }
602
603        // Inner attributes require extra care, since they're not supported on
604        // blocks (which is what we're expanded into) we instead lift them
605        // outside of the function. This matches the behavior of `syn`.
606        for mut attr in self.inner_attrs {
607            attr.style = syn::AttrStyle::Outer;
608            attr.to_tokens(&mut tokens);
609        }
610
611        // Add generated macros at the end, so macros processed later are aware of them.
612        generated_attrs.to_tokens(&mut tokens);
613
614        self.vis.to_tokens(&mut tokens);
615        self.sig.to_tokens(&mut tokens);
616
617        self.brace_token.surround(&mut tokens, |tokens| {
618            body.to_tokens(tokens);
619            last_block.to_tokens(tokens);
620        });
621
622        tokens
623    }
624}
625
626impl Parse for ItemFn {
627    #[inline]
628    fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
629        // This parse implementation has been largely lifted from `syn`, with
630        // the exception of:
631        // * We don't have access to the plumbing necessary to parse inner
632        //   attributes in-place.
633        // * We do our own statements parsing to avoid recursively parsing
634        //   entire statements and only look for the parts we're interested in.
635
636        let outer_attrs = input.call(Attribute::parse_outer)?;
637        let vis: Visibility = input.parse()?;
638        let sig: Signature = input.parse()?;
639
640        let content;
641        let brace_token = braced!(content in input);
642        let inner_attrs = Attribute::parse_inner(&content)?;
643
644        let mut buf = proc_macro2::TokenStream::new();
645        let mut stmts = Vec::new();
646
647        while !content.is_empty() {
648            if let Some(semi) = content.parse::<Option<syn::Token![;]>>()? {
649                semi.to_tokens(&mut buf);
650                stmts.push(buf);
651                buf = proc_macro2::TokenStream::new();
652                continue;
653            }
654
655            // Parse a single token tree and extend our current buffer with it.
656            // This avoids parsing the entire content of the sub-tree.
657            buf.extend([content.parse::<TokenTree>()?]);
658        }
659
660        if !buf.is_empty() {
661            stmts.push(buf);
662        }
663
664        Ok(Self {
665            outer_attrs,
666            vis,
667            sig,
668            brace_token,
669            inner_attrs,
670            stmts,
671        })
672    }
673}
674
675struct Body<'a> {
676    brace_token: syn::token::Brace,
677    // Statements, with terminating `;`.
678    stmts: &'a [TokenStream],
679}
680
681impl ToTokens for Body<'_> {
682    fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
683        self.brace_token.surround(tokens, |tokens| {
684            for stmt in self.stmts {
685                stmt.to_tokens(tokens);
686            }
687        });
688    }
689}