Skip to content

Commit 7f0a180

Browse files
authored
Rollup merge of rust-lang#133292 - dianne:e0277-suggest-deref, r=estebank
E0277: suggest dereferencing function arguments in more cases This unifies and generalizes some of the logic in `TypeErrCtxt::suggest_dereferences` so that it will suggest dereferencing arguments to function/method calls in order to satisfy trait bounds in more cases. Previously it would only fire on reference types, and it had two separate cases (one specifically to get through custom `Deref` impls when passing by-reference, and one specifically to catch rust-lang#87437). I've based the new checks loosely on what's done for `E0308` in `FnCtxt::suggest_deref_or_ref`: it will suggest dereferences to satisfy trait bounds whenever the referent is `Copy`, is boxed (& so can be moved out of the boxes), or is being passed by reference. This doesn't make the suggestion fire in contexts other than function arguments or binary operators (which are in a separate case that this doesn't touch), and doesn't make it suggest a combination of `&`-removal and dereferences. Those would require a bit more restructuring, so I figured just doing this would be a decent first step. Closes rust-lang#90997
2 parents 6f3cf8d + 403c8c2 commit 7f0a180

File tree

6 files changed

+206
-113
lines changed

6 files changed

+206
-113
lines changed

compiler/rustc_trait_selection/src/error_reporting/traits/suggestions.rs

+85-113
Original file line numberDiff line numberDiff line change
@@ -445,9 +445,8 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> {
445445
}
446446
}
447447

448-
/// When after several dereferencing, the reference satisfies the trait
449-
/// bound. This function provides dereference suggestion for this
450-
/// specific situation.
448+
/// Provide a suggestion to dereference arguments to functions and binary operators, if that
449+
/// would satisfy trait bounds.
451450
pub(super) fn suggest_dereferences(
452451
&self,
453452
obligation: &PredicateObligation<'tcx>,
@@ -461,127 +460,100 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> {
461460
&& let Some(arg_ty) = typeck_results.expr_ty_adjusted_opt(expr)
462461
{
463462
// Suggest dereferencing the argument to a function/method call if possible
463+
464+
// Get the root obligation, since the leaf obligation we have may be unhelpful (#87437)
464465
let mut real_trait_pred = trait_pred;
465466
while let Some((parent_code, parent_trait_pred)) = code.parent() {
466467
code = parent_code;
467468
if let Some(parent_trait_pred) = parent_trait_pred {
468469
real_trait_pred = parent_trait_pred;
469470
}
471+
}
470472

471-
// We `instantiate_bound_regions_with_erased` here because `make_subregion` does not handle
472-
// `ReBound`, and we don't particularly care about the regions.
473-
let real_ty =
474-
self.tcx.instantiate_bound_regions_with_erased(real_trait_pred.self_ty());
473+
// We `instantiate_bound_regions_with_erased` here because `make_subregion` does not handle
474+
// `ReBound`, and we don't particularly care about the regions.
475+
let real_ty = self.tcx.instantiate_bound_regions_with_erased(real_trait_pred.self_ty());
476+
if !self.can_eq(obligation.param_env, real_ty, arg_ty) {
477+
return false;
478+
}
475479

476-
if self.can_eq(obligation.param_env, real_ty, arg_ty)
477-
&& let ty::Ref(region, base_ty, mutbl) = *real_ty.kind()
480+
// Potentially, we'll want to place our dereferences under a `&`. We don't try this for
481+
// `&mut`, since we can't be sure users will get the side-effects they want from it.
482+
// If this doesn't work, we'll try removing the `&` in `suggest_remove_reference`.
483+
// FIXME(dianne): this misses the case where users need both to deref and remove `&`s.
484+
// This method could be combined with `TypeErrCtxt::suggest_remove_reference` to handle
485+
// that, similar to what `FnCtxt::suggest_deref_or_ref` does.
486+
let (is_under_ref, base_ty, span) = match expr.kind {
487+
hir::ExprKind::AddrOf(hir::BorrowKind::Ref, hir::Mutability::Not, subexpr)
488+
if let &ty::Ref(region, base_ty, hir::Mutability::Not) = real_ty.kind() =>
478489
{
479-
let autoderef = (self.autoderef_steps)(base_ty);
480-
if let Some(steps) =
481-
autoderef.into_iter().enumerate().find_map(|(steps, (ty, obligations))| {
482-
// Re-add the `&`
483-
let ty = Ty::new_ref(self.tcx, region, ty, mutbl);
484-
485-
// Remapping bound vars here
486-
let real_trait_pred_and_ty = real_trait_pred
487-
.map_bound(|inner_trait_pred| (inner_trait_pred, ty));
488-
let obligation = self.mk_trait_obligation_with_new_self_ty(
489-
obligation.param_env,
490-
real_trait_pred_and_ty,
491-
);
492-
let may_hold = obligations
493-
.iter()
494-
.chain([&obligation])
495-
.all(|obligation| self.predicate_may_hold(obligation))
496-
.then_some(steps);
490+
(Some(region), base_ty, subexpr.span)
491+
}
492+
// Don't suggest `*&mut`, etc.
493+
hir::ExprKind::AddrOf(..) => return false,
494+
_ => (None, real_ty, obligation.cause.span),
495+
};
497496

498-
may_hold
499-
})
500-
{
501-
if steps > 0 {
502-
// Don't care about `&mut` because `DerefMut` is used less
503-
// often and user will not expect that an autoderef happens.
504-
if let hir::Node::Expr(hir::Expr {
505-
kind:
506-
hir::ExprKind::AddrOf(
507-
hir::BorrowKind::Ref,
508-
hir::Mutability::Not,
509-
expr,
510-
),
511-
..
512-
}) = self.tcx.hir_node(*arg_hir_id)
513-
{
514-
let derefs = "*".repeat(steps);
515-
err.span_suggestion_verbose(
516-
expr.span.shrink_to_lo(),
517-
"consider dereferencing here",
518-
derefs,
519-
Applicability::MachineApplicable,
520-
);
521-
return true;
522-
}
523-
}
524-
} else if real_trait_pred != trait_pred {
525-
// This branch addresses #87437.
526-
527-
let span = obligation.cause.span;
528-
// Remapping bound vars here
529-
let real_trait_pred_and_base_ty = real_trait_pred
530-
.map_bound(|inner_trait_pred| (inner_trait_pred, base_ty));
531-
let obligation = self.mk_trait_obligation_with_new_self_ty(
532-
obligation.param_env,
533-
real_trait_pred_and_base_ty,
534-
);
535-
let sized_obligation = Obligation::new(
536-
self.tcx,
537-
obligation.cause.clone(),
538-
obligation.param_env,
539-
ty::TraitRef::new(
540-
self.tcx,
541-
self.tcx.require_lang_item(
542-
hir::LangItem::Sized,
543-
Some(obligation.cause.span),
544-
),
545-
[base_ty],
546-
),
547-
);
548-
if self.predicate_may_hold(&obligation)
549-
&& self.predicate_must_hold_modulo_regions(&sized_obligation)
550-
// Do not suggest * if it is already a reference,
551-
// will suggest removing the borrow instead in that case.
552-
&& !matches!(expr.kind, hir::ExprKind::AddrOf(..))
553-
{
554-
let call_node = self.tcx.hir_node(*call_hir_id);
555-
let msg = "consider dereferencing here";
556-
let is_receiver = matches!(
557-
call_node,
558-
Node::Expr(hir::Expr {
559-
kind: hir::ExprKind::MethodCall(_, receiver_expr, ..),
560-
..
561-
})
562-
if receiver_expr.hir_id == *arg_hir_id
563-
);
564-
if is_receiver {
565-
err.multipart_suggestion_verbose(
566-
msg,
567-
vec![
568-
(span.shrink_to_lo(), "(*".to_string()),
569-
(span.shrink_to_hi(), ")".to_string()),
570-
],
571-
Applicability::MachineApplicable,
572-
)
573-
} else {
574-
err.span_suggestion_verbose(
575-
span.shrink_to_lo(),
576-
msg,
577-
'*',
578-
Applicability::MachineApplicable,
579-
)
580-
};
581-
return true;
582-
}
583-
}
497+
let autoderef = (self.autoderef_steps)(base_ty);
498+
let mut is_boxed = base_ty.is_box();
499+
if let Some(steps) = autoderef.into_iter().position(|(mut ty, obligations)| {
500+
// Ensure one of the following for dereferencing to be valid: we're passing by
501+
// reference, `ty` is `Copy`, or we're moving out of a (potentially nested) `Box`.
502+
let can_deref = is_under_ref.is_some()
503+
|| self.type_is_copy_modulo_regions(obligation.param_env, ty)
504+
|| ty.is_numeric() // for inference vars (presumably but not provably `Copy`)
505+
|| is_boxed && self.type_is_sized_modulo_regions(obligation.param_env, ty);
506+
is_boxed &= ty.is_box();
507+
508+
// Re-add the `&` if necessary
509+
if let Some(region) = is_under_ref {
510+
ty = Ty::new_ref(self.tcx, region, ty, hir::Mutability::Not);
584511
}
512+
513+
// Remapping bound vars here
514+
let real_trait_pred_and_ty =
515+
real_trait_pred.map_bound(|inner_trait_pred| (inner_trait_pred, ty));
516+
let obligation = self.mk_trait_obligation_with_new_self_ty(
517+
obligation.param_env,
518+
real_trait_pred_and_ty,
519+
);
520+
521+
can_deref
522+
&& obligations
523+
.iter()
524+
.chain([&obligation])
525+
.all(|obligation| self.predicate_may_hold(obligation))
526+
}) && steps > 0
527+
{
528+
let derefs = "*".repeat(steps);
529+
let msg = "consider dereferencing here";
530+
let call_node = self.tcx.hir_node(*call_hir_id);
531+
let is_receiver = matches!(
532+
call_node,
533+
Node::Expr(hir::Expr {
534+
kind: hir::ExprKind::MethodCall(_, receiver_expr, ..),
535+
..
536+
})
537+
if receiver_expr.hir_id == *arg_hir_id
538+
);
539+
if is_receiver {
540+
err.multipart_suggestion_verbose(
541+
msg,
542+
vec![
543+
(span.shrink_to_lo(), format!("({derefs}")),
544+
(span.shrink_to_hi(), ")".to_string()),
545+
],
546+
Applicability::MachineApplicable,
547+
)
548+
} else {
549+
err.span_suggestion_verbose(
550+
span.shrink_to_lo(),
551+
msg,
552+
derefs,
553+
Applicability::MachineApplicable,
554+
)
555+
};
556+
return true;
585557
}
586558
} else if let (
587559
ObligationCauseCode::BinOp { lhs_hir_id, rhs_hir_id: Some(rhs_hir_id), .. },

tests/ui/no_send-rc.stderr

+4
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@ note: required by a bound in `bar`
1212
|
1313
LL | fn bar<T: Send>(_: T) {}
1414
| ^^^^ required by this bound in `bar`
15+
help: consider dereferencing here
16+
|
17+
LL | bar(*x);
18+
| +
1519

1620
error: aborting due to 1 previous error
1721

tests/ui/suggestions/issue-84973-blacklist.stderr

+4
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,10 @@ note: required by a bound in `f_send`
8686
|
8787
LL | fn f_send<T: Send>(t: T) {}
8888
| ^^^^ required by this bound in `f_send`
89+
help: consider dereferencing here
90+
|
91+
LL | f_send(*rc);
92+
| +
8993

9094
error: aborting due to 5 previous errors
9195

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
//@ run-rustfix
2+
//! diagnostic test for #90997.
3+
//! test that E0277 suggests dereferences to satisfy bounds when the referent is `Copy` or boxed.
4+
use std::ops::Deref;
5+
6+
trait Test {
7+
fn test(self);
8+
}
9+
fn consume_test(x: impl Test) { x.test() }
10+
11+
impl Test for u32 {
12+
fn test(self) {}
13+
}
14+
struct MyRef(u32);
15+
impl Deref for MyRef {
16+
type Target = u32;
17+
fn deref(&self) -> &Self::Target {
18+
&self.0
19+
}
20+
}
21+
22+
struct NonCopy;
23+
impl Test for NonCopy {
24+
fn test(self) {}
25+
}
26+
27+
fn main() {
28+
let my_ref = MyRef(0);
29+
consume_test(*my_ref);
30+
//~^ ERROR the trait bound `MyRef: Test` is not satisfied
31+
//~| SUGGESTION *
32+
33+
let nested_box = Box::new(Box::new(Box::new(NonCopy)));
34+
consume_test(***nested_box);
35+
//~^ ERROR the trait bound `Box<Box<Box<NonCopy>>>: Test` is not satisfied
36+
//~| SUGGESTION ***
37+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
//@ run-rustfix
2+
//! diagnostic test for #90997.
3+
//! test that E0277 suggests dereferences to satisfy bounds when the referent is `Copy` or boxed.
4+
use std::ops::Deref;
5+
6+
trait Test {
7+
fn test(self);
8+
}
9+
fn consume_test(x: impl Test) { x.test() }
10+
11+
impl Test for u32 {
12+
fn test(self) {}
13+
}
14+
struct MyRef(u32);
15+
impl Deref for MyRef {
16+
type Target = u32;
17+
fn deref(&self) -> &Self::Target {
18+
&self.0
19+
}
20+
}
21+
22+
struct NonCopy;
23+
impl Test for NonCopy {
24+
fn test(self) {}
25+
}
26+
27+
fn main() {
28+
let my_ref = MyRef(0);
29+
consume_test(my_ref);
30+
//~^ ERROR the trait bound `MyRef: Test` is not satisfied
31+
//~| SUGGESTION *
32+
33+
let nested_box = Box::new(Box::new(Box::new(NonCopy)));
34+
consume_test(nested_box);
35+
//~^ ERROR the trait bound `Box<Box<Box<NonCopy>>>: Test` is not satisfied
36+
//~| SUGGESTION ***
37+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
error[E0277]: the trait bound `MyRef: Test` is not satisfied
2+
--> $DIR/deref-argument.rs:29:18
3+
|
4+
LL | consume_test(my_ref);
5+
| ------------ ^^^^^^ the trait `Test` is not implemented for `MyRef`
6+
| |
7+
| required by a bound introduced by this call
8+
|
9+
note: required by a bound in `consume_test`
10+
--> $DIR/deref-argument.rs:9:25
11+
|
12+
LL | fn consume_test(x: impl Test) { x.test() }
13+
| ^^^^ required by this bound in `consume_test`
14+
help: consider dereferencing here
15+
|
16+
LL | consume_test(*my_ref);
17+
| +
18+
19+
error[E0277]: the trait bound `Box<Box<Box<NonCopy>>>: Test` is not satisfied
20+
--> $DIR/deref-argument.rs:34:18
21+
|
22+
LL | consume_test(nested_box);
23+
| ------------ ^^^^^^^^^^ the trait `Test` is not implemented for `Box<Box<Box<NonCopy>>>`
24+
| |
25+
| required by a bound introduced by this call
26+
|
27+
note: required by a bound in `consume_test`
28+
--> $DIR/deref-argument.rs:9:25
29+
|
30+
LL | fn consume_test(x: impl Test) { x.test() }
31+
| ^^^^ required by this bound in `consume_test`
32+
help: consider dereferencing here
33+
|
34+
LL | consume_test(***nested_box);
35+
| +++
36+
37+
error: aborting due to 2 previous errors
38+
39+
For more information about this error, try `rustc --explain E0277`.

0 commit comments

Comments
 (0)