Add render configs for memory layout hovers

This commit is contained in:
Lukas Wirth 2023-05-30 16:20:01 +02:00
parent 76d86502f7
commit 3c862507b9
12 changed files with 400 additions and 163 deletions

View file

@ -27,12 +27,27 @@ use crate::{
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct HoverConfig {
pub links_in_hover: bool,
pub memory_layout: bool,
pub memory_layout: Option<MemoryLayoutHoverConfig>,
pub documentation: bool,
pub keywords: bool,
pub format: HoverDocFormat,
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct MemoryLayoutHoverConfig {
pub size: Option<MemoryLayoutHoverRenderKind>,
pub offset: Option<MemoryLayoutHoverRenderKind>,
pub alignment: Option<MemoryLayoutHoverRenderKind>,
pub niches: bool,
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum MemoryLayoutHoverRenderKind {
Decimal,
Hexadecimal,
Both,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum HoverDocFormat {
Markdown,

View file

@ -3,8 +3,8 @@ use std::fmt::Display;
use either::Either;
use hir::{
Adt, AsAssocItem, AttributeTemplate, CaptureKind, HasAttrs, HasCrate, HasSource, HirDisplay,
Layout, Semantics, TypeInfo,
Adt, AsAssocItem, AttributeTemplate, CaptureKind, HasAttrs, HasSource, HirDisplay, Layout,
LayoutError, Semantics, TypeInfo,
};
use ide_db::{
base_db::SourceDatabase,
@ -27,7 +27,8 @@ use syntax::{
use crate::{
doc_links::{remove_links, rewrite_links},
hover::walk_and_push_ty,
HoverAction, HoverConfig, HoverResult, Markup,
HoverAction, HoverConfig, HoverResult, Markup, MemoryLayoutHoverConfig,
MemoryLayoutHoverRenderKind,
};
pub(super) fn type_info_of(
@ -393,32 +394,27 @@ pub(super) fn definition(
let mod_path = definition_mod_path(db, &def);
let (label, docs) = match def {
Definition::Macro(it) => label_and_docs(db, it),
Definition::Field(it) => label_and_layout_info_and_docs(db, it, config, |&it| {
let var_def = it.parent_def(db);
let id = it.index();
let layout = it.layout(db).ok()?;
let offset = match var_def {
hir::VariantDef::Struct(s) => Adt::from(s)
.layout(db)
.ok()
.and_then(|layout| Some(format!(", offset = {:#X}", layout.field_offset(id)?))),
_ => None,
};
let niches = niches(db, it, &layout).unwrap_or_default();
Some(format!(
"size = {:#X}, align = {:#X}{}{niches}",
layout.size(),
layout.align(),
offset.as_deref().unwrap_or_default()
))
}),
Definition::Field(it) => label_and_layout_info_and_docs(
db,
it,
config,
|&it| it.layout(db),
|_| {
let var_def = it.parent_def(db);
let id = it.index();
match var_def {
hir::VariantDef::Struct(s) => {
Adt::from(s).layout(db).ok().and_then(|layout| layout.field_offset(id))
}
_ => None,
}
},
),
Definition::Module(it) => label_and_docs(db, it),
Definition::Function(it) => label_and_docs(db, it),
Definition::Adt(it) => label_and_layout_info_and_docs(db, it, config, |&it| {
let layout = it.layout(db).ok()?;
let niches = niches(db, it, &layout).unwrap_or_default();
Some(format!("size = {:#X}, align = {:#X}{niches}", layout.size(), layout.align()))
}),
Definition::Adt(it) => {
label_and_layout_info_and_docs(db, it, config, |&it| it.layout(db), |_| None)
}
Definition::Variant(it) => label_value_and_layout_info_and_docs(
db,
it,
@ -435,16 +431,8 @@ pub(super) fn definition(
None
}
},
|&it| {
let (layout, tag_size) = it.layout(db).ok()?;
let size = layout.size() as usize - tag_size;
if size == 0 {
// There is no value in showing layout info for fieldless variants
return None;
}
let niches = niches(db, it, &layout).unwrap_or_default();
Some(format!("size = {:#X}{niches}", layout.size()))
},
|it| it.layout(db),
|layout| layout.enum_tag_size(),
),
Definition::Const(it) => label_value_and_docs(db, it, |it| {
let body = it.render_eval(db);
@ -470,11 +458,9 @@ pub(super) fn definition(
}),
Definition::Trait(it) => label_and_docs(db, it),
Definition::TraitAlias(it) => label_and_docs(db, it),
Definition::TypeAlias(it) => label_and_layout_info_and_docs(db, it, config, |&it| {
let layout = it.ty(db).layout(db).ok()?;
let niches = niches(db, it, &layout).unwrap_or_default();
Some(format!("size = {:#X}, align = {:#X}{niches}", layout.size(), layout.align(),))
}),
Definition::TypeAlias(it) => {
label_and_layout_info_and_docs(db, it, config, |&it| it.ty(db).layout(db), |_| None)
}
Definition::BuiltinType(it) => {
return famous_defs
.and_then(|fd| builtin(fd, it))
@ -509,10 +495,6 @@ pub(super) fn definition(
markup(docs, label, mod_path)
}
fn niches(db: &RootDatabase, it: impl HasCrate, layout: &Layout) -> Option<String> {
Some(format!(", niches = {}", layout.niches(db, it.krate(db).into())?))
}
fn type_info(
sema: &Semantics<'_, RootDatabase>,
config: &HoverConfig,
@ -557,14 +539,6 @@ fn closure_ty(
TypeInfo { original, adjusted }: &TypeInfo,
) -> Option<HoverResult> {
let c = original.as_closure()?;
let layout = if config.memory_layout {
original
.layout(sema.db)
.map(|x| format!(" // size = {}, align = {}", x.size(), x.align()))
.unwrap_or_default()
} else {
String::default()
};
let mut captures_rendered = c.captured_items(sema.db)
.into_iter()
.map(|it| {
@ -600,17 +574,23 @@ fn closure_ty(
} else {
String::new()
};
let mut markup = format!("```rust\n{}", c.display_with_id(sema.db),);
if let Some(layout) =
render_memory_layout(config.memory_layout, || original.layout(sema.db), |_| None, |_| None)
{
format_to!(markup, "{layout}");
}
format_to!(
markup,
"\n{}\n```{adjusted}\n\n## Captures\n{}",
c.display_with_impl(sema.db),
captures_rendered,
);
let mut res = HoverResult::default();
res.actions.push(HoverAction::goto_type_from_targets(sema.db, targets));
res.markup = format!(
"```rust\n{}{}\n{}\n```{adjusted}\n\n## Captures\n{}",
c.display_with_id(sema.db),
layout,
c.display_with_impl(sema.db),
captures_rendered,
)
.into();
res.markup = markup.into();
Some(res)
}
@ -644,48 +624,59 @@ where
(label, docs)
}
fn label_and_layout_info_and_docs<D, E, V>(
fn label_and_layout_info_and_docs<D, E, E2>(
db: &RootDatabase,
def: D,
config: &HoverConfig,
layout_extractor: E,
layout_offset_extractor: E2,
) -> (String, Option<hir::Documentation>)
where
D: HasAttrs + HirDisplay,
E: Fn(&D) -> Option<V>,
V: Display,
E: Fn(&D) -> Result<Layout, LayoutError>,
E2: Fn(&Layout) -> Option<u64>,
{
let label = match config.memory_layout.then(|| layout_extractor(&def)).flatten() {
Some(layout) => format!("{} // {layout}", def.display(db)),
_ => def.display(db).to_string(),
};
let mut label = def.display(db).to_string();
if let Some(layout) = render_memory_layout(
config.memory_layout,
|| layout_extractor(&def),
layout_offset_extractor,
|_| None,
) {
format_to!(label, "{layout}");
}
let docs = def.attrs(db).docs();
(label, docs)
}
fn label_value_and_layout_info_and_docs<D, E, E2, V, L>(
fn label_value_and_layout_info_and_docs<D, E, E2, E3, V>(
db: &RootDatabase,
def: D,
config: &HoverConfig,
value_extractor: E,
layout_extractor: E2,
layout_tag_extractor: E3,
) -> (String, Option<hir::Documentation>)
where
D: HasAttrs + HirDisplay,
E: Fn(&D) -> Option<V>,
E2: Fn(&D) -> Option<L>,
E2: Fn(&D) -> Result<Layout, LayoutError>,
E3: Fn(&Layout) -> Option<usize>,
V: Display,
L: Display,
{
let value = value_extractor(&def);
let label = match value {
let mut label = match value {
Some(value) => format!("{} = {value}", def.display(db)),
None => def.display(db).to_string(),
};
let label = match config.memory_layout.then(|| layout_extractor(&def)).flatten() {
Some(layout) => format!("{} // {layout}", label),
_ => label,
};
if let Some(layout) = render_memory_layout(
config.memory_layout,
|| layout_extractor(&def),
|_| None,
layout_tag_extractor,
) {
format_to!(label, "{layout}");
}
let docs = def.attrs(db).docs();
(label, docs)
}
@ -769,14 +760,87 @@ fn local(db: &RootDatabase, it: hir::Local, config: &HoverConfig) -> Option<Mark
}
None => format!("{is_mut}self: {ty}"),
};
if config.memory_layout {
if let Ok(layout) = it.ty(db).layout(db) {
format_to!(desc, " // size = {}, align = {}", layout.size(), layout.align());
}
if let Some(layout) =
render_memory_layout(config.memory_layout, || it.ty(db).layout(db), |_| None, |_| None)
{
format_to!(desc, "{layout}");
}
markup(None, desc, None)
}
fn render_memory_layout(
config: Option<MemoryLayoutHoverConfig>,
layout: impl FnOnce() -> Result<Layout, LayoutError>,
offset: impl FnOnce(&Layout) -> Option<u64>,
tag: impl FnOnce(&Layout) -> Option<usize>,
) -> Option<String> {
// field
let config = config?;
let layout = layout().ok()?;
let mut label = String::from(" // ");
if let Some(render) = config.size {
let size = match tag(&layout) {
Some(tag) => layout.size() as usize - tag,
None => layout.size() as usize,
};
format_to!(label, "size = ");
match render {
MemoryLayoutHoverRenderKind::Decimal => format_to!(label, "{size}"),
MemoryLayoutHoverRenderKind::Hexadecimal => format_to!(label, "{size:#X}"),
MemoryLayoutHoverRenderKind::Both if size >= 10 => {
format_to!(label, "{size} ({size:#X})")
}
MemoryLayoutHoverRenderKind::Both => format_to!(label, "{size}"),
}
format_to!(label, ", ");
}
if let Some(render) = config.alignment {
let align = layout.align();
format_to!(label, "align = ");
match render {
MemoryLayoutHoverRenderKind::Decimal => format_to!(label, "{align}",),
MemoryLayoutHoverRenderKind::Hexadecimal => format_to!(label, "{align:#X}",),
MemoryLayoutHoverRenderKind::Both if align >= 10 => {
format_to!(label, "{align} ({align:#X})")
}
MemoryLayoutHoverRenderKind::Both => {
format_to!(label, "{align}")
}
}
format_to!(label, ", ");
}
if let Some(render) = config.offset {
if let Some(offset) = offset(&layout) {
format_to!(label, "offset = ");
match render {
MemoryLayoutHoverRenderKind::Decimal => format_to!(label, "{offset}"),
MemoryLayoutHoverRenderKind::Hexadecimal => format_to!(label, "{offset:#X}"),
MemoryLayoutHoverRenderKind::Both if offset >= 10 => {
format_to!(label, "{offset} ({offset:#X})")
}
MemoryLayoutHoverRenderKind::Both => {
format_to!(label, "{offset}")
}
}
format_to!(label, ", ");
}
}
if config.niches {
if let Some(niches) = layout.niches() {
format_to!(label, "niches = {niches}, ");
}
}
label.pop(); // ' '
label.pop(); // ','
Some(label)
}
struct KeywordHint {
description: String,
keyword_mod: String,

View file

@ -2,11 +2,18 @@ use expect_test::{expect, Expect};
use ide_db::base_db::{FileLoader, FileRange};
use syntax::TextRange;
use crate::{fixture, HoverConfig, HoverDocFormat};
use crate::{
fixture, HoverConfig, HoverDocFormat, MemoryLayoutHoverConfig, MemoryLayoutHoverRenderKind,
};
const HOVER_BASE_CONFIG: HoverConfig = HoverConfig {
links_in_hover: false,
memory_layout: true,
memory_layout: Some(MemoryLayoutHoverConfig {
size: Some(MemoryLayoutHoverRenderKind::Both),
offset: Some(MemoryLayoutHoverRenderKind::Both),
alignment: Some(MemoryLayoutHoverRenderKind::Both),
niches: true,
}),
documentation: true,
format: HoverDocFormat::Markdown,
keywords: true,
@ -62,7 +69,7 @@ fn check_hover_no_memory_layout(ra_fixture: &str, expect: Expect) {
let (analysis, position) = fixture::position(ra_fixture);
let hover = analysis
.hover(
&HoverConfig { memory_layout: false, ..HOVER_BASE_CONFIG },
&HoverConfig { memory_layout: None, ..HOVER_BASE_CONFIG },
FileRange { file_id: position.file_id, range: TextRange::empty(position.offset) },
)
.unwrap()
@ -237,7 +244,7 @@ fn main() {
expect![[r#"
*|*
```rust
{closure#0} // size = 8, align = 8
{closure#0} // size = 8, align = 8, niches = 1
impl Fn(i32) -> i32
```
@ -292,7 +299,7 @@ fn main() {
expect![[r#"
*|*
```rust
{closure#0} // size = 16, align = 8
{closure#0} // size = 16 (0x10), align = 8, niches = 1
impl FnOnce()
```
@ -320,7 +327,7 @@ fn main() {
expect![[r#"
*|*
```rust
{closure#0} // size = 8, align = 8
{closure#0} // size = 8, align = 8, niches = 1
impl FnMut()
```
@ -344,7 +351,7 @@ fn main() {
"#,
expect![[r#"
```rust
{closure#0} // size = 8, align = 8
{closure#0} // size = 8, align = 8, niches = 1
impl FnOnce() -> S2
```
Coerced to: &impl FnOnce() -> S2
@ -667,7 +674,7 @@ struct Foo { fiel$0d_a: u8, field_b: i32, field_c: i16 }
```
```rust
field_a: u8 // size = 0x1, align = 0x1, offset = 0x4
field_a: u8 // size = 1, align = 1, offset = 4
```
"#]],
);
@ -692,7 +699,7 @@ fn main() {
```
```rust
field_a: u32 // size = 0x4, align = 0x4, offset = 0x0
field_a: u32 // size = 4, align = 4, offset = 0
```
"#]],
);
@ -714,7 +721,7 @@ fn main() {
```
```rust
field_a: u32 // size = 0x4, align = 0x4, offset = 0x0
field_a: u32 // size = 4, align = 4, offset = 0
```
"#]],
);
@ -1528,7 +1535,7 @@ fn test_hover_function_pointer_show_identifiers() {
```
```rust
type foo = fn(a: i32, b: i32) -> i32 // size = 0x8, align = 0x8, niches = 1
type foo = fn(a: i32, b: i32) -> i32 // size = 8, align = 8, niches = 1
```
"#]],
);
@ -1546,7 +1553,7 @@ fn test_hover_function_pointer_no_identifier() {
```
```rust
type foo = fn(i32, i32) -> i32 // size = 0x8, align = 0x8, niches = 1
type foo = fn(i32, i32) -> i32 // size = 8, align = 8, niches = 1
```
"#]],
);
@ -1674,7 +1681,7 @@ fn foo() { let bar = Ba$0r; }
```
```rust
struct Bar // size = 0x0, align = 0x1
struct Bar // size = 0, align = 1
```
---
@ -1710,7 +1717,7 @@ fn foo() { let bar = Ba$0r; }
```
```rust
struct Bar // size = 0x0, align = 0x1
struct Bar // size = 0, align = 1
```
---
@ -1739,7 +1746,7 @@ fn foo() { let bar = Ba$0r; }
```
```rust
struct Bar // size = 0x0, align = 0x1
struct Bar // size = 0, align = 1
```
---
@ -1767,7 +1774,7 @@ pub struct B$0ar
```
```rust
pub struct Bar // size = 0x0, align = 0x1
pub struct Bar // size = 0, align = 1
```
---
@ -1794,7 +1801,7 @@ pub struct B$0ar
```
```rust
pub struct Bar // size = 0x0, align = 0x1
pub struct Bar // size = 0, align = 1
```
---
@ -1883,7 +1890,7 @@ fn test_hover_layout_of_variant() {
```
```rust
Variant1(u8, u16) // size = 0x4
Variant1(u8, u16) // size = 4, align = 2
```
"#]],
);
@ -1904,7 +1911,7 @@ fn test_hover_layout_of_enum() {
```
```rust
enum Foo // size = 0x10, align = 0x8, niches = 254
enum Foo // size = 16 (0x10), align = 8, niches = 254
```
"#]],
);
@ -3204,7 +3211,7 @@ fn main() {
*f*
```rust
f: &i32 // size = 8, align = 8
f: &i32 // size = 8, align = 8, niches = 1
```
---
@ -3213,7 +3220,7 @@ fn main() {
```
```rust
f: i32 // size = 0x4, align = 0x4, offset = 0x0
f: i32 // size = 4, align = 4, offset = 0
```
"#]],
);
@ -3353,7 +3360,7 @@ impl Foo {
*self*
```rust
self: &Foo // size = 8, align = 8
self: &Foo // size = 8, align = 8, niches = 1
```
"#]],
);
@ -3758,7 +3765,7 @@ type Fo$0o2 = Foo<2>;
```
```rust
type Foo2 = Foo<2> // size = 0x0, align = 0x1
type Foo2 = Foo<2> // size = 0, align = 1
```
"#]],
);
@ -3800,7 +3807,7 @@ enum E {
```
```rust
A = 8
A = 8 // size = 1, align = 1
```
---
@ -3825,7 +3832,7 @@ enum E {
```
```rust
A = 12 (0xC)
A = 12 (0xC) // size = 1, align = 1
```
---
@ -3851,7 +3858,7 @@ enum E {
```
```rust
B = 2
B = 2 // size = 1, align = 1
```
---
@ -3877,7 +3884,7 @@ enum E {
```
```rust
B = 5
B = 5 // size = 1, align = 1
```
---
@ -4411,7 +4418,7 @@ fn foo(e: E) {
```
```rust
A = 3
A = 3 // size = 0, align = 1
```
---
@ -4433,7 +4440,7 @@ fn main() {
*tile4*
```rust
let tile4: [u32; 8] // size = 32, align = 4
let tile4: [u32; 8] // size = 32 (0x20), align = 4
```
"#]],
);
@ -4669,7 +4676,7 @@ pub fn gimme() -> theitem::TheItem {
```
```rust
pub struct TheItem // size = 0x0, align = 0x1
pub struct TheItem // size = 0, align = 1
```
---
@ -4817,7 +4824,7 @@ mod string {
```
```rust
struct String // size = 0x0, align = 0x1
struct String // size = 0, align = 1
```
---
@ -5486,7 +5493,7 @@ foo_macro!(
```
```rust
pub struct Foo // size = 0x0, align = 0x1
pub struct Foo // size = 0, align = 1
```
---
@ -5511,7 +5518,7 @@ pub struct Foo(i32);
```
```rust
pub struct Foo // size = 0x4, align = 0x4
pub struct Foo // size = 4, align = 4
```
---
@ -5610,7 +5617,7 @@ enum Enum {
```
```rust
RecordV { field: u32 } // size = 0x4
RecordV { field: u32 } // size = 4, align = 4
```
"#]],
);
@ -5632,7 +5639,7 @@ enum Enum {
```
```rust
field: u32 // size = 0x4, align = 0x4
field: u32 // size = 4, align = 4
```
"#]],
);
@ -6134,7 +6141,7 @@ fn test() {
```
```rust
f: u32 // size = 0x4, align = 0x4, offset = 0x0
f: u32 // size = 4, align = 4, offset = 0
```
"#]],
);

View file

@ -84,7 +84,10 @@ pub use crate::{
file_structure::{StructureNode, StructureNodeKind},
folding_ranges::{Fold, FoldKind},
highlight_related::{HighlightRelatedConfig, HighlightedRange},
hover::{HoverAction, HoverConfig, HoverDocFormat, HoverGotoTypeData, HoverResult},
hover::{
HoverAction, HoverConfig, HoverDocFormat, HoverGotoTypeData, HoverResult,
MemoryLayoutHoverConfig, MemoryLayoutHoverRenderKind,
},
inlay_hints::{
AdjustmentHints, AdjustmentHintsMode, ClosureReturnTypeHints, DiscriminantHints, InlayHint,
InlayHintLabel, InlayHintLabelPart, InlayHintPosition, InlayHintsConfig, InlayKind,

View file

@ -138,7 +138,7 @@ impl StaticIndex<'_> {
});
let hover_config = HoverConfig {
links_in_hover: true,
memory_layout: true,
memory_layout: None,
documentation: true,
keywords: true,
format: crate::HoverDocFormat::Markdown,