Skip to content

Commit 842ad62

Browse files
Represent doc comments using their own node and not as COMMENT trivia
For two reasons: - This simplifies my work to fix #23088; to fix that issue, macros must have to be able to return doc comments (and not just desugared doc comments), and code in `syntax-bridge` doesn't expect macros to return trivia. Making doc comments non-trivia solves that. - It should simplify the work to attach trivia to tokens; doc comments have no obvious place to attach (for example, when between two attributes we must attach them to either the preceding `]` or the following `#`, both will complicate code handling them). Furthermore, arguably doc comments are really not a trivia: it's an error to put them in an unexpected place, and reason 2 above reveals that they're more like a kind of an attribute than a comment. This touches a lot of places (especially assists etc.) subtly; I fixed what I found and the tests helped reveal more, but it's certainly possible some places are still not handling them correctly now.
1 parent 919d6c2 commit 842ad62

54 files changed

Lines changed: 696 additions & 706 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

crates/hir-def/src/attrs/docs.rs

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,8 @@ use hir_expand::{
2222
};
2323
use span::AstIdMap;
2424
use syntax::{
25-
AstNode, AstToken, SyntaxNode,
26-
ast::{self, AttrDocCommentIter, IsString},
25+
AstNode, SyntaxNode,
26+
ast::{self, IsString},
2727
};
2828
use thin_vec::ThinVec;
2929
use tt::{TextRange, TextSize};
@@ -199,10 +199,10 @@ impl Docs {
199199
));
200200
}
201201

202-
fn extend_with_doc_comment(&mut self, comment: ast::Comment, indent: &mut usize) {
203-
let Some((doc, offset)) = comment.doc_comment() else { return };
204-
let offset = comment.syntax().text_range().start() + offset;
205-
self.extend_with_doc_str(doc, offset, indent, comment.kind().shape);
202+
fn extend_with_doc_comment(&mut self, comment: ast::DocComment, indent: &mut usize) {
203+
let doc = comment.text();
204+
let offset = comment.syntax().text_range().start() + ast::DocComment::PREFIX_LEN;
205+
self.extend_with_doc_str(doc, offset, indent, comment.shape());
206206
}
207207

208208
fn extend_with_doc_attr(&mut self, value: ast::String, indent: &mut usize) {
@@ -591,13 +591,13 @@ fn extend_with_attrs<'a, 'db>(
591591
let mut expander = None;
592592

593593
expand_cfg_attr_with_doc_comments::<_, Infallible>(
594-
AttrDocCommentIter::from_syntax_node(node).filter(|attr| match attr {
595-
Either::Left(attr) => attr.kind().is_inner() == expect_inner_attrs,
596-
Either::Right(comment) => comment
597-
.kind()
598-
.doc
599-
.is_some_and(|kind| (kind == ast::CommentPlacement::Inner) == expect_inner_attrs),
600-
}),
594+
node.children()
595+
.filter_map(ast::AnyAttr::cast)
596+
.filter(|attr| attr.kind().is_inner() == expect_inner_attrs)
597+
.map(|attr| match attr {
598+
ast::AnyAttr::Attr(it) => Either::Left(it),
599+
ast::AnyAttr::DocComment(it) => Either::Right(it),
600+
}),
601601
|| *cfg_options.get_or_insert_with(get_cfg_options),
602602
|attr| {
603603
match attr {
@@ -727,7 +727,7 @@ pub(crate) fn extract_docs<'a, 'db>(
727727
mod tests {
728728
use expect_test::expect;
729729
use hir_expand::InFile;
730-
use syntax::{AstToken, ast};
730+
use syntax::{AstNode, ast};
731731
use test_fixture::WithFixture;
732732
use thin_vec::ThinVec;
733733
use tt::{TextRange, TextSize};
@@ -911,8 +911,8 @@ mod tests {
911911
let comment = syntax::SourceFile::parse(source, span::Edition::CURRENT)
912912
.syntax_node()
913913
.descendants_with_tokens()
914-
.filter_map(|it| it.into_token())
915-
.find_map(ast::Comment::cast)
914+
.filter_map(|it| it.into_node())
915+
.find_map(ast::DocComment::cast)
916916
.expect("no comment in the fixture");
917917
let mut docs = Docs {
918918
docs: String::new(),

crates/ide-assists/src/handlers/convert_comment_block.rs

Lines changed: 19 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use itertools::Itertools;
22
use syntax::{
3-
AstToken, Direction, SyntaxElement, TextRange,
4-
ast::{self, Comment, CommentKind, CommentShape, Whitespace, edit::IndentLevel},
3+
AstToken, SyntaxToken, TextRange,
4+
ast::{self, CommentKind, CommentShape, Whitespace, edit::IndentLevel},
55
};
66

77
use crate::{AssistContext, AssistId, Assists};
@@ -22,19 +22,19 @@ use crate::{AssistContext, AssistId, Assists};
2222
// */
2323
// ```
2424
pub(crate) fn convert_comment_block(acc: &mut Assists, ctx: &AssistContext<'_, '_>) -> Option<()> {
25-
let comment = ctx.find_token_at_offset::<ast::Comment>()?;
25+
let comment = ctx.find_token_at_offset::<ast::AnyComment>()?;
2626
// Only allow comments which are alone on their line
2727
if let Some(prev) = comment.syntax().prev_token() {
2828
Whitespace::cast(prev).filter(|w| w.text().contains('\n'))?;
2929
}
3030

31-
match comment.kind().shape {
31+
match comment.shape() {
3232
ast::CommentShape::Block => block_to_line(acc, comment),
3333
ast::CommentShape::Line => line_to_block(acc, comment),
3434
}
3535
}
3636

37-
fn block_to_line(acc: &mut Assists, comment: ast::Comment) -> Option<()> {
37+
fn block_to_line(acc: &mut Assists, comment: ast::AnyComment) -> Option<()> {
3838
let target = comment.syntax().text_range();
3939

4040
acc.add(
@@ -45,9 +45,7 @@ fn block_to_line(acc: &mut Assists, comment: ast::Comment) -> Option<()> {
4545
let indentation = IndentLevel::from_token(comment.syntax());
4646
let line_prefix = CommentKind { shape: CommentShape::Line, ..comment.kind() }.prefix();
4747

48-
let text = comment.text();
49-
let text = &text[comment.prefix().len()..(text.len() - "*/".len())].trim();
50-
48+
let text = comment.text().trim();
5149
let lines = text.lines().peekable();
5250

5351
let indent_spaces = indentation.to_string();
@@ -69,7 +67,7 @@ fn block_to_line(acc: &mut Assists, comment: ast::Comment) -> Option<()> {
6967
)
7068
}
7169

72-
fn line_to_block(acc: &mut Assists, comment: ast::Comment) -> Option<()> {
70+
fn line_to_block(acc: &mut Assists, comment: ast::AnyComment) -> Option<()> {
7371
// Find all the comments we'll be collapsing into a block
7472
let comments = relevant_line_comments(&comment);
7573

@@ -109,37 +107,26 @@ fn line_to_block(acc: &mut Assists, comment: ast::Comment) -> Option<()> {
109107
/// The line -> block assist can be invoked from anywhere within a sequence of line comments.
110108
/// relevant_line_comments crawls backwards and forwards finding the complete sequence of comments that will
111109
/// be joined.
112-
pub(crate) fn relevant_line_comments(comment: &ast::Comment) -> Vec<Comment> {
113-
// The prefix identifies the kind of comment we're dealing with
114-
let prefix = comment.prefix();
115-
let same_prefix = |c: &ast::Comment| c.prefix() == prefix;
110+
pub(crate) fn relevant_line_comments(comment: &ast::AnyComment) -> Vec<ast::AnyComment> {
111+
let expected_kind = comment.kind();
112+
let same_kind = |c: &ast::AnyComment| c.kind() == expected_kind;
116113

117114
// These tokens are allowed to exist between comments
118-
let skippable = |not: &SyntaxElement| {
119-
not.clone()
120-
.into_token()
121-
.and_then(Whitespace::cast)
122-
.map(|w| !w.spans_multiple_lines())
123-
.unwrap_or(false)
115+
let skippable = |not: &SyntaxToken| {
116+
Whitespace::cast(not.clone()).map(|w| !w.spans_multiple_lines()).unwrap_or(false)
124117
};
125118

126119
// Find all preceding comments (in reverse order) that have the same prefix
127-
let prev_comments = comment
128-
.syntax()
129-
.siblings_with_tokens(Direction::Prev)
120+
let prev_comments = std::iter::successors(Some(comment.syntax().clone()), |it| it.prev_token())
130121
.filter(|s| !skippable(s))
131-
.map(|not| not.into_token().and_then(Comment::cast).filter(same_prefix))
132-
.take_while(|opt_com| opt_com.is_some())
133-
.flatten()
122+
.map_while(ast::AnyComment::cast)
123+
.take_while(same_kind)
134124
.skip(1); // skip the first element so we don't duplicate it in next_comments
135125

136-
let next_comments = comment
137-
.syntax()
138-
.siblings_with_tokens(Direction::Next)
126+
let next_comments = std::iter::successors(Some(comment.syntax().clone()), |it| it.next_token())
139127
.filter(|s| !skippable(s))
140-
.map(|not| not.into_token().and_then(Comment::cast).filter(same_prefix))
141-
.take_while(|opt_com| opt_com.is_some())
142-
.flatten();
128+
.map_while(ast::AnyComment::cast)
129+
.take_while(same_kind);
143130

144131
let mut comments: Vec<_> = prev_comments.collect();
145132
comments.reverse();
@@ -161,7 +148,7 @@ pub(crate) fn relevant_line_comments(comment: &ast::Comment) -> Vec<Comment> {
161148
// */
162149
//
163150
// But since such comments aren't idiomatic we're okay with this.
164-
pub(crate) fn line_comment_text(indentation: IndentLevel, comm: ast::Comment) -> String {
151+
pub(crate) fn line_comment_text(indentation: IndentLevel, comm: ast::AnyComment) -> String {
165152
let text = comm.text();
166153
let contents_without_prefix = text.strip_prefix(comm.prefix()).unwrap_or(text);
167154
let contents = contents_without_prefix.strip_prefix(' ').unwrap_or(contents_without_prefix);

crates/ide-assists/src/handlers/convert_comment_from_or_to_doc.rs

Lines changed: 20 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
use itertools::Itertools;
22
use syntax::{
3-
AstToken, Direction, SyntaxElement, TextRange,
4-
ast::{self, Comment, CommentPlacement, Whitespace, edit::IndentLevel},
3+
AstToken, TextRange,
4+
ast::{self, AttrKind, Whitespace, edit::IndentLevel},
55
};
66

7-
use crate::{AssistContext, AssistId, Assists};
7+
use crate::{
8+
AssistContext, AssistId, Assists, handlers::convert_comment_block::relevant_line_comments,
9+
};
810

911
// Assist: comment_to_doc
1012
//
@@ -23,15 +25,15 @@ pub(crate) fn convert_comment_from_or_to_doc(
2325
acc: &mut Assists,
2426
ctx: &AssistContext<'_, '_>,
2527
) -> Option<()> {
26-
let comment = ctx.find_token_at_offset::<ast::Comment>()?;
28+
let comment = ctx.find_token_at_offset::<ast::AnyComment>()?;
2729

2830
match comment.kind().doc {
2931
Some(_) => doc_to_comment(acc, comment),
3032
None => can_be_doc_comment(&comment).and_then(|style| comment_to_doc(acc, comment, style)),
3133
}
3234
}
3335

34-
fn doc_to_comment(acc: &mut Assists, comment: ast::Comment) -> Option<()> {
36+
fn doc_to_comment(acc: &mut Assists, comment: ast::AnyComment) -> Option<()> {
3537
let target = if comment.kind().shape.is_line() {
3638
line_comments_text_range(&comment)?
3739
} else {
@@ -52,15 +54,15 @@ fn doc_to_comment(acc: &mut Assists, comment: ast::Comment) -> Option<()> {
5254
let prefix = format!("{indentation}//");
5355
relevant_line_comments(&comment)
5456
.iter()
55-
.map(|comment| comment.text())
57+
.map(|comment| comment.text_with_markers())
5658
.flat_map(|text| text.lines())
5759
.map(|line| line.replacen(line_start, &prefix, 1))
5860
.join("\n")
5961
}
6062
ast::CommentShape::Block => {
6163
let block_start = comment.prefix();
6264
comment
63-
.text()
65+
.text_with_markers()
6466
.lines()
6567
.enumerate()
6668
.map(|(idx, line)| {
@@ -78,7 +80,7 @@ fn doc_to_comment(acc: &mut Assists, comment: ast::Comment) -> Option<()> {
7880
)
7981
}
8082

81-
fn comment_to_doc(acc: &mut Assists, comment: ast::Comment, style: CommentPlacement) -> Option<()> {
83+
fn comment_to_doc(acc: &mut Assists, comment: ast::AnyComment, style: AttrKind) -> Option<()> {
8284
let target = if comment.kind().shape.is_line() {
8385
line_comments_text_range(&comment)?
8486
} else {
@@ -96,23 +98,23 @@ fn comment_to_doc(acc: &mut Assists, comment: ast::Comment, style: CommentPlacem
9698
ast::CommentShape::Line => {
9799
let indentation = IndentLevel::from_token(comment.syntax());
98100
let line_start = match style {
99-
CommentPlacement::Inner => format!("{indentation}//!"),
100-
CommentPlacement::Outer => format!("{indentation}///"),
101+
AttrKind::Inner => format!("{indentation}//!"),
102+
AttrKind::Outer => format!("{indentation}///"),
101103
};
102104
relevant_line_comments(&comment)
103105
.iter()
104-
.map(|comment| comment.text())
106+
.map(|comment| comment.text_with_markers())
105107
.flat_map(|text| text.lines())
106108
.map(|line| line.replacen("//", &line_start, 1))
107109
.join("\n")
108110
}
109111
ast::CommentShape::Block => {
110112
let block_start = match style {
111-
CommentPlacement::Inner => "/*!",
112-
CommentPlacement::Outer => "/**",
113+
AttrKind::Inner => "/*!",
114+
AttrKind::Outer => "/**",
113115
};
114116
comment
115-
.text()
117+
.text_with_markers()
116118
.lines()
117119
.enumerate()
118120
.map(|(idx, line)| {
@@ -176,7 +178,7 @@ fn comment_to_doc(acc: &mut Assists, comment: ast::Comment, style: CommentPlacem
176178
/// // Modules only normally get inner documentation when they are defined as a separate file.
177179
/// }
178180
/// ```
179-
fn can_be_doc_comment(comment: &ast::Comment) -> Option<CommentPlacement> {
181+
fn can_be_doc_comment(comment: &ast::AnyComment) -> Option<AttrKind> {
180182
use syntax::SyntaxKind::*;
181183

182184
// if the comment is not on its own line, then we do not propose anything.
@@ -186,59 +188,18 @@ fn can_be_doc_comment(comment: &ast::Comment) -> Option<CommentPlacement> {
186188
Whitespace::cast(prev).filter(|w| w.text().contains('\n'))?;
187189
}
188190
// There is no previous token, this is the start of the file.
189-
None => return Some(CommentPlacement::Inner),
191+
None => return Some(AttrKind::Inner),
190192
}
191193

192194
// check if comment is followed by: `struct`, `trait`, `mod`, `fn`, `type`, `extern crate`,
193195
// `use` or `const`.
194196
let parent = comment.syntax().parent();
195197
let par_kind = parent.as_ref().map(|parent| parent.kind());
196198
matches!(par_kind, Some(STRUCT | TRAIT | MODULE | FN | TYPE_ALIAS | EXTERN_CRATE | USE | CONST))
197-
.then_some(CommentPlacement::Outer)
198-
}
199-
200-
/// The line -> block assist can be invoked from anywhere within a sequence of line comments.
201-
/// relevant_line_comments crawls backwards and forwards finding the complete sequence of comments that will
202-
/// be joined.
203-
pub(crate) fn relevant_line_comments(comment: &ast::Comment) -> Vec<Comment> {
204-
// The prefix identifies the kind of comment we're dealing with
205-
let prefix = comment.prefix();
206-
let same_prefix = |c: &ast::Comment| c.prefix() == prefix;
207-
208-
// These tokens are allowed to exist between comments
209-
let skippable = |not: &SyntaxElement| {
210-
not.clone()
211-
.into_token()
212-
.and_then(Whitespace::cast)
213-
.map(|w| !w.spans_multiple_lines())
214-
.unwrap_or(false)
215-
};
216-
217-
// Find all preceding comments (in reverse order) that have the same prefix
218-
let prev_comments = comment
219-
.syntax()
220-
.siblings_with_tokens(Direction::Prev)
221-
.filter(|s| !skippable(s))
222-
.map(|not| not.into_token().and_then(Comment::cast).filter(same_prefix))
223-
.take_while(|opt_com| opt_com.is_some())
224-
.flatten()
225-
.skip(1); // skip the first element so we don't duplicate it in next_comments
226-
227-
let next_comments = comment
228-
.syntax()
229-
.siblings_with_tokens(Direction::Next)
230-
.filter(|s| !skippable(s))
231-
.map(|not| not.into_token().and_then(Comment::cast).filter(same_prefix))
232-
.take_while(|opt_com| opt_com.is_some())
233-
.flatten();
234-
235-
let mut comments: Vec<_> = prev_comments.collect();
236-
comments.reverse();
237-
comments.extend(next_comments);
238-
comments
199+
.then_some(AttrKind::Outer)
239200
}
240201

241-
fn line_comments_text_range(comment: &ast::Comment) -> Option<TextRange> {
202+
fn line_comments_text_range(comment: &ast::AnyComment) -> Option<TextRange> {
242203
let comments = relevant_line_comments(comment);
243204
let first = comments.first()?;
244205
let indentation = IndentLevel::from_token(first.syntax());

0 commit comments

Comments
 (0)