Skip to content

Commit 05428c5

Browse files
committed
Auto merge of rust-lang#17024 - roife:fix-issue-16980, r=Veykril
fix: handle escaped chars in doc comments fix rust-lang#16980. For `ast::LiteralKind::String`, store the original string value.
2 parents 77ce295 + 3e232bb commit 05428c5

File tree

5 files changed

+68
-14
lines changed

5 files changed

+68
-14
lines changed

crates/hir-def/src/attr.rs

+5-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ pub mod builtin;
55
#[cfg(test)]
66
mod tests;
77

8-
use std::{hash::Hash, ops, slice::Iter as SliceIter};
8+
use std::{borrow::Cow, hash::Hash, ops, slice::Iter as SliceIter};
99

1010
use base_db::CrateId;
1111
use cfg::{CfgExpr, CfgOptions};
@@ -573,6 +573,10 @@ impl<'attr> AttrQuery<'attr> {
573573
self.attrs().find_map(|attr| attr.string_value())
574574
}
575575

576+
pub fn string_value_unescape(self) -> Option<Cow<'attr, str>> {
577+
self.attrs().find_map(|attr| attr.string_value_unescape())
578+
}
579+
576580
pub fn exists(self) -> bool {
577581
self.attrs().next().is_some()
578582
}

crates/hir-def/src/nameres/collector.rs

+9-4
Original file line numberDiff line numberDiff line change
@@ -1917,7 +1917,7 @@ impl ModCollector<'_, '_> {
19171917
}
19181918

19191919
fn collect_module(&mut self, module_id: FileItemTreeId<Mod>, attrs: &Attrs) {
1920-
let path_attr = attrs.by_key("path").string_value();
1920+
let path_attr = attrs.by_key("path").string_value_unescape();
19211921
let is_macro_use = attrs.by_key("macro_use").exists();
19221922
let module = &self.item_tree[module_id];
19231923
match &module.kind {
@@ -1931,7 +1931,8 @@ impl ModCollector<'_, '_> {
19311931
module_id,
19321932
);
19331933

1934-
let Some(mod_dir) = self.mod_dir.descend_into_definition(&module.name, path_attr)
1934+
let Some(mod_dir) =
1935+
self.mod_dir.descend_into_definition(&module.name, path_attr.as_deref())
19351936
else {
19361937
return;
19371938
};
@@ -1952,8 +1953,12 @@ impl ModCollector<'_, '_> {
19521953
ModKind::Outline => {
19531954
let ast_id = AstId::new(self.file_id(), module.ast_id);
19541955
let db = self.def_collector.db;
1955-
match self.mod_dir.resolve_declaration(db, self.file_id(), &module.name, path_attr)
1956-
{
1956+
match self.mod_dir.resolve_declaration(
1957+
db,
1958+
self.file_id(),
1959+
&module.name,
1960+
path_attr.as_deref(),
1961+
) {
19571962
Ok((file_id, is_mod_rs, mod_dir)) => {
19581963
let item_tree = db.file_item_tree(file_id.into());
19591964
let krate = self.def_collector.def_map.krate;

crates/hir-expand/src/attrs.rs

+46-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
//! A higher level attributes based on TokenTree, with also some shortcuts.
2-
use std::{fmt, ops};
2+
use std::{borrow::Cow, fmt, ops};
33

44
use base_db::CrateId;
55
use cfg::CfgExpr;
@@ -8,6 +8,7 @@ use intern::Interned;
88
use mbe::{syntax_node_to_token_tree, DelimiterKind, Punct};
99
use smallvec::{smallvec, SmallVec};
1010
use span::{Span, SyntaxContextId};
11+
use syntax::unescape;
1112
use syntax::{ast, format_smolstr, match_ast, AstNode, AstToken, SmolStr, SyntaxNode};
1213
use triomphe::ThinArc;
1314

@@ -54,8 +55,7 @@ impl RawAttrs {
5455
Attr {
5556
id,
5657
input: Some(Interned::new(AttrInput::Literal(tt::Literal {
57-
// FIXME: Escape quotes from comment content
58-
text: SmolStr::new(format_smolstr!("\"{doc}\"",)),
58+
text: SmolStr::new(format_smolstr!("\"{}\"", Self::escape_chars(doc))),
5959
span,
6060
}))),
6161
path: Interned::new(ModPath::from(crate::name!(doc))),
@@ -74,6 +74,10 @@ impl RawAttrs {
7474
RawAttrs { entries }
7575
}
7676

77+
fn escape_chars(s: &str) -> String {
78+
s.replace('\\', r#"\\"#).replace('"', r#"\""#)
79+
}
80+
7781
pub fn from_attrs_owner(
7882
db: &dyn ExpandDatabase,
7983
owner: InFile<&dyn ast::HasAttrs>,
@@ -297,6 +301,18 @@ impl Attr {
297301
}
298302
}
299303

304+
pub fn string_value_unescape(&self) -> Option<Cow<'_, str>> {
305+
match self.input.as_deref()? {
306+
AttrInput::Literal(it) => match it.text.strip_prefix('r') {
307+
Some(it) => {
308+
it.trim_matches('#').strip_prefix('"')?.strip_suffix('"').map(Cow::Borrowed)
309+
}
310+
None => it.text.strip_prefix('"')?.strip_suffix('"').and_then(unescape),
311+
},
312+
_ => None,
313+
}
314+
}
315+
300316
/// #[path(ident)]
301317
pub fn single_ident_value(&self) -> Option<&tt::Ident> {
302318
match self.input.as_deref()? {
@@ -346,6 +362,33 @@ impl Attr {
346362
}
347363
}
348364

365+
fn unescape(s: &str) -> Option<Cow<'_, str>> {
366+
let mut buf = String::new();
367+
let mut prev_end = 0;
368+
let mut has_error = false;
369+
unescape::unescape_unicode(s, unescape::Mode::Str, &mut |char_range, unescaped_char| match (
370+
unescaped_char,
371+
buf.capacity() == 0,
372+
) {
373+
(Ok(c), false) => buf.push(c),
374+
(Ok(_), true) if char_range.len() == 1 && char_range.start == prev_end => {
375+
prev_end = char_range.end
376+
}
377+
(Ok(c), true) => {
378+
buf.reserve_exact(s.len());
379+
buf.push_str(&s[..prev_end]);
380+
buf.push(c);
381+
}
382+
(Err(_), _) => has_error = true,
383+
});
384+
385+
match (has_error, buf.capacity() == 0) {
386+
(true, _) => None,
387+
(false, false) => Some(Cow::Owned(buf)),
388+
(false, true) => Some(Cow::Borrowed(s)),
389+
}
390+
}
391+
349392
pub fn collect_attrs(
350393
owner: &dyn ast::HasAttrs,
351394
) -> impl Iterator<Item = (AttrId, Either<ast::Attr, ast::Comment>)> {

crates/ide-db/src/documentation.rs

+7-6
Original file line numberDiff line numberDiff line change
@@ -91,8 +91,10 @@ pub fn docs_with_rangemap(
9191
db: &dyn DefDatabase,
9292
attrs: &AttrsWithOwner,
9393
) -> Option<(Documentation, DocsRangeMap)> {
94-
let docs =
95-
attrs.by_key("doc").attrs().filter_map(|attr| attr.string_value().map(|s| (s, attr.id)));
94+
let docs = attrs
95+
.by_key("doc")
96+
.attrs()
97+
.filter_map(|attr| attr.string_value_unescape().map(|s| (s, attr.id)));
9698
let indent = doc_indent(attrs);
9799
let mut buf = String::new();
98100
let mut mapping = Vec::new();
@@ -132,7 +134,7 @@ pub fn docs_with_rangemap(
132134
}
133135

134136
pub fn docs_from_attrs(attrs: &hir::Attrs) -> Option<String> {
135-
let docs = attrs.by_key("doc").attrs().filter_map(|attr| attr.string_value());
137+
let docs = attrs.by_key("doc").attrs().filter_map(|attr| attr.string_value_unescape());
136138
let indent = doc_indent(attrs);
137139
let mut buf = String::new();
138140
for doc in docs {
@@ -270,10 +272,9 @@ fn doc_indent(attrs: &hir::Attrs) -> usize {
270272
attrs
271273
.by_key("doc")
272274
.attrs()
273-
.filter_map(|attr| attr.string_value())
275+
.filter_map(|attr| attr.string_value()) // no need to use unescape version here
274276
.flat_map(|s| s.lines())
275-
.filter(|line| !line.chars().all(|c| c.is_whitespace()))
276-
.map(|line| line.chars().take_while(|c| c.is_whitespace()).count())
277+
.filter_map(|line| line.chars().position(|c| !c.is_whitespace()))
277278
.min()
278279
.unwrap_or(0)
279280
}

crates/syntax/src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ pub use rowan::{
6565
api::Preorder, Direction, GreenNode, NodeOrToken, SyntaxText, TextRange, TextSize,
6666
TokenAtOffset, WalkEvent,
6767
};
68+
pub use rustc_lexer::unescape;
6869
pub use smol_str::{format_smolstr, SmolStr};
6970

7071
/// `Parse` is the result of the parsing: a syntax tree and a collection of

0 commit comments

Comments
 (0)