Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Resolve intra-doc links on additional documentation for re-exports in lexical scope #77519

Merged
merged 3 commits into from
Oct 9, 2020
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 40 additions & 21 deletions src/librustdoc/clean/inline.rs
Original file line number Diff line number Diff line change
@@ -15,7 +15,7 @@ use rustc_span::hygiene::MacroKind;
use rustc_span::symbol::{sym, Symbol};
use rustc_span::Span;

use crate::clean::{self, GetDefId, ToSource, TypeKind};
use crate::clean::{self, Attributes, GetDefId, ToSource, TypeKind};
use crate::core::DocContext;
use crate::doctree;

@@ -35,8 +35,11 @@ type Attrs<'hir> = rustc_middle::ty::Attributes<'hir>;
///
/// The returned value is `None` if the definition could not be inlined,
/// 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 fn try_inline(
cx: &DocContext<'_>,
parent_module: DefId,
res: Res,
name: Symbol,
attrs: Option<Attrs<'_>>,
@@ -48,12 +51,13 @@ pub fn try_inline(
}
let mut ret = Vec::new();

debug!("attrs={:?}", attrs);
let attrs_clone = attrs;

let inner = match res {
Res::Def(DefKind::Trait, did) => {
record_extern_fqn(cx, did, clean::TypeKind::Trait);
ret.extend(build_impls(cx, did, attrs));
ret.extend(build_impls(cx, Some(parent_module), did, attrs));
clean::TraitItem(build_external_trait(cx, did))
}
Res::Def(DefKind::Fn, did) => {
@@ -62,27 +66,27 @@ pub fn try_inline(
}
Res::Def(DefKind::Struct, did) => {
record_extern_fqn(cx, did, clean::TypeKind::Struct);
ret.extend(build_impls(cx, did, attrs));
ret.extend(build_impls(cx, Some(parent_module), did, attrs));
clean::StructItem(build_struct(cx, did))
}
Res::Def(DefKind::Union, did) => {
record_extern_fqn(cx, did, clean::TypeKind::Union);
ret.extend(build_impls(cx, did, attrs));
ret.extend(build_impls(cx, Some(parent_module), did, attrs));
clean::UnionItem(build_union(cx, did))
}
Res::Def(DefKind::TyAlias, did) => {
record_extern_fqn(cx, did, clean::TypeKind::Typedef);
ret.extend(build_impls(cx, did, attrs));
ret.extend(build_impls(cx, Some(parent_module), did, attrs));
clean::TypedefItem(build_type_alias(cx, did), false)
}
Res::Def(DefKind::Enum, did) => {
record_extern_fqn(cx, did, clean::TypeKind::Enum);
ret.extend(build_impls(cx, did, attrs));
ret.extend(build_impls(cx, Some(parent_module), did, attrs));
clean::EnumItem(build_enum(cx, did))
}
Res::Def(DefKind::ForeignTy, did) => {
record_extern_fqn(cx, did, clean::TypeKind::Foreign);
ret.extend(build_impls(cx, did, attrs));
ret.extend(build_impls(cx, Some(parent_module), did, attrs));
clean::ForeignTypeItem
}
// Never inline enum variants but leave them shown as re-exports.
@@ -117,7 +121,7 @@ pub fn try_inline(
};

let target_attrs = load_attrs(cx, did);
let attrs = merge_attrs(cx, target_attrs, attrs_clone);
let attrs = merge_attrs(cx, Some(parent_module), target_attrs, attrs_clone);

cx.renderinfo.borrow_mut().inlined.insert(did);
ret.push(clean::Item {
@@ -291,40 +295,52 @@ pub fn build_ty(cx: &DocContext<'_>, did: DefId) -> Option<clean::Type> {
}

/// Builds all inherent implementations of an ADT (struct/union/enum) or Trait item/path/reexport.
pub fn build_impls(cx: &DocContext<'_>, did: DefId, attrs: Option<Attrs<'_>>) -> Vec<clean::Item> {
pub fn build_impls(
cx: &DocContext<'_>,
parent_module: Option<DefId>,
did: DefId,
attrs: Option<Attrs<'_>>,
) -> Vec<clean::Item> {
let tcx = cx.tcx;
let mut impls = Vec::new();

// for each implementation of an item represented by `did`, build the clean::Item for that impl
for &did in tcx.inherent_impls(did).iter() {
build_impl(cx, did, attrs, &mut impls);
build_impl(cx, parent_module, did, attrs, &mut impls);
}

impls
}

/// `parent_module` refers to the parent of the re-export, not the original item
fn merge_attrs(
cx: &DocContext<'_>,
attrs: Attrs<'_>,
other_attrs: Option<Attrs<'_>>,
parent_module: Option<DefId>,
old_attrs: Attrs<'_>,
new_attrs: Option<Attrs<'_>>,
) -> clean::Attributes {
// NOTE: If we have additional attributes (from a re-export),
// always insert them first. This ensure that re-export
// doc comments show up before the original doc comments
// when we render them.
let merged_attrs = if let Some(inner) = other_attrs {
let mut both = inner.to_vec();
both.extend_from_slice(attrs);
both
if let Some(inner) = new_attrs {
if let Some(new_id) = parent_module {
let diag = cx.sess().diagnostic();
Attributes::from_ast(diag, old_attrs, Some((inner, new_id)))
} else {
let mut both = inner.to_vec();
both.extend_from_slice(old_attrs);
both.clean(cx)
}
} else {
attrs.to_vec()
};
merged_attrs.clean(cx)
old_attrs.clean(cx)
}
}

/// Builds a specific implementation of a type. The `did` could be a type method or trait method.
pub fn build_impl(
cx: &DocContext<'_>,
parent_module: impl Into<Option<DefId>>,
did: DefId,
attrs: Option<Attrs<'_>>,
ret: &mut Vec<clean::Item>,
@@ -333,7 +349,8 @@ pub fn build_impl(
return;
}

let attrs = merge_attrs(cx, load_attrs(cx, did), attrs);
let attrs = merge_attrs(cx, parent_module.into(), load_attrs(cx, did), attrs);
debug!("merged_attrs={:?}", attrs);

let tcx = cx.tcx;
let associated_trait = tcx.impl_trait_ref(did);
@@ -499,7 +516,9 @@ fn build_module(cx: &DocContext<'_>, did: DefId, visited: &mut FxHashSet<DefId>)
},
)),
});
} else if let Some(i) = try_inline(cx, item.res, item.ident.name, None, visited) {
} else if let Some(i) =
try_inline(cx, did, item.res, item.ident.name, None, visited)
{
items.extend(i)
}
}
24 changes: 17 additions & 7 deletions src/librustdoc/clean/mod.rs
Original file line number Diff line number Diff line change
@@ -284,7 +284,7 @@ impl Clean<Item> for doctree::Module<'_> {

impl Clean<Attributes> for [ast::Attribute] {
fn clean(&self, cx: &DocContext<'_>) -> Attributes {
Attributes::from_ast(cx.sess().diagnostic(), self)
Attributes::from_ast(cx.sess().diagnostic(), self, None)
}
}

@@ -2205,9 +2205,14 @@ impl Clean<Vec<Item>> for doctree::ExternCrate<'_> {

let res = Res::Def(DefKind::Mod, DefId { krate: self.cnum, index: CRATE_DEF_INDEX });

if let Some(items) =
inline::try_inline(cx, res, self.name, Some(self.attrs), &mut visited)
{
if let Some(items) = inline::try_inline(
cx,
cx.tcx.parent_module(self.hir_id).to_def_id(),
res,
self.name,
Some(self.attrs),
&mut visited,
) {
return items;
}
}
@@ -2268,9 +2273,14 @@ impl Clean<Vec<Item>> for doctree::Import<'_> {
}
if !denied {
let mut visited = FxHashSet::default();
if let Some(items) =
inline::try_inline(cx, path.res, name, Some(self.attrs), &mut visited)
{
if let Some(items) = inline::try_inline(
cx,
cx.tcx.parent_module(self.id).to_def_id(),
path.res,
name,
Some(self.attrs),
&mut visited,
) {
return items;
}
}
127 changes: 85 additions & 42 deletions src/librustdoc/clean/types.rs
Original file line number Diff line number Diff line change
@@ -373,6 +373,11 @@ impl<I: IntoIterator<Item = ast::NestedMetaItem>> NestedAttributesExt for I {
pub struct DocFragment {
pub line: usize,
pub span: rustc_span::Span,
/// The module this doc-comment came from.
///
/// This allows distinguishing between the original documentation and a pub re-export.
/// If it is `None`, the item was not re-exported.
pub parent_module: Option<DefId>,
pub doc: String,
pub kind: DocFragmentKind,
}
@@ -386,6 +391,24 @@ pub enum DocFragmentKind {
/// A doc fragment created from a `#[doc(include="filename")]` attribute. Contains both the
/// given filename and the file contents.
Include { filename: String },
/// A doc fragment used to distinguish between documentation in different modules.
///
/// In particular, this prevents `collapse_docs` from turning all documentation comments
/// into a single giant attributes even when the item is re-exported with documentation on the re-export.
Divider,
}

impl DocFragment {
/// Creates a dummy doc-fragment which divides earlier and later fragments.
fn divider() -> Self {
DocFragment {
line: 0,
span: DUMMY_SP,
parent_module: None,
doc: String::new(),
kind: DocFragmentKind::Divider,
}
}
}

impl<'a> FromIterator<&'a DocFragment> for String {
@@ -521,57 +544,77 @@ impl Attributes {
false
}

pub fn from_ast(diagnostic: &::rustc_errors::Handler, attrs: &[ast::Attribute]) -> Attributes {
let mut doc_strings = vec![];
pub fn from_ast(
diagnostic: &::rustc_errors::Handler,
attrs: &[ast::Attribute],
additional_attrs: Option<(&[ast::Attribute], DefId)>,
) -> Attributes {
let doc_strings = RefCell::new(vec![]);
let mut sp = None;
let mut cfg = Cfg::True;
let mut doc_line = 0;

let other_attrs = attrs
.iter()
.filter_map(|attr| {
if let Some(value) = attr.doc_str() {
let value = beautify_doc_string(value);
let kind = if attr.is_doc_comment() {
DocFragmentKind::SugaredDoc
} else {
DocFragmentKind::RawDoc
};

let line = doc_line;
doc_line += value.lines().count();
doc_strings.push(DocFragment { line, span: attr.span, doc: value, kind });

if sp.is_none() {
sp = Some(attr.span);
}
None
let clean_attr = |(attr, parent_module): (&ast::Attribute, _)| {
if let Some(value) = attr.doc_str() {
trace!("got doc_str={:?}", value);
let value = beautify_doc_string(value);
let kind = if attr.is_doc_comment() {
DocFragmentKind::SugaredDoc
} else {
if attr.has_name(sym::doc) {
if let Some(mi) = attr.meta() {
if let Some(cfg_mi) = Attributes::extract_cfg(&mi) {
// Extracted #[doc(cfg(...))]
match Cfg::parse(cfg_mi) {
Ok(new_cfg) => cfg &= new_cfg,
Err(e) => diagnostic.span_err(e.span, e.msg),
}
} else if let Some((filename, contents)) =
Attributes::extract_include(&mi)
{
let line = doc_line;
doc_line += contents.lines().count();
doc_strings.push(DocFragment {
line,
span: attr.span,
doc: contents,
kind: DocFragmentKind::Include { filename },
});
DocFragmentKind::RawDoc
};

let line = doc_line;
doc_line += value.lines().count();
doc_strings.borrow_mut().push(DocFragment {
line,
span: attr.span,
doc: value,
kind,
parent_module,
});

if sp.is_none() {
sp = Some(attr.span);
}
None
} else {
if attr.has_name(sym::doc) {
if let Some(mi) = attr.meta() {
if let Some(cfg_mi) = Attributes::extract_cfg(&mi) {
// Extracted #[doc(cfg(...))]
match Cfg::parse(cfg_mi) {
Ok(new_cfg) => cfg &= new_cfg,
Err(e) => diagnostic.span_err(e.span, e.msg),
}
} else if let Some((filename, contents)) = Attributes::extract_include(&mi)
{
let line = doc_line;
doc_line += contents.lines().count();
doc_strings.borrow_mut().push(DocFragment {
line,
span: attr.span,
doc: contents,
kind: DocFragmentKind::Include { filename },
parent_module: parent_module,
});
}
}
Some(attr.clone())
}
Some(attr.clone())
}
};

// Additional documentation should be shown before the original documentation
let other_attrs = additional_attrs
.into_iter()
.map(|(attrs, id)| {
doc_strings.borrow_mut().push(DocFragment::divider());
attrs.iter().map(move |attr| (attr, Some(id)))
})
.flatten()
.chain(attrs.iter().map(|attr| (attr, None)))
.filter_map(clean_attr)
.collect();

// treat #[target_feature(enable = "feat")] attributes as if they were
@@ -597,7 +640,7 @@ impl Attributes {
.map_or(true, |a| a.style == AttrStyle::Inner);

Attributes {
doc_strings,
doc_strings: doc_strings.into_inner(),
other_attrs,
cfg: if cfg == Cfg::True { None } else { Some(Arc::new(cfg)) },
span: sp,
4 changes: 2 additions & 2 deletions src/librustdoc/clean/utils.rs
Original file line number Diff line number Diff line change
@@ -361,7 +361,7 @@ pub fn build_deref_target_impls(cx: &DocContext<'_>, items: &[Item], ret: &mut V
let primitive = match *target {
ResolvedPath { did, .. } if did.is_local() => continue,
ResolvedPath { did, .. } => {
ret.extend(inline::build_impls(cx, did, None));
ret.extend(inline::build_impls(cx, None, did, None));
continue;
}
_ => match target.primitive_type() {
@@ -371,7 +371,7 @@ pub fn build_deref_target_impls(cx: &DocContext<'_>, items: &[Item], ret: &mut V
};
for &did in primitive.impls(tcx) {
if !did.is_local() {
inline::build_impl(cx, did, None, ret);
inline::build_impl(cx, None, did, None, ret);
}
}
}
2 changes: 1 addition & 1 deletion src/librustdoc/doctest.rs
Original file line number Diff line number Diff line change
@@ -927,7 +927,7 @@ impl<'a, 'hir, 'tcx> HirCollector<'a, 'hir, 'tcx> {
sp: Span,
nested: F,
) {
let mut attrs = Attributes::from_ast(self.sess.diagnostic(), attrs);
let mut attrs = Attributes::from_ast(self.sess.diagnostic(), attrs, None);
if let Some(ref cfg) = attrs.cfg {
if !cfg.matches(&self.sess.parse_sess, Some(&self.sess.features_untracked())) {
return;
2 changes: 2 additions & 0 deletions src/librustdoc/doctree.rs
Original file line number Diff line number Diff line change
@@ -8,6 +8,7 @@ use rustc_span::{self, Span, Symbol};

use rustc_hir as hir;
use rustc_hir::def_id::CrateNum;
use rustc_hir::HirId;

pub struct Module<'hir> {
pub name: Option<Symbol>,
@@ -236,6 +237,7 @@ pub struct Macro<'hir> {

pub struct ExternCrate<'hir> {
pub name: Symbol,
pub hir_id: HirId,
pub cnum: CrateNum,
pub path: Option<String>,
pub vis: &'hir hir::Visibility<'hir>,
102 changes: 66 additions & 36 deletions src/librustdoc/passes/collect_intra_doc_links.rs
Original file line number Diff line number Diff line change
@@ -8,7 +8,7 @@ use rustc_hir::def::{
Namespace::{self, *},
PerNS, Res,
};
use rustc_hir::def_id::DefId;
use rustc_hir::def_id::{CrateNum, DefId};
use rustc_middle::ty;
use rustc_resolve::ParentScope;
use rustc_session::lint::{
@@ -767,9 +767,6 @@ impl<'a, 'tcx> DocFolder for LinkCollector<'a, 'tcx> {
self.mod_ids.push(item.def_id);
}

let dox = item.attrs.collapsed_doc_value().unwrap_or_else(String::new);
trace!("got documentation '{}'", dox);

// find item's parent to resolve `Self` in item's docs below
let parent_name = self.cx.as_local_hir_id(item.def_id).and_then(|item_hir| {
let parent_hir = self.cx.tcx.hir().get_parent_item(item_hir);
@@ -807,16 +804,53 @@ impl<'a, 'tcx> DocFolder for LinkCollector<'a, 'tcx> {
}
});

for (ori_link, link_range) in markdown_links(&dox) {
self.resolve_link(
&mut item,
&dox,
&current_item,
parent_node,
&parent_name,
ori_link,
link_range,
);
// 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.
let mut attrs = item.attrs.doc_strings.iter().peekable();
while let Some(attr) = attrs.next() {
// `collapse_docs` does not have the behavior we want:
// we want `///` and `#[doc]` to count as the same attribute,
// but currently it will treat them as separate.
// As a workaround, combine all attributes with the same parent module into the same attribute.
let mut combined_docs = attr.doc.clone();
loop {
match attrs.peek() {
Some(next) if next.parent_module == attr.parent_module => {
combined_docs.push('\n');
combined_docs.push_str(&attrs.next().unwrap().doc);
}
_ => break,
}
}
debug!("combined_docs={}", combined_docs);

let (krate, parent_node) = if let Some(id) = attr.parent_module {
trace!("docs {:?} came from {:?}", attr.doc, id);
(id.krate, Some(id))
} else {
trace!("no parent found for {:?}", attr.doc);
(item.def_id.krate, parent_node)
};
// 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.
// FIXME: this will break links that start in `#[doc = ...]` and end as a sugared doc. Should this be supported?
for (ori_link, link_range) in markdown_links(&combined_docs) {
let link = self.resolve_link(
&item,
&combined_docs,
&current_item,
parent_node,
&parent_name,
krate,
ori_link,
link_range,
);
if let Some(link) = link {
item.attrs.links.push(link);
}
}
}

if item.is_mod() && !item.attrs.inner_docs {
@@ -838,36 +872,37 @@ impl<'a, 'tcx> DocFolder for LinkCollector<'a, 'tcx> {
impl LinkCollector<'_, '_> {
fn resolve_link(
&self,
item: &mut Item,
item: &Item,
dox: &str,
current_item: &Option<String>,
parent_node: Option<DefId>,
parent_name: &Option<String>,
krate: CrateNum,
ori_link: String,
link_range: Option<Range<usize>>,
) {
) -> Option<ItemLink> {
trace!("considering link '{}'", ori_link);

// Bail early for real links.
if ori_link.contains('/') {
return;
return None;
}

// [] is mostly likely not supposed to be a link
if ori_link.is_empty() {
return;
return None;
}

let cx = self.cx;
let link = ori_link.replace("`", "");
let parts = link.split('#').collect::<Vec<_>>();
let (link, extra_fragment) = if parts.len() > 2 {
anchor_failure(cx, &item, &link, dox, link_range, AnchorFailure::MultipleAnchors);
return;
return None;
} else if parts.len() == 2 {
if parts[0].trim().is_empty() {
// This is an anchor to an element of the current page, nothing to do in here!
return;
return None;
}
(parts[0], Some(parts[1].to_owned()))
} else {
@@ -888,7 +923,7 @@ impl LinkCollector<'_, '_> {
.trim();

if path_str.contains(|ch: char| !(ch.is_alphanumeric() || ch == ':' || ch == '_')) {
return;
return None;
}

// We stripped `()` and `!` when parsing the disambiguator.
@@ -928,7 +963,7 @@ impl LinkCollector<'_, '_> {
link_range,
smallvec![err_kind],
);
return;
return None;
};

// replace `Self` with suitable item's parent name
@@ -947,7 +982,7 @@ impl LinkCollector<'_, '_> {
// (consider `crate::char`). Instead, change it to `self::`. This works because 'self' is now the crate root.
resolved_self = format!("self::{}", &path_str["crate::".len()..]);
path_str = &resolved_self;
module_id = DefId { krate: item.def_id.krate, index: CRATE_DEF_INDEX };
module_id = DefId { krate, index: CRATE_DEF_INDEX };
}

match self.resolve_with_disambiguator(
@@ -962,7 +997,7 @@ impl LinkCollector<'_, '_> {
link_range.clone(),
) {
Some(x) => x,
None => return,
None => return None,
}
};

@@ -986,15 +1021,15 @@ impl LinkCollector<'_, '_> {
link_range,
AnchorFailure::RustdocAnchorConflict(prim),
);
return;
return None;
}
res = prim;
fragment = Some(path.to_owned());
} else {
// `[char]` when a `char` module is in scope
let candidates = vec![res, prim];
ambiguity_error(cx, &item, path_str, dox, link_range, candidates);
return;
return None;
}
}
}
@@ -1018,16 +1053,11 @@ impl LinkCollector<'_, '_> {
if let Res::PrimTy(..) = res {
match disambiguator {
Some(Disambiguator::Primitive | Disambiguator::Namespace(_)) | None => {
item.attrs.links.push(ItemLink {
link: ori_link,
link_text,
did: None,
fragment,
});
Some(ItemLink { link: ori_link, link_text, did: None, fragment })
}
Some(other) => {
report_mismatch(other, Disambiguator::Primitive);
return;
None
}
}
} else {
@@ -1050,7 +1080,7 @@ impl LinkCollector<'_, '_> {
(actual, Some(Disambiguator::Kind(expected))) if actual == expected => {}
(_, Some(specified @ Disambiguator::Kind(_) | specified @ Disambiguator::Primitive)) => {
report_mismatch(specified, Disambiguator::Kind(kind));
return;
return None;
}
}
}
@@ -1073,14 +1103,14 @@ impl LinkCollector<'_, '_> {
}
}
let id = register_res(cx, res);
item.attrs.links.push(ItemLink { link: ori_link, link_text, did: Some(id), fragment });
Some(ItemLink { link: ori_link, link_text, did: Some(id), fragment })
}
}

fn resolve_with_disambiguator(
&self,
disambiguator: Option<Disambiguator>,
item: &mut Item,
item: &Item,
dox: &str,
path_str: &str,
current_item: &Option<String>,
6 changes: 3 additions & 3 deletions src/librustdoc/passes/collect_trait_impls.rs
Original file line number Diff line number Diff line change
@@ -30,15 +30,15 @@ pub fn collect_trait_impls(krate: Crate, cx: &DocContext<'_>) -> Crate {
for &cnum in cx.tcx.crates().iter() {
for &(did, _) in cx.tcx.all_trait_implementations(cnum).iter() {
cx.tcx.sess.time("build_extern_trait_impl", || {
inline::build_impl(cx, did, None, &mut new_items);
inline::build_impl(cx, None, did, None, &mut new_items);
});
}
}

// Also try to inline primitive impls from other crates.
for &def_id in PrimitiveType::all_impls(cx.tcx).values().flatten() {
if !def_id.is_local() {
inline::build_impl(cx, def_id, None, &mut new_items);
inline::build_impl(cx, None, def_id, None, &mut new_items);

// FIXME(eddyb) is this `doc(hidden)` check needed?
if !cx.tcx.get_attrs(def_id).lists(sym::doc).has_word(sym::hidden) {
@@ -90,7 +90,7 @@ pub fn collect_trait_impls(krate: Crate, cx: &DocContext<'_>) -> Crate {
for &impl_node in cx.tcx.hir().trait_impls(trait_did) {
let impl_did = cx.tcx.hir().local_def_id(impl_node);
cx.tcx.sess.time("build_local_trait_impl", || {
inline::build_impl(cx, impl_did.to_def_id(), None, &mut new_items);
inline::build_impl(cx, None, impl_did.to_def_id(), None, &mut new_items);
});
}
}
1 change: 1 addition & 0 deletions src/librustdoc/visit_ast.rs
Original file line number Diff line number Diff line change
@@ -399,6 +399,7 @@ impl<'a, 'tcx> RustdocVisitor<'a, 'tcx> {
om.extern_crates.push(ExternCrate {
cnum: self.cx.tcx.extern_mod_stmt_cnum(def_id).unwrap_or(LOCAL_CRATE),
name: ident.name,
hir_id: item.hir_id,
path: orig_name.map(|x| x.to_string()),
vis: &item.vis,
attrs: &item.attrs,
18 changes: 18 additions & 0 deletions src/test/rustdoc/intra-link-reexport-additional-docs.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#![crate_name = "foo"]

// @has foo/struct.JoinPathsError.html '//a[@href="../foo/fn.with_code.html"]' 'crate::with_code'
/// [crate::with_code]
// @has - '//a[@href="../foo/fn.with_code.html"]' 'different text'
/// [different text][with_code]
// @has - '//a[@href="../foo/fn.me_too.html"]' 'me_too'
#[doc = "[me_too]"]
// @has - '//a[@href="../foo/fn.me_three.html"]' 'reference link'
/// This [reference link]
#[doc = "has an attr in the way"]
///
/// [reference link]: me_three
pub use std::env::JoinPathsError;

pub fn with_code() {}
pub fn me_too() {}
pub fn me_three() {}