about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--compiler/rustc_error_messages/locales/en-US/lint.ftl2
-rw-r--r--compiler/rustc_lint/src/lib.rs6
-rw-r--r--compiler/rustc_lint/src/opaque_hidden_inferred_bound.rs125
-rw-r--r--compiler/rustc_lint/src/rpit_hidden_inferred_bound.rs143
-rw-r--r--src/test/ui/impl-trait/nested-return-type2-tait.rs1
-rw-r--r--src/test/ui/impl-trait/nested-return-type2-tait.stderr17
-rw-r--r--src/test/ui/impl-trait/nested-return-type2.rs1
-rw-r--r--src/test/ui/impl-trait/nested-return-type2.stderr17
-rw-r--r--src/test/ui/impl-trait/nested-return-type3-tait.rs1
-rw-r--r--src/test/ui/impl-trait/nested-return-type3-tait.stderr17
-rw-r--r--src/test/ui/impl-trait/nested-return-type3-tait2.rs1
-rw-r--r--src/test/ui/impl-trait/nested-return-type3-tait2.stderr17
-rw-r--r--src/test/ui/impl-trait/nested-return-type3-tait3.rs1
-rw-r--r--src/test/ui/impl-trait/nested-return-type3-tait3.stderr17
-rw-r--r--src/test/ui/impl-trait/nested-return-type3.rs1
-rw-r--r--src/test/ui/impl-trait/nested-return-type3.stderr17
16 files changed, 237 insertions, 147 deletions
diff --git a/compiler/rustc_error_messages/locales/en-US/lint.ftl b/compiler/rustc_error_messages/locales/en-US/lint.ftl
index 31c9a845e7e..0fd9b0ead16 100644
--- a/compiler/rustc_error_messages/locales/en-US/lint.ftl
+++ b/compiler/rustc_error_messages/locales/en-US/lint.ftl
@@ -434,6 +434,6 @@ lint_check_name_warning = {$msg}
 
 lint_check_name_deprecated = lint name `{$lint_name}` is deprecated and does not have an effect anymore. Use: {$new_name}
 
-lint_rpit_hidden_inferred_bound = return-position `{$ty}` does not satisfy its associated type bounds
+lint_opaque_hidden_inferred_bound = opaque type `{$ty}` does not satisfy its associated type bounds
     .specifically = this associated type bound is unsatisfied for `{$proj_ty}`
     .suggestion = add this bound
diff --git a/compiler/rustc_lint/src/lib.rs b/compiler/rustc_lint/src/lib.rs
index fd7ea5a4bb5..9148c42195f 100644
--- a/compiler/rustc_lint/src/lib.rs
+++ b/compiler/rustc_lint/src/lib.rs
@@ -62,10 +62,10 @@ mod non_ascii_idents;
 mod non_fmt_panic;
 mod nonstandard_style;
 mod noop_method_call;
+mod opaque_hidden_inferred_bound;
 mod pass_by_value;
 mod passes;
 mod redundant_semicolon;
-mod rpit_hidden_inferred_bound;
 mod traits;
 mod types;
 mod unused;
@@ -94,9 +94,9 @@ use non_ascii_idents::*;
 use non_fmt_panic::NonPanicFmt;
 use nonstandard_style::*;
 use noop_method_call::*;
+use opaque_hidden_inferred_bound::*;
 use pass_by_value::*;
 use redundant_semicolon::*;
-use rpit_hidden_inferred_bound::*;
 use traits::*;
 use types::*;
 use unused::*;
@@ -225,7 +225,7 @@ macro_rules! late_lint_mod_passes {
                 EnumIntrinsicsNonEnums: EnumIntrinsicsNonEnums,
                 InvalidAtomicOrdering: InvalidAtomicOrdering,
                 NamedAsmLabels: NamedAsmLabels,
-                RpitHiddenInferredBound: RpitHiddenInferredBound,
+                OpaqueHiddenInferredBound: OpaqueHiddenInferredBound,
             ]
         );
     };
diff --git a/compiler/rustc_lint/src/opaque_hidden_inferred_bound.rs b/compiler/rustc_lint/src/opaque_hidden_inferred_bound.rs
new file mode 100644
index 00000000000..7f18bf0018f
--- /dev/null
+++ b/compiler/rustc_lint/src/opaque_hidden_inferred_bound.rs
@@ -0,0 +1,125 @@
+use rustc_hir as hir;
+use rustc_infer::infer::TyCtxtInferExt;
+use rustc_macros::LintDiagnostic;
+use rustc_middle::ty::{self, fold::BottomUpFolder, Ty, TypeFoldable};
+use rustc_span::Span;
+use rustc_trait_selection::traits;
+use rustc_trait_selection::traits::query::evaluate_obligation::InferCtxtExt;
+
+use crate::{LateContext, LateLintPass, LintContext};
+
+declare_lint! {
+    /// The `opaque_hidden_inferred_bound` lint detects cases in which nested
+    /// `impl Trait` in associated type bounds are not written generally enough
+    /// to satisfy the bounds of the associated type. This functionality was
+    /// removed in #97346, but then rolled back in #99860 because it was made
+    /// into a hard error too quickly.
+    ///
+    /// We plan on reintroducing this as a hard error, but in the mean time, this
+    /// lint serves to warn and suggest fixes for any use-cases which rely on this
+    /// behavior.
+    pub OPAQUE_HIDDEN_INFERRED_BOUND,
+    Warn,
+    "detects the use of nested `impl Trait` types in associated type bounds that are not general enough"
+}
+
+declare_lint_pass!(OpaqueHiddenInferredBound => [OPAQUE_HIDDEN_INFERRED_BOUND]);
+
+impl<'tcx> LateLintPass<'tcx> for OpaqueHiddenInferredBound {
+    fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::Item<'tcx>) {
+        let hir::ItemKind::OpaqueTy(_) = &item.kind else { return; };
+        let def_id = item.def_id.def_id.to_def_id();
+        cx.tcx.infer_ctxt().enter(|ref infcx| {
+            // For every projection predicate in the opaque type's explicit bounds,
+            // check that the type that we're assigning actually satisfies the bounds
+            // of the associated type.
+            for &(pred, pred_span) in cx.tcx.explicit_item_bounds(def_id) {
+                // Liberate bound regions in the predicate since we
+                // don't actually care about lifetimes in this check.
+                let predicate = cx.tcx.liberate_late_bound_regions(
+                    def_id,
+                    pred.kind(),
+                );
+                let ty::PredicateKind::Projection(proj) = predicate else {
+                    continue;
+                };
+                // Only check types, since those are the only things that may
+                // have opaques in them anyways.
+                let Some(proj_term) = proj.term.ty() else { continue };
+
+                let proj_ty =
+                    cx
+                    .tcx
+                    .mk_projection(proj.projection_ty.item_def_id, proj.projection_ty.substs);
+                // For every instance of the projection type in the bounds,
+                // replace them with the term we're assigning to the associated
+                // type in our opaque type.
+                let proj_replacer = &mut BottomUpFolder {
+                    tcx: cx.tcx,
+                    ty_op: |ty| if ty == proj_ty { proj_term } else { ty },
+                    lt_op: |lt| lt,
+                    ct_op: |ct| ct,
+                };
+                // For example, in `impl Trait<Assoc = impl Send>`, for all of the bounds on `Assoc`,
+                // e.g. `type Assoc: OtherTrait`, replace `<impl Trait as Trait>::Assoc: OtherTrait`
+                // with `impl Send: OtherTrait`.
+                for assoc_pred_and_span in cx
+                    .tcx
+                    .bound_explicit_item_bounds(proj.projection_ty.item_def_id)
+                    .transpose_iter()
+                {
+                    let assoc_pred_span = assoc_pred_and_span.0.1;
+                    let assoc_pred = assoc_pred_and_span
+                        .map_bound(|(pred, _)| *pred)
+                        .subst(cx.tcx, &proj.projection_ty.substs)
+                        .fold_with(proj_replacer);
+                    let Ok(assoc_pred) = traits::fully_normalize(infcx, traits::ObligationCause::dummy(), cx.param_env, assoc_pred) else {
+                        continue;
+                    };
+                    // If that predicate doesn't hold modulo regions (but passed during type-check),
+                    // then we must've taken advantage of the hack in `project_and_unify_types` where
+                    // we replace opaques with inference vars. Emit a warning!
+                    if !infcx.predicate_must_hold_modulo_regions(&traits::Obligation::new(
+                        traits::ObligationCause::dummy(),
+                        cx.param_env,
+                        assoc_pred,
+                    )) {
+                        // If it's a trait bound and an opaque that doesn't satisfy it,
+                        // then we can emit a suggestion to add the bound.
+                        let (suggestion, suggest_span) =
+                            match (proj_term.kind(), assoc_pred.kind().skip_binder()) {
+                                (ty::Opaque(def_id, _), ty::PredicateKind::Trait(trait_pred)) => (
+                                    format!(" + {}", trait_pred.print_modifiers_and_trait_path()),
+                                    Some(cx.tcx.def_span(def_id).shrink_to_hi()),
+                                ),
+                                _ => (String::new(), None),
+                            };
+                        cx.emit_spanned_lint(
+                            OPAQUE_HIDDEN_INFERRED_BOUND,
+                            pred_span,
+                            OpaqueHiddenInferredBoundLint {
+                                ty: cx.tcx.mk_opaque(def_id, ty::InternalSubsts::identity_for_item(cx.tcx, def_id)),
+                                proj_ty: proj_term,
+                                assoc_pred_span,
+                                suggestion,
+                                suggest_span,
+                            },
+                        );
+                    }
+                }
+            }
+        });
+    }
+}
+
+#[derive(LintDiagnostic)]
+#[diag(lint::opaque_hidden_inferred_bound)]
+struct OpaqueHiddenInferredBoundLint<'tcx> {
+    ty: Ty<'tcx>,
+    proj_ty: Ty<'tcx>,
+    #[label(lint::specifically)]
+    assoc_pred_span: Span,
+    #[suggestion_verbose(applicability = "machine-applicable", code = "{suggestion}")]
+    suggest_span: Option<Span>,
+    suggestion: String,
+}
diff --git a/compiler/rustc_lint/src/rpit_hidden_inferred_bound.rs b/compiler/rustc_lint/src/rpit_hidden_inferred_bound.rs
deleted file mode 100644
index fd9872c80a9..00000000000
--- a/compiler/rustc_lint/src/rpit_hidden_inferred_bound.rs
+++ /dev/null
@@ -1,143 +0,0 @@
-use hir::def_id::LocalDefId;
-use rustc_hir as hir;
-use rustc_infer::infer::{InferCtxt, TyCtxtInferExt};
-use rustc_macros::LintDiagnostic;
-use rustc_middle::ty::{
-    self, fold::BottomUpFolder, Ty, TypeFoldable, TypeSuperVisitable, TypeVisitable, TypeVisitor,
-};
-use rustc_span::Span;
-use rustc_trait_selection::traits;
-use rustc_trait_selection::traits::query::evaluate_obligation::InferCtxtExt;
-
-use crate::{LateContext, LateLintPass, LintContext};
-
-declare_lint! {
-    /// The `rpit_hidden_inferred_bound` lint detects cases in which nested RPITs
-    /// in associated type bounds are not written generally enough to satisfy the
-    /// bounds of the associated type. This functionality was removed in #97346,
-    /// but then rolled back in #99860 because it was made into a hard error too
-    /// quickly.
-    ///
-    /// We plan on reintroducing this as a hard error, but in the mean time, this
-    /// lint serves to warn and suggest fixes for any use-cases which rely on this
-    /// behavior.
-    pub RPIT_HIDDEN_INFERRED_BOUND,
-    Warn,
-    "detects the use of nested RPITs in associated type bounds that are not general enough"
-}
-
-declare_lint_pass!(RpitHiddenInferredBound => [RPIT_HIDDEN_INFERRED_BOUND]);
-
-impl<'tcx> LateLintPass<'tcx> for RpitHiddenInferredBound {
-    fn check_fn(
-        &mut self,
-        cx: &LateContext<'tcx>,
-        kind: hir::intravisit::FnKind<'tcx>,
-        _: &'tcx hir::FnDecl<'tcx>,
-        _: &'tcx hir::Body<'tcx>,
-        _: rustc_span::Span,
-        id: hir::HirId,
-    ) {
-        if matches!(kind, hir::intravisit::FnKind::Closure) {
-            return;
-        }
-
-        let fn_def_id = cx.tcx.hir().local_def_id(id);
-        let sig: ty::FnSig<'tcx> =
-            cx.tcx.liberate_late_bound_regions(fn_def_id.to_def_id(), cx.tcx.fn_sig(fn_def_id));
-        cx.tcx.infer_ctxt().enter(|ref infcx| {
-            sig.output().visit_with(&mut VisitOpaqueBounds { infcx, cx, fn_def_id });
-        });
-    }
-}
-
-struct VisitOpaqueBounds<'a, 'cx, 'tcx> {
-    infcx: &'a InferCtxt<'a, 'tcx>,
-    cx: &'cx LateContext<'tcx>,
-    fn_def_id: LocalDefId,
-}
-
-impl<'tcx> TypeVisitor<'tcx> for VisitOpaqueBounds<'_, '_, 'tcx> {
-    fn visit_ty(&mut self, ty: Ty<'tcx>) -> std::ops::ControlFlow<Self::BreakTy> {
-        if let ty::Opaque(def_id, substs) = *ty.kind()
-            && let Some(hir::Node::Item(item)) = self.cx.tcx.hir().get_if_local(def_id)
-            && let hir::ItemKind::OpaqueTy(opaque) = &item.kind
-            && let hir::OpaqueTyOrigin::FnReturn(origin_def_id) = opaque.origin
-            && origin_def_id == self.fn_def_id
-        {
-            for pred_and_span in self.cx.tcx.bound_explicit_item_bounds(def_id).transpose_iter() {
-                let pred_span = pred_and_span.0.1;
-                let predicate = self.cx.tcx.liberate_late_bound_regions(
-                    def_id,
-                    pred_and_span.map_bound(|(pred, _)| *pred).subst(self.cx.tcx, substs).kind(),
-                );
-                let ty::PredicateKind::Projection(proj) = predicate else {
-                    continue;
-                };
-                let Some(proj_term) = proj.term.ty() else { continue };
-
-                let proj_ty = self
-                    .cx
-                    .tcx
-                    .mk_projection(proj.projection_ty.item_def_id, proj.projection_ty.substs);
-                let proj_replacer = &mut BottomUpFolder {
-                    tcx: self.cx.tcx,
-                    ty_op: |ty| if ty == proj_ty { proj_term } else { ty },
-                    lt_op: |lt| lt,
-                    ct_op: |ct| ct,
-                };
-                for assoc_pred_and_span in self
-                    .cx
-                    .tcx
-                    .bound_explicit_item_bounds(proj.projection_ty.item_def_id)
-                    .transpose_iter()
-                {
-                    let assoc_pred_span = assoc_pred_and_span.0.1;
-                    let assoc_pred = assoc_pred_and_span
-                        .map_bound(|(pred, _)| *pred)
-                        .subst(self.cx.tcx, &proj.projection_ty.substs)
-                        .fold_with(proj_replacer);
-                    if !self.infcx.predicate_must_hold_modulo_regions(&traits::Obligation::new(
-                        traits::ObligationCause::dummy(),
-                        self.cx.param_env,
-                        assoc_pred,
-                    )) {
-                        let (suggestion, suggest_span) =
-                            match (proj_term.kind(), assoc_pred.kind().skip_binder()) {
-                                (ty::Opaque(def_id, _), ty::PredicateKind::Trait(trait_pred)) => (
-                                    format!(" + {}", trait_pred.print_modifiers_and_trait_path()),
-                                    Some(self.cx.tcx.def_span(def_id).shrink_to_hi()),
-                                ),
-                                _ => (String::new(), None),
-                            };
-                        self.cx.emit_spanned_lint(
-                            RPIT_HIDDEN_INFERRED_BOUND,
-                            pred_span,
-                            RpitHiddenInferredBoundLint {
-                                ty,
-                                proj_ty: proj_term,
-                                assoc_pred_span,
-                                suggestion,
-                                suggest_span,
-                            },
-                        );
-                    }
-                }
-            }
-        }
-
-        ty.super_visit_with(self)
-    }
-}
-
-#[derive(LintDiagnostic)]
-#[diag(lint::rpit_hidden_inferred_bound)]
-struct RpitHiddenInferredBoundLint<'tcx> {
-    ty: Ty<'tcx>,
-    proj_ty: Ty<'tcx>,
-    #[label(lint::specifically)]
-    assoc_pred_span: Span,
-    #[suggestion_verbose(applicability = "machine-applicable", code = "{suggestion}")]
-    suggest_span: Option<Span>,
-    suggestion: String,
-}
diff --git a/src/test/ui/impl-trait/nested-return-type2-tait.rs b/src/test/ui/impl-trait/nested-return-type2-tait.rs
index 42613d5ccd9..089018a1cdf 100644
--- a/src/test/ui/impl-trait/nested-return-type2-tait.rs
+++ b/src/test/ui/impl-trait/nested-return-type2-tait.rs
@@ -26,6 +26,7 @@ type Sendable = impl Send;
 // var to make it uphold the `: Duh` bound on `Trait::Assoc`. The opaque
 // type does not implement `Duh`, but if its hidden type does.
 fn foo() -> impl Trait<Assoc = Sendable> {
+    //~^ WARN opaque type `impl Trait<Assoc = Sendable>` does not satisfy its associated type bounds
     || 42
 }
 
diff --git a/src/test/ui/impl-trait/nested-return-type2-tait.stderr b/src/test/ui/impl-trait/nested-return-type2-tait.stderr
new file mode 100644
index 00000000000..a8eb69cfcb7
--- /dev/null
+++ b/src/test/ui/impl-trait/nested-return-type2-tait.stderr
@@ -0,0 +1,17 @@
+warning: opaque type `impl Trait<Assoc = Sendable>` does not satisfy its associated type bounds
+  --> $DIR/nested-return-type2-tait.rs:28:24
+   |
+LL |     type Assoc: Duh;
+   |                 --- this associated type bound is unsatisfied for `Sendable`
+...
+LL | fn foo() -> impl Trait<Assoc = Sendable> {
+   |                        ^^^^^^^^^^^^^^^^
+   |
+   = note: `#[warn(opaque_hidden_inferred_bound)]` on by default
+help: add this bound
+   |
+LL | type Sendable = impl Send + Duh;
+   |                           +++++
+
+warning: 1 warning emitted
+
diff --git a/src/test/ui/impl-trait/nested-return-type2.rs b/src/test/ui/impl-trait/nested-return-type2.rs
index 39928d543e1..cc1f1f4ec44 100644
--- a/src/test/ui/impl-trait/nested-return-type2.rs
+++ b/src/test/ui/impl-trait/nested-return-type2.rs
@@ -23,6 +23,7 @@ impl<R: Duh, F: FnMut() -> R> Trait for F {
 // Lazy TAIT would error out, but we inserted a hack to make it work again,
 // keeping backwards compatibility.
 fn foo() -> impl Trait<Assoc = impl Send> {
+    //~^ WARN opaque type `impl Trait<Assoc = impl Send>` does not satisfy its associated type bounds
     || 42
 }
 
diff --git a/src/test/ui/impl-trait/nested-return-type2.stderr b/src/test/ui/impl-trait/nested-return-type2.stderr
new file mode 100644
index 00000000000..3aed05ca132
--- /dev/null
+++ b/src/test/ui/impl-trait/nested-return-type2.stderr
@@ -0,0 +1,17 @@
+warning: opaque type `impl Trait<Assoc = impl Send>` does not satisfy its associated type bounds
+  --> $DIR/nested-return-type2.rs:25:24
+   |
+LL |     type Assoc: Duh;
+   |                 --- this associated type bound is unsatisfied for `impl Send`
+...
+LL | fn foo() -> impl Trait<Assoc = impl Send> {
+   |                        ^^^^^^^^^^^^^^^^^
+   |
+   = note: `#[warn(opaque_hidden_inferred_bound)]` on by default
+help: add this bound
+   |
+LL | fn foo() -> impl Trait<Assoc = impl Send + Duh> {
+   |                                          +++++
+
+warning: 1 warning emitted
+
diff --git a/src/test/ui/impl-trait/nested-return-type3-tait.rs b/src/test/ui/impl-trait/nested-return-type3-tait.rs
index 3936f4dbbb4..3a97e35b4c4 100644
--- a/src/test/ui/impl-trait/nested-return-type3-tait.rs
+++ b/src/test/ui/impl-trait/nested-return-type3-tait.rs
@@ -17,6 +17,7 @@ impl<F: Duh> Trait for F {
 type Sendable = impl Send;
 
 fn foo() -> impl Trait<Assoc = Sendable> {
+    //~^ WARN opaque type `impl Trait<Assoc = Sendable>` does not satisfy its associated type bounds
     42
 }
 
diff --git a/src/test/ui/impl-trait/nested-return-type3-tait.stderr b/src/test/ui/impl-trait/nested-return-type3-tait.stderr
new file mode 100644
index 00000000000..5f58c8dca4a
--- /dev/null
+++ b/src/test/ui/impl-trait/nested-return-type3-tait.stderr
@@ -0,0 +1,17 @@
+warning: opaque type `impl Trait<Assoc = Sendable>` does not satisfy its associated type bounds
+  --> $DIR/nested-return-type3-tait.rs:19:24
+   |
+LL |     type Assoc: Duh;
+   |                 --- this associated type bound is unsatisfied for `Sendable`
+...
+LL | fn foo() -> impl Trait<Assoc = Sendable> {
+   |                        ^^^^^^^^^^^^^^^^
+   |
+   = note: `#[warn(opaque_hidden_inferred_bound)]` on by default
+help: add this bound
+   |
+LL | type Sendable = impl Send + Duh;
+   |                           +++++
+
+warning: 1 warning emitted
+
diff --git a/src/test/ui/impl-trait/nested-return-type3-tait2.rs b/src/test/ui/impl-trait/nested-return-type3-tait2.rs
index 56778ed90dc..5b6f78a9896 100644
--- a/src/test/ui/impl-trait/nested-return-type3-tait2.rs
+++ b/src/test/ui/impl-trait/nested-return-type3-tait2.rs
@@ -16,6 +16,7 @@ impl<F: Duh> Trait for F {
 
 type Sendable = impl Send;
 type Traitable = impl Trait<Assoc = Sendable>;
+//~^ WARN opaque type `Traitable` does not satisfy its associated type bounds
 
 fn foo() -> Traitable {
     42
diff --git a/src/test/ui/impl-trait/nested-return-type3-tait2.stderr b/src/test/ui/impl-trait/nested-return-type3-tait2.stderr
new file mode 100644
index 00000000000..c07f6ead750
--- /dev/null
+++ b/src/test/ui/impl-trait/nested-return-type3-tait2.stderr
@@ -0,0 +1,17 @@
+warning: opaque type `Traitable` does not satisfy its associated type bounds
+  --> $DIR/nested-return-type3-tait2.rs:18:29
+   |
+LL |     type Assoc: Duh;
+   |                 --- this associated type bound is unsatisfied for `Sendable`
+...
+LL | type Traitable = impl Trait<Assoc = Sendable>;
+   |                             ^^^^^^^^^^^^^^^^
+   |
+   = note: `#[warn(opaque_hidden_inferred_bound)]` on by default
+help: add this bound
+   |
+LL | type Sendable = impl Send + Duh;
+   |                           +++++
+
+warning: 1 warning emitted
+
diff --git a/src/test/ui/impl-trait/nested-return-type3-tait3.rs b/src/test/ui/impl-trait/nested-return-type3-tait3.rs
index 04c6c92b1a3..394d8f58110 100644
--- a/src/test/ui/impl-trait/nested-return-type3-tait3.rs
+++ b/src/test/ui/impl-trait/nested-return-type3-tait3.rs
@@ -15,6 +15,7 @@ impl<F: Duh> Trait for F {
 }
 
 type Traitable = impl Trait<Assoc = impl Send>;
+//~^ WARN opaque type `Traitable` does not satisfy its associated type bounds
 
 fn foo() -> Traitable {
     42
diff --git a/src/test/ui/impl-trait/nested-return-type3-tait3.stderr b/src/test/ui/impl-trait/nested-return-type3-tait3.stderr
new file mode 100644
index 00000000000..d98ad89222f
--- /dev/null
+++ b/src/test/ui/impl-trait/nested-return-type3-tait3.stderr
@@ -0,0 +1,17 @@
+warning: opaque type `Traitable` does not satisfy its associated type bounds
+  --> $DIR/nested-return-type3-tait3.rs:17:29
+   |
+LL |     type Assoc: Duh;
+   |                 --- this associated type bound is unsatisfied for `impl Send`
+...
+LL | type Traitable = impl Trait<Assoc = impl Send>;
+   |                             ^^^^^^^^^^^^^^^^^
+   |
+   = note: `#[warn(opaque_hidden_inferred_bound)]` on by default
+help: add this bound
+   |
+LL | type Traitable = impl Trait<Assoc = impl Send + Duh>;
+   |                                               +++++
+
+warning: 1 warning emitted
+
diff --git a/src/test/ui/impl-trait/nested-return-type3.rs b/src/test/ui/impl-trait/nested-return-type3.rs
index 74b4dae22eb..5a764fc4c28 100644
--- a/src/test/ui/impl-trait/nested-return-type3.rs
+++ b/src/test/ui/impl-trait/nested-return-type3.rs
@@ -13,6 +13,7 @@ impl<F: Duh> Trait for F {
 }
 
 fn foo() -> impl Trait<Assoc = impl Send> {
+    //~^ WARN opaque type `impl Trait<Assoc = impl Send>` does not satisfy its associated type bounds
     42
 }
 
diff --git a/src/test/ui/impl-trait/nested-return-type3.stderr b/src/test/ui/impl-trait/nested-return-type3.stderr
new file mode 100644
index 00000000000..632de71aa4c
--- /dev/null
+++ b/src/test/ui/impl-trait/nested-return-type3.stderr
@@ -0,0 +1,17 @@
+warning: opaque type `impl Trait<Assoc = impl Send>` does not satisfy its associated type bounds
+  --> $DIR/nested-return-type3.rs:15:24
+   |
+LL |     type Assoc: Duh;
+   |                 --- this associated type bound is unsatisfied for `impl Send`
+...
+LL | fn foo() -> impl Trait<Assoc = impl Send> {
+   |                        ^^^^^^^^^^^^^^^^^
+   |
+   = note: `#[warn(opaque_hidden_inferred_bound)]` on by default
+help: add this bound
+   |
+LL | fn foo() -> impl Trait<Assoc = impl Send + Duh> {
+   |                                          +++++
+
+warning: 1 warning emitted
+