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            if let ty::Adt(expected_adt, args) = exp_found.expected.kind() {
96                let compatible_variants: Vec<_> = expected_adt
97                    .variants()
98                    .iter()
99                    .filter(|variant| {
100                        variant.fields.len() == 1 && variant.ctor_kind() == Some(CtorKind::Fn)
101                    })
102                    .filter_map(|variant| {
103                        let sole_field = &variant.single_field();
104                        let sole_field_ty = sole_field.ty(self.tcx, args);
105                        if self.same_type_modulo_infer(sole_field_ty, exp_found.found) {
106                            let variant_path =
107                                with_no_trimmed_paths!(self.tcx.def_path_str(variant.def_id));
108                            // FIXME #56861: DRYer prelude filtering
109                            if let Some(path) = variant_path.strip_prefix("std::prelude::") {
110                                if let Some((_, path)) = path.split_once("::") {
111                                    return Some(path.to_string());
112                                }
113                            }
114                            Some(variant_path)
115                        } else {
116                            None
117                        }
118                    })
119                    .collect();
120                match &compatible_variants[..] {
121                    [] => {}
122                    [variant] => {
123                        let sugg = SuggestTuplePatternOne {
124                            variant: variant.to_owned(),
125                            span_low: cause.span.shrink_to_lo(),
126                            span_high: cause.span.shrink_to_hi(),
127                        };
128                        diag.subdiagnostic(sugg);
129                    }
130                    _ => {
131                        // More than one matching variant.
132                        let sugg = SuggestTuplePatternMany {
133                            path: self.tcx.def_path_str(expected_adt.did()),
134                            cause_span: cause.span,
135                            compatible_variants,
136                        };
137                        diag.subdiagnostic(sugg);
138                    }
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            {
292                if let ObligationCauseCode::Pattern { span: Some(span), .. } = *cause.code() {
293                    if let Ok(snippet) = self.tcx.sess.source_map().span_to_snippet(span) {
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    }
307
308    pub(super) fn suggest_turning_stmt_into_expr(
309        &self,
310        cause: &ObligationCause<'tcx>,
311        exp_found: &ty::error::ExpectedFound<Ty<'tcx>>,
312        diag: &mut Diag<'_>,
313    ) {
314        let ty::error::ExpectedFound { expected, found } = exp_found;
315        if !found.peel_refs().is_unit() {
316            return;
317        }
318
319        let ObligationCauseCode::BlockTailExpression(hir_id, MatchSource::Normal) = cause.code()
320        else {
321            return;
322        };
323
324        let node = self.tcx.hir_node(*hir_id);
325        let mut blocks = vec![];
326        if let hir::Node::Block(block) = node
327            && let Some(expr) = block.expr
328            && let hir::ExprKind::Path(QPath::Resolved(_, Path { res, .. })) = expr.kind
329            && let Res::Local(local) = res
330            && let Node::LetStmt(LetStmt { init: Some(init), .. }) =
331                self.tcx.parent_hir_node(*local)
332        {
333            fn collect_blocks<'hir>(expr: &hir::Expr<'hir>, blocks: &mut Vec<&hir::Block<'hir>>) {
334                match expr.kind {
335                    // `blk1` and `blk2` must be have the same types, it will be reported before reaching here
336                    hir::ExprKind::If(_, blk1, Some(blk2)) => {
337                        collect_blocks(blk1, blocks);
338                        collect_blocks(blk2, blocks);
339                    }
340                    hir::ExprKind::Match(_, arms, _) => {
341                        // all arms must have same types
342                        for arm in arms.iter() {
343                            collect_blocks(arm.body, blocks);
344                        }
345                    }
346                    hir::ExprKind::Block(blk, _) => {
347                        blocks.push(blk);
348                    }
349                    _ => {}
350                }
351            }
352            collect_blocks(init, &mut blocks);
353        }
354
355        let expected_inner: Ty<'_> = expected.peel_refs();
356        for block in blocks.iter() {
357            self.consider_removing_semicolon(block, expected_inner, diag);
358        }
359    }
360
361    /// A common error is to add an extra semicolon:
362    ///
363    /// ```compile_fail,E0308
364    /// fn foo() -> usize {
365    ///     22;
366    /// }
367    /// ```
368    ///
369    /// This routine checks if the final statement in a block is an
370    /// expression with an explicit semicolon whose type is compatible
371    /// with `expected_ty`. If so, it suggests removing the semicolon.
372    pub fn consider_removing_semicolon(
373        &self,
374        blk: &'tcx hir::Block<'tcx>,
375        expected_ty: Ty<'tcx>,
376        diag: &mut Diag<'_>,
377    ) -> bool {
378        if let Some((span_semi, boxed)) = self.could_remove_semicolon(blk, expected_ty) {
379            if let StatementAsExpression::NeedsBoxing = boxed {
380                diag.span_suggestion_verbose(
381                    span_semi,
382                    "consider removing this semicolon and boxing the expression",
383                    "",
384                    Applicability::HasPlaceholders,
385                );
386            } else {
387                diag.span_suggestion_short(
388                    span_semi,
389                    "remove this semicolon to return this value",
390                    "",
391                    Applicability::MachineApplicable,
392                );
393            }
394            true
395        } else {
396            false
397        }
398    }
399
400    pub(crate) fn suggest_function_pointers_impl(
401        &self,
402        span: Option<Span>,
403        exp_found: &ty::error::ExpectedFound<Ty<'tcx>>,
404        diag: &mut Diag<'_>,
405    ) {
406        let ty::error::ExpectedFound { expected, found } = exp_found;
407        let expected_inner = expected.peel_refs();
408        let found_inner = found.peel_refs();
409        if !expected_inner.is_fn() || !found_inner.is_fn() {
410            return;
411        }
412        match (expected_inner.kind(), found_inner.kind()) {
413            (ty::FnPtr(sig_tys, hdr), ty::FnDef(did, args)) => {
414                let sig = sig_tys.with(*hdr);
415                let expected_sig = &(self.normalize_fn_sig)(sig);
416                let found_sig =
417                    &(self.normalize_fn_sig)(self.tcx.fn_sig(*did).instantiate(self.tcx, args));
418
419                let fn_name = self.tcx.def_path_str_with_args(*did, args);
420
421                if !self.same_type_modulo_infer(*found_sig, *expected_sig)
422                    || !sig.is_suggestable(self.tcx, true)
423                    || self.tcx.intrinsic(*did).is_some()
424                {
425                    return;
426                }
427
428                let Some(span) = span else {
429                    let casting = format!("{fn_name} as {sig}");
430                    diag.subdiagnostic(FnItemsAreDistinct);
431                    diag.subdiagnostic(FnConsiderCasting { casting });
432                    return;
433                };
434
435                let sugg = match (expected.is_ref(), found.is_ref()) {
436                    (true, false) => {
437                        FunctionPointerSuggestion::UseRef { span: span.shrink_to_lo() }
438                    }
439                    (false, true) => FunctionPointerSuggestion::RemoveRef { span, fn_name },
440                    (true, true) => {
441                        diag.subdiagnostic(FnItemsAreDistinct);
442                        FunctionPointerSuggestion::CastRef { span, fn_name, sig }
443                    }
444                    (false, false) => {
445                        diag.subdiagnostic(FnItemsAreDistinct);
446                        FunctionPointerSuggestion::Cast { span: span.shrink_to_hi(), sig }
447                    }
448                };
449                diag.subdiagnostic(sugg);
450            }
451            (ty::FnDef(did1, args1), ty::FnDef(did2, args2)) => {
452                let expected_sig =
453                    &(self.normalize_fn_sig)(self.tcx.fn_sig(*did1).instantiate(self.tcx, args1));
454                let found_sig =
455                    &(self.normalize_fn_sig)(self.tcx.fn_sig(*did2).instantiate(self.tcx, args2));
456
457                if self.same_type_modulo_infer(*expected_sig, *found_sig) {
458                    diag.subdiagnostic(FnUniqTypes);
459                }
460
461                if !self.same_type_modulo_infer(*found_sig, *expected_sig)
462                    || !found_sig.is_suggestable(self.tcx, true)
463                    || !expected_sig.is_suggestable(self.tcx, true)
464                    || self.tcx.intrinsic(*did1).is_some()
465                    || self.tcx.intrinsic(*did2).is_some()
466                {
467                    return;
468                }
469
470                let fn_name = self.tcx.def_path_str_with_args(*did2, args2);
471
472                let Some(span) = span else {
473                    diag.subdiagnostic(FnConsiderCastingBoth { sig: *expected_sig });
474                    return;
475                };
476
477                let sug = if found.is_ref() {
478                    FunctionPointerSuggestion::CastBothRef {
479                        span,
480                        fn_name,
481                        found_sig: *found_sig,
482                        expected_sig: *expected_sig,
483                    }
484                } else {
485                    FunctionPointerSuggestion::CastBoth {
486                        span: span.shrink_to_hi(),
487                        found_sig: *found_sig,
488                        expected_sig: *expected_sig,
489                    }
490                };
491
492                diag.subdiagnostic(sug);
493            }
494            (ty::FnDef(did, args), ty::FnPtr(sig_tys, hdr)) => {
495                let expected_sig =
496                    &(self.normalize_fn_sig)(self.tcx.fn_sig(*did).instantiate(self.tcx, args));
497                let found_sig = &(self.normalize_fn_sig)(sig_tys.with(*hdr));
498
499                if !self.same_type_modulo_infer(*found_sig, *expected_sig) {
500                    return;
501                }
502
503                let fn_name = self.tcx.def_path_str_with_args(*did, args);
504
505                let casting = if expected.is_ref() {
506                    format!("&({fn_name} as {found_sig})")
507                } else {
508                    format!("{fn_name} as {found_sig}")
509                };
510
511                diag.subdiagnostic(FnConsiderCasting { casting });
512            }
513            _ => {
514                return;
515            }
516        };
517    }
518
519    pub(super) fn suggest_function_pointers(
520        &self,
521        cause: &ObligationCause<'tcx>,
522        span: Span,
523        exp_found: &ty::error::ExpectedFound<Ty<'tcx>>,
524        terr: TypeError<'tcx>,
525        diag: &mut Diag<'_>,
526    ) {
527        debug!("suggest_function_pointers(cause={:?}, exp_found={:?})", cause, exp_found);
528
529        if exp_found.expected.peel_refs().is_fn() && exp_found.found.peel_refs().is_fn() {
530            self.suggest_function_pointers_impl(Some(span), exp_found, diag);
531        } else if let TypeError::Sorts(exp_found) = terr {
532            self.suggest_function_pointers_impl(None, &exp_found, diag);
533        }
534    }
535
536    fn should_suggest_as_ref_kind(
537        &self,
538        expected: Ty<'tcx>,
539        found: Ty<'tcx>,
540    ) -> Option<SuggestAsRefKind> {
541        if let (ty::Adt(exp_def, exp_args), ty::Ref(_, found_ty, _)) =
542            (expected.kind(), found.kind())
543        {
544            if let ty::Adt(found_def, found_args) = *found_ty.kind() {
545                if exp_def == &found_def {
546                    let have_as_ref = &[
547                        (sym::Option, SuggestAsRefKind::Option),
548                        (sym::Result, SuggestAsRefKind::Result),
549                    ];
550                    if let Some(msg) = have_as_ref.iter().find_map(|(name, msg)| {
551                        self.tcx.is_diagnostic_item(*name, exp_def.did()).then_some(msg)
552                    }) {
553                        let mut show_suggestion = true;
554                        for (exp_ty, found_ty) in
555                            std::iter::zip(exp_args.types(), found_args.types())
556                        {
557                            match *exp_ty.kind() {
558                                ty::Ref(_, exp_ty, _) => {
559                                    match (exp_ty.kind(), found_ty.kind()) {
560                                        (_, ty::Param(_))
561                                        | (_, ty::Infer(_))
562                                        | (ty::Param(_), _)
563                                        | (ty::Infer(_), _) => {}
564                                        _ if self.same_type_modulo_infer(exp_ty, found_ty) => {}
565                                        _ => show_suggestion = false,
566                                    };
567                                }
568                                ty::Param(_) | ty::Infer(_) => {}
569                                _ => show_suggestion = false,
570                            }
571                        }
572                        if show_suggestion {
573                            return Some(*msg);
574                        }
575                    }
576                }
577            }
578        }
579        None
580    }
581
582    // FIXME: Remove once `rustc_hir_typeck` is migrated to diagnostic structs
583    pub fn should_suggest_as_ref(&self, expected: Ty<'tcx>, found: Ty<'tcx>) -> Option<&str> {
584        match self.should_suggest_as_ref_kind(expected, found) {
585            Some(SuggestAsRefKind::Option) => Some(
586                "you can convert from `&Option<T>` to `Option<&T>` using \
587            `.as_ref()`",
588            ),
589            Some(SuggestAsRefKind::Result) => Some(
590                "you can convert from `&Result<T, E>` to \
591            `Result<&T, &E>` using `.as_ref()`",
592            ),
593            None => None,
594        }
595    }
596    /// Try to find code with pattern `if Some(..) = expr`
597    /// use a `visitor` to mark the `if` which its span contains given error span,
598    /// and then try to find a assignment in the `cond` part, which span is equal with error span
599    pub(super) fn suggest_let_for_letchains(
600        &self,
601        cause: &ObligationCause<'_>,
602        span: Span,
603    ) -> Option<TypeErrorAdditionalDiags> {
604        /// Find the if expression with given span
605        struct IfVisitor {
606            found_if: bool,
607            err_span: Span,
608        }
609
610        impl<'v> Visitor<'v> for IfVisitor {
611            type Result = ControlFlow<()>;
612            fn visit_expr(&mut self, ex: &'v hir::Expr<'v>) -> Self::Result {
613                match ex.kind {
614                    hir::ExprKind::If(cond, _, _) => {
615                        self.found_if = true;
616                        walk_expr(self, cond)?;
617                        self.found_if = false;
618                        ControlFlow::Continue(())
619                    }
620                    _ => walk_expr(self, ex),
621                }
622            }
623
624            fn visit_stmt(&mut self, ex: &'v hir::Stmt<'v>) -> Self::Result {
625                if let hir::StmtKind::Let(LetStmt {
626                    span,
627                    pat: hir::Pat { .. },
628                    ty: None,
629                    init: Some(_),
630                    ..
631                }) = &ex.kind
632                    && self.found_if
633                    && span.eq(&self.err_span)
634                {
635                    ControlFlow::Break(())
636                } else {
637                    walk_stmt(self, ex)
638                }
639            }
640        }
641
642        self.tcx.hir_maybe_body_owned_by(cause.body_id).and_then(|body| {
643            IfVisitor { err_span: span, found_if: false }
644                .visit_body(&body)
645                .is_break()
646                .then(|| TypeErrorAdditionalDiags::AddLetForLetChains { span: span.shrink_to_lo() })
647        })
648    }
649
650    /// For "one type is more general than the other" errors on closures, suggest changing the lifetime
651    /// of the parameters to accept all lifetimes.
652    pub(super) fn suggest_for_all_lifetime_closure(
653        &self,
654        span: Span,
655        hir: hir::Node<'_>,
656        exp_found: &ty::error::ExpectedFound<ty::TraitRef<'tcx>>,
657        diag: &mut Diag<'_>,
658    ) {
659        // 0. Extract fn_decl from hir
660        let hir::Node::Expr(hir::Expr {
661            kind: hir::ExprKind::Closure(hir::Closure { body, fn_decl, .. }),
662            ..
663        }) = hir
664        else {
665            return;
666        };
667        let hir::Body { params, .. } = self.tcx.hir_body(*body);
668
669        // 1. Get the args of the closure.
670        // 2. Assume exp_found is FnOnce / FnMut / Fn, we can extract function parameters from [1].
671        let Some(expected) = exp_found.expected.args.get(1) else {
672            return;
673        };
674        let Some(found) = exp_found.found.args.get(1) else {
675            return;
676        };
677        let expected = expected.kind();
678        let found = found.kind();
679        // 3. Extract the tuple type from Fn trait and suggest the change.
680        if let GenericArgKind::Type(expected) = expected
681            && let GenericArgKind::Type(found) = found
682            && let ty::Tuple(expected) = expected.kind()
683            && let ty::Tuple(found) = found.kind()
684            && expected.len() == found.len()
685        {
686            let mut suggestion = "|".to_string();
687            let mut is_first = true;
688            let mut has_suggestion = false;
689
690            for (((expected, found), param_hir), arg_hir) in
691                expected.iter().zip(found.iter()).zip(params.iter()).zip(fn_decl.inputs.iter())
692            {
693                if is_first {
694                    is_first = false;
695                } else {
696                    suggestion += ", ";
697                }
698
699                if let ty::Ref(expected_region, _, _) = expected.kind()
700                    && let ty::Ref(found_region, _, _) = found.kind()
701                    && expected_region.is_bound()
702                    && !found_region.is_bound()
703                    && let hir::TyKind::Infer(()) = arg_hir.kind
704                {
705                    // If the expected region is late bound, the found region is not, and users are asking compiler
706                    // to infer the type, we can suggest adding `: &_`.
707                    if param_hir.pat.span == param_hir.ty_span {
708                        // for `|x|`, `|_|`, `|x: impl Foo|`
709                        let Ok(pat) =
710                            self.tcx.sess.source_map().span_to_snippet(param_hir.pat.span)
711                        else {
712                            return;
713                        };
714                        suggestion += &format!("{pat}: &_");
715                    } else {
716                        // for `|x: ty|`, `|_: ty|`
717                        let Ok(pat) =
718                            self.tcx.sess.source_map().span_to_snippet(param_hir.pat.span)
719                        else {
720                            return;
721                        };
722                        let Ok(ty) = self.tcx.sess.source_map().span_to_snippet(param_hir.ty_span)
723                        else {
724                            return;
725                        };
726                        suggestion += &format!("{pat}: &{ty}");
727                    }
728                    has_suggestion = true;
729                } else {
730                    let Ok(arg) = self.tcx.sess.source_map().span_to_snippet(param_hir.span) else {
731                        return;
732                    };
733                    // Otherwise, keep it as-is.
734                    suggestion += &arg;
735                }
736            }
737            suggestion += "|";
738
739            if has_suggestion {
740                diag.span_suggestion_verbose(
741                    span,
742                    "consider specifying the type of the closure parameters",
743                    suggestion,
744                    Applicability::MaybeIncorrect,
745                );
746            }
747        }
748    }
749}
750
751impl<'tcx> TypeErrCtxt<'_, 'tcx> {
752    /// Be helpful when the user wrote `{... expr; }` and taking the `;` off
753    /// is enough to fix the error.
754    fn could_remove_semicolon(
755        &self,
756        blk: &'tcx hir::Block<'tcx>,
757        expected_ty: Ty<'tcx>,
758    ) -> Option<(Span, StatementAsExpression)> {
759        let blk = blk.innermost_block();
760        // Do not suggest if we have a tail expr.
761        if blk.expr.is_some() {
762            return None;
763        }
764        let last_stmt = blk.stmts.last()?;
765        let hir::StmtKind::Semi(last_expr) = last_stmt.kind else {
766            return None;
767        };
768        let last_expr_ty = self.typeck_results.as_ref()?.expr_ty_opt(last_expr)?;
769        let needs_box = match (last_expr_ty.kind(), expected_ty.kind()) {
770            _ if last_expr_ty.references_error() => return None,
771            _ if self.same_type_modulo_infer(last_expr_ty, expected_ty) => {
772                StatementAsExpression::CorrectType
773            }
774            (
775                ty::Alias(ty::Opaque, ty::AliasTy { def_id: last_def_id, .. }),
776                ty::Alias(ty::Opaque, ty::AliasTy { def_id: exp_def_id, .. }),
777            ) if last_def_id == exp_def_id => StatementAsExpression::CorrectType,
778            (
779                ty::Alias(ty::Opaque, ty::AliasTy { def_id: last_def_id, args: last_bounds, .. }),
780                ty::Alias(ty::Opaque, ty::AliasTy { def_id: exp_def_id, args: exp_bounds, .. }),
781            ) => {
782                debug!(
783                    "both opaque, likely future {:?} {:?} {:?} {:?}",
784                    last_def_id, last_bounds, exp_def_id, exp_bounds
785                );
786
787                let last_local_id = last_def_id.as_local()?;
788                let exp_local_id = exp_def_id.as_local()?;
789
790                match (
791                    &self.tcx.hir_expect_opaque_ty(last_local_id),
792                    &self.tcx.hir_expect_opaque_ty(exp_local_id),
793                ) {
794                    (
795                        hir::OpaqueTy { bounds: last_bounds, .. },
796                        hir::OpaqueTy { bounds: exp_bounds, .. },
797                    ) if std::iter::zip(*last_bounds, *exp_bounds).all(|(left, right)| match (
798                        left, right,
799                    ) {
800                        // FIXME: Suspicious
801                        (hir::GenericBound::Trait(tl), hir::GenericBound::Trait(tr))
802                            if tl.trait_ref.trait_def_id() == tr.trait_ref.trait_def_id()
803                                && tl.modifiers == tr.modifiers =>
804                        {
805                            true
806                        }
807                        _ => false,
808                    }) =>
809                    {
810                        StatementAsExpression::NeedsBoxing
811                    }
812                    _ => StatementAsExpression::CorrectType,
813                }
814            }
815            _ => return None,
816        };
817        let span = if last_stmt.span.from_expansion() {
818            let mac_call = rustc_span::source_map::original_sp(last_stmt.span, blk.span);
819            self.tcx.sess.source_map().mac_call_stmt_semi_span(mac_call)?
820        } else {
821            self.tcx
822                .sess
823                .source_map()
824                .span_extend_while_whitespace(last_expr.span)
825                .shrink_to_hi()
826                .with_hi(last_stmt.span.hi())
827        };
828
829        Some((span, needs_box))
830    }
831
832    /// Suggest returning a local binding with a compatible type if the block
833    /// has no return expression.
834    fn consider_returning_binding_diag(
835        &self,
836        blk: &'tcx hir::Block<'tcx>,
837        expected_ty: Ty<'tcx>,
838    ) -> Option<SuggestRemoveSemiOrReturnBinding> {
839        let blk = blk.innermost_block();
840        // Do not suggest if we have a tail expr.
841        if blk.expr.is_some() {
842            return None;
843        }
844        let mut shadowed = FxIndexSet::default();
845        let mut candidate_idents = vec![];
846        let mut find_compatible_candidates = |pat: &hir::Pat<'_>| {
847            if let hir::PatKind::Binding(_, hir_id, ident, _) = &pat.kind
848                && let Some(pat_ty) = self
849                    .typeck_results
850                    .as_ref()
851                    .and_then(|typeck_results| typeck_results.node_type_opt(*hir_id))
852            {
853                let pat_ty = self.resolve_vars_if_possible(pat_ty);
854                if self.same_type_modulo_infer(pat_ty, expected_ty)
855                    && !(pat_ty, expected_ty).references_error()
856                    && shadowed.insert(ident.name)
857                {
858                    candidate_idents.push((*ident, pat_ty));
859                }
860            }
861            true
862        };
863
864        for stmt in blk.stmts.iter().rev() {
865            let hir::StmtKind::Let(local) = &stmt.kind else {
866                continue;
867            };
868            local.pat.walk(&mut find_compatible_candidates);
869        }
870        match self.tcx.parent_hir_node(blk.hir_id) {
871            hir::Node::Expr(hir::Expr { hir_id, .. }) => match self.tcx.parent_hir_node(*hir_id) {
872                hir::Node::Arm(hir::Arm { pat, .. }) => {
873                    pat.walk(&mut find_compatible_candidates);
874                }
875
876                hir::Node::Item(hir::Item { kind: hir::ItemKind::Fn { body, .. }, .. })
877                | hir::Node::ImplItem(hir::ImplItem {
878                    kind: hir::ImplItemKind::Fn(_, body), ..
879                })
880                | hir::Node::TraitItem(hir::TraitItem {
881                    kind: hir::TraitItemKind::Fn(_, hir::TraitFn::Provided(body)),
882                    ..
883                })
884                | hir::Node::Expr(hir::Expr {
885                    kind: hir::ExprKind::Closure(hir::Closure { body, .. }),
886                    ..
887                }) => {
888                    for param in self.tcx.hir_body(*body).params {
889                        param.pat.walk(&mut find_compatible_candidates);
890                    }
891                }
892                hir::Node::Expr(hir::Expr {
893                    kind:
894                        hir::ExprKind::If(
895                            hir::Expr { kind: hir::ExprKind::Let(let_), .. },
896                            then_block,
897                            _,
898                        ),
899                    ..
900                }) if then_block.hir_id == *hir_id => {
901                    let_.pat.walk(&mut find_compatible_candidates);
902                }
903                _ => {}
904            },
905            _ => {}
906        }
907
908        match &candidate_idents[..] {
909            [(ident, _ty)] => {
910                let sm = self.tcx.sess.source_map();
911                let (span, sugg) = if let Some(stmt) = blk.stmts.last() {
912                    let stmt_span = sm.stmt_span(stmt.span, blk.span);
913                    let sugg = if sm.is_multiline(blk.span)
914                        && let Some(spacing) = sm.indentation_before(stmt_span)
915                    {
916                        format!("\n{spacing}{ident}")
917                    } else {
918                        format!(" {ident}")
919                    };
920                    (stmt_span.shrink_to_hi(), sugg)
921                } else {
922                    let sugg = if sm.is_multiline(blk.span)
923                        && let Some(spacing) = sm.indentation_before(blk.span.shrink_to_lo())
924                    {
925                        format!("\n{spacing}    {ident}\n{spacing}")
926                    } else {
927                        format!(" {ident} ")
928                    };
929                    let left_span = sm.span_through_char(blk.span, '{').shrink_to_hi();
930                    (sm.span_extend_while_whitespace(left_span), sugg)
931                };
932                Some(SuggestRemoveSemiOrReturnBinding::Add { sp: span, code: sugg, ident: *ident })
933            }
934            values if (1..3).contains(&values.len()) => {
935                let spans = values.iter().map(|(ident, _)| ident.span).collect::<Vec<_>>();
936                Some(SuggestRemoveSemiOrReturnBinding::AddOne { spans: spans.into() })
937            }
938            _ => None,
939        }
940    }
941
942    pub fn consider_returning_binding(
943        &self,
944        blk: &'tcx hir::Block<'tcx>,
945        expected_ty: Ty<'tcx>,
946        err: &mut Diag<'_>,
947    ) -> bool {
948        let diag = self.consider_returning_binding_diag(blk, expected_ty);
949        match diag {
950            Some(diag) => {
951                err.subdiagnostic(diag);
952                true
953            }
954            None => false,
955        }
956    }
957}