1 use hir::def::CtorKind;
2 use hir::intravisit::{walk_expr, walk_stmt, Visitor};
3 use rustc_data_structures::fx::FxIndexSet;
4 use rustc_errors::{Applicability, Diagnostic};
6 use rustc_middle::traits::{
7 IfExpressionCause, MatchExpressionArmCause, ObligationCause, ObligationCauseCode,
10 use rustc_middle::ty::print::with_no_trimmed_paths;
11 use rustc_middle::ty::{self as ty, Ty, TypeVisitable};
12 use rustc_span::{sym, BytePos, Span};
14 use crate::errors::SuggAddLetForLetChains;
16 use super::TypeErrCtxt;
18 impl<'tcx> TypeErrCtxt<'_, 'tcx> {
19 pub(super) fn suggest_remove_semi_or_return_binding(
22 first_id: Option<hir::HirId>,
25 second_id: Option<hir::HirId>,
29 let remove_semicolon = [
30 (first_id, self.resolve_vars_if_possible(second_ty)),
31 (second_id, self.resolve_vars_if_possible(first_ty)),
34 .find_map(|(id, ty)| {
35 let hir::Node::Block(blk) = self.tcx.hir().get(id?) else { return None };
36 self.could_remove_semicolon(blk, ty)
38 match remove_semicolon {
39 Some((sp, StatementAsExpression::NeedsBoxing)) => {
40 err.multipart_suggestion(
41 "consider removing this semicolon and boxing the expressions",
43 (first_span.shrink_to_lo(), "Box::new(".to_string()),
44 (first_span.shrink_to_hi(), ")".to_string()),
45 (second_span.shrink_to_lo(), "Box::new(".to_string()),
46 (second_span.shrink_to_hi(), ")".to_string()),
49 Applicability::MachineApplicable,
52 Some((sp, StatementAsExpression::CorrectType)) => {
53 err.span_suggestion_short(
55 "consider removing this semicolon",
57 Applicability::MachineApplicable,
61 for (id, ty) in [(first_id, second_ty), (second_id, first_ty)] {
63 && let hir::Node::Block(blk) = self.tcx.hir().get(id)
64 && self.consider_returning_binding(blk, ty, err)
73 pub(super) fn suggest_boxing_for_return_impl_trait(
77 arm_spans: impl Iterator<Item = Span>,
79 err.multipart_suggestion(
80 "you could change the return type to be a boxed trait object",
82 (return_sp.with_hi(return_sp.lo() + BytePos(4)), "Box<dyn".to_string()),
83 (return_sp.shrink_to_hi(), ">".to_string()),
85 Applicability::MaybeIncorrect,
89 [(sp.shrink_to_lo(), "Box::new(".to_string()), (sp.shrink_to_hi(), ")".to_string())]
93 err.multipart_suggestion(
94 "if you change the return type to expect trait objects, box the returned expressions",
96 Applicability::MaybeIncorrect,
100 pub(super) fn suggest_tuple_pattern(
102 cause: &ObligationCause<'tcx>,
103 exp_found: &ty::error::ExpectedFound<Ty<'tcx>>,
104 diag: &mut Diagnostic,
106 // Heavily inspired by `FnCtxt::suggest_compatible_variants`, with
107 // some modifications due to that being in typeck and this being in infer.
108 if let ObligationCauseCode::Pattern { .. } = cause.code() {
109 if let ty::Adt(expected_adt, substs) = exp_found.expected.kind() {
110 let compatible_variants: Vec<_> = expected_adt
114 variant.fields.len() == 1 && variant.ctor_kind() == Some(CtorKind::Fn)
116 .filter_map(|variant| {
117 let sole_field = &variant.fields[0];
118 let sole_field_ty = sole_field.ty(self.tcx, substs);
119 if self.same_type_modulo_infer(sole_field_ty, exp_found.found) {
121 with_no_trimmed_paths!(self.tcx.def_path_str(variant.def_id));
122 // FIXME #56861: DRYer prelude filtering
123 if let Some(path) = variant_path.strip_prefix("std::prelude::") {
124 if let Some((_, path)) = path.split_once("::") {
125 return Some(path.to_string());
134 match &compatible_variants[..] {
137 diag.multipart_suggestion_verbose(
138 &format!("try wrapping the pattern in `{}`", variant),
140 (cause.span.shrink_to_lo(), format!("{}(", variant)),
141 (cause.span.shrink_to_hi(), ")".to_string()),
143 Applicability::MaybeIncorrect,
147 // More than one matching variant.
148 diag.multipart_suggestions(
150 "try wrapping the pattern in a variant of `{}`",
151 self.tcx.def_path_str(expected_adt.did())
153 compatible_variants.into_iter().map(|variant| {
155 (cause.span.shrink_to_lo(), format!("{}(", variant)),
156 (cause.span.shrink_to_hi(), ")".to_string()),
159 Applicability::MaybeIncorrect,
167 /// A possible error is to forget to add `.await` when using futures:
169 /// ```compile_fail,E0308
170 /// async fn make_u32() -> u32 {
174 /// fn take_u32(x: u32) {}
177 /// let x = make_u32();
182 /// This routine checks if the found type `T` implements `Future<Output=U>` where `U` is the
183 /// expected type. If this is the case, and we are inside of an async body, it suggests adding
184 /// `.await` to the tail of the expression.
185 pub(super) fn suggest_await_on_expect_found(
187 cause: &ObligationCause<'tcx>,
189 exp_found: &ty::error::ExpectedFound<Ty<'tcx>>,
190 diag: &mut Diagnostic,
193 "suggest_await_on_expect_found: exp_span={:?}, expected_ty={:?}, found_ty={:?}",
194 exp_span, exp_found.expected, exp_found.found,
197 if let ObligationCauseCode::CompareImplItemObligation { .. } = cause.code() {
202 self.get_impl_future_output_ty(exp_found.expected),
203 self.get_impl_future_output_ty(exp_found.found),
205 (Some(exp), Some(found)) if self.same_type_modulo_infer(exp, found) => match cause
208 ObligationCauseCode::IfExpression(box IfExpressionCause { then_id, .. }) => {
209 let then_span = self.find_block_span_from_hir_id(*then_id);
210 diag.multipart_suggestion(
211 "consider `await`ing on both `Future`s",
213 (then_span.shrink_to_hi(), ".await".to_string()),
214 (exp_span.shrink_to_hi(), ".await".to_string()),
216 Applicability::MaybeIncorrect,
219 ObligationCauseCode::MatchExpressionArm(box MatchExpressionArmCause {
223 if let [.., arm_span] = &prior_arms[..] {
224 diag.multipart_suggestion(
225 "consider `await`ing on both `Future`s",
227 (arm_span.shrink_to_hi(), ".await".to_string()),
228 (exp_span.shrink_to_hi(), ".await".to_string()),
230 Applicability::MaybeIncorrect,
233 diag.help("consider `await`ing on both `Future`s");
237 diag.help("consider `await`ing on both `Future`s");
240 (_, Some(ty)) if self.same_type_modulo_infer(exp_found.expected, ty) => {
241 diag.span_suggestion_verbose(
242 exp_span.shrink_to_hi(),
243 "consider `await`ing on the `Future`",
245 Applicability::MaybeIncorrect,
248 (Some(ty), _) if self.same_type_modulo_infer(ty, exp_found.found) => match cause.code()
250 ObligationCauseCode::Pattern { span: Some(then_span), .. } => {
251 diag.span_suggestion_verbose(
252 then_span.shrink_to_hi(),
253 "consider `await`ing on the `Future`",
255 Applicability::MaybeIncorrect,
258 ObligationCauseCode::IfExpression(box IfExpressionCause { then_id, .. }) => {
259 let then_span = self.find_block_span_from_hir_id(*then_id);
260 diag.span_suggestion_verbose(
261 then_span.shrink_to_hi(),
262 "consider `await`ing on the `Future`",
264 Applicability::MaybeIncorrect,
267 ObligationCauseCode::MatchExpressionArm(box MatchExpressionArmCause {
271 diag.multipart_suggestion_verbose(
272 "consider `await`ing on the `Future`",
275 .map(|arm| (arm.shrink_to_hi(), ".await".to_string()))
277 Applicability::MaybeIncorrect,
286 pub(super) fn suggest_accessing_field_where_appropriate(
288 cause: &ObligationCause<'tcx>,
289 exp_found: &ty::error::ExpectedFound<Ty<'tcx>>,
290 diag: &mut Diagnostic,
293 "suggest_accessing_field_where_appropriate(cause={:?}, exp_found={:?})",
296 if let ty::Adt(expected_def, expected_substs) = exp_found.expected.kind() {
297 if expected_def.is_enum() {
301 if let Some((name, ty)) = expected_def
305 .filter(|field| field.vis.is_accessible_from(field.did, self.tcx))
306 .map(|field| (field.name, field.ty(self.tcx, expected_substs)))
307 .find(|(_, ty)| self.same_type_modulo_infer(*ty, exp_found.found))
309 if let ObligationCauseCode::Pattern { span: Some(span), .. } = *cause.code() {
310 if let Ok(snippet) = self.tcx.sess.source_map().span_to_snippet(span) {
311 let suggestion = if expected_def.is_struct() {
312 format!("{}.{}", snippet, name)
313 } else if expected_def.is_union() {
314 format!("unsafe {{ {}.{} }}", snippet, name)
318 diag.span_suggestion(
321 "you might have meant to use field `{}` whose type is `{}`",
325 Applicability::MaybeIncorrect,
333 /// When encountering a case where `.as_ref()` on a `Result` or `Option` would be appropriate,
335 pub(super) fn suggest_as_ref_where_appropriate(
338 exp_found: &ty::error::ExpectedFound<Ty<'tcx>>,
339 diag: &mut Diagnostic,
341 if let Ok(snippet) = self.tcx.sess.source_map().span_to_snippet(span)
342 && let Some(msg) = self.should_suggest_as_ref(exp_found.expected, exp_found.found)
344 diag.span_suggestion(
347 // HACK: fix issue# 100605, suggesting convert from &Option<T> to Option<&T>, remove the extra `&`
348 format!("{}.as_ref()", snippet.trim_start_matches('&')),
349 Applicability::MachineApplicable,
354 pub fn should_suggest_as_ref(&self, expected: Ty<'tcx>, found: Ty<'tcx>) -> Option<&str> {
355 if let (ty::Adt(exp_def, exp_substs), ty::Ref(_, found_ty, _)) =
356 (expected.kind(), found.kind())
358 if let ty::Adt(found_def, found_substs) = *found_ty.kind() {
359 if exp_def == &found_def {
363 "you can convert from `&Option<T>` to `Option<&T>` using \
368 "you can convert from `&Result<T, E>` to \
369 `Result<&T, &E>` using `.as_ref()`",
372 if let Some(msg) = have_as_ref.iter().find_map(|(name, msg)| {
373 self.tcx.is_diagnostic_item(*name, exp_def.did()).then_some(msg)
375 let mut show_suggestion = true;
376 for (exp_ty, found_ty) in
377 std::iter::zip(exp_substs.types(), found_substs.types())
379 match *exp_ty.kind() {
380 ty::Ref(_, exp_ty, _) => {
381 match (exp_ty.kind(), found_ty.kind()) {
385 | (ty::Infer(_), _) => {}
386 _ if self.same_type_modulo_infer(exp_ty, found_ty) => {}
387 _ => show_suggestion = false,
390 ty::Param(_) | ty::Infer(_) => {}
391 _ => show_suggestion = false,
404 /// Try to find code with pattern `if Some(..) = expr`
405 /// use a `visitor` to mark the `if` which its span contains given error span,
406 /// and then try to find a assignment in the `cond` part, which span is equal with error span
407 pub(super) fn suggest_let_for_letchains(
409 err: &mut Diagnostic,
410 cause: &ObligationCause<'_>,
413 let hir = self.tcx.hir();
414 let fn_hir_id = hir.parent_id(cause.body_id);
415 if let Some(node) = self.tcx.hir().find(fn_hir_id) &&
416 let hir::Node::Item(hir::Item {
417 kind: hir::ItemKind::Fn(_sig, _, body_id), ..
419 let body = hir.body(*body_id);
421 /// Find the if expression with given span
428 impl<'v> Visitor<'v> for IfVisitor {
429 fn visit_expr(&mut self, ex: &'v hir::Expr<'v>) {
430 if self.result { return; }
432 hir::ExprKind::If(cond, _, _) => {
433 self.found_if = true;
434 walk_expr(self, cond);
435 self.found_if = false;
437 _ => walk_expr(self, ex),
441 fn visit_stmt(&mut self, ex: &'v hir::Stmt<'v>) {
442 if let hir::StmtKind::Local(hir::Local {
443 span, pat: hir::Pat{..}, ty: None, init: Some(_), ..
446 && span.eq(&self.err_span) {
452 fn visit_body(&mut self, body: &'v hir::Body<'v>) {
453 hir::intravisit::walk_body(self, body);
457 let mut visitor = IfVisitor { err_span: span, found_if: false, result: false };
458 visitor.visit_body(&body);
460 err.subdiagnostic(SuggAddLetForLetChains{span: span.shrink_to_lo()});
466 impl<'tcx> TypeErrCtxt<'_, 'tcx> {
467 /// Be helpful when the user wrote `{... expr; }` and taking the `;` off
468 /// is enough to fix the error.
469 pub fn could_remove_semicolon(
471 blk: &'tcx hir::Block<'tcx>,
472 expected_ty: Ty<'tcx>,
473 ) -> Option<(Span, StatementAsExpression)> {
474 let blk = blk.innermost_block();
475 // Do not suggest if we have a tail expr.
476 if blk.expr.is_some() {
479 let last_stmt = blk.stmts.last()?;
480 let hir::StmtKind::Semi(ref last_expr) = last_stmt.kind else {
483 let last_expr_ty = self.typeck_results.as_ref()?.expr_ty_opt(*last_expr)?;
484 let needs_box = match (last_expr_ty.kind(), expected_ty.kind()) {
485 _ if last_expr_ty.references_error() => return None,
486 _ if self.same_type_modulo_infer(last_expr_ty, expected_ty) => {
487 StatementAsExpression::CorrectType
490 ty::Alias(ty::Opaque, ty::AliasTy { def_id: last_def_id, .. }),
491 ty::Alias(ty::Opaque, ty::AliasTy { def_id: exp_def_id, .. }),
492 ) if last_def_id == exp_def_id => StatementAsExpression::CorrectType,
494 ty::Alias(ty::Opaque, ty::AliasTy { def_id: last_def_id, substs: last_bounds, .. }),
495 ty::Alias(ty::Opaque, ty::AliasTy { def_id: exp_def_id, substs: exp_bounds, .. }),
498 "both opaque, likely future {:?} {:?} {:?} {:?}",
499 last_def_id, last_bounds, exp_def_id, exp_bounds
502 let last_local_id = last_def_id.as_local()?;
503 let exp_local_id = exp_def_id.as_local()?;
506 &self.tcx.hir().expect_item(last_local_id).kind,
507 &self.tcx.hir().expect_item(exp_local_id).kind,
510 hir::ItemKind::OpaqueTy(hir::OpaqueTy { bounds: last_bounds, .. }),
511 hir::ItemKind::OpaqueTy(hir::OpaqueTy { bounds: exp_bounds, .. }),
512 ) if std::iter::zip(*last_bounds, *exp_bounds).all(|(left, right)| {
513 match (left, right) {
515 hir::GenericBound::Trait(tl, ml),
516 hir::GenericBound::Trait(tr, mr),
517 ) if tl.trait_ref.trait_def_id() == tr.trait_ref.trait_def_id()
523 hir::GenericBound::LangItemTrait(langl, _, _, argsl),
524 hir::GenericBound::LangItemTrait(langr, _, _, argsr),
525 ) if langl == langr => {
526 // FIXME: consider the bounds!
527 debug!("{:?} {:?}", argsl, argsr);
534 StatementAsExpression::NeedsBoxing
536 _ => StatementAsExpression::CorrectType,
541 let span = if last_stmt.span.from_expansion() {
542 let mac_call = rustc_span::source_map::original_sp(last_stmt.span, blk.span);
543 self.tcx.sess.source_map().mac_call_stmt_semi_span(mac_call)?
545 last_stmt.span.with_lo(last_stmt.span.hi() - BytePos(1))
547 Some((span, needs_box))
550 /// Suggest returning a local binding with a compatible type if the block
551 /// has no return expression.
552 pub fn consider_returning_binding(
554 blk: &'tcx hir::Block<'tcx>,
555 expected_ty: Ty<'tcx>,
556 err: &mut Diagnostic,
558 let blk = blk.innermost_block();
559 // Do not suggest if we have a tail expr.
560 if blk.expr.is_some() {
563 let mut shadowed = FxIndexSet::default();
564 let mut candidate_idents = vec![];
565 let mut find_compatible_candidates = |pat: &hir::Pat<'_>| {
566 if let hir::PatKind::Binding(_, hir_id, ident, _) = &pat.kind
567 && let Some(pat_ty) = self
570 .and_then(|typeck_results| typeck_results.node_type_opt(*hir_id))
572 let pat_ty = self.resolve_vars_if_possible(pat_ty);
573 if self.same_type_modulo_infer(pat_ty, expected_ty)
574 && !(pat_ty, expected_ty).references_error()
575 && shadowed.insert(ident.name)
577 candidate_idents.push((*ident, pat_ty));
583 let hir = self.tcx.hir();
584 for stmt in blk.stmts.iter().rev() {
585 let hir::StmtKind::Local(local) = &stmt.kind else { continue; };
586 local.pat.walk(&mut find_compatible_candidates);
588 match hir.find_parent(blk.hir_id) {
589 Some(hir::Node::Expr(hir::Expr { hir_id, .. })) => match hir.find_parent(*hir_id) {
590 Some(hir::Node::Arm(hir::Arm { pat, .. })) => {
591 pat.walk(&mut find_compatible_candidates);
594 hir::Node::Item(hir::Item { kind: hir::ItemKind::Fn(_, _, body), .. })
595 | hir::Node::ImplItem(hir::ImplItem {
596 kind: hir::ImplItemKind::Fn(_, body), ..
598 | hir::Node::TraitItem(hir::TraitItem {
599 kind: hir::TraitItemKind::Fn(_, hir::TraitFn::Provided(body)),
602 | hir::Node::Expr(hir::Expr {
603 kind: hir::ExprKind::Closure(hir::Closure { body, .. }),
607 for param in hir.body(*body).params {
608 param.pat.walk(&mut find_compatible_candidates);
611 Some(hir::Node::Expr(hir::Expr {
614 hir::Expr { kind: hir::ExprKind::Let(let_), .. },
619 })) if then_block.hir_id == *hir_id => {
620 let_.pat.walk(&mut find_compatible_candidates);
627 match &candidate_idents[..] {
629 let sm = self.tcx.sess.source_map();
630 if let Some(stmt) = blk.stmts.last() {
631 let stmt_span = sm.stmt_span(stmt.span, blk.span);
632 let sugg = if sm.is_multiline(blk.span)
633 && let Some(spacing) = sm.indentation_before(stmt_span)
635 format!("\n{spacing}{ident}")
639 err.span_suggestion_verbose(
640 stmt_span.shrink_to_hi(),
641 format!("consider returning the local binding `{ident}`"),
643 Applicability::MaybeIncorrect,
646 let sugg = if sm.is_multiline(blk.span)
647 && let Some(spacing) = sm.indentation_before(blk.span.shrink_to_lo())
649 format!("\n{spacing} {ident}\n{spacing}")
653 let left_span = sm.span_through_char(blk.span, '{').shrink_to_hi();
654 err.span_suggestion_verbose(
655 sm.span_extend_while(left_span, |c| c.is_whitespace()).unwrap_or(left_span),
656 format!("consider returning the local binding `{ident}`"),
658 Applicability::MaybeIncorrect,
663 values if (1..3).contains(&values.len()) => {
664 let spans = values.iter().map(|(ident, _)| ident.span).collect::<Vec<_>>();
665 err.span_note(spans, "consider returning one of these bindings");