about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--compiler/rustc_errors/src/lib.rs17
-rw-r--r--compiler/rustc_session/src/session.rs4
-rw-r--r--src/librustdoc/clean/blanket_impl.rs2
-rw-r--r--src/librustdoc/clean/inline.rs2
-rw-r--r--src/librustdoc/clean/mod.rs3
-rw-r--r--src/librustdoc/clean/types.rs11
-rw-r--r--src/librustdoc/config.rs12
-rw-r--r--src/librustdoc/html/format.rs18
-rw-r--r--src/librustdoc/html/highlight.rs232
-rw-r--r--src/librustdoc/html/highlight/fixtures/highlight.html4
-rw-r--r--src/librustdoc/html/highlight/fixtures/sample.html2
-rw-r--r--src/librustdoc/html/highlight/tests.rs18
-rw-r--r--src/librustdoc/html/markdown.rs1
-rw-r--r--src/librustdoc/html/render/context.rs70
-rw-r--r--src/librustdoc/html/render/mod.rs2
-rw-r--r--src/librustdoc/html/render/print_item.rs3
-rw-r--r--src/librustdoc/html/render/span_map.rs164
-rw-r--r--src/librustdoc/html/render/write_shared.rs4
-rw-r--r--src/librustdoc/html/sources.rs172
-rw-r--r--src/librustdoc/html/static/css/rustdoc.css4
-rw-r--r--src/librustdoc/lib.rs7
-rw-r--r--src/test/rustdoc-ui/generate-link-to-definition-opt-unstable.rs6
-rw-r--r--src/test/rustdoc-ui/generate-link-to-definition-opt-unstable.stderr2
-rw-r--r--src/test/rustdoc-ui/generate-link-to-definition-opt.rs6
-rw-r--r--src/test/rustdoc-ui/generate-link-to-definition-opt.stderr2
-rw-r--r--src/test/rustdoc-ui/generate-link-to-definition-opt2.rs6
-rw-r--r--src/test/rustdoc-ui/generate-link-to-definition-opt2.stderr2
-rw-r--r--src/test/rustdoc/auxiliary/source-code-bar.rs17
-rw-r--r--src/test/rustdoc/auxiliary/source_code.rs1
-rw-r--r--src/test/rustdoc/check-source-code-urls-to-def.rs44
30 files changed, 707 insertions, 131 deletions
diff --git a/compiler/rustc_errors/src/lib.rs b/compiler/rustc_errors/src/lib.rs
index 993a7c2c162..fc0924ac5f9 100644
--- a/compiler/rustc_errors/src/lib.rs
+++ b/compiler/rustc_errors/src/lib.rs
@@ -342,6 +342,9 @@ struct HandlerInner {
     deduplicated_warn_count: usize,
 
     future_breakage_diagnostics: Vec<Diagnostic>,
+
+    /// If set to `true`, no warning or error will be emitted.
+    quiet: bool,
 }
 
 /// A key denoting where from a diagnostic was stashed.
@@ -456,10 +459,19 @@ impl Handler {
                 emitted_diagnostics: Default::default(),
                 stashed_diagnostics: Default::default(),
                 future_breakage_diagnostics: Vec::new(),
+                quiet: false,
             }),
         }
     }
 
+    pub fn with_disabled_diagnostic<T, F: FnOnce() -> T>(&self, f: F) -> T {
+        let prev = self.inner.borrow_mut().quiet;
+        self.inner.borrow_mut().quiet = true;
+        let ret = f();
+        self.inner.borrow_mut().quiet = prev;
+        ret
+    }
+
     // This is here to not allow mutation of flags;
     // as of this writing it's only used in tests in librustc_middle.
     pub fn can_emit_warnings(&self) -> bool {
@@ -818,7 +830,7 @@ impl HandlerInner {
     }
 
     fn emit_diagnostic(&mut self, diagnostic: &Diagnostic) {
-        if diagnostic.cancelled() {
+        if diagnostic.cancelled() || self.quiet {
             return;
         }
 
@@ -1035,6 +1047,9 @@ impl HandlerInner {
     }
 
     fn delay_as_bug(&mut self, diagnostic: Diagnostic) {
+        if self.quiet {
+            return;
+        }
         if self.flags.report_delayed_bugs {
             self.emit_diagnostic(&diagnostic);
         }
diff --git a/compiler/rustc_session/src/session.rs b/compiler/rustc_session/src/session.rs
index 9ab6dbb1ea9..fe87867d299 100644
--- a/compiler/rustc_session/src/session.rs
+++ b/compiler/rustc_session/src/session.rs
@@ -500,6 +500,10 @@ impl Session {
         &self.parse_sess.span_diagnostic
     }
 
+    pub fn with_disabled_diagnostic<T, F: FnOnce() -> T>(&self, f: F) -> T {
+        self.parse_sess.span_diagnostic.with_disabled_diagnostic(f)
+    }
+
     /// Analogous to calling methods on the given `DiagnosticBuilder`, but
     /// deduplicates on lint ID, span (if any), and message for this `Session`
     fn diag_once<'a, 'b>(
diff --git a/src/librustdoc/clean/blanket_impl.rs b/src/librustdoc/clean/blanket_impl.rs
index 8f74a48547d..207c89cbfe8 100644
--- a/src/librustdoc/clean/blanket_impl.rs
+++ b/src/librustdoc/clean/blanket_impl.rs
@@ -98,7 +98,7 @@ impl<'a, 'tcx> BlanketImplFinder<'a, 'tcx> {
                     visibility: Inherited,
                     def_id: ItemId::Blanket { impl_id: impl_def_id, for_: item_def_id },
                     kind: box ImplItem(Impl {
-                        span: Span::from_rustc_span(self.cx.tcx.def_span(impl_def_id)),
+                        span: Span::new(self.cx.tcx.def_span(impl_def_id)),
                         unsafety: hir::Unsafety::Normal,
                         generics: (
                             self.cx.tcx.generics_of(impl_def_id),
diff --git a/src/librustdoc/clean/inline.rs b/src/librustdoc/clean/inline.rs
index b3b89e6e673..43979423ae6 100644
--- a/src/librustdoc/clean/inline.rs
+++ b/src/librustdoc/clean/inline.rs
@@ -517,7 +517,7 @@ fn build_module(
         }
     }
 
-    let span = clean::Span::from_rustc_span(cx.tcx.def_span(did));
+    let span = clean::Span::new(cx.tcx.def_span(did));
     clean::Module { items, span }
 }
 
diff --git a/src/librustdoc/clean/mod.rs b/src/librustdoc/clean/mod.rs
index b3fc1e73f78..3d65fcedaf4 100644
--- a/src/librustdoc/clean/mod.rs
+++ b/src/librustdoc/clean/mod.rs
@@ -95,7 +95,8 @@ impl Clean<Item> for doctree::Module<'_> {
 
         // determine if we should display the inner contents or
         // the outer `mod` item for the source code.
-        let span = Span::from_rustc_span({
+
+        let span = Span::new({
             let where_outer = self.where_outer(cx.tcx);
             let sm = cx.sess().source_map();
             let outer = sm.lookup_char_pos(where_outer.lo());
diff --git a/src/librustdoc/clean/types.rs b/src/librustdoc/clean/types.rs
index 5c73d3de5b9..22e4d21c87b 100644
--- a/src/librustdoc/clean/types.rs
+++ b/src/librustdoc/clean/types.rs
@@ -343,7 +343,7 @@ crate struct Item {
 rustc_data_structures::static_assert_size!(Item, 56);
 
 crate fn rustc_span(def_id: DefId, tcx: TyCtxt<'_>) -> Span {
-    Span::from_rustc_span(def_id.as_local().map_or_else(
+    Span::new(def_id.as_local().map_or_else(
         || tcx.def_span(def_id),
         |local| {
             let hir = tcx.hir();
@@ -1943,10 +1943,11 @@ crate enum Variant {
 crate struct Span(rustc_span::Span);
 
 impl Span {
-    crate fn from_rustc_span(sp: rustc_span::Span) -> Self {
-        // Get the macro invocation instead of the definition,
-        // in case the span is result of a macro expansion.
-        // (See rust-lang/rust#39726)
+    /// Wraps a [`rustc_span::Span`]. In case this span is the result of a macro expansion, the
+    /// span will be updated to point to the macro invocation instead of the macro definition.
+    ///
+    /// (See rust-lang/rust#39726)
+    crate fn new(sp: rustc_span::Span) -> Self {
         Self(sp.source_callsite())
     }
 
diff --git a/src/librustdoc/config.rs b/src/librustdoc/config.rs
index abd1fd2bf39..e44158bc042 100644
--- a/src/librustdoc/config.rs
+++ b/src/librustdoc/config.rs
@@ -276,6 +276,8 @@ crate struct RenderOptions {
     crate show_type_layout: bool,
     crate unstable_features: rustc_feature::UnstableFeatures,
     crate emit: Vec<EmitType>,
+    /// If `true`, HTML source pages will generate links for items to their definition.
+    crate generate_link_to_definition: bool,
 }
 
 #[derive(Copy, Clone, Debug, PartialEq, Eq)]
@@ -655,6 +657,15 @@ impl Options {
         let generate_redirect_map = matches.opt_present("generate-redirect-map");
         let show_type_layout = matches.opt_present("show-type-layout");
         let nocapture = matches.opt_present("nocapture");
+        let generate_link_to_definition = matches.opt_present("generate-link-to-definition");
+
+        if generate_link_to_definition && (show_coverage || output_format != OutputFormat::Html) {
+            diag.struct_err(
+                "--generate-link-to-definition option can only be used with HTML output format",
+            )
+            .emit();
+            return Err(1);
+        }
 
         let (lint_opts, describe_lints, lint_cap) =
             get_cmd_lint_options(matches, error_format, &debugging_opts);
@@ -721,6 +732,7 @@ impl Options {
                     crate_name.as_deref(),
                 ),
                 emit,
+                generate_link_to_definition,
             },
             crate_name,
             output_format,
diff --git a/src/librustdoc/html/format.rs b/src/librustdoc/html/format.rs
index 8ab6aa775d2..eb7c12d13c3 100644
--- a/src/librustdoc/html/format.rs
+++ b/src/librustdoc/html/format.rs
@@ -484,7 +484,11 @@ crate enum HrefError {
     NotInExternalCache,
 }
 
-crate fn href(did: DefId, cx: &Context<'_>) -> Result<(String, ItemType, Vec<String>), HrefError> {
+crate fn href_with_root_path(
+    did: DefId,
+    cx: &Context<'_>,
+    root_path: Option<&str>,
+) -> Result<(String, ItemType, Vec<String>), HrefError> {
     let cache = &cx.cache();
     let relative_to = &cx.current;
     fn to_module_fqp(shortty: ItemType, fqp: &[String]) -> &[String] {
@@ -495,6 +499,7 @@ crate fn href(did: DefId, cx: &Context<'_>) -> Result<(String, ItemType, Vec<Str
         return Err(HrefError::Private);
     }
 
+    let mut is_remote = false;
     let (fqp, shortty, mut url_parts) = match cache.paths.get(&did) {
         Some(&(ref fqp, shortty)) => (fqp, shortty, {
             let module_fqp = to_module_fqp(shortty, fqp);
@@ -508,6 +513,7 @@ crate fn href(did: DefId, cx: &Context<'_>) -> Result<(String, ItemType, Vec<Str
                     shortty,
                     match cache.extern_locations[&did.krate] {
                         ExternalLocation::Remote(ref s) => {
+                            is_remote = true;
                             let s = s.trim_end_matches('/');
                             let mut s = vec![s];
                             s.extend(module_fqp[..].iter().map(String::as_str));
@@ -522,6 +528,12 @@ crate fn href(did: DefId, cx: &Context<'_>) -> Result<(String, ItemType, Vec<Str
             }
         }
     };
+    if !is_remote {
+        if let Some(root_path) = root_path {
+            let root = root_path.trim_end_matches('/');
+            url_parts.insert(0, root);
+        }
+    }
     let last = &fqp.last().unwrap()[..];
     let filename;
     match shortty {
@@ -536,6 +548,10 @@ crate fn href(did: DefId, cx: &Context<'_>) -> Result<(String, ItemType, Vec<Str
     Ok((url_parts.join("/"), shortty, fqp.to_vec()))
 }
 
+crate fn href(did: DefId, cx: &Context<'_>) -> Result<(String, ItemType, Vec<String>), HrefError> {
+    href_with_root_path(did, cx, None)
+}
+
 /// Both paths should only be modules.
 /// This is because modules get their own directories; that is, `std::vec` and `std::vec::Vec` will
 /// both need `../iter/trait.Iterator.html` to get at the iterator trait.
diff --git a/src/librustdoc/html/highlight.rs b/src/librustdoc/html/highlight.rs
index 33b1d98313c..3cdb1352bef 100644
--- a/src/librustdoc/html/highlight.rs
+++ b/src/librustdoc/html/highlight.rs
@@ -6,15 +6,28 @@
 //! Use the `render_with_highlighting` to highlight some rust code.
 
 use crate::html::escape::Escape;
+use crate::html::render::Context;
 
-use std::fmt::Display;
+use std::fmt::{Display, Write};
 use std::iter::Peekable;
 
 use rustc_lexer::{LiteralKind, TokenKind};
 use rustc_span::edition::Edition;
 use rustc_span::symbol::Symbol;
-
-use super::format::Buffer;
+use rustc_span::{BytePos, Span, DUMMY_SP};
+
+use super::format::{self, Buffer};
+use super::render::LinkFromSrc;
+
+/// This type is needed in case we want to render links on items to allow to go to their definition.
+crate struct ContextInfo<'a, 'b, 'c> {
+    crate context: &'a Context<'b>,
+    /// This span contains the current file we're going through.
+    crate file_span: Span,
+    /// This field is used to know "how far" from the top of the directory we are to link to either
+    /// documentation pages or other source pages.
+    crate root_path: &'c str,
+}
 
 /// Highlights `src`, returning the HTML output.
 crate fn render_with_highlighting(
@@ -25,6 +38,7 @@ crate fn render_with_highlighting(
     tooltip: Option<(Option<Edition>, &str)>,
     edition: Edition,
     extra_content: Option<Buffer>,
+    context_info: Option<ContextInfo<'_, '_, '_>>,
 ) {
     debug!("highlighting: ================\n{}\n==============", src);
     if let Some((edition_info, class)) = tooltip {
@@ -41,7 +55,7 @@ crate fn render_with_highlighting(
     }
 
     write_header(out, class, extra_content);
-    write_code(out, &src, edition);
+    write_code(out, &src, edition, context_info);
     write_footer(out, playground_button);
 }
 
@@ -57,16 +71,33 @@ fn write_header(out: &mut Buffer, class: Option<&str>, extra_content: Option<Buf
     }
 }
 
-fn write_code(out: &mut Buffer, src: &str, edition: Edition) {
+/// Convert the given `src` source code into HTML by adding classes for highlighting.
+///
+/// This code is used to render code blocks (in the documentation) as well as the source code pages.
+///
+/// Some explanations on the last arguments:
+///
+/// In case we are rendering a code block and not a source code file, `context_info` will be `None`.
+/// To put it more simply: if `context_info` is `None`, the code won't try to generate links to an
+/// item definition.
+///
+/// More explanations about spans and how we use them here are provided in the
+fn write_code(
+    out: &mut Buffer,
+    src: &str,
+    edition: Edition,
+    context_info: Option<ContextInfo<'_, '_, '_>>,
+) {
     // This replace allows to fix how the code source with DOS backline characters is displayed.
     let src = src.replace("\r\n", "\n");
-    Classifier::new(&src, edition).highlight(&mut |highlight| {
-        match highlight {
-            Highlight::Token { text, class } => string(out, Escape(text), class),
-            Highlight::EnterSpan { class } => enter_span(out, class),
-            Highlight::ExitSpan => exit_span(out),
-        };
-    });
+    Classifier::new(&src, edition, context_info.as_ref().map(|c| c.file_span).unwrap_or(DUMMY_SP))
+        .highlight(&mut |highlight| {
+            match highlight {
+                Highlight::Token { text, class } => string(out, Escape(text), class, &context_info),
+                Highlight::EnterSpan { class } => enter_span(out, class),
+                Highlight::ExitSpan => exit_span(out),
+            };
+        });
 }
 
 fn write_footer(out: &mut Buffer, playground_button: Option<&str>) {
@@ -82,14 +113,14 @@ enum Class {
     KeyWord,
     // Keywords that do pointer/reference stuff.
     RefKeyWord,
-    Self_,
+    Self_(Span),
     Op,
     Macro,
     MacroNonTerminal,
     String,
     Number,
     Bool,
-    Ident,
+    Ident(Span),
     Lifetime,
     PreludeTy,
     PreludeVal,
@@ -105,20 +136,29 @@ impl Class {
             Class::Attribute => "attribute",
             Class::KeyWord => "kw",
             Class::RefKeyWord => "kw-2",
-            Class::Self_ => "self",
+            Class::Self_(_) => "self",
             Class::Op => "op",
             Class::Macro => "macro",
             Class::MacroNonTerminal => "macro-nonterminal",
             Class::String => "string",
             Class::Number => "number",
             Class::Bool => "bool-val",
-            Class::Ident => "ident",
+            Class::Ident(_) => "ident",
             Class::Lifetime => "lifetime",
             Class::PreludeTy => "prelude-ty",
             Class::PreludeVal => "prelude-val",
             Class::QuestionMark => "question-mark",
         }
     }
+
+    /// In case this is an item which can be converted into a link to a definition, it'll contain
+    /// a "span" (a tuple representing `(lo, hi)` equivalent of `Span`).
+    fn get_span(self) -> Option<Span> {
+        match self {
+            Self::Ident(sp) | Self::Self_(sp) => Some(sp),
+            _ => None,
+        }
+    }
 }
 
 enum Highlight<'a> {
@@ -144,14 +184,19 @@ impl Iterator for TokenIter<'a> {
     }
 }
 
-fn get_real_ident_class(text: &str, edition: Edition) -> Class {
-    match text {
+/// Classifies into identifier class; returns `None` if this is a non-keyword identifier.
+fn get_real_ident_class(text: &str, edition: Edition, allow_path_keywords: bool) -> Option<Class> {
+    let ignore: &[&str] =
+        if allow_path_keywords { &["self", "Self", "super", "crate"] } else { &["self", "Self"] };
+    if ignore.iter().any(|k| *k == text) {
+        return None;
+    }
+    Some(match text {
         "ref" | "mut" => Class::RefKeyWord,
-        "self" | "Self" => Class::Self_,
         "false" | "true" => Class::Bool,
         _ if Symbol::intern(text).is_reserved(|| edition) => Class::KeyWord,
-        _ => Class::Ident,
-    }
+        _ => return None,
+    })
 }
 
 /// Processes program tokens, classifying strings of text by highlighting
@@ -163,11 +208,14 @@ struct Classifier<'a> {
     in_macro_nonterminal: bool,
     edition: Edition,
     byte_pos: u32,
+    file_span: Span,
     src: &'a str,
 }
 
 impl<'a> Classifier<'a> {
-    fn new(src: &str, edition: Edition) -> Classifier<'_> {
+    /// Takes as argument the source code to HTML-ify, the rust edition to use and the source code
+    /// file span which will be used later on by the `span_correspondance_map`.
+    fn new(src: &str, edition: Edition, file_span: Span) -> Classifier<'_> {
         let tokens = TokenIter { src }.peekable();
         Classifier {
             tokens,
@@ -176,10 +224,18 @@ impl<'a> Classifier<'a> {
             in_macro_nonterminal: false,
             edition,
             byte_pos: 0,
+            file_span,
             src,
         }
     }
 
+    /// Convenient wrapper to create a [`Span`] from a position in the file.
+    fn new_span(&self, lo: u32, text: &str) -> Span {
+        let hi = lo + text.len() as u32;
+        let file_lo = self.file_span.lo();
+        self.file_span.with_lo(file_lo + BytePos(lo)).with_hi(file_lo + BytePos(hi))
+    }
+
     /// Concatenate colons and idents as one when possible.
     fn get_full_ident_path(&mut self) -> Vec<(TokenKind, usize, usize)> {
         let start = self.byte_pos as usize;
@@ -201,17 +257,17 @@ impl<'a> Classifier<'a> {
                 if has_ident {
                     return vec![(TokenKind::Ident, start, pos), (TokenKind::Colon, pos, pos + nb)];
                 } else {
-                    return vec![(TokenKind::Colon, pos, pos + nb)];
+                    return vec![(TokenKind::Colon, start, pos + nb)];
                 }
             }
 
-            if let Some((Class::Ident, text)) = self.tokens.peek().map(|(token, text)| {
+            if let Some((None, text)) = self.tokens.peek().map(|(token, text)| {
                 if *token == TokenKind::Ident {
-                    let class = get_real_ident_class(text, edition);
+                    let class = get_real_ident_class(text, edition, true);
                     (class, text)
                 } else {
                     // Doesn't matter which Class we put in here...
-                    (Class::Comment, text)
+                    (Some(Class::Comment), text)
                 }
             }) {
                 // We only "add" the colon if there is an ident behind.
@@ -221,7 +277,7 @@ impl<'a> Classifier<'a> {
             } else if nb > 0 && has_ident {
                 return vec![(TokenKind::Ident, start, pos), (TokenKind::Colon, pos, pos + nb)];
             } else if nb > 0 {
-                return vec![(TokenKind::Colon, pos, pos + nb)];
+                return vec![(TokenKind::Colon, start, start + nb)];
             } else if has_ident {
                 return vec![(TokenKind::Ident, start, pos)];
             } else {
@@ -230,11 +286,15 @@ impl<'a> Classifier<'a> {
         }
     }
 
-    /// Wraps the tokens iteration to ensure that the byte_pos is always correct.
-    fn next(&mut self) -> Option<(TokenKind, &'a str)> {
+    /// Wraps the tokens iteration to ensure that the `byte_pos` is always correct.
+    ///
+    /// It returns the token's kind, the token as a string and its byte position in the source
+    /// string.
+    fn next(&mut self) -> Option<(TokenKind, &'a str, u32)> {
         if let Some((kind, text)) = self.tokens.next() {
+            let before = self.byte_pos;
             self.byte_pos += text.len() as u32;
-            Some((kind, text))
+            Some((kind, text, before))
         } else {
             None
         }
@@ -254,23 +314,36 @@ impl<'a> Classifier<'a> {
                 .unwrap_or(false)
             {
                 let tokens = self.get_full_ident_path();
-                for (token, start, end) in tokens {
-                    let text = &self.src[start..end];
-                    self.advance(token, text, sink);
+                for (token, start, end) in &tokens {
+                    let text = &self.src[*start..*end];
+                    self.advance(*token, text, sink, *start as u32);
                     self.byte_pos += text.len() as u32;
                 }
+                if !tokens.is_empty() {
+                    continue;
+                }
             }
-            if let Some((token, text)) = self.next() {
-                self.advance(token, text, sink);
+            if let Some((token, text, before)) = self.next() {
+                self.advance(token, text, sink, before);
             } else {
                 break;
             }
         }
     }
 
-    /// Single step of highlighting. This will classify `token`, but maybe also
-    /// a couple of following ones as well.
-    fn advance(&mut self, token: TokenKind, text: &'a str, sink: &mut dyn FnMut(Highlight<'a>)) {
+    /// Single step of highlighting. This will classify `token`, but maybe also a couple of
+    /// following ones as well.
+    ///
+    /// `before` is the position of the given token in the `source` string and is used as "lo" byte
+    /// in case we want to try to generate a link for this token using the
+    /// `span_correspondance_map`.
+    fn advance(
+        &mut self,
+        token: TokenKind,
+        text: &'a str,
+        sink: &mut dyn FnMut(Highlight<'a>),
+        before: u32,
+    ) {
         let lookahead = self.peek();
         let no_highlight = |sink: &mut dyn FnMut(_)| sink(Highlight::Token { text, class: None });
         let class = match token {
@@ -401,19 +474,22 @@ impl<'a> Classifier<'a> {
                 sink(Highlight::Token { text, class: None });
                 return;
             }
-            TokenKind::Ident => match get_real_ident_class(text, self.edition) {
-                Class::Ident => match text {
+            TokenKind::Ident => match get_real_ident_class(text, self.edition, false) {
+                None => match text {
                     "Option" | "Result" => Class::PreludeTy,
                     "Some" | "None" | "Ok" | "Err" => Class::PreludeVal,
                     _ if self.in_macro_nonterminal => {
                         self.in_macro_nonterminal = false;
                         Class::MacroNonTerminal
                     }
-                    _ => Class::Ident,
+                    "self" | "Self" => Class::Self_(self.new_span(before, text)),
+                    _ => Class::Ident(self.new_span(before, text)),
                 },
-                c => c,
+                Some(c) => c,
             },
-            TokenKind::RawIdent | TokenKind::UnknownPrefix => Class::Ident,
+            TokenKind::RawIdent | TokenKind::UnknownPrefix => {
+                Class::Ident(self.new_span(before, text))
+            }
             TokenKind::Lifetime { .. } => Class::Lifetime,
         };
         // Anything that didn't return above is the simple case where we the
@@ -446,13 +522,75 @@ fn exit_span(out: &mut Buffer) {
 ///     enter_span(Foo), string("text", None), exit_span()
 ///     string("text", Foo)
 /// ```
+///
 /// The latter can be thought of as a shorthand for the former, which is more
 /// flexible.
-fn string<T: Display>(out: &mut Buffer, text: T, klass: Option<Class>) {
-    match klass {
-        None => write!(out, "{}", text),
-        Some(klass) => write!(out, "<span class=\"{}\">{}</span>", klass.as_html(), text),
+///
+/// Note that if `context` is not `None` and that the given `klass` contains a `Span`, the function
+/// will then try to find this `span` in the `span_correspondance_map`. If found, it'll then
+/// generate a link for this element (which corresponds to where its definition is located).
+fn string<T: Display>(
+    out: &mut Buffer,
+    text: T,
+    klass: Option<Class>,
+    context_info: &Option<ContextInfo<'_, '_, '_>>,
+) {
+    let klass = match klass {
+        None => return write!(out, "{}", text),
+        Some(klass) => klass,
+    };
+    let def_span = match klass.get_span() {
+        Some(d) => d,
+        None => {
+            write!(out, "<span class=\"{}\">{}</span>", klass.as_html(), text);
+            return;
+        }
+    };
+    let mut text_s = text.to_string();
+    if text_s.contains("::") {
+        text_s = text_s.split("::").intersperse("::").fold(String::new(), |mut path, t| {
+            match t {
+                "self" | "Self" => write!(
+                    &mut path,
+                    "<span class=\"{}\">{}</span>",
+                    Class::Self_(DUMMY_SP).as_html(),
+                    t
+                ),
+                "crate" | "super" => {
+                    write!(&mut path, "<span class=\"{}\">{}</span>", Class::KeyWord.as_html(), t)
+                }
+                t => write!(&mut path, "{}", t),
+            }
+            .expect("Failed to build source HTML path");
+            path
+        });
+    }
+    if let Some(context_info) = context_info {
+        if let Some(href) =
+            context_info.context.shared.span_correspondance_map.get(&def_span).and_then(|href| {
+                let context = context_info.context;
+                // FIXME: later on, it'd be nice to provide two links (if possible) for all items:
+                // one to the documentation page and one to the source definition.
+                // FIXME: currently, external items only generate a link to their documentation,
+                // a link to their definition can be generated using this:
+                // https://github.com/rust-lang/rust/blob/60f1a2fc4b535ead9c85ce085fdce49b1b097531/src/librustdoc/html/render/context.rs#L315-L338
+                match href {
+                    LinkFromSrc::Local(span) => context
+                        .href_from_span(*span)
+                        .map(|s| format!("{}{}", context_info.root_path, s)),
+                    LinkFromSrc::External(def_id) => {
+                        format::href_with_root_path(*def_id, context, Some(context_info.root_path))
+                            .ok()
+                            .map(|(url, _, _)| url)
+                    }
+                }
+            })
+        {
+            write!(out, "<a class=\"{}\" href=\"{}\">{}</a>", klass.as_html(), href, text_s);
+            return;
+        }
     }
+    write!(out, "<span class=\"{}\">{}</span>", klass.as_html(), text_s);
 }
 
 #[cfg(test)]
diff --git a/src/librustdoc/html/highlight/fixtures/highlight.html b/src/librustdoc/html/highlight/fixtures/highlight.html
new file mode 100644
index 00000000000..abc2db1790c
--- /dev/null
+++ b/src/librustdoc/html/highlight/fixtures/highlight.html
@@ -0,0 +1,4 @@
+<span class="kw">use</span> <span class="ident"><span class="kw">crate</span>::a::foo</span>;
+<span class="kw">use</span> <span class="ident"><span class="self">self</span>::whatever</span>;
+<span class="kw">let</span> <span class="ident">x</span> <span class="op">=</span> <span class="ident"><span class="kw">super</span>::b::foo</span>;
+<span class="kw">let</span> <span class="ident">y</span> <span class="op">=</span> <span class="ident"><span class="self">Self</span>::whatever</span>;
\ No newline at end of file
diff --git a/src/librustdoc/html/highlight/fixtures/sample.html b/src/librustdoc/html/highlight/fixtures/sample.html
index 8d23477bbcb..866caea9256 100644
--- a/src/librustdoc/html/highlight/fixtures/sample.html
+++ b/src/librustdoc/html/highlight/fixtures/sample.html
@@ -23,7 +23,7 @@
     <span class="macro">assert!</span>(<span class="self">self</span>.<span class="ident">length</span> <span class="op">&lt;</span> <span class="ident">N</span> <span class="op">&amp;&amp;</span> <span class="ident">index</span> <span class="op">&lt;</span><span class="op">=</span> <span class="self">self</span>.<span class="ident">length</span>);
     <span class="ident">::std::env::var</span>(<span class="string">&quot;gateau&quot;</span>).<span class="ident">is_ok</span>();
     <span class="attribute">#[<span class="ident">rustfmt::skip</span>]</span>
-    <span class="kw">let</span> <span class="ident">s</span>:<span class="ident">std</span><span class="ident">::path::PathBuf</span> <span class="op">=</span> <span class="ident">std::path::PathBuf::new</span>();
+    <span class="kw">let</span> <span class="ident">s</span>:<span class="ident">std::path::PathBuf</span> <span class="op">=</span> <span class="ident">std::path::PathBuf::new</span>();
     <span class="kw">let</span> <span class="kw-2">mut</span> <span class="ident">s</span> <span class="op">=</span> <span class="ident">String::new</span>();
 
     <span class="kw">match</span> <span class="kw-2">&amp;</span><span class="ident">s</span> {
diff --git a/src/librustdoc/html/highlight/tests.rs b/src/librustdoc/html/highlight/tests.rs
index a505865b149..68592ae96c1 100644
--- a/src/librustdoc/html/highlight/tests.rs
+++ b/src/librustdoc/html/highlight/tests.rs
@@ -22,7 +22,7 @@ fn test_html_highlighting() {
         let src = include_str!("fixtures/sample.rs");
         let html = {
             let mut out = Buffer::new();
-            write_code(&mut out, src, Edition::Edition2018);
+            write_code(&mut out, src, Edition::Edition2018, None);
             format!("{}<pre><code>{}</code></pre>\n", STYLE, out.into_inner())
         };
         expect_file!["fixtures/sample.html"].assert_eq(&html);
@@ -36,7 +36,21 @@ fn test_dos_backline() {
     println!(\"foo\");\r\n\
 }\r\n";
         let mut html = Buffer::new();
-        write_code(&mut html, src, Edition::Edition2018);
+        write_code(&mut html, src, Edition::Edition2018, None);
         expect_file!["fixtures/dos_line.html"].assert_eq(&html.into_inner());
     });
 }
+
+#[test]
+fn test_keyword_highlight() {
+    create_default_session_globals_then(|| {
+        let src = "use crate::a::foo;
+use self::whatever;
+let x = super::b::foo;
+let y = Self::whatever;";
+
+        let mut html = Buffer::new();
+        write_code(&mut html, src, Edition::Edition2018, None);
+        expect_file!["fixtures/highlight.html"].assert_eq(&html.into_inner());
+    });
+}
diff --git a/src/librustdoc/html/markdown.rs b/src/librustdoc/html/markdown.rs
index b8756d2526e..472323daf30 100644
--- a/src/librustdoc/html/markdown.rs
+++ b/src/librustdoc/html/markdown.rs
@@ -330,6 +330,7 @@ impl<'a, I: Iterator<Item = Event<'a>>> Iterator for CodeBlocks<'_, 'a, I> {
             tooltip,
             edition,
             None,
+            None,
         );
         Some(Event::Html(s.into_inner().into()))
     }
diff --git a/src/librustdoc/html/render/context.rs b/src/librustdoc/html/render/context.rs
index b6c3220901f..6ce0828e159 100644
--- a/src/librustdoc/html/render/context.rs
+++ b/src/librustdoc/html/render/context.rs
@@ -17,7 +17,10 @@ use rustc_span::symbol::sym;
 use super::cache::{build_index, ExternalLocation};
 use super::print_item::{full_path, item_path, print_item};
 use super::write_shared::write_shared;
-use super::{print_sidebar, settings, AllTypes, NameDoc, StylePath, BASIC_KEYWORDS};
+use super::{
+    collect_spans_and_sources, print_sidebar, settings, AllTypes, LinkFromSrc, NameDoc, StylePath,
+    BASIC_KEYWORDS,
+};
 
 use crate::clean;
 use crate::clean::ExternalCrate;
@@ -46,7 +49,7 @@ crate struct Context<'tcx> {
     pub(crate) current: Vec<String>,
     /// The current destination folder of where HTML artifacts should be placed.
     /// This changes as the context descends into the module hierarchy.
-    pub(super) dst: PathBuf,
+    crate dst: PathBuf,
     /// A flag, which when `true`, will render pages which redirect to the
     /// real location of an item. This is used to allow external links to
     /// publicly reused items to redirect to the right location.
@@ -58,7 +61,7 @@ crate struct Context<'tcx> {
     /// Issue for improving the situation: [#82381][]
     ///
     /// [#82381]: https://github.com/rust-lang/rust/issues/82381
-    pub(super) shared: Rc<SharedContext<'tcx>>,
+    crate shared: Rc<SharedContext<'tcx>>,
     /// The [`Cache`] used during rendering.
     ///
     /// Ideally the cache would be in [`SharedContext`], but it's mutated
@@ -68,7 +71,11 @@ crate struct Context<'tcx> {
     /// It's immutable once in `Context`, so it's not as bad that it's not in
     /// `SharedContext`.
     // FIXME: move `cache` to `SharedContext`
-    pub(super) cache: Rc<Cache>,
+    crate cache: Rc<Cache>,
+    /// This flag indicates whether `[src]` links should be generated or not. If
+    /// the source files are present in the html rendering, then this will be
+    /// `true`.
+    crate include_sources: bool,
 }
 
 // `Context` is cloned a lot, so we don't want the size to grow unexpectedly.
@@ -84,10 +91,6 @@ crate struct SharedContext<'tcx> {
     /// This describes the layout of each page, and is not modified after
     /// creation of the context (contains info like the favicon and added html).
     crate layout: layout::Layout,
-    /// This flag indicates whether `[src]` links should be generated or not. If
-    /// the source files are present in the html rendering, then this will be
-    /// `true`.
-    crate include_sources: bool,
     /// The local file sources we've emitted and their respective url-paths.
     crate local_sources: FxHashMap<PathBuf, String>,
     /// Show the memory layout of types in the docs.
@@ -125,6 +128,10 @@ crate struct SharedContext<'tcx> {
     redirections: Option<RefCell<FxHashMap<String, String>>>,
 
     pub(crate) templates: tera::Tera,
+
+    /// Correspondance map used to link types used in the source code pages to allow to click on
+    /// links to jump to the type's definition.
+    crate span_correspondance_map: FxHashMap<rustc_span::Span, LinkFromSrc>,
 }
 
 impl SharedContext<'_> {
@@ -293,15 +300,19 @@ impl<'tcx> Context<'tcx> {
     /// may happen, for example, with externally inlined items where the source
     /// of their crate documentation isn't known.
     pub(super) fn src_href(&self, item: &clean::Item) -> Option<String> {
-        if item.span(self.tcx()).is_dummy() {
+        self.href_from_span(item.span(self.tcx()))
+    }
+
+    crate fn href_from_span(&self, span: clean::Span) -> Option<String> {
+        if span.is_dummy() {
             return None;
         }
         let mut root = self.root_path();
         let mut path = String::new();
-        let cnum = item.span(self.tcx()).cnum(self.sess());
+        let cnum = span.cnum(self.sess());
 
         // We can safely ignore synthetic `SourceFile`s.
-        let file = match item.span(self.tcx()).filename(self.sess()) {
+        let file = match span.filename(self.sess()) {
             FileName::Real(ref path) => path.local_path_if_available().to_path_buf(),
             _ => return None,
         };
@@ -339,8 +350,8 @@ impl<'tcx> Context<'tcx> {
             (&*symbol, &path)
         };
 
-        let loline = item.span(self.tcx()).lo(self.sess()).line;
-        let hiline = item.span(self.tcx()).hi(self.sess()).line;
+        let loline = span.lo(self.sess()).line;
+        let hiline = span.hi(self.sess()).line;
         let lines =
             if loline == hiline { loline.to_string() } else { format!("{}-{}", loline, hiline) };
         Some(format!(
@@ -362,9 +373,9 @@ impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> {
     const RUN_ON_MODULE: bool = true;
 
     fn init(
-        mut krate: clean::Crate,
+        krate: clean::Crate,
         options: RenderOptions,
-        mut cache: Cache,
+        cache: Cache,
         tcx: TyCtxt<'tcx>,
     ) -> Result<(Self, clean::Crate), Error> {
         // need to save a copy of the options for rendering the index page
@@ -385,6 +396,7 @@ impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> {
             unstable_features,
             generate_redirect_map,
             show_type_layout,
+            generate_link_to_definition,
             ..
         } = options;
 
@@ -444,13 +456,21 @@ impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> {
                 _ => {}
             }
         }
+
+        let (mut krate, local_sources, matches) = collect_spans_and_sources(
+            tcx,
+            krate,
+            &src_root,
+            include_sources,
+            generate_link_to_definition,
+        );
+
         let (sender, receiver) = channel();
         let mut scx = SharedContext {
             tcx,
             collapsed: krate.collapsed,
             src_root,
-            include_sources,
-            local_sources: Default::default(),
+            local_sources,
             issue_tracker_base_url,
             layout,
             created_dirs: Default::default(),
@@ -466,6 +486,7 @@ impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> {
             redirections: if generate_redirect_map { Some(Default::default()) } else { None },
             show_type_layout,
             templates,
+            span_correspondance_map: matches,
         };
 
         // Add the default themes to the `Vec` of stylepaths
@@ -483,12 +504,6 @@ impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> {
 
         let dst = output;
         scx.ensure_dir(&dst)?;
-        if emit_crate {
-            krate = sources::render(&dst, &mut scx, krate)?;
-        }
-
-        // Build our search index
-        let index = build_index(&krate, &mut cache, tcx);
 
         let mut cx = Context {
             current: Vec::new(),
@@ -497,8 +512,16 @@ impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> {
             id_map: RefCell::new(id_map),
             shared: Rc::new(scx),
             cache: Rc::new(cache),
+            include_sources,
         };
 
+        if emit_crate {
+            krate = sources::render(&mut cx, krate)?;
+        }
+
+        // Build our search index
+        let index = build_index(&krate, Rc::get_mut(&mut cx.cache).unwrap(), tcx);
+
         // Write shared runs within a flock; disable thread dispatching of IO temporarily.
         Rc::get_mut(&mut cx.shared).unwrap().fs.set_sync_only(true);
         write_shared(&cx, &krate, index, &md_opts)?;
@@ -514,6 +537,7 @@ impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> {
             id_map: RefCell::new(IdMap::new()),
             shared: Rc::clone(&self.shared),
             cache: Rc::clone(&self.cache),
+            include_sources: self.include_sources,
         }
     }
 
diff --git a/src/librustdoc/html/render/mod.rs b/src/librustdoc/html/render/mod.rs
index c05ea81ac1f..fd2e18a8be7 100644
--- a/src/librustdoc/html/render/mod.rs
+++ b/src/librustdoc/html/render/mod.rs
@@ -30,9 +30,11 @@ mod tests;
 
 mod context;
 mod print_item;
+mod span_map;
 mod write_shared;
 
 crate use context::*;
+crate use span_map::{collect_spans_and_sources, LinkFromSrc};
 
 use std::collections::VecDeque;
 use std::default::Default;
diff --git a/src/librustdoc/html/render/print_item.rs b/src/librustdoc/html/render/print_item.rs
index 5c30d8bbd17..f31305c76e6 100644
--- a/src/librustdoc/html/render/print_item.rs
+++ b/src/librustdoc/html/render/print_item.rs
@@ -119,7 +119,7 @@ pub(super) fn print_item(cx: &Context<'_>, item: &clean::Item, buf: &mut Buffer,
     // [src] link in the downstream documentation will actually come back to
     // this page, and this link will be auto-clicked. The `id` attribute is
     // used to find the link to auto-click.
-    if cx.shared.include_sources && !item.is_primitive() {
+    if cx.include_sources && !item.is_primitive() {
         write_srclink(cx, item, buf);
     }
 
@@ -1081,6 +1081,7 @@ fn item_macro(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, t: &clean::Mac
             None,
             it.span(cx.tcx()).inner().edition(),
             None,
+            None,
         );
     });
     document(w, cx, it, None)
diff --git a/src/librustdoc/html/render/span_map.rs b/src/librustdoc/html/render/span_map.rs
new file mode 100644
index 00000000000..b35cd45dc9a
--- /dev/null
+++ b/src/librustdoc/html/render/span_map.rs
@@ -0,0 +1,164 @@
+use crate::clean;
+use crate::html::sources;
+
+use rustc_data_structures::fx::FxHashMap;
+use rustc_hir::def::{DefKind, Res};
+use rustc_hir::def_id::DefId;
+use rustc_hir::intravisit::{self, NestedVisitorMap, Visitor};
+use rustc_hir::{ExprKind, GenericParam, GenericParamKind, HirId, Mod, Node};
+use rustc_middle::ty::TyCtxt;
+use rustc_span::Span;
+
+use std::path::{Path, PathBuf};
+
+/// This enum allows us to store two different kinds of information:
+///
+/// In case the `span` definition comes from the same crate, we can simply get the `span` and use
+/// it as is.
+///
+/// Otherwise, we store the definition `DefId` and will generate a link to the documentation page
+/// instead of the source code directly.
+#[derive(Debug)]
+crate enum LinkFromSrc {
+    Local(clean::Span),
+    External(DefId),
+}
+
+/// This function will do at most two things:
+///
+/// 1. Generate a `span` correspondance map which links an item `span` to its definition `span`.
+/// 2. Collect the source code files.
+///
+/// It returns the `krate`, the source code files and the `span` correspondance map.
+///
+/// Note about the `span` correspondance map: the keys are actually `(lo, hi)` of `span`s. We don't
+/// need the `span` context later on, only their position, so instead of keep a whole `Span`, we
+/// only keep the `lo` and `hi`.
+crate fn collect_spans_and_sources(
+    tcx: TyCtxt<'_>,
+    krate: clean::Crate,
+    src_root: &Path,
+    include_sources: bool,
+    generate_link_to_definition: bool,
+) -> (clean::Crate, FxHashMap<PathBuf, String>, FxHashMap<Span, LinkFromSrc>) {
+    let mut visitor = SpanMapVisitor { tcx, matches: FxHashMap::default() };
+
+    if include_sources {
+        if generate_link_to_definition {
+            intravisit::walk_crate(&mut visitor, tcx.hir().krate());
+        }
+        let (krate, sources) = sources::collect_local_sources(tcx, src_root, krate);
+        (krate, sources, visitor.matches)
+    } else {
+        (krate, Default::default(), Default::default())
+    }
+}
+
+struct SpanMapVisitor<'tcx> {
+    crate tcx: TyCtxt<'tcx>,
+    crate matches: FxHashMap<Span, LinkFromSrc>,
+}
+
+impl<'tcx> SpanMapVisitor<'tcx> {
+    /// This function is where we handle `hir::Path` elements and add them into the "span map".
+    fn handle_path(&mut self, path: &rustc_hir::Path<'_>, path_span: Option<Span>) {
+        let info = match path.res {
+            // FIXME: For now, we only handle `DefKind` if it's not `DefKind::TyParam` or
+            // `DefKind::Macro`. Would be nice to support them too alongside the other `DefKind`
+            // (such as primitive types!).
+            Res::Def(kind, def_id) if kind != DefKind::TyParam => {
+                if matches!(kind, DefKind::Macro(_)) {
+                    return;
+                }
+                Some(def_id)
+            }
+            Res::Local(_) => None,
+            Res::Err => return,
+            _ => return,
+        };
+        if let Some(span) = self.tcx.hir().res_span(path.res) {
+            self.matches.insert(
+                path_span.unwrap_or_else(|| path.span),
+                LinkFromSrc::Local(clean::Span::new(span)),
+            );
+        } else if let Some(def_id) = info {
+            self.matches
+                .insert(path_span.unwrap_or_else(|| path.span), LinkFromSrc::External(def_id));
+        }
+    }
+}
+
+impl Visitor<'tcx> for SpanMapVisitor<'tcx> {
+    type Map = rustc_middle::hir::map::Map<'tcx>;
+
+    fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
+        NestedVisitorMap::All(self.tcx.hir())
+    }
+
+    fn visit_generic_param(&mut self, p: &'tcx GenericParam<'tcx>) {
+        if !matches!(p.kind, GenericParamKind::Type { .. }) {
+            return;
+        }
+        for bound in p.bounds {
+            if let Some(trait_ref) = bound.trait_ref() {
+                self.handle_path(&trait_ref.path, None);
+            }
+        }
+    }
+
+    fn visit_path(&mut self, path: &'tcx rustc_hir::Path<'tcx>, _id: HirId) {
+        self.handle_path(path, None);
+        intravisit::walk_path(self, path);
+    }
+
+    fn visit_mod(&mut self, m: &'tcx Mod<'tcx>, span: Span, id: HirId) {
+        // To make the difference between "mod foo {}" and "mod foo;". In case we "import" another
+        // file, we want to link to it. Otherwise no need to create a link.
+        if !span.overlaps(m.inner) {
+            // Now that we confirmed it's a file import, we want to get the span for the module
+            // name only and not all the "mod foo;".
+            if let Some(node) = self.tcx.hir().find(id) {
+                match node {
+                    Node::Item(item) => {
+                        self.matches
+                            .insert(item.ident.span, LinkFromSrc::Local(clean::Span::new(m.inner)));
+                    }
+                    _ => {}
+                }
+            }
+        }
+        intravisit::walk_mod(self, m, id);
+    }
+
+    fn visit_expr(&mut self, expr: &'tcx rustc_hir::Expr<'tcx>) {
+        match expr.kind {
+            ExprKind::MethodCall(segment, method_span, _, _) => {
+                if let Some(hir_id) = segment.hir_id {
+                    let hir = self.tcx.hir();
+                    let body_id = hir.enclosing_body_owner(hir_id);
+                    let typeck_results = self.tcx.sess.with_disabled_diagnostic(|| {
+                        self.tcx.typeck_body(
+                            hir.maybe_body_owned_by(body_id).expect("a body which isn't a body"),
+                        )
+                    });
+                    if let Some(def_id) = typeck_results.type_dependent_def_id(expr.hir_id) {
+                        self.matches.insert(
+                            method_span,
+                            match hir.span_if_local(def_id) {
+                                Some(span) => LinkFromSrc::Local(clean::Span::new(span)),
+                                None => LinkFromSrc::External(def_id),
+                            },
+                        );
+                    }
+                }
+            }
+            _ => {}
+        }
+        intravisit::walk_expr(self, expr);
+    }
+
+    fn visit_use(&mut self, path: &'tcx rustc_hir::Path<'tcx>, id: HirId) {
+        self.handle_path(path, None);
+        intravisit::walk_use(self, path, id);
+    }
+}
diff --git a/src/librustdoc/html/render/write_shared.rs b/src/librustdoc/html/render/write_shared.rs
index 4411b7771ed..c16769c474a 100644
--- a/src/librustdoc/html/render/write_shared.rs
+++ b/src/librustdoc/html/render/write_shared.rs
@@ -272,7 +272,7 @@ pub(super) fn write_shared(
     write_minify("search.js", static_files::SEARCH_JS)?;
     write_minify("settings.js", static_files::SETTINGS_JS)?;
 
-    if cx.shared.include_sources {
+    if cx.include_sources {
         write_minify("source-script.js", static_files::sidebar::SOURCE_SCRIPT)?;
     }
 
@@ -398,7 +398,7 @@ pub(super) fn write_shared(
         }
     }
 
-    if cx.shared.include_sources {
+    if cx.include_sources {
         let mut hierarchy = Hierarchy::new(OsString::new());
         for source in cx
             .shared
diff --git a/src/librustdoc/html/sources.rs b/src/librustdoc/html/sources.rs
index 80dd7a7a952..73916e204d9 100644
--- a/src/librustdoc/html/sources.rs
+++ b/src/librustdoc/html/sources.rs
@@ -5,8 +5,10 @@ use crate::fold::DocFolder;
 use crate::html::format::Buffer;
 use crate::html::highlight;
 use crate::html::layout;
-use crate::html::render::{SharedContext, BASIC_KEYWORDS};
+use crate::html::render::{Context, BASIC_KEYWORDS};
+use rustc_data_structures::fx::{FxHashMap, FxHashSet};
 use rustc_hir::def_id::LOCAL_CRATE;
+use rustc_middle::ty::TyCtxt;
 use rustc_session::Session;
 use rustc_span::edition::Edition;
 use rustc_span::source_map::FileName;
@@ -14,52 +16,117 @@ use std::ffi::OsStr;
 use std::fs;
 use std::path::{Component, Path, PathBuf};
 
-crate fn render(
-    dst: &Path,
-    scx: &mut SharedContext<'_>,
-    krate: clean::Crate,
-) -> Result<clean::Crate, Error> {
+crate fn render(cx: &mut Context<'_>, krate: clean::Crate) -> Result<clean::Crate, Error> {
     info!("emitting source files");
-    let dst = dst.join("src").join(&*krate.name.as_str());
-    scx.ensure_dir(&dst)?;
-    let mut folder = SourceCollector { dst, scx };
+    let dst = cx.dst.join("src").join(&*krate.name.as_str());
+    cx.shared.ensure_dir(&dst)?;
+    let mut folder = SourceCollector { dst, cx, emitted_local_sources: FxHashSet::default() };
     Ok(folder.fold_crate(krate))
 }
 
+crate fn collect_local_sources<'tcx>(
+    tcx: TyCtxt<'tcx>,
+    src_root: &Path,
+    krate: clean::Crate,
+) -> (clean::Crate, FxHashMap<PathBuf, String>) {
+    let mut lsc = LocalSourcesCollector { tcx, local_sources: FxHashMap::default(), src_root };
+
+    let krate = lsc.fold_crate(krate);
+    (krate, lsc.local_sources)
+}
+
+struct LocalSourcesCollector<'a, 'tcx> {
+    tcx: TyCtxt<'tcx>,
+    local_sources: FxHashMap<PathBuf, String>,
+    src_root: &'a Path,
+}
+
+fn is_real_and_local(span: clean::Span, sess: &Session) -> bool {
+    span.filename(sess).is_real() && span.cnum(sess) == LOCAL_CRATE
+}
+
+impl LocalSourcesCollector<'_, '_> {
+    fn add_local_source(&mut self, item: &clean::Item) {
+        let sess = self.tcx.sess;
+        let span = item.span(self.tcx);
+        // skip all synthetic "files"
+        if !is_real_and_local(span, sess) {
+            return;
+        }
+        let filename = span.filename(sess);
+        let p = match filename {
+            FileName::Real(ref file) => match file.local_path() {
+                Some(p) => p.to_path_buf(),
+                _ => return,
+            },
+            _ => return,
+        };
+        if self.local_sources.contains_key(&*p) {
+            // We've already emitted this source
+            return;
+        }
+
+        let mut href = String::new();
+        clean_path(&self.src_root, &p, false, |component| {
+            href.push_str(&component.to_string_lossy());
+            href.push('/');
+        });
+
+        let src_fname = p.file_name().expect("source has no filename").to_os_string();
+        let mut fname = src_fname.clone();
+        fname.push(".html");
+        href.push_str(&fname.to_string_lossy());
+        self.local_sources.insert(p, href);
+    }
+}
+
+impl DocFolder for LocalSourcesCollector<'_, '_> {
+    fn fold_item(&mut self, item: clean::Item) -> Option<clean::Item> {
+        self.add_local_source(&item);
+
+        // FIXME: if `include_sources` isn't set and DocFolder didn't require consuming the crate by value,
+        // we could return None here without having to walk the rest of the crate.
+        Some(self.fold_item_recur(item))
+    }
+}
+
 /// Helper struct to render all source code to HTML pages
 struct SourceCollector<'a, 'tcx> {
-    scx: &'a mut SharedContext<'tcx>,
+    cx: &'a mut Context<'tcx>,
 
     /// Root destination to place all HTML output into
     dst: PathBuf,
+    emitted_local_sources: FxHashSet<PathBuf>,
 }
 
 impl DocFolder for SourceCollector<'_, '_> {
     fn fold_item(&mut self, item: clean::Item) -> Option<clean::Item> {
+        let tcx = self.cx.tcx();
+        let span = item.span(tcx);
+        let sess = tcx.sess;
+
         // If we're not rendering sources, there's nothing to do.
         // 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 synthetic "files"
-            && item.span(self.scx.tcx).filename(self.sess()).is_real()
-            // skip non-local files
-            && item.span(self.scx.tcx).cnum(self.sess()) == LOCAL_CRATE
-        {
-            let filename = item.span(self.scx.tcx).filename(self.sess());
+        if self.cx.include_sources && is_real_and_local(span, sess) {
+            let filename = span.filename(sess);
+            let span = span.inner();
+            let pos = sess.source_map().lookup_source_file(span.lo());
+            let file_span = span.with_lo(pos.start_pos).with_hi(pos.end_pos);
             // 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(&filename) {
+            self.cx.include_sources = match self.emit_source(&filename, file_span) {
                 Ok(()) => true,
                 Err(e) => {
-                    self.scx.tcx.sess.span_err(
-                        item.span(self.scx.tcx).inner(),
+                    self.cx.shared.tcx.sess.span_err(
+                        span,
                         &format!(
                             "failed to render source code for `{}`: {}",
                             filename.prefer_local(),
-                            e
+                            e,
                         ),
                     );
                     false
@@ -73,12 +140,12 @@ impl DocFolder for SourceCollector<'_, '_> {
 }
 
 impl SourceCollector<'_, 'tcx> {
-    fn sess(&self) -> &'tcx Session {
-        &self.scx.tcx.sess
-    }
-
     /// Renders the given filename into its corresponding HTML source file.
-    fn emit_source(&mut self, filename: &FileName) -> Result<(), Error> {
+    fn emit_source(
+        &mut self,
+        filename: &FileName,
+        file_span: rustc_span::Span,
+    ) -> Result<(), Error> {
         let p = match *filename {
             FileName::Real(ref file) => {
                 if let Some(local_path) = file.local_path() {
@@ -89,7 +156,7 @@ impl SourceCollector<'_, 'tcx> {
             }
             _ => return Ok(()),
         };
-        if self.scx.local_sources.contains_key(&*p) {
+        if self.emitted_local_sources.contains(&*p) {
             // We've already emitted this source
             return Ok(());
         }
@@ -107,20 +174,17 @@ impl SourceCollector<'_, 'tcx> {
         // 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| {
+        clean_path(&self.cx.shared.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)?;
+
+        self.cx.shared.ensure_dir(&cur)?;
 
         let src_fname = p.file_name().expect("source has no filename").to_os_string();
         let mut fname = src_fname.clone();
         fname.push(".html");
         cur.push(&fname);
-        href.push_str(&fname.to_string_lossy());
 
         let title = format!("{} - source", src_fname.to_string_lossy());
         let desc = format!("Source of the Rust file `{}`.", filename.prefer_remapped());
@@ -128,23 +192,25 @@ impl SourceCollector<'_, 'tcx> {
             title: &title,
             css_class: "source",
             root_path: &root_path,
-            static_root_path: self.scx.static_root_path.as_deref(),
+            static_root_path: self.cx.shared.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)],
+            resource_suffix: &self.cx.shared.resource_suffix,
+            extra_scripts: &[&format!("source-files{}", self.cx.shared.resource_suffix)],
+            static_extra_scripts: &[&format!("source-script{}", self.cx.shared.resource_suffix)],
         };
         let v = layout::render(
-            &self.scx.templates,
-            &self.scx.layout,
+            &self.cx.shared.templates,
+            &self.cx.shared.layout,
             &page,
             "",
-            |buf: &mut _| print_src(buf, contents, self.scx.edition()),
-            &self.scx.style_files,
+            |buf: &mut _| {
+                print_src(buf, contents, self.cx.shared.edition(), file_span, &self.cx, &root_path)
+            },
+            &self.cx.shared.style_files,
         );
-        self.scx.fs.write(&cur, v.as_bytes())?;
-        self.scx.local_sources.insert(p, href);
+        self.cx.shared.fs.write(&cur, v.as_bytes())?;
+        self.emitted_local_sources.insert(p);
         Ok(())
     }
 }
@@ -178,7 +244,14 @@ where
 
 /// Wrapper struct to render the source code of a file. This will do things like
 /// adding line numbers to the left-hand side.
-fn print_src(buf: &mut Buffer, s: &str, edition: Edition) {
+fn print_src(
+    buf: &mut Buffer,
+    s: &str,
+    edition: Edition,
+    file_span: rustc_span::Span,
+    context: &Context<'_>,
+    root_path: &str,
+) {
     let lines = s.lines().count();
     let mut line_numbers = Buffer::empty_from(buf);
     let mut cols = 0;
@@ -192,5 +265,14 @@ fn print_src(buf: &mut Buffer, s: &str, edition: Edition) {
         writeln!(line_numbers, "<span id=\"{0}\">{0:1$}</span>", i, cols);
     }
     line_numbers.write_str("</pre>");
-    highlight::render_with_highlighting(s, buf, None, None, None, edition, Some(line_numbers));
+    highlight::render_with_highlighting(
+        s,
+        buf,
+        None,
+        None,
+        None,
+        edition,
+        Some(line_numbers),
+        Some(highlight::ContextInfo { context, file_span, root_path }),
+    );
 }
diff --git a/src/librustdoc/html/static/css/rustdoc.css b/src/librustdoc/html/static/css/rustdoc.css
index 4e33eab5650..bbc48f49e63 100644
--- a/src/librustdoc/html/static/css/rustdoc.css
+++ b/src/librustdoc/html/static/css/rustdoc.css
@@ -450,6 +450,10 @@ nav.sub {
 	border-bottom-left-radius: 5px;
 }
 
+.example-wrap > pre.rust a:hover {
+	text-decoration: underline;
+}
+
 .rustdoc:not(.source) .example-wrap > pre:not(.line-number) {
 	width: 100%;
 	overflow-x: auto;
diff --git a/src/librustdoc/lib.rs b/src/librustdoc/lib.rs
index fa755777584..a98725e683c 100644
--- a/src/librustdoc/lib.rs
+++ b/src/librustdoc/lib.rs
@@ -607,6 +607,13 @@ fn opts() -> Vec<RustcOptGroup> {
         unstable("nocapture", |o| {
             o.optflag("", "nocapture", "Don't capture stdout and stderr of tests")
         }),
+        unstable("generate-link-to-definition", |o| {
+            o.optflag(
+                "",
+                "generate-link-to-definition",
+                "Make the identifiers in the HTML source code pages navigable",
+            )
+        }),
     ]
 }
 
diff --git a/src/test/rustdoc-ui/generate-link-to-definition-opt-unstable.rs b/src/test/rustdoc-ui/generate-link-to-definition-opt-unstable.rs
new file mode 100644
index 00000000000..87620d74ee6
--- /dev/null
+++ b/src/test/rustdoc-ui/generate-link-to-definition-opt-unstable.rs
@@ -0,0 +1,6 @@
+// This test purpose is to check that the "--generate-link-to-definition"
+// option can only be used on nightly.
+
+// compile-flags: --generate-link-to-definition
+
+pub fn f() {}
diff --git a/src/test/rustdoc-ui/generate-link-to-definition-opt-unstable.stderr b/src/test/rustdoc-ui/generate-link-to-definition-opt-unstable.stderr
new file mode 100644
index 00000000000..a8ddf91bcbf
--- /dev/null
+++ b/src/test/rustdoc-ui/generate-link-to-definition-opt-unstable.stderr
@@ -0,0 +1,2 @@
+error: the `-Z unstable-options` flag must also be passed to enable the flag `generate-link-to-definition`
+
diff --git a/src/test/rustdoc-ui/generate-link-to-definition-opt.rs b/src/test/rustdoc-ui/generate-link-to-definition-opt.rs
new file mode 100644
index 00000000000..8f4f561b44d
--- /dev/null
+++ b/src/test/rustdoc-ui/generate-link-to-definition-opt.rs
@@ -0,0 +1,6 @@
+// This test purpose is to check that the "--generate-link-to-definition"
+// option can only be used with HTML generation.
+
+// compile-flags: -Zunstable-options --generate-link-to-definition --output-format json
+
+pub fn f() {}
diff --git a/src/test/rustdoc-ui/generate-link-to-definition-opt.stderr b/src/test/rustdoc-ui/generate-link-to-definition-opt.stderr
new file mode 100644
index 00000000000..4c8c607e7da
--- /dev/null
+++ b/src/test/rustdoc-ui/generate-link-to-definition-opt.stderr
@@ -0,0 +1,2 @@
+error: --generate-link-to-definition option can only be used with HTML output format
+
diff --git a/src/test/rustdoc-ui/generate-link-to-definition-opt2.rs b/src/test/rustdoc-ui/generate-link-to-definition-opt2.rs
new file mode 100644
index 00000000000..da5142087dd
--- /dev/null
+++ b/src/test/rustdoc-ui/generate-link-to-definition-opt2.rs
@@ -0,0 +1,6 @@
+// This test purpose is to check that the "--generate-link-to-definition"
+// option can only be used with HTML generation.
+
+// compile-flags: -Zunstable-options --generate-link-to-definition --show-coverage
+
+pub fn f() {}
diff --git a/src/test/rustdoc-ui/generate-link-to-definition-opt2.stderr b/src/test/rustdoc-ui/generate-link-to-definition-opt2.stderr
new file mode 100644
index 00000000000..4c8c607e7da
--- /dev/null
+++ b/src/test/rustdoc-ui/generate-link-to-definition-opt2.stderr
@@ -0,0 +1,2 @@
+error: --generate-link-to-definition option can only be used with HTML output format
+
diff --git a/src/test/rustdoc/auxiliary/source-code-bar.rs b/src/test/rustdoc/auxiliary/source-code-bar.rs
new file mode 100644
index 00000000000..8700d688ef7
--- /dev/null
+++ b/src/test/rustdoc/auxiliary/source-code-bar.rs
@@ -0,0 +1,17 @@
+//! just some other file. :)
+
+use crate::Foo;
+
+pub struct Bar {
+    field: Foo,
+}
+
+pub struct Bar2 {
+    field: crate::Foo,
+}
+
+pub mod sub {
+    pub trait Trait {
+        fn tadam() {}
+    }
+}
diff --git a/src/test/rustdoc/auxiliary/source_code.rs b/src/test/rustdoc/auxiliary/source_code.rs
new file mode 100644
index 00000000000..72a5c1a0ae9
--- /dev/null
+++ b/src/test/rustdoc/auxiliary/source_code.rs
@@ -0,0 +1 @@
+pub struct SourceCode;
diff --git a/src/test/rustdoc/check-source-code-urls-to-def.rs b/src/test/rustdoc/check-source-code-urls-to-def.rs
new file mode 100644
index 00000000000..e3ae79ccdb1
--- /dev/null
+++ b/src/test/rustdoc/check-source-code-urls-to-def.rs
@@ -0,0 +1,44 @@
+// compile-flags: -Zunstable-options --generate-link-to-definition
+// aux-build:source_code.rs
+// build-aux-docs
+
+#![crate_name = "foo"]
+
+extern crate source_code;
+
+// @has 'src/foo/check-source-code-urls-to-def.rs.html'
+
+// @has - '//a[@href="../../src/foo/auxiliary/source-code-bar.rs.html#1-17"]' 'bar'
+#[path = "auxiliary/source-code-bar.rs"]
+pub mod bar;
+
+// @count - '//a[@href="../../src/foo/auxiliary/source-code-bar.rs.html#5-7"]' 4
+use bar::Bar;
+// @has - '//a[@href="../../src/foo/auxiliary/source-code-bar.rs.html#13-17"]' 'self'
+// @has - '//a[@href="../../src/foo/auxiliary/source-code-bar.rs.html#14-16"]' 'Trait'
+use bar::sub::{self, Trait};
+
+pub struct Foo;
+
+impl Foo {
+    fn hello(&self) {}
+}
+
+fn babar() {}
+
+// @has - '//a/@href' '/struct.String.html'
+// @count - '//a[@href="../../src/foo/check-source-code-urls-to-def.rs.html#21"]' 5
+// @has - '//a[@href="../../source_code/struct.SourceCode.html"]' 'source_code::SourceCode'
+pub fn foo(a: u32, b: &str, c: String, d: Foo, e: bar::Bar, f: source_code::SourceCode) {
+    let x = 12;
+    let y: Foo = Foo;
+    let z: Bar = bar::Bar { field: Foo };
+    babar();
+    // @has - '//a[@href="../../src/foo/check-source-code-urls-to-def.rs.html#24"]' 'hello'
+    y.hello();
+}
+
+// @has - '//a[@href="../../src/foo/auxiliary/source-code-bar.rs.html#14-16"]' 'bar::sub::Trait'
+// @has - '//a[@href="../../src/foo/auxiliary/source-code-bar.rs.html#14-16"]' 'Trait'
+pub fn foo2<T: bar::sub::Trait, V: Trait>(t: &T, v: &V) {
+}