rustdoc/passes/
collect_intra_doc_links.rs

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