about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--src/librustdoc/clean/mod.rs179
-rw-r--r--src/librustdoc/core.rs6
-rw-r--r--src/librustdoc/html/format.rs146
-rw-r--r--src/librustdoc/html/markdown.rs20
-rw-r--r--src/librustdoc/html/render.rs378
-rw-r--r--src/librustdoc/html/sources.rs187
-rw-r--r--src/librustdoc/lib.rs1
-rw-r--r--src/librustdoc/visit_ast.rs8
-rw-r--r--src/librustdoc/visit_lib.rs24
9 files changed, 442 insertions, 507 deletions
diff --git a/src/librustdoc/clean/mod.rs b/src/librustdoc/clean/mod.rs
index ba792a413b3..ed82c522779 100644
--- a/src/librustdoc/clean/mod.rs
+++ b/src/librustdoc/clean/mod.rs
@@ -136,94 +136,88 @@ pub struct Crate {
     pub collapsed: bool,
 }
 
-impl Clean<Crate> for hir::Crate {
-    // note that self here is ignored in favor of `cx.tcx.hir().krate()` since
-    // that gets around tying self's lifetime to the '_ in cx.
-    fn clean(&self, cx: &DocContext<'_>) -> Crate {
-        use crate::visit_lib::LibEmbargoVisitor;
-
-        let v = crate::visit_ast::RustdocVisitor::new(&cx);
-        let module = v.visit(cx.tcx.hir().krate());
-
-        {
-            let mut r = cx.renderinfo.borrow_mut();
-            r.deref_trait_did = cx.tcx.lang_items().deref_trait();
-            r.deref_mut_trait_did = cx.tcx.lang_items().deref_mut_trait();
-            r.owned_box_did = cx.tcx.lang_items().owned_box();
-        }
-
-        let mut externs = Vec::new();
-        for &cnum in cx.tcx.crates().iter() {
-            externs.push((cnum, cnum.clean(cx)));
-            // Analyze doc-reachability for extern items
-            LibEmbargoVisitor::new(cx).visit_lib(cnum);
-        }
-        externs.sort_by(|&(a, _), &(b, _)| a.cmp(&b));
-
-        // Clean the crate, translating the entire libsyntax AST to one that is
-        // understood by rustdoc.
-        let mut module = module.clean(cx);
-        let mut masked_crates = FxHashSet::default();
-
-        match module.inner {
-            ModuleItem(ref module) => {
-                for it in &module.items {
-                    // `compiler_builtins` should be masked too, but we can't apply
-                    // `#[doc(masked)]` to the injected `extern crate` because it's unstable.
-                    if it.is_extern_crate()
-                        && (it.attrs.has_doc_flag(sym::masked)
-                            || cx.tcx.is_compiler_builtins(it.def_id.krate))
-                    {
-                        masked_crates.insert(it.def_id.krate);
-                    }
+pub fn krate(mut cx: &mut DocContext<'_>) -> Crate {
+    use crate::visit_lib::LibEmbargoVisitor;
+
+    let krate = cx.tcx.hir().krate();
+    let module = crate::visit_ast::RustdocVisitor::new(&mut cx).visit(krate);
+
+    let mut r = cx.renderinfo.get_mut();
+    r.deref_trait_did = cx.tcx.lang_items().deref_trait();
+    r.deref_mut_trait_did = cx.tcx.lang_items().deref_mut_trait();
+    r.owned_box_did = cx.tcx.lang_items().owned_box();
+
+    let mut externs = Vec::new();
+    for &cnum in cx.tcx.crates().iter() {
+        externs.push((cnum, cnum.clean(cx)));
+        // Analyze doc-reachability for extern items
+        LibEmbargoVisitor::new(&mut cx).visit_lib(cnum);
+    }
+    externs.sort_by(|&(a, _), &(b, _)| a.cmp(&b));
+
+    // Clean the crate, translating the entire libsyntax AST to one that is
+    // understood by rustdoc.
+    let mut module = module.clean(cx);
+    let mut masked_crates = FxHashSet::default();
+
+    match module.inner {
+        ModuleItem(ref module) => {
+            for it in &module.items {
+                // `compiler_builtins` should be masked too, but we can't apply
+                // `#[doc(masked)]` to the injected `extern crate` because it's unstable.
+                if it.is_extern_crate()
+                    && (it.attrs.has_doc_flag(sym::masked)
+                        || cx.tcx.is_compiler_builtins(it.def_id.krate))
+                {
+                    masked_crates.insert(it.def_id.krate);
                 }
             }
-            _ => unreachable!(),
         }
+        _ => unreachable!(),
+    }
 
-        let ExternalCrate { name, src, primitives, keywords, .. } = LOCAL_CRATE.clean(cx);
-        {
-            let m = match module.inner {
-                ModuleItem(ref mut m) => m,
-                _ => unreachable!(),
-            };
-            m.items.extend(primitives.iter().map(|&(def_id, prim, ref attrs)| {
-                Item {
-                    source: Span::empty(),
-                    name: Some(prim.to_url_str().to_string()),
-                    attrs: attrs.clone(),
-                    visibility: Some(Public),
-                    stability: get_stability(cx, def_id),
-                    deprecation: get_deprecation(cx, def_id),
-                    def_id,
-                    inner: PrimitiveItem(prim),
-                }
-            }));
-            m.items.extend(keywords.into_iter().map(|(def_id, kw, attrs)| {
-                Item {
-                    source: Span::empty(),
-                    name: Some(kw.clone()),
-                    attrs: attrs,
-                    visibility: Some(Public),
-                    stability: get_stability(cx, def_id),
-                    deprecation: get_deprecation(cx, def_id),
-                    def_id,
-                    inner: KeywordItem(kw),
-                }
-            }));
-        }
+    let ExternalCrate { name, src, primitives, keywords, .. } = LOCAL_CRATE.clean(cx);
+    {
+        let m = match module.inner {
+            ModuleItem(ref mut m) => m,
+            _ => unreachable!(),
+        };
+        m.items.extend(primitives.iter().map(|&(def_id, prim, ref attrs)| {
+            Item {
+                source: Span::empty(),
+                name: Some(prim.to_url_str().to_string()),
+                attrs: attrs.clone(),
+                visibility: Some(Public),
+                stability: get_stability(cx, def_id),
+                deprecation: get_deprecation(cx, def_id),
+                def_id,
+                inner: PrimitiveItem(prim),
+            }
+        }));
+        m.items.extend(keywords.into_iter().map(|(def_id, kw, attrs)| {
+            Item {
+                source: Span::empty(),
+                name: Some(kw.clone()),
+                attrs: attrs,
+                visibility: Some(Public),
+                stability: get_stability(cx, def_id),
+                deprecation: get_deprecation(cx, def_id),
+                def_id,
+                inner: KeywordItem(kw),
+            }
+        }));
+    }
 
-        Crate {
-            name,
-            version: None,
-            src,
-            module: Some(module),
-            externs,
-            primitives,
-            external_traits: cx.external_traits.clone(),
-            masked_crates,
-            collapsed: false,
-        }
+    Crate {
+        name,
+        version: None,
+        src,
+        module: Some(module),
+        externs,
+        primitives,
+        external_traits: cx.external_traits.clone(),
+        masked_crates,
+        collapsed: false,
     }
 }
 
@@ -572,23 +566,6 @@ pub enum ItemEnum {
 }
 
 impl ItemEnum {
-    pub fn generics(&self) -> Option<&Generics> {
-        Some(match *self {
-            ItemEnum::StructItem(ref s) => &s.generics,
-            ItemEnum::EnumItem(ref e) => &e.generics,
-            ItemEnum::FunctionItem(ref f) => &f.generics,
-            ItemEnum::TypedefItem(ref t, _) => &t.generics,
-            ItemEnum::OpaqueTyItem(ref t, _) => &t.generics,
-            ItemEnum::TraitItem(ref t) => &t.generics,
-            ItemEnum::ImplItem(ref i) => &i.generics,
-            ItemEnum::TyMethodItem(ref i) => &i.generics,
-            ItemEnum::MethodItem(ref i) => &i.generics,
-            ItemEnum::ForeignFunctionItem(ref f) => &f.generics,
-            ItemEnum::TraitAliasItem(ref ta) => &ta.generics,
-            _ => return None,
-        })
-    }
-
     pub fn is_associated(&self) -> bool {
         match *self {
             ItemEnum::TypedefItem(_, _) |
@@ -1541,8 +1518,6 @@ impl Clean<GenericParamDef> for ty::GenericParamDef {
                 (self.name.to_string(), GenericParamDefKind::Lifetime)
             }
             ty::GenericParamDefKind::Type { has_default, synthetic, .. } => {
-                cx.renderinfo.borrow_mut().external_param_names
-                             .insert(self.def_id, self.name.clean(cx));
                 let default = if has_default {
                     Some(cx.tcx.type_of(self.def_id).clean(cx))
                 } else {
diff --git a/src/librustdoc/core.rs b/src/librustdoc/core.rs
index 04e69613d4b..45ebaa11506 100644
--- a/src/librustdoc/core.rs
+++ b/src/librustdoc/core.rs
@@ -30,7 +30,7 @@ use std::rc::Rc;
 
 use crate::config::{Options as RustdocOptions, RenderOptions};
 use crate::clean;
-use crate::clean::{Clean, MAX_DEF_ID, AttributesExt};
+use crate::clean::{MAX_DEF_ID, AttributesExt};
 use crate::html::render::RenderInfo;
 
 use crate::passes;
@@ -363,7 +363,7 @@ pub fn run_core(options: RustdocOptions) -> (clean::Crate, RenderInfo, RenderOpt
             let mut renderinfo = RenderInfo::default();
             renderinfo.access_levels = access_levels;
 
-            let ctxt = DocContext {
+            let mut ctxt = DocContext {
                 tcx,
                 resolver,
                 cstore: compiler.cstore().clone(),
@@ -383,7 +383,7 @@ pub fn run_core(options: RustdocOptions) -> (clean::Crate, RenderInfo, RenderOpt
             };
             debug!("crate: {:?}", tcx.hir().krate());
 
-            let mut krate = tcx.hir().krate().clean(&ctxt);
+            let mut krate = clean::krate(&mut ctxt);
 
             fn report_deprecated_attr(name: &str, diag: &errors::Handler) {
                 let mut msg = diag.struct_warn(&format!("the `#![doc({})]` attribute is \
diff --git a/src/librustdoc/html/format.rs b/src/librustdoc/html/format.rs
index 9c22837bdae..313734e3fdd 100644
--- a/src/librustdoc/html/format.rs
+++ b/src/librustdoc/html/format.rs
@@ -6,6 +6,7 @@
 //! them in the future to instead emit any format desired.
 
 use std::borrow::Cow;
+use std::cell::Cell;
 use std::fmt;
 
 use rustc::hir::def_id::DefId;
@@ -15,7 +16,7 @@ use rustc::hir;
 
 use crate::clean::{self, PrimitiveType};
 use crate::html::item_type::ItemType;
-use crate::html::render::{self, cache, CURRENT_LOCATION_KEY};
+use crate::html::render::{self, cache, CURRENT_DEPTH};
 
 /// Helper to render an optional visibility with a space after it (if the
 /// visibility is preset)
@@ -36,13 +37,8 @@ pub struct AsyncSpace(pub hir::IsAsync);
 /// Similar to VisSpace, but used for mutability
 #[derive(Copy, Clone)]
 pub struct MutableSpace(pub clean::Mutability);
-/// Similar to VisSpace, but used for mutability
-#[derive(Copy, Clone)]
-pub struct RawMutableSpace(pub clean::Mutability);
 /// Wrapper struct for emitting type parameter bounds.
 pub struct GenericBounds<'a>(pub &'a [clean::GenericBound]);
-/// Wrapper struct for emitting a comma-separated list of items
-pub struct CommaSep<'a, T>(pub &'a [T]);
 pub struct AbiSpace(pub Abi);
 pub struct DefaultSpace(pub bool);
 
@@ -71,11 +67,6 @@ pub struct WhereClause<'a>{
     pub end_newline: bool,
 }
 
-pub struct HRef<'a> {
-    pub did: DefId,
-    pub text: &'a str,
-}
-
 impl<'a> VisSpace<'a> {
     pub fn get(self) -> &'a Option<clean::Visibility> {
         let VisSpace(v) = self; v
@@ -94,14 +85,14 @@ impl ConstnessSpace {
     }
 }
 
-impl<'a, T: fmt::Display> fmt::Display for CommaSep<'a, T> {
-    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        for (i, item) in self.0.iter().enumerate() {
+fn comma_sep<T: fmt::Display>(items: &[T]) -> impl fmt::Display + '_ {
+    display_fn(move |f| {
+        for (i, item) in items.iter().enumerate() {
             if i != 0 { write!(f, ", ")?; }
             fmt::Display::fmt(item, f)?;
         }
         Ok(())
-    }
+    })
 }
 
 impl<'a> fmt::Display for GenericBounds<'a> {
@@ -168,9 +159,9 @@ impl fmt::Display for clean::Generics {
             return Ok(());
         }
         if f.alternate() {
-            write!(f, "<{:#}>", CommaSep(&real_params))
+            write!(f, "<{:#}>", comma_sep(&real_params))
         } else {
-            write!(f, "&lt;{}&gt;", CommaSep(&real_params))
+            write!(f, "&lt;{}&gt;", comma_sep(&real_params))
         }
     }
 }
@@ -268,9 +259,9 @@ impl fmt::Display for clean::PolyTrait {
     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
         if !self.generic_params.is_empty() {
             if f.alternate() {
-                write!(f, "for<{:#}> ", CommaSep(&self.generic_params))?;
+                write!(f, "for<{:#}> ", comma_sep(&self.generic_params))?;
             } else {
-                write!(f, "for&lt;{}&gt; ", CommaSep(&self.generic_params))?;
+                write!(f, "for&lt;{}&gt; ", comma_sep(&self.generic_params))?;
             }
         }
         if f.alternate() {
@@ -407,16 +398,16 @@ pub fn href(did: DefId) -> Option<(String, ItemType, Vec<String>)> {
         return None
     }
 
-    let loc = CURRENT_LOCATION_KEY.with(|l| l.borrow().clone());
+    let depth = CURRENT_DEPTH.with(|l| l.get());
     let (fqp, shortty, mut url) = match cache.paths.get(&did) {
         Some(&(ref fqp, shortty)) => {
-            (fqp, shortty, "../".repeat(loc.len()))
+            (fqp, shortty, "../".repeat(depth))
         }
         None => {
             let &(ref fqp, shortty) = cache.external_paths.get(&did)?;
             (fqp, shortty, match cache.extern_locations[&did.krate] {
                 (.., render::Remote(ref s)) => s.to_string(),
-                (.., render::Local) => "../".repeat(loc.len()),
+                (.., render::Local) => "../".repeat(depth),
                 (.., render::Unknown) => return None,
             })
         }
@@ -452,19 +443,18 @@ fn resolved_path(w: &mut fmt::Formatter<'_>, did: DefId, path: &clean::Path,
         }
     }
     if w.alternate() {
-        write!(w, "{:#}{:#}", HRef::new(did, &last.name), last.args)?;
+        write!(w, "{}{:#}", &last.name, last.args)?;
     } else {
         let path = if use_absolute {
-            match href(did) {
-                Some((_, _, fqp)) => {
-                    format!("{}::{}",
-                            fqp[..fqp.len() - 1].join("::"),
-                            HRef::new(did, fqp.last().unwrap()))
-                }
-                None => HRef::new(did, &last.name).to_string(),
+            if let Some((_, _, fqp)) = href(did) {
+                format!("{}::{}",
+                        fqp[..fqp.len() - 1].join("::"),
+                        anchor(did, fqp.last().unwrap()))
+            } else {
+                last.name.to_string()
             }
         } else {
-            HRef::new(did, &last.name).to_string()
+            anchor(did, &last.name).to_string()
         };
         write!(w, "{}{}", path, last.args)?;
     }
@@ -479,7 +469,7 @@ fn primitive_link(f: &mut fmt::Formatter<'_>,
     if !f.alternate() {
         match m.primitive_locations.get(&prim) {
             Some(&def_id) if def_id.is_local() => {
-                let len = CURRENT_LOCATION_KEY.with(|s| s.borrow().len());
+                let len = CURRENT_DEPTH.with(|s| s.get());
                 let len = if len == 0 {0} else {len - 1};
                 write!(f, "<a class=\"primitive\" href=\"{}primitive.{}.html\">",
                        "../".repeat(len),
@@ -492,7 +482,7 @@ fn primitive_link(f: &mut fmt::Formatter<'_>,
                         Some((cname, s.to_string()))
                     }
                     (ref cname, _, render::Local) => {
-                        let len = CURRENT_LOCATION_KEY.with(|s| s.borrow().len());
+                        let len = CURRENT_DEPTH.with(|s| s.get());
                         Some((cname, "../".repeat(len)))
                     }
                     (.., render::Unknown) => None,
@@ -516,38 +506,30 @@ fn primitive_link(f: &mut fmt::Formatter<'_>,
 }
 
 /// Helper to render type parameters
-fn tybounds(w: &mut fmt::Formatter<'_>,
-            param_names: &Option<Vec<clean::GenericBound>>) -> fmt::Result {
-    match *param_names {
-        Some(ref params) => {
-            for param in params {
-                write!(w, " + ")?;
-                fmt::Display::fmt(param, w)?;
+fn tybounds(param_names: &Option<Vec<clean::GenericBound>>) -> impl fmt::Display + '_ {
+    display_fn(move |f| {
+        match *param_names {
+            Some(ref params) => {
+                for param in params {
+                    write!(f, " + ")?;
+                    fmt::Display::fmt(param, f)?;
+                }
+                Ok(())
             }
-            Ok(())
+            None => Ok(())
         }
-        None => Ok(())
-    }
-}
-
-impl<'a> HRef<'a> {
-    pub fn new(did: DefId, text: &'a str) -> HRef<'a> {
-        HRef { did: did, text: text }
-    }
+    })
 }
 
-impl<'a> fmt::Display for HRef<'a> {
-    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        match href(self.did) {
-            Some((url, shortty, fqp)) => if !f.alternate() {
-                write!(f, "<a class=\"{}\" href=\"{}\" title=\"{} {}\">{}</a>",
-                       shortty, url, shortty, fqp.join("::"), self.text)
-            } else {
-                write!(f, "{}", self.text)
-            },
-            _ => write!(f, "{}", self.text),
+pub fn anchor(did: DefId, text: &str) -> impl fmt::Display + '_ {
+    display_fn(move |f| {
+        if let Some((url, short_ty, fqp)) = href(did) {
+            write!(f, r#"<a class="{}" href="{}" title="{} {}">{}</a>"#,
+                short_ty, url, short_ty, fqp.join("::"), text)
+        } else {
+            write!(f, "{}", text)
         }
-    }
+    })
 }
 
 fn fmt_type(t: &clean::Type, f: &mut fmt::Formatter<'_>, use_absolute: bool) -> fmt::Result {
@@ -561,7 +543,7 @@ fn fmt_type(t: &clean::Type, f: &mut fmt::Formatter<'_>, use_absolute: bool) ->
             }
             // Paths like `T::Output` and `Self::Output` should be rendered with all segments.
             resolved_path(f, did, path, is_generic, use_absolute)?;
-            tybounds(f, param_names)
+            fmt::Display::fmt(&tybounds(param_names), f)
         }
         clean::Infer => write!(f, "_"),
         clean::Primitive(prim) => primitive_link(f, prim, prim.as_str()),
@@ -570,12 +552,12 @@ fn fmt_type(t: &clean::Type, f: &mut fmt::Formatter<'_>, use_absolute: bool) ->
                 write!(f, "{}{:#}fn{:#}{:#}",
                        UnsafetySpace(decl.unsafety),
                        AbiSpace(decl.abi),
-                       CommaSep(&decl.generic_params),
+                       comma_sep(&decl.generic_params),
                        decl.decl)
             } else {
                 write!(f, "{}{}", UnsafetySpace(decl.unsafety), AbiSpace(decl.abi))?;
                 primitive_link(f, PrimitiveType::Fn, "fn")?;
-                write!(f, "{}{}", CommaSep(&decl.generic_params), decl.decl)
+                write!(f, "{}{}", comma_sep(&decl.generic_params), decl.decl)
             }
         }
         clean::Tuple(ref typs) => {
@@ -589,7 +571,7 @@ fn fmt_type(t: &clean::Type, f: &mut fmt::Formatter<'_>, use_absolute: bool) ->
                 }
                 many => {
                     primitive_link(f, PrimitiveType::Tuple, "(")?;
-                    fmt::Display::fmt(&CommaSep(many), f)?;
+                    fmt::Display::fmt(&comma_sep(many), f)?;
                     primitive_link(f, PrimitiveType::Tuple, ")")
                 }
             }
@@ -607,19 +589,22 @@ fn fmt_type(t: &clean::Type, f: &mut fmt::Formatter<'_>, use_absolute: bool) ->
         clean::Never => primitive_link(f, PrimitiveType::Never, "!"),
         clean::CVarArgs => primitive_link(f, PrimitiveType::CVarArgs, "..."),
         clean::RawPointer(m, ref t) => {
+            let m = match m {
+                clean::Immutable => "const",
+                clean::Mutable => "mut",
+            };
             match **t {
                 clean::Generic(_) | clean::ResolvedPath {is_generic: true, ..} => {
                     if f.alternate() {
                         primitive_link(f, clean::PrimitiveType::RawPointer,
-                                       &format!("*{}{:#}", RawMutableSpace(m), t))
+                                       &format!("*{} {:#}", m, t))
                     } else {
                         primitive_link(f, clean::PrimitiveType::RawPointer,
-                                       &format!("*{}{}", RawMutableSpace(m), t))
+                                       &format!("*{} {}", m, t))
                     }
                 }
                 _ => {
-                    primitive_link(f, clean::PrimitiveType::RawPointer,
-                                   &format!("*{}", RawMutableSpace(m)))?;
+                    primitive_link(f, clean::PrimitiveType::RawPointer, &format!("*{} ", m))?;
                     fmt::Display::fmt(t, f)
                 }
             }
@@ -1047,15 +1032,6 @@ impl fmt::Display for MutableSpace {
     }
 }
 
-impl fmt::Display for RawMutableSpace {
-    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        match *self {
-            RawMutableSpace(clean::Immutable) => write!(f, "const "),
-            RawMutableSpace(clean::Mutable) => write!(f, "mut "),
-        }
-    }
-}
-
 impl fmt::Display for AbiSpace {
     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
         let quot = if f.alternate() { "\"" } else { "&quot;" };
@@ -1075,3 +1051,19 @@ impl fmt::Display for DefaultSpace {
         }
     }
 }
+
+crate fn display_fn(
+    f: impl FnOnce(&mut fmt::Formatter<'_>) -> fmt::Result,
+) -> impl fmt::Display {
+    WithFormatter(Cell::new(Some(f)))
+}
+
+struct WithFormatter<F>(Cell<Option<F>>);
+
+impl<F> fmt::Display for WithFormatter<F>
+    where F: FnOnce(&mut fmt::Formatter<'_>) -> fmt::Result,
+{
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        (self.0.take()).unwrap()(f)
+    }
+}
diff --git a/src/librustdoc/html/markdown.rs b/src/librustdoc/html/markdown.rs
index 5a7deb651b0..74413a7f905 100644
--- a/src/librustdoc/html/markdown.rs
+++ b/src/librustdoc/html/markdown.rs
@@ -785,10 +785,6 @@ impl MarkdownSummaryLine<'_> {
 }
 
 pub fn plain_summary_line(md: &str) -> String {
-    plain_summary_line_full(md, false)
-}
-
-pub fn plain_summary_line_full(md: &str, limit_length: bool) -> String {
     struct ParserWrapper<'a> {
         inner: Parser<'a>,
         is_in: isize,
@@ -834,21 +830,7 @@ pub fn plain_summary_line_full(md: &str, limit_length: bool) -> String {
             s.push_str(&t);
         }
     }
-    if limit_length && s.chars().count() > 60 {
-        let mut len = 0;
-        let mut ret = s.split_whitespace()
-                       .take_while(|p| {
-                           // + 1 for the added character after the word.
-                           len += p.chars().count() + 1;
-                           len < 60
-                       })
-                       .collect::<Vec<_>>()
-                       .join(" ");
-        ret.push('…');
-        ret
-    } else {
-        s
-    }
+    s
 }
 
 pub fn markdown_links(md: &str) -> Vec<(String, Option<Range<usize>>)> {
diff --git a/src/librustdoc/html/render.rs b/src/librustdoc/html/render.rs
index b64e74468e6..2a4a2fcea58 100644
--- a/src/librustdoc/html/render.rs
+++ b/src/librustdoc/html/render.rs
@@ -28,7 +28,7 @@
 pub use self::ExternalLocation::*;
 
 use std::borrow::Cow;
-use std::cell::RefCell;
+use std::cell::{Cell, RefCell};
 use std::cmp::Ordering;
 use std::collections::{BTreeMap, VecDeque};
 use std::default::Default;
@@ -72,6 +72,7 @@ use crate::html::format::fmt_impl_for_trait_page;
 use crate::html::item_type::ItemType;
 use crate::html::markdown::{self, Markdown, MarkdownHtml, MarkdownSummaryLine, ErrorCodes, IdMap};
 use crate::html::{highlight, layout, static_files};
+use crate::html::sources;
 
 use minifier;
 
@@ -173,7 +174,7 @@ struct Context {
     playground: Option<markdown::Playground>,
 }
 
-struct SharedContext {
+crate struct SharedContext {
     /// The path to the crate root source minus the file name.
     /// Used for simplifying paths to the highlighted source code files.
     pub src_root: PathBuf,
@@ -218,7 +219,7 @@ struct SharedContext {
 }
 
 impl SharedContext {
-    fn ensure_dir(&self, dst: &Path) -> Result<(), Error> {
+    crate fn ensure_dir(&self, dst: &Path) -> Result<(), Error> {
         let mut dirs = self.created_dirs.borrow_mut();
         if !dirs.contains(dst) {
             try_err!(self.fs.create_dir_all(dst), dst);
@@ -281,11 +282,6 @@ impl Impl {
 /// rendering threads.
 #[derive(Default)]
 pub struct Cache {
-    /// Mapping of typaram ids to the name of the type parameter. This is used
-    /// when pretty-printing a type (so pretty-printing doesn't have to
-    /// painfully maintain a context like this)
-    pub param_names: FxHashMap<DefId, String>,
-
     /// Maps a type ID to all known implementations for that type. This is only
     /// recognized for intra-crate `ResolvedPath` types, and is used to print
     /// out extra documentation on the page of an enum/struct.
@@ -381,7 +377,6 @@ pub struct Cache {
 pub struct RenderInfo {
     pub inlined: FxHashSet<DefId>,
     pub external_paths: crate::core::ExternalPaths,
-    pub external_param_names: FxHashMap<DefId, String>,
     pub exact_paths: FxHashMap<DefId, Vec<String>>,
     pub access_levels: AccessLevels<DefId>,
     pub deref_trait_did: Option<DefId>,
@@ -389,18 +384,6 @@ pub struct RenderInfo {
     pub owned_box_did: Option<DefId>,
 }
 
-/// Helper struct to render all source code to HTML pages
-struct SourceCollector<'a> {
-    scx: &'a mut SharedContext,
-
-    /// Root destination to place all HTML output into
-    dst: PathBuf,
-}
-
-/// Wrapper struct to render the source code of a file. This will do things like
-/// adding line numbers to the left-hand side.
-struct Source<'a>(&'a str);
-
 // Helper structs for rendering items/sidebars and carrying along contextual
 // information
 
@@ -496,7 +479,7 @@ impl ToJson for IndexItemFunctionType {
 }
 
 thread_local!(static CACHE_KEY: RefCell<Arc<Cache>> = Default::default());
-thread_local!(pub static CURRENT_LOCATION_KEY: RefCell<Vec<String>> = RefCell::new(Vec::new()));
+thread_local!(pub static CURRENT_DEPTH: Cell<usize> = Cell::new(0));
 
 pub fn initial_ids() -> Vec<String> {
     [
@@ -612,7 +595,7 @@ pub fn run(mut krate: clean::Crate,
     }
     let dst = output;
     scx.ensure_dir(&dst)?;
-    krate = render_sources(&dst, &mut scx, krate)?;
+    krate = sources::render(&dst, &mut scx, krate)?;
     let mut cx = Context {
         current: Vec::new(),
         dst,
@@ -628,7 +611,6 @@ pub fn run(mut krate: clean::Crate,
     let RenderInfo {
         inlined: _,
         external_paths,
-        external_param_names,
         exact_paths,
         access_levels,
         deref_trait_did,
@@ -662,7 +644,6 @@ pub fn run(mut krate: clean::Crate,
         deref_mut_trait_did,
         owned_box_did,
         masked_crates: mem::take(&mut krate.masked_crates),
-        param_names: external_param_names,
         aliases: Default::default(),
     };
 
@@ -714,7 +695,7 @@ pub fn run(mut krate: clean::Crate,
     // for future parallelization opportunities
     let cache = Arc::new(cache);
     CACHE_KEY.with(|v| *v.borrow_mut() = cache.clone());
-    CURRENT_LOCATION_KEY.with(|s| s.borrow_mut().clear());
+    CURRENT_DEPTH.with(|s| s.set(0));
 
     // Write shared runs within a flock; disable thread dispatching of IO temporarily.
     Arc::get_mut(&mut cx.shared).unwrap().fs.set_sync_only(true);
@@ -751,7 +732,7 @@ fn build_index(krate: &clean::Crate, cache: &mut Cache) -> String {
                 ty: item.type_(),
                 name: item.name.clone().unwrap(),
                 path: fqp[..fqp.len() - 1].join("::"),
-                desc: plain_summary_line_short(item.doc_value()),
+                desc: shorten(plain_summary_line(item.doc_value())),
                 parent: Some(did),
                 parent_idx: None,
                 search_type: get_index_search_type(&item),
@@ -789,7 +770,7 @@ fn build_index(krate: &clean::Crate, cache: &mut Cache) -> String {
     }
 
     let crate_doc = krate.module.as_ref().map(|module| {
-        plain_summary_line_short(module.doc_value())
+        shorten(plain_summary_line(module.doc_value()))
     }).unwrap_or(String::new());
 
     let mut crate_data = BTreeMap::new();
@@ -1293,18 +1274,6 @@ themePicker.onblur = handleThemeButtonsBlur;
     Ok(())
 }
 
-fn render_sources(dst: &Path, scx: &mut SharedContext,
-                  krate: clean::Crate) -> Result<clean::Crate, Error> {
-    info!("emitting source files");
-    let dst = dst.join("src").join(&krate.name);
-    scx.ensure_dir(&dst)?;
-    let mut folder = SourceCollector {
-        dst,
-        scx,
-    };
-    Ok(folder.fold_crate(krate))
-}
-
 fn write_minify(fs:&DocFS, dst: PathBuf, contents: &str, enable_minification: bool
                 ) -> Result<(), Error> {
     if enable_minification {
@@ -1384,33 +1353,6 @@ fn write_minify_replacer<W: Write>(
     }
 }
 
-/// Takes a path to a source file and cleans the path to it. This canonicalizes
-/// things like ".." to components which preserve the "top down" hierarchy of a
-/// static HTML tree. Each component in the cleaned path will be passed as an
-/// argument to `f`. The very last component of the path (ie the file name) will
-/// be passed to `f` if `keep_filename` is true, and ignored otherwise.
-fn clean_srcpath<F>(src_root: &Path, p: &Path, keep_filename: bool, mut f: F)
-where
-    F: FnMut(&OsStr),
-{
-    // make it relative, if possible
-    let p = p.strip_prefix(src_root).unwrap_or(p);
-
-    let mut iter = p.components().peekable();
-
-    while let Some(c) = iter.next() {
-        if !keep_filename && iter.peek().is_none() {
-            break;
-        }
-
-        match c {
-            Component::ParentDir => f("up".as_ref()),
-            Component::Normal(c) => f(c),
-            _ => continue,
-        }
-    }
-}
-
 /// Attempts to find where an external crate is located, given that we're
 /// rendering in to the specified source destination.
 fn extern_location(e: &clean::ExternalCrate, extern_url: Option<&str>, dst: &Path)
@@ -1444,102 +1386,6 @@ fn extern_location(e: &clean::ExternalCrate, extern_url: Option<&str>, dst: &Pat
     }).next().unwrap_or(Unknown) // Well, at least we tried.
 }
 
-impl<'a> DocFolder for SourceCollector<'a> {
-    fn fold_item(&mut self, item: clean::Item) -> Option<clean::Item> {
-        // If we're including source files, and we haven't seen this file yet,
-        // then we need to render it out to the filesystem.
-        if self.scx.include_sources
-            // skip all invalid or macro spans
-            && item.source.filename.is_real()
-            // skip non-local items
-            && item.def_id.is_local() {
-
-            // If it turns out that we couldn't read this file, then we probably
-            // can't read any of the files (generating html output from json or
-            // something like that), so just don't include sources for the
-            // entire crate. The other option is maintaining this mapping on a
-            // per-file basis, but that's probably not worth it...
-            self.scx
-                .include_sources = match self.emit_source(&item.source.filename) {
-                Ok(()) => true,
-                Err(e) => {
-                    println!("warning: source code was requested to be rendered, \
-                              but processing `{}` had an error: {}",
-                             item.source.filename, e);
-                    println!("         skipping rendering of source code");
-                    false
-                }
-            };
-        }
-        self.fold_item_recur(item)
-    }
-}
-
-impl<'a> SourceCollector<'a> {
-    /// Renders the given filename into its corresponding HTML source file.
-    fn emit_source(&mut self, filename: &FileName) -> Result<(), Error> {
-        let p = match *filename {
-            FileName::Real(ref file) => file,
-            _ => return Ok(()),
-        };
-        if self.scx.local_sources.contains_key(&**p) {
-            // We've already emitted this source
-            return Ok(());
-        }
-
-        let contents = try_err!(fs::read_to_string(&p), &p);
-
-        // Remove the utf-8 BOM if any
-        let contents = if contents.starts_with("\u{feff}") {
-            &contents[3..]
-        } else {
-            &contents[..]
-        };
-
-        // Create the intermediate directories
-        let mut cur = self.dst.clone();
-        let mut root_path = String::from("../../");
-        let mut href = String::new();
-        clean_srcpath(&self.scx.src_root, &p, false, |component| {
-            cur.push(component);
-            root_path.push_str("../");
-            href.push_str(&component.to_string_lossy());
-            href.push('/');
-        });
-        self.scx.ensure_dir(&cur)?;
-        let mut fname = p.file_name()
-                         .expect("source has no filename")
-                         .to_os_string();
-        fname.push(".html");
-        cur.push(&fname);
-        href.push_str(&fname.to_string_lossy());
-
-        let mut v = Vec::new();
-        let title = format!("{} -- source", cur.file_name().expect("failed to get file name")
-                                               .to_string_lossy());
-        let desc = format!("Source to the Rust file `{}`.", filename);
-        let page = layout::Page {
-            title: &title,
-            css_class: "source",
-            root_path: &root_path,
-            static_root_path: self.scx.static_root_path.as_deref(),
-            description: &desc,
-            keywords: BASIC_KEYWORDS,
-            resource_suffix: &self.scx.resource_suffix,
-            extra_scripts: &[&format!("source-files{}", self.scx.resource_suffix)],
-            static_extra_scripts: &[&format!("source-script{}", self.scx.resource_suffix)],
-        };
-        try_err!(layout::render(&mut v, &self.scx.layout,
-                       &page, &(""), &Source(contents),
-                       self.scx.css_file_extension.is_some(),
-                       &self.scx.themes,
-                       self.scx.generate_search_filter), &cur);
-        self.scx.fs.write(&cur, &v)?;
-        self.scx.local_sources.insert(p.clone(), href);
-        Ok(())
-    }
-}
-
 impl DocFolder for Cache {
     fn fold_item(&mut self, item: clean::Item) -> Option<clean::Item> {
         if item.def_id.is_local() {
@@ -1565,12 +1411,6 @@ impl DocFolder for Cache {
             }
         }
 
-        // Register any generics to their corresponding string. This is used
-        // when pretty-printing types.
-        if let Some(generics) = item.inner.generics() {
-            self.generics(generics);
-        }
-
         // Propagate a trait method's documentation to all implementors of the
         // trait.
         if let clean::TraitItem(ref t) = item.inner {
@@ -1642,7 +1482,7 @@ impl DocFolder for Cache {
                             ty: item.type_(),
                             name: s.to_string(),
                             path: path.join("::"),
-                            desc: plain_summary_line_short(item.doc_value()),
+                            desc: shorten(plain_summary_line(item.doc_value())),
                             parent,
                             parent_idx: None,
                             search_type: get_index_search_type(&item),
@@ -1803,18 +1643,6 @@ impl DocFolder for Cache {
 }
 
 impl Cache {
-    fn generics(&mut self, generics: &clean::Generics) {
-        for param in &generics.params {
-            match param.kind {
-                clean::GenericParamDefKind::Lifetime => {}
-                clean::GenericParamDefKind::Type { did, .. } |
-                clean::GenericParamDefKind::Const { did, .. } => {
-                    self.param_names.insert(did, param.name.clone());
-                }
-            }
-        }
-    }
-
     fn add_aliases(&mut self, item: &clean::Item) {
         if item.def_id.index == CRATE_DEF_INDEX {
             return
@@ -1836,7 +1664,7 @@ impl Cache {
                                 ty: item.type_(),
                                 name: item_name.to_string(),
                                 path: path.clone(),
-                                desc: plain_summary_line_short(item.doc_value()),
+                                desc: shorten(plain_summary_line(item.doc_value())),
                                 parent: None,
                                 parent_idx: None,
                                 search_type: get_index_search_type(&item),
@@ -2057,31 +1885,6 @@ impl Context {
         "../".repeat(self.current.len())
     }
 
-    /// Recurse in the directory structure and change the "root path" to make
-    /// sure it always points to the top (relatively).
-    fn recurse<T, F>(&mut self, s: String, f: F) -> T where
-        F: FnOnce(&mut Context) -> T,
-    {
-        if s.is_empty() {
-            panic!("Unexpected empty destination: {:?}", self.current);
-        }
-        let prev = self.dst.clone();
-        self.dst.push(&s);
-        self.current.push(s);
-
-        info!("Recursing into {}", self.dst.display());
-
-        let ret = f(self);
-
-        info!("Recursed; leaving {}", self.dst.display());
-
-        // Go back to where we were at
-        self.dst = prev;
-        self.current.pop().unwrap();
-
-        ret
-    }
-
     /// Main method for rendering a crate.
     ///
     /// This currently isn't parallelized, but it'd be pretty easy to add
@@ -2175,8 +1978,8 @@ impl Context {
                    -> io::Result<()> {
         // A little unfortunate that this is done like this, but it sure
         // does make formatting *a lot* nicer.
-        CURRENT_LOCATION_KEY.with(|slot| {
-            *slot.borrow_mut() = self.current.clone();
+        CURRENT_DEPTH.with(|slot| {
+            slot.set(self.current.len());
         });
 
         let mut title = if it.is_primitive() || it.is_keyword() {
@@ -2262,42 +2065,50 @@ impl Context {
             // modules are special because they add a namespace. We also need to
             // recurse into the items of the module as well.
             let name = item.name.as_ref().unwrap().to_string();
-            let mut item = Some(item);
-            let scx = self.shared.clone();
-            self.recurse(name, |this| {
-                let item = item.take().unwrap();
-
-                let mut buf = Vec::new();
-                this.render_item(&mut buf, &item, false).unwrap();
-                // buf will be empty if the module is stripped and there is no redirect for it
-                if !buf.is_empty() {
-                    this.shared.ensure_dir(&this.dst)?;
-                    let joint_dst = this.dst.join("index.html");
-                    scx.fs.write(&joint_dst, buf)?;
-                }
+            let scx = &self.shared;
+            if name.is_empty() {
+                panic!("Unexpected empty destination: {:?}", self.current);
+            }
+            let prev = self.dst.clone();
+            self.dst.push(&name);
+            self.current.push(name);
 
-                let m = match item.inner {
-                    clean::StrippedItem(box clean::ModuleItem(m)) |
-                    clean::ModuleItem(m) => m,
-                    _ => unreachable!()
-                };
+            info!("Recursing into {}", self.dst.display());
 
-                // Render sidebar-items.js used throughout this module.
-                if !this.render_redirect_pages {
-                    let items = this.build_sidebar_items(&m);
-                    let js_dst = this.dst.join("sidebar-items.js");
-                    let mut v = Vec::new();
-                    try_err!(write!(&mut v, "initSidebarItems({});",
-                                    as_json(&items)), &js_dst);
-                    scx.fs.write(&js_dst, &v)?;
-                }
+            let mut buf = Vec::new();
+            self.render_item(&mut buf, &item, false).unwrap();
+            // buf will be empty if the module is stripped and there is no redirect for it
+            if !buf.is_empty() {
+                self.shared.ensure_dir(&self.dst)?;
+                let joint_dst = self.dst.join("index.html");
+                scx.fs.write(&joint_dst, buf)?;
+            }
 
-                for item in m.items {
-                    f(this, item);
-                }
+            let m = match item.inner {
+                clean::StrippedItem(box clean::ModuleItem(m)) |
+                clean::ModuleItem(m) => m,
+                _ => unreachable!()
+            };
+
+            // Render sidebar-items.js used throughout this module.
+            if !self.render_redirect_pages {
+                let items = self.build_sidebar_items(&m);
+                let js_dst = self.dst.join("sidebar-items.js");
+                let mut v = Vec::new();
+                try_err!(write!(&mut v, "initSidebarItems({});",
+                                as_json(&items)), &js_dst);
+                scx.fs.write(&js_dst, &v)?;
+            }
+
+            for item in m.items {
+                f(self, item);
+            }
 
-                Ok(())
-            })?;
+            info!("Recursed; leaving {}", self.dst.display());
+
+            // Go back to where we were at
+            self.dst = prev;
+            self.current.pop().unwrap();
         } else if item.name.is_some() {
             let mut buf = Vec::new();
             self.render_item(&mut buf, &item, true).unwrap();
@@ -2399,7 +2210,7 @@ impl<'a> Item<'a> {
                 (_, _, Unknown) => return None,
             };
 
-            clean_srcpath(&src_root, file, false, |component| {
+            sources::clean_path(&src_root, file, false, |component| {
                 path.push_str(&component.to_string_lossy());
                 path.push('/');
             });
@@ -2549,29 +2360,39 @@ fn full_path(cx: &Context, item: &clean::Item) -> String {
     s
 }
 
-fn shorter(s: Option<&str>) -> String {
-    match s {
-        Some(s) => s.lines()
-            .skip_while(|s| s.chars().all(|c| c.is_whitespace()))
-            .take_while(|line|{
-            (*line).chars().any(|chr|{
-                !chr.is_whitespace()
-            })
-        }).collect::<Vec<_>>().join("\n"),
-        None => String::new()
-    }
-}
-
 #[inline]
 fn plain_summary_line(s: Option<&str>) -> String {
-    let line = shorter(s).replace("\n", " ");
-    markdown::plain_summary_line_full(&line[..], false)
-}
-
-#[inline]
-fn plain_summary_line_short(s: Option<&str>) -> String {
-    let line = shorter(s).replace("\n", " ");
-    markdown::plain_summary_line_full(&line[..], true)
+    let s = s.unwrap_or("");
+    // This essentially gets the first paragraph of text in one line.
+    let mut line = s.lines()
+        .skip_while(|line| line.chars().all(|c| c.is_whitespace()))
+        .take_while(|line| line.chars().any(|c| !c.is_whitespace()))
+        .fold(String::new(), |mut acc, line| {
+            acc.push_str(line);
+            acc.push(' ');
+            acc
+        });
+    // remove final whitespace
+    line.pop();
+    markdown::plain_summary_line(&line[..])
+}
+
+fn shorten(s: String) -> String {
+    if s.chars().count() > 60 {
+        let mut len = 0;
+        let mut ret = s.split_whitespace()
+                        .take_while(|p| {
+                            // + 1 for the added character after the word.
+                            len += p.chars().count() + 1;
+                            len < 60
+                        })
+                        .collect::<Vec<_>>()
+                        .join(" ");
+        ret.push('…');
+        ret
+    } else {
+        s
+    }
 }
 
 fn document(w: &mut fmt::Formatter<'_>, cx: &Context, item: &clean::Item) -> fmt::Result {
@@ -2816,19 +2637,19 @@ fn item_module(w: &mut fmt::Formatter<'_>, cx: &Context,
 
         match myitem.inner {
             clean::ExternCrateItem(ref name, ref src) => {
-                use crate::html::format::HRef;
+                use crate::html::format::anchor;
 
                 match *src {
                     Some(ref src) => {
                         write!(w, "<tr><td><code>{}extern crate {} as {};",
                                VisSpace(&myitem.visibility),
-                               HRef::new(myitem.def_id, src),
+                               anchor(myitem.def_id, src),
                                name)?
                     }
                     None => {
                         write!(w, "<tr><td><code>{}extern crate {};",
                                VisSpace(&myitem.visibility),
-                               HRef::new(myitem.def_id, name))?
+                               anchor(myitem.def_id, name))?
                     }
                 }
                 write!(w, "</code></td></tr>")?;
@@ -5048,27 +4869,6 @@ fn sidebar_foreign_type(fmt: &mut fmt::Formatter<'_>, it: &clean::Item) -> fmt::
     Ok(())
 }
 
-impl<'a> fmt::Display for Source<'a> {
-    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
-        let Source(s) = *self;
-        let lines = s.lines().count();
-        let mut cols = 0;
-        let mut tmp = lines;
-        while tmp > 0 {
-            cols += 1;
-            tmp /= 10;
-        }
-        write!(fmt, "<pre class=\"line-numbers\">")?;
-        for i in 1..=lines {
-            write!(fmt, "<span id=\"{0}\">{0:1$}</span>\n", i, cols)?;
-        }
-        write!(fmt, "</pre>")?;
-        write!(fmt, "{}",
-               highlight::render_with_highlighting(s, None, None, None))?;
-        Ok(())
-    }
-}
-
 fn item_macro(w: &mut fmt::Formatter<'_>, cx: &Context, it: &clean::Item,
               t: &clean::Macro) -> fmt::Result {
     wrap_into_docblock(w, |w| {
@@ -5125,7 +4925,7 @@ fn item_keyword(w: &mut fmt::Formatter<'_>, cx: &Context,
     document(w, cx, it)
 }
 
-const BASIC_KEYWORDS: &'static str = "rust, rustlang, rust-lang";
+crate const BASIC_KEYWORDS: &'static str = "rust, rustlang, rust-lang";
 
 fn make_item_keywords(it: &clean::Item) -> String {
     format!("{}, {}", BASIC_KEYWORDS, it.name.as_ref().unwrap())
diff --git a/src/librustdoc/html/sources.rs b/src/librustdoc/html/sources.rs
new file mode 100644
index 00000000000..c1f1f59d914
--- /dev/null
+++ b/src/librustdoc/html/sources.rs
@@ -0,0 +1,187 @@
+use crate::clean;
+use crate::docfs::PathError;
+use crate::fold::DocFolder;
+use crate::html::layout;
+use crate::html::render::{Error, SharedContext, BASIC_KEYWORDS};
+use crate::html::highlight;
+use std::ffi::OsStr;
+use std::fs;
+use std::path::{Component, Path, PathBuf};
+use std::fmt;
+use syntax::source_map::FileName;
+
+crate fn render(dst: &Path, scx: &mut SharedContext,
+                  krate: clean::Crate) -> Result<clean::Crate, Error> {
+    info!("emitting source files");
+    let dst = dst.join("src").join(&krate.name);
+    scx.ensure_dir(&dst)?;
+    let mut folder = SourceCollector {
+        dst,
+        scx,
+    };
+    Ok(folder.fold_crate(krate))
+}
+
+/// Helper struct to render all source code to HTML pages
+struct SourceCollector<'a> {
+    scx: &'a mut SharedContext,
+
+    /// Root destination to place all HTML output into
+    dst: PathBuf,
+}
+
+impl<'a> DocFolder for SourceCollector<'a> {
+    fn fold_item(&mut self, item: clean::Item) -> Option<clean::Item> {
+        // If we're including source files, and we haven't seen this file yet,
+        // then we need to render it out to the filesystem.
+        if self.scx.include_sources
+            // skip all invalid or macro spans
+            && item.source.filename.is_real()
+            // skip non-local items
+            && item.def_id.is_local() {
+
+            // If it turns out that we couldn't read this file, then we probably
+            // can't read any of the files (generating html output from json or
+            // something like that), so just don't include sources for the
+            // entire crate. The other option is maintaining this mapping on a
+            // per-file basis, but that's probably not worth it...
+            self.scx
+                .include_sources = match self.emit_source(&item.source.filename) {
+                Ok(()) => true,
+                Err(e) => {
+                    println!("warning: source code was requested to be rendered, \
+                              but processing `{}` had an error: {}",
+                             item.source.filename, e);
+                    println!("         skipping rendering of source code");
+                    false
+                }
+            };
+        }
+        self.fold_item_recur(item)
+    }
+}
+
+impl<'a> SourceCollector<'a> {
+    /// Renders the given filename into its corresponding HTML source file.
+    fn emit_source(&mut self, filename: &FileName) -> Result<(), Error> {
+        let p = match *filename {
+            FileName::Real(ref file) => file,
+            _ => return Ok(()),
+        };
+        if self.scx.local_sources.contains_key(&**p) {
+            // We've already emitted this source
+            return Ok(());
+        }
+
+        let contents = match fs::read_to_string(&p) {
+            Ok(contents) => contents,
+            Err(e) => {
+                return Err(Error::new(e, &p));
+            }
+        };
+
+        // Remove the utf-8 BOM if any
+        let contents = if contents.starts_with("\u{feff}") {
+            &contents[3..]
+        } else {
+            &contents[..]
+        };
+
+        // Create the intermediate directories
+        let mut cur = self.dst.clone();
+        let mut root_path = String::from("../../");
+        let mut href = String::new();
+        clean_path(&self.scx.src_root, &p, false, |component| {
+            cur.push(component);
+            root_path.push_str("../");
+            href.push_str(&component.to_string_lossy());
+            href.push('/');
+        });
+        self.scx.ensure_dir(&cur)?;
+        let mut fname = p.file_name()
+                         .expect("source has no filename")
+                         .to_os_string();
+        fname.push(".html");
+        cur.push(&fname);
+        href.push_str(&fname.to_string_lossy());
+
+        let mut v = Vec::new();
+        let title = format!("{} -- source", cur.file_name().expect("failed to get file name")
+                                               .to_string_lossy());
+        let desc = format!("Source to the Rust file `{}`.", filename);
+        let page = layout::Page {
+            title: &title,
+            css_class: "source",
+            root_path: &root_path,
+            static_root_path: self.scx.static_root_path.as_deref(),
+            description: &desc,
+            keywords: BASIC_KEYWORDS,
+            resource_suffix: &self.scx.resource_suffix,
+            extra_scripts: &[&format!("source-files{}", self.scx.resource_suffix)],
+            static_extra_scripts: &[&format!("source-script{}", self.scx.resource_suffix)],
+        };
+        let result = layout::render(&mut v, &self.scx.layout,
+                       &page, &(""), &Source(contents),
+                       self.scx.css_file_extension.is_some(),
+                       &self.scx.themes,
+                       self.scx.generate_search_filter);
+        if let Err(e) = result {
+            return Err(Error::new(e, &cur));
+        }
+        self.scx.fs.write(&cur, &v)?;
+        self.scx.local_sources.insert(p.clone(), href);
+        Ok(())
+    }
+}
+
+/// Takes a path to a source file and cleans the path to it. This canonicalizes
+/// things like ".." to components which preserve the "top down" hierarchy of a
+/// static HTML tree. Each component in the cleaned path will be passed as an
+/// argument to `f`. The very last component of the path (ie the file name) will
+/// be passed to `f` if `keep_filename` is true, and ignored otherwise.
+pub fn clean_path<F>(src_root: &Path, p: &Path, keep_filename: bool, mut f: F)
+where
+    F: FnMut(&OsStr),
+{
+    // make it relative, if possible
+    let p = p.strip_prefix(src_root).unwrap_or(p);
+
+    let mut iter = p.components().peekable();
+
+    while let Some(c) = iter.next() {
+        if !keep_filename && iter.peek().is_none() {
+            break;
+        }
+
+        match c {
+            Component::ParentDir => f("up".as_ref()),
+            Component::Normal(c) => f(c),
+            _ => continue,
+        }
+    }
+}
+
+/// Wrapper struct to render the source code of a file. This will do things like
+/// adding line numbers to the left-hand side.
+struct Source<'a>(&'a str);
+
+impl<'a> fmt::Display for Source<'a> {
+    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
+        let Source(s) = *self;
+        let lines = s.lines().count();
+        let mut cols = 0;
+        let mut tmp = lines;
+        while tmp > 0 {
+            cols += 1;
+            tmp /= 10;
+        }
+        write!(fmt, "<pre class=\"line-numbers\">")?;
+        for i in 1..=lines {
+            write!(fmt, "<span id=\"{0}\">{0:1$}</span>\n", i, cols)?;
+        }
+        write!(fmt, "</pre>")?;
+        write!(fmt, "{}",
+               highlight::render_with_highlighting(s, None, None, None))?;
+        Ok(())
+    }
+}
diff --git a/src/librustdoc/lib.rs b/src/librustdoc/lib.rs
index e30b35937db..968578f957c 100644
--- a/src/librustdoc/lib.rs
+++ b/src/librustdoc/lib.rs
@@ -67,6 +67,7 @@ pub mod html {
     crate mod render;
     crate mod static_files;
     crate mod toc;
+    crate mod sources;
 }
 mod markdown;
 mod passes;
diff --git a/src/librustdoc/visit_ast.rs b/src/librustdoc/visit_ast.rs
index 903ed3aae14..ee330cb3211 100644
--- a/src/librustdoc/visit_ast.rs
+++ b/src/librustdoc/visit_ast.rs
@@ -41,7 +41,7 @@ fn def_id_to_path(
 // framework from syntax?.
 
 pub struct RustdocVisitor<'a, 'tcx> {
-    cx: &'a core::DocContext<'tcx>,
+    cx: &'a mut core::DocContext<'tcx>,
     view_item_stack: FxHashSet<hir::HirId>,
     inlining: bool,
     /// Are the current module and all of its parents public?
@@ -51,7 +51,7 @@ pub struct RustdocVisitor<'a, 'tcx> {
 
 impl<'a, 'tcx> RustdocVisitor<'a, 'tcx> {
     pub fn new(
-        cx: &'a core::DocContext<'tcx>
+        cx: &'a mut core::DocContext<'tcx>
     ) -> RustdocVisitor<'a, 'tcx> {
         // If the root is re-exported, terminate all recursion.
         let mut stack = FxHashSet::default();
@@ -84,7 +84,7 @@ impl<'a, 'tcx> RustdocVisitor<'a, 'tcx> {
         );
         module.is_crate = true;
 
-        self.cx.renderinfo.borrow_mut().exact_paths = self.exact_paths;
+        self.cx.renderinfo.get_mut().exact_paths = self.exact_paths;
 
         module
     }
@@ -292,7 +292,7 @@ impl<'a, 'tcx> RustdocVisitor<'a, 'tcx> {
                 Res::Def(DefKind::ForeignTy, did) |
                 Res::Def(DefKind::TyAlias, did) if !self_is_hidden => {
                     self.cx.renderinfo
-                        .borrow_mut()
+                        .get_mut()
                         .access_levels.map
                         .insert(did, AccessLevel::Public);
                 },
diff --git a/src/librustdoc/visit_lib.rs b/src/librustdoc/visit_lib.rs
index 2547e3a06e9..b229b5f6884 100644
--- a/src/librustdoc/visit_lib.rs
+++ b/src/librustdoc/visit_lib.rs
@@ -1,12 +1,10 @@
 use rustc::middle::privacy::{AccessLevels, AccessLevel};
 use rustc::hir::def::{Res, DefKind};
 use rustc::hir::def_id::{CrateNum, CRATE_DEF_INDEX, DefId};
-use rustc::ty::Visibility;
+use rustc::ty::{TyCtxt, Visibility};
 use rustc::util::nodemap::FxHashSet;
 use syntax::symbol::sym;
 
-use std::cell::RefMut;
-
 use crate::clean::{AttributesExt, NestedAttributesExt};
 
 // FIXME: this may not be exhaustive, but is sufficient for rustdocs current uses
@@ -14,9 +12,9 @@ use crate::clean::{AttributesExt, NestedAttributesExt};
 /// Similar to `librustc_privacy::EmbargoVisitor`, but also takes
 /// specific rustdoc annotations into account (i.e., `doc(hidden)`)
 pub struct LibEmbargoVisitor<'a, 'tcx> {
-    cx: &'a crate::core::DocContext<'tcx>,
+    tcx: TyCtxt<'tcx>,
     // Accessibility levels for reachable nodes
-    access_levels: RefMut<'a, AccessLevels<DefId>>,
+    access_levels: &'a mut AccessLevels<DefId>,
     // Previous accessibility level, None means unreachable
     prev_level: Option<AccessLevel>,
     // Keeps track of already visited modules, in case a module re-exports its parent
@@ -25,13 +23,13 @@ pub struct LibEmbargoVisitor<'a, 'tcx> {
 
 impl<'a, 'tcx> LibEmbargoVisitor<'a, 'tcx> {
     pub fn new(
-        cx: &'a crate::core::DocContext<'tcx>
+        cx: &'a mut crate::core::DocContext<'tcx>
     ) -> LibEmbargoVisitor<'a, 'tcx> {
         LibEmbargoVisitor {
-            cx,
-            access_levels: RefMut::map(cx.renderinfo.borrow_mut(), |ri| &mut ri.access_levels),
+            tcx: cx.tcx,
+            access_levels: &mut cx.renderinfo.get_mut().access_levels,
             prev_level: Some(AccessLevel::Public),
-            visited_mods: FxHashSet::default()
+            visited_mods: FxHashSet::default(),
         }
     }
 
@@ -43,7 +41,7 @@ impl<'a, 'tcx> LibEmbargoVisitor<'a, 'tcx> {
 
     // Updates node level and returns the updated level
     fn update(&mut self, did: DefId, level: Option<AccessLevel>) -> Option<AccessLevel> {
-        let is_hidden = self.cx.tcx.get_attrs(did).lists(sym::doc).has_word(sym::hidden);
+        let is_hidden = self.tcx.get_attrs(did).lists(sym::doc).has_word(sym::hidden);
 
         let old_level = self.access_levels.map.get(&did).cloned();
         // Accessibility levels can only grow
@@ -60,9 +58,9 @@ impl<'a, 'tcx> LibEmbargoVisitor<'a, 'tcx> {
             return;
         }
 
-        for item in self.cx.tcx.item_children(def_id).iter() {
+        for item in self.tcx.item_children(def_id).iter() {
             if let Some(def_id) = item.res.opt_def_id() {
-                if self.cx.tcx.def_key(def_id).parent.map_or(false, |d| d == def_id.index) ||
+                if self.tcx.def_key(def_id).parent.map_or(false, |d| d == def_id.index) ||
                     item.vis == Visibility::Public {
                     self.visit_item(item.res);
                 }
@@ -72,7 +70,7 @@ impl<'a, 'tcx> LibEmbargoVisitor<'a, 'tcx> {
 
     fn visit_item(&mut self, res: Res) {
         let def_id = res.def_id();
-        let vis = self.cx.tcx.visibility(def_id);
+        let vis = self.tcx.visibility(def_id);
         let inherited_item_level = if vis == Visibility::Public {
             self.prev_level
         } else {