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}
;
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
::{DefIdTree, Ty, TyCtxt}
;
17 use rustc_middle
::{bug, ty}
;
18 use rustc_resolve
::ParentScope
;
19 use rustc_session
::lint
::Lint
;
20 use rustc_span
::hygiene
::MacroKind
;
21 use rustc_span
::symbol
::{sym, Ident, Symbol}
;
22 use rustc_span
::BytePos
;
23 use smallvec
::{smallvec, SmallVec}
;
29 use crate::clean
::{self, utils::find_nearest_parent_module}
;
30 use crate::clean
::{Crate, Item, ItemId, ItemLink, PrimitiveType}
;
31 use crate::core
::DocContext
;
32 use crate::html
::markdown
::{markdown_links, MarkdownLink}
;
33 use crate::lint
::{BROKEN_INTRA_DOC_LINKS, PRIVATE_INTRA_DOC_LINKS}
;
34 use crate::passes
::Pass
;
35 use crate::visit
::DocVisitor
;
38 pub(crate) use early
::early_resolve_intra_doc_links
;
40 pub(crate) const COLLECT_INTRA_DOC_LINKS
: Pass
= Pass
{
41 name
: "collect-intra-doc-links",
42 run
: collect_intra_doc_links
,
43 description
: "resolves intra-doc links",
46 fn collect_intra_doc_links(krate
: Crate
, cx
: &mut DocContext
<'_
>) -> Crate
{
48 LinkCollector { cx, mod_ids: Vec::new(), visited_links: FxHashMap::default() }
;
49 collector
.visit_crate(&krate
);
53 #[derive(Copy, Clone, Debug, Hash)]
56 Primitive(PrimitiveType
),
59 type ResolveRes
= rustc_hir
::def
::Res
<rustc_ast
::NodeId
>;
62 fn descr(self) -> &'
static str {
64 Res
::Def(kind
, id
) => ResolveRes
::Def(kind
, id
).descr(),
65 Res
::Primitive(_
) => "builtin type",
69 fn article(self) -> &'
static str {
71 Res
::Def(kind
, id
) => ResolveRes
::Def(kind
, id
).article(),
72 Res
::Primitive(_
) => "a",
76 fn name(self, tcx
: TyCtxt
<'_
>) -> Symbol
{
78 Res
::Def(_
, id
) => tcx
.item_name(id
),
79 Res
::Primitive(prim
) => prim
.as_sym(),
83 fn def_id(self, tcx
: TyCtxt
<'_
>) -> Option
<DefId
> {
85 Res
::Def(_
, id
) => Some(id
),
86 Res
::Primitive(prim
) => PrimitiveType
::primitive_locations(tcx
).get(&prim
).copied(),
90 fn from_def_id(tcx
: TyCtxt
<'_
>, def_id
: DefId
) -> Res
{
91 Res
::Def(tcx
.def_kind(def_id
), def_id
)
94 /// Used for error reporting.
95 fn disambiguator_suggestion(self) -> Suggestion
{
96 let kind
= match self {
97 Res
::Primitive(_
) => return Suggestion
::Prefix("prim"),
98 Res
::Def(kind
, _
) => kind
,
100 if kind
== DefKind
::Macro(MacroKind
::Bang
) {
101 return Suggestion
::Macro
;
102 } else if kind
== DefKind
::Fn
|| kind
== DefKind
::AssocFn
{
103 return Suggestion
::Function
;
104 } else if kind
== DefKind
::Field
{
105 return Suggestion
::RemoveDisambiguator
;
108 let prefix
= match kind
{
109 DefKind
::Struct
=> "struct",
110 DefKind
::Enum
=> "enum",
111 DefKind
::Trait
=> "trait",
112 DefKind
::Union
=> "union",
113 DefKind
::Mod
=> "mod",
114 DefKind
::Const
| DefKind
::ConstParam
| DefKind
::AssocConst
| DefKind
::AnonConst
=> {
117 DefKind
::Static(_
) => "static",
118 DefKind
::Macro(MacroKind
::Derive
) => "derive",
119 // Now handle things that don't have a specific disambiguator
122 .expect("tried to calculate a disambiguator for a def without a namespace?")
124 Namespace
::TypeNS
=> "type",
125 Namespace
::ValueNS
=> "value",
126 Namespace
::MacroNS
=> "macro",
130 Suggestion
::Prefix(prefix
)
134 impl TryFrom
<ResolveRes
> for Res
{
137 fn try_from(res
: ResolveRes
) -> Result
<Self, ()> {
138 use rustc_hir
::def
::Res
::*;
140 Def(kind
, id
) => Ok(Res
::Def(kind
, id
)),
141 PrimTy(prim
) => Ok(Res
::Primitive(PrimitiveType
::from_hir(prim
))),
143 NonMacroAttr(..) | Err
=> Result
::Err(()),
144 other
=> bug
!("unrecognized res {:?}", other
),
149 /// The link failed to resolve. [`resolution_failure`] should look to see if there's
150 /// a more helpful error that can be given.
152 struct UnresolvedPath
<'a
> {
153 /// Item on which the link is resolved, used for resolving `Self`.
155 /// The scope the link was resolved in.
157 /// If part of the link resolved, this has the `Res`.
159 /// In `[std::io::Error::x]`, `std::io::Error` would be a partial resolution.
160 partial_res
: Option
<Res
>,
161 /// The remaining unresolved path segments.
163 /// In `[std::io::Error::x]`, `x` would be unresolved.
164 unresolved
: Cow
<'a
, str>,
168 enum ResolutionFailure
<'a
> {
169 /// This resolved, but with the wrong namespace.
171 /// What the link resolved to.
173 /// The expected namespace for the resolution, determined from the link's disambiguator.
175 /// E.g., for `[fn@Result]` this is [`Namespace::ValueNS`],
176 /// even though `Result`'s actual namespace is [`Namespace::TypeNS`].
177 expected_ns
: Namespace
,
179 NotResolved(UnresolvedPath
<'a
>),
182 #[derive(Clone, Copy, Debug)]
183 enum MalformedGenerics
{
184 /// This link has unbalanced angle brackets.
186 /// For example, `Vec<T` should trigger this, as should `Vec<T>>`.
187 UnbalancedAngleBrackets
,
188 /// The generics are not attached to a type.
190 /// For example, `<T>` should trigger this.
192 /// This is detected by checking if the path is empty after the generics are stripped.
194 /// The link uses fully-qualified syntax, which is currently unsupported.
196 /// For example, `<Vec as IntoIterator>::into_iter` should trigger this.
198 /// This is detected by checking if ` as ` (the keyword `as` with spaces around it) is inside
200 HasFullyQualifiedSyntax
,
201 /// The link has an invalid path separator.
203 /// For example, `Vec:<T>:new()` should trigger this. Note that `Vec:new()` will **not**
204 /// trigger this because it has no generics and thus [`strip_generics_from_path`] will not be
207 /// Note that this will also **not** be triggered if the invalid path separator is inside angle
208 /// brackets because rustdoc mostly ignores what's inside angle brackets (except for
209 /// [`HasFullyQualifiedSyntax`](MalformedGenerics::HasFullyQualifiedSyntax)).
211 /// This is detected by checking if there is a colon followed by a non-colon in the link.
212 InvalidPathSeparator
,
213 /// The link has too many angle brackets.
215 /// For example, `Vec<<T>>` should trigger this.
216 TooManyAngleBrackets
,
217 /// The link has empty angle brackets.
219 /// For example, `Vec<>` should trigger this.
223 #[derive(Clone, Debug, Hash, PartialEq, Eq)]
224 pub(crate) enum UrlFragment
{
226 /// A part of a page that isn't a rust item.
228 /// Eg: `[Vector Examples](std::vec::Vec#examples)`
233 /// Render the fragment, including the leading `#`.
234 pub(crate) fn render(&self, s
: &mut String
, tcx
: TyCtxt
<'_
>) {
237 &UrlFragment
::Item(def_id
) => {
238 let kind
= match tcx
.def_kind(def_id
) {
239 DefKind
::AssocFn
=> {
240 if tcx
.impl_defaultness(def_id
).has_value() {
246 DefKind
::AssocConst
=> "associatedconstant.",
247 DefKind
::AssocTy
=> "associatedtype.",
248 DefKind
::Variant
=> "variant.",
250 let parent_id
= tcx
.parent(def_id
);
251 if tcx
.def_kind(parent_id
) == DefKind
::Variant
{
252 s
.push_str("variant.");
253 s
.push_str(tcx
.item_name(parent_id
).as_str());
259 kind
=> bug
!("unexpected associated item kind: {:?}", kind
),
262 s
.push_str(tcx
.item_name(def_id
).as_str());
264 UrlFragment
::UserWritten(raw
) => s
.push_str(&raw
),
269 #[derive(Clone, Debug, Hash, PartialEq, Eq)]
270 struct ResolutionInfo
{
273 dis
: Option
<Disambiguator
>,
275 extra_fragment
: Option
<String
>,
279 struct DiagnosticInfo
<'a
> {
283 link_range
: Range
<usize>,
286 struct LinkCollector
<'a
, 'tcx
> {
287 cx
: &'a
mut DocContext
<'tcx
>,
288 /// A stack of modules used to decide what scope to resolve in.
290 /// The last module will be used if the parent scope of the current item is
293 /// Cache the resolved links so we can avoid resolving (and emitting errors for) the same link.
294 /// The link will be `None` if it could not be resolved (i.e. the error was cached).
295 visited_links
: FxHashMap
<ResolutionInfo
, Option
<(Res
, Option
<UrlFragment
>)>>,
298 impl<'a
, 'tcx
> LinkCollector
<'a
, 'tcx
> {
299 /// Given a full link, parse it as an [enum struct variant].
301 /// In particular, this will return an error whenever there aren't three
302 /// full path segments left in the link.
304 /// [enum struct variant]: rustc_hir::VariantData::Struct
305 fn variant_field
<'path
>(
307 path_str
: &'path
str,
310 ) -> Result
<(Res
, DefId
), UnresolvedPath
<'path
>> {
311 let tcx
= self.cx
.tcx
;
312 let no_res
= || UnresolvedPath
{
316 unresolved
: path_str
.into(),
319 debug
!("looking for enum variant {}", path_str
);
320 let mut split
= path_str
.rsplitn(3, "::");
321 let variant_field_name
= split
323 .map(|f
| Symbol
::intern(f
))
324 .expect("fold_item should ensure link is non-empty");
326 // we're not sure this is a variant at all, so use the full string
327 // If there's no second component, the link looks like `[path]`.
328 // So there's no partial res and we should say the whole link failed to resolve.
329 split
.next().map(|f
| Symbol
::intern(f
)).ok_or_else(no_res
)?
;
332 .map(|f
| f
.to_owned())
333 // If there's no third component, we saw `[a::b]` before and it failed to resolve.
334 // So there's no partial res.
335 .ok_or_else(no_res
)?
;
336 let ty_res
= self.resolve_path(&path
, TypeNS
, item_id
, module_id
).ok_or_else(no_res
)?
;
339 Res
::Def(DefKind
::Enum
, did
) => match tcx
.type_of(did
).kind() {
340 ty
::Adt(def
, _
) if def
.is_enum() => {
341 if let Some(field
) = def
.all_fields().find(|f
| f
.name
== variant_field_name
) {
342 Ok((ty_res
, field
.did
))
347 partial_res
: Some(Res
::Def(DefKind
::Enum
, def
.did())),
348 unresolved
: variant_field_name
.to_string().into(),
354 _
=> Err(UnresolvedPath
{
357 partial_res
: Some(ty_res
),
358 unresolved
: variant_name
.to_string().into(),
363 /// Given a primitive type, try to resolve an associated item.
364 fn resolve_primitive_associated_item(
366 prim_ty
: PrimitiveType
,
369 ) -> Option
<(Res
, DefId
)> {
370 let tcx
= self.cx
.tcx
;
372 prim_ty
.impls(tcx
).find_map(|impl_
| {
373 tcx
.associated_items(impl_
)
374 .find_by_name_and_namespace(tcx
, Ident
::with_dummy_span(item_name
), ns
, impl_
)
375 .map(|item
| (Res
::Primitive(prim_ty
), item
.def_id
))
379 fn resolve_self_ty(&self, path_str
: &str, ns
: Namespace
, item_id
: ItemId
) -> Option
<Res
> {
380 if ns
!= TypeNS
|| path_str
!= "Self" {
384 let tcx
= self.cx
.tcx
;
387 .map(|def_id
| match tcx
.def_kind(def_id
) {
388 def_kind @
(DefKind
::AssocFn
389 | DefKind
::AssocConst
392 | DefKind
::Field
) => {
393 let parent_def_id
= tcx
.parent(def_id
);
394 if def_kind
== DefKind
::Field
&& tcx
.def_kind(parent_def_id
) == DefKind
::Variant
396 tcx
.parent(parent_def_id
)
403 .and_then(|self_id
| match tcx
.def_kind(self_id
) {
404 DefKind
::Impl
=> self.def_id_to_res(self_id
),
405 def_kind
=> Some(Res
::Def(def_kind
, self_id
)),
409 /// Convenience wrapper around `resolve_rustdoc_path`.
411 /// This also handles resolving `true` and `false` as booleans.
412 /// NOTE: `resolve_rustdoc_path` knows only about paths, not about types.
413 /// Associated items will never be resolved by this function.
421 if let res @
Some(..) = self.resolve_self_ty(path_str
, ns
, item_id
) {
425 // Resolver doesn't know about true, false, and types that aren't paths (e.g. `()`).
429 .doc_link_resolutions
430 .get(&(Symbol
::intern(path_str
), ns
, module_id
))
433 self.cx
.enter_resolver(|resolver
| {
435 ParentScope
::module(resolver
.expect_module(module_id
), resolver
);
436 resolver
.resolve_rustdoc_path(path_str
, ns
, parent_scope
)
439 .and_then(|res
| res
.try_into().ok())
440 .or_else(|| resolve_primitive(path_str
, ns
));
441 debug
!("{} resolved to {:?} in namespace {:?}", path_str
, result
, ns
);
445 /// Resolves a string as a path within a particular namespace. Returns an
446 /// optional URL fragment in the case of variants and methods.
449 path_str
: &'path
str,
453 ) -> Result
<(Res
, Option
<DefId
>), UnresolvedPath
<'path
>> {
454 if let Some(res
) = self.resolve_path(path_str
, ns
, item_id
, module_id
) {
455 return Ok(match res
{
457 DefKind
::AssocFn
| DefKind
::AssocConst
| DefKind
::AssocTy
| DefKind
::Variant
,
459 ) => (Res
::from_def_id(self.cx
.tcx
, self.cx
.tcx
.parent(def_id
)), Some(def_id
)),
462 } else if ns
== MacroNS
{
463 return Err(UnresolvedPath
{
467 unresolved
: path_str
.into(),
471 // Try looking for methods and associated items.
472 let mut split
= path_str
.rsplitn(2, "::");
473 // NB: `split`'s first element is always defined, even if the delimiter was not present.
474 // NB: `item_str` could be empty when resolving in the root namespace (e.g. `::std`).
475 let item_str
= split
.next().unwrap();
476 let item_name
= Symbol
::intern(item_str
);
477 let path_root
= split
479 .map(|f
| f
.to_owned())
480 // If there's no `::`, it's not an associated item.
481 // So we can be sure that `rustc_resolve` was accurate when it said it wasn't resolved.
483 debug
!("found no `::`, assuming {} was correctly not in scope", item_name
);
488 unresolved
: item_str
.into(),
492 // FIXME(#83862): this arbitrarily gives precedence to primitives over modules to support
493 // links to primitives when `#[doc(primitive)]` is present. It should give an ambiguity
494 // error instead and special case *only* modules with `#[doc(primitive)]`, not all
496 resolve_primitive(&path_root
, TypeNS
)
497 .or_else(|| self.resolve_path(&path_root
, TypeNS
, item_id
, module_id
))
499 self.resolve_associated_item(ty_res
, item_name
, ns
, module_id
).map(Ok
)
502 if ns
== Namespace
::ValueNS
{
503 self.variant_field(path_str
, item_id
, module_id
)
509 unresolved
: path_root
.into(),
513 .map(|(res
, def_id
)| (res
, Some(def_id
)))
516 /// Convert a DefId to a Res, where possible.
518 /// This is used for resolving type aliases.
519 fn def_id_to_res(&self, ty_id
: DefId
) -> Option
<Res
> {
520 use PrimitiveType
::*;
521 Some(match *self.cx
.tcx
.type_of(ty_id
).kind() {
522 ty
::Bool
=> Res
::Primitive(Bool
),
523 ty
::Char
=> Res
::Primitive(Char
),
524 ty
::Int(ity
) => Res
::Primitive(ity
.into()),
525 ty
::Uint(uty
) => Res
::Primitive(uty
.into()),
526 ty
::Float(fty
) => Res
::Primitive(fty
.into()),
527 ty
::Str
=> Res
::Primitive(Str
),
528 ty
::Tuple(tys
) if tys
.is_empty() => Res
::Primitive(Unit
),
529 ty
::Tuple(_
) => Res
::Primitive(Tuple
),
530 ty
::Array(..) => Res
::Primitive(Array
),
531 ty
::Slice(_
) => Res
::Primitive(Slice
),
532 ty
::RawPtr(_
) => Res
::Primitive(RawPointer
),
533 ty
::Ref(..) => Res
::Primitive(Reference
),
534 ty
::FnDef(..) => panic
!("type alias to a function definition"),
535 ty
::FnPtr(_
) => Res
::Primitive(Fn
),
536 ty
::Never
=> Res
::Primitive(Never
),
537 ty
::Adt(ty
::AdtDef(Interned(&ty
::AdtDefData { did, .. }
, _
)), _
) | ty
::Foreign(did
) => {
538 Res
::from_def_id(self.cx
.tcx
, did
)
543 | ty
::GeneratorWitness(_
)
550 | ty
::Error(_
) => return None
,
554 /// Convert a PrimitiveType to a Ty, where possible.
556 /// This is used for resolving trait impls for primitives
557 fn primitive_type_to_ty(&mut self, prim
: PrimitiveType
) -> Option
<Ty
<'tcx
>> {
558 use PrimitiveType
::*;
559 let tcx
= self.cx
.tcx
;
561 // FIXME: Only simple types are supported here, see if we can support
562 // other types such as Tuple, Array, Slice, etc.
563 // See https://github.com/rust-lang/rust/issues/90703#issuecomment-1004263455
564 Some(tcx
.mk_ty(match prim
{
569 I8
=> ty
::Int(ty
::IntTy
::I8
),
570 I16
=> ty
::Int(ty
::IntTy
::I16
),
571 I32
=> ty
::Int(ty
::IntTy
::I32
),
572 I64
=> ty
::Int(ty
::IntTy
::I64
),
573 I128
=> ty
::Int(ty
::IntTy
::I128
),
574 Isize
=> ty
::Int(ty
::IntTy
::Isize
),
575 F32
=> ty
::Float(ty
::FloatTy
::F32
),
576 F64
=> ty
::Float(ty
::FloatTy
::F64
),
577 U8
=> ty
::Uint(ty
::UintTy
::U8
),
578 U16
=> ty
::Uint(ty
::UintTy
::U16
),
579 U32
=> ty
::Uint(ty
::UintTy
::U32
),
580 U64
=> ty
::Uint(ty
::UintTy
::U64
),
581 U128
=> ty
::Uint(ty
::UintTy
::U128
),
582 Usize
=> ty
::Uint(ty
::UintTy
::Usize
),
587 /// Resolve an associated item, returning its containing page's `Res`
588 /// and the fragment targeting the associated item on its page.
589 fn resolve_associated_item(
595 ) -> Option
<(Res
, DefId
)> {
596 let tcx
= self.cx
.tcx
;
599 Res
::Primitive(prim
) => {
600 self.resolve_primitive_associated_item(prim
, ns
, item_name
).or_else(|| {
601 self.primitive_type_to_ty(prim
)
603 resolve_associated_trait_item(ty
, module_id
, item_name
, ns
, self.cx
)
605 .map(|item
| (root_res
, item
.def_id
))
608 Res
::Def(DefKind
::TyAlias
, did
) => {
609 // Resolve the link on the type the alias points to.
610 // FIXME: if the associated item is defined directly on the type alias,
611 // it will show up on its documentation page, we should link there instead.
612 let res
= self.def_id_to_res(did
)?
;
613 self.resolve_associated_item(res
, item_name
, ns
, module_id
)
616 def_kind @
(DefKind
::Struct
| DefKind
::Union
| DefKind
::Enum
| DefKind
::ForeignTy
),
619 debug
!("looking for associated item named {} for item {:?}", item_name
, did
);
620 // Checks if item_name is a variant of the `SomeItem` enum
621 if ns
== TypeNS
&& def_kind
== DefKind
::Enum
{
622 match tcx
.type_of(did
).kind() {
623 ty
::Adt(adt_def
, _
) => {
624 for variant
in adt_def
.variants() {
625 if variant
.name
== item_name
{
626 return Some((root_res
, variant
.def_id
));
634 // Checks if item_name belongs to `impl SomeItem`
639 tcx
.associated_items(imp
).find_by_name_and_namespace(
641 Ident
::with_dummy_span(item_name
),
647 // There should only ever be one associated item that matches from any inherent impl
649 // Check if item_name belongs to `impl SomeTrait for SomeItem`
650 // FIXME(#74563): This gives precedence to `impl SomeItem`:
651 // Although having both would be ambiguous, use impl version for compatibility's sake.
652 // To handle that properly resolve() would have to support
653 // something like [`ambi_fn`](<SomeStruct as SomeTrait>::ambi_fn)
655 resolve_associated_trait_item(
664 debug
!("got associated item {:?}", assoc_item
);
666 if let Some(item
) = assoc_item
{
667 return Some((root_res
, item
.def_id
));
670 if ns
!= Namespace
::ValueNS
{
673 debug
!("looking for fields named {} for {:?}", item_name
, did
);
674 // FIXME: this doesn't really belong in `associated_item` (maybe `variant_field` is better?)
675 // NOTE: it's different from variant_field because it only resolves struct fields,
676 // not variant fields (2 path segments, not 3).
678 // We need to handle struct (and union) fields in this code because
679 // syntactically their paths are identical to associated item paths:
680 // `module::Type::field` and `module::Type::Assoc`.
682 // On the other hand, variant fields can't be mistaken for associated
683 // items because they look like this: `module::Type::Variant::field`.
685 // Variants themselves don't need to be handled here, even though
686 // they also look like associated items (`module::Type::Variant`),
687 // because they are real Rust syntax (unlike the intra-doc links
688 // field syntax) and are handled by the compiler's resolver.
689 let def
= match tcx
.type_of(did
).kind() {
690 ty
::Adt(def
, _
) if !def
.is_enum() => def
,
694 def
.non_enum_variant().fields
.iter().find(|item
| item
.name
== item_name
)?
;
695 Some((root_res
, field
.did
))
697 Res
::Def(DefKind
::Trait
, did
) => tcx
698 .associated_items(did
)
699 .find_by_name_and_namespace(tcx
, Ident
::with_dummy_span(item_name
), ns
, did
)
701 let res
= Res
::Def(item
.kind
.as_def_kind(), item
.def_id
);
709 fn full_res(tcx
: TyCtxt
<'_
>, (base
, assoc_item
): (Res
, Option
<DefId
>)) -> Res
{
710 assoc_item
.map_or(base
, |def_id
| Res
::from_def_id(tcx
, def_id
))
713 /// Look to see if a resolved item has an associated item named `item_name`.
715 /// Given `[std::io::Error::source]`, where `source` is unresolved, this would
716 /// find `std::error::Error::source` and return
717 /// `<io::Error as error::Error>::source`.
718 fn resolve_associated_trait_item
<'a
>(
723 cx
: &mut DocContext
<'a
>,
724 ) -> Option
<ty
::AssocItem
> {
725 // FIXME: this should also consider blanket impls (`impl<T> X for T`). Unfortunately
726 // `get_auto_trait_and_blanket_impls` is broken because the caching behavior is wrong. In the
727 // meantime, just don't look for these blanket impls.
729 // Next consider explicit impls: `impl MyTrait for MyType`
730 // Give precedence to inherent impls.
731 let traits
= trait_impls_for(cx
, ty
, module
);
732 debug
!("considering traits {:?}", traits
);
733 let mut candidates
= traits
.iter().filter_map(|&(impl_
, trait_
)| {
735 .associated_items(trait_
)
736 .find_by_name_and_namespace(cx
.tcx
, Ident
::with_dummy_span(item_name
), ns
, trait_
)
738 trait_assoc_to_impl_assoc_item(cx
.tcx
, impl_
, trait_assoc
.def_id
)
739 .unwrap_or(trait_assoc
)
742 // FIXME(#74563): warn about ambiguity
743 debug
!("the candidates were {:?}", candidates
.clone().collect
::<Vec
<_
>>());
744 candidates
.next().copied()
747 /// Find the associated item in the impl `impl_id` that corresponds to the
748 /// trait associated item `trait_assoc_id`.
750 /// This function returns `None` if no associated item was found in the impl.
751 /// This can occur when the trait associated item has a default value that is
752 /// not overridden in the impl.
754 /// This is just a wrapper around [`TyCtxt::impl_item_implementor_ids()`] and
755 /// [`TyCtxt::associated_item()`] (with some helpful logging added).
756 #[instrument(level = "debug", skip(tcx), ret)]
757 fn trait_assoc_to_impl_assoc_item
<'tcx
>(
760 trait_assoc_id
: DefId
,
761 ) -> Option
<&'tcx ty
::AssocItem
> {
762 let trait_to_impl_assoc_map
= tcx
.impl_item_implementor_ids(impl_id
);
763 debug
!(?trait_to_impl_assoc_map
);
764 let impl_assoc_id
= *trait_to_impl_assoc_map
.get(&trait_assoc_id
)?
;
765 debug
!(?impl_assoc_id
);
766 Some(tcx
.associated_item(impl_assoc_id
))
769 /// Given a type, return all trait impls in scope in `module` for that type.
770 /// Returns a set of pairs of `(impl_id, trait_id)`.
772 /// NOTE: this cannot be a query because more traits could be available when more crates are compiled!
773 /// So it is not stable to serialize cross-crate.
774 #[instrument(level = "debug", skip(cx))]
775 fn trait_impls_for
<'a
>(
776 cx
: &mut DocContext
<'a
>,
779 ) -> FxHashSet
<(DefId
, DefId
)> {
781 let iter
= cx
.resolver_caches
.traits_in_scope
[&module
].iter().flat_map(|trait_candidate
| {
782 let trait_
= trait_candidate
.def_id
;
783 trace
!("considering explicit impl for trait {:?}", trait_
);
785 // Look at each trait implementation to see if it's an impl for `did`
786 tcx
.find_map_relevant_impl(trait_
, ty
, |impl_
| {
787 let trait_ref
= tcx
.impl_trait_ref(impl_
).expect("this is not an inherent impl");
788 // Check if these are the same type.
789 let impl_type
= trait_ref
.self_ty();
791 "comparing type {} with kind {:?} against type {:?}",
796 // Fast path: if this is a primitive simple `==` will work
797 // NOTE: the `match` is necessary; see #92662.
798 // this allows us to ignore generics because the user input
799 // may not include the generic placeholders
800 // e.g. this allows us to match Foo (user comment) with Foo<T> (actual type)
801 let saw_impl
= impl_type
== ty
802 || match (impl_type
.kind(), ty
.kind()) {
803 (ty
::Adt(impl_def
, _
), ty
::Adt(ty_def
, _
)) => {
804 debug
!("impl def_id: {:?}, ty def_id: {:?}", impl_def
.did(), ty_def
.did());
805 impl_def
.did() == ty_def
.did()
810 if saw_impl { Some((impl_, trait_)) }
else { None }
816 /// Check for resolve collisions between a trait and its derive.
818 /// These are common and we should just resolve to the trait in that case.
819 fn is_derive_trait_collision
<T
>(ns
: &PerNS
<Result
<(Res
, T
), ResolutionFailure
<'_
>>>) -> bool
{
823 type_ns
: Ok((Res
::Def(DefKind
::Trait
, _
), _
)),
824 macro_ns
: Ok((Res
::Def(DefKind
::Macro(MacroKind
::Derive
), _
), _
)),
830 impl<'a
, 'tcx
> DocVisitor
for LinkCollector
<'a
, 'tcx
> {
831 fn visit_item(&mut self, item
: &Item
) {
833 item
.item_id
.as_def_id().and_then(|did
| find_nearest_parent_module(self.cx
.tcx
, did
));
834 if parent_node
.is_some() {
835 trace
!("got parent node for {:?} {:?}, id {:?}", item
.type_(), item
.name
, item
.item_id
);
838 let inner_docs
= item
.inner_docs(self.cx
.tcx
);
840 if item
.is_mod() && inner_docs
{
841 self.mod_ids
.push(item
.item_id
.expect_def_id());
844 // We want to resolve in the lexical scope of the documentation.
845 // In the presence of re-exports, this is not the same as the module of the item.
846 // Rather than merging all documentation into one, resolve it one attribute at a time
847 // so we know which module it came from.
848 for (parent_module
, doc
) in item
.attrs
.prepare_to_doc_link_resolution() {
849 if !may_have_doc_links(&doc
) {
852 debug
!("combined_docs={}", doc
);
853 // NOTE: if there are links that start in one crate and end in another, this will not resolve them.
854 // This is a degenerate case and it's not supported by rustdoc.
855 let parent_node
= parent_module
.or(parent_node
);
856 let mut tmp_links
= self
861 .expect("`markdown_links` are already borrowed");
862 if !tmp_links
.contains_key(&doc
) {
863 tmp_links
.insert(doc
.clone(), preprocessed_markdown_links(&doc
));
865 for md_link
in &tmp_links
[&doc
] {
866 let link
= self.resolve_link(item
, &doc
, parent_node
, md_link
);
867 if let Some(link
) = link
{
868 self.cx
.cache
.intra_doc_links
.entry(item
.item_id
).or_default().push(link
);
871 self.cx
.resolver_caches
.markdown_links
= Some(tmp_links
);
876 self.mod_ids
.push(item
.item_id
.expect_def_id());
879 self.visit_item_recur(item
);
882 self.visit_item_recur(item
)
887 enum PreprocessingError
{
888 /// User error: `[std#x#y]` is not valid
890 Disambiguator(Range
<usize>, String
),
891 MalformedGenerics(MalformedGenerics
, String
),
894 impl PreprocessingError
{
895 fn report(&self, cx
: &DocContext
<'_
>, diag_info
: DiagnosticInfo
<'_
>) {
897 PreprocessingError
::MultipleAnchors
=> report_multiple_anchors(cx
, diag_info
),
898 PreprocessingError
::Disambiguator(range
, msg
) => {
899 disambiguator_error(cx
, diag_info
, range
.clone(), msg
)
901 PreprocessingError
::MalformedGenerics(err
, path_str
) => {
902 report_malformed_generics(cx
, diag_info
, *err
, path_str
)
909 struct PreprocessingInfo
{
911 disambiguator
: Option
<Disambiguator
>,
912 extra_fragment
: Option
<String
>,
916 // Not a typedef to avoid leaking several private structures from this module.
917 pub(crate) struct PreprocessedMarkdownLink(
918 Result
<PreprocessingInfo
, PreprocessingError
>,
923 /// - `None` if the link should be ignored.
924 /// - `Some(Err)` if the link should emit an error
925 /// - `Some(Ok)` if the link is valid
927 /// `link_buffer` is needed for lifetime reasons; it will always be overwritten and the contents ignored.
929 ori_link
: &MarkdownLink
,
930 ) -> Option
<Result
<PreprocessingInfo
, PreprocessingError
>> {
931 // [] is mostly likely not supposed to be a link
932 if ori_link
.link
.is_empty() {
936 // Bail early for real links.
937 if ori_link
.link
.contains('
/'
) {
941 let stripped
= ori_link
.link
.replace('`'
, "");
942 let mut parts
= stripped
.split('
#');
944 let link
= parts
.next().unwrap();
945 if link
.trim().is_empty() {
946 // This is an anchor to an element of the current page, nothing to do in here!
949 let extra_fragment
= parts
.next();
950 if parts
.next().is_some() {
951 // A valid link can't have multiple #'s
952 return Some(Err(PreprocessingError
::MultipleAnchors
));
955 // Parse and strip the disambiguator from the link, if present.
956 let (disambiguator
, path_str
, link_text
) = match Disambiguator
::from_str(link
) {
957 Ok(Some((d
, path
, link_text
))) => (Some(d
), path
.trim(), link_text
.trim()),
958 Ok(None
) => (None
, link
.trim(), link
.trim()),
959 Err((err_msg
, relative_range
)) => {
960 // Only report error if we would not have ignored this link. See issue #83859.
961 if !should_ignore_link_with_disambiguators(link
) {
962 let no_backticks_range
= range_between_backticks(ori_link
);
963 let disambiguator_range
= (no_backticks_range
.start
+ relative_range
.start
)
964 ..(no_backticks_range
.start
+ relative_range
.end
);
965 return Some(Err(PreprocessingError
::Disambiguator(disambiguator_range
, err_msg
)));
972 if should_ignore_link(path_str
) {
976 // Strip generics from the path.
977 let path_str
= if path_str
.contains(['
<'
, '
>'
].as_slice()) {
978 match strip_generics_from_path(path_str
) {
981 debug
!("link has malformed generics: {}", path_str
);
982 return Some(Err(PreprocessingError
::MalformedGenerics(err
, path_str
.to_owned())));
989 // Sanity check to make sure we don't have any angle brackets after stripping generics.
990 assert
!(!path_str
.contains(['
<'
, '
>'
].as_slice()));
992 // The link is not an intra-doc link if it still contains spaces after stripping generics.
993 if path_str
.contains(' '
) {
997 Some(Ok(PreprocessingInfo
{
1000 extra_fragment
: extra_fragment
.map(|frag
| frag
.to_owned()),
1001 link_text
: link_text
.to_owned(),
1005 fn preprocessed_markdown_links(s
: &str) -> Vec
<PreprocessedMarkdownLink
> {
1006 markdown_links(s
, |link
| {
1007 preprocess_link(&link
).map(|pp_link
| PreprocessedMarkdownLink(pp_link
, link
))
1011 impl LinkCollector
<'_
, '_
> {
1012 /// This is the entry point for resolving an intra-doc link.
1014 /// FIXME(jynelson): this is way too many arguments
1019 parent_node
: Option
<DefId
>,
1020 link
: &PreprocessedMarkdownLink
,
1021 ) -> Option
<ItemLink
> {
1022 let PreprocessedMarkdownLink(pp_link
, ori_link
) = link
;
1023 trace
!("considering link '{}'", ori_link
.link
);
1025 let diag_info
= DiagnosticInfo
{
1028 ori_link
: &ori_link
.link
,
1029 link_range
: ori_link
.range
.clone(),
1032 let PreprocessingInfo { path_str, disambiguator, extra_fragment, link_text }
=
1033 pp_link
.as_ref().map_err(|err
| err
.report(self.cx
, diag_info
.clone())).ok()?
;
1034 let disambiguator
= *disambiguator
;
1036 // In order to correctly resolve intra-doc links we need to
1037 // pick a base AST node to work from. If the documentation for
1038 // this module came from an inner comment (//!) then we anchor
1039 // our name resolution *inside* the module. If, on the other
1040 // hand it was an outer comment (///) then we anchor the name
1041 // resolution in the parent module on the basis that the names
1042 // used are more likely to be intended to be parent names. For
1043 // this, we set base_node to None for inner comments since
1044 // we've already pushed this node onto the resolution stack but
1045 // for outer comments we explicitly try and resolve against the
1046 // parent_node first.
1047 let inner_docs
= item
.inner_docs(self.cx
.tcx
);
1049 if item
.is_mod() && inner_docs { self.mod_ids.last().copied() }
else { parent_node }
;
1050 let module_id
= base_node
.expect("doc link without parent module");
1052 let (mut res
, fragment
) = self.resolve_with_disambiguator_cached(
1054 item_id
: item
.item_id
,
1057 path_str
: path_str
.to_owned(),
1058 extra_fragment
: extra_fragment
.clone(),
1060 diag_info
.clone(), // this struct should really be Copy, but Range is not :(
1061 // For reference-style links we want to report only one error so unsuccessful
1062 // resolutions are cached, for other links we want to report an error every
1063 // time so they are not cached.
1064 matches
!(ori_link
.kind
, LinkType
::Reference
| LinkType
::Shortcut
),
1067 // Check for a primitive which might conflict with a module
1068 // Report the ambiguity and require that the user specify which one they meant.
1069 // FIXME: could there ever be a primitive not in the type namespace?
1072 None
| Some(Disambiguator
::Namespace(Namespace
::TypeNS
) | Disambiguator
::Primitive
)
1073 ) && !matches
!(res
, Res
::Primitive(_
))
1075 if let Some(prim
) = resolve_primitive(path_str
, TypeNS
) {
1077 if matches
!(disambiguator
, Some(Disambiguator
::Primitive
)) {
1080 // `[char]` when a `char` module is in scope
1081 let candidates
= vec
![res
, prim
];
1082 ambiguity_error(self.cx
, diag_info
, path_str
, candidates
);
1089 Res
::Primitive(prim
) => {
1090 if let Some(UrlFragment
::Item(id
)) = fragment
{
1091 // We're actually resolving an associated item of a primitive, so we need to
1092 // verify the disambiguator (if any) matches the type of the associated item.
1093 // This case should really follow the same flow as the `Res::Def` branch below,
1094 // but attempting to add a call to `clean::register_res` causes an ICE. @jyn514
1095 // thinks `register_res` is only needed for cross-crate re-exports, but Rust
1096 // doesn't allow statements like `use str::trim;`, making this a (hopefully)
1097 // valid omission. See https://github.com/rust-lang/rust/pull/80660#discussion_r551585677
1098 // for discussion on the matter.
1099 let kind
= self.cx
.tcx
.def_kind(id
);
1100 self.verify_disambiguator(
1110 // FIXME: it would be nice to check that the feature gate was enabled in the original crate, not just ignore it altogether.
1111 // However I'm not sure how to check that across crates.
1112 if prim
== PrimitiveType
::RawPointer
1113 && item
.item_id
.is_local()
1114 && !self.cx
.tcx
.features().intra_doc_pointers
1116 self.report_rawptr_assoc_feature_gate(dox
, ori_link
, item
);
1119 match disambiguator
{
1120 Some(Disambiguator
::Primitive
| Disambiguator
::Namespace(_
)) | None
=> {}
1122 self.report_disambiguator_mismatch(
1123 path_str
, ori_link
, other
, res
, &diag_info
,
1130 res
.def_id(self.cx
.tcx
).map(|page_id
| ItemLink
{
1131 link
: ori_link
.link
.clone(),
1132 link_text
: link_text
.clone(),
1137 Res
::Def(kind
, id
) => {
1138 let (kind_for_dis
, id_for_dis
) = if let Some(UrlFragment
::Item(id
)) = fragment
{
1139 (self.cx
.tcx
.def_kind(id
), id
)
1143 self.verify_disambiguator(
1153 let page_id
= clean
::register_res(self.cx
, rustc_hir
::def
::Res
::Def(kind
, id
));
1155 link
: ori_link
.link
.clone(),
1156 link_text
: link_text
.clone(),
1164 fn verify_disambiguator(
1167 ori_link
: &MarkdownLink
,
1170 disambiguator
: Option
<Disambiguator
>,
1172 diag_info
: &DiagnosticInfo
<'_
>,
1174 debug
!("intra-doc link to {} resolved to {:?}", path_str
, (kind
, id
));
1176 // Disallow e.g. linking to enums with `struct@`
1177 debug
!("saw kind {:?} with disambiguator {:?}", kind
, disambiguator
);
1178 match (kind
, disambiguator
) {
1179 | (DefKind
::Const
| DefKind
::ConstParam
| DefKind
::AssocConst
| DefKind
::AnonConst
, Some(Disambiguator
::Kind(DefKind
::Const
)))
1180 // NOTE: this allows 'method' to mean both normal functions and associated functions
1181 // This can't cause ambiguity because both are in the same namespace.
1182 | (DefKind
::Fn
| DefKind
::AssocFn
, Some(Disambiguator
::Kind(DefKind
::Fn
)))
1183 // These are namespaces; allow anything in the namespace to match
1184 | (_
, Some(Disambiguator
::Namespace(_
)))
1185 // If no disambiguator given, allow anything
1187 // All of these are valid, so do nothing
1189 (actual
, Some(Disambiguator
::Kind(expected
))) if actual
== expected
=> {}
1190 (_
, Some(specified @ Disambiguator
::Kind(_
) | specified @ Disambiguator
::Primitive
)) => {
1191 self.report_disambiguator_mismatch(path_str
,ori_link
,specified
, Res
::Def(kind
, id
),diag_info
);
1196 // item can be non-local e.g. when using #[doc(primitive = "pointer")]
1197 if let Some((src_id
, dst_id
)) = id
1199 // The `expect_def_id()` should be okay because `local_def_id_to_hir_id`
1200 // would presumably panic if a fake `DefIndex` were passed.
1201 .and_then(|dst_id
| {
1202 item
.item_id
.expect_def_id().as_local().map(|src_id
| (src_id
, dst_id
))
1205 if self.cx
.tcx
.effective_visibilities(()).is_exported(src_id
)
1206 && !self.cx
.tcx
.effective_visibilities(()).is_exported(dst_id
)
1208 privacy_error(self.cx
, diag_info
, path_str
);
1215 fn report_disambiguator_mismatch(
1218 ori_link
: &MarkdownLink
,
1219 specified
: Disambiguator
,
1221 diag_info
: &DiagnosticInfo
<'_
>,
1223 // The resolved item did not match the disambiguator; give a better error than 'not found'
1224 let msg
= format
!("incompatible link kind for `{}`", path_str
);
1225 let callback
= |diag
: &mut Diagnostic
, sp
: Option
<rustc_span
::Span
>| {
1227 "this link resolved to {} {}, which is not {} {}",
1230 specified
.article(),
1233 if let Some(sp
) = sp
{
1234 diag
.span_label(sp
, ¬e
);
1238 suggest_disambiguator(resolved
, diag
, path_str
, &ori_link
.link
, sp
);
1240 report_diagnostic(self.cx
.tcx
, BROKEN_INTRA_DOC_LINKS
, &msg
, diag_info
, callback
);
1243 fn report_rawptr_assoc_feature_gate(&self, dox
: &str, ori_link
: &MarkdownLink
, item
: &Item
) {
1245 super::source_span_for_markdown_range(self.cx
.tcx
, dox
, &ori_link
.range
, &item
.attrs
)
1246 .unwrap_or_else(|| item
.attr_span(self.cx
.tcx
));
1247 rustc_session
::parse
::feature_err(
1248 &self.cx
.tcx
.sess
.parse_sess
,
1249 sym
::intra_doc_pointers
,
1251 "linking to associated items of raw pointers is experimental",
1253 .note("rustdoc does not allow disambiguating between `*const` and `*mut`, and pointers are unstable until it does")
1257 fn resolve_with_disambiguator_cached(
1259 key
: ResolutionInfo
,
1260 diag
: DiagnosticInfo
<'_
>,
1261 // If errors are cached then they are only reported on first occurrence
1262 // which we want in some cases but not in others.
1264 ) -> Option
<(Res
, Option
<UrlFragment
>)> {
1265 if let Some(res
) = self.visited_links
.get(&key
) {
1266 if res
.is_some() || cache_errors
{
1271 let res
= self.resolve_with_disambiguator(&key
, diag
.clone()).and_then(|(res
, def_id
)| {
1272 let fragment
= match (&key
.extra_fragment
, def_id
) {
1273 (Some(_
), Some(def_id
)) => {
1274 report_anchor_conflict(self.cx
, diag
, def_id
);
1277 (Some(u_frag
), None
) => Some(UrlFragment
::UserWritten(u_frag
.clone())),
1278 (None
, Some(def_id
)) => Some(UrlFragment
::Item(def_id
)),
1279 (None
, None
) => None
,
1281 Some((res
, fragment
))
1284 if res
.is_some() || cache_errors
{
1285 self.visited_links
.insert(key
, res
.clone());
1290 /// After parsing the disambiguator, resolve the main part of the link.
1291 // FIXME(jynelson): wow this is just so much
1292 fn resolve_with_disambiguator(
1294 key
: &ResolutionInfo
,
1295 diag
: DiagnosticInfo
<'_
>,
1296 ) -> Option
<(Res
, Option
<DefId
>)> {
1297 let disambiguator
= key
.dis
;
1298 let path_str
= &key
.path_str
;
1299 let item_id
= key
.item_id
;
1300 let base_node
= key
.module_id
;
1302 match disambiguator
.map(Disambiguator
::ns
) {
1303 Some(expected_ns
) => {
1304 match self.resolve(path_str
, expected_ns
, item_id
, base_node
) {
1305 Ok(res
) => Some(res
),
1307 // We only looked in one namespace. Try to give a better error if possible.
1308 // FIXME: really it should be `resolution_failure` that does this, not `resolve_with_disambiguator`.
1309 // See https://github.com/rust-lang/rust/pull/76955#discussion_r493953382 for a good approach.
1310 let mut err
= ResolutionFailure
::NotResolved(err
);
1311 for other_ns
in [TypeNS
, ValueNS
, MacroNS
] {
1312 if other_ns
!= expected_ns
{
1314 self.resolve(path_str
, other_ns
, item_id
, base_node
)
1316 err
= ResolutionFailure
::WrongNamespace
{
1317 res
: full_res(self.cx
.tcx
, res
),
1324 resolution_failure(self, diag
, path_str
, disambiguator
, smallvec
![err
])
1330 let mut candidate
= |ns
| {
1331 self.resolve(path_str
, ns
, item_id
, base_node
)
1332 .map_err(ResolutionFailure
::NotResolved
)
1335 let candidates
= PerNS
{
1336 macro_ns
: candidate(MacroNS
),
1337 type_ns
: candidate(TypeNS
),
1338 value_ns
: candidate(ValueNS
).and_then(|(res
, def_id
)| {
1340 // Constructors are picked up in the type namespace.
1341 Res
::Def(DefKind
::Ctor(..), _
) => {
1342 Err(ResolutionFailure
::WrongNamespace { res, expected_ns: TypeNS }
)
1344 _
=> Ok((res
, def_id
)),
1349 let len
= candidates
.iter().filter(|res
| res
.is_ok()).count();
1352 return resolution_failure(
1357 candidates
.into_iter().filter_map(|res
| res
.err()).collect(),
1362 Some(candidates
.into_iter().find_map(|res
| res
.ok()).unwrap())
1363 } else if len
== 2 && is_derive_trait_collision(&candidates
) {
1364 Some(candidates
.type_ns
.unwrap())
1366 let ignore_macro
= is_derive_trait_collision(&candidates
);
1367 // If we're reporting an ambiguity, don't mention the namespaces that failed
1368 let mut candidates
=
1369 candidates
.map(|candidate
| candidate
.ok().map(|(res
, _
)| res
));
1371 candidates
.macro_ns
= None
;
1373 ambiguity_error(self.cx
, diag
, path_str
, candidates
.present_items().collect());
1381 /// Get the section of a link between the backticks,
1382 /// or the whole link if there aren't any backticks.
1390 fn range_between_backticks(ori_link
: &MarkdownLink
) -> Range
<usize> {
1391 let after_first_backtick_group
= ori_link
.link
.bytes().position(|b
| b
!= b'`'
).unwrap_or(0);
1392 let before_second_backtick_group
= ori_link
1395 .skip(after_first_backtick_group
)
1396 .position(|b
| b
== b'`'
)
1397 .unwrap_or(ori_link
.link
.len());
1398 (ori_link
.range
.start
+ after_first_backtick_group
)
1399 ..(ori_link
.range
.start
+ before_second_backtick_group
)
1402 /// Returns true if we should ignore `link` due to it being unlikely
1403 /// that it is an intra-doc link. `link` should still have disambiguators
1404 /// if there were any.
1406 /// The difference between this and [`should_ignore_link()`] is that this
1407 /// check should only be used on links that still have disambiguators.
1408 fn should_ignore_link_with_disambiguators(link
: &str) -> bool
{
1409 link
.contains(|ch
: char| !(ch
.is_alphanumeric() || ":_<>, !*&;@()".contains(ch
)))
1412 /// Returns true if we should ignore `path_str` due to it being unlikely
1413 /// that it is an intra-doc link.
1414 fn should_ignore_link(path_str
: &str) -> bool
{
1415 path_str
.contains(|ch
: char| !(ch
.is_alphanumeric() || ":_<>, !*&;".contains(ch
)))
1418 #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
1419 /// Disambiguators for a link.
1420 enum Disambiguator
{
1423 /// This is buggy, see <https://github.com/rust-lang/rust/pull/77875#discussion_r503583103>
1425 /// `struct@` or `f()`
1428 Namespace(Namespace
),
1431 impl Disambiguator
{
1432 /// Given a link, parse and return `(disambiguator, path_str, link_text)`.
1434 /// This returns `Ok(Some(...))` if a disambiguator was found,
1435 /// `Ok(None)` if no disambiguator was found, or `Err(...)`
1436 /// if there was a problem with the disambiguator.
1437 fn from_str(link
: &str) -> Result
<Option
<(Self, &str, &str)>, (String
, Range
<usize>)> {
1438 use Disambiguator
::{Kind, Namespace as NS, Primitive}
;
1440 if let Some(idx
) = link
.find('@'
) {
1441 let (prefix
, rest
) = link
.split_at(idx
);
1442 let d
= match prefix
{
1443 "struct" => Kind(DefKind
::Struct
),
1444 "enum" => Kind(DefKind
::Enum
),
1445 "trait" => Kind(DefKind
::Trait
),
1446 "union" => Kind(DefKind
::Union
),
1447 "module" | "mod" => Kind(DefKind
::Mod
),
1448 "const" | "constant" => Kind(DefKind
::Const
),
1449 "static" => Kind(DefKind
::Static(Mutability
::Not
)),
1450 "function" | "fn" | "method" => Kind(DefKind
::Fn
),
1451 "derive" => Kind(DefKind
::Macro(MacroKind
::Derive
)),
1452 "type" => NS(Namespace
::TypeNS
),
1453 "value" => NS(Namespace
::ValueNS
),
1454 "macro" => NS(Namespace
::MacroNS
),
1455 "prim" | "primitive" => Primitive
,
1456 _
=> return Err((format
!("unknown disambiguator `{}`", prefix
), 0..idx
)),
1458 Ok(Some((d
, &rest
[1..], &rest
[1..])))
1461 ("!()", DefKind
::Macro(MacroKind
::Bang
)),
1462 ("!{}", DefKind
::Macro(MacroKind
::Bang
)),
1463 ("![]", DefKind
::Macro(MacroKind
::Bang
)),
1464 ("()", DefKind
::Fn
),
1465 ("!", DefKind
::Macro(MacroKind
::Bang
)),
1467 for (suffix
, kind
) in suffixes
{
1468 if let Some(path_str
) = link
.strip_suffix(suffix
) {
1469 // Avoid turning `!` or `()` into an empty string
1470 if !path_str
.is_empty() {
1471 return Ok(Some((Kind(kind
), path_str
, link
)));
1479 fn ns(self) -> Namespace
{
1481 Self::Namespace(n
) => n
,
1483 k
.ns().expect("only DefKinds with a valid namespace can be disambiguators")
1485 Self::Primitive
=> TypeNS
,
1489 fn article(self) -> &'
static str {
1491 Self::Namespace(_
) => panic
!("article() doesn't make sense for namespaces"),
1492 Self::Kind(k
) => k
.article(),
1493 Self::Primitive
=> "a",
1497 fn descr(self) -> &'
static str {
1499 Self::Namespace(n
) => n
.descr(),
1500 // HACK(jynelson): the source of `DefKind::descr` only uses the DefId for
1501 // printing "module" vs "crate" so using the wrong ID is not a huge problem
1502 Self::Kind(k
) => k
.descr(CRATE_DEF_ID
.to_def_id()),
1503 Self::Primitive
=> "builtin type",
1508 /// A suggestion to show in a diagnostic.
1511 Prefix(&'
static str),
1516 /// `foo` without any disambiguator
1517 RemoveDisambiguator
,
1521 fn descr(&self) -> Cow
<'
static, str> {
1523 Self::Prefix(x
) => format
!("prefix with `{}@`", x
).into(),
1524 Self::Function
=> "add parentheses".into(),
1525 Self::Macro
=> "add an exclamation mark".into(),
1526 Self::RemoveDisambiguator
=> "remove the disambiguator".into(),
1530 fn as_help(&self, path_str
: &str) -> String
{
1531 // FIXME: if this is an implied shortcut link, it's bad style to suggest `@`
1533 Self::Prefix(prefix
) => format
!("{}@{}", prefix
, path_str
),
1534 Self::Function
=> format
!("{}()", path_str
),
1535 Self::Macro
=> format
!("{}!", path_str
),
1536 Self::RemoveDisambiguator
=> path_str
.into(),
1544 sp
: rustc_span
::Span
,
1545 ) -> Vec
<(rustc_span
::Span
, String
)> {
1546 let inner_sp
= match ori_link
.find('
('
) {
1547 Some(index
) => sp
.with_hi(sp
.lo() + BytePos(index
as _
)),
1550 let inner_sp
= match ori_link
.find('
!'
) {
1551 Some(index
) => inner_sp
.with_hi(inner_sp
.lo() + BytePos(index
as _
)),
1554 let inner_sp
= match ori_link
.find('@'
) {
1555 Some(index
) => inner_sp
.with_lo(inner_sp
.lo() + BytePos(index
as u32 + 1)),
1559 Self::Prefix(prefix
) => {
1560 // FIXME: if this is an implied shortcut link, it's bad style to suggest `@`
1561 let mut sugg
= vec
![(sp
.with_hi(inner_sp
.lo()), format
!("{}@", prefix
))];
1562 if sp
.hi() != inner_sp
.hi() {
1563 sugg
.push((inner_sp
.shrink_to_hi().with_hi(sp
.hi()), String
::new()));
1568 let mut sugg
= vec
![(inner_sp
.shrink_to_hi().with_hi(sp
.hi()), "()".to_string())];
1569 if sp
.lo() != inner_sp
.lo() {
1570 sugg
.push((inner_sp
.shrink_to_lo().with_lo(sp
.lo()), String
::new()));
1575 let mut sugg
= vec
![(inner_sp
.shrink_to_hi(), "!".to_string())];
1576 if sp
.lo() != inner_sp
.lo() {
1577 sugg
.push((inner_sp
.shrink_to_lo().with_lo(sp
.lo()), String
::new()));
1581 Self::RemoveDisambiguator
=> vec
![(sp
, path_str
.into())],
1586 /// Reports a diagnostic for an intra-doc link.
1588 /// If no link range is provided, or the source span of the link cannot be determined, the span of
1589 /// the entire documentation block is used for the lint. If a range is provided but the span
1590 /// calculation fails, a note is added to the diagnostic pointing to the link in the markdown.
1592 /// The `decorate` callback is invoked in all cases to allow further customization of the
1593 /// diagnostic before emission. If the span of the link was able to be determined, the second
1594 /// parameter of the callback will contain it, and the primary span of the diagnostic will be set
1596 fn report_diagnostic(
1598 lint
: &'
static Lint
,
1600 DiagnosticInfo { item, ori_link: _, dox, link_range }
: &DiagnosticInfo
<'_
>,
1601 decorate
: impl FnOnce(&mut Diagnostic
, Option
<rustc_span
::Span
>),
1603 let Some(hir_id
) = DocContext
::as_local_hir_id(tcx
, item
.item_id
)
1605 // If non-local, no need to check anything.
1606 info
!("ignoring warning from parent crate: {}", msg
);
1610 let sp
= item
.attr_span(tcx
);
1612 tcx
.struct_span_lint_hir(lint
, hir_id
, sp
, msg
, |lint
| {
1614 super::source_span_for_markdown_range(tcx
, dox
, link_range
, &item
.attrs
).map(|sp
| {
1615 if dox
.as_bytes().get(link_range
.start
) == Some(&b'`'
)
1616 && dox
.as_bytes().get(link_range
.end
- 1) == Some(&b'`'
)
1618 sp
.with_lo(sp
.lo() + BytePos(1)).with_hi(sp
.hi() - BytePos(1))
1624 if let Some(sp
) = span
{
1627 // blah blah blah\nblah\nblah [blah] blah blah\nblah blah
1630 // last_new_line_offset
1631 let last_new_line_offset
= dox
[..link_range
.start
].rfind('
\n'
).map_or(0, |n
| n
+ 1);
1632 let line
= dox
[last_new_line_offset
..].lines().next().unwrap_or("");
1634 // Print the line containing the `link_range` and manually mark it with '^'s.
1636 "the link appears in this line:\n\n{line}\n\
1637 {indicator: <before$}{indicator:^<found$}",
1640 before
= link_range
.start
- last_new_line_offset
,
1641 found
= link_range
.len(),
1645 decorate(lint
, span
);
1651 /// Reports a link that failed to resolve.
1653 /// This also tries to resolve any intermediate path segments that weren't
1654 /// handled earlier. For example, if passed `Item::Crate(std)` and `path_str`
1655 /// `std::io::Error::x`, this will resolve `std::io::Error`.
1656 fn resolution_failure(
1657 collector
: &mut LinkCollector
<'_
, '_
>,
1658 diag_info
: DiagnosticInfo
<'_
>,
1660 disambiguator
: Option
<Disambiguator
>,
1661 kinds
: SmallVec
<[ResolutionFailure
<'_
>; 3]>,
1662 ) -> Option
<(Res
, Option
<DefId
>)> {
1663 let tcx
= collector
.cx
.tcx
;
1664 let mut recovered_res
= None
;
1667 BROKEN_INTRA_DOC_LINKS
,
1668 &format
!("unresolved link to `{}`", path_str
),
1671 let item
= |res
: Res
| format
!("the {} `{}`", res
.descr(), res
.name(tcx
),);
1672 let assoc_item_not_allowed
= |res
: Res
| {
1673 let name
= res
.name(tcx
);
1675 "`{}` is {} {}, not a module or type, and cannot have associated items",
1681 // ignore duplicates
1682 let mut variants_seen
= SmallVec
::<[_
; 3]>::new();
1683 for mut failure
in kinds
{
1684 let variant
= std
::mem
::discriminant(&failure
);
1685 if variants_seen
.contains(&variant
) {
1688 variants_seen
.push(variant
);
1690 if let ResolutionFailure
::NotResolved(UnresolvedPath
{
1699 let item_id
= *item_id
;
1700 let module_id
= *module_id
;
1701 // FIXME(jynelson): this might conflict with my `Self` fix in #76467
1702 // FIXME: maybe use itertools `collect_tuple` instead?
1703 fn split(path
: &str) -> Option
<(&str, &str)> {
1704 let mut splitter
= path
.rsplitn(2, "::");
1705 splitter
.next().and_then(|right
| splitter
.next().map(|left
| (left
, right
)))
1708 // Check if _any_ parent of the path gets resolved.
1709 // If so, report it and say the first which failed; if not, say the first path segment didn't resolve.
1710 let mut name
= path_str
;
1712 let Some((start
, end
)) = split(name
) else {
1713 // avoid bug that marked [Quux::Z] as missing Z, not Quux
1714 if partial_res
.is_none() {
1715 *unresolved
= name
.into();
1720 for ns
in [TypeNS
, ValueNS
, MacroNS
] {
1721 if let Ok(res
) = collector
.resolve(start
, ns
, item_id
, module_id
) {
1722 debug
!("found partial_res={:?}", res
);
1723 *partial_res
= Some(full_res(collector
.cx
.tcx
, res
));
1724 *unresolved
= end
.into();
1728 *unresolved
= end
.into();
1731 let last_found_module
= match *partial_res
{
1732 Some(Res
::Def(DefKind
::Mod
, id
)) => Some(id
),
1733 None
=> Some(module_id
),
1736 // See if this was a module: `[path]` or `[std::io::nope]`
1737 if let Some(module
) = last_found_module
{
1738 let note
= if partial_res
.is_some() {
1739 // Part of the link resolved; e.g. `std::io::nonexistent`
1740 let module_name
= tcx
.item_name(module
);
1741 format
!("no item named `{}` in module `{}`", unresolved
, module_name
)
1743 // None of the link resolved; e.g. `Notimported`
1744 format
!("no item named `{}` in scope", unresolved
)
1746 if let Some(span
) = sp
{
1747 diag
.span_label(span
, ¬e
);
1752 if !path_str
.contains("::") {
1753 if disambiguator
.map_or(true, |d
| d
.ns() == MacroNS
)
1754 && let Some(&res
) = collector
.cx
.resolver_caches
.all_macro_rules
1755 .get(&Symbol
::intern(path_str
))
1758 "`macro_rules` named `{path_str}` exists in this crate, \
1759 but it is not in scope at this link's location"
1761 recovered_res
= res
.try_into().ok().map(|res
| (res
, None
));
1763 // If the link has `::` in it, assume it was meant to be an
1764 // intra-doc link. Otherwise, the `[]` might be unrelated.
1765 diag
.help("to escape `[` and `]` characters, \
1766 add '\\' before them like `\\[` or `\\]`");
1773 // Otherwise, it must be an associated item or variant
1774 let res
= partial_res
.expect("None case was handled by `last_found_module`");
1775 let name
= res
.name(tcx
);
1776 let kind
= match res
{
1777 Res
::Def(kind
, _
) => Some(kind
),
1778 Res
::Primitive(_
) => None
,
1780 let path_description
= if let Some(kind
) = kind
{
1782 Mod
| ForeignMod
=> "inner item",
1783 Struct
=> "field or associated item",
1784 Enum
| Union
=> "variant or associated item",
1802 let note
= assoc_item_not_allowed(res
);
1803 if let Some(span
) = sp
{
1804 diag
.span_label(span
, ¬e
);
1810 Trait
| TyAlias
| ForeignTy
| OpaqueTy
| ImplTraitPlaceholder
1811 | TraitAlias
| TyParam
| Static(_
) => "associated item",
1812 Impl
| GlobalAsm
=> unreachable
!("not a path"),
1818 "the {} `{}` has no {} named `{}`",
1821 disambiguator
.map_or(path_description
, |d
| d
.descr()),
1824 if let Some(span
) = sp
{
1825 diag
.span_label(span
, ¬e
);
1832 let note
= match failure
{
1833 ResolutionFailure
::NotResolved { .. }
=> unreachable
!("handled above"),
1834 ResolutionFailure
::WrongNamespace { res, expected_ns }
=> {
1835 suggest_disambiguator(res
, diag
, path_str
, diag_info
.ori_link
, sp
);
1838 "this link resolves to {}, which is not in the {} namespace",
1844 if let Some(span
) = sp
{
1845 diag
.span_label(span
, ¬e
);
1856 fn report_multiple_anchors(cx
: &DocContext
<'_
>, diag_info
: DiagnosticInfo
<'_
>) {
1857 let msg
= format
!("`{}` contains multiple anchors", diag_info
.ori_link
);
1858 anchor_failure(cx
, diag_info
, &msg
, 1)
1861 fn report_anchor_conflict(cx
: &DocContext
<'_
>, diag_info
: DiagnosticInfo
<'_
>, def_id
: DefId
) {
1862 let (link
, kind
) = (diag_info
.ori_link
, Res
::from_def_id(cx
.tcx
, def_id
).descr());
1863 let msg
= format
!("`{link}` contains an anchor, but links to {kind}s are already anchored");
1864 anchor_failure(cx
, diag_info
, &msg
, 0)
1867 /// Report an anchor failure.
1869 cx
: &DocContext
<'_
>,
1870 diag_info
: DiagnosticInfo
<'_
>,
1874 report_diagnostic(cx
.tcx
, BROKEN_INTRA_DOC_LINKS
, msg
, &diag_info
, |diag
, sp
| {
1875 if let Some(mut sp
) = sp
{
1876 if let Some((fragment_offset
, _
)) =
1877 diag_info
.ori_link
.char_indices().filter(|(_
, x
)| *x
== '
#').nth(anchor_idx)
1879 sp
= sp
.with_lo(sp
.lo() + BytePos(fragment_offset
as _
));
1881 diag
.span_label(sp
, "invalid anchor");
1886 /// Report an error in the link disambiguator.
1887 fn disambiguator_error(
1888 cx
: &DocContext
<'_
>,
1889 mut diag_info
: DiagnosticInfo
<'_
>,
1890 disambiguator_range
: Range
<usize>,
1893 diag_info
.link_range
= disambiguator_range
;
1894 report_diagnostic(cx
.tcx
, BROKEN_INTRA_DOC_LINKS
, msg
, &diag_info
, |diag
, _sp
| {
1896 "see {}/rustdoc/linking-to-items-by-name.html#namespaces-and-disambiguators for more info about disambiguators",
1897 crate::DOC_RUST_LANG_ORG_CHANNEL
1903 fn report_malformed_generics(
1904 cx
: &DocContext
<'_
>,
1905 diag_info
: DiagnosticInfo
<'_
>,
1906 err
: MalformedGenerics
,
1911 BROKEN_INTRA_DOC_LINKS
,
1912 &format
!("unresolved link to `{}`", path_str
),
1915 let note
= match err
{
1916 MalformedGenerics
::UnbalancedAngleBrackets
=> "unbalanced angle brackets",
1917 MalformedGenerics
::MissingType
=> "missing type for generic parameters",
1918 MalformedGenerics
::HasFullyQualifiedSyntax
=> {
1920 "see https://github.com/rust-lang/rust/issues/74563 for more information",
1922 "fully-qualified syntax is unsupported"
1924 MalformedGenerics
::InvalidPathSeparator
=> "has invalid path separator",
1925 MalformedGenerics
::TooManyAngleBrackets
=> "too many angle brackets",
1926 MalformedGenerics
::EmptyAngleBrackets
=> "empty angle brackets",
1928 if let Some(span
) = sp
{
1929 diag
.span_label(span
, note
);
1937 /// Report an ambiguity error, where there were multiple possible resolutions.
1939 cx
: &DocContext
<'_
>,
1940 diag_info
: DiagnosticInfo
<'_
>,
1942 candidates
: Vec
<Res
>,
1944 let mut msg
= format
!("`{}` is ", path_str
);
1946 match candidates
.as_slice() {
1947 [first_def
, second_def
] => {
1949 "both {} {} and {} {}",
1950 first_def
.article(),
1952 second_def
.article(),
1957 let mut candidates
= candidates
.iter().peekable();
1958 while let Some(res
) = candidates
.next() {
1959 if candidates
.peek().is_some() {
1960 msg
+= &format
!("{} {}, ", res
.article(), res
.descr());
1962 msg
+= &format
!("and {} {}", res
.article(), res
.descr());
1968 report_diagnostic(cx
.tcx
, BROKEN_INTRA_DOC_LINKS
, &msg
, &diag_info
, |diag
, sp
| {
1969 if let Some(sp
) = sp
{
1970 diag
.span_label(sp
, "ambiguous link");
1972 diag
.note("ambiguous link");
1975 for res
in candidates
{
1976 suggest_disambiguator(res
, diag
, path_str
, diag_info
.ori_link
, sp
);
1981 /// In case of an ambiguity or mismatched disambiguator, suggest the correct
1983 fn suggest_disambiguator(
1985 diag
: &mut Diagnostic
,
1988 sp
: Option
<rustc_span
::Span
>,
1990 let suggestion
= res
.disambiguator_suggestion();
1991 let help
= format
!("to link to the {}, {}", res
.descr(), suggestion
.descr());
1993 if let Some(sp
) = sp
{
1994 let mut spans
= suggestion
.as_help_span(path_str
, ori_link
, sp
);
1995 if spans
.len() > 1 {
1996 diag
.multipart_suggestion(&help
, spans
, Applicability
::MaybeIncorrect
);
1998 let (sp
, suggestion_text
) = spans
.pop().unwrap();
1999 diag
.span_suggestion_verbose(sp
, &help
, suggestion_text
, Applicability
::MaybeIncorrect
);
2002 diag
.help(&format
!("{}: {}", help
, suggestion
.as_help(path_str
)));
2006 /// Report a link from a public item to a private one.
2007 fn privacy_error(cx
: &DocContext
<'_
>, diag_info
: &DiagnosticInfo
<'_
>, path_str
: &str) {
2009 let item_name
= match diag_info
.item
.name
{
2014 None
=> "<unknown>",
2017 format
!("public documentation for `{}` links to private item `{}`", item_name
, path_str
);
2019 report_diagnostic(cx
.tcx
, PRIVATE_INTRA_DOC_LINKS
, &msg
, diag_info
, |diag
, sp
| {
2020 if let Some(sp
) = sp
{
2021 diag
.span_label(sp
, "this item is private");
2024 let note_msg
= if cx
.render_options
.document_private
{
2025 "this link resolves only because you passed `--document-private-items`, but will break without"
2027 "this link will resolve properly if you pass `--document-private-items`"
2029 diag
.note(note_msg
);
2033 /// Resolve a primitive type or value.
2034 fn resolve_primitive(path_str
: &str, ns
: Namespace
) -> Option
<Res
> {
2038 use PrimitiveType
::*;
2039 let prim
= match path_str
{
2055 "bool" | "true" | "false" => Bool
,
2056 "str" | "&str" => Str
,
2057 // See #80181 for why these don't have symbols associated.
2062 "pointer" | "*const" | "*mut" => RawPointer
,
2063 "reference" | "&" | "&mut" => Reference
,
2065 "never" | "!" => Never
,
2068 debug
!("resolved primitives {:?}", prim
);
2069 Some(Res
::Primitive(prim
))
2072 fn strip_generics_from_path(path_str
: &str) -> Result
<String
, MalformedGenerics
> {
2073 let mut stripped_segments
= vec
![];
2074 let mut path
= path_str
.chars().peekable();
2075 let mut segment
= Vec
::new();
2077 while let Some(chr
) = path
.next() {
2080 if path
.next_if_eq(&'
:'
).is_some() {
2081 let stripped_segment
=
2082 strip_generics_from_path_segment(mem
::take(&mut segment
))?
;
2083 if !stripped_segment
.is_empty() {
2084 stripped_segments
.push(stripped_segment
);
2087 return Err(MalformedGenerics
::InvalidPathSeparator
);
2095 return Err(MalformedGenerics
::TooManyAngleBrackets
);
2098 return Err(MalformedGenerics
::EmptyAngleBrackets
);
2103 while let Some(chr
) = path
.next_if(|c
| *c
!= '
>'
) {
2110 _
=> segment
.push(chr
),
2112 trace
!("raw segment: {:?}", segment
);
2115 if !segment
.is_empty() {
2116 let stripped_segment
= strip_generics_from_path_segment(segment
)?
;
2117 if !stripped_segment
.is_empty() {
2118 stripped_segments
.push(stripped_segment
);
2122 debug
!("path_str: {:?}\nstripped segments: {:?}", path_str
, &stripped_segments
);
2124 let stripped_path
= stripped_segments
.join("::");
2126 if !stripped_path
.is_empty() { Ok(stripped_path) }
else { Err(MalformedGenerics::MissingType) }
2129 fn strip_generics_from_path_segment(segment
: Vec
<char>) -> Result
<String
, MalformedGenerics
> {
2130 let mut stripped_segment
= String
::new();
2131 let mut param_depth
= 0;
2133 let mut latest_generics_chunk
= String
::new();
2138 latest_generics_chunk
.clear();
2139 } else if c
== '
>'
{
2141 if latest_generics_chunk
.contains(" as ") {
2142 // The segment tries to use fully-qualified syntax, which is currently unsupported.
2143 // Give a helpful error message instead of completely ignoring the angle brackets.
2144 return Err(MalformedGenerics
::HasFullyQualifiedSyntax
);
2147 if param_depth
== 0 {
2148 stripped_segment
.push(c
);
2150 latest_generics_chunk
.push(c
);
2155 if param_depth
== 0 {
2156 Ok(stripped_segment
)
2158 // The segment has unbalanced angle brackets, e.g. `Vec<T` or `Vec<T>>`
2159 Err(MalformedGenerics
::UnbalancedAngleBrackets
)