diff --git a/compiler/rustc_errors/src/lib.rs b/compiler/rustc_errors/src/lib.rs index 993a7c2c162c6..fc0924ac5f920 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, + + /// 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>(&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 9ab6dbb1ea906..fe87867d2996c 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>(&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 8f74a48547d88..207c89cbfe87e 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 b3b89e6e673a2..43979423ae615 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 b3fc1e73f7885..3d65fcedaf4e5 100644 --- a/src/librustdoc/clean/mod.rs +++ b/src/librustdoc/clean/mod.rs @@ -95,7 +95,8 @@ impl Clean 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 5c73d3de5b93f..22e4d21c87bdf 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 abd1fd2bf39a2..e44158bc04230 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, + /// 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 8ab6aa775d2e4..eb7c12d13c339 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), HrefError> { +crate fn href_with_root_path( + did: DefId, + cx: &Context<'_>, + root_path: Option<&str>, +) -> Result<(String, ItemType, Vec), 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 (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 { + 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) -> Result<(String, ItemType, Vec) -> Result<(String, ItemType, Vec), 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 33b1d98313ce3..3cdb1352bef95 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, &str)>, edition: Edition, extra_content: Option, + context_info: Option>, ) { 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>, +) { // 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 { + 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 { + 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(out: &mut Buffer, text: T, klass: Option) { - match klass { - None => write!(out, "{}", text), - Some(klass) => write!(out, "{}", 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( + out: &mut Buffer, + text: T, + klass: Option, + context_info: &Option>, +) { + 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, "{}", 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, + "{}", + Class::Self_(DUMMY_SP).as_html(), + t + ), + "crate" | "super" => { + write!(&mut path, "{}", 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, "{}", klass.as_html(), href, text_s); + return; + } } + write!(out, "{}", 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 0000000000000..abc2db1790c53 --- /dev/null +++ b/src/librustdoc/html/highlight/fixtures/highlight.html @@ -0,0 +1,4 @@ +use crate::a::foo; +use self::whatever; +let x = super::b::foo; +let y = Self::whatever; \ 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 8d23477bbcb8c..866caea925609 100644 --- a/src/librustdoc/html/highlight/fixtures/sample.html +++ b/src/librustdoc/html/highlight/fixtures/sample.html @@ -23,7 +23,7 @@ assert!(self.length < N && index <= self.length); ::std::env::var("gateau").is_ok(); #[rustfmt::skip] - let s:std::path::PathBuf = std::path::PathBuf::new(); + let s:std::path::PathBuf = std::path::PathBuf::new(); let mut s = String::new(); match &s { diff --git a/src/librustdoc/html/highlight/tests.rs b/src/librustdoc/html/highlight/tests.rs index a505865b149c4..68592ae96c187 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!("{}
{}
\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 b8756d2526edf..472323daf3017 100644 --- a/src/librustdoc/html/markdown.rs +++ b/src/librustdoc/html/markdown.rs @@ -330,6 +330,7 @@ impl<'a, I: Iterator>> 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 b6c3220901f06..6ce0828e15940 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, /// 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>, + crate shared: Rc>, /// 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, + 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 @@ 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, /// Show the memory layout of types in the docs. @@ -125,6 +128,10 @@ crate struct SharedContext<'tcx> { 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, } 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 { - 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 @@ 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 c05ea81ac1f36..fd2e18a8be77f 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 5c30d8bbd173c..f31305c76e642 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 0000000000000..b35cd45dc9a88 --- /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, FxHashMap) { + 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, +} + +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) { + 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 { + 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 4411b7771eda7..c16769c474a21 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 80dd7a7a952f0..73916e204d942 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 { +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 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, "{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), + 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 4e33eab565006..bbc48f49e63e1 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 fa755777584f3..a98725e683cb6 100644 --- a/src/librustdoc/lib.rs +++ b/src/librustdoc/lib.rs @@ -607,6 +607,13 @@ fn opts() -> Vec { 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 0000000000000..87620d74ee645 --- /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 0000000000000..a8ddf91bcbf15 --- /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 0000000000000..8f4f561b44dcc --- /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 0000000000000..4c8c607e7da23 --- /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 0000000000000..da5142087ddee --- /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 0000000000000..4c8c607e7da23 --- /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 0000000000000..8700d688ef7e2 --- /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 0000000000000..72a5c1a0ae97e --- /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 0000000000000..e3ae79ccdb17a --- /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: &T, v: &V) { +}