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