Skip to content

Commit

Permalink
Add suggestion to diagnostic when user has array but trait wants slice.
Browse files Browse the repository at this point in the history
For #90528.
  • Loading branch information
BGR360 authored and jackh726 committed Mar 7, 2023
1 parent 81be7b8 commit 8ac7d0e
Show file tree
Hide file tree
Showing 10 changed files with 490 additions and 7 deletions.
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

0 comments on commit 8ac7d0e

Please sign in to comment.