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_attr_ir::{Attribute, AttributeKind, find_attr};
12use rustc_data_structures::fx::{FxHashMap, FxHashSet, FxIndexMap, FxIndexSet};
13use rustc_data_structures::intern::Interned;
14use rustc_errors::{Applicability, Diag, DiagMessage};
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::{Mutability, Safety};
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    DocStrings, MalformedGenerics, has_primitive_or_keyword_or_attribute_docs,
25    prepare_to_doc_link_resolution, 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_inner = |item_id, module_id, doc: &str| {
1095            for md_link in preprocessed_markdown_links(&doc) {
1096                let link = self.resolve_link(&doc, item, item_id, module_id, &md_link);
1097                if let Some(link) = link {
1098                    self.cx
1099                        .cache
1100                        .intra_doc_links
1101                        .entry(item.item_or_reexport_id())
1102                        .or_default()
1103                        .insert(link);
1104                }
1105            }
1106        };
1107
1108        let mut try_insert_links = |item_id, docs: &DocStrings| {
1109            if should_skip_link_resolution(item_id) {
1110                return;
1111            }
1112            match tcx.def_kind(item_id) {
1113                DefKind::Mod => {
1114                    if !docs.outer.is_empty() {
1115                        try_insert_links_inner(
1116                            item_id,
1117                            find_nearest_parent_module(tcx, item_id).unwrap(),
1118                            &docs.outer,
1119                        );
1120                    }
1121                    if !docs.inner.is_empty() {
1122                        try_insert_links_inner(item_id, ModId::new_unchecked(item_id), &docs.inner);
1123                    }
1124                }
1125                _ => {
1126                    // It's the same handling for non-module items.
1127                    let module_id = find_nearest_parent_module(tcx, item_id).unwrap();
1128                    try_insert_links_inner(
1129                        item_id,
1130                        module_id,
1131                        &format!("{}{}", docs.outer, docs.inner),
1132                    );
1133                }
1134            }
1135        };
1136
1137        // We want to resolve in the lexical scope of the documentation.
1138        // In the presence of re-exports, this is not the same as the module of the item.
1139        // Rather than merging all documentation into one, resolve it one attribute at a time
1140        // so we know which module it came from.
1141        for (item_id, doc) in prepare_to_doc_link_resolution(&item.attrs.doc_strings) {
1142            if !may_have_doc_links(&doc.inner) && !may_have_doc_links(&doc.outer) {
1143                continue;
1144            }
1145
1146            debug!("combined_docs=outer: {} inner: {}", doc.outer, doc.inner);
1147            // NOTE: if there are links that start in one crate and end in another, this will not resolve them.
1148            // This is a degenerate case and it's not supported by rustdoc.
1149            let item_id = item_id.unwrap_or_else(|| item.item_id.expect_def_id());
1150            try_insert_links(item_id, &doc)
1151        }
1152
1153        // Also resolve links in the note text of `#[deprecated]`.
1154        for attr in &item.attrs.other_attrs {
1155            let Attribute::Parsed(AttributeKind::Deprecated { span: depr_span, deprecation }) =
1156                attr
1157            else {
1158                continue;
1159            };
1160            let Some(note_sym) = deprecation.note else { continue };
1161            let note = note_sym.as_str();
1162
1163            if !may_have_doc_links(note) {
1164                continue;
1165            }
1166
1167            debug!("deprecated_note={note}");
1168            // When resolving an intra-doc link inside a deprecation note that is on an inlined
1169            // `use` statement, we need to use the `def_id` of the `use` statement, not the
1170            // inlined item.
1171            // <https://github.com/rust-lang/rust/pull/151120>
1172            let item_id = if let Some(inline_stmt_id) = item.inline_stmt_id {
1173                let target_def_id = item.item_id.expect_def_id();
1174                reexport_chain(tcx, inline_stmt_id, target_def_id)
1175                    .iter()
1176                    .flat_map(|reexport| reexport.id())
1177                    .find(|&reexport_def_id| {
1178                        find_attr!(
1179                            tcx,
1180                            reexport_def_id,
1181                            Deprecated { span, .. } if span == depr_span
1182                        )
1183                    })
1184                    .unwrap_or(target_def_id)
1185            } else {
1186                item.item_id.expect_def_id()
1187            };
1188            try_insert_links(item_id, &DocStrings { outer: note.to_owned(), inner: String::new() });
1189        }
1190    }
1191
1192    pub(crate) fn save_link(&mut self, item_id: ItemId, link: ItemLink) {
1193        self.cx.cache.intra_doc_links.entry(item_id).or_default().insert(link);
1194    }
1195
1196    /// This is the entry point for resolving an intra-doc link.
1197    fn resolve_link(
1198        &mut self,
1199        dox: &str,
1200        item: &Item,
1201        item_id: DefId,
1202        module_id: ModId,
1203        PreprocessedMarkdownLink(pp_link, ori_link): &PreprocessedMarkdownLink,
1204    ) -> Option<ItemLink> {
1205        trace!("considering link '{}'", ori_link.link);
1206
1207        let diag_info = DiagnosticInfo {
1208            item,
1209            dox,
1210            ori_link: &ori_link.link,
1211            link_range: ori_link.range.clone(),
1212        };
1213        let PreprocessingInfo { path_str, disambiguator, extra_fragment, link_text } =
1214            pp_link.as_ref().map_err(|err| err.report(self.cx, diag_info.clone())).ok()?;
1215        let disambiguator = *disambiguator;
1216
1217        let mut resolved = self.resolve_with_disambiguator_cached(
1218            ResolutionInfo {
1219                item_id,
1220                module_id,
1221                dis: disambiguator,
1222                path_str: path_str.clone(),
1223                extra_fragment: extra_fragment.clone(),
1224            },
1225            diag_info.clone(), // this struct should really be Copy, but Range is not :(
1226            // For reference-style links we want to report only one error so unsuccessful
1227            // resolutions are cached, for other links we want to report an error every
1228            // time so they are not cached.
1229            matches!(ori_link.kind, LinkType::Reference | LinkType::Shortcut),
1230        )?;
1231
1232        if resolved.len() > 1 {
1233            let links = AmbiguousLinks {
1234                link_text: link_text.clone(),
1235                diag_info: diag_info.into(),
1236                resolved,
1237            };
1238
1239            self.links
1240                .ambiguous
1241                .entry((item.item_id, path_str.to_string()))
1242                .or_default()
1243                .push(links);
1244            None
1245        } else if let Some((res, fragment)) = resolved.pop() {
1246            self.compute_link(res, fragment, path_str, disambiguator, diag_info, link_text)
1247        } else {
1248            None
1249        }
1250    }
1251
1252    /// Returns `true` if a link could be generated from the given intra-doc information.
1253    ///
1254    /// This is a very light version of `format::href_with_jump_to_def_path_depth` since we're only interested
1255    /// about whether we can generate a link to an item or not.
1256    ///
1257    /// * If `original_did` is local, then we check if the item is reexported or public.
1258    /// * If `original_did` is not local, then we check if the crate it comes from is a direct
1259    ///   public dependency.
1260    fn validate_link(&self, original_did: DefId) -> bool {
1261        let tcx = self.cx.tcx;
1262        let def_kind = tcx.def_kind(original_did);
1263        let did = match def_kind {
1264            DefKind::AssocTy | DefKind::AssocFn | DefKind::AssocConst | DefKind::Variant => {
1265                // documented on their parent's page
1266                tcx.parent(original_did)
1267            }
1268            // If this a constructor, we get the parent (either a struct or a variant) and then
1269            // generate the link for this item.
1270            DefKind::Ctor(..) => return self.validate_link(tcx.parent(original_did)),
1271            DefKind::ExternCrate => {
1272                // Link to the crate itself, not the `extern crate` item.
1273                if let Some(local_did) = original_did.as_local() {
1274                    tcx.extern_mod_stmt_cnum(local_did).unwrap_or(LOCAL_CRATE).as_def_id()
1275                } else {
1276                    original_did
1277                }
1278            }
1279            _ => original_did,
1280        };
1281
1282        let cache = &self.cx.cache;
1283        if !original_did.is_local()
1284            && !cache.effective_visibilities.is_directly_public(tcx, did)
1285            && !cache.document_private
1286            && !cache.primitive_locations.values().any(|&id| id == did)
1287        {
1288            return false;
1289        }
1290
1291        cache.paths.get(&did).is_some()
1292            || cache.external_paths.contains_key(&did)
1293            || !did.is_local()
1294    }
1295
1296    fn resolve_ambiguities(&mut self) {
1297        let mut ambiguous_links = mem::take(&mut self.links.ambiguous);
1298        for ((item_id, path_str), info_items) in ambiguous_links.iter_mut() {
1299            for info in info_items {
1300                info.resolved.retain(|(res, _)| match res {
1301                    Res::Def(_, def_id) => self.validate_link(*def_id),
1302                    // Primitive types are always valid.
1303                    Res::Primitive(_) => true,
1304                });
1305                let diag_info = info.diag_info.as_info();
1306                match info.resolved.len() {
1307                    1 => {
1308                        let (res, fragment) = info.resolved.pop().unwrap();
1309                        if let Some(link) = self.compute_link(
1310                            res,
1311                            fragment,
1312                            path_str,
1313                            None,
1314                            diag_info,
1315                            &info.link_text,
1316                        ) {
1317                            self.save_link(*item_id, link);
1318                        }
1319                    }
1320                    0 => {
1321                        report_diagnostic(
1322                            self.cx.tcx,
1323                            BROKEN_INTRA_DOC_LINKS,
1324                            format!("all items matching `{path_str}` are private or doc(hidden)"),
1325                            &diag_info,
1326                            |diag, sp, _| {
1327                                if let Some(sp) = sp {
1328                                    diag.span_label(sp, "unresolved link");
1329                                } else {
1330                                    diag.note("unresolved link");
1331                                }
1332                            },
1333                        );
1334                    }
1335                    _ => {
1336                        let candidates = info
1337                            .resolved
1338                            .iter()
1339                            .map(|(res, fragment)| {
1340                                let def_id = if let Some(UrlFragment::Item(def_id)) = fragment {
1341                                    Some(*def_id)
1342                                } else {
1343                                    None
1344                                };
1345                                (*res, def_id)
1346                            })
1347                            .collect::<Vec<_>>();
1348                        ambiguity_error(self.cx, &diag_info, path_str, &candidates, true);
1349                    }
1350                }
1351            }
1352        }
1353    }
1354
1355    fn compute_link(
1356        &mut self,
1357        mut res: Res,
1358        fragment: Option<UrlFragment>,
1359        path_str: &str,
1360        disambiguator: Option<Disambiguator>,
1361        diag_info: DiagnosticInfo<'_>,
1362        link_text: &Box<str>,
1363    ) -> Option<ItemLink> {
1364        // Check for a primitive which might conflict with a module
1365        // Report the ambiguity and require that the user specify which one they meant.
1366        // FIXME: could there ever be a primitive not in the type namespace?
1367        if matches!(
1368            disambiguator,
1369            None | Some(Disambiguator::Namespace(Namespace::TypeNS) | Disambiguator::Primitive)
1370        ) && !matches!(res, Res::Primitive(_))
1371            && let Some(prim) = resolve_primitive(path_str, TypeNS)
1372        {
1373            // `prim@char`
1374            if matches!(disambiguator, Some(Disambiguator::Primitive)) {
1375                res = prim;
1376            } else {
1377                // `[char]` when a `char` module is in scope
1378                let candidates = &[(res, res.def_id(self.cx.tcx)), (prim, None)];
1379                ambiguity_error(self.cx, &diag_info, path_str, candidates, true);
1380                return None;
1381            }
1382        }
1383
1384        match res {
1385            Res::Primitive(_) => {
1386                if let Some(UrlFragment::Item(id)) = fragment {
1387                    // We're actually resolving an associated item of a primitive, so we need to
1388                    // verify the disambiguator (if any) matches the type of the associated item.
1389                    // This case should really follow the same flow as the `Res::Def` branch below,
1390                    // but attempting to add a call to `clean::register_res` causes an ICE. @jyn514
1391                    // thinks `register_res` is only needed for cross-crate re-exports, but Rust
1392                    // doesn't allow statements like `use str::trim;`, making this a (hopefully)
1393                    // valid omission. See https://github.com/rust-lang/rust/pull/80660#discussion_r551585677
1394                    // for discussion on the matter.
1395                    let kind = self.cx.tcx.def_kind(id);
1396                    self.verify_disambiguator(path_str, kind, id, disambiguator, &diag_info)?;
1397                } else {
1398                    match disambiguator {
1399                        Some(Disambiguator::Primitive | Disambiguator::Namespace(_)) | None => {}
1400                        Some(other) => {
1401                            self.report_disambiguator_mismatch(path_str, other, res, &diag_info);
1402                            return None;
1403                        }
1404                    }
1405                }
1406
1407                res.def_id(self.cx.tcx).map(|page_id| ItemLink {
1408                    link: Box::<str>::from(diag_info.ori_link),
1409                    link_text: link_text.clone(),
1410                    page_id,
1411                    fragment,
1412                })
1413            }
1414            Res::Def(kind, id) => {
1415                let (kind_for_dis, id_for_dis) = if let Some(UrlFragment::Item(id)) = fragment {
1416                    (self.cx.tcx.def_kind(id), id)
1417                } else {
1418                    (kind, id)
1419                };
1420                self.verify_disambiguator(
1421                    path_str,
1422                    kind_for_dis,
1423                    id_for_dis,
1424                    disambiguator,
1425                    &diag_info,
1426                )?;
1427
1428                let page_id = clean::register_res(self.cx, rustc_hir::def::Res::Def(kind, id));
1429                Some(ItemLink {
1430                    link: Box::<str>::from(diag_info.ori_link),
1431                    link_text: link_text.clone(),
1432                    page_id,
1433                    fragment,
1434                })
1435            }
1436        }
1437    }
1438
1439    fn verify_disambiguator(
1440        &self,
1441        path_str: &str,
1442        kind: DefKind,
1443        id: DefId,
1444        disambiguator: Option<Disambiguator>,
1445        diag_info: &DiagnosticInfo<'_>,
1446    ) -> Option<()> {
1447        debug!("intra-doc link to {path_str} resolved to {:?}", (kind, id));
1448
1449        // Disallow e.g. linking to enums with `struct@`
1450        debug!("saw kind {kind:?} with disambiguator {disambiguator:?}");
1451        match (kind, disambiguator) {
1452                | (
1453                    DefKind::Const
1454                    | DefKind::ConstParam
1455                    | DefKind::AssocConst
1456                    | DefKind::AnonConst,
1457                    Some(Disambiguator::Kind(DefKind::Const)),
1458                )
1459                // NOTE: this allows 'method' to mean both normal functions and associated functions
1460                // This can't cause ambiguity because both are in the same namespace.
1461                | (DefKind::Fn | DefKind::AssocFn, Some(Disambiguator::Kind(DefKind::Fn)))
1462                // These are namespaces; allow anything in the namespace to match
1463                | (_, Some(Disambiguator::Namespace(_)))
1464                // If no disambiguator given, allow anything
1465                | (_, None)
1466                // All of these are valid, so do nothing
1467                => {}
1468                (actual, Some(Disambiguator::Kind(expected))) if actual == expected => {}
1469                (_, Some(specified @ Disambiguator::Kind(_) | specified @ Disambiguator::Primitive)) => {
1470                    self.report_disambiguator_mismatch(path_str, specified, Res::Def(kind, id), diag_info);
1471                    return None;
1472                }
1473            }
1474
1475        // item can be non-local e.g. when using `#[rustc_doc_primitive = "pointer"]`
1476        if let Some(dst_id) = id.as_local()
1477            && let Some(src_id) = diag_info.item.item_id.expect_def_id().as_local()
1478            && self.cx.tcx.effective_visibilities(()).is_exported(src_id)
1479            && !self.cx.tcx.effective_visibilities(()).is_exported(dst_id)
1480        {
1481            privacy_error(self.cx, diag_info, path_str);
1482        }
1483
1484        Some(())
1485    }
1486
1487    fn report_disambiguator_mismatch(
1488        &self,
1489        path_str: &str,
1490        specified: Disambiguator,
1491        resolved: Res,
1492        diag_info: &DiagnosticInfo<'_>,
1493    ) {
1494        // The resolved item did not match the disambiguator; give a better error than 'not found'
1495        let msg = format!("incompatible link kind for `{path_str}`");
1496        let callback = |diag: &mut Diag<'_>, sp: Option<rustc_span::Span>, link_range| {
1497            let note = format!(
1498                "this link resolved to {} {}, which is not {} {}",
1499                resolved.article(),
1500                resolved.descr(),
1501                specified.article(),
1502                specified.descr(),
1503            );
1504            if let Some(sp) = sp {
1505                diag.span_label(sp, note);
1506            } else {
1507                diag.note(note);
1508            }
1509            suggest_disambiguator(resolved, diag, path_str, link_range, sp, diag_info);
1510        };
1511        report_diagnostic(self.cx.tcx, BROKEN_INTRA_DOC_LINKS, msg, diag_info, callback);
1512    }
1513
1514    fn report_rawptr_assoc_feature_gate(
1515        &self,
1516        dox: &str,
1517        ori_link: &MarkdownLinkRange,
1518        item: &Item,
1519    ) {
1520        let span = match source_span_for_markdown_range(
1521            self.cx.tcx,
1522            dox,
1523            ori_link.inner_range(),
1524            &item.attrs.doc_strings,
1525        ) {
1526            Some((sp, _)) => sp,
1527            None => item.attr_span(self.cx.tcx),
1528        };
1529        rustc_session::diagnostics::feature_err(
1530            self.cx.tcx.sess,
1531            sym::intra_doc_pointers,
1532            span,
1533            "linking to associated items of raw pointers is experimental",
1534        )
1535        .with_note("rustdoc does not allow disambiguating between `*const` and `*mut`, and pointers are unstable until it does")
1536        .emit();
1537    }
1538
1539    fn resolve_with_disambiguator_cached(
1540        &mut self,
1541        key: ResolutionInfo,
1542        diag: DiagnosticInfo<'_>,
1543        // If errors are cached then they are only reported on first occurrence
1544        // which we want in some cases but not in others.
1545        cache_errors: bool,
1546    ) -> Option<Vec<(Res, Option<UrlFragment>)>> {
1547        if let Some(res) = self.links.visited.get(&key)
1548            && (res.is_some() || cache_errors)
1549        {
1550            return res.clone().map(|r| vec![r]);
1551        }
1552
1553        let mut candidates = self.resolve_with_disambiguator(&key, diag.clone());
1554
1555        // FIXME: it would be nice to check that the feature gate was enabled in the original crate, not just ignore it altogether.
1556        // However I'm not sure how to check that across crates.
1557        if let Some(candidate) = candidates.first()
1558            && candidate.0 == Res::Primitive(PrimitiveType::RawPointer)
1559            && key.path_str.contains("::")
1560        // We only want to check this if this is an associated item.
1561        {
1562            if key.item_id.is_local() && !self.cx.tcx.features().intra_doc_pointers() {
1563                self.report_rawptr_assoc_feature_gate(diag.dox, &diag.link_range, diag.item);
1564                return None;
1565            } else {
1566                candidates = vec![*candidate];
1567            }
1568        }
1569
1570        // If there are multiple items with the same "kind" (for example, both "associated types")
1571        // and after removing duplicated kinds, only one remains, the `ambiguity_error` function
1572        // won't emit an error. So at this point, we can just take the first candidate as it was
1573        // the first retrieved and use it to generate the link.
1574        if let [candidate, _candidate2, ..] = *candidates
1575            && !ambiguity_error(self.cx, &diag, &key.path_str, &candidates, false)
1576        {
1577            candidates = vec![candidate];
1578        }
1579
1580        let mut out = Vec::with_capacity(candidates.len());
1581        for (res, def_id) in candidates {
1582            let fragment = match (&key.extra_fragment, def_id) {
1583                (Some(_), Some(def_id)) => {
1584                    report_anchor_conflict(self.cx, diag, def_id);
1585                    return None;
1586                }
1587                (Some(u_frag), None) => Some(UrlFragment::UserWritten(u_frag.clone())),
1588                (None, Some(def_id)) => Some(UrlFragment::Item(def_id)),
1589                (None, None) => None,
1590            };
1591            out.push((res, fragment));
1592        }
1593        if let [r] = out.as_slice() {
1594            self.links.visited.insert(key, Some(r.clone()));
1595        } else if cache_errors {
1596            self.links.visited.insert(key, None);
1597        }
1598        Some(out)
1599    }
1600
1601    /// After parsing the disambiguator, resolve the main part of the link.
1602    fn resolve_with_disambiguator(
1603        &mut self,
1604        key: &ResolutionInfo,
1605        diag: DiagnosticInfo<'_>,
1606    ) -> Vec<(Res, Option<DefId>)> {
1607        let disambiguator = key.dis;
1608        let path_str = &key.path_str;
1609        let item_id = key.item_id;
1610        let module_id = key.module_id;
1611
1612        match disambiguator.map(Disambiguator::ns) {
1613            Some(expected_ns) => {
1614                match self.resolve(path_str, expected_ns, disambiguator, item_id, module_id) {
1615                    Ok(candidates) => candidates,
1616                    Err(err) => {
1617                        // We only looked in one namespace. Try to give a better error if possible.
1618                        // FIXME: really it should be `resolution_failure` that does this, not `resolve_with_disambiguator`.
1619                        // See https://github.com/rust-lang/rust/pull/76955#discussion_r493953382 for a good approach.
1620                        let mut err = ResolutionFailure::NotResolved(err);
1621                        for other_ns in [TypeNS, ValueNS, MacroNS] {
1622                            if other_ns != expected_ns
1623                                && let Ok(&[res, ..]) = self
1624                                    .resolve(path_str, other_ns, None, item_id, module_id)
1625                                    .as_deref()
1626                            {
1627                                err = ResolutionFailure::WrongNamespace {
1628                                    res: full_res(self.cx.tcx, res),
1629                                    expected_ns,
1630                                };
1631                                break;
1632                            }
1633                        }
1634                        resolution_failure(self, diag, path_str, disambiguator, smallvec![err]);
1635                        vec![]
1636                    }
1637                }
1638            }
1639            None => {
1640                // Try everything!
1641                let candidate = |ns| {
1642                    self.resolve(path_str, ns, None, item_id, module_id)
1643                        .map_err(ResolutionFailure::NotResolved)
1644                };
1645
1646                let candidates = PerNS {
1647                    macro_ns: candidate(MacroNS),
1648                    type_ns: candidate(TypeNS),
1649                    value_ns: candidate(ValueNS).and_then(|v_res| {
1650                        for (res, _) in v_res.iter() {
1651                            // Constructors are picked up in the type namespace.
1652                            if let Res::Def(DefKind::Ctor(..), _) = res {
1653                                return Err(ResolutionFailure::WrongNamespace {
1654                                    res: *res,
1655                                    expected_ns: TypeNS,
1656                                });
1657                            }
1658                        }
1659                        Ok(v_res)
1660                    }),
1661                };
1662
1663                let len = candidates
1664                    .iter()
1665                    .fold(0, |acc, res| if let Ok(res) = res { acc + res.len() } else { acc });
1666
1667                if len == 0 {
1668                    resolution_failure(
1669                        self,
1670                        diag,
1671                        path_str,
1672                        disambiguator,
1673                        candidates.into_iter().filter_map(|res| res.err()).collect(),
1674                    );
1675                    vec![]
1676                } else if len == 1 {
1677                    candidates.into_iter().filter_map(|res| res.ok()).flatten().collect::<Vec<_>>()
1678                } else {
1679                    let has_derive_trait_collision = is_derive_trait_collision(&candidates);
1680                    if len == 2 && has_derive_trait_collision {
1681                        candidates.type_ns.unwrap()
1682                    } else {
1683                        // If we're reporting an ambiguity, don't mention the namespaces that failed
1684                        let mut candidates = candidates.map(|candidate| candidate.ok());
1685                        // If there a collision between a trait and a derive, we ignore the derive.
1686                        if has_derive_trait_collision {
1687                            candidates.macro_ns = None;
1688                        }
1689                        candidates.into_iter().flatten().flatten().collect::<Vec<_>>()
1690                    }
1691                }
1692            }
1693        }
1694    }
1695}
1696
1697/// Get the section of a link between the backticks,
1698/// or the whole link if there aren't any backticks.
1699///
1700/// For example:
1701///
1702/// ```text
1703/// [`Foo`]
1704///   ^^^
1705/// ```
1706///
1707/// This function does nothing if `ori_link.range` is a `MarkdownLinkRange::WholeLink`.
1708fn range_between_backticks(ori_link_range: &MarkdownLinkRange, dox: &str) -> MarkdownLinkRange {
1709    let range = match ori_link_range {
1710        mdlr @ MarkdownLinkRange::WholeLink(_) => return mdlr.clone(),
1711        MarkdownLinkRange::Destination(inner) => inner.clone(),
1712    };
1713    let ori_link_text = &dox[range.clone()];
1714    let after_first_backtick_group = ori_link_text.bytes().position(|b| b != b'`').unwrap_or(0);
1715    let before_second_backtick_group = ori_link_text
1716        .bytes()
1717        .skip(after_first_backtick_group)
1718        .position(|b| b == b'`')
1719        .unwrap_or(ori_link_text.len());
1720    MarkdownLinkRange::Destination(
1721        (range.start + after_first_backtick_group)..(range.start + before_second_backtick_group),
1722    )
1723}
1724
1725/// Returns true if we should ignore `link` due to it being unlikely
1726/// that it is an intra-doc link. `link` should still have disambiguators
1727/// if there were any.
1728///
1729/// The difference between this and [`should_ignore_link()`] is that this
1730/// check should only be used on links that still have disambiguators.
1731fn should_ignore_link_with_disambiguators(link: &str) -> bool {
1732    link.contains(|ch: char| !(ch.is_alphanumeric() || ":_<>, !*&;@()".contains(ch)))
1733}
1734
1735/// Returns true if we should ignore `path_str` due to it being unlikely
1736/// that it is an intra-doc link.
1737fn should_ignore_link(path_str: &str) -> bool {
1738    path_str.contains(|ch: char| !(ch.is_alphanumeric() || ":_<>, !*&;".contains(ch)))
1739}
1740
1741#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
1742/// Disambiguators for a link.
1743enum Disambiguator {
1744    /// `prim@`
1745    ///
1746    /// This is buggy, see <https://github.com/rust-lang/rust/pull/77875#discussion_r503583103>
1747    Primitive,
1748    /// `struct@` or `f()`
1749    Kind(DefKind),
1750    /// `type@`
1751    Namespace(Namespace),
1752}
1753
1754impl Disambiguator {
1755    /// Given a link, parse and return `(disambiguator, path_str, link_text)`.
1756    ///
1757    /// This returns `Ok(Some(...))` if a disambiguator was found,
1758    /// `Ok(None)` if no disambiguator was found, or `Err(...)`
1759    /// if there was a problem with the disambiguator.
1760    fn from_str(link: &str) -> Result<Option<(Self, &str, &str)>, (String, Range<usize>)> {
1761        use Disambiguator::{Kind, Namespace as NS, Primitive};
1762
1763        let suffixes = [
1764            // If you update this list, please also update the relevant rustdoc book section!
1765            ("!()", DefKind::Macro(MacroKinds::BANG)),
1766            ("!{}", DefKind::Macro(MacroKinds::BANG)),
1767            ("![]", DefKind::Macro(MacroKinds::BANG)),
1768            ("()", DefKind::Fn),
1769            ("!", DefKind::Macro(MacroKinds::BANG)),
1770        ];
1771
1772        if let Some(idx) = link.find('@') {
1773            let (prefix, rest) = link.split_at(idx);
1774            let d = match prefix {
1775                // If you update this list, please also update the relevant rustdoc book section!
1776                "struct" => Kind(DefKind::Struct),
1777                "enum" => Kind(DefKind::Enum),
1778                "trait" => Kind(DefKind::Trait),
1779                "union" => Kind(DefKind::Union),
1780                "module" | "mod" => Kind(DefKind::Mod),
1781                "const" | "constant" => Kind(DefKind::Const),
1782                "static" => Kind(DefKind::Static {
1783                    mutability: Mutability::Not,
1784                    nested: false,
1785                    safety: Safety::Safe,
1786                }),
1787                "function" | "fn" | "method" => Kind(DefKind::Fn),
1788                "derive" => Kind(DefKind::Macro(MacroKinds::DERIVE)),
1789                "field" => Kind(DefKind::Field),
1790                "variant" => Kind(DefKind::Variant),
1791                "type" => NS(Namespace::TypeNS),
1792                "value" => NS(Namespace::ValueNS),
1793                "macro" => NS(Namespace::MacroNS),
1794                "prim" | "primitive" => Primitive,
1795                "tyalias" | "typealias" => Kind(DefKind::TyAlias),
1796                _ => return Err((format!("unknown disambiguator `{prefix}`"), 0..idx)),
1797            };
1798
1799            for (suffix, kind) in suffixes {
1800                if let Some(path_str) = rest.strip_suffix(suffix) {
1801                    if d.ns() != Kind(kind).ns() {
1802                        return Err((
1803                            format!("unmatched disambiguator `{prefix}` and suffix `{suffix}`"),
1804                            0..idx,
1805                        ));
1806                    } else if path_str.len() > 1 {
1807                        // path_str != "@"
1808                        return Ok(Some((d, &path_str[1..], &rest[1..])));
1809                    }
1810                }
1811            }
1812
1813            Ok(Some((d, &rest[1..], &rest[1..])))
1814        } else {
1815            for (suffix, kind) in suffixes {
1816                // Avoid turning `!` or `()` into an empty string
1817                if let Some(path_str) = link.strip_suffix(suffix)
1818                    && !path_str.is_empty()
1819                {
1820                    return Ok(Some((Kind(kind), path_str, link)));
1821                }
1822            }
1823            Ok(None)
1824        }
1825    }
1826
1827    fn ns(self) -> Namespace {
1828        match self {
1829            Self::Namespace(n) => n,
1830            // for purposes of link resolution, fields are in the value namespace.
1831            Self::Kind(DefKind::Field) => ValueNS,
1832            Self::Kind(k) => {
1833                k.ns().expect("only DefKinds with a valid namespace can be disambiguators")
1834            }
1835            Self::Primitive => TypeNS,
1836        }
1837    }
1838
1839    fn article(self) -> &'static str {
1840        match self {
1841            Self::Namespace(_) => panic!("article() doesn't make sense for namespaces"),
1842            Self::Kind(k) => k.article(),
1843            Self::Primitive => "a",
1844        }
1845    }
1846
1847    fn descr(self) -> &'static str {
1848        match self {
1849            Self::Namespace(n) => n.descr(),
1850            // HACK(jynelson): the source of `DefKind::descr` only uses the DefId for
1851            // printing "module" vs "crate" so using the wrong ID is not a huge problem
1852            Self::Kind(k) => k.descr(CRATE_DEF_ID.to_def_id()),
1853            Self::Primitive => "builtin type",
1854        }
1855    }
1856}
1857
1858/// A suggestion to show in a diagnostic.
1859enum Suggestion {
1860    /// `struct@`
1861    Prefix(&'static str),
1862    /// `f()`
1863    Function,
1864    /// `m!`
1865    Macro,
1866}
1867
1868impl Suggestion {
1869    fn descr(&self) -> Cow<'static, str> {
1870        match self {
1871            Self::Prefix(x) => format!("prefix with `{x}@`").into(),
1872            Self::Function => "add parentheses".into(),
1873            Self::Macro => "add an exclamation mark".into(),
1874        }
1875    }
1876
1877    fn as_help(&self, path_str: &str) -> String {
1878        // FIXME: if this is an implied shortcut link, it's bad style to suggest `@`
1879        match self {
1880            Self::Prefix(prefix) => format!("{prefix}@{path_str}"),
1881            Self::Function => format!("{path_str}()"),
1882            Self::Macro => format!("{path_str}!"),
1883        }
1884    }
1885
1886    fn as_help_span(
1887        &self,
1888        ori_link: &str,
1889        sp: rustc_span::Span,
1890    ) -> Vec<(rustc_span::Span, String)> {
1891        let inner_sp = match ori_link.find('(') {
1892            Some(index) if index != 0 && ori_link.as_bytes()[index - 1] == b'\\' => {
1893                sp.with_hi(sp.lo() + BytePos((index - 1) as _))
1894            }
1895            Some(index) => sp.with_hi(sp.lo() + BytePos(index as _)),
1896            None => sp,
1897        };
1898        let inner_sp = match ori_link.find('!') {
1899            Some(index) if index != 0 && ori_link.as_bytes()[index - 1] == b'\\' => {
1900                sp.with_hi(sp.lo() + BytePos((index - 1) as _))
1901            }
1902            Some(index) => inner_sp.with_hi(inner_sp.lo() + BytePos(index as _)),
1903            None => inner_sp,
1904        };
1905        let inner_sp = match ori_link.find('@') {
1906            Some(index) if index != 0 && ori_link.as_bytes()[index - 1] == b'\\' => {
1907                sp.with_hi(sp.lo() + BytePos((index - 1) as _))
1908            }
1909            Some(index) => inner_sp.with_lo(inner_sp.lo() + BytePos(index as u32 + 1)),
1910            None => inner_sp,
1911        };
1912        match self {
1913            Self::Prefix(prefix) => {
1914                // FIXME: if this is an implied shortcut link, it's bad style to suggest `@`
1915                let mut sugg = vec![(sp.with_hi(inner_sp.lo()), format!("{prefix}@"))];
1916                if sp.hi() != inner_sp.hi() {
1917                    sugg.push((inner_sp.shrink_to_hi().with_hi(sp.hi()), String::new()));
1918                }
1919                sugg
1920            }
1921            Self::Function => {
1922                let mut sugg = vec![(inner_sp.shrink_to_hi().with_hi(sp.hi()), "()".to_string())];
1923                if sp.lo() != inner_sp.lo() {
1924                    sugg.push((inner_sp.shrink_to_lo().with_lo(sp.lo()), String::new()));
1925                }
1926                sugg
1927            }
1928            Self::Macro => {
1929                let mut sugg = vec![(inner_sp.shrink_to_hi(), "!".to_string())];
1930                if sp.lo() != inner_sp.lo() {
1931                    sugg.push((inner_sp.shrink_to_lo().with_lo(sp.lo()), String::new()));
1932                }
1933                sugg
1934            }
1935        }
1936    }
1937}
1938
1939/// Reports a diagnostic for an intra-doc link.
1940///
1941/// If no link range is provided, or the source span of the link cannot be determined, the span of
1942/// the entire documentation block is used for the lint. If a range is provided but the span
1943/// calculation fails, a note is added to the diagnostic pointing to the link in the markdown.
1944///
1945/// The `decorate` callback is invoked in all cases to allow further customization of the
1946/// diagnostic before emission. If the span of the link was able to be determined, the second
1947/// parameter of the callback will contain it, and the primary span of the diagnostic will be set
1948/// to it.
1949fn report_diagnostic(
1950    tcx: TyCtxt<'_>,
1951    lint: &'static Lint,
1952    msg: impl Into<DiagMessage> + Display,
1953    DiagnosticInfo { item, ori_link: _, dox, link_range }: &DiagnosticInfo<'_>,
1954    decorate: impl FnOnce(&mut Diag<'_>, Option<rustc_span::Span>, MarkdownLinkRange),
1955) {
1956    let Some(hir_id) = DocContext::as_local_hir_id(tcx, item.item_id) else {
1957        // If non-local, no need to check anything.
1958        info!("ignoring warning from parent crate: {msg}");
1959        return;
1960    };
1961
1962    let sp = item.attr_span(tcx);
1963
1964    tcx.emit_node_span_lint(
1965        lint,
1966        hir_id,
1967        sp,
1968        rustc_errors::DiagDecorator(|lint| {
1969            lint.primary_message(msg);
1970
1971            let (span, link_range) = match link_range {
1972                MarkdownLinkRange::Destination(md_range) => {
1973                    let mut md_range = md_range.clone();
1974                    let sp = source_span_for_markdown_range(
1975                        tcx,
1976                        dox,
1977                        &md_range,
1978                        &item.attrs.doc_strings,
1979                    )
1980                    .map(|(mut sp, _)| {
1981                        while dox.as_bytes().get(md_range.start) == Some(&b' ')
1982                            || dox.as_bytes().get(md_range.start) == Some(&b'`')
1983                        {
1984                            md_range.start += 1;
1985                            sp = sp.with_lo(sp.lo() + BytePos(1));
1986                        }
1987                        while dox.as_bytes().get(md_range.end - 1) == Some(&b' ')
1988                            || dox.as_bytes().get(md_range.end - 1) == Some(&b'`')
1989                        {
1990                            md_range.end -= 1;
1991                            sp = sp.with_hi(sp.hi() - BytePos(1));
1992                        }
1993                        sp
1994                    });
1995                    (sp, MarkdownLinkRange::Destination(md_range))
1996                }
1997                MarkdownLinkRange::WholeLink(md_range) => (
1998                    source_span_for_markdown_range(tcx, dox, md_range, &item.attrs.doc_strings)
1999                        .map(|(sp, _)| sp),
2000                    link_range.clone(),
2001                ),
2002            };
2003
2004            if let Some(sp) = span {
2005                lint.span(sp);
2006            } else {
2007                // blah blah blah\nblah\nblah [blah] blah blah\nblah blah
2008                //                       ^     ~~~~
2009                //                       |     link_range
2010                //                       last_new_line_offset
2011                let md_range = link_range.inner_range().clone();
2012                let last_new_line_offset = dox[..md_range.start].rfind('\n').map_or(0, |n| n + 1);
2013                let line = dox[last_new_line_offset..].lines().next().unwrap_or("");
2014
2015                // Print the line containing the `md_range` and manually mark it with '^'s.
2016                lint.note(format!(
2017                    "the link appears in this line:\n\n{line}\n\
2018                     {indicator: <before$}{indicator:^<found$}",
2019                    indicator = "",
2020                    before = md_range.start - last_new_line_offset,
2021                    found = md_range.len(),
2022                ));
2023            }
2024
2025            decorate(lint, span, link_range);
2026        }),
2027    );
2028}
2029
2030fn suggest_path_name_typo(
2031    collector: &LinkCollector<'_, '_>,
2032    diag: &mut Diag<'_>,
2033    span: Option<rustc_span::Span>,
2034    link_range: &MarkdownLinkRange,
2035    dox: &str,
2036    module: ModId,
2037    unresolved: &str,
2038    has_partial_res: bool,
2039    disambiguator: Option<Disambiguator>,
2040) {
2041    if unresolved.chars().count() <= 1 {
2042        // There are too many false positives for single character typos.
2043        return;
2044    }
2045
2046    let tcx = collector.cx.tcx;
2047    let lookup = Symbol::intern(unresolved);
2048    let children = if let Some(local_module) = module.as_local() {
2049        tcx.module_children_local(local_module.to_local_def_id())
2050    } else {
2051        tcx.module_children(module.to_def_id())
2052    };
2053    let candidates = children
2054        .iter()
2055        .filter(|child| {
2056            disambiguator.is_none_or(|disambiguator| child.res.matches_ns(disambiguator.ns()))
2057        })
2058        .map(|child| child.ident.name)
2059        .filter(|&name| name != lookup)
2060        .collect::<Vec<_>>();
2061    let Some(candidate) = find_best_match_for_name(&candidates, lookup, None) else {
2062        return;
2063    };
2064
2065    let msg = format!("there's a similarly named item `{candidate}`");
2066    if let (Some(span), MarkdownLinkRange::Destination(range)) = (span, link_range) {
2067        let link = &dox[range.clone()];
2068        // A partial resolution means that the unresolved name follows a resolved parent path.
2069        let start = if has_partial_res { link.rfind(unresolved) } else { link.find(unresolved) };
2070        if let Some(start) = start {
2071            let mut suggestion = link.to_owned();
2072            suggestion.replace_range(start..start + unresolved.len(), candidate.as_str());
2073            diag.span_suggestion_verbose(span, msg, suggestion, Applicability::MaybeIncorrect);
2074            return;
2075        }
2076    }
2077    diag.help(msg);
2078}
2079
2080/// Reports a link that failed to resolve.
2081///
2082/// This also tries to resolve any intermediate path segments that weren't
2083/// handled earlier. For example, if passed `Item::Crate(std)` and `path_str`
2084/// `std::io::Error::x`, this will resolve `std::io::Error`.
2085fn resolution_failure(
2086    collector: &LinkCollector<'_, '_>,
2087    diag_info: DiagnosticInfo<'_>,
2088    path_str: &str,
2089    disambiguator: Option<Disambiguator>,
2090    kinds: SmallVec<[ResolutionFailure<'_>; 3]>,
2091) {
2092    let tcx = collector.cx.tcx;
2093    report_diagnostic(
2094        tcx,
2095        BROKEN_INTRA_DOC_LINKS,
2096        format!("unresolved link to `{path_str}`"),
2097        &diag_info,
2098        |diag, sp, link_range| {
2099            let item = |res: Res| format!("the {} `{}`", res.descr(), res.name(tcx));
2100            let assoc_item_not_allowed = |res: Res| {
2101                let name = res.name(tcx);
2102                format!(
2103                    "`{name}` is {} {}, not a module or type, and cannot have associated items",
2104                    res.article(),
2105                    res.descr()
2106                )
2107            };
2108            // ignore duplicates
2109            let mut variants_seen =
2110                SmallVec::<[_; const { mem::variant_count::<ResolutionFailure<'_>>() }]>::new();
2111            for mut failure in kinds {
2112                let variant = mem::discriminant(&failure);
2113                if variants_seen.contains(&variant) {
2114                    continue;
2115                }
2116                variants_seen.push(variant);
2117
2118                if let ResolutionFailure::NotResolved(UnresolvedPath {
2119                    item_id,
2120                    module_id,
2121                    partial_res,
2122                    unresolved,
2123                }) = &mut failure
2124                {
2125                    use DefKind::*;
2126
2127                    let item_id = *item_id;
2128                    let module_id = *module_id;
2129
2130                    // Check if _any_ parent of the path gets resolved.
2131                    // If so, report it and say the first which failed; if not, say the first path segment didn't resolve.
2132                    // Also check if `path_str` is an invalid path.
2133
2134                    // Examples of `path_str` that are invalid:
2135                    // - "std::::path", during splitting this would yield an empty segment
2136                    // - "std:::path", this would eventually yield "std:"
2137                    let mut path_is_invalid = false;
2138                    let is_invalid_segment =
2139                        |segment: &str| segment.is_empty() || segment.contains(':');
2140
2141                    let mut name = path_str;
2142                    'outer: loop {
2143                        // FIXME(jynelson): this might conflict with my `Self` fix in #76467
2144                        let Some((start, end)) = name.rsplit_once("::") else {
2145                            // `name` is now the first path segment, which didn't resolve.
2146                            // avoid bug that marked [Quux::Z] as missing Z, not Quux
2147                            if is_invalid_segment(name) {
2148                                path_is_invalid = true;
2149                                break;
2150                            }
2151                            if partial_res.is_none() {
2152                                *unresolved = name.into();
2153                                // If `partial_res` somehow had a value, we preserve the original `unresolved`.
2154                            }
2155                            break;
2156                        };
2157                        if is_invalid_segment(end) {
2158                            // If any segment is invalid, stop and say so, instead of saying
2159                            // "no item named ...", which would look nonsensical.
2160                            path_is_invalid = true;
2161                            break;
2162                        }
2163                        for ns in [TypeNS, ValueNS, MacroNS] {
2164                            if let Ok(v_res) =
2165                                collector.resolve(start, ns, None, item_id, module_id)
2166                            {
2167                                debug!("found partial_res={v_res:?}");
2168                                if let Some(&res) = v_res.first() {
2169                                    *partial_res = Some(full_res(tcx, res));
2170                                    *unresolved = end.into();
2171                                    break 'outer;
2172                                }
2173                            }
2174                        }
2175                        if start.is_empty() && partial_res.is_none() {
2176                            // `start` being empty means `path_str` was written like "::path::to::item".
2177                            // In this case, `end` is the first path segment that we should report.
2178                            *unresolved = end.into();
2179                            break;
2180                        }
2181                        name = start;
2182                    }
2183
2184                    let last_found_module = match *partial_res {
2185                        Some(Res::Def(DefKind::Mod, id)) => Some(ModId::new_unchecked(id)),
2186                        None => Some(module_id),
2187                        _ => None,
2188                    };
2189                    // See if this was a module: `[path]` or `[std::io::nope]`
2190                    if let Some(module) = last_found_module {
2191                        let note = if path_is_invalid {
2192                            "invalid path separator".into()
2193                        } else if partial_res.is_some() {
2194                            // Part of the link resolved; e.g. `std::io::nonexistent`
2195                            let module_name = tcx.item_name(module);
2196                            format!("no item named `{unresolved}` in module `{module_name}`")
2197                        } else {
2198                            // None of the link resolved; e.g. `Notimported`
2199                            format!("no item named `{unresolved}` in scope")
2200                        };
2201                        if let Some(span) = sp {
2202                            diag.span_label(span, note);
2203                        } else {
2204                            diag.note(note);
2205                        }
2206
2207                        if !path_is_invalid {
2208                            suggest_path_name_typo(
2209                                collector,
2210                                diag,
2211                                sp,
2212                                &link_range,
2213                                diag_info.dox,
2214                                module,
2215                                unresolved,
2216                                partial_res.is_some(),
2217                                disambiguator,
2218                            );
2219                        }
2220
2221                        if !path_str.contains("::") {
2222                            if disambiguator.is_none_or(|d| d.ns() == MacroNS)
2223                                && collector
2224                                    .cx
2225                                    .tcx
2226                                    .resolutions(())
2227                                    .all_macro_rules
2228                                    .contains(&Symbol::intern(path_str))
2229                            {
2230                                diag.note(format!(
2231                                    "`macro_rules` named `{path_str}` exists in this crate, \
2232                                     but it is not in scope at this link's location"
2233                                ));
2234                            } else {
2235                                // If the link has `::` in it, assume it was meant to be an
2236                                // intra-doc link. Otherwise, the `[]` might be unrelated.
2237                                diag.help(
2238                                    "to escape `[` and `]` characters, \
2239                                           add '\\' before them like `\\[` or `\\]`",
2240                                );
2241                            }
2242                        }
2243
2244                        continue;
2245                    }
2246
2247                    // Otherwise, it must be an associated item or variant
2248                    let res = partial_res.expect("None case was handled by `last_found_module`");
2249                    let kind_did = match res {
2250                        Res::Def(kind, did) => Some((kind, did)),
2251                        Res::Primitive(_) => None,
2252                    };
2253                    let is_struct_variant = |did| {
2254                        if let ty::Adt(def, _) =
2255                            tcx.type_of(did).instantiate_identity().skip_norm_wip().kind()
2256                            && def.is_enum()
2257                            && let Some(variant) =
2258                                def.variants().iter().find(|v| v.name == res.name(tcx))
2259                        {
2260                            // ctor is `None` if variant is a struct
2261                            variant.ctor.is_none()
2262                        } else {
2263                            false
2264                        }
2265                    };
2266                    let path_description = if let Some((kind, did)) = kind_did {
2267                        match kind {
2268                            Mod | ForeignMod => "inner item",
2269                            Struct => "field or associated item",
2270                            Enum | Union => "variant or associated item",
2271                            Variant if is_struct_variant(did) => {
2272                                let variant = res.name(tcx);
2273                                let note = format!("variant `{variant}` has no such field");
2274                                if let Some(span) = sp {
2275                                    diag.span_label(span, note);
2276                                } else {
2277                                    diag.note(note);
2278                                }
2279                                return;
2280                            }
2281                            Variant
2282                            | Field
2283                            | Closure
2284                            | AssocTy
2285                            | AssocConst { .. }
2286                            | AssocFn
2287                            | Fn
2288                            | Macro(_)
2289                            | Const { .. }
2290                            | ConstParam
2291                            | ExternCrate
2292                            | Use
2293                            | LifetimeParam
2294                            | Ctor(_, _)
2295                            | AnonConst => {
2296                                let note = assoc_item_not_allowed(res);
2297                                if let Some(span) = sp {
2298                                    diag.span_label(span, note);
2299                                } else {
2300                                    diag.note(note);
2301                                }
2302                                return;
2303                            }
2304                            Trait
2305                            | TyAlias
2306                            | ForeignTy
2307                            | OpaqueTy
2308                            | TraitAlias
2309                            | TyParam
2310                            | Static { .. } => "associated item",
2311                            Impl { .. }
2312                            | GlobalAsm
2313                            | SyntheticCoroutineBody
2314                            | TestBinderConstraints => {
2315                                unreachable!("not a path")
2316                            }
2317                        }
2318                    } else {
2319                        "associated item"
2320                    };
2321                    let name = res.name(tcx);
2322                    let note = format!(
2323                        "the {res} `{name}` has no {disamb_res} named `{unresolved}`",
2324                        res = res.descr(),
2325                        disamb_res = disambiguator.map_or(path_description, |d| d.descr()),
2326                    );
2327                    if let Some(span) = sp {
2328                        diag.span_label(span, note);
2329                    } else {
2330                        diag.note(note);
2331                    }
2332
2333                    continue;
2334                }
2335                let note = match failure {
2336                    ResolutionFailure::NotResolved { .. } => unreachable!("handled above"),
2337                    ResolutionFailure::WrongNamespace { res, expected_ns } => {
2338                        suggest_disambiguator(
2339                            res,
2340                            diag,
2341                            path_str,
2342                            link_range.clone(),
2343                            sp,
2344                            &diag_info,
2345                        );
2346
2347                        if let Some(disambiguator) = disambiguator
2348                            && !matches!(disambiguator, Disambiguator::Namespace(..))
2349                        {
2350                            format!(
2351                                "this link resolves to {}, which is not {} {}",
2352                                item(res),
2353                                disambiguator.article(),
2354                                disambiguator.descr()
2355                            )
2356                        } else {
2357                            format!(
2358                                "this link resolves to {}, which is not in the {} namespace",
2359                                item(res),
2360                                expected_ns.descr()
2361                            )
2362                        }
2363                    }
2364                };
2365                if let Some(span) = sp {
2366                    diag.span_label(span, note);
2367                } else {
2368                    diag.note(note);
2369                }
2370            }
2371        },
2372    );
2373}
2374
2375fn report_multiple_anchors(cx: &DocContext<'_>, diag_info: DiagnosticInfo<'_>) {
2376    let msg = format!("`{}` contains multiple anchors", diag_info.ori_link);
2377    anchor_failure(cx, diag_info, msg, 1)
2378}
2379
2380fn report_anchor_conflict(cx: &DocContext<'_>, diag_info: DiagnosticInfo<'_>, def_id: DefId) {
2381    let (link, kind) = (diag_info.ori_link, Res::from_def_id(cx.tcx, def_id).descr());
2382    let msg = format!("`{link}` contains an anchor, but links to {kind}s are already anchored");
2383    anchor_failure(cx, diag_info, msg, 0)
2384}
2385
2386/// Report an anchor failure.
2387fn anchor_failure(
2388    cx: &DocContext<'_>,
2389    diag_info: DiagnosticInfo<'_>,
2390    msg: String,
2391    anchor_idx: usize,
2392) {
2393    report_diagnostic(cx.tcx, BROKEN_INTRA_DOC_LINKS, msg, &diag_info, |diag, sp, _link_range| {
2394        if let Some(mut sp) = sp {
2395            if let Some((fragment_offset, _)) =
2396                diag_info.ori_link.char_indices().filter(|(_, x)| *x == '#').nth(anchor_idx)
2397            {
2398                sp = sp.with_lo(sp.lo() + BytePos(fragment_offset as _));
2399            }
2400            diag.span_label(sp, "invalid anchor");
2401        }
2402    });
2403}
2404
2405/// Report an error in the link disambiguator.
2406fn disambiguator_error(
2407    cx: &DocContext<'_>,
2408    mut diag_info: DiagnosticInfo<'_>,
2409    disambiguator_range: MarkdownLinkRange,
2410    msg: impl Into<DiagMessage> + Display,
2411) {
2412    diag_info.link_range = disambiguator_range;
2413    report_diagnostic(cx.tcx, BROKEN_INTRA_DOC_LINKS, msg, &diag_info, |diag, _sp, _link_range| {
2414        let msg = format!(
2415            "see {}/rustdoc/write-documentation/linking-to-items-by-name.html#namespaces-and-disambiguators for more info about disambiguators",
2416            crate::DOC_RUST_LANG_ORG_VERSION
2417        );
2418        diag.note(msg);
2419    });
2420}
2421
2422fn report_malformed_generics(
2423    cx: &DocContext<'_>,
2424    diag_info: DiagnosticInfo<'_>,
2425    err: MalformedGenerics,
2426    path_str: &str,
2427) {
2428    report_diagnostic(
2429        cx.tcx,
2430        BROKEN_INTRA_DOC_LINKS,
2431        format!("unresolved link to `{path_str}`"),
2432        &diag_info,
2433        |diag, sp, _link_range| {
2434            let note = match err {
2435                MalformedGenerics::UnbalancedAngleBrackets => "unbalanced angle brackets",
2436                MalformedGenerics::MissingType => "missing type for generic parameters",
2437                MalformedGenerics::HasFullyQualifiedSyntax => {
2438                    diag.note(
2439                        "see https://github.com/rust-lang/rust/issues/74563 for more information",
2440                    );
2441                    "fully-qualified syntax is unsupported"
2442                }
2443                MalformedGenerics::InvalidPathSeparator => "invalid path separator",
2444                MalformedGenerics::TooManyAngleBrackets => "too many angle brackets",
2445                MalformedGenerics::EmptyAngleBrackets => "empty angle brackets",
2446            };
2447            if let Some(span) = sp {
2448                diag.span_label(span, note);
2449            } else {
2450                diag.note(note);
2451            }
2452        },
2453    );
2454}
2455
2456/// Report an ambiguity error, where there were multiple possible resolutions.
2457///
2458/// If all `candidates` have the same kind, it's not possible to disambiguate so in this case,
2459/// the function won't emit an error and will return `false`. Otherwise, it'll emit the error and
2460/// return `true`.
2461fn ambiguity_error(
2462    cx: &DocContext<'_>,
2463    diag_info: &DiagnosticInfo<'_>,
2464    path_str: &str,
2465    candidates: &[(Res, Option<DefId>)],
2466    emit_error: bool,
2467) -> bool {
2468    let mut descrs = FxHashSet::default();
2469    // proc macro can exist in multiple namespaces at once, so we need to compare `DefIds`
2470    //  to remove the candidate in the fn namespace.
2471    let mut possible_proc_macro_id = None;
2472    let is_proc_macro_crate = cx.tcx.crate_types() == [CrateType::ProcMacro];
2473    let mut kinds = candidates
2474        .iter()
2475        .map(|(res, def_id)| {
2476            let r =
2477                if let Some(def_id) = def_id { Res::from_def_id(cx.tcx, *def_id) } else { *res };
2478            if is_proc_macro_crate && let Res::Def(DefKind::Macro(_), id) = r {
2479                possible_proc_macro_id = Some(id);
2480            }
2481            r
2482        })
2483        .collect::<Vec<_>>();
2484    // In order to properly dedup proc macros, we have to do it in two passes:
2485    // 1. Completing the full traversal to find the possible duplicate in the macro namespace,
2486    // 2. Another full traversal to eliminate the candidate in the fn namespace.
2487    //
2488    // Thus, we have to do an iteration after collection is finished.
2489    //
2490    // As an optimization, we only deduplicate if we're in a proc-macro crate,
2491    // and only if we already found something that looks like a proc macro.
2492    if is_proc_macro_crate && let Some(macro_id) = possible_proc_macro_id {
2493        kinds.retain(|res| !matches!(res, Res::Def(DefKind::Fn, fn_id) if macro_id == *fn_id));
2494    }
2495
2496    kinds.retain(|res| descrs.insert(res.descr()));
2497
2498    if descrs.len() == 1 {
2499        // There is no way for users to disambiguate at this point, so better return the first
2500        // candidate and not show a warning.
2501        return false;
2502    } else if !emit_error {
2503        return true;
2504    }
2505
2506    let mut msg = format!("`{path_str}` is ");
2507    match kinds.as_slice() {
2508        [res1, res2] => {
2509            msg += &format!(
2510                "both {} {} and {} {}",
2511                res1.article(),
2512                res1.descr(),
2513                res2.article(),
2514                res2.descr()
2515            );
2516        }
2517        _ => {
2518            let mut kinds = kinds.iter().peekable();
2519            while let Some(res) = kinds.next() {
2520                if kinds.peek().is_some() {
2521                    msg += &format!("{} {}, ", res.article(), res.descr());
2522                } else {
2523                    msg += &format!("and {} {}", res.article(), res.descr());
2524                }
2525            }
2526        }
2527    }
2528
2529    report_diagnostic(cx.tcx, BROKEN_INTRA_DOC_LINKS, msg, diag_info, |diag, sp, link_range| {
2530        if let Some(sp) = sp {
2531            diag.span_label(sp, "ambiguous link");
2532        } else {
2533            diag.note("ambiguous link");
2534        }
2535
2536        for res in kinds {
2537            suggest_disambiguator(res, diag, path_str, link_range.clone(), sp, diag_info);
2538        }
2539    });
2540    true
2541}
2542
2543/// In case of an ambiguity or mismatched disambiguator, suggest the correct
2544/// disambiguator.
2545fn suggest_disambiguator(
2546    res: Res,
2547    diag: &mut Diag<'_>,
2548    path_str: &str,
2549    link_range: MarkdownLinkRange,
2550    sp: Option<rustc_span::Span>,
2551    diag_info: &DiagnosticInfo<'_>,
2552) {
2553    let suggestion = res.disambiguator_suggestion();
2554    let help = format!("to link to the {}, {}", res.descr(), suggestion.descr());
2555
2556    let ori_link = match link_range {
2557        MarkdownLinkRange::Destination(range) => Some(&diag_info.dox[range]),
2558        MarkdownLinkRange::WholeLink(_) => None,
2559    };
2560
2561    if let (Some(sp), Some(ori_link)) = (sp, ori_link) {
2562        let mut spans = suggestion.as_help_span(ori_link, sp);
2563        if spans.len() > 1 {
2564            diag.multipart_suggestion(help, spans, Applicability::MaybeIncorrect);
2565        } else {
2566            let (sp, suggestion_text) = spans.pop().unwrap();
2567            diag.span_suggestion_verbose(sp, help, suggestion_text, Applicability::MaybeIncorrect);
2568        }
2569    } else {
2570        diag.help(format!("{help}: {}", suggestion.as_help(path_str)));
2571    }
2572}
2573
2574/// Report a link from a public item to a private one.
2575fn privacy_error(cx: &DocContext<'_>, diag_info: &DiagnosticInfo<'_>, path_str: &str) {
2576    let sym;
2577    let item_name = match diag_info.item.name {
2578        Some(name) => {
2579            sym = name;
2580            sym.as_str()
2581        }
2582        None => "<unknown>",
2583    };
2584    let msg = format!("public documentation for `{item_name}` links to private item `{path_str}`");
2585
2586    report_diagnostic(cx.tcx, PRIVATE_INTRA_DOC_LINKS, msg, diag_info, |diag, sp, _link_range| {
2587        if let Some(sp) = sp {
2588            diag.span_label(sp, "this item is private");
2589        }
2590
2591        let note_msg = if cx.document_private() {
2592            "this link resolves only because you passed `--document-private-items`, but will break without"
2593        } else {
2594            "this link will resolve properly if you pass `--document-private-items`"
2595        };
2596        diag.note(note_msg);
2597    });
2598}
2599
2600/// Resolve a primitive type or value.
2601fn resolve_primitive(path_str: &str, ns: Namespace) -> Option<Res> {
2602    if ns != TypeNS {
2603        return None;
2604    }
2605    use PrimitiveType::*;
2606    let prim = match path_str {
2607        "isize" => Isize,
2608        "i8" => I8,
2609        "i16" => I16,
2610        "i32" => I32,
2611        "i64" => I64,
2612        "i128" => I128,
2613        "usize" => Usize,
2614        "u8" => U8,
2615        "u16" => U16,
2616        "u32" => U32,
2617        "u64" => U64,
2618        "u128" => U128,
2619        "f16" => F16,
2620        "f32" => F32,
2621        "f64" => F64,
2622        "f128" => F128,
2623        "char" => Char,
2624        "bool" | "true" | "false" => Bool,
2625        "str" | "&str" => Str,
2626        // See #80181 for why these don't have symbols associated.
2627        "slice" => Slice,
2628        "array" => Array,
2629        "tuple" => Tuple,
2630        "unit" => Unit,
2631        "pointer" | "*const" | "*mut" => RawPointer,
2632        "reference" | "&" | "&mut" => Reference,
2633        "fn" => Fn,
2634        "never" | "!" => Never,
2635        _ => return None,
2636    };
2637    debug!("resolved primitives {prim:?}");
2638    Some(Res::Primitive(prim))
2639}