rustc_trait_selection/error_reporting/infer/
suggest.rs

1use core::ops::ControlFlow;
2
3use hir::def::CtorKind;
4use hir::intravisit::{Visitor, walk_expr, walk_stmt};
5use hir::{LetStmt, QPath};
6use rustc_data_structures::fx::FxIndexSet;
7use rustc_errors::{Applicability, Diag};
8use rustc_hir as hir;
9use rustc_hir::def::Res;
10use rustc_hir::{MatchSource, Node};
11use rustc_middle::traits::{
12    IfExpressionCause, MatchExpressionArmCause, ObligationCause, ObligationCauseCode,
13};
14use rustc_middle::ty::error::TypeError;
15use rustc_middle::ty::print::with_no_trimmed_paths;
16use rustc_middle::ty::{self as ty, GenericArgKind, IsSuggestable, Ty, TypeVisitableExt};
17use rustc_span::{Span, sym};
18use tracing::debug;
19
20use crate::error_reporting::TypeErrCtxt;
21use crate::error_reporting::infer::hir::Path;
22use crate::errors::{
23    ConsiderAddingAwait, FnConsiderCasting, FnConsiderCastingBoth, FnItemsAreDistinct, FnUniqTypes,
24    FunctionPointerSuggestion, SuggestAccessingField, SuggestRemoveSemiOrReturnBinding,
25    SuggestTuplePatternMany, SuggestTuplePatternOne, TypeErrorAdditionalDiags,
26};
27
28#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
29enum StatementAsExpression {
30    CorrectType,
31    NeedsBoxing,
32}
33
34#[derive(Clone, Copy)]
35enum SuggestAsRefKind {
36    Option,
37    Result,
38}
39
40impl<'tcx> TypeErrCtxt<'_, 'tcx> {
41    pub(super) fn suggest_remove_semi_or_return_binding(
42        &self,
43        first_id: Option<hir::HirId>,
44        first_ty: Ty<'tcx>,
45        first_span: Span,
46        second_id: Option<hir::HirId>,
47        second_ty: Ty<'tcx>,
48        second_span: Span,
49    ) -> Option<SuggestRemoveSemiOrReturnBinding> {
50        let remove_semicolon = [
51            (first_id, self.resolve_vars_if_possible(second_ty)),
52            (second_id, self.resolve_vars_if_possible(first_ty)),
53        ]
54        .into_iter()
55        .find_map(|(id, ty)| {
56            let hir::Node::Block(blk) = self.tcx.hir_node(id?) else { return None };
57            self.could_remove_semicolon(blk, ty)
58        });
59        match remove_semicolon {
60            Some((sp, StatementAsExpression::NeedsBoxing)) => {
61                Some(SuggestRemoveSemiOrReturnBinding::RemoveAndBox {
62                    first_lo: first_span.shrink_to_lo(),
63                    first_hi: first_span.shrink_to_hi(),
64                    second_lo: second_span.shrink_to_lo(),
65                    second_hi: second_span.shrink_to_hi(),
66                    sp,
67                })
68            }
69            Some((sp, StatementAsExpression::CorrectType)) => {
70                Some(SuggestRemoveSemiOrReturnBinding::Remove { sp })
71            }
72            None => {
73                let mut ret = None;
74                for (id, ty) in [(first_id, second_ty), (second_id, first_ty)] {
75                    if let Some(id) = id
76                        && let hir::Node::Block(blk) = self.tcx.hir_node(id)
77                        && let Some(diag) = self.consider_returning_binding_diag(blk, ty)
78                    {
79                        ret = Some(diag);
80                        break;
81                    }
82                }
83                ret
84            }
85        }
86    }
87
88    pub(super) fn suggest_tuple_pattern(
89        &self,
90        cause: &ObligationCause<'tcx>,
91        exp_found: &ty::error::ExpectedFound<Ty<'tcx>>,
92        diag: &mut Diag<'_>,
93    ) {
94        // Heavily inspired by `FnCtxt::suggest_compatible_variants`, with
95        // some modifications due to that being in typeck and this being in infer.
96        if let ObligationCauseCode::Pattern { .. } = cause.code() {
97            if let ty::Adt(expected_adt, args) = exp_found.expected.kind() {
98                let compatible_variants: Vec<_> = expected_adt
99                    .variants()
100                    .iter()
101                    .filter(|variant| {
102                        variant.fields.len() == 1 && variant.ctor_kind() == Some(CtorKind::Fn)
103                    })
104                    .filter_map(|variant| {
105                        let sole_field = &variant.single_field();
106                        let sole_field_ty = sole_field.ty(self.tcx, args);
107                        if self.same_type_modulo_infer(sole_field_ty, exp_found.found) {
108                            let variant_path =
109                                with_no_trimmed_paths!(self.tcx.def_path_str(variant.def_id));
110                            // FIXME #56861: DRYer prelude filtering
111                            if let Some(path) = variant_path.strip_prefix("std::prelude::") {
112                                if let Some((_, path)) = path.split_once("::") {
113                                    return Some(path.to_string());
114                                }
115                            }
116                            Some(variant_path)
117                        } else {
118                            None
119                        }
120                    })
121                    .collect();
122                match &compatible_variants[..] {
123                    [] => {}
124                    [variant] => {
125                        let sugg = SuggestTuplePatternOne {
126                            variant: variant.to_owned(),
127                            span_low: cause.span.shrink_to_lo(),
128                            span_high: cause.span.shrink_to_hi(),
129                        };
130                        diag.subdiagnostic(sugg);
131                    }
132                    _ => {
133                        // More than one matching variant.
134                        let sugg = SuggestTuplePatternMany {
135                            path: self.tcx.def_path_str(expected_adt.did()),
136                            cause_span: cause.span,
137                            compatible_variants,
138                        };
139                        diag.subdiagnostic(sugg);
140                    }
141                }
142            }
143        }
144    }
145
146    /// A possible error is to forget to add `.await` when using futures:
147    ///
148    /// ```compile_fail,E0308
149    /// async fn make_u32() -> u32 {
150    ///     22
151    /// }
152    ///
153    /// fn take_u32(x: u32) {}
154    ///
155    /// async fn foo() {
156    ///     let x = make_u32();
157    ///     take_u32(x);
158    /// }
159    /// ```
160    ///
161    /// This routine checks if the found type `T` implements `Future<Output=U>` where `U` is the
162    /// expected type. If this is the case, and we are inside of an async body, it suggests adding
163    /// `.await` to the tail of the expression.
164    pub(super) fn suggest_await_on_expect_found(
165        &self,
166        cause: &ObligationCause<'tcx>,
167        exp_span: Span,
168        exp_found: &ty::error::ExpectedFound<Ty<'tcx>>,
169        diag: &mut Diag<'_>,
170    ) {
171        debug!(
172            "suggest_await_on_expect_found: exp_span={:?}, expected_ty={:?}, found_ty={:?}",
173            exp_span, exp_found.expected, exp_found.found,
174        );
175
176        match self.tcx.coroutine_kind(cause.body_id) {
177            Some(hir::CoroutineKind::Desugared(
178                hir::CoroutineDesugaring::Async | hir::CoroutineDesugaring::AsyncGen,
179                _,
180            )) => (),
181            None
182            | Some(
183                hir::CoroutineKind::Coroutine(_)
184                | hir::CoroutineKind::Desugared(hir::CoroutineDesugaring::Gen, _),
185            ) => return,
186        }
187
188        if let ObligationCauseCode::CompareImplItem { .. } = cause.code() {
189            return;
190        }
191
192        let subdiag = match (
193            self.get_impl_future_output_ty(exp_found.expected),
194            self.get_impl_future_output_ty(exp_found.found),
195        ) {
196            (Some(exp), Some(found)) if self.same_type_modulo_infer(exp, found) => match cause
197                .code()
198            {
199                ObligationCauseCode::IfExpression(box IfExpressionCause { then_id, .. }) => {
200                    let then_span = self.find_block_span_from_hir_id(*then_id);
201                    Some(ConsiderAddingAwait::BothFuturesSugg {
202                        first: then_span.shrink_to_hi(),
203                        second: exp_span.shrink_to_hi(),
204                    })
205                }
206                ObligationCauseCode::MatchExpressionArm(box MatchExpressionArmCause {
207                    prior_non_diverging_arms,
208                    ..
209                }) => {
210                    if let [.., arm_span] = &prior_non_diverging_arms[..] {
211                        Some(ConsiderAddingAwait::BothFuturesSugg {
212                            first: arm_span.shrink_to_hi(),
213                            second: exp_span.shrink_to_hi(),
214                        })
215                    } else {
216                        Some(ConsiderAddingAwait::BothFuturesHelp)
217                    }
218                }
219                _ => Some(ConsiderAddingAwait::BothFuturesHelp),
220            },
221            (_, Some(ty)) if self.same_type_modulo_infer(exp_found.expected, ty) => {
222                // FIXME: Seems like we can't have a suggestion and a note with different spans in a single subdiagnostic
223                diag.subdiagnostic(ConsiderAddingAwait::FutureSugg {
224                    span: exp_span.shrink_to_hi(),
225                });
226                Some(ConsiderAddingAwait::FutureSuggNote { span: exp_span })
227            }
228            (Some(ty), _) if self.same_type_modulo_infer(ty, exp_found.found) => match cause.code()
229            {
230                ObligationCauseCode::Pattern { span: Some(then_span), origin_expr, .. } => {
231                    origin_expr.is_some().then_some(ConsiderAddingAwait::FutureSugg {
232                        span: then_span.shrink_to_hi(),
233                    })
234                }
235                ObligationCauseCode::IfExpression(box IfExpressionCause { then_id, .. }) => {
236                    let then_span = self.find_block_span_from_hir_id(*then_id);
237                    Some(ConsiderAddingAwait::FutureSugg { span: then_span.shrink_to_hi() })
238                }
239                ObligationCauseCode::MatchExpressionArm(box MatchExpressionArmCause {
240                    prior_non_diverging_arms,
241                    ..
242                }) => Some({
243                    ConsiderAddingAwait::FutureSuggMultiple {
244                        spans: prior_non_diverging_arms
245                            .iter()
246                            .map(|arm| arm.shrink_to_hi())
247                            .collect(),
248                    }
249                }),
250                _ => None,
251            },
252            _ => None,
253        };
254        if let Some(subdiag) = subdiag {
255            diag.subdiagnostic(subdiag);
256        }
257    }
258
259    pub(super) fn suggest_accessing_field_where_appropriate(
260        &self,
261        cause: &ObligationCause<'tcx>,
262        exp_found: &ty::error::ExpectedFound<Ty<'tcx>>,
263        diag: &mut Diag<'_>,
264    ) {
265        debug!(
266            "suggest_accessing_field_where_appropriate(cause={:?}, exp_found={:?})",
267            cause, exp_found
268        );
269        if let ty::Adt(expected_def, expected_args) = exp_found.expected.kind() {
270            if expected_def.is_enum() {
271                return;
272            }
273
274            if let Some((name, ty)) = expected_def
275                .non_enum_variant()
276                .fields
277                .iter()
278                .filter(|field| field.vis.is_accessible_from(field.did, self.tcx))
279                .map(|field| (field.name, field.ty(self.tcx, expected_args)))
280                .find(|(_, ty)| self.same_type_modulo_infer(*ty, exp_found.found))
281            {
282                if let ObligationCauseCode::Pattern { span: Some(span), .. } = *cause.code() {
283                    if let Ok(snippet) = self.tcx.sess.source_map().span_to_snippet(span) {
284                        let suggestion = if expected_def.is_struct() {
285                            SuggestAccessingField::Safe { span, snippet, name, ty }
286                        } else if expected_def.is_union() {
287                            SuggestAccessingField::Unsafe { span, snippet, name, ty }
288                        } else {
289                            return;
290                        };
291                        diag.subdiagnostic(suggestion);
292                    }
293                }
294            }
295        }
296    }
297
298    pub(super) fn suggest_turning_stmt_into_expr(
299        &self,
300        cause: &ObligationCause<'tcx>,
301        exp_found: &ty::error::ExpectedFound<Ty<'tcx>>,
302        diag: &mut Diag<'_>,
303    ) {
304        let ty::error::ExpectedFound { expected, found } = exp_found;
305        if !found.peel_refs().is_unit() {
306            return;
307        }
308
309        let ObligationCauseCode::BlockTailExpression(hir_id, MatchSource::Normal) = cause.code()
310        else {
311            return;
312        };
313
314        let node = self.tcx.hir_node(*hir_id);
315        let mut blocks = vec![];
316        if let hir::Node::Block(block) = node
317            && let Some(expr) = block.expr
318            && let hir::ExprKind::Path(QPath::Resolved(_, Path { res, .. })) = expr.kind
319            && let Res::Local(local) = res
320            && let Node::LetStmt(LetStmt { init: Some(init), .. }) =
321                self.tcx.parent_hir_node(*local)
322        {
323            fn collect_blocks<'hir>(expr: &hir::Expr<'hir>, blocks: &mut Vec<&hir::Block<'hir>>) {
324                match expr.kind {
325                    // `blk1` and `blk2` must be have the same types, it will be reported before reaching here
326                    hir::ExprKind::If(_, blk1, Some(blk2)) => {
327                        collect_blocks(blk1, blocks);
328                        collect_blocks(blk2, blocks);
329                    }
330                    hir::ExprKind::Match(_, arms, _) => {
331                        // all arms must have same types
332                        for arm in arms.iter() {
333                            collect_blocks(arm.body, blocks);
334                        }
335                    }
336                    hir::ExprKind::Block(blk, _) => {
337                        blocks.push(blk);
338                    }
339                    _ => {}
340                }
341            }
342            collect_blocks(init, &mut blocks);
343        }
344
345        let expected_inner: Ty<'_> = expected.peel_refs();
346        for block in blocks.iter() {
347            self.consider_removing_semicolon(block, expected_inner, diag);
348        }
349    }
350
351    /// A common error is to add an extra semicolon:
352    ///
353    /// ```compile_fail,E0308
354    /// fn foo() -> usize {
355    ///     22;
356    /// }
357    /// ```
358    ///
359    /// This routine checks if the final statement in a block is an
360    /// expression with an explicit semicolon whose type is compatible
361    /// with `expected_ty`. If so, it suggests removing the semicolon.
362    pub fn consider_removing_semicolon(
363        &self,
364        blk: &'tcx hir::Block<'tcx>,
365        expected_ty: Ty<'tcx>,
366        diag: &mut Diag<'_>,
367    ) -> bool {
368        if let Some((span_semi, boxed)) = self.could_remove_semicolon(blk, expected_ty) {
369            if let StatementAsExpression::NeedsBoxing = boxed {
370                diag.span_suggestion_verbose(
371                    span_semi,
372                    "consider removing this semicolon and boxing the expression",
373                    "",
374                    Applicability::HasPlaceholders,
375                );
376            } else {
377                diag.span_suggestion_short(
378                    span_semi,
379                    "remove this semicolon to return this value",
380                    "",
381                    Applicability::MachineApplicable,
382                );
383            }
384            true
385        } else {
386            false
387        }
388    }
389
390    pub(crate) fn suggest_function_pointers_impl(
391        &self,
392        span: Option<Span>,
393        exp_found: &ty::error::ExpectedFound<Ty<'tcx>>,
394        diag: &mut Diag<'_>,
395    ) {
396        let ty::error::ExpectedFound { expected, found } = exp_found;
397        let expected_inner = expected.peel_refs();
398        let found_inner = found.peel_refs();
399        if !expected_inner.is_fn() || !found_inner.is_fn() {
400            return;
401        }
402        match (expected_inner.kind(), found_inner.kind()) {
403            (ty::FnPtr(sig_tys, hdr), ty::FnDef(did, args)) => {
404                let sig = sig_tys.with(*hdr);
405                let expected_sig = &(self.normalize_fn_sig)(sig);
406                let found_sig =
407                    &(self.normalize_fn_sig)(self.tcx.fn_sig(*did).instantiate(self.tcx, args));
408
409                let fn_name = self.tcx.def_path_str_with_args(*did, args);
410
411                if !self.same_type_modulo_infer(*found_sig, *expected_sig)
412                    || !sig.is_suggestable(self.tcx, true)
413                    || self.tcx.intrinsic(*did).is_some()
414                {
415                    return;
416                }
417
418                let Some(span) = span else {
419                    let casting = format!("{fn_name} as {sig}");
420                    diag.subdiagnostic(FnItemsAreDistinct);
421                    diag.subdiagnostic(FnConsiderCasting { casting });
422                    return;
423                };
424
425                let sugg = match (expected.is_ref(), found.is_ref()) {
426                    (true, false) => {
427                        FunctionPointerSuggestion::UseRef { span: span.shrink_to_lo() }
428                    }
429                    (false, true) => FunctionPointerSuggestion::RemoveRef { span, fn_name },
430                    (true, true) => {
431                        diag.subdiagnostic(FnItemsAreDistinct);
432                        FunctionPointerSuggestion::CastRef { span, fn_name, sig }
433                    }
434                    (false, false) => {
435                        diag.subdiagnostic(FnItemsAreDistinct);
436                        FunctionPointerSuggestion::Cast { span: span.shrink_to_hi(), sig }
437                    }
438                };
439                diag.subdiagnostic(sugg);
440            }
441            (ty::FnDef(did1, args1), ty::FnDef(did2, args2)) => {
442                let expected_sig =
443                    &(self.normalize_fn_sig)(self.tcx.fn_sig(*did1).instantiate(self.tcx, args1));
444                let found_sig =
445                    &(self.normalize_fn_sig)(self.tcx.fn_sig(*did2).instantiate(self.tcx, args2));
446
447                if self.same_type_modulo_infer(*expected_sig, *found_sig) {
448                    diag.subdiagnostic(FnUniqTypes);
449                }
450
451                if !self.same_type_modulo_infer(*found_sig, *expected_sig)
452                    || !found_sig.is_suggestable(self.tcx, true)
453                    || !expected_sig.is_suggestable(self.tcx, true)
454                    || self.tcx.intrinsic(*did1).is_some()
455                    || self.tcx.intrinsic(*did2).is_some()
456                {
457                    return;
458                }
459
460                let fn_name = self.tcx.def_path_str_with_args(*did2, args2);
461
462                let Some(span) = span else {
463                    diag.subdiagnostic(FnConsiderCastingBoth { sig: *expected_sig });
464                    return;
465                };
466
467                let sug = if found.is_ref() {
468                    FunctionPointerSuggestion::CastBothRef {
469                        span,
470                        fn_name,
471                        found_sig: *found_sig,
472                        expected_sig: *expected_sig,
473                    }
474                } else {
475                    FunctionPointerSuggestion::CastBoth {
476                        span: span.shrink_to_hi(),
477                        found_sig: *found_sig,
478                        expected_sig: *expected_sig,
479                    }
480                };
481
482                diag.subdiagnostic(sug);
483            }
484            (ty::FnDef(did, args), ty::FnPtr(sig_tys, hdr)) => {
485                let expected_sig =
486                    &(self.normalize_fn_sig)(self.tcx.fn_sig(*did).instantiate(self.tcx, args));
487                let found_sig = &(self.normalize_fn_sig)(sig_tys.with(*hdr));
488
489                if !self.same_type_modulo_infer(*found_sig, *expected_sig) {
490                    return;
491                }
492
493                let fn_name = self.tcx.def_path_str_with_args(*did, args);
494
495                let casting = if expected.is_ref() {
496                    format!("&({fn_name} as {found_sig})")
497                } else {
498                    format!("{fn_name} as {found_sig}")
499                };
500
501                diag.subdiagnostic(FnConsiderCasting { casting });
502            }
503            _ => {
504                return;
505            }
506        };
507    }
508
509    pub(super) fn suggest_function_pointers(
510        &self,
511        cause: &ObligationCause<'tcx>,
512        span: Span,
513        exp_found: &ty::error::ExpectedFound<Ty<'tcx>>,
514        terr: TypeError<'tcx>,
515        diag: &mut Diag<'_>,
516    ) {
517        debug!("suggest_function_pointers(cause={:?}, exp_found={:?})", cause, exp_found);
518
519        if exp_found.expected.peel_refs().is_fn() && exp_found.found.peel_refs().is_fn() {
520            self.suggest_function_pointers_impl(Some(span), exp_found, diag);
521        } else if let TypeError::Sorts(exp_found) = terr {
522            self.suggest_function_pointers_impl(None, &exp_found, diag);
523        }
524    }
525
526    fn should_suggest_as_ref_kind(
527        &self,
528        expected: Ty<'tcx>,
529        found: Ty<'tcx>,
530    ) -> Option<SuggestAsRefKind> {
531        if let (ty::Adt(exp_def, exp_args), ty::Ref(_, found_ty, _)) =
532            (expected.kind(), found.kind())
533        {
534            if let ty::Adt(found_def, found_args) = *found_ty.kind() {
535                if exp_def == &found_def {
536                    let have_as_ref = &[
537                        (sym::Option, SuggestAsRefKind::Option),
538                        (sym::Result, SuggestAsRefKind::Result),
539                    ];
540                    if let Some(msg) = have_as_ref.iter().find_map(|(name, msg)| {
541                        self.tcx.is_diagnostic_item(*name, exp_def.did()).then_some(msg)
542                    }) {
543                        let mut show_suggestion = true;
544                        for (exp_ty, found_ty) in
545                            std::iter::zip(exp_args.types(), found_args.types())
546                        {
547                            match *exp_ty.kind() {
548                                ty::Ref(_, exp_ty, _) => {
549                                    match (exp_ty.kind(), found_ty.kind()) {
550                                        (_, ty::Param(_))
551                                        | (_, ty::Infer(_))
552                                        | (ty::Param(_), _)
553                                        | (ty::Infer(_), _) => {}
554                                        _ if self.same_type_modulo_infer(exp_ty, found_ty) => {}
555                                        _ => show_suggestion = false,
556                                    };
557                                }
558                                ty::Param(_) | ty::Infer(_) => {}
559                                _ => show_suggestion = false,
560                            }
561                        }
562                        if show_suggestion {
563                            return Some(*msg);
564                        }
565                    }
566                }
567            }
568        }
569        None
570    }
571
572    // FIXME: Remove once `rustc_hir_typeck` is migrated to diagnostic structs
573    pub fn should_suggest_as_ref(&self, expected: Ty<'tcx>, found: Ty<'tcx>) -> Option<&str> {
574        match self.should_suggest_as_ref_kind(expected, found) {
575            Some(SuggestAsRefKind::Option) => Some(
576                "you can convert from `&Option<T>` to `Option<&T>` using \
577            `.as_ref()`",
578            ),
579            Some(SuggestAsRefKind::Result) => Some(
580                "you can convert from `&Result<T, E>` to \
581            `Result<&T, &E>` using `.as_ref()`",
582            ),
583            None => None,
584        }
585    }
586    /// Try to find code with pattern `if Some(..) = expr`
587    /// use a `visitor` to mark the `if` which its span contains given error span,
588    /// and then try to find a assignment in the `cond` part, which span is equal with error span
589    pub(super) fn suggest_let_for_letchains(
590        &self,
591        cause: &ObligationCause<'_>,
592        span: Span,
593    ) -> Option<TypeErrorAdditionalDiags> {
594        /// Find the if expression with given span
595        struct IfVisitor {
596            found_if: bool,
597            err_span: Span,
598        }
599
600        impl<'v> Visitor<'v> for IfVisitor {
601            type Result = ControlFlow<()>;
602            fn visit_expr(&mut self, ex: &'v hir::Expr<'v>) -> Self::Result {
603                match ex.kind {
604                    hir::ExprKind::If(cond, _, _) => {
605                        self.found_if = true;
606                        walk_expr(self, cond)?;
607                        self.found_if = false;
608                        ControlFlow::Continue(())
609                    }
610                    _ => walk_expr(self, ex),
611                }
612            }
613
614            fn visit_stmt(&mut self, ex: &'v hir::Stmt<'v>) -> Self::Result {
615                if let hir::StmtKind::Let(LetStmt {
616                    span,
617                    pat: hir::Pat { .. },
618                    ty: None,
619                    init: Some(_),
620                    ..
621                }) = &ex.kind
622                    && self.found_if
623                    && span.eq(&self.err_span)
624                {
625                    ControlFlow::Break(())
626                } else {
627                    walk_stmt(self, ex)
628                }
629            }
630        }
631
632        self.tcx.hir_maybe_body_owned_by(cause.body_id).and_then(|body| {
633            IfVisitor { err_span: span, found_if: false }
634                .visit_body(&body)
635                .is_break()
636                .then(|| TypeErrorAdditionalDiags::AddLetForLetChains { span: span.shrink_to_lo() })
637        })
638    }
639
640    /// For "one type is more general than the other" errors on closures, suggest changing the lifetime
641    /// of the parameters to accept all lifetimes.
642    pub(super) fn suggest_for_all_lifetime_closure(
643        &self,
644        span: Span,
645        hir: hir::Node<'_>,
646        exp_found: &ty::error::ExpectedFound<ty::TraitRef<'tcx>>,
647        diag: &mut Diag<'_>,
648    ) {
649        // 0. Extract fn_decl from hir
650        let hir::Node::Expr(hir::Expr {
651            kind: hir::ExprKind::Closure(hir::Closure { body, fn_decl, .. }),
652            ..
653        }) = hir
654        else {
655            return;
656        };
657        let hir::Body { params, .. } = self.tcx.hir_body(*body);
658
659        // 1. Get the args of the closure.
660        // 2. Assume exp_found is FnOnce / FnMut / Fn, we can extract function parameters from [1].
661        let Some(expected) = exp_found.expected.args.get(1) else {
662            return;
663        };
664        let Some(found) = exp_found.found.args.get(1) else {
665            return;
666        };
667        let expected = expected.unpack();
668        let found = found.unpack();
669        // 3. Extract the tuple type from Fn trait and suggest the change.
670        if let GenericArgKind::Type(expected) = expected
671            && let GenericArgKind::Type(found) = found
672            && let ty::Tuple(expected) = expected.kind()
673            && let ty::Tuple(found) = found.kind()
674            && expected.len() == found.len()
675        {
676            let mut suggestion = "|".to_string();
677            let mut is_first = true;
678            let mut has_suggestion = false;
679
680            for (((expected, found), param_hir), arg_hir) in
681                expected.iter().zip(found.iter()).zip(params.iter()).zip(fn_decl.inputs.iter())
682            {
683                if is_first {
684                    is_first = false;
685                } else {
686                    suggestion += ", ";
687                }
688
689                if let ty::Ref(expected_region, _, _) = expected.kind()
690                    && let ty::Ref(found_region, _, _) = found.kind()
691                    && expected_region.is_bound()
692                    && !found_region.is_bound()
693                    && let hir::TyKind::Infer(()) = arg_hir.kind
694                {
695                    // If the expected region is late bound, the found region is not, and users are asking compiler
696                    // to infer the type, we can suggest adding `: &_`.
697                    if param_hir.pat.span == param_hir.ty_span {
698                        // for `|x|`, `|_|`, `|x: impl Foo|`
699                        let Ok(pat) =
700                            self.tcx.sess.source_map().span_to_snippet(param_hir.pat.span)
701                        else {
702                            return;
703                        };
704                        suggestion += &format!("{pat}: &_");
705                    } else {
706                        // for `|x: ty|`, `|_: ty|`
707                        let Ok(pat) =
708                            self.tcx.sess.source_map().span_to_snippet(param_hir.pat.span)
709                        else {
710                            return;
711                        };
712                        let Ok(ty) = self.tcx.sess.source_map().span_to_snippet(param_hir.ty_span)
713                        else {
714                            return;
715                        };
716                        suggestion += &format!("{pat}: &{ty}");
717                    }
718                    has_suggestion = true;
719                } else {
720                    let Ok(arg) = self.tcx.sess.source_map().span_to_snippet(param_hir.span) else {
721                        return;
722                    };
723                    // Otherwise, keep it as-is.
724                    suggestion += &arg;
725                }
726            }
727            suggestion += "|";
728
729            if has_suggestion {
730                diag.span_suggestion_verbose(
731                    span,
732                    "consider specifying the type of the closure parameters",
733                    suggestion,
734                    Applicability::MaybeIncorrect,
735                );
736            }
737        }
738    }
739}
740
741impl<'tcx> TypeErrCtxt<'_, 'tcx> {
742    /// Be helpful when the user wrote `{... expr; }` and taking the `;` off
743    /// is enough to fix the error.
744    fn could_remove_semicolon(
745        &self,
746        blk: &'tcx hir::Block<'tcx>,
747        expected_ty: Ty<'tcx>,
748    ) -> Option<(Span, StatementAsExpression)> {
749        let blk = blk.innermost_block();
750        // Do not suggest if we have a tail expr.
751        if blk.expr.is_some() {
752            return None;
753        }
754        let last_stmt = blk.stmts.last()?;
755        let hir::StmtKind::Semi(last_expr) = last_stmt.kind else {
756            return None;
757        };
758        let last_expr_ty = self.typeck_results.as_ref()?.expr_ty_opt(last_expr)?;
759        let needs_box = match (last_expr_ty.kind(), expected_ty.kind()) {
760            _ if last_expr_ty.references_error() => return None,
761            _ if self.same_type_modulo_infer(last_expr_ty, expected_ty) => {
762                StatementAsExpression::CorrectType
763            }
764            (
765                ty::Alias(ty::Opaque, ty::AliasTy { def_id: last_def_id, .. }),
766                ty::Alias(ty::Opaque, ty::AliasTy { def_id: exp_def_id, .. }),
767            ) if last_def_id == exp_def_id => StatementAsExpression::CorrectType,
768            (
769                ty::Alias(ty::Opaque, ty::AliasTy { def_id: last_def_id, args: last_bounds, .. }),
770                ty::Alias(ty::Opaque, ty::AliasTy { def_id: exp_def_id, args: exp_bounds, .. }),
771            ) => {
772                debug!(
773                    "both opaque, likely future {:?} {:?} {:?} {:?}",
774                    last_def_id, last_bounds, exp_def_id, exp_bounds
775                );
776
777                let last_local_id = last_def_id.as_local()?;
778                let exp_local_id = exp_def_id.as_local()?;
779
780                match (
781                    &self.tcx.hir_expect_opaque_ty(last_local_id),
782                    &self.tcx.hir_expect_opaque_ty(exp_local_id),
783                ) {
784                    (
785                        hir::OpaqueTy { bounds: last_bounds, .. },
786                        hir::OpaqueTy { bounds: exp_bounds, .. },
787                    ) if std::iter::zip(*last_bounds, *exp_bounds).all(|(left, right)| match (
788                        left, right,
789                    ) {
790                        // FIXME: Suspicious
791                        (hir::GenericBound::Trait(tl), hir::GenericBound::Trait(tr))
792                            if tl.trait_ref.trait_def_id() == tr.trait_ref.trait_def_id()
793                                && tl.modifiers == tr.modifiers =>
794                        {
795                            true
796                        }
797                        _ => false,
798                    }) =>
799                    {
800                        StatementAsExpression::NeedsBoxing
801                    }
802                    _ => StatementAsExpression::CorrectType,
803                }
804            }
805            _ => return None,
806        };
807        let span = if last_stmt.span.from_expansion() {
808            let mac_call = rustc_span::source_map::original_sp(last_stmt.span, blk.span);
809            self.tcx.sess.source_map().mac_call_stmt_semi_span(mac_call)?
810        } else {
811            self.tcx
812                .sess
813                .source_map()
814                .span_extend_while_whitespace(last_expr.span)
815                .shrink_to_hi()
816                .with_hi(last_stmt.span.hi())
817        };
818
819        Some((span, needs_box))
820    }
821
822    /// Suggest returning a local binding with a compatible type if the block
823    /// has no return expression.
824    fn consider_returning_binding_diag(
825        &self,
826        blk: &'tcx hir::Block<'tcx>,
827        expected_ty: Ty<'tcx>,
828    ) -> Option<SuggestRemoveSemiOrReturnBinding> {
829        let blk = blk.innermost_block();
830        // Do not suggest if we have a tail expr.
831        if blk.expr.is_some() {
832            return None;
833        }
834        let mut shadowed = FxIndexSet::default();
835        let mut candidate_idents = vec![];
836        let mut find_compatible_candidates = |pat: &hir::Pat<'_>| {
837            if let hir::PatKind::Binding(_, hir_id, ident, _) = &pat.kind
838                && let Some(pat_ty) = self
839                    .typeck_results
840                    .as_ref()
841                    .and_then(|typeck_results| typeck_results.node_type_opt(*hir_id))
842            {
843                let pat_ty = self.resolve_vars_if_possible(pat_ty);
844                if self.same_type_modulo_infer(pat_ty, expected_ty)
845                    && !(pat_ty, expected_ty).references_error()
846                    && shadowed.insert(ident.name)
847                {
848                    candidate_idents.push((*ident, pat_ty));
849                }
850            }
851            true
852        };
853
854        for stmt in blk.stmts.iter().rev() {
855            let hir::StmtKind::Let(local) = &stmt.kind else {
856                continue;
857            };
858            local.pat.walk(&mut find_compatible_candidates);
859        }
860        match self.tcx.parent_hir_node(blk.hir_id) {
861            hir::Node::Expr(hir::Expr { hir_id, .. }) => match self.tcx.parent_hir_node(*hir_id) {
862                hir::Node::Arm(hir::Arm { pat, .. }) => {
863                    pat.walk(&mut find_compatible_candidates);
864                }
865
866                hir::Node::Item(hir::Item { kind: hir::ItemKind::Fn { body, .. }, .. })
867                | hir::Node::ImplItem(hir::ImplItem {
868                    kind: hir::ImplItemKind::Fn(_, body), ..
869                })
870                | hir::Node::TraitItem(hir::TraitItem {
871                    kind: hir::TraitItemKind::Fn(_, hir::TraitFn::Provided(body)),
872                    ..
873                })
874                | hir::Node::Expr(hir::Expr {
875                    kind: hir::ExprKind::Closure(hir::Closure { body, .. }),
876                    ..
877                }) => {
878                    for param in self.tcx.hir_body(*body).params {
879                        param.pat.walk(&mut find_compatible_candidates);
880                    }
881                }
882                hir::Node::Expr(hir::Expr {
883                    kind:
884                        hir::ExprKind::If(
885                            hir::Expr { kind: hir::ExprKind::Let(let_), .. },
886                            then_block,
887                            _,
888                        ),
889                    ..
890                }) if then_block.hir_id == *hir_id => {
891                    let_.pat.walk(&mut find_compatible_candidates);
892                }
893                _ => {}
894            },
895            _ => {}
896        }
897
898        match &candidate_idents[..] {
899            [(ident, _ty)] => {
900                let sm = self.tcx.sess.source_map();
901                let (span, sugg) = if let Some(stmt) = blk.stmts.last() {
902                    let stmt_span = sm.stmt_span(stmt.span, blk.span);
903                    let sugg = if sm.is_multiline(blk.span)
904                        && let Some(spacing) = sm.indentation_before(stmt_span)
905                    {
906                        format!("\n{spacing}{ident}")
907                    } else {
908                        format!(" {ident}")
909                    };
910                    (stmt_span.shrink_to_hi(), sugg)
911                } else {
912                    let sugg = if sm.is_multiline(blk.span)
913                        && let Some(spacing) = sm.indentation_before(blk.span.shrink_to_lo())
914                    {
915                        format!("\n{spacing}    {ident}\n{spacing}")
916                    } else {
917                        format!(" {ident} ")
918                    };
919                    let left_span = sm.span_through_char(blk.span, '{').shrink_to_hi();
920                    (sm.span_extend_while_whitespace(left_span), sugg)
921                };
922                Some(SuggestRemoveSemiOrReturnBinding::Add { sp: span, code: sugg, ident: *ident })
923            }
924            values if (1..3).contains(&values.len()) => {
925                let spans = values.iter().map(|(ident, _)| ident.span).collect::<Vec<_>>();
926                Some(SuggestRemoveSemiOrReturnBinding::AddOne { spans: spans.into() })
927            }
928            _ => None,
929        }
930    }
931
932    pub fn consider_returning_binding(
933        &self,
934        blk: &'tcx hir::Block<'tcx>,
935        expected_ty: Ty<'tcx>,
936        err: &mut Diag<'_>,
937    ) -> bool {
938        let diag = self.consider_returning_binding_diag(blk, expected_ty);
939        match diag {
940            Some(diag) => {
941                err.subdiagnostic(diag);
942                true
943            }
944            None => false,
945        }
946    }
947}