about summary refs log tree commit diff
diff options
context:
space:
mode:
authorAlex Crichton <alex@alexcrichton.com>2015-04-07 00:16:35 -0700
committerAlex Crichton <alex@alexcrichton.com>2015-04-07 17:54:34 -0700
commit75ef0832ae00477f837a73356ea7f12f64134c7c (patch)
tree589e3b6fb2f10c5d5905650bbd936b8fd249a04f
parent11f26f999581e639233e64f4f1fbc210a2bb856a (diff)
downloadrust-75ef0832ae00477f837a73356ea7f12f64134c7c.tar.gz
rust-75ef0832ae00477f837a73356ea7f12f64134c7c.zip
rustdoc: Improve handling inlined associated types
* All bounds are now discovered through the trait to be inlined.
* The `?Sized` bound now renders correctly for inlined associated types.
* All `QPath`s (`<A as B>::C`) instances are rendered as `A::C` where `C` is a
  hyperlink to the trait `B`. This should improve at least how the docs look at
  least.
* Supertrait bounds are now separated and display as the source lists them.

Closes #20727
Closes #21145
-rw-r--r--src/librustc/metadata/encoder.rs3
-rw-r--r--src/librustdoc/clean/inline.rs52
-rw-r--r--src/librustdoc/clean/mod.rs176
-rw-r--r--src/librustdoc/html/format.rs35
-rw-r--r--src/test/auxiliary/issue-20727.rs38
-rw-r--r--src/test/rustdoc/issue-20727-2.rs32
-rw-r--r--src/test/rustdoc/issue-20727-3.rs33
-rw-r--r--src/test/rustdoc/issue-20727-4.rs49
-rw-r--r--src/test/rustdoc/issue-20727.rs33
9 files changed, 382 insertions, 69 deletions
diff --git a/src/librustc/metadata/encoder.rs b/src/librustc/metadata/encoder.rs
index b9559abbdf4..f9d3d707bb5 100644
--- a/src/librustc/metadata/encoder.rs
+++ b/src/librustc/metadata/encoder.rs
@@ -1264,7 +1264,8 @@ fn encode_info_for_item(ecx: &EncodeContext,
         encode_paren_sugar(rbml_w, trait_def.paren_sugar);
         encode_defaulted(rbml_w, ty::trait_has_default_impl(tcx, def_id));
         encode_associated_type_names(rbml_w, &trait_def.associated_type_names);
-        encode_generics(rbml_w, ecx, &trait_def.generics, &trait_predicates, tag_item_generics);
+        encode_generics(rbml_w, ecx, &trait_def.generics, &trait_predicates,
+                        tag_item_generics);
         encode_predicates(rbml_w, ecx, &ty::lookup_super_predicates(tcx, def_id),
                           tag_item_super_predicates);
         encode_trait_ref(rbml_w, ecx, &*trait_def.trait_ref, tag_item_trait_ref);
diff --git a/src/librustdoc/clean/inline.rs b/src/librustdoc/clean/inline.rs
index e4b2e82b21b..24184bae95b 100644
--- a/src/librustdoc/clean/inline.rs
+++ b/src/librustdoc/clean/inline.rs
@@ -150,11 +150,14 @@ pub fn build_external_trait(cx: &DocContext, tcx: &ty::ctxt,
     let def = ty::lookup_trait_def(tcx, did);
     let trait_items = ty::trait_items(tcx, did).clean(cx);
     let predicates = ty::lookup_predicates(tcx, did);
+    let generics = (&def.generics, &predicates, subst::TypeSpace).clean(cx);
+    let generics = filter_non_trait_generics(did, generics);
+    let (generics, supertrait_bounds) = separate_supertrait_bounds(generics);
     clean::Trait {
         unsafety: def.unsafety,
-        generics: (&def.generics, &predicates, subst::TypeSpace).clean(cx),
+        generics: generics,
         items: trait_items,
-        bounds: vec![], // supertraits can be found in the list of predicates
+        bounds: supertrait_bounds,
     }
 }
 
@@ -447,3 +450,48 @@ fn build_static(cx: &DocContext, tcx: &ty::ctxt,
         expr: "\n\n\n".to_string(), // trigger the "[definition]" links
     }
 }
+
+/// A trait's generics clause actually contains all of the predicates for all of
+/// its associated types as well. We specifically move these clauses to the
+/// associated types instead when displaying, so when we're genering the
+/// generics for the trait itself we need to be sure to remove them.
+///
+/// The inverse of this filtering logic can be found in the `Clean`
+/// implementation for `AssociatedType`
+fn filter_non_trait_generics(trait_did: ast::DefId, mut g: clean::Generics)
+                             -> clean::Generics {
+    g.where_predicates.retain(|pred| {
+        match *pred {
+            clean::WherePredicate::BoundPredicate {
+                ty: clean::QPath {
+                    self_type: box clean::Generic(ref s),
+                    trait_: box clean::ResolvedPath { did, .. },
+                    name: ref _name,
+                }, ..
+            } => *s != "Self" || did != trait_did,
+            _ => true,
+        }
+    });
+    return g;
+}
+
+/// Supertrait bounds for a trait are also listed in the generics coming from
+/// the metadata for a crate, so we want to separate those out and create a new
+/// list of explicit supertrait bounds to render nicely.
+fn separate_supertrait_bounds(mut g: clean::Generics)
+                              -> (clean::Generics, Vec<clean::TyParamBound>) {
+    let mut ty_bounds = Vec::new();
+    g.where_predicates.retain(|pred| {
+        match *pred {
+            clean::WherePredicate::BoundPredicate {
+                ty: clean::Generic(ref s),
+                ref bounds
+            } if *s == "Self" => {
+                ty_bounds.extend(bounds.iter().cloned());
+                false
+            }
+            _ => true,
+        }
+    });
+    (g, ty_bounds)
+}
diff --git a/src/librustdoc/clean/mod.rs b/src/librustdoc/clean/mod.rs
index 630dc18dca7..65778114fe0 100644
--- a/src/librustdoc/clean/mod.rs
+++ b/src/librustdoc/clean/mod.rs
@@ -498,6 +498,35 @@ pub enum TyParamBound {
     TraitBound(PolyTrait, ast::TraitBoundModifier)
 }
 
+impl TyParamBound {
+    fn maybe_sized(cx: &DocContext) -> TyParamBound {
+        use syntax::ast::TraitBoundModifier as TBM;
+        let mut sized_bound = ty::BuiltinBound::BoundSized.clean(cx);
+        if let TyParamBound::TraitBound(_, ref mut tbm) = sized_bound {
+            *tbm = TBM::Maybe
+        };
+        sized_bound
+    }
+
+    fn is_sized_bound(&self, cx: &DocContext) -> bool {
+        use syntax::ast::TraitBoundModifier as TBM;
+        if let Some(tcx) = cx.tcx_opt() {
+            let sized_did = match tcx.lang_items.sized_trait() {
+                Some(did) => did,
+                None => return false
+            };
+            if let TyParamBound::TraitBound(PolyTrait {
+                trait_: Type::ResolvedPath { did, .. }, ..
+            }, TBM::None) = *self {
+                if did == sized_did {
+                    return true
+                }
+            }
+        }
+        false
+    }
+}
+
 impl Clean<TyParamBound> for ast::TyParamBound {
     fn clean(&self, cx: &DocContext) -> TyParamBound {
         match *self {
@@ -835,7 +864,9 @@ impl<'tcx> Clean<Type> for ty::ProjectionTy<'tcx> {
     fn clean(&self, cx: &DocContext) -> Type {
         let trait_ = match self.trait_ref.clean(cx) {
             TyParamBound::TraitBound(t, _) => t.trait_,
-            TyParamBound::RegionBound(_) => panic!("cleaning a trait got a region??"),
+            TyParamBound::RegionBound(_) => {
+                panic!("cleaning a trait got a region")
+            }
         };
         Type::QPath {
             name: self.item_name.clean(cx),
@@ -868,28 +899,8 @@ impl<'a, 'tcx> Clean<Generics> for (&'a ty::Generics<'tcx>,
                                     subst::ParamSpace) {
     fn clean(&self, cx: &DocContext) -> Generics {
         use std::collections::HashSet;
-        use syntax::ast::TraitBoundModifier as TBM;
         use self::WherePredicate as WP;
 
-        fn has_sized_bound(bounds: &[TyParamBound], cx: &DocContext) -> bool {
-            if let Some(tcx) = cx.tcx_opt() {
-                let sized_did = match tcx.lang_items.sized_trait() {
-                    Some(did) => did,
-                    None => return false
-                };
-                for bound in bounds {
-                    if let TyParamBound::TraitBound(PolyTrait {
-                        trait_: Type::ResolvedPath { did, .. }, ..
-                    }, TBM::None) = *bound {
-                        if did == sized_did {
-                            return true
-                        }
-                    }
-                }
-            }
-            false
-        }
-
         let (gens, preds, space) = *self;
 
         // Bounds in the type_params and lifetimes fields are repeated in the
@@ -904,34 +915,38 @@ impl<'a, 'tcx> Clean<Generics> for (&'a ty::Generics<'tcx>,
             srp.clean(cx)
         }).collect::<Vec<_>>();
 
-        let where_predicates = preds.predicates.get_slice(space)
-                                               .to_vec().clean(cx);
+        let mut where_predicates = preds.predicates.get_slice(space)
+                                                   .to_vec().clean(cx);
 
-        // Type parameters have a Sized bound by default unless removed with
+        // Type parameters and have a Sized bound by default unless removed with
         // ?Sized.  Scan through the predicates and mark any type parameter with
         // a Sized bound, removing the bounds as we find them.
+        //
+        // Note that associated types also have a sized bound by default, but we
+        // don't actually konw the set of associated types right here so that's
+        // handled in cleaning associated types
         let mut sized_params = HashSet::new();
-        let mut where_predicates = where_predicates.into_iter().filter_map(|pred| {
-            if let WP::BoundPredicate { ty: Type::Generic(ref g), ref bounds } = pred {
-                if has_sized_bound(&**bounds, cx) {
-                    sized_params.insert(g.clone());
-                    return None
+        where_predicates.retain(|pred| {
+            match *pred {
+                WP::BoundPredicate { ty: Generic(ref g), ref bounds } => {
+                    if bounds.iter().any(|b| b.is_sized_bound(cx)) {
+                        sized_params.insert(g.clone());
+                        false
+                    } else {
+                        true
+                    }
                 }
+                _ => true,
             }
-            Some(pred)
-        }).collect::<Vec<_>>();
+        });
 
-        // Finally, run through the type parameters again and insert a ?Sized
+        // Run through the type parameters again and insert a ?Sized
         // unbound for any we didn't find to be Sized.
         for tp in &stripped_typarams {
             if !sized_params.contains(&tp.name) {
-                let mut sized_bound = ty::BuiltinBound::BoundSized.clean(cx);
-                if let TyParamBound::TraitBound(_, ref mut tbm) = sized_bound {
-                    *tbm = TBM::Maybe
-                };
                 where_predicates.push(WP::BoundPredicate {
                     ty: Type::Generic(tp.name.clone()),
-                    bounds: vec![sized_bound]
+                    bounds: vec![TyParamBound::maybe_sized(cx)],
                 })
             }
         }
@@ -1597,17 +1612,7 @@ impl<'tcx> Clean<Type> for ty::Ty<'tcx> {
             }
             ty::ty_tup(ref t) => Tuple(t.clean(cx)),
 
-            ty::ty_projection(ref data) => {
-                let trait_ref = match data.trait_ref.clean(cx) {
-                    TyParamBound::TraitBound(t, _) => t.trait_,
-                    TyParamBound::RegionBound(_) => panic!("cleaning a trait got a region??"),
-                };
-                Type::QPath {
-                    name: data.item_name.clean(cx),
-                    self_type: box data.trait_ref.self_ty().clean(cx),
-                    trait_: box trait_ref,
-                }
-            }
+            ty::ty_projection(ref data) => data.clean(cx),
 
             ty::ty_param(ref p) => Generic(token::get_name(p.name).to_string()),
 
@@ -1881,6 +1886,22 @@ pub struct Path {
     pub segments: Vec<PathSegment>,
 }
 
+impl Path {
+    pub fn singleton(name: String) -> Path {
+        Path {
+            global: false,
+            segments: vec![PathSegment {
+                name: name,
+                params: PathParameters::AngleBracketed {
+                    lifetimes: Vec::new(),
+                    types: Vec::new(),
+                    bindings: Vec::new()
+                }
+            }]
+        }
+    }
+}
+
 impl Clean<Path> for ast::Path {
     fn clean(&self, cx: &DocContext) -> Path {
         Path {
@@ -2516,21 +2537,66 @@ impl Clean<Stability> for attr::Stability {
 
 impl Clean<Item> for ty::AssociatedType {
     fn clean(&self, cx: &DocContext) -> Item {
+        // When loading a cross-crate associated type, the bounds for this type
+        // are actually located on the trait/impl itself, so we need to load
+        // all of the generics from there and then look for bounds that are
+        // applied to this associated type in question.
+        let predicates = ty::lookup_predicates(cx.tcx(), self.container.id());
+        let generics = match self.container {
+            ty::TraitContainer(did) => {
+                let def = ty::lookup_trait_def(cx.tcx(), did);
+                (&def.generics, &predicates, subst::TypeSpace).clean(cx)
+            }
+            ty::ImplContainer(did) => {
+                let ty = ty::lookup_item_type(cx.tcx(), did);
+                (&ty.generics, &predicates, subst::TypeSpace).clean(cx)
+            }
+        };
+        let my_name = self.name.clean(cx);
+        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 }
+            match **trait_ {
+                ResolvedPath { did, .. } if did == self.container.id() => {}
+                _ => return None,
+            }
+            match **self_type {
+                Generic(ref s) if *s == "Self" => {}
+                _ => return None,
+            }
+            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
+        // (some of them may have come from the trait). If we do have a sized
+        // bound, we remove it, and if we don't then we add the `?Sized` bound
+        // at the end.
+        match bounds.iter().position(|b| b.is_sized_bound(cx)) {
+            Some(i) => { bounds.remove(i); }
+            None => bounds.push(TyParamBound::maybe_sized(cx)),
+        }
+
         Item {
             source: DUMMY_SP.clean(cx),
             name: Some(self.name.clean(cx)),
-            attrs: Vec::new(),
-            // FIXME(#20727): bounds are missing and need to be filled in from the
-            // predicates on the trait itself
-            inner: AssociatedTypeItem(vec![], None),
-            visibility: None,
+            attrs: inline::load_attrs(cx, cx.tcx(), self.def_id),
+            inner: AssociatedTypeItem(bounds, None),
+            visibility: self.vis.clean(cx),
             def_id: self.def_id,
-            stability: None,
+            stability: stability::lookup(cx.tcx(), self.def_id).clean(cx),
         }
     }
 }
 
-impl<'a> Clean<Typedef> for (ty::TypeScheme<'a>, ty::GenericPredicates<'a>, ParamSpace) {
+impl<'a> Clean<Typedef> for (ty::TypeScheme<'a>, ty::GenericPredicates<'a>,
+                             ParamSpace) {
     fn clean(&self, cx: &DocContext) -> Typedef {
         let (ref ty_scheme, ref predicates, ps) = *self;
         Typedef {
diff --git a/src/librustdoc/html/format.rs b/src/librustdoc/html/format.rs
index 365e34476aa..ae9d9761001 100644
--- a/src/librustdoc/html/format.rs
+++ b/src/librustdoc/html/format.rs
@@ -502,6 +502,29 @@ impl fmt::Display for clean::Type {
                 }
                 Ok(())
             }
+            // It's pretty unsightly to look at `<A as B>::C` in output, and
+            // we've got hyperlinking on our side, so try to avoid longer
+            // notation as much as possible by making `C` a hyperlink to trait
+            // `B` to disambiguate.
+            //
+            // FIXME: this is still a lossy conversion and there should probably
+            //        be a better way of representing this in general? Most of
+            //        the ugliness comes from inlining across crates where
+            //        everything comes in as a fully resolved QPath (hard to
+            //        look at).
+            clean::QPath {
+                ref name,
+                ref self_type,
+                trait_: box clean::ResolvedPath { did, ref typarams, .. },
+            } => {
+                try!(write!(f, "{}::", self_type));
+                let path = clean::Path::singleton(name.clone());
+                try!(resolved_path(f, did, &path, false));
+
+                // FIXME: `typarams` are not rendered, and this seems bad?
+                drop(typarams);
+                Ok(())
+            }
             clean::QPath { ref name, ref self_type, ref trait_ } => {
                 write!(f, "&lt;{} as {}&gt;::{}", self_type, trait_, name)
             }
@@ -636,17 +659,7 @@ impl fmt::Display for clean::ViewListIdent {
     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
         match self.source {
             Some(did) => {
-                let path = clean::Path {
-                    global: false,
-                    segments: vec!(clean::PathSegment {
-                        name: self.name.clone(),
-                        params: clean::PathParameters::AngleBracketed {
-                            lifetimes: Vec::new(),
-                            types: Vec::new(),
-                            bindings: Vec::new()
-                        }
-                    })
-                };
+                let path = clean::Path::singleton(self.name.clone());
                 resolved_path(f, did, &path, false)
             }
             _ => write!(f, "{}", self.name),
diff --git a/src/test/auxiliary/issue-20727.rs b/src/test/auxiliary/issue-20727.rs
new file mode 100644
index 00000000000..aea8b429d9f
--- /dev/null
+++ b/src/test/auxiliary/issue-20727.rs
@@ -0,0 +1,38 @@
+// Copyright 2015 The Rust Project Developers. See the COPYRIGHT
+// file at the top-level directory of this distribution and at
+// http://rust-lang.org/COPYRIGHT.
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+pub trait Deref {
+    type Target: ?Sized;
+
+    fn deref<'a>(&'a self) -> &'a Self::Target;
+}
+
+pub trait Add<RHS = Self> {
+    type Output;
+
+    fn add(self, rhs: RHS) -> Self::Output;
+}
+
+
+pub trait Bar {}
+pub trait Deref2 {
+    type Target: Bar;
+
+    fn deref(&self) -> Self::Target;
+}
+
+pub trait Index<Idx: ?Sized> {
+    type Output: ?Sized;
+    fn index(&self, index: Idx) -> &Self::Output;
+}
+
+pub trait IndexMut<Idx: ?Sized>: Index<Idx> {
+    fn index_mut(&mut self, index: Idx) -> &mut Self::Output;
+}
diff --git a/src/test/rustdoc/issue-20727-2.rs b/src/test/rustdoc/issue-20727-2.rs
new file mode 100644
index 00000000000..e0122d66de1
--- /dev/null
+++ b/src/test/rustdoc/issue-20727-2.rs
@@ -0,0 +1,32 @@
+// Copyright 2015 The Rust Project Developers. See the COPYRIGHT
+// file at the top-level directory of this distribution and at
+// http://rust-lang.org/COPYRIGHT.
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+// aux-build:issue-20727.rs
+
+extern crate issue_20727;
+
+// @has issue_20727_2/trait.Add.html
+pub trait Add<RHS = Self> {
+    // @has - '//*[@class="rust trait"]' 'trait Add<RHS = Self> {'
+    // @has - '//*[@class="rust trait"]' 'type Output;'
+    type Output;
+
+    // @has - '//*[@class="rust trait"]' 'fn add(self, rhs: RHS) -> Self::Output;'
+    fn add(self, rhs: RHS) -> Self::Output;
+}
+
+// @has issue_20727_2/reexport/trait.Add.html
+pub mod reexport {
+    // @has - '//*[@class="rust trait"]' 'trait Add<RHS = Self> {'
+    // @has - '//*[@class="rust trait"]' 'type Output;'
+    // @has - '//*[@class="rust trait"]' 'fn add(self, rhs: RHS) -> Self::Output;'
+    pub use issue_20727::Add;
+}
+
diff --git a/src/test/rustdoc/issue-20727-3.rs b/src/test/rustdoc/issue-20727-3.rs
new file mode 100644
index 00000000000..8338239a29d
--- /dev/null
+++ b/src/test/rustdoc/issue-20727-3.rs
@@ -0,0 +1,33 @@
+// Copyright 2015 The Rust Project Developers. See the COPYRIGHT
+// file at the top-level directory of this distribution and at
+// http://rust-lang.org/COPYRIGHT.
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+// aux-build:issue-20727.rs
+
+extern crate issue_20727;
+
+pub trait Bar {}
+
+// @has issue_20727_3/trait.Deref2.html
+pub trait Deref2 {
+    // @has - '//*[@class="rust trait"]' 'trait Deref2 {'
+    // @has - '//*[@class="rust trait"]' 'type Target: Bar;'
+    type Target: Bar;
+
+    // @has - '//*[@class="rust trait"]' 'fn deref(&self) -> Self::Target;'
+    fn deref(&self) -> Self::Target;
+}
+
+// @has issue_20727_3/reexport/trait.Deref2.html
+pub mod reexport {
+    // @has - '//*[@class="rust trait"]' 'trait Deref2 {'
+    // @has - '//*[@class="rust trait"]' 'type Target: Bar;'
+    // @has - '//*[@class="rust trait"]' 'fn deref(&self) -> Self::Target;'
+    pub use issue_20727::Deref2;
+}
diff --git a/src/test/rustdoc/issue-20727-4.rs b/src/test/rustdoc/issue-20727-4.rs
new file mode 100644
index 00000000000..ed361ed990a
--- /dev/null
+++ b/src/test/rustdoc/issue-20727-4.rs
@@ -0,0 +1,49 @@
+// Copyright 2015 The Rust Project Developers. See the COPYRIGHT
+// file at the top-level directory of this distribution and at
+// http://rust-lang.org/COPYRIGHT.
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+// aux-build:issue-20727.rs
+
+extern crate issue_20727;
+
+// @has issue_20727_4/trait.Index.html
+pub trait Index<Idx: ?Sized> {
+    // @has - '//*[@class="rust trait"]' 'trait Index<Idx: ?Sized> {'
+    // @has - '//*[@class="rust trait"]' 'type Output: ?Sized'
+    type Output: ?Sized;
+
+    // @has - '//*[@class="rust trait"]' \
+    //        'fn index(&self, index: Idx) -> &Self::Output'
+    fn index(&self, index: Idx) -> &Self::Output;
+}
+
+// @has issue_20727_4/trait.IndexMut.html
+pub trait IndexMut<Idx: ?Sized>: Index<Idx> {
+    // @has - '//*[@class="rust trait"]' \
+    //        'trait IndexMut<Idx: ?Sized>: Index<Idx> {'
+    // @has - '//*[@class="rust trait"]' \
+    //        'fn index_mut(&mut self, index: Idx) -> &mut Self::Output;'
+    fn index_mut(&mut self, index: Idx) -> &mut Self::Output;
+}
+
+pub mod reexport {
+    // @has issue_20727_4/reexport/trait.Index.html
+    // @has - '//*[@class="rust trait"]' 'trait Index<Idx> where Idx: ?Sized {'
+    // @has - '//*[@class="rust trait"]' 'type Output: ?Sized'
+    // @has - '//*[@class="rust trait"]' \
+    //        'fn index(&self, index: Idx) -> &Self::Output'
+    pub use issue_20727::Index;
+
+    // @has issue_20727_4/reexport/trait.IndexMut.html
+    // @has - '//*[@class="rust trait"]' \
+    //        'trait IndexMut<Idx>: Index<Idx> where Idx: ?Sized {'
+    // @has - '//*[@class="rust trait"]' \
+    //        'fn index_mut(&mut self, index: Idx) -> &mut Self::Output;'
+    pub use issue_20727::IndexMut;
+}
diff --git a/src/test/rustdoc/issue-20727.rs b/src/test/rustdoc/issue-20727.rs
new file mode 100644
index 00000000000..9903ad34b4c
--- /dev/null
+++ b/src/test/rustdoc/issue-20727.rs
@@ -0,0 +1,33 @@
+// Copyright 2015 The Rust Project Developers. See the COPYRIGHT
+// file at the top-level directory of this distribution and at
+// http://rust-lang.org/COPYRIGHT.
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+// aux-build:issue-20727.rs
+
+extern crate issue_20727;
+
+// @has issue_20727/trait.Deref.html
+pub trait Deref {
+    // @has - '//*[@class="rust trait"]' 'trait Deref {'
+    // @has - '//*[@class="rust trait"]' 'type Target: ?Sized;'
+    type Target: ?Sized;
+
+    // @has - '//*[@class="rust trait"]' \
+    //        "fn deref<'a>(&'a self) -> &'a Self::Target;"
+    fn deref<'a>(&'a self) -> &'a Self::Target;
+}
+
+// @has issue_20727/reexport/trait.Deref.html
+pub mod reexport {
+    // @has - '//*[@class="rust trait"]' 'trait Deref {'
+    // @has - '//*[@class="rust trait"]' 'type Target: ?Sized;'
+    // @has - '//*[@class="rust trait"]' \
+    //      "fn deref(&'a self) -> &'a Self::Target;"
+    pub use issue_20727::Deref;
+}