]>
Commit | Line | Data |
---|---|---|
29967ef6 XL |
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 | ||
04454e1e FG |
5 | use pulldown_cmark::LinkType; |
6 | use rustc_ast::util::comments::may_have_doc_links; | |
5e7ed085 FG |
7 | use rustc_data_structures::{fx::FxHashMap, intern::Interned, stable_set::FxHashSet}; |
8 | use rustc_errors::{Applicability, Diagnostic}; | |
04454e1e FG |
9 | use rustc_hir::def::Namespace::*; |
10 | use rustc_hir::def::{DefKind, Namespace, PerNS}; | |
11 | use rustc_hir::def_id::{DefId, CRATE_DEF_ID}; | |
5e7ed085 | 12 | use rustc_hir::Mutability; |
5099ac24 | 13 | use rustc_middle::ty::{DefIdTree, Ty, TyCtxt}; |
04454e1e FG |
14 | use rustc_middle::{bug, ty}; |
15 | use rustc_resolve::ParentScope; | |
6a06907d | 16 | use rustc_session::lint::Lint; |
5099ac24 | 17 | use rustc_span::hygiene::MacroKind; |
5869c6ff | 18 | use rustc_span::symbol::{sym, Ident, Symbol}; |
04454e1e | 19 | use rustc_span::BytePos; |
1b1a35ee | 20 | use smallvec::{smallvec, SmallVec}; |
b7449926 | 21 | |
1b1a35ee | 22 | use std::borrow::Cow; |
29967ef6 | 23 | use std::mem; |
b7449926 XL |
24 | use std::ops::Range; |
25 | ||
5e7ed085 FG |
26 | use crate::clean::{self, utils::find_nearest_parent_module}; |
27 | use crate::clean::{Crate, Item, ItemId, ItemLink, PrimitiveType}; | |
9fa01778 | 28 | use crate::core::DocContext; |
5869c6ff | 29 | use crate::html::markdown::{markdown_links, MarkdownLink}; |
6a06907d | 30 | use crate::lint::{BROKEN_INTRA_DOC_LINKS, PRIVATE_INTRA_DOC_LINKS}; |
3dfed10e | 31 | use crate::passes::Pass; |
3c0e092e | 32 | use crate::visit::DocVisitor; |
0bf4aa26 | 33 | |
94222f64 | 34 | mod early; |
923072b8 | 35 | pub(crate) use early::early_resolve_intra_doc_links; |
94222f64 | 36 | |
923072b8 | 37 | pub(crate) const COLLECT_INTRA_DOC_LINKS: Pass = Pass { |
532ac7d7 | 38 | name: "collect-intra-doc-links", |
60c5eb7d | 39 | run: collect_intra_doc_links, |
5869c6ff | 40 | description: "resolves intra-doc links", |
532ac7d7 | 41 | }; |
b7449926 | 42 | |
cdc7bbd5 | 43 | fn collect_intra_doc_links(krate: Crate, cx: &mut DocContext<'_>) -> Crate { |
5099ac24 FG |
44 | let mut collector = |
45 | LinkCollector { cx, mod_ids: Vec::new(), visited_links: FxHashMap::default() }; | |
3c0e092e XL |
46 | collector.visit_crate(&krate); |
47 | krate | |
1b1a35ee | 48 | } |
b7449926 | 49 | |
5869c6ff XL |
50 | #[derive(Copy, Clone, Debug, Hash)] |
51 | enum Res { | |
52 | Def(DefKind, DefId), | |
53 | Primitive(PrimitiveType), | |
54 | } | |
55 | ||
56 | type ResolveRes = rustc_hir::def::Res<rustc_ast::NodeId>; | |
57 | ||
58 | impl Res { | |
59 | fn descr(self) -> &'static str { | |
60 | match self { | |
61 | Res::Def(kind, id) => ResolveRes::Def(kind, id).descr(), | |
62 | Res::Primitive(_) => "builtin type", | |
63 | } | |
64 | } | |
65 | ||
66 | fn article(self) -> &'static str { | |
67 | match self { | |
68 | Res::Def(kind, id) => ResolveRes::Def(kind, id).article(), | |
69 | Res::Primitive(_) => "a", | |
70 | } | |
71 | } | |
72 | ||
17df50a5 | 73 | fn name(self, tcx: TyCtxt<'_>) -> Symbol { |
5869c6ff | 74 | match self { |
17df50a5 XL |
75 | Res::Def(_, id) => tcx.item_name(id), |
76 | Res::Primitive(prim) => prim.as_sym(), | |
5869c6ff XL |
77 | } |
78 | } | |
79 | ||
c295e0f8 | 80 | fn def_id(self, tcx: TyCtxt<'_>) -> DefId { |
5869c6ff | 81 | match self { |
c295e0f8 XL |
82 | Res::Def(_, id) => id, |
83 | Res::Primitive(prim) => *PrimitiveType::primitive_locations(tcx).get(&prim).unwrap(), | |
5869c6ff XL |
84 | } |
85 | } | |
86 | ||
04454e1e FG |
87 | fn from_def_id(tcx: TyCtxt<'_>, def_id: DefId) -> Res { |
88 | Res::Def(tcx.def_kind(def_id), def_id) | |
5869c6ff | 89 | } |
5099ac24 FG |
90 | |
91 | /// Used for error reporting. | |
92 | fn disambiguator_suggestion(self) -> Suggestion { | |
93 | let kind = match self { | |
94 | Res::Primitive(_) => return Suggestion::Prefix("prim"), | |
95 | Res::Def(kind, _) => kind, | |
96 | }; | |
97 | if kind == DefKind::Macro(MacroKind::Bang) { | |
98 | return Suggestion::Macro; | |
99 | } else if kind == DefKind::Fn || kind == DefKind::AssocFn { | |
100 | return Suggestion::Function; | |
101 | } else if kind == DefKind::Field { | |
102 | return Suggestion::RemoveDisambiguator; | |
103 | } | |
104 | ||
105 | let prefix = match kind { | |
106 | DefKind::Struct => "struct", | |
107 | DefKind::Enum => "enum", | |
108 | DefKind::Trait => "trait", | |
109 | DefKind::Union => "union", | |
110 | DefKind::Mod => "mod", | |
111 | DefKind::Const | DefKind::ConstParam | DefKind::AssocConst | DefKind::AnonConst => { | |
112 | "const" | |
113 | } | |
5e7ed085 | 114 | DefKind::Static(_) => "static", |
5099ac24 FG |
115 | DefKind::Macro(MacroKind::Derive) => "derive", |
116 | // Now handle things that don't have a specific disambiguator | |
117 | _ => match kind | |
118 | .ns() | |
119 | .expect("tried to calculate a disambiguator for a def without a namespace?") | |
120 | { | |
121 | Namespace::TypeNS => "type", | |
122 | Namespace::ValueNS => "value", | |
123 | Namespace::MacroNS => "macro", | |
124 | }, | |
125 | }; | |
126 | ||
127 | Suggestion::Prefix(prefix) | |
128 | } | |
5869c6ff XL |
129 | } |
130 | ||
131 | impl TryFrom<ResolveRes> for Res { | |
132 | type Error = (); | |
133 | ||
134 | fn try_from(res: ResolveRes) -> Result<Self, ()> { | |
135 | use rustc_hir::def::Res::*; | |
136 | match res { | |
137 | Def(kind, id) => Ok(Res::Def(kind, id)), | |
138 | PrimTy(prim) => Ok(Res::Primitive(PrimitiveType::from_hir(prim))), | |
139 | // e.g. `#[derive]` | |
140 | NonMacroAttr(..) | Err => Result::Err(()), | |
141 | other => bug!("unrecognized res {:?}", other), | |
142 | } | |
143 | } | |
144 | } | |
145 | ||
04454e1e FG |
146 | /// The link failed to resolve. [`resolution_failure`] should look to see if there's |
147 | /// a more helpful error that can be given. | |
148 | #[derive(Debug)] | |
149 | struct UnresolvedPath<'a> { | |
150 | /// Item on which the link is resolved, used for resolving `Self`. | |
151 | item_id: ItemId, | |
152 | /// The scope the link was resolved in. | |
153 | module_id: DefId, | |
154 | /// If part of the link resolved, this has the `Res`. | |
155 | /// | |
156 | /// In `[std::io::Error::x]`, `std::io::Error` would be a partial resolution. | |
157 | partial_res: Option<Res>, | |
158 | /// The remaining unresolved path segments. | |
159 | /// | |
160 | /// In `[std::io::Error::x]`, `x` would be unresolved. | |
161 | unresolved: Cow<'a, str>, | |
162 | } | |
163 | ||
6a06907d | 164 | #[derive(Debug)] |
1b1a35ee XL |
165 | enum ResolutionFailure<'a> { |
166 | /// This resolved, but with the wrong namespace. | |
6a06907d XL |
167 | WrongNamespace { |
168 | /// What the link resolved to. | |
169 | res: Res, | |
170 | /// The expected namespace for the resolution, determined from the link's disambiguator. | |
171 | /// | |
172 | /// E.g., for `[fn@Result]` this is [`Namespace::ValueNS`], | |
173 | /// even though `Result`'s actual namespace is [`Namespace::TypeNS`]. | |
174 | expected_ns: Namespace, | |
175 | }, | |
04454e1e | 176 | NotResolved(UnresolvedPath<'a>), |
1b1a35ee XL |
177 | } |
178 | ||
04454e1e | 179 | #[derive(Clone, Copy, Debug)] |
29967ef6 XL |
180 | enum MalformedGenerics { |
181 | /// This link has unbalanced angle brackets. | |
182 | /// | |
183 | /// For example, `Vec<T` should trigger this, as should `Vec<T>>`. | |
184 | UnbalancedAngleBrackets, | |
185 | /// The generics are not attached to a type. | |
186 | /// | |
187 | /// For example, `<T>` should trigger this. | |
188 | /// | |
189 | /// This is detected by checking if the path is empty after the generics are stripped. | |
190 | MissingType, | |
191 | /// The link uses fully-qualified syntax, which is currently unsupported. | |
192 | /// | |
193 | /// For example, `<Vec as IntoIterator>::into_iter` should trigger this. | |
194 | /// | |
195 | /// This is detected by checking if ` as ` (the keyword `as` with spaces around it) is inside | |
196 | /// angle brackets. | |
197 | HasFullyQualifiedSyntax, | |
198 | /// The link has an invalid path separator. | |
199 | /// | |
200 | /// For example, `Vec:<T>:new()` should trigger this. Note that `Vec:new()` will **not** | |
201 | /// trigger this because it has no generics and thus [`strip_generics_from_path`] will not be | |
202 | /// called. | |
203 | /// | |
204 | /// Note that this will also **not** be triggered if the invalid path separator is inside angle | |
205 | /// brackets because rustdoc mostly ignores what's inside angle brackets (except for | |
206 | /// [`HasFullyQualifiedSyntax`](MalformedGenerics::HasFullyQualifiedSyntax)). | |
207 | /// | |
208 | /// This is detected by checking if there is a colon followed by a non-colon in the link. | |
209 | InvalidPathSeparator, | |
210 | /// The link has too many angle brackets. | |
211 | /// | |
212 | /// For example, `Vec<<T>>` should trigger this. | |
213 | TooManyAngleBrackets, | |
214 | /// The link has empty angle brackets. | |
215 | /// | |
216 | /// For example, `Vec<>` should trigger this. | |
217 | EmptyAngleBrackets, | |
218 | } | |
219 | ||
a2a8927a | 220 | #[derive(Clone, Debug, Hash, PartialEq, Eq)] |
923072b8 FG |
221 | pub(crate) enum UrlFragment { |
222 | Item(DefId), | |
a2a8927a XL |
223 | UserWritten(String), |
224 | } | |
225 | ||
226 | impl UrlFragment { | |
5099ac24 | 227 | /// Render the fragment, including the leading `#`. |
923072b8 FG |
228 | pub(crate) fn render(&self, s: &mut String, tcx: TyCtxt<'_>) { |
229 | s.push('#'); | |
5099ac24 | 230 | match self { |
923072b8 FG |
231 | &UrlFragment::Item(def_id) => { |
232 | let kind = match tcx.def_kind(def_id) { | |
233 | DefKind::AssocFn => { | |
234 | if tcx.associated_item(def_id).defaultness.has_value() { | |
235 | "method." | |
236 | } else { | |
237 | "tymethod." | |
238 | } | |
5099ac24 | 239 | } |
923072b8 FG |
240 | DefKind::AssocConst => "associatedconstant.", |
241 | DefKind::AssocTy => "associatedtype.", | |
242 | DefKind::Variant => "variant.", | |
243 | DefKind::Field => { | |
244 | let parent_id = tcx.parent(def_id); | |
245 | if tcx.def_kind(parent_id) == DefKind::Variant { | |
246 | s.push_str("variant."); | |
247 | s.push_str(tcx.item_name(parent_id).as_str()); | |
248 | ".field." | |
249 | } else { | |
250 | "structfield." | |
251 | } | |
252 | } | |
253 | kind => bug!("unexpected associated item kind: {:?}", kind), | |
254 | }; | |
255 | s.push_str(kind); | |
256 | s.push_str(tcx.item_name(def_id).as_str()); | |
a2a8927a | 257 | } |
923072b8 | 258 | UrlFragment::UserWritten(raw) => s.push_str(&raw), |
a2a8927a XL |
259 | } |
260 | } | |
261 | } | |
262 | ||
fc512014 XL |
263 | #[derive(Clone, Debug, Hash, PartialEq, Eq)] |
264 | struct ResolutionInfo { | |
5e7ed085 | 265 | item_id: ItemId, |
fc512014 XL |
266 | module_id: DefId, |
267 | dis: Option<Disambiguator>, | |
268 | path_str: String, | |
5099ac24 | 269 | extra_fragment: Option<String>, |
fc512014 XL |
270 | } |
271 | ||
cdc7bbd5 | 272 | #[derive(Clone)] |
fc512014 XL |
273 | struct DiagnosticInfo<'a> { |
274 | item: &'a Item, | |
275 | dox: &'a str, | |
276 | ori_link: &'a str, | |
277 | link_range: Range<usize>, | |
278 | } | |
279 | ||
532ac7d7 | 280 | struct LinkCollector<'a, 'tcx> { |
6a06907d | 281 | cx: &'a mut DocContext<'tcx>, |
29967ef6 XL |
282 | /// A stack of modules used to decide what scope to resolve in. |
283 | /// | |
284 | /// The last module will be used if the parent scope of the current item is | |
285 | /// unknown. | |
3dfed10e | 286 | mod_ids: Vec<DefId>, |
5869c6ff XL |
287 | /// Cache the resolved links so we can avoid resolving (and emitting errors for) the same link. |
288 | /// The link will be `None` if it could not be resolved (i.e. the error was cached). | |
923072b8 | 289 | visited_links: FxHashMap<ResolutionInfo, Option<(Res, Option<UrlFragment>)>>, |
b7449926 XL |
290 | } |
291 | ||
532ac7d7 | 292 | impl<'a, 'tcx> LinkCollector<'a, 'tcx> { |
29967ef6 XL |
293 | /// Given a full link, parse it as an [enum struct variant]. |
294 | /// | |
295 | /// In particular, this will return an error whenever there aren't three | |
296 | /// full path segments left in the link. | |
297 | /// | |
5099ac24 | 298 | /// [enum struct variant]: rustc_hir::VariantData::Struct |
a2a8927a | 299 | fn variant_field<'path>( |
60c5eb7d | 300 | &self, |
1b1a35ee | 301 | path_str: &'path str, |
5e7ed085 | 302 | item_id: ItemId, |
3dfed10e | 303 | module_id: DefId, |
04454e1e | 304 | ) -> Result<(Res, DefId), UnresolvedPath<'path>> { |
6a06907d | 305 | let tcx = self.cx.tcx; |
04454e1e | 306 | let no_res = || UnresolvedPath { |
5e7ed085 | 307 | item_id, |
3c0e092e | 308 | module_id, |
1b1a35ee XL |
309 | partial_res: None, |
310 | unresolved: path_str.into(), | |
311 | }; | |
60c5eb7d | 312 | |
1b1a35ee | 313 | debug!("looking for enum variant {}", path_str); |
60c5eb7d | 314 | let mut split = path_str.rsplitn(3, "::"); |
a2a8927a | 315 | let variant_field_name = split |
1b1a35ee | 316 | .next() |
a2a8927a | 317 | .map(|f| Symbol::intern(f)) |
1b1a35ee | 318 | .expect("fold_item should ensure link is non-empty"); |
a2a8927a | 319 | let variant_name = |
1b1a35ee XL |
320 | // we're not sure this is a variant at all, so use the full string |
321 | // If there's no second component, the link looks like `[path]`. | |
322 | // So there's no partial res and we should say the whole link failed to resolve. | |
a2a8927a | 323 | split.next().map(|f| Symbol::intern(f)).ok_or_else(no_res)?; |
60c5eb7d XL |
324 | let path = split |
325 | .next() | |
fc512014 | 326 | .map(|f| f.to_owned()) |
1b1a35ee XL |
327 | // If there's no third component, we saw `[a::b]` before and it failed to resolve. |
328 | // So there's no partial res. | |
329 | .ok_or_else(no_res)?; | |
5e7ed085 | 330 | let ty_res = self.resolve_path(&path, TypeNS, item_id, module_id).ok_or_else(no_res)?; |
5869c6ff | 331 | |
60c5eb7d | 332 | match ty_res { |
04454e1e FG |
333 | Res::Def(DefKind::Enum, did) => match tcx.type_of(did).kind() { |
334 | ty::Adt(def, _) if def.is_enum() => { | |
335 | if let Some(field) = def.all_fields().find(|f| f.name == variant_field_name) { | |
336 | Ok((ty_res, field.did)) | |
337 | } else { | |
338 | Err(UnresolvedPath { | |
339 | item_id, | |
340 | module_id, | |
341 | partial_res: Some(Res::Def(DefKind::Enum, def.did())), | |
342 | unresolved: variant_field_name.to_string().into(), | |
343 | }) | |
60c5eb7d | 344 | } |
60c5eb7d | 345 | } |
04454e1e FG |
346 | _ => unreachable!(), |
347 | }, | |
348 | _ => Err(UnresolvedPath { | |
5e7ed085 | 349 | item_id, |
1b1a35ee XL |
350 | module_id, |
351 | partial_res: Some(ty_res), | |
a2a8927a | 352 | unresolved: variant_name.to_string().into(), |
04454e1e | 353 | }), |
b7449926 XL |
354 | } |
355 | } | |
356 | ||
29967ef6 | 357 | /// Given a primitive type, try to resolve an associated item. |
29967ef6 XL |
358 | fn resolve_primitive_associated_item( |
359 | &self, | |
5869c6ff | 360 | prim_ty: PrimitiveType, |
29967ef6 | 361 | ns: Namespace, |
29967ef6 | 362 | item_name: Symbol, |
04454e1e | 363 | ) -> Option<(Res, DefId)> { |
6a06907d | 364 | let tcx = self.cx.tcx; |
29967ef6 | 365 | |
5e7ed085 | 366 | prim_ty.impls(tcx).find_map(|impl_| { |
cdc7bbd5 XL |
367 | tcx.associated_items(impl_) |
368 | .find_by_name_and_namespace(tcx, Ident::with_dummy_span(item_name), ns, impl_) | |
04454e1e | 369 | .map(|item| (Res::Primitive(prim_ty), item.def_id)) |
f035d41b XL |
370 | }) |
371 | } | |
1b1a35ee | 372 | |
5e7ed085 FG |
373 | fn resolve_self_ty(&self, path_str: &str, ns: Namespace, item_id: ItemId) -> Option<Res> { |
374 | if ns != TypeNS || path_str != "Self" { | |
375 | return None; | |
376 | } | |
377 | ||
378 | let tcx = self.cx.tcx; | |
379 | item_id | |
380 | .as_def_id() | |
381 | .map(|def_id| match tcx.def_kind(def_id) { | |
382 | def_kind @ (DefKind::AssocFn | |
383 | | DefKind::AssocConst | |
384 | | DefKind::AssocTy | |
385 | | DefKind::Variant | |
386 | | DefKind::Field) => { | |
04454e1e | 387 | let parent_def_id = tcx.parent(def_id); |
5e7ed085 FG |
388 | if def_kind == DefKind::Field && tcx.def_kind(parent_def_id) == DefKind::Variant |
389 | { | |
04454e1e | 390 | tcx.parent(parent_def_id) |
5e7ed085 FG |
391 | } else { |
392 | parent_def_id | |
393 | } | |
394 | } | |
395 | _ => def_id, | |
396 | }) | |
397 | .and_then(|self_id| match tcx.def_kind(self_id) { | |
398 | DefKind::Impl => self.def_id_to_res(self_id), | |
399 | def_kind => Some(Res::Def(def_kind, self_id)), | |
400 | }) | |
401 | } | |
402 | ||
403 | /// Convenience wrapper around `resolve_rustdoc_path`. | |
29967ef6 XL |
404 | /// |
405 | /// This also handles resolving `true` and `false` as booleans. | |
5e7ed085 | 406 | /// NOTE: `resolve_rustdoc_path` knows only about paths, not about types. |
29967ef6 | 407 | /// Associated items will never be resolved by this function. |
5e7ed085 FG |
408 | fn resolve_path( |
409 | &self, | |
410 | path_str: &str, | |
411 | ns: Namespace, | |
412 | item_id: ItemId, | |
413 | module_id: DefId, | |
414 | ) -> Option<Res> { | |
415 | if let res @ Some(..) = self.resolve_self_ty(path_str, ns, item_id) { | |
416 | return res; | |
29967ef6 | 417 | } |
5e7ed085 FG |
418 | |
419 | // Resolver doesn't know about true, false, and types that aren't paths (e.g. `()`). | |
420 | let result = self | |
421 | .cx | |
04454e1e FG |
422 | .resolver_caches |
423 | .doc_link_resolutions | |
424 | .get(&(Symbol::intern(path_str), ns, module_id)) | |
425 | .copied() | |
426 | .unwrap_or_else(|| { | |
427 | self.cx.enter_resolver(|resolver| { | |
428 | let parent_scope = | |
429 | ParentScope::module(resolver.expect_module(module_id), resolver); | |
430 | resolver.resolve_rustdoc_path(path_str, ns, parent_scope) | |
431 | }) | |
432 | }) | |
5e7ed085 | 433 | .and_then(|res| res.try_into().ok()) |
923072b8 | 434 | .or_else(|| resolve_primitive(path_str, ns)); |
5e7ed085 FG |
435 | debug!("{} resolved to {:?} in namespace {:?}", path_str, result, ns); |
436 | result | |
29967ef6 XL |
437 | } |
438 | ||
439 | /// Resolves a string as a path within a particular namespace. Returns an | |
440 | /// optional URL fragment in the case of variants and methods. | |
1b1a35ee | 441 | fn resolve<'path>( |
6a06907d | 442 | &mut self, |
1b1a35ee | 443 | path_str: &'path str, |
60c5eb7d | 444 | ns: Namespace, |
5e7ed085 | 445 | item_id: ItemId, |
1b1a35ee | 446 | module_id: DefId, |
04454e1e | 447 | ) -> Result<(Res, Option<DefId>), UnresolvedPath<'path>> { |
5e7ed085 | 448 | if let Some(res) = self.resolve_path(path_str, ns, item_id, module_id) { |
04454e1e FG |
449 | return Ok(match res { |
450 | Res::Def( | |
451 | DefKind::AssocFn | DefKind::AssocConst | DefKind::AssocTy | DefKind::Variant, | |
452 | def_id, | |
453 | ) => (Res::from_def_id(self.cx.tcx, self.cx.tcx.parent(def_id)), Some(def_id)), | |
923072b8 | 454 | _ => (res, None), |
04454e1e FG |
455 | }); |
456 | } else if ns == MacroNS { | |
457 | return Err(UnresolvedPath { | |
458 | item_id, | |
459 | module_id, | |
460 | partial_res: None, | |
461 | unresolved: path_str.into(), | |
462 | }); | |
1b1a35ee | 463 | } |
b7449926 | 464 | |
1b1a35ee XL |
465 | // Try looking for methods and associated items. |
466 | let mut split = path_str.rsplitn(2, "::"); | |
fc512014 XL |
467 | // NB: `split`'s first element is always defined, even if the delimiter was not present. |
468 | // NB: `item_str` could be empty when resolving in the root namespace (e.g. `::std`). | |
469 | let item_str = split.next().unwrap(); | |
470 | let item_name = Symbol::intern(item_str); | |
1b1a35ee XL |
471 | let path_root = split |
472 | .next() | |
fc512014 | 473 | .map(|f| f.to_owned()) |
1b1a35ee XL |
474 | // If there's no `::`, it's not an associated item. |
475 | // So we can be sure that `rustc_resolve` was accurate when it said it wasn't resolved. | |
476 | .ok_or_else(|| { | |
477 | debug!("found no `::`, assumming {} was correctly not in scope", item_name); | |
04454e1e | 478 | UnresolvedPath { |
5e7ed085 | 479 | item_id, |
1b1a35ee XL |
480 | module_id, |
481 | partial_res: None, | |
482 | unresolved: item_str.into(), | |
483 | } | |
484 | })?; | |
485 | ||
cdc7bbd5 XL |
486 | // FIXME(#83862): this arbitrarily gives precedence to primitives over modules to support |
487 | // links to primitives when `#[doc(primitive)]` is present. It should give an ambiguity | |
488 | // error instead and special case *only* modules with `#[doc(primitive)]`, not all | |
489 | // primitives. | |
490 | resolve_primitive(&path_root, TypeNS) | |
5e7ed085 | 491 | .or_else(|| self.resolve_path(&path_root, TypeNS, item_id, module_id)) |
cdc7bbd5 | 492 | .and_then(|ty_res| { |
04454e1e | 493 | self.resolve_associated_item(ty_res, item_name, ns, module_id).map(Ok) |
cdc7bbd5 XL |
494 | }) |
495 | .unwrap_or_else(|| { | |
496 | if ns == Namespace::ValueNS { | |
5e7ed085 | 497 | self.variant_field(path_str, item_id, module_id) |
cdc7bbd5 | 498 | } else { |
04454e1e | 499 | Err(UnresolvedPath { |
5e7ed085 | 500 | item_id, |
cdc7bbd5 XL |
501 | module_id, |
502 | partial_res: None, | |
503 | unresolved: path_root.into(), | |
04454e1e | 504 | }) |
1b1a35ee | 505 | } |
cdc7bbd5 | 506 | }) |
04454e1e | 507 | .map(|(res, def_id)| (res, Some(def_id))) |
cdc7bbd5 | 508 | } |
29967ef6 | 509 | |
136023e0 XL |
510 | /// Convert a DefId to a Res, where possible. |
511 | /// | |
512 | /// This is used for resolving type aliases. | |
513 | fn def_id_to_res(&self, ty_id: DefId) -> Option<Res> { | |
514 | use PrimitiveType::*; | |
515 | Some(match *self.cx.tcx.type_of(ty_id).kind() { | |
516 | ty::Bool => Res::Primitive(Bool), | |
517 | ty::Char => Res::Primitive(Char), | |
518 | ty::Int(ity) => Res::Primitive(ity.into()), | |
519 | ty::Uint(uty) => Res::Primitive(uty.into()), | |
520 | ty::Float(fty) => Res::Primitive(fty.into()), | |
521 | ty::Str => Res::Primitive(Str), | |
3c0e092e | 522 | ty::Tuple(tys) if tys.is_empty() => Res::Primitive(Unit), |
136023e0 XL |
523 | ty::Tuple(_) => Res::Primitive(Tuple), |
524 | ty::Array(..) => Res::Primitive(Array), | |
525 | ty::Slice(_) => Res::Primitive(Slice), | |
526 | ty::RawPtr(_) => Res::Primitive(RawPointer), | |
527 | ty::Ref(..) => Res::Primitive(Reference), | |
528 | ty::FnDef(..) => panic!("type alias to a function definition"), | |
529 | ty::FnPtr(_) => Res::Primitive(Fn), | |
530 | ty::Never => Res::Primitive(Never), | |
5e7ed085 | 531 | ty::Adt(ty::AdtDef(Interned(&ty::AdtDefData { did, .. }, _)), _) | ty::Foreign(did) => { |
04454e1e | 532 | Res::from_def_id(self.cx.tcx, did) |
136023e0 XL |
533 | } |
534 | ty::Projection(_) | |
535 | | ty::Closure(..) | |
536 | | ty::Generator(..) | |
537 | | ty::GeneratorWitness(_) | |
538 | | ty::Opaque(..) | |
539 | | ty::Dynamic(..) | |
540 | | ty::Param(_) | |
541 | | ty::Bound(..) | |
542 | | ty::Placeholder(_) | |
543 | | ty::Infer(_) | |
544 | | ty::Error(_) => return None, | |
545 | }) | |
546 | } | |
547 | ||
a2a8927a XL |
548 | /// Convert a PrimitiveType to a Ty, where possible. |
549 | /// | |
550 | /// This is used for resolving trait impls for primitives | |
551 | fn primitive_type_to_ty(&mut self, prim: PrimitiveType) -> Option<Ty<'tcx>> { | |
552 | use PrimitiveType::*; | |
553 | let tcx = self.cx.tcx; | |
554 | ||
555 | // FIXME: Only simple types are supported here, see if we can support | |
556 | // other types such as Tuple, Array, Slice, etc. | |
557 | // See https://github.com/rust-lang/rust/issues/90703#issuecomment-1004263455 | |
558 | Some(tcx.mk_ty(match prim { | |
559 | Bool => ty::Bool, | |
560 | Str => ty::Str, | |
561 | Char => ty::Char, | |
562 | Never => ty::Never, | |
563 | I8 => ty::Int(ty::IntTy::I8), | |
564 | I16 => ty::Int(ty::IntTy::I16), | |
565 | I32 => ty::Int(ty::IntTy::I32), | |
566 | I64 => ty::Int(ty::IntTy::I64), | |
567 | I128 => ty::Int(ty::IntTy::I128), | |
568 | Isize => ty::Int(ty::IntTy::Isize), | |
569 | F32 => ty::Float(ty::FloatTy::F32), | |
570 | F64 => ty::Float(ty::FloatTy::F64), | |
571 | U8 => ty::Uint(ty::UintTy::U8), | |
572 | U16 => ty::Uint(ty::UintTy::U16), | |
573 | U32 => ty::Uint(ty::UintTy::U32), | |
574 | U64 => ty::Uint(ty::UintTy::U64), | |
575 | U128 => ty::Uint(ty::UintTy::U128), | |
576 | Usize => ty::Uint(ty::UintTy::Usize), | |
577 | _ => return None, | |
578 | })) | |
579 | } | |
580 | ||
5099ac24 FG |
581 | /// Resolve an associated item, returning its containing page's `Res` |
582 | /// and the fragment targeting the associated item on its page. | |
cdc7bbd5 XL |
583 | fn resolve_associated_item( |
584 | &mut self, | |
585 | root_res: Res, | |
586 | item_name: Symbol, | |
587 | ns: Namespace, | |
588 | module_id: DefId, | |
04454e1e | 589 | ) -> Option<(Res, DefId)> { |
cdc7bbd5 XL |
590 | let tcx = self.cx.tcx; |
591 | ||
592 | match root_res { | |
a2a8927a XL |
593 | Res::Primitive(prim) => { |
594 | self.resolve_primitive_associated_item(prim, ns, item_name).or_else(|| { | |
04454e1e | 595 | self.primitive_type_to_ty(prim) |
923072b8 | 596 | .and_then(|ty| { |
a2a8927a XL |
597 | resolve_associated_trait_item(ty, module_id, item_name, ns, self.cx) |
598 | }) | |
04454e1e | 599 | .map(|item| (root_res, item.def_id)) |
a2a8927a XL |
600 | }) |
601 | } | |
136023e0 XL |
602 | Res::Def(DefKind::TyAlias, did) => { |
603 | // Resolve the link on the type the alias points to. | |
604 | // FIXME: if the associated item is defined directly on the type alias, | |
605 | // it will show up on its documentation page, we should link there instead. | |
606 | let res = self.def_id_to_res(did)?; | |
607 | self.resolve_associated_item(res, item_name, ns, module_id) | |
608 | } | |
fc512014 | 609 | Res::Def( |
5e7ed085 | 610 | def_kind @ (DefKind::Struct | DefKind::Union | DefKind::Enum | DefKind::ForeignTy), |
fc512014 XL |
611 | did, |
612 | ) => { | |
1b1a35ee | 613 | debug!("looking for associated item named {} for item {:?}", item_name, did); |
5e7ed085 FG |
614 | // Checks if item_name is a variant of the `SomeItem` enum |
615 | if ns == TypeNS && def_kind == DefKind::Enum { | |
616 | match tcx.type_of(did).kind() { | |
617 | ty::Adt(adt_def, _) => { | |
618 | for variant in adt_def.variants() { | |
619 | if variant.name == item_name { | |
04454e1e | 620 | return Some((root_res, variant.def_id)); |
5e7ed085 FG |
621 | } |
622 | } | |
623 | } | |
624 | _ => unreachable!(), | |
625 | } | |
626 | } | |
627 | ||
1b1a35ee | 628 | // Checks if item_name belongs to `impl SomeItem` |
6a06907d | 629 | let assoc_item = tcx |
1b1a35ee XL |
630 | .inherent_impls(did) |
631 | .iter() | |
632 | .flat_map(|&imp| { | |
6a06907d XL |
633 | tcx.associated_items(imp).find_by_name_and_namespace( |
634 | tcx, | |
1b1a35ee XL |
635 | Ident::with_dummy_span(item_name), |
636 | ns, | |
637 | imp, | |
638 | ) | |
639 | }) | |
a2a8927a | 640 | .copied() |
1b1a35ee XL |
641 | // There should only ever be one associated item that matches from any inherent impl |
642 | .next() | |
643 | // Check if item_name belongs to `impl SomeTrait for SomeItem` | |
29967ef6 XL |
644 | // FIXME(#74563): This gives precedence to `impl SomeItem`: |
645 | // Although having both would be ambiguous, use impl version for compatibility's sake. | |
1b1a35ee XL |
646 | // To handle that properly resolve() would have to support |
647 | // something like [`ambi_fn`](<SomeStruct as SomeTrait>::ambi_fn) | |
648 | .or_else(|| { | |
5099ac24 | 649 | resolve_associated_trait_item( |
a2a8927a XL |
650 | tcx.type_of(did), |
651 | module_id, | |
652 | item_name, | |
653 | ns, | |
654 | self.cx, | |
5099ac24 | 655 | ) |
1b1a35ee XL |
656 | }); |
657 | ||
5099ac24 FG |
658 | debug!("got associated item {:?}", assoc_item); |
659 | ||
a2a8927a | 660 | if let Some(item) = assoc_item { |
04454e1e | 661 | return Some((root_res, item.def_id)); |
1b1a35ee | 662 | } |
cdc7bbd5 XL |
663 | |
664 | if ns != Namespace::ValueNS { | |
665 | return None; | |
666 | } | |
a2a8927a | 667 | debug!("looking for fields named {} for {:?}", item_name, did); |
cdc7bbd5 | 668 | // FIXME: this doesn't really belong in `associated_item` (maybe `variant_field` is better?) |
a2a8927a | 669 | // NOTE: it's different from variant_field because it only resolves struct fields, |
cdc7bbd5 | 670 | // not variant fields (2 path segments, not 3). |
a2a8927a XL |
671 | // |
672 | // We need to handle struct (and union) fields in this code because | |
673 | // syntactically their paths are identical to associated item paths: | |
674 | // `module::Type::field` and `module::Type::Assoc`. | |
675 | // | |
676 | // On the other hand, variant fields can't be mistaken for associated | |
677 | // items because they look like this: `module::Type::Variant::field`. | |
678 | // | |
679 | // Variants themselves don't need to be handled here, even though | |
680 | // they also look like associated items (`module::Type::Variant`), | |
681 | // because they are real Rust syntax (unlike the intra-doc links | |
682 | // field syntax) and are handled by the compiler's resolver. | |
cdc7bbd5 | 683 | let def = match tcx.type_of(did).kind() { |
a2a8927a | 684 | ty::Adt(def, _) if !def.is_enum() => def, |
cdc7bbd5 XL |
685 | _ => return None, |
686 | }; | |
5099ac24 FG |
687 | let field = |
688 | def.non_enum_variant().fields.iter().find(|item| item.name == item_name)?; | |
04454e1e | 689 | Some((root_res, field.did)) |
1b1a35ee | 690 | } |
6a06907d | 691 | Res::Def(DefKind::Trait, did) => tcx |
1b1a35ee | 692 | .associated_items(did) |
6a06907d | 693 | .find_by_name_and_namespace(tcx, Ident::with_dummy_span(item_name), ns, did) |
1b1a35ee | 694 | .map(|item| { |
cdc7bbd5 | 695 | let res = Res::Def(item.kind.as_def_kind(), item.def_id); |
04454e1e | 696 | (res, item.def_id) |
1b1a35ee XL |
697 | }), |
698 | _ => None, | |
cdc7bbd5 | 699 | } |
1b1a35ee | 700 | } |
04454e1e | 701 | } |
b7449926 | 702 | |
04454e1e FG |
703 | fn full_res(tcx: TyCtxt<'_>, (base, assoc_item): (Res, Option<DefId>)) -> Res { |
704 | assoc_item.map_or(base, |def_id| Res::from_def_id(tcx, def_id)) | |
b7449926 XL |
705 | } |
706 | ||
29967ef6 XL |
707 | /// Look to see if a resolved item has an associated item named `item_name`. |
708 | /// | |
709 | /// Given `[std::io::Error::source]`, where `source` is unresolved, this would | |
710 | /// find `std::error::Error::source` and return | |
711 | /// `<io::Error as error::Error>::source`. | |
a2a8927a XL |
712 | fn resolve_associated_trait_item<'a>( |
713 | ty: Ty<'a>, | |
3dfed10e XL |
714 | module: DefId, |
715 | item_name: Symbol, | |
716 | ns: Namespace, | |
a2a8927a XL |
717 | cx: &mut DocContext<'a>, |
718 | ) -> Option<ty::AssocItem> { | |
fc512014 XL |
719 | // FIXME: this should also consider blanket impls (`impl<T> X for T`). Unfortunately |
720 | // `get_auto_trait_and_blanket_impls` is broken because the caching behavior is wrong. In the | |
721 | // meantime, just don't look for these blanket impls. | |
3dfed10e XL |
722 | |
723 | // Next consider explicit impls: `impl MyTrait for MyType` | |
724 | // Give precedence to inherent impls. | |
5099ac24 | 725 | let traits = trait_impls_for(cx, ty, module); |
fc512014 | 726 | debug!("considering traits {:?}", traits); |
5099ac24 FG |
727 | let mut candidates = traits.iter().filter_map(|&(impl_, trait_)| { |
728 | cx.tcx | |
729 | .associated_items(trait_) | |
730 | .find_by_name_and_namespace(cx.tcx, Ident::with_dummy_span(item_name), ns, trait_) | |
731 | .map(|trait_assoc| { | |
732 | trait_assoc_to_impl_assoc_item(cx.tcx, impl_, trait_assoc.def_id) | |
733 | .unwrap_or(trait_assoc) | |
734 | }) | |
fc512014 | 735 | }); |
29967ef6 | 736 | // FIXME(#74563): warn about ambiguity |
fc512014 | 737 | debug!("the candidates were {:?}", candidates.clone().collect::<Vec<_>>()); |
a2a8927a | 738 | candidates.next().copied() |
3dfed10e XL |
739 | } |
740 | ||
5099ac24 FG |
741 | /// Find the associated item in the impl `impl_id` that corresponds to the |
742 | /// trait associated item `trait_assoc_id`. | |
743 | /// | |
744 | /// This function returns `None` if no associated item was found in the impl. | |
745 | /// This can occur when the trait associated item has a default value that is | |
5e7ed085 | 746 | /// not overridden in the impl. |
5099ac24 FG |
747 | /// |
748 | /// This is just a wrapper around [`TyCtxt::impl_item_implementor_ids()`] and | |
749 | /// [`TyCtxt::associated_item()`] (with some helpful logging added). | |
750 | #[instrument(level = "debug", skip(tcx))] | |
751 | fn trait_assoc_to_impl_assoc_item<'tcx>( | |
752 | tcx: TyCtxt<'tcx>, | |
753 | impl_id: DefId, | |
754 | trait_assoc_id: DefId, | |
755 | ) -> Option<&'tcx ty::AssocItem> { | |
756 | let trait_to_impl_assoc_map = tcx.impl_item_implementor_ids(impl_id); | |
757 | debug!(?trait_to_impl_assoc_map); | |
758 | let impl_assoc_id = *trait_to_impl_assoc_map.get(&trait_assoc_id)?; | |
759 | debug!(?impl_assoc_id); | |
760 | let impl_assoc = tcx.associated_item(impl_assoc_id); | |
761 | debug!(?impl_assoc); | |
762 | Some(impl_assoc) | |
763 | } | |
764 | ||
765 | /// Given a type, return all trait impls in scope in `module` for that type. | |
766 | /// Returns a set of pairs of `(impl_id, trait_id)`. | |
3dfed10e XL |
767 | /// |
768 | /// NOTE: this cannot be a query because more traits could be available when more crates are compiled! | |
769 | /// So it is not stable to serialize cross-crate. | |
5099ac24 FG |
770 | #[instrument(level = "debug", skip(cx))] |
771 | fn trait_impls_for<'a>( | |
a2a8927a XL |
772 | cx: &mut DocContext<'a>, |
773 | ty: Ty<'a>, | |
774 | module: DefId, | |
5099ac24 | 775 | ) -> FxHashSet<(DefId, DefId)> { |
6a06907d | 776 | let tcx = cx.tcx; |
5099ac24 FG |
777 | let iter = cx.resolver_caches.traits_in_scope[&module].iter().flat_map(|trait_candidate| { |
778 | let trait_ = trait_candidate.def_id; | |
3dfed10e | 779 | trace!("considering explicit impl for trait {:?}", trait_); |
3dfed10e | 780 | |
29967ef6 | 781 | // Look at each trait implementation to see if it's an impl for `did` |
6a06907d XL |
782 | tcx.find_map_relevant_impl(trait_, ty, |impl_| { |
783 | let trait_ref = tcx.impl_trait_ref(impl_).expect("this is not an inherent impl"); | |
3dfed10e XL |
784 | // Check if these are the same type. |
785 | let impl_type = trait_ref.self_ty(); | |
1b1a35ee | 786 | trace!( |
3dfed10e | 787 | "comparing type {} with kind {:?} against type {:?}", |
1b1a35ee XL |
788 | impl_type, |
789 | impl_type.kind(), | |
a2a8927a | 790 | ty |
3dfed10e XL |
791 | ); |
792 | // Fast path: if this is a primitive simple `==` will work | |
a2a8927a XL |
793 | // NOTE: the `match` is necessary; see #92662. |
794 | // this allows us to ignore generics because the user input | |
795 | // may not include the generic placeholders | |
796 | // e.g. this allows us to match Foo (user comment) with Foo<T> (actual type) | |
29967ef6 | 797 | let saw_impl = impl_type == ty |
a2a8927a XL |
798 | || match (impl_type.kind(), ty.kind()) { |
799 | (ty::Adt(impl_def, _), ty::Adt(ty_def, _)) => { | |
5e7ed085 FG |
800 | debug!("impl def_id: {:?}, ty def_id: {:?}", impl_def.did(), ty_def.did()); |
801 | impl_def.did() == ty_def.did() | |
3dfed10e | 802 | } |
3dfed10e XL |
803 | _ => false, |
804 | }; | |
29967ef6 | 805 | |
5099ac24 | 806 | if saw_impl { Some((impl_, trait_)) } else { None } |
29967ef6 | 807 | }) |
3dfed10e XL |
808 | }); |
809 | iter.collect() | |
810 | } | |
811 | ||
29967ef6 | 812 | /// Check for resolve collisions between a trait and its derive. |
f035d41b | 813 | /// |
29967ef6 | 814 | /// These are common and we should just resolve to the trait in that case. |
1b1a35ee | 815 | fn is_derive_trait_collision<T>(ns: &PerNS<Result<(Res, T), ResolutionFailure<'_>>>) -> bool { |
fc512014 XL |
816 | matches!( |
817 | *ns, | |
818 | PerNS { | |
819 | type_ns: Ok((Res::Def(DefKind::Trait, _), _)), | |
820 | macro_ns: Ok((Res::Def(DefKind::Macro(MacroKind::Derive), _), _)), | |
821 | .. | |
822 | } | |
823 | ) | |
f035d41b XL |
824 | } |
825 | ||
3c0e092e XL |
826 | impl<'a, 'tcx> DocVisitor for LinkCollector<'a, 'tcx> { |
827 | fn visit_item(&mut self, item: &Item) { | |
136023e0 | 828 | let parent_node = |
04454e1e | 829 | item.item_id.as_def_id().and_then(|did| find_nearest_parent_module(self.cx.tcx, did)); |
0bf4aa26 | 830 | if parent_node.is_some() { |
04454e1e | 831 | trace!("got parent node for {:?} {:?}, id {:?}", item.type_(), item.name, item.item_id); |
0bf4aa26 XL |
832 | } |
833 | ||
cdc7bbd5 XL |
834 | let inner_docs = item.inner_docs(self.cx.tcx); |
835 | ||
836 | if item.is_mod() && inner_docs { | |
04454e1e | 837 | self.mod_ids.push(item.item_id.expect_def_id()); |
b7449926 XL |
838 | } |
839 | ||
29967ef6 XL |
840 | // We want to resolve in the lexical scope of the documentation. |
841 | // In the presence of re-exports, this is not the same as the module of the item. | |
842 | // Rather than merging all documentation into one, resolve it one attribute at a time | |
843 | // so we know which module it came from. | |
04454e1e FG |
844 | for (parent_module, doc) in item.attrs.prepare_to_doc_link_resolution() { |
845 | if !may_have_doc_links(&doc) { | |
846 | continue; | |
847 | } | |
5869c6ff | 848 | debug!("combined_docs={}", doc); |
29967ef6 XL |
849 | // NOTE: if there are links that start in one crate and end in another, this will not resolve them. |
850 | // This is a degenerate case and it's not supported by rustdoc. | |
04454e1e FG |
851 | let parent_node = parent_module.or(parent_node); |
852 | let mut tmp_links = self | |
853 | .cx | |
854 | .resolver_caches | |
855 | .markdown_links | |
856 | .take() | |
857 | .expect("`markdown_links` are already borrowed"); | |
858 | if !tmp_links.contains_key(&doc) { | |
859 | tmp_links.insert(doc.clone(), preprocessed_markdown_links(&doc)); | |
860 | } | |
861 | for md_link in &tmp_links[&doc] { | |
923072b8 | 862 | let link = self.resolve_link(item, &doc, parent_node, md_link); |
29967ef6 | 863 | if let Some(link) = link { |
04454e1e | 864 | self.cx.cache.intra_doc_links.entry(item.item_id).or_default().push(link); |
29967ef6 XL |
865 | } |
866 | } | |
04454e1e | 867 | self.cx.resolver_caches.markdown_links = Some(tmp_links); |
1b1a35ee XL |
868 | } |
869 | ||
3c0e092e | 870 | if item.is_mod() { |
cdc7bbd5 | 871 | if !inner_docs { |
04454e1e | 872 | self.mod_ids.push(item.item_id.expect_def_id()); |
29967ef6 | 873 | } |
1b1a35ee | 874 | |
3c0e092e | 875 | self.visit_item_recur(item); |
1b1a35ee | 876 | self.mod_ids.pop(); |
1b1a35ee | 877 | } else { |
3c0e092e XL |
878 | self.visit_item_recur(item) |
879 | } | |
1b1a35ee XL |
880 | } |
881 | } | |
882 | ||
04454e1e FG |
883 | enum PreprocessingError { |
884 | /// User error: `[std#x#y]` is not valid | |
885 | MultipleAnchors, | |
cdc7bbd5 | 886 | Disambiguator(Range<usize>, String), |
04454e1e | 887 | MalformedGenerics(MalformedGenerics, String), |
cdc7bbd5 XL |
888 | } |
889 | ||
04454e1e FG |
890 | impl PreprocessingError { |
891 | fn report(&self, cx: &DocContext<'_>, diag_info: DiagnosticInfo<'_>) { | |
892 | match self { | |
893 | PreprocessingError::MultipleAnchors => report_multiple_anchors(cx, diag_info), | |
894 | PreprocessingError::Disambiguator(range, msg) => { | |
895 | disambiguator_error(cx, diag_info, range.clone(), msg) | |
896 | } | |
897 | PreprocessingError::MalformedGenerics(err, path_str) => { | |
898 | report_malformed_generics(cx, diag_info, *err, path_str) | |
899 | } | |
900 | } | |
cdc7bbd5 XL |
901 | } |
902 | } | |
903 | ||
04454e1e | 904 | #[derive(Clone)] |
cdc7bbd5 XL |
905 | struct PreprocessingInfo { |
906 | path_str: String, | |
907 | disambiguator: Option<Disambiguator>, | |
5099ac24 | 908 | extra_fragment: Option<String>, |
cdc7bbd5 XL |
909 | link_text: String, |
910 | } | |
911 | ||
04454e1e | 912 | // Not a typedef to avoid leaking several private structures from this module. |
923072b8 FG |
913 | pub(crate) struct PreprocessedMarkdownLink( |
914 | Result<PreprocessingInfo, PreprocessingError>, | |
915 | MarkdownLink, | |
916 | ); | |
04454e1e | 917 | |
cdc7bbd5 XL |
918 | /// Returns: |
919 | /// - `None` if the link should be ignored. | |
920 | /// - `Some(Err)` if the link should emit an error | |
921 | /// - `Some(Ok)` if the link is valid | |
922 | /// | |
923 | /// `link_buffer` is needed for lifetime reasons; it will always be overwritten and the contents ignored. | |
04454e1e FG |
924 | fn preprocess_link( |
925 | ori_link: &MarkdownLink, | |
926 | ) -> Option<Result<PreprocessingInfo, PreprocessingError>> { | |
cdc7bbd5 XL |
927 | // [] is mostly likely not supposed to be a link |
928 | if ori_link.link.is_empty() { | |
929 | return None; | |
930 | } | |
931 | ||
932 | // Bail early for real links. | |
933 | if ori_link.link.contains('/') { | |
934 | return None; | |
935 | } | |
936 | ||
a2a8927a | 937 | let stripped = ori_link.link.replace('`', ""); |
cdc7bbd5 XL |
938 | let mut parts = stripped.split('#'); |
939 | ||
940 | let link = parts.next().unwrap(); | |
941 | if link.trim().is_empty() { | |
942 | // This is an anchor to an element of the current page, nothing to do in here! | |
943 | return None; | |
944 | } | |
945 | let extra_fragment = parts.next(); | |
946 | if parts.next().is_some() { | |
947 | // A valid link can't have multiple #'s | |
04454e1e | 948 | return Some(Err(PreprocessingError::MultipleAnchors)); |
cdc7bbd5 XL |
949 | } |
950 | ||
951 | // Parse and strip the disambiguator from the link, if present. | |
3c0e092e | 952 | let (disambiguator, path_str, link_text) = match Disambiguator::from_str(link) { |
136023e0 XL |
953 | Ok(Some((d, path, link_text))) => (Some(d), path.trim(), link_text.trim()), |
954 | Ok(None) => (None, link.trim(), link.trim()), | |
cdc7bbd5 XL |
955 | Err((err_msg, relative_range)) => { |
956 | // Only report error if we would not have ignored this link. See issue #83859. | |
957 | if !should_ignore_link_with_disambiguators(link) { | |
3c0e092e | 958 | let no_backticks_range = range_between_backticks(ori_link); |
cdc7bbd5 XL |
959 | let disambiguator_range = (no_backticks_range.start + relative_range.start) |
960 | ..(no_backticks_range.start + relative_range.end); | |
961 | return Some(Err(PreprocessingError::Disambiguator(disambiguator_range, err_msg))); | |
962 | } else { | |
963 | return None; | |
964 | } | |
965 | } | |
966 | }; | |
967 | ||
968 | if should_ignore_link(path_str) { | |
969 | return None; | |
970 | } | |
971 | ||
cdc7bbd5 XL |
972 | // Strip generics from the path. |
973 | let path_str = if path_str.contains(['<', '>'].as_slice()) { | |
3c0e092e | 974 | match strip_generics_from_path(path_str) { |
cdc7bbd5 | 975 | Ok(path) => path, |
04454e1e | 976 | Err(err) => { |
cdc7bbd5 | 977 | debug!("link has malformed generics: {}", path_str); |
04454e1e | 978 | return Some(Err(PreprocessingError::MalformedGenerics(err, path_str.to_owned()))); |
cdc7bbd5 XL |
979 | } |
980 | } | |
981 | } else { | |
982 | path_str.to_owned() | |
983 | }; | |
984 | ||
985 | // Sanity check to make sure we don't have any angle brackets after stripping generics. | |
986 | assert!(!path_str.contains(['<', '>'].as_slice())); | |
987 | ||
988 | // The link is not an intra-doc link if it still contains spaces after stripping generics. | |
989 | if path_str.contains(' ') { | |
990 | return None; | |
991 | } | |
992 | ||
993 | Some(Ok(PreprocessingInfo { | |
994 | path_str, | |
995 | disambiguator, | |
5099ac24 | 996 | extra_fragment: extra_fragment.map(|frag| frag.to_owned()), |
136023e0 | 997 | link_text: link_text.to_owned(), |
cdc7bbd5 XL |
998 | })) |
999 | } | |
1000 | ||
04454e1e FG |
1001 | fn preprocessed_markdown_links(s: &str) -> Vec<PreprocessedMarkdownLink> { |
1002 | markdown_links(s, |link| { | |
1003 | preprocess_link(&link).map(|pp_link| PreprocessedMarkdownLink(pp_link, link)) | |
1004 | }) | |
1005 | } | |
1006 | ||
1b1a35ee | 1007 | impl LinkCollector<'_, '_> { |
29967ef6 XL |
1008 | /// This is the entry point for resolving an intra-doc link. |
1009 | /// | |
1010 | /// FIXME(jynelson): this is way too many arguments | |
1b1a35ee | 1011 | fn resolve_link( |
fc512014 | 1012 | &mut self, |
29967ef6 | 1013 | item: &Item, |
1b1a35ee | 1014 | dox: &str, |
1b1a35ee | 1015 | parent_node: Option<DefId>, |
04454e1e | 1016 | link: &PreprocessedMarkdownLink, |
29967ef6 | 1017 | ) -> Option<ItemLink> { |
04454e1e | 1018 | let PreprocessedMarkdownLink(pp_link, ori_link) = link; |
5869c6ff | 1019 | trace!("considering link '{}'", ori_link.link); |
1b1a35ee | 1020 | |
cdc7bbd5 XL |
1021 | let diag_info = DiagnosticInfo { |
1022 | item, | |
1023 | dox, | |
1024 | ori_link: &ori_link.link, | |
1025 | link_range: ori_link.range.clone(), | |
29967ef6 | 1026 | }; |
b7449926 | 1027 | |
cdc7bbd5 | 1028 | let PreprocessingInfo { path_str, disambiguator, extra_fragment, link_text } = |
04454e1e FG |
1029 | pp_link.as_ref().map_err(|err| err.report(self.cx, diag_info.clone())).ok()?; |
1030 | let disambiguator = *disambiguator; | |
29967ef6 | 1031 | |
5869c6ff | 1032 | // In order to correctly resolve intra-doc links we need to |
29967ef6 XL |
1033 | // pick a base AST node to work from. If the documentation for |
1034 | // this module came from an inner comment (//!) then we anchor | |
1035 | // our name resolution *inside* the module. If, on the other | |
1036 | // hand it was an outer comment (///) then we anchor the name | |
1037 | // resolution in the parent module on the basis that the names | |
1038 | // used are more likely to be intended to be parent names. For | |
1039 | // this, we set base_node to None for inner comments since | |
1040 | // we've already pushed this node onto the resolution stack but | |
1041 | // for outer comments we explicitly try and resolve against the | |
1042 | // parent_node first. | |
04454e1e | 1043 | let inner_docs = item.inner_docs(self.cx.tcx); |
cdc7bbd5 XL |
1044 | let base_node = |
1045 | if item.is_mod() && inner_docs { self.mod_ids.last().copied() } else { parent_node }; | |
04454e1e | 1046 | let module_id = base_node.expect("doc link without parent module"); |
29967ef6 | 1047 | |
c295e0f8 | 1048 | let (mut res, fragment) = self.resolve_with_disambiguator_cached( |
5869c6ff | 1049 | ResolutionInfo { |
04454e1e | 1050 | item_id: item.item_id, |
5869c6ff XL |
1051 | module_id, |
1052 | dis: disambiguator, | |
1053 | path_str: path_str.to_owned(), | |
04454e1e | 1054 | extra_fragment: extra_fragment.clone(), |
5869c6ff | 1055 | }, |
cdc7bbd5 | 1056 | diag_info.clone(), // this struct should really be Copy, but Range is not :( |
923072b8 FG |
1057 | // For reference-style links we want to report only one error so unsuccessful |
1058 | // resolutions are cached, for other links we want to report an error every | |
1059 | // time so they are not cached. | |
5869c6ff XL |
1060 | matches!(ori_link.kind, LinkType::Reference | LinkType::Shortcut), |
1061 | )?; | |
29967ef6 | 1062 | |
1b1a35ee XL |
1063 | // Check for a primitive which might conflict with a module |
1064 | // Report the ambiguity and require that the user specify which one they meant. | |
1065 | // FIXME: could there ever be a primitive not in the type namespace? | |
1066 | if matches!( | |
1067 | disambiguator, | |
1068 | None | Some(Disambiguator::Namespace(Namespace::TypeNS) | Disambiguator::Primitive) | |
5869c6ff | 1069 | ) && !matches!(res, Res::Primitive(_)) |
1b1a35ee | 1070 | { |
5869c6ff | 1071 | if let Some(prim) = resolve_primitive(path_str, TypeNS) { |
1b1a35ee XL |
1072 | // `prim@char` |
1073 | if matches!(disambiguator, Some(Disambiguator::Primitive)) { | |
1b1a35ee | 1074 | res = prim; |
1b1a35ee XL |
1075 | } else { |
1076 | // `[char]` when a `char` module is in scope | |
1077 | let candidates = vec![res, prim]; | |
cdc7bbd5 | 1078 | ambiguity_error(self.cx, diag_info, path_str, candidates); |
29967ef6 | 1079 | return None; |
f9f354fc | 1080 | } |
1b1a35ee XL |
1081 | } |
1082 | } | |
f9f354fc | 1083 | |
5869c6ff XL |
1084 | match res { |
1085 | Res::Primitive(prim) => { | |
923072b8 | 1086 | if let Some(UrlFragment::Item(id)) = fragment { |
5869c6ff XL |
1087 | // We're actually resolving an associated item of a primitive, so we need to |
1088 | // verify the disambiguator (if any) matches the type of the associated item. | |
1089 | // This case should really follow the same flow as the `Res::Def` branch below, | |
1090 | // but attempting to add a call to `clean::register_res` causes an ICE. @jyn514 | |
1091 | // thinks `register_res` is only needed for cross-crate re-exports, but Rust | |
1092 | // doesn't allow statements like `use str::trim;`, making this a (hopefully) | |
1093 | // valid omission. See https://github.com/rust-lang/rust/pull/80660#discussion_r551585677 | |
1094 | // for discussion on the matter. | |
5099ac24 FG |
1095 | let kind = self.cx.tcx.def_kind(id); |
1096 | self.verify_disambiguator( | |
1097 | path_str, | |
923072b8 | 1098 | ori_link, |
5099ac24 FG |
1099 | kind, |
1100 | id, | |
1101 | disambiguator, | |
1102 | item, | |
1103 | &diag_info, | |
1104 | )?; | |
5869c6ff XL |
1105 | |
1106 | // FIXME: it would be nice to check that the feature gate was enabled in the original crate, not just ignore it altogether. | |
1107 | // However I'm not sure how to check that across crates. | |
1108 | if prim == PrimitiveType::RawPointer | |
04454e1e | 1109 | && item.item_id.is_local() |
5869c6ff XL |
1110 | && !self.cx.tcx.features().intra_doc_pointers |
1111 | { | |
923072b8 | 1112 | self.report_rawptr_assoc_feature_gate(dox, ori_link, item); |
5869c6ff XL |
1113 | } |
1114 | } else { | |
1115 | match disambiguator { | |
1116 | Some(Disambiguator::Primitive | Disambiguator::Namespace(_)) | None => {} | |
1117 | Some(other) => { | |
5099ac24 | 1118 | self.report_disambiguator_mismatch( |
923072b8 | 1119 | path_str, ori_link, other, res, &diag_info, |
5099ac24 | 1120 | ); |
5869c6ff XL |
1121 | return None; |
1122 | } | |
1123 | } | |
1b1a35ee | 1124 | } |
5869c6ff | 1125 | |
c295e0f8 | 1126 | Some(ItemLink { |
04454e1e FG |
1127 | link: ori_link.link.clone(), |
1128 | link_text: link_text.clone(), | |
c295e0f8 XL |
1129 | did: res.def_id(self.cx.tcx), |
1130 | fragment, | |
1131 | }) | |
5869c6ff XL |
1132 | } |
1133 | Res::Def(kind, id) => { | |
923072b8 FG |
1134 | let (kind_for_dis, id_for_dis) = if let Some(UrlFragment::Item(id)) = fragment { |
1135 | (self.cx.tcx.def_kind(id), id) | |
1136 | } else { | |
1137 | (kind, id) | |
1138 | }; | |
5099ac24 FG |
1139 | self.verify_disambiguator( |
1140 | path_str, | |
923072b8 | 1141 | ori_link, |
5099ac24 FG |
1142 | kind_for_dis, |
1143 | id_for_dis, | |
1144 | disambiguator, | |
1145 | item, | |
1146 | &diag_info, | |
1147 | )?; | |
6a06907d | 1148 | let id = clean::register_res(self.cx, rustc_hir::def::Res::Def(kind, id)); |
04454e1e FG |
1149 | Some(ItemLink { |
1150 | link: ori_link.link.clone(), | |
1151 | link_text: link_text.clone(), | |
1152 | did: id, | |
1153 | fragment, | |
1154 | }) | |
1b1a35ee | 1155 | } |
1b1a35ee XL |
1156 | } |
1157 | } | |
1158 | ||
5099ac24 FG |
1159 | fn verify_disambiguator( |
1160 | &self, | |
1161 | path_str: &str, | |
1162 | ori_link: &MarkdownLink, | |
1163 | kind: DefKind, | |
1164 | id: DefId, | |
1165 | disambiguator: Option<Disambiguator>, | |
1166 | item: &Item, | |
1167 | diag_info: &DiagnosticInfo<'_>, | |
1168 | ) -> Option<()> { | |
1169 | debug!("intra-doc link to {} resolved to {:?}", path_str, (kind, id)); | |
1170 | ||
1171 | // Disallow e.g. linking to enums with `struct@` | |
1172 | debug!("saw kind {:?} with disambiguator {:?}", kind, disambiguator); | |
1173 | match (kind, disambiguator) { | |
1174 | | (DefKind::Const | DefKind::ConstParam | DefKind::AssocConst | DefKind::AnonConst, Some(Disambiguator::Kind(DefKind::Const))) | |
1175 | // NOTE: this allows 'method' to mean both normal functions and associated functions | |
1176 | // This can't cause ambiguity because both are in the same namespace. | |
1177 | | (DefKind::Fn | DefKind::AssocFn, Some(Disambiguator::Kind(DefKind::Fn))) | |
1178 | // These are namespaces; allow anything in the namespace to match | |
1179 | | (_, Some(Disambiguator::Namespace(_))) | |
1180 | // If no disambiguator given, allow anything | |
1181 | | (_, None) | |
1182 | // All of these are valid, so do nothing | |
1183 | => {} | |
1184 | (actual, Some(Disambiguator::Kind(expected))) if actual == expected => {} | |
1185 | (_, Some(specified @ Disambiguator::Kind(_) | specified @ Disambiguator::Primitive)) => { | |
1186 | self.report_disambiguator_mismatch(path_str,ori_link,specified, Res::Def(kind, id),diag_info); | |
1187 | return None; | |
1188 | } | |
1189 | } | |
1190 | ||
1191 | // item can be non-local e.g. when using #[doc(primitive = "pointer")] | |
1192 | if let Some((src_id, dst_id)) = id | |
1193 | .as_local() | |
1194 | // The `expect_def_id()` should be okay because `local_def_id_to_hir_id` | |
1195 | // would presumably panic if a fake `DefIndex` were passed. | |
1196 | .and_then(|dst_id| { | |
04454e1e | 1197 | item.item_id.expect_def_id().as_local().map(|src_id| (src_id, dst_id)) |
5099ac24 FG |
1198 | }) |
1199 | { | |
1200 | if self.cx.tcx.privacy_access_levels(()).is_exported(src_id) | |
1201 | && !self.cx.tcx.privacy_access_levels(()).is_exported(dst_id) | |
1202 | { | |
1203 | privacy_error(self.cx, diag_info, path_str); | |
1204 | } | |
1205 | } | |
1206 | ||
1207 | Some(()) | |
1208 | } | |
1209 | ||
1210 | fn report_disambiguator_mismatch( | |
1211 | &self, | |
1212 | path_str: &str, | |
1213 | ori_link: &MarkdownLink, | |
1214 | specified: Disambiguator, | |
1215 | resolved: Res, | |
1216 | diag_info: &DiagnosticInfo<'_>, | |
1217 | ) { | |
1218 | // The resolved item did not match the disambiguator; give a better error than 'not found' | |
1219 | let msg = format!("incompatible link kind for `{}`", path_str); | |
5e7ed085 | 1220 | let callback = |diag: &mut Diagnostic, sp: Option<rustc_span::Span>| { |
5099ac24 FG |
1221 | let note = format!( |
1222 | "this link resolved to {} {}, which is not {} {}", | |
1223 | resolved.article(), | |
1224 | resolved.descr(), | |
1225 | specified.article(), | |
1226 | specified.descr(), | |
1227 | ); | |
1228 | if let Some(sp) = sp { | |
1229 | diag.span_label(sp, ¬e); | |
1230 | } else { | |
1231 | diag.note(¬e); | |
1232 | } | |
1233 | suggest_disambiguator(resolved, diag, path_str, &ori_link.link, sp); | |
1234 | }; | |
923072b8 | 1235 | report_diagnostic(self.cx.tcx, BROKEN_INTRA_DOC_LINKS, &msg, diag_info, callback); |
5099ac24 FG |
1236 | } |
1237 | ||
1238 | fn report_rawptr_assoc_feature_gate(&self, dox: &str, ori_link: &MarkdownLink, item: &Item) { | |
1239 | let span = | |
1240 | super::source_span_for_markdown_range(self.cx.tcx, dox, &ori_link.range, &item.attrs) | |
1241 | .unwrap_or_else(|| item.attr_span(self.cx.tcx)); | |
1242 | rustc_session::parse::feature_err( | |
1243 | &self.cx.tcx.sess.parse_sess, | |
1244 | sym::intra_doc_pointers, | |
1245 | span, | |
1246 | "linking to associated items of raw pointers is experimental", | |
1247 | ) | |
1248 | .note("rustdoc does not allow disambiguating between `*const` and `*mut`, and pointers are unstable until it does") | |
1249 | .emit(); | |
1250 | } | |
1251 | ||
fc512014 XL |
1252 | fn resolve_with_disambiguator_cached( |
1253 | &mut self, | |
1254 | key: ResolutionInfo, | |
1255 | diag: DiagnosticInfo<'_>, | |
923072b8 FG |
1256 | // If errors are cached then they are only reported on first ocurrence |
1257 | // which we want in some cases but not in others. | |
1258 | cache_errors: bool, | |
a2a8927a | 1259 | ) -> Option<(Res, Option<UrlFragment>)> { |
923072b8 FG |
1260 | if let Some(res) = self.visited_links.get(&key) { |
1261 | if res.is_some() || cache_errors { | |
1262 | return res.clone(); | |
5869c6ff | 1263 | } |
fc512014 XL |
1264 | } |
1265 | ||
04454e1e FG |
1266 | let res = self.resolve_with_disambiguator(&key, diag.clone()).and_then(|(res, def_id)| { |
1267 | let fragment = match (&key.extra_fragment, def_id) { | |
1268 | (Some(_), Some(def_id)) => { | |
923072b8 | 1269 | report_anchor_conflict(self.cx, diag, def_id); |
04454e1e FG |
1270 | return None; |
1271 | } | |
1272 | (Some(u_frag), None) => Some(UrlFragment::UserWritten(u_frag.clone())), | |
923072b8 | 1273 | (None, Some(def_id)) => Some(UrlFragment::Item(def_id)), |
04454e1e FG |
1274 | (None, None) => None, |
1275 | }; | |
1276 | Some((res, fragment)) | |
1277 | }); | |
fc512014 | 1278 | |
923072b8 FG |
1279 | if res.is_some() || cache_errors { |
1280 | self.visited_links.insert(key, res.clone()); | |
5869c6ff | 1281 | } |
923072b8 | 1282 | res |
fc512014 XL |
1283 | } |
1284 | ||
29967ef6 XL |
1285 | /// After parsing the disambiguator, resolve the main part of the link. |
1286 | // FIXME(jynelson): wow this is just so much | |
1b1a35ee | 1287 | fn resolve_with_disambiguator( |
6a06907d | 1288 | &mut self, |
fc512014 XL |
1289 | key: &ResolutionInfo, |
1290 | diag: DiagnosticInfo<'_>, | |
04454e1e | 1291 | ) -> Option<(Res, Option<DefId>)> { |
fc512014 XL |
1292 | let disambiguator = key.dis; |
1293 | let path_str = &key.path_str; | |
5e7ed085 | 1294 | let item_id = key.item_id; |
fc512014 | 1295 | let base_node = key.module_id; |
fc512014 | 1296 | |
1b1a35ee | 1297 | match disambiguator.map(Disambiguator::ns) { |
04454e1e FG |
1298 | Some(expected_ns) => { |
1299 | match self.resolve(path_str, expected_ns, item_id, base_node) { | |
1b1a35ee | 1300 | Ok(res) => Some(res), |
04454e1e | 1301 | Err(err) => { |
1b1a35ee | 1302 | // We only looked in one namespace. Try to give a better error if possible. |
04454e1e FG |
1303 | // FIXME: really it should be `resolution_failure` that does this, not `resolve_with_disambiguator`. |
1304 | // See https://github.com/rust-lang/rust/pull/76955#discussion_r493953382 for a good approach. | |
1305 | let mut err = ResolutionFailure::NotResolved(err); | |
1306 | for other_ns in [TypeNS, ValueNS, MacroNS] { | |
1307 | if other_ns != expected_ns { | |
1308 | if let Ok(res) = | |
1309 | self.resolve(path_str, other_ns, item_id, base_node) | |
1310 | { | |
1311 | err = ResolutionFailure::WrongNamespace { | |
1312 | res: full_res(self.cx.tcx, res), | |
1313 | expected_ns, | |
1314 | }; | |
1b1a35ee XL |
1315 | break; |
1316 | } | |
f035d41b | 1317 | } |
532ac7d7 | 1318 | } |
923072b8 | 1319 | resolution_failure(self, diag, path_str, disambiguator, smallvec![err]) |
b7449926 | 1320 | } |
b7449926 | 1321 | } |
1b1a35ee XL |
1322 | } |
1323 | None => { | |
1324 | // Try everything! | |
04454e1e FG |
1325 | let mut candidate = |ns| { |
1326 | self.resolve(path_str, ns, item_id, base_node) | |
1327 | .map_err(ResolutionFailure::NotResolved) | |
1328 | }; | |
1329 | ||
1330 | let candidates = PerNS { | |
1331 | macro_ns: candidate(MacroNS), | |
1332 | type_ns: candidate(TypeNS), | |
1333 | value_ns: candidate(ValueNS).and_then(|(res, def_id)| { | |
1b1a35ee | 1334 | match res { |
04454e1e | 1335 | // Constructors are picked up in the type namespace. |
5869c6ff | 1336 | Res::Def(DefKind::Ctor(..), _) => { |
6a06907d | 1337 | Err(ResolutionFailure::WrongNamespace { res, expected_ns: TypeNS }) |
1b1a35ee | 1338 | } |
04454e1e | 1339 | _ => Ok((res, def_id)), |
1b1a35ee XL |
1340 | } |
1341 | }), | |
1342 | }; | |
3dfed10e | 1343 | |
1b1a35ee XL |
1344 | let len = candidates.iter().filter(|res| res.is_ok()).count(); |
1345 | ||
1346 | if len == 0 { | |
923072b8 | 1347 | return resolution_failure( |
1b1a35ee | 1348 | self, |
cdc7bbd5 | 1349 | diag, |
1b1a35ee XL |
1350 | path_str, |
1351 | disambiguator, | |
1b1a35ee | 1352 | candidates.into_iter().filter_map(|res| res.err()).collect(), |
3dfed10e | 1353 | ); |
3dfed10e | 1354 | } |
1b1a35ee XL |
1355 | |
1356 | if len == 1 { | |
17df50a5 | 1357 | Some(candidates.into_iter().find_map(|res| res.ok()).unwrap()) |
1b1a35ee XL |
1358 | } else if len == 2 && is_derive_trait_collision(&candidates) { |
1359 | Some(candidates.type_ns.unwrap()) | |
1360 | } else { | |
04454e1e | 1361 | let ignore_macro = is_derive_trait_collision(&candidates); |
1b1a35ee | 1362 | // If we're reporting an ambiguity, don't mention the namespaces that failed |
04454e1e FG |
1363 | let mut candidates = |
1364 | candidates.map(|candidate| candidate.ok().map(|(res, _)| res)); | |
1365 | if ignore_macro { | |
1366 | candidates.macro_ns = None; | |
1367 | } | |
cdc7bbd5 | 1368 | ambiguity_error(self.cx, diag, path_str, candidates.present_items().collect()); |
5869c6ff | 1369 | None |
3dfed10e | 1370 | } |
1b1a35ee | 1371 | } |
b7449926 | 1372 | } |
48663c56 | 1373 | } |
b7449926 XL |
1374 | } |
1375 | ||
cdc7bbd5 XL |
1376 | /// Get the section of a link between the backticks, |
1377 | /// or the whole link if there aren't any backticks. | |
1378 | /// | |
1379 | /// For example: | |
1380 | /// | |
1381 | /// ```text | |
1382 | /// [`Foo`] | |
1383 | /// ^^^ | |
1384 | /// ``` | |
1385 | fn range_between_backticks(ori_link: &MarkdownLink) -> Range<usize> { | |
1386 | let after_first_backtick_group = ori_link.link.bytes().position(|b| b != b'`').unwrap_or(0); | |
1387 | let before_second_backtick_group = ori_link | |
1388 | .link | |
1389 | .bytes() | |
1390 | .skip(after_first_backtick_group) | |
1391 | .position(|b| b == b'`') | |
1392 | .unwrap_or(ori_link.link.len()); | |
1393 | (ori_link.range.start + after_first_backtick_group) | |
1394 | ..(ori_link.range.start + before_second_backtick_group) | |
1395 | } | |
1396 | ||
1397 | /// Returns true if we should ignore `link` due to it being unlikely | |
1398 | /// that it is an intra-doc link. `link` should still have disambiguators | |
1399 | /// if there were any. | |
1400 | /// | |
1401 | /// The difference between this and [`should_ignore_link()`] is that this | |
1402 | /// check should only be used on links that still have disambiguators. | |
1403 | fn should_ignore_link_with_disambiguators(link: &str) -> bool { | |
1404 | link.contains(|ch: char| !(ch.is_alphanumeric() || ":_<>, !*&;@()".contains(ch))) | |
1405 | } | |
1406 | ||
1407 | /// Returns true if we should ignore `path_str` due to it being unlikely | |
1408 | /// that it is an intra-doc link. | |
1409 | fn should_ignore_link(path_str: &str) -> bool { | |
1410 | path_str.contains(|ch: char| !(ch.is_alphanumeric() || ":_<>, !*&;".contains(ch))) | |
1411 | } | |
1412 | ||
fc512014 | 1413 | #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] |
29967ef6 | 1414 | /// Disambiguators for a link. |
3dfed10e | 1415 | enum Disambiguator { |
29967ef6 XL |
1416 | /// `prim@` |
1417 | /// | |
1418 | /// This is buggy, see <https://github.com/rust-lang/rust/pull/77875#discussion_r503583103> | |
3dfed10e | 1419 | Primitive, |
29967ef6 | 1420 | /// `struct@` or `f()` |
3dfed10e | 1421 | Kind(DefKind), |
29967ef6 | 1422 | /// `type@` |
3dfed10e XL |
1423 | Namespace(Namespace), |
1424 | } | |
1425 | ||
1426 | impl Disambiguator { | |
136023e0 | 1427 | /// Given a link, parse and return `(disambiguator, path_str, link_text)`. |
cdc7bbd5 XL |
1428 | /// |
1429 | /// This returns `Ok(Some(...))` if a disambiguator was found, | |
1430 | /// `Ok(None)` if no disambiguator was found, or `Err(...)` | |
1431 | /// if there was a problem with the disambiguator. | |
136023e0 | 1432 | fn from_str(link: &str) -> Result<Option<(Self, &str, &str)>, (String, Range<usize>)> { |
3dfed10e XL |
1433 | use Disambiguator::{Kind, Namespace as NS, Primitive}; |
1434 | ||
3dfed10e XL |
1435 | if let Some(idx) = link.find('@') { |
1436 | let (prefix, rest) = link.split_at(idx); | |
1437 | let d = match prefix { | |
1438 | "struct" => Kind(DefKind::Struct), | |
1439 | "enum" => Kind(DefKind::Enum), | |
1440 | "trait" => Kind(DefKind::Trait), | |
1441 | "union" => Kind(DefKind::Union), | |
1442 | "module" | "mod" => Kind(DefKind::Mod), | |
1443 | "const" | "constant" => Kind(DefKind::Const), | |
5e7ed085 | 1444 | "static" => Kind(DefKind::Static(Mutability::Not)), |
3dfed10e XL |
1445 | "function" | "fn" | "method" => Kind(DefKind::Fn), |
1446 | "derive" => Kind(DefKind::Macro(MacroKind::Derive)), | |
1447 | "type" => NS(Namespace::TypeNS), | |
1448 | "value" => NS(Namespace::ValueNS), | |
1449 | "macro" => NS(Namespace::MacroNS), | |
1450 | "prim" | "primitive" => Primitive, | |
cdc7bbd5 | 1451 | _ => return Err((format!("unknown disambiguator `{}`", prefix), 0..idx)), |
3dfed10e | 1452 | }; |
136023e0 | 1453 | Ok(Some((d, &rest[1..], &rest[1..]))) |
3dfed10e | 1454 | } else { |
cdc7bbd5 XL |
1455 | let suffixes = [ |
1456 | ("!()", DefKind::Macro(MacroKind::Bang)), | |
136023e0 XL |
1457 | ("!{}", DefKind::Macro(MacroKind::Bang)), |
1458 | ("![]", DefKind::Macro(MacroKind::Bang)), | |
cdc7bbd5 XL |
1459 | ("()", DefKind::Fn), |
1460 | ("!", DefKind::Macro(MacroKind::Bang)), | |
1461 | ]; | |
136023e0 XL |
1462 | for (suffix, kind) in suffixes { |
1463 | if let Some(path_str) = link.strip_suffix(suffix) { | |
cdc7bbd5 | 1464 | // Avoid turning `!` or `()` into an empty string |
136023e0 XL |
1465 | if !path_str.is_empty() { |
1466 | return Ok(Some((Kind(kind), path_str, link))); | |
cdc7bbd5 XL |
1467 | } |
1468 | } | |
1469 | } | |
1470 | Ok(None) | |
3dfed10e XL |
1471 | } |
1472 | } | |
1473 | ||
3dfed10e XL |
1474 | fn ns(self) -> Namespace { |
1475 | match self { | |
1476 | Self::Namespace(n) => n, | |
1477 | Self::Kind(k) => { | |
1478 | k.ns().expect("only DefKinds with a valid namespace can be disambiguators") | |
1479 | } | |
1480 | Self::Primitive => TypeNS, | |
1481 | } | |
1482 | } | |
1483 | ||
1484 | fn article(self) -> &'static str { | |
1485 | match self { | |
1486 | Self::Namespace(_) => panic!("article() doesn't make sense for namespaces"), | |
1487 | Self::Kind(k) => k.article(), | |
1488 | Self::Primitive => "a", | |
1489 | } | |
1490 | } | |
1491 | ||
1492 | fn descr(self) -> &'static str { | |
1493 | match self { | |
1494 | Self::Namespace(n) => n.descr(), | |
5099ac24 FG |
1495 | // HACK(jynelson): the source of `DefKind::descr` only uses the DefId for |
1496 | // printing "module" vs "crate" so using the wrong ID is not a huge problem | |
1497 | Self::Kind(k) => k.descr(CRATE_DEF_ID.to_def_id()), | |
3dfed10e XL |
1498 | Self::Primitive => "builtin type", |
1499 | } | |
1500 | } | |
1501 | } | |
1502 | ||
29967ef6 | 1503 | /// A suggestion to show in a diagnostic. |
1b1a35ee | 1504 | enum Suggestion { |
29967ef6 | 1505 | /// `struct@` |
1b1a35ee | 1506 | Prefix(&'static str), |
29967ef6 | 1507 | /// `f()` |
1b1a35ee | 1508 | Function, |
29967ef6 | 1509 | /// `m!` |
1b1a35ee | 1510 | Macro, |
136023e0 XL |
1511 | /// `foo` without any disambiguator |
1512 | RemoveDisambiguator, | |
1b1a35ee XL |
1513 | } |
1514 | ||
1515 | impl Suggestion { | |
1516 | fn descr(&self) -> Cow<'static, str> { | |
1517 | match self { | |
1518 | Self::Prefix(x) => format!("prefix with `{}@`", x).into(), | |
1519 | Self::Function => "add parentheses".into(), | |
1520 | Self::Macro => "add an exclamation mark".into(), | |
136023e0 | 1521 | Self::RemoveDisambiguator => "remove the disambiguator".into(), |
1b1a35ee XL |
1522 | } |
1523 | } | |
1524 | ||
1525 | fn as_help(&self, path_str: &str) -> String { | |
1526 | // FIXME: if this is an implied shortcut link, it's bad style to suggest `@` | |
1527 | match self { | |
1528 | Self::Prefix(prefix) => format!("{}@{}", prefix, path_str), | |
1529 | Self::Function => format!("{}()", path_str), | |
1530 | Self::Macro => format!("{}!", path_str), | |
136023e0 | 1531 | Self::RemoveDisambiguator => path_str.into(), |
1b1a35ee XL |
1532 | } |
1533 | } | |
94222f64 XL |
1534 | |
1535 | fn as_help_span( | |
1536 | &self, | |
1537 | path_str: &str, | |
1538 | ori_link: &str, | |
1539 | sp: rustc_span::Span, | |
1540 | ) -> Vec<(rustc_span::Span, String)> { | |
1541 | let inner_sp = match ori_link.find('(') { | |
1542 | Some(index) => sp.with_hi(sp.lo() + BytePos(index as _)), | |
1543 | None => sp, | |
1544 | }; | |
1545 | let inner_sp = match ori_link.find('!') { | |
1546 | Some(index) => inner_sp.with_hi(inner_sp.lo() + BytePos(index as _)), | |
1547 | None => inner_sp, | |
1548 | }; | |
1549 | let inner_sp = match ori_link.find('@') { | |
1550 | Some(index) => inner_sp.with_lo(inner_sp.lo() + BytePos(index as u32 + 1)), | |
1551 | None => inner_sp, | |
1552 | }; | |
1553 | match self { | |
1554 | Self::Prefix(prefix) => { | |
1555 | // FIXME: if this is an implied shortcut link, it's bad style to suggest `@` | |
1556 | let mut sugg = vec![(sp.with_hi(inner_sp.lo()), format!("{}@", prefix))]; | |
1557 | if sp.hi() != inner_sp.hi() { | |
1558 | sugg.push((inner_sp.shrink_to_hi().with_hi(sp.hi()), String::new())); | |
1559 | } | |
1560 | sugg | |
1561 | } | |
1562 | Self::Function => { | |
1563 | let mut sugg = vec![(inner_sp.shrink_to_hi().with_hi(sp.hi()), "()".to_string())]; | |
1564 | if sp.lo() != inner_sp.lo() { | |
1565 | sugg.push((inner_sp.shrink_to_lo().with_lo(sp.lo()), String::new())); | |
1566 | } | |
1567 | sugg | |
1568 | } | |
1569 | Self::Macro => { | |
1570 | let mut sugg = vec![(inner_sp.shrink_to_hi(), "!".to_string())]; | |
1571 | if sp.lo() != inner_sp.lo() { | |
1572 | sugg.push((inner_sp.shrink_to_lo().with_lo(sp.lo()), String::new())); | |
1573 | } | |
1574 | sugg | |
1575 | } | |
a2a8927a | 1576 | Self::RemoveDisambiguator => vec![(sp, path_str.into())], |
94222f64 XL |
1577 | } |
1578 | } | |
1b1a35ee XL |
1579 | } |
1580 | ||
3dfed10e XL |
1581 | /// Reports a diagnostic for an intra-doc link. |
1582 | /// | |
1583 | /// If no link range is provided, or the source span of the link cannot be determined, the span of | |
1584 | /// the entire documentation block is used for the lint. If a range is provided but the span | |
1585 | /// calculation fails, a note is added to the diagnostic pointing to the link in the markdown. | |
1586 | /// | |
1587 | /// The `decorate` callback is invoked in all cases to allow further customization of the | |
1588 | /// diagnostic before emission. If the span of the link was able to be determined, the second | |
1589 | /// parameter of the callback will contain it, and the primary span of the diagnostic will be set | |
1590 | /// to it. | |
1591 | fn report_diagnostic( | |
6a06907d | 1592 | tcx: TyCtxt<'_>, |
1b1a35ee | 1593 | lint: &'static Lint, |
3dfed10e | 1594 | msg: &str, |
cdc7bbd5 | 1595 | DiagnosticInfo { item, ori_link: _, dox, link_range }: &DiagnosticInfo<'_>, |
5e7ed085 | 1596 | decorate: impl FnOnce(&mut Diagnostic, Option<rustc_span::Span>), |
b7449926 | 1597 | ) { |
04454e1e | 1598 | let Some(hir_id) = DocContext::as_local_hir_id(tcx, item.item_id) |
5e7ed085 FG |
1599 | else { |
1600 | // If non-local, no need to check anything. | |
1601 | info!("ignoring warning from parent crate: {}", msg); | |
1602 | return; | |
48663c56 | 1603 | }; |
3dfed10e | 1604 | |
cdc7bbd5 | 1605 | let sp = item.attr_span(tcx); |
b7449926 | 1606 | |
6a06907d | 1607 | tcx.struct_span_lint_hir(lint, hir_id, sp, |lint| { |
3dfed10e XL |
1608 | let mut diag = lint.build(msg); |
1609 | ||
94222f64 XL |
1610 | let span = |
1611 | super::source_span_for_markdown_range(tcx, dox, link_range, &item.attrs).map(|sp| { | |
3c0e092e XL |
1612 | if dox.as_bytes().get(link_range.start) == Some(&b'`') |
1613 | && dox.as_bytes().get(link_range.end - 1) == Some(&b'`') | |
94222f64 XL |
1614 | { |
1615 | sp.with_lo(sp.lo() + BytePos(1)).with_hi(sp.hi() - BytePos(1)) | |
1616 | } else { | |
1617 | sp | |
1618 | } | |
1619 | }); | |
5869c6ff | 1620 | |
fc512014 XL |
1621 | if let Some(sp) = span { |
1622 | diag.set_span(sp); | |
1623 | } else { | |
1624 | // blah blah blah\nblah\nblah [blah] blah blah\nblah blah | |
1625 | // ^ ~~~~ | |
1626 | // | link_range | |
1627 | // last_new_line_offset | |
1628 | let last_new_line_offset = dox[..link_range.start].rfind('\n').map_or(0, |n| n + 1); | |
1629 | let line = dox[last_new_line_offset..].lines().next().unwrap_or(""); | |
1630 | ||
1631 | // Print the line containing the `link_range` and manually mark it with '^'s. | |
1632 | diag.note(&format!( | |
1633 | "the link appears in this line:\n\n{line}\n\ | |
5869c6ff | 1634 | {indicator: <before$}{indicator:^<found$}", |
fc512014 XL |
1635 | line = line, |
1636 | indicator = "", | |
1637 | before = link_range.start - last_new_line_offset, | |
1638 | found = link_range.len(), | |
1639 | )); | |
3dfed10e XL |
1640 | } |
1641 | ||
1642 | decorate(&mut diag, span); | |
1643 | ||
1644 | diag.emit(); | |
1645 | }); | |
b7449926 XL |
1646 | } |
1647 | ||
29967ef6 XL |
1648 | /// Reports a link that failed to resolve. |
1649 | /// | |
1650 | /// This also tries to resolve any intermediate path segments that weren't | |
1651 | /// handled earlier. For example, if passed `Item::Crate(std)` and `path_str` | |
1652 | /// `std::io::Error::x`, this will resolve `std::io::Error`. | |
60c5eb7d | 1653 | fn resolution_failure( |
6a06907d | 1654 | collector: &mut LinkCollector<'_, '_>, |
cdc7bbd5 | 1655 | diag_info: DiagnosticInfo<'_>, |
60c5eb7d | 1656 | path_str: &str, |
1b1a35ee | 1657 | disambiguator: Option<Disambiguator>, |
1b1a35ee | 1658 | kinds: SmallVec<[ResolutionFailure<'_>; 3]>, |
923072b8 | 1659 | ) -> Option<(Res, Option<DefId>)> { |
5869c6ff | 1660 | let tcx = collector.cx.tcx; |
923072b8 | 1661 | let mut recovered_res = None; |
3dfed10e | 1662 | report_diagnostic( |
6a06907d | 1663 | tcx, |
1b1a35ee | 1664 | BROKEN_INTRA_DOC_LINKS, |
3dfed10e | 1665 | &format!("unresolved link to `{}`", path_str), |
cdc7bbd5 | 1666 | &diag_info, |
3dfed10e | 1667 | |diag, sp| { |
5869c6ff | 1668 | let item = |res: Res| format!("the {} `{}`", res.descr(), res.name(tcx),); |
1b1a35ee | 1669 | let assoc_item_not_allowed = |res: Res| { |
5869c6ff | 1670 | let name = res.name(tcx); |
1b1a35ee XL |
1671 | format!( |
1672 | "`{}` is {} {}, not a module or type, and cannot have associated items", | |
1673 | name, | |
1674 | res.article(), | |
1675 | res.descr() | |
1676 | ) | |
1677 | }; | |
1678 | // ignore duplicates | |
1679 | let mut variants_seen = SmallVec::<[_; 3]>::new(); | |
1680 | for mut failure in kinds { | |
1681 | let variant = std::mem::discriminant(&failure); | |
1682 | if variants_seen.contains(&variant) { | |
1683 | continue; | |
1684 | } | |
1685 | variants_seen.push(variant); | |
1686 | ||
04454e1e | 1687 | if let ResolutionFailure::NotResolved(UnresolvedPath { |
5e7ed085 FG |
1688 | item_id, |
1689 | module_id, | |
1690 | partial_res, | |
1691 | unresolved, | |
04454e1e | 1692 | }) = &mut failure |
1b1a35ee XL |
1693 | { |
1694 | use DefKind::*; | |
1695 | ||
5e7ed085 | 1696 | let item_id = *item_id; |
1b1a35ee XL |
1697 | let module_id = *module_id; |
1698 | // FIXME(jynelson): this might conflict with my `Self` fix in #76467 | |
1699 | // FIXME: maybe use itertools `collect_tuple` instead? | |
1700 | fn split(path: &str) -> Option<(&str, &str)> { | |
1701 | let mut splitter = path.rsplitn(2, "::"); | |
1702 | splitter.next().and_then(|right| splitter.next().map(|left| (left, right))) | |
1703 | } | |
3dfed10e | 1704 | |
1b1a35ee XL |
1705 | // Check if _any_ parent of the path gets resolved. |
1706 | // If so, report it and say the first which failed; if not, say the first path segment didn't resolve. | |
1707 | let mut name = path_str; | |
1708 | 'outer: loop { | |
5099ac24 | 1709 | let Some((start, end)) = split(name) else { |
1b1a35ee XL |
1710 | // avoid bug that marked [Quux::Z] as missing Z, not Quux |
1711 | if partial_res.is_none() { | |
1712 | *unresolved = name.into(); | |
1713 | } | |
1714 | break; | |
1715 | }; | |
1716 | name = start; | |
136023e0 | 1717 | for ns in [TypeNS, ValueNS, MacroNS] { |
04454e1e | 1718 | if let Ok(res) = collector.resolve(start, ns, item_id, module_id) { |
1b1a35ee | 1719 | debug!("found partial_res={:?}", res); |
04454e1e | 1720 | *partial_res = Some(full_res(collector.cx.tcx, res)); |
1b1a35ee XL |
1721 | *unresolved = end.into(); |
1722 | break 'outer; | |
1723 | } | |
1724 | } | |
1725 | *unresolved = end.into(); | |
1726 | } | |
1727 | ||
1728 | let last_found_module = match *partial_res { | |
1729 | Some(Res::Def(DefKind::Mod, id)) => Some(id), | |
1730 | None => Some(module_id), | |
1731 | _ => None, | |
1732 | }; | |
1733 | // See if this was a module: `[path]` or `[std::io::nope]` | |
1734 | if let Some(module) = last_found_module { | |
29967ef6 XL |
1735 | let note = if partial_res.is_some() { |
1736 | // Part of the link resolved; e.g. `std::io::nonexistent` | |
5869c6ff | 1737 | let module_name = tcx.item_name(module); |
29967ef6 XL |
1738 | format!("no item named `{}` in module `{}`", unresolved, module_name) |
1739 | } else { | |
1740 | // None of the link resolved; e.g. `Notimported` | |
1741 | format!("no item named `{}` in scope", unresolved) | |
1742 | }; | |
1b1a35ee XL |
1743 | if let Some(span) = sp { |
1744 | diag.span_label(span, ¬e); | |
1745 | } else { | |
1746 | diag.note(¬e); | |
1747 | } | |
29967ef6 | 1748 | |
1b1a35ee | 1749 | if !path_str.contains("::") { |
923072b8 FG |
1750 | if disambiguator.map_or(true, |d| d.ns() == MacroNS) |
1751 | && let Some(&res) = collector.cx.resolver_caches.all_macro_rules | |
1752 | .get(&Symbol::intern(path_str)) | |
1753 | { | |
1754 | diag.note(format!( | |
1755 | "`macro_rules` named `{path_str}` exists in this crate, \ | |
1756 | but it is not in scope at this link's location" | |
1757 | )); | |
1758 | recovered_res = res.try_into().ok().map(|res| (res, None)); | |
1759 | } else { | |
1760 | // If the link has `::` in it, assume it was meant to be an | |
1761 | // intra-doc link. Otherwise, the `[]` might be unrelated. | |
1762 | diag.help("to escape `[` and `]` characters, \ | |
1763 | add '\\' before them like `\\[` or `\\]`"); | |
1764 | } | |
1b1a35ee | 1765 | } |
29967ef6 | 1766 | |
1b1a35ee XL |
1767 | continue; |
1768 | } | |
1769 | ||
1770 | // Otherwise, it must be an associated item or variant | |
1771 | let res = partial_res.expect("None case was handled by `last_found_module`"); | |
5869c6ff XL |
1772 | let name = res.name(tcx); |
1773 | let kind = match res { | |
1774 | Res::Def(kind, _) => Some(kind), | |
1775 | Res::Primitive(_) => None, | |
1b1a35ee XL |
1776 | }; |
1777 | let path_description = if let Some(kind) = kind { | |
1778 | match kind { | |
1779 | Mod | ForeignMod => "inner item", | |
1780 | Struct => "field or associated item", | |
1781 | Enum | Union => "variant or associated item", | |
1782 | Variant | |
1783 | | Field | |
1784 | | Closure | |
1785 | | Generator | |
1786 | | AssocTy | |
1787 | | AssocConst | |
1788 | | AssocFn | |
1789 | | Fn | |
1790 | | Macro(_) | |
1791 | | Const | |
1792 | | ConstParam | |
1793 | | ExternCrate | |
1794 | | Use | |
1795 | | LifetimeParam | |
1796 | | Ctor(_, _) | |
3c0e092e XL |
1797 | | AnonConst |
1798 | | InlineConst => { | |
1b1a35ee XL |
1799 | let note = assoc_item_not_allowed(res); |
1800 | if let Some(span) = sp { | |
1801 | diag.span_label(span, ¬e); | |
1802 | } else { | |
1803 | diag.note(¬e); | |
1804 | } | |
1805 | return; | |
1806 | } | |
1807 | Trait | TyAlias | ForeignTy | OpaqueTy | TraitAlias | TyParam | |
5e7ed085 | 1808 | | Static(_) => "associated item", |
1b1a35ee XL |
1809 | Impl | GlobalAsm => unreachable!("not a path"), |
1810 | } | |
1811 | } else { | |
1812 | "associated item" | |
1813 | }; | |
1814 | let note = format!( | |
1815 | "the {} `{}` has no {} named `{}`", | |
1816 | res.descr(), | |
1817 | name, | |
1818 | disambiguator.map_or(path_description, |d| d.descr()), | |
1819 | unresolved, | |
1820 | ); | |
1821 | if let Some(span) = sp { | |
1822 | diag.span_label(span, ¬e); | |
1823 | } else { | |
1824 | diag.note(¬e); | |
1825 | } | |
1826 | ||
1827 | continue; | |
1828 | } | |
1829 | let note = match failure { | |
1830 | ResolutionFailure::NotResolved { .. } => unreachable!("handled above"), | |
6a06907d | 1831 | ResolutionFailure::WrongNamespace { res, expected_ns } => { |
5099ac24 | 1832 | suggest_disambiguator(res, diag, path_str, diag_info.ori_link, sp); |
1b1a35ee XL |
1833 | |
1834 | format!( | |
1835 | "this link resolves to {}, which is not in the {} namespace", | |
1836 | item(res), | |
1837 | expected_ns.descr() | |
1838 | ) | |
1839 | } | |
1b1a35ee XL |
1840 | }; |
1841 | if let Some(span) = sp { | |
1842 | diag.span_label(span, ¬e); | |
1843 | } else { | |
1844 | diag.note(¬e); | |
1845 | } | |
1846 | } | |
3dfed10e | 1847 | }, |
60c5eb7d | 1848 | ); |
923072b8 FG |
1849 | |
1850 | recovered_res | |
60c5eb7d XL |
1851 | } |
1852 | ||
04454e1e FG |
1853 | fn report_multiple_anchors(cx: &DocContext<'_>, diag_info: DiagnosticInfo<'_>) { |
1854 | let msg = format!("`{}` contains multiple anchors", diag_info.ori_link); | |
1855 | anchor_failure(cx, diag_info, &msg, 1) | |
1856 | } | |
3dfed10e | 1857 | |
923072b8 FG |
1858 | fn report_anchor_conflict(cx: &DocContext<'_>, diag_info: DiagnosticInfo<'_>, def_id: DefId) { |
1859 | let (link, kind) = (diag_info.ori_link, Res::from_def_id(cx.tcx, def_id).descr()); | |
04454e1e FG |
1860 | let msg = format!("`{link}` contains an anchor, but links to {kind}s are already anchored"); |
1861 | anchor_failure(cx, diag_info, &msg, 0) | |
1862 | } | |
1863 | ||
1864 | /// Report an anchor failure. | |
1865 | fn anchor_failure( | |
1866 | cx: &DocContext<'_>, | |
1867 | diag_info: DiagnosticInfo<'_>, | |
1868 | msg: &str, | |
1869 | anchor_idx: usize, | |
1870 | ) { | |
923072b8 | 1871 | report_diagnostic(cx.tcx, BROKEN_INTRA_DOC_LINKS, msg, &diag_info, |diag, sp| { |
cdc7bbd5 XL |
1872 | if let Some(mut sp) = sp { |
1873 | if let Some((fragment_offset, _)) = | |
1874 | diag_info.ori_link.char_indices().filter(|(_, x)| *x == '#').nth(anchor_idx) | |
1875 | { | |
94222f64 | 1876 | sp = sp.with_lo(sp.lo() + BytePos(fragment_offset as _)); |
cdc7bbd5 XL |
1877 | } |
1878 | diag.span_label(sp, "invalid anchor"); | |
1879 | } | |
3dfed10e | 1880 | }); |
60c5eb7d XL |
1881 | } |
1882 | ||
cdc7bbd5 XL |
1883 | /// Report an error in the link disambiguator. |
1884 | fn disambiguator_error( | |
1885 | cx: &DocContext<'_>, | |
1886 | mut diag_info: DiagnosticInfo<'_>, | |
1887 | disambiguator_range: Range<usize>, | |
1888 | msg: &str, | |
1889 | ) { | |
1890 | diag_info.link_range = disambiguator_range; | |
17df50a5 XL |
1891 | report_diagnostic(cx.tcx, BROKEN_INTRA_DOC_LINKS, msg, &diag_info, |diag, _sp| { |
1892 | let msg = format!( | |
1893 | "see {}/rustdoc/linking-to-items-by-name.html#namespaces-and-disambiguators for more info about disambiguators", | |
1894 | crate::DOC_RUST_LANG_ORG_CHANNEL | |
1895 | ); | |
1896 | diag.note(&msg); | |
1897 | }); | |
cdc7bbd5 XL |
1898 | } |
1899 | ||
04454e1e FG |
1900 | fn report_malformed_generics( |
1901 | cx: &DocContext<'_>, | |
1902 | diag_info: DiagnosticInfo<'_>, | |
1903 | err: MalformedGenerics, | |
1904 | path_str: &str, | |
1905 | ) { | |
1906 | report_diagnostic( | |
1907 | cx.tcx, | |
1908 | BROKEN_INTRA_DOC_LINKS, | |
1909 | &format!("unresolved link to `{}`", path_str), | |
1910 | &diag_info, | |
1911 | |diag, sp| { | |
1912 | let note = match err { | |
1913 | MalformedGenerics::UnbalancedAngleBrackets => "unbalanced angle brackets", | |
1914 | MalformedGenerics::MissingType => "missing type for generic parameters", | |
1915 | MalformedGenerics::HasFullyQualifiedSyntax => { | |
1916 | diag.note( | |
1917 | "see https://github.com/rust-lang/rust/issues/74563 for more information", | |
1918 | ); | |
1919 | "fully-qualified syntax is unsupported" | |
1920 | } | |
1921 | MalformedGenerics::InvalidPathSeparator => "has invalid path separator", | |
1922 | MalformedGenerics::TooManyAngleBrackets => "too many angle brackets", | |
1923 | MalformedGenerics::EmptyAngleBrackets => "empty angle brackets", | |
1924 | }; | |
1925 | if let Some(span) = sp { | |
1926 | diag.span_label(span, note); | |
1927 | } else { | |
1928 | diag.note(note); | |
1929 | } | |
1930 | }, | |
1931 | ); | |
1932 | } | |
1933 | ||
29967ef6 | 1934 | /// Report an ambiguity error, where there were multiple possible resolutions. |
532ac7d7 XL |
1935 | fn ambiguity_error( |
1936 | cx: &DocContext<'_>, | |
cdc7bbd5 | 1937 | diag_info: DiagnosticInfo<'_>, |
532ac7d7 | 1938 | path_str: &str, |
1b1a35ee | 1939 | candidates: Vec<Res>, |
532ac7d7 | 1940 | ) { |
3dfed10e XL |
1941 | let mut msg = format!("`{}` is ", path_str); |
1942 | ||
1943 | match candidates.as_slice() { | |
1b1a35ee | 1944 | [first_def, second_def] => { |
3dfed10e XL |
1945 | msg += &format!( |
1946 | "both {} {} and {} {}", | |
1947 | first_def.article(), | |
1948 | first_def.descr(), | |
1949 | second_def.article(), | |
1950 | second_def.descr(), | |
1951 | ); | |
48663c56 | 1952 | } |
3dfed10e XL |
1953 | _ => { |
1954 | let mut candidates = candidates.iter().peekable(); | |
1b1a35ee | 1955 | while let Some(res) = candidates.next() { |
3dfed10e XL |
1956 | if candidates.peek().is_some() { |
1957 | msg += &format!("{} {}, ", res.article(), res.descr()); | |
1958 | } else { | |
1959 | msg += &format!("and {} {}", res.article(), res.descr()); | |
74b04a01 XL |
1960 | } |
1961 | } | |
3dfed10e XL |
1962 | } |
1963 | } | |
74b04a01 | 1964 | |
cdc7bbd5 | 1965 | report_diagnostic(cx.tcx, BROKEN_INTRA_DOC_LINKS, &msg, &diag_info, |diag, sp| { |
3dfed10e XL |
1966 | if let Some(sp) = sp { |
1967 | diag.span_label(sp, "ambiguous link"); | |
1b1a35ee XL |
1968 | } else { |
1969 | diag.note("ambiguous link"); | |
1970 | } | |
74b04a01 | 1971 | |
1b1a35ee | 1972 | for res in candidates { |
5099ac24 | 1973 | suggest_disambiguator(res, diag, path_str, diag_info.ori_link, sp); |
1b1a35ee XL |
1974 | } |
1975 | }); | |
1976 | } | |
532ac7d7 | 1977 | |
29967ef6 XL |
1978 | /// In case of an ambiguity or mismatched disambiguator, suggest the correct |
1979 | /// disambiguator. | |
1b1a35ee | 1980 | fn suggest_disambiguator( |
5099ac24 | 1981 | res: Res, |
5e7ed085 | 1982 | diag: &mut Diagnostic, |
1b1a35ee | 1983 | path_str: &str, |
94222f64 | 1984 | ori_link: &str, |
1b1a35ee | 1985 | sp: Option<rustc_span::Span>, |
1b1a35ee | 1986 | ) { |
5099ac24 FG |
1987 | let suggestion = res.disambiguator_suggestion(); |
1988 | let help = format!("to link to the {}, {}", res.descr(), suggestion.descr()); | |
3dfed10e | 1989 | |
1b1a35ee | 1990 | if let Some(sp) = sp { |
94222f64 XL |
1991 | let mut spans = suggestion.as_help_span(path_str, ori_link, sp); |
1992 | if spans.len() > 1 { | |
1993 | diag.multipart_suggestion(&help, spans, Applicability::MaybeIncorrect); | |
1b1a35ee | 1994 | } else { |
94222f64 XL |
1995 | let (sp, suggestion_text) = spans.pop().unwrap(); |
1996 | diag.span_suggestion_verbose(sp, &help, suggestion_text, Applicability::MaybeIncorrect); | |
1997 | } | |
1b1a35ee XL |
1998 | } else { |
1999 | diag.help(&format!("{}: {}", help, suggestion.as_help(path_str))); | |
2000 | } | |
3dfed10e XL |
2001 | } |
2002 | ||
29967ef6 | 2003 | /// Report a link from a public item to a private one. |
cdc7bbd5 | 2004 | fn privacy_error(cx: &DocContext<'_>, diag_info: &DiagnosticInfo<'_>, path_str: &str) { |
fc512014 | 2005 | let sym; |
cdc7bbd5 | 2006 | let item_name = match diag_info.item.name { |
fc512014 | 2007 | Some(name) => { |
a2a8927a XL |
2008 | sym = name; |
2009 | sym.as_str() | |
fc512014 XL |
2010 | } |
2011 | None => "<unknown>", | |
2012 | }; | |
3dfed10e XL |
2013 | let msg = |
2014 | format!("public documentation for `{}` links to private item `{}`", item_name, path_str); | |
2015 | ||
cdc7bbd5 | 2016 | report_diagnostic(cx.tcx, PRIVATE_INTRA_DOC_LINKS, &msg, diag_info, |diag, sp| { |
3dfed10e XL |
2017 | if let Some(sp) = sp { |
2018 | diag.span_label(sp, "this item is private"); | |
2019 | } | |
2020 | ||
2021 | let note_msg = if cx.render_options.document_private { | |
2022 | "this link resolves only because you passed `--document-private-items`, but will break without" | |
2023 | } else { | |
2024 | "this link will resolve properly if you pass `--document-private-items`" | |
2025 | }; | |
2026 | diag.note(note_msg); | |
2027 | }); | |
b7449926 XL |
2028 | } |
2029 | ||
29967ef6 | 2030 | /// Resolve a primitive type or value. |
5869c6ff XL |
2031 | fn resolve_primitive(path_str: &str, ns: Namespace) -> Option<Res> { |
2032 | if ns != TypeNS { | |
2033 | return None; | |
3dfed10e | 2034 | } |
5869c6ff XL |
2035 | use PrimitiveType::*; |
2036 | let prim = match path_str { | |
2037 | "isize" => Isize, | |
2038 | "i8" => I8, | |
2039 | "i16" => I16, | |
2040 | "i32" => I32, | |
2041 | "i64" => I64, | |
2042 | "i128" => I128, | |
2043 | "usize" => Usize, | |
2044 | "u8" => U8, | |
2045 | "u16" => U16, | |
2046 | "u32" => U32, | |
2047 | "u64" => U64, | |
2048 | "u128" => U128, | |
2049 | "f32" => F32, | |
2050 | "f64" => F64, | |
2051 | "char" => Char, | |
2052 | "bool" | "true" | "false" => Bool, | |
2053 | "str" | "&str" => Str, | |
2054 | // See #80181 for why these don't have symbols associated. | |
2055 | "slice" => Slice, | |
2056 | "array" => Array, | |
2057 | "tuple" => Tuple, | |
2058 | "unit" => Unit, | |
2059 | "pointer" | "*const" | "*mut" => RawPointer, | |
2060 | "reference" | "&" | "&mut" => Reference, | |
2061 | "fn" => Fn, | |
2062 | "never" | "!" => Never, | |
2063 | _ => return None, | |
2064 | }; | |
2065 | debug!("resolved primitives {:?}", prim); | |
2066 | Some(Res::Primitive(prim)) | |
b7449926 | 2067 | } |
9fa01778 | 2068 | |
04454e1e | 2069 | fn strip_generics_from_path(path_str: &str) -> Result<String, MalformedGenerics> { |
29967ef6 XL |
2070 | let mut stripped_segments = vec![]; |
2071 | let mut path = path_str.chars().peekable(); | |
2072 | let mut segment = Vec::new(); | |
2073 | ||
2074 | while let Some(chr) = path.next() { | |
2075 | match chr { | |
2076 | ':' => { | |
2077 | if path.next_if_eq(&':').is_some() { | |
2078 | let stripped_segment = | |
2079 | strip_generics_from_path_segment(mem::take(&mut segment))?; | |
2080 | if !stripped_segment.is_empty() { | |
2081 | stripped_segments.push(stripped_segment); | |
2082 | } | |
2083 | } else { | |
04454e1e | 2084 | return Err(MalformedGenerics::InvalidPathSeparator); |
29967ef6 XL |
2085 | } |
2086 | } | |
2087 | '<' => { | |
2088 | segment.push(chr); | |
2089 | ||
2090 | match path.next() { | |
2091 | Some('<') => { | |
04454e1e | 2092 | return Err(MalformedGenerics::TooManyAngleBrackets); |
29967ef6 XL |
2093 | } |
2094 | Some('>') => { | |
04454e1e | 2095 | return Err(MalformedGenerics::EmptyAngleBrackets); |
29967ef6 XL |
2096 | } |
2097 | Some(chr) => { | |
2098 | segment.push(chr); | |
2099 | ||
2100 | while let Some(chr) = path.next_if(|c| *c != '>') { | |
2101 | segment.push(chr); | |
2102 | } | |
2103 | } | |
2104 | None => break, | |
2105 | } | |
2106 | } | |
2107 | _ => segment.push(chr), | |
2108 | } | |
fc512014 | 2109 | trace!("raw segment: {:?}", segment); |
29967ef6 XL |
2110 | } |
2111 | ||
2112 | if !segment.is_empty() { | |
2113 | let stripped_segment = strip_generics_from_path_segment(segment)?; | |
2114 | if !stripped_segment.is_empty() { | |
2115 | stripped_segments.push(stripped_segment); | |
2116 | } | |
2117 | } | |
2118 | ||
2119 | debug!("path_str: {:?}\nstripped segments: {:?}", path_str, &stripped_segments); | |
2120 | ||
2121 | let stripped_path = stripped_segments.join("::"); | |
2122 | ||
04454e1e | 2123 | if !stripped_path.is_empty() { Ok(stripped_path) } else { Err(MalformedGenerics::MissingType) } |
29967ef6 XL |
2124 | } |
2125 | ||
04454e1e | 2126 | fn strip_generics_from_path_segment(segment: Vec<char>) -> Result<String, MalformedGenerics> { |
29967ef6 XL |
2127 | let mut stripped_segment = String::new(); |
2128 | let mut param_depth = 0; | |
2129 | ||
2130 | let mut latest_generics_chunk = String::new(); | |
2131 | ||
2132 | for c in segment { | |
2133 | if c == '<' { | |
2134 | param_depth += 1; | |
2135 | latest_generics_chunk.clear(); | |
2136 | } else if c == '>' { | |
2137 | param_depth -= 1; | |
2138 | if latest_generics_chunk.contains(" as ") { | |
2139 | // The segment tries to use fully-qualified syntax, which is currently unsupported. | |
2140 | // Give a helpful error message instead of completely ignoring the angle brackets. | |
04454e1e | 2141 | return Err(MalformedGenerics::HasFullyQualifiedSyntax); |
29967ef6 XL |
2142 | } |
2143 | } else { | |
2144 | if param_depth == 0 { | |
2145 | stripped_segment.push(c); | |
2146 | } else { | |
2147 | latest_generics_chunk.push(c); | |
2148 | } | |
2149 | } | |
2150 | } | |
2151 | ||
2152 | if param_depth == 0 { | |
2153 | Ok(stripped_segment) | |
2154 | } else { | |
2155 | // The segment has unbalanced angle brackets, e.g. `Vec<T` or `Vec<T>>` | |
04454e1e | 2156 | Err(MalformedGenerics::UnbalancedAngleBrackets) |
29967ef6 | 2157 | } |
9fa01778 | 2158 | } |