Fix not applicable c-str and byte-str for raw_string

Example
---

Assist: `make_raw_string`

```rust
fn f() {
    let s = $0b"random\nstring";
}
```
->
```rust
fn f() {
    let s = br#"random
string"#;
}
```

---

Assist: `make_raw_string`

```rust
fn f() {
    let s = $0c"random\nstring";
}
```
->
```rust
fn f() {
    let s = cr#"random
string"#;
}
```

---

Assist: `add_hash`

```rust
fn f() {
    let s = $0cr"random string";
}
```
->
```rust
fn f() {
    let s = cr#"random string"#;
}
```

---

Assist: `remove_hash`

```rust
fn f() {
    let s = $0cr#"random string"#;
}
```
->
```rust
fn f() {
    let s = cr"random string";
}
```

---

Assist: `make_usual_string`

```rust
fn f() {
    let s = $0cr#"random string"#;
}
```
->
```rust
fn f() {
    let s = c"random string";
}
```
This commit is contained in:
A4-Tacks 2025-10-03 17:03:00 +08:00
parent 90d2e1ce4d
commit e4cceca607
No known key found for this signature in database
GPG key ID: DBD861323040663B
4 changed files with 202 additions and 27 deletions

View file

@ -29,7 +29,9 @@ pub use self::{
SlicePatComponents, StructKind, TypeBoundKind, TypeOrConstParam, VisibilityKind,
},
operators::{ArithOp, BinaryOp, CmpOp, LogicOp, Ordering, RangeOp, UnaryOp},
token_ext::{CommentKind, CommentPlacement, CommentShape, IsString, QuoteOffsets, Radix},
token_ext::{
AnyString, CommentKind, CommentPlacement, CommentShape, IsString, QuoteOffsets, Radix,
},
traits::{
AttrDocCommentIter, DocCommentIter, HasArgList, HasAttrs, HasDocComments, HasGenericArgs,
HasGenericParams, HasLoopBody, HasModuleItem, HasName, HasTypeBounds, HasVisibility,

View file

@ -151,10 +151,10 @@ impl QuoteOffsets {
}
pub trait IsString: AstToken {
const RAW_PREFIX: &'static str;
fn unescape(s: &str, callback: impl FnMut(Range<usize>, Result<char, EscapeError>));
fn raw_prefix(&self) -> &'static str;
fn unescape(&self, s: &str, callback: impl FnMut(Range<usize>, Result<char, EscapeError>));
fn is_raw(&self) -> bool {
self.text().starts_with(Self::RAW_PREFIX)
self.text().starts_with(self.raw_prefix())
}
fn quote_offsets(&self) -> Option<QuoteOffsets> {
let text = self.text();
@ -187,7 +187,7 @@ pub trait IsString: AstToken {
let text = &self.text()[text_range_no_quotes - start];
let offset = text_range_no_quotes.start() - start;
Self::unescape(text, &mut |range: Range<usize>, unescaped_char| {
self.unescape(text, &mut |range: Range<usize>, unescaped_char| {
if let Some((s, e)) = range.start.try_into().ok().zip(range.end.try_into().ok()) {
cb(TextRange::new(s, e) + offset, unescaped_char);
}
@ -204,8 +204,10 @@ pub trait IsString: AstToken {
}
impl IsString for ast::String {
const RAW_PREFIX: &'static str = "r";
fn unescape(s: &str, cb: impl FnMut(Range<usize>, Result<char, EscapeError>)) {
fn raw_prefix(&self) -> &'static str {
"r"
}
fn unescape(&self, s: &str, cb: impl FnMut(Range<usize>, Result<char, EscapeError>)) {
unescape_str(s, cb)
}
}
@ -246,8 +248,10 @@ impl ast::String {
}
impl IsString for ast::ByteString {
const RAW_PREFIX: &'static str = "br";
fn unescape(s: &str, mut callback: impl FnMut(Range<usize>, Result<char, EscapeError>)) {
fn raw_prefix(&self) -> &'static str {
"br"
}
fn unescape(&self, s: &str, mut callback: impl FnMut(Range<usize>, Result<char, EscapeError>)) {
unescape_byte_str(s, |range, res| callback(range, res.map(char::from)))
}
}
@ -288,10 +292,12 @@ impl ast::ByteString {
}
impl IsString for ast::CString {
const RAW_PREFIX: &'static str = "cr";
fn raw_prefix(&self) -> &'static str {
"cr"
}
// NOTE: This method should only be used for highlighting ranges. The unescaped
// char/byte is not used. For simplicity, we return an arbitrary placeholder char.
fn unescape(s: &str, mut callback: impl FnMut(Range<usize>, Result<char, EscapeError>)) {
fn unescape(&self, s: &str, mut callback: impl FnMut(Range<usize>, Result<char, EscapeError>)) {
unescape_c_str(s, |range, _res| callback(range, Ok('_')))
}
}
@ -465,6 +471,74 @@ impl ast::Byte {
}
}
pub enum AnyString {
ByteString(ast::ByteString),
CString(ast::CString),
String(ast::String),
}
impl AnyString {
pub fn value(&self) -> Result<Cow<'_, str>, EscapeError> {
fn from_utf8(s: Cow<'_, [u8]>) -> Result<Cow<'_, str>, EscapeError> {
match s {
Cow::Borrowed(s) => str::from_utf8(s)
.map_err(|_| EscapeError::NonAsciiCharInByte)
.map(Cow::Borrowed),
Cow::Owned(s) => String::from_utf8(s)
.map_err(|_| EscapeError::NonAsciiCharInByte)
.map(Cow::Owned),
}
}
match self {
AnyString::String(s) => s.value(),
AnyString::ByteString(s) => s.value().and_then(from_utf8),
AnyString::CString(s) => s.value().and_then(from_utf8),
}
}
}
impl ast::AstToken for AnyString {
fn can_cast(kind: crate::SyntaxKind) -> bool {
ast::String::can_cast(kind)
|| ast::ByteString::can_cast(kind)
|| ast::CString::can_cast(kind)
}
fn cast(syntax: crate::SyntaxToken) -> Option<Self> {
ast::String::cast(syntax.clone())
.map(Self::String)
.or_else(|| ast::ByteString::cast(syntax.clone()).map(Self::ByteString))
.or_else(|| ast::CString::cast(syntax).map(Self::CString))
}
fn syntax(&self) -> &crate::SyntaxToken {
match self {
Self::ByteString(it) => it.syntax(),
Self::CString(it) => it.syntax(),
Self::String(it) => it.syntax(),
}
}
}
impl IsString for AnyString {
fn raw_prefix(&self) -> &'static str {
match self {
AnyString::ByteString(s) => s.raw_prefix(),
AnyString::CString(s) => s.raw_prefix(),
AnyString::String(s) => s.raw_prefix(),
}
}
fn unescape(&self, s: &str, callback: impl FnMut(Range<usize>, Result<char, EscapeError>)) {
match self {
AnyString::ByteString(it) => it.unescape(s, callback),
AnyString::CString(it) => it.unescape(s, callback),
AnyString::String(it) => it.unescape(s, callback),
}
}
}
#[cfg(test)]
mod tests {
use rustc_apfloat::ieee::Quad as f128;