about summary refs log tree commit diff
path: root/src/librustdoc/passes
diff options
context:
space:
mode:
Diffstat (limited to 'src/librustdoc/passes')
-rw-r--r--src/librustdoc/passes/calculate_doc_coverage.rs13
-rw-r--r--src/librustdoc/passes/check_code_block_syntax.rs15
-rw-r--r--src/librustdoc/passes/collapse_docs.rs72
-rw-r--r--src/librustdoc/passes/collect_intra_doc_links.rs542
-rw-r--r--src/librustdoc/passes/collect_trait_impls.rs8
-rw-r--r--src/librustdoc/passes/doc_test_lints.rs6
-rw-r--r--src/librustdoc/passes/html_tags.rs2
-rw-r--r--src/librustdoc/passes/mod.rs5
-rw-r--r--src/librustdoc/passes/strip_hidden.rs8
-rw-r--r--src/librustdoc/passes/stripper.rs12
-rw-r--r--src/librustdoc/passes/unindent_comments.rs18
-rw-r--r--src/librustdoc/passes/unindent_comments/tests.rs14
12 files changed, 345 insertions, 370 deletions
diff --git a/src/librustdoc/passes/calculate_doc_coverage.rs b/src/librustdoc/passes/calculate_doc_coverage.rs
index 52f6a97089b..05a3a15adac 100644
--- a/src/librustdoc/passes/calculate_doc_coverage.rs
+++ b/src/librustdoc/passes/calculate_doc_coverage.rs
@@ -5,7 +5,7 @@ use crate::html::markdown::{find_testable_code, ErrorCodes};
 use crate::passes::doc_test_lints::{should_have_doc_example, Tests};
 use crate::passes::Pass;
 use rustc_lint::builtin::MISSING_DOCS;
-use rustc_middle::lint::LintSource;
+use rustc_middle::lint::LintLevelSource;
 use rustc_session::lint;
 use rustc_span::symbol::sym;
 use rustc_span::FileName;
@@ -187,7 +187,7 @@ impl<'a, 'b> CoverageCalculator<'a, 'b> {
 
 impl<'a, 'b> fold::DocFolder for CoverageCalculator<'a, 'b> {
     fn fold_item(&mut self, i: clean::Item) -> Option<clean::Item> {
-        match i.kind {
+        match *i.kind {
             _ if !i.def_id.is_local() => {
                 // non-local items are skipped because they can be out of the users control,
                 // especially in the case of trait impls, which rustdoc eagerly inlines
@@ -235,12 +235,7 @@ impl<'a, 'b> fold::DocFolder for CoverageCalculator<'a, 'b> {
                 let mut tests = Tests { found_tests: 0 };
 
                 find_testable_code(
-                    &i.attrs
-                        .doc_strings
-                        .iter()
-                        .map(|d| d.doc.as_str())
-                        .collect::<Vec<_>>()
-                        .join("\n"),
+                    &i.attrs.collapsed_doc_value().unwrap_or_default(),
                     &mut tests,
                     ErrorCodes::No,
                     false,
@@ -254,7 +249,7 @@ impl<'a, 'b> fold::DocFolder for CoverageCalculator<'a, 'b> {
                 // `missing_docs` is allow-by-default, so don't treat this as ignoring the item
                 // unless the user had an explicit `allow`
                 let should_have_docs =
-                    level != lint::Level::Allow || matches!(source, LintSource::Default);
+                    level != lint::Level::Allow || matches!(source, LintLevelSource::Default);
                 debug!("counting {:?} {:?} in {}", i.type_(), i.name, filename);
                 self.items.entry(filename).or_default().count_item(
                     has_docs,
diff --git a/src/librustdoc/passes/check_code_block_syntax.rs b/src/librustdoc/passes/check_code_block_syntax.rs
index 0c76dc571be..554392c213e 100644
--- a/src/librustdoc/passes/check_code_block_syntax.rs
+++ b/src/librustdoc/passes/check_code_block_syntax.rs
@@ -51,10 +51,10 @@ impl<'a, 'tcx> SyntaxChecker<'a, 'tcx> {
             let mut diag = if let Some(sp) =
                 super::source_span_for_markdown_range(self.cx, &dox, &code_block.range, &item.attrs)
             {
-                let warning_message = if buffer.has_errors {
-                    "could not parse code block as Rust code"
+                let (warning_message, suggest_using_text) = if buffer.has_errors {
+                    ("could not parse code block as Rust code", true)
                 } else {
-                    "Rust code block is empty"
+                    ("Rust code block is empty", false)
                 };
 
                 let mut diag = self.cx.sess().struct_span_warn(sp, warning_message);
@@ -67,6 +67,15 @@ impl<'a, 'tcx> SyntaxChecker<'a, 'tcx> {
                         String::from("```text"),
                         Applicability::MachineApplicable,
                     );
+                } else if suggest_using_text && code_block.is_ignore {
+                    let sp = sp.from_inner(InnerSpan::new(0, 3));
+                    diag.span_suggestion(
+                        sp,
+                        "`ignore` code blocks require valid Rust code for syntax highlighting. \
+                         Mark blocks that do not contain Rust code as text",
+                        String::from("```text,"),
+                        Applicability::MachineApplicable,
+                    );
                 }
 
                 diag
diff --git a/src/librustdoc/passes/collapse_docs.rs b/src/librustdoc/passes/collapse_docs.rs
deleted file mode 100644
index e1ba75baa0f..00000000000
--- a/src/librustdoc/passes/collapse_docs.rs
+++ /dev/null
@@ -1,72 +0,0 @@
-use crate::clean::{self, DocFragment, DocFragmentKind, Item};
-use crate::core::DocContext;
-use crate::fold;
-use crate::fold::DocFolder;
-use crate::passes::Pass;
-
-use std::mem::take;
-
-crate const COLLAPSE_DOCS: Pass = Pass {
-    name: "collapse-docs",
-    run: collapse_docs,
-    description: "concatenates all document attributes into one document attribute",
-};
-
-crate fn collapse_docs(krate: clean::Crate, _: &DocContext<'_>) -> clean::Crate {
-    let mut krate = Collapser.fold_crate(krate);
-    krate.collapsed = true;
-    krate
-}
-
-struct Collapser;
-
-impl fold::DocFolder for Collapser {
-    fn fold_item(&mut self, mut i: Item) -> Option<Item> {
-        i.attrs.collapse_doc_comments();
-        Some(self.fold_item_recur(i))
-    }
-}
-
-fn collapse(doc_strings: &mut Vec<DocFragment>) {
-    let mut docs = vec![];
-    let mut last_frag: Option<DocFragment> = None;
-
-    for frag in take(doc_strings) {
-        if let Some(mut curr_frag) = last_frag.take() {
-            let curr_kind = &curr_frag.kind;
-            let new_kind = &frag.kind;
-
-            if matches!(*curr_kind, DocFragmentKind::Include { .. })
-                || curr_kind != new_kind
-                || curr_frag.parent_module != frag.parent_module
-            {
-                if *curr_kind == DocFragmentKind::SugaredDoc
-                    || *curr_kind == DocFragmentKind::RawDoc
-                {
-                    // add a newline for extra padding between segments
-                    curr_frag.doc.push('\n');
-                }
-                docs.push(curr_frag);
-                last_frag = Some(frag);
-            } else {
-                curr_frag.doc.push('\n');
-                curr_frag.doc.push_str(&frag.doc);
-                curr_frag.span = curr_frag.span.to(frag.span);
-                last_frag = Some(curr_frag);
-            }
-        } else {
-            last_frag = Some(frag);
-        }
-    }
-
-    if let Some(frag) = last_frag.take() {
-        docs.push(frag);
-    }
-    *doc_strings = docs;
-}
-
-impl clean::Attributes {
-    crate fn collapse_doc_comments(&mut self) {
-        collapse(&mut self.doc_strings);
-    }
-}
diff --git a/src/librustdoc/passes/collect_intra_doc_links.rs b/src/librustdoc/passes/collect_intra_doc_links.rs
index a8adfe08b25..11ee59b2401 100644
--- a/src/librustdoc/passes/collect_intra_doc_links.rs
+++ b/src/librustdoc/passes/collect_intra_doc_links.rs
@@ -10,31 +10,33 @@ use rustc_hir as hir;
 use rustc_hir::def::{
     DefKind,
     Namespace::{self, *},
-    PerNS, Res,
+    PerNS,
 };
 use rustc_hir::def_id::{CrateNum, DefId};
-use rustc_middle::ty;
+use rustc_middle::{bug, ty};
 use rustc_resolve::ParentScope;
 use rustc_session::lint::{
     builtin::{BROKEN_INTRA_DOC_LINKS, PRIVATE_INTRA_DOC_LINKS},
     Lint,
 };
 use rustc_span::hygiene::MacroKind;
-use rustc_span::symbol::sym;
 use rustc_span::symbol::Ident;
 use rustc_span::symbol::Symbol;
 use rustc_span::DUMMY_SP;
 use smallvec::{smallvec, SmallVec};
 
+use pulldown_cmark::LinkType;
+
 use std::borrow::Cow;
 use std::cell::Cell;
+use std::convert::{TryFrom, TryInto};
 use std::mem;
 use std::ops::Range;
 
-use crate::clean::{self, Crate, Item, ItemLink, PrimitiveType};
+use crate::clean::{self, utils::find_nearest_parent_module, Crate, Item, ItemLink, PrimitiveType};
 use crate::core::DocContext;
 use crate::fold::DocFolder;
-use crate::html::markdown::markdown_links;
+use crate::html::markdown::{markdown_links, MarkdownLink};
 use crate::passes::Pass;
 
 use super::span_of_attrs;
@@ -61,6 +63,71 @@ impl<'a> From<ResolutionFailure<'a>> for ErrorKind<'a> {
     }
 }
 
+#[derive(Copy, Clone, Debug, Hash)]
+enum Res {
+    Def(DefKind, DefId),
+    Primitive(PrimitiveType),
+}
+
+type ResolveRes = rustc_hir::def::Res<rustc_ast::NodeId>;
+
+impl Res {
+    fn descr(self) -> &'static str {
+        match self {
+            Res::Def(kind, id) => ResolveRes::Def(kind, id).descr(),
+            Res::Primitive(_) => "builtin type",
+        }
+    }
+
+    fn article(self) -> &'static str {
+        match self {
+            Res::Def(kind, id) => ResolveRes::Def(kind, id).article(),
+            Res::Primitive(_) => "a",
+        }
+    }
+
+    fn name(self, tcx: ty::TyCtxt<'_>) -> String {
+        match self {
+            Res::Def(_, id) => tcx.item_name(id).to_string(),
+            Res::Primitive(prim) => prim.as_str().to_string(),
+        }
+    }
+
+    fn def_id(self) -> DefId {
+        self.opt_def_id().expect("called def_id() on a primitive")
+    }
+
+    fn opt_def_id(self) -> Option<DefId> {
+        match self {
+            Res::Def(_, id) => Some(id),
+            Res::Primitive(_) => None,
+        }
+    }
+
+    fn as_hir_res(self) -> Option<rustc_hir::def::Res> {
+        match self {
+            Res::Def(kind, id) => Some(rustc_hir::def::Res::Def(kind, id)),
+            // FIXME: maybe this should handle the subset of PrimitiveType that fits into hir::PrimTy?
+            Res::Primitive(_) => None,
+        }
+    }
+}
+
+impl TryFrom<ResolveRes> for Res {
+    type Error = ();
+
+    fn try_from(res: ResolveRes) -> Result<Self, ()> {
+        use rustc_hir::def::Res::*;
+        match res {
+            Def(kind, id) => Ok(Res::Def(kind, id)),
+            PrimTy(prim) => Ok(Res::Primitive(PrimitiveType::from_hir(prim))),
+            // e.g. `#[derive]`
+            NonMacroAttr(..) | Err => Result::Err(()),
+            other => bug!("unrecognized res {:?}", other),
+        }
+    }
+}
+
 #[derive(Debug)]
 /// A link failed to resolve.
 enum ResolutionFailure<'a> {
@@ -200,8 +267,9 @@ struct LinkCollector<'a, 'tcx> {
     /// because `clean` and the disambiguator code expect them to be different.
     /// See the code for associated items on inherent impls for details.
     kind_side_channel: Cell<Option<(DefKind, DefId)>>,
-    /// Cache the resolved links so we can avoid resolving (and emitting errors for) the same link
-    visited_links: FxHashMap<ResolutionInfo, CachedLink>,
+    /// Cache the resolved links so we can avoid resolving (and emitting errors for) the same link.
+    /// The link will be `None` if it could not be resolved (i.e. the error was cached).
+    visited_links: FxHashMap<ResolutionInfo, Option<CachedLink>>,
 }
 
 impl<'a, 'tcx> LinkCollector<'a, 'tcx> {
@@ -253,12 +321,9 @@ impl<'a, 'tcx> LinkCollector<'a, 'tcx> {
             .enter_resolver(|resolver| {
                 resolver.resolve_str_path_error(DUMMY_SP, &path, TypeNS, module_id)
             })
-            .map(|(_, res)| res)
-            .unwrap_or(Res::Err);
-        if let Res::Err = ty_res {
-            return Err(no_res().into());
-        }
-        let ty_res = ty_res.map_id(|_| panic!("unexpected node_id"));
+            .and_then(|(_, res)| res.try_into())
+            .map_err(|()| no_res())?;
+
         match ty_res {
             Res::Def(DefKind::Enum, did) => {
                 if cx
@@ -309,7 +374,7 @@ impl<'a, 'tcx> LinkCollector<'a, 'tcx> {
     /// lifetimes on `&'path` will work.
     fn resolve_primitive_associated_item(
         &self,
-        prim_ty: hir::PrimTy,
+        prim_ty: PrimitiveType,
         ns: Namespace,
         module_id: DefId,
         item_name: Symbol,
@@ -317,7 +382,7 @@ impl<'a, 'tcx> LinkCollector<'a, 'tcx> {
     ) -> Result<(Res, Option<String>), ErrorKind<'path>> {
         let cx = self.cx;
 
-        PrimitiveType::from_hir(prim_ty)
+        prim_ty
             .impls(cx.tcx)
             .into_iter()
             .find_map(|&impl_| {
@@ -329,28 +394,32 @@ impl<'a, 'tcx> LinkCollector<'a, 'tcx> {
                         ns,
                         impl_,
                     )
-                    .map(|item| match item.kind {
-                        ty::AssocKind::Fn => "method",
-                        ty::AssocKind::Const => "associatedconstant",
-                        ty::AssocKind::Type => "associatedtype",
+                    .map(|item| {
+                        let kind = item.kind;
+                        self.kind_side_channel.set(Some((kind.as_def_kind(), item.def_id)));
+                        match kind {
+                            ty::AssocKind::Fn => "method",
+                            ty::AssocKind::Const => "associatedconstant",
+                            ty::AssocKind::Type => "associatedtype",
+                        }
                     })
                     .map(|out| {
                         (
-                            Res::PrimTy(prim_ty),
-                            Some(format!("{}#{}.{}", prim_ty.name(), out, item_str)),
+                            Res::Primitive(prim_ty),
+                            Some(format!("{}#{}.{}", prim_ty.as_str(), out, item_str)),
                         )
                     })
             })
             .ok_or_else(|| {
                 debug!(
                     "returning primitive error for {}::{} in {} namespace",
-                    prim_ty.name(),
+                    prim_ty.as_str(),
                     item_name,
                     ns.descr()
                 );
                 ResolutionFailure::NotResolved {
                     module_id,
-                    partial_res: Some(Res::PrimTy(prim_ty)),
+                    partial_res: Some(Res::Primitive(prim_ty)),
                     unresolved: item_str.into(),
                 }
                 .into()
@@ -377,19 +446,18 @@ impl<'a, 'tcx> LinkCollector<'a, 'tcx> {
                 false,
             ) {
                 if let SyntaxExtensionKind::LegacyBang { .. } = ext.kind {
-                    return Ok(res.map_id(|_| panic!("unexpected id")));
+                    return Ok(res.try_into().unwrap());
                 }
             }
-            if let Some(res) = resolver.all_macros().get(&Symbol::intern(path_str)) {
-                return Ok(res.map_id(|_| panic!("unexpected id")));
+            if let Some(&res) = resolver.all_macros().get(&Symbol::intern(path_str)) {
+                return Ok(res.try_into().unwrap());
             }
             debug!("resolving {} as a macro in the module {:?}", path_str, module_id);
             if let Ok((_, res)) =
                 resolver.resolve_str_path_error(DUMMY_SP, path_str, MacroNS, module_id)
             {
                 // don't resolve builtins like `#[derive]`
-                if let Res::Def(..) = res {
-                    let res = res.map_id(|_| panic!("unexpected node_id"));
+                if let Ok(res) = res.try_into() {
                     return Ok(res);
                 }
             }
@@ -408,14 +476,16 @@ impl<'a, 'tcx> LinkCollector<'a, 'tcx> {
     /// Associated items will never be resolved by this function.
     fn resolve_path(&self, path_str: &str, ns: Namespace, module_id: DefId) -> Option<Res> {
         let result = self.cx.enter_resolver(|resolver| {
-            resolver.resolve_str_path_error(DUMMY_SP, &path_str, ns, module_id)
+            resolver
+                .resolve_str_path_error(DUMMY_SP, &path_str, ns, module_id)
+                .and_then(|(_, res)| res.try_into())
         });
         debug!("{} resolved to {:?} in namespace {:?}", path_str, result, ns);
-        match result.map(|(_, res)| res) {
-            // resolver doesn't know about true and false so we'll have to resolve them
+        match result {
+            // resolver doesn't know about true, false, and types that aren't paths (e.g. `()`)
             // manually as bool
-            Ok(Res::Err) | Err(()) => is_bool_value(path_str, ns).map(|(_, res)| res),
-            Ok(res) => Some(res.map_id(|_| panic!("unexpected node_id"))),
+            Err(()) => resolve_primitive(path_str, ns),
+            Ok(res) => Some(res),
         }
     }
 
@@ -444,13 +514,13 @@ impl<'a, 'tcx> LinkCollector<'a, 'tcx> {
                     return handle_variant(cx, res, extra_fragment);
                 }
                 // Not a trait item; just return what we found.
-                Res::PrimTy(ty) => {
+                Res::Primitive(ty) => {
                     if extra_fragment.is_some() {
                         return Err(ErrorKind::AnchorFailure(
                             AnchorFailure::RustdocAnchorConflict(res),
                         ));
                     }
-                    return Ok((res, Some(ty.name_str().to_owned())));
+                    return Ok((res, Some(ty.as_str().to_owned())));
                 }
                 Res::Def(DefKind::Mod, _) => {
                     return Ok((res, extra_fragment.clone()));
@@ -483,7 +553,6 @@ impl<'a, 'tcx> LinkCollector<'a, 'tcx> {
 
         // FIXME: are these both necessary?
         let ty_res = if let Some(ty_res) = resolve_primitive(&path_root, TypeNS)
-            .map(|(_, res)| res)
             .or_else(|| self.resolve_path(&path_root, TypeNS, module_id))
         {
             ty_res
@@ -502,7 +571,7 @@ impl<'a, 'tcx> LinkCollector<'a, 'tcx> {
         };
 
         let res = match ty_res {
-            Res::PrimTy(prim) => Some(
+            Res::Primitive(prim) => Some(
                 self.resolve_primitive_associated_item(prim, ns, module_id, item_name, item_str),
             ),
             Res::Def(
@@ -768,31 +837,9 @@ impl<'a, 'tcx> DocFolder for LinkCollector<'a, 'tcx> {
         use rustc_middle::ty::DefIdTree;
 
         let parent_node = if item.is_fake() {
-            // FIXME: is this correct?
             None
-        // If we're documenting the crate root itself, it has no parent. Use the root instead.
-        } else if item.def_id.is_top_level_module() {
-            Some(item.def_id)
         } else {
-            let mut current = item.def_id;
-            // The immediate parent might not always be a module.
-            // Find the first parent which is.
-            loop {
-                if let Some(parent) = self.cx.tcx.parent(current) {
-                    if self.cx.tcx.def_kind(parent) == DefKind::Mod {
-                        break Some(parent);
-                    }
-                    current = parent;
-                } else {
-                    debug!(
-                        "{:?} has no parent (kind={:?}, original was {:?})",
-                        current,
-                        self.cx.tcx.def_kind(current),
-                        item.def_id
-                    );
-                    break None;
-                }
-            }
+            find_nearest_parent_module(self.cx.tcx, item.def_id)
         };
 
         if parent_node.is_some() {
@@ -851,43 +898,18 @@ impl<'a, 'tcx> DocFolder for LinkCollector<'a, 'tcx> {
         // In the presence of re-exports, this is not the same as the module of the item.
         // Rather than merging all documentation into one, resolve it one attribute at a time
         // so we know which module it came from.
-        let mut attrs = item.attrs.doc_strings.iter().peekable();
-        while let Some(attr) = attrs.next() {
-            // `collapse_docs` does not have the behavior we want:
-            // we want `///` and `#[doc]` to count as the same attribute,
-            // but currently it will treat them as separate.
-            // As a workaround, combine all attributes with the same parent module into the same attribute.
-            let mut combined_docs = attr.doc.clone();
-            loop {
-                match attrs.peek() {
-                    Some(next) if next.parent_module == attr.parent_module => {
-                        combined_docs.push('\n');
-                        combined_docs.push_str(&attrs.next().unwrap().doc);
-                    }
-                    _ => break,
-                }
-            }
-            debug!("combined_docs={}", combined_docs);
+        for (parent_module, doc) in item.attrs.collapsed_doc_value_by_module_level() {
+            debug!("combined_docs={}", doc);
 
-            let (krate, parent_node) = if let Some(id) = attr.parent_module {
-                trace!("docs {:?} came from {:?}", attr.doc, id);
+            let (krate, parent_node) = if let Some(id) = parent_module {
                 (id.krate, Some(id))
             } else {
-                trace!("no parent found for {:?}", attr.doc);
                 (item.def_id.krate, parent_node)
             };
             // NOTE: if there are links that start in one crate and end in another, this will not resolve them.
             // This is a degenerate case and it's not supported by rustdoc.
-            for (ori_link, link_range) in markdown_links(&combined_docs) {
-                let link = self.resolve_link(
-                    &item,
-                    &combined_docs,
-                    &self_name,
-                    parent_node,
-                    krate,
-                    ori_link,
-                    link_range,
-                );
+            for md_link in markdown_links(&doc) {
+                let link = self.resolve_link(&item, &doc, &self_name, parent_node, krate, md_link);
                 if let Some(link) = link {
                     item.attrs.links.push(link);
                 }
@@ -919,27 +941,26 @@ impl LinkCollector<'_, '_> {
         self_name: &Option<String>,
         parent_node: Option<DefId>,
         krate: CrateNum,
-        ori_link: String,
-        link_range: Range<usize>,
+        ori_link: MarkdownLink,
     ) -> Option<ItemLink> {
-        trace!("considering link '{}'", ori_link);
+        trace!("considering link '{}'", ori_link.link);
 
         // Bail early for real links.
-        if ori_link.contains('/') {
+        if ori_link.link.contains('/') {
             return None;
         }
 
         // [] is mostly likely not supposed to be a link
-        if ori_link.is_empty() {
+        if ori_link.link.is_empty() {
             return None;
         }
 
         let cx = self.cx;
-        let link = ori_link.replace("`", "");
+        let link = ori_link.link.replace("`", "");
         let parts = link.split('#').collect::<Vec<_>>();
         let (link, extra_fragment) = if parts.len() > 2 {
             // A valid link can't have multiple #'s
-            anchor_failure(cx, &item, &link, dox, link_range, AnchorFailure::MultipleAnchors);
+            anchor_failure(cx, &item, &link, dox, ori_link.range, AnchorFailure::MultipleAnchors);
             return None;
         } else if parts.len() == 2 {
             if parts[0].trim().is_empty() {
@@ -958,7 +979,7 @@ impl LinkCollector<'_, '_> {
             (link.trim(), None)
         };
 
-        if path_str.contains(|ch: char| !(ch.is_alphanumeric() || ":_<>, !".contains(ch))) {
+        if path_str.contains(|ch: char| !(ch.is_alphanumeric() || ":_<>, !*&;".contains(ch))) {
             return None;
         }
 
@@ -995,7 +1016,7 @@ impl LinkCollector<'_, '_> {
                 path_str,
                 disambiguator,
                 dox,
-                link_range,
+                ori_link.range,
                 smallvec![ResolutionFailure::NoParentItem],
             );
             return None;
@@ -1035,7 +1056,7 @@ impl LinkCollector<'_, '_> {
                         path_str,
                         disambiguator,
                         dox,
-                        link_range,
+                        ori_link.range,
                         smallvec![err_kind],
                     );
                     return None;
@@ -1046,21 +1067,27 @@ impl LinkCollector<'_, '_> {
         // Sanity check to make sure we don't have any angle brackets after stripping generics.
         assert!(!path_str.contains(['<', '>'].as_slice()));
 
-        // The link is not an intra-doc link if it still contains commas or spaces after
-        // stripping generics.
-        if path_str.contains([',', ' '].as_slice()) {
+        // The link is not an intra-doc link if it still contains spaces after stripping generics.
+        if path_str.contains(' ') {
             return None;
         }
 
-        let key = ResolutionInfo {
-            module_id,
-            dis: disambiguator,
-            path_str: path_str.to_owned(),
-            extra_fragment,
+        let diag_info = DiagnosticInfo {
+            item,
+            dox,
+            ori_link: &ori_link.link,
+            link_range: ori_link.range.clone(),
         };
-        let diag =
-            DiagnosticInfo { item, dox, ori_link: &ori_link, link_range: link_range.clone() };
-        let (mut res, mut fragment) = self.resolve_with_disambiguator_cached(key, diag)?;
+        let (mut res, mut fragment) = self.resolve_with_disambiguator_cached(
+            ResolutionInfo {
+                module_id,
+                dis: disambiguator,
+                path_str: path_str.to_owned(),
+                extra_fragment,
+            },
+            diag_info,
+            matches!(ori_link.kind, LinkType::Reference | LinkType::Shortcut),
+        )?;
 
         // Check for a primitive which might conflict with a module
         // Report the ambiguity and require that the user specify which one they meant.
@@ -1068,9 +1095,9 @@ impl LinkCollector<'_, '_> {
         if matches!(
             disambiguator,
             None | Some(Disambiguator::Namespace(Namespace::TypeNS) | Disambiguator::Primitive)
-        ) && !matches!(res, Res::PrimTy(_))
+        ) && !matches!(res, Res::Primitive(_))
         {
-            if let Some((path, prim)) = resolve_primitive(path_str, TypeNS) {
+            if let Some(prim) = resolve_primitive(path_str, TypeNS) {
                 // `prim@char`
                 if matches!(disambiguator, Some(Disambiguator::Primitive)) {
                     if fragment.is_some() {
@@ -1079,17 +1106,17 @@ impl LinkCollector<'_, '_> {
                             &item,
                             path_str,
                             dox,
-                            link_range,
+                            ori_link.range,
                             AnchorFailure::RustdocAnchorConflict(prim),
                         );
                         return None;
                     }
                     res = prim;
-                    fragment = Some(path.as_str().to_string());
+                    fragment = Some(prim.name(self.cx.tcx));
                 } else {
                     // `[char]` when a `char` module is in scope
                     let candidates = vec![res, prim];
-                    ambiguity_error(cx, &item, path_str, dox, link_range, candidates);
+                    ambiguity_error(cx, &item, path_str, dox, ori_link.range, candidates);
                     return None;
                 }
             }
@@ -1107,49 +1134,45 @@ impl LinkCollector<'_, '_> {
                     specified.descr()
                 );
                 diag.note(&note);
-                suggest_disambiguator(resolved, diag, path_str, dox, sp, &link_range);
+                suggest_disambiguator(resolved, diag, path_str, dox, sp, &ori_link.range);
             };
-            report_diagnostic(cx, BROKEN_INTRA_DOC_LINKS, &msg, &item, dox, &link_range, callback);
+            report_diagnostic(
+                cx,
+                BROKEN_INTRA_DOC_LINKS,
+                &msg,
+                &item,
+                dox,
+                &ori_link.range,
+                callback,
+            );
         };
-        if let Res::PrimTy(..) = res {
-            match disambiguator {
-                Some(Disambiguator::Primitive | Disambiguator::Namespace(_)) | None => {
-                    Some(ItemLink { link: ori_link, link_text, did: None, fragment })
-                }
-                Some(other) => {
-                    report_mismatch(other, Disambiguator::Primitive);
-                    None
-                }
-            }
-        } else {
+
+        let verify = |kind: DefKind, id: DefId| {
             debug!("intra-doc link to {} resolved to {:?}", path_str, res);
 
             // Disallow e.g. linking to enums with `struct@`
-            if let Res::Def(kind, _) = res {
-                debug!("saw kind {:?} with disambiguator {:?}", kind, disambiguator);
-                match (self.kind_side_channel.take().map(|(kind, _)| kind).unwrap_or(kind), disambiguator) {
-                    | (DefKind::Const | DefKind::ConstParam | DefKind::AssocConst | DefKind::AnonConst, Some(Disambiguator::Kind(DefKind::Const)))
-                    // NOTE: this allows 'method' to mean both normal functions and associated functions
-                    // This can't cause ambiguity because both are in the same namespace.
-                    | (DefKind::Fn | DefKind::AssocFn, Some(Disambiguator::Kind(DefKind::Fn)))
-                    // These are namespaces; allow anything in the namespace to match
-                    | (_, Some(Disambiguator::Namespace(_)))
-                    // If no disambiguator given, allow anything
-                    | (_, None)
-                    // All of these are valid, so do nothing
-                    => {}
-                    (actual, Some(Disambiguator::Kind(expected))) if actual == expected => {}
-                    (_, Some(specified @ Disambiguator::Kind(_) | specified @ Disambiguator::Primitive)) => {
-                        report_mismatch(specified, Disambiguator::Kind(kind));
-                        return None;
-                    }
+            debug!("saw kind {:?} with disambiguator {:?}", kind, disambiguator);
+            match (self.kind_side_channel.take().map(|(kind, _)| kind).unwrap_or(kind), disambiguator) {
+                | (DefKind::Const | DefKind::ConstParam | DefKind::AssocConst | DefKind::AnonConst, Some(Disambiguator::Kind(DefKind::Const)))
+                // NOTE: this allows 'method' to mean both normal functions and associated functions
+                // This can't cause ambiguity because both are in the same namespace.
+                | (DefKind::Fn | DefKind::AssocFn, Some(Disambiguator::Kind(DefKind::Fn)))
+                // These are namespaces; allow anything in the namespace to match
+                | (_, Some(Disambiguator::Namespace(_)))
+                // If no disambiguator given, allow anything
+                | (_, None)
+                // All of these are valid, so do nothing
+                => {}
+                (actual, Some(Disambiguator::Kind(expected))) if actual == expected => {}
+                (_, Some(specified @ Disambiguator::Kind(_) | specified @ Disambiguator::Primitive)) => {
+                    report_mismatch(specified, Disambiguator::Kind(kind));
+                    return None;
                 }
             }
 
             // item can be non-local e.g. when using #[doc(primitive = "pointer")]
-            if let Some((src_id, dst_id)) = res
-                .opt_def_id()
-                .and_then(|def_id| def_id.as_local())
+            if let Some((src_id, dst_id)) = id
+                .as_local()
                 .and_then(|dst_id| item.def_id.as_local().map(|src_id| (src_id, dst_id)))
             {
                 use rustc_hir::def_id::LOCAL_CRATE;
@@ -1160,11 +1183,41 @@ impl LinkCollector<'_, '_> {
                 if self.cx.tcx.privacy_access_levels(LOCAL_CRATE).is_exported(hir_src)
                     && !self.cx.tcx.privacy_access_levels(LOCAL_CRATE).is_exported(hir_dst)
                 {
-                    privacy_error(cx, &item, &path_str, dox, link_range);
+                    privacy_error(cx, &item, &path_str, dox, &ori_link);
                 }
             }
-            let id = clean::register_res(cx, res);
-            Some(ItemLink { link: ori_link, link_text, did: Some(id), fragment })
+
+            Some((kind, id))
+        };
+
+        match res {
+            Res::Primitive(_) => {
+                if let Some((kind, id)) = self.kind_side_channel.take() {
+                    // We're actually resolving an associated item of a primitive, so we need to
+                    // verify the disambiguator (if any) matches the type of the associated item.
+                    // This case should really follow the same flow as the `Res::Def` branch below,
+                    // but attempting to add a call to `clean::register_res` causes an ICE. @jyn514
+                    // thinks `register_res` is only needed for cross-crate re-exports, but Rust
+                    // doesn't allow statements like `use str::trim;`, making this a (hopefully)
+                    // valid omission. See https://github.com/rust-lang/rust/pull/80660#discussion_r551585677
+                    // for discussion on the matter.
+                    verify(kind, id)?;
+                } else {
+                    match disambiguator {
+                        Some(Disambiguator::Primitive | Disambiguator::Namespace(_)) | None => {}
+                        Some(other) => {
+                            report_mismatch(other, Disambiguator::Primitive);
+                            return None;
+                        }
+                    }
+                }
+                Some(ItemLink { link: ori_link.link, link_text, did: None, fragment })
+            }
+            Res::Def(kind, id) => {
+                let (kind, id) = verify(kind, id)?;
+                let id = clean::register_res(cx, rustc_hir::def::Res::Def(kind, id));
+                Some(ItemLink { link: ori_link.link, link_text, did: Some(id), fragment })
+            }
         }
     }
 
@@ -1172,28 +1225,47 @@ impl LinkCollector<'_, '_> {
         &mut self,
         key: ResolutionInfo,
         diag: DiagnosticInfo<'_>,
+        cache_resolution_failure: bool,
     ) -> Option<(Res, Option<String>)> {
         // Try to look up both the result and the corresponding side channel value
         if let Some(ref cached) = self.visited_links.get(&key) {
-            self.kind_side_channel.set(cached.side_channel.clone());
-            return Some(cached.res.clone());
+            match cached {
+                Some(cached) => {
+                    self.kind_side_channel.set(cached.side_channel.clone());
+                    return Some(cached.res.clone());
+                }
+                None if cache_resolution_failure => return None,
+                None => {
+                    // Although we hit the cache and found a resolution error, this link isn't
+                    // supposed to cache those. Run link resolution again to emit the expected
+                    // resolution error.
+                }
+            }
         }
 
         let res = self.resolve_with_disambiguator(&key, diag);
 
         // Cache only if resolved successfully - don't silence duplicate errors
-        if let Some(res) = &res {
+        if let Some(res) = res {
             // Store result for the actual namespace
             self.visited_links.insert(
                 key,
-                CachedLink {
+                Some(CachedLink {
                     res: res.clone(),
                     side_channel: self.kind_side_channel.clone().into_inner(),
-                },
+                }),
             );
-        }
 
-        res
+            Some(res)
+        } else {
+            if cache_resolution_failure {
+                // For reference-style links we only want to report one resolution error
+                // so let's cache them as well.
+                self.visited_links.insert(key, None);
+            }
+
+            None
+        }
     }
 
     /// After parsing the disambiguator, resolve the main part of the link.
@@ -1239,7 +1311,7 @@ impl LinkCollector<'_, '_> {
                         // This could just be a normal link or a broken link
                         // we could potentially check if something is
                         // "intra-doc-link-like" and warn in that case.
-                        return None;
+                        None
                     }
                     Err(ErrorKind::AnchorFailure(msg)) => {
                         anchor_failure(
@@ -1250,7 +1322,7 @@ impl LinkCollector<'_, '_> {
                             diag.link_range,
                             msg,
                         );
-                        return None;
+                        None
                     }
                 }
             }
@@ -1296,16 +1368,18 @@ impl LinkCollector<'_, '_> {
                     .and_then(|(res, fragment)| {
                         // Constructors are picked up in the type namespace.
                         match res {
-                            Res::Def(DefKind::Ctor(..), _) | Res::SelfCtor(..) => {
+                            Res::Def(DefKind::Ctor(..), _) => {
                                 Err(ResolutionFailure::WrongNamespace(res, TypeNS))
                             }
-                            _ => match (fragment, extra_fragment.clone()) {
-                                (Some(fragment), Some(_)) => {
-                                    // Shouldn't happen but who knows?
-                                    Ok((res, Some(fragment)))
+                            _ => {
+                                match (fragment, extra_fragment.clone()) {
+                                    (Some(fragment), Some(_)) => {
+                                        // Shouldn't happen but who knows?
+                                        Ok((res, Some(fragment)))
+                                    }
+                                    (fragment, None) | (None, fragment) => Ok((res, fragment)),
                                 }
-                                (fragment, None) | (None, fragment) => Ok((res, fragment)),
-                            },
+                            }
                         }
                     }),
                 };
@@ -1344,7 +1418,7 @@ impl LinkCollector<'_, '_> {
                         diag.link_range,
                         candidates.present_items().collect(),
                     );
-                    return None;
+                    None
                 }
             }
             Some(MacroNS) => {
@@ -1369,7 +1443,7 @@ impl LinkCollector<'_, '_> {
                             diag.link_range,
                             smallvec![kind],
                         );
-                        return None;
+                        None
                     }
                 }
             }
@@ -1414,8 +1488,11 @@ impl Disambiguator {
                 ("!", DefKind::Macro(MacroKind::Bang)),
             ];
             for &(suffix, kind) in &suffixes {
-                if link.ends_with(suffix) {
-                    return Ok((Kind(kind), link.trim_end_matches(suffix)));
+                if let Some(link) = link.strip_suffix(suffix) {
+                    // Avoid turning `!` or `()` into an empty string
+                    if !link.is_empty() {
+                        return Ok((Kind(kind), link));
+                    }
                 }
             }
             Err(())
@@ -1445,12 +1522,10 @@ impl Disambiguator {
         }
     }
 
-    /// WARNING: panics on `Res::Err`
     fn from_res(res: Res) -> Self {
         match res {
             Res::Def(kind, _) => Disambiguator::Kind(kind),
-            Res::PrimTy(_) => Disambiguator::Primitive,
-            _ => Disambiguator::Namespace(res.ns().expect("can't call `from_res` on Res::err")),
+            Res::Primitive(_) => Disambiguator::Primitive,
         }
     }
 
@@ -1585,6 +1660,7 @@ fn report_diagnostic(
         let mut diag = lint.build(msg);
 
         let span = super::source_span_for_markdown_range(cx, dox, link_range, attrs);
+
         if let Some(sp) = span {
             diag.set_span(sp);
         } else {
@@ -1598,7 +1674,7 @@ fn report_diagnostic(
             // Print the line containing the `link_range` and manually mark it with '^'s.
             diag.note(&format!(
                 "the link appears in this line:\n\n{line}\n\
-                    {indicator: <before$}{indicator:^<found$}",
+                     {indicator: <before$}{indicator:^<found$}",
                 line = line,
                 indicator = "",
                 before = link_range.start - last_new_line_offset,
@@ -1626,6 +1702,7 @@ fn resolution_failure(
     link_range: Range<usize>,
     kinds: SmallVec<[ResolutionFailure<'_>; 3]>,
 ) {
+    let tcx = collector.cx.tcx;
     report_diagnostic(
         collector.cx,
         BROKEN_INTRA_DOC_LINKS,
@@ -1634,16 +1711,9 @@ fn resolution_failure(
         dox,
         &link_range,
         |diag, sp| {
-            let item = |res: Res| {
-                format!(
-                    "the {} `{}`",
-                    res.descr(),
-                    collector.cx.tcx.item_name(res.def_id()).to_string()
-                )
-            };
+            let item = |res: Res| format!("the {} `{}`", res.descr(), res.name(tcx),);
             let assoc_item_not_allowed = |res: Res| {
-                let def_id = res.def_id();
-                let name = collector.cx.tcx.item_name(def_id);
+                let name = res.name(tcx);
                 format!(
                     "`{}` is {} {}, not a module or type, and cannot have associated items",
                     name,
@@ -1709,7 +1779,7 @@ fn resolution_failure(
                     if let Some(module) = last_found_module {
                         let note = if partial_res.is_some() {
                             // Part of the link resolved; e.g. `std::io::nonexistent`
-                            let module_name = collector.cx.tcx.item_name(module);
+                            let module_name = tcx.item_name(module);
                             format!("no item named `{}` in module `{}`", unresolved, module_name)
                         } else {
                             // None of the link resolved; e.g. `Notimported`
@@ -1733,14 +1803,10 @@ fn resolution_failure(
 
                     // Otherwise, it must be an associated item or variant
                     let res = partial_res.expect("None case was handled by `last_found_module`");
-                    let diagnostic_name;
-                    let (kind, name) = match res {
-                        Res::Def(kind, def_id) => {
-                            diagnostic_name = collector.cx.tcx.item_name(def_id).as_str();
-                            (Some(kind), &*diagnostic_name)
-                        }
-                        Res::PrimTy(ty) => (None, ty.name_str()),
-                        _ => unreachable!("only ADTs and primitives are in scope at module level"),
+                    let name = res.name(tcx);
+                    let kind = match res {
+                        Res::Def(kind, _) => Some(kind),
+                        Res::Primitive(_) => None,
                     };
                     let path_description = if let Some(kind) = kind {
                         match kind {
@@ -1950,13 +2016,7 @@ fn suggest_disambiguator(
 }
 
 /// Report a link from a public item to a private one.
-fn privacy_error(
-    cx: &DocContext<'_>,
-    item: &Item,
-    path_str: &str,
-    dox: &str,
-    link_range: Range<usize>,
-) {
+fn privacy_error(cx: &DocContext<'_>, item: &Item, path_str: &str, dox: &str, link: &MarkdownLink) {
     let sym;
     let item_name = match item.name {
         Some(name) => {
@@ -1968,7 +2028,7 @@ fn privacy_error(
     let msg =
         format!("public documentation for `{}` links to private item `{}`", item_name, path_str);
 
-    report_diagnostic(cx, PRIVATE_INTRA_DOC_LINKS, &msg, item, dox, &link_range, |diag, sp| {
+    report_diagnostic(cx, PRIVATE_INTRA_DOC_LINKS, &msg, item, dox, &link.range, |diag, sp| {
         if let Some(sp) = sp {
             diag.span_label(sp, "this item is private");
         }
@@ -1997,53 +2057,49 @@ fn handle_variant(
         .parent(res.def_id())
         .map(|parent| {
             let parent_def = Res::Def(DefKind::Enum, parent);
-            let variant = cx.tcx.expect_variant_res(res);
+            let variant = cx.tcx.expect_variant_res(res.as_hir_res().unwrap());
             (parent_def, Some(format!("variant.{}", variant.ident.name)))
         })
         .ok_or_else(|| ResolutionFailure::NoParentItem.into())
 }
 
-// FIXME: At this point, this is basically a copy of the PrimitiveTypeTable
-const PRIMITIVES: &[(Symbol, Res)] = &[
-    (sym::u8, Res::PrimTy(hir::PrimTy::Uint(rustc_ast::UintTy::U8))),
-    (sym::u16, Res::PrimTy(hir::PrimTy::Uint(rustc_ast::UintTy::U16))),
-    (sym::u32, Res::PrimTy(hir::PrimTy::Uint(rustc_ast::UintTy::U32))),
-    (sym::u64, Res::PrimTy(hir::PrimTy::Uint(rustc_ast::UintTy::U64))),
-    (sym::u128, Res::PrimTy(hir::PrimTy::Uint(rustc_ast::UintTy::U128))),
-    (sym::usize, Res::PrimTy(hir::PrimTy::Uint(rustc_ast::UintTy::Usize))),
-    (sym::i8, Res::PrimTy(hir::PrimTy::Int(rustc_ast::IntTy::I8))),
-    (sym::i16, Res::PrimTy(hir::PrimTy::Int(rustc_ast::IntTy::I16))),
-    (sym::i32, Res::PrimTy(hir::PrimTy::Int(rustc_ast::IntTy::I32))),
-    (sym::i64, Res::PrimTy(hir::PrimTy::Int(rustc_ast::IntTy::I64))),
-    (sym::i128, Res::PrimTy(hir::PrimTy::Int(rustc_ast::IntTy::I128))),
-    (sym::isize, Res::PrimTy(hir::PrimTy::Int(rustc_ast::IntTy::Isize))),
-    (sym::f32, Res::PrimTy(hir::PrimTy::Float(rustc_ast::FloatTy::F32))),
-    (sym::f64, Res::PrimTy(hir::PrimTy::Float(rustc_ast::FloatTy::F64))),
-    (sym::str, Res::PrimTy(hir::PrimTy::Str)),
-    (sym::bool, Res::PrimTy(hir::PrimTy::Bool)),
-    (sym::char, Res::PrimTy(hir::PrimTy::Char)),
-];
-
 /// Resolve a primitive type or value.
-fn resolve_primitive(path_str: &str, ns: Namespace) -> Option<(Symbol, Res)> {
-    is_bool_value(path_str, ns).or_else(|| {
-        if ns == TypeNS {
-            // FIXME: this should be replaced by a lookup in PrimitiveTypeTable
-            let maybe_primitive = Symbol::intern(path_str);
-            PRIMITIVES.iter().find(|x| x.0 == maybe_primitive).copied()
-        } else {
-            None
-        }
-    })
-}
-
-/// Resolve a primitive value.
-fn is_bool_value(path_str: &str, ns: Namespace) -> Option<(Symbol, Res)> {
-    if ns == TypeNS && (path_str == "true" || path_str == "false") {
-        Some((sym::bool, Res::PrimTy(hir::PrimTy::Bool)))
-    } else {
-        None
+fn resolve_primitive(path_str: &str, ns: Namespace) -> Option<Res> {
+    if ns != TypeNS {
+        return None;
     }
+    use PrimitiveType::*;
+    let prim = match path_str {
+        "isize" => Isize,
+        "i8" => I8,
+        "i16" => I16,
+        "i32" => I32,
+        "i64" => I64,
+        "i128" => I128,
+        "usize" => Usize,
+        "u8" => U8,
+        "u16" => U16,
+        "u32" => U32,
+        "u64" => U64,
+        "u128" => U128,
+        "f32" => F32,
+        "f64" => F64,
+        "char" => Char,
+        "bool" | "true" | "false" => Bool,
+        "str" => Str,
+        // See #80181 for why these don't have symbols associated.
+        "slice" => Slice,
+        "array" => Array,
+        "tuple" => Tuple,
+        "unit" => Unit,
+        "pointer" | "*" | "*const" | "*mut" => RawPointer,
+        "reference" | "&" | "&mut" => Reference,
+        "fn" => Fn,
+        "never" | "!" => Never,
+        _ => return None,
+    };
+    debug!("resolved primitives {:?}", prim);
+    Some(Res::Primitive(prim))
 }
 
 fn strip_generics_from_path(path_str: &str) -> Result<String, ResolutionFailure<'static>> {
diff --git a/src/librustdoc/passes/collect_trait_impls.rs b/src/librustdoc/passes/collect_trait_impls.rs
index 60fcbe74872..9b0ae09cb3f 100644
--- a/src/librustdoc/passes/collect_trait_impls.rs
+++ b/src/librustdoc/passes/collect_trait_impls.rs
@@ -58,11 +58,11 @@ crate fn collect_trait_impls(krate: Crate, cx: &DocContext<'_>) -> Crate {
 
     // scan through included items ahead of time to splice in Deref targets to the "valid" sets
     for it in &new_items {
-        if let ImplItem(Impl { ref for_, ref trait_, ref items, .. }) = it.kind {
+        if let ImplItem(Impl { ref for_, ref trait_, ref items, .. }) = *it.kind {
             if cleaner.keep_item(for_) && trait_.def_id() == cx.tcx.lang_items().deref_trait() {
                 let target = items
                     .iter()
-                    .find_map(|item| match item.kind {
+                    .find_map(|item| match *item.kind {
                         TypedefItem(ref t, true) => Some(&t.type_),
                         _ => None,
                     })
@@ -78,7 +78,7 @@ crate fn collect_trait_impls(krate: Crate, cx: &DocContext<'_>) -> Crate {
     }
 
     new_items.retain(|it| {
-        if let ImplItem(Impl { ref for_, ref trait_, ref blanket_impl, .. }) = it.kind {
+        if let ImplItem(Impl { ref for_, ref trait_, ref blanket_impl, .. }) = *it.kind {
             cleaner.keep_item(for_)
                 || trait_.as_ref().map_or(false, |t| cleaner.keep_item(t))
                 || blanket_impl.is_some()
@@ -124,7 +124,7 @@ crate fn collect_trait_impls(krate: Crate, cx: &DocContext<'_>) -> Crate {
     }
 
     if let Some(ref mut it) = krate.module {
-        if let ModuleItem(Module { ref mut items, .. }) = it.kind {
+        if let ModuleItem(Module { ref mut items, .. }) = *it.kind {
             items.extend(synth.impls);
             items.extend(new_items);
         } else {
diff --git a/src/librustdoc/passes/doc_test_lints.rs b/src/librustdoc/passes/doc_test_lints.rs
index 1c1141e7c81..a513c2abc87 100644
--- a/src/librustdoc/passes/doc_test_lints.rs
+++ b/src/librustdoc/passes/doc_test_lints.rs
@@ -9,7 +9,7 @@ use crate::clean::*;
 use crate::core::DocContext;
 use crate::fold::DocFolder;
 use crate::html::markdown::{find_testable_code, ErrorCodes, Ignore, LangString};
-use rustc_middle::lint::LintSource;
+use rustc_middle::lint::LintLevelSource;
 use rustc_session::lint;
 
 crate const CHECK_PRIVATE_ITEMS_DOC_TESTS: Pass = Pass {
@@ -59,7 +59,7 @@ impl crate::doctest::Tester for Tests {
 
 crate fn should_have_doc_example(cx: &DocContext<'_>, item: &clean::Item) -> bool {
     if matches!(
-        item.kind,
+        *item.kind,
         clean::StructFieldItem(_)
             | clean::VariantItem(_)
             | clean::AssocConstItem(_, _)
@@ -77,7 +77,7 @@ crate fn should_have_doc_example(cx: &DocContext<'_>, item: &clean::Item) -> boo
     let hir_id = cx.tcx.hir().local_def_id_to_hir_id(item.def_id.expect_local());
     let (level, source) =
         cx.tcx.lint_level_at_node(lint::builtin::MISSING_DOC_CODE_EXAMPLES, hir_id);
-    level != lint::Level::Allow || matches!(source, LintSource::Default)
+    level != lint::Level::Allow || matches!(source, LintLevelSource::Default)
 }
 
 crate fn look_for_tests<'tcx>(cx: &DocContext<'tcx>, dox: &str, item: &Item) {
diff --git a/src/librustdoc/passes/html_tags.rs b/src/librustdoc/passes/html_tags.rs
index a7a1ba1118d..38ec2bef0ad 100644
--- a/src/librustdoc/passes/html_tags.rs
+++ b/src/librustdoc/passes/html_tags.rs
@@ -59,7 +59,7 @@ fn drop_tag(
                 continue;
             }
             let last_tag_name_low = last_tag_name.to_lowercase();
-            if ALLOWED_UNCLOSED.iter().any(|&at| at == &last_tag_name_low) {
+            if ALLOWED_UNCLOSED.iter().any(|&at| at == last_tag_name_low) {
                 continue;
             }
             // `tags` is used as a queue, meaning that everything after `pos` is included inside it.
diff --git a/src/librustdoc/passes/mod.rs b/src/librustdoc/passes/mod.rs
index 51818d7faf0..7ac42c75992 100644
--- a/src/librustdoc/passes/mod.rs
+++ b/src/librustdoc/passes/mod.rs
@@ -14,9 +14,6 @@ crate use stripper::*;
 mod non_autolinks;
 crate use self::non_autolinks::CHECK_NON_AUTOLINKS;
 
-mod collapse_docs;
-crate use self::collapse_docs::COLLAPSE_DOCS;
-
 mod strip_hidden;
 crate use self::strip_hidden::STRIP_HIDDEN;
 
@@ -84,7 +81,6 @@ crate const PASSES: &[Pass] = &[
     CHECK_PRIVATE_ITEMS_DOC_TESTS,
     STRIP_HIDDEN,
     UNINDENT_COMMENTS,
-    COLLAPSE_DOCS,
     STRIP_PRIVATE,
     STRIP_PRIV_IMPORTS,
     PROPAGATE_DOC_CFG,
@@ -99,7 +95,6 @@ crate const PASSES: &[Pass] = &[
 /// The list of passes run by default.
 crate const DEFAULT_PASSES: &[ConditionalPass] = &[
     ConditionalPass::always(COLLECT_TRAIT_IMPLS),
-    ConditionalPass::always(COLLAPSE_DOCS),
     ConditionalPass::always(UNINDENT_COMMENTS),
     ConditionalPass::always(CHECK_PRIVATE_ITEMS_DOC_TESTS),
     ConditionalPass::new(STRIP_HIDDEN, WhenNotDocumentHidden),
diff --git a/src/librustdoc/passes/strip_hidden.rs b/src/librustdoc/passes/strip_hidden.rs
index 6b59eb8cf28..a276b7a6337 100644
--- a/src/librustdoc/passes/strip_hidden.rs
+++ b/src/librustdoc/passes/strip_hidden.rs
@@ -26,9 +26,7 @@ crate fn strip_hidden(krate: clean::Crate, _: &DocContext<'_>) -> clean::Crate {
 
     // strip all impls referencing stripped items
     let mut stripper = ImplStripper { retained: &retained };
-    let krate = stripper.fold_crate(krate);
-
-    krate
+    stripper.fold_crate(krate)
 }
 
 struct Stripper<'a> {
@@ -41,7 +39,7 @@ impl<'a> DocFolder for Stripper<'a> {
         if i.attrs.lists(sym::doc).has_word(sym::hidden) {
             debug!("strip_hidden: stripping {:?} {:?}", i.type_(), i.name);
             // use a dedicated hidden item for given item type if any
-            match i.kind {
+            match *i.kind {
                 clean::StructFieldItem(..) | clean::ModuleItem(..) => {
                     // We need to recurse into stripped modules to
                     // strip things like impl methods but when doing so
@@ -49,7 +47,7 @@ impl<'a> DocFolder for Stripper<'a> {
                     let old = mem::replace(&mut self.update_retained, false);
                     let ret = StripItem(self.fold_item_recur(i)).strip();
                     self.update_retained = old;
-                    return ret;
+                    return Some(ret);
                 }
                 _ => return None,
             }
diff --git a/src/librustdoc/passes/stripper.rs b/src/librustdoc/passes/stripper.rs
index 444fd593ec9..a1924422f0e 100644
--- a/src/librustdoc/passes/stripper.rs
+++ b/src/librustdoc/passes/stripper.rs
@@ -13,7 +13,7 @@ crate struct Stripper<'a> {
 
 impl<'a> DocFolder for Stripper<'a> {
     fn fold_item(&mut self, i: Item) -> Option<Item> {
-        match i.kind {
+        match *i.kind {
             clean::StrippedItem(..) => {
                 // We need to recurse into stripped modules to strip things
                 // like impl methods but when doing so we must not add any
@@ -51,7 +51,7 @@ impl<'a> DocFolder for Stripper<'a> {
 
             clean::StructFieldItem(..) => {
                 if !i.visibility.is_public() {
-                    return StripItem(i).strip();
+                    return Some(StripItem(i).strip());
                 }
             }
 
@@ -61,7 +61,7 @@ impl<'a> DocFolder for Stripper<'a> {
                     let old = mem::replace(&mut self.update_retained, false);
                     let ret = StripItem(self.fold_item_recur(i)).strip();
                     self.update_retained = old;
-                    return ret;
+                    return Some(ret);
                 }
             }
 
@@ -86,7 +86,7 @@ impl<'a> DocFolder for Stripper<'a> {
             clean::KeywordItem(..) => {}
         }
 
-        let fastreturn = match i.kind {
+        let fastreturn = match *i.kind {
             // nothing left to do for traits (don't want to filter their
             // methods out, visibility controlled by the trait)
             clean::TraitItem(..) => true,
@@ -121,7 +121,7 @@ crate struct ImplStripper<'a> {
 
 impl<'a> DocFolder for ImplStripper<'a> {
     fn fold_item(&mut self, i: Item) -> Option<Item> {
-        if let clean::ImplItem(ref imp) = i.kind {
+        if let clean::ImplItem(ref imp) = *i.kind {
             // emptied none trait impls can be stripped
             if imp.trait_.is_none() && imp.items.is_empty() {
                 return None;
@@ -160,7 +160,7 @@ crate struct ImportStripper;
 
 impl DocFolder for ImportStripper {
     fn fold_item(&mut self, i: Item) -> Option<Item> {
-        match i.kind {
+        match *i.kind {
             clean::ExternCrateItem(..) | clean::ImportItem(..) if !i.visibility.is_public() => None,
             _ => Some(self.fold_item_recur(i)),
         }
diff --git a/src/librustdoc/passes/unindent_comments.rs b/src/librustdoc/passes/unindent_comments.rs
index d0345d1e48c..1cad480d4e8 100644
--- a/src/librustdoc/passes/unindent_comments.rs
+++ b/src/librustdoc/passes/unindent_comments.rs
@@ -68,7 +68,7 @@ fn unindent_fragments(docs: &mut Vec<DocFragment>) {
     let min_indent = match docs
         .iter()
         .map(|fragment| {
-            fragment.doc.lines().fold(usize::MAX, |min_indent, line| {
+            fragment.doc.as_str().lines().fold(usize::MAX, |min_indent, line| {
                 if line.chars().all(|c| c.is_whitespace()) {
                     min_indent
                 } else {
@@ -87,7 +87,7 @@ fn unindent_fragments(docs: &mut Vec<DocFragment>) {
     };
 
     for fragment in docs {
-        if fragment.doc.lines().count() == 0 {
+        if fragment.doc.as_str().lines().count() == 0 {
             continue;
         }
 
@@ -97,18 +97,6 @@ fn unindent_fragments(docs: &mut Vec<DocFragment>) {
             min_indent
         };
 
-        fragment.doc = fragment
-            .doc
-            .lines()
-            .map(|line| {
-                if line.chars().all(|c| c.is_whitespace()) {
-                    line.to_string()
-                } else {
-                    assert!(line.len() >= min_indent);
-                    line[min_indent..].to_string()
-                }
-            })
-            .collect::<Vec<_>>()
-            .join("\n");
+        fragment.indent = min_indent;
     }
 }
diff --git a/src/librustdoc/passes/unindent_comments/tests.rs b/src/librustdoc/passes/unindent_comments/tests.rs
index 9dec71f7683..9c9924841b9 100644
--- a/src/librustdoc/passes/unindent_comments/tests.rs
+++ b/src/librustdoc/passes/unindent_comments/tests.rs
@@ -1,21 +1,27 @@
 use super::*;
 use rustc_span::source_map::DUMMY_SP;
+use rustc_span::symbol::Symbol;
+use rustc_span::with_default_session_globals;
 
 fn create_doc_fragment(s: &str) -> Vec<DocFragment> {
     vec![DocFragment {
         line: 0,
         span: DUMMY_SP,
         parent_module: None,
-        doc: s.to_string(),
+        doc: Symbol::intern(s),
         kind: DocFragmentKind::SugaredDoc,
+        need_backline: false,
+        indent: 0,
     }]
 }
 
 #[track_caller]
 fn run_test(input: &str, expected: &str) {
-    let mut s = create_doc_fragment(input);
-    unindent_fragments(&mut s);
-    assert_eq!(s[0].doc, expected);
+    with_default_session_globals(|| {
+        let mut s = create_doc_fragment(input);
+        unindent_fragments(&mut s);
+        assert_eq!(&s.iter().collect::<String>(), expected);
+    });
 }
 
 #[test]