Skip to content

Commit f727dca

Browse files
committed
Move variant_res to early pass
1 parent 06567a0 commit f727dca

File tree

3 files changed

+151
-43
lines changed

3 files changed

+151
-43
lines changed

src/librustdoc/passes/collect_intra_doc_links.rs

Lines changed: 8 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -67,12 +67,19 @@ impl<'a> From<ResolutionFailure<'a>> for ErrorKind<'a> {
6767
}
6868
}
6969

70+
crate enum EarlyResult {
71+
Resolved(Res, Option<String>),
72+
Unresolved(UnresolvedLink),
73+
UnresolvedVariant(Res),
74+
Error(ErrorKind<'static>),
75+
}
76+
7077
crate struct UnresolvedLink {
7178
/// The resolution for all path segments excluding the last.
7279
///
7380
/// For example, in `[a::b::c]`, it will hold the Res for `a::b`.
7481
/// This is used for `resolve_associated_item`.
75-
ty_res: Res,
82+
ty_res: Option<Res>,
7683
/// The resolution for all path segments excluding the last two.
7784
///
7885
/// For example, in `[a::b::c]`, it will hold the Res for `a`.
@@ -1126,27 +1133,6 @@ fn privacy_error(cx: &DocContext<'_>, diag_info: &DiagnosticInfo<'_>, path_str:
11261133
});
11271134
}
11281135

1129-
/// Given an enum variant's res, return the res of its enum and the associated fragment.
1130-
fn handle_variant(
1131-
cx: &DocContext<'_>,
1132-
res: Res,
1133-
extra_fragment: &Option<String>,
1134-
) -> Result<(Res, Option<String>), ErrorKind<'static>> {
1135-
use rustc_middle::ty::DefIdTree;
1136-
1137-
if extra_fragment.is_some() {
1138-
return Err(ErrorKind::AnchorFailure(AnchorFailure::RustdocAnchorConflict(res)));
1139-
}
1140-
cx.tcx
1141-
.parent(res.def_id())
1142-
.map(|parent| {
1143-
let parent_def = Res::Def(DefKind::Enum, parent);
1144-
let variant = cx.tcx.expect_variant_res(res.as_hir_res().unwrap());
1145-
(parent_def, Some(format!("variant.{}", variant.ident.name)))
1146-
})
1147-
.ok_or_else(|| ResolutionFailure::NoParentItem.into())
1148-
}
1149-
11501136
/// Resolve a primitive type or value.
11511137
fn resolve_primitive(path_str: &str, ns: Namespace) -> Option<Res> {
11521138
if ns != TypeNS {

src/librustdoc/passes/collect_intra_doc_links/early.rs

Lines changed: 122 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
11
use rustc_ast as ast;
2-
use rustc_hir::def::Namespace::TypeNS;
2+
use rustc_hir::def::Namespace::{self, *};
33
use rustc_hir::def_id::{DefId, LocalDefId, CRATE_DEF_INDEX};
44
use rustc_interface::interface;
5+
use rustc_resolve::Resolver;
56

67
use std::cell::RefCell;
78
use std::mem;
89
use std::rc::Rc;
910

11+
use super::*;
12+
1013
// Letting the resolver escape at the end of the function leads to inconsistencies between the
1114
// crates the TyCtxt sees and the resolver sees (because the resolver could load more crates
1215
// after escaping). Hopefully `IntraLinkCrateLoader` gets all the crates we need ...
@@ -20,6 +23,124 @@ impl IntraLinkCrateLoader {
2023
let crate_id = LocalDefId { local_def_index: CRATE_DEF_INDEX }.to_def_id();
2124
Self { current_mod: crate_id, resolver }
2225
}
26+
27+
crate fn enter_resolver<F, R>(&self, f: F) -> R
28+
where
29+
F: FnOnce(&mut Resolver<'_>) -> R,
30+
{
31+
self.resolver.borrow_mut().access(f)
32+
}
33+
34+
}
35+
36+
impl IntraLinkCrateLoader {
37+
/// Convenience wrapper around `resolve_str_path_error`.
38+
///
39+
/// This also handles resolving `true` and `false` as booleans.
40+
/// NOTE: `resolve_str_path_error` knows only about paths, not about types.
41+
/// Associated items will never be resolved by this function.
42+
fn resolve_path(&self, path_str: &str, ns: Namespace, module_id: DefId) -> Option<Res> {
43+
let result = self.enter_resolver(|resolver| {
44+
resolver
45+
.resolve_str_path_error(DUMMY_SP, &path_str, ns, module_id)
46+
.and_then(|(_, res)| res.try_into())
47+
});
48+
debug!("{} resolved to {:?} in namespace {:?}", path_str, result, ns);
49+
match result {
50+
// resolver doesn't know about true, false, and types that aren't paths (e.g. `()`)
51+
// manually as bool
52+
Err(()) => resolve_primitive(path_str, ns),
53+
Ok(res) => Some(res),
54+
}
55+
}
56+
57+
/// Resolves a string as a path within a particular namespace. Returns an
58+
/// optional URL fragment in the case of variants and methods.
59+
fn resolve<'path>(
60+
&mut self,
61+
path_str: &'path str,
62+
ns: Namespace,
63+
module_id: DefId,
64+
extra_fragment: &Option<String>,
65+
) -> EarlyResult {
66+
if let Some(res) = self.resolve_path(path_str, ns, module_id) {
67+
match res {
68+
// FIXME(#76467): make this fallthrough to lookup the associated
69+
// item a separate function.
70+
Res::Def(DefKind::AssocFn | DefKind::AssocConst, _) => assert_eq!(ns, ValueNS),
71+
Res::Def(DefKind::AssocTy, _) => assert_eq!(ns, TypeNS),
72+
Res::Def(DefKind::Variant, _) => {
73+
return EarlyResult::UnresolvedVariant(res);
74+
}
75+
// Not a trait item; just return what we found.
76+
Res::Primitive(ty) => {
77+
if extra_fragment.is_some() {
78+
return EarlyResult::Error(ErrorKind::AnchorFailure(
79+
AnchorFailure::RustdocAnchorConflict(res),
80+
));
81+
}
82+
return EarlyResult::Resolved(res, Some(ty.as_str().to_owned()));
83+
}
84+
_ => return EarlyResult::Resolved(res, extra_fragment.clone()),
85+
}
86+
}
87+
88+
// Try looking for methods and associated items.
89+
let mut split = path_str.rsplitn(2, "::");
90+
// NB: `split`'s first element is always defined, even if the delimiter was not present.
91+
// NB: `item_str` could be empty when resolving in the root namespace (e.g. `::std`).
92+
let item_str = split.next().unwrap();
93+
let item_name = Symbol::intern(item_str);
94+
let path_root = match split.next() {
95+
Some(r) => r.to_owned(),
96+
None => {
97+
// If there's no `::`, it's not an associated item.
98+
// So we can be sure that `rustc_resolve` was accurate when it said it wasn't resolved.
99+
debug!("found no `::`, assumming {} was correctly not in scope", item_name);
100+
return EarlyResult::Error(ResolutionFailure::NotResolved {
101+
module_id,
102+
partial_res: None,
103+
unresolved: item_str.into(),
104+
}.into());
105+
}
106+
};
107+
108+
// FIXME(#83862): this arbitrarily gives precedence to primitives over modules to support
109+
// links to primitives when `#[doc(primitive)]` is present. It should give an ambiguity
110+
// error instead and special case *only* modules with `#[doc(primitive)]`, not all
111+
// primitives.
112+
let ty_res = resolve_primitive(&path_root, TypeNS)
113+
.or_else(|| self.resolve_path(&path_root, TypeNS, module_id));
114+
let variant_res = if ns == Namespace::ValueNS {
115+
self.variant_res(path_str, module_id)
116+
} else {
117+
None
118+
};
119+
EarlyResult::Unresolved(UnresolvedLink {
120+
ty_res, variant_res
121+
})
122+
}
123+
124+
fn variant_res(&self, path_str: &str, module_id: DefId) -> Option<Res> {
125+
debug!("looking for enum variant {}", path_str);
126+
let mut split = path_str.rsplitn(3, "::");
127+
let (variant_field_str, variant_field_name) = split
128+
.next()
129+
.map(|f| (f, Symbol::intern(f)))
130+
.expect("fold_item should ensure link is non-empty");
131+
// we're not sure this is a variant at all, so use the full string
132+
// If there's no second component, the link looks like `[path]`.
133+
// So there's no partial res and we should say the whole link failed to resolve.
134+
let variant_str = split.next()?;
135+
let variant_name = Symbol::intern(variant_str);
136+
let path = split.next()?.to_owned();
137+
self
138+
.enter_resolver(|resolver| {
139+
resolver.resolve_str_path_error(DUMMY_SP, &path, TypeNS, module_id)
140+
})
141+
.and_then(|(_, res)| res.try_into())
142+
.ok()
143+
}
23144
}
24145

25146
impl ast::visit::Visitor<'_> for IntraLinkCrateLoader {

src/librustdoc/passes/collect_intra_doc_links/late.rs

Lines changed: 21 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -179,26 +179,6 @@ impl<'a, 'tcx> LinkCollector<'a, 'tcx> {
179179
})
180180
}
181181

182-
/// Convenience wrapper around `resolve_str_path_error`.
183-
///
184-
/// This also handles resolving `true` and `false` as booleans.
185-
/// NOTE: `resolve_str_path_error` knows only about paths, not about types.
186-
/// Associated items will never be resolved by this function.
187-
fn resolve_path(&self, path_str: &str, ns: Namespace, module_id: DefId) -> Option<Res> {
188-
let result = self.cx.enter_resolver(|resolver| {
189-
resolver
190-
.resolve_str_path_error(DUMMY_SP, &path_str, ns, module_id)
191-
.and_then(|(_, res)| res.try_into())
192-
});
193-
debug!("{} resolved to {:?} in namespace {:?}", path_str, result, ns);
194-
match result {
195-
// resolver doesn't know about true, false, and types that aren't paths (e.g. `()`)
196-
// manually as bool
197-
Err(()) => resolve_primitive(path_str, ns),
198-
Ok(res) => Some(res),
199-
}
200-
}
201-
202182
/// Resolves a string as a path within a particular namespace. Returns an
203183
/// optional URL fragment in the case of variants and methods.
204184
fn resolve<'path>(
@@ -991,3 +971,24 @@ impl LinkCollector<'_, '_> {
991971
}
992972
}
993973
}
974+
975+
/// Given an enum variant's res, return the res of its enum and the associated fragment.
976+
fn handle_variant(
977+
cx: &DocContext<'_>,
978+
res: Res,
979+
extra_fragment: &Option<String>,
980+
) -> Result<(Res, Option<String>), ErrorKind<'static>> {
981+
use rustc_middle::ty::DefIdTree;
982+
983+
if extra_fragment.is_some() {
984+
return Err(ErrorKind::AnchorFailure(AnchorFailure::RustdocAnchorConflict(res)));
985+
}
986+
cx.tcx
987+
.parent(res.def_id())
988+
.map(|parent| {
989+
let parent_def = Res::Def(DefKind::Enum, parent);
990+
let variant = cx.tcx.expect_variant_res(res.as_hir_res().unwrap());
991+
(parent_def, Some(format!("variant.{}", variant.ident.name)))
992+
})
993+
.ok_or_else(|| ResolutionFailure::NoParentItem.into())
994+
}

0 commit comments

Comments
 (0)