From 5314d80be40ad9ad31f8e743d2b3468da0bea7ae Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Tue, 8 Oct 2024 23:36:38 +0200 Subject: [PATCH 1/5] Prevent private/hidden items to create ambiguities in intra-doc links --- src/librustdoc/core.rs | 8 + .../passes/collect_intra_doc_links.rs | 222 +++++++++++++++--- src/librustdoc/passes/mod.rs | 3 - 3 files changed, 193 insertions(+), 40 deletions(-) diff --git a/src/librustdoc/core.rs b/src/librustdoc/core.rs index aaf4c80f99763..ad576ed7a7b10 100644 --- a/src/librustdoc/core.rs +++ b/src/librustdoc/core.rs @@ -30,6 +30,7 @@ use crate::clean::{self, ItemId}; use crate::config::{Options as RustdocOptions, OutputFormat, RenderOptions}; use crate::formats::cache::Cache; use crate::passes::Condition::*; +use crate::passes::collect_intra_doc_links::LinkCollector; use crate::passes::{self}; pub(crate) struct DocContext<'tcx> { @@ -440,6 +441,10 @@ pub(crate) fn run_global_ctxt( } } + let (mut krate, LinkCollector { visited_links, ambiguous_links, .. }) = + tcx.sess.time("collect_intra_doc_links", || { + passes::collect_intra_doc_links::collect_intra_doc_links(krate, &mut ctxt) + }); tcx.sess.time("check_lint_expectations", || tcx.check_expectations(Some(sym::rustdoc))); if let Some(guar) = tcx.dcx().has_errors() { @@ -448,6 +453,9 @@ pub(crate) fn run_global_ctxt( krate = tcx.sess.time("create_format_cache", || Cache::populate(&mut ctxt, krate)); + let mut collector = LinkCollector { cx: &mut ctxt, visited_links, ambiguous_links }; + collector.resolve_ambiguities(); + Ok((krate, ctxt.render_options, ctxt.cache)) } diff --git a/src/librustdoc/passes/collect_intra_doc_links.rs b/src/librustdoc/passes/collect_intra_doc_links.rs index db235786cf49a..76d91442c51a2 100644 --- a/src/librustdoc/passes/collect_intra_doc_links.rs +++ b/src/librustdoc/passes/collect_intra_doc_links.rs @@ -14,7 +14,7 @@ use rustc_data_structures::intern::Interned; use rustc_errors::{Applicability, Diag, DiagMessage}; use rustc_hir::def::Namespace::*; use rustc_hir::def::{DefKind, Namespace, PerNS}; -use rustc_hir::def_id::{CRATE_DEF_ID, DefId}; +use rustc_hir::def_id::{CRATE_DEF_ID, DefId, LOCAL_CRATE}; use rustc_hir::{Mutability, Safety}; use rustc_middle::ty::{Ty, TyCtxt}; use rustc_middle::{bug, span_bug, ty}; @@ -30,23 +30,30 @@ use smallvec::{SmallVec, smallvec}; use tracing::{debug, info, instrument, trace}; use crate::clean::utils::find_nearest_parent_module; -use crate::clean::{self, Crate, Item, ItemLink, PrimitiveType}; +use crate::clean::{self, Crate, Item, ItemId, ItemLink, PrimitiveType}; use crate::core::DocContext; use crate::html::markdown::{MarkdownLink, MarkdownLinkRange, markdown_links}; use crate::lint::{BROKEN_INTRA_DOC_LINKS, PRIVATE_INTRA_DOC_LINKS}; -use crate::passes::Pass; use crate::visit::DocVisitor; -pub(crate) const COLLECT_INTRA_DOC_LINKS: Pass = Pass { - name: "collect-intra-doc-links", - run: collect_intra_doc_links, - description: "resolves intra-doc links", -}; - -fn collect_intra_doc_links(krate: Crate, cx: &mut DocContext<'_>) -> Crate { - let mut collector = LinkCollector { cx, visited_links: FxHashMap::default() }; +pub(crate) fn collect_intra_doc_links<'a, 'tcx>( + krate: Crate, + cx: &'a mut DocContext<'tcx>, +) -> (Crate, LinkCollector<'a, 'tcx>) { + let mut collector = LinkCollector { + cx, + visited_links: FxHashMap::default(), + ambiguous_links: FxHashMap::default(), + }; collector.visit_crate(&krate); - krate + (krate, collector) +} + +pub(crate) struct AmbiguousLinks { + pub(crate) disambiguator: Option, + pub(crate) link_text: Box, + pub(crate) diag_info: OwnedDiagnosticInfo, + pub(crate) resolved: Vec<(Res, Option)>, } fn filter_assoc_items_by_name_and_namespace<'a>( @@ -61,7 +68,7 @@ fn filter_assoc_items_by_name_and_namespace<'a>( } #[derive(Copy, Clone, Debug, Hash, PartialEq)] -enum Res { +pub(crate) enum Res { Def(DefKind, DefId), Primitive(PrimitiveType), } @@ -234,7 +241,7 @@ impl UrlFragment { } #[derive(Clone, Debug, Hash, PartialEq, Eq)] -struct ResolutionInfo { +pub(crate) struct ResolutionInfo { item_id: DefId, module_id: DefId, dis: Option, @@ -243,18 +250,54 @@ struct ResolutionInfo { } #[derive(Clone)] -struct DiagnosticInfo<'a> { +pub(crate) struct DiagnosticInfo<'a> { item: &'a Item, dox: &'a str, ori_link: &'a str, link_range: MarkdownLinkRange, } -struct LinkCollector<'a, 'tcx> { - cx: &'a mut DocContext<'tcx>, +pub(crate) struct OwnedDiagnosticInfo { + item: Item, + ori_link: String, + link_range: MarkdownLinkRange, +} + +impl From> for OwnedDiagnosticInfo { + fn from(f: DiagnosticInfo<'_>) -> Self { + Self { + item: f.item.clone(), + ori_link: f.ori_link.to_string(), + link_range: f.link_range.clone(), + } + } +} + +impl OwnedDiagnosticInfo { + pub(crate) fn into_info(&self) -> DiagnosticInfo<'_> { + DiagnosticInfo { + item: &self.item, + ori_link: &self.ori_link, + dox: "", + link_range: self.link_range.clone(), + } + } +} + +pub(crate) struct LinkCollector<'a, 'tcx> { + pub(crate) cx: &'a mut DocContext<'tcx>, /// Cache the resolved links so we can avoid resolving (and emitting errors for) the same link. /// The link will be `None` if it could not be resolved (i.e. the error was cached). - visited_links: FxHashMap)>>, + pub(crate) visited_links: FxHashMap)>>, + /// These links are ambiguous. We need for the cache to have its paths filled. Unfortunately, + /// if we run the `LinkCollector` pass after `Cache::populate`, a lot of items that we need + /// to go through will be removed, making a lot of intra-doc links to not be inferred. + /// + /// So instead, we store the ambiguous links and we wait for cache paths to be filled before + /// inferring them (if possible). + /// + /// Key is `(item ID, path str)`. + pub(crate) ambiguous_links: FxHashMap<(ItemId, String), AmbiguousLinks>, } impl<'a, 'tcx> LinkCollector<'a, 'tcx> { @@ -993,14 +1036,17 @@ impl LinkCollector<'_, '_> { _ => find_nearest_parent_module(self.cx.tcx, item_id).unwrap(), }; for md_link in preprocessed_markdown_links(&doc) { - let link = self.resolve_link(&doc, item, item_id, module_id, &md_link); - if let Some(link) = link { - self.cx.cache.intra_doc_links.entry(item.item_id).or_default().insert(link); + if let Some(link) = self.resolve_link(&doc, item, item_id, module_id, &md_link) { + self.save_link(item.item_id, link); } } } } + pub(crate) fn save_link(&mut self, item_id: ItemId, link: ItemLink) { + self.cx.cache.intra_doc_links.entry(item_id).or_default().insert(link); + } + /// This is the entry point for resolving an intra-doc link. /// /// FIXME(jynelson): this is way too many arguments @@ -1024,7 +1070,7 @@ impl LinkCollector<'_, '_> { pp_link.as_ref().map_err(|err| err.report(self.cx, diag_info.clone())).ok()?; let disambiguator = *disambiguator; - let (mut res, fragment) = self.resolve_with_disambiguator_cached( + let mut resolved = self.resolve_with_disambiguator_cached( ResolutionInfo { item_id, module_id, @@ -1040,6 +1086,101 @@ impl LinkCollector<'_, '_> { false, )?; + if resolved.len() > 1 { + let links = AmbiguousLinks { + disambiguator, + link_text: link_text.clone(), + diag_info: diag_info.into(), + resolved, + }; + self.ambiguous_links.insert((item.item_id, path_str.to_string()), links); + None + } else if let Some((res, fragment)) = resolved.pop() { + self.compute_link(res, fragment, path_str, disambiguator, diag_info, link_text) + } else { + None + } + } + + /// Returns `true` if a link could be generated from the given intra-doc information. + /// + /// This is a very light version of `format::href_with_root_path` since we're only interested + /// about whether we can generate a link to an item or not. + /// + /// * If `original_did` is local, then we check if the item is reexported or public. + /// * If `original_did` is not local, then we check if the crate it comes from is a direct + /// public dependency. + fn validate_link(&self, original_did: DefId) -> bool { + let tcx = self.cx.tcx; + let def_kind = tcx.def_kind(original_did); + let did = match def_kind { + DefKind::AssocTy | DefKind::AssocFn | DefKind::AssocConst | DefKind::Variant => { + // documented on their parent's page + tcx.parent(original_did) + } + // If this a constructor, we get the parent (either a struct or a variant) and then + // generate the link for this item. + DefKind::Ctor(..) => return self.validate_link(tcx.parent(original_did)), + DefKind::ExternCrate => { + // Link to the crate itself, not the `extern crate` item. + if let Some(local_did) = original_did.as_local() { + tcx.extern_mod_stmt_cnum(local_did).unwrap_or(LOCAL_CRATE).as_def_id() + } else { + original_did + } + } + _ => original_did, + }; + + let cache = &self.cx.cache; + if !original_did.is_local() + && !cache.effective_visibilities.is_directly_public(tcx, did) + && !cache.document_private + && !cache.primitive_locations.values().any(|&id| id == did) + { + return false; + } + + cache.paths.get(&did).is_some() + || cache.external_paths.get(&did).is_some() + || !did.is_local() + } + + pub(crate) fn resolve_ambiguities(&mut self) { + let mut ambiguous_links = mem::take(&mut self.ambiguous_links); + + for ((item_id, path_str), info) in ambiguous_links.iter_mut() { + info.resolved.retain(|(res, _)| match res { + Res::Def(_, def_id) => self.validate_link(*def_id), + // Primitive types are always valid. + Res::Primitive(_) => true, + }); + if info.resolved.len() == 1 { + let (res, fragment) = info.resolved.pop().unwrap(); + let diag_info = info.diag_info.into_info(); + if let Some(link) = self.compute_link( + res, + fragment, + path_str, + info.disambiguator, + diag_info, + &info.link_text, + ) { + self.save_link(*item_id, link); + } + } + } + } + + fn compute_link( + &mut self, + mut res: Res, + fragment: Option, + path_str: &str, + disambiguator: Option, + diag_info: DiagnosticInfo<'_>, + link_text: &Box, + ) -> Option { // Check for a primitive which might conflict with a module // Report the ambiguity and require that the user specify which one they meant. // FIXME: could there ever be a primitive not in the type namespace? @@ -1055,7 +1196,7 @@ impl LinkCollector<'_, '_> { } else { // `[char]` when a `char` module is in scope let candidates = &[(res, res.def_id(self.cx.tcx)), (prim, None)]; - ambiguity_error(self.cx, &diag_info, path_str, candidates); + ambiguity_error(self.cx, &diag_info, path_str, candidates, true); return None; } } @@ -1085,7 +1226,7 @@ impl LinkCollector<'_, '_> { } res.def_id(self.cx.tcx).map(|page_id| ItemLink { - link: Box::::from(&*ori_link.link), + link: Box::::from(&*diag_info.ori_link), link_text: link_text.clone(), page_id, fragment, @@ -1107,7 +1248,7 @@ impl LinkCollector<'_, '_> { let page_id = clean::register_res(self.cx, rustc_hir::def::Res::Def(kind, id)); Some(ItemLink { - link: Box::::from(&*ori_link.link), + link: Box::::from(&*diag_info.ori_link), link_text: link_text.clone(), page_id, fragment, @@ -1220,10 +1361,10 @@ impl LinkCollector<'_, '_> { // If this call is intended to be recoverable, then pass true to silence. // This is only recoverable when path is failed to resolved. recoverable: bool, - ) -> Option<(Res, Option)> { + ) -> Option)>> { if let Some(res) = self.visited_links.get(&key) { if res.is_some() || cache_errors { - return res.clone(); + return res.clone().map(|r| vec![r]); } } @@ -1249,30 +1390,33 @@ impl LinkCollector<'_, '_> { // won't emit an error. So at this point, we can just take the first candidate as it was // the first retrieved and use it to generate the link. if let [candidate, _candidate2, ..] = *candidates - && !ambiguity_error(self.cx, &diag, &key.path_str, &candidates) + && !ambiguity_error(self.cx, &diag, &key.path_str, &candidates, false) { candidates = vec![candidate]; } - if let &[(res, def_id)] = candidates.as_slice() { + let mut resolved = Vec::with_capacity(candidates.len()); + for (res, def_id) in candidates.as_slice() { let fragment = match (&key.extra_fragment, def_id) { (Some(_), Some(def_id)) => { - report_anchor_conflict(self.cx, diag, def_id); + report_anchor_conflict(self.cx, diag, *def_id); return None; } (Some(u_frag), None) => Some(UrlFragment::UserWritten(u_frag.clone())), - (None, Some(def_id)) => Some(UrlFragment::Item(def_id)), + (None, Some(def_id)) => Some(UrlFragment::Item(*def_id)), (None, None) => None, }; - let r = Some((res, fragment)); - self.visited_links.insert(key, r.clone()); - return r; + let r = (res.clone(), fragment.clone()); + self.visited_links.insert(key.clone(), Some(r.clone())); + resolved.push(r); } - if cache_errors { + if resolved.is_empty() && cache_errors { self.visited_links.insert(key, None); + None + } else { + Some(resolved) } - None } /// After parsing the disambiguator, resolve the main part of the link. @@ -1429,7 +1573,7 @@ fn should_ignore_link(path_str: &str) -> bool { #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] /// Disambiguators for a link. -enum Disambiguator { +pub(crate) enum Disambiguator { /// `prim@` /// /// This is buggy, see @@ -2046,6 +2190,7 @@ fn ambiguity_error( diag_info: &DiagnosticInfo<'_>, path_str: &str, candidates: &[(Res, Option)], + emit_error: bool, ) -> bool { let mut descrs = FxHashSet::default(); let kinds = candidates @@ -2062,6 +2207,9 @@ fn ambiguity_error( // candidate and not show a warning. return false; } + if !emit_error { + return true; + } let mut msg = format!("`{path_str}` is "); match kinds.as_slice() { diff --git a/src/librustdoc/passes/mod.rs b/src/librustdoc/passes/mod.rs index f5b7802372149..168980aef1c1b 100644 --- a/src/librustdoc/passes/mod.rs +++ b/src/librustdoc/passes/mod.rs @@ -27,7 +27,6 @@ mod propagate_stability; pub(crate) use self::propagate_stability::PROPAGATE_STABILITY; pub(crate) mod collect_intra_doc_links; -pub(crate) use self::collect_intra_doc_links::COLLECT_INTRA_DOC_LINKS; mod check_doc_test_visibility; pub(crate) use self::check_doc_test_visibility::CHECK_DOC_TEST_VISIBILITY; @@ -79,7 +78,6 @@ pub(crate) const PASSES: &[Pass] = &[ STRIP_PRIV_IMPORTS, PROPAGATE_DOC_CFG, PROPAGATE_STABILITY, - COLLECT_INTRA_DOC_LINKS, COLLECT_TRAIT_IMPLS, CALCULATE_DOC_COVERAGE, RUN_LINTS, @@ -93,7 +91,6 @@ pub(crate) const DEFAULT_PASSES: &[ConditionalPass] = &[ ConditionalPass::new(STRIP_HIDDEN, WhenNotDocumentHidden), ConditionalPass::new(STRIP_PRIVATE, WhenNotDocumentPrivate), ConditionalPass::new(STRIP_PRIV_IMPORTS, WhenDocumentPrivate), - ConditionalPass::always(COLLECT_INTRA_DOC_LINKS), ConditionalPass::always(PROPAGATE_DOC_CFG), ConditionalPass::always(PROPAGATE_STABILITY), ConditionalPass::always(RUN_LINTS), From 24b1316b2279ef40d9ab89576dd613574fc03b45 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Tue, 8 Oct 2024 23:54:47 +0200 Subject: [PATCH 2/5] Add regression test for #130233 --- tests/rustdoc/intra-doc/filter-out-private.rs | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 tests/rustdoc/intra-doc/filter-out-private.rs diff --git a/tests/rustdoc/intra-doc/filter-out-private.rs b/tests/rustdoc/intra-doc/filter-out-private.rs new file mode 100644 index 0000000000000..70591b120d823 --- /dev/null +++ b/tests/rustdoc/intra-doc/filter-out-private.rs @@ -0,0 +1,26 @@ +// This test ensures that private/hidden items don't create ambiguity. +// This is a regression test for . + +#![deny(rustdoc::broken_intra_doc_links)] +#![crate_name = "foo"] + +pub struct Thing {} + +#[doc(hidden)] +#[allow(non_snake_case)] +pub fn Thing() {} + +pub struct Bar {} + +#[allow(non_snake_case)] +fn Bar() {} + +//@ has 'foo/fn.repro.html' +//@ has - '//*[@class="toggle top-doc"]/*[@class="docblock"]//a/@href' 'struct.Thing.html' +/// Do stuff with [`Thing`]. +pub fn repro(_: Thing) {} + +//@ has 'foo/fn.repro2.html' +//@ has - '//*[@class="toggle top-doc"]/*[@class="docblock"]//a/@href' 'struct.Bar.html' +/// Do stuff with [`Bar`]. +pub fn repro2(_: Bar) {} From 524a9fb703a2ed59c10885725448668ec75ce0b7 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Wed, 9 Oct 2024 10:47:09 +0200 Subject: [PATCH 3/5] Fix rustc lint --- src/librustdoc/passes/collect_intra_doc_links.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/librustdoc/passes/collect_intra_doc_links.rs b/src/librustdoc/passes/collect_intra_doc_links.rs index 76d91442c51a2..bc02363c991b0 100644 --- a/src/librustdoc/passes/collect_intra_doc_links.rs +++ b/src/librustdoc/passes/collect_intra_doc_links.rs @@ -1146,6 +1146,7 @@ impl LinkCollector<'_, '_> { || !did.is_local() } + #[allow(rustc::potential_query_instability)] pub(crate) fn resolve_ambiguities(&mut self) { let mut ambiguous_links = mem::take(&mut self.ambiguous_links); From 26fb3060821da8f34a0cdd948418a7fe3f90db6b Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Wed, 9 Oct 2024 11:25:38 +0200 Subject: [PATCH 4/5] Emit error for ambiguities still not resolved at later stage --- .../passes/collect_intra_doc_links.rs | 57 ++++++++++++++----- .../intra-doc/filter-out-private-2.rs | 15 +++++ .../intra-doc/filter-out-private-2.stderr | 14 +++++ .../intra-doc/filter-out-private.rs | 13 +++++ .../intra-doc/filter-out-private.stderr | 22 +++++++ 5 files changed, 107 insertions(+), 14 deletions(-) create mode 100644 tests/rustdoc-ui/intra-doc/filter-out-private-2.rs create mode 100644 tests/rustdoc-ui/intra-doc/filter-out-private-2.stderr create mode 100644 tests/rustdoc-ui/intra-doc/filter-out-private.rs create mode 100644 tests/rustdoc-ui/intra-doc/filter-out-private.stderr diff --git a/src/librustdoc/passes/collect_intra_doc_links.rs b/src/librustdoc/passes/collect_intra_doc_links.rs index bc02363c991b0..6521a9a4b39c2 100644 --- a/src/librustdoc/passes/collect_intra_doc_links.rs +++ b/src/librustdoc/passes/collect_intra_doc_links.rs @@ -249,7 +249,7 @@ pub(crate) struct ResolutionInfo { extra_fragment: Option, } -#[derive(Clone)] +#[derive(Clone, Debug)] pub(crate) struct DiagnosticInfo<'a> { item: &'a Item, dox: &'a str, @@ -259,6 +259,7 @@ pub(crate) struct DiagnosticInfo<'a> { pub(crate) struct OwnedDiagnosticInfo { item: Item, + dox: String, ori_link: String, link_range: MarkdownLinkRange, } @@ -267,6 +268,7 @@ impl From> for OwnedDiagnosticInfo { fn from(f: DiagnosticInfo<'_>) -> Self { Self { item: f.item.clone(), + dox: f.dox.to_string(), ori_link: f.ori_link.to_string(), link_range: f.link_range.clone(), } @@ -278,7 +280,7 @@ impl OwnedDiagnosticInfo { DiagnosticInfo { item: &self.item, ori_link: &self.ori_link, - dox: "", + dox: &self.dox, link_range: self.link_range.clone(), } } @@ -1156,18 +1158,45 @@ impl LinkCollector<'_, '_> { // Primitive types are always valid. Res::Primitive(_) => true, }); - if info.resolved.len() == 1 { - let (res, fragment) = info.resolved.pop().unwrap(); - let diag_info = info.diag_info.into_info(); - if let Some(link) = self.compute_link( - res, - fragment, - path_str, - info.disambiguator, - diag_info, - &info.link_text, - ) { - self.save_link(*item_id, link); + let diag_info = info.diag_info.into_info(); + match info.resolved.len() { + 1 => { + let (res, fragment) = info.resolved.pop().unwrap(); + if let Some(link) = self.compute_link( + res, + fragment, + path_str, + info.disambiguator, + diag_info, + &info.link_text, + ) { + self.save_link(*item_id, link); + } + } + 0 => { + report_diagnostic( + self.cx.tcx, + BROKEN_INTRA_DOC_LINKS, + format!( + "all items matching `{path_str}` are either private or doc(hidden)" + ), + &diag_info, + |diag, sp, _| { + if let Some(sp) = sp { + diag.span_label(sp, "unresolved link"); + } else { + diag.note("unresolved link"); + } + }, + ); + } + _ => { + let candidates = info + .resolved + .iter() + .map(|(res, _)| (*res, res.def_id(self.cx.tcx))) + .collect::>(); + ambiguity_error(self.cx, &diag_info, path_str, &candidates, true); } } } diff --git a/tests/rustdoc-ui/intra-doc/filter-out-private-2.rs b/tests/rustdoc-ui/intra-doc/filter-out-private-2.rs new file mode 100644 index 0000000000000..9209203c99ee5 --- /dev/null +++ b/tests/rustdoc-ui/intra-doc/filter-out-private-2.rs @@ -0,0 +1,15 @@ +// This test ensures that ambiguities (not) resolved at a later stage still emit an error. + +#![deny(rustdoc::broken_intra_doc_links)] +#![crate_name = "foo"] + +#[doc(hidden)] +pub struct Thing {} + +#[allow(non_snake_case)] +#[doc(hidden)] +pub fn Thing() {} + +/// Do stuff with [`Thing`]. +//~^ ERROR all items matching `Thing` are either private or doc(hidden) +pub fn repro(_: Thing) {} diff --git a/tests/rustdoc-ui/intra-doc/filter-out-private-2.stderr b/tests/rustdoc-ui/intra-doc/filter-out-private-2.stderr new file mode 100644 index 0000000000000..394f919de943e --- /dev/null +++ b/tests/rustdoc-ui/intra-doc/filter-out-private-2.stderr @@ -0,0 +1,14 @@ +error: all items matching `Thing` are either private or doc(hidden) + --> $DIR/filter-out-private-2.rs:13:21 + | +LL | /// Do stuff with [`Thing`]. + | ^^^^^ unresolved link + | +note: the lint level is defined here + --> $DIR/filter-out-private-2.rs:3:9 + | +LL | #![deny(rustdoc::broken_intra_doc_links)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 1 previous error + diff --git a/tests/rustdoc-ui/intra-doc/filter-out-private.rs b/tests/rustdoc-ui/intra-doc/filter-out-private.rs new file mode 100644 index 0000000000000..f481b51dad066 --- /dev/null +++ b/tests/rustdoc-ui/intra-doc/filter-out-private.rs @@ -0,0 +1,13 @@ +// This test ensures that ambiguities resolved at a later stage still emit an error. + +#![deny(rustdoc::broken_intra_doc_links)] +#![crate_name = "foo"] + +pub struct Thing {} + +#[allow(non_snake_case)] +pub fn Thing() {} + +/// Do stuff with [`Thing`]. +//~^ ERROR `Thing` is both a function and a struct +pub fn repro(_: Thing) {} diff --git a/tests/rustdoc-ui/intra-doc/filter-out-private.stderr b/tests/rustdoc-ui/intra-doc/filter-out-private.stderr new file mode 100644 index 0000000000000..1d1830b1f1c37 --- /dev/null +++ b/tests/rustdoc-ui/intra-doc/filter-out-private.stderr @@ -0,0 +1,22 @@ +error: `Thing` is both a function and a struct + --> $DIR/filter-out-private.rs:11:21 + | +LL | /// Do stuff with [`Thing`]. + | ^^^^^ ambiguous link + | +note: the lint level is defined here + --> $DIR/filter-out-private.rs:3:9 + | +LL | #![deny(rustdoc::broken_intra_doc_links)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +help: to link to the function, add parentheses + | +LL | /// Do stuff with [`Thing()`]. + | ++ +help: to link to the struct, prefix with `struct@` + | +LL | /// Do stuff with [`struct@Thing`]. + | +++++++ + +error: aborting due to 1 previous error + From 759c771a828717417734423df78d9d3bab6546a2 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Thu, 10 Oct 2024 23:06:29 +0200 Subject: [PATCH 5/5] test --- src/librustdoc/clean/types.rs | 13 +- src/librustdoc/core.rs | 14 +- src/librustdoc/html/markdown.rs | 2 +- .../passes/collect_intra_doc_links.rs | 137 ++++++++++-------- src/librustdoc/passes/mod.rs | 3 + tests/rustdoc-ui/intra-doc/ambiguity.stderr | 35 ++--- .../html-as-generics-intra-doc.stderr | 58 ++++---- .../issue-108653-associated-items-3.stderr | 36 ++--- .../issue-108653-associated-items.stderr | 20 +-- .../intra-doc/malformed-generics.stderr | 112 +++++++------- tests/rustdoc-ui/lints/lint-group.stderr | 16 +- .../lints/renamed-lint-still-applies.stderr | 26 ++-- 12 files changed, 241 insertions(+), 231 deletions(-) diff --git a/src/librustdoc/clean/types.rs b/src/librustdoc/clean/types.rs index bc5bf4c05838a..44d9a5fdbe8ad 100644 --- a/src/librustdoc/clean/types.rs +++ b/src/librustdoc/clean/types.rs @@ -1228,15 +1228,14 @@ impl Attributes { for attr in self.other_attrs.lists(sym::doc).filter(|a| a.has_name(sym::alias)) { if let Some(values) = attr.meta_item_list() { for l in values { - match l.lit().unwrap().kind { - ast::LitKind::Str(s, _) => { - aliases.insert(s); - } - _ => unreachable!(), + if let Some(lit) = l.lit() + && let ast::LitKind::Str(s, _) = lit.kind + { + aliases.insert(s); } } - } else { - aliases.insert(attr.value_str().unwrap()); + } else if let Some(value) = attr.value_str() { + aliases.insert(value); } } aliases.into_iter().collect::>().into() diff --git a/src/librustdoc/core.rs b/src/librustdoc/core.rs index ad576ed7a7b10..57f0e637d8d6e 100644 --- a/src/librustdoc/core.rs +++ b/src/librustdoc/core.rs @@ -31,7 +31,7 @@ use crate::config::{Options as RustdocOptions, OutputFormat, RenderOptions}; use crate::formats::cache::Cache; use crate::passes::Condition::*; use crate::passes::collect_intra_doc_links::LinkCollector; -use crate::passes::{self}; +use crate::passes; pub(crate) struct DocContext<'tcx> { pub(crate) tcx: TyCtxt<'tcx>, @@ -441,21 +441,23 @@ pub(crate) fn run_global_ctxt( } } + debug!("running pass {}", passes::collect_intra_doc_links::COLLECT_INTRA_DOC_LINKS.name); let (mut krate, LinkCollector { visited_links, ambiguous_links, .. }) = tcx.sess.time("collect_intra_doc_links", || { passes::collect_intra_doc_links::collect_intra_doc_links(krate, &mut ctxt) }); - tcx.sess.time("check_lint_expectations", || tcx.check_expectations(Some(sym::rustdoc))); - - if let Some(guar) = tcx.dcx().has_errors() { - return Err(guar); - } krate = tcx.sess.time("create_format_cache", || Cache::populate(&mut ctxt, krate)); let mut collector = LinkCollector { cx: &mut ctxt, visited_links, ambiguous_links }; collector.resolve_ambiguities(); + tcx.sess.time("check_lint_expectations", || tcx.check_expectations(Some(sym::rustdoc))); + + if let Some(guar) = tcx.dcx().has_errors() { + return Err(guar); + } + Ok((krate, ctxt.render_options, ctxt.cache)) } diff --git a/src/librustdoc/html/markdown.rs b/src/librustdoc/html/markdown.rs index 5dacabd031e06..2fa5a94d8cc69 100644 --- a/src/librustdoc/html/markdown.rs +++ b/src/librustdoc/html/markdown.rs @@ -1688,7 +1688,7 @@ pub(crate) struct MarkdownLink { pub range: MarkdownLinkRange, } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, PartialEq, Eq, Hash)] pub(crate) enum MarkdownLinkRange { /// Normally, markdown link warnings point only at the destination. Destination(Range), diff --git a/src/librustdoc/passes/collect_intra_doc_links.rs b/src/librustdoc/passes/collect_intra_doc_links.rs index 6521a9a4b39c2..38e7c8ed96f97 100644 --- a/src/librustdoc/passes/collect_intra_doc_links.rs +++ b/src/librustdoc/passes/collect_intra_doc_links.rs @@ -34,8 +34,17 @@ use crate::clean::{self, Crate, Item, ItemId, ItemLink, PrimitiveType}; use crate::core::DocContext; use crate::html::markdown::{MarkdownLink, MarkdownLinkRange, markdown_links}; use crate::lint::{BROKEN_INTRA_DOC_LINKS, PRIVATE_INTRA_DOC_LINKS}; +use crate::passes::Pass; use crate::visit::DocVisitor; +/// We create an empty pass for `collect_intra_doc_link` so it still listed when displaying +/// passes list. +pub(crate) const COLLECT_INTRA_DOC_LINKS: Pass = Pass { + name: "collect-intra-doc-links", + run: |krate: Crate, _: &mut DocContext<'_>| krate, + description: "resolves intra-doc links", +}; + pub(crate) fn collect_intra_doc_links<'a, 'tcx>( krate: Crate, cx: &'a mut DocContext<'tcx>, @@ -247,6 +256,7 @@ pub(crate) struct ResolutionInfo { dis: Option, path_str: Box, extra_fragment: Option, + link_range: MarkdownLinkRange, } #[derive(Clone, Debug)] @@ -290,7 +300,7 @@ pub(crate) struct LinkCollector<'a, 'tcx> { pub(crate) cx: &'a mut DocContext<'tcx>, /// Cache the resolved links so we can avoid resolving (and emitting errors for) the same link. /// The link will be `None` if it could not be resolved (i.e. the error was cached). - pub(crate) visited_links: FxHashMap)>>, + pub(crate) visited_links: FxHashMap)>>>, /// These links are ambiguous. We need for the cache to have its paths filled. Unfortunately, /// if we run the `LinkCollector` pass after `Cache::populate`, a lot of items that we need /// to go through will be removed, making a lot of intra-doc links to not be inferred. @@ -299,7 +309,7 @@ pub(crate) struct LinkCollector<'a, 'tcx> { /// inferring them (if possible). /// /// Key is `(item ID, path str)`. - pub(crate) ambiguous_links: FxHashMap<(ItemId, String), AmbiguousLinks>, + pub(crate) ambiguous_links: FxHashMap<(ItemId, String), Vec>, } impl<'a, 'tcx> LinkCollector<'a, 'tcx> { @@ -1079,6 +1089,7 @@ impl LinkCollector<'_, '_> { dis: disambiguator, path_str: path_str.clone(), extra_fragment: extra_fragment.clone(), + link_range: ori_link.range.clone(), }, diag_info.clone(), // this struct should really be Copy, but Range is not :( // For reference-style links we want to report only one error so unsuccessful @@ -1095,7 +1106,11 @@ impl LinkCollector<'_, '_> { diag_info: diag_info.into(), resolved, }; - self.ambiguous_links.insert((item.item_id, path_str.to_string()), links); + + self.ambiguous_links + .entry((item.item_id, path_str.to_string())) + .or_default() + .push(links); None } else if let Some((res, fragment)) = resolved.pop() { self.compute_link(res, fragment, path_str, disambiguator, diag_info, link_text) @@ -1152,51 +1167,60 @@ impl LinkCollector<'_, '_> { pub(crate) fn resolve_ambiguities(&mut self) { let mut ambiguous_links = mem::take(&mut self.ambiguous_links); - for ((item_id, path_str), info) in ambiguous_links.iter_mut() { - info.resolved.retain(|(res, _)| match res { - Res::Def(_, def_id) => self.validate_link(*def_id), - // Primitive types are always valid. - Res::Primitive(_) => true, - }); - let diag_info = info.diag_info.into_info(); - match info.resolved.len() { - 1 => { - let (res, fragment) = info.resolved.pop().unwrap(); - if let Some(link) = self.compute_link( - res, - fragment, - path_str, - info.disambiguator, - diag_info, - &info.link_text, - ) { - self.save_link(*item_id, link); + for ((item_id, path_str), info_items) in ambiguous_links.iter_mut() { + for info in info_items { + info.resolved.retain(|(res, _)| match res { + Res::Def(_, def_id) => self.validate_link(*def_id), + // Primitive types are always valid. + Res::Primitive(_) => true, + }); + let diag_info = info.diag_info.into_info(); + match info.resolved.len() { + 1 => { + let (res, fragment) = info.resolved.pop().unwrap(); + if let Some(link) = self.compute_link( + res, + fragment, + path_str, + info.disambiguator, + diag_info, + &info.link_text, + ) { + self.save_link(*item_id, link); + } + } + 0 => { + report_diagnostic( + self.cx.tcx, + BROKEN_INTRA_DOC_LINKS, + format!( + "all items matching `{path_str}` are either private or doc(hidden)" + ), + &diag_info, + |diag, sp, _| { + if let Some(sp) = sp { + diag.span_label(sp, "unresolved link"); + } else { + diag.note("unresolved link"); + } + }, + ); + } + _ => { + let candidates = info + .resolved + .iter() + .map(|(res, fragment)| { + let def_id = if let Some(UrlFragment::Item(def_id)) = fragment { + Some(*def_id) + } else { + None + }; + (*res, def_id) + }) + .collect::>(); + ambiguity_error(self.cx, &diag_info, path_str, &candidates, true); } - } - 0 => { - report_diagnostic( - self.cx.tcx, - BROKEN_INTRA_DOC_LINKS, - format!( - "all items matching `{path_str}` are either private or doc(hidden)" - ), - &diag_info, - |diag, sp, _| { - if let Some(sp) = sp { - diag.span_label(sp, "unresolved link"); - } else { - diag.note("unresolved link"); - } - }, - ); - } - _ => { - let candidates = info - .resolved - .iter() - .map(|(res, _)| (*res, res.def_id(self.cx.tcx))) - .collect::>(); - ambiguity_error(self.cx, &diag_info, path_str, &candidates, true); } } } @@ -1387,15 +1411,13 @@ impl LinkCollector<'_, '_> { diag: DiagnosticInfo<'_>, // If errors are cached then they are only reported on first occurrence // which we want in some cases but not in others. - cache_errors: bool, + _cache_errors: bool, // If this call is intended to be recoverable, then pass true to silence. // This is only recoverable when path is failed to resolved. recoverable: bool, ) -> Option)>> { if let Some(res) = self.visited_links.get(&key) { - if res.is_some() || cache_errors { - return res.clone().map(|r| vec![r]); - } + return res.clone(); } let mut candidates = self.resolve_with_disambiguator(&key, diag.clone(), recoverable); @@ -1426,25 +1448,24 @@ impl LinkCollector<'_, '_> { } let mut resolved = Vec::with_capacity(candidates.len()); - for (res, def_id) in candidates.as_slice() { + for (res, def_id) in candidates { let fragment = match (&key.extra_fragment, def_id) { (Some(_), Some(def_id)) => { - report_anchor_conflict(self.cx, diag, *def_id); + report_anchor_conflict(self.cx, diag, def_id); return None; } (Some(u_frag), None) => Some(UrlFragment::UserWritten(u_frag.clone())), - (None, Some(def_id)) => Some(UrlFragment::Item(*def_id)), + (None, Some(def_id)) => Some(UrlFragment::Item(def_id)), (None, None) => None, }; - let r = (res.clone(), fragment.clone()); - self.visited_links.insert(key.clone(), Some(r.clone())); - resolved.push(r); + resolved.push((res, fragment)); } - if resolved.is_empty() && cache_errors { + if resolved.is_empty() { self.visited_links.insert(key, None); None } else { + self.visited_links.insert(key.clone(), Some(resolved.clone())); Some(resolved) } } diff --git a/src/librustdoc/passes/mod.rs b/src/librustdoc/passes/mod.rs index 168980aef1c1b..f5b7802372149 100644 --- a/src/librustdoc/passes/mod.rs +++ b/src/librustdoc/passes/mod.rs @@ -27,6 +27,7 @@ mod propagate_stability; pub(crate) use self::propagate_stability::PROPAGATE_STABILITY; pub(crate) mod collect_intra_doc_links; +pub(crate) use self::collect_intra_doc_links::COLLECT_INTRA_DOC_LINKS; mod check_doc_test_visibility; pub(crate) use self::check_doc_test_visibility::CHECK_DOC_TEST_VISIBILITY; @@ -78,6 +79,7 @@ pub(crate) const PASSES: &[Pass] = &[ STRIP_PRIV_IMPORTS, PROPAGATE_DOC_CFG, PROPAGATE_STABILITY, + COLLECT_INTRA_DOC_LINKS, COLLECT_TRAIT_IMPLS, CALCULATE_DOC_COVERAGE, RUN_LINTS, @@ -91,6 +93,7 @@ pub(crate) const DEFAULT_PASSES: &[ConditionalPass] = &[ ConditionalPass::new(STRIP_HIDDEN, WhenNotDocumentHidden), ConditionalPass::new(STRIP_PRIVATE, WhenNotDocumentPrivate), ConditionalPass::new(STRIP_PRIV_IMPORTS, WhenDocumentPrivate), + ConditionalPass::always(COLLECT_INTRA_DOC_LINKS), ConditionalPass::always(PROPAGATE_DOC_CFG), ConditionalPass::always(PROPAGATE_STABILITY), ConditionalPass::always(RUN_LINTS), diff --git a/tests/rustdoc-ui/intra-doc/ambiguity.stderr b/tests/rustdoc-ui/intra-doc/ambiguity.stderr index 47853e0b5899d..ed705ccca296c 100644 --- a/tests/rustdoc-ui/intra-doc/ambiguity.stderr +++ b/tests/rustdoc-ui/intra-doc/ambiguity.stderr @@ -33,20 +33,20 @@ help: to link to the struct, prefix with `struct@` LL | /// [`struct@ambiguous`] is ambiguous. | +++++++ -error: `ambiguous` is both a function and a struct - --> $DIR/ambiguity.rs:29:6 +error: `foo::bar` is both a function and an enum + --> $DIR/ambiguity.rs:35:43 | -LL | /// [ambiguous] is ambiguous. - | ^^^^^^^^^ ambiguous link +LL | /// Ambiguous non-implied shortcut link [`foo::bar`]. + | ^^^^^^^^ ambiguous link | help: to link to the function, add parentheses | -LL | /// [ambiguous()] is ambiguous. - | ++ -help: to link to the struct, prefix with `struct@` +LL | /// Ambiguous non-implied shortcut link [`foo::bar()`]. + | ++ +help: to link to the enum, prefix with `enum@` | -LL | /// [struct@ambiguous] is ambiguous. - | +++++++ +LL | /// Ambiguous non-implied shortcut link [`enum@foo::bar`]. + | +++++ error: `multi_conflict` is a function, a struct, and a macro --> $DIR/ambiguity.rs:31:7 @@ -82,20 +82,5 @@ help: to link to the module, prefix with `mod@` LL | /// Ambiguous [mod@type_and_value]. | ++++ -error: `foo::bar` is both a function and an enum - --> $DIR/ambiguity.rs:35:43 - | -LL | /// Ambiguous non-implied shortcut link [`foo::bar`]. - | ^^^^^^^^ ambiguous link - | -help: to link to the function, add parentheses - | -LL | /// Ambiguous non-implied shortcut link [`foo::bar()`]. - | ++ -help: to link to the enum, prefix with `enum@` - | -LL | /// Ambiguous non-implied shortcut link [`enum@foo::bar`]. - | +++++ - -error: aborting due to 6 previous errors +error: aborting due to 5 previous errors diff --git a/tests/rustdoc-ui/intra-doc/html-as-generics-intra-doc.stderr b/tests/rustdoc-ui/intra-doc/html-as-generics-intra-doc.stderr index 7c81044dbf81b..bee0bbb2f4328 100644 --- a/tests/rustdoc-ui/intra-doc/html-as-generics-intra-doc.stderr +++ b/tests/rustdoc-ui/intra-doc/html-as-generics-intra-doc.stderr @@ -1,32 +1,3 @@ -error: unresolved link to `NonExistentStruct` - --> $DIR/html-as-generics-intra-doc.rs:13:17 - | -LL | /// This [test][NonExistentStruct] thing! - | ^^^^^^^^^^^^^^^^^^^^^^ no item named `NonExistentStruct` in scope - | - = help: to escape `[` and `]` characters, add '\' before them like `\[` or `\]` -note: the lint level is defined here - --> $DIR/html-as-generics-intra-doc.rs:2:9 - | -LL | #![deny(rustdoc::broken_intra_doc_links)] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -error: unresolved link to `NonExistentStruct2` - --> $DIR/html-as-generics-intra-doc.rs:17:11 - | -LL | /// This [NonExistentStruct2] thing! - | ^^^^^^^^^^^^^^^^^^^^^^^ no item named `NonExistentStruct2` in scope - | - = help: to escape `[` and `]` characters, add '\' before them like `\[` or `\]` - -error: unresolved link to `NonExistentStruct3` - --> $DIR/html-as-generics-intra-doc.rs:22:11 - | -LL | /// This [NonExistentStruct3][] thing! - | ^^^^^^^^^^^^^^^^^^^^^^^ no item named `NonExistentStruct3` in scope - | - = help: to escape `[` and `]` characters, add '\' before them like `\[` or `\]` - error: unclosed HTML tag `i32` --> $DIR/html-as-generics-intra-doc.rs:9:25 | @@ -65,5 +36,34 @@ help: try marking as source code LL | /// This [`NonExistentStruct3`][] thing! | + + +error: unresolved link to `NonExistentStruct` + --> $DIR/html-as-generics-intra-doc.rs:13:17 + | +LL | /// This [test][NonExistentStruct] thing! + | ^^^^^^^^^^^^^^^^^^^^^^ no item named `NonExistentStruct` in scope + | + = help: to escape `[` and `]` characters, add '\' before them like `\[` or `\]` +note: the lint level is defined here + --> $DIR/html-as-generics-intra-doc.rs:2:9 + | +LL | #![deny(rustdoc::broken_intra_doc_links)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: unresolved link to `NonExistentStruct2` + --> $DIR/html-as-generics-intra-doc.rs:17:11 + | +LL | /// This [NonExistentStruct2] thing! + | ^^^^^^^^^^^^^^^^^^^^^^^ no item named `NonExistentStruct2` in scope + | + = help: to escape `[` and `]` characters, add '\' before them like `\[` or `\]` + +error: unresolved link to `NonExistentStruct3` + --> $DIR/html-as-generics-intra-doc.rs:22:11 + | +LL | /// This [NonExistentStruct3][] thing! + | ^^^^^^^^^^^^^^^^^^^^^^^ no item named `NonExistentStruct3` in scope + | + = help: to escape `[` and `]` characters, add '\' before them like `\[` or `\]` + error: aborting due to 6 previous errors diff --git a/tests/rustdoc-ui/intra-doc/issue-108653-associated-items-3.stderr b/tests/rustdoc-ui/intra-doc/issue-108653-associated-items-3.stderr index 6401dacb57a8e..211dc44fe3499 100644 --- a/tests/rustdoc-ui/intra-doc/issue-108653-associated-items-3.stderr +++ b/tests/rustdoc-ui/intra-doc/issue-108653-associated-items-3.stderr @@ -1,37 +1,37 @@ -error: `Trait` is both a constant and a trait - --> $DIR/issue-108653-associated-items-3.rs:12:7 +error: `Trait::Trait` is both an associated constant and an associated type + --> $DIR/issue-108653-associated-items-3.rs:14:7 | -LL | /// [`Trait`] - | ^^^^^ ambiguous link +LL | /// [`Trait::Trait`] + | ^^^^^^^^^^^^ ambiguous link | note: the lint level is defined here --> $DIR/issue-108653-associated-items-3.rs:4:9 | LL | #![deny(rustdoc::broken_intra_doc_links)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -help: to link to the constant, prefix with `const@` +help: to link to the associated constant, prefix with `const@` | -LL | /// [`const@Trait`] +LL | /// [`const@Trait::Trait`] | ++++++ -help: to link to the trait, prefix with `trait@` +help: to link to the associated type, prefix with `type@` | -LL | /// [`trait@Trait`] - | ++++++ +LL | /// [`type@Trait::Trait`] + | +++++ -error: `Trait::Trait` is both an associated constant and an associated type - --> $DIR/issue-108653-associated-items-3.rs:14:7 +error: `Trait` is both a constant and a trait + --> $DIR/issue-108653-associated-items-3.rs:12:7 | -LL | /// [`Trait::Trait`] - | ^^^^^^^^^^^^ ambiguous link +LL | /// [`Trait`] + | ^^^^^ ambiguous link | -help: to link to the associated constant, prefix with `const@` +help: to link to the constant, prefix with `const@` | -LL | /// [`const@Trait::Trait`] +LL | /// [`const@Trait`] | ++++++ -help: to link to the associated type, prefix with `type@` +help: to link to the trait, prefix with `trait@` | -LL | /// [`type@Trait::Trait`] - | +++++ +LL | /// [`trait@Trait`] + | ++++++ error: aborting due to 2 previous errors diff --git a/tests/rustdoc-ui/intra-doc/issue-108653-associated-items.stderr b/tests/rustdoc-ui/intra-doc/issue-108653-associated-items.stderr index 9cd855b69ff99..624e6b6e22f71 100644 --- a/tests/rustdoc-ui/intra-doc/issue-108653-associated-items.stderr +++ b/tests/rustdoc-ui/intra-doc/issue-108653-associated-items.stderr @@ -1,5 +1,5 @@ -error: `Self::IDENT` is both an associated function and an associated type - --> $DIR/issue-108653-associated-items.rs:9:7 +error: `Self::IDENT` is both an associated function and a variant + --> $DIR/issue-108653-associated-items.rs:16:7 | LL | /// [`Self::IDENT`] | ^^^^^^^^^^^ ambiguous link @@ -13,10 +13,10 @@ help: to link to the associated function, add parentheses | LL | /// [`Self::IDENT()`] | ++ -help: to link to the associated type, prefix with `type@` +help: to link to the variant, prefix with `variant@` | -LL | /// [`type@Self::IDENT`] - | +++++ +LL | /// [`variant@Self::IDENT`] + | ++++++++ error: `Self::IDENT2` is both an associated constant and an associated type --> $DIR/issue-108653-associated-items.rs:23:7 @@ -33,8 +33,8 @@ help: to link to the associated type, prefix with `type@` LL | /// [`type@Self::IDENT2`] | +++++ -error: `Self::IDENT` is both an associated function and a variant - --> $DIR/issue-108653-associated-items.rs:16:7 +error: `Self::IDENT` is both an associated function and an associated type + --> $DIR/issue-108653-associated-items.rs:9:7 | LL | /// [`Self::IDENT`] | ^^^^^^^^^^^ ambiguous link @@ -43,10 +43,10 @@ help: to link to the associated function, add parentheses | LL | /// [`Self::IDENT()`] | ++ -help: to link to the variant, prefix with `variant@` +help: to link to the associated type, prefix with `type@` | -LL | /// [`variant@Self::IDENT`] - | ++++++++ +LL | /// [`type@Self::IDENT`] + | +++++ error: `Self::IDENT2` is both an associated constant and an associated type --> $DIR/issue-108653-associated-items.rs:30:7 diff --git a/tests/rustdoc-ui/intra-doc/malformed-generics.stderr b/tests/rustdoc-ui/intra-doc/malformed-generics.stderr index 08349fef88d38..8c760a05f08a6 100644 --- a/tests/rustdoc-ui/intra-doc/malformed-generics.stderr +++ b/tests/rustdoc-ui/intra-doc/malformed-generics.stderr @@ -1,3 +1,59 @@ +warning: unclosed HTML tag `T` + --> $DIR/malformed-generics.rs:5:13 + | +LL | //! [Vec] + | ^^^ + | + = note: `#[warn(rustdoc::invalid_html_tags)]` on by default + +warning: unclosed HTML tag `T` + --> $DIR/malformed-generics.rs:7:13 + | +LL | //! [Vec>>] + | ^^^ + +warning: unclosed HTML tag `T` + --> $DIR/malformed-generics.rs:9:9 + | +LL | //! [Vec>>] + | ^^^ + +warning: unclosed HTML tag `T` + --> $DIR/malformed-generics.rs:13:6 + | +LL | //! [] + | ^^^ + +warning: unclosed HTML tag `invalid` + --> $DIR/malformed-generics.rs:15:6 + | +LL | //! [] + | ^^^^^^^^ + +warning: unclosed HTML tag `T` + --> $DIR/malformed-generics.rs:17:10 + | +LL | //! [Vec::new()] + | ^^^ + +warning: unclosed HTML tag `T` + --> $DIR/malformed-generics.rs:19:10 + | +LL | //! [Vec<>] + | ^^^ + +warning: unclosed HTML tag `Vec` + --> $DIR/malformed-generics.rs:25:6 + | +LL | //! [::into_iter] + | ^^^^ + +warning: unclosed HTML tag `T` + --> $DIR/malformed-generics.rs:27:10 + | +LL | //! [ as IntoIterator>::iter] + | ^^^ + error: unresolved link to `Vec<` --> $DIR/malformed-generics.rs:3:6 | @@ -98,61 +154,5 @@ LL | //! [ as IntoIterator>::iter] | = note: see https://github.com/rust-lang/rust/issues/74563 for more information -warning: unclosed HTML tag `T` - --> $DIR/malformed-generics.rs:5:13 - | -LL | //! [Vec] - | ^^^ - | - = note: `#[warn(rustdoc::invalid_html_tags)]` on by default - -warning: unclosed HTML tag `T` - --> $DIR/malformed-generics.rs:7:13 - | -LL | //! [Vec>>] - | ^^^ - -warning: unclosed HTML tag `T` - --> $DIR/malformed-generics.rs:9:9 - | -LL | //! [Vec>>] - | ^^^ - -warning: unclosed HTML tag `T` - --> $DIR/malformed-generics.rs:13:6 - | -LL | //! [] - | ^^^ - -warning: unclosed HTML tag `invalid` - --> $DIR/malformed-generics.rs:15:6 - | -LL | //! [] - | ^^^^^^^^ - -warning: unclosed HTML tag `T` - --> $DIR/malformed-generics.rs:17:10 - | -LL | //! [Vec::new()] - | ^^^ - -warning: unclosed HTML tag `T` - --> $DIR/malformed-generics.rs:19:10 - | -LL | //! [Vec<>] - | ^^^ - -warning: unclosed HTML tag `Vec` - --> $DIR/malformed-generics.rs:25:6 - | -LL | //! [::into_iter] - | ^^^^ - -warning: unclosed HTML tag `T` - --> $DIR/malformed-generics.rs:27:10 - | -LL | //! [ as IntoIterator>::iter] - | ^^^ - error: aborting due to 15 previous errors; 9 warnings emitted diff --git a/tests/rustdoc-ui/lints/lint-group.stderr b/tests/rustdoc-ui/lints/lint-group.stderr index 7ff09fcc45a12..76b281dfaa64f 100644 --- a/tests/rustdoc-ui/lints/lint-group.stderr +++ b/tests/rustdoc-ui/lints/lint-group.stderr @@ -33,6 +33,14 @@ error: missing code example in this documentation LL | /// | ^^^^^^^^^^^^^ +error: unclosed HTML tag `unknown` + --> $DIR/lint-group.rs:29:5 + | +LL | /// + | ^^^^^^^^^ + | + = note: `#[deny(rustdoc::invalid_html_tags)]` implied by `#[deny(rustdoc::all)]` + error: unresolved link to `error` --> $DIR/lint-group.rs:12:29 | @@ -42,13 +50,5 @@ LL | /// what up, let's make an [error] = help: to escape `[` and `]` characters, add '\' before them like `\[` or `\]` = note: `#[deny(rustdoc::broken_intra_doc_links)]` implied by `#[deny(rustdoc::all)]` -error: unclosed HTML tag `unknown` - --> $DIR/lint-group.rs:29:5 - | -LL | /// - | ^^^^^^^^^ - | - = note: `#[deny(rustdoc::invalid_html_tags)]` implied by `#[deny(rustdoc::all)]` - error: aborting due to 5 previous errors diff --git a/tests/rustdoc-ui/lints/renamed-lint-still-applies.stderr b/tests/rustdoc-ui/lints/renamed-lint-still-applies.stderr index 88807dfb495d0..f48a69ffb6553 100644 --- a/tests/rustdoc-ui/lints/renamed-lint-still-applies.stderr +++ b/tests/rustdoc-ui/lints/renamed-lint-still-applies.stderr @@ -12,19 +12,6 @@ warning: lint `rustdoc::non_autolinks` has been renamed to `rustdoc::bare_urls` LL | #![deny(rustdoc::non_autolinks)] | ^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `rustdoc::bare_urls` -error: unresolved link to `x` - --> $DIR/renamed-lint-still-applies.rs:4:6 - | -LL | //! [x] - | ^ no item named `x` in scope - | - = help: to escape `[` and `]` characters, add '\' before them like `\[` or `\]` -note: the lint level is defined here - --> $DIR/renamed-lint-still-applies.rs:2:9 - | -LL | #![deny(broken_intra_doc_links)] - | ^^^^^^^^^^^^^^^^^^^^^^ - error: this URL is not a hyperlink --> $DIR/renamed-lint-still-applies.rs:9:5 | @@ -42,5 +29,18 @@ help: use an automatic link instead LL | //! | + + +error: unresolved link to `x` + --> $DIR/renamed-lint-still-applies.rs:4:6 + | +LL | //! [x] + | ^ no item named `x` in scope + | + = help: to escape `[` and `]` characters, add '\' before them like `\[` or `\]` +note: the lint level is defined here + --> $DIR/renamed-lint-still-applies.rs:2:9 + | +LL | #![deny(broken_intra_doc_links)] + | ^^^^^^^^^^^^^^^^^^^^^^ + error: aborting due to 2 previous errors; 2 warnings emitted