Rollup merge of #155219 - nataliakokoromyti:fix-155088-borrow-suggestion-v2, r=JohnTitor

Do not suggest borrowing enclosing calls for nested where-clause obligations

In rust-lang/rust#155088, the compiler was blaming the whole call expr instead of the value that  actually failed the trait bound, so for foo(&[String::from("a")]) it was suggesting stuff like &foo(...). I changed the suggestion logic so it only emits borrow help if the expr it found actually matches the failed self type,  and used the same check for the “similar impl exists” help too. So now the compiler should give the normal error + required bound note.

Fix rust-lang/rust#155088
This commit is contained in:
Jonathan Brouwer
2026-04-24 18:19:17 +02:00
committed by GitHub
5 changed files with 83 additions and 0 deletions
@@ -363,6 +363,7 @@ pub(super) fn maybe_report_ambiguity(
if impl_candidates.len() < 40 { if impl_candidates.len() < 40 {
self.report_similar_impl_candidates( self.report_similar_impl_candidates(
impl_candidates.as_slice(), impl_candidates.as_slice(),
obligation,
trait_pred, trait_pred,
obligation.cause.body_id, obligation.cause.body_id,
&mut err, &mut err,
@@ -1973,6 +1973,7 @@ pub(super) fn find_similar_impl_candidates(
pub(super) fn report_similar_impl_candidates( pub(super) fn report_similar_impl_candidates(
&self, &self,
impl_candidates: &[ImplCandidate<'tcx>], impl_candidates: &[ImplCandidate<'tcx>],
obligation: &PredicateObligation<'tcx>,
trait_pred: ty::PolyTraitPredicate<'tcx>, trait_pred: ty::PolyTraitPredicate<'tcx>,
body_def_id: LocalDefId, body_def_id: LocalDefId,
err: &mut Diag<'_>, err: &mut Diag<'_>,
@@ -2037,6 +2038,20 @@ pub(super) fn report_similar_impl_candidates(
}; };
if let [single] = &impl_candidates { if let [single] = &impl_candidates {
let self_ty = trait_pred.skip_binder().self_ty();
if !self_ty.has_escaping_bound_vars() {
let self_ty = self.tcx.instantiate_bound_regions_with_erased(trait_pred.self_ty());
if let ty::Ref(_, inner_ty, _) = self_ty.kind()
&& self.can_eq(param_env, single.trait_ref.self_ty(), *inner_ty)
&& !self.where_clause_expr_matches_failed_self_ty(obligation, self_ty)
{
// Avoid pointing at a nearby impl like `String: Borrow<str>` when the
// failing obligation comes from something nested inside an enclosing call
// expression such as `foo(&[String::from("a")])`.
return true;
}
}
// If we have a single implementation, try to unify it with the trait ref // If we have a single implementation, try to unify it with the trait ref
// that failed. This should uncover a better hint for what *is* implemented. // that failed. This should uncover a better hint for what *is* implemented.
if self.probe(|_| { if self.probe(|_| {
@@ -2456,6 +2471,7 @@ fn report_similar_impl_candidates_for_root_obligation(
let impl_candidates = self.find_similar_impl_candidates(trait_pred); let impl_candidates = self.find_similar_impl_candidates(trait_pred);
self.report_similar_impl_candidates( self.report_similar_impl_candidates(
&impl_candidates, &impl_candidates,
obligation,
trait_pred, trait_pred,
body_def_id, body_def_id,
err, err,
@@ -3137,6 +3153,7 @@ fn try_to_add_help_message(
let impl_candidates = self.find_similar_impl_candidates(trait_predicate); let impl_candidates = self.find_similar_impl_candidates(trait_predicate);
if !self.report_similar_impl_candidates( if !self.report_similar_impl_candidates(
&impl_candidates, &impl_candidates,
obligation,
trait_predicate, trait_predicate,
body_def_id, body_def_id,
err, err,
@@ -1584,6 +1584,39 @@ pub fn extract_callable_info(
if output.is_ty_var() { None } else { Some((def_id_or_name, output, inputs)) } if output.is_ty_var() { None } else { Some((def_id_or_name, output, inputs)) }
} }
pub(super) fn where_clause_expr_matches_failed_self_ty(
&self,
obligation: &PredicateObligation<'tcx>,
old_self_ty: Ty<'tcx>,
) -> bool {
let ObligationCauseCode::WhereClauseInExpr(..) = obligation.cause.code() else {
return true;
};
let (Some(typeck_results), Some(body)) = (
self.typeck_results.as_ref(),
self.tcx.hir_maybe_body_owned_by(obligation.cause.body_id),
) else {
return true;
};
let mut expr_finder = FindExprBySpan::new(obligation.cause.span, self.tcx);
expr_finder.visit_expr(body.value);
let Some(expr) = expr_finder.result else {
return true;
};
let inner_old_self_ty = match old_self_ty.kind() {
ty::Ref(_, inner_ty, _) => Some(*inner_ty),
_ => None,
};
[typeck_results.expr_ty_adjusted_opt(expr)].into_iter().flatten().any(|expr_ty| {
self.can_eq(obligation.param_env, expr_ty, old_self_ty)
|| inner_old_self_ty
.is_some_and(|inner_ty| self.can_eq(obligation.param_env, expr_ty, inner_ty))
})
}
pub(super) fn suggest_add_reference_to_arg( pub(super) fn suggest_add_reference_to_arg(
&self, &self,
obligation: &PredicateObligation<'tcx>, obligation: &PredicateObligation<'tcx>,
@@ -1857,6 +1890,15 @@ pub(super) fn suggest_add_reference_to_arg(
if let hir::ExprKind::AddrOf(_, _, _) = expr.kind { if let hir::ExprKind::AddrOf(_, _, _) = expr.kind {
return false; return false;
} }
let old_self_ty = old_pred.skip_binder().self_ty();
if !old_self_ty.has_escaping_bound_vars()
&& !self.where_clause_expr_matches_failed_self_ty(
obligation,
self.tcx.instantiate_bound_regions_with_erased(old_pred.self_ty()),
)
{
return false;
}
let needs_parens_post = expr_needs_parens(expr); let needs_parens_post = expr_needs_parens(expr);
let needs_parens_pre = match self.tcx.parent_hir_node(expr.hir_id) { let needs_parens_pre = match self.tcx.parent_hir_node(expr.hir_id) {
Node::Expr(e) Node::Expr(e)
@@ -0,0 +1,8 @@
use std::borrow::Borrow;
fn foo(_v: impl IntoIterator<Item = impl Borrow<str>>) {}
fn main() {
foo(&[String::from("a")]);
//~^ ERROR the trait bound `&String: Borrow<str>` is not satisfied
}
@@ -0,0 +1,15 @@
error[E0277]: the trait bound `&String: Borrow<str>` is not satisfied
--> $DIR/dont-suggest-borrow-whole-call-issue-155088.rs:6:5
|
LL | foo(&[String::from("a")]);
| ^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `Borrow<str>` is not implemented for `&String`
|
note: required by a bound in `foo`
--> $DIR/dont-suggest-borrow-whole-call-issue-155088.rs:3:42
|
LL | fn foo(_v: impl IntoIterator<Item = impl Borrow<str>>) {}
| ^^^^^^^^^^^ required by this bound in `foo`
error: aborting due to 1 previous error
For more information about this error, try `rustc --explain E0277`.