Skip to content

Commit

Permalink
Suggest dereferncing when possible in E0277, fix rust-lang#87437
Browse files Browse the repository at this point in the history
  • Loading branch information
estebank committed Apr 4, 2022
1 parent ac8cbbd commit 883b93c
Show file tree
Hide file tree
Showing 7 changed files with 121 additions and 41 deletions.
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use super::{
EvaluationResult, Obligation, ObligationCause, ObligationCauseCode, PredicateObligation,
SelectionContext,
DerivedObligationCause, EvaluationResult, ImplDerivedObligationCause, Obligation,
ObligationCause, ObligationCauseCode, PredicateObligation, SelectionContext,
};

use crate::autoderef::Autoderef;
Expand Down Expand Up @@ -496,50 +496,78 @@ impl<'a, 'tcx> InferCtxtExt<'tcx> for InferCtxt<'a, 'tcx> {
trait_pred: ty::PolyTraitPredicate<'tcx>,
) -> bool {
// It only make sense when suggesting dereferences for arguments
let code = if let ObligationCauseCode::FunctionArgumentObligation { parent_code, .. } =
obligation.cause.code()
{
parent_code.clone()
} else {
let ObligationCauseCode::FunctionArgumentObligation { .. } = obligation.cause.code() else {
return false;
};
let param_env = obligation.param_env;
let body_id = obligation.cause.body_id;
let span = obligation.cause.span;
let real_trait_pred = match &*code {
ObligationCauseCode::ImplDerivedObligation(cause) => cause.derived.parent_trait_pred,
ObligationCauseCode::DerivedObligation(cause)
| ObligationCauseCode::BuiltinDerivedObligation(cause) => cause.parent_trait_pred,
_ => trait_pred,
};
let Some(real_ty) = real_trait_pred.self_ty().no_bound_vars() else {
return false;
};
let mut real_trait_pred = trait_pred;
let mut code = obligation.cause.code();
loop {
match &code {
ObligationCauseCode::FunctionArgumentObligation { parent_code, .. } => {
code = &parent_code;
}
ObligationCauseCode::ImplDerivedObligation(box ImplDerivedObligationCause {
derived: DerivedObligationCause { parent_code, parent_trait_pred },
..
})
| ObligationCauseCode::BuiltinDerivedObligation(DerivedObligationCause {
parent_code,
parent_trait_pred,
})
| ObligationCauseCode::DerivedObligation(DerivedObligationCause {
parent_code,
parent_trait_pred,
}) => {
code = &parent_code;
real_trait_pred = *parent_trait_pred;
}
_ => break,
};
let Some(real_ty) = real_trait_pred.self_ty().no_bound_vars() else {
continue;
};

if let ty::Ref(region, base_ty, mutbl) = *real_ty.kind() {
let mut autoderef = Autoderef::new(self, param_env, body_id, span, base_ty, span);
if let Some(steps) = autoderef.find_map(|(ty, steps)| {
// Re-add the `&`
let ty = self.tcx.mk_ref(region, TypeAndMut { ty, mutbl });
let obligation =
self.mk_trait_obligation_with_new_self_ty(param_env, real_trait_pred, ty);
Some(steps).filter(|_| self.predicate_may_hold(&obligation))
}) {
if steps > 0 {
if let Ok(src) = self.tcx.sess.source_map().span_to_snippet(span) {
// Don't care about `&mut` because `DerefMut` is used less
// often and user will not expect autoderef happens.
if src.starts_with('&') && !src.starts_with("&mut ") {
let derefs = "*".repeat(steps);
err.span_suggestion(
span,
"consider adding dereference here",
format!("&{}{}", derefs, &src[1..]),
Applicability::MachineApplicable,
);
return true;
if let ty::Ref(region, base_ty, mutbl) = *real_ty.kind() {
let mut autoderef = Autoderef::new(self, param_env, body_id, span, base_ty, span);
if let Some(steps) = autoderef.find_map(|(ty, steps)| {
// Re-add the `&`
let ty = self.tcx.mk_ref(region, TypeAndMut { ty, mutbl });
let obligation =
self.mk_trait_obligation_with_new_self_ty(param_env, real_trait_pred, ty);
Some(steps).filter(|_| self.predicate_may_hold(&obligation))
}) {
if steps > 0 {
if let Ok(src) = self.tcx.sess.source_map().span_to_snippet(span) {
// Don't care about `&mut` because `DerefMut` is used less
// often and user will not expect autoderef happens.
if src.starts_with('&') && !src.starts_with("&mut ") {
let derefs = "*".repeat(steps);
err.span_suggestion(
span,
"consider dereferencing here",
format!("&{}{}", derefs, &src[1..]),
Applicability::MachineApplicable,
);
return true;
}
}
}
} else if real_trait_pred != trait_pred {
// This branch addresses #87437.
let obligation =
self.mk_trait_obligation_with_new_self_ty(param_env, real_trait_pred, base_ty);
if self.predicate_may_hold(&obligation) {
err.span_suggestion_verbose(
span.shrink_to_lo(),
"consider dereferencing here",
"*".to_string(),
Applicability::MachineApplicable,
);
return true;
}
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/test/ui/traits/suggest-deferences/issue-39029.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ LL | let _errors = TcpListener::bind(&bad);
| ----------------- ^^^^
| | |
| | the trait `ToSocketAddrs` is not implemented for `NoToSocketAddrs`
| | help: consider adding dereference here: `&*bad`
| | help: consider dereferencing here: `&*bad`
| required by a bound introduced by this call
|
= note: required because of the requirements on the impl of `ToSocketAddrs` for `&NoToSocketAddrs`
Expand Down
2 changes: 1 addition & 1 deletion src/test/ui/traits/suggest-deferences/issue-62530.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ LL | takes_type_parameter(&string); // Error
| -------------------- ^^^^^^^
| | |
| | the trait `SomeTrait` is not implemented for `&String`
| | help: consider adding dereference here: `&*string`
| | help: consider dereferencing here: `&*string`
| required by a bound introduced by this call
|
note: required by a bound in `takes_type_parameter`
Expand Down
2 changes: 1 addition & 1 deletion src/test/ui/traits/suggest-deferences/multiple-0.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ LL | foo(&baz);
| --- ^^^^
| | |
| | the trait `Happy` is not implemented for `&Baz`
| | help: consider adding dereference here: `&***baz`
| | help: consider dereferencing here: `&***baz`
| required by a bound introduced by this call
|
note: required by a bound in `foo`
Expand Down
14 changes: 14 additions & 0 deletions src/test/ui/traits/suggest-deferences/root-obligation.fixed
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// run-rustfix

fn get_vowel_count(string: &str) -> usize {
string
.chars()
.filter(|c| "aeiou".contains(*c))
//~^ ERROR expected a `Fn<(char,)>` closure, found `char`
.count()
}

fn main() {
let _ = get_vowel_count("asdf");
}

14 changes: 14 additions & 0 deletions src/test/ui/traits/suggest-deferences/root-obligation.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// run-rustfix

fn get_vowel_count(string: &str) -> usize {
string
.chars()
.filter(|c| "aeiou".contains(c))
//~^ ERROR expected a `Fn<(char,)>` closure, found `char`
.count()
}

fn main() {
let _ = get_vowel_count("asdf");
}

24 changes: 24 additions & 0 deletions src/test/ui/traits/suggest-deferences/root-obligation.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
error[E0277]: expected a `Fn<(char,)>` closure, found `char`
--> $DIR/root-obligation.rs:6:38
|
LL | .filter(|c| "aeiou".contains(c))
| -------- ^ expected an `Fn<(char,)>` closure, found `char`
| |
| required by a bound introduced by this call
|
= help: the trait `Fn<(char,)>` is not implemented for `char`
= note: required because of the requirements on the impl of `FnOnce<(char,)>` for `&char`
= note: required because of the requirements on the impl of `Pattern<'_>` for `&char`
note: required by a bound in `core::str::<impl str>::contains`
--> $SRC_DIR/core/src/str/mod.rs:LL:COL
|
LL | pub fn contains<'a, P: Pattern<'a>>(&'a self, pat: P) -> bool {
| ^^^^^^^^^^^ required by this bound in `core::str::<impl str>::contains`
help: consider dereferencing here
|
LL | .filter(|c| "aeiou".contains(*c))
| +

error: aborting due to previous error

For more information about this error, try `rustc --explain E0277`.

0 comments on commit 883b93c

Please sign in to comment.