summaryrefslogtreecommitdiffstats
path: root/compiler/rustc_hir_typeck/src/fn_ctxt
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-06-07 05:48:48 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-06-07 05:48:48 +0000
commitef24de24a82fe681581cc130f342363c47c0969a (patch)
tree0d494f7e1a38b95c92426f58fe6eaa877303a86c /compiler/rustc_hir_typeck/src/fn_ctxt
parentReleasing progress-linux version 1.74.1+dfsg1-1~progress7.99u1. (diff)
downloadrustc-ef24de24a82fe681581cc130f342363c47c0969a.tar.xz
rustc-ef24de24a82fe681581cc130f342363c47c0969a.zip
Merging upstream version 1.75.0+dfsg1.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'compiler/rustc_hir_typeck/src/fn_ctxt')
-rw-r--r--compiler/rustc_hir_typeck/src/fn_ctxt/_impl.rs100
-rw-r--r--compiler/rustc_hir_typeck/src/fn_ctxt/adjust_fulfillment_errors.rs65
-rw-r--r--compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs167
-rw-r--r--compiler/rustc_hir_typeck/src/fn_ctxt/mod.rs6
-rw-r--r--compiler/rustc_hir_typeck/src/fn_ctxt/suggestions.rs1616
5 files changed, 1751 insertions, 203 deletions
diff --git a/compiler/rustc_hir_typeck/src/fn_ctxt/_impl.rs b/compiler/rustc_hir_typeck/src/fn_ctxt/_impl.rs
index 415920221..750ed2c34 100644
--- a/compiler/rustc_hir_typeck/src/fn_ctxt/_impl.rs
+++ b/compiler/rustc_hir_typeck/src/fn_ctxt/_impl.rs
@@ -26,7 +26,7 @@ use rustc_middle::ty::error::TypeError;
use rustc_middle::ty::fold::TypeFoldable;
use rustc_middle::ty::visit::{TypeVisitable, TypeVisitableExt};
use rustc_middle::ty::{
- self, AdtKind, CanonicalUserType, GenericParamDefKind, Ty, TyCtxt, UserType,
+ self, AdtKind, CanonicalUserType, GenericParamDefKind, IsIdentity, Ty, TyCtxt, UserType,
};
use rustc_middle::ty::{GenericArgKind, GenericArgsRef, UserArgs, UserSelfTy};
use rustc_session::lint;
@@ -207,6 +207,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
) {
debug!("fcx {}", self.tag());
+ // FIXME: is_identity being on `UserType` and not `Canonical<UserType>` is awkward
if !canonical_user_type_annotation.is_identity() {
self.typeck_results
.borrow_mut()
@@ -445,7 +446,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
pub fn node_ty(&self, id: hir::HirId) -> Ty<'tcx> {
match self.typeck_results.borrow().node_types().get(id) {
Some(&t) => t,
- None if let Some(e) = self.tainted_by_errors() => Ty::new_error(self.tcx,e),
+ None if let Some(e) = self.tainted_by_errors() => Ty::new_error(self.tcx, e),
None => {
bug!(
"no type for node {} in fcx {}",
@@ -459,7 +460,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
pub fn node_ty_opt(&self, id: hir::HirId) -> Option<Ty<'tcx>> {
match self.typeck_results.borrow().node_types().get(id) {
Some(&t) => Some(t),
- None if let Some(e) = self.tainted_by_errors() => Some(Ty::new_error(self.tcx,e)),
+ None if let Some(e) = self.tainted_by_errors() => Some(Ty::new_error(self.tcx, e)),
None => None,
}
}
@@ -509,40 +510,40 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
typeck_results.rvalue_scopes = rvalue_scopes;
}
- /// Unify the inference variables corresponding to generator witnesses, and save all the
+ /// Unify the inference variables corresponding to coroutine witnesses, and save all the
/// predicates that were stalled on those inference variables.
///
- /// This process allows to conservatively save all predicates that do depend on the generator
- /// interior types, for later processing by `check_generator_obligations`.
+ /// This process allows to conservatively save all predicates that do depend on the coroutine
+ /// interior types, for later processing by `check_coroutine_obligations`.
///
/// We must not attempt to select obligations after this method has run, or risk query cycle
/// ICE.
#[instrument(level = "debug", skip(self))]
- pub(in super::super) fn resolve_generator_interiors(&self, def_id: DefId) {
+ pub(in super::super) fn resolve_coroutine_interiors(&self, def_id: DefId) {
// Try selecting all obligations that are not blocked on inference variables.
- // Once we start unifying generator witnesses, trying to select obligations on them will
+ // Once we start unifying coroutine witnesses, trying to select obligations on them will
// trigger query cycle ICEs, as doing so requires MIR.
self.select_obligations_where_possible(|_| {});
- let generators = std::mem::take(&mut *self.deferred_generator_interiors.borrow_mut());
- debug!(?generators);
+ let coroutines = std::mem::take(&mut *self.deferred_coroutine_interiors.borrow_mut());
+ debug!(?coroutines);
- for &(expr_def_id, body_id, interior, _) in generators.iter() {
+ for &(expr_def_id, body_id, interior, _) in coroutines.iter() {
debug!(?expr_def_id);
- // Create the `GeneratorWitness` type that we will unify with `interior`.
+ // Create the `CoroutineWitness` type that we will unify with `interior`.
let args = ty::GenericArgs::identity_for_item(
self.tcx,
self.tcx.typeck_root_def_id(expr_def_id.to_def_id()),
);
- let witness = Ty::new_generator_witness(self.tcx, expr_def_id.to_def_id(), args);
+ let witness = Ty::new_coroutine_witness(self.tcx, expr_def_id.to_def_id(), args);
// Unify `interior` with `witness` and collect all the resulting obligations.
let span = self.tcx.hir().body(body_id).value.span;
let ok = self
.at(&self.misc(span), self.param_env)
.eq(DefineOpaqueTypes::No, interior, witness)
- .expect("Failed to unify generator interior type");
+ .expect("Failed to unify coroutine interior type");
let mut obligations = ok.obligations;
// Also collect the obligations that were unstalled by this unification.
@@ -553,7 +554,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
debug!(?obligations);
self.typeck_results
.borrow_mut()
- .generator_interior_predicates
+ .coroutine_interior_predicates
.insert(expr_def_id, obligations);
}
}
@@ -564,7 +565,12 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
if !errors.is_empty() {
self.adjust_fulfillment_errors_for_expr_obligation(&mut errors);
- self.err_ctxt().report_fulfillment_errors(&errors);
+ let errors_causecode = errors
+ .iter()
+ .map(|e| (e.obligation.cause.span, e.root_obligation.cause.code().clone()))
+ .collect::<Vec<_>>();
+ self.err_ctxt().report_fulfillment_errors(errors);
+ self.collect_unused_stmts_for_coerce_return_ty(errors_causecode);
}
}
@@ -577,7 +583,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
if !result.is_empty() {
mutate_fulfillment_errors(&mut result);
self.adjust_fulfillment_errors_for_expr_obligation(&mut result);
- self.err_ctxt().report_fulfillment_errors(&result);
+ self.err_ctxt().report_fulfillment_errors(result);
}
}
@@ -713,7 +719,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
if let ty::GenericArgKind::Type(ty) = ty.unpack()
&& let ty::Alias(ty::Opaque, ty::AliasTy { def_id, .. }) = *ty.kind()
&& let Some(def_id) = def_id.as_local()
- && self.opaque_type_origin(def_id).is_some() {
+ && self.opaque_type_origin(def_id).is_some()
+ {
return None;
}
}
@@ -790,6 +797,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
qpath: &'tcx QPath<'tcx>,
hir_id: hir::HirId,
span: Span,
+ args: Option<&'tcx [hir::Expr<'tcx>]>,
) -> (Res, Option<RawTy<'tcx>>, &'tcx [hir::PathSegment<'tcx>]) {
debug!(
"resolve_ty_and_res_fully_qualified_call: qpath={:?} hir_id={:?} span={:?}",
@@ -833,7 +841,13 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
.resolve_fully_qualified_call(span, item_name, ty.normalized, qself.span, hir_id)
.and_then(|r| {
// lint bare trait if the method is found in the trait
- if span.edition().at_least_rust_2021() && let Some(mut diag) = self.tcx.sess.diagnostic().steal_diagnostic(qself.span, StashKey::TraitMissingMethod) {
+ if span.edition().at_least_rust_2021()
+ && let Some(mut diag) = self
+ .tcx
+ .sess
+ .diagnostic()
+ .steal_diagnostic(qself.span, StashKey::TraitMissingMethod)
+ {
diag.emit();
}
Ok(r)
@@ -863,7 +877,13 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
}
// emit or cancel the diagnostic for bare traits
- if span.edition().at_least_rust_2021() && let Some(mut diag) = self.tcx.sess.diagnostic().steal_diagnostic(qself.span, StashKey::TraitMissingMethod) {
+ if span.edition().at_least_rust_2021()
+ && let Some(mut diag) = self
+ .tcx
+ .sess
+ .diagnostic()
+ .steal_diagnostic(qself.span, StashKey::TraitMissingMethod)
+ {
if trait_missing_method {
// cancel the diag for bare traits when meeting `MyTrait::missing_method`
diag.cancel();
@@ -879,7 +899,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
item_name,
SelfSource::QPath(qself),
error,
- None,
+ args,
Expectation::NoExpectation,
trait_missing_method && span.edition().at_least_rust_2021(), // emits missing method for trait only after edition 2021
) {
@@ -949,12 +969,15 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
kind: hir::ItemKind::Fn(ref sig, ..),
owner_id,
..
- })) = self.tcx.hir().find_parent(hir_id) => Some((
- hir::HirId::make_owner(owner_id.def_id),
- &sig.decl,
- ident,
- ident.name != sym::main,
- )),
+ })) = self.tcx.hir().find_parent(hir_id) =>
+ {
+ Some((
+ hir::HirId::make_owner(owner_id.def_id),
+ &sig.decl,
+ ident,
+ ident.name != sym::main,
+ ))
+ }
_ => None,
}
}
@@ -1077,11 +1100,10 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
let mut user_self_ty = None;
let mut is_alias_variant_ctor = false;
match res {
- Res::Def(DefKind::Ctor(CtorOf::Variant, _), _)
- if let Some(self_ty) = self_ty =>
- {
+ Res::Def(DefKind::Ctor(CtorOf::Variant, _), _) if let Some(self_ty) = self_ty => {
let adt_def = self_ty.normalized.ty_adt_def().unwrap();
- user_self_ty = Some(UserSelfTy { impl_def_id: adt_def.did(), self_ty: self_ty.raw });
+ user_self_ty =
+ Some(UserSelfTy { impl_def_id: adt_def.did(), self_ty: self_ty.raw });
is_alias_variant_ctor = true;
}
Res::Def(DefKind::AssocFn | DefKind::AssocConst, def_id) => {
@@ -1090,9 +1112,13 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
let container_id = assoc_item.container_id(tcx);
debug!(?def_id, ?container, ?container_id);
match container {
- ty::TraitContainer => {
- callee::check_legal_trait_for_method_call(tcx, span, None, span, container_id)
- }
+ ty::TraitContainer => callee::check_legal_trait_for_method_call(
+ tcx,
+ span,
+ None,
+ span,
+ container_id,
+ ),
ty::ImplContainer => {
if segments.len() == 1 {
// `<T>::assoc` will end up here, and so
@@ -1477,13 +1503,13 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
{
Ok(normalized_ty) => normalized_ty,
Err(errors) => {
- let guar = self.err_ctxt().report_fulfillment_errors(&errors);
- return Ty::new_error(self.tcx,guar);
+ let guar = self.err_ctxt().report_fulfillment_errors(errors);
+ return Ty::new_error(self.tcx, guar);
}
}
} else {
ty
- }
+ }
}
/// Resolves `ty` by a single level if `ty` is a type variable.
diff --git a/compiler/rustc_hir_typeck/src/fn_ctxt/adjust_fulfillment_errors.rs b/compiler/rustc_hir_typeck/src/fn_ctxt/adjust_fulfillment_errors.rs
index 43d4496dd..facbeb8ba 100644
--- a/compiler/rustc_hir_typeck/src/fn_ctxt/adjust_fulfillment_errors.rs
+++ b/compiler/rustc_hir_typeck/src/fn_ctxt/adjust_fulfillment_errors.rs
@@ -129,21 +129,19 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
return false;
}
- for param in
- [param_to_point_at, fallback_param_to_point_at, self_param_to_point_at]
+ for param in [param_to_point_at, fallback_param_to_point_at, self_param_to_point_at]
.into_iter()
.flatten()
{
if self.blame_specific_arg_if_possible(
- error,
- def_id,
- param,
- *call_hir_id,
- callee.span,
- None,
- args,
- )
- {
+ error,
+ def_id,
+ param,
+ *call_hir_id,
+ callee.span,
+ None,
+ args,
+ ) {
return true;
}
}
@@ -346,8 +344,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
&& let TypeVariableOriginKind::TypeParameterDefinition(_, def_id) = origin.kind
&& let generics = self.0.tcx.generics_of(self.1)
&& let Some(index) = generics.param_def_id_to_index(self.0.tcx, def_id)
- && let Some(subst) = ty::GenericArgs::identity_for_item(self.0.tcx, self.1)
- .get(index as usize)
+ && let Some(subst) =
+ ty::GenericArgs::identity_for_item(self.0.tcx, self.1).get(index as usize)
{
ControlFlow::Break(*subst)
} else {
@@ -364,11 +362,12 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
span: Span,
) -> bool {
if let traits::FulfillmentErrorCode::CodeSelectionError(
- traits::SelectionError::OutputTypeParameterMismatch(box traits::SelectionOutputTypeParameterMismatch{
- expected_trait_ref, ..
- }),
+ traits::SelectionError::OutputTypeParameterMismatch(
+ box traits::SelectionOutputTypeParameterMismatch { expected_trait_ref, .. },
+ ),
) = error.code
- && let ty::Closure(def_id, _) | ty::Generator(def_id, ..) = expected_trait_ref.skip_binder().self_ty().kind()
+ && let ty::Closure(def_id, _) | ty::Coroutine(def_id, ..) =
+ expected_trait_ref.skip_binder().self_ty().kind()
&& span.overlaps(self.tcx.def_span(*def_id))
{
true
@@ -446,10 +445,14 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
.collect();
// If there's one field that references the given generic, great!
if let [(idx, _)] = args_referencing_param.as_slice()
- && let Some(arg) = receiver
- .map_or(args.get(*idx), |rcvr| if *idx == 0 { Some(rcvr) } else { args.get(*idx - 1) }) {
-
- error.obligation.cause.span = arg.span.find_ancestor_in_same_ctxt(error.obligation.cause.span).unwrap_or(arg.span);
+ && let Some(arg) = receiver.map_or(args.get(*idx), |rcvr| {
+ if *idx == 0 { Some(rcvr) } else { args.get(*idx - 1) }
+ })
+ {
+ error.obligation.cause.span = arg
+ .span
+ .find_ancestor_in_same_ctxt(error.obligation.cause.span)
+ .unwrap_or(arg.span);
if let hir::Node::Expr(arg_expr) = self.tcx.hir().get(arg.hir_id) {
// This is more specific than pointing at the entire argument.
@@ -934,16 +937,16 @@ fn find_param_in_ty<'tcx>(
return true;
}
if let ty::GenericArgKind::Type(ty) = arg.unpack()
- && let ty::Alias(ty::Projection | ty::Inherent, ..) = ty.kind()
- {
- // This logic may seem a bit strange, but typically when
- // we have a projection type in a function signature, the
- // argument that's being passed into that signature is
- // not actually constraining that projection's args in
- // a meaningful way. So we skip it, and see improvements
- // in some UI tests.
- walk.skip_current_subtree();
- }
+ && let ty::Alias(ty::Projection | ty::Inherent, ..) = ty.kind()
+ {
+ // This logic may seem a bit strange, but typically when
+ // we have a projection type in a function signature, the
+ // argument that's being passed into that signature is
+ // not actually constraining that projection's args in
+ // a meaningful way. So we skip it, and see improvements
+ // in some UI tests.
+ walk.skip_current_subtree();
+ }
}
false
}
diff --git a/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs b/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs
index c0332a48b..33dfa16a6 100644
--- a/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs
+++ b/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs
@@ -11,11 +11,12 @@ use crate::{
use rustc_ast as ast;
use rustc_data_structures::fx::FxIndexSet;
use rustc_errors::{
- pluralize, Applicability, Diagnostic, DiagnosticId, ErrorGuaranteed, MultiSpan,
+ pluralize, Applicability, Diagnostic, DiagnosticId, ErrorGuaranteed, MultiSpan, StashKey,
};
use rustc_hir as hir;
use rustc_hir::def::{CtorOf, DefKind, Res};
use rustc_hir::def_id::DefId;
+use rustc_hir::intravisit::Visitor;
use rustc_hir::{ExprKind, Node, QPath};
use rustc_hir_analysis::astconv::AstConv;
use rustc_hir_analysis::check::intrinsicck::InlineAsmCtxt;
@@ -26,9 +27,10 @@ use rustc_infer::infer::error_reporting::{FailureCode, ObligationCauseExt};
use rustc_infer::infer::type_variable::{TypeVariableOrigin, TypeVariableOriginKind};
use rustc_infer::infer::TypeTrace;
use rustc_infer::infer::{DefineOpaqueTypes, InferOk};
+use rustc_middle::traits::ObligationCauseCode::ExprBindingObligation;
use rustc_middle::ty::adjustment::AllowTwoPhase;
use rustc_middle::ty::visit::TypeVisitableExt;
-use rustc_middle::ty::{self, IsSuggestable, Ty};
+use rustc_middle::ty::{self, IsSuggestable, Ty, TyCtxt};
use rustc_session::Session;
use rustc_span::symbol::{kw, Ident};
use rustc_span::{self, sym, BytePos, Span};
@@ -365,13 +367,13 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
continue;
}
- // For this check, we do *not* want to treat async generator closures (async blocks)
+ // For this check, we do *not* want to treat async coroutine closures (async blocks)
// as proper closures. Doing so would regress type inference when feeding
// the return value of an argument-position async block to an argument-position
// closure wrapped in a block.
// See <https://github.com/rust-lang/rust/issues/112225>.
let is_closure = if let ExprKind::Closure(closure) = arg.kind {
- !tcx.generator_is_async(closure.def_id.to_def_id())
+ !tcx.coroutine_is_async(closure.def_id.to_def_id())
} else {
false
};
@@ -651,7 +653,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
&& provided_arg_tys.len() == formal_and_expected_inputs.len() - 1 + tys.len()
{
// Wrap up the N provided arguments starting at this position in a tuple.
- let provided_as_tuple = Ty::new_tup_from_iter(tcx,
+ let provided_as_tuple = Ty::new_tup_from_iter(
+ tcx,
provided_arg_tys.iter().map(|(ty, _)| *ty).skip(mismatch_idx).take(tys.len()),
);
@@ -722,6 +725,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
&mut err,
fn_def_id,
callee_ty,
+ call_expr,
+ None,
Some(mismatch_idx),
is_method,
);
@@ -826,6 +831,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
&mut err,
fn_def_id,
callee_ty,
+ call_expr,
+ Some(expected_ty),
Some(expected_idx.as_usize()),
is_method,
);
@@ -879,8 +886,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
&& self.tcx.def_kind(fn_def_id).is_fn_like()
&& let self_implicit =
matches!(call_expr.kind, hir::ExprKind::MethodCall(..)) as usize
- && let Some(arg) = self.tcx.fn_arg_names(fn_def_id)
- .get(expected_idx.as_usize() + self_implicit)
+ && let Some(arg) =
+ self.tcx.fn_arg_names(fn_def_id).get(expected_idx.as_usize() + self_implicit)
&& arg.name != kw::SelfLower
{
format!("/* {} */", arg.name)
@@ -941,9 +948,9 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
&& error_span.can_be_used_for_suggestions()
{
if arg_idx.index() > 0
- && let Some((_, prev)) = provided_arg_tys
- .get(ProvidedIdx::from_usize(arg_idx.index() - 1)
- ) {
+ && let Some((_, prev)) =
+ provided_arg_tys.get(ProvidedIdx::from_usize(arg_idx.index() - 1))
+ {
// Include previous comma
span = prev.shrink_to_hi().to(span);
}
@@ -1208,7 +1215,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
}
// Call out where the function is defined
- self.label_fn_like(&mut err, fn_def_id, callee_ty, None, is_method);
+ self.label_fn_like(&mut err, fn_def_id, callee_ty, call_expr, None, None, is_method);
// And add a suggestion block for all of the parameters
let suggestion_text = match suggestion_text {
@@ -1286,7 +1293,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
err: &mut rustc_errors::DiagnosticBuilder<'tcx, ErrorGuaranteed>,
) {
if let ty::RawPtr(ty::TypeAndMut { mutbl: hir::Mutability::Mut, .. }) = expected_ty.kind()
- && let ty::RawPtr(ty::TypeAndMut { mutbl: hir::Mutability::Not, .. }) = provided_ty.kind()
+ && let ty::RawPtr(ty::TypeAndMut { mutbl: hir::Mutability::Not, .. }) =
+ provided_ty.kind()
&& let hir::ExprKind::Call(callee, _) = arg.kind
&& let hir::ExprKind::Path(hir::QPath::Resolved(_, path)) = callee.kind
&& let Res::Def(_, def_id) = path.res
@@ -1294,9 +1302,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
{
// The user provided `ptr::null()`, but the function expects
// `ptr::null_mut()`.
- err.subdiagnostic(SuggestPtrNullMut {
- span: arg.span
- });
+ err.subdiagnostic(SuggestPtrNullMut { span: arg.span });
}
}
@@ -1370,7 +1376,10 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
}
_ => bug!("unexpected type: {:?}", ty.normalized),
},
- Res::Def(DefKind::Struct | DefKind::Union | DefKind::TyAlias | DefKind::AssocTy, _)
+ Res::Def(
+ DefKind::Struct | DefKind::Union | DefKind::TyAlias { .. } | DefKind::AssocTy,
+ _,
+ )
| Res::SelfTyParam { .. }
| Res::SelfTyAlias { .. } => match ty.normalized.ty_adt_def() {
Some(adt) if !adt.is_enum() => {
@@ -1840,6 +1849,55 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
}
}
+ pub(super) fn collect_unused_stmts_for_coerce_return_ty(
+ &self,
+ errors_causecode: Vec<(Span, ObligationCauseCode<'tcx>)>,
+ ) {
+ for (span, code) in errors_causecode {
+ let Some(mut diag) =
+ self.tcx.sess.diagnostic().steal_diagnostic(span, StashKey::MaybeForgetReturn)
+ else {
+ continue;
+ };
+
+ if let Some(fn_sig) = self.body_fn_sig()
+ && let ExprBindingObligation(_, _, hir_id, ..) = code
+ && !fn_sig.output().is_unit()
+ {
+ let mut block_num = 0;
+ let mut found_semi = false;
+ for (_, node) in self.tcx.hir().parent_iter(hir_id) {
+ match node {
+ hir::Node::Stmt(stmt) => if let hir::StmtKind::Semi(ref expr) = stmt.kind {
+ let expr_ty = self.typeck_results.borrow().expr_ty(expr);
+ let return_ty = fn_sig.output();
+ if !matches!(expr.kind, hir::ExprKind::Ret(..)) &&
+ self.can_coerce(expr_ty, return_ty) {
+ found_semi = true;
+ }
+ },
+ hir::Node::Block(_block) => if found_semi {
+ block_num += 1;
+ }
+ hir::Node::Item(item) => if let hir::ItemKind::Fn(..) = item.kind {
+ break;
+ }
+ _ => {}
+ }
+ }
+ if block_num > 1 && found_semi {
+ diag.span_suggestion_verbose(
+ span.shrink_to_lo(),
+ "you might have meant to return this to infer its type parameters",
+ "return ",
+ Applicability::MaybeIncorrect,
+ );
+ }
+ }
+ diag.emit();
+ }
+ }
+
/// Given a vector of fulfillment errors, try to adjust the spans of the
/// errors to more accurately point at the cause of the failure.
///
@@ -1899,6 +1957,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
err: &mut Diagnostic,
callable_def_id: Option<DefId>,
callee_ty: Option<Ty<'tcx>>,
+ call_expr: &'tcx hir::Expr<'tcx>,
+ expected_ty: Option<Ty<'tcx>>,
// A specific argument should be labeled, instead of all of them
expected_idx: Option<usize>,
is_method: bool,
@@ -1921,8 +1981,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
let callee_ty = callee_ty.peel_refs();
match *callee_ty.kind() {
ty::Param(param) => {
- let param =
- self.tcx.generics_of(self.body_id).type_param(&param, self.tcx);
+ let param = self.tcx.generics_of(self.body_id).type_param(&param, self.tcx);
if param.kind.is_synthetic() {
// if it's `impl Fn() -> ..` then just fall down to the def-id based logic
def_id = param.def_id;
@@ -1936,8 +1995,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
// FIXME(compiler-errors): This could be problematic if something has two
// fn-like predicates with different args, but callable types really never
// do that, so it's OK.
- for (predicate, span) in instantiated
- {
+ for (predicate, span) in instantiated {
if let ty::ClauseKind::Trait(pred) = predicate.kind().skip_binder()
&& pred.self_ty().peel_refs() == callee_ty
&& self.tcx.is_fn_trait(pred.def_id())
@@ -1956,7 +2014,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
_ => {
// Look for a user-provided impl of a `Fn` trait, and point to it.
let new_def_id = self.probe(|_| {
- let trait_ref = ty::TraitRef::new(self.tcx,
+ let trait_ref = ty::TraitRef::new(
+ self.tcx,
call_kind.to_def_id(self.tcx),
[
callee_ty,
@@ -1988,7 +2047,9 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
}
}
- if let Some(def_span) = self.tcx.def_ident_span(def_id) && !def_span.is_dummy() {
+ if let Some(def_span) = self.tcx.def_ident_span(def_id)
+ && !def_span.is_dummy()
+ {
let mut spans: MultiSpan = def_span.into();
let params = self
@@ -2015,6 +2076,48 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
let param = expected_idx
.and_then(|expected_idx| self.tcx.hir().body(*body).params.get(expected_idx));
let (kind, span) = if let Some(param) = param {
+ // Try to find earlier invocations of this closure to find if the type mismatch
+ // is because of inference. If we find one, point at them.
+ let mut call_finder = FindClosureArg { tcx: self.tcx, calls: vec![] };
+ let node = self
+ .tcx
+ .opt_local_def_id_to_hir_id(self.tcx.hir().get_parent_item(call_expr.hir_id))
+ .and_then(|hir_id| self.tcx.hir().find(hir_id));
+ match node {
+ Some(hir::Node::Item(item)) => call_finder.visit_item(item),
+ Some(hir::Node::TraitItem(item)) => call_finder.visit_trait_item(item),
+ Some(hir::Node::ImplItem(item)) => call_finder.visit_impl_item(item),
+ _ => {}
+ }
+ let typeck = self.typeck_results.borrow();
+ for (rcvr, args) in call_finder.calls {
+ if rcvr.hir_id.owner == typeck.hir_owner
+ && let Some(rcvr_ty) = typeck.node_type_opt(rcvr.hir_id)
+ && let ty::Closure(call_def_id, _) = rcvr_ty.kind()
+ && def_id == *call_def_id
+ && let Some(idx) = expected_idx
+ && let Some(arg) = args.get(idx)
+ && let Some(arg_ty) = typeck.node_type_opt(arg.hir_id)
+ && let Some(expected_ty) = expected_ty
+ && self.can_eq(self.param_env, arg_ty, expected_ty)
+ {
+ let mut sp: MultiSpan = vec![arg.span].into();
+ sp.push_span_label(
+ arg.span,
+ format!("expected because this argument is of type `{arg_ty}`"),
+ );
+ sp.push_span_label(rcvr.span, "in this closure call");
+ err.span_note(
+ sp,
+ format!(
+ "expected because the closure was earlier called with an \
+ argument of type `{arg_ty}`",
+ ),
+ );
+ break;
+ }
+ }
+
("closure parameter", param.span)
} else {
("closure", self.tcx.def_span(def_id))
@@ -2028,3 +2131,23 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
}
}
}
+
+struct FindClosureArg<'tcx> {
+ tcx: TyCtxt<'tcx>,
+ calls: Vec<(&'tcx hir::Expr<'tcx>, &'tcx [hir::Expr<'tcx>])>,
+}
+
+impl<'tcx> Visitor<'tcx> for FindClosureArg<'tcx> {
+ type NestedFilter = rustc_middle::hir::nested_filter::All;
+
+ fn nested_visit_map(&mut self) -> Self::Map {
+ self.tcx.hir()
+ }
+
+ fn visit_expr(&mut self, ex: &'tcx hir::Expr<'tcx>) {
+ if let hir::ExprKind::Call(rcvr, args) = ex.kind {
+ self.calls.push((rcvr, args));
+ }
+ hir::intravisit::walk_expr(self, ex);
+ }
+}
diff --git a/compiler/rustc_hir_typeck/src/fn_ctxt/mod.rs b/compiler/rustc_hir_typeck/src/fn_ctxt/mod.rs
index 4a245d30c..e93d180fc 100644
--- a/compiler/rustc_hir_typeck/src/fn_ctxt/mod.rs
+++ b/compiler/rustc_hir_typeck/src/fn_ctxt/mod.rs
@@ -4,9 +4,7 @@ mod arg_matrix;
mod checks;
mod suggestions;
-pub use _impl::*;
use rustc_errors::ErrorGuaranteed;
-pub use suggestions::*;
use crate::coercion::DynamicCoerceMany;
use crate::{Diverges, EnclosingBreakables, Inherited};
@@ -221,14 +219,14 @@ impl<'a, 'tcx> AstConv<'tcx> for FnCtxt<'a, 'tcx> {
let item_def_id = tcx.hir().ty_param_owner(def_id);
let generics = tcx.generics_of(item_def_id);
let index = generics.param_def_id_to_index[&def_id.to_def_id()];
+ // HACK(eddyb) should get the original `Span`.
+ let span = tcx.def_span(def_id);
ty::GenericPredicates {
parent: None,
predicates: tcx.arena.alloc_from_iter(
self.param_env.caller_bounds().iter().filter_map(|predicate| {
match predicate.kind().skip_binder() {
ty::ClauseKind::Trait(data) if data.self_ty().is_param(index) => {
- // HACK(eddyb) should get the original `Span`.
- let span = tcx.def_span(def_id);
Some((predicate, span))
}
_ => None,
diff --git a/compiler/rustc_hir_typeck/src/fn_ctxt/suggestions.rs b/compiler/rustc_hir_typeck/src/fn_ctxt/suggestions.rs
index abb689892..c43d4932f 100644
--- a/compiler/rustc_hir_typeck/src/fn_ctxt/suggestions.rs
+++ b/compiler/rustc_hir_typeck/src/fn_ctxt/suggestions.rs
@@ -2,19 +2,28 @@ use super::FnCtxt;
use crate::errors;
use crate::fluent_generated as fluent;
+use crate::fn_ctxt::rustc_span::BytePos;
+use crate::hir::is_range_literal;
+use crate::method::probe;
use crate::method::probe::{IsSuggestion, Mode, ProbeScope};
+use crate::rustc_middle::ty::Article;
+use crate::ty::TypeAndMut;
+use core::cmp::min;
+use core::iter;
use rustc_ast::util::parser::{ExprPrecedence, PREC_POSTFIX};
use rustc_errors::{Applicability, Diagnostic, MultiSpan};
use rustc_hir as hir;
+use rustc_hir::def::Res;
use rustc_hir::def::{CtorKind, CtorOf, DefKind};
use rustc_hir::lang_items::LangItem;
use rustc_hir::{
- AsyncGeneratorKind, Expr, ExprKind, GeneratorKind, GenericBound, HirId, Node, Path, QPath,
- Stmt, StmtKind, TyKind, WherePredicate,
+ CoroutineKind, CoroutineSource, Expr, ExprKind, GenericBound, HirId, Node, Path, QPath, Stmt,
+ StmtKind, TyKind, WherePredicate,
};
use rustc_hir_analysis::astconv::AstConv;
use rustc_infer::traits::{self, StatementAsExpression};
use rustc_middle::lint::in_external_macro;
+use rustc_middle::middle::stability::EvalResult;
use rustc_middle::ty::print::with_no_trimmed_paths;
use rustc_middle::ty::{
self, suggest_constraining_type_params, Binder, IsSuggestable, ToPredicate, Ty,
@@ -254,22 +263,24 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
expr: &hir::Expr<'tcx>,
expected: Ty<'tcx>,
) -> bool {
- if let hir::ExprKind::MethodCall(hir::PathSegment { ident: method, .. }, recv_expr, &[], _) = expr.kind &&
- let Some(recv_ty) = self.typeck_results.borrow().expr_ty_opt(recv_expr) &&
- self.can_coerce(recv_ty, expected) {
- let span = if let Some(recv_span) = recv_expr.span.find_ancestor_inside(expr.span) {
- expr.span.with_lo(recv_span.hi())
- } else {
- expr.span.with_lo(method.span.lo() - rustc_span::BytePos(1))
- };
- err.span_suggestion_verbose(
- span,
- "try removing the method call",
- "",
- Applicability::MachineApplicable,
- );
- return true;
- }
+ if let hir::ExprKind::MethodCall(hir::PathSegment { ident: method, .. }, recv_expr, &[], _) =
+ expr.kind
+ && let Some(recv_ty) = self.typeck_results.borrow().expr_ty_opt(recv_expr)
+ && self.can_coerce(recv_ty, expected)
+ {
+ let span = if let Some(recv_span) = recv_expr.span.find_ancestor_inside(expr.span) {
+ expr.span.with_lo(recv_span.hi())
+ } else {
+ expr.span.with_lo(method.span.lo() - rustc_span::BytePos(1))
+ };
+ err.span_suggestion_verbose(
+ span,
+ "try removing the method call",
+ "",
+ Applicability::MachineApplicable,
+ );
+ return true;
+ }
false
}
@@ -347,10 +358,16 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
let name = self.tcx.item_name(def_id);
let kind = self.tcx.def_kind(def_id);
if let DefKind::Ctor(of, CtorKind::Fn) = kind {
- err.span_label(sp, format!("`{name}` defines {} constructor here, which should be called", match of {
- CtorOf::Struct => "a struct",
- CtorOf::Variant => "an enum variant",
- }));
+ err.span_label(
+ sp,
+ format!(
+ "`{name}` defines {} constructor here, which should be called",
+ match of {
+ CtorOf::Struct => "a struct",
+ CtorOf::Variant => "an enum variant",
+ }
+ ),
+ );
} else {
let descr = self.tcx.def_kind_descr(kind, def_id);
err.span_label(sp, format!("{descr} `{name}` defined here"));
@@ -370,25 +387,20 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
if let Some(method_ident) = receiver_method_ident
&& method_ident.name == conversion_method.name
{
- return None // do not suggest code that is already there (#53348)
+ return None; // do not suggest code that is already there (#53348)
}
let method_call_list = [sym::to_vec, sym::to_string];
let mut sugg = if let ExprKind::MethodCall(receiver_method, ..) = expr.kind
&& receiver_method.ident.name == sym::clone
&& method_call_list.contains(&conversion_method.name)
- // If receiver is `.clone()` and found type has one of those methods,
- // we guess that the user wants to convert from a slice type (`&[]` or `&str`)
- // to an owned type (`Vec` or `String`). These conversions clone internally,
- // so we remove the user's `clone` call.
- {
- vec![(
- receiver_method.ident.span,
- conversion_method.name.to_string()
- )]
- } else if expr.precedence().order()
- < ExprPrecedence::MethodCall.order()
+ // If receiver is `.clone()` and found type has one of those methods,
+ // we guess that the user wants to convert from a slice type (`&[]` or `&str`)
+ // to an owned type (`Vec` or `String`). These conversions clone internally,
+ // so we remove the user's `clone` call.
{
+ vec![(receiver_method.ident.span, conversion_method.name.to_string())]
+ } else if expr.precedence().order() < ExprPrecedence::MethodCall.order() {
vec![
(expr.span.shrink_to_lo(), "(".to_string()),
(expr.span.shrink_to_hi(), format!(").{}()", conversion_method.name)),
@@ -431,7 +443,11 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
// Given `Result<_, E>`, check our expected ty is `Result<_, &E>` for
// `as_ref` and `as_deref` compatibility.
let error_tys_equate_as_ref = error_tys.map_or(true, |(found, expected)| {
- self.can_eq(self.param_env, Ty::new_imm_ref(self.tcx,self.tcx.lifetimes.re_erased, found), expected)
+ self.can_eq(
+ self.param_env,
+ Ty::new_imm_ref(self.tcx, self.tcx.lifetimes.re_erased, found),
+ expected,
+ )
});
// FIXME: This could/should be extended to suggest `as_mut` and `as_deref_mut`,
// but those checks need to be a bit more delicate and the benefit is diminishing.
@@ -525,10 +541,10 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
ty::Tuple(tuple) if tuple.is_empty() => {
errors::SuggestBoxing::Unit { start: span.shrink_to_lo(), end: span }
}
- ty::Generator(def_id, ..)
+ ty::Coroutine(def_id, ..)
if matches!(
- self.tcx.generator_kind(def_id),
- Some(GeneratorKind::Async(AsyncGeneratorKind::Closure))
+ self.tcx.coroutine_kind(def_id),
+ Some(CoroutineKind::Async(CoroutineSource::Closure))
) =>
{
errors::SuggestBoxing::AsyncBody
@@ -604,8 +620,12 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
return false;
}
let box_found = Ty::new_box(self.tcx, found);
- let pin_box_found = Ty::new_lang_item(self.tcx, box_found, LangItem::Pin).unwrap();
- let pin_found = Ty::new_lang_item(self.tcx, found, LangItem::Pin).unwrap();
+ let Some(pin_box_found) = Ty::new_lang_item(self.tcx, box_found, LangItem::Pin) else {
+ return false;
+ };
+ let Some(pin_found) = Ty::new_lang_item(self.tcx, found, LangItem::Pin) else {
+ return false;
+ };
match expected.kind() {
ty::Adt(def, _) if Some(def.did()) == pin_did => {
if self.can_coerce(pin_box_found, expected) {
@@ -766,61 +786,75 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
}
&hir::FnRetTy::DefaultReturn(span) if expected.is_unit() => {
if let Some(found) = found.make_suggestable(self.tcx, false) {
- err.subdiagnostic(errors::AddReturnTypeSuggestion::Add { span, found: found.to_string() });
+ err.subdiagnostic(errors::AddReturnTypeSuggestion::Add {
+ span,
+ found: found.to_string(),
+ });
return true;
} else if let ty::Closure(_, args) = found.kind()
// FIXME(compiler-errors): Get better at printing binders...
&& let closure = args.as_closure()
&& closure.sig().is_suggestable(self.tcx, false)
{
- err.subdiagnostic(errors::AddReturnTypeSuggestion::Add { span, found: closure.print_as_impl_trait().to_string() });
+ err.subdiagnostic(errors::AddReturnTypeSuggestion::Add {
+ span,
+ found: closure.print_as_impl_trait().to_string(),
+ });
return true;
} else {
// FIXME: if `found` could be `impl Iterator` we should suggest that.
err.subdiagnostic(errors::AddReturnTypeSuggestion::MissingHere { span });
- return true
+ return true;
}
}
hir::FnRetTy::Return(hir_ty) => {
- let span = hir_ty.span;
-
if let hir::TyKind::OpaqueDef(item_id, ..) = hir_ty.kind
&& let hir::Node::Item(hir::Item {
- kind: hir::ItemKind::OpaqueTy(op_ty),
- ..
+ kind: hir::ItemKind::OpaqueTy(op_ty), ..
}) = self.tcx.hir().get(item_id.hir_id())
- && let [hir::GenericBound::LangItemTrait(
- hir::LangItem::Future, _, _, generic_args)] = op_ty.bounds
+ && let [
+ hir::GenericBound::LangItemTrait(hir::LangItem::Future, _, _, generic_args),
+ ] = op_ty.bounds
&& let hir::GenericArgs { bindings: [ty_binding], .. } = generic_args
- && let hir::TypeBindingKind::Equality { term: hir::Term::Ty(term) } = ty_binding.kind
+ && let hir::TypeBindingKind::Equality { term: hir::Term::Ty(term) } =
+ ty_binding.kind
{
// Check if async function's return type was omitted.
// Don't emit suggestions if the found type is `impl Future<...>`.
debug!(?found);
if found.is_suggestable(self.tcx, false) {
if term.span.is_empty() {
- err.subdiagnostic(errors::AddReturnTypeSuggestion::Add { span, found: found.to_string() });
+ err.subdiagnostic(errors::AddReturnTypeSuggestion::Add {
+ span: term.span,
+ found: found.to_string(),
+ });
return true;
} else {
- err.subdiagnostic(errors::ExpectedReturnTypeLabel::Other { span, expected });
+ err.subdiagnostic(errors::ExpectedReturnTypeLabel::Other {
+ span: term.span,
+ expected,
+ });
}
}
- }
-
- // Only point to return type if the expected type is the return type, as if they
- // are not, the expectation must have been caused by something else.
- debug!("return type {:?}", hir_ty);
- let ty = self.astconv().ast_ty_to_ty(hir_ty);
- debug!("return type {:?}", ty);
- debug!("expected type {:?}", expected);
- let bound_vars = self.tcx.late_bound_vars(hir_ty.hir_id.owner.into());
- let ty = Binder::bind_with_vars(ty, bound_vars);
- let ty = self.normalize(span, ty);
- let ty = self.tcx.erase_late_bound_regions(ty);
- if self.can_coerce(expected, ty) {
- err.subdiagnostic(errors::ExpectedReturnTypeLabel::Other { span, expected });
- self.try_suggest_return_impl_trait(err, expected, ty, fn_id);
- return true;
+ } else {
+ // Only point to return type if the expected type is the return type, as if they
+ // are not, the expectation must have been caused by something else.
+ debug!("return type {:?}", hir_ty);
+ let ty = self.astconv().ast_ty_to_ty(hir_ty);
+ debug!("return type {:?}", ty);
+ debug!("expected type {:?}", expected);
+ let bound_vars = self.tcx.late_bound_vars(hir_ty.hir_id.owner.into());
+ let ty = Binder::bind_with_vars(ty, bound_vars);
+ let ty = self.normalize(hir_ty.span, ty);
+ let ty = self.tcx.erase_late_bound_regions(ty);
+ if self.can_coerce(expected, ty) {
+ err.subdiagnostic(errors::ExpectedReturnTypeLabel::Other {
+ span: hir_ty.span,
+ expected,
+ });
+ self.try_suggest_return_impl_trait(err, expected, ty, fn_id);
+ return true;
+ }
}
}
_ => {}
@@ -1075,13 +1109,13 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
.type_implements_trait(
clone_trait_def,
[self.tcx.erase_regions(expected_ty)],
- self.param_env
+ self.param_env,
)
.must_apply_modulo_regions()
- {
+ {
let suggestion = match self.tcx.hir().maybe_get_struct_pattern_shorthand_field(expr) {
Some(ident) => format!(": {ident}.clone()"),
- None => ".clone()".to_string()
+ None => ".clone()".to_string(),
};
diag.span_suggestion_verbose(
@@ -1091,7 +1125,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
Applicability::MachineApplicable,
);
return true;
- }
+ }
false
}
@@ -1119,31 +1153,27 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
let expr_inner_ty = args.type_at(0);
let expected_inner_ty = expected_args.type_at(0);
if let &ty::Ref(_, ty, _mutability) = expr_inner_ty.kind()
- && self.can_eq(self.param_env, ty, expected_inner_ty)
+ && self.can_eq(self.param_env, ty, expected_inner_ty)
+ {
+ let def_path = self.tcx.def_path_str(adt_def.did());
+ let span = expr.span.shrink_to_hi();
+ let subdiag = if self.type_is_copy_modulo_regions(self.param_env, ty) {
+ errors::OptionResultRefMismatch::Copied { span, def_path }
+ } else if let Some(clone_did) = self.tcx.lang_items().clone_trait()
+ && rustc_trait_selection::traits::type_known_to_meet_bound_modulo_regions(
+ self,
+ self.param_env,
+ ty,
+ clone_did,
+ )
{
- let def_path = self.tcx.def_path_str(adt_def.did());
- let span = expr.span.shrink_to_hi();
- let subdiag = if self.type_is_copy_modulo_regions(self.param_env, ty) {
- errors::OptionResultRefMismatch::Copied {
- span, def_path
- }
- } else if let Some(clone_did) = self.tcx.lang_items().clone_trait()
- && rustc_trait_selection::traits::type_known_to_meet_bound_modulo_regions(
- self,
- self.param_env,
- ty,
- clone_did,
- )
- {
- errors::OptionResultRefMismatch::Cloned {
- span, def_path
- }
- } else {
- return false;
- };
- diag.subdiagnostic(subdiag);
- return true;
- }
+ errors::OptionResultRefMismatch::Cloned { span, def_path }
+ } else {
+ return false;
+ };
+ diag.subdiagnostic(subdiag);
+ return true;
+ }
}
false
@@ -1179,14 +1209,12 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
self.tcx,
self.misc(expr.span),
self.param_env,
- ty::TraitRef::new(self.tcx,
- into_def_id,
- [expr_ty, expected_ty]
- ),
+ ty::TraitRef::new(self.tcx, into_def_id, [expr_ty, expected_ty]),
))
{
let mut span = expr.span;
- while expr.span.eq_ctxt(span) && let Some(parent_callsite) = span.parent_callsite()
+ while expr.span.eq_ctxt(span)
+ && let Some(parent_callsite) = span.parent_callsite()
{
span = parent_callsite;
}
@@ -1194,7 +1222,10 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
let sugg = if expr.precedence().order() >= PREC_POSTFIX {
vec![(span.shrink_to_hi(), ".into()".to_owned())]
} else {
- vec![(span.shrink_to_lo(), "(".to_owned()), (span.shrink_to_hi(), ").into()".to_owned())]
+ vec![
+ (span.shrink_to_lo(), "(".to_owned()),
+ (span.shrink_to_hi(), ").into()".to_owned()),
+ ]
};
diag.multipart_suggestion(
format!("call `Into::into` on this expression to convert `{expr_ty}` into `{expected_ty}`"),
@@ -1236,9 +1267,12 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
// since the user probably just misunderstood how `let else`
// and `&&` work together.
if let Some((_, hir::Node::Local(local))) = cond_parent
- && let hir::PatKind::Path(qpath) | hir::PatKind::TupleStruct(qpath, _, _) = &local.pat.kind
+ && let hir::PatKind::Path(qpath) | hir::PatKind::TupleStruct(qpath, _, _) =
+ &local.pat.kind
&& let hir::QPath::Resolved(None, path) = qpath
- && let Some(did) = path.res.opt_def_id()
+ && let Some(did) = path
+ .res
+ .opt_def_id()
.and_then(|did| self.tcx.opt_parent(did))
.and_then(|did| self.tcx.opt_parent(did))
&& self.tcx.is_diagnostic_item(sym::Option, did)
@@ -1605,7 +1639,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
..
}) => {
let Some(hir::Node::Local(hir::Local { init: Some(init), .. })) =
- self.tcx.hir().find(self.tcx.hir().parent_id(*pat_hir_id)) else {
+ self.tcx.hir().find(self.tcx.hir().parent_id(*pat_hir_id))
+ else {
return expr;
};
@@ -1632,12 +1667,18 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
// to worry if it's a call to a typed function or closure as this would ne handled
// previously.
hir::ExprKind::Call(Expr { kind: call_expr_kind, .. }, _) => {
- if let hir::ExprKind::Path(hir::QPath::Resolved(None, call_expr_path)) = call_expr_kind
- && let hir::Path { segments: [_], res: crate::Res::Local(binding), .. } = call_expr_path
- && let Some(hir::Node::Pat(hir::Pat { hir_id, .. })) = self.tcx.hir().find(*binding)
+ if let hir::ExprKind::Path(hir::QPath::Resolved(None, call_expr_path)) =
+ call_expr_kind
+ && let hir::Path { segments: [_], res: crate::Res::Local(binding), .. } =
+ call_expr_path
+ && let Some(hir::Node::Pat(hir::Pat { hir_id, .. })) =
+ self.tcx.hir().find(*binding)
&& let Some(closure) = self.tcx.hir().find(self.tcx.hir().parent_id(*hir_id))
&& let hir::Node::Local(hir::Local { init: Some(init), .. }) = closure
- && let Expr { kind: hir::ExprKind::Closure(hir::Closure { body: body_id, .. }), ..} = init
+ && let Expr {
+ kind: hir::ExprKind::Closure(hir::Closure { body: body_id, .. }),
+ ..
+ } = init
{
let hir::Body { value: body_expr, .. } = self.tcx.hir().body(*body_id);
self.note_type_is_not_clone_inner_expr(body_expr)
@@ -1687,4 +1728,1361 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
false
}
}
+
+ pub(crate) fn is_field_suggestable(
+ &self,
+ field: &ty::FieldDef,
+ hir_id: HirId,
+ span: Span,
+ ) -> bool {
+ // The field must be visible in the containing module.
+ field.vis.is_accessible_from(self.tcx.parent_module(hir_id), self.tcx)
+ // The field must not be unstable.
+ && !matches!(
+ self.tcx.eval_stability(field.did, None, rustc_span::DUMMY_SP, None),
+ rustc_middle::middle::stability::EvalResult::Deny { .. }
+ )
+ // If the field is from an external crate it must not be `doc(hidden)`.
+ && (field.did.is_local() || !self.tcx.is_doc_hidden(field.did))
+ // If the field is hygienic it must come from the same syntax context.
+ && self.tcx.def_ident_span(field.did).unwrap().normalize_to_macros_2_0().eq_ctxt(span)
+ }
+
+ pub(crate) fn suggest_missing_unwrap_expect(
+ &self,
+ err: &mut Diagnostic,
+ expr: &hir::Expr<'tcx>,
+ expected: Ty<'tcx>,
+ found: Ty<'tcx>,
+ ) -> bool {
+ let ty::Adt(adt, args) = found.kind() else {
+ return false;
+ };
+ let ret_ty_matches = |diagnostic_item| {
+ let Some(sig) = self.body_fn_sig() else {
+ return false;
+ };
+ let ty::Adt(kind, _) = sig.output().kind() else {
+ return false;
+ };
+ self.tcx.is_diagnostic_item(diagnostic_item, kind.did())
+ };
+
+ // don't suggest anything like `Ok(ok_val).unwrap()` , `Some(some_val).unwrap()`,
+ // `None.unwrap()` etc.
+ let is_ctor = matches!(
+ expr.kind,
+ hir::ExprKind::Call(
+ hir::Expr {
+ kind: hir::ExprKind::Path(hir::QPath::Resolved(
+ None,
+ hir::Path { res: Res::Def(hir::def::DefKind::Ctor(_, _), _), .. },
+ )),
+ ..
+ },
+ ..,
+ ) | hir::ExprKind::Path(hir::QPath::Resolved(
+ None,
+ hir::Path { res: Res::Def(hir::def::DefKind::Ctor(_, _), _), .. },
+ )),
+ );
+
+ let (article, kind, variant, sugg_operator) =
+ if self.tcx.is_diagnostic_item(sym::Result, adt.did()) {
+ ("a", "Result", "Err", ret_ty_matches(sym::Result))
+ } else if self.tcx.is_diagnostic_item(sym::Option, adt.did()) {
+ ("an", "Option", "None", ret_ty_matches(sym::Option))
+ } else {
+ return false;
+ };
+ if is_ctor || !self.can_coerce(args.type_at(0), expected) {
+ return false;
+ }
+
+ let (msg, sugg) = if sugg_operator {
+ (
+ format!(
+ "use the `?` operator to extract the `{found}` value, propagating \
+ {article} `{kind}::{variant}` value to the caller"
+ ),
+ "?",
+ )
+ } else {
+ (
+ format!(
+ "consider using `{kind}::expect` to unwrap the `{found}` value, \
+ panicking if the value is {article} `{kind}::{variant}`"
+ ),
+ ".expect(\"REASON\")",
+ )
+ };
+ err.span_suggestion_verbose(
+ expr.span.shrink_to_hi(),
+ msg,
+ sugg,
+ Applicability::HasPlaceholders,
+ );
+ return true;
+ }
+
+ pub(crate) fn suggest_coercing_result_via_try_operator(
+ &self,
+ err: &mut Diagnostic,
+ expr: &hir::Expr<'tcx>,
+ expected: Ty<'tcx>,
+ found: Ty<'tcx>,
+ ) -> bool {
+ let map = self.tcx.hir();
+ let returned = matches!(
+ map.find_parent(expr.hir_id),
+ Some(hir::Node::Expr(hir::Expr { kind: hir::ExprKind::Ret(_), .. }))
+ ) || map.get_return_block(expr.hir_id).is_some();
+ if returned
+ && let ty::Adt(e, args_e) = expected.kind()
+ && let ty::Adt(f, args_f) = found.kind()
+ && e.did() == f.did()
+ && Some(e.did()) == self.tcx.get_diagnostic_item(sym::Result)
+ && let e_ok = args_e.type_at(0)
+ && let f_ok = args_f.type_at(0)
+ && self.infcx.can_eq(self.param_env, f_ok, e_ok)
+ && let e_err = args_e.type_at(1)
+ && let f_err = args_f.type_at(1)
+ && self
+ .infcx
+ .type_implements_trait(
+ self.tcx.get_diagnostic_item(sym::Into).unwrap(),
+ [f_err, e_err],
+ self.param_env,
+ )
+ .must_apply_modulo_regions()
+ {
+ err.multipart_suggestion(
+ "use `?` to coerce and return an appropriate `Err`, and wrap the resulting value \
+ in `Ok` so the expression remains of type `Result`",
+ vec![
+ (expr.span.shrink_to_lo(), "Ok(".to_string()),
+ (expr.span.shrink_to_hi(), "?)".to_string()),
+ ],
+ Applicability::MaybeIncorrect,
+ );
+ return true;
+ }
+ false
+ }
+
+ /// If the expected type is an enum (Issue #55250) with any variants whose
+ /// sole field is of the found type, suggest such variants. (Issue #42764)
+ pub(crate) fn suggest_compatible_variants(
+ &self,
+ err: &mut Diagnostic,
+ expr: &hir::Expr<'_>,
+ expected: Ty<'tcx>,
+ expr_ty: Ty<'tcx>,
+ ) -> bool {
+ if in_external_macro(self.tcx.sess, expr.span) {
+ return false;
+ }
+ if let ty::Adt(expected_adt, args) = expected.kind() {
+ if let hir::ExprKind::Field(base, ident) = expr.kind {
+ let base_ty = self.typeck_results.borrow().expr_ty(base);
+ if self.can_eq(self.param_env, base_ty, expected)
+ && let Some(base_span) = base.span.find_ancestor_inside(expr.span)
+ {
+ err.span_suggestion_verbose(
+ expr.span.with_lo(base_span.hi()),
+ format!("consider removing the tuple struct field `{ident}`"),
+ "",
+ Applicability::MaybeIncorrect,
+ );
+ return true;
+ }
+ }
+
+ // If the expression is of type () and it's the return expression of a block,
+ // we suggest adding a separate return expression instead.
+ // (To avoid things like suggesting `Ok(while .. { .. })`.)
+ if expr_ty.is_unit() {
+ let mut id = expr.hir_id;
+ let mut parent;
+
+ // Unroll desugaring, to make sure this works for `for` loops etc.
+ loop {
+ parent = self.tcx.hir().parent_id(id);
+ if let Some(parent_span) = self.tcx.hir().opt_span(parent) {
+ if parent_span.find_ancestor_inside(expr.span).is_some() {
+ // The parent node is part of the same span, so is the result of the
+ // same expansion/desugaring and not the 'real' parent node.
+ id = parent;
+ continue;
+ }
+ }
+ break;
+ }
+
+ if let Some(hir::Node::Block(&hir::Block {
+ span: block_span, expr: Some(e), ..
+ })) = self.tcx.hir().find(parent)
+ {
+ if e.hir_id == id {
+ if let Some(span) = expr.span.find_ancestor_inside(block_span) {
+ let return_suggestions = if self
+ .tcx
+ .is_diagnostic_item(sym::Result, expected_adt.did())
+ {
+ vec!["Ok(())"]
+ } else if self.tcx.is_diagnostic_item(sym::Option, expected_adt.did()) {
+ vec!["None", "Some(())"]
+ } else {
+ return false;
+ };
+ if let Some(indent) =
+ self.tcx.sess.source_map().indentation_before(span.shrink_to_lo())
+ {
+ // Add a semicolon, except after `}`.
+ let semicolon =
+ match self.tcx.sess.source_map().span_to_snippet(span) {
+ Ok(s) if s.ends_with('}') => "",
+ _ => ";",
+ };
+ err.span_suggestions(
+ span.shrink_to_hi(),
+ "try adding an expression at the end of the block",
+ return_suggestions
+ .into_iter()
+ .map(|r| format!("{semicolon}\n{indent}{r}")),
+ Applicability::MaybeIncorrect,
+ );
+ }
+ return true;
+ }
+ }
+ }
+ }
+
+ let compatible_variants: Vec<(String, _, _, Option<String>)> = expected_adt
+ .variants()
+ .iter()
+ .filter(|variant| {
+ variant.fields.len() == 1
+ })
+ .filter_map(|variant| {
+ let sole_field = &variant.single_field();
+
+ let field_is_local = sole_field.did.is_local();
+ let field_is_accessible =
+ sole_field.vis.is_accessible_from(expr.hir_id.owner.def_id, self.tcx)
+ // Skip suggestions for unstable public fields (for example `Pin::pointer`)
+ && matches!(self.tcx.eval_stability(sole_field.did, None, expr.span, None), EvalResult::Allow | EvalResult::Unmarked);
+
+ if !field_is_local && !field_is_accessible {
+ return None;
+ }
+
+ let note_about_variant_field_privacy = (field_is_local && !field_is_accessible)
+ .then(|| " (its field is private, but it's local to this crate and its privacy can be changed)".to_string());
+
+ let sole_field_ty = sole_field.ty(self.tcx, args);
+ if self.can_coerce(expr_ty, sole_field_ty) {
+ 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::")
+ && let Some((_, path)) = path.split_once("::")
+ {
+ return Some((path.to_string(), variant.ctor_kind(), sole_field.name, note_about_variant_field_privacy));
+ }
+ Some((variant_path, variant.ctor_kind(), sole_field.name, note_about_variant_field_privacy))
+ } else {
+ None
+ }
+ })
+ .collect();
+
+ let suggestions_for = |variant: &_, ctor_kind, field_name| {
+ let prefix = match self.tcx.hir().maybe_get_struct_pattern_shorthand_field(expr) {
+ Some(ident) => format!("{ident}: "),
+ None => String::new(),
+ };
+
+ let (open, close) = match ctor_kind {
+ Some(CtorKind::Fn) => ("(".to_owned(), ")"),
+ None => (format!(" {{ {field_name}: "), " }"),
+
+ // unit variants don't have fields
+ Some(CtorKind::Const) => unreachable!(),
+ };
+
+ // Suggest constructor as deep into the block tree as possible.
+ // This fixes https://github.com/rust-lang/rust/issues/101065,
+ // and also just helps make the most minimal suggestions.
+ let mut expr = expr;
+ while let hir::ExprKind::Block(block, _) = &expr.kind
+ && let Some(expr_) = &block.expr
+ {
+ expr = expr_
+ }
+
+ vec![
+ (expr.span.shrink_to_lo(), format!("{prefix}{variant}{open}")),
+ (expr.span.shrink_to_hi(), close.to_owned()),
+ ]
+ };
+
+ match &compatible_variants[..] {
+ [] => { /* No variants to format */ }
+ [(variant, ctor_kind, field_name, note)] => {
+ // Just a single matching variant.
+ err.multipart_suggestion_verbose(
+ format!(
+ "try wrapping the expression in `{variant}`{note}",
+ note = note.as_deref().unwrap_or("")
+ ),
+ suggestions_for(&**variant, *ctor_kind, *field_name),
+ Applicability::MaybeIncorrect,
+ );
+ return true;
+ }
+ _ => {
+ // More than one matching variant.
+ err.multipart_suggestions(
+ format!(
+ "try wrapping the expression in a variant of `{}`",
+ self.tcx.def_path_str(expected_adt.did())
+ ),
+ compatible_variants.into_iter().map(
+ |(variant, ctor_kind, field_name, _)| {
+ suggestions_for(&variant, ctor_kind, field_name)
+ },
+ ),
+ Applicability::MaybeIncorrect,
+ );
+ return true;
+ }
+ }
+ }
+
+ false
+ }
+
+ pub(crate) fn suggest_non_zero_new_unwrap(
+ &self,
+ err: &mut Diagnostic,
+ expr: &hir::Expr<'_>,
+ expected: Ty<'tcx>,
+ expr_ty: Ty<'tcx>,
+ ) -> bool {
+ let tcx = self.tcx;
+ let (adt, unwrap) = match expected.kind() {
+ // In case Option<NonZero*> is wanted, but * is provided, suggest calling new
+ ty::Adt(adt, args) if tcx.is_diagnostic_item(sym::Option, adt.did()) => {
+ // Unwrap option
+ let ty::Adt(adt, _) = args.type_at(0).kind() else {
+ return false;
+ };
+
+ (adt, "")
+ }
+ // In case NonZero* is wanted, but * is provided also add `.unwrap()` to satisfy types
+ ty::Adt(adt, _) => (adt, ".unwrap()"),
+ _ => return false,
+ };
+
+ let map = [
+ (sym::NonZeroU8, tcx.types.u8),
+ (sym::NonZeroU16, tcx.types.u16),
+ (sym::NonZeroU32, tcx.types.u32),
+ (sym::NonZeroU64, tcx.types.u64),
+ (sym::NonZeroU128, tcx.types.u128),
+ (sym::NonZeroI8, tcx.types.i8),
+ (sym::NonZeroI16, tcx.types.i16),
+ (sym::NonZeroI32, tcx.types.i32),
+ (sym::NonZeroI64, tcx.types.i64),
+ (sym::NonZeroI128, tcx.types.i128),
+ ];
+
+ let Some((s, _)) = map.iter().find(|&&(s, t)| {
+ self.tcx.is_diagnostic_item(s, adt.did()) && self.can_coerce(expr_ty, t)
+ }) else {
+ return false;
+ };
+
+ let path = self.tcx.def_path_str(adt.non_enum_variant().def_id);
+
+ err.multipart_suggestion(
+ format!("consider calling `{s}::new`"),
+ vec![
+ (expr.span.shrink_to_lo(), format!("{path}::new(")),
+ (expr.span.shrink_to_hi(), format!("){unwrap}")),
+ ],
+ Applicability::MaybeIncorrect,
+ );
+
+ true
+ }
+
+ /// Identify some cases where `as_ref()` would be appropriate and suggest it.
+ ///
+ /// Given the following code:
+ /// ```compile_fail,E0308
+ /// struct Foo;
+ /// fn takes_ref(_: &Foo) {}
+ /// let ref opt = Some(Foo);
+ ///
+ /// opt.map(|param| takes_ref(param));
+ /// ```
+ /// Suggest using `opt.as_ref().map(|param| takes_ref(param));` instead.
+ ///
+ /// It only checks for `Option` and `Result` and won't work with
+ /// ```ignore (illustrative)
+ /// opt.map(|param| { takes_ref(param) });
+ /// ```
+ fn can_use_as_ref(&self, expr: &hir::Expr<'_>) -> Option<(Vec<(Span, String)>, &'static str)> {
+ let hir::ExprKind::Path(hir::QPath::Resolved(_, ref path)) = expr.kind else {
+ return None;
+ };
+
+ let hir::def::Res::Local(local_id) = path.res else {
+ return None;
+ };
+
+ let local_parent = self.tcx.hir().parent_id(local_id);
+ let Some(Node::Param(hir::Param { hir_id: param_hir_id, .. })) =
+ self.tcx.hir().find(local_parent)
+ else {
+ return None;
+ };
+
+ let param_parent = self.tcx.hir().parent_id(*param_hir_id);
+ let Some(Node::Expr(hir::Expr {
+ hir_id: expr_hir_id,
+ kind: hir::ExprKind::Closure(hir::Closure { fn_decl: closure_fn_decl, .. }),
+ ..
+ })) = self.tcx.hir().find(param_parent)
+ else {
+ return None;
+ };
+
+ let expr_parent = self.tcx.hir().parent_id(*expr_hir_id);
+ let hir = self.tcx.hir().find(expr_parent);
+ let closure_params_len = closure_fn_decl.inputs.len();
+ let (
+ Some(Node::Expr(hir::Expr {
+ kind: hir::ExprKind::MethodCall(method_path, receiver, ..),
+ ..
+ })),
+ 1,
+ ) = (hir, closure_params_len)
+ else {
+ return None;
+ };
+
+ let self_ty = self.typeck_results.borrow().expr_ty(receiver);
+ let name = method_path.ident.name;
+ let is_as_ref_able = match self_ty.peel_refs().kind() {
+ ty::Adt(def, _) => {
+ (self.tcx.is_diagnostic_item(sym::Option, def.did())
+ || self.tcx.is_diagnostic_item(sym::Result, def.did()))
+ && (name == sym::map || name == sym::and_then)
+ }
+ _ => false,
+ };
+ if is_as_ref_able {
+ Some((
+ vec![(method_path.ident.span.shrink_to_lo(), "as_ref().".to_string())],
+ "consider using `as_ref` instead",
+ ))
+ } else {
+ None
+ }
+ }
+
+ /// This function is used to determine potential "simple" improvements or users' errors and
+ /// provide them useful help. For example:
+ ///
+ /// ```compile_fail,E0308
+ /// fn some_fn(s: &str) {}
+ ///
+ /// let x = "hey!".to_owned();
+ /// some_fn(x); // error
+ /// ```
+ ///
+ /// No need to find every potential function which could make a coercion to transform a
+ /// `String` into a `&str` since a `&` would do the trick!
+ ///
+ /// In addition of this check, it also checks between references mutability state. If the
+ /// expected is mutable but the provided isn't, maybe we could just say "Hey, try with
+ /// `&mut`!".
+ pub(crate) fn suggest_deref_or_ref(
+ &self,
+ expr: &hir::Expr<'tcx>,
+ checked_ty: Ty<'tcx>,
+ expected: Ty<'tcx>,
+ ) -> Option<(
+ Vec<(Span, String)>,
+ String,
+ Applicability,
+ bool, /* verbose */
+ bool, /* suggest `&` or `&mut` type annotation */
+ )> {
+ let sess = self.sess();
+ let sp = expr.span;
+
+ // If the span is from an external macro, there's no suggestion we can make.
+ if in_external_macro(sess, sp) {
+ return None;
+ }
+
+ let sm = sess.source_map();
+
+ let replace_prefix = |s: &str, old: &str, new: &str| {
+ s.strip_prefix(old).map(|stripped| new.to_string() + stripped)
+ };
+
+ // `ExprKind::DropTemps` is semantically irrelevant for these suggestions.
+ let expr = expr.peel_drop_temps();
+
+ match (&expr.kind, expected.kind(), checked_ty.kind()) {
+ (_, &ty::Ref(_, exp, _), &ty::Ref(_, check, _)) => match (exp.kind(), check.kind()) {
+ (&ty::Str, &ty::Array(arr, _) | &ty::Slice(arr)) if arr == self.tcx.types.u8 => {
+ if let hir::ExprKind::Lit(_) = expr.kind
+ && let Ok(src) = sm.span_to_snippet(sp)
+ && replace_prefix(&src, "b\"", "\"").is_some()
+ {
+ let pos = sp.lo() + BytePos(1);
+ return Some((
+ vec![(sp.with_hi(pos), String::new())],
+ "consider removing the leading `b`".to_string(),
+ Applicability::MachineApplicable,
+ true,
+ false,
+ ));
+ }
+ }
+ (&ty::Array(arr, _) | &ty::Slice(arr), &ty::Str) if arr == self.tcx.types.u8 => {
+ if let hir::ExprKind::Lit(_) = expr.kind
+ && let Ok(src) = sm.span_to_snippet(sp)
+ && replace_prefix(&src, "\"", "b\"").is_some()
+ {
+ return Some((
+ vec![(sp.shrink_to_lo(), "b".to_string())],
+ "consider adding a leading `b`".to_string(),
+ Applicability::MachineApplicable,
+ true,
+ false,
+ ));
+ }
+ }
+ _ => {}
+ },
+ (_, &ty::Ref(_, _, mutability), _) => {
+ // Check if it can work when put into a ref. For example:
+ //
+ // ```
+ // fn bar(x: &mut i32) {}
+ //
+ // let x = 0u32;
+ // bar(&x); // error, expected &mut
+ // ```
+ let ref_ty = match mutability {
+ hir::Mutability::Mut => {
+ Ty::new_mut_ref(self.tcx, self.tcx.lifetimes.re_static, checked_ty)
+ }
+ hir::Mutability::Not => {
+ Ty::new_imm_ref(self.tcx, self.tcx.lifetimes.re_static, checked_ty)
+ }
+ };
+ if self.can_coerce(ref_ty, expected) {
+ let mut sugg_sp = sp;
+ if let hir::ExprKind::MethodCall(ref segment, receiver, args, _) = expr.kind {
+ let clone_trait =
+ self.tcx.require_lang_item(LangItem::Clone, Some(segment.ident.span));
+ if args.is_empty()
+ && self.typeck_results.borrow().type_dependent_def_id(expr.hir_id).map(
+ |did| {
+ let ai = self.tcx.associated_item(did);
+ ai.trait_container(self.tcx) == Some(clone_trait)
+ },
+ ) == Some(true)
+ && segment.ident.name == sym::clone
+ {
+ // If this expression had a clone call when suggesting borrowing
+ // we want to suggest removing it because it'd now be unnecessary.
+ sugg_sp = receiver.span;
+ }
+ }
+
+ if let hir::ExprKind::Unary(hir::UnOp::Deref, ref inner) = expr.kind
+ && let Some(1) = self.deref_steps(expected, checked_ty)
+ {
+ // We have `*&T`, check if what was expected was `&T`.
+ // If so, we may want to suggest removing a `*`.
+ sugg_sp = sugg_sp.with_hi(inner.span.lo());
+ return Some((
+ vec![(sugg_sp, String::new())],
+ "consider removing deref here".to_string(),
+ Applicability::MachineApplicable,
+ true,
+ false,
+ ));
+ }
+
+ let needs_parens = match expr.kind {
+ // parenthesize if needed (Issue #46756)
+ hir::ExprKind::Cast(_, _) | hir::ExprKind::Binary(_, _, _) => true,
+ // parenthesize borrows of range literals (Issue #54505)
+ _ if is_range_literal(expr) => true,
+ _ => false,
+ };
+
+ if let Some((sugg, msg)) = self.can_use_as_ref(expr) {
+ return Some((
+ sugg,
+ msg.to_string(),
+ Applicability::MachineApplicable,
+ true,
+ false,
+ ));
+ }
+
+ let prefix = match self.tcx.hir().maybe_get_struct_pattern_shorthand_field(expr)
+ {
+ Some(ident) => format!("{ident}: "),
+ None => String::new(),
+ };
+
+ if let Some(hir::Node::Expr(hir::Expr {
+ kind: hir::ExprKind::Assign(..),
+ ..
+ })) = self.tcx.hir().find_parent(expr.hir_id)
+ {
+ if mutability.is_mut() {
+ // Suppressing this diagnostic, we'll properly print it in `check_expr_assign`
+ return None;
+ }
+ }
+
+ let sugg = mutability.ref_prefix_str();
+ let (sugg, verbose) = if needs_parens {
+ (
+ vec![
+ (sp.shrink_to_lo(), format!("{prefix}{sugg}(")),
+ (sp.shrink_to_hi(), ")".to_string()),
+ ],
+ false,
+ )
+ } else {
+ (vec![(sp.shrink_to_lo(), format!("{prefix}{sugg}"))], true)
+ };
+ return Some((
+ sugg,
+ format!("consider {}borrowing here", mutability.mutably_str()),
+ Applicability::MachineApplicable,
+ verbose,
+ false,
+ ));
+ }
+ }
+ (
+ hir::ExprKind::AddrOf(hir::BorrowKind::Ref, _, ref expr),
+ _,
+ &ty::Ref(_, checked, _),
+ ) if self.can_sub(self.param_env, checked, expected) => {
+ let make_sugg = |start: Span, end: BytePos| {
+ // skip `(` for tuples such as `(c) = (&123)`.
+ // make sure we won't suggest like `(c) = 123)` which is incorrect.
+ let sp = sm
+ .span_extend_while(start.shrink_to_lo(), |c| c == '(' || c.is_whitespace())
+ .map_or(start, |s| s.shrink_to_hi());
+ Some((
+ vec![(sp.with_hi(end), String::new())],
+ "consider removing the borrow".to_string(),
+ Applicability::MachineApplicable,
+ true,
+ true,
+ ))
+ };
+
+ // We have `&T`, check if what was expected was `T`. If so,
+ // we may want to suggest removing a `&`.
+ if sm.is_imported(expr.span) {
+ // Go through the spans from which this span was expanded,
+ // and find the one that's pointing inside `sp`.
+ //
+ // E.g. for `&format!("")`, where we want the span to the
+ // `format!()` invocation instead of its expansion.
+ if let Some(call_span) =
+ iter::successors(Some(expr.span), |s| s.parent_callsite())
+ .find(|&s| sp.contains(s))
+ && sm.is_span_accessible(call_span)
+ {
+ return make_sugg(sp, call_span.lo());
+ }
+ return None;
+ }
+ if sp.contains(expr.span) && sm.is_span_accessible(expr.span) {
+ return make_sugg(sp, expr.span.lo());
+ }
+ }
+ (
+ _,
+ &ty::RawPtr(TypeAndMut { ty: ty_b, mutbl: mutbl_b }),
+ &ty::Ref(_, ty_a, mutbl_a),
+ ) => {
+ if let Some(steps) = self.deref_steps(ty_a, ty_b)
+ // Only suggest valid if dereferencing needed.
+ && steps > 0
+ // The pointer type implements `Copy` trait so the suggestion is always valid.
+ && let Ok(src) = sm.span_to_snippet(sp)
+ {
+ let derefs = "*".repeat(steps);
+ let old_prefix = mutbl_a.ref_prefix_str();
+ let new_prefix = mutbl_b.ref_prefix_str().to_owned() + &derefs;
+
+ let suggestion = replace_prefix(&src, old_prefix, &new_prefix).map(|_| {
+ // skip `&` or `&mut ` if both mutabilities are mutable
+ let lo = sp.lo()
+ + BytePos(min(old_prefix.len(), mutbl_b.ref_prefix_str().len()) as _);
+ // skip `&` or `&mut `
+ let hi = sp.lo() + BytePos(old_prefix.len() as _);
+ let sp = sp.with_lo(lo).with_hi(hi);
+
+ (
+ sp,
+ format!(
+ "{}{derefs}",
+ if mutbl_a != mutbl_b { mutbl_b.prefix_str() } else { "" }
+ ),
+ if mutbl_b <= mutbl_a {
+ Applicability::MachineApplicable
+ } else {
+ Applicability::MaybeIncorrect
+ },
+ )
+ });
+
+ if let Some((span, src, applicability)) = suggestion {
+ return Some((
+ vec![(span, src)],
+ "consider dereferencing".to_string(),
+ applicability,
+ true,
+ false,
+ ));
+ }
+ }
+ }
+ _ if sp == expr.span => {
+ if let Some(mut steps) = self.deref_steps(checked_ty, expected) {
+ let mut expr = expr.peel_blocks();
+ let mut prefix_span = expr.span.shrink_to_lo();
+ let mut remove = String::new();
+
+ // Try peeling off any existing `&` and `&mut` to reach our target type
+ while steps > 0 {
+ if let hir::ExprKind::AddrOf(_, mutbl, inner) = expr.kind {
+ // If the expression has `&`, removing it would fix the error
+ prefix_span = prefix_span.with_hi(inner.span.lo());
+ expr = inner;
+ remove.push_str(mutbl.ref_prefix_str());
+ steps -= 1;
+ } else {
+ break;
+ }
+ }
+ // If we've reached our target type with just removing `&`, then just print now.
+ if steps == 0 && !remove.trim().is_empty() {
+ return Some((
+ vec![(prefix_span, String::new())],
+ format!("consider removing the `{}`", remove.trim()),
+ // Do not remove `&&` to get to bool, because it might be something like
+ // { a } && b, which we have a separate fixup suggestion that is more
+ // likely correct...
+ if remove.trim() == "&&" && expected == self.tcx.types.bool {
+ Applicability::MaybeIncorrect
+ } else {
+ Applicability::MachineApplicable
+ },
+ true,
+ false,
+ ));
+ }
+
+ // For this suggestion to make sense, the type would need to be `Copy`,
+ // or we have to be moving out of a `Box<T>`
+ if self.type_is_copy_modulo_regions(self.param_env, expected)
+ // FIXME(compiler-errors): We can actually do this if the checked_ty is
+ // `steps` layers of boxes, not just one, but this is easier and most likely.
+ || (checked_ty.is_box() && steps == 1)
+ // We can always deref a binop that takes its arguments by ref.
+ || matches!(
+ self.tcx.hir().get_parent(expr.hir_id),
+ hir::Node::Expr(hir::Expr { kind: hir::ExprKind::Binary(op, ..), .. })
+ if !op.node.is_by_value()
+ )
+ {
+ let deref_kind = if checked_ty.is_box() {
+ "unboxing the value"
+ } else if checked_ty.is_ref() {
+ "dereferencing the borrow"
+ } else {
+ "dereferencing the type"
+ };
+
+ // Suggest removing `&` if we have removed any, otherwise suggest just
+ // dereferencing the remaining number of steps.
+ let message = if remove.is_empty() {
+ format!("consider {deref_kind}")
+ } else {
+ format!(
+ "consider removing the `{}` and {} instead",
+ remove.trim(),
+ deref_kind
+ )
+ };
+
+ let prefix =
+ match self.tcx.hir().maybe_get_struct_pattern_shorthand_field(expr) {
+ Some(ident) => format!("{ident}: "),
+ None => String::new(),
+ };
+
+ let (span, suggestion) = if self.is_else_if_block(expr) {
+ // Don't suggest nonsense like `else *if`
+ return None;
+ } else if let Some(expr) = self.maybe_get_block_expr(expr) {
+ // prefix should be empty here..
+ (expr.span.shrink_to_lo(), "*".to_string())
+ } else {
+ (prefix_span, format!("{}{}", prefix, "*".repeat(steps)))
+ };
+ if suggestion.trim().is_empty() {
+ return None;
+ }
+
+ return Some((
+ vec![(span, suggestion)],
+ message,
+ Applicability::MachineApplicable,
+ true,
+ false,
+ ));
+ }
+ }
+ }
+ _ => {}
+ }
+ None
+ }
+
+ /// Returns whether the given expression is an `else if`.
+ fn is_else_if_block(&self, expr: &hir::Expr<'_>) -> bool {
+ if let hir::ExprKind::If(..) = expr.kind {
+ let parent_id = self.tcx.hir().parent_id(expr.hir_id);
+ if let Some(Node::Expr(hir::Expr {
+ kind: hir::ExprKind::If(_, _, Some(else_expr)),
+ ..
+ })) = self.tcx.hir().find(parent_id)
+ {
+ return else_expr.hir_id == expr.hir_id;
+ }
+ }
+ false
+ }
+
+ pub(crate) fn suggest_cast(
+ &self,
+ err: &mut Diagnostic,
+ expr: &hir::Expr<'_>,
+ checked_ty: Ty<'tcx>,
+ expected_ty: Ty<'tcx>,
+ expected_ty_expr: Option<&'tcx hir::Expr<'tcx>>,
+ ) -> bool {
+ if self.tcx.sess.source_map().is_imported(expr.span) {
+ // Ignore if span is from within a macro.
+ return false;
+ }
+
+ let Ok(src) = self.tcx.sess.source_map().span_to_snippet(expr.span) else {
+ return false;
+ };
+
+ // If casting this expression to a given numeric type would be appropriate in case of a type
+ // mismatch.
+ //
+ // We want to minimize the amount of casting operations that are suggested, as it can be a
+ // lossy operation with potentially bad side effects, so we only suggest when encountering
+ // an expression that indicates that the original type couldn't be directly changed.
+ //
+ // For now, don't suggest casting with `as`.
+ let can_cast = false;
+
+ let mut sugg = vec![];
+
+ if let Some(hir::Node::ExprField(field)) = self.tcx.hir().find_parent(expr.hir_id) {
+ // `expr` is a literal field for a struct, only suggest if appropriate
+ if field.is_shorthand {
+ // This is a field literal
+ sugg.push((field.ident.span.shrink_to_lo(), format!("{}: ", field.ident)));
+ } else {
+ // Likely a field was meant, but this field wasn't found. Do not suggest anything.
+ return false;
+ }
+ };
+
+ if let hir::ExprKind::Call(path, args) = &expr.kind
+ && let (hir::ExprKind::Path(hir::QPath::TypeRelative(base_ty, path_segment)), 1) =
+ (&path.kind, args.len())
+ // `expr` is a conversion like `u32::from(val)`, do not suggest anything (#63697).
+ && let (hir::TyKind::Path(hir::QPath::Resolved(None, base_ty_path)), sym::from) =
+ (&base_ty.kind, path_segment.ident.name)
+ {
+ if let Some(ident) = &base_ty_path.segments.iter().map(|s| s.ident).next() {
+ match ident.name {
+ sym::i128
+ | sym::i64
+ | sym::i32
+ | sym::i16
+ | sym::i8
+ | sym::u128
+ | sym::u64
+ | sym::u32
+ | sym::u16
+ | sym::u8
+ | sym::isize
+ | sym::usize
+ if base_ty_path.segments.len() == 1 =>
+ {
+ return false;
+ }
+ _ => {}
+ }
+ }
+ }
+
+ let msg = format!(
+ "you can convert {} `{}` to {} `{}`",
+ checked_ty.kind().article(),
+ checked_ty,
+ expected_ty.kind().article(),
+ expected_ty,
+ );
+ let cast_msg = format!(
+ "you can cast {} `{}` to {} `{}`",
+ checked_ty.kind().article(),
+ checked_ty,
+ expected_ty.kind().article(),
+ expected_ty,
+ );
+ let lit_msg = format!(
+ "change the type of the numeric literal from `{checked_ty}` to `{expected_ty}`",
+ );
+
+ let close_paren = if expr.precedence().order() < PREC_POSTFIX {
+ sugg.push((expr.span.shrink_to_lo(), "(".to_string()));
+ ")"
+ } else {
+ ""
+ };
+
+ let mut cast_suggestion = sugg.clone();
+ cast_suggestion.push((expr.span.shrink_to_hi(), format!("{close_paren} as {expected_ty}")));
+ let mut into_suggestion = sugg.clone();
+ into_suggestion.push((expr.span.shrink_to_hi(), format!("{close_paren}.into()")));
+ let mut suffix_suggestion = sugg.clone();
+ suffix_suggestion.push((
+ if matches!(
+ (&expected_ty.kind(), &checked_ty.kind()),
+ (ty::Int(_) | ty::Uint(_), ty::Float(_))
+ ) {
+ // Remove fractional part from literal, for example `42.0f32` into `42`
+ let src = src.trim_end_matches(&checked_ty.to_string());
+ let len = src.split('.').next().unwrap().len();
+ expr.span.with_lo(expr.span.lo() + BytePos(len as u32))
+ } else {
+ let len = src.trim_end_matches(&checked_ty.to_string()).len();
+ expr.span.with_lo(expr.span.lo() + BytePos(len as u32))
+ },
+ if expr.precedence().order() < PREC_POSTFIX {
+ // Readd `)`
+ format!("{expected_ty})")
+ } else {
+ expected_ty.to_string()
+ },
+ ));
+ let literal_is_ty_suffixed = |expr: &hir::Expr<'_>| {
+ if let hir::ExprKind::Lit(lit) = &expr.kind { lit.node.is_suffixed() } else { false }
+ };
+ let is_negative_int =
+ |expr: &hir::Expr<'_>| matches!(expr.kind, hir::ExprKind::Unary(hir::UnOp::Neg, ..));
+ let is_uint = |ty: Ty<'_>| matches!(ty.kind(), ty::Uint(..));
+
+ let in_const_context = self.tcx.hir().is_inside_const_context(expr.hir_id);
+
+ let suggest_fallible_into_or_lhs_from =
+ |err: &mut Diagnostic, exp_to_found_is_fallible: bool| {
+ // If we know the expression the expected type is derived from, we might be able
+ // to suggest a widening conversion rather than a narrowing one (which may
+ // panic). For example, given x: u8 and y: u32, if we know the span of "x",
+ // x > y
+ // can be given the suggestion "u32::from(x) > y" rather than
+ // "x > y.try_into().unwrap()".
+ let lhs_expr_and_src = expected_ty_expr.and_then(|expr| {
+ self.tcx
+ .sess
+ .source_map()
+ .span_to_snippet(expr.span)
+ .ok()
+ .map(|src| (expr, src))
+ });
+ let (msg, suggestion) = if let (Some((lhs_expr, lhs_src)), false) =
+ (lhs_expr_and_src, exp_to_found_is_fallible)
+ {
+ let msg = format!(
+ "you can convert `{lhs_src}` from `{expected_ty}` to `{checked_ty}`, matching the type of `{src}`",
+ );
+ let suggestion = vec![
+ (lhs_expr.span.shrink_to_lo(), format!("{checked_ty}::from(")),
+ (lhs_expr.span.shrink_to_hi(), ")".to_string()),
+ ];
+ (msg, suggestion)
+ } else {
+ let msg =
+ format!("{} and panic if the converted value doesn't fit", msg.clone());
+ let mut suggestion = sugg.clone();
+ suggestion.push((
+ expr.span.shrink_to_hi(),
+ format!("{close_paren}.try_into().unwrap()"),
+ ));
+ (msg, suggestion)
+ };
+ err.multipart_suggestion_verbose(msg, suggestion, Applicability::MachineApplicable);
+ };
+
+ let suggest_to_change_suffix_or_into =
+ |err: &mut Diagnostic,
+ found_to_exp_is_fallible: bool,
+ exp_to_found_is_fallible: bool| {
+ let exp_is_lhs = expected_ty_expr.is_some_and(|e| self.tcx.hir().is_lhs(e.hir_id));
+
+ if exp_is_lhs {
+ return;
+ }
+
+ let always_fallible = found_to_exp_is_fallible
+ && (exp_to_found_is_fallible || expected_ty_expr.is_none());
+ let msg = if literal_is_ty_suffixed(expr) {
+ lit_msg.clone()
+ } else if always_fallible && (is_negative_int(expr) && is_uint(expected_ty)) {
+ // We now know that converting either the lhs or rhs is fallible. Before we
+ // suggest a fallible conversion, check if the value can never fit in the
+ // expected type.
+ let msg = format!("`{src}` cannot fit into type `{expected_ty}`");
+ err.note(msg);
+ return;
+ } else if in_const_context {
+ // Do not recommend `into` or `try_into` in const contexts.
+ return;
+ } else if found_to_exp_is_fallible {
+ return suggest_fallible_into_or_lhs_from(err, exp_to_found_is_fallible);
+ } else {
+ msg.clone()
+ };
+ let suggestion = if literal_is_ty_suffixed(expr) {
+ suffix_suggestion.clone()
+ } else {
+ into_suggestion.clone()
+ };
+ err.multipart_suggestion_verbose(msg, suggestion, Applicability::MachineApplicable);
+ };
+
+ match (&expected_ty.kind(), &checked_ty.kind()) {
+ (ty::Int(exp), ty::Int(found)) => {
+ let (f2e_is_fallible, e2f_is_fallible) = match (exp.bit_width(), found.bit_width())
+ {
+ (Some(exp), Some(found)) if exp < found => (true, false),
+ (Some(exp), Some(found)) if exp > found => (false, true),
+ (None, Some(8 | 16)) => (false, true),
+ (Some(8 | 16), None) => (true, false),
+ (None, _) | (_, None) => (true, true),
+ _ => (false, false),
+ };
+ suggest_to_change_suffix_or_into(err, f2e_is_fallible, e2f_is_fallible);
+ true
+ }
+ (ty::Uint(exp), ty::Uint(found)) => {
+ let (f2e_is_fallible, e2f_is_fallible) = match (exp.bit_width(), found.bit_width())
+ {
+ (Some(exp), Some(found)) if exp < found => (true, false),
+ (Some(exp), Some(found)) if exp > found => (false, true),
+ (None, Some(8 | 16)) => (false, true),
+ (Some(8 | 16), None) => (true, false),
+ (None, _) | (_, None) => (true, true),
+ _ => (false, false),
+ };
+ suggest_to_change_suffix_or_into(err, f2e_is_fallible, e2f_is_fallible);
+ true
+ }
+ (&ty::Int(exp), &ty::Uint(found)) => {
+ let (f2e_is_fallible, e2f_is_fallible) = match (exp.bit_width(), found.bit_width())
+ {
+ (Some(exp), Some(found)) if found < exp => (false, true),
+ (None, Some(8)) => (false, true),
+ _ => (true, true),
+ };
+ suggest_to_change_suffix_or_into(err, f2e_is_fallible, e2f_is_fallible);
+ true
+ }
+ (&ty::Uint(exp), &ty::Int(found)) => {
+ let (f2e_is_fallible, e2f_is_fallible) = match (exp.bit_width(), found.bit_width())
+ {
+ (Some(exp), Some(found)) if found > exp => (true, false),
+ (Some(8), None) => (true, false),
+ _ => (true, true),
+ };
+ suggest_to_change_suffix_or_into(err, f2e_is_fallible, e2f_is_fallible);
+ true
+ }
+ (ty::Float(exp), ty::Float(found)) => {
+ if found.bit_width() < exp.bit_width() {
+ suggest_to_change_suffix_or_into(err, false, true);
+ } else if literal_is_ty_suffixed(expr) {
+ err.multipart_suggestion_verbose(
+ lit_msg,
+ suffix_suggestion,
+ Applicability::MachineApplicable,
+ );
+ } else if can_cast {
+ // Missing try_into implementation for `f64` to `f32`
+ err.multipart_suggestion_verbose(
+ format!("{cast_msg}, producing the closest possible value"),
+ cast_suggestion,
+ Applicability::MaybeIncorrect, // lossy conversion
+ );
+ }
+ true
+ }
+ (&ty::Uint(_) | &ty::Int(_), &ty::Float(_)) => {
+ if literal_is_ty_suffixed(expr) {
+ err.multipart_suggestion_verbose(
+ lit_msg,
+ suffix_suggestion,
+ Applicability::MachineApplicable,
+ );
+ } else if can_cast {
+ // Missing try_into implementation for `{float}` to `{integer}`
+ err.multipart_suggestion_verbose(
+ format!("{msg}, rounding the float towards zero"),
+ cast_suggestion,
+ Applicability::MaybeIncorrect, // lossy conversion
+ );
+ }
+ true
+ }
+ (ty::Float(exp), ty::Uint(found)) => {
+ // if `found` is `None` (meaning found is `usize`), don't suggest `.into()`
+ if exp.bit_width() > found.bit_width().unwrap_or(256) {
+ err.multipart_suggestion_verbose(
+ format!(
+ "{msg}, producing the floating point representation of the integer",
+ ),
+ into_suggestion,
+ Applicability::MachineApplicable,
+ );
+ } else if literal_is_ty_suffixed(expr) {
+ err.multipart_suggestion_verbose(
+ lit_msg,
+ suffix_suggestion,
+ Applicability::MachineApplicable,
+ );
+ } else {
+ // Missing try_into implementation for `{integer}` to `{float}`
+ err.multipart_suggestion_verbose(
+ format!(
+ "{cast_msg}, producing the floating point representation of the integer, \
+ rounded if necessary",
+ ),
+ cast_suggestion,
+ Applicability::MaybeIncorrect, // lossy conversion
+ );
+ }
+ true
+ }
+ (ty::Float(exp), ty::Int(found)) => {
+ // if `found` is `None` (meaning found is `isize`), don't suggest `.into()`
+ if exp.bit_width() > found.bit_width().unwrap_or(256) {
+ err.multipart_suggestion_verbose(
+ format!(
+ "{}, producing the floating point representation of the integer",
+ msg.clone(),
+ ),
+ into_suggestion,
+ Applicability::MachineApplicable,
+ );
+ } else if literal_is_ty_suffixed(expr) {
+ err.multipart_suggestion_verbose(
+ lit_msg,
+ suffix_suggestion,
+ Applicability::MachineApplicable,
+ );
+ } else {
+ // Missing try_into implementation for `{integer}` to `{float}`
+ err.multipart_suggestion_verbose(
+ format!(
+ "{}, producing the floating point representation of the integer, \
+ rounded if necessary",
+ &msg,
+ ),
+ cast_suggestion,
+ Applicability::MaybeIncorrect, // lossy conversion
+ );
+ }
+ true
+ }
+ (
+ &ty::Uint(ty::UintTy::U32 | ty::UintTy::U64 | ty::UintTy::U128)
+ | &ty::Int(ty::IntTy::I32 | ty::IntTy::I64 | ty::IntTy::I128),
+ &ty::Char,
+ ) => {
+ err.multipart_suggestion_verbose(
+ format!("{cast_msg}, since a `char` always occupies 4 bytes"),
+ cast_suggestion,
+ Applicability::MachineApplicable,
+ );
+ true
+ }
+ _ => false,
+ }
+ }
+
+ /// Identify when the user has written `foo..bar()` instead of `foo.bar()`.
+ pub(crate) fn suggest_method_call_on_range_literal(
+ &self,
+ err: &mut Diagnostic,
+ expr: &hir::Expr<'tcx>,
+ checked_ty: Ty<'tcx>,
+ expected_ty: Ty<'tcx>,
+ ) {
+ if !hir::is_range_literal(expr) {
+ return;
+ }
+ let hir::ExprKind::Struct(hir::QPath::LangItem(LangItem::Range, ..), [start, end], _) =
+ expr.kind
+ else {
+ return;
+ };
+ let parent = self.tcx.hir().parent_id(expr.hir_id);
+ if let Some(hir::Node::ExprField(_)) = self.tcx.hir().find(parent) {
+ // Ignore `Foo { field: a..Default::default() }`
+ return;
+ }
+ let mut expr = end.expr;
+ let mut expectation = Some(expected_ty);
+ while let hir::ExprKind::MethodCall(_, rcvr, ..) = expr.kind {
+ // Getting to the root receiver and asserting it is a fn call let's us ignore cases in
+ // `tests/ui/methods/issues/issue-90315.stderr`.
+ expr = rcvr;
+ // If we have more than one layer of calls, then the expected ty
+ // cannot guide the method probe.
+ expectation = None;
+ }
+ let hir::ExprKind::Call(method_name, _) = expr.kind else {
+ return;
+ };
+ let ty::Adt(adt, _) = checked_ty.kind() else {
+ return;
+ };
+ if self.tcx.lang_items().range_struct() != Some(adt.did()) {
+ return;
+ }
+ if let ty::Adt(adt, _) = expected_ty.kind()
+ && self.tcx.lang_items().range_struct() == Some(adt.did())
+ {
+ return;
+ }
+ // Check if start has method named end.
+ let hir::ExprKind::Path(hir::QPath::Resolved(None, p)) = method_name.kind else {
+ return;
+ };
+ let [hir::PathSegment { ident, .. }] = p.segments else {
+ return;
+ };
+ let self_ty = self.typeck_results.borrow().expr_ty(start.expr);
+ let Ok(_pick) = self.lookup_probe_for_diagnostic(
+ *ident,
+ self_ty,
+ expr,
+ probe::ProbeScope::AllTraits,
+ expectation,
+ ) else {
+ return;
+ };
+ let mut sugg = ".";
+ let mut span = start.expr.span.between(end.expr.span);
+ if span.lo() + BytePos(2) == span.hi() {
+ // There's no space between the start, the range op and the end, suggest removal which
+ // will be more noticeable than the replacement of `..` with `.`.
+ span = span.with_lo(span.lo() + BytePos(1));
+ sugg = "";
+ }
+ err.span_suggestion_verbose(
+ span,
+ "you likely meant to write a method call instead of a range",
+ sugg,
+ Applicability::MachineApplicable,
+ );
+ }
+
+ /// Identify when the type error is because `()` is found in a binding that was assigned a
+ /// block without a tail expression.
+ pub(crate) fn suggest_return_binding_for_missing_tail_expr(
+ &self,
+ err: &mut Diagnostic,
+ expr: &hir::Expr<'_>,
+ checked_ty: Ty<'tcx>,
+ expected_ty: Ty<'tcx>,
+ ) {
+ if !checked_ty.is_unit() {
+ return;
+ }
+ let hir::ExprKind::Path(hir::QPath::Resolved(None, path)) = expr.kind else {
+ return;
+ };
+ let hir::def::Res::Local(hir_id) = path.res else {
+ return;
+ };
+ let Some(hir::Node::Pat(pat)) = self.tcx.hir().find(hir_id) else {
+ return;
+ };
+ let Some(hir::Node::Local(hir::Local { ty: None, init: Some(init), .. })) =
+ self.tcx.hir().find_parent(pat.hir_id)
+ else {
+ return;
+ };
+ let hir::ExprKind::Block(block, None) = init.kind else {
+ return;
+ };
+ if block.expr.is_some() {
+ return;
+ }
+ let [.., stmt] = block.stmts else {
+ err.span_label(block.span, "this empty block is missing a tail expression");
+ return;
+ };
+ let hir::StmtKind::Semi(tail_expr) = stmt.kind else {
+ return;
+ };
+ let Some(ty) = self.node_ty_opt(tail_expr.hir_id) else {
+ return;
+ };
+ if self.can_eq(self.param_env, expected_ty, ty) {
+ err.span_suggestion_short(
+ stmt.span.with_lo(tail_expr.span.hi()),
+ "remove this semicolon",
+ "",
+ Applicability::MachineApplicable,
+ );
+ } else {
+ err.span_label(block.span, "this block is missing a tail expression");
+ }
+ }
}