Move some suggestions from error_reporting to error_reporting::suggest

This commit is contained in:
Michael Goulet 2022-12-08 05:58:30 +00:00
parent 25a6daccab
commit 3b9daac6a2
5 changed files with 1305 additions and 661 deletions

View file

@ -56,22 +56,17 @@ use crate::infer::ExpectedFound;
use crate::traits::error_reporting::report_object_safety_error; use crate::traits::error_reporting::report_object_safety_error;
use crate::traits::{ use crate::traits::{
IfExpressionCause, MatchExpressionArmCause, ObligationCause, ObligationCauseCode, IfExpressionCause, MatchExpressionArmCause, ObligationCause, ObligationCauseCode,
StatementAsExpression,
}; };
use crate::errors::SuggAddLetForLetChains;
use hir::intravisit::{walk_expr, walk_stmt};
use rustc_data_structures::fx::{FxIndexMap, FxIndexSet}; use rustc_data_structures::fx::{FxIndexMap, FxIndexSet};
use rustc_errors::{pluralize, struct_span_err, Diagnostic, ErrorGuaranteed, IntoDiagnosticArg}; use rustc_errors::{pluralize, struct_span_err, Diagnostic, ErrorGuaranteed, IntoDiagnosticArg};
use rustc_errors::{Applicability, DiagnosticBuilder, DiagnosticStyledString, MultiSpan}; use rustc_errors::{Applicability, DiagnosticBuilder, DiagnosticStyledString, MultiSpan};
use rustc_hir as hir; use rustc_hir as hir;
use rustc_hir::def::{CtorKind, DefKind}; use rustc_hir::def::DefKind;
use rustc_hir::def_id::{DefId, LocalDefId}; use rustc_hir::def_id::{DefId, LocalDefId};
use rustc_hir::intravisit::Visitor;
use rustc_hir::lang_items::LangItem; use rustc_hir::lang_items::LangItem;
use rustc_hir::Node; use rustc_hir::Node;
use rustc_middle::dep_graph::DepContext; use rustc_middle::dep_graph::DepContext;
use rustc_middle::ty::print::with_no_trimmed_paths;
use rustc_middle::ty::relate::{self, RelateResult, TypeRelation}; use rustc_middle::ty::relate::{self, RelateResult, TypeRelation};
use rustc_middle::ty::{ use rustc_middle::ty::{
self, error::TypeError, List, Region, Ty, TyCtxt, TypeFoldable, TypeSuperVisitable, self, error::TypeError, List, Region, Ty, TyCtxt, TypeFoldable, TypeSuperVisitable,
@ -83,6 +78,7 @@ use std::ops::{ControlFlow, Deref};
use std::{cmp, fmt, iter}; use std::{cmp, fmt, iter};
mod note; mod note;
mod suggest;
pub(crate) mod need_type_info; pub(crate) mod need_type_info;
pub use need_type_info::TypeAnnotationNeeded; pub use need_type_info::TypeAnnotationNeeded;
@ -806,87 +802,6 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> {
} }
} }
fn suggest_remove_semi_or_return_binding(
&self,
err: &mut Diagnostic,
first_id: Option<hir::HirId>,
first_ty: Ty<'tcx>,
first_span: Span,
second_id: Option<hir::HirId>,
second_ty: Ty<'tcx>,
second_span: Span,
) {
let remove_semicolon = [
(first_id, self.resolve_vars_if_possible(second_ty)),
(second_id, self.resolve_vars_if_possible(first_ty)),
]
.into_iter()
.find_map(|(id, ty)| {
let hir::Node::Block(blk) = self.tcx.hir().get(id?) else { return None };
self.could_remove_semicolon(blk, ty)
});
match remove_semicolon {
Some((sp, StatementAsExpression::NeedsBoxing)) => {
err.multipart_suggestion(
"consider removing this semicolon and boxing the expressions",
vec![
(first_span.shrink_to_lo(), "Box::new(".to_string()),
(first_span.shrink_to_hi(), ")".to_string()),
(second_span.shrink_to_lo(), "Box::new(".to_string()),
(second_span.shrink_to_hi(), ")".to_string()),
(sp, String::new()),
],
Applicability::MachineApplicable,
);
}
Some((sp, StatementAsExpression::CorrectType)) => {
err.span_suggestion_short(
sp,
"consider removing this semicolon",
"",
Applicability::MachineApplicable,
);
}
None => {
for (id, ty) in [(first_id, second_ty), (second_id, first_ty)] {
if let Some(id) = id
&& let hir::Node::Block(blk) = self.tcx.hir().get(id)
&& self.consider_returning_binding(blk, ty, err)
{
break;
}
}
}
}
}
fn suggest_boxing_for_return_impl_trait(
&self,
err: &mut Diagnostic,
return_sp: Span,
arm_spans: impl Iterator<Item = Span>,
) {
err.multipart_suggestion(
"you could change the return type to be a boxed trait object",
vec![
(return_sp.with_hi(return_sp.lo() + BytePos(4)), "Box<dyn".to_string()),
(return_sp.shrink_to_hi(), ">".to_string()),
],
Applicability::MaybeIncorrect,
);
let sugg = arm_spans
.flat_map(|sp| {
[(sp.shrink_to_lo(), "Box::new(".to_string()), (sp.shrink_to_hi(), ")".to_string())]
.into_iter()
})
.collect::<Vec<_>>();
err.multipart_suggestion(
"if you change the return type to expect trait objects, box the returned expressions",
sugg,
Applicability::MaybeIncorrect,
);
}
/// Given that `other_ty` is the same as a type argument for `name` in `sub`, populate `value` /// Given that `other_ty` is the same as a type argument for `name` in `sub`, populate `value`
/// highlighting `name` and every type argument that isn't at `pos` (which is `other_ty`), and /// highlighting `name` and every type argument that isn't at `pos` (which is `other_ty`), and
/// populate `other_value` with `other_ty`. /// populate `other_value` with `other_ty`.
@ -1940,310 +1855,6 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> {
debug!(?diag); debug!(?diag);
} }
fn suggest_tuple_pattern(
&self,
cause: &ObligationCause<'tcx>,
exp_found: &ty::error::ExpectedFound<Ty<'tcx>>,
diag: &mut Diagnostic,
) {
// Heavily inspired by `FnCtxt::suggest_compatible_variants`, with
// some modifications due to that being in typeck and this being in infer.
if let ObligationCauseCode::Pattern { .. } = cause.code() {
if let ty::Adt(expected_adt, substs) = exp_found.expected.kind() {
let compatible_variants: Vec<_> = expected_adt
.variants()
.iter()
.filter(|variant| {
variant.fields.len() == 1 && variant.ctor_kind() == Some(CtorKind::Fn)
})
.filter_map(|variant| {
let sole_field = &variant.fields[0];
let sole_field_ty = sole_field.ty(self.tcx, substs);
if self.same_type_modulo_infer(sole_field_ty, exp_found.found) {
let variant_path =
with_no_trimmed_paths!(self.tcx.def_path_str(variant.def_id));
// FIXME #56861: DRYer prelude filtering
if let Some(path) = variant_path.strip_prefix("std::prelude::") {
if let Some((_, path)) = path.split_once("::") {
return Some(path.to_string());
}
}
Some(variant_path)
} else {
None
}
})
.collect();
match &compatible_variants[..] {
[] => {}
[variant] => {
diag.multipart_suggestion_verbose(
&format!("try wrapping the pattern in `{}`", variant),
vec![
(cause.span.shrink_to_lo(), format!("{}(", variant)),
(cause.span.shrink_to_hi(), ")".to_string()),
],
Applicability::MaybeIncorrect,
);
}
_ => {
// More than one matching variant.
diag.multipart_suggestions(
&format!(
"try wrapping the pattern in a variant of `{}`",
self.tcx.def_path_str(expected_adt.did())
),
compatible_variants.into_iter().map(|variant| {
vec![
(cause.span.shrink_to_lo(), format!("{}(", variant)),
(cause.span.shrink_to_hi(), ")".to_string()),
]
}),
Applicability::MaybeIncorrect,
);
}
}
}
}
}
/// A possible error is to forget to add `.await` when using futures:
///
/// ```compile_fail,E0308
/// async fn make_u32() -> u32 {
/// 22
/// }
///
/// fn take_u32(x: u32) {}
///
/// async fn foo() {
/// let x = make_u32();
/// take_u32(x);
/// }
/// ```
///
/// This routine checks if the found type `T` implements `Future<Output=U>` where `U` is the
/// expected type. If this is the case, and we are inside of an async body, it suggests adding
/// `.await` to the tail of the expression.
fn suggest_await_on_expect_found(
&self,
cause: &ObligationCause<'tcx>,
exp_span: Span,
exp_found: &ty::error::ExpectedFound<Ty<'tcx>>,
diag: &mut Diagnostic,
) {
debug!(
"suggest_await_on_expect_found: exp_span={:?}, expected_ty={:?}, found_ty={:?}",
exp_span, exp_found.expected, exp_found.found,
);
if let ObligationCauseCode::CompareImplItemObligation { .. } = cause.code() {
return;
}
match (
self.get_impl_future_output_ty(exp_found.expected),
self.get_impl_future_output_ty(exp_found.found),
) {
(Some(exp), Some(found)) if self.same_type_modulo_infer(exp, found) => match cause
.code()
{
ObligationCauseCode::IfExpression(box IfExpressionCause { then_id, .. }) => {
let then_span = self.find_block_span_from_hir_id(*then_id);
diag.multipart_suggestion(
"consider `await`ing on both `Future`s",
vec![
(then_span.shrink_to_hi(), ".await".to_string()),
(exp_span.shrink_to_hi(), ".await".to_string()),
],
Applicability::MaybeIncorrect,
);
}
ObligationCauseCode::MatchExpressionArm(box MatchExpressionArmCause {
prior_arms,
..
}) => {
if let [.., arm_span] = &prior_arms[..] {
diag.multipart_suggestion(
"consider `await`ing on both `Future`s",
vec![
(arm_span.shrink_to_hi(), ".await".to_string()),
(exp_span.shrink_to_hi(), ".await".to_string()),
],
Applicability::MaybeIncorrect,
);
} else {
diag.help("consider `await`ing on both `Future`s");
}
}
_ => {
diag.help("consider `await`ing on both `Future`s");
}
},
(_, Some(ty)) if self.same_type_modulo_infer(exp_found.expected, ty) => {
diag.span_suggestion_verbose(
exp_span.shrink_to_hi(),
"consider `await`ing on the `Future`",
".await",
Applicability::MaybeIncorrect,
);
}
(Some(ty), _) if self.same_type_modulo_infer(ty, exp_found.found) => match cause.code()
{
ObligationCauseCode::Pattern { span: Some(then_span), .. } => {
diag.span_suggestion_verbose(
then_span.shrink_to_hi(),
"consider `await`ing on the `Future`",
".await",
Applicability::MaybeIncorrect,
);
}
ObligationCauseCode::IfExpression(box IfExpressionCause { then_id, .. }) => {
let then_span = self.find_block_span_from_hir_id(*then_id);
diag.span_suggestion_verbose(
then_span.shrink_to_hi(),
"consider `await`ing on the `Future`",
".await",
Applicability::MaybeIncorrect,
);
}
ObligationCauseCode::MatchExpressionArm(box MatchExpressionArmCause {
ref prior_arms,
..
}) => {
diag.multipart_suggestion_verbose(
"consider `await`ing on the `Future`",
prior_arms
.iter()
.map(|arm| (arm.shrink_to_hi(), ".await".to_string()))
.collect(),
Applicability::MaybeIncorrect,
);
}
_ => {}
},
_ => {}
}
}
fn suggest_accessing_field_where_appropriate(
&self,
cause: &ObligationCause<'tcx>,
exp_found: &ty::error::ExpectedFound<Ty<'tcx>>,
diag: &mut Diagnostic,
) {
debug!(
"suggest_accessing_field_where_appropriate(cause={:?}, exp_found={:?})",
cause, exp_found
);
if let ty::Adt(expected_def, expected_substs) = exp_found.expected.kind() {
if expected_def.is_enum() {
return;
}
if let Some((name, ty)) = expected_def
.non_enum_variant()
.fields
.iter()
.filter(|field| field.vis.is_accessible_from(field.did, self.tcx))
.map(|field| (field.name, field.ty(self.tcx, expected_substs)))
.find(|(_, ty)| self.same_type_modulo_infer(*ty, exp_found.found))
{
if let ObligationCauseCode::Pattern { span: Some(span), .. } = *cause.code() {
if let Ok(snippet) = self.tcx.sess.source_map().span_to_snippet(span) {
let suggestion = if expected_def.is_struct() {
format!("{}.{}", snippet, name)
} else if expected_def.is_union() {
format!("unsafe {{ {}.{} }}", snippet, name)
} else {
return;
};
diag.span_suggestion(
span,
&format!(
"you might have meant to use field `{}` whose type is `{}`",
name, ty
),
suggestion,
Applicability::MaybeIncorrect,
);
}
}
}
}
}
/// When encountering a case where `.as_ref()` on a `Result` or `Option` would be appropriate,
/// suggests it.
fn suggest_as_ref_where_appropriate(
&self,
span: Span,
exp_found: &ty::error::ExpectedFound<Ty<'tcx>>,
diag: &mut Diagnostic,
) {
if let Ok(snippet) = self.tcx.sess.source_map().span_to_snippet(span)
&& let Some(msg) = self.should_suggest_as_ref(exp_found.expected, exp_found.found)
{
diag.span_suggestion(
span,
msg,
// HACK: fix issue# 100605, suggesting convert from &Option<T> to Option<&T>, remove the extra `&`
format!("{}.as_ref()", snippet.trim_start_matches('&')),
Applicability::MachineApplicable,
);
}
}
pub fn should_suggest_as_ref(&self, expected: Ty<'tcx>, found: Ty<'tcx>) -> Option<&str> {
if let (ty::Adt(exp_def, exp_substs), ty::Ref(_, found_ty, _)) =
(expected.kind(), found.kind())
{
if let ty::Adt(found_def, found_substs) = *found_ty.kind() {
if exp_def == &found_def {
let have_as_ref = &[
(
sym::Option,
"you can convert from `&Option<T>` to `Option<&T>` using \
`.as_ref()`",
),
(
sym::Result,
"you can convert from `&Result<T, E>` to \
`Result<&T, &E>` using `.as_ref()`",
),
];
if let Some(msg) = have_as_ref.iter().find_map(|(name, msg)| {
self.tcx.is_diagnostic_item(*name, exp_def.did()).then_some(msg)
}) {
let mut show_suggestion = true;
for (exp_ty, found_ty) in
iter::zip(exp_substs.types(), found_substs.types())
{
match *exp_ty.kind() {
ty::Ref(_, exp_ty, _) => {
match (exp_ty.kind(), found_ty.kind()) {
(_, ty::Param(_))
| (_, ty::Infer(_))
| (ty::Param(_), _)
| (ty::Infer(_), _) => {}
_ if self.same_type_modulo_infer(exp_ty, found_ty) => {}
_ => show_suggestion = false,
};
}
ty::Param(_) | ty::Infer(_) => {}
_ => show_suggestion = false,
}
}
if show_suggestion {
return Some(*msg);
}
}
}
}
}
None
}
pub fn report_and_explain_type_error( pub fn report_and_explain_type_error(
&self, &self,
trace: TypeTrace<'tcx>, trace: TypeTrace<'tcx>,
@ -2357,67 +1968,6 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> {
diag diag
} }
/// Try to find code with pattern `if Some(..) = expr`
/// use a `visitor` to mark the `if` which its span contains given error span,
/// and then try to find a assignment in the `cond` part, which span is equal with error span
fn suggest_let_for_letchains(
&self,
err: &mut Diagnostic,
cause: &ObligationCause<'_>,
span: Span,
) {
let hir = self.tcx.hir();
let fn_hir_id = hir.get_parent_node(cause.body_id);
if let Some(node) = self.tcx.hir().find(fn_hir_id) &&
let hir::Node::Item(hir::Item {
kind: hir::ItemKind::Fn(_sig, _, body_id), ..
}) = node {
let body = hir.body(*body_id);
/// Find the if expression with given span
struct IfVisitor {
pub result: bool,
pub found_if: bool,
pub err_span: Span,
}
impl<'v> Visitor<'v> for IfVisitor {
fn visit_expr(&mut self, ex: &'v hir::Expr<'v>) {
if self.result { return; }
match ex.kind {
hir::ExprKind::If(cond, _, _) => {
self.found_if = true;
walk_expr(self, cond);
self.found_if = false;
}
_ => walk_expr(self, ex),
}
}
fn visit_stmt(&mut self, ex: &'v hir::Stmt<'v>) {
if let hir::StmtKind::Local(hir::Local {
span, pat: hir::Pat{..}, ty: None, init: Some(_), ..
}) = &ex.kind
&& self.found_if
&& span.eq(&self.err_span) {
self.result = true;
}
walk_stmt(self, ex);
}
fn visit_body(&mut self, body: &'v hir::Body<'v>) {
hir::intravisit::walk_body(self, body);
}
}
let mut visitor = IfVisitor { err_span: span, found_if: false, result: false };
visitor.visit_body(&body);
if visitor.result {
err.subdiagnostic(SuggAddLetForLetChains{span: span.shrink_to_lo()});
}
}
}
fn emit_tuple_wrap_err( fn emit_tuple_wrap_err(
&self, &self,
err: &mut Diagnostic, err: &mut Diagnostic,
@ -3228,211 +2778,3 @@ impl<'tcx> InferCtxt<'tcx> {
} }
} }
} }
impl<'tcx> TypeErrCtxt<'_, 'tcx> {
/// Be helpful when the user wrote `{... expr; }` and taking the `;` off
/// is enough to fix the error.
pub fn could_remove_semicolon(
&self,
blk: &'tcx hir::Block<'tcx>,
expected_ty: Ty<'tcx>,
) -> Option<(Span, StatementAsExpression)> {
let blk = blk.innermost_block();
// Do not suggest if we have a tail expr.
if blk.expr.is_some() {
return None;
}
let last_stmt = blk.stmts.last()?;
let hir::StmtKind::Semi(ref last_expr) = last_stmt.kind else {
return None;
};
let last_expr_ty = self.typeck_results.as_ref()?.expr_ty_opt(*last_expr)?;
let needs_box = match (last_expr_ty.kind(), expected_ty.kind()) {
_ if last_expr_ty.references_error() => return None,
_ if self.same_type_modulo_infer(last_expr_ty, expected_ty) => {
StatementAsExpression::CorrectType
}
(ty::Opaque(last_def_id, _), ty::Opaque(exp_def_id, _))
if last_def_id == exp_def_id =>
{
StatementAsExpression::CorrectType
}
(ty::Opaque(last_def_id, last_bounds), ty::Opaque(exp_def_id, exp_bounds)) => {
debug!(
"both opaque, likely future {:?} {:?} {:?} {:?}",
last_def_id, last_bounds, exp_def_id, exp_bounds
);
let last_local_id = last_def_id.as_local()?;
let exp_local_id = exp_def_id.as_local()?;
match (
&self.tcx.hir().expect_item(last_local_id).kind,
&self.tcx.hir().expect_item(exp_local_id).kind,
) {
(
hir::ItemKind::OpaqueTy(hir::OpaqueTy { bounds: last_bounds, .. }),
hir::ItemKind::OpaqueTy(hir::OpaqueTy { bounds: exp_bounds, .. }),
) if iter::zip(*last_bounds, *exp_bounds).all(|(left, right)| {
match (left, right) {
(
hir::GenericBound::Trait(tl, ml),
hir::GenericBound::Trait(tr, mr),
) if tl.trait_ref.trait_def_id() == tr.trait_ref.trait_def_id()
&& ml == mr =>
{
true
}
(
hir::GenericBound::LangItemTrait(langl, _, _, argsl),
hir::GenericBound::LangItemTrait(langr, _, _, argsr),
) if langl == langr => {
// FIXME: consider the bounds!
debug!("{:?} {:?}", argsl, argsr);
true
}
_ => false,
}
}) =>
{
StatementAsExpression::NeedsBoxing
}
_ => StatementAsExpression::CorrectType,
}
}
_ => return None,
};
let span = if last_stmt.span.from_expansion() {
let mac_call = rustc_span::source_map::original_sp(last_stmt.span, blk.span);
self.tcx.sess.source_map().mac_call_stmt_semi_span(mac_call)?
} else {
last_stmt.span.with_lo(last_stmt.span.hi() - BytePos(1))
};
Some((span, needs_box))
}
/// Suggest returning a local binding with a compatible type if the block
/// has no return expression.
pub fn consider_returning_binding(
&self,
blk: &'tcx hir::Block<'tcx>,
expected_ty: Ty<'tcx>,
err: &mut Diagnostic,
) -> bool {
let blk = blk.innermost_block();
// Do not suggest if we have a tail expr.
if blk.expr.is_some() {
return false;
}
let mut shadowed = FxIndexSet::default();
let mut candidate_idents = vec![];
let mut find_compatible_candidates = |pat: &hir::Pat<'_>| {
if let hir::PatKind::Binding(_, hir_id, ident, _) = &pat.kind
&& let Some(pat_ty) = self
.typeck_results
.as_ref()
.and_then(|typeck_results| typeck_results.node_type_opt(*hir_id))
{
let pat_ty = self.resolve_vars_if_possible(pat_ty);
if self.same_type_modulo_infer(pat_ty, expected_ty)
&& !(pat_ty, expected_ty).references_error()
&& shadowed.insert(ident.name)
{
candidate_idents.push((*ident, pat_ty));
}
}
true
};
let hir = self.tcx.hir();
for stmt in blk.stmts.iter().rev() {
let hir::StmtKind::Local(local) = &stmt.kind else { continue; };
local.pat.walk(&mut find_compatible_candidates);
}
match hir.find(hir.get_parent_node(blk.hir_id)) {
Some(hir::Node::Expr(hir::Expr { hir_id, .. })) => {
match hir.find(hir.get_parent_node(*hir_id)) {
Some(hir::Node::Arm(hir::Arm { pat, .. })) => {
pat.walk(&mut find_compatible_candidates);
}
Some(
hir::Node::Item(hir::Item { kind: hir::ItemKind::Fn(_, _, body), .. })
| hir::Node::ImplItem(hir::ImplItem {
kind: hir::ImplItemKind::Fn(_, body),
..
})
| hir::Node::TraitItem(hir::TraitItem {
kind: hir::TraitItemKind::Fn(_, hir::TraitFn::Provided(body)),
..
})
| hir::Node::Expr(hir::Expr {
kind: hir::ExprKind::Closure(hir::Closure { body, .. }),
..
}),
) => {
for param in hir.body(*body).params {
param.pat.walk(&mut find_compatible_candidates);
}
}
Some(hir::Node::Expr(hir::Expr {
kind:
hir::ExprKind::If(
hir::Expr { kind: hir::ExprKind::Let(let_), .. },
then_block,
_,
),
..
})) if then_block.hir_id == *hir_id => {
let_.pat.walk(&mut find_compatible_candidates);
}
_ => {}
}
}
_ => {}
}
match &candidate_idents[..] {
[(ident, _ty)] => {
let sm = self.tcx.sess.source_map();
if let Some(stmt) = blk.stmts.last() {
let stmt_span = sm.stmt_span(stmt.span, blk.span);
let sugg = if sm.is_multiline(blk.span)
&& let Some(spacing) = sm.indentation_before(stmt_span)
{
format!("\n{spacing}{ident}")
} else {
format!(" {ident}")
};
err.span_suggestion_verbose(
stmt_span.shrink_to_hi(),
format!("consider returning the local binding `{ident}`"),
sugg,
Applicability::MaybeIncorrect,
);
} else {
let sugg = if sm.is_multiline(blk.span)
&& let Some(spacing) = sm.indentation_before(blk.span.shrink_to_lo())
{
format!("\n{spacing} {ident}\n{spacing}")
} else {
format!(" {ident} ")
};
let left_span = sm.span_through_char(blk.span, '{').shrink_to_hi();
err.span_suggestion_verbose(
sm.span_extend_while(left_span, |c| c.is_whitespace()).unwrap_or(left_span),
format!("consider returning the local binding `{ident}`"),
sugg,
Applicability::MaybeIncorrect,
);
}
true
}
values if (1..3).contains(&values.len()) => {
let spans = values.iter().map(|(ident, _)| ident.span).collect::<Vec<_>>();
err.span_note(spans, "consider returning one of these bindings");
true
}
_ => false,
}
}
}

View file

@ -0,0 +1,427 @@
use crate::errors::RegionOriginNote;
use crate::infer::error_reporting::{note_and_explain_region, TypeErrCtxt};
use crate::infer::{self, SubregionOrigin};
use rustc_errors::{
fluent, struct_span_err, AddToDiagnostic, Diagnostic, DiagnosticBuilder, ErrorGuaranteed,
};
use rustc_middle::traits::ObligationCauseCode;
use rustc_middle::ty::error::TypeError;
use rustc_middle::ty::{self, Region};
use super::ObligationCauseAsDiagArg;
impl<'tcx> TypeErrCtxt<'_, 'tcx> {
pub(super) fn note_region_origin(&self, err: &mut Diagnostic, origin: &SubregionOrigin<'tcx>) {
match *origin {
infer::Subtype(ref trace) => RegionOriginNote::WithRequirement {
span: trace.cause.span,
requirement: ObligationCauseAsDiagArg(trace.cause.clone()),
expected_found: self.values_str(trace.values),
}
.add_to_diagnostic(err),
infer::Reborrow(span) => {
RegionOriginNote::Plain { span, msg: fluent::infer_reborrow }.add_to_diagnostic(err)
}
infer::ReborrowUpvar(span, ref upvar_id) => {
let var_name = self.tcx.hir().name(upvar_id.var_path.hir_id);
RegionOriginNote::WithName {
span,
msg: fluent::infer_reborrow,
name: &var_name.to_string(),
continues: false,
}
.add_to_diagnostic(err);
}
infer::RelateObjectBound(span) => {
RegionOriginNote::Plain { span, msg: fluent::infer_relate_object_bound }
.add_to_diagnostic(err);
}
infer::DataBorrowed(ty, span) => {
RegionOriginNote::WithName {
span,
msg: fluent::infer_data_borrowed,
name: &self.ty_to_string(ty),
continues: false,
}
.add_to_diagnostic(err);
}
infer::ReferenceOutlivesReferent(ty, span) => {
RegionOriginNote::WithName {
span,
msg: fluent::infer_reference_outlives_referent,
name: &self.ty_to_string(ty),
continues: false,
}
.add_to_diagnostic(err);
}
infer::RelateParamBound(span, ty, opt_span) => {
RegionOriginNote::WithName {
span,
msg: fluent::infer_relate_param_bound,
name: &self.ty_to_string(ty),
continues: opt_span.is_some(),
}
.add_to_diagnostic(err);
if let Some(span) = opt_span {
RegionOriginNote::Plain { span, msg: fluent::infer_relate_param_bound_2 }
.add_to_diagnostic(err);
}
}
infer::RelateRegionParamBound(span) => {
RegionOriginNote::Plain { span, msg: fluent::infer_relate_region_param_bound }
.add_to_diagnostic(err);
}
infer::CompareImplItemObligation { span, .. } => {
RegionOriginNote::Plain { span, msg: fluent::infer_compare_impl_item_obligation }
.add_to_diagnostic(err);
}
infer::CheckAssociatedTypeBounds { ref parent, .. } => {
self.note_region_origin(err, &parent);
}
infer::AscribeUserTypeProvePredicate(span) => {
RegionOriginNote::Plain {
span,
msg: fluent::infer_ascribe_user_type_prove_predicate,
}
.add_to_diagnostic(err);
}
}
}
pub(super) fn report_concrete_failure(
&self,
origin: SubregionOrigin<'tcx>,
sub: Region<'tcx>,
sup: Region<'tcx>,
) -> DiagnosticBuilder<'tcx, ErrorGuaranteed> {
match origin {
infer::Subtype(box trace) => {
let terr = TypeError::RegionsDoesNotOutlive(sup, sub);
let mut err = self.report_and_explain_type_error(trace, terr);
match (*sub, *sup) {
(ty::RePlaceholder(_), ty::RePlaceholder(_)) => {}
(ty::RePlaceholder(_), _) => {
note_and_explain_region(
self.tcx,
&mut err,
"",
sup,
" doesn't meet the lifetime requirements",
None,
);
}
(_, ty::RePlaceholder(_)) => {
note_and_explain_region(
self.tcx,
&mut err,
"the required lifetime does not necessarily outlive ",
sub,
"",
None,
);
}
_ => {
note_and_explain_region(self.tcx, &mut err, "", sup, "...", None);
note_and_explain_region(
self.tcx,
&mut err,
"...does not necessarily outlive ",
sub,
"",
None,
);
}
}
err
}
infer::Reborrow(span) => {
let mut err = struct_span_err!(
self.tcx.sess,
span,
E0312,
"lifetime of reference outlives lifetime of borrowed content..."
);
note_and_explain_region(
self.tcx,
&mut err,
"...the reference is valid for ",
sub,
"...",
None,
);
note_and_explain_region(
self.tcx,
&mut err,
"...but the borrowed content is only valid for ",
sup,
"",
None,
);
err
}
infer::ReborrowUpvar(span, ref upvar_id) => {
let var_name = self.tcx.hir().name(upvar_id.var_path.hir_id);
let mut err = struct_span_err!(
self.tcx.sess,
span,
E0313,
"lifetime of borrowed pointer outlives lifetime of captured variable `{}`...",
var_name
);
note_and_explain_region(
self.tcx,
&mut err,
"...the borrowed pointer is valid for ",
sub,
"...",
None,
);
note_and_explain_region(
self.tcx,
&mut err,
&format!("...but `{}` is only valid for ", var_name),
sup,
"",
None,
);
err
}
infer::RelateObjectBound(span) => {
let mut err = struct_span_err!(
self.tcx.sess,
span,
E0476,
"lifetime of the source pointer does not outlive lifetime bound of the \
object type"
);
note_and_explain_region(
self.tcx,
&mut err,
"object type is valid for ",
sub,
"",
None,
);
note_and_explain_region(
self.tcx,
&mut err,
"source pointer is only valid for ",
sup,
"",
None,
);
err
}
infer::RelateParamBound(span, ty, opt_span) => {
let mut err = struct_span_err!(
self.tcx.sess,
span,
E0477,
"the type `{}` does not fulfill the required lifetime",
self.ty_to_string(ty)
);
match *sub {
ty::ReStatic => note_and_explain_region(
self.tcx,
&mut err,
"type must satisfy ",
sub,
if opt_span.is_some() { " as required by this binding" } else { "" },
opt_span,
),
_ => note_and_explain_region(
self.tcx,
&mut err,
"type must outlive ",
sub,
if opt_span.is_some() { " as required by this binding" } else { "" },
opt_span,
),
}
err
}
infer::RelateRegionParamBound(span) => {
let mut err =
struct_span_err!(self.tcx.sess, span, E0478, "lifetime bound not satisfied");
note_and_explain_region(
self.tcx,
&mut err,
"lifetime parameter instantiated with ",
sup,
"",
None,
);
note_and_explain_region(
self.tcx,
&mut err,
"but lifetime parameter must outlive ",
sub,
"",
None,
);
err
}
infer::DataBorrowed(ty, span) => {
let mut err = struct_span_err!(
self.tcx.sess,
span,
E0490,
"a value of type `{}` is borrowed for too long",
self.ty_to_string(ty)
);
note_and_explain_region(
self.tcx,
&mut err,
"the type is valid for ",
sub,
"",
None,
);
note_and_explain_region(
self.tcx,
&mut err,
"but the borrow lasts for ",
sup,
"",
None,
);
err
}
infer::ReferenceOutlivesReferent(ty, span) => {
let mut err = struct_span_err!(
self.tcx.sess,
span,
E0491,
"in type `{}`, reference has a longer lifetime than the data it references",
self.ty_to_string(ty)
);
note_and_explain_region(
self.tcx,
&mut err,
"the pointer is valid for ",
sub,
"",
None,
);
note_and_explain_region(
self.tcx,
&mut err,
"but the referenced data is only valid for ",
sup,
"",
None,
);
err
}
infer::CompareImplItemObligation { span, impl_item_def_id, trait_item_def_id } => self
.report_extra_impl_obligation(
span,
impl_item_def_id,
trait_item_def_id,
&format!("`{}: {}`", sup, sub),
),
infer::CheckAssociatedTypeBounds { impl_item_def_id, trait_item_def_id, parent } => {
let mut err = self.report_concrete_failure(*parent, sub, sup);
let trait_item_span = self.tcx.def_span(trait_item_def_id);
let item_name = self.tcx.item_name(impl_item_def_id.to_def_id());
err.span_label(
trait_item_span,
format!("definition of `{}` from trait", item_name),
);
let trait_predicates = self.tcx.explicit_predicates_of(trait_item_def_id);
let impl_predicates = self.tcx.explicit_predicates_of(impl_item_def_id);
let impl_predicates: rustc_data_structures::fx::FxHashSet<_> =
impl_predicates.predicates.into_iter().map(|(pred, _)| pred).collect();
let clauses: Vec<_> = trait_predicates
.predicates
.into_iter()
.filter(|&(pred, _)| !impl_predicates.contains(pred))
.map(|(pred, _)| format!("{}", pred))
.collect();
if !clauses.is_empty() {
let generics = self.tcx.hir().get_generics(impl_item_def_id).unwrap();
let where_clause_span = generics.tail_span_for_predicate_suggestion();
let suggestion = format!(
"{} {}",
generics.add_where_or_trailing_comma(),
clauses.join(", "),
);
err.span_suggestion(
where_clause_span,
&format!(
"try copying {} from the trait",
if clauses.len() > 1 { "these clauses" } else { "this clause" }
),
suggestion,
rustc_errors::Applicability::MaybeIncorrect,
);
}
err
}
infer::AscribeUserTypeProvePredicate(span) => {
let mut err =
struct_span_err!(self.tcx.sess, span, E0478, "lifetime bound not satisfied");
note_and_explain_region(
self.tcx,
&mut err,
"lifetime instantiated with ",
sup,
"",
None,
);
note_and_explain_region(
self.tcx,
&mut err,
"but lifetime must outlive ",
sub,
"",
None,
);
err
}
}
}
pub(super) fn report_placeholder_failure(
&self,
placeholder_origin: SubregionOrigin<'tcx>,
sub: Region<'tcx>,
sup: Region<'tcx>,
) -> DiagnosticBuilder<'tcx, ErrorGuaranteed> {
// I can't think how to do better than this right now. -nikomatsakis
debug!(?placeholder_origin, ?sub, ?sup, "report_placeholder_failure");
match placeholder_origin {
infer::Subtype(box ref trace)
if matches!(
&trace.cause.code().peel_derives(),
ObligationCauseCode::BindingObligation(..)
| ObligationCauseCode::ExprBindingObligation(..)
) =>
{
// Hack to get around the borrow checker because trace.cause has an `Rc`.
if let ObligationCauseCode::BindingObligation(_, span)
| ObligationCauseCode::ExprBindingObligation(_, span, ..) =
&trace.cause.code().peel_derives()
{
let span = *span;
let mut err = self.report_concrete_failure(placeholder_origin, sub, sup);
err.span_note(span, "the lifetime requirement is introduced here");
err
} else {
unreachable!()
}
}
infer::Subtype(box trace) => {
let terr = TypeError::RegionsPlaceholderMismatch;
return self.report_and_explain_type_error(trace, terr);
}
_ => return self.report_concrete_failure(placeholder_origin, sub, sup),
}
}
}

View file

@ -0,0 +1,672 @@
use hir::def::CtorKind;
use hir::intravisit::{walk_expr, walk_stmt, Visitor};
use rustc_data_structures::fx::FxIndexSet;
use rustc_errors::{Applicability, Diagnostic};
use rustc_hir as hir;
use rustc_middle::traits::{
IfExpressionCause, MatchExpressionArmCause, ObligationCause, ObligationCauseCode,
StatementAsExpression,
};
use rustc_middle::ty::print::with_no_trimmed_paths;
use rustc_middle::ty::{self as ty, Ty, TypeVisitable};
use rustc_span::{sym, BytePos, Span};
use crate::errors::SuggAddLetForLetChains;
use super::TypeErrCtxt;
impl<'tcx> TypeErrCtxt<'_, 'tcx> {
pub(super) fn suggest_remove_semi_or_return_binding(
&self,
err: &mut Diagnostic,
first_id: Option<hir::HirId>,
first_ty: Ty<'tcx>,
first_span: Span,
second_id: Option<hir::HirId>,
second_ty: Ty<'tcx>,
second_span: Span,
) {
let remove_semicolon = [
(first_id, self.resolve_vars_if_possible(second_ty)),
(second_id, self.resolve_vars_if_possible(first_ty)),
]
.into_iter()
.find_map(|(id, ty)| {
let hir::Node::Block(blk) = self.tcx.hir().get(id?) else { return None };
self.could_remove_semicolon(blk, ty)
});
match remove_semicolon {
Some((sp, StatementAsExpression::NeedsBoxing)) => {
err.multipart_suggestion(
"consider removing this semicolon and boxing the expressions",
vec![
(first_span.shrink_to_lo(), "Box::new(".to_string()),
(first_span.shrink_to_hi(), ")".to_string()),
(second_span.shrink_to_lo(), "Box::new(".to_string()),
(second_span.shrink_to_hi(), ")".to_string()),
(sp, String::new()),
],
Applicability::MachineApplicable,
);
}
Some((sp, StatementAsExpression::CorrectType)) => {
err.span_suggestion_short(
sp,
"consider removing this semicolon",
"",
Applicability::MachineApplicable,
);
}
None => {
for (id, ty) in [(first_id, second_ty), (second_id, first_ty)] {
if let Some(id) = id
&& let hir::Node::Block(blk) = self.tcx.hir().get(id)
&& self.consider_returning_binding(blk, ty, err)
{
break;
}
}
}
}
}
pub(super) fn suggest_boxing_for_return_impl_trait(
&self,
err: &mut Diagnostic,
return_sp: Span,
arm_spans: impl Iterator<Item = Span>,
) {
err.multipart_suggestion(
"you could change the return type to be a boxed trait object",
vec![
(return_sp.with_hi(return_sp.lo() + BytePos(4)), "Box<dyn".to_string()),
(return_sp.shrink_to_hi(), ">".to_string()),
],
Applicability::MaybeIncorrect,
);
let sugg = arm_spans
.flat_map(|sp| {
[(sp.shrink_to_lo(), "Box::new(".to_string()), (sp.shrink_to_hi(), ")".to_string())]
.into_iter()
})
.collect::<Vec<_>>();
err.multipart_suggestion(
"if you change the return type to expect trait objects, box the returned expressions",
sugg,
Applicability::MaybeIncorrect,
);
}
pub(super) fn suggest_tuple_pattern(
&self,
cause: &ObligationCause<'tcx>,
exp_found: &ty::error::ExpectedFound<Ty<'tcx>>,
diag: &mut Diagnostic,
) {
// Heavily inspired by `FnCtxt::suggest_compatible_variants`, with
// some modifications due to that being in typeck and this being in infer.
if let ObligationCauseCode::Pattern { .. } = cause.code() {
if let ty::Adt(expected_adt, substs) = exp_found.expected.kind() {
let compatible_variants: Vec<_> = expected_adt
.variants()
.iter()
.filter(|variant| {
variant.fields.len() == 1 && variant.ctor_kind() == Some(CtorKind::Fn)
})
.filter_map(|variant| {
let sole_field = &variant.fields[0];
let sole_field_ty = sole_field.ty(self.tcx, substs);
if self.same_type_modulo_infer(sole_field_ty, exp_found.found) {
let variant_path =
with_no_trimmed_paths!(self.tcx.def_path_str(variant.def_id));
// FIXME #56861: DRYer prelude filtering
if let Some(path) = variant_path.strip_prefix("std::prelude::") {
if let Some((_, path)) = path.split_once("::") {
return Some(path.to_string());
}
}
Some(variant_path)
} else {
None
}
})
.collect();
match &compatible_variants[..] {
[] => {}
[variant] => {
diag.multipart_suggestion_verbose(
&format!("try wrapping the pattern in `{}`", variant),
vec![
(cause.span.shrink_to_lo(), format!("{}(", variant)),
(cause.span.shrink_to_hi(), ")".to_string()),
],
Applicability::MaybeIncorrect,
);
}
_ => {
// More than one matching variant.
diag.multipart_suggestions(
&format!(
"try wrapping the pattern in a variant of `{}`",
self.tcx.def_path_str(expected_adt.did())
),
compatible_variants.into_iter().map(|variant| {
vec![
(cause.span.shrink_to_lo(), format!("{}(", variant)),
(cause.span.shrink_to_hi(), ")".to_string()),
]
}),
Applicability::MaybeIncorrect,
);
}
}
}
}
}
/// A possible error is to forget to add `.await` when using futures:
///
/// ```compile_fail,E0308
/// async fn make_u32() -> u32 {
/// 22
/// }
///
/// fn take_u32(x: u32) {}
///
/// async fn foo() {
/// let x = make_u32();
/// take_u32(x);
/// }
/// ```
///
/// This routine checks if the found type `T` implements `Future<Output=U>` where `U` is the
/// expected type. If this is the case, and we are inside of an async body, it suggests adding
/// `.await` to the tail of the expression.
pub(super) fn suggest_await_on_expect_found(
&self,
cause: &ObligationCause<'tcx>,
exp_span: Span,
exp_found: &ty::error::ExpectedFound<Ty<'tcx>>,
diag: &mut Diagnostic,
) {
debug!(
"suggest_await_on_expect_found: exp_span={:?}, expected_ty={:?}, found_ty={:?}",
exp_span, exp_found.expected, exp_found.found,
);
if let ObligationCauseCode::CompareImplItemObligation { .. } = cause.code() {
return;
}
match (
self.get_impl_future_output_ty(exp_found.expected),
self.get_impl_future_output_ty(exp_found.found),
) {
(Some(exp), Some(found)) if self.same_type_modulo_infer(exp, found) => match cause
.code()
{
ObligationCauseCode::IfExpression(box IfExpressionCause { then_id, .. }) => {
let then_span = self.find_block_span_from_hir_id(*then_id);
diag.multipart_suggestion(
"consider `await`ing on both `Future`s",
vec![
(then_span.shrink_to_hi(), ".await".to_string()),
(exp_span.shrink_to_hi(), ".await".to_string()),
],
Applicability::MaybeIncorrect,
);
}
ObligationCauseCode::MatchExpressionArm(box MatchExpressionArmCause {
prior_arms,
..
}) => {
if let [.., arm_span] = &prior_arms[..] {
diag.multipart_suggestion(
"consider `await`ing on both `Future`s",
vec![
(arm_span.shrink_to_hi(), ".await".to_string()),
(exp_span.shrink_to_hi(), ".await".to_string()),
],
Applicability::MaybeIncorrect,
);
} else {
diag.help("consider `await`ing on both `Future`s");
}
}
_ => {
diag.help("consider `await`ing on both `Future`s");
}
},
(_, Some(ty)) if self.same_type_modulo_infer(exp_found.expected, ty) => {
diag.span_suggestion_verbose(
exp_span.shrink_to_hi(),
"consider `await`ing on the `Future`",
".await",
Applicability::MaybeIncorrect,
);
}
(Some(ty), _) if self.same_type_modulo_infer(ty, exp_found.found) => match cause.code()
{
ObligationCauseCode::Pattern { span: Some(then_span), .. } => {
diag.span_suggestion_verbose(
then_span.shrink_to_hi(),
"consider `await`ing on the `Future`",
".await",
Applicability::MaybeIncorrect,
);
}
ObligationCauseCode::IfExpression(box IfExpressionCause { then_id, .. }) => {
let then_span = self.find_block_span_from_hir_id(*then_id);
diag.span_suggestion_verbose(
then_span.shrink_to_hi(),
"consider `await`ing on the `Future`",
".await",
Applicability::MaybeIncorrect,
);
}
ObligationCauseCode::MatchExpressionArm(box MatchExpressionArmCause {
ref prior_arms,
..
}) => {
diag.multipart_suggestion_verbose(
"consider `await`ing on the `Future`",
prior_arms
.iter()
.map(|arm| (arm.shrink_to_hi(), ".await".to_string()))
.collect(),
Applicability::MaybeIncorrect,
);
}
_ => {}
},
_ => {}
}
}
pub(super) fn suggest_accessing_field_where_appropriate(
&self,
cause: &ObligationCause<'tcx>,
exp_found: &ty::error::ExpectedFound<Ty<'tcx>>,
diag: &mut Diagnostic,
) {
debug!(
"suggest_accessing_field_where_appropriate(cause={:?}, exp_found={:?})",
cause, exp_found
);
if let ty::Adt(expected_def, expected_substs) = exp_found.expected.kind() {
if expected_def.is_enum() {
return;
}
if let Some((name, ty)) = expected_def
.non_enum_variant()
.fields
.iter()
.filter(|field| field.vis.is_accessible_from(field.did, self.tcx))
.map(|field| (field.name, field.ty(self.tcx, expected_substs)))
.find(|(_, ty)| self.same_type_modulo_infer(*ty, exp_found.found))
{
if let ObligationCauseCode::Pattern { span: Some(span), .. } = *cause.code() {
if let Ok(snippet) = self.tcx.sess.source_map().span_to_snippet(span) {
let suggestion = if expected_def.is_struct() {
format!("{}.{}", snippet, name)
} else if expected_def.is_union() {
format!("unsafe {{ {}.{} }}", snippet, name)
} else {
return;
};
diag.span_suggestion(
span,
&format!(
"you might have meant to use field `{}` whose type is `{}`",
name, ty
),
suggestion,
Applicability::MaybeIncorrect,
);
}
}
}
}
}
/// When encountering a case where `.as_ref()` on a `Result` or `Option` would be appropriate,
/// suggests it.
pub(super) fn suggest_as_ref_where_appropriate(
&self,
span: Span,
exp_found: &ty::error::ExpectedFound<Ty<'tcx>>,
diag: &mut Diagnostic,
) {
if let Ok(snippet) = self.tcx.sess.source_map().span_to_snippet(span)
&& let Some(msg) = self.should_suggest_as_ref(exp_found.expected, exp_found.found)
{
diag.span_suggestion(
span,
msg,
// HACK: fix issue# 100605, suggesting convert from &Option<T> to Option<&T>, remove the extra `&`
format!("{}.as_ref()", snippet.trim_start_matches('&')),
Applicability::MachineApplicable,
);
}
}
pub fn should_suggest_as_ref(&self, expected: Ty<'tcx>, found: Ty<'tcx>) -> Option<&str> {
if let (ty::Adt(exp_def, exp_substs), ty::Ref(_, found_ty, _)) =
(expected.kind(), found.kind())
{
if let ty::Adt(found_def, found_substs) = *found_ty.kind() {
if exp_def == &found_def {
let have_as_ref = &[
(
sym::Option,
"you can convert from `&Option<T>` to `Option<&T>` using \
`.as_ref()`",
),
(
sym::Result,
"you can convert from `&Result<T, E>` to \
`Result<&T, &E>` using `.as_ref()`",
),
];
if let Some(msg) = have_as_ref.iter().find_map(|(name, msg)| {
self.tcx.is_diagnostic_item(*name, exp_def.did()).then_some(msg)
}) {
let mut show_suggestion = true;
for (exp_ty, found_ty) in
std::iter::zip(exp_substs.types(), found_substs.types())
{
match *exp_ty.kind() {
ty::Ref(_, exp_ty, _) => {
match (exp_ty.kind(), found_ty.kind()) {
(_, ty::Param(_))
| (_, ty::Infer(_))
| (ty::Param(_), _)
| (ty::Infer(_), _) => {}
_ if self.same_type_modulo_infer(exp_ty, found_ty) => {}
_ => show_suggestion = false,
};
}
ty::Param(_) | ty::Infer(_) => {}
_ => show_suggestion = false,
}
}
if show_suggestion {
return Some(*msg);
}
}
}
}
}
None
}
/// Try to find code with pattern `if Some(..) = expr`
/// use a `visitor` to mark the `if` which its span contains given error span,
/// and then try to find a assignment in the `cond` part, which span is equal with error span
pub(super) fn suggest_let_for_letchains(
&self,
err: &mut Diagnostic,
cause: &ObligationCause<'_>,
span: Span,
) {
let hir = self.tcx.hir();
let fn_hir_id = hir.get_parent_node(cause.body_id);
if let Some(node) = self.tcx.hir().find(fn_hir_id) &&
let hir::Node::Item(hir::Item {
kind: hir::ItemKind::Fn(_sig, _, body_id), ..
}) = node {
let body = hir.body(*body_id);
/// Find the if expression with given span
struct IfVisitor {
pub result: bool,
pub found_if: bool,
pub err_span: Span,
}
impl<'v> Visitor<'v> for IfVisitor {
fn visit_expr(&mut self, ex: &'v hir::Expr<'v>) {
if self.result { return; }
match ex.kind {
hir::ExprKind::If(cond, _, _) => {
self.found_if = true;
walk_expr(self, cond);
self.found_if = false;
}
_ => walk_expr(self, ex),
}
}
fn visit_stmt(&mut self, ex: &'v hir::Stmt<'v>) {
if let hir::StmtKind::Local(hir::Local {
span, pat: hir::Pat{..}, ty: None, init: Some(_), ..
}) = &ex.kind
&& self.found_if
&& span.eq(&self.err_span) {
self.result = true;
}
walk_stmt(self, ex);
}
fn visit_body(&mut self, body: &'v hir::Body<'v>) {
hir::intravisit::walk_body(self, body);
}
}
let mut visitor = IfVisitor { err_span: span, found_if: false, result: false };
visitor.visit_body(&body);
if visitor.result {
err.subdiagnostic(SuggAddLetForLetChains{span: span.shrink_to_lo()});
}
}
}
}
impl<'tcx> TypeErrCtxt<'_, 'tcx> {
/// Be helpful when the user wrote `{... expr; }` and taking the `;` off
/// is enough to fix the error.
pub fn could_remove_semicolon(
&self,
blk: &'tcx hir::Block<'tcx>,
expected_ty: Ty<'tcx>,
) -> Option<(Span, StatementAsExpression)> {
let blk = blk.innermost_block();
// Do not suggest if we have a tail expr.
if blk.expr.is_some() {
return None;
}
let last_stmt = blk.stmts.last()?;
let hir::StmtKind::Semi(ref last_expr) = last_stmt.kind else {
return None;
};
let last_expr_ty = self.typeck_results.as_ref()?.expr_ty_opt(*last_expr)?;
let needs_box = match (last_expr_ty.kind(), expected_ty.kind()) {
_ if last_expr_ty.references_error() => return None,
_ if self.same_type_modulo_infer(last_expr_ty, expected_ty) => {
StatementAsExpression::CorrectType
}
(ty::Opaque(last_def_id, _), ty::Opaque(exp_def_id, _))
if last_def_id == exp_def_id =>
{
StatementAsExpression::CorrectType
}
(ty::Opaque(last_def_id, last_bounds), ty::Opaque(exp_def_id, exp_bounds)) => {
debug!(
"both opaque, likely future {:?} {:?} {:?} {:?}",
last_def_id, last_bounds, exp_def_id, exp_bounds
);
let last_local_id = last_def_id.as_local()?;
let exp_local_id = exp_def_id.as_local()?;
match (
&self.tcx.hir().expect_item(last_local_id).kind,
&self.tcx.hir().expect_item(exp_local_id).kind,
) {
(
hir::ItemKind::OpaqueTy(hir::OpaqueTy { bounds: last_bounds, .. }),
hir::ItemKind::OpaqueTy(hir::OpaqueTy { bounds: exp_bounds, .. }),
) if std::iter::zip(*last_bounds, *exp_bounds).all(|(left, right)| {
match (left, right) {
(
hir::GenericBound::Trait(tl, ml),
hir::GenericBound::Trait(tr, mr),
) if tl.trait_ref.trait_def_id() == tr.trait_ref.trait_def_id()
&& ml == mr =>
{
true
}
(
hir::GenericBound::LangItemTrait(langl, _, _, argsl),
hir::GenericBound::LangItemTrait(langr, _, _, argsr),
) if langl == langr => {
// FIXME: consider the bounds!
debug!("{:?} {:?}", argsl, argsr);
true
}
_ => false,
}
}) =>
{
StatementAsExpression::NeedsBoxing
}
_ => StatementAsExpression::CorrectType,
}
}
_ => return None,
};
let span = if last_stmt.span.from_expansion() {
let mac_call = rustc_span::source_map::original_sp(last_stmt.span, blk.span);
self.tcx.sess.source_map().mac_call_stmt_semi_span(mac_call)?
} else {
last_stmt.span.with_lo(last_stmt.span.hi() - BytePos(1))
};
Some((span, needs_box))
}
/// Suggest returning a local binding with a compatible type if the block
/// has no return expression.
pub fn consider_returning_binding(
&self,
blk: &'tcx hir::Block<'tcx>,
expected_ty: Ty<'tcx>,
err: &mut Diagnostic,
) -> bool {
let blk = blk.innermost_block();
// Do not suggest if we have a tail expr.
if blk.expr.is_some() {
return false;
}
let mut shadowed = FxIndexSet::default();
let mut candidate_idents = vec![];
let mut find_compatible_candidates = |pat: &hir::Pat<'_>| {
if let hir::PatKind::Binding(_, hir_id, ident, _) = &pat.kind
&& let Some(pat_ty) = self
.typeck_results
.as_ref()
.and_then(|typeck_results| typeck_results.node_type_opt(*hir_id))
{
let pat_ty = self.resolve_vars_if_possible(pat_ty);
if self.same_type_modulo_infer(pat_ty, expected_ty)
&& !(pat_ty, expected_ty).references_error()
&& shadowed.insert(ident.name)
{
candidate_idents.push((*ident, pat_ty));
}
}
true
};
let hir = self.tcx.hir();
for stmt in blk.stmts.iter().rev() {
let hir::StmtKind::Local(local) = &stmt.kind else { continue; };
local.pat.walk(&mut find_compatible_candidates);
}
match hir.find(hir.get_parent_node(blk.hir_id)) {
Some(hir::Node::Expr(hir::Expr { hir_id, .. })) => {
match hir.find(hir.get_parent_node(*hir_id)) {
Some(hir::Node::Arm(hir::Arm { pat, .. })) => {
pat.walk(&mut find_compatible_candidates);
}
Some(
hir::Node::Item(hir::Item { kind: hir::ItemKind::Fn(_, _, body), .. })
| hir::Node::ImplItem(hir::ImplItem {
kind: hir::ImplItemKind::Fn(_, body),
..
})
| hir::Node::TraitItem(hir::TraitItem {
kind: hir::TraitItemKind::Fn(_, hir::TraitFn::Provided(body)),
..
})
| hir::Node::Expr(hir::Expr {
kind: hir::ExprKind::Closure(hir::Closure { body, .. }),
..
}),
) => {
for param in hir.body(*body).params {
param.pat.walk(&mut find_compatible_candidates);
}
}
Some(hir::Node::Expr(hir::Expr {
kind:
hir::ExprKind::If(
hir::Expr { kind: hir::ExprKind::Let(let_), .. },
then_block,
_,
),
..
})) if then_block.hir_id == *hir_id => {
let_.pat.walk(&mut find_compatible_candidates);
}
_ => {}
}
}
_ => {}
}
match &candidate_idents[..] {
[(ident, _ty)] => {
let sm = self.tcx.sess.source_map();
if let Some(stmt) = blk.stmts.last() {
let stmt_span = sm.stmt_span(stmt.span, blk.span);
let sugg = if sm.is_multiline(blk.span)
&& let Some(spacing) = sm.indentation_before(stmt_span)
{
format!("\n{spacing}{ident}")
} else {
format!(" {ident}")
};
err.span_suggestion_verbose(
stmt_span.shrink_to_hi(),
format!("consider returning the local binding `{ident}`"),
sugg,
Applicability::MaybeIncorrect,
);
} else {
let sugg = if sm.is_multiline(blk.span)
&& let Some(spacing) = sm.indentation_before(blk.span.shrink_to_lo())
{
format!("\n{spacing} {ident}\n{spacing}")
} else {
format!(" {ident} ")
};
let left_span = sm.span_through_char(blk.span, '{').shrink_to_hi();
err.span_suggestion_verbose(
sm.span_extend_while(left_span, |c| c.is_whitespace()).unwrap_or(left_span),
format!("consider returning the local binding `{ident}`"),
sugg,
Applicability::MaybeIncorrect,
);
}
true
}
values if (1..3).contains(&values.len()) => {
let spans = values.iter().map(|(ident, _)| ident.span).collect::<Vec<_>>();
err.span_note(spans, "consider returning one of these bindings");
true
}
_ => false,
}
}
}

View file

@ -0,0 +1,203 @@
impl<'tcx> TypeErrCtxt<'_, 'tcx> {
fn note_error_origin(
&self,
err: &mut Diagnostic,
cause: &ObligationCause<'tcx>,
exp_found: Option<ty::error::ExpectedFound<Ty<'tcx>>>,
terr: TypeError<'tcx>,
) {
match *cause.code() {
ObligationCauseCode::Pattern { origin_expr: true, span: Some(span), root_ty } => {
let ty = self.resolve_vars_if_possible(root_ty);
if !matches!(ty.kind(), ty::Infer(ty::InferTy::TyVar(_) | ty::InferTy::FreshTy(_)))
{
// don't show type `_`
if span.desugaring_kind() == Some(DesugaringKind::ForLoop)
&& let ty::Adt(def, substs) = ty.kind()
&& Some(def.did()) == self.tcx.get_diagnostic_item(sym::Option)
{
err.span_label(span, format!("this is an iterator with items of type `{}`", substs.type_at(0)));
} else {
err.span_label(span, format!("this expression has type `{}`", ty));
}
}
if let Some(ty::error::ExpectedFound { found, .. }) = exp_found
&& ty.is_box() && ty.boxed_ty() == found
&& let Ok(snippet) = self.tcx.sess.source_map().span_to_snippet(span)
{
err.span_suggestion(
span,
"consider dereferencing the boxed value",
format!("*{}", snippet),
Applicability::MachineApplicable,
);
}
}
ObligationCauseCode::Pattern { origin_expr: false, span: Some(span), .. } => {
err.span_label(span, "expected due to this");
}
ObligationCauseCode::MatchExpressionArm(box MatchExpressionArmCause {
arm_block_id,
arm_span,
arm_ty,
prior_arm_block_id,
prior_arm_span,
prior_arm_ty,
source,
ref prior_arms,
scrut_hir_id,
opt_suggest_box_span,
scrut_span,
..
}) => match source {
hir::MatchSource::TryDesugar => {
if let Some(ty::error::ExpectedFound { expected, .. }) = exp_found {
let scrut_expr = self.tcx.hir().expect_expr(scrut_hir_id);
let scrut_ty = if let hir::ExprKind::Call(_, args) = &scrut_expr.kind {
let arg_expr = args.first().expect("try desugaring call w/out arg");
self.typeck_results.as_ref().and_then(|typeck_results| {
typeck_results.expr_ty_opt(arg_expr)
})
} else {
bug!("try desugaring w/out call expr as scrutinee");
};
match scrut_ty {
Some(ty) if expected == ty => {
let source_map = self.tcx.sess.source_map();
err.span_suggestion(
source_map.end_point(cause.span),
"try removing this `?`",
"",
Applicability::MachineApplicable,
);
}
_ => {}
}
}
}
_ => {
// `prior_arm_ty` can be `!`, `expected` will have better info when present.
let t = self.resolve_vars_if_possible(match exp_found {
Some(ty::error::ExpectedFound { expected, .. }) => expected,
_ => prior_arm_ty,
});
let source_map = self.tcx.sess.source_map();
let mut any_multiline_arm = source_map.is_multiline(arm_span);
if prior_arms.len() <= 4 {
for sp in prior_arms {
any_multiline_arm |= source_map.is_multiline(*sp);
err.span_label(*sp, format!("this is found to be of type `{}`", t));
}
} else if let Some(sp) = prior_arms.last() {
any_multiline_arm |= source_map.is_multiline(*sp);
err.span_label(
*sp,
format!("this and all prior arms are found to be of type `{}`", t),
);
}
let outer_error_span = if any_multiline_arm {
// Cover just `match` and the scrutinee expression, not
// the entire match body, to reduce diagram noise.
cause.span.shrink_to_lo().to(scrut_span)
} else {
cause.span
};
let msg = "`match` arms have incompatible types";
err.span_label(outer_error_span, msg);
self.suggest_remove_semi_or_return_binding(
err,
prior_arm_block_id,
prior_arm_ty,
prior_arm_span,
arm_block_id,
arm_ty,
arm_span,
);
if let Some(ret_sp) = opt_suggest_box_span {
// Get return type span and point to it.
self.suggest_boxing_for_return_impl_trait(
err,
ret_sp,
prior_arms.iter().chain(std::iter::once(&arm_span)).map(|s| *s),
);
}
}
},
ObligationCauseCode::IfExpression(box IfExpressionCause {
then_id,
else_id,
then_ty,
else_ty,
outer_span,
opt_suggest_box_span,
}) => {
let then_span = self.find_block_span_from_hir_id(then_id);
let else_span = self.find_block_span_from_hir_id(else_id);
err.span_label(then_span, "expected because of this");
if let Some(sp) = outer_span {
err.span_label(sp, "`if` and `else` have incompatible types");
}
self.suggest_remove_semi_or_return_binding(
err,
Some(then_id),
then_ty,
then_span,
Some(else_id),
else_ty,
else_span,
);
if let Some(ret_sp) = opt_suggest_box_span {
self.suggest_boxing_for_return_impl_trait(
err,
ret_sp,
[then_span, else_span].into_iter(),
);
}
}
ObligationCauseCode::LetElse => {
err.help("try adding a diverging expression, such as `return` or `panic!(..)`");
err.help("...or use `match` instead of `let...else`");
}
_ => {
if let ObligationCauseCode::BindingObligation(_, span)
| ObligationCauseCode::ExprBindingObligation(_, span, ..)
= cause.code().peel_derives()
&& let TypeError::RegionsPlaceholderMismatch = terr
{
err.span_note( * span,
"the lifetime requirement is introduced here");
}
}
}
}
}
impl<'tcx> InferCtxt<'tcx> {
/// Given a [`hir::Block`], get the span of its last expression or
/// statement, peeling off any inner blocks.
pub fn find_block_span(&self, block: &'tcx hir::Block<'tcx>) -> Span {
let block = block.innermost_block();
if let Some(expr) = &block.expr {
expr.span
} else if let Some(stmt) = block.stmts.last() {
// possibly incorrect trailing `;` in the else arm
stmt.span
} else {
// empty block; point at its entirety
block.span
}
}
/// Given a [`hir::HirId`] for a block, get the span of its last expression
/// or statement, peeling off any inner blocks.
pub fn find_block_span_from_hir_id(&self, hir_id: hir::HirId) -> Span {
match self.tcx.hir().get(hir_id) {
hir::Node::Block(blk) => self.find_block_span(blk),
// The parser was in a weird state if either of these happen, but
// it's better not to panic.
hir::Node::Expr(e) => e.span,
_ => rustc_span::DUMMY_SP,
}
}
}

View file

@ -12,13 +12,13 @@ extern crate tracing;
extern crate rustc_middle; extern crate rustc_middle;
mod chalk; mod chalk;
mod codegen;
mod dropck_outlives; mod dropck_outlives;
mod evaluate_obligation; mod evaluate_obligation;
mod implied_outlives_bounds; mod implied_outlives_bounds;
mod normalize_erasing_regions; mod normalize_erasing_regions;
mod normalize_projection_ty; mod normalize_projection_ty;
mod type_op; mod type_op;
mod codegen;
pub use type_op::{type_op_ascribe_user_type_with_span, type_op_prove_predicate_with_cause}; pub use type_op::{type_op_ascribe_user_type_with_span, type_op_prove_predicate_with_cause};