diff --git a/src/librustdoc/passes/collect_intra_doc_links.rs b/src/librustdoc/passes/collect_intra_doc_links.rs index bf7a43236e0..3d8daf4e9a5 100644 --- a/src/librustdoc/passes/collect_intra_doc_links.rs +++ b/src/librustdoc/passes/collect_intra_doc_links.rs @@ -574,9 +574,9 @@ fn fold_item(&mut self, mut item: Item) -> Option { }; let resolved_self; let mut path_str; + let mut disambiguator = None; let (res, fragment) = { let mut kind = None; - let mut disambiguator = None; path_str = if let Some(prefix) = ["struct@", "enum@", "type@", "trait@", "union@", "module@", "mod@"] .iter() @@ -595,6 +595,7 @@ fn fold_item(&mut self, mut item: Item) -> Option { link.trim_start_matches(prefix) } else if link.ends_with("!()") { kind = Some(MacroNS); + disambiguator = Some("bang"); link.trim_end_matches("!()") } else if link.ends_with("()") { kind = Some(ValueNS); @@ -610,7 +611,7 @@ fn fold_item(&mut self, mut item: Item) -> Option { link.trim_start_matches("derive@") } else if link.ends_with('!') { kind = Some(MacroNS); - disambiguator = Some("macro"); + disambiguator = Some("bang"); link.trim_end_matches('!') } else { &link[..] @@ -789,6 +790,46 @@ fn fold_item(&mut self, mut item: Item) -> Option { } else { debug!("intra-doc link to {} resolved to {:?}", path_str, res); + // Disallow e.g. linking to enums with `struct@` + if let Res::Def(kind, id) = res { + debug!("saw kind {:?} with disambiguator {:?}", kind, disambiguator); + // NOTE: this relies on the fact that `''` is never parsed as a disambiguator + // NOTE: this needs to be kept in sync with the disambiguator parsing + match (kind, disambiguator.unwrap_or_default().trim_end_matches("@")) { + | (DefKind::Struct, "struct") + | (DefKind::Enum, "enum") + | (DefKind::Trait, "trait") + | (DefKind::Union, "union") + | (DefKind::Mod, "mod" | "module") + | (DefKind::Const | DefKind::ConstParam | DefKind::AssocConst | DefKind::AnonConst, "const") + | (DefKind::Static, "static") + // NOTE: this allows 'method' to mean both normal functions and associated functions + // This can't cause ambiguity because both are in the same namespace. + | (DefKind::Fn | DefKind::AssocFn, "fn" | "function" | "method") + | (DefKind::Macro(MacroKind::Bang), "bang") + | (DefKind::Macro(MacroKind::Derive), "derive") + // These are namespaces; allow anything in the namespace to match + | (_, "type" | "macro" | "value") + // If no disambiguator given, allow anything + | (_, "") + // All of these are valid, so do nothing + => {} + (_, disambiguator) => { + // The resolved item did not match the disambiguator; give a better error than 'not found' + let msg = format!("unresolved link to `{}`", path_str); + report_diagnostic(cx, &msg, &item, &dox, link_range, |diag, sp| { + let msg = format!("this item resolved to {} {}, which did not match the disambiguator '{}'", kind.article(), kind.descr(id), disambiguator); + if let Some(sp) = sp { + diag.span_note(sp, &msg); + } else { + diag.note(&msg); + } + }); + continue; + } + } + } + // item can be non-local e.g. when using #[doc(primitive = "pointer")] if let Some((src_id, dst_id)) = res .opt_def_id() diff --git a/src/test/rustdoc-ui/intra-links-disambiguator-mismatch.rs b/src/test/rustdoc-ui/intra-links-disambiguator-mismatch.rs new file mode 100644 index 00000000000..6ab815eab54 --- /dev/null +++ b/src/test/rustdoc-ui/intra-links-disambiguator-mismatch.rs @@ -0,0 +1,53 @@ +#![deny(broken_intra_doc_links)] +//~^ NOTE lint level is defined +pub enum S {} + +macro_rules! m { + () => {}; +} + +static s: usize = 0; +const c: usize = 0; + +trait T {} + +/// Link to [struct@S] +//~^ ERROR unresolved link to `S` +//~| NOTE did not match + +/// Link to [mod@S] +//~^ ERROR unresolved link to `S` +//~| NOTE did not match + +/// Link to [union@S] +//~^ ERROR unresolved link to `S` +//~| NOTE did not match + +/// Link to [trait@S] +//~^ ERROR unresolved link to `S` +//~| NOTE did not match + +/// Link to [struct@T] +//~^ ERROR unresolved link to `T` +//~| NOTE did not match + +/// Link to [derive@m] +//~^ ERROR unresolved link to `m` +//~| NOTE did not match + +/// Link to [const@s] +//~^ ERROR unresolved link to `s` +//~| NOTE did not match + +/// Link to [static@c] +//~^ ERROR unresolved link to `c` +//~| NOTE did not match + +/// Link to [fn@c] +//~^ ERROR unresolved link to `c` +//~| NOTE did not match + +/// Link to [c()] +//~^ ERROR unresolved link to `c` +//~| NOTE did not match +pub fn f() {} diff --git a/src/test/rustdoc-ui/intra-links-disambiguator-mismatch.stderr b/src/test/rustdoc-ui/intra-links-disambiguator-mismatch.stderr new file mode 100644 index 00000000000..21276ecb20a --- /dev/null +++ b/src/test/rustdoc-ui/intra-links-disambiguator-mismatch.stderr @@ -0,0 +1,127 @@ +error: unresolved link to `S` + --> $DIR/intra-links-disambiguator-mismatch.rs:16:14 + | +LL | /// Link to [struct@S] + | ^^^^^^^^ + | +note: the lint level is defined here + --> $DIR/intra-links-disambiguator-mismatch.rs:1:9 + | +LL | #![deny(broken_intra_doc_links)] + | ^^^^^^^^^^^^^^^^^^^^^^ +note: this item resolved to an enum, which did not match the disambiguator 'struct' + --> $DIR/intra-links-disambiguator-mismatch.rs:16:14 + | +LL | /// Link to [struct@S] + | ^^^^^^^^ + +error: unresolved link to `S` + --> $DIR/intra-links-disambiguator-mismatch.rs:20:14 + | +LL | /// Link to [mod@S] + | ^^^^^ + | +note: this item resolved to an enum, which did not match the disambiguator 'mod' + --> $DIR/intra-links-disambiguator-mismatch.rs:20:14 + | +LL | /// Link to [mod@S] + | ^^^^^ + +error: unresolved link to `S` + --> $DIR/intra-links-disambiguator-mismatch.rs:24:14 + | +LL | /// Link to [union@S] + | ^^^^^^^ + | +note: this item resolved to an enum, which did not match the disambiguator 'union' + --> $DIR/intra-links-disambiguator-mismatch.rs:24:14 + | +LL | /// Link to [union@S] + | ^^^^^^^ + +error: unresolved link to `S` + --> $DIR/intra-links-disambiguator-mismatch.rs:28:14 + | +LL | /// Link to [trait@S] + | ^^^^^^^ + | +note: this item resolved to an enum, which did not match the disambiguator 'trait' + --> $DIR/intra-links-disambiguator-mismatch.rs:28:14 + | +LL | /// Link to [trait@S] + | ^^^^^^^ + +error: unresolved link to `T` + --> $DIR/intra-links-disambiguator-mismatch.rs:32:14 + | +LL | /// Link to [struct@T] + | ^^^^^^^^ + | +note: this item resolved to a trait, which did not match the disambiguator 'struct' + --> $DIR/intra-links-disambiguator-mismatch.rs:32:14 + | +LL | /// Link to [struct@T] + | ^^^^^^^^ + +error: unresolved link to `m` + --> $DIR/intra-links-disambiguator-mismatch.rs:36:14 + | +LL | /// Link to [derive@m] + | ^^^^^^^^ + | +note: this item resolved to a macro, which did not match the disambiguator 'derive' + --> $DIR/intra-links-disambiguator-mismatch.rs:36:14 + | +LL | /// Link to [derive@m] + | ^^^^^^^^ + +error: unresolved link to `s` + --> $DIR/intra-links-disambiguator-mismatch.rs:40:14 + | +LL | /// Link to [const@s] + | ^^^^^^^ + | +note: this item resolved to a static, which did not match the disambiguator 'const' + --> $DIR/intra-links-disambiguator-mismatch.rs:40:14 + | +LL | /// Link to [const@s] + | ^^^^^^^ + +error: unresolved link to `c` + --> $DIR/intra-links-disambiguator-mismatch.rs:44:14 + | +LL | /// Link to [static@c] + | ^^^^^^^^ + | +note: this item resolved to a constant, which did not match the disambiguator 'static' + --> $DIR/intra-links-disambiguator-mismatch.rs:44:14 + | +LL | /// Link to [static@c] + | ^^^^^^^^ + +error: unresolved link to `c` + --> $DIR/intra-links-disambiguator-mismatch.rs:48:14 + | +LL | /// Link to [fn@c] + | ^^^^ + | +note: this item resolved to a constant, which did not match the disambiguator 'fn' + --> $DIR/intra-links-disambiguator-mismatch.rs:48:14 + | +LL | /// Link to [fn@c] + | ^^^^ + +error: unresolved link to `c` + --> $DIR/intra-links-disambiguator-mismatch.rs:52:14 + | +LL | /// Link to [c()] + | ^^^ + | +note: this item resolved to a constant, which did not match the disambiguator 'fn' + --> $DIR/intra-links-disambiguator-mismatch.rs:52:14 + | +LL | /// Link to [c()] + | ^^^ + +error: aborting due to 10 previous errors +