about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--src/librustdoc/clean/auto_trait.rs6
-rw-r--r--src/librustdoc/clean/inline.rs2
-rw-r--r--src/librustdoc/clean/mod.rs161
-rw-r--r--src/librustdoc/clean/simplify.rs4
-rw-r--r--src/librustdoc/clean/types.rs20
-rw-r--r--src/librustdoc/fold.rs2
-rw-r--r--src/librustdoc/html/format.rs16
-rw-r--r--src/librustdoc/html/render/mod.rs226
-rw-r--r--src/librustdoc/json/conversions.rs16
-rw-r--r--src/librustdoc/lib.rs1
-rw-r--r--src/librustdoc/passes/check_doc_test_visibility.rs2
-rw-r--r--src/librustdoc/visit.rs2
-rw-r--r--src/rustdoc-json-types/lib.rs5
-rw-r--r--src/test/rustdoc-json/generic-associated-types/gats.rs42
-rw-r--r--src/test/rustdoc/generic-associated-types/gats.rs34
15 files changed, 363 insertions, 176 deletions
diff --git a/src/librustdoc/clean/auto_trait.rs b/src/librustdoc/clean/auto_trait.rs
index a3154d8f03b..62f3527525e 100644
--- a/src/librustdoc/clean/auto_trait.rs
+++ b/src/librustdoc/clean/auto_trait.rs
@@ -546,11 +546,11 @@ impl<'a, 'tcx> AutoTraitFinder<'a, 'tcx> {
                 }
                 WherePredicate::EqPredicate { lhs, rhs } => {
                     match lhs {
-                        Type::QPath { name: left_name, ref self_type, ref trait_, .. } => {
+                        Type::QPath { ref assoc, ref self_type, ref trait_, .. } => {
                             let ty = &*self_type;
                             let mut new_trait = trait_.clone();
 
-                            if self.is_fn_trait(trait_) && left_name == sym::Output {
+                            if self.is_fn_trait(trait_) && assoc.name == sym::Output {
                                 ty_to_fn
                                     .entry(*ty.clone())
                                     .and_modify(|e| {
@@ -571,7 +571,7 @@ impl<'a, 'tcx> AutoTraitFinder<'a, 'tcx> {
                                 // to 'T: Iterator<Item=u8>'
                                 GenericArgs::AngleBracketed { ref mut bindings, .. } => {
                                     bindings.push(TypeBinding {
-                                        name: left_name,
+                                        assoc: *assoc.clone(),
                                         kind: TypeBindingKind::Equality { term: rhs },
                                     });
                                 }
diff --git a/src/librustdoc/clean/inline.rs b/src/librustdoc/clean/inline.rs
index 3ae1df51695..2e66db425d6 100644
--- a/src/librustdoc/clean/inline.rs
+++ b/src/librustdoc/clean/inline.rs
@@ -636,7 +636,7 @@ fn filter_non_trait_generics(trait_did: DefId, mut g: clean::Generics) -> clean:
 
     g.where_predicates.retain(|pred| match pred {
         clean::WherePredicate::BoundPredicate {
-            ty: clean::QPath { self_type: box clean::Generic(ref s), trait_, name: _, .. },
+            ty: clean::QPath { self_type: box clean::Generic(ref s), trait_, .. },
             bounds,
             ..
         } => !(bounds.is_empty() || *s == kw::SelfUpper && trait_.def_id() == trait_did),
diff --git a/src/librustdoc/clean/mod.rs b/src/librustdoc/clean/mod.rs
index 8fad625973f..0d1482ce470 100644
--- a/src/librustdoc/clean/mod.rs
+++ b/src/librustdoc/clean/mod.rs
@@ -388,7 +388,7 @@ impl<'tcx> Clean<Type> for ty::ProjectionTy<'tcx> {
         let trait_ = lifted.trait_ref(cx.tcx).clean(cx);
         let self_type = self.self_ty().clean(cx);
         Type::QPath {
-            name: cx.tcx.associated_item(self.item_def_id).name,
+            assoc: Box::new(projection_to_path_segment(*self, cx)),
             self_def_id: self_type.def_id(&cx.cache),
             self_type: box self_type,
             trait_,
@@ -396,6 +396,27 @@ impl<'tcx> Clean<Type> for ty::ProjectionTy<'tcx> {
     }
 }
 
+fn projection_to_path_segment(ty: ty::ProjectionTy<'_>, cx: &mut DocContext<'_>) -> PathSegment {
+    let item = cx.tcx.associated_item(ty.item_def_id);
+    let generics = cx.tcx.generics_of(ty.item_def_id);
+    PathSegment {
+        name: item.name,
+        args: GenericArgs::AngleBracketed {
+            args: ty.substs[generics.parent_count..]
+                .iter()
+                .map(|ty| match ty.unpack() {
+                    ty::subst::GenericArgKind::Lifetime(lt) => {
+                        GenericArg::Lifetime(lt.clean(cx).unwrap())
+                    }
+                    ty::subst::GenericArgKind::Type(ty) => GenericArg::Type(ty.clean(cx)),
+                    ty::subst::GenericArgKind::Const(c) => GenericArg::Const(Box::new(c.clean(cx))),
+                })
+                .collect(),
+            bindings: Default::default(),
+        },
+    }
+}
+
 impl Clean<GenericParamDef> for ty::GenericParamDef {
     fn clean(&self, cx: &mut DocContext<'_>) -> GenericParamDef {
         let (name, kind) = match self.kind {
@@ -601,8 +622,8 @@ fn clean_ty_generics(
         })
         .collect::<Vec<GenericParamDef>>();
 
-    // param index -> [(DefId of trait, associated type name, type)]
-    let mut impl_trait_proj = FxHashMap::<u32, Vec<(DefId, Symbol, Ty<'_>)>>::default();
+    // param index -> [(DefId of trait, associated type name and generics, type)]
+    let mut impl_trait_proj = FxHashMap::<u32, Vec<(DefId, PathSegment, Ty<'_>)>>::default();
 
     let where_predicates = preds
         .predicates
@@ -648,8 +669,9 @@ fn clean_ty_generics(
 
                     let proj = projection
                         .map(|p| (p.skip_binder().projection_ty.clean(cx), p.skip_binder().term));
-                    if let Some(((_, trait_did, name), rhs)) =
-                        proj.as_ref().and_then(|(lhs, rhs)| Some((lhs.projection()?, rhs)))
+                    if let Some(((_, trait_did, name), rhs)) = proj
+                        .as_ref()
+                        .and_then(|(lhs, rhs): &(Type, _)| Some((lhs.projection()?, rhs)))
                     {
                         // FIXME(...): Remove this unwrap()
                         impl_trait_proj.entry(param_idx).or_default().push((
@@ -992,9 +1014,10 @@ impl Clean<Item> for hir::TraitItem<'_> {
                     TyMethodItem(t)
                 }
                 hir::TraitItemKind::Type(bounds, ref default) => {
+                    let generics = enter_impl_trait(cx, |cx| self.generics.clean(cx));
                     let bounds = bounds.iter().filter_map(|x| x.clean(cx)).collect();
                     let default = default.map(|t| t.clean(cx));
-                    AssocTypeItem(bounds, default)
+                    AssocTypeItem(Box::new(generics), bounds, default)
                 }
             };
             let what_rustc_thinks =
@@ -1026,15 +1049,9 @@ impl Clean<Item> for hir::ImplItem<'_> {
                 }
                 hir::ImplItemKind::TyAlias(ref hir_ty) => {
                     let type_ = hir_ty.clean(cx);
+                    let generics = self.generics.clean(cx);
                     let item_type = hir_ty_to_ty(cx.tcx, hir_ty).clean(cx);
-                    TypedefItem(
-                        Typedef {
-                            type_,
-                            generics: Generics::default(),
-                            item_type: Some(item_type),
-                        },
-                        true,
-                    )
+                    TypedefItem(Typedef { type_, generics, item_type: Some(item_type) }, true)
                 }
             };
 
@@ -1140,35 +1157,79 @@ impl Clean<Item> for ty::AssocItem {
             ty::AssocKind::Type => {
                 let my_name = self.name;
 
+                fn param_eq_arg(param: &GenericParamDef, arg: &GenericArg) -> bool {
+                    match (&param.kind, arg) {
+                        (GenericParamDefKind::Type { .. }, GenericArg::Type(Type::Generic(ty)))
+                            if *ty == param.name =>
+                        {
+                            true
+                        }
+                        (
+                            GenericParamDefKind::Lifetime { .. },
+                            GenericArg::Lifetime(Lifetime(lt)),
+                        ) if *lt == param.name => true,
+                        (GenericParamDefKind::Const { .. }, GenericArg::Const(c)) => {
+                            match &c.kind {
+                                ConstantKind::TyConst { expr } => expr == param.name.as_str(),
+                                _ => false,
+                            }
+                        }
+                        _ => false,
+                    }
+                }
+
                 if let ty::TraitContainer(_) = self.container {
                     let bounds = tcx.explicit_item_bounds(self.def_id);
                     let predicates = ty::GenericPredicates { parent: None, predicates: bounds };
-                    let generics = clean_ty_generics(cx, tcx.generics_of(self.def_id), predicates);
+                    let mut generics =
+                        clean_ty_generics(cx, tcx.generics_of(self.def_id), predicates);
+                    // Filter out the bounds that are (likely?) directly attached to the associated type,
+                    // as opposed to being located in the where clause.
                     let mut bounds = generics
                         .where_predicates
-                        .iter()
-                        .filter_map(|pred| {
-                            let (name, self_type, trait_, bounds) = match *pred {
-                                WherePredicate::BoundPredicate {
-                                    ty: QPath { ref name, ref self_type, ref trait_, .. },
-                                    ref bounds,
-                                    ..
-                                } => (name, self_type, trait_, bounds),
-                                _ => return None,
-                            };
-                            if *name != my_name {
-                                return None;
-                            }
-                            if trait_.def_id() != self.container.id() {
-                                return None;
+                        .drain_filter(|pred| match *pred {
+                            WherePredicate::BoundPredicate {
+                                ty: QPath { ref assoc, ref self_type, ref trait_, .. },
+                                ..
+                            } => {
+                                if assoc.name != my_name {
+                                    return false;
+                                }
+                                if trait_.def_id() != self.container.id() {
+                                    return false;
+                                }
+                                match **self_type {
+                                    Generic(ref s) if *s == kw::SelfUpper => {}
+                                    _ => return false,
+                                }
+                                match &assoc.args {
+                                    GenericArgs::AngleBracketed { args, bindings } => {
+                                        if !bindings.is_empty()
+                                            || generics
+                                                .params
+                                                .iter()
+                                                .zip(args)
+                                                .any(|(param, arg)| !param_eq_arg(param, arg))
+                                        {
+                                            return false;
+                                        }
+                                    }
+                                    GenericArgs::Parenthesized { .. } => {
+                                        // The only time this happens is if we're inside the rustdoc for Fn(),
+                                        // which only has one associated type, which is not a GAT, so whatever.
+                                    }
+                                }
+                                true
                             }
-                            match **self_type {
-                                Generic(ref s) if *s == kw::SelfUpper => {}
-                                _ => return None,
+                            _ => false,
+                        })
+                        .flat_map(|pred| {
+                            if let WherePredicate::BoundPredicate { bounds, .. } = pred {
+                                bounds
+                            } else {
+                                unreachable!()
                             }
-                            Some(bounds)
                         })
-                        .flat_map(|i| i.iter().cloned())
                         .collect::<Vec<_>>();
                     // Our Sized/?Sized bound didn't get handled when creating the generics
                     // because we didn't actually get our whole set of bounds until just now
@@ -1188,7 +1249,7 @@ impl Clean<Item> for ty::AssocItem {
                         None
                     };
 
-                    AssocTypeItem(bounds, ty.map(|t| t.clean(cx)))
+                    AssocTypeItem(Box::new(generics), bounds, ty.map(|t| t.clean(cx)))
                 } else {
                     // FIXME: when could this happen? Associated items in inherent impls?
                     let type_ = tcx.type_of(self.def_id).clean(cx);
@@ -1259,7 +1320,7 @@ fn clean_qpath(hir_ty: &hir::Ty<'_>, cx: &mut DocContext<'_>) -> Type {
             };
             register_res(cx, trait_.res);
             Type::QPath {
-                name: p.segments.last().expect("segments were empty").ident.name,
+                assoc: Box::new(p.segments.last().expect("segments were empty").clean(cx)),
                 self_def_id: Some(DefId::local(qself.hir_id.owner.local_def_index)),
                 self_type: box qself.clean(cx),
                 trait_,
@@ -1276,7 +1337,7 @@ fn clean_qpath(hir_ty: &hir::Ty<'_>, cx: &mut DocContext<'_>) -> Type {
             let trait_ = hir::Path { span, res, segments: &[] }.clean(cx);
             register_res(cx, trait_.res);
             Type::QPath {
-                name: segment.ident.name,
+                assoc: Box::new(segment.clean(cx)),
                 self_def_id: res.opt_def_id(),
                 self_type: box qself.clean(cx),
                 trait_,
@@ -1548,7 +1609,16 @@ impl<'tcx> Clean<Type> for Ty<'tcx> {
                 let mut bindings = vec![];
                 for pb in obj.projection_bounds() {
                     bindings.push(TypeBinding {
-                        name: cx.tcx.associated_item(pb.item_def_id()).name,
+                        assoc: projection_to_path_segment(
+                            pb.skip_binder()
+                                .lift_to_tcx(cx.tcx)
+                                .unwrap()
+                                // HACK(compiler-errors): Doesn't actually matter what self
+                                // type we put here, because we're only using the GAT's substs.
+                                .with_self_ty(cx.tcx, cx.tcx.types.self_param)
+                                .projection_ty,
+                            cx,
+                        ),
                         kind: TypeBindingKind::Equality { term: pb.skip_binder().term.clean(cx) },
                     });
                 }
@@ -1614,10 +1684,10 @@ impl<'tcx> Clean<Type> for Ty<'tcx> {
                                         == trait_ref.skip_binder()
                                     {
                                         Some(TypeBinding {
-                                            name: cx
-                                                .tcx
-                                                .associated_item(proj.projection_ty.item_def_id)
-                                                .name,
+                                            assoc: projection_to_path_segment(
+                                                proj.projection_ty,
+                                                cx,
+                                            ),
                                             kind: TypeBindingKind::Equality {
                                                 term: proj.term.clean(cx),
                                             },
@@ -2160,7 +2230,10 @@ fn clean_maybe_renamed_foreign_item(
 
 impl Clean<TypeBinding> for hir::TypeBinding<'_> {
     fn clean(&self, cx: &mut DocContext<'_>) -> TypeBinding {
-        TypeBinding { name: self.ident.name, kind: self.kind.clean(cx) }
+        TypeBinding {
+            assoc: PathSegment { name: self.ident.name, args: self.gen_args.clean(cx) },
+            kind: self.kind.clean(cx),
+        }
     }
 }
 
diff --git a/src/librustdoc/clean/simplify.rs b/src/librustdoc/clean/simplify.rs
index ea18d915deb..dd8e1132572 100644
--- a/src/librustdoc/clean/simplify.rs
+++ b/src/librustdoc/clean/simplify.rs
@@ -89,7 +89,7 @@ crate fn merge_bounds(
     cx: &clean::DocContext<'_>,
     bounds: &mut Vec<clean::GenericBound>,
     trait_did: DefId,
-    name: Symbol,
+    assoc: clean::PathSegment,
     rhs: &clean::Term,
 ) -> bool {
     !bounds.iter_mut().any(|b| {
@@ -107,7 +107,7 @@ crate fn merge_bounds(
         match last.args {
             PP::AngleBracketed { ref mut bindings, .. } => {
                 bindings.push(clean::TypeBinding {
-                    name,
+                    assoc: assoc.clone(),
                     kind: clean::TypeBindingKind::Equality { term: rhs.clone() },
                 });
             }
diff --git a/src/librustdoc/clean/types.rs b/src/librustdoc/clean/types.rs
index 467a6940628..78928fb4059 100644
--- a/src/librustdoc/clean/types.rs
+++ b/src/librustdoc/clean/types.rs
@@ -683,7 +683,7 @@ crate enum ItemKind {
     ///
     /// The bounds may be non-empty if there is a `where` clause.
     /// The `Option<Type>` is the default concrete type (e.g. `trait Trait { type Target = usize; }`)
-    AssocTypeItem(Vec<GenericBound>, Option<Type>),
+    AssocTypeItem(Box<Generics>, Vec<GenericBound>, Option<Type>),
     /// An item that has been stripped by a rustdoc pass
     StrippedItem(Box<ItemKind>),
     KeywordItem(Symbol),
@@ -721,7 +721,7 @@ impl ItemKind {
             | ProcMacroItem(_)
             | PrimitiveItem(_)
             | AssocConstItem(_, _)
-            | AssocTypeItem(_, _)
+            | AssocTypeItem(..)
             | StrippedItem(_)
             | KeywordItem(_) => [].iter(),
         }
@@ -1397,7 +1397,7 @@ crate enum Type {
 
     /// A qualified path to an associated item: `<Type as Trait>::Name`
     QPath {
-        name: Symbol,
+        assoc: Box<PathSegment>,
         self_type: Box<Type>,
         /// FIXME: This is a hack that should be removed; see [this discussion][1].
         ///
@@ -1415,7 +1415,7 @@ crate enum Type {
 
 // `Type` is used a lot. Make sure it doesn't unintentionally get bigger.
 #[cfg(all(target_arch = "x86_64", target_pointer_width = "64"))]
-rustc_data_structures::static_assert_size!(Type, 72);
+rustc_data_structures::static_assert_size!(Type, 80);
 
 impl Type {
     /// When comparing types for equality, it can help to ignore `&` wrapping.
@@ -1505,12 +1505,12 @@ impl Type {
         self.primitive_type().is_some()
     }
 
-    crate fn projection(&self) -> Option<(&Type, DefId, Symbol)> {
-        let (self_, trait_, name) = match self {
-            QPath { self_type, trait_, name, .. } => (self_type, trait_, name),
+    crate fn projection(&self) -> Option<(&Type, DefId, PathSegment)> {
+        let (self_, trait_, assoc) = match self {
+            QPath { self_type, trait_, assoc, .. } => (self_type, trait_, assoc),
             _ => return None,
         };
-        Some((&self_, trait_.def_id(), *name))
+        Some((&self_, trait_.def_id(), *assoc.clone()))
     }
 
     fn inner_def_id(&self, cache: Option<&Cache>) -> Option<DefId> {
@@ -2018,7 +2018,7 @@ crate enum GenericArg {
 // `GenericArg` can occur many times in a single `Path`, so make sure it
 // doesn't increase in size unexpectedly.
 #[cfg(all(target_arch = "x86_64", target_pointer_width = "64"))]
-rustc_data_structures::static_assert_size!(GenericArg, 80);
+rustc_data_structures::static_assert_size!(GenericArg, 88);
 
 #[derive(Clone, PartialEq, Eq, Debug, Hash)]
 crate enum GenericArgs {
@@ -2256,7 +2256,7 @@ crate struct ProcMacro {
 /// `A: Send + Sync` in `Foo<A: Send + Sync>`).
 #[derive(Clone, PartialEq, Eq, Debug, Hash)]
 crate struct TypeBinding {
-    crate name: Symbol,
+    crate assoc: PathSegment,
     crate kind: TypeBindingKind,
 }
 
diff --git a/src/librustdoc/fold.rs b/src/librustdoc/fold.rs
index cd0f44e5696..fbb8b572ea4 100644
--- a/src/librustdoc/fold.rs
+++ b/src/librustdoc/fold.rs
@@ -86,7 +86,7 @@ crate trait DocFolder: Sized {
             | ProcMacroItem(_)
             | PrimitiveItem(_)
             | AssocConstItem(_, _)
-            | AssocTypeItem(_, _)
+            | AssocTypeItem(..)
             | KeywordItem(_) => kind,
         }
     }
diff --git a/src/librustdoc/html/format.rs b/src/librustdoc/html/format.rs
index c0115bfc6d4..78965712dfa 100644
--- a/src/librustdoc/html/format.rs
+++ b/src/librustdoc/html/format.rs
@@ -961,7 +961,7 @@ fn fmt_type<'cx>(
                 write!(f, "impl {}", print_generic_bounds(bounds, cx))
             }
         }
-        clean::QPath { ref name, ref self_type, ref trait_, ref self_def_id } => {
+        clean::QPath { ref assoc, ref self_type, ref trait_, ref self_def_id } => {
             let should_show_cast = !trait_.segments.is_empty()
                 && self_def_id
                     .zip(Some(trait_.def_id()))
@@ -994,14 +994,15 @@ fn fmt_type<'cx>(
                     write!(
                         f,
                         "<a class=\"associatedtype\" href=\"{url}#{shortty}.{name}\" \
-                                    title=\"type {path}::{name}\">{name}</a>",
+                                    title=\"type {path}::{name}\">{name}</a>{args}",
                         url = url,
                         shortty = ItemType::AssocType,
-                        name = name,
+                        name = assoc.name,
                         path = join_with_double_colon(path),
+                        args = assoc.args.print(cx),
                     )?;
                 }
-                _ => write!(f, "{}", name)?,
+                _ => write!(f, "{}{:#}", assoc.name, assoc.args.print(cx))?,
             }
             Ok(())
         }
@@ -1457,7 +1458,12 @@ impl clean::TypeBinding {
         cx: &'a Context<'tcx>,
     ) -> impl fmt::Display + 'a + Captures<'tcx> {
         display_fn(move |f| {
-            f.write_str(self.name.as_str())?;
+            f.write_str(self.assoc.name.as_str())?;
+            if f.alternate() {
+                write!(f, "{:#}", self.assoc.args.print(cx))?;
+            } else {
+                write!(f, "{}", self.assoc.args.print(cx))?;
+            }
             match self.kind {
                 clean::TypeBindingKind::Equality { ref term } => {
                     if f.alternate() {
diff --git a/src/librustdoc/html/render/mod.rs b/src/librustdoc/html/render/mod.rs
index 3c1737c5e3f..558dbb3b396 100644
--- a/src/librustdoc/html/render/mod.rs
+++ b/src/librustdoc/html/render/mod.rs
@@ -773,27 +773,115 @@ fn assoc_const(
 fn assoc_type(
     w: &mut Buffer,
     it: &clean::Item,
+    generics: &clean::Generics,
     bounds: &[clean::GenericBound],
     default: Option<&clean::Type>,
     link: AssocItemLink<'_>,
-    extra: &str,
+    indent: usize,
     cx: &Context<'_>,
 ) {
     write!(
         w,
-        "{}type <a href=\"{}\" class=\"associatedtype\">{}</a>",
-        extra,
-        naive_assoc_href(it, link, cx),
-        it.name.as_ref().unwrap()
+        "{indent}type <a href=\"{href}\" class=\"associatedtype\">{name}</a>{generics}",
+        indent = " ".repeat(indent),
+        href = naive_assoc_href(it, link, cx),
+        name = it.name.as_ref().unwrap(),
+        generics = generics.print(cx),
     );
     if !bounds.is_empty() {
         write!(w, ": {}", print_generic_bounds(bounds, cx))
     }
+    write!(w, "{}", print_where_clause(generics, cx, indent, false));
     if let Some(default) = default {
         write!(w, " = {}", default.print(cx))
     }
 }
 
+fn assoc_method(
+    w: &mut Buffer,
+    meth: &clean::Item,
+    header: hir::FnHeader,
+    g: &clean::Generics,
+    d: &clean::FnDecl,
+    link: AssocItemLink<'_>,
+    parent: ItemType,
+    cx: &Context<'_>,
+    render_mode: RenderMode,
+) {
+    let name = meth.name.as_ref().unwrap();
+    let href = match link {
+        AssocItemLink::Anchor(Some(ref id)) => Some(format!("#{}", id)),
+        AssocItemLink::Anchor(None) => Some(format!("#{}.{}", meth.type_(), name)),
+        AssocItemLink::GotoSource(did, provided_methods) => {
+            // We're creating a link from an impl-item to the corresponding
+            // trait-item and need to map the anchored type accordingly.
+            let ty =
+                if provided_methods.contains(name) { ItemType::Method } else { ItemType::TyMethod };
+
+            match (href(did.expect_def_id(), cx), ty) {
+                (Ok(p), ty) => Some(format!("{}#{}.{}", p.0, ty, name)),
+                (Err(HrefError::DocumentationNotBuilt), ItemType::TyMethod) => None,
+                (Err(_), ty) => Some(format!("#{}.{}", ty, name)),
+            }
+        }
+    };
+    let vis = meth.visibility.print_with_space(meth.def_id, cx).to_string();
+    // FIXME: Once https://github.com/rust-lang/rust/issues/67792 is implemented, we can remove
+    // this condition.
+    let constness = match render_mode {
+        RenderMode::Normal => {
+            print_constness_with_space(&header.constness, meth.const_stability(cx.tcx()))
+        }
+        RenderMode::ForDeref { .. } => "",
+    };
+    let asyncness = header.asyncness.print_with_space();
+    let unsafety = header.unsafety.print_with_space();
+    let defaultness = print_default_space(meth.is_default());
+    let abi = print_abi_with_space(header.abi).to_string();
+
+    // NOTE: `{:#}` does not print HTML formatting, `{}` does. So `g.print` can't be reused between the length calculation and `write!`.
+    let generics_len = format!("{:#}", g.print(cx)).len();
+    let mut header_len = "fn ".len()
+        + vis.len()
+        + constness.len()
+        + asyncness.len()
+        + unsafety.len()
+        + defaultness.len()
+        + abi.len()
+        + name.as_str().len()
+        + generics_len;
+
+    let (indent, indent_str, end_newline) = if parent == ItemType::Trait {
+        header_len += 4;
+        let indent_str = "    ";
+        render_attributes_in_pre(w, meth, indent_str);
+        (4, indent_str, false)
+    } else {
+        render_attributes_in_code(w, meth);
+        (0, "", true)
+    };
+    w.reserve(header_len + "<a href=\"\" class=\"fnname\">{".len() + "</a>".len());
+    write!(
+        w,
+        "{indent}{vis}{constness}{asyncness}{unsafety}{defaultness}{abi}fn <a {href} class=\"fnname\">{name}</a>\
+         {generics}{decl}{notable_traits}{where_clause}",
+        indent = indent_str,
+        vis = vis,
+        constness = constness,
+        asyncness = asyncness,
+        unsafety = unsafety,
+        defaultness = defaultness,
+        abi = abi,
+        // links without a href are valid - https://www.w3schools.com/tags/att_a_href.asp
+        href = href.map(|href| format!("href=\"{}\"", href)).unwrap_or_else(|| "".to_string()),
+        name = name,
+        generics = g.print(cx),
+        decl = d.full_print(header_len, indent, header.asyncness, cx),
+        notable_traits = notable_traits_decl(d, cx),
+        where_clause = print_where_clause(g, cx, indent, end_newline),
+    )
+}
+
 /// Writes a span containing the versions at which an item became stable and/or const-stable. For
 /// example, if the item became stable at 1.0.0, and const-stable at 1.45.0, this function would
 /// write a span containing "1.0.0 (const: 1.45.0)".
@@ -875,111 +963,25 @@ fn render_assoc_item(
     cx: &Context<'_>,
     render_mode: RenderMode,
 ) {
-    fn method(
-        w: &mut Buffer,
-        meth: &clean::Item,
-        header: hir::FnHeader,
-        g: &clean::Generics,
-        d: &clean::FnDecl,
-        link: AssocItemLink<'_>,
-        parent: ItemType,
-        cx: &Context<'_>,
-        render_mode: RenderMode,
-    ) {
-        let name = meth.name.as_ref().unwrap();
-        let href = match link {
-            AssocItemLink::Anchor(Some(ref id)) => Some(format!("#{}", id)),
-            AssocItemLink::Anchor(None) => Some(format!("#{}.{}", meth.type_(), name)),
-            AssocItemLink::GotoSource(did, provided_methods) => {
-                // We're creating a link from an impl-item to the corresponding
-                // trait-item and need to map the anchored type accordingly.
-                let ty = if provided_methods.contains(name) {
-                    ItemType::Method
-                } else {
-                    ItemType::TyMethod
-                };
-
-                match (href(did.expect_def_id(), cx), ty) {
-                    (Ok(p), ty) => Some(format!("{}#{}.{}", p.0, ty, name)),
-                    (Err(HrefError::DocumentationNotBuilt), ItemType::TyMethod) => None,
-                    (Err(_), ty) => Some(format!("#{}.{}", ty, name)),
-                }
-            }
-        };
-        let vis = meth.visibility.print_with_space(meth.def_id, cx).to_string();
-        // FIXME: Once https://github.com/rust-lang/rust/issues/67792 is implemented, we can remove
-        // this condition.
-        let constness = match render_mode {
-            RenderMode::Normal => {
-                print_constness_with_space(&header.constness, meth.const_stability(cx.tcx()))
-            }
-            RenderMode::ForDeref { .. } => "",
-        };
-        let asyncness = header.asyncness.print_with_space();
-        let unsafety = header.unsafety.print_with_space();
-        let defaultness = print_default_space(meth.is_default());
-        let abi = print_abi_with_space(header.abi).to_string();
-
-        // NOTE: `{:#}` does not print HTML formatting, `{}` does. So `g.print` can't be reused between the length calculation and `write!`.
-        let generics_len = format!("{:#}", g.print(cx)).len();
-        let mut header_len = "fn ".len()
-            + vis.len()
-            + constness.len()
-            + asyncness.len()
-            + unsafety.len()
-            + defaultness.len()
-            + abi.len()
-            + name.as_str().len()
-            + generics_len;
-
-        let (indent, indent_str, end_newline) = if parent == ItemType::Trait {
-            header_len += 4;
-            let indent_str = "    ";
-            render_attributes_in_pre(w, meth, indent_str);
-            (4, indent_str, false)
-        } else {
-            render_attributes_in_code(w, meth);
-            (0, "", true)
-        };
-        w.reserve(header_len + "<a href=\"\" class=\"fnname\">{".len() + "</a>".len());
-        write!(
-            w,
-            "{indent}{vis}{constness}{asyncness}{unsafety}{defaultness}{abi}fn <a {href} class=\"fnname\">{name}</a>\
-             {generics}{decl}{notable_traits}{where_clause}",
-            indent = indent_str,
-            vis = vis,
-            constness = constness,
-            asyncness = asyncness,
-            unsafety = unsafety,
-            defaultness = defaultness,
-            abi = abi,
-            // links without a href are valid - https://www.w3schools.com/tags/att_a_href.asp
-            href = href.map(|href| format!("href=\"{}\"", href)).unwrap_or_else(|| "".to_string()),
-            name = name,
-            generics = g.print(cx),
-            decl = d.full_print(header_len, indent, header.asyncness, cx),
-            notable_traits = notable_traits_decl(d, cx),
-            where_clause = print_where_clause(g, cx, indent, end_newline),
-        )
-    }
     match *item.kind {
         clean::StrippedItem(..) => {}
         clean::TyMethodItem(ref m) => {
-            method(w, item, m.header, &m.generics, &m.decl, link, parent, cx, render_mode)
+            assoc_method(w, item, m.header, &m.generics, &m.decl, link, parent, cx, render_mode)
         }
         clean::MethodItem(ref m, _) => {
-            method(w, item, m.header, &m.generics, &m.decl, link, parent, cx, render_mode)
+            assoc_method(w, item, m.header, &m.generics, &m.decl, link, parent, cx, render_mode)
         }
         clean::AssocConstItem(ref ty, _) => {
             assoc_const(w, item, ty, link, if parent == ItemType::Trait { "    " } else { "" }, cx)
         }
-        clean::AssocTypeItem(ref bounds, ref default) => assoc_type(
+        clean::AssocTypeItem(ref generics, ref bounds, ref default) => assoc_type(
             w,
             item,
+            generics,
             bounds,
             default.as_ref(),
             link,
-            if parent == ItemType::Trait { "    " } else { "" },
+            if parent == ItemType::Trait { 4 } else { 0 },
             cx,
         ),
         _ => panic!("render_assoc_item called on non-associated-item"),
@@ -1283,7 +1285,16 @@ fn notable_traits_decl(decl: &clean::FnDecl, cx: &Context<'_>) -> String {
                                 let empty_set = FxHashSet::default();
                                 let src_link =
                                     AssocItemLink::GotoSource(trait_did.into(), &empty_set);
-                                assoc_type(&mut out, it, &[], Some(&tydef.type_), src_link, "", cx);
+                                assoc_type(
+                                    &mut out,
+                                    it,
+                                    &tydef.generics,
+                                    &[],
+                                    Some(&tydef.type_),
+                                    src_link,
+                                    0,
+                                    cx,
+                                );
                                 out.push_str(";</span>");
                             }
                         }
@@ -1462,10 +1473,11 @@ fn render_impl(
                 assoc_type(
                     w,
                     item,
-                    &Vec::new(),
+                    &tydef.generics,
+                    &[],
                     Some(&tydef.type_),
                     link.anchor(if trait_.is_some() { &source_id } else { &id }),
-                    "",
+                    0,
                     cx,
                 );
                 w.write_str("</h4>");
@@ -1493,7 +1505,7 @@ fn render_impl(
                 w.write_str("</h4>");
                 w.write_str("</section>");
             }
-            clean::AssocTypeItem(ref bounds, ref default) => {
+            clean::AssocTypeItem(ref generics, ref bounds, ref default) => {
                 let source_id = format!("{}.{}", item_type, name);
                 let id = cx.derive_id(source_id.clone());
                 write!(w, "<section id=\"{}\" class=\"{}{}\">", id, item_type, in_trait_class,);
@@ -1502,10 +1514,11 @@ fn render_impl(
                 assoc_type(
                     w,
                     item,
+                    generics,
                     bounds,
                     default.as_ref(),
                     link.anchor(if trait_.is_some() { &source_id } else { &id }),
-                    "",
+                    0,
                     cx,
                 );
                 w.write_str("</h4>");
@@ -1726,7 +1739,16 @@ pub(crate) fn render_impl_summary(
             for it in &i.inner_impl().items {
                 if let clean::TypedefItem(ref tydef, _) = *it.kind {
                     w.write_str("<span class=\"where fmt-newline\">  ");
-                    assoc_type(w, it, &[], Some(&tydef.type_), AssocItemLink::Anchor(None), "", cx);
+                    assoc_type(
+                        w,
+                        it,
+                        &tydef.generics,
+                        &[],
+                        Some(&tydef.type_),
+                        AssocItemLink::Anchor(None),
+                        0,
+                        cx,
+                    );
                     w.write_str(";</span>");
                 }
             }
diff --git a/src/librustdoc/json/conversions.rs b/src/librustdoc/json/conversions.rs
index d4aedb41ddb..4358dc8980f 100644
--- a/src/librustdoc/json/conversions.rs
+++ b/src/librustdoc/json/conversions.rs
@@ -154,7 +154,11 @@ impl FromWithTcx<clean::Constant> for Constant {
 
 impl FromWithTcx<clean::TypeBinding> for TypeBinding {
     fn from_tcx(binding: clean::TypeBinding, tcx: TyCtxt<'_>) -> Self {
-        TypeBinding { name: binding.name.to_string(), binding: binding.kind.into_tcx(tcx) }
+        TypeBinding {
+            name: binding.assoc.name.to_string(),
+            args: binding.assoc.args.into_tcx(tcx),
+            binding: binding.kind.into_tcx(tcx),
+        }
     }
 }
 
@@ -222,8 +226,9 @@ fn from_clean_item(item: clean::Item, tcx: TyCtxt<'_>) -> ItemEnum {
         AssocConstItem(ty, default) => {
             ItemEnum::AssocConst { type_: ty.into_tcx(tcx), default: default.map(|c| c.expr(tcx)) }
         }
-        AssocTypeItem(g, t) => ItemEnum::AssocType {
-            bounds: g.into_iter().map(|x| x.into_tcx(tcx)).collect(),
+        AssocTypeItem(g, b, t) => ItemEnum::AssocType {
+            generics: (*g).into_tcx(tcx),
+            bounds: b.into_iter().map(|x| x.into_tcx(tcx)).collect(),
             default: t.map(|x| x.into_tcx(tcx)),
         },
         // `convert_item` early returns `None` for striped items
@@ -444,11 +449,12 @@ impl FromWithTcx<clean::Type> for Type {
                 mutable: mutability == ast::Mutability::Mut,
                 type_: Box::new((*type_).into_tcx(tcx)),
             },
-            QPath { name, self_type, trait_, .. } => {
+            QPath { assoc, self_type, trait_, .. } => {
                 // FIXME: should `trait_` be a clean::Path equivalent in JSON?
                 let trait_ = clean::Type::Path { path: trait_ }.into_tcx(tcx);
                 Type::QualifiedPath {
-                    name: name.to_string(),
+                    name: assoc.name.to_string(),
+                    args: Box::new(assoc.args.clone().into_tcx(tcx)),
                     self_type: Box::new((*self_type).into_tcx(tcx)),
                     trait_: Box::new(trait_),
                 }
diff --git a/src/librustdoc/lib.rs b/src/librustdoc/lib.rs
index 4d88a4da45f..f378915172a 100644
--- a/src/librustdoc/lib.rs
+++ b/src/librustdoc/lib.rs
@@ -9,6 +9,7 @@
 #![feature(box_patterns)]
 #![feature(control_flow_enum)]
 #![feature(box_syntax)]
+#![feature(drain_filter)]
 #![feature(let_else)]
 #![feature(nll)]
 #![feature(test)]
diff --git a/src/librustdoc/passes/check_doc_test_visibility.rs b/src/librustdoc/passes/check_doc_test_visibility.rs
index e8f8ff988c1..6cffb52bb87 100644
--- a/src/librustdoc/passes/check_doc_test_visibility.rs
+++ b/src/librustdoc/passes/check_doc_test_visibility.rs
@@ -62,7 +62,7 @@ crate fn should_have_doc_example(cx: &DocContext<'_>, item: &clean::Item) -> boo
             clean::StructFieldItem(_)
                 | clean::VariantItem(_)
                 | clean::AssocConstItem(_, _)
-                | clean::AssocTypeItem(_, _)
+                | clean::AssocTypeItem(..)
                 | clean::TypedefItem(_, _)
                 | clean::StaticItem(_)
                 | clean::ConstantItem(_)
diff --git a/src/librustdoc/visit.rs b/src/librustdoc/visit.rs
index df4d1558ebd..b16cab1c646 100644
--- a/src/librustdoc/visit.rs
+++ b/src/librustdoc/visit.rs
@@ -41,7 +41,7 @@ crate trait DocVisitor: Sized {
             | ProcMacroItem(_)
             | PrimitiveItem(_)
             | AssocConstItem(_, _)
-            | AssocTypeItem(_, _)
+            | AssocTypeItem(..)
             | KeywordItem(_) => {}
         }
     }
diff --git a/src/rustdoc-json-types/lib.rs b/src/rustdoc-json-types/lib.rs
index be9bbc7391d..40b0de44829 100644
--- a/src/rustdoc-json-types/lib.rs
+++ b/src/rustdoc-json-types/lib.rs
@@ -9,7 +9,7 @@ use std::path::PathBuf;
 use serde::{Deserialize, Serialize};
 
 /// rustdoc format-version.
-pub const FORMAT_VERSION: u32 = 11;
+pub const FORMAT_VERSION: u32 = 12;
 
 /// A `Crate` is the root of the emitted JSON blob. It contains all type/documentation information
 /// about the language items in the local crate, as well as info about external items to allow
@@ -145,6 +145,7 @@ pub struct Constant {
 #[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
 pub struct TypeBinding {
     pub name: String,
+    pub args: GenericArgs,
     pub binding: TypeBindingKind,
 }
 
@@ -233,6 +234,7 @@ pub enum ItemEnum {
         default: Option<String>,
     },
     AssocType {
+        generics: Generics,
         bounds: Vec<GenericBound>,
         /// e.g. `type X = usize;`
         default: Option<Type>,
@@ -432,6 +434,7 @@ pub enum Type {
     /// `<Type as Trait>::Name` or associated types like `T::Item` where `T: Iterator`
     QualifiedPath {
         name: String,
+        args: Box<GenericArgs>,
         self_type: Box<Type>,
         #[serde(rename = "trait")]
         trait_: Box<Type>,
diff --git a/src/test/rustdoc-json/generic-associated-types/gats.rs b/src/test/rustdoc-json/generic-associated-types/gats.rs
new file mode 100644
index 00000000000..7adcd712ba6
--- /dev/null
+++ b/src/test/rustdoc-json/generic-associated-types/gats.rs
@@ -0,0 +1,42 @@
+// ignore-tidy-linelength
+
+#![no_core]
+#![feature(generic_associated_types, lang_items, no_core)]
+
+#[lang = "sized"]
+pub trait Sized {}
+
+pub trait Display {}
+
+// @has gats.json
+pub trait LendingIterator {
+    // @count - "$.index[*][?(@.name=='LendingItem')].inner.generics.params[*]" 1
+    // @is - "$.index[*][?(@.name=='LendingItem')].inner.generics.params[*].name" \"\'a\"
+    // @count - "$.index[*][?(@.name=='LendingItem')].inner.generics.where_predicates[*]" 1
+    // @is - "$.index[*][?(@.name=='LendingItem')].inner.generics.where_predicates[*].bound_predicate.ty.inner" \"Self\"
+    // @is - "$.index[*][?(@.name=='LendingItem')].inner.generics.where_predicates[*].bound_predicate.bounds[*].outlives" \"\'a\"
+    // @count - "$.index[*][?(@.name=='LendingItem')].inner.bounds[*]" 1
+    type LendingItem<'a>: Display where Self: 'a;
+
+    // @is - "$.index[*][?(@.name=='lending_next')].inner.decl.output.kind" \"qualified_path\"
+    // @count - "$.index[*][?(@.name=='lending_next')].inner.decl.output.inner.args.angle_bracketed.args[*]" 1
+    // @count - "$.index[*][?(@.name=='lending_next')].inner.decl.output.inner.args.angle_bracketed.bindings[*]" 0
+    // @is - "$.index[*][?(@.name=='lending_next')].inner.decl.output.inner.self_type.inner" \"Self\"
+    // @is - "$.index[*][?(@.name=='lending_next')].inner.decl.output.inner.name" \"LendingItem\"
+    fn lending_next<'a>(&'a self) -> Self::LendingItem<'a>;
+}
+
+// @has gats.json
+pub trait Iterator {
+    // @count - "$.index[*][?(@.name=='Item')].inner.generics.params[*]" 0
+    // @count - "$.index[*][?(@.name=='Item')].inner.generics.where_predicates[*]" 0
+    // @count - "$.index[*][?(@.name=='Item')].inner.bounds[*]" 1
+    type Item: Display;
+
+    // @is - "$.index[*][?(@.name=='next')].inner.decl.output.kind" \"qualified_path\"
+    // @count - "$.index[*][?(@.name=='next')].inner.decl.output.inner.args.angle_bracketed.args[*]" 0
+    // @count - "$.index[*][?(@.name=='next')].inner.decl.output.inner.args.angle_bracketed.bindings[*]" 0
+    // @is - "$.index[*][?(@.name=='next')].inner.decl.output.inner.self_type.inner" \"Self\"
+    // @is - "$.index[*][?(@.name=='next')].inner.decl.output.inner.name" \"Item\"
+    fn next<'a>(&'a self) -> Self::Item;
+}
diff --git a/src/test/rustdoc/generic-associated-types/gats.rs b/src/test/rustdoc/generic-associated-types/gats.rs
new file mode 100644
index 00000000000..ae981b9499a
--- /dev/null
+++ b/src/test/rustdoc/generic-associated-types/gats.rs
@@ -0,0 +1,34 @@
+#![crate_name = "foo"]
+#![feature(generic_associated_types)]
+
+// @has foo/trait.LendingIterator.html
+pub trait LendingIterator {
+    // @has - '//*[@id="associatedtype.Item"]//h4[@class="code-header"]' "type Item<'a> where Self: 'a"
+    type Item<'a> where Self: 'a;
+
+    // @has - '//*[@id="tymethod.next"]//h4[@class="code-header"]' \
+    //      "fn next<'a>(&'a self) -> Self::Item<'a>"
+    // @has - '//*[@id="tymethod.next"]//h4[@class="code-header"]//a[@href="trait.LendingIterator.html#associatedtype.Item"]' \
+    //      "Item"
+    fn next<'a>(&'a self) -> Self::Item<'a>;
+}
+
+// @has foo/trait.LendingIterator.html
+// @has - '//*[@id="associatedtype.Item-1"]//h4[@class="code-header"]' "type Item<'a> = ()"
+impl LendingIterator for () {
+    type Item<'a> = ();
+
+    fn next<'a>(&self) -> () {}
+}
+
+pub struct Infinite<T>(T);
+
+// @has foo/trait.LendingIterator.html
+// @has - '//*[@id="associatedtype.Item-2"]//h4[@class="code-header"]' "type Item<'a> where Self: 'a = &'a T"
+impl<T> LendingIterator for Infinite<T> {
+    type Item<'a> where Self: 'a = &'a T;
+
+    fn next<'a>(&'a self) -> Self::Item<'a> {
+        &self.0
+    }
+}