1 //! This module implements [RFC 1946]: Intra-rustdoc-links
3 //! [RFC 1946]: https://github.com/rust-lang/rfcs/blob/master/text/1946-intra-rustdoc-links.md
5 use pulldown_cmark
::LinkType
;
6 use rustc_ast
::util
::comments
::may_have_doc_links
;
7 use rustc_data_structures
::{
8 fx
::{FxHashMap, FxHashSet}
,
11 use rustc_errors
::{Applicability, Diagnostic, DiagnosticMessage}
;
12 use rustc_hir
::def
::Namespace
::*;
13 use rustc_hir
::def
::{DefKind, Namespace, PerNS}
;
14 use rustc_hir
::def_id
::{DefId, CRATE_DEF_ID}
;
15 use rustc_hir
::Mutability
;
16 use rustc_middle
::ty
::{Ty, TyCtxt}
;
17 use rustc_middle
::{bug, span_bug, ty}
;
18 use rustc_resolve
::rustdoc
::{has_primitive_or_keyword_docs, prepare_to_doc_link_resolution}
;
19 use rustc_resolve
::rustdoc
::{
20 source_span_for_markdown_range
, strip_generics_from_path
, MalformedGenerics
,
22 use rustc_session
::lint
::Lint
;
23 use rustc_span
::hygiene
::MacroKind
;
24 use rustc_span
::symbol
::{sym, Ident, Symbol}
;
25 use rustc_span
::BytePos
;
26 use smallvec
::{smallvec, SmallVec}
;
29 use std
::fmt
::Display
;
33 use crate::clean
::{self, utils::find_nearest_parent_module}
;
34 use crate::clean
::{Crate, Item, ItemLink, PrimitiveType}
;
35 use crate::core
::DocContext
;
36 use crate::html
::markdown
::{markdown_links, MarkdownLink, MarkdownLinkRange}
;
37 use crate::lint
::{BROKEN_INTRA_DOC_LINKS, PRIVATE_INTRA_DOC_LINKS}
;
38 use crate::passes
::Pass
;
39 use crate::visit
::DocVisitor
;
41 pub(crate) const COLLECT_INTRA_DOC_LINKS
: Pass
= Pass
{
42 name
: "collect-intra-doc-links",
43 run
: collect_intra_doc_links
,
44 description
: "resolves intra-doc links",
47 fn collect_intra_doc_links(krate
: Crate
, cx
: &mut DocContext
<'_
>) -> Crate
{
48 let mut collector
= LinkCollector { cx, visited_links: FxHashMap::default() }
;
49 collector
.visit_crate(&krate
);
53 fn filter_assoc_items_by_name_and_namespace
<'a
>(
55 assoc_items_of
: DefId
,
58 ) -> impl Iterator
<Item
= &ty
::AssocItem
> + 'a
{
59 tcx
.associated_items(assoc_items_of
).filter_by_name_unhygienic(ident
.name
).filter(move |item
| {
60 item
.kind
.namespace() == ns
&& tcx
.hygienic_eq(ident
, item
.ident(tcx
), assoc_items_of
)
64 #[derive(Copy, Clone, Debug, Hash, PartialEq)]
67 Primitive(PrimitiveType
),
70 type ResolveRes
= rustc_hir
::def
::Res
<rustc_ast
::NodeId
>;
73 fn descr(self) -> &'
static str {
75 Res
::Def(kind
, id
) => ResolveRes
::Def(kind
, id
).descr(),
76 Res
::Primitive(_
) => "primitive type",
80 fn article(self) -> &'
static str {
82 Res
::Def(kind
, id
) => ResolveRes
::Def(kind
, id
).article(),
83 Res
::Primitive(_
) => "a",
87 fn name(self, tcx
: TyCtxt
<'_
>) -> Symbol
{
89 Res
::Def(_
, id
) => tcx
.item_name(id
),
90 Res
::Primitive(prim
) => prim
.as_sym(),
94 fn def_id(self, tcx
: TyCtxt
<'_
>) -> Option
<DefId
> {
96 Res
::Def(_
, id
) => Some(id
),
97 Res
::Primitive(prim
) => PrimitiveType
::primitive_locations(tcx
).get(&prim
).copied(),
101 fn from_def_id(tcx
: TyCtxt
<'_
>, def_id
: DefId
) -> Res
{
102 Res
::Def(tcx
.def_kind(def_id
), def_id
)
105 /// Used for error reporting.
106 fn disambiguator_suggestion(self) -> Suggestion
{
107 let kind
= match self {
108 Res
::Primitive(_
) => return Suggestion
::Prefix("prim"),
109 Res
::Def(kind
, _
) => kind
,
111 if kind
== DefKind
::Macro(MacroKind
::Bang
) {
112 return Suggestion
::Macro
;
113 } else if kind
== DefKind
::Fn
|| kind
== DefKind
::AssocFn
{
114 return Suggestion
::Function
;
115 } else if kind
== DefKind
::Field
{
116 return Suggestion
::RemoveDisambiguator
;
119 let prefix
= match kind
{
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
=> {
128 DefKind
::Static(_
) => "static",
129 DefKind
::Macro(MacroKind
::Derive
) => "derive",
130 // Now handle things that don't have a specific disambiguator
133 .expect("tried to calculate a disambiguator for a def without a namespace?")
135 Namespace
::TypeNS
=> "type",
136 Namespace
::ValueNS
=> "value",
137 Namespace
::MacroNS
=> "macro",
141 Suggestion
::Prefix(prefix
)
145 impl TryFrom
<ResolveRes
> for Res
{
148 fn try_from(res
: ResolveRes
) -> Result
<Self, ()> {
149 use rustc_hir
::def
::Res
::*;
151 Def(kind
, id
) => Ok(Res
::Def(kind
, id
)),
152 PrimTy(prim
) => Ok(Res
::Primitive(PrimitiveType
::from_hir(prim
))),
154 ToolMod
| NonMacroAttr(..) | Err
=> Result
::Err(()),
155 other
=> bug
!("unrecognized res {other:?}"),
160 /// The link failed to resolve. [`resolution_failure`] should look to see if there's
161 /// a more helpful error that can be given.
163 struct UnresolvedPath
<'a
> {
164 /// Item on which the link is resolved, used for resolving `Self`.
166 /// The scope the link was resolved in.
168 /// If part of the link resolved, this has the `Res`.
170 /// In `[std::io::Error::x]`, `std::io::Error` would be a partial resolution.
171 partial_res
: Option
<Res
>,
172 /// The remaining unresolved path segments.
174 /// In `[std::io::Error::x]`, `x` would be unresolved.
175 unresolved
: Cow
<'a
, str>,
179 enum ResolutionFailure
<'a
> {
180 /// This resolved, but with the wrong namespace.
182 /// What the link resolved to.
184 /// The expected namespace for the resolution, determined from the link's disambiguator.
186 /// E.g., for `[fn@Result]` this is [`Namespace::ValueNS`],
187 /// even though `Result`'s actual namespace is [`Namespace::TypeNS`].
188 expected_ns
: Namespace
,
190 NotResolved(UnresolvedPath
<'a
>),
193 #[derive(Clone, Debug, Hash, PartialEq, Eq)]
194 pub(crate) enum UrlFragment
{
196 /// A part of a page that isn't a rust item.
198 /// Eg: `[Vector Examples](std::vec::Vec#examples)`
203 /// Render the fragment, including the leading `#`.
204 pub(crate) fn render(&self, s
: &mut String
, tcx
: TyCtxt
<'_
>) {
207 &UrlFragment
::Item(def_id
) => {
208 let kind
= match tcx
.def_kind(def_id
) {
209 DefKind
::AssocFn
=> {
210 if tcx
.defaultness(def_id
).has_value() {
216 DefKind
::AssocConst
=> "associatedconstant.",
217 DefKind
::AssocTy
=> "associatedtype.",
218 DefKind
::Variant
=> "variant.",
220 let parent_id
= tcx
.parent(def_id
);
221 if tcx
.def_kind(parent_id
) == DefKind
::Variant
{
222 s
.push_str("variant.");
223 s
.push_str(tcx
.item_name(parent_id
).as_str());
229 kind
=> bug
!("unexpected associated item kind: {kind:?}"),
232 s
.push_str(tcx
.item_name(def_id
).as_str());
234 UrlFragment
::UserWritten(raw
) => s
.push_str(&raw
),
239 #[derive(Clone, Debug, Hash, PartialEq, Eq)]
240 struct ResolutionInfo
{
243 dis
: Option
<Disambiguator
>,
245 extra_fragment
: Option
<String
>,
249 struct DiagnosticInfo
<'a
> {
253 link_range
: MarkdownLinkRange
,
256 struct LinkCollector
<'a
, 'tcx
> {
257 cx
: &'a
mut DocContext
<'tcx
>,
258 /// Cache the resolved links so we can avoid resolving (and emitting errors for) the same link.
259 /// The link will be `None` if it could not be resolved (i.e. the error was cached).
260 visited_links
: FxHashMap
<ResolutionInfo
, Option
<(Res
, Option
<UrlFragment
>)>>,
263 impl<'a
, 'tcx
> LinkCollector
<'a
, 'tcx
> {
264 /// Given a full link, parse it as an [enum struct variant].
266 /// In particular, this will return an error whenever there aren't three
267 /// full path segments left in the link.
269 /// [enum struct variant]: rustc_hir::VariantData::Struct
270 fn variant_field
<'path
>(
272 path_str
: &'path
str,
275 ) -> Result
<(Res
, DefId
), UnresolvedPath
<'path
>> {
276 let tcx
= self.cx
.tcx
;
277 let no_res
= || UnresolvedPath
{
281 unresolved
: path_str
.into(),
284 debug
!("looking for enum variant {path_str}");
285 let mut split
= path_str
.rsplitn(3, "::");
286 let variant_field_name
= split
288 .map(|f
| Symbol
::intern(f
))
289 .expect("fold_item should ensure link is non-empty");
291 // we're not sure this is a variant at all, so use the full string
292 // If there's no second component, the link looks like `[path]`.
293 // So there's no partial res and we should say the whole link failed to resolve.
294 split
.next().map(|f
| Symbol
::intern(f
)).ok_or_else(no_res
)?
;
297 // If there's no third component, we saw `[a::b]` before and it failed to resolve.
298 // So there's no partial res.
299 .ok_or_else(no_res
)?
;
300 let ty_res
= self.resolve_path(&path
, TypeNS
, item_id
, module_id
).ok_or_else(no_res
)?
;
303 Res
::Def(DefKind
::Enum
, did
) => match tcx
.type_of(did
).instantiate_identity().kind() {
304 ty
::Adt(def
, _
) if def
.is_enum() => {
305 if let Some(variant
) = def
.variants().iter().find(|v
| v
.name
== variant_name
)
306 && let Some(field
) = variant
.fields
.iter().find(|f
| f
.name
== variant_field_name
) {
307 Ok((ty_res
, field
.did
))
312 partial_res
: Some(Res
::Def(DefKind
::Enum
, def
.did())),
313 unresolved
: variant_field_name
.to_string().into(),
319 _
=> Err(UnresolvedPath
{
322 partial_res
: Some(ty_res
),
323 unresolved
: variant_name
.to_string().into(),
328 /// Given a primitive type, try to resolve an associated item.
329 fn resolve_primitive_associated_item(
331 prim_ty
: PrimitiveType
,
334 ) -> Vec
<(Res
, DefId
)> {
335 let tcx
= self.cx
.tcx
;
340 filter_assoc_items_by_name_and_namespace(
343 Ident
::with_dummy_span(item_name
),
346 .map(|item
| (Res
::Primitive(prim_ty
), item
.def_id
))
351 fn resolve_self_ty(&self, path_str
: &str, ns
: Namespace
, item_id
: DefId
) -> Option
<Res
> {
352 if ns
!= TypeNS
|| path_str
!= "Self" {
356 let tcx
= self.cx
.tcx
;
357 let self_id
= match tcx
.def_kind(item_id
) {
358 def_kind @
(DefKind
::AssocFn
359 | DefKind
::AssocConst
362 | DefKind
::Field
) => {
363 let parent_def_id
= tcx
.parent(item_id
);
364 if def_kind
== DefKind
::Field
&& tcx
.def_kind(parent_def_id
) == DefKind
::Variant
{
365 tcx
.parent(parent_def_id
)
373 match tcx
.def_kind(self_id
) {
374 DefKind
::Impl { .. }
=> self.def_id_to_res(self_id
),
375 DefKind
::Use
=> None
,
376 def_kind
=> Some(Res
::Def(def_kind
, self_id
)),
380 /// Convenience wrapper around `doc_link_resolutions`.
382 /// This also handles resolving `true` and `false` as booleans.
383 /// NOTE: `doc_link_resolutions` knows only about paths, not about types.
384 /// Associated items will never be resolved by this function.
392 if let res @
Some(..) = self.resolve_self_ty(path_str
, ns
, item_id
) {
396 // Resolver doesn't know about true, false, and types that aren't paths (e.g. `()`).
400 .doc_link_resolutions(module_id
)
401 .get(&(Symbol
::intern(path_str
), ns
))
403 // NOTE: do not remove this panic! Missing links should be recorded as `Res::Err`; if
404 // `doc_link_resolutions` is missing a `path_str`, that means that there are valid links
405 // that are being missed. To fix the ICE, change
406 // `rustc_resolve::rustdoc::attrs_to_preprocessed_links` to cache the link.
409 self.cx
.tcx
.def_span(item_id
),
410 "no resolution for {path_str:?} {ns:?} {module_id:?}",
413 .and_then(|res
| res
.try_into().ok())
414 .or_else(|| resolve_primitive(path_str
, ns
));
415 debug
!("{path_str} resolved to {result:?} in namespace {ns:?}");
419 /// Resolves a string as a path within a particular namespace. Returns an
420 /// optional URL fragment in the case of variants and methods.
423 path_str
: &'path
str,
427 ) -> Result
<Vec
<(Res
, Option
<DefId
>)>, UnresolvedPath
<'path
>> {
428 if let Some(res
) = self.resolve_path(path_str
, ns
, item_id
, module_id
) {
429 return Ok(match res
{
431 DefKind
::AssocFn
| DefKind
::AssocConst
| DefKind
::AssocTy
| DefKind
::Variant
,
434 vec
![(Res
::from_def_id(self.cx
.tcx
, self.cx
.tcx
.parent(def_id
)), Some(def_id
))]
436 _
=> vec
![(res
, None
)],
438 } else if ns
== MacroNS
{
439 return Err(UnresolvedPath
{
443 unresolved
: path_str
.into(),
447 // Try looking for methods and associated items.
448 let mut split
= path_str
.rsplitn(2, "::");
449 // NB: `split`'s first element is always defined, even if the delimiter was not present.
450 // NB: `item_str` could be empty when resolving in the root namespace (e.g. `::std`).
451 let item_str
= split
.next().unwrap();
452 let item_name
= Symbol
::intern(item_str
);
453 let path_root
= split
455 // If there's no `::`, it's not an associated item.
456 // So we can be sure that `rustc_resolve` was accurate when it said it wasn't resolved.
458 debug
!("found no `::`, assuming {item_name} was correctly not in scope");
463 unresolved
: item_str
.into(),
467 // FIXME(#83862): this arbitrarily gives precedence to primitives over modules to support
468 // links to primitives when `#[rustc_doc_primitive]` is present. It should give an ambiguity
469 // error instead and special case *only* modules with `#[rustc_doc_primitive]`, not all
471 match resolve_primitive(&path_root
, TypeNS
)
472 .or_else(|| self.resolve_path(&path_root
, TypeNS
, item_id
, module_id
))
474 let candidates
= self
475 .resolve_associated_item(ty_res
, item_name
, ns
, module_id
)
477 .map(|(res
, def_id
)| (res
, Some(def_id
)))
478 .collect
::<Vec
<_
>>();
479 if !candidates
.is_empty() { Some(candidates) }
else { None }
483 if ns
== Namespace
::ValueNS
{
484 self.variant_field(path_str
, item_id
, module_id
)
485 .map(|(res
, def_id
)| vec
![(res
, Some(def_id
))])
491 unresolved
: path_root
.into(),
498 /// Convert a DefId to a Res, where possible.
500 /// This is used for resolving type aliases.
501 fn def_id_to_res(&self, ty_id
: DefId
) -> Option
<Res
> {
502 use PrimitiveType
::*;
503 Some(match *self.cx
.tcx
.type_of(ty_id
).instantiate_identity().kind() {
504 ty
::Bool
=> Res
::Primitive(Bool
),
505 ty
::Char
=> Res
::Primitive(Char
),
506 ty
::Int(ity
) => Res
::Primitive(ity
.into()),
507 ty
::Uint(uty
) => Res
::Primitive(uty
.into()),
508 ty
::Float(fty
) => Res
::Primitive(fty
.into()),
509 ty
::Str
=> Res
::Primitive(Str
),
510 ty
::Tuple(tys
) if tys
.is_empty() => Res
::Primitive(Unit
),
511 ty
::Tuple(_
) => Res
::Primitive(Tuple
),
512 ty
::Array(..) => Res
::Primitive(Array
),
513 ty
::Slice(_
) => Res
::Primitive(Slice
),
514 ty
::RawPtr(_
) => Res
::Primitive(RawPointer
),
515 ty
::Ref(..) => Res
::Primitive(Reference
),
516 ty
::FnDef(..) => panic
!("type alias to a function definition"),
517 ty
::FnPtr(_
) => Res
::Primitive(Fn
),
518 ty
::Never
=> Res
::Primitive(Never
),
519 ty
::Adt(ty
::AdtDef(Interned(&ty
::AdtDefData { did, .. }
, _
)), _
) | ty
::Foreign(did
) => {
520 Res
::from_def_id(self.cx
.tcx
, did
)
525 | ty
::CoroutineWitness(..)
531 | ty
::Error(_
) => return None
,
535 /// Convert a PrimitiveType to a Ty, where possible.
537 /// This is used for resolving trait impls for primitives
538 fn primitive_type_to_ty(&mut self, prim
: PrimitiveType
) -> Option
<Ty
<'tcx
>> {
539 use PrimitiveType
::*;
540 let tcx
= self.cx
.tcx
;
542 // FIXME: Only simple types are supported here, see if we can support
543 // other types such as Tuple, Array, Slice, etc.
544 // See https://github.com/rust-lang/rust/issues/90703#issuecomment-1004263455
546 Bool
=> tcx
.types
.bool
,
547 Str
=> tcx
.types
.str_
,
548 Char
=> tcx
.types
.char,
549 Never
=> tcx
.types
.never
,
551 I16
=> tcx
.types
.i16,
552 I32
=> tcx
.types
.i32,
553 I64
=> tcx
.types
.i64,
554 I128
=> tcx
.types
.i128
,
555 Isize
=> tcx
.types
.isize,
556 F32
=> tcx
.types
.f32,
557 F64
=> tcx
.types
.f64,
559 U16
=> tcx
.types
.u16,
560 U32
=> tcx
.types
.u32,
561 U64
=> tcx
.types
.u64,
562 U128
=> tcx
.types
.u128
,
563 Usize
=> tcx
.types
.usize,
568 /// Resolve an associated item, returning its containing page's `Res`
569 /// and the fragment targeting the associated item on its page.
570 fn resolve_associated_item(
576 ) -> Vec
<(Res
, DefId
)> {
577 let tcx
= self.cx
.tcx
;
580 Res
::Primitive(prim
) => {
581 let items
= self.resolve_primitive_associated_item(prim
, ns
, item_name
);
582 if !items
.is_empty() {
584 // Inherent associated items take precedence over items that come from trait impls.
586 self.primitive_type_to_ty(prim
)
588 resolve_associated_trait_item(ty
, module_id
, item_name
, ns
, self.cx
)
590 .map(|item
| (root_res
, item
.def_id
))
593 .unwrap_or(Vec
::new())
596 Res
::Def(DefKind
::TyAlias
, did
) => {
597 // Resolve the link on the type the alias points to.
598 // FIXME: if the associated item is defined directly on the type alias,
599 // it will show up on its documentation page, we should link there instead.
600 let Some(res
) = self.def_id_to_res(did
) else { return Vec::new() }
;
601 self.resolve_associated_item(res
, item_name
, ns
, module_id
)
604 def_kind @
(DefKind
::Struct
| DefKind
::Union
| DefKind
::Enum
| DefKind
::ForeignTy
),
607 debug
!("looking for associated item named {item_name} for item {did:?}");
608 // Checks if item_name is a variant of the `SomeItem` enum
609 if ns
== TypeNS
&& def_kind
== DefKind
::Enum
{
610 match tcx
.type_of(did
).instantiate_identity().kind() {
611 ty
::Adt(adt_def
, _
) => {
612 for variant
in adt_def
.variants() {
613 if variant
.name
== item_name
{
614 return vec
![(root_res
, variant
.def_id
)];
622 // Checks if item_name belongs to `impl SomeItem`
623 let mut assoc_items
: Vec
<_
> = tcx
627 filter_assoc_items_by_name_and_namespace(
630 Ident
::with_dummy_span(item_name
),
634 .map(|item
| (root_res
, item
.def_id
))
637 if assoc_items
.is_empty() {
638 // Check if item_name belongs to `impl SomeTrait for SomeItem`
639 // FIXME(#74563): This gives precedence to `impl SomeItem`:
640 // Although having both would be ambiguous, use impl version for compatibility's sake.
641 // To handle that properly resolve() would have to support
642 // something like [`ambi_fn`](<SomeStruct as SomeTrait>::ambi_fn)
643 assoc_items
= resolve_associated_trait_item(
644 tcx
.type_of(did
).instantiate_identity(),
651 .map(|item
| (root_res
, item
.def_id
))
652 .collect
::<Vec
<_
>>();
655 debug
!("got associated item {assoc_items:?}");
657 if !assoc_items
.is_empty() {
661 if ns
!= Namespace
::ValueNS
{
664 debug
!("looking for fields named {item_name} for {did:?}");
665 // FIXME: this doesn't really belong in `associated_item` (maybe `variant_field` is better?)
666 // NOTE: it's different from variant_field because it only resolves struct fields,
667 // not variant fields (2 path segments, not 3).
669 // We need to handle struct (and union) fields in this code because
670 // syntactically their paths are identical to associated item paths:
671 // `module::Type::field` and `module::Type::Assoc`.
673 // On the other hand, variant fields can't be mistaken for associated
674 // items because they look like this: `module::Type::Variant::field`.
676 // Variants themselves don't need to be handled here, even though
677 // they also look like associated items (`module::Type::Variant`),
678 // because they are real Rust syntax (unlike the intra-doc links
679 // field syntax) and are handled by the compiler's resolver.
680 let def
= match tcx
.type_of(did
).instantiate_identity().kind() {
681 ty
::Adt(def
, _
) if !def
.is_enum() => def
,
682 _
=> return Vec
::new(),
684 def
.non_enum_variant()
687 .filter(|field
| field
.name
== item_name
)
688 .map(|field
| (root_res
, field
.did
))
691 Res
::Def(DefKind
::Trait
, did
) => filter_assoc_items_by_name_and_namespace(
694 Ident
::with_dummy_span(item_name
),
698 let res
= Res
::Def(item
.kind
.as_def_kind(), item
.def_id
);
701 .collect
::<Vec
<_
>>(),
707 fn full_res(tcx
: TyCtxt
<'_
>, (base
, assoc_item
): (Res
, Option
<DefId
>)) -> Res
{
708 assoc_item
.map_or(base
, |def_id
| Res
::from_def_id(tcx
, def_id
))
711 /// Look to see if a resolved item has an associated item named `item_name`.
713 /// Given `[std::io::Error::source]`, where `source` is unresolved, this would
714 /// find `std::error::Error::source` and return
715 /// `<io::Error as error::Error>::source`.
716 fn resolve_associated_trait_item
<'a
>(
721 cx
: &mut DocContext
<'a
>,
722 ) -> Vec
<ty
::AssocItem
> {
723 // FIXME: this should also consider blanket impls (`impl<T> X for T`). Unfortunately
724 // `get_auto_trait_and_blanket_impls` is broken because the caching behavior is wrong. In the
725 // meantime, just don't look for these blanket impls.
727 // Next consider explicit impls: `impl MyTrait for MyType`
728 // Give precedence to inherent impls.
729 let traits
= trait_impls_for(cx
, ty
, module
);
731 debug
!("considering traits {traits:?}");
732 let candidates
= traits
734 .flat_map(|&(impl_
, trait_
)| {
735 filter_assoc_items_by_name_and_namespace(
738 Ident
::with_dummy_span(item_name
),
741 .map(move |trait_assoc
| {
742 trait_assoc_to_impl_assoc_item(tcx
, impl_
, trait_assoc
.def_id
)
743 .unwrap_or(*trait_assoc
)
746 .collect
::<Vec
<_
>>();
747 // FIXME(#74563): warn about ambiguity
748 debug
!("the candidates were {candidates:?}");
752 /// Find the associated item in the impl `impl_id` that corresponds to the
753 /// trait associated item `trait_assoc_id`.
755 /// This function returns `None` if no associated item was found in the impl.
756 /// This can occur when the trait associated item has a default value that is
757 /// not overridden in the impl.
759 /// This is just a wrapper around [`TyCtxt::impl_item_implementor_ids()`] and
760 /// [`TyCtxt::associated_item()`] (with some helpful logging added).
761 #[instrument(level = "debug", skip(tcx), ret)]
762 fn trait_assoc_to_impl_assoc_item
<'tcx
>(
765 trait_assoc_id
: DefId
,
766 ) -> Option
<ty
::AssocItem
> {
767 let trait_to_impl_assoc_map
= tcx
.impl_item_implementor_ids(impl_id
);
768 debug
!(?trait_to_impl_assoc_map
);
769 let impl_assoc_id
= *trait_to_impl_assoc_map
.get(&trait_assoc_id
)?
;
770 debug
!(?impl_assoc_id
);
771 Some(tcx
.associated_item(impl_assoc_id
))
774 /// Given a type, return all trait impls in scope in `module` for that type.
775 /// Returns a set of pairs of `(impl_id, trait_id)`.
777 /// NOTE: this cannot be a query because more traits could be available when more crates are compiled!
778 /// So it is not stable to serialize cross-crate.
779 #[instrument(level = "debug", skip(cx))]
780 fn trait_impls_for
<'a
>(
781 cx
: &mut DocContext
<'a
>,
784 ) -> FxHashSet
<(DefId
, DefId
)> {
786 let mut impls
= FxHashSet
::default();
788 for &trait_
in tcx
.doc_link_traits_in_scope(module
) {
789 tcx
.for_each_relevant_impl(trait_
, ty
, |impl_
| {
790 let trait_ref
= tcx
.impl_trait_ref(impl_
).expect("this is not an inherent impl");
791 // Check if these are the same type.
792 let impl_type
= trait_ref
.skip_binder().self_ty();
794 "comparing type {impl_type} with kind {kind:?} against type {ty:?}",
795 kind
= impl_type
.kind(),
797 // Fast path: if this is a primitive simple `==` will work
798 // NOTE: the `match` is necessary; see #92662.
799 // this allows us to ignore generics because the user input
800 // may not include the generic placeholders
801 // e.g. this allows us to match Foo (user comment) with Foo<T> (actual type)
802 let saw_impl
= impl_type
== ty
803 || match (impl_type
.kind(), ty
.kind()) {
804 (ty
::Adt(impl_def
, _
), ty
::Adt(ty_def
, _
)) => {
805 debug
!("impl def_id: {:?}, ty def_id: {:?}", impl_def
.did(), ty_def
.did());
806 impl_def
.did() == ty_def
.did()
812 impls
.insert((impl_
, trait_
));
820 /// Check for resolve collisions between a trait and its derive.
822 /// These are common and we should just resolve to the trait in that case.
823 fn is_derive_trait_collision
<T
>(ns
: &PerNS
<Result
<Vec
<(Res
, T
)>, ResolutionFailure
<'_
>>>) -> bool
{
824 if let (Ok(type_ns
), Ok(macro_ns
)) = (&ns
.type_ns
, &ns
.macro_ns
) {
825 type_ns
.iter().any(|(res
, _
)| matches
!(res
, Res
::Def(DefKind
::Trait
, _
)))
828 .any(|(res
, _
)| matches
!(res
, Res
::Def(DefKind
::Macro(MacroKind
::Derive
), _
)))
834 impl<'a
, 'tcx
> DocVisitor
for LinkCollector
<'a
, 'tcx
> {
835 fn visit_item(&mut self, item
: &Item
) {
836 self.resolve_links(item
);
837 self.visit_item_recur(item
)
841 enum PreprocessingError
{
842 /// User error: `[std#x#y]` is not valid
844 Disambiguator(MarkdownLinkRange
, String
),
845 MalformedGenerics(MalformedGenerics
, String
),
848 impl PreprocessingError
{
849 fn report(&self, cx
: &DocContext
<'_
>, diag_info
: DiagnosticInfo
<'_
>) {
851 PreprocessingError
::MultipleAnchors
=> report_multiple_anchors(cx
, diag_info
),
852 PreprocessingError
::Disambiguator(range
, msg
) => {
853 disambiguator_error(cx
, diag_info
, range
.clone(), msg
.clone())
855 PreprocessingError
::MalformedGenerics(err
, path_str
) => {
856 report_malformed_generics(cx
, diag_info
, *err
, path_str
)
863 struct PreprocessingInfo
{
865 disambiguator
: Option
<Disambiguator
>,
866 extra_fragment
: Option
<String
>,
870 // Not a typedef to avoid leaking several private structures from this module.
871 pub(crate) struct PreprocessedMarkdownLink(
872 Result
<PreprocessingInfo
, PreprocessingError
>,
877 /// - `None` if the link should be ignored.
878 /// - `Some(Err)` if the link should emit an error
879 /// - `Some(Ok)` if the link is valid
881 /// `link_buffer` is needed for lifetime reasons; it will always be overwritten and the contents ignored.
883 ori_link
: &MarkdownLink
,
885 ) -> Option
<Result
<PreprocessingInfo
, PreprocessingError
>> {
886 // [] is mostly likely not supposed to be a link
887 if ori_link
.link
.is_empty() {
891 // Bail early for real links.
892 if ori_link
.link
.contains('
/'
) {
896 let stripped
= ori_link
.link
.replace('`'
, "");
897 let mut parts
= stripped
.split('
#');
899 let link
= parts
.next().unwrap();
900 let link
= link
.trim();
902 // This is an anchor to an element of the current page, nothing to do in here!
905 let extra_fragment
= parts
.next();
906 if parts
.next().is_some() {
907 // A valid link can't have multiple #'s
908 return Some(Err(PreprocessingError
::MultipleAnchors
));
911 // Parse and strip the disambiguator from the link, if present.
912 let (disambiguator
, path_str
, link_text
) = match Disambiguator
::from_str(link
) {
913 Ok(Some((d
, path
, link_text
))) => (Some(d
), path
.trim(), link_text
.trim()),
914 Ok(None
) => (None
, link
, link
),
915 Err((err_msg
, relative_range
)) => {
916 // Only report error if we would not have ignored this link. See issue #83859.
917 if !should_ignore_link_with_disambiguators(link
) {
918 let disambiguator_range
= match range_between_backticks(&ori_link
.range
, dox
) {
919 MarkdownLinkRange
::Destination(no_backticks_range
) => {
920 MarkdownLinkRange
::Destination(
921 (no_backticks_range
.start
+ relative_range
.start
)
922 ..(no_backticks_range
.start
+ relative_range
.end
),
925 mdlr @ MarkdownLinkRange
::WholeLink(_
) => mdlr
,
927 return Some(Err(PreprocessingError
::Disambiguator(disambiguator_range
, err_msg
)));
934 if should_ignore_link(path_str
) {
938 // Strip generics from the path.
939 let path_str
= match strip_generics_from_path(path_str
) {
942 debug
!("link has malformed generics: {path_str}");
943 return Some(Err(PreprocessingError
::MalformedGenerics(err
, path_str
.to_owned())));
947 // Sanity check to make sure we don't have any angle brackets after stripping generics.
948 assert
!(!path_str
.contains(['
<'
, '
>'
].as_slice()));
950 // The link is not an intra-doc link if it still contains spaces after stripping generics.
951 if path_str
.contains(' '
) {
955 Some(Ok(PreprocessingInfo
{
958 extra_fragment
: extra_fragment
.map(|frag
| frag
.to_owned()),
959 link_text
: Box
::<str>::from(link_text
),
963 fn preprocessed_markdown_links(s
: &str) -> Vec
<PreprocessedMarkdownLink
> {
964 markdown_links(s
, |link
| {
965 preprocess_link(&link
, s
).map(|pp_link
| PreprocessedMarkdownLink(pp_link
, link
))
969 impl LinkCollector
<'_
, '_
> {
970 #[instrument(level = "debug", skip_all)]
971 fn resolve_links(&mut self, item
: &Item
) {
972 if !self.cx
.render_options
.document_private
973 && let Some(def_id
) = item
.item_id
.as_def_id()
974 && let Some(def_id
) = def_id
.as_local()
975 && !self.cx
.tcx
.effective_visibilities(()).is_exported(def_id
)
976 && !has_primitive_or_keyword_docs(&item
.attrs
.other_attrs
) {
977 // Skip link resolution for non-exported items.
981 // We want to resolve in the lexical scope of the documentation.
982 // In the presence of re-exports, this is not the same as the module of the item.
983 // Rather than merging all documentation into one, resolve it one attribute at a time
984 // so we know which module it came from.
985 for (item_id
, doc
) in prepare_to_doc_link_resolution(&item
.attrs
.doc_strings
) {
986 if !may_have_doc_links(&doc
) {
989 debug
!("combined_docs={doc}");
990 // NOTE: if there are links that start in one crate and end in another, this will not resolve them.
991 // This is a degenerate case and it's not supported by rustdoc.
992 let item_id
= item_id
.unwrap_or_else(|| item
.item_id
.expect_def_id());
993 let module_id
= match self.cx
.tcx
.def_kind(item_id
) {
994 DefKind
::Mod
if item
.inner_docs(self.cx
.tcx
) => item_id
,
995 _
=> find_nearest_parent_module(self.cx
.tcx
, item_id
).unwrap(),
997 for md_link
in preprocessed_markdown_links(&doc
) {
998 let link
= self.resolve_link(&doc
, item
, item_id
, module_id
, &md_link
);
999 if let Some(link
) = link
{
1000 self.cx
.cache
.intra_doc_links
.entry(item
.item_id
).or_default().insert(link
);
1006 /// This is the entry point for resolving an intra-doc link.
1008 /// FIXME(jynelson): this is way too many arguments
1015 PreprocessedMarkdownLink(pp_link
, ori_link
): &PreprocessedMarkdownLink
,
1016 ) -> Option
<ItemLink
> {
1017 trace
!("considering link '{}'", ori_link
.link
);
1019 let diag_info
= DiagnosticInfo
{
1022 ori_link
: &ori_link
.link
,
1023 link_range
: ori_link
.range
.clone(),
1025 let PreprocessingInfo { path_str, disambiguator, extra_fragment, link_text }
=
1026 pp_link
.as_ref().map_err(|err
| err
.report(self.cx
, diag_info
.clone())).ok()?
;
1027 let disambiguator
= *disambiguator
;
1029 let (mut res
, fragment
) = self.resolve_with_disambiguator_cached(
1034 path_str
: path_str
.clone(),
1035 extra_fragment
: extra_fragment
.clone(),
1037 diag_info
.clone(), // this struct should really be Copy, but Range is not :(
1038 // For reference-style links we want to report only one error so unsuccessful
1039 // resolutions are cached, for other links we want to report an error every
1040 // time so they are not cached.
1041 matches
!(ori_link
.kind
, LinkType
::Reference
| LinkType
::Shortcut
),
1045 if ori_link
.display_text
.is_some() {
1046 self.resolve_display_text(
1052 path_str
: ori_link
.display_text
.clone()?
.into_boxed_str(),
1053 extra_fragment
: extra_fragment
.clone(),
1060 // Check for a primitive which might conflict with a module
1061 // Report the ambiguity and require that the user specify which one they meant.
1062 // FIXME: could there ever be a primitive not in the type namespace?
1065 None
| Some(Disambiguator
::Namespace(Namespace
::TypeNS
) | Disambiguator
::Primitive
)
1066 ) && !matches
!(res
, Res
::Primitive(_
))
1068 if let Some(prim
) = resolve_primitive(path_str
, TypeNS
) {
1070 if matches
!(disambiguator
, Some(Disambiguator
::Primitive
)) {
1073 // `[char]` when a `char` module is in scope
1074 let candidates
= &[(res
, res
.def_id(self.cx
.tcx
)), (prim
, None
)];
1075 ambiguity_error(self.cx
, &diag_info
, path_str
, candidates
);
1082 Res
::Primitive(_
) => {
1083 if let Some(UrlFragment
::Item(id
)) = fragment
{
1084 // We're actually resolving an associated item of a primitive, so we need to
1085 // verify the disambiguator (if any) matches the type of the associated item.
1086 // This case should really follow the same flow as the `Res::Def` branch below,
1087 // but attempting to add a call to `clean::register_res` causes an ICE. @jyn514
1088 // thinks `register_res` is only needed for cross-crate re-exports, but Rust
1089 // doesn't allow statements like `use str::trim;`, making this a (hopefully)
1090 // valid omission. See https://github.com/rust-lang/rust/pull/80660#discussion_r551585677
1091 // for discussion on the matter.
1092 let kind
= self.cx
.tcx
.def_kind(id
);
1093 self.verify_disambiguator(path_str
, kind
, id
, disambiguator
, item
, &diag_info
)?
;
1095 match disambiguator
{
1096 Some(Disambiguator
::Primitive
| Disambiguator
::Namespace(_
)) | None
=> {}
1098 self.report_disambiguator_mismatch(path_str
, other
, res
, &diag_info
);
1104 res
.def_id(self.cx
.tcx
).map(|page_id
| ItemLink
{
1105 link
: Box
::<str>::from(&*ori_link
.link
),
1106 link_text
: link_text
.clone(),
1111 Res
::Def(kind
, id
) => {
1112 let (kind_for_dis
, id_for_dis
) = if let Some(UrlFragment
::Item(id
)) = fragment
{
1113 (self.cx
.tcx
.def_kind(id
), id
)
1117 self.verify_disambiguator(
1126 let page_id
= clean
::register_res(self.cx
, rustc_hir
::def
::Res
::Def(kind
, id
));
1128 link
: Box
::<str>::from(&*ori_link
.link
),
1129 link_text
: link_text
.clone(),
1137 fn verify_disambiguator(
1142 disambiguator
: Option
<Disambiguator
>,
1144 diag_info
: &DiagnosticInfo
<'_
>,
1146 debug
!("intra-doc link to {path_str} resolved to {:?}", (kind
, id
));
1148 // Disallow e.g. linking to enums with `struct@`
1149 debug
!("saw kind {kind:?} with disambiguator {disambiguator:?}");
1150 match (kind
, disambiguator
) {
1151 | (DefKind
::Const
| DefKind
::ConstParam
| DefKind
::AssocConst
| DefKind
::AnonConst
, Some(Disambiguator
::Kind(DefKind
::Const
)))
1152 // NOTE: this allows 'method' to mean both normal functions and associated functions
1153 // This can't cause ambiguity because both are in the same namespace.
1154 | (DefKind
::Fn
| DefKind
::AssocFn
, Some(Disambiguator
::Kind(DefKind
::Fn
)))
1155 // These are namespaces; allow anything in the namespace to match
1156 | (_
, Some(Disambiguator
::Namespace(_
)))
1157 // If no disambiguator given, allow anything
1159 // All of these are valid, so do nothing
1161 (actual
, Some(Disambiguator
::Kind(expected
))) if actual
== expected
=> {}
1162 (_
, Some(specified @ Disambiguator
::Kind(_
) | specified @ Disambiguator
::Primitive
)) => {
1163 self.report_disambiguator_mismatch(path_str
, specified
, Res
::Def(kind
, id
), diag_info
);
1168 // item can be non-local e.g. when using `#[rustc_doc_primitive = "pointer"]`
1169 if let Some((src_id
, dst_id
)) = id
.as_local().and_then(|dst_id
| {
1170 item
.item_id
.expect_def_id().as_local().map(|src_id
| (src_id
, dst_id
))
1172 if self.cx
.tcx
.effective_visibilities(()).is_exported(src_id
)
1173 && !self.cx
.tcx
.effective_visibilities(()).is_exported(dst_id
)
1175 privacy_error(self.cx
, diag_info
, path_str
);
1182 fn report_disambiguator_mismatch(
1185 specified
: Disambiguator
,
1187 diag_info
: &DiagnosticInfo
<'_
>,
1189 // The resolved item did not match the disambiguator; give a better error than 'not found'
1190 let msg
= format
!("incompatible link kind for `{path_str}`");
1191 let callback
= |diag
: &mut Diagnostic
, sp
: Option
<rustc_span
::Span
>, link_range
| {
1193 "this link resolved to {} {}, which is not {} {}",
1196 specified
.article(),
1199 if let Some(sp
) = sp
{
1200 diag
.span_label(sp
, note
);
1204 suggest_disambiguator(resolved
, diag
, path_str
, link_range
, sp
, diag_info
);
1206 report_diagnostic(self.cx
.tcx
, BROKEN_INTRA_DOC_LINKS
, msg
, diag_info
, callback
);
1209 fn report_rawptr_assoc_feature_gate(
1212 ori_link
: &MarkdownLinkRange
,
1215 let span
= source_span_for_markdown_range(
1218 ori_link
.inner_range(),
1219 &item
.attrs
.doc_strings
,
1221 .unwrap_or_else(|| item
.attr_span(self.cx
.tcx
));
1222 rustc_session
::parse
::feature_err(
1223 &self.cx
.tcx
.sess
.parse_sess
,
1224 sym
::intra_doc_pointers
,
1226 "linking to associated items of raw pointers is experimental",
1228 .note("rustdoc does not allow disambiguating between `*const` and `*mut`, and pointers are unstable until it does")
1232 fn resolve_with_disambiguator_cached(
1234 key
: ResolutionInfo
,
1235 diag
: DiagnosticInfo
<'_
>,
1236 // If errors are cached then they are only reported on first occurrence
1237 // which we want in some cases but not in others.
1239 // If this call is intended to be recoverable, then pass true to silence.
1240 // This is only recoverable when path is failed to resolved.
1242 ) -> Option
<(Res
, Option
<UrlFragment
>)> {
1243 if let Some(res
) = self.visited_links
.get(&key
) {
1244 if res
.is_some() || cache_errors
{
1249 let mut candidates
= self.resolve_with_disambiguator(&key
, diag
.clone(), recoverable
);
1251 // FIXME: it would be nice to check that the feature gate was enabled in the original crate, not just ignore it altogether.
1252 // However I'm not sure how to check that across crates.
1253 if let Some(candidate
) = candidates
.get(0) &&
1254 candidate
.0 == Res
::Primitive(PrimitiveType
::RawPointer
) &&
1255 key
.path_str
.contains("::") // We only want to check this if this is an associated item.
1257 if key
.item_id
.is_local() && !self.cx
.tcx
.features().intra_doc_pointers
{
1258 self.report_rawptr_assoc_feature_gate(diag
.dox
, &diag
.link_range
, diag
.item
);
1261 candidates
= vec
![candidates
[0]];
1265 // If there are multiple items with the same "kind" (for example, both "associated types")
1266 // and after removing duplicated kinds, only one remains, the `ambiguity_error` function
1267 // won't emit an error. So at this point, we can just take the first candidate as it was
1268 // the first retrieved and use it to generate the link.
1269 if candidates
.len() > 1 && !ambiguity_error(self.cx
, &diag
, &key
.path_str
, &candidates
) {
1270 candidates
= vec
![candidates
[0]];
1273 if let &[(res
, def_id
)] = candidates
.as_slice() {
1274 let fragment
= match (&key
.extra_fragment
, def_id
) {
1275 (Some(_
), Some(def_id
)) => {
1276 report_anchor_conflict(self.cx
, diag
, def_id
);
1279 (Some(u_frag
), None
) => Some(UrlFragment
::UserWritten(u_frag
.clone())),
1280 (None
, Some(def_id
)) => Some(UrlFragment
::Item(def_id
)),
1281 (None
, None
) => None
,
1283 let r
= Some((res
, fragment
));
1284 self.visited_links
.insert(key
, r
.clone());
1289 self.visited_links
.insert(key
, None
);
1294 /// After parsing the disambiguator, resolve the main part of the link.
1295 // FIXME(jynelson): wow this is just so much
1296 fn resolve_with_disambiguator(
1298 key
: &ResolutionInfo
,
1299 diag
: DiagnosticInfo
<'_
>,
1300 // If this call is intended to be recoverable, then pass true to silence.
1301 // This is only recoverable when path is failed to resolved.
1303 ) -> Vec
<(Res
, Option
<DefId
>)> {
1304 let disambiguator
= key
.dis
;
1305 let path_str
= &key
.path_str
;
1306 let item_id
= key
.item_id
;
1307 let module_id
= key
.module_id
;
1309 match disambiguator
.map(Disambiguator
::ns
) {
1310 Some(expected_ns
) => {
1311 match self.resolve(path_str
, expected_ns
, item_id
, module_id
) {
1312 Ok(candidates
) => candidates
,
1314 // We only looked in one namespace. Try to give a better error if possible.
1315 // FIXME: really it should be `resolution_failure` that does this, not `resolve_with_disambiguator`.
1316 // See https://github.com/rust-lang/rust/pull/76955#discussion_r493953382 for a good approach.
1317 let mut err
= ResolutionFailure
::NotResolved(err
);
1318 for other_ns
in [TypeNS
, ValueNS
, MacroNS
] {
1319 if other_ns
!= expected_ns
{
1321 self.resolve(path_str
, other_ns
, item_id
, module_id
) &&
1324 err
= ResolutionFailure
::WrongNamespace
{
1325 res
: full_res(self.cx
.tcx
, res
[0]),
1333 resolution_failure(self, diag
, path_str
, disambiguator
, smallvec
![err
]);
1341 let mut candidate
= |ns
| {
1342 self.resolve(path_str
, ns
, item_id
, module_id
)
1343 .map_err(ResolutionFailure
::NotResolved
)
1346 let candidates
= PerNS
{
1347 macro_ns
: candidate(MacroNS
),
1348 type_ns
: candidate(TypeNS
),
1349 value_ns
: candidate(ValueNS
).and_then(|v_res
| {
1350 for (res
, _
) in v_res
.iter() {
1352 // Constructors are picked up in the type namespace.
1353 Res
::Def(DefKind
::Ctor(..), _
) => {
1354 return Err(ResolutionFailure
::WrongNamespace
{
1356 expected_ns
: TypeNS
,
1366 let len
= candidates
1368 .fold(0, |acc
, res
| if let Ok(res
) = res { acc + res.len() }
else { acc }
);
1377 candidates
.into_iter().filter_map(|res
| res
.err()).collect(),
1381 } else if len
== 1 {
1382 candidates
.into_iter().filter_map(|res
| res
.ok()).flatten().collect
::<Vec
<_
>>()
1384 let has_derive_trait_collision
= is_derive_trait_collision(&candidates
);
1385 if len
== 2 && has_derive_trait_collision
{
1386 candidates
.type_ns
.unwrap()
1388 // If we're reporting an ambiguity, don't mention the namespaces that failed
1389 let mut candidates
= candidates
.map(|candidate
| candidate
.ok());
1390 // If there a collision between a trait and a derive, we ignore the derive.
1391 if has_derive_trait_collision
{
1392 candidates
.macro_ns
= None
;
1394 candidates
.into_iter().flatten().flatten().collect
::<Vec
<_
>>()
1401 /// Resolve display text if the provided link has separated parts of links.
1404 /// Inline link `[display_text](dest_link)` and reference link `[display_text][reference_link]` has
1405 /// separated parts of links.
1406 fn resolve_display_text(
1408 explicit_link
: &Box
<str>,
1409 display_res_info
: ResolutionInfo
,
1410 ori_link
: &MarkdownLink
,
1411 diag_info
: &DiagnosticInfo
<'_
>,
1413 // Check if explicit resolution's path is same as resolution of original link's display text path, see
1414 // tests/rustdoc-ui/lint/redundant_explicit_links.rs for more cases.
1416 // To avoid disambiguator from panicking, we check if display text path is possible to be disambiguated
1417 // into explicit path.
1420 LinkType
::Inline
| LinkType
::Reference
| LinkType
::ReferenceUnknown
1425 // Algorithm to check if display text could possibly be the explicit link:
1427 // Consider 2 links which are display text and explicit link, pick the shorter
1428 // one as symbol and longer one as full qualified path, and tries to match symbol
1429 // to the full qualified path's last symbol.
1431 // Otherwise, check if 2 links are same, if so, skip the resolve process.
1433 // Notice that this algorithm is passive, might possibly miss actual redudant cases.
1434 let explicit_link
= explicit_link
.to_string();
1435 let display_text
= ori_link
.display_text
.as_ref().unwrap();
1437 if display_text
.len() == explicit_link
.len() {
1438 // Whether they are same or not, skip the resolve process.
1442 if explicit_link
.ends_with(&display_text
[..]) || display_text
.ends_with(&explicit_link
[..])
1444 self.resolve_with_disambiguator_cached(
1446 diag_info
.clone(), // this struct should really be Copy, but Range is not :(
1454 /// Get the section of a link between the backticks,
1455 /// or the whole link if there aren't any backticks.
1464 /// This function does nothing if `ori_link.range` is a `MarkdownLinkRange::WholeLink`.
1465 fn range_between_backticks(ori_link_range
: &MarkdownLinkRange
, dox
: &str) -> MarkdownLinkRange
{
1466 let range
= match ori_link_range
{
1467 mdlr @ MarkdownLinkRange
::WholeLink(_
) => return mdlr
.clone(),
1468 MarkdownLinkRange
::Destination(inner
) => inner
.clone(),
1470 let ori_link_text
= &dox
[range
.clone()];
1471 let after_first_backtick_group
= ori_link_text
.bytes().position(|b
| b
!= b'`'
).unwrap_or(0);
1472 let before_second_backtick_group
= ori_link_text
1474 .skip(after_first_backtick_group
)
1475 .position(|b
| b
== b'`'
)
1476 .unwrap_or(ori_link_text
.len());
1477 MarkdownLinkRange
::Destination(
1478 (range
.start
+ after_first_backtick_group
)..(range
.start
+ before_second_backtick_group
),
1482 /// Returns true if we should ignore `link` due to it being unlikely
1483 /// that it is an intra-doc link. `link` should still have disambiguators
1484 /// if there were any.
1486 /// The difference between this and [`should_ignore_link()`] is that this
1487 /// check should only be used on links that still have disambiguators.
1488 fn should_ignore_link_with_disambiguators(link
: &str) -> bool
{
1489 link
.contains(|ch
: char| !(ch
.is_alphanumeric() || ":_<>, !*&;@()".contains(ch
)))
1492 /// Returns true if we should ignore `path_str` due to it being unlikely
1493 /// that it is an intra-doc link.
1494 fn should_ignore_link(path_str
: &str) -> bool
{
1495 path_str
.contains(|ch
: char| !(ch
.is_alphanumeric() || ":_<>, !*&;".contains(ch
)))
1498 #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
1499 /// Disambiguators for a link.
1500 enum Disambiguator
{
1503 /// This is buggy, see <https://github.com/rust-lang/rust/pull/77875#discussion_r503583103>
1505 /// `struct@` or `f()`
1508 Namespace(Namespace
),
1511 impl Disambiguator
{
1512 /// Given a link, parse and return `(disambiguator, path_str, link_text)`.
1514 /// This returns `Ok(Some(...))` if a disambiguator was found,
1515 /// `Ok(None)` if no disambiguator was found, or `Err(...)`
1516 /// if there was a problem with the disambiguator.
1517 fn from_str(link
: &str) -> Result
<Option
<(Self, &str, &str)>, (String
, Range
<usize>)> {
1518 use Disambiguator
::{Kind, Namespace as NS, Primitive}
;
1520 if let Some(idx
) = link
.find('@'
) {
1521 let (prefix
, rest
) = link
.split_at(idx
);
1522 let d
= match prefix
{
1523 // If you update this list, please also update the relevant rustdoc book section!
1524 "struct" => Kind(DefKind
::Struct
),
1525 "enum" => Kind(DefKind
::Enum
),
1526 "trait" => Kind(DefKind
::Trait
),
1527 "union" => Kind(DefKind
::Union
),
1528 "module" | "mod" => Kind(DefKind
::Mod
),
1529 "const" | "constant" => Kind(DefKind
::Const
),
1530 "static" => Kind(DefKind
::Static(Mutability
::Not
)),
1531 "function" | "fn" | "method" => Kind(DefKind
::Fn
),
1532 "derive" => Kind(DefKind
::Macro(MacroKind
::Derive
)),
1533 "type" => NS(Namespace
::TypeNS
),
1534 "value" => NS(Namespace
::ValueNS
),
1535 "macro" => NS(Namespace
::MacroNS
),
1536 "prim" | "primitive" => Primitive
,
1537 _
=> return Err((format
!("unknown disambiguator `{prefix}`"), 0..idx
)),
1539 Ok(Some((d
, &rest
[1..], &rest
[1..])))
1542 // If you update this list, please also update the relevant rustdoc book section!
1543 ("!()", DefKind
::Macro(MacroKind
::Bang
)),
1544 ("!{}", DefKind
::Macro(MacroKind
::Bang
)),
1545 ("![]", DefKind
::Macro(MacroKind
::Bang
)),
1546 ("()", DefKind
::Fn
),
1547 ("!", DefKind
::Macro(MacroKind
::Bang
)),
1549 for (suffix
, kind
) in suffixes
{
1550 if let Some(path_str
) = link
.strip_suffix(suffix
) {
1551 // Avoid turning `!` or `()` into an empty string
1552 if !path_str
.is_empty() {
1553 return Ok(Some((Kind(kind
), path_str
, link
)));
1561 fn ns(self) -> Namespace
{
1563 Self::Namespace(n
) => n
,
1565 k
.ns().expect("only DefKinds with a valid namespace can be disambiguators")
1567 Self::Primitive
=> TypeNS
,
1571 fn article(self) -> &'
static str {
1573 Self::Namespace(_
) => panic
!("article() doesn't make sense for namespaces"),
1574 Self::Kind(k
) => k
.article(),
1575 Self::Primitive
=> "a",
1579 fn descr(self) -> &'
static str {
1581 Self::Namespace(n
) => n
.descr(),
1582 // HACK(jynelson): the source of `DefKind::descr` only uses the DefId for
1583 // printing "module" vs "crate" so using the wrong ID is not a huge problem
1584 Self::Kind(k
) => k
.descr(CRATE_DEF_ID
.to_def_id()),
1585 Self::Primitive
=> "builtin type",
1590 /// A suggestion to show in a diagnostic.
1593 Prefix(&'
static str),
1598 /// `foo` without any disambiguator
1599 RemoveDisambiguator
,
1603 fn descr(&self) -> Cow
<'
static, str> {
1605 Self::Prefix(x
) => format
!("prefix with `{x}@`").into(),
1606 Self::Function
=> "add parentheses".into(),
1607 Self::Macro
=> "add an exclamation mark".into(),
1608 Self::RemoveDisambiguator
=> "remove the disambiguator".into(),
1612 fn as_help(&self, path_str
: &str) -> String
{
1613 // FIXME: if this is an implied shortcut link, it's bad style to suggest `@`
1615 Self::Prefix(prefix
) => format
!("{prefix}@{path_str}"),
1616 Self::Function
=> format
!("{path_str}()"),
1617 Self::Macro
=> format
!("{path_str}!"),
1618 Self::RemoveDisambiguator
=> path_str
.into(),
1626 sp
: rustc_span
::Span
,
1627 ) -> Vec
<(rustc_span
::Span
, String
)> {
1628 let inner_sp
= match ori_link
.find('
('
) {
1629 Some(index
) if index
!= 0 && ori_link
.as_bytes()[index
- 1] == b'
\\'
=> {
1630 sp
.with_hi(sp
.lo() + BytePos((index
- 1) as _
))
1632 Some(index
) => sp
.with_hi(sp
.lo() + BytePos(index
as _
)),
1635 let inner_sp
= match ori_link
.find('
!'
) {
1636 Some(index
) if index
!= 0 && ori_link
.as_bytes()[index
- 1] == b'
\\'
=> {
1637 sp
.with_hi(sp
.lo() + BytePos((index
- 1) as _
))
1639 Some(index
) => inner_sp
.with_hi(inner_sp
.lo() + BytePos(index
as _
)),
1642 let inner_sp
= match ori_link
.find('@'
) {
1643 Some(index
) if index
!= 0 && ori_link
.as_bytes()[index
- 1] == b'
\\'
=> {
1644 sp
.with_hi(sp
.lo() + BytePos((index
- 1) as _
))
1646 Some(index
) => inner_sp
.with_lo(inner_sp
.lo() + BytePos(index
as u32 + 1)),
1650 Self::Prefix(prefix
) => {
1651 // FIXME: if this is an implied shortcut link, it's bad style to suggest `@`
1652 let mut sugg
= vec
![(sp
.with_hi(inner_sp
.lo()), format
!("{prefix}@"))];
1653 if sp
.hi() != inner_sp
.hi() {
1654 sugg
.push((inner_sp
.shrink_to_hi().with_hi(sp
.hi()), String
::new()));
1659 let mut sugg
= vec
![(inner_sp
.shrink_to_hi().with_hi(sp
.hi()), "()".to_string())];
1660 if sp
.lo() != inner_sp
.lo() {
1661 sugg
.push((inner_sp
.shrink_to_lo().with_lo(sp
.lo()), String
::new()));
1666 let mut sugg
= vec
![(inner_sp
.shrink_to_hi(), "!".to_string())];
1667 if sp
.lo() != inner_sp
.lo() {
1668 sugg
.push((inner_sp
.shrink_to_lo().with_lo(sp
.lo()), String
::new()));
1672 Self::RemoveDisambiguator
=> vec
![(sp
, path_str
.into())],
1677 /// Reports a diagnostic for an intra-doc link.
1679 /// If no link range is provided, or the source span of the link cannot be determined, the span of
1680 /// the entire documentation block is used for the lint. If a range is provided but the span
1681 /// calculation fails, a note is added to the diagnostic pointing to the link in the markdown.
1683 /// The `decorate` callback is invoked in all cases to allow further customization of the
1684 /// diagnostic before emission. If the span of the link was able to be determined, the second
1685 /// parameter of the callback will contain it, and the primary span of the diagnostic will be set
1687 fn report_diagnostic(
1689 lint
: &'
static Lint
,
1690 msg
: impl Into
<DiagnosticMessage
> + Display
,
1691 DiagnosticInfo { item, ori_link: _, dox, link_range }
: &DiagnosticInfo
<'_
>,
1692 decorate
: impl FnOnce(&mut Diagnostic
, Option
<rustc_span
::Span
>, MarkdownLinkRange
),
1694 let Some(hir_id
) = DocContext
::as_local_hir_id(tcx
, item
.item_id
) else {
1695 // If non-local, no need to check anything.
1696 info
!("ignoring warning from parent crate: {msg}");
1700 let sp
= item
.attr_span(tcx
);
1702 tcx
.struct_span_lint_hir(lint
, hir_id
, sp
, msg
, |lint
| {
1703 let (span
, link_range
) = match link_range
{
1704 MarkdownLinkRange
::Destination(md_range
) => {
1705 let mut md_range
= md_range
.clone();
1707 source_span_for_markdown_range(tcx
, dox
, &md_range
, &item
.attrs
.doc_strings
)
1709 while dox
.as_bytes().get(md_range
.start
) == Some(&b' '
)
1710 || dox
.as_bytes().get(md_range
.start
) == Some(&b'`'
)
1712 md_range
.start
+= 1;
1713 sp
= sp
.with_lo(sp
.lo() + BytePos(1));
1715 while dox
.as_bytes().get(md_range
.end
- 1) == Some(&b' '
)
1716 || dox
.as_bytes().get(md_range
.end
- 1) == Some(&b'`'
)
1719 sp
= sp
.with_hi(sp
.hi() - BytePos(1));
1723 (sp
, MarkdownLinkRange
::Destination(md_range
))
1725 MarkdownLinkRange
::WholeLink(md_range
) => (
1726 source_span_for_markdown_range(tcx
, dox
, &md_range
, &item
.attrs
.doc_strings
),
1731 if let Some(sp
) = span
{
1734 // blah blah blah\nblah\nblah [blah] blah blah\nblah blah
1737 // last_new_line_offset
1738 let md_range
= link_range
.inner_range().clone();
1739 let last_new_line_offset
= dox
[..md_range
.start
].rfind('
\n'
).map_or(0, |n
| n
+ 1);
1740 let line
= dox
[last_new_line_offset
..].lines().next().unwrap_or("");
1742 // Print the line containing the `md_range` and manually mark it with '^'s.
1744 "the link appears in this line:\n\n{line}\n\
1745 {indicator: <before$}{indicator:^<found$}",
1748 before
= md_range
.start
- last_new_line_offset
,
1749 found
= md_range
.len(),
1753 decorate(lint
, span
, link_range
);
1759 /// Reports a link that failed to resolve.
1761 /// This also tries to resolve any intermediate path segments that weren't
1762 /// handled earlier. For example, if passed `Item::Crate(std)` and `path_str`
1763 /// `std::io::Error::x`, this will resolve `std::io::Error`.
1764 fn resolution_failure(
1765 collector
: &mut LinkCollector
<'_
, '_
>,
1766 diag_info
: DiagnosticInfo
<'_
>,
1768 disambiguator
: Option
<Disambiguator
>,
1769 kinds
: SmallVec
<[ResolutionFailure
<'_
>; 3]>,
1771 let tcx
= collector
.cx
.tcx
;
1774 BROKEN_INTRA_DOC_LINKS
,
1775 format
!("unresolved link to `{path_str}`"),
1777 |diag
, sp
, link_range
| {
1778 let item
= |res
: Res
| format
!("the {} `{}`", res
.descr(), res
.name(tcx
));
1779 let assoc_item_not_allowed
= |res
: Res
| {
1780 let name
= res
.name(tcx
);
1782 "`{name}` is {} {}, not a module or type, and cannot have associated items",
1787 // ignore duplicates
1788 let mut variants_seen
= SmallVec
::<[_
; 3]>::new();
1789 for mut failure
in kinds
{
1790 let variant
= mem
::discriminant(&failure
);
1791 if variants_seen
.contains(&variant
) {
1794 variants_seen
.push(variant
);
1796 if let ResolutionFailure
::NotResolved(UnresolvedPath
{
1805 let item_id
= *item_id
;
1806 let module_id
= *module_id
;
1807 // FIXME(jynelson): this might conflict with my `Self` fix in #76467
1808 // FIXME: maybe use itertools `collect_tuple` instead?
1809 fn split(path
: &str) -> Option
<(&str, &str)> {
1810 let mut splitter
= path
.rsplitn(2, "::");
1811 splitter
.next().and_then(|right
| splitter
.next().map(|left
| (left
, right
)))
1814 // Check if _any_ parent of the path gets resolved.
1815 // If so, report it and say the first which failed; if not, say the first path segment didn't resolve.
1816 let mut name
= path_str
;
1818 let Some((start
, end
)) = split(name
) else {
1819 // avoid bug that marked [Quux::Z] as missing Z, not Quux
1820 if partial_res
.is_none() {
1821 *unresolved
= name
.into();
1826 for ns
in [TypeNS
, ValueNS
, MacroNS
] {
1827 if let Ok(v_res
) = collector
.resolve(start
, ns
, item_id
, module_id
) {
1828 debug
!("found partial_res={v_res:?}");
1829 if !v_res
.is_empty() {
1830 *partial_res
= Some(full_res(tcx
, v_res
[0]));
1831 *unresolved
= end
.into();
1836 *unresolved
= end
.into();
1839 let last_found_module
= match *partial_res
{
1840 Some(Res
::Def(DefKind
::Mod
, id
)) => Some(id
),
1841 None
=> Some(module_id
),
1844 // See if this was a module: `[path]` or `[std::io::nope]`
1845 if let Some(module
) = last_found_module
{
1846 let note
= if partial_res
.is_some() {
1847 // Part of the link resolved; e.g. `std::io::nonexistent`
1848 let module_name
= tcx
.item_name(module
);
1849 format
!("no item named `{unresolved}` in module `{module_name}`")
1851 // None of the link resolved; e.g. `Notimported`
1852 format
!("no item named `{unresolved}` in scope")
1854 if let Some(span
) = sp
{
1855 diag
.span_label(span
, note
);
1860 if !path_str
.contains("::") {
1861 if disambiguator
.map_or(true, |d
| d
.ns() == MacroNS
)
1867 .get(&Symbol
::intern(path_str
))
1871 "`macro_rules` named `{path_str}` exists in this crate, \
1872 but it is not in scope at this link's location"
1875 // If the link has `::` in it, assume it was meant to be an
1876 // intra-doc link. Otherwise, the `[]` might be unrelated.
1878 "to escape `[` and `]` characters, \
1879 add '\\' before them like `\\[` or `\\]`",
1887 // Otherwise, it must be an associated item or variant
1888 let res
= partial_res
.expect("None case was handled by `last_found_module`");
1889 let kind_did
= match res
{
1890 Res
::Def(kind
, did
) => Some((kind
, did
)),
1891 Res
::Primitive(_
) => None
,
1893 let is_struct_variant
= |did
| {
1894 if let ty
::Adt(def
, _
) = tcx
.type_of(did
).instantiate_identity().kind()
1896 && let Some(variant
) = def
.variants().iter().find(|v
| v
.name
== res
.name(tcx
)) {
1897 // ctor is `None` if variant is a struct
1898 variant
.ctor
.is_none()
1903 let path_description
= if let Some((kind
, did
)) = kind_did
{
1905 Mod
| ForeignMod
=> "inner item",
1906 Struct
=> "field or associated item",
1907 Enum
| Union
=> "variant or associated item",
1908 Variant
if is_struct_variant(did
) => {
1909 let variant
= res
.name(tcx
);
1910 let note
= format
!("variant `{variant}` has no such field");
1911 if let Some(span
) = sp
{
1912 diag
.span_label(span
, note
);
1935 let note
= assoc_item_not_allowed(res
);
1936 if let Some(span
) = sp
{
1937 diag
.span_label(span
, note
);
1949 | Static(_
) => "associated item",
1950 Impl { .. }
| GlobalAsm
=> unreachable
!("not a path"),
1955 let name
= res
.name(tcx
);
1957 "the {res} `{name}` has no {disamb_res} named `{unresolved}`",
1959 disamb_res
= disambiguator
.map_or(path_description
, |d
| d
.descr()),
1961 if let Some(span
) = sp
{
1962 diag
.span_label(span
, note
);
1969 let note
= match failure
{
1970 ResolutionFailure
::NotResolved { .. }
=> unreachable
!("handled above"),
1971 ResolutionFailure
::WrongNamespace { res, expected_ns }
=> {
1972 suggest_disambiguator(
1982 "this link resolves to {}, which is not in the {} namespace",
1988 if let Some(span
) = sp
{
1989 diag
.span_label(span
, note
);
1998 fn report_multiple_anchors(cx
: &DocContext
<'_
>, diag_info
: DiagnosticInfo
<'_
>) {
1999 let msg
= format
!("`{}` contains multiple anchors", diag_info
.ori_link
);
2000 anchor_failure(cx
, diag_info
, msg
, 1)
2003 fn report_anchor_conflict(cx
: &DocContext
<'_
>, diag_info
: DiagnosticInfo
<'_
>, def_id
: DefId
) {
2004 let (link
, kind
) = (diag_info
.ori_link
, Res
::from_def_id(cx
.tcx
, def_id
).descr());
2005 let msg
= format
!("`{link}` contains an anchor, but links to {kind}s are already anchored");
2006 anchor_failure(cx
, diag_info
, msg
, 0)
2009 /// Report an anchor failure.
2011 cx
: &DocContext
<'_
>,
2012 diag_info
: DiagnosticInfo
<'_
>,
2016 report_diagnostic(cx
.tcx
, BROKEN_INTRA_DOC_LINKS
, msg
, &diag_info
, |diag
, sp
, _link_range
| {
2017 if let Some(mut sp
) = sp
{
2018 if let Some((fragment_offset
, _
)) =
2019 diag_info
.ori_link
.char_indices().filter(|(_
, x
)| *x
== '
#').nth(anchor_idx)
2021 sp
= sp
.with_lo(sp
.lo() + BytePos(fragment_offset
as _
));
2023 diag
.span_label(sp
, "invalid anchor");
2028 /// Report an error in the link disambiguator.
2029 fn disambiguator_error(
2030 cx
: &DocContext
<'_
>,
2031 mut diag_info
: DiagnosticInfo
<'_
>,
2032 disambiguator_range
: MarkdownLinkRange
,
2033 msg
: impl Into
<DiagnosticMessage
> + Display
,
2035 diag_info
.link_range
= disambiguator_range
;
2036 report_diagnostic(cx
.tcx
, BROKEN_INTRA_DOC_LINKS
, msg
, &diag_info
, |diag
, _sp
, _link_range
| {
2038 "see {}/rustdoc/write-documentation/linking-to-items-by-name.html#namespaces-and-disambiguators for more info about disambiguators",
2039 crate::DOC_RUST_LANG_ORG_CHANNEL
2045 fn report_malformed_generics(
2046 cx
: &DocContext
<'_
>,
2047 diag_info
: DiagnosticInfo
<'_
>,
2048 err
: MalformedGenerics
,
2053 BROKEN_INTRA_DOC_LINKS
,
2054 format
!("unresolved link to `{path_str}`"),
2056 |diag
, sp
, _link_range
| {
2057 let note
= match err
{
2058 MalformedGenerics
::UnbalancedAngleBrackets
=> "unbalanced angle brackets",
2059 MalformedGenerics
::MissingType
=> "missing type for generic parameters",
2060 MalformedGenerics
::HasFullyQualifiedSyntax
=> {
2062 "see https://github.com/rust-lang/rust/issues/74563 for more information",
2064 "fully-qualified syntax is unsupported"
2066 MalformedGenerics
::InvalidPathSeparator
=> "has invalid path separator",
2067 MalformedGenerics
::TooManyAngleBrackets
=> "too many angle brackets",
2068 MalformedGenerics
::EmptyAngleBrackets
=> "empty angle brackets",
2070 if let Some(span
) = sp
{
2071 diag
.span_label(span
, note
);
2079 /// Report an ambiguity error, where there were multiple possible resolutions.
2081 /// If all `candidates` have the same kind, it's not possible to disambiguate so in this case,
2082 /// the function won't emit an error and will return `false`. Otherwise, it'll emit the error and
2085 cx
: &DocContext
<'_
>,
2086 diag_info
: &DiagnosticInfo
<'_
>,
2088 candidates
: &[(Res
, Option
<DefId
>)],
2090 let mut descrs
= FxHashSet
::default();
2091 let kinds
= candidates
2095 if let Some(def_id
) = def_id { Res::from_def_id(cx.tcx, *def_id) }
else { *res }
2098 .filter(|res
| descrs
.insert(res
.descr()))
2099 .collect
::<Vec
<_
>>();
2100 if descrs
.len() == 1 {
2101 // There is no way for users to disambiguate at this point, so better return the first
2102 // candidate and not show a warning.
2106 let mut msg
= format
!("`{path_str}` is ");
2107 match kinds
.as_slice() {
2110 "both {} {} and {} {}",
2118 let mut kinds
= kinds
.iter().peekable();
2119 while let Some(res
) = kinds
.next() {
2120 if kinds
.peek().is_some() {
2121 msg
+= &format
!("{} {}, ", res
.article(), res
.descr());
2123 msg
+= &format
!("and {} {}", res
.article(), res
.descr());
2129 report_diagnostic(cx
.tcx
, BROKEN_INTRA_DOC_LINKS
, msg
, diag_info
, |diag
, sp
, link_range
| {
2130 if let Some(sp
) = sp
{
2131 diag
.span_label(sp
, "ambiguous link");
2133 diag
.note("ambiguous link");
2137 suggest_disambiguator(res
, diag
, path_str
, link_range
.clone(), sp
, diag_info
);
2143 /// In case of an ambiguity or mismatched disambiguator, suggest the correct
2145 fn suggest_disambiguator(
2147 diag
: &mut Diagnostic
,
2149 link_range
: MarkdownLinkRange
,
2150 sp
: Option
<rustc_span
::Span
>,
2151 diag_info
: &DiagnosticInfo
<'_
>,
2153 let suggestion
= res
.disambiguator_suggestion();
2154 let help
= format
!("to link to the {}, {}", res
.descr(), suggestion
.descr());
2156 let ori_link
= match link_range
{
2157 MarkdownLinkRange
::Destination(range
) => Some(&diag_info
.dox
[range
]),
2158 MarkdownLinkRange
::WholeLink(_
) => None
,
2161 if let (Some(sp
), Some(ori_link
)) = (sp
, ori_link
) {
2162 let mut spans
= suggestion
.as_help_span(path_str
, ori_link
, sp
);
2163 if spans
.len() > 1 {
2164 diag
.multipart_suggestion(help
, spans
, Applicability
::MaybeIncorrect
);
2166 let (sp
, suggestion_text
) = spans
.pop().unwrap();
2167 diag
.span_suggestion_verbose(sp
, help
, suggestion_text
, Applicability
::MaybeIncorrect
);
2170 diag
.help(format
!("{help}: {}", suggestion
.as_help(path_str
)));
2174 /// Report a link from a public item to a private one.
2175 fn privacy_error(cx
: &DocContext
<'_
>, diag_info
: &DiagnosticInfo
<'_
>, path_str
: &str) {
2177 let item_name
= match diag_info
.item
.name
{
2182 None
=> "<unknown>",
2184 let msg
= format
!("public documentation for `{item_name}` links to private item `{path_str}`");
2186 report_diagnostic(cx
.tcx
, PRIVATE_INTRA_DOC_LINKS
, msg
, diag_info
, |diag
, sp
, _link_range
| {
2187 if let Some(sp
) = sp
{
2188 diag
.span_label(sp
, "this item is private");
2191 let note_msg
= if cx
.render_options
.document_private
{
2192 "this link resolves only because you passed `--document-private-items`, but will break without"
2194 "this link will resolve properly if you pass `--document-private-items`"
2196 diag
.note(note_msg
);
2200 /// Resolve a primitive type or value.
2201 fn resolve_primitive(path_str
: &str, ns
: Namespace
) -> Option
<Res
> {
2205 use PrimitiveType
::*;
2206 let prim
= match path_str
{
2222 "bool" | "true" | "false" => Bool
,
2223 "str" | "&str" => Str
,
2224 // See #80181 for why these don't have symbols associated.
2229 "pointer" | "*const" | "*mut" => RawPointer
,
2230 "reference" | "&" | "&mut" => Reference
,
2232 "never" | "!" => Never
,
2235 debug
!("resolved primitives {prim:?}");
2236 Some(Res
::Primitive(prim
))