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 aaf4c80f99763..57f0e637d8d6e 100644 --- a/src/librustdoc/core.rs +++ b/src/librustdoc/core.rs @@ -30,7 +30,8 @@ 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::{self}; +use crate::passes::collect_intra_doc_links::LinkCollector; +use crate::passes; pub(crate) struct DocContext<'tcx> { pub(crate) tcx: TyCtxt<'tcx>, @@ -440,14 +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) + }); + + 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); } - krate = tcx.sess.time("create_format_cache", || Cache::populate(&mut ctxt, krate)); - 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 db235786cf49a..38e7c8ed96f97 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,39 @@ 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; +/// 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: collect_intra_doc_links, + run: |krate: Crate, _: &mut DocContext<'_>| krate, 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 +77,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,27 +250,66 @@ impl UrlFragment { } #[derive(Clone, Debug, Hash, PartialEq, Eq)] -struct ResolutionInfo { +pub(crate) struct ResolutionInfo { item_id: DefId, module_id: DefId, dis: Option, path_str: Box, extra_fragment: Option, + link_range: MarkdownLinkRange, } -#[derive(Clone)] -struct DiagnosticInfo<'a> { +#[derive(Clone, Debug)] +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, + dox: String, + ori_link: String, + link_range: MarkdownLinkRange, +} + +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(), + } + } +} + +impl OwnedDiagnosticInfo { + pub(crate) fn into_info(&self) -> DiagnosticInfo<'_> { + DiagnosticInfo { + item: &self.item, + ori_link: &self.ori_link, + dox: &self.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), Vec>, } impl<'a, 'tcx> LinkCollector<'a, 'tcx> { @@ -993,14 +1048,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,13 +1082,14 @@ 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, 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 @@ -1040,6 +1099,142 @@ 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 + .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) + } 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() + } + + #[allow(rustc::potential_query_instability)] + pub(crate) fn resolve_ambiguities(&mut self) { + let mut ambiguous_links = mem::take(&mut self.ambiguous_links); + + 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); + } + } + } + } + } + + 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 +1250,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 +1280,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 +1302,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, @@ -1216,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<(Res, Option)> { + ) -> Option)>> { if let Some(res) = self.visited_links.get(&key) { - if res.is_some() || cache_errors { - return res.clone(); - } + return res.clone(); } let mut candidates = self.resolve_with_disambiguator(&key, diag.clone(), recoverable); @@ -1249,12 +1442,13 @@ 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 { let fragment = match (&key.extra_fragment, def_id) { (Some(_), Some(def_id)) => { report_anchor_conflict(self.cx, diag, def_id); @@ -1264,15 +1458,16 @@ impl LinkCollector<'_, '_> { (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; + resolved.push((res, fragment)); } - if 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) } - None } /// After parsing the disambiguator, resolve the main part of the link. @@ -1429,7 +1624,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 +2241,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 +2258,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/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/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 + 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 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) {}