• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 use std::iter;
2 
3 use proc_macro2::TokenStream;
4 use quote::{quote, quote_spanned, ToTokens};
5 use syn::visit_mut::VisitMut;
6 use syn::{
7     punctuated::Punctuated, spanned::Spanned, Block, Expr, ExprAsync, ExprCall, FieldPat, FnArg,
8     Ident, Item, ItemFn, Pat, PatIdent, PatReference, PatStruct, PatTuple, PatTupleStruct, PatType,
9     Path, ReturnType, Signature, Stmt, Token, Type, TypePath,
10 };
11 
12 use crate::{
13     attr::{Field, Fields, FormatMode, InstrumentArgs},
14     MaybeItemFn, MaybeItemFnRef,
15 };
16 
17 /// Given an existing function, generate an instrumented version of that function
gen_function<'a, B: ToTokens + 'a>( input: MaybeItemFnRef<'a, B>, args: InstrumentArgs, instrumented_function_name: &str, self_type: Option<&TypePath>, ) -> proc_macro2::TokenStream18 pub(crate) fn gen_function<'a, B: ToTokens + 'a>(
19     input: MaybeItemFnRef<'a, B>,
20     args: InstrumentArgs,
21     instrumented_function_name: &str,
22     self_type: Option<&TypePath>,
23 ) -> proc_macro2::TokenStream {
24     // these are needed ahead of time, as ItemFn contains the function body _and_
25     // isn't representable inside a quote!/quote_spanned! macro
26     // (Syn's ToTokens isn't implemented for ItemFn)
27     let MaybeItemFnRef {
28         outer_attrs,
29         inner_attrs,
30         vis,
31         sig,
32         block,
33     } = input;
34 
35     let Signature {
36         output,
37         inputs: params,
38         unsafety,
39         asyncness,
40         constness,
41         abi,
42         ident,
43         generics:
44             syn::Generics {
45                 params: gen_params,
46                 where_clause,
47                 ..
48             },
49         ..
50     } = sig;
51 
52     let warnings = args.warnings();
53 
54     let (return_type, return_span) = if let ReturnType::Type(_, return_type) = &output {
55         (erase_impl_trait(return_type), return_type.span())
56     } else {
57         // Point at function name if we don't have an explicit return type
58         (syn::parse_quote! { () }, ident.span())
59     };
60     // Install a fake return statement as the first thing in the function
61     // body, so that we eagerly infer that the return type is what we
62     // declared in the async fn signature.
63     // The `#[allow(..)]` is given because the return statement is
64     // unreachable, but does affect inference, so it needs to be written
65     // exactly that way for it to do its magic.
66     let fake_return_edge = quote_spanned! {return_span=>
67         #[allow(unreachable_code, clippy::diverging_sub_expression, clippy::let_unit_value)]
68         if false {
69             let __tracing_attr_fake_return: #return_type =
70                 unreachable!("this is just for type inference, and is unreachable code");
71             return __tracing_attr_fake_return;
72         }
73     };
74     let block = quote! {
75         {
76             #fake_return_edge
77             #block
78         }
79     };
80 
81     let body = gen_block(
82         &block,
83         params,
84         asyncness.is_some(),
85         args,
86         instrumented_function_name,
87         self_type,
88     );
89 
90     quote!(
91         #(#outer_attrs) *
92         #vis #constness #unsafety #asyncness #abi fn #ident<#gen_params>(#params) #output
93         #where_clause
94         {
95             #(#inner_attrs) *
96             #warnings
97             #body
98         }
99     )
100 }
101 
102 /// Instrument a block
gen_block<B: ToTokens>( block: &B, params: &Punctuated<FnArg, Token![,]>, async_context: bool, mut args: InstrumentArgs, instrumented_function_name: &str, self_type: Option<&TypePath>, ) -> proc_macro2::TokenStream103 fn gen_block<B: ToTokens>(
104     block: &B,
105     params: &Punctuated<FnArg, Token![,]>,
106     async_context: bool,
107     mut args: InstrumentArgs,
108     instrumented_function_name: &str,
109     self_type: Option<&TypePath>,
110 ) -> proc_macro2::TokenStream {
111     // generate the span's name
112     let span_name = args
113         // did the user override the span's name?
114         .name
115         .as_ref()
116         .map(|name| quote!(#name))
117         .unwrap_or_else(|| quote!(#instrumented_function_name));
118 
119     let level = args.level();
120 
121     let follows_from = args.follows_from.iter();
122     let follows_from = quote! {
123         #(for cause in #follows_from {
124             __tracing_attr_span.follows_from(cause);
125         })*
126     };
127 
128     // generate this inside a closure, so we can return early on errors.
129     let span = (|| {
130         // Pull out the arguments-to-be-skipped first, so we can filter results
131         // below.
132         let param_names: Vec<(Ident, (Ident, RecordType))> = params
133             .clone()
134             .into_iter()
135             .flat_map(|param| match param {
136                 FnArg::Typed(PatType { pat, ty, .. }) => {
137                     param_names(*pat, RecordType::parse_from_ty(&*ty))
138                 }
139                 FnArg::Receiver(_) => Box::new(iter::once((
140                     Ident::new("self", param.span()),
141                     RecordType::Debug,
142                 ))),
143             })
144             // Little dance with new (user-exposed) names and old (internal)
145             // names of identifiers. That way, we could do the following
146             // even though async_trait (<=0.1.43) rewrites "self" as "_self":
147             // ```
148             // #[async_trait]
149             // impl Foo for FooImpl {
150             //     #[instrument(skip(self))]
151             //     async fn foo(&self, v: usize) {}
152             // }
153             // ```
154             .map(|(x, record_type)| {
155                 // if we are inside a function generated by async-trait <=0.1.43, we need to
156                 // take care to rewrite "_self" as "self" for 'user convenience'
157                 if self_type.is_some() && x == "_self" {
158                     (Ident::new("self", x.span()), (x, record_type))
159                 } else {
160                     (x.clone(), (x, record_type))
161                 }
162             })
163             .collect();
164 
165         for skip in &args.skips {
166             if !param_names.iter().map(|(user, _)| user).any(|y| y == skip) {
167                 return quote_spanned! {skip.span()=>
168                     compile_error!("attempting to skip non-existent parameter")
169                 };
170             }
171         }
172 
173         let target = args.target();
174 
175         let parent = args.parent.iter();
176 
177         // filter out skipped fields
178         let quoted_fields: Vec<_> = param_names
179             .iter()
180             .filter(|(param, _)| {
181                 if args.skip_all || args.skips.contains(param) {
182                     return false;
183                 }
184 
185                 // If any parameters have the same name as a custom field, skip
186                 // and allow them to be formatted by the custom field.
187                 if let Some(ref fields) = args.fields {
188                     fields.0.iter().all(|Field { ref name, .. }| {
189                         let first = name.first();
190                         first != name.last() || !first.iter().any(|name| name == &param)
191                     })
192                 } else {
193                     true
194                 }
195             })
196             .map(|(user_name, (real_name, record_type))| match record_type {
197                 RecordType::Value => quote!(#user_name = #real_name),
198                 RecordType::Debug => quote!(#user_name = tracing::field::debug(&#real_name)),
199             })
200             .collect();
201 
202         // replace every use of a variable with its original name
203         if let Some(Fields(ref mut fields)) = args.fields {
204             let mut replacer = IdentAndTypesRenamer {
205                 idents: param_names.into_iter().map(|(a, (b, _))| (a, b)).collect(),
206                 types: Vec::new(),
207             };
208 
209             // when async-trait <=0.1.43 is in use, replace instances
210             // of the "Self" type inside the fields values
211             if let Some(self_type) = self_type {
212                 replacer.types.push(("Self", self_type.clone()));
213             }
214 
215             for e in fields.iter_mut().filter_map(|f| f.value.as_mut()) {
216                 syn::visit_mut::visit_expr_mut(&mut replacer, e);
217             }
218         }
219 
220         let custom_fields = &args.fields;
221 
222         quote!(tracing::span!(
223             target: #target,
224             #(parent: #parent,)*
225             #level,
226             #span_name,
227             #(#quoted_fields,)*
228             #custom_fields
229 
230         ))
231     })();
232 
233     let target = args.target();
234 
235     let err_event = match args.err_mode {
236         Some(FormatMode::Default) | Some(FormatMode::Display) => {
237             Some(quote!(tracing::error!(target: #target, error = %e)))
238         }
239         Some(FormatMode::Debug) => Some(quote!(tracing::error!(target: #target, error = ?e))),
240         _ => None,
241     };
242 
243     let ret_event = match args.ret_mode {
244         Some(FormatMode::Display) => Some(quote!(
245             tracing::event!(target: #target, #level, return = %x)
246         )),
247         Some(FormatMode::Default) | Some(FormatMode::Debug) => Some(quote!(
248             tracing::event!(target: #target, #level, return = ?x)
249         )),
250         _ => None,
251     };
252 
253     // Generate the instrumented function body.
254     // If the function is an `async fn`, this will wrap it in an async block,
255     // which is `instrument`ed using `tracing-futures`. Otherwise, this will
256     // enter the span and then perform the rest of the body.
257     // If `err` is in args, instrument any resulting `Err`s.
258     // If `ret` is in args, instrument any resulting `Ok`s when the function
259     // returns `Result`s, otherwise instrument any resulting values.
260     if async_context {
261         let mk_fut = match (err_event, ret_event) {
262             (Some(err_event), Some(ret_event)) => quote_spanned!(block.span()=>
263                 async move {
264                     match async move #block.await {
265                         #[allow(clippy::unit_arg)]
266                         Ok(x) => {
267                             #ret_event;
268                             Ok(x)
269                         },
270                         Err(e) => {
271                             #err_event;
272                             Err(e)
273                         }
274                     }
275                 }
276             ),
277             (Some(err_event), None) => quote_spanned!(block.span()=>
278                 async move {
279                     match async move #block.await {
280                         #[allow(clippy::unit_arg)]
281                         Ok(x) => Ok(x),
282                         Err(e) => {
283                             #err_event;
284                             Err(e)
285                         }
286                     }
287                 }
288             ),
289             (None, Some(ret_event)) => quote_spanned!(block.span()=>
290                 async move {
291                     let x = async move #block.await;
292                     #ret_event;
293                     x
294                 }
295             ),
296             (None, None) => quote_spanned!(block.span()=>
297                 async move #block
298             ),
299         };
300 
301         return quote!(
302             let __tracing_attr_span = #span;
303             let __tracing_instrument_future = #mk_fut;
304             if !__tracing_attr_span.is_disabled() {
305                 #follows_from
306                 tracing::Instrument::instrument(
307                     __tracing_instrument_future,
308                     __tracing_attr_span
309                 )
310                 .await
311             } else {
312                 __tracing_instrument_future.await
313             }
314         );
315     }
316 
317     let span = quote!(
318         // These variables are left uninitialized and initialized only
319         // if the tracing level is statically enabled at this point.
320         // While the tracing level is also checked at span creation
321         // time, that will still create a dummy span, and a dummy guard
322         // and drop the dummy guard later. By lazily initializing these
323         // variables, Rust will generate a drop flag for them and thus
324         // only drop the guard if it was created. This creates code that
325         // is very straightforward for LLVM to optimize out if the tracing
326         // level is statically disabled, while not causing any performance
327         // regression in case the level is enabled.
328         let __tracing_attr_span;
329         let __tracing_attr_guard;
330         if tracing::level_enabled!(#level) {
331             __tracing_attr_span = #span;
332             #follows_from
333             __tracing_attr_guard = __tracing_attr_span.enter();
334         }
335     );
336 
337     match (err_event, ret_event) {
338         (Some(err_event), Some(ret_event)) => quote_spanned! {block.span()=>
339             #span
340             #[allow(clippy::redundant_closure_call)]
341             match (move || #block)() {
342                 #[allow(clippy::unit_arg)]
343                 Ok(x) => {
344                     #ret_event;
345                     Ok(x)
346                 },
347                 Err(e) => {
348                     #err_event;
349                     Err(e)
350                 }
351             }
352         },
353         (Some(err_event), None) => quote_spanned!(block.span()=>
354             #span
355             #[allow(clippy::redundant_closure_call)]
356             match (move || #block)() {
357                 #[allow(clippy::unit_arg)]
358                 Ok(x) => Ok(x),
359                 Err(e) => {
360                     #err_event;
361                     Err(e)
362                 }
363             }
364         ),
365         (None, Some(ret_event)) => quote_spanned!(block.span()=>
366             #span
367             #[allow(clippy::redundant_closure_call)]
368             let x = (move || #block)();
369             #ret_event;
370             x
371         ),
372         (None, None) => quote_spanned!(block.span() =>
373             // Because `quote` produces a stream of tokens _without_ whitespace, the
374             // `if` and the block will appear directly next to each other. This
375             // generates a clippy lint about suspicious `if/else` formatting.
376             // Therefore, suppress the lint inside the generated code...
377             #[allow(clippy::suspicious_else_formatting)]
378             {
379                 #span
380                 // ...but turn the lint back on inside the function body.
381                 #[warn(clippy::suspicious_else_formatting)]
382                 #block
383             }
384         ),
385     }
386 }
387 
388 /// Indicates whether a field should be recorded as `Value` or `Debug`.
389 enum RecordType {
390     /// The field should be recorded using its `Value` implementation.
391     Value,
392     /// The field should be recorded using `tracing::field::debug()`.
393     Debug,
394 }
395 
396 impl RecordType {
397     /// Array of primitive types which should be recorded as [RecordType::Value].
398     const TYPES_FOR_VALUE: &'static [&'static str] = &[
399         "bool",
400         "str",
401         "u8",
402         "i8",
403         "u16",
404         "i16",
405         "u32",
406         "i32",
407         "u64",
408         "i64",
409         "f32",
410         "f64",
411         "usize",
412         "isize",
413         "NonZeroU8",
414         "NonZeroI8",
415         "NonZeroU16",
416         "NonZeroI16",
417         "NonZeroU32",
418         "NonZeroI32",
419         "NonZeroU64",
420         "NonZeroI64",
421         "NonZeroUsize",
422         "NonZeroIsize",
423         "Wrapping",
424     ];
425 
426     /// Parse `RecordType` from [Type] by looking up
427     /// the [RecordType::TYPES_FOR_VALUE] array.
parse_from_ty(ty: &Type) -> Self428     fn parse_from_ty(ty: &Type) -> Self {
429         match ty {
430             Type::Path(TypePath { path, .. })
431                 if path
432                     .segments
433                     .iter()
434                     .last()
435                     .map(|path_segment| {
436                         let ident = path_segment.ident.to_string();
437                         Self::TYPES_FOR_VALUE.iter().any(|&t| t == ident)
438                     })
439                     .unwrap_or(false) =>
440             {
441                 RecordType::Value
442             }
443             Type::Reference(syn::TypeReference { elem, .. }) => RecordType::parse_from_ty(elem),
444             _ => RecordType::Debug,
445         }
446     }
447 }
448 
param_names(pat: Pat, record_type: RecordType) -> Box<dyn Iterator<Item = (Ident, RecordType)>>449 fn param_names(pat: Pat, record_type: RecordType) -> Box<dyn Iterator<Item = (Ident, RecordType)>> {
450     match pat {
451         Pat::Ident(PatIdent { ident, .. }) => Box::new(iter::once((ident, record_type))),
452         Pat::Reference(PatReference { pat, .. }) => param_names(*pat, record_type),
453         // We can't get the concrete type of fields in the struct/tuple
454         // patterns by using `syn`. e.g. `fn foo(Foo { x, y }: Foo) {}`.
455         // Therefore, the struct/tuple patterns in the arguments will just
456         // always be recorded as `RecordType::Debug`.
457         Pat::Struct(PatStruct { fields, .. }) => Box::new(
458             fields
459                 .into_iter()
460                 .flat_map(|FieldPat { pat, .. }| param_names(*pat, RecordType::Debug)),
461         ),
462         Pat::Tuple(PatTuple { elems, .. }) => Box::new(
463             elems
464                 .into_iter()
465                 .flat_map(|p| param_names(p, RecordType::Debug)),
466         ),
467         Pat::TupleStruct(PatTupleStruct {
468             pat: PatTuple { elems, .. },
469             ..
470         }) => Box::new(
471             elems
472                 .into_iter()
473                 .flat_map(|p| param_names(p, RecordType::Debug)),
474         ),
475 
476         // The above *should* cover all cases of irrefutable patterns,
477         // but we purposefully don't do any funny business here
478         // (such as panicking) because that would obscure rustc's
479         // much more informative error message.
480         _ => Box::new(iter::empty()),
481     }
482 }
483 
484 /// The specific async code pattern that was detected
485 enum AsyncKind<'a> {
486     /// Immediately-invoked async fn, as generated by `async-trait <= 0.1.43`:
487     /// `async fn foo<...>(...) {...}; Box::pin(foo<...>(...))`
488     Function(&'a ItemFn),
489     /// A function returning an async (move) block, optionally `Box::pin`-ed,
490     /// as generated by `async-trait >= 0.1.44`:
491     /// `Box::pin(async move { ... })`
492     Async {
493         async_expr: &'a ExprAsync,
494         pinned_box: bool,
495     },
496 }
497 
498 pub(crate) struct AsyncInfo<'block> {
499     // statement that must be patched
500     source_stmt: &'block Stmt,
501     kind: AsyncKind<'block>,
502     self_type: Option<TypePath>,
503     input: &'block ItemFn,
504 }
505 
506 impl<'block> AsyncInfo<'block> {
507     /// Get the AST of the inner function we need to hook, if it looks like a
508     /// manual future implementation.
509     ///
510     /// When we are given a function that returns a (pinned) future containing the
511     /// user logic, it is that (pinned) future that needs to be instrumented.
512     /// Were we to instrument its parent, we would only collect information
513     /// regarding the allocation of that future, and not its own span of execution.
514     ///
515     /// We inspect the block of the function to find if it matches any of the
516     /// following patterns:
517     ///
518     /// - Immediately-invoked async fn, as generated by `async-trait <= 0.1.43`:
519     ///   `async fn foo<...>(...) {...}; Box::pin(foo<...>(...))`
520     ///
521     /// - A function returning an async (move) block, optionally `Box::pin`-ed,
522     ///   as generated by `async-trait >= 0.1.44`:
523     ///   `Box::pin(async move { ... })`
524     ///
525     /// We the return the statement that must be instrumented, along with some
526     /// other information.
527     /// 'gen_body' will then be able to use that information to instrument the
528     /// proper function/future.
529     ///
530     /// (this follows the approach suggested in
531     /// https://github.com/dtolnay/async-trait/issues/45#issuecomment-571245673)
from_fn(input: &'block ItemFn) -> Option<Self>532     pub(crate) fn from_fn(input: &'block ItemFn) -> Option<Self> {
533         // are we in an async context? If yes, this isn't a manual async-like pattern
534         if input.sig.asyncness.is_some() {
535             return None;
536         }
537 
538         let block = &input.block;
539 
540         // list of async functions declared inside the block
541         let inside_funs = block.stmts.iter().filter_map(|stmt| {
542             if let Stmt::Item(Item::Fn(fun)) = &stmt {
543                 // If the function is async, this is a candidate
544                 if fun.sig.asyncness.is_some() {
545                     return Some((stmt, fun));
546                 }
547             }
548             None
549         });
550 
551         // last expression of the block: it determines the return value of the
552         // block, this is quite likely a `Box::pin` statement or an async block
553         let (last_expr_stmt, last_expr) = block.stmts.iter().rev().find_map(|stmt| {
554             if let Stmt::Expr(expr) = stmt {
555                 Some((stmt, expr))
556             } else {
557                 None
558             }
559         })?;
560 
561         // is the last expression an async block?
562         if let Expr::Async(async_expr) = last_expr {
563             return Some(AsyncInfo {
564                 source_stmt: last_expr_stmt,
565                 kind: AsyncKind::Async {
566                     async_expr,
567                     pinned_box: false,
568                 },
569                 self_type: None,
570                 input,
571             });
572         }
573 
574         // is the last expression a function call?
575         let (outside_func, outside_args) = match last_expr {
576             Expr::Call(ExprCall { func, args, .. }) => (func, args),
577             _ => return None,
578         };
579 
580         // is it a call to `Box::pin()`?
581         let path = match outside_func.as_ref() {
582             Expr::Path(path) => &path.path,
583             _ => return None,
584         };
585         if !path_to_string(path).ends_with("Box::pin") {
586             return None;
587         }
588 
589         // Does the call take an argument? If it doesn't,
590         // it's not gonna compile anyway, but that's no reason
591         // to (try to) perform an out of bounds access
592         if outside_args.is_empty() {
593             return None;
594         }
595 
596         // Is the argument to Box::pin an async block that
597         // captures its arguments?
598         if let Expr::Async(async_expr) = &outside_args[0] {
599             return Some(AsyncInfo {
600                 source_stmt: last_expr_stmt,
601                 kind: AsyncKind::Async {
602                     async_expr,
603                     pinned_box: true,
604                 },
605                 self_type: None,
606                 input,
607             });
608         }
609 
610         // Is the argument to Box::pin a function call itself?
611         let func = match &outside_args[0] {
612             Expr::Call(ExprCall { func, .. }) => func,
613             _ => return None,
614         };
615 
616         // "stringify" the path of the function called
617         let func_name = match **func {
618             Expr::Path(ref func_path) => path_to_string(&func_path.path),
619             _ => return None,
620         };
621 
622         // Was that function defined inside of the current block?
623         // If so, retrieve the statement where it was declared and the function itself
624         let (stmt_func_declaration, func) = inside_funs
625             .into_iter()
626             .find(|(_, fun)| fun.sig.ident == func_name)?;
627 
628         // If "_self" is present as an argument, we store its type to be able to rewrite "Self" (the
629         // parameter type) with the type of "_self"
630         let mut self_type = None;
631         for arg in &func.sig.inputs {
632             if let FnArg::Typed(ty) = arg {
633                 if let Pat::Ident(PatIdent { ref ident, .. }) = *ty.pat {
634                     if ident == "_self" {
635                         let mut ty = *ty.ty.clone();
636                         // extract the inner type if the argument is "&self" or "&mut self"
637                         if let Type::Reference(syn::TypeReference { elem, .. }) = ty {
638                             ty = *elem;
639                         }
640 
641                         if let Type::Path(tp) = ty {
642                             self_type = Some(tp);
643                             break;
644                         }
645                     }
646                 }
647             }
648         }
649 
650         Some(AsyncInfo {
651             source_stmt: stmt_func_declaration,
652             kind: AsyncKind::Function(func),
653             self_type,
654             input,
655         })
656     }
657 
gen_async( self, args: InstrumentArgs, instrumented_function_name: &str, ) -> Result<proc_macro::TokenStream, syn::Error>658     pub(crate) fn gen_async(
659         self,
660         args: InstrumentArgs,
661         instrumented_function_name: &str,
662     ) -> Result<proc_macro::TokenStream, syn::Error> {
663         // let's rewrite some statements!
664         let mut out_stmts: Vec<TokenStream> = self
665             .input
666             .block
667             .stmts
668             .iter()
669             .map(|stmt| stmt.to_token_stream())
670             .collect();
671 
672         if let Some((iter, _stmt)) = self
673             .input
674             .block
675             .stmts
676             .iter()
677             .enumerate()
678             .find(|(_iter, stmt)| *stmt == self.source_stmt)
679         {
680             // instrument the future by rewriting the corresponding statement
681             out_stmts[iter] = match self.kind {
682                 // `Box::pin(immediately_invoked_async_fn())`
683                 AsyncKind::Function(fun) => {
684                     let fun = MaybeItemFn::from(fun.clone());
685                     gen_function(
686                         fun.as_ref(),
687                         args,
688                         instrumented_function_name,
689                         self.self_type.as_ref(),
690                     )
691                 }
692                 // `async move { ... }`, optionally pinned
693                 AsyncKind::Async {
694                     async_expr,
695                     pinned_box,
696                 } => {
697                     let instrumented_block = gen_block(
698                         &async_expr.block,
699                         &self.input.sig.inputs,
700                         true,
701                         args,
702                         instrumented_function_name,
703                         None,
704                     );
705                     let async_attrs = &async_expr.attrs;
706                     if pinned_box {
707                         quote! {
708                             Box::pin(#(#async_attrs) * async move { #instrumented_block })
709                         }
710                     } else {
711                         quote! {
712                             #(#async_attrs) * async move { #instrumented_block }
713                         }
714                     }
715                 }
716             };
717         }
718 
719         let vis = &self.input.vis;
720         let sig = &self.input.sig;
721         let attrs = &self.input.attrs;
722         Ok(quote!(
723             #(#attrs) *
724             #vis #sig {
725                 #(#out_stmts) *
726             }
727         )
728         .into())
729     }
730 }
731 
732 // Return a path as a String
path_to_string(path: &Path) -> String733 fn path_to_string(path: &Path) -> String {
734     use std::fmt::Write;
735     // some heuristic to prevent too many allocations
736     let mut res = String::with_capacity(path.segments.len() * 5);
737     for i in 0..path.segments.len() {
738         write!(&mut res, "{}", path.segments[i].ident)
739             .expect("writing to a String should never fail");
740         if i < path.segments.len() - 1 {
741             res.push_str("::");
742         }
743     }
744     res
745 }
746 
747 /// A visitor struct to replace idents and types in some piece
748 /// of code (e.g. the "self" and "Self" tokens in user-supplied
749 /// fields expressions when the function is generated by an old
750 /// version of async-trait).
751 struct IdentAndTypesRenamer<'a> {
752     types: Vec<(&'a str, TypePath)>,
753     idents: Vec<(Ident, Ident)>,
754 }
755 
756 impl<'a> VisitMut for IdentAndTypesRenamer<'a> {
757     // we deliberately compare strings because we want to ignore the spans
758     // If we apply clippy's lint, the behavior changes
759     #[allow(clippy::cmp_owned)]
visit_ident_mut(&mut self, id: &mut Ident)760     fn visit_ident_mut(&mut self, id: &mut Ident) {
761         for (old_ident, new_ident) in &self.idents {
762             if id.to_string() == old_ident.to_string() {
763                 *id = new_ident.clone();
764             }
765         }
766     }
767 
visit_type_mut(&mut self, ty: &mut Type)768     fn visit_type_mut(&mut self, ty: &mut Type) {
769         for (type_name, new_type) in &self.types {
770             if let Type::Path(TypePath { path, .. }) = ty {
771                 if path_to_string(path) == *type_name {
772                     *ty = Type::Path(new_type.clone());
773                 }
774             }
775         }
776     }
777 }
778 
779 // A visitor struct that replace an async block by its patched version
780 struct AsyncTraitBlockReplacer<'a> {
781     block: &'a Block,
782     patched_block: Block,
783 }
784 
785 impl<'a> VisitMut for AsyncTraitBlockReplacer<'a> {
visit_block_mut(&mut self, i: &mut Block)786     fn visit_block_mut(&mut self, i: &mut Block) {
787         if i == self.block {
788             *i = self.patched_block.clone();
789         }
790     }
791 }
792 
793 // Replaces any `impl Trait` with `_` so it can be used as the type in
794 // a `let` statement's LHS.
795 struct ImplTraitEraser;
796 
797 impl VisitMut for ImplTraitEraser {
visit_type_mut(&mut self, t: &mut Type)798     fn visit_type_mut(&mut self, t: &mut Type) {
799         if let Type::ImplTrait(..) = t {
800             *t = syn::TypeInfer {
801                 underscore_token: Token![_](t.span()),
802             }
803             .into();
804         } else {
805             syn::visit_mut::visit_type_mut(self, t);
806         }
807     }
808 }
809 
erase_impl_trait(ty: &Type) -> Type810 fn erase_impl_trait(ty: &Type) -> Type {
811     let mut ty = ty.clone();
812     ImplTraitEraser.visit_type_mut(&mut ty);
813     ty
814 }
815