about summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
authorbors <bors@rust-lang.org>2023-06-10 18:28:14 +0000
committerbors <bors@rust-lang.org>2023-06-10 18:28:14 +0000
commit7820972f866ca2cea281ccc39201c0cd27087110 (patch)
treedf3ecf5581de552cb863e001ca6ee552dcea2e2a /src
parentef8ee73fc4ff70a426e71f900441f040db252dae (diff)
parent5b5d84fd6a36a8ea4f39e3b730262321cc8f8622 (diff)
Auto merge of #107637 - fmease:rustdoc-reelide-x-crate-def-tr-obj-lt-bnds, r=notriddle,cgillot,GuillaumeGomez
rustdoc: re-elide cross-crate default trait-object lifetime bounds

Hide trait-object lifetime bounds (re-exported from an external crate) if they coincide with [their default](https://doc.rust-lang.org/reference/lifetime-elision.html#default-trait-object-lifetimes).
Partially addresses #44306. Follow-up to #103885. [Zulip discussion](https://rust-lang.zulipchat.com/#narrow/stream/266220-rustdoc/topic/clean_middle_ty.3A.20I.20need.20to.20add.20a.20parameter/near/307143097).

Most notably, if `std` exported something from `core` containing a type like `Box<dyn Fn()>`, then it would now be rendered as `Box<dyn Fn(), Global>` instead of `Box<dyn Fn() + 'static, Global>` (hiding `+ 'static` as it is the default in this case). Showing `Global` here is a separate issue, #80379, which is on my agenda.

Note that I am not really fond of the fact that I had to add a parameter to such a widely used function (30+ call sites) to address such a niche bug.

CC `@GuillaumeGomez`
Requesting a review from a compiler contributor or team member as recommended on Zulip.
r? compiler

---

`@rustbot` label T-compiler T-rustdoc A-cross-crate-reexports
Diffstat (limited to 'src')
-rw-r--r--src/librustdoc/clean/auto_trait.rs2
-rw-r--r--src/librustdoc/clean/blanket_impl.rs8
-rw-r--r--src/librustdoc/clean/inline.rs19
-rw-r--r--src/librustdoc/clean/mod.rs212
-rw-r--r--src/librustdoc/clean/utils.rs43
5 files changed, 235 insertions, 49 deletions
diff --git a/src/librustdoc/clean/auto_trait.rs b/src/librustdoc/clean/auto_trait.rs
index baf2b0a8585..c8a40e01501 100644
--- a/src/librustdoc/clean/auto_trait.rs
+++ b/src/librustdoc/clean/auto_trait.rs
@@ -124,7 +124,7 @@ where
                 unsafety: hir::Unsafety::Normal,
                 generics: new_generics,
                 trait_: Some(clean_trait_ref_with_bindings(self.cx, trait_ref, ThinVec::new())),
-                for_: clean_middle_ty(ty::Binder::dummy(ty), self.cx, None),
+                for_: clean_middle_ty(ty::Binder::dummy(ty), self.cx, None, None),
                 items: Vec::new(),
                 polarity,
                 kind: ImplKind::Auto,
diff --git a/src/librustdoc/clean/blanket_impl.rs b/src/librustdoc/clean/blanket_impl.rs
index 9183fdaa087..a3604158851 100644
--- a/src/librustdoc/clean/blanket_impl.rs
+++ b/src/librustdoc/clean/blanket_impl.rs
@@ -107,7 +107,12 @@ impl<'a, 'tcx> BlanketImplFinder<'a, 'tcx> {
                             ty::Binder::dummy(trait_ref.subst_identity()),
                             ThinVec::new(),
                         )),
-                        for_: clean_middle_ty(ty::Binder::dummy(ty.subst_identity()), cx, None),
+                        for_: clean_middle_ty(
+                            ty::Binder::dummy(ty.subst_identity()),
+                            cx,
+                            None,
+                            None,
+                        ),
                         items: cx
                             .tcx
                             .associated_items(impl_def_id)
@@ -119,6 +124,7 @@ impl<'a, 'tcx> BlanketImplFinder<'a, 'tcx> {
                             ty::Binder::dummy(trait_ref.subst_identity().self_ty()),
                             cx,
                             None,
+                            None,
                         ))),
                     }))),
                     cfg: None,
diff --git a/src/librustdoc/clean/inline.rs b/src/librustdoc/clean/inline.rs
index 7dc08b3b1ff..3aa98da1c80 100644
--- a/src/librustdoc/clean/inline.rs
+++ b/src/librustdoc/clean/inline.rs
@@ -278,8 +278,12 @@ fn build_union(cx: &mut DocContext<'_>, did: DefId) -> clean::Union {
 
 fn build_type_alias(cx: &mut DocContext<'_>, did: DefId) -> Box<clean::Typedef> {
     let predicates = cx.tcx.explicit_predicates_of(did);
-    let type_ =
-        clean_middle_ty(ty::Binder::dummy(cx.tcx.type_of(did).subst_identity()), cx, Some(did));
+    let type_ = clean_middle_ty(
+        ty::Binder::dummy(cx.tcx.type_of(did).subst_identity()),
+        cx,
+        Some(did),
+        None,
+    );
 
     Box::new(clean::Typedef {
         type_,
@@ -386,9 +390,12 @@ pub(crate) fn build_impl(
 
     let for_ = match &impl_item {
         Some(impl_) => clean_ty(impl_.self_ty, cx),
-        None => {
-            clean_middle_ty(ty::Binder::dummy(tcx.type_of(did).subst_identity()), cx, Some(did))
-        }
+        None => clean_middle_ty(
+            ty::Binder::dummy(tcx.type_of(did).subst_identity()),
+            cx,
+            Some(did),
+            None,
+        ),
     };
 
     // Only inline impl if the implementing type is
@@ -630,6 +637,7 @@ fn build_const(cx: &mut DocContext<'_>, def_id: DefId) -> clean::Constant {
             ty::Binder::dummy(cx.tcx.type_of(def_id).subst_identity()),
             cx,
             Some(def_id),
+            None,
         ),
         kind: clean::ConstantKind::Extern { def_id },
     }
@@ -641,6 +649,7 @@ fn build_static(cx: &mut DocContext<'_>, did: DefId, mutable: bool) -> clean::St
             ty::Binder::dummy(cx.tcx.type_of(did).subst_identity()),
             cx,
             Some(did),
+            None,
         ),
         mutability: if mutable { Mutability::Mut } else { Mutability::Not },
         expr: None,
diff --git a/src/librustdoc/clean/mod.rs b/src/librustdoc/clean/mod.rs
index b56b8127996..0c7950bfd3d 100644
--- a/src/librustdoc/clean/mod.rs
+++ b/src/librustdoc/clean/mod.rs
@@ -31,6 +31,7 @@ use rustc_middle::{bug, span_bug};
 use rustc_span::hygiene::{AstPass, MacroKind};
 use rustc_span::symbol::{kw, sym, Ident, Symbol};
 use rustc_span::{self, ExpnKind};
+use rustc_trait_selection::traits::wf::object_region_bounds;
 
 use std::borrow::Cow;
 use std::collections::hash_map::Entry;
@@ -253,6 +254,7 @@ pub(crate) fn clean_const<'tcx>(constant: &hir::ConstArg, cx: &mut DocContext<'t
             ty::Binder::dummy(cx.tcx.type_of(def_id).subst_identity()),
             cx,
             Some(def_id),
+            None,
         ),
         kind: ConstantKind::Anonymous { body: constant.value.body },
     }
@@ -264,7 +266,7 @@ pub(crate) fn clean_middle_const<'tcx>(
 ) -> Constant {
     // FIXME: instead of storing the stringified expression, store `self` directly instead.
     Constant {
-        type_: clean_middle_ty(constant.map_bound(|c| c.ty()), cx, None),
+        type_: clean_middle_ty(constant.map_bound(|c| c.ty()), cx, None, None),
         kind: ConstantKind::TyConst { expr: constant.skip_binder().to_string().into() },
     }
 }
@@ -370,7 +372,7 @@ fn clean_poly_trait_predicate<'tcx>(
 
     let poly_trait_ref = pred.map_bound(|pred| pred.trait_ref);
     Some(WherePredicate::BoundPredicate {
-        ty: clean_middle_ty(poly_trait_ref.self_ty(), cx, None),
+        ty: clean_middle_ty(poly_trait_ref.self_ty(), cx, None, None),
         bounds: vec![clean_poly_trait_ref_with_bindings(cx, poly_trait_ref, ThinVec::new())],
         bound_params: Vec::new(),
     })
@@ -396,7 +398,7 @@ fn clean_type_outlives_predicate<'tcx>(
     let ty::OutlivesPredicate(ty, lt) = pred.skip_binder();
 
     Some(WherePredicate::BoundPredicate {
-        ty: clean_middle_ty(pred.rebind(ty), cx, None),
+        ty: clean_middle_ty(pred.rebind(ty), cx, None, None),
         bounds: vec![GenericBound::Outlives(
             clean_middle_region(lt).expect("failed to clean lifetimes"),
         )],
@@ -409,7 +411,7 @@ fn clean_middle_term<'tcx>(
     cx: &mut DocContext<'tcx>,
 ) -> Term {
     match term.skip_binder().unpack() {
-        ty::TermKind::Ty(ty) => Term::Type(clean_middle_ty(term.rebind(ty), cx, None)),
+        ty::TermKind::Ty(ty) => Term::Type(clean_middle_ty(term.rebind(ty), cx, None, None)),
         ty::TermKind::Const(c) => Term::Constant(clean_middle_const(term.rebind(c), cx)),
     }
 }
@@ -462,7 +464,7 @@ fn clean_projection<'tcx>(
 
     let trait_ =
         clean_trait_ref_with_bindings(cx, ty.map_bound(|ty| ty.trait_ref(cx.tcx)), ThinVec::new());
-    let self_type = clean_middle_ty(ty.map_bound(|ty| ty.self_ty()), cx, None);
+    let self_type = clean_middle_ty(ty.map_bound(|ty| ty.self_ty()), cx, None, None);
     let self_def_id = if let Some(def_id) = def_id {
         cx.tcx.opt_parent(def_id).or(Some(def_id))
     } else {
@@ -493,8 +495,13 @@ fn projection_to_path_segment<'tcx>(
     PathSegment {
         name: item.name,
         args: GenericArgs::AngleBracketed {
-            args: substs_to_args(cx, ty.map_bound(|ty| &ty.substs[generics.parent_count..]), false)
-                .into(),
+            args: substs_to_args(
+                cx,
+                ty.map_bound(|ty| &ty.substs[generics.parent_count..]),
+                false,
+                None,
+            )
+            .into(),
             bindings: Default::default(),
         },
     }
@@ -514,6 +521,7 @@ fn clean_generic_param_def<'tcx>(
                     ty::Binder::dummy(cx.tcx.type_of(def.def_id).subst_identity()),
                     cx,
                     Some(def.def_id),
+                    None,
                 ))
             } else {
                 None
@@ -540,6 +548,7 @@ fn clean_generic_param_def<'tcx>(
                     ),
                     cx,
                     Some(def.def_id),
+                    None,
                 )),
                 default: match has_default {
                     true => Some(Box::new(
@@ -871,7 +880,7 @@ fn clean_ty_generics<'tcx>(
         let crate::core::ImplTraitParam::ParamIndex(idx) = param else { unreachable!() };
         if let Some(proj) = impl_trait_proj.remove(&idx) {
             for (trait_did, name, rhs, bound_params) in proj {
-                let rhs = clean_middle_ty(rhs, cx, None);
+                let rhs = clean_middle_ty(rhs, cx, None, None);
                 simplify::merge_bounds(
                     cx,
                     &mut bounds,
@@ -1126,7 +1135,7 @@ fn clean_fn_decl_from_did_and_sig<'tcx>(
 
     // We assume all empty tuples are default return type. This theoretically can discard `-> ()`,
     // but shouldn't change any code meaning.
-    let output = clean_middle_ty(sig.output(), cx, None);
+    let output = clean_middle_ty(sig.output(), cx, None, None);
 
     FnDecl {
         output,
@@ -1136,7 +1145,7 @@ fn clean_fn_decl_from_did_and_sig<'tcx>(
                 .inputs()
                 .iter()
                 .map(|t| Argument {
-                    type_: clean_middle_ty(t.map_bound(|t| *t), cx, None),
+                    type_: clean_middle_ty(t.map_bound(|t| *t), cx, None, None),
                     name: names
                         .next()
                         .map(|i| i.name)
@@ -1190,8 +1199,12 @@ fn clean_trait_item<'tcx>(trait_item: &hir::TraitItem<'tcx>, cx: &mut DocContext
             hir::TraitItemKind::Type(bounds, Some(default)) => {
                 let generics = enter_impl_trait(cx, |cx| clean_generics(trait_item.generics, cx));
                 let bounds = bounds.iter().filter_map(|x| clean_generic_bound(x, cx)).collect();
-                let item_type =
-                    clean_middle_ty(ty::Binder::dummy(hir_ty_to_ty(cx.tcx, default)), cx, None);
+                let item_type = clean_middle_ty(
+                    ty::Binder::dummy(hir_ty_to_ty(cx.tcx, default)),
+                    cx,
+                    None,
+                    None,
+                );
                 AssocTypeItem(
                     Box::new(Typedef {
                         type_: clean_ty(default, cx),
@@ -1230,8 +1243,12 @@ pub(crate) fn clean_impl_item<'tcx>(
             hir::ImplItemKind::Type(hir_ty) => {
                 let type_ = clean_ty(hir_ty, cx);
                 let generics = clean_generics(impl_.generics, cx);
-                let item_type =
-                    clean_middle_ty(ty::Binder::dummy(hir_ty_to_ty(cx.tcx, hir_ty)), cx, None);
+                let item_type = clean_middle_ty(
+                    ty::Binder::dummy(hir_ty_to_ty(cx.tcx, hir_ty)),
+                    cx,
+                    None,
+                    None,
+                );
                 AssocTypeItem(
                     Box::new(Typedef { type_, generics, item_type: Some(item_type) }),
                     Vec::new(),
@@ -1254,6 +1271,7 @@ pub(crate) fn clean_middle_assoc_item<'tcx>(
                 ty::Binder::dummy(tcx.type_of(assoc_item.def_id).subst_identity()),
                 cx,
                 Some(assoc_item.def_id),
+                None,
             );
 
             let provided = match assoc_item.container {
@@ -1447,6 +1465,7 @@ pub(crate) fn clean_middle_assoc_item<'tcx>(
                                 ty::Binder::dummy(tcx.type_of(assoc_item.def_id).subst_identity()),
                                 cx,
                                 Some(assoc_item.def_id),
+                                None,
                             ),
                             generics,
                             // FIXME: should we obtain the Type from HIR and pass it on here?
@@ -1465,6 +1484,7 @@ pub(crate) fn clean_middle_assoc_item<'tcx>(
                             ty::Binder::dummy(tcx.type_of(assoc_item.def_id).subst_identity()),
                             cx,
                             Some(assoc_item.def_id),
+                            None,
                         ),
                         generics: Generics {
                             params: ThinVec::new(),
@@ -1510,7 +1530,7 @@ fn clean_qpath<'tcx>(hir_ty: &hir::Ty<'tcx>, cx: &mut DocContext<'tcx>) -> Type
             if !ty.has_escaping_bound_vars()
                 && let Some(normalized_value) = normalize(cx, ty::Binder::dummy(ty))
             {
-                return clean_middle_ty(normalized_value, cx, None);
+                return clean_middle_ty(normalized_value, cx, None, None);
             }
 
             let trait_segments = &p.segments[..p.segments.len() - 1];
@@ -1738,11 +1758,140 @@ fn normalize<'tcx>(
     }
 }
 
+fn clean_trait_object_lifetime_bound<'tcx>(
+    region: ty::Region<'tcx>,
+    container: Option<ContainerTy<'tcx>>,
+    preds: &'tcx ty::List<ty::PolyExistentialPredicate<'tcx>>,
+    tcx: TyCtxt<'tcx>,
+) -> Option<Lifetime> {
+    if can_elide_trait_object_lifetime_bound(region, container, preds, tcx) {
+        return None;
+    }
+
+    // Since there is a semantic difference between an implicitly elided (i.e. "defaulted") object
+    // lifetime and an explicitly elided object lifetime (`'_`), we intentionally don't hide the
+    // latter contrary to `clean_middle_region`.
+    match *region {
+        ty::ReStatic => Some(Lifetime::statik()),
+        ty::ReEarlyBound(region) if region.name != kw::Empty => Some(Lifetime(region.name)),
+        ty::ReLateBound(_, ty::BoundRegion { kind: ty::BrNamed(_, name), .. })
+            if name != kw::Empty =>
+        {
+            Some(Lifetime(name))
+        }
+        ty::ReEarlyBound(_)
+        | ty::ReLateBound(..)
+        | ty::ReFree(_)
+        | ty::ReVar(_)
+        | ty::RePlaceholder(_)
+        | ty::ReErased
+        | ty::ReError(_) => None,
+    }
+}
+
+fn can_elide_trait_object_lifetime_bound<'tcx>(
+    region: ty::Region<'tcx>,
+    container: Option<ContainerTy<'tcx>>,
+    preds: &'tcx ty::List<ty::PolyExistentialPredicate<'tcx>>,
+    tcx: TyCtxt<'tcx>,
+) -> bool {
+    // Below we quote extracts from https://doc.rust-lang.org/reference/lifetime-elision.html#default-trait-object-lifetimes
+
+    // > If the trait object is used as a type argument of a generic type then the containing type is
+    // > first used to try to infer a bound.
+    let default = container
+        .map_or(ObjectLifetimeDefault::Empty, |container| container.object_lifetime_default(tcx));
+
+    // > If there is a unique bound from the containing type then that is the default
+    // If there is a default object lifetime and the given region is lexically equal to it, elide it.
+    match default {
+        ObjectLifetimeDefault::Static => return *region == ty::ReStatic,
+        // FIXME(fmease): Don't compare lexically but respect de Bruijn indices etc. to handle shadowing correctly.
+        ObjectLifetimeDefault::Arg(default) => return region.get_name() == default.get_name(),
+        // > If there is more than one bound from the containing type then an explicit bound must be specified
+        // Due to ambiguity there is no default trait-object lifetime and thus elision is impossible.
+        // Don't elide the lifetime.
+        ObjectLifetimeDefault::Ambiguous => return false,
+        // There is no meaningful bound. Further processing is needed...
+        ObjectLifetimeDefault::Empty => {}
+    }
+
+    // > If neither of those rules apply, then the bounds on the trait are used:
+    match *object_region_bounds(tcx, preds) {
+        // > If the trait has no lifetime bounds, then the lifetime is inferred in expressions
+        // > and is 'static outside of expressions.
+        // FIXME: If we are in an expression context (i.e. fn bodies and const exprs) then the default is
+        // `'_` and not `'static`. Only if we are in a non-expression one, the default is `'static`.
+        // Note however that at the time of this writing it should be fine to disregard this subtlety
+        // as we neither render const exprs faithfully anyway (hiding them in some places or using `_` instead)
+        // nor show the contents of fn bodies.
+        [] => *region == ty::ReStatic,
+        // > If the trait is defined with a single lifetime bound then that bound is used.
+        // > If 'static is used for any lifetime bound then 'static is used.
+        // FIXME(fmease): Don't compare lexically but respect de Bruijn indices etc. to handle shadowing correctly.
+        [object_region] => object_region.get_name() == region.get_name(),
+        // There are several distinct trait regions and none are `'static`.
+        // Due to ambiguity there is no default trait-object lifetime and thus elision is impossible.
+        // Don't elide the lifetime.
+        _ => false,
+    }
+}
+
+#[derive(Debug)]
+pub(crate) enum ContainerTy<'tcx> {
+    Ref(ty::Region<'tcx>),
+    Regular { ty: DefId, substs: ty::Binder<'tcx, &'tcx [ty::GenericArg<'tcx>]>, arg: usize },
+}
+
+impl<'tcx> ContainerTy<'tcx> {
+    fn object_lifetime_default(self, tcx: TyCtxt<'tcx>) -> ObjectLifetimeDefault<'tcx> {
+        match self {
+            Self::Ref(region) => ObjectLifetimeDefault::Arg(region),
+            Self::Regular { ty: container, substs, arg: index } => {
+                let (DefKind::Struct
+                | DefKind::Union
+                | DefKind::Enum
+                | DefKind::TyAlias
+                | DefKind::Trait
+                | DefKind::AssocTy
+                | DefKind::Variant) = tcx.def_kind(container)
+                else {
+                    return ObjectLifetimeDefault::Empty;
+                };
+
+                let generics = tcx.generics_of(container);
+                let param = generics.params[index].def_id;
+                let default = tcx.object_lifetime_default(param);
+
+                match default {
+                    rbv::ObjectLifetimeDefault::Param(lifetime) => {
+                        let index = generics.param_def_id_to_index[&lifetime];
+                        let arg = substs.skip_binder()[index as usize].expect_region();
+                        ObjectLifetimeDefault::Arg(arg)
+                    }
+                    rbv::ObjectLifetimeDefault::Empty => ObjectLifetimeDefault::Empty,
+                    rbv::ObjectLifetimeDefault::Static => ObjectLifetimeDefault::Static,
+                    rbv::ObjectLifetimeDefault::Ambiguous => ObjectLifetimeDefault::Ambiguous,
+                }
+            }
+        }
+    }
+}
+
+#[derive(Debug, Clone, Copy)]
+pub(crate) enum ObjectLifetimeDefault<'tcx> {
+    Empty,
+    Static,
+    Ambiguous,
+    Arg(ty::Region<'tcx>),
+}
+
 #[instrument(level = "trace", skip(cx), ret)]
 pub(crate) fn clean_middle_ty<'tcx>(
     bound_ty: ty::Binder<'tcx, Ty<'tcx>>,
     cx: &mut DocContext<'tcx>,
     parent_def_id: Option<DefId>,
+    container: Option<ContainerTy<'tcx>>,
 ) -> Type {
     let bound_ty = normalize(cx, bound_ty).unwrap_or(bound_ty);
     match *bound_ty.skip_binder().kind() {
@@ -1753,19 +1902,24 @@ pub(crate) fn clean_middle_ty<'tcx>(
         ty::Uint(uint_ty) => Primitive(uint_ty.into()),
         ty::Float(float_ty) => Primitive(float_ty.into()),
         ty::Str => Primitive(PrimitiveType::Str),
-        ty::Slice(ty) => Slice(Box::new(clean_middle_ty(bound_ty.rebind(ty), cx, None))),
+        ty::Slice(ty) => Slice(Box::new(clean_middle_ty(bound_ty.rebind(ty), cx, None, None))),
         ty::Array(ty, mut n) => {
             n = n.eval(cx.tcx, ty::ParamEnv::reveal_all());
             let n = print_const(cx, n);
-            Array(Box::new(clean_middle_ty(bound_ty.rebind(ty), cx, None)), n.into())
+            Array(Box::new(clean_middle_ty(bound_ty.rebind(ty), cx, None, None)), n.into())
         }
         ty::RawPtr(mt) => {
-            RawPointer(mt.mutbl, Box::new(clean_middle_ty(bound_ty.rebind(mt.ty), cx, None)))
+            RawPointer(mt.mutbl, Box::new(clean_middle_ty(bound_ty.rebind(mt.ty), cx, None, None)))
         }
         ty::Ref(r, ty, mutbl) => BorrowedRef {
             lifetime: clean_middle_region(r),
             mutability: mutbl,
-            type_: Box::new(clean_middle_ty(bound_ty.rebind(ty), cx, None)),
+            type_: Box::new(clean_middle_ty(
+                bound_ty.rebind(ty),
+                cx,
+                None,
+                Some(ContainerTy::Ref(r)),
+            )),
         },
         ty::FnDef(..) | ty::FnPtr(_) => {
             // FIXME: should we merge the outer and inner binders somehow?
@@ -1817,10 +1971,8 @@ pub(crate) fn clean_middle_ty<'tcx>(
 
             inline::record_extern_fqn(cx, did, ItemType::Trait);
 
-            // FIXME(fmease): Hide the trait-object lifetime bound if it coincides with its default
-            // to partially address #44306. Follow the rules outlined at
-            // https://doc.rust-lang.org/reference/lifetime-elision.html#default-trait-object-lifetimes
-            let lifetime = clean_middle_region(*reg);
+            let lifetime = clean_trait_object_lifetime_bound(*reg, container, obj, cx.tcx);
+
             let mut bounds = dids
                 .map(|did| {
                     let empty = ty::Binder::dummy(InternalSubsts::empty());
@@ -1869,7 +2021,7 @@ pub(crate) fn clean_middle_ty<'tcx>(
             DynTrait(bounds, lifetime)
         }
         ty::Tuple(t) => {
-            Tuple(t.iter().map(|t| clean_middle_ty(bound_ty.rebind(t), cx, None)).collect())
+            Tuple(t.iter().map(|t| clean_middle_ty(bound_ty.rebind(t), cx, None, None)).collect())
         }
 
         ty::Alias(ty::Projection, ref data) => {
@@ -1878,7 +2030,7 @@ pub(crate) fn clean_middle_ty<'tcx>(
 
         ty::Alias(ty::Inherent, alias_ty) => {
             let alias_ty = bound_ty.rebind(alias_ty);
-            let self_type = clean_middle_ty(alias_ty.map_bound(|ty| ty.self_ty()), cx, None);
+            let self_type = clean_middle_ty(alias_ty.map_bound(|ty| ty.self_ty()), cx, None, None);
 
             Type::QPath(Box::new(QPathData {
                 assoc: PathSegment {
@@ -1888,6 +2040,7 @@ pub(crate) fn clean_middle_ty<'tcx>(
                             cx,
                             alias_ty.map_bound(|ty| ty.substs.as_slice()),
                             true,
+                            None,
                         )
                         .into(),
                         bindings: Default::default(),
@@ -2023,6 +2176,7 @@ pub(crate) fn clean_middle_field<'tcx>(field: &ty::FieldDef, cx: &mut DocContext
             ty::Binder::dummy(cx.tcx.type_of(field.did).subst_identity()),
             cx,
             Some(field.did),
+            None,
         ),
         cx,
     )
@@ -2314,7 +2468,12 @@ fn clean_maybe_renamed_item<'tcx>(
             ItemKind::TyAlias(hir_ty, generics) => {
                 *cx.current_type_aliases.entry(def_id).or_insert(0) += 1;
                 let rustdoc_ty = clean_ty(hir_ty, cx);
-                let ty = clean_middle_ty(ty::Binder::dummy(hir_ty_to_ty(cx.tcx, hir_ty)), cx, None);
+                let ty = clean_middle_ty(
+                    ty::Binder::dummy(hir_ty_to_ty(cx.tcx, hir_ty)),
+                    cx,
+                    None,
+                    None,
+                );
                 let generics = clean_generics(generics, cx);
                 if let Some(count) = cx.current_type_aliases.get_mut(&def_id) {
                     *count -= 1;
@@ -2418,6 +2577,7 @@ fn clean_impl<'tcx>(
                 ty::Binder::dummy(tcx.type_of(def_id).subst_identity()),
                 cx,
                 Some(def_id.to_def_id()),
+                None,
             )),
             _ => None,
         });
diff --git a/src/librustdoc/clean/utils.rs b/src/librustdoc/clean/utils.rs
index 366f9395296..294de12cea8 100644
--- a/src/librustdoc/clean/utils.rs
+++ b/src/librustdoc/clean/utils.rs
@@ -73,8 +73,9 @@ pub(crate) fn krate(cx: &mut DocContext<'_>) -> Crate {
 
 pub(crate) fn substs_to_args<'tcx>(
     cx: &mut DocContext<'tcx>,
-    substs: ty::Binder<'tcx, &[ty::subst::GenericArg<'tcx>]>,
+    substs: ty::Binder<'tcx, &'tcx [ty::subst::GenericArg<'tcx>]>,
     mut skip_first: bool,
+    container: Option<DefId>,
 ) -> Vec<GenericArg> {
     let mut ret_val =
         Vec::with_capacity(substs.skip_binder().len().saturating_sub(if skip_first {
@@ -82,19 +83,29 @@ pub(crate) fn substs_to_args<'tcx>(
         } else {
             0
         }));
-    ret_val.extend(substs.iter().filter_map(|kind| match kind.skip_binder().unpack() {
-        GenericArgKind::Lifetime(lt) => {
-            Some(GenericArg::Lifetime(clean_middle_region(lt).unwrap_or(Lifetime::elided())))
-        }
-        GenericArgKind::Type(_) if skip_first => {
-            skip_first = false;
-            None
-        }
-        GenericArgKind::Type(ty) => {
-            Some(GenericArg::Type(clean_middle_ty(kind.rebind(ty), cx, None)))
-        }
-        GenericArgKind::Const(ct) => {
-            Some(GenericArg::Const(Box::new(clean_middle_const(kind.rebind(ct), cx))))
+
+    ret_val.extend(substs.iter().enumerate().filter_map(|(index, kind)| {
+        match kind.skip_binder().unpack() {
+            GenericArgKind::Lifetime(lt) => {
+                Some(GenericArg::Lifetime(clean_middle_region(lt).unwrap_or(Lifetime::elided())))
+            }
+            GenericArgKind::Type(_) if skip_first => {
+                skip_first = false;
+                None
+            }
+            GenericArgKind::Type(ty) => Some(GenericArg::Type(clean_middle_ty(
+                kind.rebind(ty),
+                cx,
+                None,
+                container.map(|container| crate::clean::ContainerTy::Regular {
+                    ty: container,
+                    substs,
+                    arg: index,
+                }),
+            ))),
+            GenericArgKind::Const(ct) => {
+                Some(GenericArg::Const(Box::new(clean_middle_const(kind.rebind(ct), cx))))
+            }
         }
     }));
     ret_val
@@ -107,7 +118,7 @@ fn external_generic_args<'tcx>(
     bindings: ThinVec<TypeBinding>,
     substs: ty::Binder<'tcx, SubstsRef<'tcx>>,
 ) -> GenericArgs {
-    let args = substs_to_args(cx, substs.map_bound(|substs| &substs[..]), has_self);
+    let args = substs_to_args(cx, substs.map_bound(|substs| &substs[..]), has_self, Some(did));
 
     if cx.tcx.fn_trait_kind_from_def_id(did).is_some() {
         let ty = substs
@@ -118,7 +129,7 @@ fn external_generic_args<'tcx>(
         let inputs =
             // The trait's first substitution is the one after self, if there is one.
             match ty.skip_binder().kind() {
-                ty::Tuple(tys) => tys.iter().map(|t| clean_middle_ty(ty.rebind(t), cx, None)).collect::<Vec<_>>().into(),
+                ty::Tuple(tys) => tys.iter().map(|t| clean_middle_ty(ty.rebind(t), cx, None, None)).collect::<Vec<_>>().into(),
                 _ => return GenericArgs::AngleBracketed { args: args.into(), bindings },
             };
         let output = bindings.into_iter().next().and_then(|binding| match binding.kind {