about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--crates/hir/src/lib.rs74
-rw-r--r--crates/ide-assists/src/handlers/convert_named_struct_to_tuple_struct.rs3
-rw-r--r--crates/ide-assists/src/handlers/convert_tuple_struct_to_named_struct.rs3
-rw-r--r--crates/ide-assists/src/tests.rs3
-rw-r--r--crates/ide/src/hover.rs17
-rw-r--r--crates/ide/src/hover/render.rs226
-rw-r--r--crates/ide/src/hover/tests.rs79
-rw-r--r--crates/ide/src/lib.rs5
-rw-r--r--crates/ide/src/static_index.rs2
-rw-r--r--crates/rust-analyzer/src/config.rs57
-rw-r--r--docs/user/generated_config.adoc20
-rw-r--r--editors/code/package.json74
12 files changed, 400 insertions, 163 deletions
diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs
index 5c0320f7334..eee45b74d94 100644
--- a/crates/hir/src/lib.rs
+++ b/crates/hir/src/lib.rs
@@ -45,7 +45,7 @@ use hir_def::{
     hir::{BindingAnnotation, BindingId, ExprOrPatId, LabelId, Pat},
     item_tree::ItemTreeNode,
     lang_item::LangItemTarget,
-    layout::{self, ReprOptions},
+    layout::{self, ReprOptions, TargetDataLayout},
     macro_id_to_def_id,
     nameres::{self, diagnostics::DefDiagnostic, ModuleOrigin},
     per_ns::PerNs,
@@ -62,7 +62,7 @@ use hir_ty::{
     consteval::{try_const_usize, unknown_const_as_generic, ConstEvalError, ConstExt},
     diagnostics::BodyValidationDiagnostic,
     display::HexifiedConst,
-    layout::{Layout as TyLayout, LayoutError, RustcEnumVariantIdx, TagEncoding},
+    layout::{Layout as TyLayout, RustcEnumVariantIdx, TagEncoding},
     method_resolution::{self, TyFingerprint},
     mir::{self, interpret_mir},
     primitive::UintTy,
@@ -133,6 +133,7 @@ pub use {
     },
     hir_ty::{
         display::{ClosureStyle, HirDisplay, HirDisplayError, HirWrite},
+        layout::LayoutError,
         mir::MirEvalError,
         PointerCast, Safety,
     },
@@ -962,7 +963,8 @@ impl Field {
     }
 
     pub fn layout(&self, db: &dyn HirDatabase) -> Result<Layout, LayoutError> {
-        db.layout_of_ty(self.ty(db).ty.clone(), self.parent.module(db).krate().into()).map(Layout)
+        db.layout_of_ty(self.ty(db).ty.clone(), self.parent.module(db).krate().into())
+            .map(|layout| Layout(layout, db.target_data_layout(self.krate(db).into()).unwrap()))
     }
 
     pub fn parent_def(&self, _db: &dyn HirDatabase) -> VariantDef {
@@ -1135,23 +1137,8 @@ impl Enum {
         self.variants(db).iter().any(|v| !matches!(v.kind(db), StructKind::Unit))
     }
 
-    pub fn layout(self, db: &dyn HirDatabase) -> Result<(Layout, usize), LayoutError> {
-        let layout = Adt::from(self).layout(db)?;
-        let tag_size =
-            if let layout::Variants::Multiple { tag, tag_encoding, .. } = &layout.0.variants {
-                match tag_encoding {
-                    TagEncoding::Direct => {
-                        let target_data_layout = db
-                            .target_data_layout(self.module(db).krate().id)
-                            .ok_or(LayoutError::TargetLayoutNotAvailable)?;
-                        tag.size(&*target_data_layout).bytes_usize()
-                    }
-                    TagEncoding::Niche { .. } => 0,
-                }
-            } else {
-                0
-            };
-        Ok((layout, tag_size))
+    pub fn layout(self, db: &dyn HirDatabase) -> Result<Layout, LayoutError> {
+        Adt::from(self).layout(db)
     }
 }
 
@@ -1214,19 +1201,16 @@ impl Variant {
         db.const_eval_discriminant(self.into())
     }
 
-    /// Return layout of the variant and tag size of the parent enum.
-    pub fn layout(&self, db: &dyn HirDatabase) -> Result<(Layout, usize), LayoutError> {
+    pub fn layout(&self, db: &dyn HirDatabase) -> Result<Layout, LayoutError> {
         let parent_enum = self.parent_enum(db);
-        let (parent_layout, tag_size) = parent_enum.layout(db)?;
-        Ok((
-            match &parent_layout.0.variants {
-                layout::Variants::Multiple { variants, .. } => {
-                    Layout(Arc::new(variants[RustcEnumVariantIdx(self.id)].clone()))
-                }
-                _ => parent_layout,
-            },
-            tag_size,
-        ))
+        let parent_layout = parent_enum.layout(db)?;
+        Ok(match &parent_layout.0.variants {
+            layout::Variants::Multiple { variants, .. } => Layout(
+                Arc::new(variants[RustcEnumVariantIdx(self.id)].clone()),
+                db.target_data_layout(parent_enum.krate(db).into()).unwrap(),
+            ),
+            _ => parent_layout,
+        })
     }
 }
 
@@ -1259,7 +1243,9 @@ impl Adt {
         if db.generic_params(self.into()).iter().count() != 0 {
             return Err(LayoutError::HasPlaceholder);
         }
-        db.layout_of_adt(self.into(), Substitution::empty(Interner), self.krate(db).id).map(Layout)
+        let krate = self.krate(db).id;
+        db.layout_of_adt(self.into(), Substitution::empty(Interner), krate)
+            .map(|layout| Layout(layout, db.target_data_layout(krate).unwrap()))
     }
 
     /// Turns this ADT into a type. Any type parameters of the ADT will be
@@ -4244,7 +4230,8 @@ impl Type {
     }
 
     pub fn layout(&self, db: &dyn HirDatabase) -> Result<Layout, LayoutError> {
-        db.layout_of_ty(self.ty.clone(), self.env.krate).map(Layout)
+        db.layout_of_ty(self.ty.clone(), self.env.krate)
+            .map(|layout| Layout(layout, db.target_data_layout(self.env.krate).unwrap()))
     }
 }
 
@@ -4356,7 +4343,7 @@ fn closure_source(db: &dyn HirDatabase, closure: ClosureId) -> Option<ast::Closu
 }
 
 #[derive(Clone, Debug, Eq, PartialEq)]
-pub struct Layout(Arc<TyLayout>);
+pub struct Layout(Arc<TyLayout>, Arc<TargetDataLayout>);
 
 impl Layout {
     pub fn size(&self) -> u64 {
@@ -4367,8 +4354,8 @@ impl Layout {
         self.0.align.abi.bytes()
     }
 
-    pub fn niches(&self, db: &dyn HirDatabase, krate: Crate) -> Option<u128> {
-        Some(self.0.largest_niche?.available(&*db.target_data_layout(krate.id)?))
+    pub fn niches(&self) -> Option<u128> {
+        Some(self.0.largest_niche?.available(&*self.1))
     }
 
     pub fn field_offset(&self, idx: usize) -> Option<u64> {
@@ -4382,6 +4369,19 @@ impl Layout {
             layout::FieldsShape::Arbitrary { ref offsets, .. } => Some(offsets.get(idx)?.bytes()),
         }
     }
+
+    pub fn enum_tag_size(&self) -> Option<usize> {
+        let tag_size =
+            if let layout::Variants::Multiple { tag, tag_encoding, .. } = &self.0.variants {
+                match tag_encoding {
+                    TagEncoding::Direct => tag.size(&*self.1).bytes_usize(),
+                    TagEncoding::Niche { .. } => 0,
+                }
+            } else {
+                return None;
+            };
+        Some(tag_size)
+    }
 }
 
 #[derive(Copy, Clone, Debug, Eq, PartialEq)]
diff --git a/crates/ide-assists/src/handlers/convert_named_struct_to_tuple_struct.rs b/crates/ide-assists/src/handlers/convert_named_struct_to_tuple_struct.rs
index 00a4e0530d2..fe1cb6fce36 100644
--- a/crates/ide-assists/src/handlers/convert_named_struct_to_tuple_struct.rs
+++ b/crates/ide-assists/src/handlers/convert_named_struct_to_tuple_struct.rs
@@ -55,7 +55,8 @@ pub(crate) fn convert_named_struct_to_tuple_struct(
     // XXX: We don't currently provide this assist for struct definitions inside macros, but if we
     // are to lift this limitation, don't forget to make `edit_struct_def()` consider macro files
     // too.
-    let strukt = ctx.find_node_at_offset::<Either<ast::Struct, ast::Variant>>()?;
+    let name = ctx.find_node_at_offset::<ast::Name>()?;
+    let strukt = name.syntax().parent().and_then(<Either<ast::Struct, ast::Variant>>::cast)?;
     let field_list = strukt.as_ref().either(|s| s.field_list(), |v| v.field_list())?;
     let record_fields = match field_list {
         ast::FieldList::RecordFieldList(it) => it,
diff --git a/crates/ide-assists/src/handlers/convert_tuple_struct_to_named_struct.rs b/crates/ide-assists/src/handlers/convert_tuple_struct_to_named_struct.rs
index 772e032fb29..017853a4a20 100644
--- a/crates/ide-assists/src/handlers/convert_tuple_struct_to_named_struct.rs
+++ b/crates/ide-assists/src/handlers/convert_tuple_struct_to_named_struct.rs
@@ -50,7 +50,8 @@ pub(crate) fn convert_tuple_struct_to_named_struct(
     acc: &mut Assists,
     ctx: &AssistContext<'_>,
 ) -> Option<()> {
-    let strukt = ctx.find_node_at_offset::<Either<ast::Struct, ast::Variant>>()?;
+    let name = ctx.find_node_at_offset::<ast::Name>()?;
+    let strukt = name.syntax().parent().and_then(<Either<ast::Struct, ast::Variant>>::cast)?;
     let field_list = strukt.as_ref().either(|s| s.field_list(), |v| v.field_list())?;
     let tuple_fields = match field_list {
         ast::FieldList::TupleFieldList(it) => it,
diff --git a/crates/ide-assists/src/tests.rs b/crates/ide-assists/src/tests.rs
index 6e161ca4396..344f2bfcce1 100644
--- a/crates/ide-assists/src/tests.rs
+++ b/crates/ide-assists/src/tests.rs
@@ -273,8 +273,9 @@ fn assist_order_field_struct() {
     assert_eq!(assists.next().expect("expected assist").label, "Generate a getter method");
     assert_eq!(assists.next().expect("expected assist").label, "Generate a mut getter method");
     assert_eq!(assists.next().expect("expected assist").label, "Generate a setter method");
-    assert_eq!(assists.next().expect("expected assist").label, "Convert to tuple struct");
     assert_eq!(assists.next().expect("expected assist").label, "Add `#[derive]`");
+    assert_eq!(assists.next().expect("expected assist").label, "Generate `new`");
+    assert_eq!(assists.next().map(|it| it.label.to_string()), None);
 }
 
 #[test]
diff --git a/crates/ide/src/hover.rs b/crates/ide/src/hover.rs
index 1a84a963f55..5ef6ac94807 100644
--- a/crates/ide/src/hover.rs
+++ b/crates/ide/src/hover.rs
@@ -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,
diff --git a/crates/ide/src/hover/render.rs b/crates/ide/src/hover/render.rs
index 96a97ab44c0..1362146413e 100644
--- a/crates/ide/src/hover/render.rs
+++ b/crates/ide/src/hover/render.rs
@@ -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),);
 
-    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,
+    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,
-    )
-    .into();
+    );
+
+    let mut res = HoverResult::default();
+    res.actions.push(HoverAction::goto_type_from_targets(sema.db, targets));
+    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,
diff --git a/crates/ide/src/hover/tests.rs b/crates/ide/src/hover/tests.rs
index f8b5b654546..f2ee79a23e6 100644
--- a/crates/ide/src/hover/tests.rs
+++ b/crates/ide/src/hover/tests.rs
@@ -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
             ```
         "#]],
     );
diff --git a/crates/ide/src/lib.rs b/crates/ide/src/lib.rs
index c02dbc60a33..87e769e4230 100644
--- a/crates/ide/src/lib.rs
+++ b/crates/ide/src/lib.rs
@@ -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,
diff --git a/crates/ide/src/static_index.rs b/crates/ide/src/static_index.rs
index e7b223caab4..3e3d9f8f85c 100644
--- a/crates/ide/src/static_index.rs
+++ b/crates/ide/src/static_index.rs
@@ -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,
diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs
index 649ac90fc6e..6355c620f78 100644
--- a/crates/rust-analyzer/src/config.rs
+++ b/crates/rust-analyzer/src/config.rs
@@ -14,7 +14,7 @@ use flycheck::FlycheckConfig;
 use ide::{
     AssistConfig, CallableSnippets, CompletionConfig, DiagnosticsConfig, ExprFillDefaultMode,
     HighlightConfig, HighlightRelatedConfig, HoverConfig, HoverDocFormat, InlayHintsConfig,
-    JoinLinesConfig, Snippet, SnippetScope,
+    JoinLinesConfig, MemoryLayoutHoverConfig, MemoryLayoutHoverRenderKind, Snippet, SnippetScope,
 };
 use ide_db::{
     imports::insert_use::{ImportGranularity, InsertUseConfig, PrefixKind},
@@ -317,8 +317,16 @@ config_data! {
         hover_documentation_keywords_enable: bool  = "true",
         /// Use markdown syntax for links on hover.
         hover_links_enable: bool = "true",
+        /// How to render the align information in a memory layout hover.
+        hover_memoryLayout_alignment: Option<MemoryLayoutHoverRenderKindDef> = "\"hexadecimal\"",
         /// Whether to show memory layout data on hover.
         hover_memoryLayout_enable: bool = "true",
+        /// How to render the niche information in a memory layout hover.
+        hover_memoryLayout_niches: Option<bool> = "false",
+        /// How to render the offset information in a memory layout hover.
+        hover_memoryLayout_offset: Option<MemoryLayoutHoverRenderKindDef> = "\"hexadecimal\"",
+        /// How to render the size information in a memory layout hover.
+        hover_memoryLayout_size: Option<MemoryLayoutHoverRenderKindDef> = "\"both\"",
 
         /// Whether to enforce the import granularity setting for all files. If set to false rust-analyzer will try to keep import styles consistent per file.
         imports_granularity_enforce: bool              = "false",
@@ -1514,9 +1522,19 @@ impl Config {
     }
 
     pub fn hover(&self) -> HoverConfig {
+        let mem_kind = |kind| match kind {
+            MemoryLayoutHoverRenderKindDef::Both => MemoryLayoutHoverRenderKind::Both,
+            MemoryLayoutHoverRenderKindDef::Decimal => MemoryLayoutHoverRenderKind::Decimal,
+            MemoryLayoutHoverRenderKindDef::Hexadecimal => MemoryLayoutHoverRenderKind::Hexadecimal,
+        };
         HoverConfig {
             links_in_hover: self.data.hover_links_enable,
-            memory_layout: self.data.hover_memoryLayout_enable,
+            memory_layout: self.data.hover_memoryLayout_enable.then_some(MemoryLayoutHoverConfig {
+                size: self.data.hover_memoryLayout_size.map(mem_kind),
+                offset: self.data.hover_memoryLayout_offset.map(mem_kind),
+                alignment: self.data.hover_memoryLayout_alignment.map(mem_kind),
+                niches: self.data.hover_memoryLayout_niches.unwrap_or_default(),
+            }),
             documentation: self.data.hover_documentation_enable,
             format: {
                 let is_markdown = try_or_def!(self
@@ -1726,6 +1744,9 @@ mod de_unit_v {
     named_unit_variant!(reborrow);
     named_unit_variant!(fieldless);
     named_unit_variant!(with_block);
+    named_unit_variant!(decimal);
+    named_unit_variant!(hexadecimal);
+    named_unit_variant!(both);
 }
 
 #[derive(Deserialize, Debug, Clone, Copy)]
@@ -1956,6 +1977,18 @@ enum WorkspaceSymbolSearchKindDef {
     AllSymbols,
 }
 
+#[derive(Deserialize, Debug, Copy, Clone)]
+#[serde(rename_all = "snake_case")]
+#[serde(untagged)]
+pub enum MemoryLayoutHoverRenderKindDef {
+    #[serde(deserialize_with = "de_unit_v::decimal")]
+    Decimal,
+    #[serde(deserialize_with = "de_unit_v::hexadecimal")]
+    Hexadecimal,
+    #[serde(deserialize_with = "de_unit_v::both")]
+    Both,
+}
+
 macro_rules! _config_data {
     (struct $name:ident {
         $(
@@ -2038,7 +2071,9 @@ fn get_field<T: DeserializeOwned>(
                 None
             }
         })
-        .unwrap_or_else(|| serde_json::from_str(default).unwrap())
+        .unwrap_or_else(|| {
+            serde_json::from_str(default).unwrap_or_else(|e| panic!("{e} on: `{default}`"))
+        })
 }
 
 fn schema(fields: &[(&'static str, &'static str, &[&str], &str)]) -> serde_json::Value {
@@ -2366,6 +2401,22 @@ fn field_props(field: &str, ty: &str, doc: &[&str], default: &str) -> serde_json
                 "`hide`: Shows `...` for every closure type",
             ],
         },
+        "Option<MemoryLayoutHoverRenderKindDef>" => set! {
+            "anyOf": [
+                {
+                    "type": "null"
+                },
+                {
+                    "type": "string",
+                    "enum": ["both", "decimal", "hexadecimal", ],
+                    "enumDescriptions": [
+                        "Render as 12 (0xC)",
+                        "Render as 12",
+                        "Render as 0xC"
+                    ],
+                },
+            ],
+        },
         _ => panic!("missing entry for {ty}: {default}"),
     }
 
diff --git a/docs/user/generated_config.adoc b/docs/user/generated_config.adoc
index dc97366eef1..ea00c9540ff 100644
--- a/docs/user/generated_config.adoc
+++ b/docs/user/generated_config.adoc
@@ -428,11 +428,31 @@ Whether to show keyword hover popups. Only applies when
 --
 Use markdown syntax for links on hover.
 --
+[[rust-analyzer.hover.memoryLayout.alignment]]rust-analyzer.hover.memoryLayout.alignment (default: `"hexadecimal"`)::
++
+--
+How to render the align information in a memory layout hover.
+--
 [[rust-analyzer.hover.memoryLayout.enable]]rust-analyzer.hover.memoryLayout.enable (default: `true`)::
 +
 --
 Whether to show memory layout data on hover.
 --
+[[rust-analyzer.hover.memoryLayout.niches]]rust-analyzer.hover.memoryLayout.niches (default: `false`)::
++
+--
+How to render the niche information in a memory layout hover.
+--
+[[rust-analyzer.hover.memoryLayout.offset]]rust-analyzer.hover.memoryLayout.offset (default: `"hexadecimal"`)::
++
+--
+How to render the offset information in a memory layout hover.
+--
+[[rust-analyzer.hover.memoryLayout.size]]rust-analyzer.hover.memoryLayout.size (default: `"both"`)::
++
+--
+How to render the size information in a memory layout hover.
+--
 [[rust-analyzer.imports.granularity.enforce]]rust-analyzer.imports.granularity.enforce (default: `false`)::
 +
 --
diff --git a/editors/code/package.json b/editors/code/package.json
index aa63c40c0d2..ee1f832d323 100644
--- a/editors/code/package.json
+++ b/editors/code/package.json
@@ -966,11 +966,85 @@
                     "default": true,
                     "type": "boolean"
                 },
+                "rust-analyzer.hover.memoryLayout.alignment": {
+                    "markdownDescription": "How to render the align information in a memory layout hover.",
+                    "default": "hexadecimal",
+                    "anyOf": [
+                        {
+                            "type": "null"
+                        },
+                        {
+                            "type": "string",
+                            "enum": [
+                                "both",
+                                "decimal",
+                                "hexadecimal"
+                            ],
+                            "enumDescriptions": [
+                                "Render as 12 (0xC)",
+                                "Render as 12",
+                                "Render as 0xC"
+                            ]
+                        }
+                    ]
+                },
                 "rust-analyzer.hover.memoryLayout.enable": {
                     "markdownDescription": "Whether to show memory layout data on hover.",
                     "default": true,
                     "type": "boolean"
                 },
+                "rust-analyzer.hover.memoryLayout.niches": {
+                    "markdownDescription": "How to render the niche information in a memory layout hover.",
+                    "default": false,
+                    "type": [
+                        "null",
+                        "boolean"
+                    ]
+                },
+                "rust-analyzer.hover.memoryLayout.offset": {
+                    "markdownDescription": "How to render the offset information in a memory layout hover.",
+                    "default": "hexadecimal",
+                    "anyOf": [
+                        {
+                            "type": "null"
+                        },
+                        {
+                            "type": "string",
+                            "enum": [
+                                "both",
+                                "decimal",
+                                "hexadecimal"
+                            ],
+                            "enumDescriptions": [
+                                "Render as 12 (0xC)",
+                                "Render as 12",
+                                "Render as 0xC"
+                            ]
+                        }
+                    ]
+                },
+                "rust-analyzer.hover.memoryLayout.size": {
+                    "markdownDescription": "How to render the size information in a memory layout hover.",
+                    "default": "both",
+                    "anyOf": [
+                        {
+                            "type": "null"
+                        },
+                        {
+                            "type": "string",
+                            "enum": [
+                                "both",
+                                "decimal",
+                                "hexadecimal"
+                            ],
+                            "enumDescriptions": [
+                                "Render as 12 (0xC)",
+                                "Render as 12",
+                                "Render as 0xC"
+                            ]
+                        }
+                    ]
+                },
                 "rust-analyzer.imports.granularity.enforce": {
                     "markdownDescription": "Whether to enforce the import granularity setting for all files. If set to false rust-analyzer will try to keep import styles consistent per file.",
                     "default": false,