Skip to main content

rustdoc/passes/
collect_intra_doc_links.rs

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