Skip to content

Commit 994f3cf

Browse files
committed
Auto merge of rust-lang#12636 - xuhongxu96:fix-12148, r=Veykril
complete raw identifier with "r#" prefix Fix rust-lang#12148 Escape Names and Paths used in `insert_text`/`insert_snippet` while rendering the completion items.
2 parents ed44fe5 + f536766 commit 994f3cf

File tree

12 files changed

+304
-60
lines changed

12 files changed

+304
-60
lines changed

crates/hir-expand/src/mod_path.rs

+25-4
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@ pub struct ModPath {
2020
segments: Vec<Name>,
2121
}
2222

23+
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
24+
pub struct EscapedModPath<'a>(&'a ModPath);
25+
2326
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
2427
pub enum PathKind {
2528
Plain,
@@ -97,10 +100,12 @@ impl ModPath {
97100
_ => None,
98101
}
99102
}
100-
}
101103

102-
impl Display for ModPath {
103-
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
104+
pub fn escaped(&self) -> EscapedModPath {
105+
EscapedModPath(self)
106+
}
107+
108+
fn _fmt(&self, f: &mut fmt::Formatter<'_>, escaped: bool) -> fmt::Result {
104109
let mut first_segment = true;
105110
let mut add_segment = |s| -> fmt::Result {
106111
if !first_segment {
@@ -127,12 +132,28 @@ impl Display for ModPath {
127132
f.write_str("::")?;
128133
}
129134
first_segment = false;
130-
segment.fmt(f)?;
135+
if escaped {
136+
segment.escaped().fmt(f)?
137+
} else {
138+
segment.fmt(f)?
139+
};
131140
}
132141
Ok(())
133142
}
134143
}
135144

145+
impl Display for ModPath {
146+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
147+
self._fmt(f, false)
148+
}
149+
}
150+
151+
impl<'a> Display for EscapedModPath<'a> {
152+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
153+
self.0._fmt(f, true)
154+
}
155+
}
156+
136157
impl From<Name> for ModPath {
137158
fn from(name: Name) -> ModPath {
138159
ModPath::from_segments(PathKind::Plain, iter::once(name))

crates/hir-expand/src/name.rs

+54-1
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,18 @@
22
33
use std::fmt;
44

5-
use syntax::{ast, SmolStr};
5+
use syntax::{ast, SmolStr, SyntaxKind};
66

77
/// `Name` is a wrapper around string, which is used in hir for both references
88
/// and declarations. In theory, names should also carry hygiene info, but we are
99
/// not there yet!
1010
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
1111
pub struct Name(Repr);
1212

13+
/// `EscapedName` will add a prefix "r#" to the wrapped `Name` when it is a raw identifier
14+
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
15+
pub struct EscapedName<'a>(&'a Name);
16+
1317
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
1418
enum Repr {
1519
Text(SmolStr),
@@ -25,6 +29,51 @@ impl fmt::Display for Name {
2529
}
2630
}
2731

32+
fn is_raw_identifier(name: &str) -> bool {
33+
let is_keyword = SyntaxKind::from_keyword(name).is_some();
34+
is_keyword && !matches!(name, "self" | "crate" | "super" | "Self")
35+
}
36+
37+
impl<'a> fmt::Display for EscapedName<'a> {
38+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
39+
match &self.0 .0 {
40+
Repr::Text(text) => {
41+
if is_raw_identifier(text) {
42+
write!(f, "r#{}", &text)
43+
} else {
44+
fmt::Display::fmt(&text, f)
45+
}
46+
}
47+
Repr::TupleField(idx) => fmt::Display::fmt(&idx, f),
48+
}
49+
}
50+
}
51+
52+
impl<'a> EscapedName<'a> {
53+
pub fn is_escaped(&self) -> bool {
54+
match &self.0 .0 {
55+
Repr::Text(it) => is_raw_identifier(&it),
56+
Repr::TupleField(_) => false,
57+
}
58+
}
59+
60+
/// Returns the textual representation of this name as a [`SmolStr`].
61+
/// Prefer using this over [`ToString::to_string`] if possible as this conversion is cheaper in
62+
/// the general case.
63+
pub fn to_smol_str(&self) -> SmolStr {
64+
match &self.0 .0 {
65+
Repr::Text(it) => {
66+
if is_raw_identifier(&it) {
67+
SmolStr::from_iter(["r#", &it])
68+
} else {
69+
it.clone()
70+
}
71+
}
72+
Repr::TupleField(it) => SmolStr::new(&it.to_string()),
73+
}
74+
}
75+
}
76+
2877
impl Name {
2978
/// Note: this is private to make creating name from random string hard.
3079
/// Hopefully, this should allow us to integrate hygiene cleaner in the
@@ -92,6 +141,10 @@ impl Name {
92141
Repr::TupleField(it) => SmolStr::new(&it.to_string()),
93142
}
94143
}
144+
145+
pub fn escaped(&self) -> EscapedName {
146+
EscapedName(self)
147+
}
95148
}
96149

97150
pub trait AsName {

crates/ide-completion/src/completions/item_list/trait_impl.rs

+3-2
Original file line numberDiff line numberDiff line change
@@ -232,10 +232,11 @@ fn add_type_alias_impl(
232232
replacement_range: TextRange,
233233
type_alias: hir::TypeAlias,
234234
) {
235-
let alias_name = type_alias.name(ctx.db).to_smol_str();
235+
let alias_name = type_alias.name(ctx.db);
236+
let (alias_name, escaped_name) = (alias_name.to_smol_str(), alias_name.escaped().to_smol_str());
236237

237238
let label = format!("type {} =", alias_name);
238-
let replacement = format!("type {} = ", alias_name);
239+
let replacement = format!("type {} = ", escaped_name);
239240

240241
let mut item = CompletionItem::new(SymbolKind::TypeAlias, replacement_range, label);
241242
item.lookup_by(format!("type {}", alias_name))

crates/ide-completion/src/render.rs

+155-9
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,8 @@ pub(crate) fn render_field(
116116
ty: &hir::Type,
117117
) -> CompletionItem {
118118
let is_deprecated = ctx.is_deprecated(field);
119-
let name = field.name(ctx.db()).to_smol_str();
119+
let name = field.name(ctx.db());
120+
let (name, escaped_name) = (name.to_smol_str(), name.escaped().to_smol_str());
120121
let mut item = CompletionItem::new(
121122
SymbolKind::Field,
122123
ctx.source_range(),
@@ -131,10 +132,7 @@ pub(crate) fn render_field(
131132
.set_documentation(field.docs(ctx.db()))
132133
.set_deprecated(is_deprecated)
133134
.lookup_by(name.clone());
134-
let is_keyword = SyntaxKind::from_keyword(name.as_str()).is_some();
135-
if is_keyword && !matches!(name.as_str(), "self" | "crate" | "super" | "Self") {
136-
item.insert_text(format!("r#{}", name));
137-
}
135+
item.insert_text(escaped_name);
138136
if let Some(receiver) = &dot_access.receiver {
139137
if let Some(ref_match) = compute_ref_match(ctx.completion, ty) {
140138
item.ref_match(ref_match, receiver.syntax().text_range().start());
@@ -235,7 +233,7 @@ fn render_resolution_pat(
235233
_ => (),
236234
}
237235

238-
render_resolution_simple_(ctx, local_name, import_to_add, resolution)
236+
render_resolution_simple_(ctx, &local_name, import_to_add, resolution)
239237
}
240238

241239
fn render_resolution_path(
@@ -274,7 +272,10 @@ fn render_resolution_path(
274272
let config = completion.config;
275273

276274
let name = local_name.to_smol_str();
277-
let mut item = render_resolution_simple_(ctx, local_name, import_to_add, resolution);
275+
let mut item = render_resolution_simple_(ctx, &local_name, import_to_add, resolution);
276+
if local_name.escaped().is_escaped() {
277+
item.insert_text(local_name.escaped().to_smol_str());
278+
}
278279
// Add `<>` for generic types
279280
let type_path_no_ty_args = matches!(
280281
path_ctx,
@@ -295,7 +296,7 @@ fn render_resolution_path(
295296
item.lookup_by(name.clone())
296297
.label(SmolStr::from_iter([&name, "<…>"]))
297298
.trigger_call_info()
298-
.insert_snippet(cap, format!("{}<$0>", name));
299+
.insert_snippet(cap, format!("{}<$0>", local_name.escaped()));
299300
}
300301
}
301302
}
@@ -321,7 +322,7 @@ fn render_resolution_path(
321322

322323
fn render_resolution_simple_(
323324
ctx: RenderContext<'_>,
324-
local_name: hir::Name,
325+
local_name: &hir::Name,
325326
import_to_add: Option<LocatedImport>,
326327
resolution: ScopeDef,
327328
) -> Builder {
@@ -1725,4 +1726,149 @@ fn f() {
17251726
"#]],
17261727
);
17271728
}
1729+
1730+
#[test]
1731+
fn completes_struct_with_raw_identifier() {
1732+
check_edit(
1733+
"type",
1734+
r#"
1735+
mod m { pub struct r#type {} }
1736+
fn main() {
1737+
let r#type = m::t$0;
1738+
}
1739+
"#,
1740+
r#"
1741+
mod m { pub struct r#type {} }
1742+
fn main() {
1743+
let r#type = m::r#type;
1744+
}
1745+
"#,
1746+
)
1747+
}
1748+
1749+
#[test]
1750+
fn completes_fn_with_raw_identifier() {
1751+
check_edit(
1752+
"type",
1753+
r#"
1754+
mod m { pub fn r#type {} }
1755+
fn main() {
1756+
m::t$0
1757+
}
1758+
"#,
1759+
r#"
1760+
mod m { pub fn r#type {} }
1761+
fn main() {
1762+
m::r#type()$0
1763+
}
1764+
"#,
1765+
)
1766+
}
1767+
1768+
#[test]
1769+
fn completes_macro_with_raw_identifier() {
1770+
check_edit(
1771+
"let!",
1772+
r#"
1773+
macro_rules! r#let { () => {} }
1774+
fn main() {
1775+
$0
1776+
}
1777+
"#,
1778+
r#"
1779+
macro_rules! r#let { () => {} }
1780+
fn main() {
1781+
r#let!($0)
1782+
}
1783+
"#,
1784+
)
1785+
}
1786+
1787+
#[test]
1788+
fn completes_variant_with_raw_identifier() {
1789+
check_edit(
1790+
"type",
1791+
r#"
1792+
enum A { r#type }
1793+
fn main() {
1794+
let a = A::t$0
1795+
}
1796+
"#,
1797+
r#"
1798+
enum A { r#type }
1799+
fn main() {
1800+
let a = A::r#type$0
1801+
}
1802+
"#,
1803+
)
1804+
}
1805+
1806+
#[test]
1807+
fn completes_field_with_raw_identifier() {
1808+
check_edit(
1809+
"fn",
1810+
r#"
1811+
mod r#type {
1812+
pub struct r#struct {
1813+
pub r#fn: u32
1814+
}
1815+
}
1816+
1817+
fn main() {
1818+
let a = r#type::r#struct {};
1819+
a.$0
1820+
}
1821+
"#,
1822+
r#"
1823+
mod r#type {
1824+
pub struct r#struct {
1825+
pub r#fn: u32
1826+
}
1827+
}
1828+
1829+
fn main() {
1830+
let a = r#type::r#struct {};
1831+
a.r#fn
1832+
}
1833+
"#,
1834+
)
1835+
}
1836+
1837+
#[test]
1838+
fn completes_const_with_raw_identifier() {
1839+
check_edit(
1840+
"type",
1841+
r#"
1842+
struct r#struct {}
1843+
impl r#struct { pub const r#type: u8 = 1; }
1844+
fn main() {
1845+
r#struct::t$0
1846+
}
1847+
"#,
1848+
r#"
1849+
struct r#struct {}
1850+
impl r#struct { pub const r#type: u8 = 1; }
1851+
fn main() {
1852+
r#struct::r#type
1853+
}
1854+
"#,
1855+
)
1856+
}
1857+
1858+
#[test]
1859+
fn completes_type_alias_with_raw_identifier() {
1860+
check_edit(
1861+
"type type",
1862+
r#"
1863+
struct r#struct {}
1864+
trait r#trait { type r#type; }
1865+
impl r#trait for r#struct { type t$0 }
1866+
"#,
1867+
r#"
1868+
struct r#struct {}
1869+
trait r#trait { type r#type; }
1870+
impl r#trait for r#struct { type r#type = $0; }
1871+
"#,
1872+
)
1873+
}
17281874
}

crates/ide-completion/src/render/const_.rs

+3-2
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@ pub(crate) fn render_const(ctx: RenderContext<'_>, const_: hir::Const) -> Option
1212

1313
fn render(ctx: RenderContext<'_>, const_: hir::Const) -> Option<CompletionItem> {
1414
let db = ctx.db();
15-
let name = const_.name(db)?.to_smol_str();
15+
let name = const_.name(db)?;
16+
let (name, escaped_name) = (name.to_smol_str(), name.escaped().to_smol_str());
1617
let detail = const_.display(db).to_string();
1718

1819
let mut item = CompletionItem::new(SymbolKind::Const, ctx.source_range(), name.clone());
@@ -24,9 +25,9 @@ fn render(ctx: RenderContext<'_>, const_: hir::Const) -> Option<CompletionItem>
2425
if let Some(actm) = const_.as_assoc_item(db) {
2526
if let Some(trt) = actm.containing_trait_or_trait_impl(db) {
2627
item.trait_name(trt.name(db).to_smol_str());
27-
item.insert_text(name);
2828
}
2929
}
30+
item.insert_text(escaped_name);
3031

3132
Some(item.build())
3233
}

0 commit comments

Comments
 (0)