src/librustdoc/passes/collect_intra_doc_links.rs RUST 2,545 lines View on github.com → Search inside
File is large — showing lines 1–2,000 of 2,545.
1//! This module implements [RFC 1946]: Intra-rustdoc-links2//!3//! [RFC 1946]: https://github.com/rust-lang/rfcs/blob/master/text/1946-intra-rustdoc-links.md45use std::borrow::Cow;6use std::fmt::Display;7use std::mem;8use std::ops::Range;910use rustc_ast::util::comments::may_have_doc_links;11use rustc_data_structures::fx::{FxHashMap, FxHashSet, FxIndexMap, FxIndexSet};12use rustc_data_structures::intern::Interned;13use rustc_errors::{Applicability, Diag, DiagMessage};14use rustc_hir::attrs::AttributeKind;15use rustc_hir::def::Namespace::*;16use rustc_hir::def::{DefKind, MacroKinds, Namespace, PerNS};17use rustc_hir::def_id::{CRATE_DEF_ID, DefId, LOCAL_CRATE};18use rustc_hir::{Attribute, Mutability, Safety, find_attr};19use rustc_middle::ty::{Ty, TyCtxt};20use rustc_middle::{bug, span_bug, ty};21use rustc_resolve::rustdoc::pulldown_cmark::LinkType;22use rustc_resolve::rustdoc::{23    MalformedGenerics, has_primitive_or_keyword_or_attribute_docs, prepare_to_doc_link_resolution,24    source_span_for_markdown_range, strip_generics_from_path,25};26use rustc_session::config::CrateType;27use rustc_session::lint::Lint;28use rustc_span::BytePos;29use rustc_span::symbol::{Ident, Symbol, sym};30use smallvec::{SmallVec, smallvec};31use tracing::{debug, info, instrument, trace};3233use crate::clean::utils::find_nearest_parent_module;34use crate::clean::{self, Crate, Item, ItemId, ItemLink, PrimitiveType};35use crate::core::DocContext;36use crate::html::markdown::{MarkdownLink, MarkdownLinkRange, markdown_links};37use crate::lint::{BROKEN_INTRA_DOC_LINKS, PRIVATE_INTRA_DOC_LINKS};38use crate::passes::Pass;39use crate::visit::DocVisitor;4041pub(crate) const COLLECT_INTRA_DOC_LINKS: Pass =42    Pass { name: "collect-intra-doc-links", run: None, description: "resolves intra-doc links" };4344pub(crate) fn collect_intra_doc_links<'a, 'tcx>(45    krate: Crate,46    cx: &'a mut DocContext<'tcx>,47) -> (Crate, LinkCollector<'a, 'tcx>) {48    let mut collector = LinkCollector {49        cx,50        visited_links: FxHashMap::default(),51        ambiguous_links: FxIndexMap::default(),52    };53    collector.visit_crate(&krate);54    (krate, collector)55}5657fn filter_assoc_items_by_name_and_namespace(58    tcx: TyCtxt<'_>,59    assoc_items_of: DefId,60    ident: Ident,61    ns: Namespace,62) -> impl Iterator<Item = &ty::AssocItem> {63    tcx.associated_items(assoc_items_of).filter_by_name_unhygienic(ident.name).filter(move |item| {64        item.namespace() == ns && tcx.hygienic_eq(ident, item.ident(tcx), assoc_items_of)65    })66}6768#[derive(Copy, Clone, Debug, Hash, PartialEq)]69pub(crate) enum Res {70    Def(DefKind, DefId),71    Primitive(PrimitiveType),72}7374type ResolveRes = rustc_hir::def::Res<rustc_ast::NodeId>;7576impl Res {77    fn descr(self) -> &'static str {78        match self {79            Res::Def(kind, id) => ResolveRes::Def(kind, id).descr(),80            Res::Primitive(_) => "primitive type",81        }82    }8384    fn article(self) -> &'static str {85        match self {86            Res::Def(kind, id) => ResolveRes::Def(kind, id).article(),87            Res::Primitive(_) => "a",88        }89    }9091    fn name(self, tcx: TyCtxt<'_>) -> Symbol {92        match self {93            Res::Def(_, id) => tcx.item_name(id),94            Res::Primitive(prim) => prim.as_sym(),95        }96    }9798    fn def_id(self, tcx: TyCtxt<'_>) -> Option<DefId> {99        match self {100            Res::Def(_, id) => Some(id),101            Res::Primitive(prim) => PrimitiveType::primitive_locations(tcx).get(&prim).copied(),102        }103    }104105    fn from_def_id(tcx: TyCtxt<'_>, def_id: DefId) -> Res {106        Res::Def(tcx.def_kind(def_id), def_id)107    }108109    /// Used for error reporting.110    fn disambiguator_suggestion(self) -> Suggestion {111        let kind = match self {112            Res::Primitive(_) => return Suggestion::Prefix("prim"),113            Res::Def(kind, _) => kind,114        };115116        let prefix = match kind {117            DefKind::Fn | DefKind::AssocFn => return Suggestion::Function,118            // FIXME: handle macros with multiple kinds, and attribute/derive macros that aren't119            // proc macros120            DefKind::Macro(MacroKinds::ATTR) => "attribute",121            DefKind::Macro(MacroKinds::DERIVE) => "derive",122            DefKind::Macro(_) => return Suggestion::Macro,123            DefKind::Struct => "struct",124            DefKind::Enum => "enum",125            DefKind::Trait => "trait",126            DefKind::Union => "union",127            DefKind::Mod => "mod",128            DefKind::Const { .. }129            | DefKind::ConstParam130            | DefKind::AssocConst { .. }131            | DefKind::AnonConst => "const",132            DefKind::Static { .. } => "static",133            DefKind::Field => "field",134            DefKind::Variant | DefKind::Ctor(..) => "variant",135            DefKind::TyAlias => "tyalias",136            // Now handle things that don't have a specific disambiguator137            _ => match kind138                .ns()139                .expect("tried to calculate a disambiguator for a def without a namespace?")140            {141                Namespace::TypeNS => "type",142                Namespace::ValueNS => "value",143                Namespace::MacroNS => "macro",144            },145        };146147        Suggestion::Prefix(prefix)148    }149}150151impl TryFrom<ResolveRes> for Res {152    type Error = ();153154    fn try_from(res: ResolveRes) -> Result<Self, ()> {155        use rustc_hir::def::Res::*;156        match res {157            Def(kind, id) => Ok(Res::Def(kind, id)),158            PrimTy(prim) => Ok(Res::Primitive(PrimitiveType::from_hir(prim))),159            // e.g. `#[derive]`160            ToolMod | NonMacroAttr(..) | Err => Result::Err(()),161            other => bug!("unrecognized res {other:?}"),162        }163    }164}165166/// The link failed to resolve. [`resolution_failure`] should look to see if there's167/// a more helpful error that can be given.168#[derive(Debug)]169struct UnresolvedPath<'a> {170    /// Item on which the link is resolved, used for resolving `Self`.171    item_id: DefId,172    /// The scope the link was resolved in.173    module_id: DefId,174    /// If part of the link resolved, this has the `Res`.175    ///176    /// In `[std::io::Error::x]`, `std::io::Error` would be a partial resolution.177    partial_res: Option<Res>,178    /// The remaining unresolved path segments.179    ///180    /// In `[std::io::Error::x]`, `x` would be unresolved.181    unresolved: Cow<'a, str>,182}183184#[derive(Debug)]185enum ResolutionFailure<'a> {186    /// This resolved, but with the wrong namespace.187    WrongNamespace {188        /// What the link resolved to.189        res: Res,190        /// The expected namespace for the resolution, determined from the link's disambiguator.191        ///192        /// E.g., for `[fn@Result]` this is [`Namespace::ValueNS`],193        /// even though `Result`'s actual namespace is [`Namespace::TypeNS`].194        expected_ns: Namespace,195    },196    NotResolved(UnresolvedPath<'a>),197}198199#[derive(Clone, Debug, Hash, PartialEq, Eq)]200pub(crate) enum UrlFragment {201    Item(DefId),202    /// A part of a page that isn't a rust item.203    ///204    /// Eg: `[Vector Examples](std::vec::Vec#examples)`205    UserWritten(String),206}207208#[derive(Clone, Debug, Hash, PartialEq, Eq)]209pub(crate) struct ResolutionInfo {210    item_id: DefId,211    module_id: DefId,212    dis: Option<Disambiguator>,213    path_str: Box<str>,214    extra_fragment: Option<String>,215}216217#[derive(Clone)]218pub(crate) struct DiagnosticInfo<'a> {219    item: &'a Item,220    dox: &'a str,221    ori_link: &'a str,222    link_range: MarkdownLinkRange,223}224225pub(crate) struct OwnedDiagnosticInfo {226    item: Item,227    dox: String,228    ori_link: String,229    link_range: MarkdownLinkRange,230}231232impl From<DiagnosticInfo<'_>> for OwnedDiagnosticInfo {233    fn from(f: DiagnosticInfo<'_>) -> Self {234        Self {235            item: f.item.clone(),236            dox: f.dox.to_string(),237            ori_link: f.ori_link.to_string(),238            link_range: f.link_range.clone(),239        }240    }241}242243impl OwnedDiagnosticInfo {244    pub(crate) fn as_info(&self) -> DiagnosticInfo<'_> {245        DiagnosticInfo {246            item: &self.item,247            ori_link: &self.ori_link,248            dox: &self.dox,249            link_range: self.link_range.clone(),250        }251    }252}253254pub(crate) struct LinkCollector<'a, 'tcx> {255    pub(crate) cx: &'a mut DocContext<'tcx>,256    /// Cache the resolved links so we can avoid resolving (and emitting errors for) the same link.257    /// The link will be `None` if it could not be resolved (i.e. the error was cached).258    pub(crate) visited_links: FxHashMap<ResolutionInfo, Option<(Res, Option<UrlFragment>)>>,259    /// According to `rustc_resolve`, these links are ambiguous.260    ///261    /// However, we cannot link to an item that has been stripped from the documentation. If all262    /// but one of the "possibilities" are stripped, then there is no real ambiguity. To determine263    /// if an ambiguity is real, we delay resolving them until after `Cache::populate`, then filter264    /// every item that doesn't have a cached path.265    ///266    /// We could get correct results by simply delaying everything. This would have fewer happy267    /// codepaths, but we want to distinguish different kinds of error conditions, and this is easy268    /// to do by resolving links as soon as possible.269    pub(crate) ambiguous_links: FxIndexMap<(ItemId, String), Vec<AmbiguousLinks>>,270}271272pub(crate) struct AmbiguousLinks {273    link_text: Box<str>,274    diag_info: OwnedDiagnosticInfo,275    resolved: Vec<(Res, Option<UrlFragment>)>,276}277278impl<'tcx> LinkCollector<'_, 'tcx> {279    /// Given a full link, parse it as an [enum struct variant].280    ///281    /// In particular, this will return an error whenever there aren't three282    /// full path segments left in the link.283    ///284    /// [enum struct variant]: rustc_hir::VariantData::Struct285    fn variant_field<'path>(286        &self,287        path_str: &'path str,288        item_id: DefId,289        module_id: DefId,290    ) -> Result<(Res, DefId), UnresolvedPath<'path>> {291        let tcx = self.cx.tcx;292        let no_res = || UnresolvedPath {293            item_id,294            module_id,295            partial_res: None,296            unresolved: path_str.into(),297        };298299        debug!("looking for enum variant {path_str}");300        let mut split = path_str.rsplitn(3, "::");301        let variant_field_name = Symbol::intern(split.next().unwrap());302        // We're not sure this is a variant at all, so use the full string.303        // If there's no second component, the link looks like `[path]`.304        // So there's no partial res and we should say the whole link failed to resolve.305        let variant_name = Symbol::intern(split.next().ok_or_else(no_res)?);306307        // If there's no third component, we saw `[a::b]` before and it failed to resolve.308        // So there's no partial res.309        let path = split.next().ok_or_else(no_res)?;310        let ty_res = self.resolve_path(path, TypeNS, item_id, module_id).ok_or_else(no_res)?;311312        match ty_res {313            Res::Def(DefKind::Enum | DefKind::TyAlias, did) => {314                match tcx.type_of(did).instantiate_identity().skip_norm_wip().kind() {315                    ty::Adt(def, _) if def.is_enum() => {316                        if let Some(variant) =317                            def.variants().iter().find(|v| v.name == variant_name)318                            && let Some(field) =319                                variant.fields.iter().find(|f| f.name == variant_field_name)320                        {321                            Ok((ty_res, field.did))322                        } else {323                            Err(UnresolvedPath {324                                item_id,325                                module_id,326                                partial_res: Some(Res::Def(DefKind::Enum, def.did())),327                                unresolved: variant_field_name.to_string().into(),328                            })329                        }330                    }331                    _ => Err(UnresolvedPath {332                        item_id,333                        module_id,334                        partial_res: Some(Res::Def(DefKind::TyAlias, did)),335                        unresolved: variant_name.to_string().into(),336                    }),337                }338            }339            _ => Err(UnresolvedPath {340                item_id,341                module_id,342                partial_res: Some(ty_res),343                unresolved: variant_name.to_string().into(),344            }),345        }346    }347348    /// Convenience wrapper around `doc_link_resolutions`.349    ///350    /// This also handles resolving `true` and `false` as booleans.351    /// NOTE: `doc_link_resolutions` knows only about paths, not about types.352    /// Associated items will never be resolved by this function.353    fn resolve_path(354        &self,355        path_str: &str,356        ns: Namespace,357        item_id: DefId,358        module_id: DefId,359    ) -> Option<Res> {360        if let res @ Some(..) = resolve_self_ty(self.cx.tcx, path_str, ns, item_id) {361            return res;362        }363364        // Resolver doesn't know about true, false, and types that aren't paths (e.g. `()`).365        let result = self366            .cx367            .tcx368            .doc_link_resolutions(module_id)369            .get(&(Symbol::intern(path_str), ns))370            .copied()371            // NOTE: do not remove this panic! Missing links should be recorded as `Res::Err`; if372            // `doc_link_resolutions` is missing a `path_str`, that means that there are valid links373            // that are being missed. To fix the ICE, change374            // `rustc_resolve::rustdoc::attrs_to_preprocessed_links` to cache the link.375            .unwrap_or_else(|| {376                span_bug!(377                    self.cx.tcx.def_span(item_id),378                    "no resolution for {path_str:?} {ns:?} {module_id:?}",379                )380            })381            .and_then(|res| res.try_into().ok())382            .or_else(|| resolve_primitive(path_str, ns));383        debug!("{path_str} resolved to {result:?} in namespace {ns:?}");384        result385    }386387    /// Resolves a string as a path within a particular namespace. Returns an388    /// optional URL fragment in the case of variants and methods.389    fn resolve<'path>(390        &self,391        path_str: &'path str,392        ns: Namespace,393        disambiguator: Option<Disambiguator>,394        item_id: DefId,395        module_id: DefId,396    ) -> Result<Vec<(Res, Option<DefId>)>, UnresolvedPath<'path>> {397        let tcx = self.cx.tcx;398399        if let Some(res) = self.resolve_path(path_str, ns, item_id, module_id) {400            return Ok(match res {401                Res::Def(402                    DefKind::AssocFn403                    | DefKind::AssocConst { .. }404                    | DefKind::AssocTy405                    | DefKind::Variant,406                    def_id,407                ) => {408                    vec![(Res::from_def_id(self.cx.tcx, self.cx.tcx.parent(def_id)), Some(def_id))]409                }410                _ => vec![(res, None)],411            });412        } else if ns == MacroNS {413            return Err(UnresolvedPath {414                item_id,415                module_id,416                partial_res: None,417                unresolved: path_str.into(),418            });419        }420421        // Try looking for methods and associated items.422        // NB: `path_root` could be empty when resolving in the root namespace (e.g. `::std`).423        let (path_root, item_str) = match path_str.rsplit_once("::") {424            Some(res @ (_path_root, item_str)) if !item_str.is_empty() => res,425            _ => {426                // If there's no `::`, or the `::` is at the end (e.g. `String::`) it's not an427                // associated item. So we can be sure that `rustc_resolve` was accurate when it428                // said it wasn't resolved.429                debug!("`::` missing or at end, assuming {path_str} was not in scope");430                return Err(UnresolvedPath {431                    item_id,432                    module_id,433                    partial_res: None,434                    unresolved: path_str.into(),435                });436            }437        };438        let item_name = Symbol::intern(item_str);439440        // FIXME(#83862): this arbitrarily gives precedence to primitives over modules to support441        // links to primitives when `#[rustc_doc_primitive]` is present. It should give an ambiguity442        // error instead and special case *only* modules with `#[rustc_doc_primitive]`, not all443        // primitives.444        match resolve_primitive(path_root, TypeNS)445            .or_else(|| self.resolve_path(path_root, TypeNS, item_id, module_id))446            .map(|ty_res| {447                resolve_associated_item(tcx, ty_res, item_name, ns, disambiguator, module_id)448                    .into_iter()449                    .map(|(res, def_id)| (res, Some(def_id)))450                    .collect::<Vec<_>>()451            }) {452            Some(r) if !r.is_empty() => Ok(r),453            _ => {454                if ns == Namespace::ValueNS {455                    self.variant_field(path_str, item_id, module_id)456                        .map(|(res, def_id)| vec![(res, Some(def_id))])457                } else {458                    Err(UnresolvedPath {459                        item_id,460                        module_id,461                        partial_res: None,462                        unresolved: path_root.into(),463                    })464                }465            }466        }467    }468}469470fn full_res(tcx: TyCtxt<'_>, (base, assoc_item): (Res, Option<DefId>)) -> Res {471    assoc_item.map_or(base, |def_id| Res::from_def_id(tcx, def_id))472}473474/// Given a primitive type, try to resolve an associated item.475fn resolve_primitive_inherent_assoc_item<'tcx>(476    tcx: TyCtxt<'tcx>,477    prim_ty: PrimitiveType,478    ns: Namespace,479    item_ident: Ident,480) -> Vec<(Res, DefId)> {481    prim_ty482        .impls(tcx)483        .flat_map(|impl_| {484            filter_assoc_items_by_name_and_namespace(tcx, impl_, item_ident, ns)485                .map(|item| (Res::Primitive(prim_ty), item.def_id))486        })487        .collect::<Vec<_>>()488}489490fn resolve_self_ty<'tcx>(491    tcx: TyCtxt<'tcx>,492    path_str: &str,493    ns: Namespace,494    item_id: DefId,495) -> Option<Res> {496    if ns != TypeNS || path_str != "Self" {497        return None;498    }499500    let self_id = match tcx.def_kind(item_id) {501        def_kind @ (DefKind::AssocFn502        | DefKind::AssocConst { .. }503        | DefKind::AssocTy504        | DefKind::Variant505        | DefKind::Field) => {506            let parent_def_id = tcx.parent(item_id);507            if def_kind == DefKind::Field && tcx.def_kind(parent_def_id) == DefKind::Variant {508                tcx.parent(parent_def_id)509            } else {510                parent_def_id511            }512        }513        _ => item_id,514    };515516    match tcx.def_kind(self_id) {517        DefKind::Impl { .. } => {518            ty_to_res(tcx, tcx.type_of(self_id).instantiate_identity().skip_norm_wip())519        }520        DefKind::Use => None,521        def_kind => Some(Res::Def(def_kind, self_id)),522    }523}524525/// Convert a Ty to a Res, where possible.526///527/// This is used for resolving type aliases.528fn ty_to_res<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) -> Option<Res> {529    use PrimitiveType::*;530    Some(match *ty.kind() {531        ty::Bool => Res::Primitive(Bool),532        ty::Char => Res::Primitive(Char),533        ty::Int(ity) => Res::Primitive(ity.into()),534        ty::Uint(uty) => Res::Primitive(uty.into()),535        ty::Float(fty) => Res::Primitive(fty.into()),536        ty::Str => Res::Primitive(Str),537        ty::Tuple(tys) if tys.is_empty() => Res::Primitive(Unit),538        ty::Tuple(_) => Res::Primitive(Tuple),539        ty::Pat(..) => Res::Primitive(Pat),540        ty::Array(..) => Res::Primitive(Array),541        ty::Slice(_) => Res::Primitive(Slice),542        ty::RawPtr(_, _) => Res::Primitive(RawPointer),543        ty::Ref(..) => Res::Primitive(Reference),544        ty::FnDef(..) => panic!("type alias to a function definition"),545        ty::FnPtr(..) => Res::Primitive(Fn),546        ty::Never => Res::Primitive(Never),547        ty::Adt(ty::AdtDef(Interned(&ty::AdtDefData { did, .. }, _)), _) | ty::Foreign(did) => {548            Res::from_def_id(tcx, did)549        }550        ty::Alias(..)551        | ty::Closure(..)552        | ty::CoroutineClosure(..)553        | ty::Coroutine(..)554        | ty::CoroutineWitness(..)555        | ty::Dynamic(..)556        | ty::UnsafeBinder(_)557        | ty::Param(_)558        | ty::Bound(..)559        | ty::Placeholder(_)560        | ty::Infer(_)561        | ty::Error(_) => return None,562    })563}564565/// Convert a PrimitiveType to a Ty, where possible.566///567/// This is used for resolving trait impls for primitives568fn primitive_type_to_ty<'tcx>(tcx: TyCtxt<'tcx>, prim: PrimitiveType) -> Option<Ty<'tcx>> {569    use PrimitiveType::*;570571    // FIXME: Only simple types are supported here, see if we can support572    // other types such as Tuple, Array, Slice, etc.573    // See https://github.com/rust-lang/rust/issues/90703#issuecomment-1004263455574    Some(match prim {575        Bool => tcx.types.bool,576        Str => tcx.types.str_,577        Char => tcx.types.char,578        Never => tcx.types.never,579        I8 => tcx.types.i8,580        I16 => tcx.types.i16,581        I32 => tcx.types.i32,582        I64 => tcx.types.i64,583        I128 => tcx.types.i128,584        Isize => tcx.types.isize,585        F16 => tcx.types.f16,586        F32 => tcx.types.f32,587        F64 => tcx.types.f64,588        F128 => tcx.types.f128,589        U8 => tcx.types.u8,590        U16 => tcx.types.u16,591        U32 => tcx.types.u32,592        U64 => tcx.types.u64,593        U128 => tcx.types.u128,594        Usize => tcx.types.usize,595        _ => return None,596    })597}598599/// Resolve an associated item, returning its containing page's `Res`600/// and the fragment targeting the associated item on its page.601fn resolve_associated_item<'tcx>(602    tcx: TyCtxt<'tcx>,603    root_res: Res,604    item_name: Symbol,605    ns: Namespace,606    disambiguator: Option<Disambiguator>,607    module_id: DefId,608) -> Vec<(Res, DefId)> {609    let item_ident = Ident::with_dummy_span(item_name);610611    match root_res {612        Res::Def(DefKind::TyAlias, alias_did) => {613            // Resolve the link on the type the alias points to.614            // FIXME: if the associated item is defined directly on the type alias,615            // it will show up on its documentation page, we should link there instead.616            let Some(aliased_res) =617                ty_to_res(tcx, tcx.type_of(alias_did).instantiate_identity().skip_norm_wip())618            else {619                return vec![];620            };621            let aliased_items =622                resolve_associated_item(tcx, aliased_res, item_name, ns, disambiguator, module_id);623            aliased_items624                .into_iter()625                .map(|(res, assoc_did)| {626                    if is_assoc_item_on_alias_page(tcx, assoc_did) {627                        (root_res, assoc_did)628                    } else {629                        (res, assoc_did)630                    }631                })632                .collect()633        }634        Res::Primitive(prim) => resolve_assoc_on_primitive(tcx, prim, ns, item_ident, module_id),635        Res::Def(DefKind::Struct | DefKind::Union | DefKind::Enum, did) => {636            resolve_assoc_on_adt(tcx, did, item_ident, ns, disambiguator, module_id)637        }638        Res::Def(DefKind::ForeignTy, did) => {639            resolve_assoc_on_simple_type(tcx, did, item_ident, ns, module_id)640        }641        Res::Def(DefKind::Trait, did) => filter_assoc_items_by_name_and_namespace(642            tcx,643            did,644            Ident::with_dummy_span(item_name),645            ns,646        )647        .map(|item| (root_res, item.def_id))648        .collect::<Vec<_>>(),649        _ => Vec::new(),650    }651}652653// FIXME: make this fully complete by also including ALL inherent impls654// and trait impls BUT ONLY if on alias directly655fn is_assoc_item_on_alias_page<'tcx>(tcx: TyCtxt<'tcx>, assoc_did: DefId) -> bool {656    match tcx.def_kind(assoc_did) {657        // Variants and fields always have docs on the alias page.658        DefKind::Variant | DefKind::Field => true,659        _ => false,660    }661}662663fn resolve_assoc_on_primitive<'tcx>(664    tcx: TyCtxt<'tcx>,665    prim: PrimitiveType,666    ns: Namespace,667    item_ident: Ident,668    module_id: DefId,669) -> Vec<(Res, DefId)> {670    let root_res = Res::Primitive(prim);671    let items = resolve_primitive_inherent_assoc_item(tcx, prim, ns, item_ident);672    if !items.is_empty() {673        items674    // Inherent associated items take precedence over items that come from trait impls.675    } else {676        primitive_type_to_ty(tcx, prim)677            .map(|ty| {678                resolve_associated_trait_item(ty, module_id, item_ident, ns, tcx)679                    .iter()680                    .map(|item| (root_res, item.def_id))681                    .collect::<Vec<_>>()682            })683            .unwrap_or_default()684    }685}686687fn resolve_assoc_on_adt<'tcx>(688    tcx: TyCtxt<'tcx>,689    adt_def_id: DefId,690    item_ident: Ident,691    ns: Namespace,692    disambiguator: Option<Disambiguator>,693    module_id: DefId,694) -> Vec<(Res, DefId)> {695    debug!("looking for associated item named {item_ident} for item {adt_def_id:?}");696    let root_res = Res::from_def_id(tcx, adt_def_id);697    let adt_ty = tcx.type_of(adt_def_id).instantiate_identity().skip_norm_wip();698    let adt_def = adt_ty.ty_adt_def().expect("must be ADT");699    // Checks if item_name is a variant of the `SomeItem` enum700    if ns == TypeNS && adt_def.is_enum() {701        for variant in adt_def.variants() {702            if variant.name == item_ident.name {703                return vec![(root_res, variant.def_id)];704            }705        }706    }707708    if let Some(Disambiguator::Kind(DefKind::Field)) = disambiguator709        && (adt_def.is_struct() || adt_def.is_union())710    {711        return resolve_structfield(adt_def, item_ident.name)712            .into_iter()713            .map(|did| (root_res, did))714            .collect();715    }716717    let assoc_items = resolve_assoc_on_simple_type(tcx, adt_def_id, item_ident, ns, module_id);718    if !assoc_items.is_empty() {719        return assoc_items;720    }721722    if ns == Namespace::ValueNS && (adt_def.is_struct() || adt_def.is_union()) {723        return resolve_structfield(adt_def, item_ident.name)724            .into_iter()725            .map(|did| (root_res, did))726            .collect();727    }728729    vec![]730}731732/// "Simple" i.e. an ADT, foreign type, etc. -- not a type alias, primitive type, or other trickier type.733fn resolve_assoc_on_simple_type<'tcx>(734    tcx: TyCtxt<'tcx>,735    ty_def_id: DefId,736    item_ident: Ident,737    ns: Namespace,738    module_id: DefId,739) -> Vec<(Res, DefId)> {740    let root_res = Res::from_def_id(tcx, ty_def_id);741    // Checks if item_name belongs to `impl SomeItem`742    let inherent_assoc_items: Vec<_> = tcx743        .inherent_impls(ty_def_id)744        .iter()745        .flat_map(|&imp| filter_assoc_items_by_name_and_namespace(tcx, imp, item_ident, ns))746        .map(|item| (root_res, item.def_id))747        .collect();748    debug!("got inherent assoc items {inherent_assoc_items:?}");749    if !inherent_assoc_items.is_empty() {750        return inherent_assoc_items;751    }752753    // Check if item_name belongs to `impl SomeTrait for SomeItem`754    // FIXME(#74563): This gives precedence to `impl SomeItem`:755    // Although having both would be ambiguous, use impl version for compatibility's sake.756    // To handle that properly resolve() would have to support757    // something like [`ambi_fn`](<SomeStruct as SomeTrait>::ambi_fn)758    let ty = tcx.type_of(ty_def_id).instantiate_identity().skip_norm_wip();759    let trait_assoc_items = resolve_associated_trait_item(ty, module_id, item_ident, ns, tcx)760        .into_iter()761        .map(|item| (root_res, item.def_id))762        .collect::<Vec<_>>();763    debug!("got trait assoc items {trait_assoc_items:?}");764    trait_assoc_items765}766767fn resolve_structfield<'tcx>(adt_def: ty::AdtDef<'tcx>, item_name: Symbol) -> Option<DefId> {768    debug!("looking for fields named {item_name} for {adt_def:?}");769    adt_def770        .non_enum_variant()771        .fields772        .iter()773        .find(|field| field.name == item_name)774        .map(|field| field.did)775}776777/// Look to see if a resolved item has an associated item named `item_name`.778///779/// Given `[std::io::Error::source]`, where `source` is unresolved, this would780/// find `std::error::Error::source` and return781/// `<io::Error as error::Error>::source`.782fn resolve_associated_trait_item<'tcx>(783    ty: Ty<'tcx>,784    module: DefId,785    item_ident: Ident,786    ns: Namespace,787    tcx: TyCtxt<'tcx>,788) -> Vec<ty::AssocItem> {789    // FIXME: this should also consider blanket impls (`impl<T> X for T`). Unfortunately790    // `get_auto_trait_and_blanket_impls` is broken because the caching behavior is wrong. In the791    // meantime, just don't look for these blanket impls.792793    // Next consider explicit impls: `impl MyTrait for MyType`794    // Give precedence to inherent impls.795    let traits = trait_impls_for(tcx, ty, module);796    debug!("considering traits {traits:?}");797    let candidates = traits798        .iter()799        .flat_map(|&(impl_, trait_)| {800            filter_assoc_items_by_name_and_namespace(tcx, trait_, item_ident, ns).map(801                move |trait_assoc| {802                    trait_assoc_to_impl_assoc_item(tcx, impl_, trait_assoc.def_id)803                        .unwrap_or(*trait_assoc)804                },805            )806        })807        .collect::<Vec<_>>();808    // FIXME(#74563): warn about ambiguity809    debug!("the candidates were {candidates:?}");810    candidates811}812813/// Find the associated item in the impl `impl_id` that corresponds to the814/// trait associated item `trait_assoc_id`.815///816/// This function returns `None` if no associated item was found in the impl.817/// This can occur when the trait associated item has a default value that is818/// not overridden in the impl.819///820/// This is just a wrapper around [`TyCtxt::impl_item_implementor_ids()`] and821/// [`TyCtxt::associated_item()`] (with some helpful logging added).822#[instrument(level = "debug", skip(tcx), ret)]823fn trait_assoc_to_impl_assoc_item<'tcx>(824    tcx: TyCtxt<'tcx>,825    impl_id: DefId,826    trait_assoc_id: DefId,827) -> Option<ty::AssocItem> {828    let trait_to_impl_assoc_map = tcx.impl_item_implementor_ids(impl_id);829    debug!(?trait_to_impl_assoc_map);830    let impl_assoc_id = *trait_to_impl_assoc_map.get(&trait_assoc_id)?;831    debug!(?impl_assoc_id);832    Some(tcx.associated_item(impl_assoc_id))833}834835/// Given a type, return all trait impls in scope in `module` for that type.836/// Returns a set of pairs of `(impl_id, trait_id)`.837///838/// NOTE: this cannot be a query because more traits could be available when more crates are compiled!839/// So it is not stable to serialize cross-crate.840#[instrument(level = "debug", skip(tcx))]841fn trait_impls_for<'tcx>(842    tcx: TyCtxt<'tcx>,843    ty: Ty<'tcx>,844    module: DefId,845) -> FxIndexSet<(DefId, DefId)> {846    let mut impls = FxIndexSet::default();847848    for &trait_ in tcx.doc_link_traits_in_scope(module) {849        tcx.for_each_relevant_impl(trait_, ty, |impl_| {850            let trait_ref = tcx.impl_trait_ref(impl_);851            // Check if these are the same type.852            let impl_type = trait_ref.skip_binder().self_ty();853            trace!(854                "comparing type {impl_type} with kind {kind:?} against type {ty:?}",855                kind = impl_type.kind(),856            );857            // Fast path: if this is a primitive simple `==` will work858            // NOTE: the `match` is necessary; see #92662.859            // this allows us to ignore generics because the user input860            // may not include the generic placeholders861            // e.g. this allows us to match Foo (user comment) with Foo<T> (actual type)862            let saw_impl = impl_type == ty863                || match (impl_type.kind(), ty.kind()) {864                    (ty::Adt(impl_def, _), ty::Adt(ty_def, _)) => {865                        debug!("impl def_id: {:?}, ty def_id: {:?}", impl_def.did(), ty_def.did());866                        impl_def.did() == ty_def.did()867                    }868                    _ => false,869                };870871            if saw_impl {872                impls.insert((impl_, trait_));873            }874        });875    }876877    impls878}879880/// Check for resolve collisions between a trait and its derive.881///882/// These are common and we should just resolve to the trait in that case.883fn is_derive_trait_collision<T>(ns: &PerNS<Result<Vec<(Res, T)>, ResolutionFailure<'_>>>) -> bool {884    if let (Ok(type_ns), Ok(macro_ns)) = (&ns.type_ns, &ns.macro_ns) {885        type_ns.iter().any(|(res, _)| matches!(res, Res::Def(DefKind::Trait, _)))886            && macro_ns.iter().any(|(res, _)| {887                matches!(888                    res,889                    Res::Def(DefKind::Macro(kinds), _) if kinds.contains(MacroKinds::DERIVE)890                )891            })892    } else {893        false894    }895}896897impl DocVisitor<'_> for LinkCollector<'_, '_> {898    fn visit_item(&mut self, item: &Item) {899        self.resolve_links(item);900        self.visit_item_recur(item)901    }902}903904enum PreprocessingError {905    /// User error: `[std#x#y]` is not valid906    MultipleAnchors,907    Disambiguator(MarkdownLinkRange, String),908    MalformedGenerics(MalformedGenerics, String),909}910911impl PreprocessingError {912    fn report(&self, cx: &DocContext<'_>, diag_info: DiagnosticInfo<'_>) {913        match self {914            PreprocessingError::MultipleAnchors => report_multiple_anchors(cx, diag_info),915            PreprocessingError::Disambiguator(range, msg) => {916                disambiguator_error(cx, diag_info, range.clone(), msg.clone())917            }918            PreprocessingError::MalformedGenerics(err, path_str) => {919                report_malformed_generics(cx, diag_info, *err, path_str)920            }921        }922    }923}924925#[derive(Clone)]926struct PreprocessingInfo {927    path_str: Box<str>,928    disambiguator: Option<Disambiguator>,929    extra_fragment: Option<String>,930    link_text: Box<str>,931}932933// Not a typedef to avoid leaking several private structures from this module.934pub(crate) struct PreprocessedMarkdownLink(935    Result<PreprocessingInfo, PreprocessingError>,936    MarkdownLink,937);938939/// Returns:940/// - `None` if the link should be ignored.941/// - `Some(Err(_))` if the link should emit an error942/// - `Some(Ok(_))` if the link is valid943///944/// `link_buffer` is needed for lifetime reasons; it will always be overwritten and the contents ignored.945fn preprocess_link(946    ori_link: &MarkdownLink,947    dox: &str,948) -> Option<Result<PreprocessingInfo, PreprocessingError>> {949    // IMPORTANT: To be kept in sync with the corresponding function in `rustc_resolve::rustdoc`.950    // Namely, whenever this function returns a successful result for a given input,951    // the rustc counterpart *MUST* return a link that's equal to `PreprocessingInfo.path_str`!952953    // certain link kinds cannot have their path be urls,954    // so they should not be ignored, no matter how much they look like urls.955    // e.g. [https://example.com/] is not a link to example.com.956    let can_be_url = !matches!(957        ori_link.kind,958        LinkType::ShortcutUnknown | LinkType::CollapsedUnknown | LinkType::ReferenceUnknown959    );960961    // [] is mostly likely not supposed to be a link962    if ori_link.link.is_empty() {963        return None;964    }965966    // Bail early for real links.967    if can_be_url && ori_link.link.contains('/') {968        return None;969    }970971    let stripped = ori_link.link.replace('`', "");972    let mut parts = stripped.split('#');973974    let link = parts.next().unwrap();975    let link = link.trim();976    if link.is_empty() {977        // This is an anchor to an element of the current page, nothing to do in here!978        return None;979    }980    let extra_fragment = parts.next();981    if parts.next().is_some() {982        // A valid link can't have multiple #'s983        return Some(Err(PreprocessingError::MultipleAnchors));984    }985986    // Parse and strip the disambiguator from the link, if present.987    let (disambiguator, path_str, link_text) = match Disambiguator::from_str(link) {988        Ok(Some((d, path, link_text))) => (Some(d), path.trim(), link_text.trim()),989        Ok(None) => (None, link, link),990        Err((err_msg, relative_range)) => {991            // Only report error if we would not have ignored this link. See issue #83859.992            if !(can_be_url && should_ignore_link_with_disambiguators(link)) {993                let disambiguator_range = match range_between_backticks(&ori_link.range, dox) {994                    MarkdownLinkRange::Destination(no_backticks_range) => {995                        MarkdownLinkRange::Destination(996                            (no_backticks_range.start + relative_range.start)997                                ..(no_backticks_range.start + relative_range.end),998                        )999                    }1000                    mdlr @ MarkdownLinkRange::WholeLink(_) => mdlr,1001                };1002                return Some(Err(PreprocessingError::Disambiguator(disambiguator_range, err_msg)));1003            } else {1004                return None;1005            }1006        }1007    };10081009    let is_shortcut_style = ori_link.kind == LinkType::ShortcutUnknown;1010    // If there's no backticks, be lenient and revert to the old behavior.1011    // This is to prevent churn by linting on stuff that isn't meant to be a link.1012    // only shortcut links have simple enough syntax that they1013    // are likely to be written accidentally, collapsed and reference links1014    // need 4 metachars, and reference links will not usually use1015    // backticks in the reference name.1016    // therefore, only shortcut syntax gets the lenient behavior.1017    //1018    // here's a truth table for how link kinds that cannot be urls are handled:1019    //1020    // |-------------------------------------------------------|1021    // |              |  is shortcut link  | not shortcut link |1022    // |--------------|--------------------|-------------------|1023    // | has backtick |    never ignore    |    never ignore   |1024    // | no backtick  | ignore if url-like |    never ignore   |1025    // |-------------------------------------------------------|1026    let ignore_urllike = can_be_url || (is_shortcut_style && !ori_link.link.contains('`'));1027    if ignore_urllike && should_ignore_link(path_str) {1028        return None;1029    }1030    // If we have an intra-doc link starting with `!` (which isn't `[!]` because this is the never type), we ignore it1031    // as it is never valid.1032    //1033    // The case is common enough because of cases like `#[doc = include_str!("../README.md")]` which often1034    // uses GitHub-flavored Markdown (GFM) admonitions, such as `[!NOTE]`.1035    if is_shortcut_style1036        && let Some(suffix) = ori_link.link.strip_prefix('!')1037        && !suffix.is_empty()1038        && suffix.chars().all(|c| c.is_ascii_alphabetic())1039    {1040        return None;1041    }10421043    // Strip generics from the path.1044    let path_str = match strip_generics_from_path(path_str) {1045        Ok(path) => path,1046        Err(err) => {1047            debug!("link has malformed generics: {path_str}");1048            return Some(Err(PreprocessingError::MalformedGenerics(err, path_str.to_owned())));1049        }1050    };10511052    // Sanity check to make sure we don't have any angle brackets after stripping generics.1053    assert!(!path_str.contains(['<', '>'].as_slice()));10541055    // The link is not an intra-doc link if it still contains spaces after stripping generics.1056    if path_str.contains(' ') {1057        return None;1058    }10591060    Some(Ok(PreprocessingInfo {1061        path_str,1062        disambiguator,1063        extra_fragment: extra_fragment.map(|frag| frag.to_owned()),1064        link_text: Box::<str>::from(link_text),1065    }))1066}10671068fn preprocessed_markdown_links(s: &str) -> Vec<PreprocessedMarkdownLink> {1069    markdown_links(s, |link| {1070        preprocess_link(&link, s).map(|pp_link| PreprocessedMarkdownLink(pp_link, link))1071    })1072}10731074impl LinkCollector<'_, '_> {1075    #[instrument(level = "debug", skip_all)]1076    fn resolve_links(&mut self, item: &Item) {1077        let tcx = self.cx.tcx;1078        let document_private = self.cx.document_private();1079        let effective_visibilities = tcx.effective_visibilities(());1080        let should_skip_link_resolution = |item_id: DefId| {1081            !document_private1082                && item_id1083                    .as_local()1084                    .is_some_and(|local_def_id| !effective_visibilities.is_exported(local_def_id))1085                && !has_primitive_or_keyword_or_attribute_docs(&item.attrs.other_attrs)1086        };10871088        if let Some(def_id) = item.item_id.as_def_id()1089            && should_skip_link_resolution(def_id)1090        {1091            // Skip link resolution for non-exported items.1092            return;1093        }10941095        let mut try_insert_links = |item_id, doc: &str| {1096            if should_skip_link_resolution(item_id) {1097                return;1098            }1099            let module_id = match tcx.def_kind(item_id) {1100                DefKind::Mod if item.inner_docs(tcx) => item_id,1101                _ => find_nearest_parent_module(tcx, item_id).unwrap(),1102            };1103            for md_link in preprocessed_markdown_links(&doc) {1104                let link = self.resolve_link(&doc, item, item_id, module_id, &md_link);1105                if let Some(link) = link {1106                    self.cx1107                        .cache1108                        .intra_doc_links1109                        .entry(item.item_or_reexport_id())1110                        .or_default()1111                        .insert(link);1112                }1113            }1114        };11151116        // We want to resolve in the lexical scope of the documentation.1117        // In the presence of re-exports, this is not the same as the module of the item.1118        // Rather than merging all documentation into one, resolve it one attribute at a time1119        // so we know which module it came from.1120        for (item_id, doc) in prepare_to_doc_link_resolution(&item.attrs.doc_strings) {1121            if !may_have_doc_links(&doc) {1122                continue;1123            }11241125            debug!("combined_docs={doc}");1126            // NOTE: if there are links that start in one crate and end in another, this will not resolve them.1127            // This is a degenerate case and it's not supported by rustdoc.1128            let item_id = item_id.unwrap_or_else(|| item.item_id.expect_def_id());1129            try_insert_links(item_id, &doc)1130        }11311132        // Also resolve links in the note text of `#[deprecated]`.1133        for attr in &item.attrs.other_attrs {1134            let Attribute::Parsed(AttributeKind::Deprecated { span: depr_span, deprecation }) =1135                attr1136            else {1137                continue;1138            };1139            let Some(note_sym) = deprecation.note else { continue };1140            let note = note_sym.as_str();11411142            if !may_have_doc_links(note) {1143                continue;1144            }11451146            debug!("deprecated_note={note}");1147            // When resolving an intra-doc link inside a deprecation note that is on an inlined1148            // `use` statement, we need to use the `def_id` of the `use` statement, not the1149            // inlined item.1150            // <https://github.com/rust-lang/rust/pull/151120>1151            let item_id = if let Some(inline_stmt_id) = item.inline_stmt_id1152                && find_attr!(tcx, inline_stmt_id, Deprecated { span, ..} if span == depr_span)1153            {1154                inline_stmt_id.to_def_id()1155            } else {1156                item.item_id.expect_def_id()1157            };1158            try_insert_links(item_id, note)1159        }1160    }11611162    pub(crate) fn save_link(&mut self, item_id: ItemId, link: ItemLink) {1163        self.cx.cache.intra_doc_links.entry(item_id).or_default().insert(link);1164    }11651166    /// This is the entry point for resolving an intra-doc link.1167    ///1168    /// FIXME(jynelson): this is way too many arguments1169    fn resolve_link(1170        &mut self,1171        dox: &str,1172        item: &Item,1173        item_id: DefId,1174        module_id: DefId,1175        PreprocessedMarkdownLink(pp_link, ori_link): &PreprocessedMarkdownLink,1176    ) -> Option<ItemLink> {1177        trace!("considering link '{}'", ori_link.link);11781179        let diag_info = DiagnosticInfo {1180            item,1181            dox,1182            ori_link: &ori_link.link,1183            link_range: ori_link.range.clone(),1184        };1185        let PreprocessingInfo { path_str, disambiguator, extra_fragment, link_text } =1186            pp_link.as_ref().map_err(|err| err.report(self.cx, diag_info.clone())).ok()?;1187        let disambiguator = *disambiguator;11881189        let mut resolved = self.resolve_with_disambiguator_cached(1190            ResolutionInfo {1191                item_id,1192                module_id,1193                dis: disambiguator,1194                path_str: path_str.clone(),1195                extra_fragment: extra_fragment.clone(),1196            },1197            diag_info.clone(), // this struct should really be Copy, but Range is not :(1198            // For reference-style links we want to report only one error so unsuccessful1199            // resolutions are cached, for other links we want to report an error every1200            // time so they are not cached.1201            matches!(ori_link.kind, LinkType::Reference | LinkType::Shortcut),1202        )?;12031204        if resolved.len() > 1 {1205            let links = AmbiguousLinks {1206                link_text: link_text.clone(),1207                diag_info: diag_info.into(),1208                resolved,1209            };12101211            self.ambiguous_links1212                .entry((item.item_id, path_str.to_string()))1213                .or_default()1214                .push(links);1215            None1216        } else if let Some((res, fragment)) = resolved.pop() {1217            self.compute_link(res, fragment, path_str, disambiguator, diag_info, link_text)1218        } else {1219            None1220        }1221    }12221223    /// Returns `true` if a link could be generated from the given intra-doc information.1224    ///1225    /// This is a very light version of `format::href_with_root_path` since we're only interested1226    /// about whether we can generate a link to an item or not.1227    ///1228    /// * If `original_did` is local, then we check if the item is reexported or public.1229    /// * If `original_did` is not local, then we check if the crate it comes from is a direct1230    ///   public dependency.1231    fn validate_link(&self, original_did: DefId) -> bool {1232        let tcx = self.cx.tcx;1233        let def_kind = tcx.def_kind(original_did);1234        let did = match def_kind {1235            DefKind::AssocTy | DefKind::AssocFn | DefKind::AssocConst { .. } | DefKind::Variant => {1236                // documented on their parent's page1237                tcx.parent(original_did)1238            }1239            // If this a constructor, we get the parent (either a struct or a variant) and then1240            // generate the link for this item.1241            DefKind::Ctor(..) => return self.validate_link(tcx.parent(original_did)),1242            DefKind::ExternCrate => {1243                // Link to the crate itself, not the `extern crate` item.1244                if let Some(local_did) = original_did.as_local() {1245                    tcx.extern_mod_stmt_cnum(local_did).unwrap_or(LOCAL_CRATE).as_def_id()1246                } else {1247                    original_did1248                }1249            }1250            _ => original_did,1251        };12521253        let cache = &self.cx.cache;1254        if !original_did.is_local()1255            && !cache.effective_visibilities.is_directly_public(tcx, did)1256            && !cache.document_private1257            && !cache.primitive_locations.values().any(|&id| id == did)1258        {1259            return false;1260        }12611262        cache.paths.get(&did).is_some()1263            || cache.external_paths.contains_key(&did)1264            || !did.is_local()1265    }12661267    pub(crate) fn resolve_ambiguities(&mut self) {1268        let mut ambiguous_links = mem::take(&mut self.ambiguous_links);1269        for ((item_id, path_str), info_items) in ambiguous_links.iter_mut() {1270            for info in info_items {1271                info.resolved.retain(|(res, _)| match res {1272                    Res::Def(_, def_id) => self.validate_link(*def_id),1273                    // Primitive types are always valid.1274                    Res::Primitive(_) => true,1275                });1276                let diag_info = info.diag_info.as_info();1277                match info.resolved.len() {1278                    1 => {1279                        let (res, fragment) = info.resolved.pop().unwrap();1280                        if let Some(link) = self.compute_link(1281                            res,1282                            fragment,1283                            path_str,1284                            None,1285                            diag_info,1286                            &info.link_text,1287                        ) {1288                            self.save_link(*item_id, link);1289                        }1290                    }1291                    0 => {1292                        report_diagnostic(1293                            self.cx.tcx,1294                            BROKEN_INTRA_DOC_LINKS,1295                            format!("all items matching `{path_str}` are private or doc(hidden)"),1296                            &diag_info,1297                            |diag, sp, _| {1298                                if let Some(sp) = sp {1299                                    diag.span_label(sp, "unresolved link");1300                                } else {1301                                    diag.note("unresolved link");1302                                }1303                            },1304                        );1305                    }1306                    _ => {1307                        let candidates = info1308                            .resolved1309                            .iter()1310                            .map(|(res, fragment)| {1311                                let def_id = if let Some(UrlFragment::Item(def_id)) = fragment {1312                                    Some(*def_id)1313                                } else {1314                                    None1315                                };1316                                (*res, def_id)1317                            })1318                            .collect::<Vec<_>>();1319                        ambiguity_error(self.cx, &diag_info, path_str, &candidates, true);1320                    }1321                }1322            }1323        }1324    }13251326    fn compute_link(1327        &mut self,1328        mut res: Res,1329        fragment: Option<UrlFragment>,1330        path_str: &str,1331        disambiguator: Option<Disambiguator>,1332        diag_info: DiagnosticInfo<'_>,1333        link_text: &Box<str>,1334    ) -> Option<ItemLink> {1335        // Check for a primitive which might conflict with a module1336        // Report the ambiguity and require that the user specify which one they meant.1337        // FIXME: could there ever be a primitive not in the type namespace?1338        if matches!(1339            disambiguator,1340            None | Some(Disambiguator::Namespace(Namespace::TypeNS) | Disambiguator::Primitive)1341        ) && !matches!(res, Res::Primitive(_))1342            && let Some(prim) = resolve_primitive(path_str, TypeNS)1343        {1344            // `prim@char`1345            if matches!(disambiguator, Some(Disambiguator::Primitive)) {1346                res = prim;1347            } else {1348                // `[char]` when a `char` module is in scope1349                let candidates = &[(res, res.def_id(self.cx.tcx)), (prim, None)];1350                ambiguity_error(self.cx, &diag_info, path_str, candidates, true);1351                return None;1352            }1353        }13541355        match res {1356            Res::Primitive(_) => {1357                if let Some(UrlFragment::Item(id)) = fragment {1358                    // We're actually resolving an associated item of a primitive, so we need to1359                    // verify the disambiguator (if any) matches the type of the associated item.1360                    // This case should really follow the same flow as the `Res::Def` branch below,1361                    // but attempting to add a call to `clean::register_res` causes an ICE. @jyn5141362                    // thinks `register_res` is only needed for cross-crate re-exports, but Rust1363                    // doesn't allow statements like `use str::trim;`, making this a (hopefully)1364                    // valid omission. See https://github.com/rust-lang/rust/pull/80660#discussion_r5515856771365                    // for discussion on the matter.1366                    let kind = self.cx.tcx.def_kind(id);1367                    self.verify_disambiguator(path_str, kind, id, disambiguator, &diag_info)?;1368                } else {1369                    match disambiguator {1370                        Some(Disambiguator::Primitive | Disambiguator::Namespace(_)) | None => {}1371                        Some(other) => {1372                            self.report_disambiguator_mismatch(path_str, other, res, &diag_info);1373                            return None;1374                        }1375                    }1376                }13771378                res.def_id(self.cx.tcx).map(|page_id| ItemLink {1379                    link: Box::<str>::from(diag_info.ori_link),1380                    link_text: link_text.clone(),1381                    page_id,1382                    fragment,1383                })1384            }1385            Res::Def(kind, id) => {1386                let (kind_for_dis, id_for_dis) = if let Some(UrlFragment::Item(id)) = fragment {1387                    (self.cx.tcx.def_kind(id), id)1388                } else {1389                    (kind, id)1390                };1391                self.verify_disambiguator(1392                    path_str,1393                    kind_for_dis,1394                    id_for_dis,1395                    disambiguator,1396                    &diag_info,1397                )?;13981399                let page_id = clean::register_res(self.cx, rustc_hir::def::Res::Def(kind, id));1400                Some(ItemLink {1401                    link: Box::<str>::from(diag_info.ori_link),1402                    link_text: link_text.clone(),1403                    page_id,1404                    fragment,1405                })1406            }1407        }1408    }14091410    fn verify_disambiguator(1411        &self,1412        path_str: &str,1413        kind: DefKind,1414        id: DefId,1415        disambiguator: Option<Disambiguator>,1416        diag_info: &DiagnosticInfo<'_>,1417    ) -> Option<()> {1418        debug!("intra-doc link to {path_str} resolved to {:?}", (kind, id));14191420        // Disallow e.g. linking to enums with `struct@`1421        debug!("saw kind {kind:?} with disambiguator {disambiguator:?}");1422        match (kind, disambiguator) {1423                | (1424                    DefKind::Const { .. }1425                    | DefKind::ConstParam1426                    | DefKind::AssocConst { .. }1427                    | DefKind::AnonConst,1428                    Some(Disambiguator::Kind(DefKind::Const { .. })),1429                )1430                // NOTE: this allows 'method' to mean both normal functions and associated functions1431                // This can't cause ambiguity because both are in the same namespace.1432                | (DefKind::Fn | DefKind::AssocFn, Some(Disambiguator::Kind(DefKind::Fn)))1433                // These are namespaces; allow anything in the namespace to match1434                | (_, Some(Disambiguator::Namespace(_)))1435                // If no disambiguator given, allow anything1436                | (_, None)1437                // All of these are valid, so do nothing1438                => {}1439                (actual, Some(Disambiguator::Kind(expected))) if actual == expected => {}1440                (_, Some(specified @ Disambiguator::Kind(_) | specified @ Disambiguator::Primitive)) => {1441                    self.report_disambiguator_mismatch(path_str, specified, Res::Def(kind, id), diag_info);1442                    return None;1443                }1444            }14451446        // item can be non-local e.g. when using `#[rustc_doc_primitive = "pointer"]`1447        if let Some(dst_id) = id.as_local()1448            && let Some(src_id) = diag_info.item.item_id.expect_def_id().as_local()1449            && self.cx.tcx.effective_visibilities(()).is_exported(src_id)1450            && !self.cx.tcx.effective_visibilities(()).is_exported(dst_id)1451        {1452            privacy_error(self.cx, diag_info, path_str);1453        }14541455        Some(())1456    }14571458    fn report_disambiguator_mismatch(1459        &self,1460        path_str: &str,1461        specified: Disambiguator,1462        resolved: Res,1463        diag_info: &DiagnosticInfo<'_>,1464    ) {1465        // The resolved item did not match the disambiguator; give a better error than 'not found'1466        let msg = format!("incompatible link kind for `{path_str}`");1467        let callback = |diag: &mut Diag<'_, ()>, sp: Option<rustc_span::Span>, link_range| {1468            let note = format!(1469                "this link resolved to {} {}, which is not {} {}",1470                resolved.article(),1471                resolved.descr(),1472                specified.article(),1473                specified.descr(),1474            );1475            if let Some(sp) = sp {1476                diag.span_label(sp, note);1477            } else {1478                diag.note(note);1479            }1480            suggest_disambiguator(resolved, diag, path_str, link_range, sp, diag_info);1481        };1482        report_diagnostic(self.cx.tcx, BROKEN_INTRA_DOC_LINKS, msg, diag_info, callback);1483    }14841485    fn report_rawptr_assoc_feature_gate(1486        &self,1487        dox: &str,1488        ori_link: &MarkdownLinkRange,1489        item: &Item,1490    ) {1491        let span = match source_span_for_markdown_range(1492            self.cx.tcx,1493            dox,1494            ori_link.inner_range(),1495            &item.attrs.doc_strings,1496        ) {1497            Some((sp, _)) => sp,1498            None => item.attr_span(self.cx.tcx),1499        };1500        rustc_session::errors::feature_err(1501            self.cx.tcx.sess,1502            sym::intra_doc_pointers,1503            span,1504            "linking to associated items of raw pointers is experimental",1505        )1506        .with_note("rustdoc does not allow disambiguating between `*const` and `*mut`, and pointers are unstable until it does")1507        .emit();1508    }15091510    fn resolve_with_disambiguator_cached(1511        &mut self,1512        key: ResolutionInfo,1513        diag: DiagnosticInfo<'_>,1514        // If errors are cached then they are only reported on first occurrence1515        // which we want in some cases but not in others.1516        cache_errors: bool,1517    ) -> Option<Vec<(Res, Option<UrlFragment>)>> {1518        if let Some(res) = self.visited_links.get(&key)1519            && (res.is_some() || cache_errors)1520        {1521            return res.clone().map(|r| vec![r]);1522        }15231524        let mut candidates = self.resolve_with_disambiguator(&key, diag.clone());15251526        // FIXME: it would be nice to check that the feature gate was enabled in the original crate, not just ignore it altogether.1527        // However I'm not sure how to check that across crates.1528        if let Some(candidate) = candidates.first()1529            && candidate.0 == Res::Primitive(PrimitiveType::RawPointer)1530            && key.path_str.contains("::")1531        // We only want to check this if this is an associated item.1532        {1533            if key.item_id.is_local() && !self.cx.tcx.features().intra_doc_pointers() {1534                self.report_rawptr_assoc_feature_gate(diag.dox, &diag.link_range, diag.item);1535                return None;1536            } else {1537                candidates = vec![*candidate];1538            }1539        }15401541        // If there are multiple items with the same "kind" (for example, both "associated types")1542        // and after removing duplicated kinds, only one remains, the `ambiguity_error` function1543        // won't emit an error. So at this point, we can just take the first candidate as it was1544        // the first retrieved and use it to generate the link.1545        if let [candidate, _candidate2, ..] = *candidates1546            && !ambiguity_error(self.cx, &diag, &key.path_str, &candidates, false)1547        {1548            candidates = vec![candidate];1549        }15501551        let mut out = Vec::with_capacity(candidates.len());1552        for (res, def_id) in candidates {1553            let fragment = match (&key.extra_fragment, def_id) {1554                (Some(_), Some(def_id)) => {1555                    report_anchor_conflict(self.cx, diag, def_id);1556                    return None;1557                }1558                (Some(u_frag), None) => Some(UrlFragment::UserWritten(u_frag.clone())),1559                (None, Some(def_id)) => Some(UrlFragment::Item(def_id)),1560                (None, None) => None,1561            };1562            out.push((res, fragment));1563        }1564        if let [r] = out.as_slice() {1565            self.visited_links.insert(key, Some(r.clone()));1566        } else if cache_errors {1567            self.visited_links.insert(key, None);1568        }1569        Some(out)1570    }15711572    /// After parsing the disambiguator, resolve the main part of the link.1573    fn resolve_with_disambiguator(1574        &mut self,1575        key: &ResolutionInfo,1576        diag: DiagnosticInfo<'_>,1577    ) -> Vec<(Res, Option<DefId>)> {1578        let disambiguator = key.dis;1579        let path_str = &key.path_str;1580        let item_id = key.item_id;1581        let module_id = key.module_id;15821583        match disambiguator.map(Disambiguator::ns) {1584            Some(expected_ns) => {1585                match self.resolve(path_str, expected_ns, disambiguator, item_id, module_id) {1586                    Ok(candidates) => candidates,1587                    Err(err) => {1588                        // We only looked in one namespace. Try to give a better error if possible.1589                        // FIXME: really it should be `resolution_failure` that does this, not `resolve_with_disambiguator`.1590                        // See https://github.com/rust-lang/rust/pull/76955#discussion_r493953382 for a good approach.1591                        let mut err = ResolutionFailure::NotResolved(err);1592                        for other_ns in [TypeNS, ValueNS, MacroNS] {1593                            if other_ns != expected_ns1594                                && let Ok(&[res, ..]) = self1595                                    .resolve(path_str, other_ns, None, item_id, module_id)1596                                    .as_deref()1597                            {1598                                err = ResolutionFailure::WrongNamespace {1599                                    res: full_res(self.cx.tcx, res),1600                                    expected_ns,1601                                };1602                                break;1603                            }1604                        }1605                        resolution_failure(self, diag, path_str, disambiguator, smallvec![err]);1606                        vec![]1607                    }1608                }1609            }1610            None => {1611                // Try everything!1612                let candidate = |ns| {1613                    self.resolve(path_str, ns, None, item_id, module_id)1614                        .map_err(ResolutionFailure::NotResolved)1615                };16161617                let candidates = PerNS {1618                    macro_ns: candidate(MacroNS),1619                    type_ns: candidate(TypeNS),1620                    value_ns: candidate(ValueNS).and_then(|v_res| {1621                        for (res, _) in v_res.iter() {1622                            // Constructors are picked up in the type namespace.1623                            if let Res::Def(DefKind::Ctor(..), _) = res {1624                                return Err(ResolutionFailure::WrongNamespace {1625                                    res: *res,1626                                    expected_ns: TypeNS,1627                                });1628                            }1629                        }1630                        Ok(v_res)1631                    }),1632                };16331634                let len = candidates1635                    .iter()1636                    .fold(0, |acc, res| if let Ok(res) = res { acc + res.len() } else { acc });16371638                if len == 0 {1639                    resolution_failure(1640                        self,1641                        diag,1642                        path_str,1643                        disambiguator,1644                        candidates.into_iter().filter_map(|res| res.err()).collect(),1645                    );1646                    vec![]1647                } else if len == 1 {1648                    candidates.into_iter().filter_map(|res| res.ok()).flatten().collect::<Vec<_>>()1649                } else {1650                    let has_derive_trait_collision = is_derive_trait_collision(&candidates);1651                    if len == 2 && has_derive_trait_collision {1652                        candidates.type_ns.unwrap()1653                    } else {1654                        // If we're reporting an ambiguity, don't mention the namespaces that failed1655                        let mut candidates = candidates.map(|candidate| candidate.ok());1656                        // If there a collision between a trait and a derive, we ignore the derive.1657                        if has_derive_trait_collision {1658                            candidates.macro_ns = None;1659                        }1660                        candidates.into_iter().flatten().flatten().collect::<Vec<_>>()1661                    }1662                }1663            }1664        }1665    }1666}16671668/// Get the section of a link between the backticks,1669/// or the whole link if there aren't any backticks.1670///1671/// For example:1672///1673/// ```text1674/// [`Foo`]1675///   ^^^1676/// ```1677///1678/// This function does nothing if `ori_link.range` is a `MarkdownLinkRange::WholeLink`.1679fn range_between_backticks(ori_link_range: &MarkdownLinkRange, dox: &str) -> MarkdownLinkRange {1680    let range = match ori_link_range {1681        mdlr @ MarkdownLinkRange::WholeLink(_) => return mdlr.clone(),1682        MarkdownLinkRange::Destination(inner) => inner.clone(),1683    };1684    let ori_link_text = &dox[range.clone()];1685    let after_first_backtick_group = ori_link_text.bytes().position(|b| b != b'`').unwrap_or(0);1686    let before_second_backtick_group = ori_link_text1687        .bytes()1688        .skip(after_first_backtick_group)1689        .position(|b| b == b'`')1690        .unwrap_or(ori_link_text.len());1691    MarkdownLinkRange::Destination(1692        (range.start + after_first_backtick_group)..(range.start + before_second_backtick_group),1693    )1694}16951696/// Returns true if we should ignore `link` due to it being unlikely1697/// that it is an intra-doc link. `link` should still have disambiguators1698/// if there were any.1699///1700/// The difference between this and [`should_ignore_link()`] is that this1701/// check should only be used on links that still have disambiguators.1702fn should_ignore_link_with_disambiguators(link: &str) -> bool {1703    link.contains(|ch: char| !(ch.is_alphanumeric() || ":_<>, !*&;@()".contains(ch)))1704}17051706/// Returns true if we should ignore `path_str` due to it being unlikely1707/// that it is an intra-doc link.1708fn should_ignore_link(path_str: &str) -> bool {1709    path_str.contains(|ch: char| !(ch.is_alphanumeric() || ":_<>, !*&;".contains(ch)))1710}17111712#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]1713/// Disambiguators for a link.1714enum Disambiguator {1715    /// `prim@`1716    ///1717    /// This is buggy, see <https://github.com/rust-lang/rust/pull/77875#discussion_r503583103>1718    Primitive,1719    /// `struct@` or `f()`1720    Kind(DefKind),1721    /// `type@`1722    Namespace(Namespace),1723}17241725impl Disambiguator {1726    /// Given a link, parse and return `(disambiguator, path_str, link_text)`.1727    ///1728    /// This returns `Ok(Some(...))` if a disambiguator was found,1729    /// `Ok(None)` if no disambiguator was found, or `Err(...)`1730    /// if there was a problem with the disambiguator.1731    fn from_str(link: &str) -> Result<Option<(Self, &str, &str)>, (String, Range<usize>)> {1732        use Disambiguator::{Kind, Namespace as NS, Primitive};17331734        let suffixes = [1735            // If you update this list, please also update the relevant rustdoc book section!1736            ("!()", DefKind::Macro(MacroKinds::BANG)),1737            ("!{}", DefKind::Macro(MacroKinds::BANG)),1738            ("![]", DefKind::Macro(MacroKinds::BANG)),1739            ("()", DefKind::Fn),1740            ("!", DefKind::Macro(MacroKinds::BANG)),1741        ];17421743        if let Some(idx) = link.find('@') {1744            let (prefix, rest) = link.split_at(idx);1745            let d = match prefix {1746                // If you update this list, please also update the relevant rustdoc book section!1747                "struct" => Kind(DefKind::Struct),1748                "enum" => Kind(DefKind::Enum),1749                "trait" => Kind(DefKind::Trait),1750                "union" => Kind(DefKind::Union),1751                "module" | "mod" => Kind(DefKind::Mod),1752                "const" | "constant" => Kind(DefKind::Const { is_type_const: false }),1753                "static" => Kind(DefKind::Static {1754                    mutability: Mutability::Not,1755                    nested: false,1756                    safety: Safety::Safe,1757                }),1758                "function" | "fn" | "method" => Kind(DefKind::Fn),1759                "derive" => Kind(DefKind::Macro(MacroKinds::DERIVE)),1760                "field" => Kind(DefKind::Field),1761                "variant" => Kind(DefKind::Variant),1762                "type" => NS(Namespace::TypeNS),1763                "value" => NS(Namespace::ValueNS),1764                "macro" => NS(Namespace::MacroNS),1765                "prim" | "primitive" => Primitive,1766                "tyalias" | "typealias" => Kind(DefKind::TyAlias),1767                _ => return Err((format!("unknown disambiguator `{prefix}`"), 0..idx)),1768            };17691770            for (suffix, kind) in suffixes {1771                if let Some(path_str) = rest.strip_suffix(suffix) {1772                    if d.ns() != Kind(kind).ns() {1773                        return Err((1774                            format!("unmatched disambiguator `{prefix}` and suffix `{suffix}`"),1775                            0..idx,1776                        ));1777                    } else if path_str.len() > 1 {1778                        // path_str != "@"1779                        return Ok(Some((d, &path_str[1..], &rest[1..])));1780                    }1781                }1782            }17831784            Ok(Some((d, &rest[1..], &rest[1..])))1785        } else {1786            for (suffix, kind) in suffixes {1787                // Avoid turning `!` or `()` into an empty string1788                if let Some(path_str) = link.strip_suffix(suffix)1789                    && !path_str.is_empty()1790                {1791                    return Ok(Some((Kind(kind), path_str, link)));1792                }1793            }1794            Ok(None)1795        }1796    }17971798    fn ns(self) -> Namespace {1799        match self {1800            Self::Namespace(n) => n,1801            // for purposes of link resolution, fields are in the value namespace.1802            Self::Kind(DefKind::Field) => ValueNS,1803            Self::Kind(k) => {1804                k.ns().expect("only DefKinds with a valid namespace can be disambiguators")1805            }1806            Self::Primitive => TypeNS,1807        }1808    }18091810    fn article(self) -> &'static str {1811        match self {1812            Self::Namespace(_) => panic!("article() doesn't make sense for namespaces"),1813            Self::Kind(k) => k.article(),1814            Self::Primitive => "a",1815        }1816    }18171818    fn descr(self) -> &'static str {1819        match self {1820            Self::Namespace(n) => n.descr(),1821            // HACK(jynelson): the source of `DefKind::descr` only uses the DefId for1822            // printing "module" vs "crate" so using the wrong ID is not a huge problem1823            Self::Kind(k) => k.descr(CRATE_DEF_ID.to_def_id()),1824            Self::Primitive => "builtin type",1825        }1826    }1827}18281829/// A suggestion to show in a diagnostic.1830enum Suggestion {1831    /// `struct@`1832    Prefix(&'static str),1833    /// `f()`1834    Function,1835    /// `m!`1836    Macro,1837}18381839impl Suggestion {1840    fn descr(&self) -> Cow<'static, str> {1841        match self {1842            Self::Prefix(x) => format!("prefix with `{x}@`").into(),1843            Self::Function => "add parentheses".into(),1844            Self::Macro => "add an exclamation mark".into(),1845        }1846    }18471848    fn as_help(&self, path_str: &str) -> String {1849        // FIXME: if this is an implied shortcut link, it's bad style to suggest `@`1850        match self {1851            Self::Prefix(prefix) => format!("{prefix}@{path_str}"),1852            Self::Function => format!("{path_str}()"),1853            Self::Macro => format!("{path_str}!"),1854        }1855    }18561857    fn as_help_span(1858        &self,1859        ori_link: &str,1860        sp: rustc_span::Span,1861    ) -> Vec<(rustc_span::Span, String)> {1862        let inner_sp = match ori_link.find('(') {1863            Some(index) if index != 0 && ori_link.as_bytes()[index - 1] == b'\\' => {1864                sp.with_hi(sp.lo() + BytePos((index - 1) as _))1865            }1866            Some(index) => sp.with_hi(sp.lo() + BytePos(index as _)),1867            None => sp,1868        };1869        let inner_sp = match ori_link.find('!') {1870            Some(index) if index != 0 && ori_link.as_bytes()[index - 1] == b'\\' => {1871                sp.with_hi(sp.lo() + BytePos((index - 1) as _))1872            }1873            Some(index) => inner_sp.with_hi(inner_sp.lo() + BytePos(index as _)),1874            None => inner_sp,1875        };1876        let inner_sp = match ori_link.find('@') {1877            Some(index) if index != 0 && ori_link.as_bytes()[index - 1] == b'\\' => {1878                sp.with_hi(sp.lo() + BytePos((index - 1) as _))1879            }1880            Some(index) => inner_sp.with_lo(inner_sp.lo() + BytePos(index as u32 + 1)),1881            None => inner_sp,1882        };1883        match self {1884            Self::Prefix(prefix) => {1885                // FIXME: if this is an implied shortcut link, it's bad style to suggest `@`1886                let mut sugg = vec![(sp.with_hi(inner_sp.lo()), format!("{prefix}@"))];1887                if sp.hi() != inner_sp.hi() {1888                    sugg.push((inner_sp.shrink_to_hi().with_hi(sp.hi()), String::new()));1889                }1890                sugg1891            }1892            Self::Function => {1893                let mut sugg = vec![(inner_sp.shrink_to_hi().with_hi(sp.hi()), "()".to_string())];1894                if sp.lo() != inner_sp.lo() {1895                    sugg.push((inner_sp.shrink_to_lo().with_lo(sp.lo()), String::new()));1896                }1897                sugg1898            }1899            Self::Macro => {1900                let mut sugg = vec![(inner_sp.shrink_to_hi(), "!".to_string())];1901                if sp.lo() != inner_sp.lo() {1902                    sugg.push((inner_sp.shrink_to_lo().with_lo(sp.lo()), String::new()));1903                }1904                sugg1905            }1906        }1907    }1908}19091910/// Reports a diagnostic for an intra-doc link.1911///1912/// If no link range is provided, or the source span of the link cannot be determined, the span of1913/// the entire documentation block is used for the lint. If a range is provided but the span1914/// calculation fails, a note is added to the diagnostic pointing to the link in the markdown.1915///1916/// The `decorate` callback is invoked in all cases to allow further customization of the1917/// diagnostic before emission. If the span of the link was able to be determined, the second1918/// parameter of the callback will contain it, and the primary span of the diagnostic will be set1919/// to it.1920fn report_diagnostic(1921    tcx: TyCtxt<'_>,1922    lint: &'static Lint,1923    msg: impl Into<DiagMessage> + Display,1924    DiagnosticInfo { item, ori_link: _, dox, link_range }: &DiagnosticInfo<'_>,1925    decorate: impl FnOnce(&mut Diag<'_, ()>, Option<rustc_span::Span>, MarkdownLinkRange),1926) {1927    let Some(hir_id) = DocContext::as_local_hir_id(tcx, item.item_id) else {1928        // If non-local, no need to check anything.1929        info!("ignoring warning from parent crate: {msg}");1930        return;1931    };19321933    let sp = item.attr_span(tcx);19341935    tcx.emit_node_span_lint(1936        lint,1937        hir_id,1938        sp,1939        rustc_errors::DiagDecorator(|lint| {1940            lint.primary_message(msg);19411942            let (span, link_range) = match link_range {1943                MarkdownLinkRange::Destination(md_range) => {1944                    let mut md_range = md_range.clone();1945                    let sp = source_span_for_markdown_range(1946                        tcx,1947                        dox,1948                        &md_range,1949                        &item.attrs.doc_strings,1950                    )1951                    .map(|(mut sp, _)| {1952                        while dox.as_bytes().get(md_range.start) == Some(&b' ')1953                            || dox.as_bytes().get(md_range.start) == Some(&b'`')1954                        {1955                            md_range.start += 1;1956                            sp = sp.with_lo(sp.lo() + BytePos(1));1957                        }1958                        while dox.as_bytes().get(md_range.end - 1) == Some(&b' ')1959                            || dox.as_bytes().get(md_range.end - 1) == Some(&b'`')1960                        {1961                            md_range.end -= 1;1962                            sp = sp.with_hi(sp.hi() - BytePos(1));1963                        }1964                        sp1965                    });1966                    (sp, MarkdownLinkRange::Destination(md_range))1967                }1968                MarkdownLinkRange::WholeLink(md_range) => (1969                    source_span_for_markdown_range(tcx, dox, md_range, &item.attrs.doc_strings)1970                        .map(|(sp, _)| sp),1971                    link_range.clone(),1972                ),1973            };19741975            if let Some(sp) = span {1976                lint.span(sp);1977            } else {1978                // blah blah blah\nblah\nblah [blah] blah blah\nblah blah1979                //                       ^     ~~~~1980                //                       |     link_range1981                //                       last_new_line_offset1982                let md_range = link_range.inner_range().clone();1983                let last_new_line_offset = dox[..md_range.start].rfind('\n').map_or(0, |n| n + 1);1984                let line = dox[last_new_line_offset..].lines().next().unwrap_or("");19851986                // Print the line containing the `md_range` and manually mark it with '^'s.1987                lint.note(format!(1988                    "the link appears in this line:\n\n{line}\n\1989                     {indicator: <before$}{indicator:^<found$}",1990                    indicator = "",1991                    before = md_range.start - last_new_line_offset,1992                    found = md_range.len(),1993                ));1994            }19951996            decorate(lint, span, link_range);1997        }),1998    );1999}

Findings

✓ No findings reported for this file.

Get this view in your editor

Same data, no extra tab — call code_get_file + code_get_findings over MCP from Claude/Cursor/Copilot.