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