Skip to content

Commit 444f5a0

Browse files
committed
Give a much better error message if the struct failed to resolve
1 parent 99354f5 commit 444f5a0

File tree

4 files changed

+182
-145
lines changed

4 files changed

+182
-145
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/passes/collect_intra_doc_links.rs

+127-60
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,7 @@ impl<'a, 'tcx> LinkCollector<'a, 'tcx> {
174174
fn resolve(
175175
&self,
176176
path_str: &str,
177-
disambiguator: Option<&str>,
177+
disambiguator: Option<Disambiguator>,
178178
ns: Namespace,
179179
current_item: &Option<String>,
180180
parent_id: Option<DefId>,
@@ -214,7 +214,7 @@ impl<'a, 'tcx> LinkCollector<'a, 'tcx> {
214214
Res::Def(DefKind::Mod, _) => {
215215
// This resolved to a module, but if we were passed `type@`,
216216
// we want primitive types to take precedence instead.
217-
if disambiguator == Some("type") {
217+
if disambiguator == Some(Disambiguator::Namespace(Namespace::TypeNS)) {
218218
if let Some(prim) = is_primitive(path_str, ns) {
219219
if extra_fragment.is_some() {
220220
return Err(ErrorKind::AnchorFailure(AnchorFailure::Primitive));
@@ -575,47 +575,14 @@ impl<'a, 'tcx> DocFolder for LinkCollector<'a, 'tcx> {
575575
};
576576
let resolved_self;
577577
let mut path_str;
578-
let mut disambiguator = None;
578+
let disambiguator;
579579
let (res, fragment) = {
580-
let mut kind = None;
581-
path_str = if let Some(prefix) =
582-
["struct@", "enum@", "type@", "trait@", "union@", "module@", "mod@"]
583-
.iter()
584-
.find(|p| link.starts_with(**p))
585-
{
586-
kind = Some(TypeNS);
587-
disambiguator = Some(&prefix[..prefix.len() - 1]);
588-
link.trim_start_matches(prefix)
589-
} else if let Some(prefix) =
590-
["const@", "static@", "value@", "function@", "fn@", "method@"]
591-
.iter()
592-
.find(|p| link.starts_with(**p))
593-
{
594-
kind = Some(ValueNS);
595-
disambiguator = Some(&prefix[..prefix.len() - 1]);
596-
link.trim_start_matches(prefix)
597-
} else if link.ends_with("!()") {
598-
kind = Some(MacroNS);
599-
disambiguator = Some("bang");
600-
link.trim_end_matches("!()")
601-
} else if link.ends_with("()") {
602-
kind = Some(ValueNS);
603-
disambiguator = Some("fn");
604-
link.trim_end_matches("()")
605-
} else if link.starts_with("macro@") {
606-
kind = Some(MacroNS);
607-
disambiguator = Some("macro");
608-
link.trim_start_matches("macro@")
609-
} else if link.starts_with("derive@") {
610-
kind = Some(MacroNS);
611-
disambiguator = Some("derive");
612-
link.trim_start_matches("derive@")
613-
} else if link.ends_with('!') {
614-
kind = Some(MacroNS);
615-
disambiguator = Some("bang");
616-
link.trim_end_matches('!')
580+
path_str = if let Ok((d, path)) = Disambiguator::from_str(&link) {
581+
disambiguator = Some(d);
582+
path
617583
} else {
618-
&link[..]
584+
disambiguator = None;
585+
&link
619586
}
620587
.trim();
621588

@@ -648,7 +615,7 @@ impl<'a, 'tcx> DocFolder for LinkCollector<'a, 'tcx> {
648615
}
649616
}
650617

651-
match kind {
618+
match disambiguator.map(Disambiguator::ns) {
652619
Some(ns @ ValueNS) => {
653620
match self.resolve(
654621
path_str,
@@ -796,34 +763,31 @@ impl<'a, 'tcx> DocFolder for LinkCollector<'a, 'tcx> {
796763
debug!("saw kind {:?} with disambiguator {:?}", kind, disambiguator);
797764
// NOTE: this relies on the fact that `''` is never parsed as a disambiguator
798765
// NOTE: this needs to be kept in sync with the disambiguator parsing
799-
match (kind, disambiguator.unwrap_or_default().trim_end_matches("@")) {
800-
| (DefKind::Struct, "struct")
801-
| (DefKind::Enum, "enum")
802-
| (DefKind::Trait, "trait")
803-
| (DefKind::Union, "union")
804-
| (DefKind::Mod, "mod" | "module")
805-
| (DefKind::Const | DefKind::ConstParam | DefKind::AssocConst | DefKind::AnonConst, "const")
806-
| (DefKind::Static, "static")
766+
match (kind, disambiguator) {
767+
| (DefKind::Const | DefKind::ConstParam | DefKind::AssocConst | DefKind::AnonConst, Some(Disambiguator::Kind(DefKind::Const)))
807768
// NOTE: this allows 'method' to mean both normal functions and associated functions
808769
// This can't cause ambiguity because both are in the same namespace.
809-
| (DefKind::Fn | DefKind::AssocFn, "fn" | "function" | "method")
810-
| (DefKind::Macro(MacroKind::Bang), "bang")
811-
| (DefKind::Macro(MacroKind::Derive), "derive")
770+
| (DefKind::Fn | DefKind::AssocFn, Some(Disambiguator::Kind(DefKind::Fn)))
812771
// These are namespaces; allow anything in the namespace to match
813-
| (_, "type" | "macro" | "value")
772+
| (_, Some(Disambiguator::Namespace(_)))
814773
// If no disambiguator given, allow anything
815-
| (_, "")
774+
| (_, None)
816775
// All of these are valid, so do nothing
817776
=> {}
818-
(_, disambiguator) => {
777+
(_, Some(Disambiguator::Kind(expected))) if kind == expected => {}
778+
(_, Some(expected)) => {
819779
// The resolved item did not match the disambiguator; give a better error than 'not found'
820780
let msg = format!("unresolved link to `{}`", path_str);
821781
report_diagnostic(cx, &msg, &item, &dox, link_range, |diag, sp| {
822-
let msg = format!("this link resolved to {} {}, which did not match the disambiguator '{}'", kind.article(), kind.descr(id), disambiguator);
782+
// HACK(jynelson): by looking at the source I saw the DefId we pass
783+
// for `expected.descr()` doesn't matter, since it's not a crate
784+
let note = format!("this link resolved to {} {}, which is not {} {}", kind.article(), kind.descr(id), expected.article(), expected.descr(id));
785+
let suggestion = Disambiguator::display_for(kind, path_str);
786+
let help_msg = format!("to link to the {}, use its disambiguator", kind.descr(id));
787+
diag.note(&note);
823788
if let Some(sp) = sp {
824-
diag.span_note(sp, &msg);
825-
} else {
826-
diag.note(&msg);
789+
diag.span_suggestion(sp, &help_msg, suggestion, Applicability::MaybeIncorrect);
790+
diag.set_sort_span(sp);
827791
}
828792
});
829793
continue;
@@ -879,6 +843,109 @@ impl<'a, 'tcx> DocFolder for LinkCollector<'a, 'tcx> {
879843
}
880844
}
881845

846+
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
847+
enum Disambiguator {
848+
Kind(DefKind),
849+
Namespace(Namespace),
850+
}
851+
852+
impl Disambiguator {
853+
/// (disambiguator, path_str)
854+
fn from_str(link: &str) -> Result<(Self, &str), ()> {
855+
use Disambiguator::{Kind, Namespace as NS};
856+
857+
let find_suffix = || {
858+
let suffixes = [
859+
("!()", DefKind::Macro(MacroKind::Bang)),
860+
("()", DefKind::Fn),
861+
("!", DefKind::Macro(MacroKind::Bang)),
862+
];
863+
for &(suffix, kind) in &suffixes {
864+
if link.ends_with(suffix) {
865+
return Ok((Kind(kind), link.trim_end_matches(suffix)));
866+
}
867+
}
868+
Err(())
869+
};
870+
871+
if let Some(idx) = link.find('@') {
872+
let (prefix, rest) = link.split_at(idx);
873+
let d = match prefix {
874+
"struct" => Kind(DefKind::Struct),
875+
"enum" => Kind(DefKind::Enum),
876+
"trait" => Kind(DefKind::Trait),
877+
"union" => Kind(DefKind::Union),
878+
"module" | "mod" => Kind(DefKind::Mod),
879+
"const" | "constant" => Kind(DefKind::Const),
880+
"static" => Kind(DefKind::Static),
881+
"function" | "fn" | "method" => Kind(DefKind::Fn),
882+
"derive" => Kind(DefKind::Macro(MacroKind::Derive)),
883+
"type" => NS(Namespace::TypeNS),
884+
"value" => NS(Namespace::ValueNS),
885+
"macro" => NS(Namespace::MacroNS),
886+
_ => return find_suffix(),
887+
};
888+
Ok((d, &rest[1..]))
889+
} else {
890+
find_suffix()
891+
}
892+
}
893+
894+
fn display_for(kind: DefKind, path_str: &str) -> String {
895+
if kind == DefKind::Macro(MacroKind::Bang) {
896+
return format!("{}!", path_str);
897+
}
898+
let prefix = match kind {
899+
DefKind::Struct => "struct",
900+
DefKind::Enum => "enum",
901+
DefKind::Trait => "trait",
902+
DefKind::Union => "union",
903+
DefKind::Mod => "mod",
904+
DefKind::Const | DefKind::ConstParam | DefKind::AssocConst | DefKind::AnonConst => {
905+
"const"
906+
}
907+
DefKind::Static => "static",
908+
DefKind::Fn | DefKind::AssocFn => "fn",
909+
DefKind::Macro(MacroKind::Derive) => "derive",
910+
// Now handle things that don't have a specific disambiguator
911+
_ => match kind
912+
.ns()
913+
.expect("tried to calculate a disambiguator for a def without a namespace?")
914+
{
915+
Namespace::TypeNS => "type",
916+
Namespace::ValueNS => "value",
917+
Namespace::MacroNS => "macro",
918+
},
919+
};
920+
format!("{}@{}", prefix, path_str)
921+
}
922+
923+
fn ns(self) -> Namespace {
924+
match self {
925+
Self::Namespace(n) => n,
926+
Self::Kind(k) => {
927+
k.ns().expect("only DefKinds with a valid namespace can be disambiguators")
928+
}
929+
}
930+
}
931+
932+
fn article(&self) -> &'static str {
933+
match self {
934+
Self::Namespace(_) => "a",
935+
Self::Kind(kind) => kind.article(),
936+
}
937+
}
938+
939+
fn descr(&self, def_id: DefId) -> &'static str {
940+
match self {
941+
Self::Namespace(Namespace::TypeNS) => "type",
942+
Self::Namespace(Namespace::ValueNS) => "value",
943+
Self::Namespace(Namespace::MacroNS) => "macro",
944+
Self::Kind(kind) => kind.descr(def_id),
945+
}
946+
}
947+
}
948+
882949
/// Reports a diagnostic for an intra-doc link.
883950
///
884951
/// If no link range is provided, or the source span of the link cannot be determined, the span of

src/test/rustdoc-ui/intra-links-disambiguator-mismatch.rs

+20-10
Original file line numberDiff line numberDiff line change
@@ -13,41 +13,51 @@ trait T {}
1313

1414
/// Link to [struct@S]
1515
//~^ ERROR unresolved link to `S`
16-
//~| NOTE did not match
16+
//~| NOTE this link resolved
17+
//~| HELP use its disambiguator
1718

1819
/// Link to [mod@S]
1920
//~^ ERROR unresolved link to `S`
20-
//~| NOTE did not match
21+
//~| NOTE this link resolved
22+
//~| HELP use its disambiguator
2123

2224
/// Link to [union@S]
2325
//~^ ERROR unresolved link to `S`
24-
//~| NOTE did not match
26+
//~| NOTE this link resolved
27+
//~| HELP use its disambiguator
2528

2629
/// Link to [trait@S]
2730
//~^ ERROR unresolved link to `S`
28-
//~| NOTE did not match
31+
//~| NOTE this link resolved
32+
//~| HELP use its disambiguator
2933

3034
/// Link to [struct@T]
3135
//~^ ERROR unresolved link to `T`
32-
//~| NOTE did not match
36+
//~| NOTE this link resolved
37+
//~| HELP use its disambiguator
3338

3439
/// Link to [derive@m]
3540
//~^ ERROR unresolved link to `m`
36-
//~| NOTE did not match
41+
//~| NOTE this link resolved
42+
//~| HELP use its disambiguator
3743

3844
/// Link to [const@s]
3945
//~^ ERROR unresolved link to `s`
40-
//~| NOTE did not match
46+
//~| NOTE this link resolved
47+
//~| HELP use its disambiguator
4148

4249
/// Link to [static@c]
4350
//~^ ERROR unresolved link to `c`
44-
//~| NOTE did not match
51+
//~| NOTE this link resolved
52+
//~| HELP use its disambiguator
4553

4654
/// Link to [fn@c]
4755
//~^ ERROR unresolved link to `c`
48-
//~| NOTE did not match
56+
//~| NOTE this link resolved
57+
//~| HELP use its disambiguator
4958

5059
/// Link to [c()]
5160
//~^ ERROR unresolved link to `c`
52-
//~| NOTE did not match
61+
//~| NOTE this link resolved
62+
//~| HELP use its disambiguator
5363
pub fn f() {}

0 commit comments

Comments
 (0)