Skip to content

Commit 403c8c2

Browse files
committed
E0277: suggest dereferencing function arguments in more cases
1 parent 0b1bf71 commit 403c8c2

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
@@ -443,9 +443,8 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> {
443443
}
444444
}
445445

446-
/// When after several dereferencing, the reference satisfies the trait
447-
/// bound. This function provides dereference suggestion for this
448-
/// specific situation.
446+
/// Provide a suggestion to dereference arguments to functions and binary operators, if that
447+
/// would satisfy trait bounds.
449448
pub(super) fn suggest_dereferences(
450449
&self,
451450
obligation: &PredicateObligation<'tcx>,
@@ -459,127 +458,100 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> {
459458
&& let Some(arg_ty) = typeck_results.expr_ty_adjusted_opt(expr)
460459
{
461460
// Suggest dereferencing the argument to a function/method call if possible
461+
462+
// Get the root obligation, since the leaf obligation we have may be unhelpful (#87437)
462463
let mut real_trait_pred = trait_pred;
463464
while let Some((parent_code, parent_trait_pred)) = code.parent() {
464465
code = parent_code;
465466
if let Some(parent_trait_pred) = parent_trait_pred {
466467
real_trait_pred = parent_trait_pred;
467468
}
469+
}
468470

469-
// We `instantiate_bound_regions_with_erased` here because `make_subregion` does not handle
470-
// `ReBound`, and we don't particularly care about the regions.
471-
let real_ty =
472-
self.tcx.instantiate_bound_regions_with_erased(real_trait_pred.self_ty());
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 = self.tcx.instantiate_bound_regions_with_erased(real_trait_pred.self_ty());
474+
if !self.can_eq(obligation.param_env, real_ty, arg_ty) {
475+
return false;
476+
}
473477

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

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