Rollup merge of #109312 - petrochenkov:docice5, r=GuillaumeGomez
rustdoc: Cleanup parent module tracking for doc links Keep ids of the documented items themselves, not their parent modules. Parent modules can be retreived from those ids when necessary. Fixes https://github.com/rust-lang/rust/issues/108501. That issue could be fixed in a more local way, but this refactoring is something that I wanted to do since https://github.com/rust-lang/rust/pull/93805 anyway.
This commit is contained in:
commit
af3bd22783
10 changed files with 123 additions and 185 deletions
|
@ -26,11 +26,13 @@ pub enum DocFragmentKind {
|
||||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||||
pub struct DocFragment {
|
pub struct DocFragment {
|
||||||
pub span: Span,
|
pub span: Span,
|
||||||
/// The module this doc-comment came from.
|
/// The item this doc-comment came from.
|
||||||
///
|
/// Used to determine the scope in which doc links in this fragment are resolved.
|
||||||
/// This allows distinguishing between the original documentation and a pub re-export.
|
/// Typically filled for reexport docs when they are merged into the docs of the
|
||||||
/// If it is `None`, the item was not re-exported.
|
/// original reexported item.
|
||||||
pub parent_module: Option<DefId>,
|
/// If the id is not filled, which happens for the original reexported item, then
|
||||||
|
/// it has to be taken from somewhere else during doc link resolution.
|
||||||
|
pub item_id: Option<DefId>,
|
||||||
pub doc: Symbol,
|
pub doc: Symbol,
|
||||||
pub kind: DocFragmentKind,
|
pub kind: DocFragmentKind,
|
||||||
pub indent: usize,
|
pub indent: usize,
|
||||||
|
@ -186,7 +188,7 @@ pub fn attrs_to_doc_fragments<'a>(
|
||||||
) -> (Vec<DocFragment>, ast::AttrVec) {
|
) -> (Vec<DocFragment>, ast::AttrVec) {
|
||||||
let mut doc_fragments = Vec::new();
|
let mut doc_fragments = Vec::new();
|
||||||
let mut other_attrs = ast::AttrVec::new();
|
let mut other_attrs = ast::AttrVec::new();
|
||||||
for (attr, parent_module) in attrs {
|
for (attr, item_id) in attrs {
|
||||||
if let Some((doc_str, comment_kind)) = attr.doc_str_and_comment_kind() {
|
if let Some((doc_str, comment_kind)) = attr.doc_str_and_comment_kind() {
|
||||||
let doc = beautify_doc_string(doc_str, comment_kind);
|
let doc = beautify_doc_string(doc_str, comment_kind);
|
||||||
let kind = if attr.is_doc_comment() {
|
let kind = if attr.is_doc_comment() {
|
||||||
|
@ -194,7 +196,7 @@ pub fn attrs_to_doc_fragments<'a>(
|
||||||
} else {
|
} else {
|
||||||
DocFragmentKind::RawDoc
|
DocFragmentKind::RawDoc
|
||||||
};
|
};
|
||||||
let fragment = DocFragment { span: attr.span, doc, kind, parent_module, indent: 0 };
|
let fragment = DocFragment { span: attr.span, doc, kind, item_id, indent: 0 };
|
||||||
doc_fragments.push(fragment);
|
doc_fragments.push(fragment);
|
||||||
} else if !doc_only {
|
} else if !doc_only {
|
||||||
other_attrs.push(attr.clone());
|
other_attrs.push(attr.clone());
|
||||||
|
@ -216,7 +218,7 @@ pub fn prepare_to_doc_link_resolution(
|
||||||
) -> FxHashMap<Option<DefId>, String> {
|
) -> FxHashMap<Option<DefId>, String> {
|
||||||
let mut res = FxHashMap::default();
|
let mut res = FxHashMap::default();
|
||||||
for fragment in doc_fragments {
|
for fragment in doc_fragments {
|
||||||
let out_str = res.entry(fragment.parent_module).or_default();
|
let out_str = res.entry(fragment.item_id).or_default();
|
||||||
add_doc_fragment(out_str, fragment);
|
add_doc_fragment(out_str, fragment);
|
||||||
}
|
}
|
||||||
res
|
res
|
||||||
|
|
|
@ -36,15 +36,11 @@ use crate::formats::item_type::ItemType;
|
||||||
///
|
///
|
||||||
/// The returned value is `None` if the definition could not be inlined,
|
/// The returned value is `None` if the definition could not be inlined,
|
||||||
/// and `Some` of a vector of items if it was successfully expanded.
|
/// and `Some` of a vector of items if it was successfully expanded.
|
||||||
///
|
|
||||||
/// `parent_module` refers to the parent of the *re-export*, not the original item.
|
|
||||||
pub(crate) fn try_inline(
|
pub(crate) fn try_inline(
|
||||||
cx: &mut DocContext<'_>,
|
cx: &mut DocContext<'_>,
|
||||||
parent_module: DefId,
|
|
||||||
import_def_id: Option<DefId>,
|
|
||||||
res: Res,
|
res: Res,
|
||||||
name: Symbol,
|
name: Symbol,
|
||||||
attrs: Option<&[ast::Attribute]>,
|
attrs: Option<(&[ast::Attribute], Option<DefId>)>,
|
||||||
visited: &mut DefIdSet,
|
visited: &mut DefIdSet,
|
||||||
) -> Option<Vec<clean::Item>> {
|
) -> Option<Vec<clean::Item>> {
|
||||||
let did = res.opt_def_id()?;
|
let did = res.opt_def_id()?;
|
||||||
|
@ -55,38 +51,17 @@ pub(crate) fn try_inline(
|
||||||
|
|
||||||
debug!("attrs={:?}", attrs);
|
debug!("attrs={:?}", attrs);
|
||||||
|
|
||||||
let attrs_without_docs = attrs.map(|attrs| {
|
let attrs_without_docs = attrs.map(|(attrs, def_id)| {
|
||||||
attrs.into_iter().filter(|a| a.doc_str().is_none()).cloned().collect::<Vec<_>>()
|
(attrs.into_iter().filter(|a| a.doc_str().is_none()).cloned().collect::<Vec<_>>(), def_id)
|
||||||
});
|
});
|
||||||
// We need this ugly code because:
|
let attrs_without_docs =
|
||||||
//
|
attrs_without_docs.as_ref().map(|(attrs, def_id)| (&attrs[..], *def_id));
|
||||||
// ```
|
|
||||||
// attrs_without_docs.map(|a| a.as_slice())
|
|
||||||
// ```
|
|
||||||
//
|
|
||||||
// will fail because it returns a temporary slice and:
|
|
||||||
//
|
|
||||||
// ```
|
|
||||||
// attrs_without_docs.map(|s| {
|
|
||||||
// vec = s.as_slice();
|
|
||||||
// vec
|
|
||||||
// })
|
|
||||||
// ```
|
|
||||||
//
|
|
||||||
// will fail because we're moving an uninitialized variable into a closure.
|
|
||||||
let vec;
|
|
||||||
let attrs_without_docs = match attrs_without_docs {
|
|
||||||
Some(s) => {
|
|
||||||
vec = s;
|
|
||||||
Some(vec.as_slice())
|
|
||||||
}
|
|
||||||
None => None,
|
|
||||||
};
|
|
||||||
|
|
||||||
|
let import_def_id = attrs.and_then(|(_, def_id)| def_id);
|
||||||
let kind = match res {
|
let kind = match res {
|
||||||
Res::Def(DefKind::Trait, did) => {
|
Res::Def(DefKind::Trait, did) => {
|
||||||
record_extern_fqn(cx, did, ItemType::Trait);
|
record_extern_fqn(cx, did, ItemType::Trait);
|
||||||
build_impls(cx, Some(parent_module), did, attrs_without_docs, &mut ret);
|
build_impls(cx, did, attrs_without_docs, &mut ret);
|
||||||
clean::TraitItem(Box::new(build_external_trait(cx, did)))
|
clean::TraitItem(Box::new(build_external_trait(cx, did)))
|
||||||
}
|
}
|
||||||
Res::Def(DefKind::Fn, did) => {
|
Res::Def(DefKind::Fn, did) => {
|
||||||
|
@ -95,27 +70,27 @@ pub(crate) fn try_inline(
|
||||||
}
|
}
|
||||||
Res::Def(DefKind::Struct, did) => {
|
Res::Def(DefKind::Struct, did) => {
|
||||||
record_extern_fqn(cx, did, ItemType::Struct);
|
record_extern_fqn(cx, did, ItemType::Struct);
|
||||||
build_impls(cx, Some(parent_module), did, attrs_without_docs, &mut ret);
|
build_impls(cx, did, attrs_without_docs, &mut ret);
|
||||||
clean::StructItem(build_struct(cx, did))
|
clean::StructItem(build_struct(cx, did))
|
||||||
}
|
}
|
||||||
Res::Def(DefKind::Union, did) => {
|
Res::Def(DefKind::Union, did) => {
|
||||||
record_extern_fqn(cx, did, ItemType::Union);
|
record_extern_fqn(cx, did, ItemType::Union);
|
||||||
build_impls(cx, Some(parent_module), did, attrs_without_docs, &mut ret);
|
build_impls(cx, did, attrs_without_docs, &mut ret);
|
||||||
clean::UnionItem(build_union(cx, did))
|
clean::UnionItem(build_union(cx, did))
|
||||||
}
|
}
|
||||||
Res::Def(DefKind::TyAlias, did) => {
|
Res::Def(DefKind::TyAlias, did) => {
|
||||||
record_extern_fqn(cx, did, ItemType::Typedef);
|
record_extern_fqn(cx, did, ItemType::Typedef);
|
||||||
build_impls(cx, Some(parent_module), did, attrs_without_docs, &mut ret);
|
build_impls(cx, did, attrs_without_docs, &mut ret);
|
||||||
clean::TypedefItem(build_type_alias(cx, did))
|
clean::TypedefItem(build_type_alias(cx, did))
|
||||||
}
|
}
|
||||||
Res::Def(DefKind::Enum, did) => {
|
Res::Def(DefKind::Enum, did) => {
|
||||||
record_extern_fqn(cx, did, ItemType::Enum);
|
record_extern_fqn(cx, did, ItemType::Enum);
|
||||||
build_impls(cx, Some(parent_module), did, attrs_without_docs, &mut ret);
|
build_impls(cx, did, attrs_without_docs, &mut ret);
|
||||||
clean::EnumItem(build_enum(cx, did))
|
clean::EnumItem(build_enum(cx, did))
|
||||||
}
|
}
|
||||||
Res::Def(DefKind::ForeignTy, did) => {
|
Res::Def(DefKind::ForeignTy, did) => {
|
||||||
record_extern_fqn(cx, did, ItemType::ForeignType);
|
record_extern_fqn(cx, did, ItemType::ForeignType);
|
||||||
build_impls(cx, Some(parent_module), did, attrs_without_docs, &mut ret);
|
build_impls(cx, did, attrs_without_docs, &mut ret);
|
||||||
clean::ForeignTypeItem
|
clean::ForeignTypeItem
|
||||||
}
|
}
|
||||||
// Never inline enum variants but leave them shown as re-exports.
|
// Never inline enum variants but leave them shown as re-exports.
|
||||||
|
@ -149,7 +124,7 @@ pub(crate) fn try_inline(
|
||||||
_ => return None,
|
_ => return None,
|
||||||
};
|
};
|
||||||
|
|
||||||
let (attrs, cfg) = merge_attrs(cx, Some(parent_module), load_attrs(cx, did), attrs);
|
let (attrs, cfg) = merge_attrs(cx, load_attrs(cx, did), attrs);
|
||||||
cx.inlined.insert(did.into());
|
cx.inlined.insert(did.into());
|
||||||
let mut item =
|
let mut item =
|
||||||
clean::Item::from_def_id_and_attrs_and_parts(did, Some(name), kind, Box::new(attrs), cfg);
|
clean::Item::from_def_id_and_attrs_and_parts(did, Some(name), kind, Box::new(attrs), cfg);
|
||||||
|
@ -316,9 +291,8 @@ fn build_type_alias(cx: &mut DocContext<'_>, did: DefId) -> Box<clean::Typedef>
|
||||||
/// Builds all inherent implementations of an ADT (struct/union/enum) or Trait item/path/reexport.
|
/// Builds all inherent implementations of an ADT (struct/union/enum) or Trait item/path/reexport.
|
||||||
pub(crate) fn build_impls(
|
pub(crate) fn build_impls(
|
||||||
cx: &mut DocContext<'_>,
|
cx: &mut DocContext<'_>,
|
||||||
parent_module: Option<DefId>,
|
|
||||||
did: DefId,
|
did: DefId,
|
||||||
attrs: Option<&[ast::Attribute]>,
|
attrs: Option<(&[ast::Attribute], Option<DefId>)>,
|
||||||
ret: &mut Vec<clean::Item>,
|
ret: &mut Vec<clean::Item>,
|
||||||
) {
|
) {
|
||||||
let _prof_timer = cx.tcx.sess.prof.generic_activity("build_inherent_impls");
|
let _prof_timer = cx.tcx.sess.prof.generic_activity("build_inherent_impls");
|
||||||
|
@ -326,7 +300,7 @@ pub(crate) fn build_impls(
|
||||||
|
|
||||||
// for each implementation of an item represented by `did`, build the clean::Item for that impl
|
// for each implementation of an item represented by `did`, build the clean::Item for that impl
|
||||||
for &did in tcx.inherent_impls(did).iter() {
|
for &did in tcx.inherent_impls(did).iter() {
|
||||||
build_impl(cx, parent_module, did, attrs, ret);
|
build_impl(cx, did, attrs, ret);
|
||||||
}
|
}
|
||||||
|
|
||||||
// This pretty much exists expressly for `dyn Error` traits that exist in the `alloc` crate.
|
// This pretty much exists expressly for `dyn Error` traits that exist in the `alloc` crate.
|
||||||
|
@ -340,28 +314,26 @@ pub(crate) fn build_impls(
|
||||||
let type_ =
|
let type_ =
|
||||||
if tcx.is_trait(did) { TraitSimplifiedType(did) } else { AdtSimplifiedType(did) };
|
if tcx.is_trait(did) { TraitSimplifiedType(did) } else { AdtSimplifiedType(did) };
|
||||||
for &did in tcx.incoherent_impls(type_) {
|
for &did in tcx.incoherent_impls(type_) {
|
||||||
build_impl(cx, parent_module, did, attrs, ret);
|
build_impl(cx, did, attrs, ret);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// `parent_module` refers to the parent of the re-export, not the original item
|
|
||||||
pub(crate) fn merge_attrs(
|
pub(crate) fn merge_attrs(
|
||||||
cx: &mut DocContext<'_>,
|
cx: &mut DocContext<'_>,
|
||||||
parent_module: Option<DefId>,
|
|
||||||
old_attrs: &[ast::Attribute],
|
old_attrs: &[ast::Attribute],
|
||||||
new_attrs: Option<&[ast::Attribute]>,
|
new_attrs: Option<(&[ast::Attribute], Option<DefId>)>,
|
||||||
) -> (clean::Attributes, Option<Arc<clean::cfg::Cfg>>) {
|
) -> (clean::Attributes, Option<Arc<clean::cfg::Cfg>>) {
|
||||||
// NOTE: If we have additional attributes (from a re-export),
|
// NOTE: If we have additional attributes (from a re-export),
|
||||||
// always insert them first. This ensure that re-export
|
// always insert them first. This ensure that re-export
|
||||||
// doc comments show up before the original doc comments
|
// doc comments show up before the original doc comments
|
||||||
// when we render them.
|
// when we render them.
|
||||||
if let Some(inner) = new_attrs {
|
if let Some((inner, item_id)) = new_attrs {
|
||||||
let mut both = inner.to_vec();
|
let mut both = inner.to_vec();
|
||||||
both.extend_from_slice(old_attrs);
|
both.extend_from_slice(old_attrs);
|
||||||
(
|
(
|
||||||
if let Some(new_id) = parent_module {
|
if let Some(item_id) = item_id {
|
||||||
Attributes::from_ast_with_additional(old_attrs, (inner, new_id))
|
Attributes::from_ast_with_additional(old_attrs, (inner, item_id))
|
||||||
} else {
|
} else {
|
||||||
Attributes::from_ast(&both)
|
Attributes::from_ast(&both)
|
||||||
},
|
},
|
||||||
|
@ -375,9 +347,8 @@ pub(crate) fn merge_attrs(
|
||||||
/// Inline an `impl`, inherent or of a trait. The `did` must be for an `impl`.
|
/// Inline an `impl`, inherent or of a trait. The `did` must be for an `impl`.
|
||||||
pub(crate) fn build_impl(
|
pub(crate) fn build_impl(
|
||||||
cx: &mut DocContext<'_>,
|
cx: &mut DocContext<'_>,
|
||||||
parent_module: Option<DefId>,
|
|
||||||
did: DefId,
|
did: DefId,
|
||||||
attrs: Option<&[ast::Attribute]>,
|
attrs: Option<(&[ast::Attribute], Option<DefId>)>,
|
||||||
ret: &mut Vec<clean::Item>,
|
ret: &mut Vec<clean::Item>,
|
||||||
) {
|
) {
|
||||||
if !cx.inlined.insert(did.into()) {
|
if !cx.inlined.insert(did.into()) {
|
||||||
|
@ -539,7 +510,7 @@ pub(crate) fn build_impl(
|
||||||
record_extern_trait(cx, did);
|
record_extern_trait(cx, did);
|
||||||
}
|
}
|
||||||
|
|
||||||
let (merged_attrs, cfg) = merge_attrs(cx, parent_module, load_attrs(cx, did), attrs);
|
let (merged_attrs, cfg) = merge_attrs(cx, load_attrs(cx, did), attrs);
|
||||||
trace!("merged_attrs={:?}", merged_attrs);
|
trace!("merged_attrs={:?}", merged_attrs);
|
||||||
|
|
||||||
trace!(
|
trace!(
|
||||||
|
@ -635,7 +606,7 @@ fn build_module_items(
|
||||||
cfg: None,
|
cfg: None,
|
||||||
inline_stmt_id: None,
|
inline_stmt_id: None,
|
||||||
});
|
});
|
||||||
} else if let Some(i) = try_inline(cx, did, None, res, item.ident.name, None, visited) {
|
} else if let Some(i) = try_inline(cx, res, item.ident.name, None, visited) {
|
||||||
items.extend(i)
|
items.extend(i)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2388,12 +2388,12 @@ fn clean_maybe_renamed_item<'tcx>(
|
||||||
target_attrs.extend_from_slice(inline::load_attrs(cx, def_id));
|
target_attrs.extend_from_slice(inline::load_attrs(cx, def_id));
|
||||||
}
|
}
|
||||||
|
|
||||||
let import_parent = import_id.map(|import_id| cx.tcx.local_parent(import_id).to_def_id());
|
let import_id = import_id.map(|def_id| def_id.to_def_id());
|
||||||
let (attrs, cfg) = merge_attrs(cx, import_parent, &target_attrs, Some(&import_attrs));
|
let (attrs, cfg) = merge_attrs(cx, &target_attrs, Some((&import_attrs, import_id)));
|
||||||
|
|
||||||
let mut item =
|
let mut item =
|
||||||
Item::from_def_id_and_attrs_and_parts(def_id, Some(name), kind, Box::new(attrs), cfg);
|
Item::from_def_id_and_attrs_and_parts(def_id, Some(name), kind, Box::new(attrs), cfg);
|
||||||
item.inline_stmt_id = import_id.map(|def_id| def_id.to_def_id());
|
item.inline_stmt_id = import_id;
|
||||||
vec![item]
|
vec![item]
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -2478,18 +2478,12 @@ fn clean_extern_crate<'tcx>(
|
||||||
|
|
||||||
let krate_owner_def_id = krate.owner_id.to_def_id();
|
let krate_owner_def_id = krate.owner_id.to_def_id();
|
||||||
if please_inline {
|
if please_inline {
|
||||||
let mut visited = DefIdSet::default();
|
|
||||||
|
|
||||||
let res = Res::Def(DefKind::Mod, crate_def_id);
|
|
||||||
|
|
||||||
if let Some(items) = inline::try_inline(
|
if let Some(items) = inline::try_inline(
|
||||||
cx,
|
cx,
|
||||||
cx.tcx.parent_module(krate.hir_id()).to_def_id(),
|
Res::Def(DefKind::Mod, crate_def_id),
|
||||||
Some(krate_owner_def_id),
|
|
||||||
res,
|
|
||||||
name,
|
name,
|
||||||
Some(attrs),
|
Some((attrs, Some(krate_owner_def_id))),
|
||||||
&mut visited,
|
&mut Default::default(),
|
||||||
) {
|
) {
|
||||||
return items;
|
return items;
|
||||||
}
|
}
|
||||||
|
@ -2613,17 +2607,13 @@ fn clean_use_statement_inner<'tcx>(
|
||||||
denied = true;
|
denied = true;
|
||||||
}
|
}
|
||||||
if !denied {
|
if !denied {
|
||||||
let mut visited = DefIdSet::default();
|
|
||||||
let import_def_id = import.owner_id.to_def_id();
|
let import_def_id = import.owner_id.to_def_id();
|
||||||
|
|
||||||
if let Some(mut items) = inline::try_inline(
|
if let Some(mut items) = inline::try_inline(
|
||||||
cx,
|
cx,
|
||||||
cx.tcx.parent_module(import.hir_id()).to_def_id(),
|
|
||||||
Some(import_def_id),
|
|
||||||
path.res,
|
path.res,
|
||||||
name,
|
name,
|
||||||
Some(attrs),
|
Some((attrs, Some(import_def_id))),
|
||||||
&mut visited,
|
&mut Default::default(),
|
||||||
) {
|
) {
|
||||||
items.push(Item::from_def_id_and_parts(
|
items.push(Item::from_def_id_and_parts(
|
||||||
import_def_id,
|
import_def_id,
|
||||||
|
|
|
@ -10,7 +10,7 @@ use rustc_span::symbol::Symbol;
|
||||||
fn create_doc_fragment(s: &str) -> Vec<DocFragment> {
|
fn create_doc_fragment(s: &str) -> Vec<DocFragment> {
|
||||||
vec![DocFragment {
|
vec![DocFragment {
|
||||||
span: DUMMY_SP,
|
span: DUMMY_SP,
|
||||||
parent_module: None,
|
item_id: None,
|
||||||
doc: Symbol::intern(s),
|
doc: Symbol::intern(s),
|
||||||
kind: DocFragmentKind::SugaredDoc,
|
kind: DocFragmentKind::SugaredDoc,
|
||||||
indent: 0,
|
indent: 0,
|
||||||
|
|
|
@ -195,12 +195,12 @@ pub(crate) fn build_deref_target_impls(
|
||||||
if let Some(prim) = target.primitive_type() {
|
if let Some(prim) = target.primitive_type() {
|
||||||
let _prof_timer = cx.tcx.sess.prof.generic_activity("build_primitive_inherent_impls");
|
let _prof_timer = cx.tcx.sess.prof.generic_activity("build_primitive_inherent_impls");
|
||||||
for did in prim.impls(tcx).filter(|did| !did.is_local()) {
|
for did in prim.impls(tcx).filter(|did| !did.is_local()) {
|
||||||
inline::build_impl(cx, None, did, None, ret);
|
inline::build_impl(cx, did, None, ret);
|
||||||
}
|
}
|
||||||
} else if let Type::Path { path } = target {
|
} else if let Type::Path { path } = target {
|
||||||
let did = path.def_id();
|
let did = path.def_id();
|
||||||
if !did.is_local() {
|
if !did.is_local() {
|
||||||
inline::build_impls(cx, None, did, None, ret);
|
inline::build_impls(cx, did, None, ret);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,7 +28,7 @@ use std::mem;
|
||||||
use std::ops::Range;
|
use std::ops::Range;
|
||||||
|
|
||||||
use crate::clean::{self, utils::find_nearest_parent_module};
|
use crate::clean::{self, utils::find_nearest_parent_module};
|
||||||
use crate::clean::{Crate, Item, ItemId, ItemLink, PrimitiveType};
|
use crate::clean::{Crate, Item, ItemLink, PrimitiveType};
|
||||||
use crate::core::DocContext;
|
use crate::core::DocContext;
|
||||||
use crate::html::markdown::{markdown_links, MarkdownLink};
|
use crate::html::markdown::{markdown_links, MarkdownLink};
|
||||||
use crate::lint::{BROKEN_INTRA_DOC_LINKS, PRIVATE_INTRA_DOC_LINKS};
|
use crate::lint::{BROKEN_INTRA_DOC_LINKS, PRIVATE_INTRA_DOC_LINKS};
|
||||||
|
@ -42,8 +42,7 @@ pub(crate) const COLLECT_INTRA_DOC_LINKS: Pass = Pass {
|
||||||
};
|
};
|
||||||
|
|
||||||
fn collect_intra_doc_links(krate: Crate, cx: &mut DocContext<'_>) -> Crate {
|
fn collect_intra_doc_links(krate: Crate, cx: &mut DocContext<'_>) -> Crate {
|
||||||
let mut collector =
|
let mut collector = LinkCollector { cx, visited_links: FxHashMap::default() };
|
||||||
LinkCollector { cx, mod_ids: Vec::new(), visited_links: FxHashMap::default() };
|
|
||||||
collector.visit_crate(&krate);
|
collector.visit_crate(&krate);
|
||||||
krate
|
krate
|
||||||
}
|
}
|
||||||
|
@ -149,7 +148,7 @@ impl TryFrom<ResolveRes> for Res {
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct UnresolvedPath<'a> {
|
struct UnresolvedPath<'a> {
|
||||||
/// Item on which the link is resolved, used for resolving `Self`.
|
/// Item on which the link is resolved, used for resolving `Self`.
|
||||||
item_id: ItemId,
|
item_id: DefId,
|
||||||
/// The scope the link was resolved in.
|
/// The scope the link was resolved in.
|
||||||
module_id: DefId,
|
module_id: DefId,
|
||||||
/// If part of the link resolved, this has the `Res`.
|
/// If part of the link resolved, this has the `Res`.
|
||||||
|
@ -225,7 +224,7 @@ impl UrlFragment {
|
||||||
|
|
||||||
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
|
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
|
||||||
struct ResolutionInfo {
|
struct ResolutionInfo {
|
||||||
item_id: ItemId,
|
item_id: DefId,
|
||||||
module_id: DefId,
|
module_id: DefId,
|
||||||
dis: Option<Disambiguator>,
|
dis: Option<Disambiguator>,
|
||||||
path_str: Box<str>,
|
path_str: Box<str>,
|
||||||
|
@ -242,11 +241,6 @@ struct DiagnosticInfo<'a> {
|
||||||
|
|
||||||
struct LinkCollector<'a, 'tcx> {
|
struct LinkCollector<'a, 'tcx> {
|
||||||
cx: &'a mut DocContext<'tcx>,
|
cx: &'a mut DocContext<'tcx>,
|
||||||
/// A stack of modules used to decide what scope to resolve in.
|
|
||||||
///
|
|
||||||
/// The last module will be used if the parent scope of the current item is
|
|
||||||
/// unknown.
|
|
||||||
mod_ids: Vec<DefId>,
|
|
||||||
/// Cache the resolved links so we can avoid resolving (and emitting errors for) the same link.
|
/// Cache the resolved links so we can avoid resolving (and emitting errors for) the same link.
|
||||||
/// The link will be `None` if it could not be resolved (i.e. the error was cached).
|
/// The link will be `None` if it could not be resolved (i.e. the error was cached).
|
||||||
visited_links: FxHashMap<ResolutionInfo, Option<(Res, Option<UrlFragment>)>>,
|
visited_links: FxHashMap<ResolutionInfo, Option<(Res, Option<UrlFragment>)>>,
|
||||||
|
@ -262,7 +256,7 @@ impl<'a, 'tcx> LinkCollector<'a, 'tcx> {
|
||||||
fn variant_field<'path>(
|
fn variant_field<'path>(
|
||||||
&self,
|
&self,
|
||||||
path_str: &'path str,
|
path_str: &'path str,
|
||||||
item_id: ItemId,
|
item_id: DefId,
|
||||||
module_id: DefId,
|
module_id: DefId,
|
||||||
) -> Result<(Res, DefId), UnresolvedPath<'path>> {
|
) -> Result<(Res, DefId), UnresolvedPath<'path>> {
|
||||||
let tcx = self.cx.tcx;
|
let tcx = self.cx.tcx;
|
||||||
|
@ -333,35 +327,33 @@ impl<'a, 'tcx> LinkCollector<'a, 'tcx> {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn resolve_self_ty(&self, path_str: &str, ns: Namespace, item_id: ItemId) -> Option<Res> {
|
fn resolve_self_ty(&self, path_str: &str, ns: Namespace, item_id: DefId) -> Option<Res> {
|
||||||
if ns != TypeNS || path_str != "Self" {
|
if ns != TypeNS || path_str != "Self" {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
let tcx = self.cx.tcx;
|
let tcx = self.cx.tcx;
|
||||||
item_id
|
let self_id = match tcx.def_kind(item_id) {
|
||||||
.as_def_id()
|
def_kind @ (DefKind::AssocFn
|
||||||
.map(|def_id| match tcx.def_kind(def_id) {
|
| DefKind::AssocConst
|
||||||
def_kind @ (DefKind::AssocFn
|
| DefKind::AssocTy
|
||||||
| DefKind::AssocConst
|
| DefKind::Variant
|
||||||
| DefKind::AssocTy
|
| DefKind::Field) => {
|
||||||
| DefKind::Variant
|
let parent_def_id = tcx.parent(item_id);
|
||||||
| DefKind::Field) => {
|
if def_kind == DefKind::Field && tcx.def_kind(parent_def_id) == DefKind::Variant {
|
||||||
let parent_def_id = tcx.parent(def_id);
|
tcx.parent(parent_def_id)
|
||||||
if def_kind == DefKind::Field && tcx.def_kind(parent_def_id) == DefKind::Variant
|
} else {
|
||||||
{
|
parent_def_id
|
||||||
tcx.parent(parent_def_id)
|
|
||||||
} else {
|
|
||||||
parent_def_id
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
_ => def_id,
|
}
|
||||||
})
|
_ => item_id,
|
||||||
.and_then(|self_id| match tcx.def_kind(self_id) {
|
};
|
||||||
DefKind::Impl { .. } => self.def_id_to_res(self_id),
|
|
||||||
DefKind::Use => None,
|
match tcx.def_kind(self_id) {
|
||||||
def_kind => Some(Res::Def(def_kind, self_id)),
|
DefKind::Impl { .. } => self.def_id_to_res(self_id),
|
||||||
})
|
DefKind::Use => None,
|
||||||
|
def_kind => Some(Res::Def(def_kind, self_id)),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Convenience wrapper around `doc_link_resolutions`.
|
/// Convenience wrapper around `doc_link_resolutions`.
|
||||||
|
@ -373,7 +365,7 @@ impl<'a, 'tcx> LinkCollector<'a, 'tcx> {
|
||||||
&self,
|
&self,
|
||||||
path_str: &str,
|
path_str: &str,
|
||||||
ns: Namespace,
|
ns: Namespace,
|
||||||
item_id: ItemId,
|
item_id: DefId,
|
||||||
module_id: DefId,
|
module_id: DefId,
|
||||||
) -> Option<Res> {
|
) -> Option<Res> {
|
||||||
if let res @ Some(..) = self.resolve_self_ty(path_str, ns, item_id) {
|
if let res @ Some(..) = self.resolve_self_ty(path_str, ns, item_id) {
|
||||||
|
@ -400,7 +392,7 @@ impl<'a, 'tcx> LinkCollector<'a, 'tcx> {
|
||||||
&mut self,
|
&mut self,
|
||||||
path_str: &'path str,
|
path_str: &'path str,
|
||||||
ns: Namespace,
|
ns: Namespace,
|
||||||
item_id: ItemId,
|
item_id: DefId,
|
||||||
module_id: DefId,
|
module_id: DefId,
|
||||||
) -> Result<(Res, Option<DefId>), UnresolvedPath<'path>> {
|
) -> Result<(Res, Option<DefId>), UnresolvedPath<'path>> {
|
||||||
if let Some(res) = self.resolve_path(path_str, ns, item_id, module_id) {
|
if let Some(res) = self.resolve_path(path_str, ns, item_id, module_id) {
|
||||||
|
@ -779,48 +771,8 @@ fn is_derive_trait_collision<T>(ns: &PerNS<Result<(Res, T), ResolutionFailure<'_
|
||||||
|
|
||||||
impl<'a, 'tcx> DocVisitor for LinkCollector<'a, 'tcx> {
|
impl<'a, 'tcx> DocVisitor for LinkCollector<'a, 'tcx> {
|
||||||
fn visit_item(&mut self, item: &Item) {
|
fn visit_item(&mut self, item: &Item) {
|
||||||
let parent_node =
|
self.resolve_links(item);
|
||||||
item.item_id.as_def_id().and_then(|did| find_nearest_parent_module(self.cx.tcx, did));
|
self.visit_item_recur(item)
|
||||||
if parent_node.is_some() {
|
|
||||||
trace!("got parent node for {:?} {:?}, id {:?}", item.type_(), item.name, item.item_id);
|
|
||||||
}
|
|
||||||
|
|
||||||
let inner_docs = item.inner_docs(self.cx.tcx);
|
|
||||||
|
|
||||||
if item.is_mod() && inner_docs {
|
|
||||||
self.mod_ids.push(item.item_id.expect_def_id());
|
|
||||||
}
|
|
||||||
|
|
||||||
// We want to resolve in the lexical scope of the documentation.
|
|
||||||
// In the presence of re-exports, this is not the same as the module of the item.
|
|
||||||
// Rather than merging all documentation into one, resolve it one attribute at a time
|
|
||||||
// so we know which module it came from.
|
|
||||||
for (parent_module, doc) in prepare_to_doc_link_resolution(&item.attrs.doc_strings) {
|
|
||||||
if !may_have_doc_links(&doc) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
debug!("combined_docs={}", doc);
|
|
||||||
// NOTE: if there are links that start in one crate and end in another, this will not resolve them.
|
|
||||||
// This is a degenerate case and it's not supported by rustdoc.
|
|
||||||
let parent_node = parent_module.or(parent_node);
|
|
||||||
for md_link in preprocessed_markdown_links(&doc) {
|
|
||||||
let link = self.resolve_link(item, &doc, parent_node, &md_link);
|
|
||||||
if let Some(link) = link {
|
|
||||||
self.cx.cache.intra_doc_links.entry(item.item_id).or_default().push(link);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if item.is_mod() {
|
|
||||||
if !inner_docs {
|
|
||||||
self.mod_ids.push(item.item_id.expect_def_id());
|
|
||||||
}
|
|
||||||
|
|
||||||
self.visit_item_recur(item);
|
|
||||||
self.mod_ids.pop();
|
|
||||||
} else {
|
|
||||||
self.visit_item_recur(item)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -946,14 +898,41 @@ fn preprocessed_markdown_links(s: &str) -> Vec<PreprocessedMarkdownLink> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LinkCollector<'_, '_> {
|
impl LinkCollector<'_, '_> {
|
||||||
|
fn resolve_links(&mut self, item: &Item) {
|
||||||
|
// We want to resolve in the lexical scope of the documentation.
|
||||||
|
// In the presence of re-exports, this is not the same as the module of the item.
|
||||||
|
// Rather than merging all documentation into one, resolve it one attribute at a time
|
||||||
|
// so we know which module it came from.
|
||||||
|
for (item_id, doc) in prepare_to_doc_link_resolution(&item.attrs.doc_strings) {
|
||||||
|
if !may_have_doc_links(&doc) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
debug!("combined_docs={}", doc);
|
||||||
|
// NOTE: if there are links that start in one crate and end in another, this will not resolve them.
|
||||||
|
// This is a degenerate case and it's not supported by rustdoc.
|
||||||
|
let item_id = item_id.unwrap_or_else(|| item.item_id.expect_def_id());
|
||||||
|
let module_id = match self.cx.tcx.def_kind(item_id) {
|
||||||
|
DefKind::Mod if item.inner_docs(self.cx.tcx) => item_id,
|
||||||
|
_ => find_nearest_parent_module(self.cx.tcx, item_id).unwrap(),
|
||||||
|
};
|
||||||
|
for md_link in preprocessed_markdown_links(&doc) {
|
||||||
|
let link = self.resolve_link(item, item_id, module_id, &doc, &md_link);
|
||||||
|
if let Some(link) = link {
|
||||||
|
self.cx.cache.intra_doc_links.entry(item.item_id).or_default().push(link);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// This is the entry point for resolving an intra-doc link.
|
/// This is the entry point for resolving an intra-doc link.
|
||||||
///
|
///
|
||||||
/// FIXME(jynelson): this is way too many arguments
|
/// FIXME(jynelson): this is way too many arguments
|
||||||
fn resolve_link(
|
fn resolve_link(
|
||||||
&mut self,
|
&mut self,
|
||||||
item: &Item,
|
item: &Item,
|
||||||
|
item_id: DefId,
|
||||||
|
module_id: DefId,
|
||||||
dox: &str,
|
dox: &str,
|
||||||
parent_node: Option<DefId>,
|
|
||||||
link: &PreprocessedMarkdownLink,
|
link: &PreprocessedMarkdownLink,
|
||||||
) -> Option<ItemLink> {
|
) -> Option<ItemLink> {
|
||||||
let PreprocessedMarkdownLink(pp_link, ori_link) = link;
|
let PreprocessedMarkdownLink(pp_link, ori_link) = link;
|
||||||
|
@ -970,25 +949,9 @@ impl LinkCollector<'_, '_> {
|
||||||
pp_link.as_ref().map_err(|err| err.report(self.cx, diag_info.clone())).ok()?;
|
pp_link.as_ref().map_err(|err| err.report(self.cx, diag_info.clone())).ok()?;
|
||||||
let disambiguator = *disambiguator;
|
let disambiguator = *disambiguator;
|
||||||
|
|
||||||
// In order to correctly resolve intra-doc links we need to
|
|
||||||
// pick a base AST node to work from. If the documentation for
|
|
||||||
// this module came from an inner comment (//!) then we anchor
|
|
||||||
// our name resolution *inside* the module. If, on the other
|
|
||||||
// hand it was an outer comment (///) then we anchor the name
|
|
||||||
// resolution in the parent module on the basis that the names
|
|
||||||
// used are more likely to be intended to be parent names. For
|
|
||||||
// this, we set base_node to None for inner comments since
|
|
||||||
// we've already pushed this node onto the resolution stack but
|
|
||||||
// for outer comments we explicitly try and resolve against the
|
|
||||||
// parent_node first.
|
|
||||||
let inner_docs = item.inner_docs(self.cx.tcx);
|
|
||||||
let base_node =
|
|
||||||
if item.is_mod() && inner_docs { self.mod_ids.last().copied() } else { parent_node };
|
|
||||||
let module_id = base_node.expect("doc link without parent module");
|
|
||||||
|
|
||||||
let (mut res, fragment) = self.resolve_with_disambiguator_cached(
|
let (mut res, fragment) = self.resolve_with_disambiguator_cached(
|
||||||
ResolutionInfo {
|
ResolutionInfo {
|
||||||
item_id: item.item_id,
|
item_id,
|
||||||
module_id,
|
module_id,
|
||||||
dis: disambiguator,
|
dis: disambiguator,
|
||||||
path_str: path_str.clone(),
|
path_str: path_str.clone(),
|
||||||
|
@ -1229,11 +1192,11 @@ impl LinkCollector<'_, '_> {
|
||||||
let disambiguator = key.dis;
|
let disambiguator = key.dis;
|
||||||
let path_str = &key.path_str;
|
let path_str = &key.path_str;
|
||||||
let item_id = key.item_id;
|
let item_id = key.item_id;
|
||||||
let base_node = key.module_id;
|
let module_id = key.module_id;
|
||||||
|
|
||||||
match disambiguator.map(Disambiguator::ns) {
|
match disambiguator.map(Disambiguator::ns) {
|
||||||
Some(expected_ns) => {
|
Some(expected_ns) => {
|
||||||
match self.resolve(path_str, expected_ns, item_id, base_node) {
|
match self.resolve(path_str, expected_ns, item_id, module_id) {
|
||||||
Ok(res) => Some(res),
|
Ok(res) => Some(res),
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
// We only looked in one namespace. Try to give a better error if possible.
|
// We only looked in one namespace. Try to give a better error if possible.
|
||||||
|
@ -1243,7 +1206,7 @@ impl LinkCollector<'_, '_> {
|
||||||
for other_ns in [TypeNS, ValueNS, MacroNS] {
|
for other_ns in [TypeNS, ValueNS, MacroNS] {
|
||||||
if other_ns != expected_ns {
|
if other_ns != expected_ns {
|
||||||
if let Ok(res) =
|
if let Ok(res) =
|
||||||
self.resolve(path_str, other_ns, item_id, base_node)
|
self.resolve(path_str, other_ns, item_id, module_id)
|
||||||
{
|
{
|
||||||
err = ResolutionFailure::WrongNamespace {
|
err = ResolutionFailure::WrongNamespace {
|
||||||
res: full_res(self.cx.tcx, res),
|
res: full_res(self.cx.tcx, res),
|
||||||
|
@ -1260,7 +1223,7 @@ impl LinkCollector<'_, '_> {
|
||||||
None => {
|
None => {
|
||||||
// Try everything!
|
// Try everything!
|
||||||
let mut candidate = |ns| {
|
let mut candidate = |ns| {
|
||||||
self.resolve(path_str, ns, item_id, base_node)
|
self.resolve(path_str, ns, item_id, module_id)
|
||||||
.map_err(ResolutionFailure::NotResolved)
|
.map_err(ResolutionFailure::NotResolved)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -49,7 +49,7 @@ pub(crate) fn collect_trait_impls(mut krate: Crate, cx: &mut DocContext<'_>) ->
|
||||||
let _prof_timer = cx.tcx.sess.prof.generic_activity("build_extern_trait_impls");
|
let _prof_timer = cx.tcx.sess.prof.generic_activity("build_extern_trait_impls");
|
||||||
for &cnum in cx.tcx.crates(()) {
|
for &cnum in cx.tcx.crates(()) {
|
||||||
for &impl_def_id in cx.tcx.trait_impls_in_crate(cnum) {
|
for &impl_def_id in cx.tcx.trait_impls_in_crate(cnum) {
|
||||||
inline::build_impl(cx, None, impl_def_id, None, &mut new_items_external);
|
inline::build_impl(cx, impl_def_id, None, &mut new_items_external);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -75,7 +75,7 @@ pub(crate) fn collect_trait_impls(mut krate: Crate, cx: &mut DocContext<'_>) ->
|
||||||
);
|
);
|
||||||
parent = cx.tcx.opt_parent(did);
|
parent = cx.tcx.opt_parent(did);
|
||||||
}
|
}
|
||||||
inline::build_impl(cx, None, impl_def_id, Some(&attr_buf), &mut new_items_local);
|
inline::build_impl(cx, impl_def_id, Some((&attr_buf, None)), &mut new_items_local);
|
||||||
attr_buf.clear();
|
attr_buf.clear();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -84,7 +84,7 @@ pub(crate) fn collect_trait_impls(mut krate: Crate, cx: &mut DocContext<'_>) ->
|
||||||
for def_id in PrimitiveType::all_impls(cx.tcx) {
|
for def_id in PrimitiveType::all_impls(cx.tcx) {
|
||||||
// Try to inline primitive impls from other crates.
|
// Try to inline primitive impls from other crates.
|
||||||
if !def_id.is_local() {
|
if !def_id.is_local() {
|
||||||
inline::build_impl(cx, None, def_id, None, &mut new_items_external);
|
inline::build_impl(cx, def_id, None, &mut new_items_external);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (prim, did) in PrimitiveType::primitive_locations(cx.tcx) {
|
for (prim, did) in PrimitiveType::primitive_locations(cx.tcx) {
|
||||||
|
|
|
@ -57,7 +57,8 @@ impl<'a, 'tcx> CfgPropagator<'a, 'tcx> {
|
||||||
next_def_id = parent_def_id;
|
next_def_id = parent_def_id;
|
||||||
}
|
}
|
||||||
|
|
||||||
let (_, cfg) = merge_attrs(self.cx, None, item.attrs.other_attrs.as_slice(), Some(&attrs));
|
let (_, cfg) =
|
||||||
|
merge_attrs(self.cx, item.attrs.other_attrs.as_slice(), Some((&attrs, None)));
|
||||||
item.cfg = cfg;
|
item.cfg = cfg;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
1
tests/rustdoc-ui/intra-doc/auxiliary/inner-crate-doc.rs
Normal file
1
tests/rustdoc-ui/intra-doc/auxiliary/inner-crate-doc.rs
Normal file
|
@ -0,0 +1 @@
|
||||||
|
//! Inner doc comment
|
10
tests/rustdoc-ui/intra-doc/import-inline-merge-module.rs
Normal file
10
tests/rustdoc-ui/intra-doc/import-inline-merge-module.rs
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
// Test for issue #108501.
|
||||||
|
// Module parent scope doesn't hijack import's parent scope for the import's doc links.
|
||||||
|
|
||||||
|
// check-pass
|
||||||
|
// aux-build: inner-crate-doc.rs
|
||||||
|
// compile-flags: --extern inner_crate_doc --edition 2018
|
||||||
|
|
||||||
|
/// Import doc comment [inner_crate_doc]
|
||||||
|
#[doc(inline)]
|
||||||
|
pub use inner_crate_doc;
|
Loading…
Add table
Add a link
Reference in a new issue