Unnamed repository; edit this file 'description' to name the repository.
Auto merge of #12636 - xuhongxu96:fix-12148, r=Veykril
complete raw identifier with "r#" prefix Fix #12148 Escape Names and Paths used in `insert_text`/`insert_snippet` while rendering the completion items.
bors 2022-07-01
parent ed44fe5 · parent f536766 · commit 994f3cf
-rw-r--r--crates/hir-expand/src/mod_path.rs29
-rw-r--r--crates/hir-expand/src/name.rs55
-rw-r--r--crates/ide-completion/src/completions/item_list/trait_impl.rs5
-rw-r--r--crates/ide-completion/src/render.rs164
-rw-r--r--crates/ide-completion/src/render/const_.rs5
-rw-r--r--crates/ide-completion/src/render/function.rs28
-rw-r--r--crates/ide-completion/src/render/literal.rs12
-rw-r--r--crates/ide-completion/src/render/macro_.rs12
-rw-r--r--crates/ide-completion/src/render/pattern.rs16
-rw-r--r--crates/ide-completion/src/render/type_alias.rs12
-rw-r--r--crates/ide-completion/src/render/union_literal.rs22
-rw-r--r--crates/ide-completion/src/render/variant.rs4
12 files changed, 304 insertions, 60 deletions
diff --git a/crates/hir-expand/src/mod_path.rs b/crates/hir-expand/src/mod_path.rs
index d38e4a52a8..af59733b9f 100644
--- a/crates/hir-expand/src/mod_path.rs
+++ b/crates/hir-expand/src/mod_path.rs
@@ -20,6 +20,9 @@ pub struct ModPath {
segments: Vec<Name>,
}
+#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
+pub struct EscapedModPath<'a>(&'a ModPath);
+
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum PathKind {
Plain,
@@ -97,10 +100,12 @@ impl ModPath {
_ => None,
}
}
-}
-impl Display for ModPath {
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ pub fn escaped(&self) -> EscapedModPath {
+ EscapedModPath(self)
+ }
+
+ fn _fmt(&self, f: &mut fmt::Formatter<'_>, escaped: bool) -> fmt::Result {
let mut first_segment = true;
let mut add_segment = |s| -> fmt::Result {
if !first_segment {
@@ -127,12 +132,28 @@ impl Display for ModPath {
f.write_str("::")?;
}
first_segment = false;
- segment.fmt(f)?;
+ if escaped {
+ segment.escaped().fmt(f)?
+ } else {
+ segment.fmt(f)?
+ };
}
Ok(())
}
}
+impl Display for ModPath {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ self._fmt(f, false)
+ }
+}
+
+impl<'a> Display for EscapedModPath<'a> {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ self.0._fmt(f, true)
+ }
+}
+
impl From<Name> for ModPath {
fn from(name: Name) -> ModPath {
ModPath::from_segments(PathKind::Plain, iter::once(name))
diff --git a/crates/hir-expand/src/name.rs b/crates/hir-expand/src/name.rs
index f1bf665707..6b48258f37 100644
--- a/crates/hir-expand/src/name.rs
+++ b/crates/hir-expand/src/name.rs
@@ -2,7 +2,7 @@
use std::fmt;
-use syntax::{ast, SmolStr};
+use syntax::{ast, SmolStr, SyntaxKind};
/// `Name` is a wrapper around string, which is used in hir for both references
/// and declarations. In theory, names should also carry hygiene info, but we are
@@ -10,6 +10,10 @@ use syntax::{ast, SmolStr};
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct Name(Repr);
+/// `EscapedName` will add a prefix "r#" to the wrapped `Name` when it is a raw identifier
+#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
+pub struct EscapedName<'a>(&'a Name);
+
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
enum Repr {
Text(SmolStr),
@@ -25,6 +29,51 @@ impl fmt::Display for Name {
}
}
+fn is_raw_identifier(name: &str) -> bool {
+ let is_keyword = SyntaxKind::from_keyword(name).is_some();
+ is_keyword && !matches!(name, "self" | "crate" | "super" | "Self")
+}
+
+impl<'a> fmt::Display for EscapedName<'a> {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ match &self.0 .0 {
+ Repr::Text(text) => {
+ if is_raw_identifier(text) {
+ write!(f, "r#{}", &text)
+ } else {
+ fmt::Display::fmt(&text, f)
+ }
+ }
+ Repr::TupleField(idx) => fmt::Display::fmt(&idx, f),
+ }
+ }
+}
+
+impl<'a> EscapedName<'a> {
+ pub fn is_escaped(&self) -> bool {
+ match &self.0 .0 {
+ Repr::Text(it) => is_raw_identifier(&it),
+ Repr::TupleField(_) => false,
+ }
+ }
+
+ /// Returns the textual representation of this name as a [`SmolStr`].
+ /// Prefer using this over [`ToString::to_string`] if possible as this conversion is cheaper in
+ /// the general case.
+ pub fn to_smol_str(&self) -> SmolStr {
+ match &self.0 .0 {
+ Repr::Text(it) => {
+ if is_raw_identifier(&it) {
+ SmolStr::from_iter(["r#", &it])
+ } else {
+ it.clone()
+ }
+ }
+ Repr::TupleField(it) => SmolStr::new(&it.to_string()),
+ }
+ }
+}
+
impl Name {
/// Note: this is private to make creating name from random string hard.
/// Hopefully, this should allow us to integrate hygiene cleaner in the
@@ -92,6 +141,10 @@ impl Name {
Repr::TupleField(it) => SmolStr::new(&it.to_string()),
}
}
+
+ pub fn escaped(&self) -> EscapedName {
+ EscapedName(self)
+ }
}
pub trait AsName {
diff --git a/crates/ide-completion/src/completions/item_list/trait_impl.rs b/crates/ide-completion/src/completions/item_list/trait_impl.rs
index 972a7d2f21..3872053f2c 100644
--- a/crates/ide-completion/src/completions/item_list/trait_impl.rs
+++ b/crates/ide-completion/src/completions/item_list/trait_impl.rs
@@ -232,10 +232,11 @@ fn add_type_alias_impl(
replacement_range: TextRange,
type_alias: hir::TypeAlias,
) {
- let alias_name = type_alias.name(ctx.db).to_smol_str();
+ let alias_name = type_alias.name(ctx.db);
+ let (alias_name, escaped_name) = (alias_name.to_smol_str(), alias_name.escaped().to_smol_str());
let label = format!("type {} =", alias_name);
- let replacement = format!("type {} = ", alias_name);
+ let replacement = format!("type {} = ", escaped_name);
let mut item = CompletionItem::new(SymbolKind::TypeAlias, replacement_range, label);
item.lookup_by(format!("type {}", alias_name))
diff --git a/crates/ide-completion/src/render.rs b/crates/ide-completion/src/render.rs
index 6571e67352..005ab3a895 100644
--- a/crates/ide-completion/src/render.rs
+++ b/crates/ide-completion/src/render.rs
@@ -116,7 +116,8 @@ pub(crate) fn render_field(
ty: &hir::Type,
) -> CompletionItem {
let is_deprecated = ctx.is_deprecated(field);
- let name = field.name(ctx.db()).to_smol_str();
+ let name = field.name(ctx.db());
+ let (name, escaped_name) = (name.to_smol_str(), name.escaped().to_smol_str());
let mut item = CompletionItem::new(
SymbolKind::Field,
ctx.source_range(),
@@ -131,10 +132,7 @@ pub(crate) fn render_field(
.set_documentation(field.docs(ctx.db()))
.set_deprecated(is_deprecated)
.lookup_by(name.clone());
- let is_keyword = SyntaxKind::from_keyword(name.as_str()).is_some();
- if is_keyword && !matches!(name.as_str(), "self" | "crate" | "super" | "Self") {
- item.insert_text(format!("r#{}", name));
- }
+ item.insert_text(escaped_name);
if let Some(receiver) = &dot_access.receiver {
if let Some(ref_match) = compute_ref_match(ctx.completion, ty) {
item.ref_match(ref_match, receiver.syntax().text_range().start());
@@ -235,7 +233,7 @@ fn render_resolution_pat(
_ => (),
}
- render_resolution_simple_(ctx, local_name, import_to_add, resolution)
+ render_resolution_simple_(ctx, &local_name, import_to_add, resolution)
}
fn render_resolution_path(
@@ -274,7 +272,10 @@ fn render_resolution_path(
let config = completion.config;
let name = local_name.to_smol_str();
- let mut item = render_resolution_simple_(ctx, local_name, import_to_add, resolution);
+ let mut item = render_resolution_simple_(ctx, &local_name, import_to_add, resolution);
+ if local_name.escaped().is_escaped() {
+ item.insert_text(local_name.escaped().to_smol_str());
+ }
// Add `<>` for generic types
let type_path_no_ty_args = matches!(
path_ctx,
@@ -295,7 +296,7 @@ fn render_resolution_path(
item.lookup_by(name.clone())
.label(SmolStr::from_iter([&name, "<…>"]))
.trigger_call_info()
- .insert_snippet(cap, format!("{}<$0>", name));
+ .insert_snippet(cap, format!("{}<$0>", local_name.escaped()));
}
}
}
@@ -321,7 +322,7 @@ fn render_resolution_path(
fn render_resolution_simple_(
ctx: RenderContext<'_>,
- local_name: hir::Name,
+ local_name: &hir::Name,
import_to_add: Option<LocatedImport>,
resolution: ScopeDef,
) -> Builder {
@@ -1725,4 +1726,149 @@ fn f() {
"#]],
);
}
+
+ #[test]
+ fn completes_struct_with_raw_identifier() {
+ check_edit(
+ "type",
+ r#"
+mod m { pub struct r#type {} }
+fn main() {
+ let r#type = m::t$0;
+}
+"#,
+ r#"
+mod m { pub struct r#type {} }
+fn main() {
+ let r#type = m::r#type;
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn completes_fn_with_raw_identifier() {
+ check_edit(
+ "type",
+ r#"
+mod m { pub fn r#type {} }
+fn main() {
+ m::t$0
+}
+"#,
+ r#"
+mod m { pub fn r#type {} }
+fn main() {
+ m::r#type()$0
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn completes_macro_with_raw_identifier() {
+ check_edit(
+ "let!",
+ r#"
+macro_rules! r#let { () => {} }
+fn main() {
+ $0
+}
+"#,
+ r#"
+macro_rules! r#let { () => {} }
+fn main() {
+ r#let!($0)
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn completes_variant_with_raw_identifier() {
+ check_edit(
+ "type",
+ r#"
+enum A { r#type }
+fn main() {
+ let a = A::t$0
+}
+"#,
+ r#"
+enum A { r#type }
+fn main() {
+ let a = A::r#type$0
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn completes_field_with_raw_identifier() {
+ check_edit(
+ "fn",
+ r#"
+mod r#type {
+ pub struct r#struct {
+ pub r#fn: u32
+ }
+}
+
+fn main() {
+ let a = r#type::r#struct {};
+ a.$0
+}
+"#,
+ r#"
+mod r#type {
+ pub struct r#struct {
+ pub r#fn: u32
+ }
+}
+
+fn main() {
+ let a = r#type::r#struct {};
+ a.r#fn
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn completes_const_with_raw_identifier() {
+ check_edit(
+ "type",
+ r#"
+struct r#struct {}
+impl r#struct { pub const r#type: u8 = 1; }
+fn main() {
+ r#struct::t$0
+}
+"#,
+ r#"
+struct r#struct {}
+impl r#struct { pub const r#type: u8 = 1; }
+fn main() {
+ r#struct::r#type
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn completes_type_alias_with_raw_identifier() {
+ check_edit(
+ "type type",
+ r#"
+struct r#struct {}
+trait r#trait { type r#type; }
+impl r#trait for r#struct { type t$0 }
+"#,
+ r#"
+struct r#struct {}
+trait r#trait { type r#type; }
+impl r#trait for r#struct { type r#type = $0; }
+"#,
+ )
+ }
}
diff --git a/crates/ide-completion/src/render/const_.rs b/crates/ide-completion/src/render/const_.rs
index 89e6c82dde..a810eef18d 100644
--- a/crates/ide-completion/src/render/const_.rs
+++ b/crates/ide-completion/src/render/const_.rs
@@ -12,7 +12,8 @@ pub(crate) fn render_const(ctx: RenderContext<'_>, const_: hir::Const) -> Option
fn render(ctx: RenderContext<'_>, const_: hir::Const) -> Option<CompletionItem> {
let db = ctx.db();
- let name = const_.name(db)?.to_smol_str();
+ let name = const_.name(db)?;
+ let (name, escaped_name) = (name.to_smol_str(), name.escaped().to_smol_str());
let detail = const_.display(db).to_string();
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>
if let Some(actm) = const_.as_assoc_item(db) {
if let Some(trt) = actm.containing_trait_or_trait_impl(db) {
item.trait_name(trt.name(db).to_smol_str());
- item.insert_text(name);
}
}
+ item.insert_text(escaped_name);
Some(item.build())
}
diff --git a/crates/ide-completion/src/render/function.rs b/crates/ide-completion/src/render/function.rs
index 37486e4d93..4a8a5d5c77 100644
--- a/crates/ide-completion/src/render/function.rs
+++ b/crates/ide-completion/src/render/function.rs
@@ -50,9 +50,12 @@ fn render(
let name = local_name.unwrap_or_else(|| func.name(db));
- let call = match &func_kind {
- FuncKind::Method(_, Some(receiver)) => format!("{}.{}", receiver, &name).into(),
- _ => name.to_smol_str(),
+ let (call, escaped_call) = match &func_kind {
+ FuncKind::Method(_, Some(receiver)) => (
+ format!("{}.{}", receiver, &name).into(),
+ format!("{}.{}", receiver.escaped(), name.escaped()).into(),
+ ),
+ _ => (name.to_smol_str(), name.escaped().to_smol_str()),
};
let mut item = CompletionItem::new(
if func.self_param(db).is_some() {
@@ -115,7 +118,15 @@ fn render(
if let Some((self_param, params)) =
params(ctx.completion, func, &func_kind, has_dot_receiver)
{
- add_call_parens(&mut item, completion, cap, call, self_param, params);
+ add_call_parens(
+ &mut item,
+ completion,
+ cap,
+ call,
+ escaped_call,
+ self_param,
+ params,
+ );
}
}
}
@@ -142,13 +153,14 @@ pub(super) fn add_call_parens<'b>(
ctx: &CompletionContext,
cap: SnippetCap,
name: SmolStr,
+ escaped_name: SmolStr,
self_param: Option<hir::SelfParam>,
params: Vec<hir::Param>,
) -> &'b mut Builder {
cov_mark::hit!(inserts_parens_for_function_calls);
let (snippet, label_suffix) = if self_param.is_none() && params.is_empty() {
- (format!("{}()$0", name), "()")
+ (format!("{}()$0", escaped_name), "()")
} else {
builder.trigger_call_info();
let snippet = if let Some(CallableSnippets::FillArguments) = ctx.config.callable {
@@ -179,19 +191,19 @@ pub(super) fn add_call_parens<'b>(
Some(self_param) => {
format!(
"{}(${{1:{}}}{}{})$0",
- name,
+ escaped_name,
self_param.display(ctx.db),
if params.is_empty() { "" } else { ", " },
function_params_snippet
)
}
None => {
- format!("{}({})$0", name, function_params_snippet)
+ format!("{}({})$0", escaped_name, function_params_snippet)
}
}
} else {
cov_mark::hit!(suppress_arg_snippets);
- format!("{}($0)", name)
+ format!("{}($0)", escaped_name)
};
(snippet, "(…)")
diff --git a/crates/ide-completion/src/render/literal.rs b/crates/ide-completion/src/render/literal.rs
index 7b0555d5a4..df80fb2fa6 100644
--- a/crates/ide-completion/src/render/literal.rs
+++ b/crates/ide-completion/src/render/literal.rs
@@ -72,17 +72,21 @@ fn render(
}
None => (name.clone().into(), name.into(), false),
};
- let qualified_name = qualified_name.to_string();
+ let (qualified_name, escaped_qualified_name) =
+ (qualified_name.to_string(), qualified_name.escaped().to_string());
let snippet_cap = ctx.snippet_cap();
let mut rendered = match kind {
StructKind::Tuple if should_add_parens => {
- render_tuple_lit(db, snippet_cap, &fields, &qualified_name)
+ render_tuple_lit(db, snippet_cap, &fields, &escaped_qualified_name)
}
StructKind::Record if should_add_parens => {
- render_record_lit(db, snippet_cap, &fields, &qualified_name)
+ render_record_lit(db, snippet_cap, &fields, &escaped_qualified_name)
}
- _ => RenderedLiteral { literal: qualified_name.clone(), detail: qualified_name.clone() },
+ _ => RenderedLiteral {
+ literal: escaped_qualified_name.clone(),
+ detail: escaped_qualified_name.clone(),
+ },
};
if snippet_cap.is_some() {
diff --git a/crates/ide-completion/src/render/macro_.rs b/crates/ide-completion/src/render/macro_.rs
index ac2091eca9..ebf8a98eb0 100644
--- a/crates/ide-completion/src/render/macro_.rs
+++ b/crates/ide-completion/src/render/macro_.rs
@@ -46,7 +46,7 @@ fn render(
ctx.source_range()
};
- let name = name.to_smol_str();
+ let (name, escaped_name) = (name.to_smol_str(), name.escaped().to_smol_str());
let docs = ctx.docs(macro_);
let docs_str = docs.as_ref().map(Documentation::as_str).unwrap_or_default();
let is_fn_like = macro_.is_fn_like(completion.db);
@@ -64,20 +64,18 @@ fn render(
.set_documentation(docs)
.set_relevance(ctx.completion_relevance());
- let name = &*name;
match ctx.snippet_cap() {
Some(cap) if needs_bang && !has_call_parens => {
- let snippet = format!("{}!{}$0{}", name, bra, ket);
- let lookup = banged_name(name);
+ let snippet = format!("{}!{}$0{}", escaped_name, bra, ket);
+ let lookup = banged_name(&name);
item.insert_snippet(cap, snippet).lookup_by(lookup);
}
_ if needs_bang => {
- let banged_name = banged_name(name);
- item.insert_text(banged_name.clone()).lookup_by(banged_name);
+ item.insert_text(banged_name(&escaped_name)).lookup_by(banged_name(&name));
}
_ => {
cov_mark::hit!(dont_insert_macro_call_parens_unncessary);
- item.insert_text(name);
+ item.insert_text(escaped_name);
}
};
if let Some(import_to_add) = ctx.import_to_add {
diff --git a/crates/ide-completion/src/render/pattern.rs b/crates/ide-completion/src/render/pattern.rs
index 463d292955..f9c4037dee 100644
--- a/crates/ide-completion/src/render/pattern.rs
+++ b/crates/ide-completion/src/render/pattern.rs
@@ -27,11 +27,12 @@ pub(crate) fn render_struct_pat(
return None;
}
- let name = local_name.unwrap_or_else(|| strukt.name(ctx.db())).to_smol_str();
+ let name = local_name.unwrap_or_else(|| strukt.name(ctx.db()));
+ let (name, escaped_name) = (name.to_smol_str(), name.escaped().to_smol_str());
let pat = render_pat(
&ctx,
pattern_ctx,
- &name,
+ &escaped_name,
strukt.kind(ctx.db()),
&visible_fields,
fields_omitted,
@@ -52,14 +53,17 @@ pub(crate) fn render_variant_pat(
let fields = variant.fields(ctx.db());
let (visible_fields, fields_omitted) = visible_fields(ctx.completion, &fields, variant)?;
- let name = match path {
- Some(path) => path.to_string().into(),
- None => local_name.unwrap_or_else(|| variant.name(ctx.db())).to_smol_str(),
+ let (name, escaped_name) = match path {
+ Some(path) => (path.to_string().into(), path.escaped().to_string().into()),
+ None => {
+ let name = local_name.unwrap_or_else(|| variant.name(ctx.db()));
+ (name.to_smol_str(), name.escaped().to_smol_str())
+ }
};
let pat = render_pat(
&ctx,
pattern_ctx,
- &name,
+ &escaped_name,
variant.kind(ctx.db()),
&visible_fields,
fields_omitted,
diff --git a/crates/ide-completion/src/render/type_alias.rs b/crates/ide-completion/src/render/type_alias.rs
index a518be87bf..f1b23c76e7 100644
--- a/crates/ide-completion/src/render/type_alias.rs
+++ b/crates/ide-completion/src/render/type_alias.rs
@@ -29,10 +29,14 @@ fn render(
) -> Option<CompletionItem> {
let db = ctx.db();
- let name = if with_eq {
- SmolStr::from_iter([&*type_alias.name(db).to_smol_str(), " = "])
+ let name = type_alias.name(db);
+ let (name, escaped_name) = if with_eq {
+ (
+ SmolStr::from_iter([&name.to_smol_str(), " = "]),
+ SmolStr::from_iter([&name.escaped().to_smol_str(), " = "]),
+ )
} else {
- type_alias.name(db).to_smol_str()
+ (name.to_smol_str(), name.escaped().to_smol_str())
};
let detail = type_alias.display(db).to_string();
@@ -45,9 +49,9 @@ fn render(
if let Some(actm) = type_alias.as_assoc_item(db) {
if let Some(trt) = actm.containing_trait_or_trait_impl(db) {
item.trait_name(trt.name(db).to_smol_str());
- item.insert_text(name);
}
}
+ item.insert_text(escaped_name);
Some(item.build())
}
diff --git a/crates/ide-completion/src/render/union_literal.rs b/crates/ide-completion/src/render/union_literal.rs
index aafedaf5aa..fc35381256 100644
--- a/crates/ide-completion/src/render/union_literal.rs
+++ b/crates/ide-completion/src/render/union_literal.rs
@@ -18,17 +18,17 @@ pub(crate) fn render_union_literal(
path: Option<hir::ModPath>,
local_name: Option<Name>,
) -> Option<CompletionItem> {
- let name = local_name.unwrap_or_else(|| un.name(ctx.db())).to_smol_str();
+ let name = local_name.unwrap_or_else(|| un.name(ctx.db()));
- let qualified_name = match path {
- Some(p) => p.to_string(),
- None => name.to_string(),
+ let (qualified_name, escaped_qualified_name) = match path {
+ Some(p) => (p.to_string(), p.escaped().to_string()),
+ None => (name.to_string(), name.escaped().to_string()),
};
let mut item = CompletionItem::new(
CompletionItemKind::SymbolKind(SymbolKind::Union),
ctx.source_range(),
- format_literal_label(&name, StructKind::Record),
+ format_literal_label(&name.to_smol_str(), StructKind::Record),
);
let fields = un.fields(ctx.db());
@@ -41,16 +41,16 @@ pub(crate) fn render_union_literal(
let literal = if ctx.snippet_cap().is_some() {
format!(
"{} {{ ${{1|{}|}}: ${{2:()}} }}$0",
- qualified_name,
- fields.iter().map(|field| field.name(ctx.db())).format(",")
+ escaped_qualified_name,
+ fields.iter().map(|field| field.name(ctx.db()).escaped().to_smol_str()).format(",")
)
} else {
format!(
"{} {{ {} }}",
- qualified_name,
- fields
- .iter()
- .format_with(", ", |field, f| { f(&format_args!("{}: ()", field.name(ctx.db()))) })
+ escaped_qualified_name,
+ fields.iter().format_with(", ", |field, f| {
+ f(&format_args!("{}: ()", field.name(ctx.db()).escaped()))
+ })
)
};
diff --git a/crates/ide-completion/src/render/variant.rs b/crates/ide-completion/src/render/variant.rs
index 2c9fb9b35a..440b9d14ca 100644
--- a/crates/ide-completion/src/render/variant.rs
+++ b/crates/ide-completion/src/render/variant.rs
@@ -24,9 +24,9 @@ pub(crate) fn render_record_lit(
) -> RenderedLiteral {
let completions = fields.iter().enumerate().format_with(", ", |(idx, field), f| {
if snippet_cap.is_some() {
- f(&format_args!("{}: ${{{}:()}}", field.name(db), idx + 1))
+ f(&format_args!("{}: ${{{}:()}}", field.name(db).escaped(), idx + 1))
} else {
- f(&format_args!("{}: ()", field.name(db)))
+ f(&format_args!("{}: ()", field.name(db).escaped()))
}
});