Skip to content

Commit edf1148

Browse files
bors[bot]Veykril
andauthored
Merge #8065
8065: Better handling of block doc comments r=Veykril a=Veykril Moves doc string processing to `Attrs::docs`, as we need the indent info from all comments before being able to know how much to strip Closes #7774 Co-authored-by: Lukas Wirth <[email protected]>
2 parents baa1999 + 5734b34 commit edf1148

File tree

10 files changed

+235
-140
lines changed

10 files changed

+235
-140
lines changed

crates/hir_def/src/attr.rs

+56-49
Original file line numberDiff line numberDiff line change
@@ -76,37 +76,23 @@ impl ops::Deref for Attrs {
7676
impl RawAttrs {
7777
pub(crate) const EMPTY: Self = Self { entries: None };
7878

79-
pub(crate) fn new(owner: &dyn AttrsOwner, hygiene: &Hygiene) -> Self {
80-
let attrs: Vec<_> = collect_attrs(owner).collect();
81-
let entries = if attrs.is_empty() {
82-
// Avoid heap allocation
83-
None
84-
} else {
85-
Some(
86-
attrs
87-
.into_iter()
88-
.enumerate()
89-
.flat_map(|(i, attr)| match attr {
90-
Either::Left(attr) => Attr::from_src(attr, hygiene).map(|attr| (i, attr)),
91-
Either::Right(comment) => comment.doc_comment().map(|doc| {
92-
(
93-
i,
94-
Attr {
95-
index: 0,
96-
input: Some(AttrInput::Literal(SmolStr::new(doc))),
97-
path: ModPath::from(hir_expand::name!(doc)),
98-
},
99-
)
100-
}),
101-
})
102-
.map(|(i, attr)| Attr { index: i as u32, ..attr })
103-
.collect(),
104-
)
105-
};
106-
Self { entries }
79+
pub(crate) fn new(owner: &dyn ast::AttrsOwner, hygiene: &Hygiene) -> Self {
80+
let entries = collect_attrs(owner)
81+
.enumerate()
82+
.flat_map(|(i, attr)| match attr {
83+
Either::Left(attr) => Attr::from_src(attr, hygiene, i as u32),
84+
Either::Right(comment) => comment.doc_comment().map(|doc| Attr {
85+
index: i as u32,
86+
input: Some(AttrInput::Literal(SmolStr::new(doc))),
87+
path: ModPath::from(hir_expand::name!(doc)),
88+
}),
89+
})
90+
.collect::<Arc<_>>();
91+
92+
Self { entries: if entries.is_empty() { None } else { Some(entries) } }
10793
}
10894

109-
fn from_attrs_owner(db: &dyn DefDatabase, owner: InFile<&dyn AttrsOwner>) -> Self {
95+
fn from_attrs_owner(db: &dyn DefDatabase, owner: InFile<&dyn ast::AttrsOwner>) -> Self {
11096
let hygiene = Hygiene::new(db.upcast(), owner.file_id);
11197
Self::new(owner.value, &hygiene)
11298
}
@@ -162,7 +148,7 @@ impl RawAttrs {
162148
let attr = ast::Attr::parse(&format!("#[{}]", tree)).ok()?;
163149
// FIXME hygiene
164150
let hygiene = Hygiene::new_unhygienic();
165-
Attr::from_src(attr, &hygiene).map(|attr| Attr { index, ..attr })
151+
Attr::from_src(attr, &hygiene, index)
166152
});
167153

168154
let cfg_options = &crate_graph[krate].cfg_options;
@@ -192,7 +178,7 @@ impl Attrs {
192178
Some(it) => {
193179
let raw_attrs = RawAttrs::from_attrs_owner(
194180
db,
195-
it.as_ref().map(|it| it as &dyn AttrsOwner),
181+
it.as_ref().map(|it| it as &dyn ast::AttrsOwner),
196182
);
197183
match mod_data.definition_source(db) {
198184
InFile { file_id, value: ModuleSource::SourceFile(file) } => raw_attrs
@@ -203,9 +189,9 @@ impl Attrs {
203189
None => RawAttrs::from_attrs_owner(
204190
db,
205191
mod_data.definition_source(db).as_ref().map(|src| match src {
206-
ModuleSource::SourceFile(file) => file as &dyn AttrsOwner,
207-
ModuleSource::Module(module) => module as &dyn AttrsOwner,
208-
ModuleSource::BlockExpr(block) => block as &dyn AttrsOwner,
192+
ModuleSource::SourceFile(file) => file as &dyn ast::AttrsOwner,
193+
ModuleSource::Module(module) => module as &dyn ast::AttrsOwner,
194+
ModuleSource::BlockExpr(block) => block as &dyn ast::AttrsOwner,
209195
}),
210196
),
211197
}
@@ -263,7 +249,7 @@ impl Attrs {
263249
let mut res = ArenaMap::default();
264250

265251
for (id, var) in src.value.iter() {
266-
let attrs = RawAttrs::from_attrs_owner(db, src.with_value(var as &dyn AttrsOwner))
252+
let attrs = RawAttrs::from_attrs_owner(db, src.with_value(var as &dyn ast::AttrsOwner))
267253
.filter(db, krate);
268254

269255
res.insert(id, attrs)
@@ -297,7 +283,7 @@ impl Attrs {
297283
/// Constructs a map that maps the lowered `Attr`s in this `Attrs` back to its original syntax nodes.
298284
///
299285
/// `owner` must be the original owner of the attributes.
300-
pub fn source_map(&self, owner: &dyn AttrsOwner) -> AttrSourceMap {
286+
pub fn source_map(&self, owner: &dyn ast::AttrsOwner) -> AttrSourceMap {
301287
AttrSourceMap { attrs: collect_attrs(owner).collect() }
302288
}
303289

@@ -325,15 +311,34 @@ impl Attrs {
325311
AttrInput::Literal(s) => Some(s),
326312
AttrInput::TokenTree(_) => None,
327313
});
328-
// FIXME: Replace `Itertools::intersperse` with `Iterator::intersperse[_with]` until the
329-
// libstd api gets stabilized (https://github.com/rust-lang/rust/issues/79524).
330-
let docs = Itertools::intersperse(docs, &SmolStr::new_inline("\n"))
331-
.map(|it| it.as_str())
332-
.collect::<String>();
333-
if docs.is_empty() {
314+
let indent = docs
315+
.clone()
316+
.flat_map(|s| s.lines())
317+
.filter(|line| !line.chars().all(|c| c.is_whitespace()))
318+
.map(|line| line.chars().take_while(|c| c.is_whitespace()).count())
319+
.min()
320+
.unwrap_or(0);
321+
let mut buf = String::new();
322+
for doc in docs {
323+
// str::lines doesn't yield anything for the empty string
324+
if !doc.is_empty() {
325+
buf.extend(Itertools::intersperse(
326+
doc.lines().map(|line| {
327+
line.char_indices()
328+
.nth(indent)
329+
.map_or(line, |(offset, _)| &line[offset..])
330+
.trim_end()
331+
}),
332+
"\n",
333+
));
334+
}
335+
buf.push('\n');
336+
}
337+
buf.pop();
338+
if buf.is_empty() {
334339
None
335340
} else {
336-
Some(Documentation(docs))
341+
Some(Documentation(buf))
337342
}
338343
}
339344
}
@@ -407,7 +412,7 @@ pub enum AttrInput {
407412
}
408413

409414
impl Attr {
410-
fn from_src(ast: ast::Attr, hygiene: &Hygiene) -> Option<Attr> {
415+
fn from_src(ast: ast::Attr, hygiene: &Hygiene, index: u32) -> Option<Attr> {
411416
let path = ModPath::from_src(ast.path()?, hygiene)?;
412417
let input = if let Some(lit) = ast.literal() {
413418
let value = match lit.kind() {
@@ -420,7 +425,7 @@ impl Attr {
420425
} else {
421426
None
422427
};
423-
Some(Attr { index: 0, path, input })
428+
Some(Attr { index, path, input })
424429
}
425430

426431
/// Maps this lowered `Attr` back to its original syntax node.
@@ -429,7 +434,7 @@ impl Attr {
429434
///
430435
/// Note that the returned syntax node might be a `#[cfg_attr]`, or a doc comment, instead of
431436
/// the attribute represented by `Attr`.
432-
pub fn to_src(&self, owner: &dyn AttrsOwner) -> Either<ast::Attr, ast::Comment> {
437+
pub fn to_src(&self, owner: &dyn ast::AttrsOwner) -> Either<ast::Attr, ast::Comment> {
433438
collect_attrs(owner).nth(self.index as usize).unwrap_or_else(|| {
434439
panic!("cannot find `Attr` at index {} in {}", self.index, owner.syntax())
435440
})
@@ -508,7 +513,7 @@ impl<'a> AttrQuery<'a> {
508513
self.attrs().next().is_some()
509514
}
510515

511-
pub fn attrs(self) -> impl Iterator<Item = &'a Attr> {
516+
pub fn attrs(self) -> impl Iterator<Item = &'a Attr> + Clone {
512517
let key = self.key;
513518
self.attrs
514519
.iter()
@@ -521,7 +526,7 @@ where
521526
N: ast::AttrsOwner,
522527
{
523528
let src = InFile::new(src.file_id, src.to_node(db.upcast()));
524-
RawAttrs::from_attrs_owner(db, src.as_ref().map(|it| it as &dyn AttrsOwner))
529+
RawAttrs::from_attrs_owner(db, src.as_ref().map(|it| it as &dyn ast::AttrsOwner))
525530
}
526531

527532
fn attrs_from_item_tree<N: ItemTreeNode>(id: ItemTreeId<N>, db: &dyn DefDatabase) -> RawAttrs {
@@ -530,7 +535,9 @@ fn attrs_from_item_tree<N: ItemTreeNode>(id: ItemTreeId<N>, db: &dyn DefDatabase
530535
tree.raw_attrs(mod_item.into()).clone()
531536
}
532537

533-
fn collect_attrs(owner: &dyn AttrsOwner) -> impl Iterator<Item = Either<ast::Attr, ast::Comment>> {
538+
fn collect_attrs(
539+
owner: &dyn ast::AttrsOwner,
540+
) -> impl Iterator<Item = Either<ast::Attr, ast::Comment>> {
534541
let (inner_attrs, inner_docs) = inner_attributes(owner.syntax())
535542
.map_or((None, None), |(attrs, docs)| ((Some(attrs), Some(docs))));
536543

crates/ide/src/goto_definition.rs

+3-5
Original file line numberDiff line numberDiff line change
@@ -95,12 +95,10 @@ fn extract_positioned_link_from_comment(
9595
let comment_range = comment.syntax().text_range();
9696
let doc_comment = comment.doc_comment()?;
9797
let def_links = extract_definitions_from_markdown(doc_comment);
98+
let start = comment_range.start() + TextSize::from(comment.prefix().len() as u32);
9899
let (def_link, ns, _) = def_links.iter().min_by_key(|(_, _, def_link_range)| {
99-
let matched_position = comment_range.start() + TextSize::from(def_link_range.start as u32);
100-
match position.offset.checked_sub(matched_position) {
101-
Some(distance) => distance,
102-
None => comment_range.end(),
103-
}
100+
let matched_position = start + TextSize::from(def_link_range.start as u32);
101+
position.offset.checked_sub(matched_position).unwrap_or_else(|| comment_range.end())
104102
})?;
105103
Some((def_link.to_string(), *ns))
106104
}

crates/ide/src/hover.rs

+56-4
Original file line numberDiff line numberDiff line change
@@ -1533,12 +1533,21 @@ fn my() {}
15331533
fn test_hover_struct_doc_comment() {
15341534
check(
15351535
r#"
1536-
/// bar docs
1536+
/// This is an example
1537+
/// multiline doc
1538+
///
1539+
/// # Example
1540+
///
1541+
/// ```
1542+
/// let five = 5;
1543+
///
1544+
/// assert_eq!(6, my_crate::add_one(5));
1545+
/// ```
15371546
struct Bar;
15381547
15391548
fn foo() { let bar = Ba$0r; }
15401549
"#,
1541-
expect![[r#"
1550+
expect![[r##"
15421551
*Bar*
15431552
15441553
```rust
@@ -1551,8 +1560,17 @@ fn foo() { let bar = Ba$0r; }
15511560
15521561
---
15531562
1554-
bar docs
1555-
"#]],
1563+
This is an example
1564+
multiline doc
1565+
1566+
# Example
1567+
1568+
```
1569+
let five = 5;
1570+
1571+
assert_eq!(6, my_crate::add_one(5));
1572+
```
1573+
"##]],
15561574
);
15571575
}
15581576

@@ -3423,6 +3441,40 @@ mod Foo$0 {
34233441
);
34243442
}
34253443

3444+
#[test]
3445+
fn hover_doc_block_style_indentend() {
3446+
check(
3447+
r#"
3448+
/**
3449+
foo
3450+
```rust
3451+
let x = 3;
3452+
```
3453+
*/
3454+
fn foo$0() {}
3455+
"#,
3456+
expect![[r#"
3457+
*foo*
3458+
3459+
```rust
3460+
test
3461+
```
3462+
3463+
```rust
3464+
fn foo()
3465+
```
3466+
3467+
---
3468+
3469+
foo
3470+
3471+
```rust
3472+
let x = 3;
3473+
```
3474+
"#]],
3475+
);
3476+
}
3477+
34263478
#[test]
34273479
fn hover_comments_dont_highlight_parent() {
34283480
check_hover_no_result(

crates/ide/src/runnables.rs

+46-2
Original file line numberDiff line numberDiff line change
@@ -576,6 +576,20 @@ fn should_have_runnable_1() {}
576576
/// ```
577577
fn should_have_runnable_2() {}
578578
579+
/**
580+
```rust
581+
let z = 55;
582+
```
583+
*/
584+
fn should_have_no_runnable_3() {}
585+
586+
/**
587+
```rust
588+
let z = 55;
589+
```
590+
*/
591+
fn should_have_no_runnable_4() {}
592+
579593
/// ```no_run
580594
/// let z = 55;
581595
/// ```
@@ -616,7 +630,7 @@ fn should_have_no_runnable_6() {}
616630
struct StructWithRunnable(String);
617631
618632
"#,
619-
&[&BIN, &DOCTEST, &DOCTEST, &DOCTEST, &DOCTEST],
633+
&[&BIN, &DOCTEST, &DOCTEST, &DOCTEST, &DOCTEST, &DOCTEST, &DOCTEST],
620634
expect![[r#"
621635
[
622636
Runnable {
@@ -682,7 +696,37 @@ struct StructWithRunnable(String);
682696
file_id: FileId(
683697
0,
684698
),
685-
full_range: 756..821,
699+
full_range: 256..320,
700+
name: "should_have_no_runnable_3",
701+
},
702+
kind: DocTest {
703+
test_id: Path(
704+
"should_have_no_runnable_3",
705+
),
706+
},
707+
cfg: None,
708+
},
709+
Runnable {
710+
nav: NavigationTarget {
711+
file_id: FileId(
712+
0,
713+
),
714+
full_range: 322..398,
715+
name: "should_have_no_runnable_4",
716+
},
717+
kind: DocTest {
718+
test_id: Path(
719+
"should_have_no_runnable_4",
720+
),
721+
},
722+
cfg: None,
723+
},
724+
Runnable {
725+
nav: NavigationTarget {
726+
file_id: FileId(
727+
0,
728+
),
729+
full_range: 900..965,
686730
name: "StructWithRunnable",
687731
},
688732
kind: DocTest {

crates/ide_completion/src/lib.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -255,7 +255,7 @@ fn foo() {
255255
bar.fo$0;
256256
}
257257
"#,
258-
DetailAndDocumentation { detail: "fn(&self)", documentation: " Do the foo" },
258+
DetailAndDocumentation { detail: "fn(&self)", documentation: "Do the foo" },
259259
);
260260
}
261261

crates/ide_db/src/call_info.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -53,15 +53,15 @@ pub fn call_info(db: &RootDatabase, position: FilePosition) -> Option<CallInfo>
5353

5454
match callable.kind() {
5555
hir::CallableKind::Function(func) => {
56-
res.doc = func.docs(db).map(|it| it.as_str().to_string());
56+
res.doc = func.docs(db).map(|it| it.into());
5757
format_to!(res.signature, "fn {}", func.name(db));
5858
}
5959
hir::CallableKind::TupleStruct(strukt) => {
60-
res.doc = strukt.docs(db).map(|it| it.as_str().to_string());
60+
res.doc = strukt.docs(db).map(|it| it.into());
6161
format_to!(res.signature, "struct {}", strukt.name(db));
6262
}
6363
hir::CallableKind::TupleEnumVariant(variant) => {
64-
res.doc = variant.docs(db).map(|it| it.as_str().to_string());
64+
res.doc = variant.docs(db).map(|it| it.into());
6565
format_to!(
6666
res.signature,
6767
"enum {}::{}",

0 commit comments

Comments
 (0)