Skip to content

Commit 60ed071

Browse files
committed
Render literal escaping errors in hovers
1 parent a739739 commit 60ed071

File tree

10 files changed

+68
-79
lines changed

10 files changed

+68
-79
lines changed

src/tools/rust-analyzer/crates/hir-def/src/hir.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -136,15 +136,15 @@ impl From<ast::LiteralKind> for Literal {
136136
Literal::Float(FloatTypeWrapper::new(lit.value().unwrap_or(Default::default())), ty)
137137
}
138138
LiteralKind::ByteString(bs) => {
139-
let text = bs.value().map(Box::from).unwrap_or_else(Default::default);
139+
let text = bs.value().map_or_else(|_| Default::default(), Box::from);
140140
Literal::ByteString(text)
141141
}
142142
LiteralKind::String(s) => {
143-
let text = s.value().map(Box::from).unwrap_or_else(Default::default);
143+
let text = s.value().map_or_else(|_| Default::default(), Box::from);
144144
Literal::String(text)
145145
}
146146
LiteralKind::CString(s) => {
147-
let text = s.value().map(Box::from).unwrap_or_else(Default::default);
147+
let text = s.value().map_or_else(|_| Default::default(), Box::from);
148148
Literal::CString(text)
149149
}
150150
LiteralKind::Byte(b) => {

src/tools/rust-analyzer/crates/hir-expand/src/builtin_fn_macro.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -441,21 +441,21 @@ fn unquote_str(lit: &tt::Literal) -> Option<(String, Span)> {
441441
let span = lit.span;
442442
let lit = ast::make::tokens::literal(&lit.to_string());
443443
let token = ast::String::cast(lit)?;
444-
token.value().map(|it| (it.into_owned(), span))
444+
token.value().ok().map(|it| (it.into_owned(), span))
445445
}
446446

447447
fn unquote_char(lit: &tt::Literal) -> Option<(char, Span)> {
448448
let span = lit.span;
449449
let lit = ast::make::tokens::literal(&lit.to_string());
450450
let token = ast::Char::cast(lit)?;
451-
token.value().zip(Some(span))
451+
token.value().ok().zip(Some(span))
452452
}
453453

454454
fn unquote_byte_string(lit: &tt::Literal) -> Option<(Vec<u8>, Span)> {
455455
let span = lit.span;
456456
let lit = ast::make::tokens::literal(&lit.to_string());
457457
let token = ast::ByteString::cast(lit)?;
458-
token.value().map(|it| (it.into_owned(), span))
458+
token.value().ok().map(|it| (it.into_owned(), span))
459459
}
460460

461461
fn compile_error_expand(

src/tools/rust-analyzer/crates/ide-assists/src/handlers/raw_string.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ pub(crate) fn make_raw_string(acc: &mut Assists, ctx: &AssistContext<'_>) -> Opt
2525
if token.is_raw() {
2626
return None;
2727
}
28-
let value = token.value()?;
28+
let value = token.value().ok()?;
2929
let target = token.syntax().text_range();
3030
acc.add(
3131
AssistId("make_raw_string", AssistKind::RefactorRewrite),
@@ -64,7 +64,7 @@ pub(crate) fn make_usual_string(acc: &mut Assists, ctx: &AssistContext<'_>) -> O
6464
if !token.is_raw() {
6565
return None;
6666
}
67-
let value = token.value()?;
67+
let value = token.value().ok()?;
6868
let target = token.syntax().text_range();
6969
acc.add(
7070
AssistId("make_usual_string", AssistKind::RefactorRewrite),
@@ -398,12 +398,12 @@ string"###;
398398
}
399399

400400
#[test]
401-
fn remove_hash_doesnt_work() {
401+
fn remove_hash_does_not_work() {
402402
check_assist_not_applicable(remove_hash, r#"fn f() { let s = $0"random string"; }"#);
403403
}
404404

405405
#[test]
406-
fn remove_hash_no_hash_doesnt_work() {
406+
fn remove_hash_no_hash_does_not_work() {
407407
check_assist_not_applicable(remove_hash, r#"fn f() { let s = $0r"random string"; }"#);
408408
}
409409

src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_string_with_char.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ use crate::{AssistContext, AssistId, AssistKind, Assists};
2525
// ```
2626
pub(crate) fn replace_string_with_char(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
2727
let token = ctx.find_token_syntax_at_offset(STRING).and_then(ast::String::cast)?;
28-
let value = token.value()?;
28+
let value = token.value().ok()?;
2929
let target = token.syntax().text_range();
3030

3131
if value.chars().take(2).count() != 1 {

src/tools/rust-analyzer/crates/ide/src/goto_definition.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ fn try_lookup_include_path(
123123
{
124124
return None;
125125
}
126-
let path = token.value()?;
126+
let path = token.value().ok()?;
127127

128128
let file_id = sema.db.resolve_path(AnchoredPath { anchor: file_id, path: &path })?;
129129
let size = sema.db.file_text(file_id).len().try_into().ok()?;
@@ -179,11 +179,11 @@ fn try_filter_trait_item_definition(
179179
AssocItem::Const(..) | AssocItem::TypeAlias(..) => {
180180
let trait_ = assoc.implemented_trait(db)?;
181181
let name = def.name(db)?;
182-
let discri_value = discriminant(&assoc);
182+
let discriminant_value = discriminant(&assoc);
183183
trait_
184184
.items(db)
185185
.iter()
186-
.filter(|itm| discriminant(*itm) == discri_value)
186+
.filter(|itm| discriminant(*itm) == discriminant_value)
187187
.find_map(|itm| (itm.name(db)? == name).then(|| itm.try_to_nav(db)).flatten())
188188
.map(|it| it.collect())
189189
}

src/tools/rust-analyzer/crates/ide/src/hover.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ use ide_db::{
1515
FxIndexSet, RootDatabase,
1616
};
1717
use itertools::{multizip, Itertools};
18-
use syntax::{ast, match_ast, AstNode, AstToken, SyntaxKind::*, SyntaxNode, T};
18+
use syntax::{ast, AstNode, SyntaxKind::*, SyntaxNode, T};
1919

2020
use crate::{
2121
doc_links::token_as_doc_comment,

src/tools/rust-analyzer/crates/ide/src/hover/render.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -533,11 +533,11 @@ pub(super) fn literal(sema: &Semantics<'_, RootDatabase>, token: SyntaxToken) ->
533533

534534
let value = match_ast! {
535535
match token {
536-
ast::String(string) => Ok(string.value()?.to_string()),
537-
ast::ByteString(string) => Ok(format_args!("{:?}", string.value()?).to_string()),
538-
ast::CString(string) => Ok(std::str::from_utf8(string.value()?.as_ref()).ok()?.to_owned()),
539-
ast::Char(char) => Ok(char.value()?.to_string()),
540-
ast::Byte(byte) => Ok(format!("0x{:X}", byte.value()?)),
536+
ast::String(string) => string.value().as_ref().map_err(|e| format!("{e:?}")).map(ToString::to_string),
537+
ast::ByteString(string) => string.value().as_ref().map_err(|e| format!("{e:?}")).map(|it| format!("{it:?}")),
538+
ast::CString(string) => string.value().as_ref().map_err(|e| format!("{e:?}")).map(|it| std::str::from_utf8(it).map_or_else(|e| format!("{e:?}"), ToOwned::to_owned)),
539+
ast::Char(char) => char .value().as_ref().map_err(|e| format!("{e:?}")).map(ToString::to_string),
540+
ast::Byte(byte) => byte .value().as_ref().map_err(|e| format!("{e:?}")).map(|it| format!("0x{it:X}")),
541541
ast::FloatNumber(num) => {
542542
let (text, _) = num.split_into_parts();
543543
let text = text.replace('_', "");

src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/escape.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ pub(super) fn highlight_escape_string<T: IsString>(
2525
}
2626

2727
pub(super) fn highlight_escape_char(stack: &mut Highlights, char: &Char, start: TextSize) {
28-
if char.value().is_none() {
28+
if char.value().is_err() {
2929
// We do not emit invalid escapes highlighting here. The lexer would likely be in a bad
3030
// state and this token contains junks, since `'` is not a reliable delimiter (consider
3131
// lifetimes). Nonetheless, parser errors should already be emitted.
@@ -48,7 +48,7 @@ pub(super) fn highlight_escape_char(stack: &mut Highlights, char: &Char, start:
4848
}
4949

5050
pub(super) fn highlight_escape_byte(stack: &mut Highlights, byte: &Byte, start: TextSize) {
51-
if byte.value().is_none() {
51+
if byte.value().is_err() {
5252
// See `highlight_escape_char` for why no error highlighting here.
5353
return;
5454
}

src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/inject.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ pub(super) fn ra_fixture(
3030
if !active_parameter.ident().map_or(false, |name| name.text().starts_with("ra_fixture")) {
3131
return None;
3232
}
33-
let value = literal.value()?;
33+
let value = literal.value().ok()?;
3434

3535
if let Some(range) = literal.open_quote_text_range() {
3636
hl.add(HlRange { range, highlight: HlTag::StringLiteral.into(), binding_hash: None })

src/tools/rust-analyzer/crates/syntax/src/ast/token_ext.rs

Lines changed: 45 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use std::{
66
};
77

88
use rustc_lexer::unescape::{
9-
unescape_byte, unescape_char, unescape_mixed, unescape_unicode, MixedUnit, Mode,
9+
unescape_byte, unescape_char, unescape_mixed, unescape_unicode, EscapeError, MixedUnit, Mode,
1010
};
1111

1212
use crate::{
@@ -180,10 +180,7 @@ pub trait IsString: AstToken {
180180
fn close_quote_text_range(&self) -> Option<TextRange> {
181181
self.quote_offsets().map(|it| it.quotes.1)
182182
}
183-
fn escaped_char_ranges(
184-
&self,
185-
cb: &mut dyn FnMut(TextRange, Result<char, rustc_lexer::unescape::EscapeError>),
186-
) {
183+
fn escaped_char_ranges(&self, cb: &mut dyn FnMut(TextRange, Result<char, EscapeError>)) {
187184
let text_range_no_quotes = match self.text_range_between_quotes() {
188185
Some(it) => it,
189186
None => return,
@@ -212,20 +209,17 @@ impl IsString for ast::String {
212209
}
213210

214211
impl ast::String {
215-
pub fn value(&self) -> Option<Cow<'_, str>> {
212+
pub fn value(&self) -> Result<Cow<'_, str>, EscapeError> {
213+
let text = self.text();
214+
let text_range = self.text_range_between_quotes().ok_or(EscapeError::LoneSlash)?;
215+
let text = &text[text_range - self.syntax().text_range().start()];
216216
if self.is_raw() {
217-
let text = self.text();
218-
let text =
219-
&text[self.text_range_between_quotes()? - self.syntax().text_range().start()];
220-
return Some(Cow::Borrowed(text));
217+
return Ok(Cow::Borrowed(text));
221218
}
222219

223-
let text = self.text();
224-
let text = &text[self.text_range_between_quotes()? - self.syntax().text_range().start()];
225-
226220
let mut buf = String::new();
227221
let mut prev_end = 0;
228-
let mut has_error = false;
222+
let mut has_error = None;
229223
unescape_unicode(text, Self::MODE, &mut |char_range, unescaped_char| match (
230224
unescaped_char,
231225
buf.capacity() == 0,
@@ -239,13 +233,13 @@ impl ast::String {
239233
buf.push_str(&text[..prev_end]);
240234
buf.push(c);
241235
}
242-
(Err(_), _) => has_error = true,
236+
(Err(e), _) => has_error = Some(e),
243237
});
244238

245239
match (has_error, buf.capacity() == 0) {
246-
(true, _) => None,
247-
(false, true) => Some(Cow::Borrowed(text)),
248-
(false, false) => Some(Cow::Owned(buf)),
240+
(Some(e), _) => Err(e),
241+
(None, true) => Ok(Cow::Borrowed(text)),
242+
(None, false) => Ok(Cow::Owned(buf)),
249243
}
250244
}
251245
}
@@ -256,20 +250,17 @@ impl IsString for ast::ByteString {
256250
}
257251

258252
impl ast::ByteString {
259-
pub fn value(&self) -> Option<Cow<'_, [u8]>> {
253+
pub fn value(&self) -> Result<Cow<'_, [u8]>, EscapeError> {
254+
let text = self.text();
255+
let text_range = self.text_range_between_quotes().ok_or(EscapeError::LoneSlash)?;
256+
let text = &text[text_range - self.syntax().text_range().start()];
260257
if self.is_raw() {
261-
let text = self.text();
262-
let text =
263-
&text[self.text_range_between_quotes()? - self.syntax().text_range().start()];
264-
return Some(Cow::Borrowed(text.as_bytes()));
258+
return Ok(Cow::Borrowed(text.as_bytes()));
265259
}
266260

267-
let text = self.text();
268-
let text = &text[self.text_range_between_quotes()? - self.syntax().text_range().start()];
269-
270261
let mut buf: Vec<u8> = Vec::new();
271262
let mut prev_end = 0;
272-
let mut has_error = false;
263+
let mut has_error = None;
273264
unescape_unicode(text, Self::MODE, &mut |char_range, unescaped_char| match (
274265
unescaped_char,
275266
buf.capacity() == 0,
@@ -283,13 +274,13 @@ impl ast::ByteString {
283274
buf.extend_from_slice(text[..prev_end].as_bytes());
284275
buf.push(c as u8);
285276
}
286-
(Err(_), _) => has_error = true,
277+
(Err(e), _) => has_error = Some(e),
287278
});
288279

289280
match (has_error, buf.capacity() == 0) {
290-
(true, _) => None,
291-
(false, true) => Some(Cow::Borrowed(text.as_bytes())),
292-
(false, false) => Some(Cow::Owned(buf)),
281+
(Some(e), _) => Err(e),
282+
(None, true) => Ok(Cow::Borrowed(text.as_bytes())),
283+
(None, false) => Ok(Cow::Owned(buf)),
293284
}
294285
}
295286
}
@@ -298,10 +289,7 @@ impl IsString for ast::CString {
298289
const RAW_PREFIX: &'static str = "cr";
299290
const MODE: Mode = Mode::CStr;
300291

301-
fn escaped_char_ranges(
302-
&self,
303-
cb: &mut dyn FnMut(TextRange, Result<char, rustc_lexer::unescape::EscapeError>),
304-
) {
292+
fn escaped_char_ranges(&self, cb: &mut dyn FnMut(TextRange, Result<char, EscapeError>)) {
305293
let text_range_no_quotes = match self.text_range_between_quotes() {
306294
Some(it) => it,
307295
None => return,
@@ -322,20 +310,17 @@ impl IsString for ast::CString {
322310
}
323311

324312
impl ast::CString {
325-
pub fn value(&self) -> Option<Cow<'_, [u8]>> {
313+
pub fn value(&self) -> Result<Cow<'_, [u8]>, EscapeError> {
314+
let text = self.text();
315+
let text_range = self.text_range_between_quotes().ok_or(EscapeError::LoneSlash)?;
316+
let text = &text[text_range - self.syntax().text_range().start()];
326317
if self.is_raw() {
327-
let text = self.text();
328-
let text =
329-
&text[self.text_range_between_quotes()? - self.syntax().text_range().start()];
330-
return Some(Cow::Borrowed(text.as_bytes()));
318+
return Ok(Cow::Borrowed(text.as_bytes()));
331319
}
332320

333-
let text = self.text();
334-
let text = &text[self.text_range_between_quotes()? - self.syntax().text_range().start()];
335-
336321
let mut buf = Vec::new();
337322
let mut prev_end = 0;
338-
let mut has_error = false;
323+
let mut has_error = None;
339324
let extend_unit = |buf: &mut Vec<u8>, unit: MixedUnit| match unit {
340325
MixedUnit::Char(c) => buf.extend(c.encode_utf8(&mut [0; 4]).as_bytes()),
341326
MixedUnit::HighByte(b) => buf.push(b),
@@ -353,13 +338,13 @@ impl ast::CString {
353338
buf.extend(text[..prev_end].as_bytes());
354339
extend_unit(&mut buf, u);
355340
}
356-
(Err(_), _) => has_error = true,
341+
(Err(e), _) => has_error = Some(e),
357342
});
358343

359344
match (has_error, buf.capacity() == 0) {
360-
(true, _) => None,
361-
(false, true) => Some(Cow::Borrowed(text.as_bytes())),
362-
(false, false) => Some(Cow::Owned(buf)),
345+
(Some(e), _) => Err(e),
346+
(None, true) => Ok(Cow::Borrowed(text.as_bytes())),
347+
(None, false) => Ok(Cow::Owned(buf)),
363348
}
364349
}
365350
}
@@ -478,34 +463,34 @@ impl Radix {
478463
}
479464

480465
impl ast::Char {
481-
pub fn value(&self) -> Option<char> {
466+
pub fn value(&self) -> Result<char, EscapeError> {
482467
let mut text = self.text();
483468
if text.starts_with('\'') {
484469
text = &text[1..];
485470
} else {
486-
return None;
471+
return Err(EscapeError::ZeroChars);
487472
}
488473
if text.ends_with('\'') {
489474
text = &text[0..text.len() - 1];
490475
}
491476

492-
unescape_char(text).ok()
477+
unescape_char(text)
493478
}
494479
}
495480

496481
impl ast::Byte {
497-
pub fn value(&self) -> Option<u8> {
482+
pub fn value(&self) -> Result<u8, EscapeError> {
498483
let mut text = self.text();
499484
if text.starts_with("b\'") {
500485
text = &text[2..];
501486
} else {
502-
return None;
487+
return Err(EscapeError::ZeroChars);
503488
}
504489
if text.ends_with('\'') {
505490
text = &text[0..text.len() - 1];
506491
}
507492

508-
unescape_byte(text).ok()
493+
unescape_byte(text)
509494
}
510495
}
511496

@@ -559,7 +544,10 @@ mod tests {
559544

560545
fn check_string_value<'a>(lit: &str, expected: impl Into<Option<&'a str>>) {
561546
assert_eq!(
562-
ast::String { syntax: make::tokens::literal(&format!("\"{lit}\"")) }.value().as_deref(),
547+
ast::String { syntax: make::tokens::literal(&format!("\"{lit}\"")) }
548+
.value()
549+
.as_deref()
550+
.ok(),
563551
expected.into()
564552
);
565553
}
@@ -584,7 +572,8 @@ bcde", "abcde",
584572
assert_eq!(
585573
ast::ByteString { syntax: make::tokens::literal(&format!("b\"{lit}\"")) }
586574
.value()
587-
.as_deref(),
575+
.as_deref()
576+
.ok(),
588577
expected.into().map(|value| &value[..])
589578
);
590579
}

0 commit comments

Comments
 (0)