Skip to content

Modify existing bounds if they exist #107555

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
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
1 change: 1 addition & 0 deletions compiler/rustc_borrowck/src/diagnostics/conflict_errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -803,6 +803,7 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
predicates
.iter()
.map(|(param, constraint)| (param.name.as_str(), &**constraint, None)),
None,
);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ impl<'tcx> NonConstOp<'tcx> for FnCallNonConst<'tcx> {
&param_ty.name.as_str(),
&constraint,
None,
None,
);
}
}
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_hir_analysis/src/coherence/builtin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,7 @@ fn visit_implementation_of_copy(tcx: TyCtxt<'_>, impl_did: LocalDefId) {
bounds.iter().map(|(param, constraint, def_id)| {
(param.as_str(), constraint.as_str(), *def_id)
}),
None,
);
err.emit();
}
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_hir_typeck/src/fn_ctxt/suggestions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1385,6 +1385,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
generics,
diag,
vec![(param.name.as_str(), "Clone", Some(clone_trait_did))].into_iter(),
None,
);
} else {
self.suggest_derive(diag, &[(trait_ref.to_predicate(self.tcx), None, None)]);
Expand Down
95 changes: 66 additions & 29 deletions compiler/rustc_infer/src/infer/error_reporting/note_and_explain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,49 +77,86 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> {
(ty::Param(p), ty::Alias(ty::Projection, proj)) | (ty::Alias(ty::Projection, proj), ty::Param(p))
if tcx.def_kind(proj.def_id) != DefKind::ImplTraitPlaceholder =>
{
let generics = tcx.generics_of(body_owner_def_id);
let p_span = tcx.def_span(generics.type_param(p, tcx).def_id);
let p_def_id = tcx
.generics_of(body_owner_def_id)
.type_param(p, tcx)
.def_id;
let p_span = tcx.def_span(p_def_id);
if !sp.contains(p_span) {
diag.span_label(p_span, "this type parameter");
}
let hir = tcx.hir();
let mut note = true;
if let Some(generics) = generics
.type_param(p, tcx)
.def_id
let parent = p_def_id
.as_local()
.map(|id| hir.local_def_id_to_hir_id(id))
.and_then(|id| tcx.hir().find_parent(id))
.as_ref()
.and_then(|node| node.generics())
.and_then(|id| {
let local_id = hir.local_def_id_to_hir_id(id);
let generics = tcx.hir().find_parent(local_id)?.generics()?;
Some((id, generics))
});
if let Some((local_id, generics)) = parent
{
// Synthesize the associated type restriction `Add<Output = Expected>`.
// FIXME: extract this logic for use in other diagnostics.
let (trait_ref, assoc_substs) = proj.trait_ref_and_own_substs(tcx);
let path =
tcx.def_path_str_with_substs(trait_ref.def_id, trait_ref.substs);
let item_name = tcx.item_name(proj.def_id);
let item_args = self.format_generic_args(assoc_substs);

let path = if path.ends_with('>') {
format!(
"{}, {}{} = {}>",
&path[..path.len() - 1],
item_name,
item_args,
p
)
// Here, we try to see if there's an existing
// trait implementation that matches the one that
// we're suggesting to restrict. If so, find the
// "end", whether it be at the end of the trait
// or the end of the generic arguments.
let mut matching_span = None;
let mut matched_end_of_args = false;
for bound in generics.bounds_for_param(local_id) {
let potential_spans = bound
.bounds
.iter()
.find_map(|bound| {
let bound_trait_path = bound.trait_ref()?.path;
let def_id = bound_trait_path.res.opt_def_id()?;
let generic_args = bound_trait_path.segments.iter().last().map(|path| path.args());
(def_id == trait_ref.def_id).then_some((bound_trait_path.span, generic_args))
});

if let Some((end_of_trait, end_of_args)) = potential_spans {
let args_span = end_of_args.and_then(|args| args.span());
matched_end_of_args = args_span.is_some();
matching_span = args_span
.or_else(|| Some(end_of_trait))
.map(|span| span.shrink_to_hi());
break;
}
}

if matched_end_of_args {
// Append suggestion to the end of our args
let path = format!(", {}{} = {}",item_name, item_args, p);
note = !suggest_constraining_type_param(
tcx,
generics,
diag,
&format!("{}", proj.self_ty()),
&path,
None,
matching_span,
);
} else {
format!("{}<{}{} = {}>", path, item_name, item_args, p)
};
note = !suggest_constraining_type_param(
tcx,
generics,
diag,
&format!("{}", proj.self_ty()),
&path,
None,
);
// Suggest adding a bound to an existing trait
// or if the trait doesn't exist, add the trait
// and the suggested bounds.
let path = format!("<{}{} = {}>", item_name, item_args, p);
note = !suggest_constraining_type_param(
tcx,
generics,
diag,
&format!("{}", proj.self_ty()),
&path,
None,
matching_span,
);
}
}
if note {
diag.note("you might be missing a type parameter or trait bound");
Expand Down
15 changes: 14 additions & 1 deletion compiler/rustc_middle/src/ty/diagnostics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -193,19 +193,24 @@ fn suggest_removing_unsized_bound(
}

/// Suggest restricting a type param with a new bound.
///
/// If `span_to_replace` is provided, then that span will be replaced with the
/// `constraint`. If one wasn't provided, then the full bound will be suggested.
pub fn suggest_constraining_type_param(
tcx: TyCtxt<'_>,
generics: &hir::Generics<'_>,
err: &mut Diagnostic,
param_name: &str,
constraint: &str,
def_id: Option<DefId>,
span_to_replace: Option<Span>,
) -> bool {
suggest_constraining_type_params(
tcx,
generics,
err,
[(param_name, constraint, def_id)].into_iter(),
span_to_replace,
)
}

Expand All @@ -215,6 +220,7 @@ pub fn suggest_constraining_type_params<'a>(
generics: &hir::Generics<'_>,
err: &mut Diagnostic,
param_names_and_constraints: impl Iterator<Item = (&'a str, &'a str, Option<DefId>)>,
span_to_replace: Option<Span>,
) -> bool {
let mut grouped = FxHashMap::default();
param_names_and_constraints.for_each(|(param_name, constraint, def_id)| {
Expand Down Expand Up @@ -253,7 +259,9 @@ pub fn suggest_constraining_type_params<'a>(
let mut suggest_restrict = |span, bound_list_non_empty| {
suggestions.push((
span,
if bound_list_non_empty {
if span_to_replace.is_some() {
constraint.clone()
} else if bound_list_non_empty {
format!(" + {}", constraint)
} else {
format!(" {}", constraint)
Expand All @@ -262,6 +270,11 @@ pub fn suggest_constraining_type_params<'a>(
))
};

if let Some(span) = span_to_replace {
suggest_restrict(span, true);
continue;
}

// When the type parameter has been provided bounds
//
// Message:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -679,6 +679,7 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> {
&param_name,
&constraint,
Some(trait_pred.def_id()),
None,
) {
return;
}
Expand Down Expand Up @@ -1087,6 +1088,7 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> {
param.name.as_str(),
"Clone",
Some(clone_trait),
None,
);
}
err.span_suggestion_verbose(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ LL | for<'b> <Self as UnsafeCopy<'b, T>>::Item: std::ops::Deref<Target = T>,
| ^^^^^^^^^^ required by this bound in `UnsafeCopy`
help: consider further restricting this bound
|
LL | impl<T: Copy + std::ops::Deref + Deref<Target = T>> UnsafeCopy<'_, T> for T {
| +++++++++++++++++++
LL | impl<T: Copy + std::ops::Deref<Target = T>> UnsafeCopy<'_, T> for T {
| ++++++++++++

error: aborting due to previous error

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ LL | type Item<'a>: std::ops::Deref<Target = T>;
| ^^^^^^^^^^ required by this bound in `UnsafeCopy::Item`
help: consider further restricting this bound
|
LL | impl<T: Copy + std::ops::Deref + Deref<Target = T>> UnsafeCopy<T> for T {
| +++++++++++++++++++
LL | impl<T: Copy + std::ops::Deref<Target = T>> UnsafeCopy<T> for T {
| ++++++++++++

error: aborting due to previous error

Expand Down
6 changes: 3 additions & 3 deletions tests/ui/generic-associated-types/missing-bounds.fixed
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use std::ops::Add;

struct A<B>(B);

impl<B> Add for A<B> where B: Add + Add<Output = B> {
impl<B> Add for A<B> where B: Add<Output = B> {
type Output = Self;

fn add(self, rhs: Self) -> Self {
Expand All @@ -14,7 +14,7 @@ impl<B> Add for A<B> where B: Add + Add<Output = B> {

struct C<B>(B);

impl<B: Add + Add<Output = B>> Add for C<B> {
impl<B: Add<Output = B>> Add for C<B> {
type Output = Self;

fn add(self, rhs: Self) -> Self {
Expand All @@ -34,7 +34,7 @@ impl<B: std::ops::Add<Output = B>> Add for D<B> {

struct E<B>(B);

impl<B: Add + Add<Output = B>> Add for E<B> where B: Add<Output = B> {
impl<B: Add<Output = B>> Add for E<B> where B: Add<Output = B> {
//~^ ERROR equality constraints are not yet supported in `where` clauses
type Output = Self;

Expand Down
12 changes: 6 additions & 6 deletions tests/ui/generic-associated-types/missing-bounds.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,8 @@ LL | struct A<B>(B);
| ^
help: consider further restricting this bound
|
LL | impl<B> Add for A<B> where B: Add + Add<Output = B> {
| +++++++++++++++++
LL | impl<B> Add for A<B> where B: Add<Output = B> {
| ++++++++++++

error[E0308]: mismatched types
--> $DIR/missing-bounds.rs:21:14
Expand All @@ -60,8 +60,8 @@ LL | struct C<B>(B);
| ^
help: consider further restricting this bound
|
LL | impl<B: Add + Add<Output = B>> Add for C<B> {
| +++++++++++++++++
LL | impl<B: Add<Output = B>> Add for C<B> {
| ++++++++++++

error[E0369]: cannot add `B` to `B`
--> $DIR/missing-bounds.rs:31:21
Expand Down Expand Up @@ -96,8 +96,8 @@ LL | struct E<B>(B);
| ^
help: consider further restricting this bound
|
LL | impl<B: Add + Add<Output = B>> Add for E<B> where <B as Add>::Output = B {
| +++++++++++++++++
LL | impl<B: Add<Output = B>> Add for E<B> where <B as Add>::Output = B {
| ++++++++++++

error: aborting due to 5 previous errors

Expand Down
30 changes: 30 additions & 0 deletions tests/ui/suggestions/restrict-existing-type-bounds.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
pub trait TryAdd<Rhs = Self> {
type Error;
type Output;

fn try_add(self, rhs: Rhs) -> Result<Self::Output, Self::Error>;
}

impl<T: TryAdd> TryAdd for Option<T> {
type Error = <T as TryAdd>::Error;
type Output = Option<<T as TryAdd>::Output>;

fn try_add(self, rhs: Self) -> Result<Self::Output, Self::Error> {
Ok(self) //~ ERROR mismatched types
}
}

struct Other<A>(A);

struct X;

impl<T: TryAdd<Error = X>> TryAdd for Other<T> {
type Error = <T as TryAdd>::Error;
type Output = Other<<T as TryAdd>::Output>;

fn try_add(self, rhs: Self) -> Result<Self::Output, Self::Error> {
Ok(self) //~ ERROR mismatched types
}
}

fn main() {}
57 changes: 57 additions & 0 deletions tests/ui/suggestions/restrict-existing-type-bounds.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
error[E0308]: mismatched types
--> $DIR/restrict-existing-type-bounds.rs:13:12
|
LL | impl<T: TryAdd> TryAdd for Option<T> {
| - this type parameter
...
LL | Ok(self)
| -- ^^^^ expected `Option<<T as TryAdd>::Output>`, found `Option<T>`
| |
| arguments to this enum variant are incorrect
|
= note: expected enum `Option<<T as TryAdd>::Output>`
found enum `Option<T>`
help: the type constructed contains `Option<T>` due to the type of the argument passed
--> $DIR/restrict-existing-type-bounds.rs:13:9
|
LL | Ok(self)
| ^^^----^
| |
| this argument influences the type of `Ok`
note: tuple variant defined here
--> $SRC_DIR/core/src/result.rs:LL:COL
help: consider further restricting this bound
|
LL | impl<T: TryAdd<Output = T>> TryAdd for Option<T> {
| ++++++++++++

error[E0308]: mismatched types
--> $DIR/restrict-existing-type-bounds.rs:26:12
|
LL | impl<T: TryAdd<Error = X>> TryAdd for Other<T> {
| - this type parameter
...
LL | Ok(self)
| -- ^^^^ expected `Other<<T as TryAdd>::Output>`, found `Other<T>`
| |
| arguments to this enum variant are incorrect
|
= note: expected struct `Other<<T as TryAdd>::Output>`
found struct `Other<T>`
help: the type constructed contains `Other<T>` due to the type of the argument passed
--> $DIR/restrict-existing-type-bounds.rs:26:9
|
LL | Ok(self)
| ^^^----^
| |
| this argument influences the type of `Ok`
note: tuple variant defined here
--> $SRC_DIR/core/src/result.rs:LL:COL
help: consider further restricting this bound
|
LL | impl<T: TryAdd<Error = X, Output = T>> TryAdd for Other<T> {
| ++++++++++++

error: aborting due to 2 previous errors

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