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 == ¶m)
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),
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