From 384488c15702e1207b704113f3f4592e3813c29c Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Tue, 16 Jan 2024 14:28:47 +0100 Subject: [PATCH] feat: Hover for literals showing additional value information --- crates/hir/src/semantics.rs | 2 +- .../src/handlers/convert_integer_literal.rs | 2 +- crates/ide/src/hover.rs | 60 +++- crates/ide/src/hover/tests.rs | 309 +++++++++++++++++- crates/ide/src/markup.rs | 3 + crates/syntax/src/ast/token_ext.rs | 26 +- 6 files changed, 377 insertions(+), 25 deletions(-) diff --git a/crates/hir/src/semantics.rs b/crates/hir/src/semantics.rs index f51fe80931..4267bd4efa 100644 --- a/crates/hir/src/semantics.rs +++ b/crates/hir/src/semantics.rs @@ -433,7 +433,7 @@ impl<'db> SemanticsImpl<'db> { .find_map(|token| { self.resolve_offset_in_format_args( ast::String::cast(token)?, - offset - quote.end(), + offset.checked_sub(quote.end())?, ) }) .map(|(range, res)| (range + quote.end(), res)); diff --git a/crates/ide-assists/src/handlers/convert_integer_literal.rs b/crates/ide-assists/src/handlers/convert_integer_literal.rs index ff2195f7e6..fd3378e8c2 100644 --- a/crates/ide-assists/src/handlers/convert_integer_literal.rs +++ b/crates/ide-assists/src/handlers/convert_integer_literal.rs @@ -20,7 +20,7 @@ pub(crate) fn convert_integer_literal(acc: &mut Assists, ctx: &AssistContext<'_> _ => return None, }; let radix = literal.radix(); - let value = literal.value()?; + let value = literal.value().ok()?; let suffix = literal.suffix(); let range = literal.syntax().text_range(); diff --git a/crates/ide/src/hover.rs b/crates/ide/src/hover.rs index 5ad119ace8..d06577d3db 100644 --- a/crates/ide/src/hover.rs +++ b/crates/ide/src/hover.rs @@ -15,7 +15,7 @@ use ide_db::{ FxIndexSet, RootDatabase, }; use itertools::Itertools; -use syntax::{ast, AstNode, SyntaxKind::*, SyntaxNode, T}; +use syntax::{ast, match_ast, AstNode, AstToken, SyntaxKind::*, SyntaxNode, T}; use crate::{ doc_links::token_as_doc_comment, @@ -268,6 +268,64 @@ fn hover_simple( let c = token.parent().and_then(|x| x.parent()).and_then(ast::ClosureExpr::cast)?; render::closure_expr(sema, config, c) }) + }) + // 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}|0x{num:b})")) + }, + Err(e) => { + Markup::fenced_block_text(format_args!("{e}")) + }, + }; + }, + _ => return None + } + } + Some(res) }); result.map(|mut res: HoverResult| { diff --git a/crates/ide/src/hover/tests.rs b/crates/ide/src/hover/tests.rs index 8ef8f29576..587a6b66c4 100644 --- a/crates/ide/src/hover/tests.rs +++ b/crates/ide/src/hover/tests.rs @@ -1473,20 +1473,6 @@ fn foo(Foo { b$0ar }: &Foo) {} ) } -#[test] -fn test_hover_through_literal_string_in_builtin_macro() { - check_hover_no_result( - r#" - #[rustc_builtin_macro] - macro_rules! format {} - - fn foo() { - format!("hel$0lo {}", 0); - } -"#, - ); -} - #[test] fn test_hover_non_ascii_space_doc() { check( @@ -6778,3 +6764,298 @@ fn main() { "#]], ); } + +#[test] +fn string_literal() { + check( + r#" +fn main() { + $0"🦀\u{1f980}\\\x41"; +} +"#, + expect![[r#" + *"🦀\u{1f980}\\\x41"* + ```text + 🦀🦀\A + ``` + "#]], + ); + check( + r#" +fn main() { + $0r"🦀\u{1f980}\\\x41"; +} +"#, + expect![[r#" + *r"🦀\u{1f980}\\\x41"* + ```text + 🦀\u{1f980}\\\x41 + ``` + "#]], + ); +} + +#[test] +fn cstring_literal() { + check( + r#" +fn main() { + $0c"🦀\u{1f980}\\\x41"; +} +"#, + expect![[r#" + *c"🦀\u{1f980}\\\x41"* + ```text + 🦀🦀\A + ``` + "#]], + ); +} + +#[test] +fn byte_string_literal() { + check( + r#" +fn main() { + $0b"\xF0\x9F\xA6\x80\\"; +} +"#, + expect![[r#" + *b"\xF0\x9F\xA6\x80\\"* + ```text + [240, 159, 166, 128, 92] + ``` + "#]], + ); + check( + r#" +fn main() { + $0br"\xF0\x9F\xA6\x80\\"; +} +"#, + 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] + ``` + "#]], + ); +} + +#[test] +fn byte_literal() { + check( + r#" +fn main() { + $0b'\xF0'; +} +"#, + expect![[r#" + *b'\xF0'* + ```text + 0xF0 + ``` + "#]], + ); + check( + r#" +fn main() { + $0b'\\'; +} +"#, + expect![[r#" + *b'\\'* + ```text + 0x5C + ``` + "#]], + ); +} + +#[test] +fn char_literal() { + check( + r#" +fn main() { + $0'\x41'; +} +"#, + expect![[r#" + *'\x41'* + + "#]], + ); + check( + r#" +fn main() { + $0'\\'; +} +"#, + expect![[r#" + *'\\'* + + "#]], + ); + check( + r#" +fn main() { + $0'\u{1f980}'; +} +"#, + expect![[r#" + *'\u{1f980}'* + + "#]], + ); +} + +#[test] +fn float_literal() { + check( + r#" +fn main() { + $01.0; +} +"#, + expect![[r#" + *1.0* + ```text + 1 (bits: 0x3FF0000000000000) + ``` + "#]], + ); + check( + r#" +fn main() { + $01.0f32; +} +"#, + expect![[r#" + *1.0f32* + ```text + 1 (bits: 0x3F800000) + ``` + "#]], + ); + check( + r#" +fn main() { + $0134e12; +} +"#, + expect![[r#" + *134e12* + ```text + 134000000000000 (bits: 0x42DE77D399980000) + ``` + "#]], + ); + check( + r#" +fn main() { + $01523527134274733643531312.0; +} +"#, + expect![[r#" + *1523527134274733643531312.0* + ```text + 1523527134274733600000000 (bits: 0x44F429E9249F629B) + ``` + "#]], + ); + check( + r#" +fn main() { + $00.1ea123; +} +"#, + expect![[r#" + *0.1ea123* + ```text + invalid float literal + ``` + "#]], + ); +} + +#[test] +fn int_literal() { + check( + r#" +fn main() { + $034325236457856836345234; +} +"#, + expect![[r#" + *34325236457856836345234* + ```text + 34325236457856836345234 (0x744C659178614489D92|0x111010001001100011001011001000101111000011000010100010010001001110110010010) + ``` + "#]], + ); + check( + r#" +fn main() { + $0134_123424_21; +} +"#, + expect![[r#" + *134_123424_21* + ```text + 13412342421 (0x31F701A95|0x1100011111011100000001101010010101) + ``` + "#]], + ); + check( + r#" +fn main() { + $00x12423423; +} +"#, + expect![[r#" + *0x12423423* + ```text + 306328611 (0x12423423|0x10010010000100011010000100011) + ``` + "#]], + ); + check( + r#" +fn main() { + $00b1111_1111; +} +"#, + expect![[r#" + *0b1111_1111* + ```text + 255 (0xFF|0x11111111) + ``` + "#]], + ); + check( + r#" +fn main() { + $00o12345; +} +"#, + expect![[r#" + *0o12345* + ```text + 5349 (0x14E5|0x1010011100101) + ``` + "#]], + ); + check( + r#" +fn main() { + $00xFFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_F; +} +"#, + expect![[r#" + *0xFFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_F* + ```text + number too large to fit in target type + ``` + "#]], + ); +} diff --git a/crates/ide/src/markup.rs b/crates/ide/src/markup.rs index 411eb695fb..4a4e29fa33 100644 --- a/crates/ide/src/markup.rs +++ b/crates/ide/src/markup.rs @@ -35,4 +35,7 @@ impl Markup { pub fn fenced_block(contents: impl fmt::Display) -> Markup { format!("```rust\n{contents}\n```").into() } + pub fn fenced_block_text(contents: impl fmt::Display) -> Markup { + format!("```text\n{contents}\n```").into() + } } diff --git a/crates/syntax/src/ast/token_ext.rs b/crates/syntax/src/ast/token_ext.rs index ede392fc62..44e49d6d44 100644 --- a/crates/syntax/src/ast/token_ext.rs +++ b/crates/syntax/src/ast/token_ext.rs @@ -1,6 +1,9 @@ //! There are many AstNodes, but only a few tokens, so we hand-write them here. -use std::borrow::Cow; +use std::{ + borrow::Cow, + num::{ParseFloatError, ParseIntError}, +}; use rustc_lexer::unescape::{ unescape_byte, unescape_c_string, unescape_char, unescape_literal, CStrUnit, Mode, @@ -391,10 +394,9 @@ impl ast::IntNumber { (prefix, text, suffix) } - pub fn value(&self) -> Option { + pub fn value(&self) -> Result { let (_, text, _) = self.split_into_parts(); - let value = u128::from_str_radix(&text.replace('_', ""), self.radix() as u32).ok()?; - Some(value) + u128::from_str_radix(&text.replace('_', ""), self.radix() as u32) } pub fn suffix(&self) -> Option<&str> { @@ -445,9 +447,14 @@ impl ast::FloatNumber { } } - pub fn value(&self) -> Option { + pub fn value(&self) -> Result { let (text, _) = self.split_into_parts(); - text.replace('_', "").parse::().ok() + text.replace('_', "").parse::() + } + + pub fn value_f32(&self) -> Result { + let (text, _) = self.split_into_parts(); + text.replace('_', "").parse::() } } @@ -484,12 +491,15 @@ mod tests { } fn check_float_value(lit: &str, expected: impl Into> + Copy) { - assert_eq!(FloatNumber { syntax: make::tokens::literal(lit) }.value(), expected.into()); + assert_eq!( + FloatNumber { syntax: make::tokens::literal(lit) }.value().ok(), + expected.into() + ); assert_eq!(IntNumber { syntax: make::tokens::literal(lit) }.float_value(), expected.into()); } fn check_int_value(lit: &str, expected: impl Into>) { - assert_eq!(IntNumber { syntax: make::tokens::literal(lit) }.value(), expected.into()); + assert_eq!(IntNumber { syntax: make::tokens::literal(lit) }.value().ok(), expected.into()); } #[test]