Skip to main content

rustdoc/passes/
collect_intra_doc_links.rs

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