From 023231a709b88e1a73ac86076aa5e1b4b3001276 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Tue, 6 Apr 2021 00:07:46 +0200 Subject: [PATCH] Add links on source types to go to definition --- src/librustdoc/clean/types.rs | 5 + src/librustdoc/html/format.rs | 5 +- src/librustdoc/html/highlight.rs | 186 +++++++++++++++++---- src/librustdoc/html/markdown.rs | 3 + src/librustdoc/html/render/context.rs | 64 ++++--- src/librustdoc/html/render/mod.rs | 2 + src/librustdoc/html/render/print_item.rs | 5 +- src/librustdoc/html/render/span_map.rs | 110 ++++++++++++ src/librustdoc/html/render/write_shared.rs | 4 +- src/librustdoc/html/sources.rs | 176 ++++++++++++++----- 10 files changed, 454 insertions(+), 106 deletions(-) create mode 100644 src/librustdoc/html/render/span_map.rs diff --git a/src/librustdoc/clean/types.rs b/src/librustdoc/clean/types.rs index 5c73d3de5b9..85c4731c6bf 100644 --- a/src/librustdoc/clean/types.rs +++ b/src/librustdoc/clean/types.rs @@ -1950,6 +1950,11 @@ impl Span { Self(sp.source_callsite()) } + /// Unless you know what you're doing, use [`Self::from_rustc_span`] instead! + crate fn wrap(sp: rustc_span::Span) -> Span { + Self(sp) + } + crate fn inner(&self) -> rustc_span::Span { self.0 } diff --git a/src/librustdoc/html/format.rs b/src/librustdoc/html/format.rs index 8ab6aa775d2..8b5c147b56c 100644 --- a/src/librustdoc/html/format.rs +++ b/src/librustdoc/html/format.rs @@ -494,7 +494,10 @@ fn to_module_fqp(shortty: ItemType, fqp: &[String]) -> &[String] { if !did.is_local() && !cache.access_levels.is_public(did) && !cache.document_private { return Err(HrefError::Private); } - + // href_with_depth_inner(did, cache, || { + // let depth = CURRENT_DEPTH.with(|l| l.get()); + // "../".repeat(depth) + // }) 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); diff --git a/src/librustdoc/html/highlight.rs b/src/librustdoc/html/highlight.rs index 33b1d98313c..329cb3f8f09 100644 --- a/src/librustdoc/html/highlight.rs +++ b/src/librustdoc/html/highlight.rs @@ -5,16 +5,19 @@ //! //! Use the `render_with_highlighting` to highlight some rust code. +use crate::clean; 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 super::format::{self, Buffer}; +use super::render::LinkFromSrc; /// Highlights `src`, returning the HTML output. crate fn render_with_highlighting( @@ -25,6 +28,9 @@ tooltip: Option<(Option, &str)>, edition: Edition, extra_content: Option, + file_span_lo: u32, + context: Option<&Context<'_>>, + root_path: &str, ) { debug!("highlighting: ================\n{}\n==============", src); if let Some((edition_info, class)) = tooltip { @@ -41,7 +47,7 @@ } write_header(out, class, extra_content); - write_code(out, &src, edition); + write_code(out, &src, edition, file_span_lo, context, root_path); write_footer(out, playground_button); } @@ -57,12 +63,21 @@ fn write_header(out: &mut Buffer, class: Option<&str>, extra_content: Option>, + root_path: &str, +) { // 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| { + Classifier::new(&src, edition, file_span_lo).highlight(&mut |highlight| { match highlight { - Highlight::Token { text, class } => string(out, Escape(text), class), + Highlight::Token { text, class } => { + string(out, Escape(text), class, context, root_path) + } Highlight::EnterSpan { class } => enter_span(out, class), Highlight::ExitSpan => exit_span(out), }; @@ -82,14 +97,14 @@ enum Class { KeyWord, // Keywords that do pointer/reference stuff. RefKeyWord, - Self_, + Self_((u32, u32)), Op, Macro, MacroNonTerminal, String, Number, Bool, - Ident, + Ident((u32, u32)), Lifetime, PreludeTy, PreludeVal, @@ -105,20 +120,27 @@ fn as_html(self) -> &'static str { 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", } } + + fn get_span(self) -> Option<(u32, u32)> { + match self { + Self::Ident(sp) | Self::Self_(sp) => Some(sp), + _ => None, + } + } } enum Highlight<'a> { @@ -144,14 +166,23 @@ fn next(&mut self) -> Option<(TokenKind, &'a str)> { } } -fn get_real_ident_class(text: &str, edition: Edition) -> Class { - match text { +/// Returns `None` if this is a `Class::Ident`. +fn get_real_ident_class(text: &str, edition: Edition, allow_path_keywords: bool) -> Option { + 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, + }) +} + +fn move_span(file_span_lo: u32, start: u32, end: u32) -> (u32, u32) { + (start + file_span_lo, end + file_span_lo) } /// Processes program tokens, classifying strings of text by highlighting @@ -163,11 +194,12 @@ struct Classifier<'a> { in_macro_nonterminal: bool, edition: Edition, byte_pos: u32, + file_span_lo: u32, src: &'a str, } impl<'a> Classifier<'a> { - fn new(src: &str, edition: Edition) -> Classifier<'_> { + fn new(src: &str, edition: Edition, file_span_lo: u32) -> Classifier<'_> { let tokens = TokenIter { src }.peekable(); Classifier { tokens, @@ -176,6 +208,7 @@ fn new(src: &str, edition: Edition) -> Classifier<'_> { in_macro_nonterminal: false, edition, byte_pos: 0, + file_span_lo, src, } } @@ -201,17 +234,17 @@ fn get_full_ident_path(&mut self) -> Vec<(TokenKind, usize, usize)> { 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 +254,7 @@ fn get_full_ident_path(&mut self) -> Vec<(TokenKind, usize, usize)> { } 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 { @@ -231,10 +264,11 @@ fn get_full_ident_path(&mut self) -> Vec<(TokenKind, usize, usize)> { } /// Wraps the tokens iteration to ensure that the byte_pos is always correct. - fn next(&mut self) -> Option<(TokenKind, &'a str)> { + 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,14 +288,18 @@ fn highlight(mut self, sink: &mut dyn FnMut(Highlight<'a>)) { .unwrap_or(false) { let tokens = self.get_full_ident_path(); + let skip = !tokens.is_empty(); for (token, start, end) in tokens { let text = &self.src[start..end]; - self.advance(token, text, sink); + self.advance(token, text, sink, start as u32); self.byte_pos += text.len() as u32; } + if skip { + 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; } @@ -270,7 +308,13 @@ fn highlight(mut self, sink: &mut dyn FnMut(Highlight<'a>)) { /// 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>)) { + 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 +445,30 @@ fn advance(&mut self, token: TokenKind, text: &'a str, sink: &mut dyn FnMut(High 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_(move_span( + self.file_span_lo, + before, + before + text.len() as u32, + )), + _ => Class::Ident(move_span( + self.file_span_lo, + before, + before + text.len() as u32, + )), }, - c => c, + Some(c) => c, }, - TokenKind::RawIdent | TokenKind::UnknownPrefix => Class::Ident, + TokenKind::RawIdent | TokenKind::UnknownPrefix => { + Class::Ident(move_span(self.file_span_lo, before, before + text.len() as u32)) + } TokenKind::Lifetime { .. } => Class::Lifetime, }; // Anything that didn't return above is the simple case where we the @@ -448,11 +503,74 @@ fn exit_span(out: &mut Buffer) { /// ``` /// The latter can be thought of as a shorthand for the former, which is more /// flexible. -fn string(out: &mut Buffer, text: T, klass: Option) { +fn string( + out: &mut Buffer, + text: T, + klass: Option, + context: Option<&Context<'_>>, + root_path: &str, +) { match klass { None => write!(out, "{}", text), - Some(klass) => write!(out, "{}", klass.as_html(), text), + Some(klass) => { + if let Some(def_span) = klass.get_span() { + let mut text = text.to_string(); + if text.contains("::") { + text = + text.split("::").enumerate().fold(String::new(), |mut path, (pos, t)| { + let pre = if pos != 0 { "::" } else { "" }; + match t { + "self" | "Self" => write!( + &mut path, + "{}{}", + pre, + Class::Self_((0, 0)).as_html(), + t + ), + "crate" | "super" => write!( + &mut path, + "{}{}", + pre, + Class::KeyWord.as_html(), + t + ), + t => write!(&mut path, "{}{}", pre, t), + } + .expect("Failed to build source HTML path"); + path + }); + } + if let Some(context) = context { + if let Some(href) = + context.shared.span_correspondance_map.get(&def_span).and_then(|href| { + match href { + LinkFromSrc::Local(span) => { + eprintln!("==> {:?}:{:?}", span.lo(), span.hi()); + context + .href_from_span(clean::Span::wrap(*span)) + .map(|s| format!("{}{}", root_path, s)) + }, + LinkFromSrc::External(def_id) => { + format::href(*def_id, context).map(|(url, _, _)| url) + } + } + }) + { + write!( + out, + "{}", + klass.as_html(), + href, + text + ); + return; + } + } + } + write!(out, "{}", klass.as_html(), text); + } } + write!(out, "{}", klass.as_html(), text); } #[cfg(test)] diff --git a/src/librustdoc/html/markdown.rs b/src/librustdoc/html/markdown.rs index b8756d2526e..5d79ccce8e1 100644 --- a/src/librustdoc/html/markdown.rs +++ b/src/librustdoc/html/markdown.rs @@ -330,6 +330,9 @@ fn dont_escape(c: u8) -> bool { tooltip, edition, None, + 0, + 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..33b10db1f88 100644 --- a/src/librustdoc/html/render/context.rs +++ b/src/librustdoc/html/render/context.rs @@ -17,7 +17,10 @@ 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 @@ pub(crate) current: Vec, /// 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 @@ /// Issue for improving the situation: [#82381][] /// /// [#82381]: https://github.com/rust-lang/rust/issues/82381 - pub(super) shared: Rc>, + crate shared: Rc>, /// The [`Cache`] used during rendering. /// /// Ideally the cache would be in [`SharedContext`], but it's mutated @@ -68,7 +71,11 @@ /// 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, + crate cache: Rc, + /// 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 @@ /// 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, /// Show the memory layout of types in the docs. @@ -125,6 +128,10 @@ redirections: Option>>, 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<(u32, u32), LinkFromSrc>, } impl SharedContext<'_> { @@ -293,15 +300,19 @@ fn build_sidebar_items(&self, m: &clean::Module) -> BTreeMap Option { - 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 { + 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 @@ pub(super) fn src_href(&self, item: &clean::Item) -> Option { (&*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 @@ fn descr() -> &'static str { 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 @@ -444,13 +455,16 @@ fn init( _ => {} } } + + let (mut krate, local_sources, matches) = + collect_spans_and_sources(tcx, krate, &src_root, include_sources); + 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 +480,7 @@ fn init( 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 +498,6 @@ fn init( 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 +506,16 @@ fn init( 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 +531,7 @@ fn make_child_renderer(&self) -> Self { 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 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..7b6bd4dd597 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,9 @@ fn item_macro(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, t: &clean::Mac None, it.span(cx.tcx()).inner().edition(), None, + 0, + 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..c7c8fa7cc45 --- /dev/null +++ b/src/librustdoc/html/render/span_map.rs @@ -0,0 +1,110 @@ +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}; +use rustc_middle::ty::TyCtxt; +use rustc_span::Span; + +#[derive(Debug)] +crate enum LinkFromSrc { + Local(Span), + External(DefId), +} + +crate fn collect_spans_and_sources( + tcx: TyCtxt<'_>, + krate: clean::Crate, + src_root: &std::path::Path, + include_sources: bool, +) -> (clean::Crate, FxHashMap, FxHashMap<(u32, u32), LinkFromSrc>) { + let mut visitor = SpanMapVisitor { tcx, matches: FxHashMap::default() }; + + if include_sources { + 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()) + } +} + +fn span_to_tuple(span: Span) -> (u32, u32) { + (span.lo().0, span.hi().0) +} + +struct SpanMapVisitor<'tcx> { + crate tcx: TyCtxt<'tcx>, + crate matches: FxHashMap<(u32, u32), LinkFromSrc>, +} + +impl<'tcx> SpanMapVisitor<'tcx> { + fn handle_path(&mut self, path: &rustc_hir::Path<'_>, path_span: Option) -> bool { + let info = match path.res { + Res::Def(kind, def_id) if kind != DefKind::TyParam => { + if matches!(kind, DefKind::Macro(_)) { + return false; + } + Some(def_id) + } + Res::Local(_) => None, + _ => return true, + }; + if let Some(span) = self.tcx.hir().res_span(path.res) { + self.matches.insert( + path_span.map(span_to_tuple).unwrap_or_else(|| span_to_tuple(path.span)), + LinkFromSrc::Local(span), + ); + } else if let Some(def_id) = info { + self.matches.insert( + path_span.map(span_to_tuple).unwrap_or_else(|| span_to_tuple(path.span)), + LinkFromSrc::External(def_id), + ); + } + true + } +} + +impl Visitor<'tcx> for SpanMapVisitor<'tcx> { + type Map = rustc_middle::hir::map::Map<'tcx>; + + fn nested_visit_map(&mut self) -> NestedVisitorMap { + 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_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 { + // // call https://doc.rust-lang.org/beta/nightly-rustc/rustc_middle/ty/context/struct.TypeckResults.html#method.type_dependent_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 @@ fn add_background_image_to_css( 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 @@ fn to_json_string(&self) -> String { } } - 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..24821950869 100644 --- a/src/librustdoc/html/sources.rs +++ b/src/librustdoc/html/sources.rs @@ -5,8 +5,10 @@ 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,116 @@ use std::fs; use std::path::{Component, Path, PathBuf}; -crate fn render( - dst: &Path, - scx: &mut SharedContext<'_>, - krate: clean::Crate, -) -> Result { +crate fn render(cx: &mut Context<'_>, krate: clean::Crate) -> Result { 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) { + 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, + 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 { + 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, } impl DocFolder for SourceCollector<'_, '_> { fn fold_item(&mut self, item: clean::Item) -> Option { + 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 start_pos = sess.source_map().lookup_source_file(span.lo()).start_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, start_pos.0) { 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 +139,8 @@ fn fold_item(&mut self, item: clean::Item) -> Option { } 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_lo: u32) -> Result<(), Error> { let p = match *filename { FileName::Real(ref file) => { if let Some(local_path) = file.local_path() { @@ -89,7 +151,7 @@ fn emit_source(&mut self, filename: &FileName) -> Result<(), Error> { } _ => 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 +169,17 @@ fn emit_source(&mut self, filename: &FileName) -> Result<(), Error> { // 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 +187,32 @@ fn emit_source(&mut self, filename: &FileName) -> Result<(), Error> { 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_lo, + &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 +246,14 @@ fn emit_source(&mut self, filename: &FileName) -> Result<(), Error> { /// 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_lo: u32, + context: &Context<'_>, + root_path: &str, +) { let lines = s.lines().count(); let mut line_numbers = Buffer::empty_from(buf); let mut cols = 0; @@ -192,5 +267,16 @@ fn print_src(buf: &mut Buffer, s: &str, edition: Edition) { writeln!(line_numbers, "{0:1$}", i, cols); } line_numbers.write_str(""); - 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), + file_span_lo, + Some(context), + root_path, + ); }