Skip to content

Commit 9c94def

Browse files
committed
[WIP] Distinguish between links on inner and outer attributes
Normally, rustdoc will resolve `//!` docs in the scope inside the module and `///` in the scope of the parent. But if there are modules with inner docs, it would previously resolve _all_ of the links inside the module. This now distinguishes between links that came from inside the module and links that came from outside. The intra-doc links part works, but unfortunately the rest of rustdoc assumes there is only one canonical resolution for a link and won't look for more than one on the same item. - Store the attribute style on each DocFragment - Don't combine attributes with different styles - Calculate the parent module in only one place - Remove outdated and fixed FIXME comments
1 parent 3478d7c commit 9c94def

File tree

7 files changed

+92
-39
lines changed

7 files changed

+92
-39
lines changed

compiler/rustc_ast/src/ast.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -2388,7 +2388,7 @@ impl UseTree {
23882388
/// Distinguishes between `Attribute`s that decorate items and Attributes that
23892389
/// are contained as statements within items. These two cases need to be
23902390
/// distinguished for pretty-printing.
2391-
#[derive(Clone, PartialEq, Encodable, Decodable, Debug, Copy, HashStable_Generic)]
2391+
#[derive(Clone, PartialEq, Eq, Hash, Encodable, Decodable, Debug, Copy, HashStable_Generic)]
23922392
pub enum AttrStyle {
23932393
Outer,
23942394
Inner,

src/librustdoc/clean/types.rs

+3-7
Original file line numberDiff line numberDiff line change
@@ -385,6 +385,7 @@ pub struct DocFragment {
385385
pub parent_module: Option<DefId>,
386386
pub doc: String,
387387
pub kind: DocFragmentKind,
388+
pub style: AttrStyle,
388389
}
389390

390391
#[derive(Clone, PartialEq, Eq, Debug, Hash)]
@@ -421,7 +422,6 @@ pub struct Attributes {
421422
pub span: Option<rustc_span::Span>,
422423
/// map from Rust paths to resolved defs and potential URL fragments
423424
pub links: Vec<ItemLink>,
424-
pub inner_docs: bool,
425425
}
426426

427427
#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
@@ -559,6 +559,7 @@ impl Attributes {
559559
doc: value,
560560
kind,
561561
parent_module,
562+
style: attr.style,
562563
});
563564

564565
if sp.is_none() {
@@ -584,6 +585,7 @@ impl Attributes {
584585
doc: contents,
585586
kind: DocFragmentKind::Include { filename },
586587
parent_module: parent_module,
588+
style: attr.style,
587589
});
588590
}
589591
}
@@ -618,18 +620,12 @@ impl Attributes {
618620
}
619621
}
620622

621-
let inner_docs = attrs
622-
.iter()
623-
.find(|a| a.doc_str().is_some())
624-
.map_or(true, |a| a.style == AttrStyle::Inner);
625-
626623
Attributes {
627624
doc_strings,
628625
other_attrs,
629626
cfg: if cfg == Cfg::True { None } else { Some(Arc::new(cfg)) },
630627
span: sp,
631628
links: vec![],
632-
inner_docs,
633629
}
634630
}
635631

src/librustdoc/passes/collapse_docs.rs

+3
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,9 @@ fn collapse(doc_strings: &mut Vec<DocFragment>) {
3939
if matches!(*curr_kind, DocFragmentKind::Include { .. })
4040
|| curr_kind != new_kind
4141
|| curr_frag.parent_module != frag.parent_module
42+
|| curr_frag.style != frag.style
4243
{
44+
// Keep these as two separate attributes
4345
if *curr_kind == DocFragmentKind::SugaredDoc
4446
|| *curr_kind == DocFragmentKind::RawDoc
4547
{
@@ -49,6 +51,7 @@ fn collapse(doc_strings: &mut Vec<DocFragment>) {
4951
docs.push(curr_frag);
5052
last_frag = Some(frag);
5153
} else {
54+
// Combine both attributes into one
5255
curr_frag.doc.push('\n');
5356
curr_frag.doc.push_str(&frag.doc);
5457
curr_frag.span = curr_frag.span.to(frag.span);

src/librustdoc/passes/collect_intra_doc_links.rs

+44-30
Original file line numberDiff line numberDiff line change
@@ -737,6 +737,7 @@ fn is_derive_trait_collision<T>(ns: &PerNS<Result<(Res, T), ResolutionFailure<'_
737737

738738
impl<'a, 'tcx> DocFolder for LinkCollector<'a, 'tcx> {
739739
fn fold_item(&mut self, mut item: Item) -> Option<Item> {
740+
use rustc_ast::AttrStyle;
740741
use rustc_middle::ty::DefIdTree;
741742

742743
let parent_node = if item.is_fake() {
@@ -773,7 +774,8 @@ impl<'a, 'tcx> DocFolder for LinkCollector<'a, 'tcx> {
773774

774775
let current_item = match item.inner {
775776
ModuleItem(..) => {
776-
if item.attrs.inner_docs {
777+
// FIXME: this will be wrong if there are both inner and outer docs.
778+
if item.attrs.doc_strings.iter().any(|attr| attr.style == AttrStyle::Inner) {
777779
if item.def_id.is_top_level_module() { item.name.clone() } else { None }
778780
} else {
779781
match parent_node.or(self.mod_ids.last().copied()) {
@@ -798,10 +800,6 @@ impl<'a, 'tcx> DocFolder for LinkCollector<'a, 'tcx> {
798800
_ => item.name.clone(),
799801
};
800802

801-
if item.is_mod() && item.attrs.inner_docs {
802-
self.mod_ids.push(item.def_id);
803-
}
804-
805803
// find item's parent to resolve `Self` in item's docs below
806804
let parent_name = self.cx.as_local_hir_id(item.def_id).and_then(|item_hir| {
807805
let parent_hir = self.cx.tcx.hir().get_parent_item(item_hir);
@@ -839,6 +837,10 @@ impl<'a, 'tcx> DocFolder for LinkCollector<'a, 'tcx> {
839837
}
840838
});
841839

840+
// If there are both inner and outer docs, we want to only resolve the inner docs
841+
// within the module.
842+
let mut seen_inner_docs = false;
843+
842844
// We want to resolve in the lexical scope of the documentation.
843845
// In the presence of re-exports, this is not the same as the module of the item.
844846
// Rather than merging all documentation into one, resolve it one attribute at a time
@@ -849,10 +851,14 @@ impl<'a, 'tcx> DocFolder for LinkCollector<'a, 'tcx> {
849851
// we want `///` and `#[doc]` to count as the same attribute,
850852
// but currently it will treat them as separate.
851853
// As a workaround, combine all attributes with the same parent module into the same attribute.
854+
// NOTE: this can combine attributes across different spans,
855+
// for example both inside and outside a crate.
852856
let mut combined_docs = attr.doc.clone();
853857
loop {
854858
match attrs.peek() {
855-
Some(next) if next.parent_module == attr.parent_module => {
859+
Some(next)
860+
if next.parent_module == attr.parent_module && next.style == attr.style =>
861+
{
856862
combined_docs.push('\n');
857863
combined_docs.push_str(&attrs.next().unwrap().doc);
858864
}
@@ -868,15 +874,39 @@ impl<'a, 'tcx> DocFolder for LinkCollector<'a, 'tcx> {
868874
trace!("no parent found for {:?}", attr.doc);
869875
(item.def_id.krate, parent_node)
870876
};
877+
878+
// In order to correctly resolve intra-doc-links we need to
879+
// pick a base AST node to work from. If the documentation for
880+
// this module came from an inner comment (//!) then we anchor
881+
// our name resolution *inside* the module. If, on the other
882+
// hand it was an outer comment (///) then we anchor the name
883+
// resolution in the parent module on the basis that the names
884+
// used are more likely to be intended to be parent names. For
885+
// this, we set base_node to None for inner comments since
886+
// we've already pushed this node onto the resolution stack but
887+
// for outer comments we explicitly try and resolve against the
888+
// parent_node first.
889+
890+
// NOTE: there is an implicit assumption here that outer docs will always come
891+
// before inner docs.
892+
let base_node = if !seen_inner_docs && item.is_mod() && attr.style == AttrStyle::Inner {
893+
// FIXME(jynelson): once `Self` handling is cleaned up I think we can get rid
894+
// of `mod_ids` altogether
895+
self.mod_ids.push(item.def_id);
896+
seen_inner_docs = true;
897+
Some(item.def_id)
898+
} else {
899+
parent_node
900+
};
901+
871902
// NOTE: if there are links that start in one crate and end in another, this will not resolve them.
872903
// This is a degenerate case and it's not supported by rustdoc.
873-
// FIXME: this will break links that start in `#[doc = ...]` and end as a sugared doc. Should this be supported?
874904
for (ori_link, link_range) in markdown_links(&combined_docs) {
875905
let link = self.resolve_link(
876906
&item,
877907
&combined_docs,
878908
&current_item,
879-
parent_node,
909+
base_node,
880910
&parent_name,
881911
krate,
882912
ori_link,
@@ -888,11 +918,10 @@ impl<'a, 'tcx> DocFolder for LinkCollector<'a, 'tcx> {
888918
}
889919
}
890920

891-
if item.is_mod() && !item.attrs.inner_docs {
892-
self.mod_ids.push(item.def_id);
893-
}
894-
895921
if item.is_mod() {
922+
if !seen_inner_docs {
923+
self.mod_ids.push(item.def_id);
924+
}
896925
let ret = self.fold_item_recur(item);
897926

898927
self.mod_ids.pop();
@@ -910,7 +939,7 @@ impl LinkCollector<'_, '_> {
910939
item: &Item,
911940
dox: &str,
912941
current_item: &Option<String>,
913-
parent_node: Option<DefId>,
942+
base_node: Option<DefId>,
914943
parent_name: &Option<String>,
915944
krate: CrateNum,
916945
ori_link: String,
@@ -968,23 +997,6 @@ impl LinkCollector<'_, '_> {
968997
.map(|d| d.display_for(path_str))
969998
.unwrap_or_else(|| path_str.to_owned());
970999

971-
// In order to correctly resolve intra-doc-links we need to
972-
// pick a base AST node to work from. If the documentation for
973-
// this module came from an inner comment (//!) then we anchor
974-
// our name resolution *inside* the module. If, on the other
975-
// hand it was an outer comment (///) then we anchor the name
976-
// resolution in the parent module on the basis that the names
977-
// used are more likely to be intended to be parent names. For
978-
// this, we set base_node to None for inner comments since
979-
// we've already pushed this node onto the resolution stack but
980-
// for outer comments we explicitly try and resolve against the
981-
// parent_node first.
982-
let base_node = if item.is_mod() && item.attrs.inner_docs {
983-
self.mod_ids.last().copied()
984-
} else {
985-
parent_node
986-
};
987-
9881000
let mut module_id = if let Some(id) = base_node {
9891001
id
9901002
} else {
@@ -1185,6 +1197,8 @@ impl LinkCollector<'_, '_> {
11851197
ori_link: &str,
11861198
link_range: Option<Range<usize>>,
11871199
) -> Option<(Res, Option<String>)> {
1200+
debug!("resolving {} relative to {:?}", path_str, base_node);
1201+
11881202
match disambiguator.map(Disambiguator::ns) {
11891203
Some(ns @ (ValueNS | TypeNS)) => {
11901204
match self.resolve(path_str, ns, &current_item, base_node, &extra_fragment) {

src/librustdoc/passes/mod.rs

+5-1
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,9 @@ crate fn source_span_for_markdown_range(
179179
return None;
180180
}
181181

182+
trace!("markdown is {}", markdown);
182183
let snippet = cx.sess().source_map().span_to_snippet(span_of_attrs(attrs)?).ok()?;
184+
trace!("snippet is {}", snippet);
183185

184186
let starting_line = markdown[..md_range.start].matches('\n').count();
185187
let ending_line = starting_line + markdown[md_range.start..md_range.end].matches('\n').count();
@@ -196,7 +198,9 @@ crate fn source_span_for_markdown_range(
196198

197199
'outer: for (line_no, md_line) in md_lines.enumerate() {
198200
loop {
199-
let source_line = src_lines.next().expect("could not find markdown in source");
201+
let source_line = src_lines.next().unwrap_or_else(|| {
202+
panic!("could not find markdown range {:?} in source", md_range)
203+
});
200204
match source_line.find(md_line) {
201205
Some(offset) => {
202206
if line_no == starting_line {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
// check-pass
2+
3+
pub enum A {}
4+
5+
/// Links to [outer][A] and [outer][B]
6+
//~^ WARNING: unresolved link to `B`
7+
pub mod M {
8+
//! Links to [inner][A] and [inner][B]
9+
//~^ WARNING: unresolved link to `A`
10+
11+
pub struct B;
12+
}
+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
#![crate_name = "foo"]
2+
3+
pub enum A {}
4+
5+
/// Links to [outer A][A] and [outer B][B]
6+
// @has foo/M/index.html '//*[@href="../foo/enum.A.html"]' 'outer A'
7+
// @!has foo/M/index.html '//*[@href="../foo/struct.B.html"]' 'outer B'
8+
// doesn't resolve unknown links
9+
pub mod M {
10+
//! Links to [inner A][A] and [inner B][B]
11+
// @!has foo/M/index.html '//*[@href="../foo/enum.A.html"]' 'inner A'
12+
// @has foo/M/index.html '//*[@href="../foo/struct.B.html"]' 'inner B'
13+
pub struct B;
14+
}
15+
16+
// distinguishes between links to inner and outer attributes
17+
/// Links to [outer A][A]
18+
// @has foo/N/index.html '//*[@href="../foo/enum.A.html"]' 'outer A'
19+
pub mod N {
20+
//! Links to [inner A][A]
21+
// @has foo/N/index.html '//*[@href="../foo/struct.A.html"]' 'inner A'
22+
23+
pub struct A;
24+
}

0 commit comments

Comments
 (0)