about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--src/librustdoc/clean/auto_trait.rs18
-rw-r--r--src/librustdoc/clean/inline.rs11
-rw-r--r--src/librustdoc/clean/mod.rs84
-rw-r--r--src/librustdoc/clean/simplify.rs28
-rw-r--r--src/librustdoc/clean/types.rs8
-rw-r--r--src/librustdoc/clean/utils.rs18
-rw-r--r--src/librustdoc/formats/cache.rs15
-rw-r--r--src/librustdoc/html/format.rs63
-rw-r--r--src/librustdoc/html/render/cache.rs1
-rw-r--r--src/librustdoc/json/conversions.rs32
-rw-r--r--src/test/rustdoc/for-lifetime.rs12
-rw-r--r--src/test/rustdoc/higher-ranked-trait-bounds.rs61
12 files changed, 233 insertions, 118 deletions
diff --git a/src/librustdoc/clean/auto_trait.rs b/src/librustdoc/clean/auto_trait.rs
index a3f63ea1046..ebab3add6c5 100644
--- a/src/librustdoc/clean/auto_trait.rs
+++ b/src/librustdoc/clean/auto_trait.rs
@@ -353,12 +353,7 @@ impl<'a, 'tcx> AutoTraitFinder<'a, 'tcx> {
                     let (poly_trait, output) =
                         (data.0.as_ref().expect("as_ref failed").clone(), data.1.as_ref().cloned());
                     let new_ty = match poly_trait.trait_ {
-                        Type::ResolvedPath {
-                            ref path,
-                            ref param_names,
-                            ref did,
-                            ref is_generic,
-                        } => {
+                        Type::ResolvedPath { ref path, ref did, ref is_generic } => {
                             let mut new_path = path.clone();
                             let last_segment =
                                 new_path.segments.pop().expect("segments were empty");
@@ -395,7 +390,6 @@ impl<'a, 'tcx> AutoTraitFinder<'a, 'tcx> {
 
                             Type::ResolvedPath {
                                 path: new_path,
-                                param_names: param_names.clone(),
                                 did: *did,
                                 is_generic: *is_generic,
                             }
@@ -414,7 +408,11 @@ impl<'a, 'tcx> AutoTraitFinder<'a, 'tcx> {
                 let mut bounds_vec = bounds.into_iter().collect();
                 self.sort_where_bounds(&mut bounds_vec);
 
-                Some(WherePredicate::BoundPredicate { ty, bounds: bounds_vec })
+                Some(WherePredicate::BoundPredicate {
+                    ty,
+                    bounds: bounds_vec,
+                    bound_params: Vec::new(),
+                })
             })
             .chain(
                 lifetime_to_bounds.into_iter().filter(|&(_, ref bounds)| !bounds.is_empty()).map(
@@ -492,7 +490,7 @@ impl<'a, 'tcx> AutoTraitFinder<'a, 'tcx> {
             }
             let p = p.unwrap();
             match p {
-                WherePredicate::BoundPredicate { ty, mut bounds } => {
+                WherePredicate::BoundPredicate { ty, mut bounds, .. } => {
                     // Writing a projection trait bound of the form
                     // <T as Trait>::Name : ?Sized
                     // is illegal, because ?Sized bounds can only
@@ -566,7 +564,6 @@ impl<'a, 'tcx> AutoTraitFinder<'a, 'tcx> {
                             match **trait_ {
                                 Type::ResolvedPath {
                                     path: ref trait_path,
-                                    ref param_names,
                                     ref did,
                                     ref is_generic,
                                 } => {
@@ -613,7 +610,6 @@ impl<'a, 'tcx> AutoTraitFinder<'a, 'tcx> {
                                         PolyTrait {
                                             trait_: Type::ResolvedPath {
                                                 path: new_trait_path,
-                                                param_names: param_names.clone(),
                                                 did: *did,
                                                 is_generic: *is_generic,
                                             },
diff --git a/src/librustdoc/clean/inline.rs b/src/librustdoc/clean/inline.rs
index ab6254dcf10..829b54f1fc1 100644
--- a/src/librustdoc/clean/inline.rs
+++ b/src/librustdoc/clean/inline.rs
@@ -583,9 +583,11 @@ fn build_macro(cx: &mut DocContext<'_>, did: DefId, name: Symbol) -> clean::Item
 fn filter_non_trait_generics(trait_did: DefId, mut g: clean::Generics) -> clean::Generics {
     for pred in &mut g.where_predicates {
         match *pred {
-            clean::WherePredicate::BoundPredicate { ty: clean::Generic(ref s), ref mut bounds }
-                if *s == kw::SelfUpper =>
-            {
+            clean::WherePredicate::BoundPredicate {
+                ty: clean::Generic(ref s),
+                ref mut bounds,
+                ..
+            } if *s == kw::SelfUpper => {
                 bounds.retain(|bound| match *bound {
                     clean::GenericBound::TraitBound(
                         clean::PolyTrait { trait_: clean::ResolvedPath { did, .. }, .. },
@@ -608,6 +610,7 @@ fn filter_non_trait_generics(trait_did: DefId, mut g: clean::Generics) -> clean:
                     ..
                 },
             ref bounds,
+            ..
         } => !(bounds.is_empty() || *s == kw::SelfUpper && did == trait_did),
         _ => true,
     });
@@ -622,7 +625,7 @@ fn separate_supertrait_bounds(
 ) -> (clean::Generics, Vec<clean::GenericBound>) {
     let mut ty_bounds = Vec::new();
     g.where_predicates.retain(|pred| match *pred {
-        clean::WherePredicate::BoundPredicate { ty: clean::Generic(ref s), ref bounds }
+        clean::WherePredicate::BoundPredicate { ty: clean::Generic(ref s), ref bounds, .. }
             if *s == kw::SelfUpper =>
         {
             ty_bounds.extend(bounds.iter().cloned());
diff --git a/src/librustdoc/clean/mod.rs b/src/librustdoc/clean/mod.rs
index bc04480ab7c..fea09b383c0 100644
--- a/src/librustdoc/clean/mod.rs
+++ b/src/librustdoc/clean/mod.rs
@@ -180,7 +180,7 @@ impl Clean<Type> for (ty::TraitRef<'_>, &[TypeBinding]) {
 
         debug!("ty::TraitRef\n  subst: {:?}\n", trait_ref.substs);
 
-        ResolvedPath { path, param_names: None, did: trait_ref.def_id, is_generic: false }
+        ResolvedPath { path, did: trait_ref.def_id, is_generic: false }
     }
 }
 
@@ -330,6 +330,7 @@ impl Clean<WherePredicate> for hir::WherePredicate<'_> {
             hir::WherePredicate::BoundPredicate(ref wbp) => WherePredicate::BoundPredicate {
                 ty: wbp.bounded_ty.clean(cx),
                 bounds: wbp.bounds.clean(cx),
+                bound_params: wbp.bound_generic_params.into_iter().map(|x| x.clean(cx)).collect(),
             },
 
             hir::WherePredicate::RegionPredicate(ref wrp) => WherePredicate::RegionPredicate {
@@ -370,6 +371,7 @@ impl<'a> Clean<WherePredicate> for ty::PolyTraitPredicate<'a> {
         WherePredicate::BoundPredicate {
             ty: poly_trait_ref.skip_binder().self_ty().clean(cx),
             bounds: vec![poly_trait_ref.clean(cx)],
+            bound_params: Vec::new(),
         }
     }
 }
@@ -402,6 +404,7 @@ impl<'tcx> Clean<Option<WherePredicate>> for ty::OutlivesPredicate<Ty<'tcx>, ty:
         Some(WherePredicate::BoundPredicate {
             ty: ty.clean(cx),
             bounds: vec![GenericBound::Outlives(lt.clean(cx).expect("failed to clean lifetimes"))],
+            bound_params: Vec::new(),
         })
     }
 }
@@ -567,7 +570,9 @@ impl Clean<Generics> for hir::Generics<'_> {
         // to where predicates when such cases occur.
         for where_pred in &mut generics.where_predicates {
             match *where_pred {
-                WherePredicate::BoundPredicate { ty: Generic(ref name), ref mut bounds } => {
+                WherePredicate::BoundPredicate {
+                    ty: Generic(ref name), ref mut bounds, ..
+                } => {
                     if bounds.is_empty() {
                         for param in &mut generics.params {
                             match param.kind {
@@ -721,7 +726,7 @@ impl<'a, 'tcx> Clean<Generics> for (&'a ty::Generics, ty::GenericPredicates<'tcx
         // handled in cleaning associated types
         let mut sized_params = FxHashSet::default();
         where_predicates.retain(|pred| match *pred {
-            WP::BoundPredicate { ty: Generic(ref g), ref bounds } => {
+            WP::BoundPredicate { ty: Generic(ref g), ref bounds, .. } => {
                 if bounds.iter().any(|b| b.is_sized_bound(cx)) {
                     sized_params.insert(*g);
                     false
@@ -741,6 +746,7 @@ impl<'a, 'tcx> Clean<Generics> for (&'a ty::Generics, ty::GenericPredicates<'tcx
                 where_predicates.push(WP::BoundPredicate {
                     ty: Type::Generic(tp.name),
                     bounds: vec![GenericBound::maybe_sized(cx)],
+                    bound_params: Vec::new(),
                 })
             }
         }
@@ -1117,6 +1123,7 @@ impl Clean<Item> for ty::AssocItem {
                                 WherePredicate::BoundPredicate {
                                     ty: QPath { ref name, ref self_type, ref trait_, .. },
                                     ref bounds,
+                                    ..
                                 } => (name, self_type, trait_, bounds),
                                 _ => return None,
                             };
@@ -1371,24 +1378,9 @@ impl Clean<Type> for hir::Ty<'_> {
             }
             TyKind::Path(_) => clean_qpath(&self, cx),
             TyKind::TraitObject(ref bounds, ref lifetime, _) => {
-                match bounds[0].clean(cx).trait_ {
-                    ResolvedPath { path, param_names: None, did, is_generic } => {
-                        let mut bounds: Vec<self::GenericBound> = bounds[1..]
-                            .iter()
-                            .map(|bound| {
-                                self::GenericBound::TraitBound(
-                                    bound.clean(cx),
-                                    hir::TraitBoundModifier::None,
-                                )
-                            })
-                            .collect();
-                        if !lifetime.is_elided() {
-                            bounds.push(self::GenericBound::Outlives(lifetime.clean(cx)));
-                        }
-                        ResolvedPath { path, param_names: Some(bounds), did, is_generic }
-                    }
-                    _ => Infer, // shouldn't happen
-                }
+                let bounds = bounds.iter().map(|bound| bound.clean(cx)).collect();
+                let lifetime = if !lifetime.is_elided() { Some(lifetime.clean(cx)) } else { None };
+                DynTrait(bounds, lifetime)
             }
             TyKind::BareFn(ref barefn) => BareFunction(box barefn.clean(cx)),
             TyKind::Infer | TyKind::Err => Infer,
@@ -1471,7 +1463,7 @@ impl<'tcx> Clean<Type> for Ty<'tcx> {
                 };
                 inline::record_extern_fqn(cx, did, kind);
                 let path = external_path(cx, cx.tcx.item_name(did), None, false, vec![], substs);
-                ResolvedPath { path, param_names: None, did, is_generic: false }
+                ResolvedPath { path, did, is_generic: false }
             }
             ty::Foreign(did) => {
                 inline::record_extern_fqn(cx, did, ItemType::ForeignType);
@@ -1483,7 +1475,7 @@ impl<'tcx> Clean<Type> for Ty<'tcx> {
                     vec![],
                     InternalSubsts::empty(),
                 );
-                ResolvedPath { path, param_names: None, did, is_generic: false }
+                ResolvedPath { path, did, is_generic: false }
             }
             ty::Dynamic(ref obj, ref reg) => {
                 // HACK: pick the first `did` as the `did` of the trait object. Someone
@@ -1501,28 +1493,19 @@ impl<'tcx> Clean<Type> for Ty<'tcx> {
 
                 inline::record_extern_fqn(cx, did, ItemType::Trait);
 
-                let mut param_names = vec![];
-                if let Some(b) = reg.clean(cx) {
-                    param_names.push(GenericBound::Outlives(b));
-                }
+                let lifetime = reg.clean(cx);
+                let mut bounds = vec![];
+
                 for did in dids {
                     let empty = cx.tcx.intern_substs(&[]);
                     let path =
                         external_path(cx, cx.tcx.item_name(did), Some(did), false, vec![], empty);
                     inline::record_extern_fqn(cx, did, ItemType::Trait);
-                    let bound = GenericBound::TraitBound(
-                        PolyTrait {
-                            trait_: ResolvedPath {
-                                path,
-                                param_names: None,
-                                did,
-                                is_generic: false,
-                            },
-                            generic_params: Vec::new(),
-                        },
-                        hir::TraitBoundModifier::None,
-                    );
-                    param_names.push(bound);
+                    let bound = PolyTrait {
+                        trait_: ResolvedPath { path, did, is_generic: false },
+                        generic_params: Vec::new(),
+                    };
+                    bounds.push(bound);
                 }
 
                 let mut bindings = vec![];
@@ -1535,7 +1518,15 @@ impl<'tcx> Clean<Type> for Ty<'tcx> {
 
                 let path =
                     external_path(cx, cx.tcx.item_name(did), Some(did), false, bindings, substs);
-                ResolvedPath { path, param_names: Some(param_names), did, is_generic: false }
+                bounds.insert(
+                    0,
+                    PolyTrait {
+                        trait_: ResolvedPath { path, did, is_generic: false },
+                        generic_params: Vec::new(),
+                    },
+                );
+
+                DynTrait(bounds, lifetime)
             }
             ty::Tuple(ref t) => {
                 Tuple(t.iter().map(|t| t.expect_ty()).collect::<Vec<_>>().clean(cx))
@@ -2239,14 +2230,9 @@ impl From<GenericBound> for SimpleBound {
         match bound.clone() {
             GenericBound::Outlives(l) => SimpleBound::Outlives(l),
             GenericBound::TraitBound(t, mod_) => match t.trait_ {
-                Type::ResolvedPath { path, param_names, .. } => SimpleBound::TraitBound(
-                    path.segments,
-                    param_names.map_or_else(Vec::new, |v| {
-                        v.iter().map(|p| SimpleBound::from(p.clone())).collect()
-                    }),
-                    t.generic_params,
-                    mod_,
-                ),
+                Type::ResolvedPath { path, .. } => {
+                    SimpleBound::TraitBound(path.segments, Vec::new(), t.generic_params, mod_)
+                }
                 _ => panic!("Unexpected bound {:?}", bound),
             },
         }
diff --git a/src/librustdoc/clean/simplify.rs b/src/librustdoc/clean/simplify.rs
index d4d0a8ce24c..3ec0a22a2c0 100644
--- a/src/librustdoc/clean/simplify.rs
+++ b/src/librustdoc/clean/simplify.rs
@@ -24,16 +24,20 @@ use crate::core::DocContext;
 
 crate fn where_clauses(cx: &DocContext<'_>, clauses: Vec<WP>) -> Vec<WP> {
     // First, partition the where clause into its separate components
-    let mut params: BTreeMap<_, Vec<_>> = BTreeMap::new();
+    let mut params: BTreeMap<_, (Vec<_>, Vec<_>)> = BTreeMap::new();
     let mut lifetimes = Vec::new();
     let mut equalities = Vec::new();
     let mut tybounds = Vec::new();
 
     for clause in clauses {
         match clause {
-            WP::BoundPredicate { ty, bounds } => match ty {
-                clean::Generic(s) => params.entry(s).or_default().extend(bounds),
-                t => tybounds.push((t, bounds)),
+            WP::BoundPredicate { ty, bounds, bound_params } => match ty {
+                clean::Generic(s) => {
+                    let (b, p) = params.entry(s).or_default();
+                    b.extend(bounds);
+                    p.extend(bound_params);
+                }
+                t => tybounds.push((t, (bounds, bound_params))),
             },
             WP::RegionPredicate { lifetime, bounds } => {
                 lifetimes.push((lifetime, bounds));
@@ -54,7 +58,7 @@ crate fn where_clauses(cx: &DocContext<'_>, clauses: Vec<WP>) -> Vec<WP> {
             clean::Generic(s) => s,
             _ => return true,
         };
-        let bounds = match params.get_mut(generic) {
+        let (bounds, _) = match params.get_mut(generic) {
             Some(bound) => bound,
             None => return true,
         };
@@ -67,10 +71,16 @@ crate fn where_clauses(cx: &DocContext<'_>, clauses: Vec<WP>) -> Vec<WP> {
     clauses.extend(
         lifetimes.into_iter().map(|(lt, bounds)| WP::RegionPredicate { lifetime: lt, bounds }),
     );
-    clauses.extend(
-        params.into_iter().map(|(k, v)| WP::BoundPredicate { ty: clean::Generic(k), bounds: v }),
-    );
-    clauses.extend(tybounds.into_iter().map(|(ty, bounds)| WP::BoundPredicate { ty, bounds }));
+    clauses.extend(params.into_iter().map(|(k, (bounds, params))| WP::BoundPredicate {
+        ty: clean::Generic(k),
+        bounds,
+        bound_params: params,
+    }));
+    clauses.extend(tybounds.into_iter().map(|(ty, (bounds, bound_params))| WP::BoundPredicate {
+        ty,
+        bounds,
+        bound_params,
+    }));
     clauses.extend(equalities.into_iter().map(|(lhs, rhs)| WP::EqPredicate { lhs, rhs }));
     clauses
 }
diff --git a/src/librustdoc/clean/types.rs b/src/librustdoc/clean/types.rs
index 66f62d97b04..4da2f14ce8a 100644
--- a/src/librustdoc/clean/types.rs
+++ b/src/librustdoc/clean/types.rs
@@ -1168,7 +1168,7 @@ impl GenericBound {
         inline::record_extern_fqn(cx, did, ItemType::Trait);
         GenericBound::TraitBound(
             PolyTrait {
-                trait_: ResolvedPath { path, param_names: None, did, is_generic: false },
+                trait_: ResolvedPath { path, did, is_generic: false },
                 generic_params: Vec::new(),
             },
             hir::TraitBoundModifier::Maybe,
@@ -1220,7 +1220,7 @@ impl Lifetime {
 
 #[derive(Clone, Debug)]
 crate enum WherePredicate {
-    BoundPredicate { ty: Type, bounds: Vec<GenericBound> },
+    BoundPredicate { ty: Type, bounds: Vec<GenericBound>, bound_params: Vec<Lifetime> },
     RegionPredicate { lifetime: Lifetime, bounds: Vec<GenericBound> },
     EqPredicate { lhs: Type, rhs: Type },
 }
@@ -1434,11 +1434,12 @@ crate enum Type {
     /// Structs/enums/traits (most that would be an `hir::TyKind::Path`).
     ResolvedPath {
         path: Path,
-        param_names: Option<Vec<GenericBound>>,
         did: DefId,
         /// `true` if is a `T::Name` path for associated types.
         is_generic: bool,
     },
+    /// `dyn for<'a> Trait<'a> + Send + 'static`
+    DynTrait(Vec<PolyTrait>, Option<Lifetime>),
     /// For parameterized types, so the consumer of the JSON don't go
     /// looking for types which don't exist anywhere.
     Generic(Symbol),
@@ -1625,6 +1626,7 @@ impl Type {
     fn inner_def_id(&self, cache: Option<&Cache>) -> Option<DefId> {
         let t: PrimitiveType = match *self {
             ResolvedPath { did, .. } => return Some(did.into()),
+            DynTrait(ref bounds, _) => return bounds[0].trait_.inner_def_id(cache),
             Primitive(p) => return cache.and_then(|c| c.primitive_locations.get(&p).cloned()),
             BorrowedRef { type_: box Generic(..), .. } => PrimitiveType::Reference,
             BorrowedRef { ref type_, .. } => return type_.inner_def_id(cache),
diff --git a/src/librustdoc/clean/utils.rs b/src/librustdoc/clean/utils.rs
index 706a56fbcbf..3d056979780 100644
--- a/src/librustdoc/clean/utils.rs
+++ b/src/librustdoc/clean/utils.rs
@@ -2,7 +2,7 @@ use crate::clean::auto_trait::AutoTraitFinder;
 use crate::clean::blanket_impl::BlanketImplFinder;
 use crate::clean::{
     inline, Clean, Crate, Generic, GenericArg, GenericArgs, ImportSource, Item, ItemKind, Lifetime,
-    Path, PathSegment, Primitive, PrimitiveType, ResolvedPath, Type, TypeBinding,
+    Path, PathSegment, PolyTrait, Primitive, PrimitiveType, ResolvedPath, Type, TypeBinding,
 };
 use crate::core::DocContext;
 use crate::formats::item_type::ItemType;
@@ -163,8 +163,18 @@ pub(super) fn external_path(
 
 crate fn strip_type(ty: Type) -> Type {
     match ty {
-        Type::ResolvedPath { path, param_names, did, is_generic } => {
-            Type::ResolvedPath { path: strip_path(&path), param_names, did, is_generic }
+        Type::ResolvedPath { path, did, is_generic } => {
+            Type::ResolvedPath { path: strip_path(&path), did, is_generic }
+        }
+        Type::DynTrait(mut bounds, lt) => {
+            let first = bounds.remove(0);
+            let stripped_trait = strip_type(first.trait_);
+
+            bounds.insert(
+                0,
+                PolyTrait { trait_: stripped_trait, generic_params: first.generic_params },
+            );
+            Type::DynTrait(bounds, lt)
         }
         Type::Tuple(inner_tys) => {
             Type::Tuple(inner_tys.iter().map(|t| strip_type(t.clone())).collect())
@@ -431,7 +441,7 @@ crate fn resolve_type(cx: &mut DocContext<'_>, path: Path, id: hir::HirId) -> Ty
         _ => false,
     };
     let did = register_res(cx, path.res);
-    ResolvedPath { path, param_names: None, did, is_generic }
+    ResolvedPath { path, did, is_generic }
 }
 
 crate fn get_auto_trait_and_blanket_impls(
diff --git a/src/librustdoc/formats/cache.rs b/src/librustdoc/formats/cache.rs
index c9682bd84f7..1e1fc2436aa 100644
--- a/src/librustdoc/formats/cache.rs
+++ b/src/librustdoc/formats/cache.rs
@@ -402,6 +402,15 @@ impl<'a, 'tcx> DocFolder for CacheBuilder<'a, 'tcx> {
                         self.cache.parent_stack.push(did);
                         true
                     }
+                    clean::DynTrait(ref bounds, _)
+                    | clean::BorrowedRef { type_: box clean::DynTrait(ref bounds, _), .. } => {
+                        if let Some(did) = bounds[0].trait_.def_id() {
+                            self.cache.parent_stack.push(did);
+                            true
+                        } else {
+                            false
+                        }
+                    }
                     ref t => {
                         let prim_did = t
                             .primitive_type()
@@ -432,6 +441,12 @@ impl<'a, 'tcx> DocFolder for CacheBuilder<'a, 'tcx> {
                 | clean::BorrowedRef { type_: box clean::ResolvedPath { did, .. }, .. } => {
                     dids.insert(did);
                 }
+                clean::DynTrait(ref bounds, _)
+                | clean::BorrowedRef { type_: box clean::DynTrait(ref bounds, _), .. } => {
+                    if let Some(did) = bounds[0].trait_.def_id() {
+                        dids.insert(did);
+                    }
+                }
                 ref t => {
                     let did = t
                         .primitive_type()
diff --git a/src/librustdoc/html/format.rs b/src/librustdoc/html/format.rs
index 58c609cf252..1e08aeb379a 100644
--- a/src/librustdoc/html/format.rs
+++ b/src/librustdoc/html/format.rs
@@ -250,17 +250,33 @@ crate fn print_where_clause<'a, 'tcx: 'a>(
             }
 
             match pred {
-                clean::WherePredicate::BoundPredicate { ty, bounds } => {
+                clean::WherePredicate::BoundPredicate { ty, bounds, bound_params } => {
                     let bounds = bounds;
+                    let for_prefix = match bound_params.len() {
+                        0 => String::new(),
+                        _ if f.alternate() => {
+                            format!(
+                                "for<{:#}> ",
+                                comma_sep(bound_params.iter().map(|lt| lt.print()))
+                            )
+                        }
+                        _ => format!(
+                            "for&lt;{}&gt; ",
+                            comma_sep(bound_params.iter().map(|lt| lt.print()))
+                        ),
+                    };
+
                     if f.alternate() {
                         clause.push_str(&format!(
-                            "{:#}: {:#}",
+                            "{}{:#}: {:#}",
+                            for_prefix,
                             ty.print(cx),
                             print_generic_bounds(bounds, cx)
                         ));
                     } else {
                         clause.push_str(&format!(
-                            "{}: {}",
+                            "{}{}: {}",
+                            for_prefix,
                             ty.print(cx),
                             print_generic_bounds(bounds, cx)
                         ));
@@ -631,18 +647,24 @@ fn primitive_link(
 
 /// Helper to render type parameters
 fn tybounds<'a, 'tcx: 'a>(
-    param_names: &'a Option<Vec<clean::GenericBound>>,
+    bounds: &'a Vec<clean::PolyTrait>,
+    lt: &'a Option<clean::Lifetime>,
     cx: &'a Context<'tcx>,
 ) -> impl fmt::Display + 'a + Captures<'tcx> {
-    display_fn(move |f| match *param_names {
-        Some(ref params) => {
-            for param in params {
+    display_fn(move |f| {
+        for (i, bound) in bounds.iter().enumerate() {
+            if i > 0 {
                 write!(f, " + ")?;
-                fmt::Display::fmt(&param.print(cx), f)?;
             }
-            Ok(())
+
+            fmt::Display::fmt(&bound.print(cx), f)?;
         }
-        None => Ok(()),
+
+        if let Some(lt) = lt {
+            write!(f, " + ")?;
+            fmt::Display::fmt(&lt.print(), f)?;
+        }
+        Ok(())
     })
 }
 
@@ -679,13 +701,13 @@ fn fmt_type<'cx>(
 
     match *t {
         clean::Generic(name) => write!(f, "{}", name),
-        clean::ResolvedPath { did, ref param_names, ref path, is_generic } => {
-            if param_names.is_some() {
-                f.write_str("dyn ")?;
-            }
+        clean::ResolvedPath { did, ref path, is_generic } => {
             // Paths like `T::Output` and `Self::Output` should be rendered with all segments.
-            resolved_path(f, did, path, is_generic, use_absolute, cx)?;
-            fmt::Display::fmt(&tybounds(param_names, cx), f)
+            resolved_path(f, did, path, is_generic, use_absolute, cx)
+        }
+        clean::DynTrait(ref bounds, ref lt) => {
+            f.write_str("dyn ")?;
+            fmt::Display::fmt(&tybounds(bounds, lt, cx), f)
         }
         clean::Infer => write!(f, "_"),
         clean::Primitive(prim) => primitive_link(f, prim, &*prim.as_sym().as_str(), cx),
@@ -820,7 +842,9 @@ fn fmt_type<'cx>(
                         }
                     }
                 }
-                clean::ResolvedPath { param_names: Some(ref v), .. } if !v.is_empty() => {
+                clean::DynTrait(ref bounds, ref trait_lt)
+                    if bounds.len() > 1 || trait_lt.is_some() =>
+                {
                     write!(f, "{}{}{}(", amp, lt, m)?;
                     fmt_type(&ty, f, use_absolute, cx)?;
                     write!(f, ")")
@@ -881,7 +905,7 @@ fn fmt_type<'cx>(
                 //        the ugliness comes from inlining across crates where
                 //        everything comes in as a fully resolved QPath (hard to
                 //        look at).
-                box clean::ResolvedPath { did, ref param_names, .. } => {
+                box clean::ResolvedPath { did, .. } => {
                     match href(did.into(), cx) {
                         Some((ref url, _, ref path)) if !f.alternate() => {
                             write!(
@@ -896,9 +920,6 @@ fn fmt_type<'cx>(
                         }
                         _ => write!(f, "{}", name)?,
                     }
-
-                    // FIXME: `param_names` are not rendered, and this seems bad?
-                    drop(param_names);
                     Ok(())
                 }
                 _ => write!(f, "{}", name),
diff --git a/src/librustdoc/html/render/cache.rs b/src/librustdoc/html/render/cache.rs
index e213a9a2949..4372ece5c01 100644
--- a/src/librustdoc/html/render/cache.rs
+++ b/src/librustdoc/html/render/cache.rs
@@ -234,6 +234,7 @@ fn get_index_type_name(clean_type: &clean::Type, accept_generic: bool) -> Option
             });
             Some(path_segment.name)
         }
+        clean::DynTrait(ref bounds, _) => get_index_type_name(&bounds[0].trait_, accept_generic),
         clean::Generic(s) if accept_generic => Some(s),
         clean::Primitive(ref p) => Some(p.as_sym()),
         clean::BorrowedRef { ref type_, .. } => get_index_type_name(type_, accept_generic),
diff --git a/src/librustdoc/json/conversions.rs b/src/librustdoc/json/conversions.rs
index 7086dd8c4d2..b89a266a695 100644
--- a/src/librustdoc/json/conversions.rs
+++ b/src/librustdoc/json/conversions.rs
@@ -328,9 +328,10 @@ impl FromWithTcx<clean::WherePredicate> for WherePredicate {
     fn from_tcx(predicate: clean::WherePredicate, tcx: TyCtxt<'_>) -> Self {
         use clean::WherePredicate::*;
         match predicate {
-            BoundPredicate { ty, bounds } => WherePredicate::BoundPredicate {
+            BoundPredicate { ty, bounds, .. } => WherePredicate::BoundPredicate {
                 ty: ty.into_tcx(tcx),
                 bounds: bounds.into_iter().map(|x| x.into_tcx(tcx)).collect(),
+                // FIXME: add `bound_params` to rustdoc-json-params?
             },
             RegionPredicate { lifetime, bounds } => WherePredicate::RegionPredicate {
                 lifetime: lifetime.0.to_string(),
@@ -372,14 +373,35 @@ impl FromWithTcx<clean::Type> for Type {
     fn from_tcx(ty: clean::Type, tcx: TyCtxt<'_>) -> Self {
         use clean::Type::*;
         match ty {
-            ResolvedPath { path, param_names, did, is_generic: _ } => Type::ResolvedPath {
+            ResolvedPath { path, did, is_generic: _ } => Type::ResolvedPath {
                 name: path.whole_name(),
                 id: from_def_id(did.into()),
                 args: path.segments.last().map(|args| Box::new(args.clone().args.into_tcx(tcx))),
-                param_names: param_names
-                    .map(|v| v.into_iter().map(|x| x.into_tcx(tcx)).collect())
-                    .unwrap_or_default(),
+                param_names: Vec::new(),
             },
+            DynTrait(mut bounds, lt) => {
+                let (path, id) = match bounds.remove(0).trait_ {
+                    ResolvedPath { path, did, .. } => (path, did),
+                    _ => unreachable!(),
+                };
+
+                Type::ResolvedPath {
+                    name: path.whole_name(),
+                    id: from_def_id(id.into()),
+                    args: path
+                        .segments
+                        .last()
+                        .map(|args| Box::new(args.clone().args.into_tcx(tcx))),
+                    param_names: bounds
+                        .into_iter()
+                        .map(|t| {
+                            clean::GenericBound::TraitBound(t, rustc_hir::TraitBoundModifier::None)
+                        })
+                        .chain(lt.into_iter().map(|lt| clean::GenericBound::Outlives(lt)))
+                        .map(|bound| bound.into_tcx(tcx))
+                        .collect(),
+                }
+            }
             Generic(s) => Type::Generic(s.to_string()),
             Primitive(p) => Type::Primitive(p.as_sym().to_string()),
             BareFunction(f) => Type::FunctionPointer(Box::new((*f).into_tcx(tcx))),
diff --git a/src/test/rustdoc/for-lifetime.rs b/src/test/rustdoc/for-lifetime.rs
deleted file mode 100644
index 34a7eae31c7..00000000000
--- a/src/test/rustdoc/for-lifetime.rs
+++ /dev/null
@@ -1,12 +0,0 @@
-#![crate_name = "foo"]
-#![crate_type = "lib"]
-
-pub struct Foo {
-    pub some_func: for<'a> fn(val: &'a i32) -> i32,
-    pub some_trait: dyn for<'a> Trait<'a>,
-}
-
-// @has foo/struct.Foo.html '//span[@id="structfield.some_func"]' "some_func: for<'a> fn(val: &'a i32) -> i32"
-// @has foo/struct.Foo.html '//span[@id="structfield.some_trait"]' "some_trait: dyn Trait<'a>"
-
-pub trait Trait<'a> {}
diff --git a/src/test/rustdoc/higher-ranked-trait-bounds.rs b/src/test/rustdoc/higher-ranked-trait-bounds.rs
new file mode 100644
index 00000000000..41940b0884e
--- /dev/null
+++ b/src/test/rustdoc/higher-ranked-trait-bounds.rs
@@ -0,0 +1,61 @@
+#![crate_name = "foo"]
+
+// @has foo/trait.Trait.html
+pub trait Trait<'x> {}
+
+// @has foo/fn.test1.html
+// @has - '//pre' "pub fn test1<T>() where for<'a> &'a T: Iterator,"
+pub fn test1<T>()
+where
+    for<'a> &'a T: Iterator,
+{
+}
+
+// @has foo/fn.test2.html
+// @has - '//pre' "pub fn test2<T>() where for<'a, 'b> &'a T: Trait<'b>,"
+pub fn test2<T>()
+where
+    for<'a, 'b> &'a T: Trait<'b>,
+{
+}
+
+// @has foo/fn.test3.html
+// @has - '//pre' "pub fn test3<F>() where F: for<'a, 'b> Fn(&'a u8, &'b u8),"
+pub fn test3<F>()
+where
+    F: for<'a, 'b> Fn(&'a u8, &'b u8),
+{
+}
+
+// @has foo/struct.Foo.html
+pub struct Foo<'a> {
+    _x: &'a u8,
+    pub some_trait: &'a dyn for<'b> Trait<'b>,
+    pub some_func: for<'c> fn(val: &'c i32) -> i32,
+}
+
+// @has - '//span[@id="structfield.some_func"]' "some_func: for<'c> fn(val: &'c i32) -> i32"
+// @has - '//span[@id="structfield.some_trait"]' "some_trait: &'a dyn for<'b> Trait<'b>"
+
+impl<'a> Foo<'a> {
+    // @has - '//code' "pub fn bar<T>() where T: Trait<'a>,"
+    pub fn bar<T>()
+    where
+        T: Trait<'a>,
+    {
+    }
+}
+
+// @has foo/trait.B.html
+pub trait B<'x> {}
+
+// @has - '//code[@class="in-band"]' "impl<'a> B<'a> for dyn for<'b> Trait<'b>"
+impl<'a> B<'a> for dyn for<'b> Trait<'b> {}
+
+// @has foo/struct.Bar.html
+// @has - '//span[@id="structfield.bar"]' "bar: &'a (dyn for<'b> Trait<'b> + Unpin)"
+// @has - '//span[@id="structfield.baz"]' "baz: &'a (dyn Unpin + for<'b> Trait<'b>)"
+pub struct Bar<'a> {
+    pub bar: &'a (dyn for<'b> Trait<'b> + Unpin),
+    pub baz: &'a (dyn Unpin + for<'b> Trait<'b>),
+}