1 use rustc
::lint
as lint
;
3 use rustc
::hir
::def
::Def
;
4 use rustc
::hir
::def_id
::DefId
;
7 use syntax
::ast
::{self, Ident, NodeId}
;
8 use syntax
::feature_gate
::UnstableFeatures
;
9 use syntax
::symbol
::Symbol
;
10 use syntax_pos
::DUMMY_SP
;
14 use crate::core
::DocContext
;
15 use crate::fold
::DocFolder
;
16 use crate::html
::markdown
::markdown_links
;
18 use crate::passes
::{look_for_tests, Pass}
;
20 use super::span_of_attrs
;
22 pub const COLLECT_INTRA_DOC_LINKS
: Pass
=
23 Pass
::early("collect-intra-doc-links", collect_intra_doc_links
,
24 "reads a crate's documentation to resolve intra-doc-links");
26 pub fn collect_intra_doc_links(krate
: Crate
, cx
: &DocContext
<'_
, '_
, '_
>) -> Crate
{
27 if !UnstableFeatures
::from_environment().is_nightly_build() {
30 let mut coll
= LinkCollector
::new(cx
);
32 coll
.fold_crate(krate
)
38 /// Either a value or type, but not a macro
42 /// Values, functions, consts, statics (everything in the value namespace)
44 /// Types, traits (everything in the type namespace)
48 struct LinkCollector
<'a
, 'tcx
: 'a
, 'rcx
: 'a
> {
49 cx
: &'a DocContext
<'a
, 'tcx
, 'rcx
>,
51 is_nightly_build
: bool
,
54 impl<'a
, 'tcx
, 'rcx
> LinkCollector
<'a
, 'tcx
, 'rcx
> {
55 fn new(cx
: &'a DocContext
<'a
, 'tcx
, 'rcx
>) -> Self {
59 is_nightly_build
: UnstableFeatures
::from_environment().is_nightly_build(),
63 /// Resolves a given string as a path, along with whether or not it is
64 /// in the value namespace. Also returns an optional URL fragment in the case
65 /// of variants and methods.
69 current_item
: &Option
<String
>,
70 parent_id
: Option
<NodeId
>)
71 -> Result
<(Def
, Option
<String
>), ()>
75 // In case we're in a module, try to resolve the relative
77 if let Some(id
) = parent_id
.or(self.mod_ids
.last().cloned()) {
78 // FIXME: `with_scope` requires the `NodeId` of a module.
79 let result
= cx
.resolver
.borrow_mut()
82 resolver
.resolve_str_path_error(DUMMY_SP
,
86 if let Ok(result
) = result
{
87 // In case this is a trait item, skip the
88 // early return and try looking for the trait.
89 let value
= match result
.def
{
90 Def
::Method(_
) | Def
::AssociatedConst(_
) => true,
91 Def
::AssociatedTy(_
) => false,
92 Def
::Variant(_
) => return handle_variant(cx
, result
.def
),
93 // Not a trait item; just return what we found.
94 _
=> return Ok((result
.def
, None
))
100 } else if let Some(prim
) = is_primitive(path_str
, is_val
) {
101 return Ok((prim
, Some(path_str
.to_owned())))
103 // If resolution failed, it may still be a method
104 // because methods are not handled by the resolver
105 // If so, bail when we're not looking for a value.
111 // Try looking for methods and associated items.
112 let mut split
= path_str
.rsplitn(2, "::");
113 let item_name
= if let Some(first
) = split
.next() {
119 let mut path
= if let Some(second
) = split
.next() {
125 if path
== "self" || path
== "Self" {
126 if let Some(name
) = current_item
.as_ref() {
130 if let Some(prim
) = is_primitive(&path
, false) {
131 let did
= primitive_impl(cx
, &path
).ok_or(())?
;
132 return cx
.tcx
.associated_items(did
)
133 .find(|item
| item
.ident
.name
== item_name
)
134 .and_then(|item
| match item
.kind
{
135 ty
::AssociatedKind
::Method
=> Some("method"),
138 .map(|out
| (prim
, Some(format
!("{}#{}.{}", path
, out
, item_name
))))
142 // FIXME: `with_scope` requires the `NodeId` of a module.
143 let ty
= cx
.resolver
.borrow_mut()
146 resolver
.resolve_str_path_error(DUMMY_SP
, &path
, false)
149 Def
::Struct(did
) | Def
::Union(did
) | Def
::Enum(did
) | Def
::TyAlias(did
) => {
150 let item
= cx
.tcx
.inherent_impls(did
)
152 .flat_map(|imp
| cx
.tcx
.associated_items(*imp
))
153 .find(|item
| item
.ident
.name
== item_name
);
154 if let Some(item
) = item
{
155 let out
= match item
.kind
{
156 ty
::AssociatedKind
::Method
if is_val
=> "method",
157 ty
::AssociatedKind
::Const
if is_val
=> "associatedconstant",
160 Ok((ty
.def
, Some(format
!("{}.{}", out
, item_name
))))
162 match cx
.tcx
.type_of(did
).sty
{
164 if let Some(item
) = if def
.is_enum() {
165 def
.all_fields().find(|item
| item
.ident
.name
== item_name
)
167 def
.non_enum_variant()
170 .find(|item
| item
.ident
.name
== item_name
)
173 Some(format
!("{}.{}",
189 let item
= cx
.tcx
.associated_item_def_ids(did
).iter()
190 .map(|item
| cx
.tcx
.associated_item(*item
))
191 .find(|item
| item
.ident
.name
== item_name
);
192 if let Some(item
) = item
{
193 let kind
= match item
.kind
{
194 ty
::AssociatedKind
::Const
if is_val
=> "associatedconstant",
195 ty
::AssociatedKind
::Type
if !is_val
=> "associatedtype",
196 ty
::AssociatedKind
::Method
if is_val
=> {
197 if item
.defaultness
.has_value() {
206 Ok((ty
.def
, Some(format
!("{}.{}", kind
, item_name
))))
219 impl<'a
, 'tcx
, 'rcx
> DocFolder
for LinkCollector
<'a
, 'tcx
, 'rcx
> {
220 fn fold_item(&mut self, mut item
: Item
) -> Option
<Item
> {
221 let item_node_id
= if item
.is_mod() {
222 if let Some(id
) = self.cx
.tcx
.hir().as_local_node_id(item
.def_id
) {
225 debug
!("attempting to fold on a non-local item: {:?}", item
);
226 return self.fold_item_recur(item
);
232 // FIXME: get the resolver to work with non-local resolve scopes.
233 let parent_node
= self.cx
.as_local_node_id(item
.def_id
).and_then(|node_id
| {
234 // FIXME: this fails hard for impls in non-module scope, but is necessary for the
235 // current `resolve()` implementation.
236 match self.cx
.tcx
.hir().get_module_parent_node(node_id
) {
237 id
if id
!= node_id
=> Some(id
),
242 if parent_node
.is_some() {
243 debug
!("got parent node for {} {:?}, id {:?}", item
.type_(), item
.name
, item
.def_id
);
246 let current_item
= match item
.inner
{
248 if item
.attrs
.inner_docs
{
249 if item_node_id
.unwrap() != NodeId
::from_u32(0) {
255 match parent_node
.or(self.mod_ids
.last().cloned()) {
256 Some(parent
) if parent
!= NodeId
::from_u32(0) => {
257 // FIXME: can we pull the parent module's name from elsewhere?
258 Some(self.cx
.tcx
.hir().name(parent
).to_string())
264 ImplItem(Impl { ref for_, .. }
) => {
265 for_
.def_id().map(|did
| self.cx
.tcx
.item_name(did
).to_string())
267 // we don't display docs on `extern crate` items anyway, so don't process them.
268 ExternCrateItem(..) => return self.fold_item_recur(item
),
269 ImportItem(Import
::Simple(ref name
, ..)) => Some(name
.clone()),
270 MacroItem(..) => None
,
271 _
=> item
.name
.clone(),
274 if item
.is_mod() && item
.attrs
.inner_docs
{
275 self.mod_ids
.push(item_node_id
.unwrap());
279 let dox
= item
.attrs
.collapsed_doc_value().unwrap_or_else(String
::new
);
281 look_for_tests(&cx
, &dox
, &item
, true);
283 if !self.is_nightly_build
{
287 for (ori_link
, link_range
) in markdown_links(&dox
) {
288 // Bail early for real links.
289 if ori_link
.contains('
/'
) {
292 let link
= ori_link
.replace("`", "");
293 let (def
, fragment
) = {
294 let mut kind
= PathKind
::Unknown
;
295 let path_str
= if let Some(prefix
) =
296 ["struct@", "enum@", "type@",
297 "trait@", "union@"].iter()
298 .find(|p
| link
.starts_with(**p
)) {
299 kind
= PathKind
::Type
;
300 link
.trim_start_matches(prefix
)
301 } else if let Some(prefix
) =
302 ["const@", "static@",
303 "value@", "function@", "mod@",
304 "fn@", "module@", "method@"]
305 .iter().find(|p
| link
.starts_with(**p
)) {
306 kind
= PathKind
::Value
;
307 link
.trim_start_matches(prefix
)
308 } else if link
.ends_with("()") {
309 kind
= PathKind
::Value
;
310 link
.trim_end_matches("()")
311 } else if link
.starts_with("macro@") {
312 kind
= PathKind
::Macro
;
313 link
.trim_start_matches("macro@")
314 } else if link
.ends_with('
!'
) {
315 kind
= PathKind
::Macro
;
316 link
.trim_end_matches('
!'
)
321 if path_str
.contains(|ch
: char| !(ch
.is_alphanumeric() ||
322 ch
== '
:'
|| ch
== '_'
)) {
328 if let Ok(def
) = self.resolve(path_str
, true, ¤t_item
, parent_node
) {
331 resolution_failure(cx
, &item
.attrs
, path_str
, &dox
, link_range
);
332 // This could just be a normal link or a broken link
333 // we could potentially check if something is
334 // "intra-doc-link-like" and warn in that case.
339 if let Ok(def
) = self.resolve(path_str
, false, ¤t_item
, parent_node
) {
342 resolution_failure(cx
, &item
.attrs
, path_str
, &dox
, link_range
);
343 // This could just be a normal link.
347 PathKind
::Unknown
=> {
349 if let Some(macro_def
) = macro_resolve(cx
, path_str
) {
350 if let Ok(type_def
) =
351 self.resolve(path_str
, false, ¤t_item
, parent_node
)
353 let (type_kind
, article
, type_disambig
)
354 = type_ns_kind(type_def
.0, path_str
);
355 ambiguity_error(cx
, &item
.attrs
, path_str
,
356 article
, type_kind
, &type_disambig
,
357 "a", "macro", &format
!("macro@{}", path_str
));
359 } else if let Ok(value_def
) =
360 self.resolve(path_str
, true, ¤t_item
, parent_node
)
362 let (value_kind
, value_disambig
)
363 = value_ns_kind(value_def
.0, path_str
)
364 .expect("struct and mod cases should have been \
365 caught in previous branch");
366 ambiguity_error(cx
, &item
.attrs
, path_str
,
367 "a", value_kind
, &value_disambig
,
368 "a", "macro", &format
!("macro@{}", path_str
));
371 } else if let Ok(type_def
) =
372 self.resolve(path_str
, false, ¤t_item
, parent_node
)
374 // It is imperative we search for not-a-value first
375 // Otherwise we will find struct ctors for when we are looking
376 // for structs, and the link won't work if there is something in
378 if let Ok(value_def
) =
379 self.resolve(path_str
, true, ¤t_item
, parent_node
)
381 let kind
= value_ns_kind(value_def
.0, path_str
);
382 if let Some((value_kind
, value_disambig
)) = kind
{
383 let (type_kind
, article
, type_disambig
)
384 = type_ns_kind(type_def
.0, path_str
);
385 ambiguity_error(cx
, &item
.attrs
, path_str
,
386 article
, type_kind
, &type_disambig
,
387 "a", value_kind
, &value_disambig
);
392 } else if let Ok(value_def
) =
393 self.resolve(path_str
, true, ¤t_item
, parent_node
)
397 resolution_failure(cx
, &item
.attrs
, path_str
, &dox
, link_range
);
398 // this could just be a normal link
403 if let Some(def
) = macro_resolve(cx
, path_str
) {
406 resolution_failure(cx
, &item
.attrs
, path_str
, &dox
, link_range
);
413 if let Def
::PrimTy(_
) = def
{
414 item
.attrs
.links
.push((ori_link
, None
, fragment
));
416 let id
= register_def(cx
, def
);
417 item
.attrs
.links
.push((ori_link
, Some(id
), fragment
));
421 if item
.is_mod() && !item
.attrs
.inner_docs
{
422 self.mod_ids
.push(item_node_id
.unwrap());
426 let ret
= self.fold_item_recur(item
);
432 self.fold_item_recur(item
)
437 /// Resolves a string as a macro.
438 fn macro_resolve(cx
: &DocContext
<'_
, '_
, '_
>, path_str
: &str) -> Option
<Def
> {
439 use syntax
::ext
::base
::{MacroKind, SyntaxExtension}
;
440 let segment
= ast
::PathSegment
::from_ident(Ident
::from_str(path_str
));
441 let path
= ast
::Path { segments: vec![segment], span: DUMMY_SP }
;
442 let mut resolver
= cx
.resolver
.borrow_mut();
443 let parent_scope
= resolver
.dummy_parent_scope();
444 if let Ok(def
) = resolver
.resolve_macro_to_def_inner(&path
, MacroKind
::Bang
,
445 &parent_scope
, false, false) {
446 if let Def
::Macro(_
, MacroKind
::ProcMacroStub
) = def
{
447 // skip proc-macro stubs, they'll cause `get_macro` to crash
449 if let SyntaxExtension
::DeclMacro { .. }
= *resolver
.get_macro(def
) {
454 if let Some(def
) = resolver
.all_macros
.get(&Symbol
::intern(path_str
)) {
460 /// Reports a resolution failure diagnostic.
462 /// If we cannot find the exact source span of the resolution failure, we use the span of the
463 /// documentation attributes themselves. This is a little heavy-handed, so we display the markdown
464 /// line containing the failure as a note as well.
465 fn resolution_failure(
466 cx
: &DocContext
<'_
, '_
, '_
>,
470 link_range
: Option
<Range
<usize>>,
472 let sp
= span_of_attrs(attrs
);
474 let mut diag
= cx
.tcx
.struct_span_lint_node(
475 lint
::builtin
::INTRA_DOC_LINK_RESOLUTION_FAILURE
,
478 &format
!("`[{}]` cannot be resolved, ignoring it...", path_str
),
480 if let Some(link_range
) = link_range
{
481 if let Some(sp
) = super::source_span_for_markdown_range(cx
, dox
, &link_range
, attrs
) {
483 diag
.span_label(sp
, "cannot be resolved, ignoring");
485 // blah blah blah\nblah\nblah [blah] blah blah\nblah blah
488 // last_new_line_offset
489 let last_new_line_offset
= dox
[..link_range
.start
].rfind('
\n'
).map_or(0, |n
| n
+ 1);
490 let line
= dox
[last_new_line_offset
..].lines().next().unwrap_or("");
492 // Print the line containing the `link_range` and manually mark it with '^'s.
494 "the link appears in this line:\n\n{line}\n\
495 {indicator: <before$}{indicator:^<found$}",
498 before
=link_range
.start
- last_new_line_offset
,
499 found
=link_range
.len(),
503 diag
.help("to escape `[` and `]` characters, just add '\\' before them like \
508 fn ambiguity_error(cx
: &DocContext
<'_
, '_
, '_
>, attrs
: &Attributes
,
510 article1
: &str, kind1
: &str, disambig1
: &str,
511 article2
: &str, kind2
: &str, disambig2
: &str) {
512 let sp
= span_of_attrs(attrs
);
514 .struct_span_warn(sp
,
515 &format
!("`{}` is both {} {} and {} {}",
516 path_str
, article1
, kind1
,
518 .help(&format
!("try `{}` if you want to select the {}, \
519 or `{}` if you want to \
521 disambig1
, kind1
, disambig2
,
526 /// Given a def, returns its name and disambiguator
527 /// for a value namespace.
529 /// Returns `None` for things which cannot be ambiguous since
530 /// they exist in both namespaces (structs and modules).
531 fn value_ns_kind(def
: Def
, path_str
: &str) -> Option
<(&'
static str, String
)> {
533 // Structs, variants, and mods exist in both namespaces; skip them.
534 Def
::StructCtor(..) | Def
::Mod(..) | Def
::Variant(..) |
535 Def
::VariantCtor(..) | Def
::SelfCtor(..)
538 => Some(("function", format
!("{}()", path_str
))),
540 => Some(("method", format
!("{}()", path_str
))),
542 => Some(("const", format
!("const@{}", path_str
))),
544 => Some(("static", format
!("static@{}", path_str
))),
545 _
=> Some(("value", format
!("value@{}", path_str
))),
549 /// Given a def, returns its name, the article to be used, and a disambiguator
550 /// for the type namespace.
551 fn type_ns_kind(def
: Def
, path_str
: &str) -> (&'
static str, &'
static str, String
) {
552 let (kind
, article
) = match def
{
553 // We can still have non-tuple structs.
554 Def
::Struct(..) => ("struct", "a"),
555 Def
::Enum(..) => ("enum", "an"),
556 Def
::Trait(..) => ("trait", "a"),
557 Def
::Union(..) => ("union", "a"),
560 (kind
, article
, format
!("{}@{}", kind
, path_str
))
563 /// Given an enum variant's def, return the def of its enum and the associated fragment.
564 fn handle_variant(cx
: &DocContext
<'_
, '_
, '_
>, def
: Def
) -> Result
<(Def
, Option
<String
>), ()> {
565 use rustc
::ty
::DefIdTree
;
567 let parent
= if let Some(parent
) = cx
.tcx
.parent(def
.def_id()) {
572 let parent_def
= Def
::Enum(parent
);
573 let variant
= cx
.tcx
.expect_variant_def(def
);
574 Ok((parent_def
, Some(format
!("{}.v", variant
.ident
.name
))))
577 const PRIMITIVES
: &[(&str, Def
)] = &[
578 ("u8", Def
::PrimTy(hir
::PrimTy
::Uint(syntax
::ast
::UintTy
::U8
))),
579 ("u16", Def
::PrimTy(hir
::PrimTy
::Uint(syntax
::ast
::UintTy
::U16
))),
580 ("u32", Def
::PrimTy(hir
::PrimTy
::Uint(syntax
::ast
::UintTy
::U32
))),
581 ("u64", Def
::PrimTy(hir
::PrimTy
::Uint(syntax
::ast
::UintTy
::U64
))),
582 ("u128", Def
::PrimTy(hir
::PrimTy
::Uint(syntax
::ast
::UintTy
::U128
))),
583 ("usize", Def
::PrimTy(hir
::PrimTy
::Uint(syntax
::ast
::UintTy
::Usize
))),
584 ("i8", Def
::PrimTy(hir
::PrimTy
::Int(syntax
::ast
::IntTy
::I8
))),
585 ("i16", Def
::PrimTy(hir
::PrimTy
::Int(syntax
::ast
::IntTy
::I16
))),
586 ("i32", Def
::PrimTy(hir
::PrimTy
::Int(syntax
::ast
::IntTy
::I32
))),
587 ("i64", Def
::PrimTy(hir
::PrimTy
::Int(syntax
::ast
::IntTy
::I64
))),
588 ("i128", Def
::PrimTy(hir
::PrimTy
::Int(syntax
::ast
::IntTy
::I128
))),
589 ("isize", Def
::PrimTy(hir
::PrimTy
::Int(syntax
::ast
::IntTy
::Isize
))),
590 ("f32", Def
::PrimTy(hir
::PrimTy
::Float(syntax
::ast
::FloatTy
::F32
))),
591 ("f64", Def
::PrimTy(hir
::PrimTy
::Float(syntax
::ast
::FloatTy
::F64
))),
592 ("str", Def
::PrimTy(hir
::PrimTy
::Str
)),
593 ("bool", Def
::PrimTy(hir
::PrimTy
::Bool
)),
594 ("char", Def
::PrimTy(hir
::PrimTy
::Char
)),
597 fn is_primitive(path_str
: &str, is_val
: bool
) -> Option
<Def
> {
601 PRIMITIVES
.iter().find(|x
| x
.0 == path_str
).map(|x
| x
.1)
605 fn primitive_impl(cx
: &DocContext
<'_
, '_
, '_
>, path_str
: &str) -> Option
<DefId
> {
608 "u8" => tcx
.lang_items().u8_impl(),
609 "u16" => tcx
.lang_items().u16_impl(),
610 "u32" => tcx
.lang_items().u32_impl(),
611 "u64" => tcx
.lang_items().u64_impl(),
612 "u128" => tcx
.lang_items().u128_impl(),
613 "usize" => tcx
.lang_items().usize_impl(),
614 "i8" => tcx
.lang_items().i8_impl(),
615 "i16" => tcx
.lang_items().i16_impl(),
616 "i32" => tcx
.lang_items().i32_impl(),
617 "i64" => tcx
.lang_items().i64_impl(),
618 "i128" => tcx
.lang_items().i128_impl(),
619 "isize" => tcx
.lang_items().isize_impl(),
620 "f32" => tcx
.lang_items().f32_impl(),
621 "f64" => tcx
.lang_items().f64_impl(),
622 "str" => tcx
.lang_items().str_impl(),
623 "char" => tcx
.lang_items().char_impl(),