Skip to content

Commit 5b1ed09

Browse files
authored
Rollup merge of #75079 - jyn514:disambiguator, r=Manishearth
Disallow linking to items with a mismatched disambiguator Closes #74851 r? @Manishearth
2 parents 5f331c0 + 9914f73 commit 5b1ed09

File tree

6 files changed

+329
-49
lines changed

6 files changed

+329
-49
lines changed

src/librustc_hir/def.rs

+6-6
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,7 @@ impl DefKind {
150150
}
151151
}
152152

153-
pub fn matches_ns(&self, ns: Namespace) -> bool {
153+
pub fn ns(&self) -> Option<Namespace> {
154154
match self {
155155
DefKind::Mod
156156
| DefKind::Struct
@@ -163,17 +163,17 @@ impl DefKind {
163163
| DefKind::ForeignTy
164164
| DefKind::TraitAlias
165165
| DefKind::AssocTy
166-
| DefKind::TyParam => ns == Namespace::TypeNS,
166+
| DefKind::TyParam => Some(Namespace::TypeNS),
167167

168168
DefKind::Fn
169169
| DefKind::Const
170170
| DefKind::ConstParam
171171
| DefKind::Static
172172
| DefKind::Ctor(..)
173173
| DefKind::AssocFn
174-
| DefKind::AssocConst => ns == Namespace::ValueNS,
174+
| DefKind::AssocConst => Some(Namespace::ValueNS),
175175

176-
DefKind::Macro(..) => ns == Namespace::MacroNS,
176+
DefKind::Macro(..) => Some(Namespace::MacroNS),
177177

178178
// Not namespaced.
179179
DefKind::AnonConst
@@ -185,7 +185,7 @@ impl DefKind {
185185
| DefKind::Use
186186
| DefKind::ForeignMod
187187
| DefKind::GlobalAsm
188-
| DefKind::Impl => false,
188+
| DefKind::Impl => None,
189189
}
190190
}
191191
}
@@ -453,7 +453,7 @@ impl<Id> Res<Id> {
453453

454454
pub fn matches_ns(&self, ns: Namespace) -> bool {
455455
match self {
456-
Res::Def(kind, ..) => kind.matches_ns(ns),
456+
Res::Def(kind, ..) => kind.ns() == Some(ns),
457457
Res::PrimTy(..) | Res::SelfTy(..) | Res::ToolMod => ns == Namespace::TypeNS,
458458
Res::SelfCtor(..) | Res::Local(..) => ns == Namespace::ValueNS,
459459
Res::NonMacroAttr(..) => ns == Namespace::MacroNS,

src/librustdoc/clean/utils.rs

+3
Original file line numberDiff line numberDiff line change
@@ -607,6 +607,9 @@ pub fn register_res(cx: &DocContext<'_>, res: Res) -> DefId {
607607
Res::Def(DefKind::TyAlias, i) => (i, TypeKind::Typedef),
608608
Res::Def(DefKind::Enum, i) => (i, TypeKind::Enum),
609609
Res::Def(DefKind::Trait, i) => (i, TypeKind::Trait),
610+
Res::Def(DefKind::AssocTy | DefKind::AssocFn | DefKind::AssocConst, i) => {
611+
(cx.tcx.parent(i).unwrap(), TypeKind::Trait)
612+
}
610613
Res::Def(DefKind::Struct, i) => (i, TypeKind::Struct),
611614
Res::Def(DefKind::Union, i) => (i, TypeKind::Union),
612615
Res::Def(DefKind::Mod, i) => (i, TypeKind::Module),

src/librustdoc/passes/collect_intra_doc_links.rs

+145-43
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ use rustc_span::symbol::Ident;
1717
use rustc_span::symbol::Symbol;
1818
use rustc_span::DUMMY_SP;
1919

20+
use std::cell::Cell;
2021
use std::ops::Range;
2122

2223
use crate::clean::*;
@@ -62,11 +63,15 @@ struct LinkCollector<'a, 'tcx> {
6263
cx: &'a DocContext<'tcx>,
6364
// NOTE: this may not necessarily be a module in the current crate
6465
mod_ids: Vec<DefId>,
66+
/// This is used to store the kind of associated items,
67+
/// because `clean` and the disambiguator code expect them to be different.
68+
/// See the code for associated items on inherent impls for details.
69+
kind_side_channel: Cell<Option<DefKind>>,
6570
}
6671

6772
impl<'a, 'tcx> LinkCollector<'a, 'tcx> {
6873
fn new(cx: &'a DocContext<'tcx>) -> Self {
69-
LinkCollector { cx, mod_ids: Vec::new() }
74+
LinkCollector { cx, mod_ids: Vec::new(), kind_side_channel: Cell::new(None) }
7075
}
7176

7277
fn variant_field(
@@ -174,7 +179,7 @@ impl<'a, 'tcx> LinkCollector<'a, 'tcx> {
174179
fn resolve(
175180
&self,
176181
path_str: &str,
177-
disambiguator: Option<&str>,
182+
disambiguator: Option<Disambiguator>,
178183
ns: Namespace,
179184
current_item: &Option<String>,
180185
parent_id: Option<DefId>,
@@ -214,7 +219,7 @@ impl<'a, 'tcx> LinkCollector<'a, 'tcx> {
214219
Res::Def(DefKind::Mod, _) => {
215220
// This resolved to a module, but if we were passed `type@`,
216221
// we want primitive types to take precedence instead.
217-
if disambiguator == Some("type") {
222+
if disambiguator == Some(Disambiguator::Namespace(Namespace::TypeNS)) {
218223
if let Some(prim) = is_primitive(path_str, ns) {
219224
if extra_fragment.is_some() {
220225
return Err(ErrorKind::AnchorFailure(AnchorFailure::Primitive));
@@ -347,6 +352,10 @@ impl<'a, 'tcx> LinkCollector<'a, 'tcx> {
347352
AnchorFailure::AssocConstant
348353
}))
349354
} else {
355+
// HACK(jynelson): `clean` expects the type, not the associated item.
356+
// but the disambiguator logic expects the associated item.
357+
// Store the kind in a side channel so that only the disambiguator logic looks at it.
358+
self.kind_side_channel.replace(Some(item.kind.as_def_kind()));
350359
Ok((ty_res, Some(format!("{}.{}", out, item_name))))
351360
}
352361
} else {
@@ -415,7 +424,8 @@ impl<'a, 'tcx> LinkCollector<'a, 'tcx> {
415424
AnchorFailure::Method
416425
}))
417426
} else {
418-
Ok((ty_res, Some(format!("{}.{}", kind, item_name))))
427+
let res = Res::Def(item.kind.as_def_kind(), item.def_id);
428+
Ok((res, Some(format!("{}.{}", kind, item_name))))
419429
}
420430
} else {
421431
self.variant_field(path_str, current_item, module_id)
@@ -574,46 +584,14 @@ impl<'a, 'tcx> DocFolder for LinkCollector<'a, 'tcx> {
574584
};
575585
let resolved_self;
576586
let mut path_str;
587+
let disambiguator;
577588
let (res, fragment) = {
578-
let mut kind = None;
579-
let mut disambiguator = None;
580-
path_str = if let Some(prefix) =
581-
["struct@", "enum@", "type@", "trait@", "union@", "module@", "mod@"]
582-
.iter()
583-
.find(|p| link.starts_with(**p))
584-
{
585-
kind = Some(TypeNS);
586-
disambiguator = Some(&prefix[..prefix.len() - 1]);
587-
link.trim_start_matches(prefix)
588-
} else if let Some(prefix) =
589-
["const@", "static@", "value@", "function@", "fn@", "method@"]
590-
.iter()
591-
.find(|p| link.starts_with(**p))
592-
{
593-
kind = Some(ValueNS);
594-
disambiguator = Some(&prefix[..prefix.len() - 1]);
595-
link.trim_start_matches(prefix)
596-
} else if link.ends_with("!()") {
597-
kind = Some(MacroNS);
598-
link.trim_end_matches("!()")
599-
} else if link.ends_with("()") {
600-
kind = Some(ValueNS);
601-
disambiguator = Some("fn");
602-
link.trim_end_matches("()")
603-
} else if link.starts_with("macro@") {
604-
kind = Some(MacroNS);
605-
disambiguator = Some("macro");
606-
link.trim_start_matches("macro@")
607-
} else if link.starts_with("derive@") {
608-
kind = Some(MacroNS);
609-
disambiguator = Some("derive");
610-
link.trim_start_matches("derive@")
611-
} else if link.ends_with('!') {
612-
kind = Some(MacroNS);
613-
disambiguator = Some("macro");
614-
link.trim_end_matches('!')
589+
path_str = if let Ok((d, path)) = Disambiguator::from_str(&link) {
590+
disambiguator = Some(d);
591+
path
615592
} else {
616-
&link[..]
593+
disambiguator = None;
594+
&link
617595
}
618596
.trim();
619597

@@ -646,7 +624,7 @@ impl<'a, 'tcx> DocFolder for LinkCollector<'a, 'tcx> {
646624
}
647625
}
648626

649-
match kind {
627+
match disambiguator.map(Disambiguator::ns) {
650628
Some(ns @ ValueNS) => {
651629
match self.resolve(
652630
path_str,
@@ -789,6 +767,42 @@ impl<'a, 'tcx> DocFolder for LinkCollector<'a, 'tcx> {
789767
} else {
790768
debug!("intra-doc link to {} resolved to {:?}", path_str, res);
791769

770+
// Disallow e.g. linking to enums with `struct@`
771+
if let Res::Def(kind, id) = res {
772+
debug!("saw kind {:?} with disambiguator {:?}", kind, disambiguator);
773+
match (self.kind_side_channel.take().unwrap_or(kind), disambiguator) {
774+
| (DefKind::Const | DefKind::ConstParam | DefKind::AssocConst | DefKind::AnonConst, Some(Disambiguator::Kind(DefKind::Const)))
775+
// NOTE: this allows 'method' to mean both normal functions and associated functions
776+
// This can't cause ambiguity because both are in the same namespace.
777+
| (DefKind::Fn | DefKind::AssocFn, Some(Disambiguator::Kind(DefKind::Fn)))
778+
// These are namespaces; allow anything in the namespace to match
779+
| (_, Some(Disambiguator::Namespace(_)))
780+
// If no disambiguator given, allow anything
781+
| (_, None)
782+
// All of these are valid, so do nothing
783+
=> {}
784+
(actual, Some(Disambiguator::Kind(expected))) if actual == expected => {}
785+
(_, Some(Disambiguator::Kind(expected))) => {
786+
// The resolved item did not match the disambiguator; give a better error than 'not found'
787+
let msg = format!("incompatible link kind for `{}`", path_str);
788+
report_diagnostic(cx, &msg, &item, &dox, link_range, |diag, sp| {
789+
// HACK(jynelson): by looking at the source I saw the DefId we pass
790+
// for `expected.descr()` doesn't matter, since it's not a crate
791+
let note = format!("this link resolved to {} {}, which is not {} {}", kind.article(), kind.descr(id), expected.article(), expected.descr(id));
792+
let suggestion = Disambiguator::display_for(kind, path_str);
793+
let help_msg = format!("to link to the {}, use its disambiguator", kind.descr(id));
794+
diag.note(&note);
795+
if let Some(sp) = sp {
796+
diag.span_suggestion(sp, &help_msg, suggestion, Applicability::MaybeIncorrect);
797+
} else {
798+
diag.help(&format!("{}: {}", help_msg, suggestion));
799+
}
800+
});
801+
continue;
802+
}
803+
}
804+
}
805+
792806
// item can be non-local e.g. when using #[doc(primitive = "pointer")]
793807
if let Some((src_id, dst_id)) = res
794808
.opt_def_id()
@@ -837,6 +851,94 @@ impl<'a, 'tcx> DocFolder for LinkCollector<'a, 'tcx> {
837851
}
838852
}
839853

854+
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
855+
enum Disambiguator {
856+
Kind(DefKind),
857+
Namespace(Namespace),
858+
}
859+
860+
impl Disambiguator {
861+
/// (disambiguator, path_str)
862+
fn from_str(link: &str) -> Result<(Self, &str), ()> {
863+
use Disambiguator::{Kind, Namespace as NS};
864+
865+
let find_suffix = || {
866+
let suffixes = [
867+
("!()", DefKind::Macro(MacroKind::Bang)),
868+
("()", DefKind::Fn),
869+
("!", DefKind::Macro(MacroKind::Bang)),
870+
];
871+
for &(suffix, kind) in &suffixes {
872+
if link.ends_with(suffix) {
873+
return Ok((Kind(kind), link.trim_end_matches(suffix)));
874+
}
875+
}
876+
Err(())
877+
};
878+
879+
if let Some(idx) = link.find('@') {
880+
let (prefix, rest) = link.split_at(idx);
881+
let d = match prefix {
882+
"struct" => Kind(DefKind::Struct),
883+
"enum" => Kind(DefKind::Enum),
884+
"trait" => Kind(DefKind::Trait),
885+
"union" => Kind(DefKind::Union),
886+
"module" | "mod" => Kind(DefKind::Mod),
887+
"const" | "constant" => Kind(DefKind::Const),
888+
"static" => Kind(DefKind::Static),
889+
"function" | "fn" | "method" => Kind(DefKind::Fn),
890+
"derive" => Kind(DefKind::Macro(MacroKind::Derive)),
891+
"type" => NS(Namespace::TypeNS),
892+
"value" => NS(Namespace::ValueNS),
893+
"macro" => NS(Namespace::MacroNS),
894+
_ => return find_suffix(),
895+
};
896+
Ok((d, &rest[1..]))
897+
} else {
898+
find_suffix()
899+
}
900+
}
901+
902+
fn display_for(kind: DefKind, path_str: &str) -> String {
903+
if kind == DefKind::Macro(MacroKind::Bang) {
904+
return format!("{}!", path_str);
905+
} else if kind == DefKind::Fn || kind == DefKind::AssocFn {
906+
return format!("{}()", path_str);
907+
}
908+
let prefix = match kind {
909+
DefKind::Struct => "struct",
910+
DefKind::Enum => "enum",
911+
DefKind::Trait => "trait",
912+
DefKind::Union => "union",
913+
DefKind::Mod => "mod",
914+
DefKind::Const | DefKind::ConstParam | DefKind::AssocConst | DefKind::AnonConst => {
915+
"const"
916+
}
917+
DefKind::Static => "static",
918+
DefKind::Macro(MacroKind::Derive) => "derive",
919+
// Now handle things that don't have a specific disambiguator
920+
_ => match kind
921+
.ns()
922+
.expect("tried to calculate a disambiguator for a def without a namespace?")
923+
{
924+
Namespace::TypeNS => "type",
925+
Namespace::ValueNS => "value",
926+
Namespace::MacroNS => "macro",
927+
},
928+
};
929+
format!("{}@{}", prefix, path_str)
930+
}
931+
932+
fn ns(self) -> Namespace {
933+
match self {
934+
Self::Namespace(n) => n,
935+
Self::Kind(k) => {
936+
k.ns().expect("only DefKinds with a valid namespace can be disambiguators")
937+
}
938+
}
939+
}
940+
}
941+
840942
/// Reports a diagnostic for an intra-doc link.
841943
///
842944
/// If no link range is provided, or the source span of the link cannot be determined, the span of
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
#![deny(broken_intra_doc_links)]
2+
//~^ NOTE lint level is defined
3+
pub enum S {}
4+
5+
macro_rules! m {
6+
() => {};
7+
}
8+
9+
static s: usize = 0;
10+
const c: usize = 0;
11+
12+
trait T {}
13+
14+
/// Link to [struct@S]
15+
//~^ ERROR incompatible link kind for `S`
16+
//~| NOTE this link resolved
17+
//~| HELP use its disambiguator
18+
19+
/// Link to [mod@S]
20+
//~^ ERROR incompatible link kind for `S`
21+
//~| NOTE this link resolved
22+
//~| HELP use its disambiguator
23+
24+
/// Link to [union@S]
25+
//~^ ERROR incompatible link kind for `S`
26+
//~| NOTE this link resolved
27+
//~| HELP use its disambiguator
28+
29+
/// Link to [trait@S]
30+
//~^ ERROR incompatible link kind for `S`
31+
//~| NOTE this link resolved
32+
//~| HELP use its disambiguator
33+
34+
/// Link to [struct@T]
35+
//~^ ERROR incompatible link kind for `T`
36+
//~| NOTE this link resolved
37+
//~| HELP use its disambiguator
38+
39+
/// Link to [derive@m]
40+
//~^ ERROR incompatible link kind for `m`
41+
//~| NOTE this link resolved
42+
//~| HELP use its disambiguator
43+
44+
/// Link to [const@s]
45+
//~^ ERROR incompatible link kind for `s`
46+
//~| NOTE this link resolved
47+
//~| HELP use its disambiguator
48+
49+
/// Link to [static@c]
50+
//~^ ERROR incompatible link kind for `c`
51+
//~| NOTE this link resolved
52+
//~| HELP use its disambiguator
53+
54+
/// Link to [fn@c]
55+
//~^ ERROR incompatible link kind for `c`
56+
//~| NOTE this link resolved
57+
//~| HELP use its disambiguator
58+
59+
/// Link to [c()]
60+
//~^ ERROR incompatible link kind for `c`
61+
//~| NOTE this link resolved
62+
//~| HELP use its disambiguator
63+
64+
/// Link to [const@f]
65+
//~^ ERROR incompatible link kind for `f`
66+
//~| NOTE this link resolved
67+
//~| HELP use its disambiguator
68+
pub fn f() {}

0 commit comments

Comments
 (0)