From f536766efb288669dede4281b55e4416fd27d54a Mon Sep 17 00:00:00 2001 From: Hongxu Xu Date: Sun, 26 Jun 2022 14:45:30 +0800 Subject: [PATCH] complete raw identifier with "r#" prefix --- crates/hir-expand/src/mod_path.rs | 29 +++- crates/hir-expand/src/name.rs | 55 +++++- .../src/completions/item_list/trait_impl.rs | 5 +- crates/ide-completion/src/render.rs | 164 +++++++++++++++++- crates/ide-completion/src/render/const_.rs | 5 +- crates/ide-completion/src/render/function.rs | 28 ++- crates/ide-completion/src/render/literal.rs | 12 +- crates/ide-completion/src/render/macro_.rs | 12 +- crates/ide-completion/src/render/pattern.rs | 16 +- .../ide-completion/src/render/type_alias.rs | 12 +- .../src/render/union_literal.rs | 22 +-- crates/ide-completion/src/render/variant.rs | 4 +- 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, } +#[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 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, 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 { 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 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, params: Vec, ) -> &'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 { 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, local_name: Option, ) -> Option { - 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())) } });