]> git.lizzy.rs Git - rust.git/blob - compiler/rustc_infer/src/infer/error_reporting/suggest.rs
drive-by: Fix path spans
[rust.git] / compiler / rustc_infer / src / infer / error_reporting / suggest.rs
1 use hir::def::CtorKind;
2 use hir::intravisit::{walk_expr, walk_stmt, Visitor};
3 use rustc_data_structures::fx::FxIndexSet;
4 use rustc_errors::{Applicability, Diagnostic};
5 use rustc_hir as hir;
6 use rustc_middle::traits::{
7     IfExpressionCause, MatchExpressionArmCause, ObligationCause, ObligationCauseCode,
8     StatementAsExpression,
9 };
10 use rustc_middle::ty::print::with_no_trimmed_paths;
11 use rustc_middle::ty::{self as ty, Ty, TypeVisitable};
12 use rustc_span::{sym, BytePos, Span};
13
14 use crate::errors::SuggAddLetForLetChains;
15
16 use super::TypeErrCtxt;
17
18 impl<'tcx> TypeErrCtxt<'_, 'tcx> {
19     pub(super) fn suggest_remove_semi_or_return_binding(
20         &self,
21         err: &mut Diagnostic,
22         first_id: Option<hir::HirId>,
23         first_ty: Ty<'tcx>,
24         first_span: Span,
25         second_id: Option<hir::HirId>,
26         second_ty: Ty<'tcx>,
27         second_span: Span,
28     ) {
29         let remove_semicolon = [
30             (first_id, self.resolve_vars_if_possible(second_ty)),
31             (second_id, self.resolve_vars_if_possible(first_ty)),
32         ]
33         .into_iter()
34         .find_map(|(id, ty)| {
35             let hir::Node::Block(blk) = self.tcx.hir().get(id?) else { return None };
36             self.could_remove_semicolon(blk, ty)
37         });
38         match remove_semicolon {
39             Some((sp, StatementAsExpression::NeedsBoxing)) => {
40                 err.multipart_suggestion(
41                     "consider removing this semicolon and boxing the expressions",
42                     vec![
43                         (first_span.shrink_to_lo(), "Box::new(".to_string()),
44                         (first_span.shrink_to_hi(), ")".to_string()),
45                         (second_span.shrink_to_lo(), "Box::new(".to_string()),
46                         (second_span.shrink_to_hi(), ")".to_string()),
47                         (sp, String::new()),
48                     ],
49                     Applicability::MachineApplicable,
50                 );
51             }
52             Some((sp, StatementAsExpression::CorrectType)) => {
53                 err.span_suggestion_short(
54                     sp,
55                     "consider removing this semicolon",
56                     "",
57                     Applicability::MachineApplicable,
58                 );
59             }
60             None => {
61                 for (id, ty) in [(first_id, second_ty), (second_id, first_ty)] {
62                     if let Some(id) = id
63                         && let hir::Node::Block(blk) = self.tcx.hir().get(id)
64                         && self.consider_returning_binding(blk, ty, err)
65                     {
66                         break;
67                     }
68                 }
69             }
70         }
71     }
72
73     pub(super) fn suggest_boxing_for_return_impl_trait(
74         &self,
75         err: &mut Diagnostic,
76         return_sp: Span,
77         arm_spans: impl Iterator<Item = Span>,
78     ) {
79         err.multipart_suggestion(
80             "you could change the return type to be a boxed trait object",
81             vec![
82                 (return_sp.with_hi(return_sp.lo() + BytePos(4)), "Box<dyn".to_string()),
83                 (return_sp.shrink_to_hi(), ">".to_string()),
84             ],
85             Applicability::MaybeIncorrect,
86         );
87         let sugg = arm_spans
88             .flat_map(|sp| {
89                 [(sp.shrink_to_lo(), "Box::new(".to_string()), (sp.shrink_to_hi(), ")".to_string())]
90                     .into_iter()
91             })
92             .collect::<Vec<_>>();
93         err.multipart_suggestion(
94             "if you change the return type to expect trait objects, box the returned expressions",
95             sugg,
96             Applicability::MaybeIncorrect,
97         );
98     }
99
100     pub(super) fn suggest_tuple_pattern(
101         &self,
102         cause: &ObligationCause<'tcx>,
103         exp_found: &ty::error::ExpectedFound<Ty<'tcx>>,
104         diag: &mut Diagnostic,
105     ) {
106         // Heavily inspired by `FnCtxt::suggest_compatible_variants`, with
107         // some modifications due to that being in typeck and this being in infer.
108         if let ObligationCauseCode::Pattern { .. } = cause.code() {
109             if let ty::Adt(expected_adt, substs) = exp_found.expected.kind() {
110                 let compatible_variants: Vec<_> = expected_adt
111                     .variants()
112                     .iter()
113                     .filter(|variant| {
114                         variant.fields.len() == 1 && variant.ctor_kind() == Some(CtorKind::Fn)
115                     })
116                     .filter_map(|variant| {
117                         let sole_field = &variant.fields[0];
118                         let sole_field_ty = sole_field.ty(self.tcx, substs);
119                         if self.same_type_modulo_infer(sole_field_ty, exp_found.found) {
120                             let variant_path =
121                                 with_no_trimmed_paths!(self.tcx.def_path_str(variant.def_id));
122                             // FIXME #56861: DRYer prelude filtering
123                             if let Some(path) = variant_path.strip_prefix("std::prelude::") {
124                                 if let Some((_, path)) = path.split_once("::") {
125                                     return Some(path.to_string());
126                                 }
127                             }
128                             Some(variant_path)
129                         } else {
130                             None
131                         }
132                     })
133                     .collect();
134                 match &compatible_variants[..] {
135                     [] => {}
136                     [variant] => {
137                         diag.multipart_suggestion_verbose(
138                             &format!("try wrapping the pattern in `{}`", variant),
139                             vec![
140                                 (cause.span.shrink_to_lo(), format!("{}(", variant)),
141                                 (cause.span.shrink_to_hi(), ")".to_string()),
142                             ],
143                             Applicability::MaybeIncorrect,
144                         );
145                     }
146                     _ => {
147                         // More than one matching variant.
148                         diag.multipart_suggestions(
149                             &format!(
150                                 "try wrapping the pattern in a variant of `{}`",
151                                 self.tcx.def_path_str(expected_adt.did())
152                             ),
153                             compatible_variants.into_iter().map(|variant| {
154                                 vec![
155                                     (cause.span.shrink_to_lo(), format!("{}(", variant)),
156                                     (cause.span.shrink_to_hi(), ")".to_string()),
157                                 ]
158                             }),
159                             Applicability::MaybeIncorrect,
160                         );
161                     }
162                 }
163             }
164         }
165     }
166
167     /// A possible error is to forget to add `.await` when using futures:
168     ///
169     /// ```compile_fail,E0308
170     /// async fn make_u32() -> u32 {
171     ///     22
172     /// }
173     ///
174     /// fn take_u32(x: u32) {}
175     ///
176     /// async fn foo() {
177     ///     let x = make_u32();
178     ///     take_u32(x);
179     /// }
180     /// ```
181     ///
182     /// This routine checks if the found type `T` implements `Future<Output=U>` where `U` is the
183     /// expected type. If this is the case, and we are inside of an async body, it suggests adding
184     /// `.await` to the tail of the expression.
185     pub(super) fn suggest_await_on_expect_found(
186         &self,
187         cause: &ObligationCause<'tcx>,
188         exp_span: Span,
189         exp_found: &ty::error::ExpectedFound<Ty<'tcx>>,
190         diag: &mut Diagnostic,
191     ) {
192         debug!(
193             "suggest_await_on_expect_found: exp_span={:?}, expected_ty={:?}, found_ty={:?}",
194             exp_span, exp_found.expected, exp_found.found,
195         );
196
197         if let ObligationCauseCode::CompareImplItemObligation { .. } = cause.code() {
198             return;
199         }
200
201         match (
202             self.get_impl_future_output_ty(exp_found.expected),
203             self.get_impl_future_output_ty(exp_found.found),
204         ) {
205             (Some(exp), Some(found)) if self.same_type_modulo_infer(exp, found) => match cause
206                 .code()
207             {
208                 ObligationCauseCode::IfExpression(box IfExpressionCause { then_id, .. }) => {
209                     let then_span = self.find_block_span_from_hir_id(*then_id);
210                     diag.multipart_suggestion(
211                         "consider `await`ing on both `Future`s",
212                         vec![
213                             (then_span.shrink_to_hi(), ".await".to_string()),
214                             (exp_span.shrink_to_hi(), ".await".to_string()),
215                         ],
216                         Applicability::MaybeIncorrect,
217                     );
218                 }
219                 ObligationCauseCode::MatchExpressionArm(box MatchExpressionArmCause {
220                     prior_arms,
221                     ..
222                 }) => {
223                     if let [.., arm_span] = &prior_arms[..] {
224                         diag.multipart_suggestion(
225                             "consider `await`ing on both `Future`s",
226                             vec![
227                                 (arm_span.shrink_to_hi(), ".await".to_string()),
228                                 (exp_span.shrink_to_hi(), ".await".to_string()),
229                             ],
230                             Applicability::MaybeIncorrect,
231                         );
232                     } else {
233                         diag.help("consider `await`ing on both `Future`s");
234                     }
235                 }
236                 _ => {
237                     diag.help("consider `await`ing on both `Future`s");
238                 }
239             },
240             (_, Some(ty)) if self.same_type_modulo_infer(exp_found.expected, ty) => {
241                 diag.span_suggestion_verbose(
242                     exp_span.shrink_to_hi(),
243                     "consider `await`ing on the `Future`",
244                     ".await",
245                     Applicability::MaybeIncorrect,
246                 );
247             }
248             (Some(ty), _) if self.same_type_modulo_infer(ty, exp_found.found) => match cause.code()
249             {
250                 ObligationCauseCode::Pattern { span: Some(then_span), .. } => {
251                     diag.span_suggestion_verbose(
252                         then_span.shrink_to_hi(),
253                         "consider `await`ing on the `Future`",
254                         ".await",
255                         Applicability::MaybeIncorrect,
256                     );
257                 }
258                 ObligationCauseCode::IfExpression(box IfExpressionCause { then_id, .. }) => {
259                     let then_span = self.find_block_span_from_hir_id(*then_id);
260                     diag.span_suggestion_verbose(
261                         then_span.shrink_to_hi(),
262                         "consider `await`ing on the `Future`",
263                         ".await",
264                         Applicability::MaybeIncorrect,
265                     );
266                 }
267                 ObligationCauseCode::MatchExpressionArm(box MatchExpressionArmCause {
268                     ref prior_arms,
269                     ..
270                 }) => {
271                     diag.multipart_suggestion_verbose(
272                         "consider `await`ing on the `Future`",
273                         prior_arms
274                             .iter()
275                             .map(|arm| (arm.shrink_to_hi(), ".await".to_string()))
276                             .collect(),
277                         Applicability::MaybeIncorrect,
278                     );
279                 }
280                 _ => {}
281             },
282             _ => {}
283         }
284     }
285
286     pub(super) fn suggest_accessing_field_where_appropriate(
287         &self,
288         cause: &ObligationCause<'tcx>,
289         exp_found: &ty::error::ExpectedFound<Ty<'tcx>>,
290         diag: &mut Diagnostic,
291     ) {
292         debug!(
293             "suggest_accessing_field_where_appropriate(cause={:?}, exp_found={:?})",
294             cause, exp_found
295         );
296         if let ty::Adt(expected_def, expected_substs) = exp_found.expected.kind() {
297             if expected_def.is_enum() {
298                 return;
299             }
300
301             if let Some((name, ty)) = expected_def
302                 .non_enum_variant()
303                 .fields
304                 .iter()
305                 .filter(|field| field.vis.is_accessible_from(field.did, self.tcx))
306                 .map(|field| (field.name, field.ty(self.tcx, expected_substs)))
307                 .find(|(_, ty)| self.same_type_modulo_infer(*ty, exp_found.found))
308             {
309                 if let ObligationCauseCode::Pattern { span: Some(span), .. } = *cause.code() {
310                     if let Ok(snippet) = self.tcx.sess.source_map().span_to_snippet(span) {
311                         let suggestion = if expected_def.is_struct() {
312                             format!("{}.{}", snippet, name)
313                         } else if expected_def.is_union() {
314                             format!("unsafe {{ {}.{} }}", snippet, name)
315                         } else {
316                             return;
317                         };
318                         diag.span_suggestion(
319                             span,
320                             &format!(
321                                 "you might have meant to use field `{}` whose type is `{}`",
322                                 name, ty
323                             ),
324                             suggestion,
325                             Applicability::MaybeIncorrect,
326                         );
327                     }
328                 }
329             }
330         }
331     }
332
333     /// When encountering a case where `.as_ref()` on a `Result` or `Option` would be appropriate,
334     /// suggests it.
335     pub(super) fn suggest_as_ref_where_appropriate(
336         &self,
337         span: Span,
338         exp_found: &ty::error::ExpectedFound<Ty<'tcx>>,
339         diag: &mut Diagnostic,
340     ) {
341         if let Ok(snippet) = self.tcx.sess.source_map().span_to_snippet(span)
342             && let Some(msg) = self.should_suggest_as_ref(exp_found.expected, exp_found.found)
343         {
344             diag.span_suggestion(
345                 span,
346                 msg,
347                 // HACK: fix issue# 100605, suggesting convert from &Option<T> to Option<&T>, remove the extra `&`
348                 format!("{}.as_ref()", snippet.trim_start_matches('&')),
349                 Applicability::MachineApplicable,
350             );
351         }
352     }
353
354     pub fn should_suggest_as_ref(&self, expected: Ty<'tcx>, found: Ty<'tcx>) -> Option<&str> {
355         if let (ty::Adt(exp_def, exp_substs), ty::Ref(_, found_ty, _)) =
356             (expected.kind(), found.kind())
357         {
358             if let ty::Adt(found_def, found_substs) = *found_ty.kind() {
359                 if exp_def == &found_def {
360                     let have_as_ref = &[
361                         (
362                             sym::Option,
363                             "you can convert from `&Option<T>` to `Option<&T>` using \
364                         `.as_ref()`",
365                         ),
366                         (
367                             sym::Result,
368                             "you can convert from `&Result<T, E>` to \
369                         `Result<&T, &E>` using `.as_ref()`",
370                         ),
371                     ];
372                     if let Some(msg) = have_as_ref.iter().find_map(|(name, msg)| {
373                         self.tcx.is_diagnostic_item(*name, exp_def.did()).then_some(msg)
374                     }) {
375                         let mut show_suggestion = true;
376                         for (exp_ty, found_ty) in
377                             std::iter::zip(exp_substs.types(), found_substs.types())
378                         {
379                             match *exp_ty.kind() {
380                                 ty::Ref(_, exp_ty, _) => {
381                                     match (exp_ty.kind(), found_ty.kind()) {
382                                         (_, ty::Param(_))
383                                         | (_, ty::Infer(_))
384                                         | (ty::Param(_), _)
385                                         | (ty::Infer(_), _) => {}
386                                         _ if self.same_type_modulo_infer(exp_ty, found_ty) => {}
387                                         _ => show_suggestion = false,
388                                     };
389                                 }
390                                 ty::Param(_) | ty::Infer(_) => {}
391                                 _ => show_suggestion = false,
392                             }
393                         }
394                         if show_suggestion {
395                             return Some(*msg);
396                         }
397                     }
398                 }
399             }
400         }
401         None
402     }
403
404     /// Try to find code with pattern `if Some(..) = expr`
405     /// use a `visitor` to mark the `if` which its span contains given error span,
406     /// and then try to find a assignment in the `cond` part, which span is equal with error span
407     pub(super) fn suggest_let_for_letchains(
408         &self,
409         err: &mut Diagnostic,
410         cause: &ObligationCause<'_>,
411         span: Span,
412     ) {
413         let hir = self.tcx.hir();
414         let fn_hir_id = hir.get_parent_node(cause.body_id);
415         if let Some(node) = self.tcx.hir().find(fn_hir_id) &&
416             let hir::Node::Item(hir::Item {
417                     kind: hir::ItemKind::Fn(_sig, _, body_id), ..
418                 }) = node {
419         let body = hir.body(*body_id);
420
421         /// Find the if expression with given span
422         struct IfVisitor {
423             pub result: bool,
424             pub found_if: bool,
425             pub err_span: Span,
426         }
427
428         impl<'v> Visitor<'v> for IfVisitor {
429             fn visit_expr(&mut self, ex: &'v hir::Expr<'v>) {
430                 if self.result { return; }
431                 match ex.kind {
432                     hir::ExprKind::If(cond, _, _) => {
433                         self.found_if = true;
434                         walk_expr(self, cond);
435                         self.found_if = false;
436                     }
437                     _ => walk_expr(self, ex),
438                 }
439             }
440
441             fn visit_stmt(&mut self, ex: &'v hir::Stmt<'v>) {
442                 if let hir::StmtKind::Local(hir::Local {
443                         span, pat: hir::Pat{..}, ty: None, init: Some(_), ..
444                     }) = &ex.kind
445                     && self.found_if
446                     && span.eq(&self.err_span) {
447                         self.result = true;
448                 }
449                 walk_stmt(self, ex);
450             }
451
452             fn visit_body(&mut self, body: &'v hir::Body<'v>) {
453                 hir::intravisit::walk_body(self, body);
454             }
455         }
456
457         let mut visitor = IfVisitor { err_span: span, found_if: false, result: false };
458         visitor.visit_body(&body);
459         if visitor.result {
460                 err.subdiagnostic(SuggAddLetForLetChains{span: span.shrink_to_lo()});
461             }
462         }
463     }
464 }
465
466 impl<'tcx> TypeErrCtxt<'_, 'tcx> {
467     /// Be helpful when the user wrote `{... expr; }` and taking the `;` off
468     /// is enough to fix the error.
469     pub fn could_remove_semicolon(
470         &self,
471         blk: &'tcx hir::Block<'tcx>,
472         expected_ty: Ty<'tcx>,
473     ) -> Option<(Span, StatementAsExpression)> {
474         let blk = blk.innermost_block();
475         // Do not suggest if we have a tail expr.
476         if blk.expr.is_some() {
477             return None;
478         }
479         let last_stmt = blk.stmts.last()?;
480         let hir::StmtKind::Semi(ref last_expr) = last_stmt.kind else {
481             return None;
482         };
483         let last_expr_ty = self.typeck_results.as_ref()?.expr_ty_opt(*last_expr)?;
484         let needs_box = match (last_expr_ty.kind(), expected_ty.kind()) {
485             _ if last_expr_ty.references_error() => return None,
486             _ if self.same_type_modulo_infer(last_expr_ty, expected_ty) => {
487                 StatementAsExpression::CorrectType
488             }
489             (
490                 ty::Alias(ty::Opaque, ty::AliasTy { def_id: last_def_id, substs: _ }),
491                 ty::Alias(ty::Opaque, ty::AliasTy { def_id: exp_def_id, substs: _ }),
492             ) if last_def_id == exp_def_id => StatementAsExpression::CorrectType,
493             (
494                 ty::Alias(ty::Opaque, ty::AliasTy { def_id: last_def_id, substs: last_bounds }),
495                 ty::Alias(ty::Opaque, ty::AliasTy { def_id: exp_def_id, substs: exp_bounds }),
496             ) => {
497                 debug!(
498                     "both opaque, likely future {:?} {:?} {:?} {:?}",
499                     last_def_id, last_bounds, exp_def_id, exp_bounds
500                 );
501
502                 let last_local_id = last_def_id.as_local()?;
503                 let exp_local_id = exp_def_id.as_local()?;
504
505                 match (
506                     &self.tcx.hir().expect_item(last_local_id).kind,
507                     &self.tcx.hir().expect_item(exp_local_id).kind,
508                 ) {
509                     (
510                         hir::ItemKind::OpaqueTy(hir::OpaqueTy { bounds: last_bounds, .. }),
511                         hir::ItemKind::OpaqueTy(hir::OpaqueTy { bounds: exp_bounds, .. }),
512                     ) if std::iter::zip(*last_bounds, *exp_bounds).all(|(left, right)| {
513                         match (left, right) {
514                             (
515                                 hir::GenericBound::Trait(tl, ml),
516                                 hir::GenericBound::Trait(tr, mr),
517                             ) if tl.trait_ref.trait_def_id() == tr.trait_ref.trait_def_id()
518                                 && ml == mr =>
519                             {
520                                 true
521                             }
522                             (
523                                 hir::GenericBound::LangItemTrait(langl, _, _, argsl),
524                                 hir::GenericBound::LangItemTrait(langr, _, _, argsr),
525                             ) if langl == langr => {
526                                 // FIXME: consider the bounds!
527                                 debug!("{:?} {:?}", argsl, argsr);
528                                 true
529                             }
530                             _ => false,
531                         }
532                     }) =>
533                     {
534                         StatementAsExpression::NeedsBoxing
535                     }
536                     _ => StatementAsExpression::CorrectType,
537                 }
538             }
539             _ => return None,
540         };
541         let span = if last_stmt.span.from_expansion() {
542             let mac_call = rustc_span::source_map::original_sp(last_stmt.span, blk.span);
543             self.tcx.sess.source_map().mac_call_stmt_semi_span(mac_call)?
544         } else {
545             last_stmt.span.with_lo(last_stmt.span.hi() - BytePos(1))
546         };
547         Some((span, needs_box))
548     }
549
550     /// Suggest returning a local binding with a compatible type if the block
551     /// has no return expression.
552     pub fn consider_returning_binding(
553         &self,
554         blk: &'tcx hir::Block<'tcx>,
555         expected_ty: Ty<'tcx>,
556         err: &mut Diagnostic,
557     ) -> bool {
558         let blk = blk.innermost_block();
559         // Do not suggest if we have a tail expr.
560         if blk.expr.is_some() {
561             return false;
562         }
563         let mut shadowed = FxIndexSet::default();
564         let mut candidate_idents = vec![];
565         let mut find_compatible_candidates = |pat: &hir::Pat<'_>| {
566             if let hir::PatKind::Binding(_, hir_id, ident, _) = &pat.kind
567                 && let Some(pat_ty) = self
568                     .typeck_results
569                     .as_ref()
570                     .and_then(|typeck_results| typeck_results.node_type_opt(*hir_id))
571             {
572                 let pat_ty = self.resolve_vars_if_possible(pat_ty);
573                 if self.same_type_modulo_infer(pat_ty, expected_ty)
574                     && !(pat_ty, expected_ty).references_error()
575                     && shadowed.insert(ident.name)
576                 {
577                     candidate_idents.push((*ident, pat_ty));
578                 }
579             }
580             true
581         };
582
583         let hir = self.tcx.hir();
584         for stmt in blk.stmts.iter().rev() {
585             let hir::StmtKind::Local(local) = &stmt.kind else { continue; };
586             local.pat.walk(&mut find_compatible_candidates);
587         }
588         match hir.find(hir.get_parent_node(blk.hir_id)) {
589             Some(hir::Node::Expr(hir::Expr { hir_id, .. })) => {
590                 match hir.find(hir.get_parent_node(*hir_id)) {
591                     Some(hir::Node::Arm(hir::Arm { pat, .. })) => {
592                         pat.walk(&mut find_compatible_candidates);
593                     }
594                     Some(
595                         hir::Node::Item(hir::Item { kind: hir::ItemKind::Fn(_, _, body), .. })
596                         | hir::Node::ImplItem(hir::ImplItem {
597                             kind: hir::ImplItemKind::Fn(_, body),
598                             ..
599                         })
600                         | hir::Node::TraitItem(hir::TraitItem {
601                             kind: hir::TraitItemKind::Fn(_, hir::TraitFn::Provided(body)),
602                             ..
603                         })
604                         | hir::Node::Expr(hir::Expr {
605                             kind: hir::ExprKind::Closure(hir::Closure { body, .. }),
606                             ..
607                         }),
608                     ) => {
609                         for param in hir.body(*body).params {
610                             param.pat.walk(&mut find_compatible_candidates);
611                         }
612                     }
613                     Some(hir::Node::Expr(hir::Expr {
614                         kind:
615                             hir::ExprKind::If(
616                                 hir::Expr { kind: hir::ExprKind::Let(let_), .. },
617                                 then_block,
618                                 _,
619                             ),
620                         ..
621                     })) if then_block.hir_id == *hir_id => {
622                         let_.pat.walk(&mut find_compatible_candidates);
623                     }
624                     _ => {}
625                 }
626             }
627             _ => {}
628         }
629
630         match &candidate_idents[..] {
631             [(ident, _ty)] => {
632                 let sm = self.tcx.sess.source_map();
633                 if let Some(stmt) = blk.stmts.last() {
634                     let stmt_span = sm.stmt_span(stmt.span, blk.span);
635                     let sugg = if sm.is_multiline(blk.span)
636                         && let Some(spacing) = sm.indentation_before(stmt_span)
637                     {
638                         format!("\n{spacing}{ident}")
639                     } else {
640                         format!(" {ident}")
641                     };
642                     err.span_suggestion_verbose(
643                         stmt_span.shrink_to_hi(),
644                         format!("consider returning the local binding `{ident}`"),
645                         sugg,
646                         Applicability::MaybeIncorrect,
647                     );
648                 } else {
649                     let sugg = if sm.is_multiline(blk.span)
650                         && let Some(spacing) = sm.indentation_before(blk.span.shrink_to_lo())
651                     {
652                         format!("\n{spacing}    {ident}\n{spacing}")
653                     } else {
654                         format!(" {ident} ")
655                     };
656                     let left_span = sm.span_through_char(blk.span, '{').shrink_to_hi();
657                     err.span_suggestion_verbose(
658                         sm.span_extend_while(left_span, |c| c.is_whitespace()).unwrap_or(left_span),
659                         format!("consider returning the local binding `{ident}`"),
660                         sugg,
661                         Applicability::MaybeIncorrect,
662                     );
663                 }
664                 true
665             }
666             values if (1..3).contains(&values.len()) => {
667                 let spans = values.iter().map(|(ident, _)| ident.span).collect::<Vec<_>>();
668                 err.span_note(spans, "consider returning one of these bindings");
669                 true
670             }
671             _ => false,
672         }
673     }
674 }