From 253929f6ae47114c633a9a208da6904778f86093 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Mon, 13 May 2024 12:26:45 +0200 Subject: [PATCH 1/3] Fix literal hovers being confusing and wrong for floats --- crates/hir/src/lib.rs | 14 ++++ crates/ide/src/hover.rs | 57 +------------ crates/ide/src/hover/render.rs | 56 +++++++++++-- crates/ide/src/hover/tests.rs | 141 ++++++++++++++++++++++++--------- 4 files changed, 171 insertions(+), 97 deletions(-) diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs index 149e75c45c..275c1390f2 100644 --- a/crates/hir/src/lib.rs +++ b/crates/hir/src/lib.rs @@ -2543,6 +2543,20 @@ impl BuiltinType { matches!(self.inner, hir_def::builtin_type::BuiltinType::Float(_)) } + pub fn is_f32(&self) -> bool { + matches!( + self.inner, + hir_def::builtin_type::BuiltinType::Float(hir_def::builtin_type::BuiltinFloat::F32) + ) + } + + pub fn is_f64(&self) -> bool { + matches!( + self.inner, + hir_def::builtin_type::BuiltinType::Float(hir_def::builtin_type::BuiltinFloat::F64) + ) + } + pub fn is_char(&self) -> bool { matches!(self.inner, hir_def::builtin_type::BuiltinType::Char) } diff --git a/crates/ide/src/hover.rs b/crates/ide/src/hover.rs index f9210da5fd..6b73afe08e 100644 --- a/crates/ide/src/hover.rs +++ b/crates/ide/src/hover.rs @@ -298,61 +298,8 @@ fn hover_simple( }) // tokens .or_else(|| { - let mut res = HoverResult::default(); - match_ast! { - match original_token { - ast::String(string) => { - res.markup = Markup::fenced_block_text(format_args!("{}", string.value()?)); - }, - ast::ByteString(string) => { - res.markup = Markup::fenced_block_text(format_args!("{:?}", string.value()?)); - }, - ast::CString(string) => { - let val = string.value()?; - res.markup = Markup::fenced_block_text(format_args!("{}", std::str::from_utf8(val.as_ref()).ok()?)); - }, - ast::Char(char) => { - let mut res = HoverResult::default(); - res.markup = Markup::fenced_block_text(format_args!("{}", char.value()?)); - }, - ast::Byte(byte) => { - res.markup = Markup::fenced_block_text(format_args!("0x{:X}", byte.value()?)); - }, - ast::FloatNumber(num) => { - res.markup = if num.suffix() == Some("f32") { - match num.value_f32() { - Ok(num) => { - Markup::fenced_block_text(format_args!("{num} (bits: 0x{:X})", num.to_bits())) - }, - Err(e) => { - Markup::fenced_block_text(format_args!("{e}")) - }, - } - } else { - match num.value() { - Ok(num) => { - Markup::fenced_block_text(format_args!("{num} (bits: 0x{:X})", num.to_bits())) - }, - Err(e) => { - Markup::fenced_block_text(format_args!("{e}")) - }, - } - }; - }, - ast::IntNumber(num) => { - res.markup = match num.value() { - Ok(num) => { - Markup::fenced_block_text(format_args!("{num} (0x{num:X}|0b{num:b})")) - }, - Err(e) => { - Markup::fenced_block_text(format_args!("{e}")) - }, - }; - }, - _ => return None - } - } - Some(res) + render::literal(sema, original_token.clone()) + .map(|markup| HoverResult { markup, actions: vec![] }) }); result.map(|mut res: HoverResult| { diff --git a/crates/ide/src/hover/render.rs b/crates/ide/src/hover/render.rs index ee96767411..674b32a7f5 100644 --- a/crates/ide/src/hover/render.rs +++ b/crates/ide/src/hover/render.rs @@ -17,11 +17,7 @@ use ide_db::{ }; use itertools::Itertools; use stdx::format_to; -use syntax::{ - algo, - ast::{self, RecordPat}, - match_ast, AstNode, Direction, SyntaxToken, T, -}; +use syntax::{algo, ast, match_ast, AstNode, AstToken, Direction, SyntaxToken, T}; use crate::{ doc_links::{remove_links, rewrite_links}, @@ -276,7 +272,7 @@ pub(super) fn keyword( pub(super) fn struct_rest_pat( sema: &Semantics<'_, RootDatabase>, _config: &HoverConfig, - pattern: &RecordPat, + pattern: &ast::RecordPat, ) -> HoverResult { let missing_fields = sema.record_pattern_missing_fields(pattern); @@ -526,6 +522,54 @@ pub(super) fn definition( markup(docs.map(Into::into), desc, mod_path) } +pub(super) fn literal(sema: &Semantics<'_, RootDatabase>, token: SyntaxToken) -> Option { + let lit = token.parent().and_then(ast::Literal::cast)?; + let ty = if let Some(p) = lit.syntax().parent().and_then(ast::Pat::cast) { + sema.type_of_pat(&p)? + } else { + sema.type_of_expr(&ast::Expr::Literal(lit))? + } + .original; + + let value = match_ast! { + match token { + ast::String(string) => Ok(string.value()?.to_string()), + ast::ByteString(string) => Ok(format_args!("{:?}", string.value()?).to_string()), + ast::CString(string) => Ok(std::str::from_utf8(string.value()?.as_ref()).ok()?.to_owned()), + ast::Char(char) => Ok(char.value()?.to_string()), + ast::Byte(byte) => Ok(format!("0x{:X}", byte.value()?)), + ast::FloatNumber(num) => { + let (text, _) = num.split_into_parts(); + let text = text.replace('_', ""); + if ty.as_builtin().map(|it| it.is_f32()).unwrap_or(false) { + match text.parse::() { + Ok(num) => Ok(format!("{num} (bits: 0x{:X})", num.to_bits())), + Err(e) => Err(e.to_string()), + } + } else { + match text.parse::() { + Ok(num) => Ok(format!("{num} (bits: 0x{:X})", num.to_bits())), + Err(e) => Err(e.to_string()), + } + } + }, + ast::IntNumber(num) => match num.value() { + Ok(num) => Ok(format!("{num} (0x{num:X}|0b{num:b})")), + Err(e) => Err(e.to_string()), + }, + _ => return None + } + }; + let ty = ty.display(sema.db); + + let mut s = format!("```rust\n{ty}\n```\n___\n\n"); + match value { + Ok(value) => format_to!(s, "value of literal: {value}"), + Err(error) => format_to!(s, "invalid literal: {error}"), + } + Some(s.into()) +} + fn render_notable_trait_comment( db: &RootDatabase, notable_traits: &[(Trait, Vec<(Option, Name)>)], diff --git a/crates/ide/src/hover/tests.rs b/crates/ide/src/hover/tests.rs index f7af795004..b5547c0514 100644 --- a/crates/ide/src/hover/tests.rs +++ b/crates/ide/src/hover/tests.rs @@ -7790,9 +7790,12 @@ fn main() { "#, expect![[r#" *"🦀\u{1f980}\\\x41"* - ```text - 🦀🦀\A + ```rust + &str ``` + ___ + + value of literal: 🦀🦀\A "#]], ); check( @@ -7803,9 +7806,12 @@ fn main() { "#, expect![[r#" *r"🦀\u{1f980}\\\x41"* - ```text - 🦀\u{1f980}\\\x41 + ```rust + &str ``` + ___ + + value of literal: 🦀\u{1f980}\\\x41 "#]], ); } @@ -7820,9 +7826,12 @@ fn main() { "#, expect![[r#" *c"🦀\u{1f980}\\\x41"* - ```text - 🦀🦀\A + ```rust + &{unknown} ``` + ___ + + value of literal: 🦀🦀\A "#]], ); } @@ -7837,9 +7846,12 @@ fn main() { "#, expect![[r#" *b"\xF0\x9F\xA6\x80\\"* - ```text - [240, 159, 166, 128, 92] + ```rust + &[u8; 5] ``` + ___ + + value of literal: [240, 159, 166, 128, 92] "#]], ); check( @@ -7850,9 +7862,12 @@ fn main() { "#, expect![[r#" *br"\xF0\x9F\xA6\x80\\"* - ```text - [92, 120, 70, 48, 92, 120, 57, 70, 92, 120, 65, 54, 92, 120, 56, 48, 92, 92] + ```rust + &[u8; 18] ``` + ___ + + value of literal: [92, 120, 70, 48, 92, 120, 57, 70, 92, 120, 65, 54, 92, 120, 56, 48, 92, 92] "#]], ); } @@ -7867,9 +7882,12 @@ fn main() { "#, expect![[r#" *b'\xF0'* - ```text - 0xF0 + ```rust + u8 ``` + ___ + + value of literal: 0xF0 "#]], ); check( @@ -7880,9 +7898,12 @@ fn main() { "#, expect![[r#" *b'\\'* - ```text - 0x5C + ```rust + u8 ``` + ___ + + value of literal: 0x5C "#]], ); } @@ -7897,7 +7918,12 @@ fn main() { "#, expect![[r#" *'\x41'* + ```rust + char + ``` + ___ + value of literal: A "#]], ); check( @@ -7908,7 +7934,12 @@ fn main() { "#, expect![[r#" *'\\'* + ```rust + char + ``` + ___ + value of literal: \ "#]], ); check( @@ -7919,7 +7950,12 @@ fn main() { "#, expect![[r#" *'\u{1f980}'* + ```rust + char + ``` + ___ + value of literal: 🦀 "#]], ); } @@ -7934,9 +7970,12 @@ fn main() { "#, expect![[r#" *1.0* - ```text - 1 (bits: 0x3FF0000000000000) + ```rust + f64 ``` + ___ + + value of literal: 1 (bits: 0x3FF0000000000000) "#]], ); check( @@ -7947,9 +7986,12 @@ fn main() { "#, expect![[r#" *1.0f32* - ```text - 1 (bits: 0x3F800000) + ```rust + f32 ``` + ___ + + value of literal: 1 (bits: 0x3F800000) "#]], ); check( @@ -7960,9 +8002,12 @@ fn main() { "#, expect![[r#" *134e12* - ```text - 134000000000000 (bits: 0x42DE77D399980000) + ```rust + f64 ``` + ___ + + value of literal: 134000000000000 (bits: 0x42DE77D399980000) "#]], ); check( @@ -7973,9 +8018,12 @@ fn main() { "#, expect![[r#" *1523527134274733643531312.0* - ```text - 1523527134274733600000000 (bits: 0x44F429E9249F629B) + ```rust + f64 ``` + ___ + + value of literal: 1523527134274733600000000 (bits: 0x44F429E9249F629B) "#]], ); check( @@ -7986,9 +8034,12 @@ fn main() { "#, expect![[r#" *0.1ea123* - ```text - invalid float literal + ```rust + f64 ``` + ___ + + invalid literal: invalid float literal "#]], ); } @@ -8003,9 +8054,12 @@ fn main() { "#, expect![[r#" *34325236457856836345234* - ```text - 34325236457856836345234 (0x744C659178614489D92|0b111010001001100011001011001000101111000011000010100010010001001110110010010) + ```rust + i32 ``` + ___ + + value of literal: 34325236457856836345234 (0x744C659178614489D92|0b111010001001100011001011001000101111000011000010100010010001001110110010010) "#]], ); check( @@ -8016,9 +8070,12 @@ fn main() { "#, expect![[r#" *134_123424_21* - ```text - 13412342421 (0x31F701A95|0b1100011111011100000001101010010101) + ```rust + i32 ``` + ___ + + value of literal: 13412342421 (0x31F701A95|0b1100011111011100000001101010010101) "#]], ); check( @@ -8029,9 +8086,12 @@ fn main() { "#, expect![[r#" *0x12423423* - ```text - 306328611 (0x12423423|0b10010010000100011010000100011) + ```rust + i32 ``` + ___ + + value of literal: 306328611 (0x12423423|0b10010010000100011010000100011) "#]], ); check( @@ -8042,9 +8102,12 @@ fn main() { "#, expect![[r#" *0b1111_1111* - ```text - 255 (0xFF|0b11111111) + ```rust + i32 ``` + ___ + + value of literal: 255 (0xFF|0b11111111) "#]], ); check( @@ -8055,9 +8118,12 @@ fn main() { "#, expect![[r#" *0o12345* - ```text - 5349 (0x14E5|0b1010011100101) + ```rust + i32 ``` + ___ + + value of literal: 5349 (0x14E5|0b1010011100101) "#]], ); check( @@ -8068,9 +8134,12 @@ fn main() { "#, expect![[r#" *0xFFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_F* - ```text - number too large to fit in target type + ```rust + i32 ``` + ___ + + invalid literal: number too large to fit in target type "#]], ); } From a39c0493a11cda3fed3c1e61099938b996886349 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Mon, 13 May 2024 12:51:57 +0200 Subject: [PATCH 2/3] Render literal escaping errors in hovers --- crates/hir-def/src/hir.rs | 6 +- crates/hir-expand/src/builtin_fn_macro.rs | 6 +- crates/ide-assists/src/handlers/raw_string.rs | 8 +- .../src/handlers/replace_string_with_char.rs | 2 +- crates/ide/src/goto_definition.rs | 6 +- crates/ide/src/hover.rs | 2 +- crates/ide/src/hover/render.rs | 10 +- crates/ide/src/syntax_highlighting/escape.rs | 4 +- crates/ide/src/syntax_highlighting/inject.rs | 2 +- crates/syntax/src/ast/token_ext.rs | 107 ++++++++---------- 10 files changed, 71 insertions(+), 82 deletions(-) diff --git a/crates/hir-def/src/hir.rs b/crates/hir-def/src/hir.rs index ac0caaf0dc..2f7ebbfec1 100644 --- a/crates/hir-def/src/hir.rs +++ b/crates/hir-def/src/hir.rs @@ -136,15 +136,15 @@ impl From for Literal { Literal::Float(FloatTypeWrapper::new(lit.value().unwrap_or(Default::default())), ty) } LiteralKind::ByteString(bs) => { - let text = bs.value().map(Box::from).unwrap_or_else(Default::default); + let text = bs.value().map_or_else(|_| Default::default(), Box::from); Literal::ByteString(text) } LiteralKind::String(s) => { - let text = s.value().map(Box::from).unwrap_or_else(Default::default); + let text = s.value().map_or_else(|_| Default::default(), Box::from); Literal::String(text) } LiteralKind::CString(s) => { - let text = s.value().map(Box::from).unwrap_or_else(Default::default); + let text = s.value().map_or_else(|_| Default::default(), Box::from); Literal::CString(text) } LiteralKind::Byte(b) => { diff --git a/crates/hir-expand/src/builtin_fn_macro.rs b/crates/hir-expand/src/builtin_fn_macro.rs index 4d6fe6db39..ba96ab6cc2 100644 --- a/crates/hir-expand/src/builtin_fn_macro.rs +++ b/crates/hir-expand/src/builtin_fn_macro.rs @@ -441,21 +441,21 @@ fn unquote_str(lit: &tt::Literal) -> Option<(String, Span)> { let span = lit.span; let lit = ast::make::tokens::literal(&lit.to_string()); let token = ast::String::cast(lit)?; - token.value().map(|it| (it.into_owned(), span)) + token.value().ok().map(|it| (it.into_owned(), span)) } fn unquote_char(lit: &tt::Literal) -> Option<(char, Span)> { let span = lit.span; let lit = ast::make::tokens::literal(&lit.to_string()); let token = ast::Char::cast(lit)?; - token.value().zip(Some(span)) + token.value().ok().zip(Some(span)) } fn unquote_byte_string(lit: &tt::Literal) -> Option<(Vec, Span)> { let span = lit.span; let lit = ast::make::tokens::literal(&lit.to_string()); let token = ast::ByteString::cast(lit)?; - token.value().map(|it| (it.into_owned(), span)) + token.value().ok().map(|it| (it.into_owned(), span)) } fn compile_error_expand( diff --git a/crates/ide-assists/src/handlers/raw_string.rs b/crates/ide-assists/src/handlers/raw_string.rs index 63db606336..5a197f23d0 100644 --- a/crates/ide-assists/src/handlers/raw_string.rs +++ b/crates/ide-assists/src/handlers/raw_string.rs @@ -25,7 +25,7 @@ pub(crate) fn make_raw_string(acc: &mut Assists, ctx: &AssistContext<'_>) -> Opt if token.is_raw() { return None; } - let value = token.value()?; + let value = token.value().ok()?; let target = token.syntax().text_range(); acc.add( AssistId("make_raw_string", AssistKind::RefactorRewrite), @@ -64,7 +64,7 @@ pub(crate) fn make_usual_string(acc: &mut Assists, ctx: &AssistContext<'_>) -> O if !token.is_raw() { return None; } - let value = token.value()?; + let value = token.value().ok()?; let target = token.syntax().text_range(); acc.add( AssistId("make_usual_string", AssistKind::RefactorRewrite), @@ -398,12 +398,12 @@ string"###; } #[test] - fn remove_hash_doesnt_work() { + fn remove_hash_does_not_work() { check_assist_not_applicable(remove_hash, r#"fn f() { let s = $0"random string"; }"#); } #[test] - fn remove_hash_no_hash_doesnt_work() { + fn remove_hash_no_hash_does_not_work() { check_assist_not_applicable(remove_hash, r#"fn f() { let s = $0r"random string"; }"#); } diff --git a/crates/ide-assists/src/handlers/replace_string_with_char.rs b/crates/ide-assists/src/handlers/replace_string_with_char.rs index 6310981ccc..a48b20acbc 100644 --- a/crates/ide-assists/src/handlers/replace_string_with_char.rs +++ b/crates/ide-assists/src/handlers/replace_string_with_char.rs @@ -25,7 +25,7 @@ use crate::{AssistContext, AssistId, AssistKind, Assists}; // ``` pub(crate) fn replace_string_with_char(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> { let token = ctx.find_token_syntax_at_offset(STRING).and_then(ast::String::cast)?; - let value = token.value()?; + let value = token.value().ok()?; let target = token.syntax().text_range(); if value.chars().take(2).count() != 1 { diff --git a/crates/ide/src/goto_definition.rs b/crates/ide/src/goto_definition.rs index ddeeca5f7b..76b80fcefa 100644 --- a/crates/ide/src/goto_definition.rs +++ b/crates/ide/src/goto_definition.rs @@ -123,7 +123,7 @@ fn try_lookup_include_path( { return None; } - let path = token.value()?; + let path = token.value().ok()?; let file_id = sema.db.resolve_path(AnchoredPath { anchor: file_id, path: &path })?; let size = sema.db.file_text(file_id).len().try_into().ok()?; @@ -179,11 +179,11 @@ fn try_filter_trait_item_definition( AssocItem::Const(..) | AssocItem::TypeAlias(..) => { let trait_ = assoc.implemented_trait(db)?; let name = def.name(db)?; - let discri_value = discriminant(&assoc); + let discriminant_value = discriminant(&assoc); trait_ .items(db) .iter() - .filter(|itm| discriminant(*itm) == discri_value) + .filter(|itm| discriminant(*itm) == discriminant_value) .find_map(|itm| (itm.name(db)? == name).then(|| itm.try_to_nav(db)).flatten()) .map(|it| it.collect()) } diff --git a/crates/ide/src/hover.rs b/crates/ide/src/hover.rs index 6b73afe08e..bdb56081c3 100644 --- a/crates/ide/src/hover.rs +++ b/crates/ide/src/hover.rs @@ -15,7 +15,7 @@ use ide_db::{ FxIndexSet, RootDatabase, }; use itertools::{multizip, Itertools}; -use syntax::{ast, match_ast, AstNode, AstToken, SyntaxKind::*, SyntaxNode, T}; +use syntax::{ast, AstNode, SyntaxKind::*, SyntaxNode, T}; use crate::{ doc_links::token_as_doc_comment, diff --git a/crates/ide/src/hover/render.rs b/crates/ide/src/hover/render.rs index 674b32a7f5..e392a8ec38 100644 --- a/crates/ide/src/hover/render.rs +++ b/crates/ide/src/hover/render.rs @@ -533,11 +533,11 @@ pub(super) fn literal(sema: &Semantics<'_, RootDatabase>, token: SyntaxToken) -> let value = match_ast! { match token { - ast::String(string) => Ok(string.value()?.to_string()), - ast::ByteString(string) => Ok(format_args!("{:?}", string.value()?).to_string()), - ast::CString(string) => Ok(std::str::from_utf8(string.value()?.as_ref()).ok()?.to_owned()), - ast::Char(char) => Ok(char.value()?.to_string()), - ast::Byte(byte) => Ok(format!("0x{:X}", byte.value()?)), + ast::String(string) => string.value().as_ref().map_err(|e| format!("{e:?}")).map(ToString::to_string), + ast::ByteString(string) => string.value().as_ref().map_err(|e| format!("{e:?}")).map(|it| format!("{it:?}")), + 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)), + ast::Char(char) => char .value().as_ref().map_err(|e| format!("{e:?}")).map(ToString::to_string), + ast::Byte(byte) => byte .value().as_ref().map_err(|e| format!("{e:?}")).map(|it| format!("0x{it:X}")), ast::FloatNumber(num) => { let (text, _) = num.split_into_parts(); let text = text.replace('_', ""); diff --git a/crates/ide/src/syntax_highlighting/escape.rs b/crates/ide/src/syntax_highlighting/escape.rs index 5913ca5e45..0439e509d2 100644 --- a/crates/ide/src/syntax_highlighting/escape.rs +++ b/crates/ide/src/syntax_highlighting/escape.rs @@ -25,7 +25,7 @@ pub(super) fn highlight_escape_string( } pub(super) fn highlight_escape_char(stack: &mut Highlights, char: &Char, start: TextSize) { - if char.value().is_none() { + if char.value().is_err() { // We do not emit invalid escapes highlighting here. The lexer would likely be in a bad // state and this token contains junks, since `'` is not a reliable delimiter (consider // lifetimes). Nonetheless, parser errors should already be emitted. @@ -48,7 +48,7 @@ pub(super) fn highlight_escape_char(stack: &mut Highlights, char: &Char, start: } pub(super) fn highlight_escape_byte(stack: &mut Highlights, byte: &Byte, start: TextSize) { - if byte.value().is_none() { + if byte.value().is_err() { // See `highlight_escape_char` for why no error highlighting here. return; } diff --git a/crates/ide/src/syntax_highlighting/inject.rs b/crates/ide/src/syntax_highlighting/inject.rs index 6bf13ffd06..10d867fe2b 100644 --- a/crates/ide/src/syntax_highlighting/inject.rs +++ b/crates/ide/src/syntax_highlighting/inject.rs @@ -30,7 +30,7 @@ pub(super) fn ra_fixture( if !active_parameter.ident().map_or(false, |name| name.text().starts_with("ra_fixture")) { return None; } - let value = literal.value()?; + let value = literal.value().ok()?; if let Some(range) = literal.open_quote_text_range() { hl.add(HlRange { range, highlight: HlTag::StringLiteral.into(), binding_hash: None }) diff --git a/crates/syntax/src/ast/token_ext.rs b/crates/syntax/src/ast/token_ext.rs index 7cd1f1550b..16599881d6 100644 --- a/crates/syntax/src/ast/token_ext.rs +++ b/crates/syntax/src/ast/token_ext.rs @@ -6,7 +6,7 @@ use std::{ }; use rustc_lexer::unescape::{ - unescape_byte, unescape_char, unescape_mixed, unescape_unicode, MixedUnit, Mode, + unescape_byte, unescape_char, unescape_mixed, unescape_unicode, EscapeError, MixedUnit, Mode, }; use crate::{ @@ -180,10 +180,7 @@ pub trait IsString: AstToken { fn close_quote_text_range(&self) -> Option { self.quote_offsets().map(|it| it.quotes.1) } - fn escaped_char_ranges( - &self, - cb: &mut dyn FnMut(TextRange, Result), - ) { + fn escaped_char_ranges(&self, cb: &mut dyn FnMut(TextRange, Result)) { let text_range_no_quotes = match self.text_range_between_quotes() { Some(it) => it, None => return, @@ -212,20 +209,17 @@ impl IsString for ast::String { } impl ast::String { - pub fn value(&self) -> Option> { - if self.is_raw() { - let text = self.text(); - let text = - &text[self.text_range_between_quotes()? - self.syntax().text_range().start()]; - return Some(Cow::Borrowed(text)); - } - + pub fn value(&self) -> Result, EscapeError> { let text = self.text(); - let text = &text[self.text_range_between_quotes()? - self.syntax().text_range().start()]; + let text_range = self.text_range_between_quotes().ok_or(EscapeError::LoneSlash)?; + let text = &text[text_range - self.syntax().text_range().start()]; + if self.is_raw() { + return Ok(Cow::Borrowed(text)); + } let mut buf = String::new(); let mut prev_end = 0; - let mut has_error = false; + let mut has_error = None; unescape_unicode(text, Self::MODE, &mut |char_range, unescaped_char| match ( unescaped_char, buf.capacity() == 0, @@ -239,13 +233,13 @@ impl ast::String { buf.push_str(&text[..prev_end]); buf.push(c); } - (Err(_), _) => has_error = true, + (Err(e), _) => has_error = Some(e), }); match (has_error, buf.capacity() == 0) { - (true, _) => None, - (false, true) => Some(Cow::Borrowed(text)), - (false, false) => Some(Cow::Owned(buf)), + (Some(e), _) => Err(e), + (None, true) => Ok(Cow::Borrowed(text)), + (None, false) => Ok(Cow::Owned(buf)), } } } @@ -256,20 +250,17 @@ impl IsString for ast::ByteString { } impl ast::ByteString { - pub fn value(&self) -> Option> { - if self.is_raw() { - let text = self.text(); - let text = - &text[self.text_range_between_quotes()? - self.syntax().text_range().start()]; - return Some(Cow::Borrowed(text.as_bytes())); - } - + pub fn value(&self) -> Result, EscapeError> { let text = self.text(); - let text = &text[self.text_range_between_quotes()? - self.syntax().text_range().start()]; + let text_range = self.text_range_between_quotes().ok_or(EscapeError::LoneSlash)?; + let text = &text[text_range - self.syntax().text_range().start()]; + if self.is_raw() { + return Ok(Cow::Borrowed(text.as_bytes())); + } let mut buf: Vec = Vec::new(); let mut prev_end = 0; - let mut has_error = false; + let mut has_error = None; unescape_unicode(text, Self::MODE, &mut |char_range, unescaped_char| match ( unescaped_char, buf.capacity() == 0, @@ -283,13 +274,13 @@ impl ast::ByteString { buf.extend_from_slice(text[..prev_end].as_bytes()); buf.push(c as u8); } - (Err(_), _) => has_error = true, + (Err(e), _) => has_error = Some(e), }); match (has_error, buf.capacity() == 0) { - (true, _) => None, - (false, true) => Some(Cow::Borrowed(text.as_bytes())), - (false, false) => Some(Cow::Owned(buf)), + (Some(e), _) => Err(e), + (None, true) => Ok(Cow::Borrowed(text.as_bytes())), + (None, false) => Ok(Cow::Owned(buf)), } } } @@ -298,10 +289,7 @@ impl IsString for ast::CString { const RAW_PREFIX: &'static str = "cr"; const MODE: Mode = Mode::CStr; - fn escaped_char_ranges( - &self, - cb: &mut dyn FnMut(TextRange, Result), - ) { + fn escaped_char_ranges(&self, cb: &mut dyn FnMut(TextRange, Result)) { let text_range_no_quotes = match self.text_range_between_quotes() { Some(it) => it, None => return, @@ -322,20 +310,17 @@ impl IsString for ast::CString { } impl ast::CString { - pub fn value(&self) -> Option> { - if self.is_raw() { - let text = self.text(); - let text = - &text[self.text_range_between_quotes()? - self.syntax().text_range().start()]; - return Some(Cow::Borrowed(text.as_bytes())); - } - + pub fn value(&self) -> Result, EscapeError> { let text = self.text(); - let text = &text[self.text_range_between_quotes()? - self.syntax().text_range().start()]; + let text_range = self.text_range_between_quotes().ok_or(EscapeError::LoneSlash)?; + let text = &text[text_range - self.syntax().text_range().start()]; + if self.is_raw() { + return Ok(Cow::Borrowed(text.as_bytes())); + } let mut buf = Vec::new(); let mut prev_end = 0; - let mut has_error = false; + let mut has_error = None; let extend_unit = |buf: &mut Vec, unit: MixedUnit| match unit { MixedUnit::Char(c) => buf.extend(c.encode_utf8(&mut [0; 4]).as_bytes()), MixedUnit::HighByte(b) => buf.push(b), @@ -353,13 +338,13 @@ impl ast::CString { buf.extend(text[..prev_end].as_bytes()); extend_unit(&mut buf, u); } - (Err(_), _) => has_error = true, + (Err(e), _) => has_error = Some(e), }); match (has_error, buf.capacity() == 0) { - (true, _) => None, - (false, true) => Some(Cow::Borrowed(text.as_bytes())), - (false, false) => Some(Cow::Owned(buf)), + (Some(e), _) => Err(e), + (None, true) => Ok(Cow::Borrowed(text.as_bytes())), + (None, false) => Ok(Cow::Owned(buf)), } } } @@ -478,34 +463,34 @@ impl Radix { } impl ast::Char { - pub fn value(&self) -> Option { + pub fn value(&self) -> Result { let mut text = self.text(); if text.starts_with('\'') { text = &text[1..]; } else { - return None; + return Err(EscapeError::ZeroChars); } if text.ends_with('\'') { text = &text[0..text.len() - 1]; } - unescape_char(text).ok() + unescape_char(text) } } impl ast::Byte { - pub fn value(&self) -> Option { + pub fn value(&self) -> Result { let mut text = self.text(); if text.starts_with("b\'") { text = &text[2..]; } else { - return None; + return Err(EscapeError::ZeroChars); } if text.ends_with('\'') { text = &text[0..text.len() - 1]; } - unescape_byte(text).ok() + unescape_byte(text) } } @@ -559,7 +544,10 @@ mod tests { fn check_string_value<'a>(lit: &str, expected: impl Into>) { assert_eq!( - ast::String { syntax: make::tokens::literal(&format!("\"{lit}\"")) }.value().as_deref(), + ast::String { syntax: make::tokens::literal(&format!("\"{lit}\"")) } + .value() + .as_deref() + .ok(), expected.into() ); } @@ -584,7 +572,8 @@ bcde", "abcde", assert_eq!( ast::ByteString { syntax: make::tokens::literal(&format!("b\"{lit}\"")) } .value() - .as_deref(), + .as_deref() + .ok(), expected.into().map(|value| &value[..]) ); } From 5b696bac5cbd6cc9108ec83c67bc84c3d5329590 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Mon, 13 May 2024 12:56:02 +0200 Subject: [PATCH 3/3] Don't render multi-line literal values --- crates/ide/src/hover/render.rs | 8 +++++++- crates/ide/src/hover/tests.rs | 22 ++++++++++++++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/crates/ide/src/hover/render.rs b/crates/ide/src/hover/render.rs index e392a8ec38..a166bab37c 100644 --- a/crates/ide/src/hover/render.rs +++ b/crates/ide/src/hover/render.rs @@ -564,7 +564,13 @@ pub(super) fn literal(sema: &Semantics<'_, RootDatabase>, token: SyntaxToken) -> let mut s = format!("```rust\n{ty}\n```\n___\n\n"); match value { - Ok(value) => format_to!(s, "value of literal: {value}"), + Ok(value) => { + if let Some(newline) = value.find('\n') { + format_to!(s, "value of literal (truncated up to newline): {}", &value[..newline]) + } else { + format_to!(s, "value of literal: {value}") + } + } Err(error) => format_to!(s, "invalid literal: {error}"), } Some(s.into()) diff --git a/crates/ide/src/hover/tests.rs b/crates/ide/src/hover/tests.rs index b5547c0514..76d86f8ede 100644 --- a/crates/ide/src/hover/tests.rs +++ b/crates/ide/src/hover/tests.rs @@ -7814,6 +7814,28 @@ fn main() { value of literal: 🦀\u{1f980}\\\x41 "#]], ); + check( + r#" +fn main() { + $0r"🦀\u{1f980}\\\x41 + + +fsdghs"; +} +"#, + expect![[r#" + *r"🦀\u{1f980}\\\x41 + + + fsdghs"* + ```rust + &str + ``` + ___ + + value of literal (truncated up to newline): 🦀\u{1f980}\\\x41 + "#]], + ); } #[test]