about summary refs log tree commit diff
diff options
context:
space:
mode:
authorDylan DPC <dylan.dpc@gmail.com>2020-05-06 22:36:43 +0200
committerGitHub <noreply@github.com>2020-05-06 22:36:43 +0200
commitce14d6db5a7a7896d2fc7713a482cc3b810b9102 (patch)
tree447323cd2ce31fd4fbbfd24c372d9ffa5931fe1a
parent1836e3b42a5b2f37fd79104eedbe8f48a5afdee6 (diff)
parentb17b20cafc87193dace2beb1cdb0e126d944f8e3 (diff)
downloadrust-ce14d6db5a7a7896d2fc7713a482cc3b810b9102.tar.gz
rust-ce14d6db5a7a7896d2fc7713a482cc3b810b9102.zip
Rollup merge of #70908 - estebank:suggest-add, r=nikomatsakis
Provide suggestions for type parameters missing bounds for associated types

When implementing the binary operator traits it is easy to forget to restrict the `Output` associated type. `rustc` now accounts for different cases to lead users in the right direction to add the necessary restrictions. The structured suggestions in the following output are new:

```
error: equality constraints are not yet supported in `where` clauses
  --> $DIR/missing-bounds.rs:37:33
   |
LL | impl<B: Add> Add for E<B> where <B as Add>::Output = B {
   |                                 ^^^^^^^^^^^^^^^^^^^^^^ not supported
   |
   = note: see issue #20041 <https://github.com/rust-lang/rust/issues/20041> for more information
help: if `Output` is an associated type you're trying to set, use the associated type binding syntax
   |
LL | impl<B: Add> Add for E<B> where B: Add<Output = B> {
   |                                 ^^^^^^^^^^^^^^^^^

error[E0308]: mismatched types
  --> $DIR/missing-bounds.rs:11:11
   |
7  | impl<B> Add for A<B> where B: Add {
   |      - this type parameter
...
11 |         A(self.0 + rhs.0)
   |           ^^^^^^^^^^^^^^ expected type parameter `B`, found associated type
   |
   = note: expected type parameter `B`
             found associated type `<B as std::ops::Add>::Output`
help: consider further restricting this bound
   |
7  | impl<B> Add for A<B> where B: Add + std::ops::Add<Output = B> {
   |                                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^

error[E0369]: cannot add `B` to `B`
  --> $DIR/missing-bounds.rs:31:21
   |
31 |         Self(self.0 + rhs.0)
   |              ------ ^ ----- B
   |              |
   |              B
   |
help: consider restricting type parameter `B`
   |
27 | impl<B: std::ops::Add<Output = B>> Add for D<B> {
   |       ^^^^^^^^^^^^^^^^^^^^^^^^^^^
```

That output is given for the following cases:

```rust
struct A<B>(B);
impl<B> Add for A<B> where B: Add {
    type Output = Self;

    fn add(self, rhs: Self) -> Self {
        A(self.0 + rhs.0) //~ ERROR mismatched types
    }
}

struct D<B>(B);
impl<B> Add for D<B> {
    type Output = Self;

    fn add(self, rhs: Self) -> Self {
        Self(self.0 + rhs.0) //~ ERROR cannot add `B` to `B`
    }
}

struct E<B>(B);
impl<B: Add> Add for E<B> where <B as Add>::Output = B {
    type Output = Self;

    fn add(self, rhs: Self) -> Self {
        Self(self.0 + rhs.0)
    }
}
```
-rw-r--r--src/librustc_ast_passes/ast_validation.rs96
-rw-r--r--src/librustc_hir/hir.rs36
-rw-r--r--src/librustc_middle/ty/diagnostics.rs184
-rw-r--r--src/librustc_middle/ty/error.rs43
-rw-r--r--src/librustc_mir/borrow_check/diagnostics/conflict_errors.rs3
-rw-r--r--src/librustc_trait_selection/traits/error_reporting/mod.rs186
-rw-r--r--src/librustc_trait_selection/traits/error_reporting/suggestions.rs4
-rw-r--r--src/librustc_typeck/check/op.rs92
-rw-r--r--src/test/ui/generic-associated-types/construct_with_other_type.stderr9
-rw-r--r--src/test/ui/generic-associated-types/missing-bounds.fixed46
-rw-r--r--src/test/ui/generic-associated-types/missing-bounds.rs46
-rw-r--r--src/test/ui/generic-associated-types/missing-bounds.stderr77
-rw-r--r--src/test/ui/issues/issue-20005.stderr12
-rw-r--r--src/test/ui/issues/issue-24204.stderr4
-rw-r--r--src/test/ui/issues/issue-6738.stderr5
-rw-r--r--src/test/ui/type/type-check/missing_trait_impl.stderr10
16 files changed, 629 insertions, 224 deletions
diff --git a/src/librustc_ast_passes/ast_validation.rs b/src/librustc_ast_passes/ast_validation.rs
index 395fd746085..cc88fbb295c 100644
--- a/src/librustc_ast_passes/ast_validation.rs
+++ b/src/librustc_ast_passes/ast_validation.rs
@@ -23,6 +23,7 @@ use rustc_session::Session;
 use rustc_span::symbol::{kw, sym};
 use rustc_span::Span;
 use std::mem;
+use std::ops::DerefMut;
 
 const MORE_EXTERN: &str =
     "for more information, visit https://doc.rust-lang.org/std/keyword.extern.html";
@@ -1113,17 +1114,7 @@ impl<'a> Visitor<'a> for AstValidator<'a> {
 
         for predicate in &generics.where_clause.predicates {
             if let WherePredicate::EqPredicate(ref predicate) = *predicate {
-                self.err_handler()
-                    .struct_span_err(
-                        predicate.span,
-                        "equality constraints are not yet supported in `where` clauses",
-                    )
-                    .span_label(predicate.span, "not supported")
-                    .note(
-                        "see issue #20041 <https://github.com/rust-lang/rust/issues/20041> \
-                         for more information",
-                    )
-                    .emit();
+                deny_equality_constraints(self, predicate, generics);
             }
         }
 
@@ -1300,6 +1291,89 @@ impl<'a> Visitor<'a> for AstValidator<'a> {
     }
 }
 
+/// When encountering an equality constraint in a `where` clause, emit an error. If the code seems
+/// like it's setting an associated type, provide an appropriate suggestion.
+fn deny_equality_constraints(
+    this: &mut AstValidator<'_>,
+    predicate: &WhereEqPredicate,
+    generics: &Generics,
+) {
+    let mut err = this.err_handler().struct_span_err(
+        predicate.span,
+        "equality constraints are not yet supported in `where` clauses",
+    );
+    err.span_label(predicate.span, "not supported");
+
+    // Given `<A as Foo>::Bar = RhsTy`, suggest `A: Foo<Bar = RhsTy>`.
+    if let TyKind::Path(Some(qself), full_path) = &predicate.lhs_ty.kind {
+        if let TyKind::Path(None, path) = &qself.ty.kind {
+            match &path.segments[..] {
+                [PathSegment { ident, args: None, .. }] => {
+                    for param in &generics.params {
+                        if param.ident == *ident {
+                            let param = ident;
+                            match &full_path.segments[qself.position..] {
+                                [PathSegment { ident, .. }] => {
+                                    // Make a new `Path` from `foo::Bar` to `Foo<Bar = RhsTy>`.
+                                    let mut assoc_path = full_path.clone();
+                                    // Remove `Bar` from `Foo::Bar`.
+                                    assoc_path.segments.pop();
+                                    let len = assoc_path.segments.len() - 1;
+                                    // Build `<Bar = RhsTy>`.
+                                    let arg = AngleBracketedArg::Constraint(AssocTyConstraint {
+                                        id: rustc_ast::node_id::DUMMY_NODE_ID,
+                                        ident: *ident,
+                                        kind: AssocTyConstraintKind::Equality {
+                                            ty: predicate.rhs_ty.clone(),
+                                        },
+                                        span: ident.span,
+                                    });
+                                    // Add `<Bar = RhsTy>` to `Foo`.
+                                    match &mut assoc_path.segments[len].args {
+                                        Some(args) => match args.deref_mut() {
+                                            GenericArgs::Parenthesized(_) => continue,
+                                            GenericArgs::AngleBracketed(args) => {
+                                                args.args.push(arg);
+                                            }
+                                        },
+                                        empty_args => {
+                                            *empty_args = AngleBracketedArgs {
+                                                span: ident.span,
+                                                args: vec![arg],
+                                            }
+                                            .into();
+                                        }
+                                    }
+                                    err.span_suggestion_verbose(
+                                        predicate.span,
+                                        &format!(
+                                            "if `{}` is an associated type you're trying to set, \
+                                            use the associated type binding syntax",
+                                            ident
+                                        ),
+                                        format!(
+                                            "{}: {}",
+                                            param,
+                                            pprust::path_to_string(&assoc_path)
+                                        ),
+                                        Applicability::MaybeIncorrect,
+                                    );
+                                }
+                                _ => {}
+                            };
+                        }
+                    }
+                }
+                _ => {}
+            }
+        }
+    }
+    err.note(
+        "see issue #20041 <https://github.com/rust-lang/rust/issues/20041> for more information",
+    );
+    err.emit();
+}
+
 pub fn check_crate(session: &Session, krate: &Crate, lints: &mut LintBuffer) -> bool {
     let mut validator = AstValidator {
         session,
diff --git a/src/librustc_hir/hir.rs b/src/librustc_hir/hir.rs
index 258428d77da..e6d673b30f7 100644
--- a/src/librustc_hir/hir.rs
+++ b/src/librustc_hir/hir.rs
@@ -2626,8 +2626,42 @@ impl Node<'_> {
         match self {
             Node::TraitItem(TraitItem { generics, .. })
             | Node::ImplItem(ImplItem { generics, .. })
-            | Node::Item(Item { kind: ItemKind::Fn(_, generics, _), .. }) => Some(generics),
+            | Node::Item(Item {
+                kind:
+                    ItemKind::Trait(_, _, generics, ..)
+                    | ItemKind::Impl { generics, .. }
+                    | ItemKind::Fn(_, generics, _),
+                ..
+            }) => Some(generics),
             _ => None,
         }
     }
+
+    pub fn hir_id(&self) -> Option<HirId> {
+        match self {
+            Node::Item(Item { hir_id, .. })
+            | Node::ForeignItem(ForeignItem { hir_id, .. })
+            | Node::TraitItem(TraitItem { hir_id, .. })
+            | Node::ImplItem(ImplItem { hir_id, .. })
+            | Node::Field(StructField { hir_id, .. })
+            | Node::AnonConst(AnonConst { hir_id, .. })
+            | Node::Expr(Expr { hir_id, .. })
+            | Node::Stmt(Stmt { hir_id, .. })
+            | Node::Ty(Ty { hir_id, .. })
+            | Node::Binding(Pat { hir_id, .. })
+            | Node::Pat(Pat { hir_id, .. })
+            | Node::Arm(Arm { hir_id, .. })
+            | Node::Block(Block { hir_id, .. })
+            | Node::Local(Local { hir_id, .. })
+            | Node::MacroDef(MacroDef { hir_id, .. })
+            | Node::Lifetime(Lifetime { hir_id, .. })
+            | Node::Param(Param { hir_id, .. })
+            | Node::GenericParam(GenericParam { hir_id, .. }) => Some(*hir_id),
+            Node::TraitRef(TraitRef { hir_ref_id, .. }) => Some(*hir_ref_id),
+            Node::PathSegment(PathSegment { hir_id, .. }) => *hir_id,
+            Node::Variant(Variant { id, .. }) => Some(*id),
+            Node::Ctor(variant) => variant.ctor_hir_id(),
+            Node::Crate(_) | Node::Visibility(_) => None,
+        }
+    }
 }
diff --git a/src/librustc_middle/ty/diagnostics.rs b/src/librustc_middle/ty/diagnostics.rs
index 790eb8f49af..613d66d59c5 100644
--- a/src/librustc_middle/ty/diagnostics.rs
+++ b/src/librustc_middle/ty/diagnostics.rs
@@ -2,7 +2,12 @@
 
 use crate::ty::sty::InferTy;
 use crate::ty::TyKind::*;
-use crate::ty::TyS;
+use crate::ty::{TyCtxt, TyS};
+use rustc_errors::{Applicability, DiagnosticBuilder};
+use rustc_hir as hir;
+use rustc_hir::def_id::DefId;
+use rustc_hir::{QPath, TyKind, WhereBoundPredicate, WherePredicate};
+use rustc_span::{BytePos, Span};
 
 impl<'tcx> TyS<'tcx> {
     /// Similar to `TyS::is_primitive`, but also considers inferred numeric values to be primitive.
@@ -67,3 +72,180 @@ impl<'tcx> TyS<'tcx> {
         }
     }
 }
+
+/// Suggest restricting a type param with a new bound.
+pub fn suggest_constraining_type_param(
+    tcx: TyCtxt<'_>,
+    generics: &hir::Generics<'_>,
+    err: &mut DiagnosticBuilder<'_>,
+    param_name: &str,
+    constraint: &str,
+    def_id: Option<DefId>,
+) -> bool {
+    let param = generics.params.iter().find(|p| p.name.ident().as_str() == param_name);
+
+    let param = if let Some(param) = param {
+        param
+    } else {
+        return false;
+    };
+
+    const MSG_RESTRICT_BOUND_FURTHER: &str = "consider further restricting this bound";
+    let msg_restrict_type = format!("consider restricting type parameter `{}`", param_name);
+    let msg_restrict_type_further =
+        format!("consider further restricting type parameter `{}`", param_name);
+
+    if def_id == tcx.lang_items().sized_trait() {
+        // Type parameters are already `Sized` by default.
+        err.span_label(param.span, &format!("this type parameter needs to be `{}`", constraint));
+        return true;
+    }
+    let mut suggest_restrict = |span| {
+        err.span_suggestion_verbose(
+            span,
+            MSG_RESTRICT_BOUND_FURTHER,
+            format!(" + {}", constraint),
+            Applicability::MachineApplicable,
+        );
+    };
+
+    if param_name.starts_with("impl ") {
+        // If there's an `impl Trait` used in argument position, suggest
+        // restricting it:
+        //
+        //   fn foo(t: impl Foo) { ... }
+        //             --------
+        //             |
+        //             help: consider further restricting this bound with `+ Bar`
+        //
+        // Suggestion for tools in this case is:
+        //
+        //   fn foo(t: impl Foo) { ... }
+        //             --------
+        //             |
+        //             replace with: `impl Foo + Bar`
+
+        suggest_restrict(param.span.shrink_to_hi());
+        return true;
+    }
+
+    if generics.where_clause.predicates.is_empty()
+        // Given `trait Base<T = String>: Super<T>` where `T: Copy`, suggest restricting in the
+        // `where` clause instead of `trait Base<T: Copy = String>: Super<T>`.
+        && !matches!(param.kind, hir::GenericParamKind::Type { default: Some(_), .. })
+    {
+        if let Some(bounds_span) = param.bounds_span() {
+            // If user has provided some bounds, suggest restricting them:
+            //
+            //   fn foo<T: Foo>(t: T) { ... }
+            //             ---
+            //             |
+            //             help: consider further restricting this bound with `+ Bar`
+            //
+            // Suggestion for tools in this case is:
+            //
+            //   fn foo<T: Foo>(t: T) { ... }
+            //          --
+            //          |
+            //          replace with: `T: Bar +`
+            suggest_restrict(bounds_span.shrink_to_hi());
+        } else {
+            // If user hasn't provided any bounds, suggest adding a new one:
+            //
+            //   fn foo<T>(t: T) { ... }
+            //          - help: consider restricting this type parameter with `T: Foo`
+            err.span_suggestion_verbose(
+                param.span.shrink_to_hi(),
+                &msg_restrict_type,
+                format!(": {}", constraint),
+                Applicability::MachineApplicable,
+            );
+        }
+
+        true
+    } else {
+        // This part is a bit tricky, because using the `where` clause user can
+        // provide zero, one or many bounds for the same type parameter, so we
+        // have following cases to consider:
+        //
+        // 1) When the type parameter has been provided zero bounds
+        //
+        //    Message:
+        //      fn foo<X, Y>(x: X, y: Y) where Y: Foo { ... }
+        //             - help: consider restricting this type parameter with `where X: Bar`
+        //
+        //    Suggestion:
+        //      fn foo<X, Y>(x: X, y: Y) where Y: Foo { ... }
+        //                                           - insert: `, X: Bar`
+        //
+        //
+        // 2) When the type parameter has been provided one bound
+        //
+        //    Message:
+        //      fn foo<T>(t: T) where T: Foo { ... }
+        //                            ^^^^^^
+        //                            |
+        //                            help: consider further restricting this bound with `+ Bar`
+        //
+        //    Suggestion:
+        //      fn foo<T>(t: T) where T: Foo { ... }
+        //                            ^^
+        //                            |
+        //                            replace with: `T: Bar +`
+        //
+        //
+        // 3) When the type parameter has been provided many bounds
+        //
+        //    Message:
+        //      fn foo<T>(t: T) where T: Foo, T: Bar {... }
+        //             - help: consider further restricting this type parameter with `where T: Zar`
+        //
+        //    Suggestion:
+        //      fn foo<T>(t: T) where T: Foo, T: Bar {... }
+        //                                          - insert: `, T: Zar`
+
+        let mut param_spans = Vec::new();
+
+        for predicate in generics.where_clause.predicates {
+            if let WherePredicate::BoundPredicate(WhereBoundPredicate {
+                span, bounded_ty, ..
+            }) = predicate
+            {
+                if let TyKind::Path(QPath::Resolved(_, path)) = &bounded_ty.kind {
+                    if let Some(segment) = path.segments.first() {
+                        if segment.ident.to_string() == param_name {
+                            param_spans.push(span);
+                        }
+                    }
+                }
+            }
+        }
+
+        let where_clause_span = generics.where_clause.span_for_predicates_or_empty_place();
+        // Account for `fn foo<T>(t: T) where T: Foo,` so we don't suggest two trailing commas.
+        let mut trailing_comma = false;
+        if let Ok(snippet) = tcx.sess.source_map().span_to_snippet(where_clause_span) {
+            trailing_comma = snippet.ends_with(',');
+        }
+        let where_clause_span = if trailing_comma {
+            let hi = where_clause_span.hi();
+            Span::new(hi - BytePos(1), hi, where_clause_span.ctxt())
+        } else {
+            where_clause_span.shrink_to_hi()
+        };
+
+        match &param_spans[..] {
+            &[&param_span] => suggest_restrict(param_span.shrink_to_hi()),
+            _ => {
+                err.span_suggestion_verbose(
+                    where_clause_span,
+                    &msg_restrict_type_further,
+                    format!(", {}: {}", param_name, constraint),
+                    Applicability::MachineApplicable,
+                );
+            }
+        }
+
+        true
+    }
+}
diff --git a/src/librustc_middle/ty/error.rs b/src/librustc_middle/ty/error.rs
index 4e1a8b0e92f..f3b6a53dfeb 100644
--- a/src/librustc_middle/ty/error.rs
+++ b/src/librustc_middle/ty/error.rs
@@ -1,4 +1,5 @@
 use crate::traits::{ObligationCause, ObligationCauseCode};
+use crate::ty::diagnostics::suggest_constraining_type_param;
 use crate::ty::{self, BoundRegion, Region, Ty, TyCtxt};
 use rustc_ast::ast;
 use rustc_errors::Applicability::{MachineApplicable, MaybeIncorrect};
@@ -401,8 +402,46 @@ impl<'tcx> TyCtxt<'tcx> {
                     (ty::Projection(_), ty::Projection(_)) => {
                         db.note("an associated type was expected, but a different one was found");
                     }
-                    (ty::Param(_), ty::Projection(_)) | (ty::Projection(_), ty::Param(_)) => {
-                        db.note("you might be missing a type parameter or trait bound");
+                    (ty::Param(p), ty::Projection(proj)) | (ty::Projection(proj), ty::Param(p)) => {
+                        let generics = self.generics_of(body_owner_def_id);
+                        let p_span = self.def_span(generics.type_param(p, self).def_id);
+                        if !sp.contains(p_span) {
+                            db.span_label(p_span, "this type parameter");
+                        }
+                        let hir = self.hir();
+                        let mut note = true;
+                        if let Some(generics) = generics
+                            .type_param(p, self)
+                            .def_id
+                            .as_local()
+                            .map(|id| hir.as_local_hir_id(id))
+                            .and_then(|id| self.hir().find(self.hir().get_parent_node(id)))
+                            .as_ref()
+                            .and_then(|node| node.generics())
+                        {
+                            // Synthesize the associated type restriction `Add<Output = Expected>`.
+                            // FIXME: extract this logic for use in other diagnostics.
+                            let trait_ref = proj.trait_ref(self);
+                            let path =
+                                self.def_path_str_with_substs(trait_ref.def_id, trait_ref.substs);
+                            let item_name = self.item_name(proj.item_def_id);
+                            let path = if path.ends_with('>') {
+                                format!("{}, {} = {}>", &path[..path.len() - 1], item_name, p)
+                            } else {
+                                format!("{}<{} = {}>", path, item_name, p)
+                            };
+                            note = !suggest_constraining_type_param(
+                                self,
+                                generics,
+                                db,
+                                &format!("{}", proj.self_ty()),
+                                &path,
+                                None,
+                            );
+                        }
+                        if note {
+                            db.note("you might be missing a type parameter or trait bound");
+                        }
                     }
                     (ty::Param(p), ty::Dynamic(..) | ty::Opaque(..))
                     | (ty::Dynamic(..) | ty::Opaque(..), ty::Param(p)) => {
diff --git a/src/librustc_mir/borrow_check/diagnostics/conflict_errors.rs b/src/librustc_mir/borrow_check/diagnostics/conflict_errors.rs
index 5bc9f6df889..14a094b9d52 100644
--- a/src/librustc_mir/borrow_check/diagnostics/conflict_errors.rs
+++ b/src/librustc_mir/borrow_check/diagnostics/conflict_errors.rs
@@ -10,10 +10,9 @@ use rustc_middle::mir::{
     FakeReadCause, Local, LocalDecl, LocalInfo, LocalKind, Location, Operand, Place, PlaceRef,
     ProjectionElem, Rvalue, Statement, StatementKind, TerminatorKind, VarBindingForm,
 };
-use rustc_middle::ty::{self, Ty};
+use rustc_middle::ty::{self, suggest_constraining_type_param, Ty};
 use rustc_span::source_map::DesugaringKind;
 use rustc_span::Span;
-use rustc_trait_selection::traits::error_reporting::suggest_constraining_type_param;
 
 use crate::dataflow::drop_flag_effects;
 use crate::dataflow::indexes::{MoveOutIndex, MovePathIndex};
diff --git a/src/librustc_trait_selection/traits/error_reporting/mod.rs b/src/librustc_trait_selection/traits/error_reporting/mod.rs
index fa2af24c945..19ed6b50f92 100644
--- a/src/librustc_trait_selection/traits/error_reporting/mod.rs
+++ b/src/librustc_trait_selection/traits/error_reporting/mod.rs
@@ -15,17 +15,16 @@ use rustc_data_structures::fx::FxHashMap;
 use rustc_errors::{pluralize, struct_span_err, Applicability, DiagnosticBuilder, ErrorReported};
 use rustc_hir as hir;
 use rustc_hir::def_id::{DefId, LOCAL_CRATE};
-use rustc_hir::{Node, QPath, TyKind, WhereBoundPredicate, WherePredicate};
+use rustc_hir::Node;
 use rustc_middle::mir::interpret::ErrorHandled;
 use rustc_middle::ty::error::ExpectedFound;
-use rustc_middle::ty::fast_reject;
 use rustc_middle::ty::fold::TypeFolder;
-use rustc_middle::ty::SubtypePredicate;
 use rustc_middle::ty::{
-    self, AdtKind, ToPolyTraitRef, ToPredicate, Ty, TyCtxt, TypeFoldable, WithConstness,
+    self, fast_reject, AdtKind, SubtypePredicate, ToPolyTraitRef, ToPredicate, Ty, TyCtxt,
+    TypeFoldable, WithConstness,
 };
 use rustc_session::DiagnosticMessageId;
-use rustc_span::{BytePos, ExpnKind, Span, DUMMY_SP};
+use rustc_span::{ExpnKind, Span, DUMMY_SP};
 use std::fmt;
 
 use crate::traits::query::evaluate_obligation::InferCtxtExt as _;
@@ -1700,180 +1699,3 @@ impl ArgKind {
         }
     }
 }
-
-/// Suggest restricting a type param with a new bound.
-pub fn suggest_constraining_type_param(
-    tcx: TyCtxt<'_>,
-    generics: &hir::Generics<'_>,
-    err: &mut DiagnosticBuilder<'_>,
-    param_name: &str,
-    constraint: &str,
-    def_id: Option<DefId>,
-) -> bool {
-    let param = generics.params.iter().find(|p| p.name.ident().as_str() == param_name);
-
-    let param = if let Some(param) = param {
-        param
-    } else {
-        return false;
-    };
-
-    const MSG_RESTRICT_BOUND_FURTHER: &str = "consider further restricting this bound";
-    let msg_restrict_type = format!("consider restricting type parameter `{}`", param_name);
-    let msg_restrict_type_further =
-        format!("consider further restricting type parameter `{}`", param_name);
-
-    if def_id == tcx.lang_items().sized_trait() {
-        // Type parameters are already `Sized` by default.
-        err.span_label(param.span, &format!("this type parameter needs to be `{}`", constraint));
-        return true;
-    }
-    let mut suggest_restrict = |span| {
-        err.span_suggestion_verbose(
-            span,
-            MSG_RESTRICT_BOUND_FURTHER,
-            format!(" + {}", constraint),
-            Applicability::MachineApplicable,
-        );
-    };
-
-    if param_name.starts_with("impl ") {
-        // If there's an `impl Trait` used in argument position, suggest
-        // restricting it:
-        //
-        //   fn foo(t: impl Foo) { ... }
-        //             --------
-        //             |
-        //             help: consider further restricting this bound with `+ Bar`
-        //
-        // Suggestion for tools in this case is:
-        //
-        //   fn foo(t: impl Foo) { ... }
-        //             --------
-        //             |
-        //             replace with: `impl Foo + Bar`
-
-        suggest_restrict(param.span.shrink_to_hi());
-        return true;
-    }
-
-    if generics.where_clause.predicates.is_empty()
-        // Given `trait Base<T = String>: Super<T>` where `T: Copy`, suggest restricting in the
-        // `where` clause instead of `trait Base<T: Copy = String>: Super<T>`.
-        && !matches!(param.kind, hir::GenericParamKind::Type { default: Some(_), .. })
-    {
-        if let Some(bounds_span) = param.bounds_span() {
-            // If user has provided some bounds, suggest restricting them:
-            //
-            //   fn foo<T: Foo>(t: T) { ... }
-            //             ---
-            //             |
-            //             help: consider further restricting this bound with `+ Bar`
-            //
-            // Suggestion for tools in this case is:
-            //
-            //   fn foo<T: Foo>(t: T) { ... }
-            //          --
-            //          |
-            //          replace with: `T: Bar +`
-            suggest_restrict(bounds_span.shrink_to_hi());
-        } else {
-            // If user hasn't provided any bounds, suggest adding a new one:
-            //
-            //   fn foo<T>(t: T) { ... }
-            //          - help: consider restricting this type parameter with `T: Foo`
-            err.span_suggestion_verbose(
-                param.span.shrink_to_hi(),
-                &msg_restrict_type,
-                format!(": {}", constraint),
-                Applicability::MachineApplicable,
-            );
-        }
-
-        true
-    } else {
-        // This part is a bit tricky, because using the `where` clause user can
-        // provide zero, one or many bounds for the same type parameter, so we
-        // have following cases to consider:
-        //
-        // 1) When the type parameter has been provided zero bounds
-        //
-        //    Message:
-        //      fn foo<X, Y>(x: X, y: Y) where Y: Foo { ... }
-        //             - help: consider restricting this type parameter with `where X: Bar`
-        //
-        //    Suggestion:
-        //      fn foo<X, Y>(x: X, y: Y) where Y: Foo { ... }
-        //                                           - insert: `, X: Bar`
-        //
-        //
-        // 2) When the type parameter has been provided one bound
-        //
-        //    Message:
-        //      fn foo<T>(t: T) where T: Foo { ... }
-        //                            ^^^^^^
-        //                            |
-        //                            help: consider further restricting this bound with `+ Bar`
-        //
-        //    Suggestion:
-        //      fn foo<T>(t: T) where T: Foo { ... }
-        //                            ^^
-        //                            |
-        //                            replace with: `T: Bar +`
-        //
-        //
-        // 3) When the type parameter has been provided many bounds
-        //
-        //    Message:
-        //      fn foo<T>(t: T) where T: Foo, T: Bar {... }
-        //             - help: consider further restricting this type parameter with `where T: Zar`
-        //
-        //    Suggestion:
-        //      fn foo<T>(t: T) where T: Foo, T: Bar {... }
-        //                                          - insert: `, T: Zar`
-
-        let mut param_spans = Vec::new();
-
-        for predicate in generics.where_clause.predicates {
-            if let WherePredicate::BoundPredicate(WhereBoundPredicate {
-                span, bounded_ty, ..
-            }) = predicate
-            {
-                if let TyKind::Path(QPath::Resolved(_, path)) = &bounded_ty.kind {
-                    if let Some(segment) = path.segments.first() {
-                        if segment.ident.to_string() == param_name {
-                            param_spans.push(span);
-                        }
-                    }
-                }
-            }
-        }
-
-        let where_clause_span = generics.where_clause.span_for_predicates_or_empty_place();
-        // Account for `fn foo<T>(t: T) where T: Foo,` so we don't suggest two trailing commas.
-        let mut trailing_comma = false;
-        if let Ok(snippet) = tcx.sess.source_map().span_to_snippet(where_clause_span) {
-            trailing_comma = snippet.ends_with(',');
-        }
-        let where_clause_span = if trailing_comma {
-            let hi = where_clause_span.hi();
-            Span::new(hi - BytePos(1), hi, where_clause_span.ctxt())
-        } else {
-            where_clause_span.shrink_to_hi()
-        };
-
-        match &param_spans[..] {
-            &[&param_span] => suggest_restrict(param_span.shrink_to_hi()),
-            _ => {
-                err.span_suggestion_verbose(
-                    where_clause_span,
-                    &msg_restrict_type_further,
-                    format!(", {}: {}", param_name, constraint),
-                    Applicability::MachineApplicable,
-                );
-            }
-        }
-
-        true
-    }
-}
diff --git a/src/librustc_trait_selection/traits/error_reporting/suggestions.rs b/src/librustc_trait_selection/traits/error_reporting/suggestions.rs
index ce7b1390d46..74dd47a91c2 100644
--- a/src/librustc_trait_selection/traits/error_reporting/suggestions.rs
+++ b/src/librustc_trait_selection/traits/error_reporting/suggestions.rs
@@ -3,7 +3,6 @@ use super::{
 };
 
 use crate::infer::InferCtxt;
-use crate::traits::error_reporting::suggest_constraining_type_param;
 
 use rustc_errors::{error_code, struct_span_err, Applicability, DiagnosticBuilder, Style};
 use rustc_hir as hir;
@@ -13,7 +12,8 @@ use rustc_hir::intravisit::Visitor;
 use rustc_hir::{AsyncGeneratorKind, GeneratorKind, Node};
 use rustc_middle::ty::TypeckTables;
 use rustc_middle::ty::{
-    self, AdtKind, DefIdTree, Infer, InferTy, ToPredicate, Ty, TyCtxt, TypeFoldable, WithConstness,
+    self, suggest_constraining_type_param, AdtKind, DefIdTree, Infer, InferTy, ToPredicate, Ty,
+    TyCtxt, TypeFoldable, WithConstness,
 };
 use rustc_span::symbol::{kw, sym, Symbol};
 use rustc_span::{MultiSpan, Span, DUMMY_SP};
diff --git a/src/librustc_typeck/check/op.rs b/src/librustc_typeck/check/op.rs
index 23004cf3647..e6cbc8ab723 100644
--- a/src/librustc_typeck/check/op.rs
+++ b/src/librustc_typeck/check/op.rs
@@ -10,7 +10,7 @@ use rustc_middle::ty::adjustment::{
     Adjust, Adjustment, AllowTwoPhase, AutoBorrow, AutoBorrowMutability,
 };
 use rustc_middle::ty::TyKind::{Adt, Array, Char, FnDef, Never, Ref, Str, Tuple, Uint};
-use rustc_middle::ty::{self, Ty, TypeFoldable};
+use rustc_middle::ty::{self, suggest_constraining_type_param, Ty, TyCtxt, TypeFoldable};
 use rustc_span::Span;
 use rustc_trait_selection::infer::InferCtxtExt;
 
@@ -253,6 +253,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
                 // error types are considered "builtin"
                 if !lhs_ty.references_error() && !rhs_ty.references_error() {
                     let source_map = self.tcx.sess.source_map();
+
                     match is_assign {
                         IsAssign::Yes => {
                             let mut err = struct_span_err!(
@@ -317,12 +318,17 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
                                     // This has nothing here because it means we did string
                                     // concatenation (e.g., "Hello " += "World!"). This means
                                     // we don't want the note in the else clause to be emitted
-                                } else if let ty::Param(_) = lhs_ty.kind {
-                                    // FIXME: point to span of param
-                                    err.note(&format!(
-                                        "`{}` might need a bound for `{}`",
-                                        lhs_ty, missing_trait
-                                    ));
+                                } else if let ty::Param(p) = lhs_ty.kind {
+                                    suggest_constraining_param(
+                                        self.tcx,
+                                        self.body_id,
+                                        &mut err,
+                                        lhs_ty,
+                                        rhs_ty,
+                                        missing_trait,
+                                        p,
+                                        false,
+                                    );
                                 } else if !suggested_deref {
                                     suggest_impl_missing(&mut err, lhs_ty, &missing_trait);
                                 }
@@ -330,46 +336,56 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
                             err.emit();
                         }
                         IsAssign::No => {
-                            let (message, missing_trait) = match op.node {
+                            let (message, missing_trait, use_output) = match op.node {
                                 hir::BinOpKind::Add => (
                                     format!("cannot add `{}` to `{}`", rhs_ty, lhs_ty),
                                     Some("std::ops::Add"),
+                                    true,
                                 ),
                                 hir::BinOpKind::Sub => (
                                     format!("cannot subtract `{}` from `{}`", rhs_ty, lhs_ty),
                                     Some("std::ops::Sub"),
+                                    true,
                                 ),
                                 hir::BinOpKind::Mul => (
                                     format!("cannot multiply `{}` to `{}`", rhs_ty, lhs_ty),
                                     Some("std::ops::Mul"),
+                                    true,
                                 ),
                                 hir::BinOpKind::Div => (
                                     format!("cannot divide `{}` by `{}`", lhs_ty, rhs_ty),
                                     Some("std::ops::Div"),
+                                    true,
                                 ),
                                 hir::BinOpKind::Rem => (
                                     format!("cannot mod `{}` by `{}`", lhs_ty, rhs_ty),
                                     Some("std::ops::Rem"),
+                                    true,
                                 ),
                                 hir::BinOpKind::BitAnd => (
                                     format!("no implementation for `{} & {}`", lhs_ty, rhs_ty),
                                     Some("std::ops::BitAnd"),
+                                    true,
                                 ),
                                 hir::BinOpKind::BitXor => (
                                     format!("no implementation for `{} ^ {}`", lhs_ty, rhs_ty),
                                     Some("std::ops::BitXor"),
+                                    true,
                                 ),
                                 hir::BinOpKind::BitOr => (
                                     format!("no implementation for `{} | {}`", lhs_ty, rhs_ty),
                                     Some("std::ops::BitOr"),
+                                    true,
                                 ),
                                 hir::BinOpKind::Shl => (
                                     format!("no implementation for `{} << {}`", lhs_ty, rhs_ty),
                                     Some("std::ops::Shl"),
+                                    true,
                                 ),
                                 hir::BinOpKind::Shr => (
                                     format!("no implementation for `{} >> {}`", lhs_ty, rhs_ty),
                                     Some("std::ops::Shr"),
+                                    true,
                                 ),
                                 hir::BinOpKind::Eq | hir::BinOpKind::Ne => (
                                     format!(
@@ -378,6 +394,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
                                         lhs_ty
                                     ),
                                     Some("std::cmp::PartialEq"),
+                                    false,
                                 ),
                                 hir::BinOpKind::Lt
                                 | hir::BinOpKind::Le
@@ -389,6 +406,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
                                         lhs_ty
                                     ),
                                     Some("std::cmp::PartialOrd"),
+                                    false,
                                 ),
                                 _ => (
                                     format!(
@@ -397,6 +415,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
                                         lhs_ty
                                     ),
                                     None,
+                                    false,
                                 ),
                             };
                             let mut err = struct_span_err!(
@@ -459,12 +478,17 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
                                     // This has nothing here because it means we did string
                                     // concatenation (e.g., "Hello " + "World!"). This means
                                     // we don't want the note in the else clause to be emitted
-                                } else if let ty::Param(_) = lhs_ty.kind {
-                                    // FIXME: point to span of param
-                                    err.note(&format!(
-                                        "`{}` might need a bound for `{}`",
-                                        lhs_ty, missing_trait
-                                    ));
+                                } else if let ty::Param(p) = lhs_ty.kind {
+                                    suggest_constraining_param(
+                                        self.tcx,
+                                        self.body_id,
+                                        &mut err,
+                                        lhs_ty,
+                                        rhs_ty,
+                                        missing_trait,
+                                        p,
+                                        use_output,
+                                    );
                                 } else if !suggested_deref && !involves_fn {
                                     suggest_impl_missing(&mut err, lhs_ty, &missing_trait);
                                 }
@@ -911,3 +935,43 @@ fn suggest_impl_missing(err: &mut DiagnosticBuilder<'_>, ty: Ty<'_>, missing_tra
         }
     }
 }
+
+fn suggest_constraining_param(
+    tcx: TyCtxt<'_>,
+    body_id: hir::HirId,
+    mut err: &mut DiagnosticBuilder<'_>,
+    lhs_ty: Ty<'_>,
+    rhs_ty: Ty<'_>,
+    missing_trait: &str,
+    p: ty::ParamTy,
+    set_output: bool,
+) {
+    let hir = tcx.hir();
+    let msg = &format!("`{}` might need a bound for `{}`", lhs_ty, missing_trait);
+    // Try to find the def-id and details for the parameter p. We have only the index,
+    // so we have to find the enclosing function's def-id, then look through its declared
+    // generic parameters to get the declaration.
+    let def_id = hir.body_owner_def_id(hir::BodyId { hir_id: body_id });
+    let generics = tcx.generics_of(def_id);
+    let param_def_id = generics.type_param(&p, tcx).def_id;
+    if let Some(generics) = param_def_id
+        .as_local()
+        .map(|id| hir.as_local_hir_id(id))
+        .and_then(|id| hir.find(hir.get_parent_item(id)))
+        .as_ref()
+        .and_then(|node| node.generics())
+    {
+        let output = if set_output { format!("<Output = {}>", rhs_ty) } else { String::new() };
+        suggest_constraining_type_param(
+            tcx,
+            generics,
+            &mut err,
+            &format!("{}", lhs_ty),
+            &format!("{}{}", missing_trait, output),
+            None,
+        );
+    } else {
+        let span = tcx.def_span(param_def_id);
+        err.span_label(span, msg);
+    }
+}
diff --git a/src/test/ui/generic-associated-types/construct_with_other_type.stderr b/src/test/ui/generic-associated-types/construct_with_other_type.stderr
index bad746f7ef1..b9468b3330b 100644
--- a/src/test/ui/generic-associated-types/construct_with_other_type.stderr
+++ b/src/test/ui/generic-associated-types/construct_with_other_type.stderr
@@ -2,11 +2,16 @@ error[E0271]: type mismatch resolving `for<'a> <<T as Baz>::Baa<'a> as std::ops:
   --> $DIR/construct_with_other_type.rs:19:9
    |
 LL | impl<T> Baz for T where T: Foo {
-   |         ^^^ expected type parameter `T`, found associated type
+   |      -  ^^^ expected type parameter `T`, found associated type
+   |      |
+   |      this type parameter
    |
    = note: expected associated type `<T as Foo>::Bar<'_, 'static>`
               found associated type `<<T as Baz>::Quux<'_> as Foo>::Bar<'_, 'static>`
-   = note: you might be missing a type parameter or trait bound
+help: consider further restricting this bound
+   |
+LL | impl<T> Baz for T where T: Foo + Baz<Quux = T> {
+   |                                ^^^^^^^^^^^^^^^
 
 error: aborting due to previous error
 
diff --git a/src/test/ui/generic-associated-types/missing-bounds.fixed b/src/test/ui/generic-associated-types/missing-bounds.fixed
new file mode 100644
index 00000000000..364d2388741
--- /dev/null
+++ b/src/test/ui/generic-associated-types/missing-bounds.fixed
@@ -0,0 +1,46 @@
+// run-rustfix
+
+use std::ops::Add;
+
+struct A<B>(B);
+
+impl<B> Add for A<B> where B: Add + std::ops::Add<Output = B> {
+    type Output = Self;
+
+    fn add(self, rhs: Self) -> Self {
+        A(self.0 + rhs.0) //~ ERROR mismatched types
+    }
+}
+
+struct C<B>(B);
+
+impl<B: Add + std::ops::Add<Output = B>> Add for C<B> {
+    type Output = Self;
+
+    fn add(self, rhs: Self) -> Self {
+        Self(self.0 + rhs.0) //~ ERROR mismatched types
+    }
+}
+
+struct D<B>(B);
+
+impl<B: std::ops::Add<Output = B>> Add for D<B> {
+    type Output = Self;
+
+    fn add(self, rhs: Self) -> Self {
+        Self(self.0 + rhs.0) //~ ERROR cannot add `B` to `B`
+    }
+}
+
+struct E<B>(B);
+
+impl<B: Add> Add for E<B> where B: Add<Output = B>, B: std::ops::Add<Output = B> {
+    //~^ ERROR equality constraints are not yet supported in `where` clauses
+    type Output = Self;
+
+    fn add(self, rhs: Self) -> Self {
+        Self(self.0 + rhs.0) //~ ERROR mismatched types
+    }
+}
+
+fn main() {}
diff --git a/src/test/ui/generic-associated-types/missing-bounds.rs b/src/test/ui/generic-associated-types/missing-bounds.rs
new file mode 100644
index 00000000000..ffafff5e9f5
--- /dev/null
+++ b/src/test/ui/generic-associated-types/missing-bounds.rs
@@ -0,0 +1,46 @@
+// run-rustfix
+
+use std::ops::Add;
+
+struct A<B>(B);
+
+impl<B> Add for A<B> where B: Add {
+    type Output = Self;
+
+    fn add(self, rhs: Self) -> Self {
+        A(self.0 + rhs.0) //~ ERROR mismatched types
+    }
+}
+
+struct C<B>(B);
+
+impl<B: Add> Add for C<B> {
+    type Output = Self;
+
+    fn add(self, rhs: Self) -> Self {
+        Self(self.0 + rhs.0) //~ ERROR mismatched types
+    }
+}
+
+struct D<B>(B);
+
+impl<B> Add for D<B> {
+    type Output = Self;
+
+    fn add(self, rhs: Self) -> Self {
+        Self(self.0 + rhs.0) //~ ERROR cannot add `B` to `B`
+    }
+}
+
+struct E<B>(B);
+
+impl<B: Add> Add for E<B> where <B as Add>::Output = B {
+    //~^ ERROR equality constraints are not yet supported in `where` clauses
+    type Output = Self;
+
+    fn add(self, rhs: Self) -> Self {
+        Self(self.0 + rhs.0) //~ ERROR mismatched types
+    }
+}
+
+fn main() {}
diff --git a/src/test/ui/generic-associated-types/missing-bounds.stderr b/src/test/ui/generic-associated-types/missing-bounds.stderr
new file mode 100644
index 00000000000..50536fdaca9
--- /dev/null
+++ b/src/test/ui/generic-associated-types/missing-bounds.stderr
@@ -0,0 +1,77 @@
+error: equality constraints are not yet supported in `where` clauses
+  --> $DIR/missing-bounds.rs:37:33
+   |
+LL | impl<B: Add> Add for E<B> where <B as Add>::Output = B {
+   |                                 ^^^^^^^^^^^^^^^^^^^^^^ not supported
+   |
+   = note: see issue #20041 <https://github.com/rust-lang/rust/issues/20041> for more information
+help: if `Output` is an associated type you're trying to set, use the associated type binding syntax
+   |
+LL | impl<B: Add> Add for E<B> where B: Add<Output = B> {
+   |                                 ^^^^^^^^^^^^^^^^^^
+
+error[E0308]: mismatched types
+  --> $DIR/missing-bounds.rs:11:11
+   |
+LL | impl<B> Add for A<B> where B: Add {
+   |      - this type parameter
+...
+LL |         A(self.0 + rhs.0)
+   |           ^^^^^^^^^^^^^^ expected type parameter `B`, found associated type
+   |
+   = note: expected type parameter `B`
+             found associated type `<B as std::ops::Add>::Output`
+help: consider further restricting this bound
+   |
+LL | impl<B> Add for A<B> where B: Add + std::ops::Add<Output = B> {
+   |                                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+error[E0308]: mismatched types
+  --> $DIR/missing-bounds.rs:21:14
+   |
+LL | impl<B: Add> Add for C<B> {
+   |      - this type parameter
+...
+LL |         Self(self.0 + rhs.0)
+   |              ^^^^^^^^^^^^^^ expected type parameter `B`, found associated type
+   |
+   = note: expected type parameter `B`
+             found associated type `<B as std::ops::Add>::Output`
+help: consider further restricting this bound
+   |
+LL | impl<B: Add + std::ops::Add<Output = B>> Add for C<B> {
+   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+error[E0369]: cannot add `B` to `B`
+  --> $DIR/missing-bounds.rs:31:21
+   |
+LL |         Self(self.0 + rhs.0)
+   |              ------ ^ ----- B
+   |              |
+   |              B
+   |
+help: consider restricting type parameter `B`
+   |
+LL | impl<B: std::ops::Add<Output = B>> Add for D<B> {
+   |       ^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+error[E0308]: mismatched types
+  --> $DIR/missing-bounds.rs:42:14
+   |
+LL | impl<B: Add> Add for E<B> where <B as Add>::Output = B {
+   |      - this type parameter
+...
+LL |         Self(self.0 + rhs.0)
+   |              ^^^^^^^^^^^^^^ expected type parameter `B`, found associated type
+   |
+   = note: expected type parameter `B`
+             found associated type `<B as std::ops::Add>::Output`
+help: consider further restricting type parameter `B`
+   |
+LL | impl<B: Add> Add for E<B> where <B as Add>::Output = B, B: std::ops::Add<Output = B> {
+   |                                                       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+error: aborting due to 5 previous errors
+
+Some errors have detailed explanations: E0308, E0369.
+For more information about an error, try `rustc --explain E0308`.
diff --git a/src/test/ui/issues/issue-20005.stderr b/src/test/ui/issues/issue-20005.stderr
index 19ccf707619..f53489a99f3 100644
--- a/src/test/ui/issues/issue-20005.stderr
+++ b/src/test/ui/issues/issue-20005.stderr
@@ -5,12 +5,18 @@ LL | trait From<Src> {
    |            --- required by this bound in `From`
 ...
 LL |     ) -> <Dst as From<Self>>::Result where Dst: From<Self> {
-   |                                                 ^^^^^^^^^^- help: consider further restricting `Self`: `, Self: std::marker::Sized`
-   |                                                 |
-   |                                                 doesn't have a size known at compile-time
+   |                                                 ^^^^^^^^^^ doesn't have a size known at compile-time
    |
    = help: the trait `std::marker::Sized` is not implemented for `Self`
    = note: to learn more, visit <https://doc.rust-lang.org/book/ch19-04-advanced-types.html#dynamically-sized-types-and-the-sized-trait>
+help: consider further restricting `Self`
+   |
+LL |     ) -> <Dst as From<Self>>::Result where Dst: From<Self>, Self: std::marker::Sized  {
+   |                                                           ^^^^^^^^^^^^^^^^^^^^^^^^^^
+help: consider relaxing the implicit `Sized` restriction
+   |
+LL | trait From<Src: ?Sized> {
+   |               ^^^^^^^^
 
 error: aborting due to previous error
 
diff --git a/src/test/ui/issues/issue-24204.stderr b/src/test/ui/issues/issue-24204.stderr
index d69efc86005..d5cbcf786bf 100644
--- a/src/test/ui/issues/issue-24204.stderr
+++ b/src/test/ui/issues/issue-24204.stderr
@@ -7,7 +7,9 @@ LL |     type A: MultiDispatch<Self::B, O = Self>;
    |                                    -------- required by this bound in `Trait`
 ...
 LL | fn test<T: Trait<B=i32>>(b: i32) -> T where T::A: MultiDispatch<i32> { T::new(b) }
-   |            ^^^^^^^^^^^^ expected type parameter `T`, found associated type
+   |         -  ^^^^^^^^^^^^ expected type parameter `T`, found associated type
+   |         |
+   |         this type parameter
    |
    = note: expected type parameter `T`
              found associated type `<<T as Trait>::A as MultiDispatch<i32>>::O`
diff --git a/src/test/ui/issues/issue-6738.stderr b/src/test/ui/issues/issue-6738.stderr
index 82b670bd03b..a428ff7e91f 100644
--- a/src/test/ui/issues/issue-6738.stderr
+++ b/src/test/ui/issues/issue-6738.stderr
@@ -6,7 +6,10 @@ LL |         self.x += v.x;
    |         |
    |         cannot use `+=` on type `T`
    |
-   = note: `T` might need a bound for `std::ops::AddAssign`
+help: consider restricting type parameter `T`
+   |
+LL | impl<T: std::ops::AddAssign> Foo<T> {
+   |       ^^^^^^^^^^^^^^^^^^^^^
 
 error: aborting due to previous error
 
diff --git a/src/test/ui/type/type-check/missing_trait_impl.stderr b/src/test/ui/type/type-check/missing_trait_impl.stderr
index 7186d6a542d..30df1261cef 100644
--- a/src/test/ui/type/type-check/missing_trait_impl.stderr
+++ b/src/test/ui/type/type-check/missing_trait_impl.stderr
@@ -6,7 +6,10 @@ LL |     let z = x + y;
    |             |
    |             T
    |
-   = note: `T` might need a bound for `std::ops::Add`
+help: consider restricting type parameter `T`
+   |
+LL | fn foo<T: std::ops::Add<Output = T>>(x: T, y: T) {
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
 error[E0368]: binary assignment operation `+=` cannot be applied to type `T`
   --> $DIR/missing_trait_impl.rs:9:5
@@ -16,7 +19,10 @@ LL |     x += x;
    |     |
    |     cannot use `+=` on type `T`
    |
-   = note: `T` might need a bound for `std::ops::AddAssign`
+help: consider restricting type parameter `T`
+   |
+LL | fn bar<T: std::ops::AddAssign>(x: T) {
+   |         ^^^^^^^^^^^^^^^^^^^^^
 
 error: aborting due to 2 previous errors