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