Skip to content

Commit e39a860

Browse files
committed
Use the new module information for intra-doc links
- Make the parent module conditional on whether the docs are on a re-export - Make `resolve_link` take `&Item` instead of `&mut Item` Previously the borrow checker gave an error about multiple mutable borrows, because `dox` borrowed from `item`. - Fix `crate::` for re-exports `crate` means something different depending on where the attribute came from. - Make it work for `#[doc]` attributes too This required combining several attributes as one so they would keep the links.
1 parent 8fbfdc5 commit e39a860

File tree

2 files changed

+84
-44
lines changed

2 files changed

+84
-44
lines changed

src/librustdoc/passes/collect_intra_doc_links.rs

+66-44
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use rustc_hir::def::{
88
Namespace::{self, *},
99
PerNS, Res,
1010
};
11-
use rustc_hir::def_id::DefId;
11+
use rustc_hir::def_id::{CrateNum, DefId};
1212
use rustc_middle::ty;
1313
use rustc_resolve::ParentScope;
1414
use rustc_session::lint::{
@@ -767,17 +767,6 @@ impl<'a, 'tcx> DocFolder for LinkCollector<'a, 'tcx> {
767767
self.mod_ids.push(item.def_id);
768768
}
769769

770-
#[cfg(debug_assertions)]
771-
for attr in &item.attrs.doc_strings {
772-
if let Some(id) = attr.parent_module {
773-
trace!("docs {:?} came from {:?}", attr.doc, id);
774-
} else {
775-
debug!("no parent found for {:?}", attr.doc);
776-
}
777-
}
778-
let dox = item.attrs.collapsed_doc_value().unwrap_or_else(String::new);
779-
//trace!("got documentation '{}'", dox);
780-
781770
// find item's parent to resolve `Self` in item's docs below
782771
let parent_name = self.cx.as_local_hir_id(item.def_id).and_then(|item_hir| {
783772
let parent_hir = self.cx.tcx.hir().get_parent_item(item_hir);
@@ -815,16 +804,53 @@ impl<'a, 'tcx> DocFolder for LinkCollector<'a, 'tcx> {
815804
}
816805
});
817806

818-
for (ori_link, link_range) in markdown_links(&dox) {
819-
self.resolve_link(
820-
&mut item,
821-
&dox,
822-
&current_item,
823-
parent_node,
824-
&parent_name,
825-
ori_link,
826-
link_range,
827-
);
807+
// We want to resolve in the lexical scope of the documentation.
808+
// In the presence of re-exports, this is not the same as the module of the item.
809+
// Rather than merging all documentation into one, resolve it one attribute at a time
810+
// so we know which module it came from.
811+
let mut attrs = item.attrs.doc_strings.iter().peekable();
812+
while let Some(attr) = attrs.next() {
813+
// `collapse_docs` does not have the behavior we want:
814+
// we want `///` and `#[doc]` to count as the same attribute,
815+
// but currently it will treat them as separate.
816+
// As a workaround, combine all attributes with the same parent module into the same attribute.
817+
let mut combined_docs = attr.doc.clone();
818+
loop {
819+
match attrs.peek() {
820+
Some(next) if next.parent_module == attr.parent_module => {
821+
combined_docs.push('\n');
822+
combined_docs.push_str(&attrs.next().unwrap().doc);
823+
}
824+
_ => break,
825+
}
826+
}
827+
debug!("combined_docs={}", combined_docs);
828+
829+
let (krate, parent_node) = if let Some(id) = attr.parent_module {
830+
trace!("docs {:?} came from {:?}", attr.doc, id);
831+
(id.krate, Some(id))
832+
} else {
833+
trace!("no parent found for {:?}", attr.doc);
834+
(item.def_id.krate, parent_node)
835+
};
836+
// NOTE: if there are links that start in one crate and end in another, this will not resolve them.
837+
// This is a degenerate case and it's not supported by rustdoc.
838+
// FIXME: this will break links that start in `#[doc = ...]` and end as a sugared doc. Should this be supported?
839+
for (ori_link, link_range) in markdown_links(&combined_docs) {
840+
let link = self.resolve_link(
841+
&item,
842+
&combined_docs,
843+
&current_item,
844+
parent_node,
845+
&parent_name,
846+
krate,
847+
ori_link,
848+
link_range,
849+
);
850+
if let Some(link) = link {
851+
item.attrs.links.push(link);
852+
}
853+
}
828854
}
829855

830856
if item.is_mod() && !item.attrs.inner_docs {
@@ -846,36 +872,37 @@ impl<'a, 'tcx> DocFolder for LinkCollector<'a, 'tcx> {
846872
impl LinkCollector<'_, '_> {
847873
fn resolve_link(
848874
&self,
849-
item: &mut Item,
875+
item: &Item,
850876
dox: &str,
851877
current_item: &Option<String>,
852878
parent_node: Option<DefId>,
853879
parent_name: &Option<String>,
880+
krate: CrateNum,
854881
ori_link: String,
855882
link_range: Option<Range<usize>>,
856-
) {
883+
) -> Option<ItemLink> {
857884
trace!("considering link '{}'", ori_link);
858885

859886
// Bail early for real links.
860887
if ori_link.contains('/') {
861-
return;
888+
return None;
862889
}
863890

864891
// [] is mostly likely not supposed to be a link
865892
if ori_link.is_empty() {
866-
return;
893+
return None;
867894
}
868895

869896
let cx = self.cx;
870897
let link = ori_link.replace("`", "");
871898
let parts = link.split('#').collect::<Vec<_>>();
872899
let (link, extra_fragment) = if parts.len() > 2 {
873900
anchor_failure(cx, &item, &link, dox, link_range, AnchorFailure::MultipleAnchors);
874-
return;
901+
return None;
875902
} else if parts.len() == 2 {
876903
if parts[0].trim().is_empty() {
877904
// This is an anchor to an element of the current page, nothing to do in here!
878-
return;
905+
return None;
879906
}
880907
(parts[0], Some(parts[1].to_owned()))
881908
} else {
@@ -896,7 +923,7 @@ impl LinkCollector<'_, '_> {
896923
.trim();
897924

898925
if path_str.contains(|ch: char| !(ch.is_alphanumeric() || ch == ':' || ch == '_')) {
899-
return;
926+
return None;
900927
}
901928

902929
// We stripped `()` and `!` when parsing the disambiguator.
@@ -936,7 +963,7 @@ impl LinkCollector<'_, '_> {
936963
link_range,
937964
smallvec![err_kind],
938965
);
939-
return;
966+
return None;
940967
};
941968

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

961988
match self.resolve_with_disambiguator(
@@ -970,7 +997,7 @@ impl LinkCollector<'_, '_> {
970997
link_range.clone(),
971998
) {
972999
Some(x) => x,
973-
None => return,
1000+
None => return None,
9741001
}
9751002
};
9761003

@@ -994,15 +1021,15 @@ impl LinkCollector<'_, '_> {
9941021
link_range,
9951022
AnchorFailure::RustdocAnchorConflict(prim),
9961023
);
997-
return;
1024+
return None;
9981025
}
9991026
res = prim;
10001027
fragment = Some(path.to_owned());
10011028
} else {
10021029
// `[char]` when a `char` module is in scope
10031030
let candidates = vec![res, prim];
10041031
ambiguity_error(cx, &item, path_str, dox, link_range, candidates);
1005-
return;
1032+
return None;
10061033
}
10071034
}
10081035
}
@@ -1026,16 +1053,11 @@ impl LinkCollector<'_, '_> {
10261053
if let Res::PrimTy(..) = res {
10271054
match disambiguator {
10281055
Some(Disambiguator::Primitive | Disambiguator::Namespace(_)) | None => {
1029-
item.attrs.links.push(ItemLink {
1030-
link: ori_link,
1031-
link_text,
1032-
did: None,
1033-
fragment,
1034-
});
1056+
Some(ItemLink { link: ori_link, link_text, did: None, fragment })
10351057
}
10361058
Some(other) => {
10371059
report_mismatch(other, Disambiguator::Primitive);
1038-
return;
1060+
None
10391061
}
10401062
}
10411063
} else {
@@ -1058,7 +1080,7 @@ impl LinkCollector<'_, '_> {
10581080
(actual, Some(Disambiguator::Kind(expected))) if actual == expected => {}
10591081
(_, Some(specified @ Disambiguator::Kind(_) | specified @ Disambiguator::Primitive)) => {
10601082
report_mismatch(specified, Disambiguator::Kind(kind));
1061-
return;
1083+
return None;
10621084
}
10631085
}
10641086
}
@@ -1081,14 +1103,14 @@ impl LinkCollector<'_, '_> {
10811103
}
10821104
}
10831105
let id = register_res(cx, res);
1084-
item.attrs.links.push(ItemLink { link: ori_link, link_text, did: Some(id), fragment });
1106+
Some(ItemLink { link: ori_link, link_text, did: Some(id), fragment })
10851107
}
10861108
}
10871109

10881110
fn resolve_with_disambiguator(
10891111
&self,
10901112
disambiguator: Option<Disambiguator>,
1091-
item: &mut Item,
1113+
item: &Item,
10921114
dox: &str,
10931115
path_str: &str,
10941116
current_item: &Option<String>,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
#![crate_name = "foo"]
2+
3+
// @has foo/struct.JoinPathsError.html '//a[@href="../foo/fn.with_code.html"]' 'crate::with_code'
4+
/// [crate::with_code]
5+
// @has - '//a[@href="../foo/fn.with_code.html"]' 'different text'
6+
/// [different text][with_code]
7+
// @has - '//a[@href="../foo/fn.me_too.html"]' 'me_too'
8+
#[doc = "[me_too]"]
9+
// @has - '//a[@href="../foo/fn.me_three.html"]' 'reference link'
10+
/// This [reference link]
11+
#[doc = "has an attr in the way"]
12+
///
13+
/// [reference link]: me_three
14+
pub use std::env::JoinPathsError;
15+
16+
pub fn with_code() {}
17+
pub fn me_too() {}
18+
pub fn me_three() {}

0 commit comments

Comments
 (0)