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