Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
232 changes: 170 additions & 62 deletions compiler/rustc_trait_selection/src/error_reporting/traits/suggestions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1643,81 +1643,189 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> {
self.predicate_must_hold_modulo_regions(&obligation)
};

let trait_pred_and_imm_ref = poly_trait_pred.map_bound(|p| {
(p, Ty::new_imm_ref(self.tcx, self.tcx.lifetimes.re_static, p.self_ty()))
});
let trait_pred_and_mut_ref = poly_trait_pred.map_bound(|p| {
(p, Ty::new_mut_ref(self.tcx, self.tcx.lifetimes.re_static, p.self_ty()))
});

let imm_ref_self_ty_satisfies_pred = mk_result(trait_pred_and_imm_ref);
let mut_ref_self_ty_satisfies_pred = mk_result(trait_pred_and_mut_ref);

let mut point_at_relevant_args =
|pred_ty: Ty<'tcx>, args_and_inputs: Vec<(hir::Expr<'_>, Ty<'tcx>)>| {
let Some(typeck_results) = &self.typeck_results else { return false };

let erased_self_ty =
self.tcx.instantiate_bound_regions_with_erased(poly_trait_pred.self_ty());
let mut spans = vec![];
for (arg, input) in args_and_inputs {
let Some(arg_ty) = typeck_results.expr_ty_adjusted_opt(&arg) else { continue };
let pred_has_arg_type = self.infcx.can_eq(param_env, arg_ty, erased_self_ty);
let arg_is_type_param = self.infcx.can_eq(param_env, pred_ty, input);
if pred_has_arg_type && arg_is_type_param {
err.span_label(
arg.span,
format!("`{arg_ty}` doesn't satisfy the trait bound"),
);
spans.push(arg.span);
}
}
let this = pluralize!("this", spans.len());
if !spans.is_empty() {
if imm_ref_self_ty_satisfies_pred {
err.multipart_suggestion(
format!("consider borrowing {this} argument"),
spans.iter().map(|sp| (sp.shrink_to_lo(), "&".into())).collect(),
Applicability::MaybeIncorrect,
);
}
if mut_ref_self_ty_satisfies_pred {
err.multipart_suggestion(
format!("consider mutably borrowing {this} argument"),
spans.iter().map(|sp| (sp.shrink_to_lo(), "&mut ".into())).collect(),
Applicability::MaybeIncorrect,
);
}
}
!spans.is_empty()
};
let code = match obligation.cause.code() {
ObligationCauseCode::FunctionArg { parent_code, .. } => parent_code,
// FIXME(compiler-errors): This is kind of a mess, but required for obligations
// that come from a path expr to affect the *call* expr.
c @ ObligationCauseCode::WhereClauseInExpr(_, _, hir_id, _)
c @ ObligationCauseCode::WhereClauseInExpr(def_id, _, hir_id, idx)
if self.tcx.hir_span(*hir_id).lo() == span.lo() =>
{
// `hir_id` corresponds to the HIR node that introduced a `where`-clause obligation.
// If that obligation comes from a type in an associated method call, we need
// special handling here.
if let hir::Node::Expr(expr) = self.tcx.parent_hir_node(*hir_id)
&& let hir::ExprKind::Call(base, _) = expr.kind
&& let hir::ExprKind::Path(hir::QPath::TypeRelative(ty, segment)) = base.kind
&& let hir::Node::Expr(outer) = self.tcx.parent_hir_node(expr.hir_id)
&& let hir::ExprKind::AddrOf(hir::BorrowKind::Ref, mtbl, _) = outer.kind
&& ty.span == span
{
// We've encountered something like `&str::from("")`, where the intended code
// was likely `<&str>::from("")`. The former is interpreted as "call method
// `from` on `str` and borrow the result", while the latter means "call method
// `from` on `&str`".

let trait_pred_and_imm_ref = poly_trait_pred.map_bound(|p| {
(p, Ty::new_imm_ref(self.tcx, self.tcx.lifetimes.re_static, p.self_ty()))
});
let trait_pred_and_mut_ref = poly_trait_pred.map_bound(|p| {
(p, Ty::new_mut_ref(self.tcx, self.tcx.lifetimes.re_static, p.self_ty()))
});
if let hir::Node::Expr(expr) = self.tcx.parent_hir_node(*hir_id) {
// If that obligation comes from a type in an associated method call, we need
// special handling here.
if let hir::ExprKind::Call(base, _) = expr.kind
&& let hir::ExprKind::Path(hir::QPath::TypeRelative(ty, segment)) =
base.kind
&& let hir::Node::Expr(outer) = self.tcx.parent_hir_node(expr.hir_id)
&& let hir::ExprKind::AddrOf(hir::BorrowKind::Ref, mtbl, _) = outer.kind
&& ty.span == span
{
// We've encountered something like `&str::from("")`, where the intended code
// was likely `<&str>::from("")`. The former is interpreted as "call method
// `from` on `str` and borrow the result", while the latter means "call method
// `from` on `&str`".

let imm_ref_self_ty_satisfies_pred = mk_result(trait_pred_and_imm_ref);
let mut_ref_self_ty_satisfies_pred = mk_result(trait_pred_and_mut_ref);
let sugg_msg = |pre: &str| {
format!(
"you likely meant to call the associated function `{FN}` for type \
`&{pre}{TY}`, but the code as written calls associated function `{FN}` on \
type `{TY}`",
FN = segment.ident,
TY = poly_trait_pred.self_ty(),
)
};
match (imm_ref_self_ty_satisfies_pred, mut_ref_self_ty_satisfies_pred, mtbl) {
(true, _, hir::Mutability::Not) | (_, true, hir::Mutability::Mut) => {
err.multipart_suggestion(
sugg_msg(mtbl.prefix_str()),
vec![
(outer.span.shrink_to_lo(), "<".to_string()),
(span.shrink_to_hi(), ">".to_string()),
],
Applicability::MachineApplicable,
);
let sugg_msg = |pre: &str| {
format!(
"you likely meant to call the associated function `{FN}` for type \
`&{pre}{TY}`, but the code as written calls associated function `{FN}` on \
type `{TY}`",
FN = segment.ident,
TY = poly_trait_pred.self_ty(),
)
};
match (imm_ref_self_ty_satisfies_pred, mut_ref_self_ty_satisfies_pred, mtbl)
{
(true, _, hir::Mutability::Not) | (_, true, hir::Mutability::Mut) => {
err.multipart_suggestion(
sugg_msg(mtbl.prefix_str()),
vec![
(outer.span.shrink_to_lo(), "<".to_string()),
(span.shrink_to_hi(), ">".to_string()),
],
Applicability::MachineApplicable,
);
}
(true, _, hir::Mutability::Mut) => {
// There's an associated function found on the immutable borrow of the
err.multipart_suggestion(
sugg_msg("mut "),
vec![
(outer.span.shrink_to_lo().until(span), "<&".to_string()),
(span.shrink_to_hi(), ">".to_string()),
],
Applicability::MachineApplicable,
);
}
(_, true, hir::Mutability::Not) => {
err.multipart_suggestion(
sugg_msg(""),
vec![
(
outer.span.shrink_to_lo().until(span),
"<&mut ".to_string(),
),
(span.shrink_to_hi(), ">".to_string()),
],
Applicability::MachineApplicable,
);
}
_ => {}
}
(true, _, hir::Mutability::Mut) => {
// There's an associated function found on the immutable borrow of the
err.multipart_suggestion(
sugg_msg("mut "),
vec![
(outer.span.shrink_to_lo().until(span), "<&".to_string()),
(span.shrink_to_hi(), ">".to_string()),
],
Applicability::MachineApplicable,
// If we didn't return early here, we would instead suggest `&&str::from("")`.
return false;
} else if let hir::ExprKind::Call(_, args) = expr.kind {
if let Some(pred) = self
.tcx
.clauses_of(*def_id)
.instantiate_identity(self.tcx)
.clauses
.into_iter()
.nth(*idx)
&& let Some(pred) = pred.as_trait_clause()
// This feature allows for `for<T> T: Trait`, which fails
// `instantiate_bound_regions_with_erased`. Avoid suggesting for now.
&& !self.tcx.features().non_lifetime_binders()
{
let pred_ty = self.tcx.instantiate_bound_regions_with_erased(
pred.self_ty().skip_norm_wip(),
);
}
(_, true, hir::Mutability::Not) => {
err.multipart_suggestion(
sugg_msg(""),
vec![
(outer.span.shrink_to_lo().until(span), "<&mut ".to_string()),
(span.shrink_to_hi(), ">".to_string()),
],
Applicability::MachineApplicable,
let fn_sig = self.tcx.instantiate_bound_regions_with_erased(
self.tcx.fn_sig(*def_id).instantiate_identity().skip_norm_wip(),
);
if point_at_relevant_args(
pred_ty,
args.into_iter()
.zip(fn_sig.inputs())
.map(|(e, t)| (*e, *t))
.collect(),
) {
return false;
}
}
_ => {}
}
// If we didn't return early here, we would instead suggest `&&str::from("")`.
}
c
}
c @ ObligationCauseCode::WhereClauseInExpr(def_id, _, hir_id, idx)
if let hir::Node::Expr(expr) = self.tcx.hir_node(*hir_id)
&& let hir::ExprKind::MethodCall(_segment, rcvr, args, ..) = expr.kind
&& let Some(pred) = self
.tcx
.clauses_of(*def_id)
.instantiate_identity(self.tcx)
.clauses
.into_iter()
.nth(*idx)
&& let Some(pred) = pred.as_trait_clause()
// This feature allows for `for<T> T: Trait`, which fails
// `instantiate_bound_regions_with_erased`. Avoid suggesting for now.
&& !self.tcx.features().non_lifetime_binders() =>
{
let fn_sig = self.tcx.instantiate_bound_regions_with_erased(
self.tcx.fn_sig(*def_id).instantiate_identity().skip_norm_wip(),
);
// We've got a method call where likely one of the arguments didn't meet a bound.
let pred_ty =
self.tcx.instantiate_bound_regions_with_erased(pred.self_ty().skip_norm_wip());
if point_at_relevant_args(
pred_ty,
[rcvr]
.into_iter()
.chain(args.into_iter())
.zip(fn_sig.inputs())
.map(|(e, t)| (*e, *t))
.collect(),
) {
return false;
}
c
Expand Down
15 changes: 10 additions & 5 deletions tests/ui/delegation/self-mapping-arguments-errors.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,16 @@ LL | | }
error[E0277]: the trait bound `(): target_expr_doesnt_relower_when_defs_inside::MyAdd` is not satisfied
--> $DIR/self-mapping-arguments-errors.rs:14:5
|
LL | / reuse impl MyAdd for W {
... |
LL | | self.0
LL | | }
| |_____^ the trait `target_expr_doesnt_relower_when_defs_inside::MyAdd` is not implemented for `()`
LL | reuse impl MyAdd for W {
| _____^ -
| |____________________________|
... ||
LL | || self.0
LL | || }
| || ^
| ||_____|
| |_____`{type error}` doesn't satisfy the trait bound
| the trait `target_expr_doesnt_relower_when_defs_inside::MyAdd` is not implemented for `()`
|
help: the following other types implement trait `target_expr_doesnt_relower_when_defs_inside::MyAdd`
--> $DIR/self-mapping-arguments-errors.rs:8:5
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@ error[E0277]: the trait bound `X: A` is not satisfied
--> $DIR/as_expression.rs:60:15
|
LL | X.start().foo().finish();
| ^^^ unsatisfied trait bound
| --------- ^^^ unsatisfied trait bound
| |
| `X` doesn't satisfy the trait bound
|
help: the trait `A` is not implemented for `X`
--> $DIR/as_expression.rs:70:1
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@ error[E0277]: the trait bound `Foo: intrinsics::bounds::FloatPrimitive` is not s
--> $DIR/bad-intrinsic-monomorphization-bounds.rs:16:5
|
LL | intrinsics::fadd_fast(a, b)
| ^^^^^^^^^^^^^^^^^^^^^ unsatisfied trait bound
| ^^^^^^^^^^^^^^^^^^^^^ - - `Foo` doesn't satisfy the trait bound
| | |
| | `Foo` doesn't satisfy the trait bound
| unsatisfied trait bound
|
help: the nightly-only, unstable trait `intrinsics::bounds::FloatPrimitive` is not implemented for `Foo`
--> $DIR/bad-intrinsic-monomorphization-bounds.rs:13:1
Expand Down
47 changes: 47 additions & 0 deletions tests/ui/trait-bounds/ownership-mismatch-on-arg.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// #134805
mod needs_deref {
#[derive(Clone, Copy, Debug)]
struct Hello;

trait Tr: Clone + Copy {}
impl Tr for Hello {}

fn foo<T: Tr, K: std::fmt::Debug>(_v: T, _w: T, _k: K) {}

struct S;
impl S {
fn foo<K: std::fmt::Debug, T: Tr>(&self, _v: T, _w: T, _k: K) {}
}

fn bar() {
let hellos = [Hello; 3];
for hi in hellos.iter() {
foo(hi, hi, hi); //~ ERROR: the trait bound `&needs_deref::Hello: needs_deref::Tr` is not satisfied
S.foo(hi, hi, hi); //~ ERROR: the trait bound `&needs_deref::Hello: needs_deref::Tr` is not satisfied
}
}
}
Comment on lines +2 to +23

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@arferreira This case I was mentioning that would be nice to handle after this PR (plus one other test for Hello not being Copy but still being Clone) is what still needs to be handled. We should suggest

            foo(*hi, *hi, hi);
            S.foo(*hi, *hi, hi);

for the case above and

            foo(hi.clone(), hi.clone(), hi);
            S.foo(hi.clone(), hi.clone(), hi);

if possible.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good, I'll pick up that. Thanks for the context!


mod needs_borrow {
#[derive(Clone, Copy, Debug)]
struct Hello;

trait Tr: Clone + Copy {}
impl Tr for &Hello {}

fn foo<T: Tr, K: std::fmt::Debug>(_v: T, _w: T, _k: K) {}

struct S;
impl S {
fn foo<T: Tr, K: std::fmt::Debug>(&self, _v: T, _w: T, _k: K) {}
}

fn bar() {
let hellos = [Hello; 3];
for hi in hellos {
foo(hi, hi, hi); //~ ERROR: the trait bound `needs_borrow::Hello: needs_borrow::Tr` is not satisfied
S.foo(hi, hi, hi); //~ ERROR: the trait bound `needs_borrow::Hello: needs_borrow::Tr` is not satisfied
}
}
}
fn main() {}
Loading
Loading