diff --git a/src/librustdoc/html/format.rs b/src/librustdoc/html/format.rs
index 493fdc6fb1b33..92a7bedb60d04 100644
--- a/src/librustdoc/html/format.rs
+++ b/src/librustdoc/html/format.rs
@@ -10,6 +10,7 @@
use std::cmp::Ordering;
use std::fmt::{self, Display, Write};
use std::iter::{self, once};
+use std::ops::ControlFlow;
use std::slice;
use itertools::{Either, Itertools};
@@ -21,7 +22,7 @@ use rustc_hir::def::DefKind;
use rustc_hir::def_id::{DefId, LOCAL_CRATE};
use rustc_hir::{ConstStability, StabilityLevel, StableSince};
use rustc_metadata::creader::{CStore, LoadedMacro};
-use rustc_middle::ty::{self, TyCtxt, TypingMode};
+use rustc_middle::ty::{self, ImplSubject, TyCtxt, TypingMode};
use rustc_span::symbol::kw;
use rustc_span::{Symbol, sym};
use tracing::{debug, trace};
@@ -483,22 +484,80 @@ fn generate_item_def_id_path(
/// Checks if the given defid refers to an item that is unnamable, such as one defined in a const block.
fn is_unnamable(tcx: TyCtxt<'_>, did: DefId) -> bool {
+ traverse_parent(tcx, did, false, true, &|_| ControlFlow::Continue(()))
+}
+
+pub(crate) fn traverse_parent(
+ tcx: TyCtxt<'_>,
+ did: DefId,
+ if_noparent: bool,
+ if_unnamable: bool,
+ callback: &dyn Fn(DefId) -> ControlFlow,
+) -> bool {
let mut cur_did = did;
while let Some(parent) = tcx.opt_parent(cur_did) {
+ if let ControlFlow::Break(x) = callback(cur_did) {
+ return x;
+ }
match tcx.def_kind(parent) {
// items defined in these can be linked to, as long as they are visible
- DefKind::Mod | DefKind::ForeignMod => cur_did = parent,
- // items in impls can be linked to,
- // as long as we can link to the item the impl is on.
- // since associated traits are not a thing,
- // it should not be possible to refer to an impl item if
- // the base type is not namable.
- DefKind::Impl { .. } => return false,
+ DefKind::Mod | DefKind::ForeignMod | DefKind::Trait => cur_did = parent,
+ // this serves both to future-proof is_unnamable for associated trait aliases,
+ // and traverses upwards to see if the trait or type is doc(hidden)
+ DefKind::Impl { .. } => {
+ match tcx.impl_subject(parent).as_ref().skip_binder() {
+ ImplSubject::Trait(trait_ref) => {
+ // first, check if the trait fufills the criteria
+ if traverse_parent(
+ tcx,
+ trait_ref.def_id,
+ if_noparent,
+ if_unnamable,
+ callback,
+ ) {
+ return true;
+ }
+
+ // for trait impls on local types, we also check if the type matches the criteria
+ if let Some(hir::Node::Item(hir::Item {
+ kind: hir::ItemKind::Impl(hir::Impl { self_ty, .. }),
+ ..
+ })) = tcx.hir_get_if_local(parent)
+ && let hir::Ty {
+ kind:
+ hir::TyKind::Path(hir::QPath::Resolved(
+ _,
+ hir::Path {
+ res: hir::def::Res::Def(_, self_ty_def_id),
+ ..
+ },
+ )),
+ ..
+ } = self_ty.peel_refs()
+ /*&& let Some(local_def_id) = parent.as_local()
+ && let Some(self_ty_def_id) = tcx.typeck(local_def_id).type_dependent_def_id(self_ty.hir_id)*/
+ {
+ cur_did = *self_ty_def_id;
+ continue;
+ }
+ return if_noparent;
+ }
+ ImplSubject::Inherent(ty) => {
+ if let Some(def_id) =
+ rustc_middle::ty::print::characteristic_def_id_of_type(ty.peel_refs())
+ {
+ cur_did = def_id;
+ } else {
+ return if_noparent;
+ }
+ }
+ }
+ }
// everything else does not have docs generated for it
- _ => return true,
+ _ => return if_unnamable,
}
}
- return false;
+ return if_noparent;
}
fn to_module_fqp(shortty: ItemType, fqp: &[Symbol]) -> &[Symbol] {
diff --git a/src/librustdoc/lint.rs b/src/librustdoc/lint.rs
index b09ea05688595..a2e70c725d811 100644
--- a/src/librustdoc/lint.rs
+++ b/src/librustdoc/lint.rs
@@ -97,6 +97,17 @@ declare_rustdoc_lint! {
"linking from a public item to a private one"
}
+declare_rustdoc_lint! {
+ /// This is a subset of `broken_intra_doc_links` that warns when linking from
+ /// a non-hidden item to a hidden one. This is a `rustdoc` only lint, see the
+ /// documentation in the [rustdoc book].
+ ///
+ /// [rustdoc book]: ../../../rustdoc/lints.html#hidden_intra_doc_links
+ HIDDEN_INTRA_DOC_LINKS,
+ Warn,
+ "linking from a non-hidden item to a hidden one"
+}
+
declare_rustdoc_lint! {
/// The `invalid_codeblock_attributes` lint detects code block attributes
/// in documentation examples that have potentially mis-typed values. This
@@ -199,6 +210,7 @@ declare_rustdoc_lint! {
pub(crate) static RUSTDOC_LINTS: Lazy> = Lazy::new(|| {
vec![
BROKEN_INTRA_DOC_LINKS,
+ HIDDEN_INTRA_DOC_LINKS,
PRIVATE_INTRA_DOC_LINKS,
MISSING_DOC_CODE_EXAMPLES,
PRIVATE_DOC_TESTS,
diff --git a/src/librustdoc/passes/collect_intra_doc_links.rs b/src/librustdoc/passes/collect_intra_doc_links.rs
index 5a9aa2a94c8b4..8371fb39011fd 100644
--- a/src/librustdoc/passes/collect_intra_doc_links.rs
+++ b/src/librustdoc/passes/collect_intra_doc_links.rs
@@ -5,7 +5,7 @@
use std::borrow::Cow;
use std::fmt::Display;
use std::mem;
-use std::ops::Range;
+use std::ops::{ControlFlow, Range};
use pulldown_cmark::LinkType;
use rustc_ast::util::comments::may_have_doc_links;
@@ -34,7 +34,7 @@ use crate::clean::utils::find_nearest_parent_module;
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::lint::{BROKEN_INTRA_DOC_LINKS, HIDDEN_INTRA_DOC_LINKS, PRIVATE_INTRA_DOC_LINKS};
use crate::passes::Pass;
use crate::visit::DocVisitor;
@@ -1341,13 +1341,19 @@ impl LinkCollector<'_, '_> {
}
}
+ let src_def_id = diag_info.item.item_id.expect_def_id();
// item can be non-local e.g. when using `#[rustc_doc_primitive = "pointer"]`
if let Some(dst_id) = id.as_local()
- && let Some(src_id) = diag_info.item.item_id.expect_def_id().as_local()
+ && let Some(src_id) = src_def_id.as_local()
&& self.cx.tcx.effective_visibilities(()).is_exported(src_id)
&& !self.cx.tcx.effective_visibilities(()).is_exported(dst_id)
{
- privacy_error(self.cx, diag_info, path_str);
+ privacy_error(self.cx, diag_info, path_str, PrivacyErrorKind::Private);
+ }
+
+ if is_effectively_hidden(self.cx.tcx, id) && !is_effectively_hidden(self.cx.tcx, src_def_id)
+ {
+ privacy_error(self.cx, diag_info, path_str, PrivacyErrorKind::Hidden);
}
Some(())
@@ -1591,6 +1597,13 @@ fn range_between_backticks(ori_link_range: &MarkdownLinkRange, dox: &str) -> Mar
)
}
+/// Is this item or any of its ancestors doc(hidden)?
+fn is_effectively_hidden(tcx: TyCtxt<'_>, id: DefId) -> bool {
+ crate::html::format::traverse_parent(tcx, id, false, false, &|id| {
+ if tcx.is_doc_hidden(id) { ControlFlow::Break(true) } else { ControlFlow::Continue(()) }
+ })
+}
+
/// Returns true if we should ignore `link` due to it being unlikely
/// that it is an intra-doc link. `link` should still have disambiguators
/// if there were any.
@@ -2334,8 +2347,20 @@ fn suggest_disambiguator(
}
}
+#[derive(Copy, Clone)]
+enum PrivacyErrorKind {
+ Private,
+ Hidden,
+}
+
/// Report a link from a public item to a private one.
-fn privacy_error(cx: &DocContext<'_>, diag_info: &DiagnosticInfo<'_>, path_str: &str) {
+fn privacy_error(
+ cx: &DocContext<'_>,
+ diag_info: &DiagnosticInfo<'_>,
+ path_str: &str,
+ kind: PrivacyErrorKind,
+) {
+ use PrivacyErrorKind::*;
let sym;
let item_name = match diag_info.item.name {
Some(name) => {
@@ -2344,17 +2369,22 @@ fn privacy_error(cx: &DocContext<'_>, diag_info: &DiagnosticInfo<'_>, path_str:
}
None => "",
};
- let msg = format!("public documentation for `{item_name}` links to private item `{path_str}`");
+ let (public, private, flag, lint) = match kind {
+ Private => ("public", "private", "--document-private-items", PRIVATE_INTRA_DOC_LINKS),
+ Hidden => ("non-hidden", "hidden", "--document-hidden-items", HIDDEN_INTRA_DOC_LINKS),
+ };
+ let msg =
+ format!("{public} documentation for `{item_name}` links to {private} item `{path_str}`");
- report_diagnostic(cx.tcx, PRIVATE_INTRA_DOC_LINKS, msg, diag_info, |diag, sp, _link_range| {
+ report_diagnostic(cx.tcx, lint, msg, diag_info, |diag, sp, _link_range| {
if let Some(sp) = sp {
- diag.span_label(sp, "this item is private");
+ diag.span_label(sp, format!("this item is {private}"));
}
let note_msg = if cx.render_options.document_private {
- "this link resolves only because you passed `--document-private-items`, but will break without"
+ format!("this link resolves only because you passed `{flag}`, but will break without")
} else {
- "this link will resolve properly if you pass `--document-private-items`"
+ format!("this link will resolve properly if you pass `{flag}`")
};
diag.note(note_msg);
});
diff --git a/tests/rustdoc-ui/intra-doc/auxiliary/hidden.rs b/tests/rustdoc-ui/intra-doc/auxiliary/hidden.rs
new file mode 100644
index 0000000000000..3deefa7a294f7
--- /dev/null
+++ b/tests/rustdoc-ui/intra-doc/auxiliary/hidden.rs
@@ -0,0 +1,9 @@
+#[doc(hidden)]
+pub fn public_hidden_fn() {}
+
+#[doc(hidden)]
+fn private_hidden_fn() {}
+
+pub fn public_non_hidden_fn() {}
+
+fn private_non_hidden_fn() {}
diff --git a/tests/rustdoc-ui/intra-doc/link-to-hidden-144664.doc-both.stderr b/tests/rustdoc-ui/intra-doc/link-to-hidden-144664.doc-both.stderr
new file mode 100644
index 0000000000000..1d0b7a98cb9d1
--- /dev/null
+++ b/tests/rustdoc-ui/intra-doc/link-to-hidden-144664.doc-both.stderr
@@ -0,0 +1,92 @@
+error: non-hidden documentation for `link_to_hidden_144664` links to hidden item `crate::local_public_hidden_fn`
+ --> $DIR/link-to-hidden-144664.rs:14:8
+ |
+LL | //! * [crate::local_public_hidden_fn]
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ this item is hidden
+ |
+ = note: this link resolves only because you passed `--document-hidden-items`, but will break without
+note: the lint level is defined here
+ --> $DIR/link-to-hidden-144664.rs:10:9
+ |
+LL | #![deny(rustdoc::hidden_intra_doc_links)]
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+error: public documentation for `link_to_hidden_144664` links to private item `crate::local_private_hidden_fn`
+ --> $DIR/link-to-hidden-144664.rs:18:8
+ |
+LL | //! * [crate::local_private_hidden_fn]
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ this item is private
+ |
+ = note: this link resolves only because you passed `--document-private-items`, but will break without
+note: the lint level is defined here
+ --> $DIR/link-to-hidden-144664.rs:9:9
+ |
+LL | #![deny(rustdoc::private_intra_doc_links)]
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+error: non-hidden documentation for `link_to_hidden_144664` links to hidden item `crate::local_private_hidden_fn`
+ --> $DIR/link-to-hidden-144664.rs:18:8
+ |
+LL | //! * [crate::local_private_hidden_fn]
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ this item is hidden
+ |
+ = note: this link resolves only because you passed `--document-hidden-items`, but will break without
+
+error: public documentation for `link_to_hidden_144664` links to private item `crate::local_private_non_hidden_fn`
+ --> $DIR/link-to-hidden-144664.rs:22:8
+ |
+LL | //! * [crate::local_private_non_hidden_fn]
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ this item is private
+ |
+ = note: this link resolves only because you passed `--document-private-items`, but will break without
+
+error: non-hidden documentation for `link_to_hidden_144664` links to hidden item `nonlocal::public_hidden_fn`
+ --> $DIR/link-to-hidden-144664.rs:24:8
+ |
+LL | //! * [nonlocal::public_hidden_fn]
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^ this item is hidden
+ |
+ = note: this link resolves only because you passed `--document-hidden-items`, but will break without
+
+error: unresolved link to `nonlocal::private_hidden_fn`
+ --> $DIR/link-to-hidden-144664.rs:28:8
+ |
+LL | //! * [nonlocal::private_hidden_fn]
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ no item named `private_hidden_fn` in module `hidden`
+ |
+note: the lint level is defined here
+ --> $DIR/link-to-hidden-144664.rs:11:9
+ |
+LL | #![deny(rustdoc::broken_intra_doc_links)]
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+error: unresolved link to `nonlocal::private_non_hidden_fn`
+ --> $DIR/link-to-hidden-144664.rs:30:8
+ |
+LL | //! * [nonlocal::private_non_hidden_fn]
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ no item named `private_non_hidden_fn` in module `hidden`
+
+error: unresolved link to `crate::hidden_mod::a`
+ --> $DIR/link-to-hidden-144664.rs:32:8
+ |
+LL | //! * [crate::hidden_mod::a]
+ | ^^^^^^^^^^^^^^^^^^^^ no item named `a` in module `hidden_mod`
+
+error: non-hidden documentation for `link_to_hidden_144664` links to hidden item `crate::hidden_mod::b`
+ --> $DIR/link-to-hidden-144664.rs:34:8
+ |
+LL | //! * [crate::hidden_mod::b]
+ | ^^^^^^^^^^^^^^^^^^^^ this item is hidden
+ |
+ = note: this link resolves only because you passed `--document-hidden-items`, but will break without
+
+error: non-hidden documentation for `link_to_hidden_144664` links to hidden item `crate::hidden_mod::c`
+ --> $DIR/link-to-hidden-144664.rs:36:8
+ |
+LL | //! * [crate::hidden_mod::c]
+ | ^^^^^^^^^^^^^^^^^^^^ this item is hidden
+ |
+ = note: this link resolves only because you passed `--document-hidden-items`, but will break without
+
+error: aborting due to 10 previous errors
+
diff --git a/tests/rustdoc-ui/intra-doc/link-to-hidden-144664.doc-hidden.stderr b/tests/rustdoc-ui/intra-doc/link-to-hidden-144664.doc-hidden.stderr
new file mode 100644
index 0000000000000..c3f699b61ac2b
--- /dev/null
+++ b/tests/rustdoc-ui/intra-doc/link-to-hidden-144664.doc-hidden.stderr
@@ -0,0 +1,92 @@
+error: non-hidden documentation for `link_to_hidden_144664` links to hidden item `crate::local_public_hidden_fn`
+ --> $DIR/link-to-hidden-144664.rs:14:8
+ |
+LL | //! * [crate::local_public_hidden_fn]
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ this item is hidden
+ |
+ = note: this link will resolve properly if you pass `--document-hidden-items`
+note: the lint level is defined here
+ --> $DIR/link-to-hidden-144664.rs:10:9
+ |
+LL | #![deny(rustdoc::hidden_intra_doc_links)]
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+error: public documentation for `link_to_hidden_144664` links to private item `crate::local_private_hidden_fn`
+ --> $DIR/link-to-hidden-144664.rs:18:8
+ |
+LL | //! * [crate::local_private_hidden_fn]
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ this item is private
+ |
+ = note: this link will resolve properly if you pass `--document-private-items`
+note: the lint level is defined here
+ --> $DIR/link-to-hidden-144664.rs:9:9
+ |
+LL | #![deny(rustdoc::private_intra_doc_links)]
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+error: non-hidden documentation for `link_to_hidden_144664` links to hidden item `crate::local_private_hidden_fn`
+ --> $DIR/link-to-hidden-144664.rs:18:8
+ |
+LL | //! * [crate::local_private_hidden_fn]
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ this item is hidden
+ |
+ = note: this link will resolve properly if you pass `--document-hidden-items`
+
+error: public documentation for `link_to_hidden_144664` links to private item `crate::local_private_non_hidden_fn`
+ --> $DIR/link-to-hidden-144664.rs:22:8
+ |
+LL | //! * [crate::local_private_non_hidden_fn]
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ this item is private
+ |
+ = note: this link will resolve properly if you pass `--document-private-items`
+
+error: non-hidden documentation for `link_to_hidden_144664` links to hidden item `nonlocal::public_hidden_fn`
+ --> $DIR/link-to-hidden-144664.rs:24:8
+ |
+LL | //! * [nonlocal::public_hidden_fn]
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^ this item is hidden
+ |
+ = note: this link will resolve properly if you pass `--document-hidden-items`
+
+error: unresolved link to `nonlocal::private_hidden_fn`
+ --> $DIR/link-to-hidden-144664.rs:28:8
+ |
+LL | //! * [nonlocal::private_hidden_fn]
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ no item named `private_hidden_fn` in module `hidden`
+ |
+note: the lint level is defined here
+ --> $DIR/link-to-hidden-144664.rs:11:9
+ |
+LL | #![deny(rustdoc::broken_intra_doc_links)]
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+error: unresolved link to `nonlocal::private_non_hidden_fn`
+ --> $DIR/link-to-hidden-144664.rs:30:8
+ |
+LL | //! * [nonlocal::private_non_hidden_fn]
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ no item named `private_non_hidden_fn` in module `hidden`
+
+error: unresolved link to `crate::hidden_mod::a`
+ --> $DIR/link-to-hidden-144664.rs:32:8
+ |
+LL | //! * [crate::hidden_mod::a]
+ | ^^^^^^^^^^^^^^^^^^^^ no item named `a` in module `hidden_mod`
+
+error: non-hidden documentation for `link_to_hidden_144664` links to hidden item `crate::hidden_mod::b`
+ --> $DIR/link-to-hidden-144664.rs:34:8
+ |
+LL | //! * [crate::hidden_mod::b]
+ | ^^^^^^^^^^^^^^^^^^^^ this item is hidden
+ |
+ = note: this link will resolve properly if you pass `--document-hidden-items`
+
+error: non-hidden documentation for `link_to_hidden_144664` links to hidden item `crate::hidden_mod::c`
+ --> $DIR/link-to-hidden-144664.rs:36:8
+ |
+LL | //! * [crate::hidden_mod::c]
+ | ^^^^^^^^^^^^^^^^^^^^ this item is hidden
+ |
+ = note: this link will resolve properly if you pass `--document-hidden-items`
+
+error: aborting due to 10 previous errors
+
diff --git a/tests/rustdoc-ui/intra-doc/link-to-hidden-144664.doc-priv.stderr b/tests/rustdoc-ui/intra-doc/link-to-hidden-144664.doc-priv.stderr
new file mode 100644
index 0000000000000..1d0b7a98cb9d1
--- /dev/null
+++ b/tests/rustdoc-ui/intra-doc/link-to-hidden-144664.doc-priv.stderr
@@ -0,0 +1,92 @@
+error: non-hidden documentation for `link_to_hidden_144664` links to hidden item `crate::local_public_hidden_fn`
+ --> $DIR/link-to-hidden-144664.rs:14:8
+ |
+LL | //! * [crate::local_public_hidden_fn]
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ this item is hidden
+ |
+ = note: this link resolves only because you passed `--document-hidden-items`, but will break without
+note: the lint level is defined here
+ --> $DIR/link-to-hidden-144664.rs:10:9
+ |
+LL | #![deny(rustdoc::hidden_intra_doc_links)]
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+error: public documentation for `link_to_hidden_144664` links to private item `crate::local_private_hidden_fn`
+ --> $DIR/link-to-hidden-144664.rs:18:8
+ |
+LL | //! * [crate::local_private_hidden_fn]
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ this item is private
+ |
+ = note: this link resolves only because you passed `--document-private-items`, but will break without
+note: the lint level is defined here
+ --> $DIR/link-to-hidden-144664.rs:9:9
+ |
+LL | #![deny(rustdoc::private_intra_doc_links)]
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+error: non-hidden documentation for `link_to_hidden_144664` links to hidden item `crate::local_private_hidden_fn`
+ --> $DIR/link-to-hidden-144664.rs:18:8
+ |
+LL | //! * [crate::local_private_hidden_fn]
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ this item is hidden
+ |
+ = note: this link resolves only because you passed `--document-hidden-items`, but will break without
+
+error: public documentation for `link_to_hidden_144664` links to private item `crate::local_private_non_hidden_fn`
+ --> $DIR/link-to-hidden-144664.rs:22:8
+ |
+LL | //! * [crate::local_private_non_hidden_fn]
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ this item is private
+ |
+ = note: this link resolves only because you passed `--document-private-items`, but will break without
+
+error: non-hidden documentation for `link_to_hidden_144664` links to hidden item `nonlocal::public_hidden_fn`
+ --> $DIR/link-to-hidden-144664.rs:24:8
+ |
+LL | //! * [nonlocal::public_hidden_fn]
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^ this item is hidden
+ |
+ = note: this link resolves only because you passed `--document-hidden-items`, but will break without
+
+error: unresolved link to `nonlocal::private_hidden_fn`
+ --> $DIR/link-to-hidden-144664.rs:28:8
+ |
+LL | //! * [nonlocal::private_hidden_fn]
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ no item named `private_hidden_fn` in module `hidden`
+ |
+note: the lint level is defined here
+ --> $DIR/link-to-hidden-144664.rs:11:9
+ |
+LL | #![deny(rustdoc::broken_intra_doc_links)]
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+error: unresolved link to `nonlocal::private_non_hidden_fn`
+ --> $DIR/link-to-hidden-144664.rs:30:8
+ |
+LL | //! * [nonlocal::private_non_hidden_fn]
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ no item named `private_non_hidden_fn` in module `hidden`
+
+error: unresolved link to `crate::hidden_mod::a`
+ --> $DIR/link-to-hidden-144664.rs:32:8
+ |
+LL | //! * [crate::hidden_mod::a]
+ | ^^^^^^^^^^^^^^^^^^^^ no item named `a` in module `hidden_mod`
+
+error: non-hidden documentation for `link_to_hidden_144664` links to hidden item `crate::hidden_mod::b`
+ --> $DIR/link-to-hidden-144664.rs:34:8
+ |
+LL | //! * [crate::hidden_mod::b]
+ | ^^^^^^^^^^^^^^^^^^^^ this item is hidden
+ |
+ = note: this link resolves only because you passed `--document-hidden-items`, but will break without
+
+error: non-hidden documentation for `link_to_hidden_144664` links to hidden item `crate::hidden_mod::c`
+ --> $DIR/link-to-hidden-144664.rs:36:8
+ |
+LL | //! * [crate::hidden_mod::c]
+ | ^^^^^^^^^^^^^^^^^^^^ this item is hidden
+ |
+ = note: this link resolves only because you passed `--document-hidden-items`, but will break without
+
+error: aborting due to 10 previous errors
+
diff --git a/tests/rustdoc-ui/intra-doc/link-to-hidden-144664.noflags.stderr b/tests/rustdoc-ui/intra-doc/link-to-hidden-144664.noflags.stderr
new file mode 100644
index 0000000000000..c3f699b61ac2b
--- /dev/null
+++ b/tests/rustdoc-ui/intra-doc/link-to-hidden-144664.noflags.stderr
@@ -0,0 +1,92 @@
+error: non-hidden documentation for `link_to_hidden_144664` links to hidden item `crate::local_public_hidden_fn`
+ --> $DIR/link-to-hidden-144664.rs:14:8
+ |
+LL | //! * [crate::local_public_hidden_fn]
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ this item is hidden
+ |
+ = note: this link will resolve properly if you pass `--document-hidden-items`
+note: the lint level is defined here
+ --> $DIR/link-to-hidden-144664.rs:10:9
+ |
+LL | #![deny(rustdoc::hidden_intra_doc_links)]
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+error: public documentation for `link_to_hidden_144664` links to private item `crate::local_private_hidden_fn`
+ --> $DIR/link-to-hidden-144664.rs:18:8
+ |
+LL | //! * [crate::local_private_hidden_fn]
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ this item is private
+ |
+ = note: this link will resolve properly if you pass `--document-private-items`
+note: the lint level is defined here
+ --> $DIR/link-to-hidden-144664.rs:9:9
+ |
+LL | #![deny(rustdoc::private_intra_doc_links)]
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+error: non-hidden documentation for `link_to_hidden_144664` links to hidden item `crate::local_private_hidden_fn`
+ --> $DIR/link-to-hidden-144664.rs:18:8
+ |
+LL | //! * [crate::local_private_hidden_fn]
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ this item is hidden
+ |
+ = note: this link will resolve properly if you pass `--document-hidden-items`
+
+error: public documentation for `link_to_hidden_144664` links to private item `crate::local_private_non_hidden_fn`
+ --> $DIR/link-to-hidden-144664.rs:22:8
+ |
+LL | //! * [crate::local_private_non_hidden_fn]
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ this item is private
+ |
+ = note: this link will resolve properly if you pass `--document-private-items`
+
+error: non-hidden documentation for `link_to_hidden_144664` links to hidden item `nonlocal::public_hidden_fn`
+ --> $DIR/link-to-hidden-144664.rs:24:8
+ |
+LL | //! * [nonlocal::public_hidden_fn]
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^ this item is hidden
+ |
+ = note: this link will resolve properly if you pass `--document-hidden-items`
+
+error: unresolved link to `nonlocal::private_hidden_fn`
+ --> $DIR/link-to-hidden-144664.rs:28:8
+ |
+LL | //! * [nonlocal::private_hidden_fn]
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ no item named `private_hidden_fn` in module `hidden`
+ |
+note: the lint level is defined here
+ --> $DIR/link-to-hidden-144664.rs:11:9
+ |
+LL | #![deny(rustdoc::broken_intra_doc_links)]
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+error: unresolved link to `nonlocal::private_non_hidden_fn`
+ --> $DIR/link-to-hidden-144664.rs:30:8
+ |
+LL | //! * [nonlocal::private_non_hidden_fn]
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ no item named `private_non_hidden_fn` in module `hidden`
+
+error: unresolved link to `crate::hidden_mod::a`
+ --> $DIR/link-to-hidden-144664.rs:32:8
+ |
+LL | //! * [crate::hidden_mod::a]
+ | ^^^^^^^^^^^^^^^^^^^^ no item named `a` in module `hidden_mod`
+
+error: non-hidden documentation for `link_to_hidden_144664` links to hidden item `crate::hidden_mod::b`
+ --> $DIR/link-to-hidden-144664.rs:34:8
+ |
+LL | //! * [crate::hidden_mod::b]
+ | ^^^^^^^^^^^^^^^^^^^^ this item is hidden
+ |
+ = note: this link will resolve properly if you pass `--document-hidden-items`
+
+error: non-hidden documentation for `link_to_hidden_144664` links to hidden item `crate::hidden_mod::c`
+ --> $DIR/link-to-hidden-144664.rs:36:8
+ |
+LL | //! * [crate::hidden_mod::c]
+ | ^^^^^^^^^^^^^^^^^^^^ this item is hidden
+ |
+ = note: this link will resolve properly if you pass `--document-hidden-items`
+
+error: aborting due to 10 previous errors
+
diff --git a/tests/rustdoc-ui/intra-doc/link-to-hidden-144664.rs b/tests/rustdoc-ui/intra-doc/link-to-hidden-144664.rs
new file mode 100644
index 0000000000000..4917d27caf9cd
--- /dev/null
+++ b/tests/rustdoc-ui/intra-doc/link-to-hidden-144664.rs
@@ -0,0 +1,82 @@
+// ignore-tidy-linelength
+//@ revisions: noflags doc-priv doc-hidden doc-both
+//@ aux-build: hidden.rs
+//@ build-aux-docs
+//@[doc-priv]compile-flags: --document-private-items
+//@[doc-hidden]compile-flags: -Z unstable-options --document-hidden-items
+//@[doc-both]compile-flags: -Z unstable-options --document-hidden-items --document-private-items
+
+#![deny(rustdoc::private_intra_doc_links)]
+#![deny(rustdoc::hidden_intra_doc_links)]
+#![deny(rustdoc::broken_intra_doc_links)]
+
+//! All these links are broken and should produce warnings:
+//! * [crate::local_public_hidden_fn]
+//~^ ERROR: non-hidden documentation for `link_to_hidden_144664` links to hidden item `crate::local_public_hidden_fn`
+
+// for local items that are hidden and private, both lints are emmitted
+//! * [crate::local_private_hidden_fn]
+//~^ ERROR: public documentation for `link_to_hidden_144664` links to private item `crate::local_private_hidden_fn`
+//~| ERROR: non-hidden documentation for `link_to_hidden_144664` links to hidden item `crate::local_private_hidden_fn`
+
+//! * [crate::local_private_non_hidden_fn]
+//~^ ERROR: public documentation for `link_to_hidden_144664` links to private item `crate::local_private_non_hidden_fn`
+//! * [nonlocal::public_hidden_fn]
+//~^ ERROR: non-hidden documentation for `link_to_hidden_144664` links to hidden item `nonlocal::public_hidden_fn`
+
+// for cross-crate private items, rustdoc doesn't even know if they exist, so we get a generic error.
+//! * [nonlocal::private_hidden_fn]
+//~^ ERROR: unresolved link
+//! * [nonlocal::private_non_hidden_fn]
+//~^ ERROR: unresolved link
+//! * [crate::hidden_mod::a]
+//~^ ERROR: unresolved link
+//! * [crate::hidden_mod::b]
+//~^ ERROR: non-hidden documentation for `link_to_hidden_144664` links to hidden item `crate::hidden_mod::b`
+//! * [crate::hidden_mod::c]
+//~^ ERROR: non-hidden documentation for `link_to_hidden_144664` links to hidden item `crate::hidden_mod::c`
+extern crate hidden as nonlocal;
+
+#[doc(hidden)] pub fn local_public_hidden_fn() {}
+/// [Private hidden to public hidden is fine](crate::local_public_hidden_fn)
+#[doc(hidden)] fn local_private_hidden_fn() {}
+pub fn local_public_non_hidden_fn() {}
+fn local_private_non_hidden_fn() {}
+
+pub struct PublicStruct;
+
+#[doc(hidden)]
+pub mod hidden_mod {
+ /// [This link is fine, both are indirectly hidden](self::b)
+ fn a() {}
+ /// [This link also fine, both are hidden, doesn't matter that only one is indirect](self::c)
+ pub fn b() {}
+ /// [Hidden docs can link to non-hidden](crate::local_public_non_hidden_fn)
+ #[doc(hidden)]
+ pub fn c() {}
+
+ #[doc(hidden)]
+ pub struct HiddenStruct;
+
+
+ impl crate::PublicNonHiddenTrait for HiddenStruct {
+ /// This method is actually hidden,
+ /// as the struct is hidden and thus it will not show up in docs.
+ /// [This link is ok](self::c)
+ fn non_hidden_trait_method(&self) {}
+ }
+
+ pub trait PublicHiddenTrait {
+ fn hidden_trait_method(&self);
+ }
+}
+
+pub trait PublicNonHiddenTrait {
+ fn non_hidden_trait_method(&self);
+}
+
+impl crate::hidden_mod::PublicHiddenTrait for PublicStruct {
+ /// This method is hidden as the trait is hidden.
+ /// [This link is ok](crate::local_public_hidden_fn)
+ fn hidden_trait_method(&self) {}
+}