Skip to content

Add suggestion to diagnostic when user has array but trait wants slice. (rebased) #108841

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Mar 12, 2023
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
20 changes: 14 additions & 6 deletions compiler/rustc_trait_selection/src/traits/error_reporting/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1035,7 +1035,7 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> {
// Can't show anything else useful, try to find similar impls.
let impl_candidates = self.find_similar_impl_candidates(trait_predicate);
if !self.report_similar_impl_candidates(
impl_candidates,
&impl_candidates,
trait_ref,
body_hir_id,
&mut err,
Expand Down Expand Up @@ -1071,14 +1071,21 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> {
let impl_candidates =
self.find_similar_impl_candidates(trait_pred);
self.report_similar_impl_candidates(
impl_candidates,
&impl_candidates,
trait_ref,
body_hir_id,
&mut err,
true,
);
}
}

self.maybe_suggest_convert_to_slice(
&mut err,
trait_ref,
impl_candidates.as_slice(),
span,
);
}

// Changing mutability doesn't make a difference to whether we have
Expand Down Expand Up @@ -1529,7 +1536,7 @@ trait InferCtxtPrivExt<'tcx> {

fn report_similar_impl_candidates(
&self,
impl_candidates: Vec<ImplCandidate<'tcx>>,
impl_candidates: &[ImplCandidate<'tcx>],
trait_ref: ty::PolyTraitRef<'tcx>,
body_id: hir::HirId,
err: &mut Diagnostic,
Expand Down Expand Up @@ -2027,7 +2034,7 @@ impl<'tcx> InferCtxtPrivExt<'tcx> for TypeErrCtxt<'_, 'tcx> {

fn report_similar_impl_candidates(
&self,
impl_candidates: Vec<ImplCandidate<'tcx>>,
impl_candidates: &[ImplCandidate<'tcx>],
trait_ref: ty::PolyTraitRef<'tcx>,
body_id: hir::HirId,
err: &mut Diagnostic,
Expand Down Expand Up @@ -2138,7 +2145,8 @@ impl<'tcx> InferCtxtPrivExt<'tcx> for TypeErrCtxt<'_, 'tcx> {
// Prefer more similar candidates first, then sort lexicographically
// by their normalized string representation.
let mut normalized_impl_candidates_and_similarities = impl_candidates
.into_iter()
.iter()
.copied()
.map(|ImplCandidate { trait_ref, similarity }| {
// FIXME(compiler-errors): This should be using `NormalizeExt::normalize`
let normalized = self
Expand Down Expand Up @@ -2351,7 +2359,7 @@ impl<'tcx> InferCtxtPrivExt<'tcx> for TypeErrCtxt<'_, 'tcx> {
let hir =
self.tcx.hir().local_def_id_to_hir_id(obligation.cause.body_id);
self.report_similar_impl_candidates(
impl_candidates,
impl_candidates.as_slice(),
trait_ref,
body_id.map(|id| id.hir_id).unwrap_or(hir),
&mut err,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// ignore-tidy-filelength

use super::{
DefIdOrName, FindExprBySpan, Obligation, ObligationCause, ObligationCauseCode,
DefIdOrName, FindExprBySpan, ImplCandidate, Obligation, ObligationCause, ObligationCauseCode,
PredicateObligation,
};

Expand Down Expand Up @@ -382,6 +382,14 @@ pub trait TypeErrCtxtExt<'tcx> {
body_id: hir::HirId,
param_env: ty::ParamEnv<'tcx>,
) -> Vec<Option<(Span, (DefId, Ty<'tcx>))>>;

fn maybe_suggest_convert_to_slice(
&self,
err: &mut Diagnostic,
trait_ref: ty::Binder<'tcx, ty::TraitRef<'tcx>>,
candidate_impls: &[ImplCandidate<'tcx>],
span: Span,
);
}

fn predicate_constraint(generics: &hir::Generics<'_>, pred: ty::Predicate<'_>) -> (Span, String) {
Expand Down Expand Up @@ -3826,6 +3834,73 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> {
}
assocs_in_this_method
}

/// If the type that failed selection is an array or a reference to an array,
/// but the trait is implemented for slices, suggest that the user converts
/// the array into a slice.
fn maybe_suggest_convert_to_slice(
&self,
err: &mut Diagnostic,
trait_ref: ty::Binder<'tcx, ty::TraitRef<'tcx>>,
candidate_impls: &[ImplCandidate<'tcx>],
span: Span,
) {
// Three cases where we can make a suggestion:
// 1. `[T; _]` (array of T)
// 2. `&[T; _]` (reference to array of T)
// 3. `&mut [T; _]` (mutable reference to array of T)
let (element_ty, mut mutability) = match *trait_ref.skip_binder().self_ty().kind() {
ty::Array(element_ty, _) => (element_ty, None),

ty::Ref(_, pointee_ty, mutability) => match *pointee_ty.kind() {
ty::Array(element_ty, _) => (element_ty, Some(mutability)),
_ => return,
},

_ => return,
};

// Go through all the candidate impls to see if any of them is for
// slices of `element_ty` with `mutability`.
let mut is_slice = |candidate: Ty<'tcx>| match *candidate.kind() {
ty::RawPtr(ty::TypeAndMut { ty: t, mutbl: m }) | ty::Ref(_, t, m) => {
if matches!(*t.kind(), ty::Slice(e) if e == element_ty)
&& m == mutability.unwrap_or(m)
{
// Use the candidate's mutability going forward.
mutability = Some(m);
true
} else {
false
}
}
_ => false,
};

// Grab the first candidate that matches, if any, and make a suggestion.
if let Some(slice_ty) = candidate_impls
.iter()
.map(|trait_ref| trait_ref.trait_ref.self_ty())
.filter(|t| is_slice(*t))
.next()
{
let msg = &format!("convert the array to a `{}` slice instead", slice_ty);

if let Ok(snippet) = self.tcx.sess.source_map().span_to_snippet(span) {
let mut suggestions = vec![];
if snippet.starts_with('&') {
} else if let Some(hir::Mutability::Mut) = mutability {
suggestions.push((span.shrink_to_lo(), "&mut ".into()));
} else {
suggestions.push((span.shrink_to_lo(), "&".into()));
}
suggestions.push((span.shrink_to_hi(), "[..]".into()));
err.multipart_suggestion_verbose(msg, suggestions, Applicability::MaybeIncorrect);
} else {
err.span_help(span, msg);
}
}
}
}

/// Add a hint to add a missing borrow or remove an unnecessary one.
Expand Down
20 changes: 20 additions & 0 deletions tests/ui/dst/issue-90528-unsizing-suggestion-1.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// Issue #90528: provide helpful suggestions when a trait bound is unsatisfied
// due to a missed unsizing coercion.
//
// This test exercises array literals and a trait implemented on immutable slices.

trait Read {}

impl Read for &[u8] {}

fn wants_read(_: impl Read) {}

fn main() {
wants_read([0u8]);
//~^ ERROR the trait bound `[u8; 1]: Read` is not satisfied
wants_read(&[0u8]);
//~^ ERROR the trait bound `&[u8; 1]: Read` is not satisfied
wants_read(&[0u8][..]);
wants_read(&mut [0u8]);
//~^ ERROR the trait bound `&mut [u8; 1]: Read` is not satisfied
}
56 changes: 56 additions & 0 deletions tests/ui/dst/issue-90528-unsizing-suggestion-1.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
error[E0277]: the trait bound `[u8; 1]: Read` is not satisfied
--> $DIR/issue-90528-unsizing-suggestion-1.rs:13:16
|
LL | wants_read([0u8]);
| ---------- ^^^^^ the trait `Read` is not implemented for `[u8; 1]`
| |
| required by a bound introduced by this call
|
= help: the trait `Read` is implemented for `&[u8]`
note: required by a bound in `wants_read`
--> $DIR/issue-90528-unsizing-suggestion-1.rs:10:23
|
LL | fn wants_read(_: impl Read) {}
| ^^^^ required by this bound in `wants_read`
help: convert the array to a `&[u8]` slice instead
|
LL | wants_read(&[0u8][..]);
| + ++++

error[E0277]: the trait bound `&[u8; 1]: Read` is not satisfied
--> $DIR/issue-90528-unsizing-suggestion-1.rs:15:16
|
LL | wants_read(&[0u8]);
| ---------- ^^^^^^ the trait `Read` is not implemented for `&[u8; 1]`
| |
| required by a bound introduced by this call
|
= help: the trait `Read` is implemented for `&[u8]`
note: required by a bound in `wants_read`
--> $DIR/issue-90528-unsizing-suggestion-1.rs:10:23
|
LL | fn wants_read(_: impl Read) {}
| ^^^^ required by this bound in `wants_read`
help: convert the array to a `&[u8]` slice instead
|
LL | wants_read(&[0u8][..]);
| ++++

error[E0277]: the trait bound `&mut [u8; 1]: Read` is not satisfied
--> $DIR/issue-90528-unsizing-suggestion-1.rs:18:16
|
LL | wants_read(&mut [0u8]);
| ---------- ^^^^^^^^^^ the trait `Read` is not implemented for `&mut [u8; 1]`
| |
| required by a bound introduced by this call
|
= help: the trait `Read` is implemented for `&[u8]`
note: required by a bound in `wants_read`
--> $DIR/issue-90528-unsizing-suggestion-1.rs:10:23
|
LL | fn wants_read(_: impl Read) {}
| ^^^^ required by this bound in `wants_read`

error: aborting due to 3 previous errors

For more information about this error, try `rustc --explain E0277`.
28 changes: 28 additions & 0 deletions tests/ui/dst/issue-90528-unsizing-suggestion-2.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// Issue #90528: provide helpful suggestions when a trait bound is unsatisfied
// due to a missed unsizing coercion.
//
// This test exercises array variables and a trait implemented on immmutable slices.

trait Read {}

impl Read for &[u8] {}

fn wants_read(_: impl Read) {}

fn main() {
let x = [0u8];
wants_read(x);
//~^ ERROR the trait bound `[u8; 1]: Read` is not satisfied
wants_read(&x);
//~^ ERROR the trait bound `&[u8; 1]: Read` is not satisfied
wants_read(&x[..]);

let x = &[0u8];
wants_read(x);
//~^ ERROR the trait bound `&[u8; 1]: Read` is not satisfied
wants_read(&x);
//~^ ERROR the trait bound `&&[u8; 1]: Read` is not satisfied
wants_read(*x);
//~^ ERROR the trait bound `[u8; 1]: Read` is not satisfied
wants_read(&x[..]);
}
94 changes: 94 additions & 0 deletions tests/ui/dst/issue-90528-unsizing-suggestion-2.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
error[E0277]: the trait bound `[u8; 1]: Read` is not satisfied
--> $DIR/issue-90528-unsizing-suggestion-2.rs:14:16
|
LL | wants_read(x);
| ---------- ^ the trait `Read` is not implemented for `[u8; 1]`
| |
| required by a bound introduced by this call
|
= help: the trait `Read` is implemented for `&[u8]`
note: required by a bound in `wants_read`
--> $DIR/issue-90528-unsizing-suggestion-2.rs:10:23
|
LL | fn wants_read(_: impl Read) {}
| ^^^^ required by this bound in `wants_read`
help: convert the array to a `&[u8]` slice instead
|
LL | wants_read(&x[..]);
| + ++++

error[E0277]: the trait bound `&[u8; 1]: Read` is not satisfied
--> $DIR/issue-90528-unsizing-suggestion-2.rs:16:16
|
LL | wants_read(&x);
| ---------- ^^ the trait `Read` is not implemented for `&[u8; 1]`
| |
| required by a bound introduced by this call
|
= help: the trait `Read` is implemented for `&[u8]`
note: required by a bound in `wants_read`
--> $DIR/issue-90528-unsizing-suggestion-2.rs:10:23
|
LL | fn wants_read(_: impl Read) {}
| ^^^^ required by this bound in `wants_read`
help: convert the array to a `&[u8]` slice instead
|
LL | wants_read(&x[..]);
| ++++

error[E0277]: the trait bound `&[u8; 1]: Read` is not satisfied
--> $DIR/issue-90528-unsizing-suggestion-2.rs:21:16
|
LL | wants_read(x);
| ---------- ^ the trait `Read` is not implemented for `&[u8; 1]`
| |
| required by a bound introduced by this call
|
= help: the trait `Read` is implemented for `&[u8]`
note: required by a bound in `wants_read`
--> $DIR/issue-90528-unsizing-suggestion-2.rs:10:23
|
LL | fn wants_read(_: impl Read) {}
| ^^^^ required by this bound in `wants_read`
help: convert the array to a `&[u8]` slice instead
|
LL | wants_read(&x[..]);
| + ++++

error[E0277]: the trait bound `&&[u8; 1]: Read` is not satisfied
--> $DIR/issue-90528-unsizing-suggestion-2.rs:23:16
|
LL | wants_read(&x);
| ---------- ^^ the trait `Read` is not implemented for `&&[u8; 1]`
| |
| required by a bound introduced by this call
|
= help: the trait `Read` is implemented for `&[u8]`
note: required by a bound in `wants_read`
--> $DIR/issue-90528-unsizing-suggestion-2.rs:10:23
|
LL | fn wants_read(_: impl Read) {}
| ^^^^ required by this bound in `wants_read`

error[E0277]: the trait bound `[u8; 1]: Read` is not satisfied
--> $DIR/issue-90528-unsizing-suggestion-2.rs:25:16
|
LL | wants_read(*x);
| ---------- ^^ the trait `Read` is not implemented for `[u8; 1]`
| |
| required by a bound introduced by this call
|
= help: the trait `Read` is implemented for `&[u8]`
note: required by a bound in `wants_read`
--> $DIR/issue-90528-unsizing-suggestion-2.rs:10:23
|
LL | fn wants_read(_: impl Read) {}
| ^^^^ required by this bound in `wants_read`
help: convert the array to a `&[u8]` slice instead
|
LL | wants_read(&*x[..]);
| + ++++

error: aborting due to 5 previous errors

For more information about this error, try `rustc --explain E0277`.
22 changes: 22 additions & 0 deletions tests/ui/dst/issue-90528-unsizing-suggestion-3.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// Issue #90528: provide helpful suggestions when a trait bound is unsatisfied
// due to a missed unsizing coercion.
//
// This test exercises array literals and a trait implemented on mutable slices.

trait Write {}

impl Write for &mut [u8] {}

fn wants_write(_: impl Write) {}

fn main() {
wants_write([0u8]);
//~^ ERROR the trait bound `[u8; 1]: Write` is not satisfied
wants_write(&mut [0u8]);
//~^ ERROR the trait bound `&mut [u8; 1]: Write` is not satisfied
wants_write(&mut [0u8][..]);
wants_write(&[0u8]);
//~^ ERROR the trait bound `&[u8; 1]: Write` is not satisfied
wants_write(&[0u8][..]);
//~^ ERROR the trait bound `&[u8]: Write` is not satisfied
}
Loading