Skip to content

Commit 2ef5570

Browse files
committed
Intra-doc links side of warning about undocumented items
Unfortunately, this currently breaks because the cache is not populated until later, so rustdoc thinks some items won't be documented when they will be. - Track why links are missing - Populate cache before running pass
1 parent 8105968 commit 2ef5570

File tree

7 files changed

+178
-35
lines changed

7 files changed

+178
-35
lines changed

src/librustdoc/config.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -339,14 +339,14 @@ impl Options {
339339
println!("{:>20} - {}", pass.name, pass.description);
340340
}
341341
println!("\nDefault passes for rustdoc:");
342-
for p in passes::DEFAULT_PASSES {
342+
for &p in passes::DEFAULT_PASSES.0.iter().chain(passes::DEFAULT_PASSES.1) {
343343
print!("{:>20}", p.pass.name);
344344
println_condition(p.condition);
345345
}
346346

347347
if nightly_options::match_is_nightly_build(matches) {
348348
println!("\nPasses run with `--show-coverage`:");
349-
for p in passes::COVERAGE_PASSES {
349+
for &p in passes::COVERAGE_PASSES.0.iter().chain(passes::COVERAGE_PASSES.1) {
350350
print!("{:>20}", p.pass.name);
351351
println_condition(p.condition);
352352
}

src/librustdoc/core.rs

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,10 @@ use std::mem;
2525
use std::rc::Rc;
2626

2727
use crate::clean::inline::build_external_trait;
28-
use crate::clean::{self, ItemId, TraitWithExtraInfo};
28+
use crate::clean::{self, ItemId, TraitWithExtraInfo, Crate};
2929
use crate::config::{Options as RustdocOptions, OutputFormat, RenderOptions};
3030
use crate::formats::cache::Cache;
31-
use crate::passes::{self, Condition::*};
31+
use crate::passes::{self, Condition::*, ConditionalPass};
3232

3333
crate use rustc_session::config::{DebuggingOptions, Input, Options};
3434

@@ -438,8 +438,7 @@ crate fn run_global_ctxt(
438438
}
439439

440440
info!("Executing passes");
441-
442-
for p in passes::defaults(show_coverage) {
441+
let run_pass = |p: ConditionalPass, krate: Crate, ctxt: &mut DocContext<'_>| {
443442
let run = match p.condition {
444443
Always => true,
445444
WhenDocumentPrivate => ctxt.render_options.document_private,
@@ -448,15 +447,23 @@ crate fn run_global_ctxt(
448447
};
449448
if run {
450449
debug!("running pass {}", p.pass.name);
451-
krate = tcx.sess.time(p.pass.name, || (p.pass.run)(krate, &mut ctxt));
450+
tcx.sess.time(p.pass.name, || (p.pass.run)(krate, ctxt))
451+
} else {
452+
krate
452453
}
453-
}
454+
};
454455

455-
if tcx.sess.diagnostic().has_errors_or_lint_errors().is_some() {
456-
rustc_errors::FatalError.raise();
456+
let (passes_before_cache, passes_after_cache) = passes::defaults(show_coverage);
457+
for &p in passes_before_cache {
458+
krate = run_pass(p, krate, &mut ctxt);
457459
}
460+
ctxt.sess().abort_if_errors();
458461

459462
krate = tcx.sess.time("create_format_cache", || Cache::populate(&mut ctxt, krate));
463+
for &p in passes_after_cache {
464+
krate = run_pass(p, krate, &mut ctxt);
465+
}
466+
ctxt.sess().abort_if_errors();
460467

461468
(krate, ctxt.render_options, ctxt.cache)
462469
}

src/librustdoc/html/format.rs

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ use crate::clean::{
2626
self, types::ExternalLocation, utils::find_nearest_parent_module, ExternalCrate, ItemId,
2727
PrimitiveType,
2828
};
29+
use crate::formats::cache::Cache;
2930
use crate::formats::item_type::ItemType;
3031
use crate::html::escape::Escape;
3132
use crate::html::render::Context;
@@ -523,6 +524,7 @@ impl clean::GenericArgs {
523524
}
524525

525526
// Possible errors when computing href link source for a `DefId`
527+
#[derive(Debug, PartialEq, Eq)]
526528
crate enum HrefError {
527529
/// This item is known to rustdoc, but from a crate that does not have documentation generated.
528530
///
@@ -551,6 +553,18 @@ crate fn href_with_root_path(
551553
root_path: Option<&str>,
552554
) -> Result<(String, ItemType, Vec<Symbol>), HrefError> {
553555
let tcx = cx.tcx();
556+
let cache = cx.cache();
557+
let relative_to = &cx.current;
558+
href_inner(did, tcx, cache, root_path, relative_to)
559+
}
560+
561+
crate fn href_inner(
562+
did: DefId,
563+
tcx: TyCtxt<'_>,
564+
cache: &Cache,
565+
root_path: Option<&str>,
566+
relative_to: &[Symbol],
567+
) -> Result<(String, ItemType, Vec<Symbol>), HrefError> {
554568
let def_kind = tcx.def_kind(did);
555569
let did = match def_kind {
556570
DefKind::AssocTy | DefKind::AssocFn | DefKind::AssocConst | DefKind::Variant => {
@@ -559,12 +573,11 @@ crate fn href_with_root_path(
559573
}
560574
_ => did,
561575
};
562-
let cache = cx.cache();
563-
let relative_to = &cx.current;
564576
fn to_module_fqp(shortty: ItemType, fqp: &[Symbol]) -> &[Symbol] {
565577
if shortty == ItemType::Module { fqp } else { &fqp[..fqp.len() - 1] }
566578
}
567579

580+
trace!("calculating href for {:?}", did);
568581
if !did.is_local()
569582
&& !cache.access_levels.is_public(did)
570583
&& !cache.document_private

src/librustdoc/passes/collect_intra_doc_links.rs

Lines changed: 90 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,16 +26,13 @@ use std::fmt::Write;
2626
use std::mem;
2727
use std::ops::Range;
2828

29-
use crate::clean::{self, utils::find_nearest_parent_module};
29+
use crate::{clean::{self, utils::find_nearest_parent_module}, html::{format::HrefError, markdown::{MarkdownLink, markdown_links}}, lint::{PRIVATE_INTRA_DOC_LINKS, BROKEN_INTRA_DOC_LINKS}, visit::DocVisitor};
3030
use crate::clean::{Crate, Item, ItemId, ItemLink, PrimitiveType};
3131
use crate::core::DocContext;
32-
use crate::html::markdown::{markdown_links, MarkdownLink};
33-
use crate::lint::{BROKEN_INTRA_DOC_LINKS, PRIVATE_INTRA_DOC_LINKS};
34-
use crate::passes::Pass;
35-
use crate::visit::DocVisitor;
3632

3733
mod early;
3834
crate use early::early_resolve_intra_doc_links;
35+
use super::Pass;
3936

4037
crate const COLLECT_INTRA_DOC_LINKS: Pass = Pass {
4138
name: "collect-intra-doc-links",
@@ -1413,6 +1410,7 @@ impl LinkCollector<'_, '_> {
14131410
}
14141411
}
14151412

1413+
let mut had_error = false;
14161414
// item can be non-local e.g. when using #[doc(primitive = "pointer")]
14171415
if let Some((src_id, dst_id)) = id
14181416
.as_local()
@@ -1426,9 +1424,21 @@ impl LinkCollector<'_, '_> {
14261424
&& !self.cx.tcx.privacy_access_levels(()).is_exported(dst_id)
14271425
{
14281426
privacy_error(self.cx, diag_info, path_str);
1427+
had_error = true;
14291428
}
14301429
}
14311430

1431+
// Even if the item exists and is public, it may not have documentation generated
1432+
// (e.g. if it's marked with `doc(hidden)`, or in a dependency with `--no-deps`).
1433+
// Give a warning if so.
1434+
if had_error {
1435+
return Some(());
1436+
}
1437+
// `relative_to` is only used for the actual path of the link, not whether it's resolved or not.
1438+
if let Err(missing) = crate::html::format::href_inner(id, self.cx.tcx, &self.cx.cache, None, &[]) {
1439+
missing_docs_for_link(self.cx, &item, id, missing, &path_str, &diag_info);
1440+
}
1441+
14321442
Some(())
14331443
}
14341444

@@ -2294,6 +2304,81 @@ fn suggest_disambiguator(
22942304
}
22952305
}
22962306

2307+
/// Report a link to an item that will not have documentation generated.
2308+
fn missing_docs_for_link(
2309+
cx: &DocContext<'_>,
2310+
item: &Item,
2311+
destination_id: DefId,
2312+
why: HrefError,
2313+
path_str: &str,
2314+
diag_info: &DiagnosticInfo<'_>,
2315+
) {
2316+
// FIXME: this is a bug, rustdoc should load all items into the cache before doing this check.
2317+
// Otherwise, there will be false negatives where rustdoc doesn't warn but should.
2318+
if why == HrefError::NotInExternalCache {
2319+
return;
2320+
}
2321+
2322+
let item_name = match item.name {
2323+
Some(name) => name.to_string(),
2324+
None => "<unknown>".into(),
2325+
};
2326+
let msg = format!(
2327+
"documentation for `{}` links to item `{}` which will not have documentation generated",
2328+
item_name, path_str
2329+
);
2330+
2331+
report_diagnostic(cx.tcx, PRIVATE_INTRA_DOC_LINKS, &msg, diag_info, |diag, sp| {
2332+
use crate::clean::types::{AttributesExt, NestedAttributesExt};
2333+
2334+
if let Some(sp) = sp {
2335+
diag.span_label(sp, "this item is will not be documented");
2336+
}
2337+
2338+
if why == HrefError::DocumentationNotBuilt {
2339+
diag.note(&format!(
2340+
"`{}` is in the crate `{}`, which will not be documented",
2341+
path_str,
2342+
cx.tcx.crate_name(destination_id.krate)
2343+
));
2344+
return;
2345+
}
2346+
2347+
// shouldn't be possible to resolve private items
2348+
assert_ne!(why, HrefError::Private, "{:?}", destination_id);
2349+
2350+
if let Some(attr) =
2351+
cx.tcx.get_attrs(destination_id).lists(sym::doc).get_word_attr(sym::hidden)
2352+
{
2353+
diag.span_label(attr.span(), &format!("`{}` is marked as `#[doc(hidden)]`", path_str));
2354+
return;
2355+
}
2356+
2357+
let mut current = destination_id;
2358+
while let Some(parent) = cx.tcx.parent(current) {
2359+
if cx.tcx.get_attrs(parent).lists(sym::doc).has_word(sym::hidden) {
2360+
let name = cx.tcx.item_name(parent);
2361+
let (_, description) = cx.tcx.article_and_description(parent);
2362+
let span = cx.tcx.def_span(parent);
2363+
diag.span_label(
2364+
span,
2365+
&format!(
2366+
"{} is in the {} `{}`, which is marked as `#[doc(hidden)]`",
2367+
path_str, description, name
2368+
),
2369+
);
2370+
return;
2371+
}
2372+
current = parent;
2373+
}
2374+
2375+
diag.note(&format!(
2376+
"`{}` may be in a private module with all re-exports marked as `#[doc(no_inline)]`",
2377+
path_str
2378+
));
2379+
});
2380+
}
2381+
22972382
/// Report a link from a public item to a private one.
22982383
fn privacy_error(cx: &DocContext<'_>, diag_info: &DiagnosticInfo<'_>, path_str: &str) {
22992384
let sym;

src/librustdoc/passes/mod.rs

Lines changed: 20 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -94,26 +94,30 @@ crate const PASSES: &[Pass] = &[
9494
];
9595

9696
/// The list of passes run by default.
97-
crate const DEFAULT_PASSES: &[ConditionalPass] = &[
98-
ConditionalPass::always(COLLECT_TRAIT_IMPLS),
99-
ConditionalPass::always(UNINDENT_COMMENTS),
100-
ConditionalPass::always(CHECK_DOC_TEST_VISIBILITY),
101-
ConditionalPass::new(STRIP_HIDDEN, WhenNotDocumentHidden),
102-
ConditionalPass::new(STRIP_PRIVATE, WhenNotDocumentPrivate),
103-
ConditionalPass::new(STRIP_PRIV_IMPORTS, WhenDocumentPrivate),
104-
ConditionalPass::always(COLLECT_INTRA_DOC_LINKS),
105-
ConditionalPass::always(CHECK_CODE_BLOCK_SYNTAX),
106-
ConditionalPass::always(CHECK_INVALID_HTML_TAGS),
107-
ConditionalPass::always(PROPAGATE_DOC_CFG),
108-
ConditionalPass::always(CHECK_BARE_URLS),
109-
];
97+
crate const DEFAULT_PASSES: (&[ConditionalPass], &[ConditionalPass]) = (
98+
&[
99+
ConditionalPass::always(COLLECT_TRAIT_IMPLS),
100+
ConditionalPass::always(UNINDENT_COMMENTS),
101+
ConditionalPass::new(STRIP_HIDDEN, WhenNotDocumentHidden),
102+
ConditionalPass::new(STRIP_PRIVATE, WhenNotDocumentPrivate),
103+
ConditionalPass::new(STRIP_PRIV_IMPORTS, WhenDocumentPrivate),
104+
],
105+
// populate cache
106+
&[
107+
ConditionalPass::always(COLLECT_INTRA_DOC_LINKS),
108+
ConditionalPass::always(CHECK_CODE_BLOCK_SYNTAX),
109+
ConditionalPass::always(CHECK_INVALID_HTML_TAGS),
110+
ConditionalPass::always(PROPAGATE_DOC_CFG),
111+
ConditionalPass::always(CHECK_BARE_URLS),
112+
]
113+
);
110114

111115
/// The list of default passes run when `--doc-coverage` is passed to rustdoc.
112-
crate const COVERAGE_PASSES: &[ConditionalPass] = &[
116+
crate const COVERAGE_PASSES: (&[ConditionalPass], &[ConditionalPass]) = (&[
113117
ConditionalPass::new(STRIP_HIDDEN, WhenNotDocumentHidden),
114118
ConditionalPass::new(STRIP_PRIVATE, WhenNotDocumentPrivate),
115119
ConditionalPass::always(CALCULATE_DOC_COVERAGE),
116-
];
120+
], &[]);
117121

118122
impl ConditionalPass {
119123
crate const fn always(pass: Pass) -> Self {
@@ -126,7 +130,7 @@ impl ConditionalPass {
126130
}
127131

128132
/// Returns the given default set of passes.
129-
crate fn defaults(show_coverage: bool) -> &'static [ConditionalPass] {
133+
crate fn defaults(show_coverage: bool) -> (&'static [ConditionalPass], &'static [ConditionalPass]) {
130134
if show_coverage { COVERAGE_PASSES } else { DEFAULT_PASSES }
131135
}
132136

src/test/rustdoc-ui/assoc-item-not-in-scope.stderr

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,20 @@
1+
warning: documentation for `f` links to item `S::fmt` which will not have documentation generated
2+
--> $DIR/assoc-item-not-in-scope.rs:12:18
3+
|
4+
LL | /// Link to [`S::fmt`]
5+
| ^^^^^^^^ this item is will not be documented
6+
|
7+
= note: `#[warn(rustdoc::private_intra_doc_links)]` on by default
8+
= note: `S::fmt` may be in a private module with all re-exports marked as `#[doc(no_inline)]`
9+
10+
warning: documentation for `f` links to item `S::fmt` which will not have documentation generated
11+
--> $DIR/assoc-item-not-in-scope.rs:20:18
12+
|
13+
LL | /// Link to [`S::fmt`]
14+
| ^^^^^^^^ this item is will not be documented
15+
|
16+
= note: `S::fmt` may be in a private module with all re-exports marked as `#[doc(no_inline)]`
17+
118
error: unresolved link to `S::fmt`
219
--> $DIR/assoc-item-not-in-scope.rs:4:15
320
|
@@ -10,5 +27,5 @@ note: the lint level is defined here
1027
LL | #![deny(rustdoc::broken_intra_doc_links)]
1128
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
1229

13-
error: aborting due to previous error
30+
error: aborting due to previous error; 2 warnings emitted
1431

src/test/rustdoc-ui/intra-doc/feature-gate-intra-doc-pointers.stderr

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,12 @@
1+
warning: documentation for `feature_gate_intra_doc_pointers` links to item `pointer::add` which will not have documentation generated
2+
--> $DIR/feature-gate-intra-doc-pointers.rs:1:6
3+
|
4+
LL | //! [pointer::add]
5+
| ^^^^^^^^^^^^ this item is will not be documented
6+
|
7+
= note: `#[warn(rustdoc::private_intra_doc_links)]` on by default
8+
= note: `pointer::add` may be in a private module with all re-exports marked as `#[doc(no_inline)]`
9+
110
error[E0658]: linking to associated items of raw pointers is experimental
211
--> $DIR/feature-gate-intra-doc-pointers.rs:1:6
312
|
@@ -8,6 +17,14 @@ LL | //! [pointer::add]
817
= help: add `#![feature(intra_doc_pointers)]` to the crate attributes to enable
918
= note: rustdoc does not allow disambiguating between `*const` and `*mut`, and pointers are unstable until it does
1019

20+
warning: documentation for `feature_gate_intra_doc_pointers` links to item `pointer::wrapping_add` which will not have documentation generated
21+
--> $DIR/feature-gate-intra-doc-pointers.rs:3:6
22+
|
23+
LL | //! [pointer::wrapping_add]
24+
| ^^^^^^^^^^^^^^^^^^^^^ this item is will not be documented
25+
|
26+
= note: `pointer::wrapping_add` may be in a private module with all re-exports marked as `#[doc(no_inline)]`
27+
1128
error[E0658]: linking to associated items of raw pointers is experimental
1229
--> $DIR/feature-gate-intra-doc-pointers.rs:3:6
1330
|
@@ -18,6 +35,6 @@ LL | //! [pointer::wrapping_add]
1835
= help: add `#![feature(intra_doc_pointers)]` to the crate attributes to enable
1936
= note: rustdoc does not allow disambiguating between `*const` and `*mut`, and pointers are unstable until it does
2037

21-
error: aborting due to 2 previous errors
38+
error: aborting due to 2 previous errors; 2 warnings emitted
2239

2340
For more information about this error, try `rustc --explain E0658`.

0 commit comments

Comments
 (0)