Skip to content

Commit a8a0c65

Browse files
authoredNov 11, 2020
Rollup merge of #78923 - jyn514:intra-doc-comments, r=Manishearth
Cleanup and comment intra-doc link pass r? ```@Manishearth``` cc ```@seeplusplus```
2 parents 0b521e5 + 03eec5c commit a8a0c65

File tree

1 file changed

+247
-167
lines changed

1 file changed

+247
-167
lines changed
 

‎src/librustdoc/passes/collect_intra_doc_links.rs

Lines changed: 247 additions & 167 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
//! This module implements [RFC 1946]: Intra-rustdoc-links
2+
//!
3+
//! [RFC 1946]: https://github.com/rust-lang/rfcs/blob/master/text/1946-intra-rustdoc-links.md
4+
15
use rustc_ast as ast;
26
use rustc_data_structures::stable_set::FxHashSet;
37
use rustc_errors::{Applicability, DiagnosticBuilder};
@@ -27,7 +31,7 @@ use std::cell::Cell;
2731
use std::mem;
2832
use std::ops::Range;
2933

30-
use crate::clean::*;
34+
use crate::clean::{self, Crate, GetDefId, Import, Item, ItemLink, PrimitiveType};
3135
use crate::core::DocContext;
3236
use crate::fold::DocFolder;
3337
use crate::html::markdown::markdown_links;
@@ -42,10 +46,10 @@ pub const COLLECT_INTRA_DOC_LINKS: Pass = Pass {
4246
};
4347

4448
pub fn collect_intra_doc_links(krate: Crate, cx: &DocContext<'_>) -> Crate {
45-
let mut coll = LinkCollector::new(cx);
46-
coll.fold_crate(krate)
49+
LinkCollector::new(cx).fold_crate(krate)
4750
}
4851

52+
/// Top-level errors emitted by this pass.
4953
enum ErrorKind<'a> {
5054
Resolve(Box<ResolutionFailure<'a>>),
5155
AnchorFailure(AnchorFailure),
@@ -58,18 +62,37 @@ impl<'a> From<ResolutionFailure<'a>> for ErrorKind<'a> {
5862
}
5963

6064
#[derive(Debug)]
65+
/// A link failed to resolve.
6166
enum ResolutionFailure<'a> {
6267
/// This resolved, but with the wrong namespace.
63-
/// `Namespace` is the expected namespace (as opposed to the actual).
64-
WrongNamespace(Res, Namespace),
68+
///
69+
/// `Namespace` is the namespace specified with a disambiguator
70+
/// (as opposed to the actual namespace of the `Res`).
71+
WrongNamespace(Res, /* disambiguated */ Namespace),
6572
/// The link failed to resolve. `resolution_failure` should look to see if there's
6673
/// a more helpful error that can be given.
67-
NotResolved { module_id: DefId, partial_res: Option<Res>, unresolved: Cow<'a, str> },
68-
/// should not ever happen
74+
NotResolved {
75+
/// The scope the link was resolved in.
76+
module_id: DefId,
77+
/// If part of the link resolved, this has the `Res`.
78+
///
79+
/// In `[std::io::Error::x]`, `std::io::Error` would be a partial resolution.
80+
partial_res: Option<Res>,
81+
/// The remaining unresolved path segments.
82+
///
83+
/// In `[std::io::Error::x]`, `x` would be unresolved.
84+
unresolved: Cow<'a, str>,
85+
},
86+
/// This happens when rustdoc can't determine the parent scope for an item.
87+
///
88+
/// It is always a bug in rustdoc.
6989
NoParentItem,
7090
/// This link has malformed generic parameters; e.g., the angle brackets are unbalanced.
7191
MalformedGenerics(MalformedGenerics),
72-
/// used to communicate that this should be ignored, but shouldn't be reported to the user
92+
/// Used to communicate that this should be ignored, but shouldn't be reported to the user
93+
///
94+
/// This happens when there is no disambiguator and one of the namespaces
95+
/// failed to resolve.
7396
Dummy,
7497
}
7598

@@ -115,7 +138,9 @@ enum MalformedGenerics {
115138
}
116139

117140
impl ResolutionFailure<'a> {
118-
// This resolved fully (not just partially) but is erroneous for some other reason
141+
/// This resolved fully (not just partially) but is erroneous for some other reason
142+
///
143+
/// Returns the full resolution of the link, if present.
119144
fn full_res(&self) -> Option<Res> {
120145
match self {
121146
Self::WrongNamespace(res, _) => Some(*res),
@@ -125,13 +150,30 @@ impl ResolutionFailure<'a> {
125150
}
126151

127152
enum AnchorFailure {
153+
/// User error: `[std#x#y]` is not valid
128154
MultipleAnchors,
155+
/// The anchor provided by the user conflicts with Rustdoc's generated anchor.
156+
///
157+
/// This is an unfortunate state of affairs. Not every item that can be
158+
/// linked to has its own page; sometimes it is a subheading within a page,
159+
/// like for associated items. In those cases, rustdoc uses an anchor to
160+
/// link to the subheading. Since you can't have two anchors for the same
161+
/// link, Rustdoc disallows having a user-specified anchor.
162+
///
163+
/// Most of the time this is fine, because you can just link to the page of
164+
/// the item if you want to provide your own anchor. For primitives, though,
165+
/// rustdoc uses the anchor as a side channel to know which page to link to;
166+
/// it doesn't show up in the generated link. Ideally, rustdoc would remove
167+
/// this limitation, allowing you to link to subheaders on primitives.
129168
RustdocAnchorConflict(Res),
130169
}
131170

132171
struct LinkCollector<'a, 'tcx> {
133172
cx: &'a DocContext<'tcx>,
134-
// NOTE: this may not necessarily be a module in the current crate
173+
/// A stack of modules used to decide what scope to resolve in.
174+
///
175+
/// The last module will be used if the parent scope of the current item is
176+
/// unknown.
135177
mod_ids: Vec<DefId>,
136178
/// This is used to store the kind of associated items,
137179
/// because `clean` and the disambiguator code expect them to be different.
@@ -144,6 +186,12 @@ impl<'a, 'tcx> LinkCollector<'a, 'tcx> {
144186
LinkCollector { cx, mod_ids: Vec::new(), kind_side_channel: Cell::new(None) }
145187
}
146188

189+
/// Given a full link, parse it as an [enum struct variant].
190+
///
191+
/// In particular, this will return an error whenever there aren't three
192+
/// full path segments left in the link.
193+
///
194+
/// [enum struct variant]: hir::VariantData::Struct
147195
fn variant_field(
148196
&self,
149197
path_str: &'path str,
@@ -235,6 +283,10 @@ impl<'a, 'tcx> LinkCollector<'a, 'tcx> {
235283
}
236284
}
237285

286+
/// Given a primitive type, try to resolve an associated item.
287+
///
288+
/// HACK(jynelson): `item_str` is passed in instead of derived from `item_name` so the
289+
/// lifetimes on `&'path` will work.
238290
fn resolve_primitive_associated_item(
239291
&self,
240292
prim_ty: hir::PrimTy,
@@ -286,14 +338,17 @@ impl<'a, 'tcx> LinkCollector<'a, 'tcx> {
286338
}
287339

288340
/// Resolves a string as a macro.
289-
fn macro_resolve(
341+
///
342+
/// FIXME(jynelson): Can this be unified with `resolve()`?
343+
fn resolve_macro(
290344
&self,
291345
path_str: &'a str,
292346
module_id: DefId,
293347
) -> Result<Res, ResolutionFailure<'a>> {
294348
let cx = self.cx;
295349
let path = ast::Path::from_ident(Ident::from_str(path_str));
296350
cx.enter_resolver(|resolver| {
351+
// FIXME(jynelson): does this really need 3 separate lookups?
297352
if let Ok((Some(ext), res)) = resolver.resolve_macro_path(
298353
&path,
299354
None,
@@ -326,6 +381,11 @@ impl<'a, 'tcx> LinkCollector<'a, 'tcx> {
326381
})
327382
}
328383

384+
/// Convenience wrapper around `resolve_str_path_error`.
385+
///
386+
/// This also handles resolving `true` and `false` as booleans.
387+
/// NOTE: `resolve_str_path_error` knows only about paths, not about types.
388+
/// Associated items will never be resolved by this function.
329389
fn resolve_path(&self, path_str: &str, ns: Namespace, module_id: DefId) -> Option<Res> {
330390
let result = self.cx.enter_resolver(|resolver| {
331391
resolver.resolve_str_path_error(DUMMY_SP, &path_str, ns, module_id)
@@ -339,12 +399,13 @@ impl<'a, 'tcx> LinkCollector<'a, 'tcx> {
339399
}
340400
}
341401

342-
/// Resolves a string as a path within a particular namespace. Also returns an optional
343-
/// URL fragment in the case of variants and methods.
402+
/// Resolves a string as a path within a particular namespace. Returns an
403+
/// optional URL fragment in the case of variants and methods.
344404
fn resolve<'path>(
345405
&self,
346406
path_str: &'path str,
347407
ns: Namespace,
408+
// FIXME(#76467): This is for `Self`, and it's wrong.
348409
current_item: &Option<String>,
349410
module_id: DefId,
350411
extra_fragment: &Option<String>,
@@ -353,15 +414,13 @@ impl<'a, 'tcx> LinkCollector<'a, 'tcx> {
353414

354415
if let Some(res) = self.resolve_path(path_str, ns, module_id) {
355416
match res {
417+
// FIXME(#76467): make this fallthrough to lookup the associated
418+
// item a separate function.
356419
Res::Def(DefKind::AssocFn | DefKind::AssocConst, _) => {
357420
assert_eq!(ns, ValueNS);
358-
// Fall through: In case this is a trait item, skip the
359-
// early return and try looking for the trait.
360421
}
361422
Res::Def(DefKind::AssocTy, _) => {
362423
assert_eq!(ns, TypeNS);
363-
// Fall through: In case this is a trait item, skip the
364-
// early return and try looking for the trait.
365424
}
366425
Res::Def(DefKind::Variant, _) => {
367426
return handle_variant(cx, res, extra_fragment);
@@ -410,7 +469,7 @@ impl<'a, 'tcx> LinkCollector<'a, 'tcx> {
410469
})?;
411470

412471
// FIXME: are these both necessary?
413-
let ty_res = if let Some(ty_res) = is_primitive(&path_root, TypeNS)
472+
let ty_res = if let Some(ty_res) = resolve_primitive(&path_root, TypeNS)
414473
.map(|(_, res)| res)
415474
.or_else(|| self.resolve_path(&path_root, TypeNS, module_id))
416475
{
@@ -452,8 +511,8 @@ impl<'a, 'tcx> LinkCollector<'a, 'tcx> {
452511
// There should only ever be one associated item that matches from any inherent impl
453512
.next()
454513
// Check if item_name belongs to `impl SomeTrait for SomeItem`
455-
// This gives precedence to `impl SomeItem`:
456-
// Although having both would be ambiguous, use impl version for compat. sake.
514+
// FIXME(#74563): This gives precedence to `impl SomeItem`:
515+
// Although having both would be ambiguous, use impl version for compatibility's sake.
457516
// To handle that properly resolve() would have to support
458517
// something like [`ambi_fn`](<SomeStruct as SomeTrait>::ambi_fn)
459518
.or_else(|| {
@@ -480,6 +539,8 @@ impl<'a, 'tcx> LinkCollector<'a, 'tcx> {
480539
})
481540
} else if ns == Namespace::ValueNS {
482541
debug!("looking for variants or fields named {} for {:?}", item_name, did);
542+
// FIXME(jynelson): why is this different from
543+
// `variant_field`?
483544
match cx.tcx.type_of(did).kind() {
484545
ty::Adt(def, _) => {
485546
let field = if def.is_enum() {
@@ -577,7 +638,7 @@ impl<'a, 'tcx> LinkCollector<'a, 'tcx> {
577638
) -> Option<Res> {
578639
// resolve() can't be used for macro namespace
579640
let result = match ns {
580-
Namespace::MacroNS => self.macro_resolve(path_str, module_id).map_err(ErrorKind::from),
641+
Namespace::MacroNS => self.resolve_macro(path_str, module_id).map_err(ErrorKind::from),
581642
Namespace::TypeNS | Namespace::ValueNS => self
582643
.resolve(path_str, ns, current_item, module_id, extra_fragment)
583644
.map(|(res, _)| res),
@@ -593,6 +654,11 @@ impl<'a, 'tcx> LinkCollector<'a, 'tcx> {
593654
}
594655
}
595656

657+
/// Look to see if a resolved item has an associated item named `item_name`.
658+
///
659+
/// Given `[std::io::Error::source]`, where `source` is unresolved, this would
660+
/// find `std::error::Error::source` and return
661+
/// `<io::Error as error::Error>::source`.
596662
fn resolve_associated_trait_item(
597663
did: DefId,
598664
module: DefId,
@@ -601,12 +667,12 @@ fn resolve_associated_trait_item(
601667
cx: &DocContext<'_>,
602668
) -> Option<(ty::AssocKind, DefId)> {
603669
let ty = cx.tcx.type_of(did);
604-
// First consider automatic impls: `impl From<T> for T`
670+
// First consider blanket impls: `impl From<T> for T`
605671
let implicit_impls = crate::clean::get_auto_trait_and_blanket_impls(cx, ty, did);
606672
let mut candidates: Vec<_> = implicit_impls
607673
.flat_map(|impl_outer| {
608674
match impl_outer.inner {
609-
ImplItem(impl_) => {
675+
clean::ImplItem(impl_) => {
610676
debug!("considering auto or blanket impl for trait {:?}", impl_.trait_);
611677
// Give precedence to methods that were overridden
612678
if !impl_.provided_trait_methods.contains(&*item_name.as_str()) {
@@ -669,7 +735,7 @@ fn resolve_associated_trait_item(
669735
.map(|assoc| (assoc.kind, assoc.def_id))
670736
}));
671737
}
672-
// FIXME: warn about ambiguity
738+
// FIXME(#74563): warn about ambiguity
673739
debug!("the candidates were {:?}", candidates);
674740
candidates.pop()
675741
}
@@ -719,20 +785,15 @@ fn traits_implemented_by(cx: &DocContext<'_>, type_: DefId, module: DefId) -> Fx
719785
iter.collect()
720786
}
721787

722-
/// Check for resolve collisions between a trait and its derive
788+
/// Check for resolve collisions between a trait and its derive.
723789
///
724-
/// These are common and we should just resolve to the trait in that case
790+
/// These are common and we should just resolve to the trait in that case.
725791
fn is_derive_trait_collision<T>(ns: &PerNS<Result<(Res, T), ResolutionFailure<'_>>>) -> bool {
726-
if let PerNS {
792+
matches!(*ns, PerNS {
727793
type_ns: Ok((Res::Def(DefKind::Trait, _), _)),
728794
macro_ns: Ok((Res::Def(DefKind::Macro(MacroKind::Derive), _), _)),
729795
..
730-
} = *ns
731-
{
732-
true
733-
} else {
734-
false
735-
}
796+
})
736797
}
737798

738799
impl<'a, 'tcx> DocFolder for LinkCollector<'a, 'tcx> {
@@ -772,29 +833,30 @@ impl<'a, 'tcx> DocFolder for LinkCollector<'a, 'tcx> {
772833
}
773834

774835
let current_item = match item.inner {
775-
ModuleItem(..) => {
836+
clean::ModuleItem(..) => {
776837
if item.attrs.inner_docs {
777838
if item.def_id.is_top_level_module() { item.name.clone() } else { None }
778839
} else {
779840
match parent_node.or(self.mod_ids.last().copied()) {
780841
Some(parent) if !parent.is_top_level_module() => {
781-
// FIXME: can we pull the parent module's name from elsewhere?
782842
Some(self.cx.tcx.item_name(parent).to_string())
783843
}
784844
_ => None,
785845
}
786846
}
787847
}
788-
ImplItem(Impl { ref for_, .. }) => {
848+
clean::ImplItem(clean::Impl { ref for_, .. }) => {
789849
for_.def_id().map(|did| self.cx.tcx.item_name(did).to_string())
790850
}
791851
// we don't display docs on `extern crate` items anyway, so don't process them.
792-
ExternCrateItem(..) => {
852+
clean::ExternCrateItem(..) => {
793853
debug!("ignoring extern crate item {:?}", item.def_id);
794854
return self.fold_item_recur(item);
795855
}
796-
ImportItem(Import { kind: ImportKind::Simple(ref name, ..), .. }) => Some(name.clone()),
797-
MacroItem(..) => None,
856+
clean::ImportItem(Import { kind: clean::ImportKind::Simple(ref name, ..), .. }) => {
857+
Some(name.clone())
858+
}
859+
clean::MacroItem(..) => None,
798860
_ => item.name.clone(),
799861
};
800862

@@ -803,6 +865,8 @@ impl<'a, 'tcx> DocFolder for LinkCollector<'a, 'tcx> {
803865
}
804866

805867
// find item's parent to resolve `Self` in item's docs below
868+
// FIXME(#76467, #75809): this is a mess and doesn't handle cross-crate
869+
// re-exports
806870
let parent_name = self.cx.as_local_hir_id(item.def_id).and_then(|item_hir| {
807871
let parent_hir = self.cx.tcx.hir().get_parent_item(item_hir);
808872
let item_parent = self.cx.tcx.hir().find(parent_hir);
@@ -870,7 +934,6 @@ impl<'a, 'tcx> DocFolder for LinkCollector<'a, 'tcx> {
870934
};
871935
// NOTE: if there are links that start in one crate and end in another, this will not resolve them.
872936
// This is a degenerate case and it's not supported by rustdoc.
873-
// FIXME: this will break links that start in `#[doc = ...]` and end as a sugared doc. Should this be supported?
874937
for (ori_link, link_range) in markdown_links(&combined_docs) {
875938
let link = self.resolve_link(
876939
&item,
@@ -888,15 +951,13 @@ impl<'a, 'tcx> DocFolder for LinkCollector<'a, 'tcx> {
888951
}
889952
}
890953

891-
if item.is_mod() && !item.attrs.inner_docs {
892-
self.mod_ids.push(item.def_id);
893-
}
894-
895954
if item.is_mod() {
896-
let ret = self.fold_item_recur(item);
955+
if !item.attrs.inner_docs {
956+
self.mod_ids.push(item.def_id);
957+
}
897958

959+
let ret = self.fold_item_recur(item);
898960
self.mod_ids.pop();
899-
900961
ret
901962
} else {
902963
self.fold_item_recur(item)
@@ -905,6 +966,9 @@ impl<'a, 'tcx> DocFolder for LinkCollector<'a, 'tcx> {
905966
}
906967

907968
impl LinkCollector<'_, '_> {
969+
/// This is the entry point for resolving an intra-doc link.
970+
///
971+
/// FIXME(jynelson): this is way too many arguments
908972
fn resolve_link(
909973
&self,
910974
item: &Item,
@@ -943,129 +1007,120 @@ impl LinkCollector<'_, '_> {
9431007
} else {
9441008
(parts[0], None)
9451009
};
946-
let resolved_self;
947-
let link_text;
948-
let mut path_str;
949-
let disambiguator;
950-
let stripped_path_string;
951-
let (mut res, mut fragment) = {
952-
path_str = if let Ok((d, path)) = Disambiguator::from_str(&link) {
953-
disambiguator = Some(d);
954-
path
955-
} else {
956-
disambiguator = None;
957-
&link
958-
}
959-
.trim();
9601010

961-
if path_str.contains(|ch: char| !(ch.is_alphanumeric() || ":_<>, ".contains(ch))) {
962-
return None;
963-
}
1011+
// Parse and strip the disambiguator from the link, if present.
1012+
let (mut path_str, disambiguator) = if let Ok((d, path)) = Disambiguator::from_str(&link) {
1013+
(path.trim(), Some(d))
1014+
} else {
1015+
(link.trim(), None)
1016+
};
9641017

965-
// We stripped `()` and `!` when parsing the disambiguator.
966-
// Add them back to be displayed, but not prefix disambiguators.
967-
link_text = disambiguator
968-
.map(|d| d.display_for(path_str))
969-
.unwrap_or_else(|| path_str.to_owned());
970-
971-
// In order to correctly resolve intra-doc-links we need to
972-
// pick a base AST node to work from. If the documentation for
973-
// this module came from an inner comment (//!) then we anchor
974-
// our name resolution *inside* the module. If, on the other
975-
// hand it was an outer comment (///) then we anchor the name
976-
// resolution in the parent module on the basis that the names
977-
// used are more likely to be intended to be parent names. For
978-
// this, we set base_node to None for inner comments since
979-
// we've already pushed this node onto the resolution stack but
980-
// for outer comments we explicitly try and resolve against the
981-
// parent_node first.
982-
let base_node = if item.is_mod() && item.attrs.inner_docs {
983-
self.mod_ids.last().copied()
984-
} else {
985-
parent_node
986-
};
1018+
if path_str.contains(|ch: char| !(ch.is_alphanumeric() || ":_<>, ".contains(ch))) {
1019+
return None;
1020+
}
9871021

988-
let mut module_id = if let Some(id) = base_node {
989-
id
990-
} else {
991-
debug!("attempting to resolve item without parent module: {}", path_str);
992-
let err_kind = ResolutionFailure::NoParentItem.into();
993-
resolution_failure(
994-
self,
995-
&item,
996-
path_str,
997-
disambiguator,
998-
dox,
999-
link_range,
1000-
smallvec![err_kind],
1001-
);
1002-
return None;
1003-
};
1022+
// We stripped `()` and `!` when parsing the disambiguator.
1023+
// Add them back to be displayed, but not prefix disambiguators.
1024+
let link_text =
1025+
disambiguator.map(|d| d.display_for(path_str)).unwrap_or_else(|| path_str.to_owned());
1026+
1027+
// In order to correctly resolve intra-doc-links we need to
1028+
// pick a base AST node to work from. If the documentation for
1029+
// this module came from an inner comment (//!) then we anchor
1030+
// our name resolution *inside* the module. If, on the other
1031+
// hand it was an outer comment (///) then we anchor the name
1032+
// resolution in the parent module on the basis that the names
1033+
// used are more likely to be intended to be parent names. For
1034+
// this, we set base_node to None for inner comments since
1035+
// we've already pushed this node onto the resolution stack but
1036+
// for outer comments we explicitly try and resolve against the
1037+
// parent_node first.
1038+
let base_node = if item.is_mod() && item.attrs.inner_docs {
1039+
self.mod_ids.last().copied()
1040+
} else {
1041+
parent_node
1042+
};
10041043

1005-
// replace `Self` with suitable item's parent name
1006-
if path_str.starts_with("Self::") {
1007-
if let Some(ref name) = parent_name {
1008-
resolved_self = format!("{}::{}", name, &path_str[6..]);
1009-
path_str = &resolved_self;
1010-
}
1011-
} else if path_str.starts_with("crate::") {
1012-
use rustc_span::def_id::CRATE_DEF_INDEX;
1013-
1014-
// HACK(jynelson): rustc_resolve thinks that `crate` is the crate currently being documented.
1015-
// But rustdoc wants it to mean the crate this item was originally present in.
1016-
// To work around this, remove it and resolve relative to the crate root instead.
1017-
// HACK(jynelson)(2): If we just strip `crate::` then suddenly primitives become ambiguous
1018-
// (consider `crate::char`). Instead, change it to `self::`. This works because 'self' is now the crate root.
1019-
resolved_self = format!("self::{}", &path_str["crate::".len()..]);
1020-
path_str = &resolved_self;
1021-
module_id = DefId { krate, index: CRATE_DEF_INDEX };
1022-
}
1044+
let mut module_id = if let Some(id) = base_node {
1045+
id
1046+
} else {
1047+
// This is a bug.
1048+
debug!("attempting to resolve item without parent module: {}", path_str);
1049+
let err_kind = ResolutionFailure::NoParentItem.into();
1050+
resolution_failure(
1051+
self,
1052+
&item,
1053+
path_str,
1054+
disambiguator,
1055+
dox,
1056+
link_range,
1057+
smallvec![err_kind],
1058+
);
1059+
return None;
1060+
};
10231061

1024-
// Strip generics from the path.
1025-
if path_str.contains(['<', '>'].as_slice()) {
1026-
stripped_path_string = match strip_generics_from_path(path_str) {
1027-
Ok(path) => path,
1028-
Err(err_kind) => {
1029-
debug!("link has malformed generics: {}", path_str);
1030-
resolution_failure(
1031-
self,
1032-
&item,
1033-
path_str,
1034-
disambiguator,
1035-
dox,
1036-
link_range,
1037-
smallvec![err_kind],
1038-
);
1039-
return None;
1040-
}
1041-
};
1042-
path_str = &stripped_path_string;
1062+
let resolved_self;
1063+
// replace `Self` with suitable item's parent name
1064+
if path_str.starts_with("Self::") {
1065+
if let Some(ref name) = parent_name {
1066+
resolved_self = format!("{}::{}", name, &path_str[6..]);
1067+
path_str = &resolved_self;
10431068
}
1069+
} else if path_str.starts_with("crate::") {
1070+
use rustc_span::def_id::CRATE_DEF_INDEX;
1071+
1072+
// HACK(jynelson): rustc_resolve thinks that `crate` is the crate currently being documented.
1073+
// But rustdoc wants it to mean the crate this item was originally present in.
1074+
// To work around this, remove it and resolve relative to the crate root instead.
1075+
// HACK(jynelson)(2): If we just strip `crate::` then suddenly primitives become ambiguous
1076+
// (consider `crate::char`). Instead, change it to `self::`. This works because 'self' is now the crate root.
1077+
// FIXME(#78696): This doesn't always work.
1078+
resolved_self = format!("self::{}", &path_str["crate::".len()..]);
1079+
path_str = &resolved_self;
1080+
module_id = DefId { krate, index: CRATE_DEF_INDEX };
1081+
}
10441082

1045-
// Sanity check to make sure we don't have any angle brackets after stripping generics.
1046-
assert!(!path_str.contains(['<', '>'].as_slice()));
1083+
// Strip generics from the path.
1084+
let stripped_path_string;
1085+
if path_str.contains(['<', '>'].as_slice()) {
1086+
stripped_path_string = match strip_generics_from_path(path_str) {
1087+
Ok(path) => path,
1088+
Err(err_kind) => {
1089+
debug!("link has malformed generics: {}", path_str);
1090+
resolution_failure(
1091+
self,
1092+
&item,
1093+
path_str,
1094+
disambiguator,
1095+
dox,
1096+
link_range,
1097+
smallvec![err_kind],
1098+
);
1099+
return None;
1100+
}
1101+
};
1102+
path_str = &stripped_path_string;
1103+
}
1104+
// Sanity check to make sure we don't have any angle brackets after stripping generics.
1105+
assert!(!path_str.contains(['<', '>'].as_slice()));
10471106

1048-
// The link is not an intra-doc link if it still contains commas or spaces after
1049-
// stripping generics.
1050-
if path_str.contains([',', ' '].as_slice()) {
1051-
return None;
1052-
}
1107+
// The link is not an intra-doc link if it still contains commas or spaces after
1108+
// stripping generics.
1109+
if path_str.contains([',', ' '].as_slice()) {
1110+
return None;
1111+
}
10531112

1054-
match self.resolve_with_disambiguator(
1055-
disambiguator,
1056-
item,
1057-
dox,
1058-
path_str,
1059-
current_item,
1060-
module_id,
1061-
extra_fragment,
1062-
&ori_link,
1063-
link_range.clone(),
1064-
) {
1065-
Some(x) => x,
1066-
None => return None,
1067-
}
1068-
};
1113+
let (mut res, mut fragment) = self.resolve_with_disambiguator(
1114+
disambiguator,
1115+
item,
1116+
dox,
1117+
path_str,
1118+
current_item,
1119+
module_id,
1120+
extra_fragment,
1121+
&ori_link,
1122+
link_range.clone(),
1123+
)?;
10691124

10701125
// Check for a primitive which might conflict with a module
10711126
// Report the ambiguity and require that the user specify which one they meant.
@@ -1075,7 +1130,7 @@ impl LinkCollector<'_, '_> {
10751130
None | Some(Disambiguator::Namespace(Namespace::TypeNS) | Disambiguator::Primitive)
10761131
) && !matches!(res, Res::PrimTy(_))
10771132
{
1078-
if let Some((path, prim)) = is_primitive(path_str, TypeNS) {
1133+
if let Some((path, prim)) = resolve_primitive(path_str, TypeNS) {
10791134
// `prim@char`
10801135
if matches!(disambiguator, Some(Disambiguator::Primitive)) {
10811136
if fragment.is_some() {
@@ -1168,11 +1223,13 @@ impl LinkCollector<'_, '_> {
11681223
privacy_error(cx, &item, &path_str, dox, link_range);
11691224
}
11701225
}
1171-
let id = register_res(cx, res);
1226+
let id = clean::register_res(cx, res);
11721227
Some(ItemLink { link: ori_link, link_text, did: Some(id), fragment })
11731228
}
11741229
}
11751230

1231+
/// After parsing the disambiguator, resolve the main part of the link.
1232+
// FIXME(jynelson): wow this is just so much
11761233
fn resolve_with_disambiguator(
11771234
&self,
11781235
disambiguator: Option<Disambiguator>,
@@ -1232,7 +1289,7 @@ impl LinkCollector<'_, '_> {
12321289
// Try everything!
12331290
let mut candidates = PerNS {
12341291
macro_ns: self
1235-
.macro_resolve(path_str, base_node)
1292+
.resolve_macro(path_str, base_node)
12361293
.map(|res| (res, extra_fragment.clone())),
12371294
type_ns: match self.resolve(
12381295
path_str,
@@ -1320,10 +1377,10 @@ impl LinkCollector<'_, '_> {
13201377
}
13211378
}
13221379
Some(MacroNS) => {
1323-
match self.macro_resolve(path_str, base_node) {
1380+
match self.resolve_macro(path_str, base_node) {
13241381
Ok(res) => Some((res, extra_fragment)),
13251382
Err(mut kind) => {
1326-
// `macro_resolve` only looks in the macro namespace. Try to give a better error if possible.
1383+
// `resolve_macro` only looks in the macro namespace. Try to give a better error if possible.
13271384
for &ns in &[TypeNS, ValueNS] {
13281385
if let Some(res) = self.check_full_res(
13291386
ns,
@@ -1354,9 +1411,15 @@ impl LinkCollector<'_, '_> {
13541411
}
13551412

13561413
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
1414+
/// Disambiguators for a link.
13571415
enum Disambiguator {
1416+
/// `prim@`
1417+
///
1418+
/// This is buggy, see <https://github.com/rust-lang/rust/pull/77875#discussion_r503583103>
13581419
Primitive,
1420+
/// `struct@` or `f()`
13591421
Kind(DefKind),
1422+
/// `type@`
13601423
Namespace(Namespace),
13611424
}
13621425

@@ -1373,7 +1436,7 @@ impl Disambiguator {
13731436
}
13741437
}
13751438

1376-
/// (disambiguator, path_str)
1439+
/// Given a link, parse and return `(disambiguator, path_str)`
13771440
fn from_str(link: &str) -> Result<(Self, &str), ()> {
13781441
use Disambiguator::{Kind, Namespace as NS, Primitive};
13791442

@@ -1424,6 +1487,7 @@ impl Disambiguator {
14241487
}
14251488
}
14261489

1490+
/// Used for error reporting.
14271491
fn suggestion(self) -> Suggestion {
14281492
let kind = match self {
14291493
Disambiguator::Primitive => return Suggestion::Prefix("prim"),
@@ -1490,9 +1554,13 @@ impl Disambiguator {
14901554
}
14911555
}
14921556

1557+
/// A suggestion to show in a diagnostic.
14931558
enum Suggestion {
1559+
/// `struct@`
14941560
Prefix(&'static str),
1561+
/// `f()`
14951562
Function,
1563+
/// `m!`
14961564
Macro,
14971565
}
14981566

@@ -1582,6 +1650,11 @@ fn report_diagnostic(
15821650
});
15831651
}
15841652

1653+
/// Reports a link that failed to resolve.
1654+
///
1655+
/// This also tries to resolve any intermediate path segments that weren't
1656+
/// handled earlier. For example, if passed `Item::Crate(std)` and `path_str`
1657+
/// `std::io::Error::x`, this will resolve `std::io::Error`.
15851658
fn resolution_failure(
15861659
collector: &LinkCollector<'_, '_>,
15871660
item: &Item,
@@ -1816,6 +1889,7 @@ fn resolution_failure(
18161889
);
18171890
}
18181891

1892+
/// Report an anchor failure.
18191893
fn anchor_failure(
18201894
cx: &DocContext<'_>,
18211895
item: &Item,
@@ -1840,6 +1914,7 @@ fn anchor_failure(
18401914
});
18411915
}
18421916

1917+
/// Report an ambiguity error, where there were multiple possible resolutions.
18431918
fn ambiguity_error(
18441919
cx: &DocContext<'_>,
18451920
item: &Item,
@@ -1886,6 +1961,8 @@ fn ambiguity_error(
18861961
});
18871962
}
18881963

1964+
/// In case of an ambiguity or mismatched disambiguator, suggest the correct
1965+
/// disambiguator.
18891966
fn suggest_disambiguator(
18901967
disambiguator: Disambiguator,
18911968
diag: &mut DiagnosticBuilder<'_>,
@@ -1911,6 +1988,7 @@ fn suggest_disambiguator(
19111988
}
19121989
}
19131990

1991+
/// Report a link from a public item to a private one.
19141992
fn privacy_error(
19151993
cx: &DocContext<'_>,
19161994
item: &Item,
@@ -1978,7 +2056,8 @@ const PRIMITIVES: &[(Symbol, Res)] = &[
19782056
(sym::char, Res::PrimTy(hir::PrimTy::Char)),
19792057
];
19802058

1981-
fn is_primitive(path_str: &str, ns: Namespace) -> Option<(Symbol, Res)> {
2059+
/// Resolve a primitive type or value.
2060+
fn resolve_primitive(path_str: &str, ns: Namespace) -> Option<(Symbol, Res)> {
19822061
is_bool_value(path_str, ns).or_else(|| {
19832062
if ns == TypeNS {
19842063
// FIXME: this should be replaced by a lookup in PrimitiveTypeTable
@@ -1990,6 +2069,7 @@ fn is_primitive(path_str: &str, ns: Namespace) -> Option<(Symbol, Res)> {
19902069
})
19912070
}
19922071

2072+
/// Resolve a primitive value.
19932073
fn is_bool_value(path_str: &str, ns: Namespace) -> Option<(Symbol, Res)> {
19942074
if ns == TypeNS && (path_str == "true" || path_str == "false") {
19952075
Some((sym::bool, Res::PrimTy(hir::PrimTy::Bool)))

0 commit comments

Comments
 (0)
Please sign in to comment.