From 53ec791dc6b22e581f9e5aabeda5edfc806564cb Mon Sep 17 00:00:00 2001 From: Ryo Yoshida Date: Mon, 1 Aug 2022 14:20:05 +0900 Subject: [PATCH] Add `UnescapedName` and make `Name` hold escaped name --- crates/hir-expand/src/name.rs | 54 +++++++++++++++++++++++++++++++++-- 1 file changed, 52 insertions(+), 2 deletions(-) diff --git a/crates/hir-expand/src/name.rs b/crates/hir-expand/src/name.rs index 47d191822d..18b0793f10 100644 --- a/crates/hir-expand/src/name.rs +++ b/crates/hir-expand/src/name.rs @@ -7,6 +7,10 @@ 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 /// not there yet! +/// +/// Note that `Name` holds and prints escaped name i.e. prefixed with "r#" when it +/// is a raw identifier. Use [`unescaped()`][Name::unescaped] when you need the +/// name without "r#". #[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] pub struct Name(Repr); @@ -14,6 +18,10 @@ pub struct Name(Repr); #[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] pub struct EscapedName<'a>(&'a Name); +/// Wrapper of `Name` to print the name without "r#" even when it is a raw identifier. +#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub struct UnescapedName<'a>(&'a Name); + #[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] enum Repr { Text(SmolStr), @@ -49,6 +57,35 @@ impl<'a> fmt::Display for EscapedName<'a> { } } +impl<'a> fmt::Display for UnescapedName<'a> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match &self.0 .0 { + Repr::Text(text) => { + let text = text.strip_prefix("r#").unwrap_or(text); + fmt::Display::fmt(&text, f) + } + Repr::TupleField(idx) => fmt::Display::fmt(&idx, f), + } + } +} + +impl<'a> UnescapedName<'a> { + /// 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 let Some(stripped) = it.strip_prefix("r#") { + SmolStr::new(stripped) + } else { + it.clone() + } + } + Repr::TupleField(it) => SmolStr::new(&it.to_string()), + } + } +} + impl<'a> EscapedName<'a> { pub fn is_escaped(&self) -> bool { match &self.0 .0 { @@ -97,9 +134,11 @@ impl Name { /// Resolve a name from the text of token. fn resolve(raw_text: &str) -> Name { + // When `raw_text` starts with "r#" but the name does not coincide with any + // keyword, we never need the prefix so we strip it. match raw_text.strip_prefix("r#") { - Some(text) => Name::new_text(SmolStr::new(text)), - None => Name::new_text(raw_text.into()), + Some(text) if !is_raw_identifier(text) => Name::new_text(SmolStr::new(text)), + _ => Name::new_text(raw_text.into()), } } @@ -145,6 +184,17 @@ impl Name { pub fn escaped(&self) -> EscapedName<'_> { EscapedName(self) } + + pub fn unescaped(&self) -> UnescapedName<'_> { + UnescapedName(self) + } + + pub fn is_escaped(&self) -> bool { + match &self.0 { + Repr::Text(it) => it.starts_with("r#"), + Repr::TupleField(_) => false, + } + } } pub trait AsName {