blob: 67ff412651c2091aaffe708a9febe2f800a80b79 [file] [log] [blame]
use crate::FnCtxt;
use rustc_errors::MultiSpan;
use rustc_errors::{Applicability, Diag};
use rustc_hir as hir;
use rustc_hir::def::Res;
use rustc_hir::intravisit::Visitor;
use rustc_infer::infer::{DefineOpaqueTypes, InferOk};
use rustc_middle::ty::adjustment::AllowTwoPhase;
use rustc_middle::ty::error::{ExpectedFound, TypeError};
use rustc_middle::ty::fold::BottomUpFolder;
use rustc_middle::ty::print::with_no_trimmed_paths;
use rustc_middle::ty::{self, AssocItem, Ty, TypeFoldable, TypeVisitableExt};
use rustc_span::symbol::sym;
use rustc_span::{Span, DUMMY_SP};
use rustc_trait_selection::traits::ObligationCause;
use super::method::probe;
impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
pub fn emit_type_mismatch_suggestions(
&self,
err: &mut Diag<'_>,
expr: &hir::Expr<'tcx>,
expr_ty: Ty<'tcx>,
expected: Ty<'tcx>,
expected_ty_expr: Option<&'tcx hir::Expr<'tcx>>,
error: Option<TypeError<'tcx>>,
) {
if expr_ty == expected {
return;
}
self.annotate_alternative_method_deref(err, expr, error);
self.explain_self_literal(err, expr, expected, expr_ty);
// Use `||` to give these suggestions a precedence
let suggested = self.suggest_missing_parentheses(err, expr)
|| self.suggest_missing_unwrap_expect(err, expr, expected, expr_ty)
|| self.suggest_remove_last_method_call(err, expr, expected)
|| self.suggest_associated_const(err, expr, expected)
|| self.suggest_deref_ref_or_into(err, expr, expected, expr_ty, expected_ty_expr)
|| self.suggest_option_to_bool(err, expr, expr_ty, expected)
|| self.suggest_compatible_variants(err, expr, expected, expr_ty)
|| self.suggest_non_zero_new_unwrap(err, expr, expected, expr_ty)
|| self.suggest_calling_boxed_future_when_appropriate(err, expr, expected, expr_ty)
|| self.suggest_no_capture_closure(err, expected, expr_ty)
|| self.suggest_boxing_when_appropriate(
err,
expr.peel_blocks().span,
expr.hir_id,
expected,
expr_ty,
)
|| self.suggest_block_to_brackets_peeling_refs(err, expr, expr_ty, expected)
|| self.suggest_copied_cloned_or_as_ref(err, expr, expr_ty, expected)
|| self.suggest_clone_for_ref(err, expr, expr_ty, expected)
|| self.suggest_into(err, expr, expr_ty, expected)
|| self.suggest_floating_point_literal(err, expr, expected)
|| self.suggest_null_ptr_for_literal_zero_given_to_ptr_arg(err, expr, expected)
|| self.suggest_coercing_result_via_try_operator(err, expr, expected, expr_ty);
if !suggested {
self.note_source_of_type_mismatch_constraint(
err,
expr,
TypeMismatchSource::Ty(expected),
);
}
}
pub fn emit_coerce_suggestions(
&self,
err: &mut Diag<'_>,
expr: &hir::Expr<'tcx>,
expr_ty: Ty<'tcx>,
expected: Ty<'tcx>,
expected_ty_expr: Option<&'tcx hir::Expr<'tcx>>,
error: Option<TypeError<'tcx>>,
) {
if expr_ty == expected {
return;
}
self.annotate_expected_due_to_let_ty(err, expr, error);
self.annotate_loop_expected_due_to_inference(err, expr, error);
// FIXME(#73154): For now, we do leak check when coercing function
// pointers in typeck, instead of only during borrowck. This can lead
// to these `RegionsInsufficientlyPolymorphic` errors that aren't helpful.
if matches!(error, Some(TypeError::RegionsInsufficientlyPolymorphic(..))) {
return;
}
if self.is_destruct_assignment_desugaring(expr) {
return;
}
self.emit_type_mismatch_suggestions(err, expr, expr_ty, expected, expected_ty_expr, error);
self.note_type_is_not_clone(err, expected, expr_ty, expr);
self.note_internal_mutation_in_method(err, expr, Some(expected), expr_ty);
self.suggest_method_call_on_range_literal(err, expr, expr_ty, expected);
self.suggest_return_binding_for_missing_tail_expr(err, expr, expr_ty, expected);
self.note_wrong_return_ty_due_to_generic_arg(err, expr, expr_ty);
}
/// Really hacky heuristic to remap an `assert_eq!` error to the user
/// expressions provided to the macro.
fn adjust_expr_for_assert_eq_macro(
&self,
found_expr: &mut &'tcx hir::Expr<'tcx>,
expected_expr: &mut Option<&'tcx hir::Expr<'tcx>>,
) {
let Some(expected_expr) = expected_expr else {
return;
};
if !found_expr.span.eq_ctxt(expected_expr.span) {
return;
}
if !found_expr
.span
.ctxt()
.outer_expn_data()
.macro_def_id
.is_some_and(|def_id| self.tcx.is_diagnostic_item(sym::assert_eq_macro, def_id))
{
return;
}
let hir::ExprKind::Unary(
hir::UnOp::Deref,
hir::Expr { kind: hir::ExprKind::Path(found_path), .. },
) = found_expr.kind
else {
return;
};
let hir::ExprKind::Unary(
hir::UnOp::Deref,
hir::Expr { kind: hir::ExprKind::Path(expected_path), .. },
) = expected_expr.kind
else {
return;
};
for (path, name, idx, var) in [
(expected_path, "left_val", 0, expected_expr),
(found_path, "right_val", 1, found_expr),
] {
if let hir::QPath::Resolved(_, path) = path
&& let [segment] = path.segments
&& segment.ident.name.as_str() == name
&& let Res::Local(hir_id) = path.res
&& let Some((_, hir::Node::Expr(match_expr))) =
self.tcx.hir().parent_iter(hir_id).nth(2)
&& let hir::ExprKind::Match(scrutinee, _, _) = match_expr.kind
&& let hir::ExprKind::Tup(exprs) = scrutinee.kind
&& let hir::ExprKind::AddrOf(_, _, macro_arg) = exprs[idx].kind
{
*var = macro_arg;
}
}
}
/// Requires that the two types unify, and prints an error message if
/// they don't.
pub fn demand_suptype(&self, sp: Span, expected: Ty<'tcx>, actual: Ty<'tcx>) {
if let Some(e) = self.demand_suptype_diag(sp, expected, actual) {
e.emit();
}
}
pub fn demand_suptype_diag(
&self,
sp: Span,
expected: Ty<'tcx>,
actual: Ty<'tcx>,
) -> Option<Diag<'tcx>> {
self.demand_suptype_with_origin(&self.misc(sp), expected, actual)
}
#[instrument(skip(self), level = "debug")]
pub fn demand_suptype_with_origin(
&self,
cause: &ObligationCause<'tcx>,
expected: Ty<'tcx>,
actual: Ty<'tcx>,
) -> Option<Diag<'tcx>> {
match self.at(cause, self.param_env).sup(DefineOpaqueTypes::Yes, expected, actual) {
Ok(InferOk { obligations, value: () }) => {
self.register_predicates(obligations);
None
}
Err(e) => Some(self.err_ctxt().report_mismatched_types(cause, expected, actual, e)),
}
}
pub fn demand_eqtype(&self, sp: Span, expected: Ty<'tcx>, actual: Ty<'tcx>) {
if let Some(err) = self.demand_eqtype_diag(sp, expected, actual) {
err.emit();
}
}
pub fn demand_eqtype_diag(
&self,
sp: Span,
expected: Ty<'tcx>,
actual: Ty<'tcx>,
) -> Option<Diag<'tcx>> {
self.demand_eqtype_with_origin(&self.misc(sp), expected, actual)
}
pub fn demand_eqtype_with_origin(
&self,
cause: &ObligationCause<'tcx>,
expected: Ty<'tcx>,
actual: Ty<'tcx>,
) -> Option<Diag<'tcx>> {
match self.at(cause, self.param_env).eq(DefineOpaqueTypes::Yes, expected, actual) {
Ok(InferOk { obligations, value: () }) => {
self.register_predicates(obligations);
None
}
Err(e) => Some(self.err_ctxt().report_mismatched_types(cause, expected, actual, e)),
}
}
pub fn demand_coerce(
&self,
expr: &'tcx hir::Expr<'tcx>,
checked_ty: Ty<'tcx>,
expected: Ty<'tcx>,
expected_ty_expr: Option<&'tcx hir::Expr<'tcx>>,
allow_two_phase: AllowTwoPhase,
) -> Ty<'tcx> {
let (ty, err) =
self.demand_coerce_diag(expr, checked_ty, expected, expected_ty_expr, allow_two_phase);
if let Some(err) = err {
err.emit();
}
ty
}
/// Checks that the type of `expr` can be coerced to `expected`.
///
/// N.B., this code relies on `self.diverges` to be accurate. In particular, assignments to `!`
/// will be permitted if the diverges flag is currently "always".
#[instrument(level = "debug", skip(self, expr, expected_ty_expr, allow_two_phase))]
pub fn demand_coerce_diag(
&self,
mut expr: &'tcx hir::Expr<'tcx>,
checked_ty: Ty<'tcx>,
expected: Ty<'tcx>,
mut expected_ty_expr: Option<&'tcx hir::Expr<'tcx>>,
allow_two_phase: AllowTwoPhase,
) -> (Ty<'tcx>, Option<Diag<'tcx>>) {
let expected = self.resolve_vars_with_obligations(expected);
let e = match self.coerce(expr, checked_ty, expected, allow_two_phase, None) {
Ok(ty) => return (ty, None),
Err(e) => e,
};
self.adjust_expr_for_assert_eq_macro(&mut expr, &mut expected_ty_expr);
self.set_tainted_by_errors(self.dcx().span_delayed_bug(
expr.span,
"`TypeError` when attempting coercion but no error emitted",
));
let expr = expr.peel_drop_temps();
let cause = self.misc(expr.span);
let expr_ty = self.resolve_vars_if_possible(checked_ty);
let mut err = self.err_ctxt().report_mismatched_types(&cause, expected, expr_ty, e);
self.emit_coerce_suggestions(&mut err, expr, expr_ty, expected, expected_ty_expr, Some(e));
(expected, Some(err))
}
/// Notes the point at which a variable is constrained to some type incompatible
/// with some expectation given by `source`.
pub fn note_source_of_type_mismatch_constraint(
&self,
err: &mut Diag<'_>,
expr: &hir::Expr<'_>,
source: TypeMismatchSource<'tcx>,
) -> bool {
let hir = self.tcx.hir();
let hir::ExprKind::Path(hir::QPath::Resolved(None, p)) = expr.kind else {
return false;
};
let [hir::PathSegment { ident, args: None, .. }] = p.segments else {
return false;
};
let hir::def::Res::Local(local_hir_id) = p.res else {
return false;
};
let hir::Node::Pat(pat) = self.tcx.hir_node(local_hir_id) else {
return false;
};
let (init_ty_hir_id, init) = match self.tcx.parent_hir_node(pat.hir_id) {
hir::Node::Local(hir::Local { ty: Some(ty), init, .. }) => (ty.hir_id, *init),
hir::Node::Local(hir::Local { init: Some(init), .. }) => (init.hir_id, Some(*init)),
_ => return false,
};
let Some(init_ty) = self.node_ty_opt(init_ty_hir_id) else {
return false;
};
// Locate all the usages of the relevant binding.
struct FindExprs<'tcx> {
hir_id: hir::HirId,
uses: Vec<&'tcx hir::Expr<'tcx>>,
}
impl<'tcx> Visitor<'tcx> for FindExprs<'tcx> {
fn visit_expr(&mut self, ex: &'tcx hir::Expr<'tcx>) {
if let hir::ExprKind::Path(hir::QPath::Resolved(None, path)) = ex.kind
&& let hir::def::Res::Local(hir_id) = path.res
&& hir_id == self.hir_id
{
self.uses.push(ex);
}
hir::intravisit::walk_expr(self, ex);
}
}
let mut expr_finder = FindExprs { hir_id: local_hir_id, uses: init.into_iter().collect() };
let body =
hir.body(hir.maybe_body_owned_by(self.body_id).expect("expected item to have body"));
expr_finder.visit_expr(body.value);
use rustc_infer::infer::type_variable::*;
use rustc_middle::infer::unify_key::*;
// Replaces all of the variables in the given type with a fresh inference variable.
let mut fudger = BottomUpFolder {
tcx: self.tcx,
ty_op: |ty| {
if let ty::Infer(infer) = ty.kind() {
match infer {
ty::TyVar(_) => self.next_ty_var(TypeVariableOrigin {
kind: TypeVariableOriginKind::MiscVariable,
span: DUMMY_SP,
}),
ty::IntVar(_) => self.next_int_var(),
ty::FloatVar(_) => self.next_float_var(),
ty::FreshTy(_) | ty::FreshIntTy(_) | ty::FreshFloatTy(_) => {
bug!("unexpected fresh ty outside of the trait solver")
}
}
} else {
ty
}
},
lt_op: |_| self.tcx.lifetimes.re_erased,
ct_op: |ct| {
if let ty::ConstKind::Infer(_) = ct.kind() {
self.next_const_var(
ct.ty(),
ConstVariableOrigin {
kind: ConstVariableOriginKind::MiscVariable,
span: DUMMY_SP,
},
)
} else {
ct
}
},
};
let expected_ty = match source {
TypeMismatchSource::Ty(expected_ty) => expected_ty,
// Try to deduce what the possible value of `expr` would be if the
// incompatible arg were compatible. For example, given `Vec<i32>`
// and `vec.push(1u32)`, we ideally want to deduce that the type of
// `vec` *should* have been `Vec<u32>`. This will allow us to then
// run the subsequent code with this expectation, finding out exactly
// when this type diverged from our expectation.
TypeMismatchSource::Arg { call_expr, incompatible_arg: idx } => {
let hir::ExprKind::MethodCall(segment, _, args, _) = call_expr.kind else {
return false;
};
let Some(arg_ty) = self.node_ty_opt(args[idx].hir_id) else {
return false;
};
let possible_rcvr_ty = expr_finder.uses.iter().find_map(|binding| {
let possible_rcvr_ty = self.node_ty_opt(binding.hir_id)?;
// Fudge the receiver, so we can do new inference on it.
let possible_rcvr_ty = possible_rcvr_ty.fold_with(&mut fudger);
let method = self
.lookup_method_for_diagnostic(
possible_rcvr_ty,
segment,
DUMMY_SP,
call_expr,
binding,
)
.ok()?;
// Unify the method signature with our incompatible arg, to
// do inference in the *opposite* direction and to find out
// what our ideal rcvr ty would look like.
let _ = self
.at(&ObligationCause::dummy(), self.param_env)
.eq(DefineOpaqueTypes::No, method.sig.inputs()[idx + 1], arg_ty)
.ok()?;
self.select_obligations_where_possible(|errs| {
// Yeet the errors, we're already reporting errors.
errs.clear();
});
Some(self.resolve_vars_if_possible(possible_rcvr_ty))
});
if let Some(rcvr_ty) = possible_rcvr_ty {
rcvr_ty
} else {
return false;
}
}
};
// If our expected_ty does not equal init_ty, then it *began* as incompatible.
// No need to note in this case...
if !self.can_eq(self.param_env, expected_ty, init_ty.fold_with(&mut fudger)) {
return false;
}
for window in expr_finder.uses.windows(2) {
// Bindings always update their recorded type after the fact, so we
// need to look at the *following* usage's type to see when the
// binding became incompatible.
let [binding, next_usage] = *window else {
continue;
};
// Don't go past the binding (always gonna be a nonsense label if so)
if binding.hir_id == expr.hir_id {
break;
}
let Some(next_use_ty) = self.node_ty_opt(next_usage.hir_id) else {
continue;
};
// If the type is not constrained in a way making it not possible to
// equate with `expected_ty` by this point, skip.
if self.can_eq(self.param_env, expected_ty, next_use_ty.fold_with(&mut fudger)) {
continue;
}
if let hir::Node::Expr(parent_expr) = self.tcx.parent_hir_node(binding.hir_id)
&& let hir::ExprKind::MethodCall(segment, rcvr, args, _) = parent_expr.kind
&& rcvr.hir_id == binding.hir_id
{
// If our binding became incompatible while it was a receiver
// to a method call, we may be able to make a better guess to
// the source of a type mismatch.
let Some(rcvr_ty) = self.node_ty_opt(rcvr.hir_id) else {
continue;
};
let rcvr_ty = rcvr_ty.fold_with(&mut fudger);
let Ok(method) = self.lookup_method_for_diagnostic(
rcvr_ty,
segment,
DUMMY_SP,
parent_expr,
rcvr,
) else {
continue;
};
let ideal_rcvr_ty = rcvr_ty.fold_with(&mut fudger);
let ideal_method = self
.lookup_method_for_diagnostic(
ideal_rcvr_ty,
segment,
DUMMY_SP,
parent_expr,
rcvr,
)
.ok()
.and_then(|method| {
let _ = self
.at(&ObligationCause::dummy(), self.param_env)
.eq(DefineOpaqueTypes::No, ideal_rcvr_ty, expected_ty)
.ok()?;
Some(method)
});
// Find what argument caused our rcvr to become incompatible
// with the expected ty.
for (idx, (expected_arg_ty, arg_expr)) in
std::iter::zip(&method.sig.inputs()[1..], args).enumerate()
{
let Some(arg_ty) = self.node_ty_opt(arg_expr.hir_id) else {
continue;
};
let arg_ty = arg_ty.fold_with(&mut fudger);
let _ =
self.coerce(arg_expr, arg_ty, *expected_arg_ty, AllowTwoPhase::No, None);
self.select_obligations_where_possible(|errs| {
// Yeet the errors, we're already reporting errors.
errs.clear();
});
// If our rcvr, after inference due to unifying the signature
// with the expected argument type, is still compatible with
// the rcvr, then it must've not been the source of blame.
if self.can_eq(self.param_env, rcvr_ty, expected_ty) {
continue;
}
err.span_label(arg_expr.span, format!("this argument has type `{arg_ty}`..."));
err.span_label(
binding.span,
format!("... which causes `{ident}` to have type `{next_use_ty}`"),
);
// Using our "ideal" method signature, suggest a fix to this
// blame arg, if possible. Don't do this if we're coming from
// arg mismatch code, because we'll possibly suggest a mutually
// incompatible fix at the original mismatch site.
if matches!(source, TypeMismatchSource::Ty(_))
&& let Some(ideal_method) = ideal_method
&& let ideal_arg_ty = self.resolve_vars_if_possible(ideal_method.sig.inputs()[idx + 1])
// HACK(compiler-errors): We don't actually consider the implications
// of our inference guesses in `emit_type_mismatch_suggestions`, so
// only suggest things when we know our type error is precisely due to
// a type mismatch, and not via some projection or something. See #116155.
&& !ideal_arg_ty.has_non_region_infer()
{
self.emit_type_mismatch_suggestions(
err,
arg_expr,
arg_ty,
ideal_arg_ty,
None,
None,
);
}
return true;
}
}
err.span_label(
binding.span,
format!("here the type of `{ident}` is inferred to be `{next_use_ty}`"),
);
return true;
}
// We must've not found something that constrained the expr.
false
}
// When encountering a type error on the value of a `break`, try to point at the reason for the
// expected type.
pub fn annotate_loop_expected_due_to_inference(
&self,
err: &mut Diag<'_>,
expr: &hir::Expr<'_>,
error: Option<TypeError<'tcx>>,
) {
let Some(TypeError::Sorts(ExpectedFound { expected, .. })) = error else {
return;
};
let mut parent_id = self.tcx.parent_hir_id(expr.hir_id);
let mut parent;
'outer: loop {
// Climb the HIR tree to see if the current `Expr` is part of a `break;` statement.
let (hir::Node::Stmt(hir::Stmt { kind: hir::StmtKind::Semi(&ref p), .. })
| hir::Node::Block(hir::Block { expr: Some(&ref p), .. })
| hir::Node::Expr(&ref p)) = self.tcx.hir_node(parent_id)
else {
break;
};
parent = p;
parent_id = self.tcx.parent_hir_id(parent_id);
let hir::ExprKind::Break(destination, _) = parent.kind else {
continue;
};
let mut parent_id = parent_id;
let mut direct = false;
loop {
// Climb the HIR tree to find the (desugared) `loop` this `break` corresponds to.
let parent = match self.tcx.hir_node(parent_id) {
hir::Node::Expr(&ref parent) => {
parent_id = self.tcx.parent_hir_id(parent.hir_id);
parent
}
hir::Node::Stmt(hir::Stmt {
hir_id,
kind: hir::StmtKind::Semi(&ref parent) | hir::StmtKind::Expr(&ref parent),
..
}) => {
parent_id = self.tcx.parent_hir_id(*hir_id);
parent
}
hir::Node::Block(_) => {
parent_id = self.tcx.parent_hir_id(parent_id);
parent
}
_ => break,
};
if let hir::ExprKind::Loop(..) = parent.kind {
// When you have `'a: loop { break; }`, the `break` corresponds to the labeled
// loop, so we need to account for that.
direct = !direct;
}
if let hir::ExprKind::Loop(block, label, _, span) = parent.kind
&& (destination.label == label || direct)
{
if let Some((reason_span, message)) =
self.maybe_get_coercion_reason(parent_id, parent.span)
{
err.span_label(reason_span, message);
err.span_label(
span,
format!("this loop is expected to be of type `{expected}`"),
);
break 'outer;
} else {
// Locate all other `break` statements within the same `loop` that might
// have affected inference.
struct FindBreaks<'tcx> {
label: Option<rustc_ast::Label>,
uses: Vec<&'tcx hir::Expr<'tcx>>,
nest_depth: usize,
}
impl<'tcx> Visitor<'tcx> for FindBreaks<'tcx> {
fn visit_expr(&mut self, ex: &'tcx hir::Expr<'tcx>) {
let nest_depth = self.nest_depth;
if let hir::ExprKind::Loop(_, label, _, _) = ex.kind {
if label == self.label {
// Account for `'a: loop { 'a: loop {...} }`.
return;
}
self.nest_depth += 1;
}
if let hir::ExprKind::Break(destination, _) = ex.kind
&& (self.label == destination.label
// Account for `loop { 'a: loop { loop { break; } } }`.
|| destination.label.is_none() && self.nest_depth == 0)
{
self.uses.push(ex);
}
hir::intravisit::walk_expr(self, ex);
self.nest_depth = nest_depth;
}
}
let mut expr_finder = FindBreaks { label, uses: vec![], nest_depth: 0 };
expr_finder.visit_block(block);
let mut exit = false;
for ex in expr_finder.uses {
let hir::ExprKind::Break(_, val) = ex.kind else {
continue;
};
let ty = match val {
Some(val) => {
match self.typeck_results.borrow().expr_ty_adjusted_opt(val) {
None => continue,
Some(ty) => ty,
}
}
None => self.tcx.types.unit,
};
if self.can_eq(self.param_env, ty, expected) {
err.span_label(ex.span, "expected because of this `break`");
exit = true;
}
}
if exit {
break 'outer;
}
}
}
}
}
}
fn annotate_expected_due_to_let_ty(
&self,
err: &mut Diag<'_>,
expr: &hir::Expr<'_>,
error: Option<TypeError<'tcx>>,
) {
match (self.tcx.parent_hir_node(expr.hir_id), error) {
(hir::Node::Local(hir::Local { ty: Some(ty), init: Some(init), .. }), _)
if init.hir_id == expr.hir_id =>
{
// Point at `let` assignment type.
err.span_label(ty.span, "expected due to this");
}
(
hir::Node::Expr(hir::Expr { kind: hir::ExprKind::Assign(lhs, rhs, _), .. }),
Some(TypeError::Sorts(ExpectedFound { expected, .. })),
) if rhs.hir_id == expr.hir_id && !expected.is_closure() => {
// We ignore closures explicitly because we already point at them elsewhere.
// Point at the assigned-to binding.
let mut primary_span = lhs.span;
let mut secondary_span = lhs.span;
let mut post_message = "";
match lhs.kind {
hir::ExprKind::Path(hir::QPath::Resolved(
None,
hir::Path {
res:
hir::def::Res::Def(
hir::def::DefKind::Static { .. } | hir::def::DefKind::Const,
def_id,
),
..
},
)) => {
if let Some(hir::Node::Item(hir::Item {
ident,
kind: hir::ItemKind::Static(ty, ..) | hir::ItemKind::Const(ty, ..),
..
})) = self.tcx.hir().get_if_local(*def_id)
{
primary_span = ty.span;
secondary_span = ident.span;
post_message = " type";
}
}
hir::ExprKind::Path(hir::QPath::Resolved(
None,
hir::Path { res: hir::def::Res::Local(hir_id), .. },
)) => {
if let hir::Node::Pat(pat) = self.tcx.hir_node(*hir_id) {
primary_span = pat.span;
secondary_span = pat.span;
match self.tcx.parent_hir_node(pat.hir_id) {
hir::Node::Local(hir::Local { ty: Some(ty), .. }) => {
primary_span = ty.span;
post_message = " type";
}
hir::Node::Local(hir::Local { init: Some(init), .. }) => {
primary_span = init.span;
post_message = " value";
}
hir::Node::Param(hir::Param { ty_span, .. }) => {
primary_span = *ty_span;
post_message = " parameter type";
}
_ => {}
}
}
}
_ => {}
}
if primary_span != secondary_span
&& self
.tcx
.sess
.source_map()
.is_multiline(secondary_span.shrink_to_hi().until(primary_span))
{
// We are pointing at the binding's type or initializer value, but it's pattern
// is in a different line, so we point at both.
err.span_label(secondary_span, "expected due to the type of this binding");
err.span_label(primary_span, format!("expected due to this{post_message}"));
} else if post_message.is_empty() {
// We are pointing at either the assignment lhs or the binding def pattern.
err.span_label(primary_span, "expected due to the type of this binding");
} else {
// We are pointing at the binding's type or initializer value.
err.span_label(primary_span, format!("expected due to this{post_message}"));
}
if !lhs.is_syntactic_place_expr() {
// We already emitted E0070 "invalid left-hand side of assignment", so we
// silence this.
err.downgrade_to_delayed_bug();
}
}
(
hir::Node::Expr(hir::Expr { kind: hir::ExprKind::Binary(_, lhs, rhs), .. }),
Some(TypeError::Sorts(ExpectedFound { expected, .. })),
) if rhs.hir_id == expr.hir_id
&& self.typeck_results.borrow().expr_ty_adjusted_opt(lhs) == Some(expected) =>
{
err.span_label(lhs.span, format!("expected because this is `{expected}`"));
}
_ => {}
}
}
fn annotate_alternative_method_deref(
&self,
err: &mut Diag<'_>,
expr: &hir::Expr<'_>,
error: Option<TypeError<'tcx>>,
) {
let Some(TypeError::Sorts(ExpectedFound { expected, .. })) = error else {
return;
};
let hir::Node::Expr(hir::Expr { kind: hir::ExprKind::Assign(lhs, rhs, _), .. }) =
self.tcx.parent_hir_node(expr.hir_id)
else {
return;
};
if rhs.hir_id != expr.hir_id || expected.is_closure() {
return;
}
let hir::ExprKind::Unary(hir::UnOp::Deref, deref) = lhs.kind else {
return;
};
let hir::ExprKind::MethodCall(path, base, args, _) = deref.kind else {
return;
};
let Some(self_ty) = self.typeck_results.borrow().expr_ty_adjusted_opt(base) else {
return;
};
let Ok(pick) = self.lookup_probe_for_diagnostic(
path.ident,
self_ty,
deref,
probe::ProbeScope::TraitsInScope,
None,
) else {
return;
};
let in_scope_methods = self.probe_for_name_many(
probe::Mode::MethodCall,
path.ident,
Some(expected),
probe::IsSuggestion(true),
self_ty,
deref.hir_id,
probe::ProbeScope::TraitsInScope,
);
let other_methods_in_scope: Vec<_> =
in_scope_methods.iter().filter(|c| c.item.def_id != pick.item.def_id).collect();
let all_methods = self.probe_for_name_many(
probe::Mode::MethodCall,
path.ident,
Some(expected),
probe::IsSuggestion(true),
self_ty,
deref.hir_id,
probe::ProbeScope::AllTraits,
);
let suggestions: Vec<_> = all_methods
.into_iter()
.filter(|c| c.item.def_id != pick.item.def_id)
.map(|c| {
let m = c.item;
let generic_args = ty::GenericArgs::for_item(self.tcx, m.def_id, |param, _| {
self.var_for_def(deref.span, param)
});
let mutability =
match self.tcx.fn_sig(m.def_id).skip_binder().input(0).skip_binder().kind() {
ty::Ref(_, _, hir::Mutability::Mut) => "&mut ",
ty::Ref(_, _, _) => "&",
_ => "",
};
vec![
(
deref.span.until(base.span),
format!(
"{}({}",
with_no_trimmed_paths!(
self.tcx.def_path_str_with_args(m.def_id, generic_args,)
),
mutability,
),
),
match &args {
[] => (base.span.shrink_to_hi().with_hi(deref.span.hi()), ")".to_string()),
[first, ..] => (base.span.between(first.span), ", ".to_string()),
},
]
})
.collect();
if suggestions.is_empty() {
return;
}
let mut path_span: MultiSpan = path.ident.span.into();
path_span.push_span_label(
path.ident.span,
with_no_trimmed_paths!(format!(
"refers to `{}`",
self.tcx.def_path_str(pick.item.def_id),
)),
);
let container_id = pick.item.container_id(self.tcx);
let container = with_no_trimmed_paths!(self.tcx.def_path_str(container_id));
for def_id in pick.import_ids {
let hir_id = self.tcx.local_def_id_to_hir_id(def_id);
path_span.push_span_label(
self.tcx.hir().span(hir_id),
format!("`{container}` imported here"),
);
}
let tail = with_no_trimmed_paths!(match &other_methods_in_scope[..] {
[] => return,
[candidate] => format!(
"the method of the same name on {} `{}`",
match candidate.kind {
probe::CandidateKind::InherentImplCandidate(..) => "the inherent impl for",
_ => "trait",
},
self.tcx.def_path_str(candidate.item.container_id(self.tcx))
),
[.., last] if other_methods_in_scope.len() < 5 => {
format!(
"the methods of the same name on {} and `{}`",
other_methods_in_scope[..other_methods_in_scope.len() - 1]
.iter()
.map(|c| format!(
"`{}`",
self.tcx.def_path_str(c.item.container_id(self.tcx))
))
.collect::<Vec<String>>()
.join(", "),
self.tcx.def_path_str(last.item.container_id(self.tcx))
)
}
_ => format!(
"the methods of the same name on {} other traits",
other_methods_in_scope.len()
),
});
err.span_note(
path_span,
format!(
"the `{}` call is resolved to the method in `{container}`, shadowing {tail}",
path.ident,
),
);
if suggestions.len() > other_methods_in_scope.len() {
err.note(format!(
"additionally, there are {} other available methods that aren't in scope",
suggestions.len() - other_methods_in_scope.len()
));
}
err.multipart_suggestions(
format!(
"you might have meant to call {}; you can use the fully-qualified path to call {} \
explicitly",
if suggestions.len() == 1 {
"the other method"
} else {
"one of the other methods"
},
if suggestions.len() == 1 { "it" } else { "one of them" },
),
suggestions,
Applicability::MaybeIncorrect,
);
}
pub fn get_conversion_methods(
&self,
span: Span,
expected: Ty<'tcx>,
checked_ty: Ty<'tcx>,
hir_id: hir::HirId,
) -> Vec<AssocItem> {
let methods = self.probe_for_return_type(
span,
probe::Mode::MethodCall,
expected,
checked_ty,
hir_id,
|m| {
self.has_only_self_parameter(m)
&& self
.tcx
// This special internal attribute is used to permit
// "identity-like" conversion methods to be suggested here.
//
// FIXME (#46459 and #46460): ideally
// `std::convert::Into::into` and `std::borrow:ToOwned` would
// also be `#[rustc_conversion_suggestion]`, if not for
// method-probing false-positives and -negatives (respectively).
//
// FIXME? Other potential candidate methods: `as_ref` and
// `as_mut`?
.has_attr(m.def_id, sym::rustc_conversion_suggestion)
},
);
methods
}
/// This function checks whether the method is not static and does not accept other parameters than `self`.
fn has_only_self_parameter(&self, method: &AssocItem) -> bool {
match method.kind {
ty::AssocKind::Fn => {
method.fn_has_self_parameter
&& self.tcx.fn_sig(method.def_id).skip_binder().inputs().skip_binder().len()
== 1
}
_ => false,
}
}
/// If the given `HirId` corresponds to a block with a trailing expression, return that expression
pub(crate) fn maybe_get_block_expr(
&self,
expr: &hir::Expr<'tcx>,
) -> Option<&'tcx hir::Expr<'tcx>> {
match expr {
hir::Expr { kind: hir::ExprKind::Block(block, ..), .. } => block.expr,
_ => None,
}
}
// Returns whether the given expression is a destruct assignment desugaring.
// For example, `(a, b) = (1, &2);`
// Here we try to find the pattern binding of the expression,
// `default_binding_modes` is false only for destruct assignment desugaring.
pub(crate) fn is_destruct_assignment_desugaring(&self, expr: &hir::Expr<'_>) -> bool {
if let hir::ExprKind::Path(hir::QPath::Resolved(
_,
hir::Path { res: hir::def::Res::Local(bind_hir_id), .. },
)) = expr.kind
{
let bind = self.tcx.hir_node(*bind_hir_id);
let parent = self.tcx.parent_hir_node(*bind_hir_id);
if let hir::Node::Pat(hir::Pat {
kind: hir::PatKind::Binding(_, _hir_id, _, _), ..
}) = bind
&& let hir::Node::Pat(hir::Pat { default_binding_modes: false, .. }) = parent
{
return true;
}
}
return false;
}
fn explain_self_literal(
&self,
err: &mut Diag<'_>,
expr: &hir::Expr<'tcx>,
expected: Ty<'tcx>,
found: Ty<'tcx>,
) {
match expr.peel_drop_temps().kind {
hir::ExprKind::Struct(
hir::QPath::Resolved(
None,
hir::Path { res: hir::def::Res::SelfTyAlias { alias_to, .. }, span, .. },
),
..,
)
| hir::ExprKind::Call(
hir::Expr {
kind:
hir::ExprKind::Path(hir::QPath::Resolved(
None,
hir::Path {
res: hir::def::Res::SelfTyAlias { alias_to, .. },
span,
..
},
)),
..
},
..,
) => {
if let Some(hir::Node::Item(hir::Item {
kind: hir::ItemKind::Impl(hir::Impl { self_ty, .. }),
..
})) = self.tcx.hir().get_if_local(*alias_to)
{
err.span_label(self_ty.span, "this is the type of the `Self` literal");
}
if let ty::Adt(e_def, e_args) = expected.kind()
&& let ty::Adt(f_def, _f_args) = found.kind()
&& e_def == f_def
{
err.span_suggestion_verbose(
*span,
"use the type name directly",
self.tcx.value_path_str_with_args(e_def.did(), e_args),
Applicability::MaybeIncorrect,
);
}
}
_ => {}
}
}
fn note_wrong_return_ty_due_to_generic_arg(
&self,
err: &mut Diag<'_>,
expr: &hir::Expr<'_>,
checked_ty: Ty<'tcx>,
) {
let hir::Node::Expr(parent_expr) = self.tcx.parent_hir_node(expr.hir_id) else {
return;
};
enum CallableKind {
Function,
Method,
Constructor,
}
let mut maybe_emit_help = |def_id: hir::def_id::DefId,
callable: rustc_span::symbol::Ident,
args: &[hir::Expr<'_>],
kind: CallableKind| {
let arg_idx = args.iter().position(|a| a.hir_id == expr.hir_id).unwrap();
let fn_ty = self.tcx.type_of(def_id).skip_binder();
if !fn_ty.is_fn() {
return;
}
let fn_sig = fn_ty.fn_sig(self.tcx).skip_binder();
let Some(&arg) = fn_sig
.inputs()
.get(arg_idx + if matches!(kind, CallableKind::Method) { 1 } else { 0 })
else {
return;
};
if matches!(arg.kind(), ty::Param(_))
&& fn_sig.output().contains(arg)
&& self.node_ty(args[arg_idx].hir_id) == checked_ty
{
let mut multi_span: MultiSpan = parent_expr.span.into();
multi_span.push_span_label(
args[arg_idx].span,
format!(
"this argument influences the {} of `{}`",
if matches!(kind, CallableKind::Constructor) {
"type"
} else {
"return type"
},
callable
),
);
err.span_help(
multi_span,
format!(
"the {} `{}` due to the type of the argument passed",
match kind {
CallableKind::Function => "return type of this call is",
CallableKind::Method => "return type of this call is",
CallableKind::Constructor => "type constructed contains",
},
checked_ty
),
);
}
};
match parent_expr.kind {
hir::ExprKind::Call(fun, args) => {
let hir::ExprKind::Path(hir::QPath::Resolved(_, path)) = fun.kind else {
return;
};
let hir::def::Res::Def(kind, def_id) = path.res else {
return;
};
let callable_kind = if matches!(kind, hir::def::DefKind::Ctor(_, _)) {
CallableKind::Constructor
} else {
CallableKind::Function
};
maybe_emit_help(def_id, path.segments[0].ident, args, callable_kind);
}
hir::ExprKind::MethodCall(method, _receiver, args, _span) => {
let Some(def_id) =
self.typeck_results.borrow().type_dependent_def_id(parent_expr.hir_id)
else {
return;
};
maybe_emit_help(def_id, method.ident, args, CallableKind::Method)
}
_ => return,
}
}
}
pub enum TypeMismatchSource<'tcx> {
/// Expected the binding to have the given type, but it was found to have
/// a different type. Find out when that type first became incompatible.
Ty(Ty<'tcx>),
/// When we fail during method argument checking, try to find out if a previous
/// expression has constrained the method's receiver in a way that makes the
/// argument's type incompatible.
Arg { call_expr: &'tcx hir::Expr<'tcx>, incompatible_arg: usize },
}