Skip to content

Commit beef07f

Browse files
committed
Auto merge of #113958 - lukas-code:doc-links, r=GuillaumeGomez,petrochenkov
fix intra-doc links on nested `use` and `extern crate` items This PR fixes two rustdoc ICEs that happen if there are any intra-doc links on nested `use` or `extern crate` items, for example: ```rust /// Re-export [`fmt`] and [`io`]. pub use std::{fmt, io}; // "nested" use = use with braces /// Re-export [`std`]. pub extern crate std; ``` Nested use items were incorrectly considered private and therefore didn't have their intra-doc links resolved. I fixed this by always resolving intra-doc links for nested `use` items that are declared `pub`. <details> During AST->HIR lowering, nested `use` items are desugared like this: ```rust pub use std::{}; // "list stem" pub use std::fmt; pub use std::io; ``` Each of these HIR nodes has it's own effective visibility and the list stem is always considered private. To check the effective visibility of an AST node, the AST node is mapped to a HIR node with `Resolver::local_def_id`, which returns the (private) list stem for nested use items. </details> For `extern crate`, there was a hack in rustdoc that stored the `DefId` of the crate itself in the cleaned item, instead of the `DefId` of the `extern crate` item. This made rustdoc look at the resolved links of the extern crate's crate root instead of the `extern crate` item. I've removed this hack and instead translate the `DefId` in the appropriate places. As as side effect of fixing `extern crate`, i've turned ```rust #[doc(masked)] extern crate self as _; ``` into a no-op instead of hiding all trait impls. Proper verification for `doc(masked)` is included as a bonus. fixes #113896
2 parents 1821920 + 637ea3f commit beef07f

File tree

17 files changed

+246
-144
lines changed

17 files changed

+246
-144
lines changed

compiler/rustc_metadata/src/rmeta/encoder.rs

+4-5
Original file line numberDiff line numberDiff line change
@@ -2244,13 +2244,12 @@ pub fn provide(providers: &mut Providers) {
22442244
tcx.resolutions(())
22452245
.doc_link_resolutions
22462246
.get(&def_id)
2247-
.expect("no resolutions for a doc link")
2247+
.unwrap_or_else(|| span_bug!(tcx.def_span(def_id), "no resolutions for a doc link"))
22482248
},
22492249
doc_link_traits_in_scope: |tcx, def_id| {
2250-
tcx.resolutions(())
2251-
.doc_link_traits_in_scope
2252-
.get(&def_id)
2253-
.expect("no traits in scope for a doc link")
2250+
tcx.resolutions(()).doc_link_traits_in_scope.get(&def_id).unwrap_or_else(|| {
2251+
span_bug!(tcx.def_span(def_id), "no traits in scope for a doc link")
2252+
})
22542253
},
22552254
traits: |tcx, LocalCrate| {
22562255
let mut traits = Vec::new();

compiler/rustc_passes/messages.ftl

+11
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,17 @@ passes_doc_keyword_not_mod =
211211
passes_doc_keyword_only_impl =
212212
`#[doc(keyword = "...")]` should be used on impl blocks
213213
214+
passes_doc_masked_not_extern_crate_self =
215+
this attribute cannot be applied to an `extern crate self` item
216+
.label = not applicable on `extern crate self` items
217+
.extern_crate_self_label = `extern crate self` defined here
218+
219+
passes_doc_masked_only_extern_crate =
220+
this attribute can only be applied to an `extern crate` item
221+
.label = only applicable on `extern crate` items
222+
.not_an_extern_crate_label = not an `extern crate` item
223+
.note = read <https://doc.rust-lang.org/unstable-book/language-features/doc-masked.html> for more information
224+
214225
passes_doc_test_literal = `#![doc(test(...)]` does not take a literal
215226
216227
passes_doc_test_takes_list =

compiler/rustc_passes/src/check_attr.rs

+49
Original file line numberDiff line numberDiff line change
@@ -878,6 +878,44 @@ impl CheckAttrVisitor<'_> {
878878
}
879879
}
880880

881+
fn check_doc_masked(
882+
&self,
883+
attr: &Attribute,
884+
meta: &NestedMetaItem,
885+
hir_id: HirId,
886+
target: Target,
887+
) -> bool {
888+
if target != Target::ExternCrate {
889+
self.tcx.emit_spanned_lint(
890+
INVALID_DOC_ATTRIBUTES,
891+
hir_id,
892+
meta.span(),
893+
errors::DocMaskedOnlyExternCrate {
894+
attr_span: meta.span(),
895+
item_span: (attr.style == AttrStyle::Outer)
896+
.then(|| self.tcx.hir().span(hir_id)),
897+
},
898+
);
899+
return false;
900+
}
901+
902+
if self.tcx.extern_mod_stmt_cnum(hir_id.owner).is_none() {
903+
self.tcx.emit_spanned_lint(
904+
INVALID_DOC_ATTRIBUTES,
905+
hir_id,
906+
meta.span(),
907+
errors::DocMaskedNotExternCrateSelf {
908+
attr_span: meta.span(),
909+
item_span: (attr.style == AttrStyle::Outer)
910+
.then(|| self.tcx.hir().span(hir_id)),
911+
},
912+
);
913+
return false;
914+
}
915+
916+
true
917+
}
918+
881919
/// Checks that an attribute is *not* used at the crate level. Returns `true` if valid.
882920
fn check_attr_not_crate_level(
883921
&self,
@@ -1048,6 +1086,17 @@ impl CheckAttrVisitor<'_> {
10481086
is_valid = false;
10491087
}
10501088

1089+
sym::masked
1090+
if !self.check_doc_masked(
1091+
attr,
1092+
meta,
1093+
hir_id,
1094+
target,
1095+
) =>
1096+
{
1097+
is_valid = false;
1098+
}
1099+
10511100
// no_default_passes: deprecated
10521101
// passes: deprecated
10531102
// plugins: removed, but rustdoc warns about it itself

compiler/rustc_passes/src/errors.rs

+19
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,25 @@ pub struct DocInlineOnlyUse {
267267
pub item_span: Option<Span>,
268268
}
269269

270+
#[derive(LintDiagnostic)]
271+
#[diag(passes_doc_masked_only_extern_crate)]
272+
#[note]
273+
pub struct DocMaskedOnlyExternCrate {
274+
#[label]
275+
pub attr_span: Span,
276+
#[label(passes_not_an_extern_crate_label)]
277+
pub item_span: Option<Span>,
278+
}
279+
280+
#[derive(LintDiagnostic)]
281+
#[diag(passes_doc_masked_not_extern_crate_self)]
282+
pub struct DocMaskedNotExternCrateSelf {
283+
#[label]
284+
pub attr_span: Span,
285+
#[label(passes_extern_crate_self_label)]
286+
pub item_span: Option<Span>,
287+
}
288+
270289
#[derive(Diagnostic)]
271290
#[diag(passes_doc_attr_not_crate_level)]
272291
pub struct DocAttrNotCrateLevel<'a> {

compiler/rustc_resolve/src/late.rs

+11-2
Original file line numberDiff line numberDiff line change
@@ -549,6 +549,7 @@ enum MaybeExported<'a> {
549549
Ok(NodeId),
550550
Impl(Option<DefId>),
551551
ImplItem(Result<DefId, &'a Visibility>),
552+
NestedUse(&'a Visibility),
552553
}
553554

554555
impl MaybeExported<'_> {
@@ -559,7 +560,9 @@ impl MaybeExported<'_> {
559560
trait_def_id.as_local()
560561
}
561562
MaybeExported::Impl(None) => return true,
562-
MaybeExported::ImplItem(Err(vis)) => return vis.kind.is_pub(),
563+
MaybeExported::ImplItem(Err(vis)) | MaybeExported::NestedUse(vis) => {
564+
return vis.kind.is_pub();
565+
}
563566
};
564567
def_id.map_or(true, |def_id| r.effective_visibilities.is_exported(def_id))
565568
}
@@ -2284,7 +2287,7 @@ impl<'a: 'ast, 'b, 'ast, 'tcx> LateResolutionVisitor<'a, 'b, 'ast, 'tcx> {
22842287
fn resolve_item(&mut self, item: &'ast Item) {
22852288
let mod_inner_docs =
22862289
matches!(item.kind, ItemKind::Mod(..)) && rustdoc::inner_docs(&item.attrs);
2287-
if !mod_inner_docs && !matches!(item.kind, ItemKind::Impl(..)) {
2290+
if !mod_inner_docs && !matches!(item.kind, ItemKind::Impl(..) | ItemKind::Use(..)) {
22882291
self.resolve_doc_links(&item.attrs, MaybeExported::Ok(item.id));
22892292
}
22902293

@@ -2428,6 +2431,12 @@ impl<'a: 'ast, 'b, 'ast, 'tcx> LateResolutionVisitor<'a, 'b, 'ast, 'tcx> {
24282431
}
24292432

24302433
ItemKind::Use(ref use_tree) => {
2434+
let maybe_exported = match use_tree.kind {
2435+
UseTreeKind::Simple(_) | UseTreeKind::Glob => MaybeExported::Ok(item.id),
2436+
UseTreeKind::Nested(_) => MaybeExported::NestedUse(&item.vis),
2437+
};
2438+
self.resolve_doc_links(&item.attrs, maybe_exported);
2439+
24312440
self.future_proof_import(use_tree);
24322441
}
24332442

src/librustdoc/clean/mod.rs

+6-9
Original file line numberDiff line numberDiff line change
@@ -2643,15 +2643,12 @@ fn clean_extern_crate<'tcx>(
26432643
}
26442644
}
26452645

2646-
// FIXME: using `from_def_id_and_kind` breaks `rustdoc/masked` for some reason
2647-
vec![Item {
2648-
name: Some(name),
2649-
attrs: Box::new(Attributes::from_ast(attrs)),
2650-
item_id: crate_def_id.into(),
2651-
kind: Box::new(ExternCrateItem { src: orig_name }),
2652-
cfg: attrs.cfg(cx.tcx, &cx.cache.hidden_cfg),
2653-
inline_stmt_id: Some(krate_owner_def_id),
2654-
}]
2646+
vec![Item::from_def_id_and_parts(
2647+
krate_owner_def_id,
2648+
Some(name),
2649+
ExternCrateItem { src: orig_name },
2650+
cx,
2651+
)]
26552652
}
26562653

26572654
fn clean_use_statement<'tcx>(

src/librustdoc/clean/utils.rs

+8-4
Original file line numberDiff line numberDiff line change
@@ -38,11 +38,15 @@ pub(crate) fn krate(cx: &mut DocContext<'_>) -> Crate {
3838
for it in &module.items {
3939
// `compiler_builtins` should be masked too, but we can't apply
4040
// `#[doc(masked)]` to the injected `extern crate` because it's unstable.
41-
if it.is_extern_crate()
42-
&& (it.attrs.has_doc_flag(sym::masked)
43-
|| cx.tcx.is_compiler_builtins(it.item_id.krate()))
44-
{
41+
if cx.tcx.is_compiler_builtins(it.item_id.krate()) {
4542
cx.cache.masked_crates.insert(it.item_id.krate());
43+
} else if it.is_extern_crate()
44+
&& it.attrs.has_doc_flag(sym::masked)
45+
&& let Some(def_id) = it.item_id.as_def_id()
46+
&& let Some(local_def_id) = def_id.as_local()
47+
&& let Some(cnum) = cx.tcx.extern_mod_stmt_cnum(local_def_id)
48+
{
49+
cx.cache.masked_crates.insert(cnum);
4650
}
4751
}
4852
}

src/librustdoc/html/format.rs

+9-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ use rustc_data_structures::captures::Captures;
1818
use rustc_data_structures::fx::FxHashSet;
1919
use rustc_hir as hir;
2020
use rustc_hir::def::DefKind;
21-
use rustc_hir::def_id::DefId;
21+
use rustc_hir::def_id::{DefId, LOCAL_CRATE};
2222
use rustc_metadata::creader::{CStore, LoadedMacro};
2323
use rustc_middle::ty;
2424
use rustc_middle::ty::TyCtxt;
@@ -662,6 +662,14 @@ pub(crate) fn href_with_root_path(
662662
// documented on their parent's page
663663
tcx.parent(did)
664664
}
665+
DefKind::ExternCrate => {
666+
// Link to the crate itself, not the `extern crate` item.
667+
if let Some(local_did) = did.as_local() {
668+
tcx.extern_mod_stmt_cnum(local_did).unwrap_or(LOCAL_CRATE).as_def_id()
669+
} else {
670+
did
671+
}
672+
}
665673
_ => did,
666674
};
667675
let cache = cx.cache();

src/librustdoc/passes/collect_intra_doc_links.rs

+8-2
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ use rustc_hir::def::{DefKind, Namespace, PerNS};
1414
use rustc_hir::def_id::{DefId, CRATE_DEF_ID};
1515
use rustc_hir::Mutability;
1616
use rustc_middle::ty::{Ty, TyCtxt};
17-
use rustc_middle::{bug, ty};
17+
use rustc_middle::{bug, span_bug, ty};
1818
use rustc_resolve::rustdoc::{has_primitive_or_keyword_docs, prepare_to_doc_link_resolution};
1919
use rustc_resolve::rustdoc::{strip_generics_from_path, MalformedGenerics};
2020
use rustc_session::lint::Lint;
@@ -402,7 +402,12 @@ impl<'a, 'tcx> LinkCollector<'a, 'tcx> {
402402
// `doc_link_resolutions` is missing a `path_str`, that means that there are valid links
403403
// that are being missed. To fix the ICE, change
404404
// `rustc_resolve::rustdoc::attrs_to_preprocessed_links` to cache the link.
405-
.unwrap_or_else(|| panic!("no resolution for {:?} {:?} {:?}", path_str, ns, module_id))
405+
.unwrap_or_else(|| {
406+
span_bug!(
407+
self.cx.tcx.def_span(item_id),
408+
"no resolution for {path_str:?} {ns:?} {module_id:?}",
409+
)
410+
})
406411
.and_then(|res| res.try_into().ok())
407412
.or_else(|| resolve_primitive(path_str, ns));
408413
debug!("{} resolved to {:?} in namespace {:?}", path_str, result, ns);
@@ -963,6 +968,7 @@ fn preprocessed_markdown_links(s: &str) -> Vec<PreprocessedMarkdownLink> {
963968
}
964969

965970
impl LinkCollector<'_, '_> {
971+
#[instrument(level = "debug", skip_all)]
966972
fn resolve_links(&mut self, item: &Item) {
967973
if !self.cx.render_options.document_private
968974
&& let Some(def_id) = item.item_id.as_def_id()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// Test that we don't ICE with broken links that don't show up in the docs.
2+
3+
// check-pass
4+
// edition: 2021
5+
6+
/// [1]
7+
//~^ WARN unresolved link to `1`
8+
//~| WARN unresolved link to `1`
9+
pub use {std, core};
10+
11+
/// [2]
12+
pub use {};
13+
14+
/// [3]
15+
//~^ WARN unresolved link to `3`
16+
pub extern crate alloc;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
warning: unresolved link to `3`
2+
--> $DIR/broken-link-in-unused-doc-string.rs:14:6
3+
|
4+
LL | /// [3]
5+
| ^ no item named `3` in scope
6+
|
7+
= help: to escape `[` and `]` characters, add '\' before them like `\[` or `\]`
8+
= note: `#[warn(rustdoc::broken_intra_doc_links)]` on by default
9+
10+
warning: unresolved link to `1`
11+
--> $DIR/broken-link-in-unused-doc-string.rs:6:6
12+
|
13+
LL | /// [1]
14+
| ^ no item named `1` in scope
15+
|
16+
= help: to escape `[` and `]` characters, add '\' before them like `\[` or `\]`
17+
18+
warning: unresolved link to `1`
19+
--> $DIR/broken-link-in-unused-doc-string.rs:6:6
20+
|
21+
LL | /// [1]
22+
| ^ no item named `1` in scope
23+
|
24+
= help: to escape `[` and `]` characters, add '\' before them like `\[` or `\]`
25+
26+
warning: 3 warnings emitted
27+

tests/rustdoc-ui/lints/invalid-doc-attr.rs

+15
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
#![crate_type = "lib"]
22
#![deny(warnings)]
3+
#![feature(doc_masked)]
4+
5+
#![doc(masked)]
6+
//~^ ERROR this attribute can only be applied to an `extern crate` item
7+
//~| WARN is being phased out
38

49
#[doc(test(no_crate_inject))]
510
//~^ ERROR can only be applied at the crate level
@@ -30,3 +35,13 @@ pub mod bar {
3035
//~^^ ERROR conflicting doc inlining attributes
3136
//~| HELP remove one of the conflicting attributes
3237
pub use bar::baz;
38+
39+
#[doc(masked)]
40+
//~^ ERROR this attribute can only be applied to an `extern crate` item
41+
//~| WARN is being phased out
42+
pub struct Masked;
43+
44+
#[doc(masked)]
45+
//~^ ERROR this attribute cannot be applied to an `extern crate self` item
46+
//~| WARN is being phased out
47+
pub extern crate self as reexport;

0 commit comments

Comments
 (0)