| use crate::FnCtxt; |
| use rustc_errors::MultiSpan; |
| use rustc_errors::{Applicability, Diagnostic, DiagnosticBuilder, ErrorGuaranteed}; |
| 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 Diagnostic, |
| 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); |
| |
| // Use `||` to give these suggestions a precedence |
| let suggested = self.suggest_missing_parentheses(err, expr) |
| || 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.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) |
| || self.suggest_missing_unwrap_expect(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 Diagnostic, |
| 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(mut 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<DiagnosticBuilder<'tcx, ErrorGuaranteed>> { |
| 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<DiagnosticBuilder<'tcx, ErrorGuaranteed>> { |
| 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(mut 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<DiagnosticBuilder<'tcx, ErrorGuaranteed>> { |
| 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<DiagnosticBuilder<'tcx, ErrorGuaranteed>> { |
| 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(mut 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<DiagnosticBuilder<'tcx, ErrorGuaranteed>>) { |
| 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.tcx.sess.delay_span_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 Diagnostic, |
| 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) = hir.get(local_hir_id) else { |
| return false; |
| }; |
| let (init_ty_hir_id, init) = match hir.get_parent(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::InferTy::TyVar(_) => self.next_ty_var(TypeVariableOrigin { |
| kind: TypeVariableOriginKind::MiscVariable, |
| span: DUMMY_SP, |
| }), |
| ty::InferTy::IntVar(_) => self.next_int_var(), |
| ty::InferTy::FloatVar(_) => self.next_float_var(), |
| _ => bug!(), |
| } |
| } 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) = hir.get_parent(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 Diagnostic, |
| expr: &hir::Expr<'_>, |
| error: Option<TypeError<'tcx>>, |
| ) { |
| let Some(TypeError::Sorts(ExpectedFound { expected, .. })) = error else { |
| return; |
| }; |
| let mut parent_id = self.tcx.hir().parent_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 Some( |
| 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().find(parent_id) |
| else { |
| break; |
| }; |
| parent = p; |
| parent_id = self.tcx.hir().parent_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().find(parent_id) { |
| Some(hir::Node::Expr(&ref parent)) => { |
| parent_id = self.tcx.hir().parent_id(parent.hir_id); |
| parent |
| } |
| Some(hir::Node::Stmt(hir::Stmt { |
| hir_id, |
| kind: hir::StmtKind::Semi(&ref parent) | hir::StmtKind::Expr(&ref parent), |
| .. |
| })) => { |
| parent_id = self.tcx.hir().parent_id(*hir_id); |
| parent |
| } |
| Some(hir::Node::Block(_)) => { |
| parent_id = self.tcx.hir().parent_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 Diagnostic, |
| expr: &hir::Expr<'_>, |
| error: Option<TypeError<'tcx>>, |
| ) { |
| let parent = self.tcx.hir().parent_id(expr.hir_id); |
| match (self.tcx.hir().find(parent), error) { |
| (Some(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"); |
| } |
| ( |
| Some(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 Some(hir::Node::Pat(pat)) = self.tcx.hir().find(*hir_id) { |
| primary_span = pat.span; |
| secondary_span = pat.span; |
| match self.tcx.hir().find_parent(pat.hir_id) { |
| Some(hir::Node::Local(hir::Local { ty: Some(ty), .. })) => { |
| primary_span = ty.span; |
| post_message = " type"; |
| } |
| Some(hir::Node::Local(hir::Local { init: Some(init), .. })) => { |
| primary_span = init.span; |
| post_message = " value"; |
| } |
| Some(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(); |
| } |
| } |
| ( |
| Some(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 Diagnostic, |
| expr: &hir::Expr<'_>, |
| error: Option<TypeError<'tcx>>, |
| ) { |
| let parent = self.tcx.hir().parent_id(expr.hir_id); |
| let Some(TypeError::Sorts(ExpectedFound { expected, .. })) = error else { |
| return; |
| }; |
| let Some(hir::Node::Expr(hir::Expr { kind: hir::ExprKind::Assign(lhs, rhs, _), .. })) = |
| self.tcx.hir().find(parent) |
| 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.hir().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().find(*bind_hir_id); |
| let parent = self.tcx.hir().find(self.tcx.hir().parent_id(*bind_hir_id)); |
| if let Some(hir::Node::Pat(hir::Pat { |
| kind: hir::PatKind::Binding(_, _hir_id, _, _), |
| .. |
| })) = bind |
| && let Some(hir::Node::Pat(hir::Pat { default_binding_modes: false, .. })) = parent |
| { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| fn note_wrong_return_ty_due_to_generic_arg( |
| &self, |
| err: &mut Diagnostic, |
| expr: &hir::Expr<'_>, |
| checked_ty: Ty<'tcx>, |
| ) { |
| let Some(hir::Node::Expr(parent_expr)) = self.tcx.hir().find_parent(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 }, |
| } |