rustdoc/passes/
collect_intra_doc_links.rs

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