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