From 4d5c66986ece98fb0015cbca5ee0de1f9870c800 Mon Sep 17 00:00:00 2001 From: Adenine Date: Fri, 7 Jul 2023 23:03:55 -0400 Subject: [PATCH] cleanup + docs + tests --- crates/ide-db/src/helpers.rs | 18 +- crates/ide/src/lib.rs | 5 +- crates/ide/src/static_index.rs | 17 +- crates/ide/src/view_memory_layout.rs | 281 +++++++++++++++++++++++---- docs/dev/lsp-extensions.md | 55 +++++- editors/code/src/commands.ts | 2 - editors/code/src/lsp_ext.ts | 2 - 7 files changed, 320 insertions(+), 60 deletions(-) diff --git a/crates/ide-db/src/helpers.rs b/crates/ide-db/src/helpers.rs index eba9d8afc4..1eb8f00020 100644 --- a/crates/ide-db/src/helpers.rs +++ b/crates/ide-db/src/helpers.rs @@ -9,7 +9,10 @@ use syntax::{ AstToken, SyntaxKind, SyntaxToken, TokenAtOffset, }; -use crate::{defs::Definition, generated, RootDatabase}; +use crate::{ + defs::{Definition, IdentClass}, + generated, RootDatabase, +}; pub fn item_name(db: &RootDatabase, item: ItemInNs) -> Option { match item { @@ -109,3 +112,16 @@ pub fn is_editable_crate(krate: Crate, db: &RootDatabase) -> bool { let source_root_id = db.file_source_root(root_file); !db.source_root(source_root_id).is_library } + +pub fn get_definition( + sema: &Semantics<'_, RootDatabase>, + token: SyntaxToken, +) -> Option { + for token in sema.descend_into_macros(token) { + let def = IdentClass::classify_token(sema, &token).map(IdentClass::definitions_no_ops); + if let Some(&[x]) = def.as_deref() { + return Some(x); + } + } + None +} diff --git a/crates/ide/src/lib.rs b/crates/ide/src/lib.rs index a9a8f6903b..0ad4c6c47e 100644 --- a/crates/ide/src/lib.rs +++ b/crates/ide/src/lib.rs @@ -726,7 +726,10 @@ impl Analysis { self.with_db(|db| move_item::move_item(db, range, direction)) } - pub fn get_recursive_memory_layout(&self, position: FilePosition) -> Cancellable> { + pub fn get_recursive_memory_layout( + &self, + position: FilePosition, + ) -> Cancellable> { self.with_db(|db| view_memory_layout(db, position)) } diff --git a/crates/ide/src/static_index.rs b/crates/ide/src/static_index.rs index 7101e8ed20..59e8300dcd 100644 --- a/crates/ide/src/static_index.rs +++ b/crates/ide/src/static_index.rs @@ -3,13 +3,14 @@ use std::collections::HashMap; -use hir::{db::HirDatabase, Crate, Module, Semantics}; +use hir::{db::HirDatabase, Crate, Module}; +use ide_db::helpers::get_definition; use ide_db::{ base_db::{FileId, FileRange, SourceDatabaseExt}, - defs::{Definition, IdentClass}, + defs::Definition, FxHashSet, RootDatabase, }; -use syntax::{AstNode, SyntaxKind::*, SyntaxToken, TextRange, T}; +use syntax::{AstNode, SyntaxKind::*, TextRange, T}; use crate::{ hover::hover_for_definition, @@ -214,16 +215,6 @@ impl StaticIndex<'_> { } } -fn get_definition(sema: &Semantics<'_, RootDatabase>, token: SyntaxToken) -> Option { - for token in sema.descend_into_macros(token) { - let def = IdentClass::classify_token(sema, &token).map(IdentClass::definitions_no_ops); - if let Some(&[it]) = def.as_deref() { - return Some(it); - } - } - None -} - #[cfg(test)] mod tests { use crate::{fixture, StaticIndex}; diff --git a/crates/ide/src/view_memory_layout.rs b/crates/ide/src/view_memory_layout.rs index 9ed0d3f224..ae8dd504e3 100644 --- a/crates/ide/src/view_memory_layout.rs +++ b/crates/ide/src/view_memory_layout.rs @@ -1,10 +1,10 @@ use hir::{Field, HirDisplay, Layout, Semantics, Type}; use ide_db::{ - defs::{Definition, IdentClass}, - helpers::pick_best_token, + defs::Definition, + helpers::{get_definition, pick_best_token}, RootDatabase, }; -use syntax::{AstNode, SyntaxKind, SyntaxToken}; +use syntax::{AstNode, SyntaxKind}; use crate::FilePosition; @@ -23,16 +23,40 @@ pub struct RecursiveMemoryLayout { pub nodes: Vec, } -fn get_definition(sema: &Semantics<'_, RootDatabase>, token: SyntaxToken) -> Option { - for token in sema.descend_into_macros(token) { - let def = IdentClass::classify_token(sema, &token).map(IdentClass::definitions_no_ops); - if let Some(&[x]) = def.as_deref() { - return Some(x); - } - } - None +enum FieldOrTupleIdx { + Field(Field), + TupleIdx(usize), } +impl FieldOrTupleIdx { + fn name(&self, db: &RootDatabase) -> String { + match *self { + FieldOrTupleIdx::Field(f) => f + .name(db) + .as_str() + .map(|s| s.to_owned()) + .unwrap_or_else(|| format!(".{}", f.name(db).as_tuple_index().unwrap())), + FieldOrTupleIdx::TupleIdx(i) => format!(".{i}").to_owned(), + } + } + + fn index(&self) -> usize { + match *self { + FieldOrTupleIdx::Field(f) => f.index(), + FieldOrTupleIdx::TupleIdx(i) => i, + } + } +} + +// Feature: View Memory Layout +// +// Displays the recursive memory layout of a datatype. +// +// |=== +// | Editor | Action Name +// +// | VS Code | **rust-analyzer: View Memory Layout** +// |=== pub(crate) fn view_memory_layout( db: &RootDatabase, position: FilePosition, @@ -53,34 +77,12 @@ pub(crate) fn view_memory_layout( Definition::BuiltinType(it) => it.ty(db), Definition::SelfType(it) => it.self_ty(db), Definition::Local(it) => it.ty(db), + Definition::Field(it) => it.ty(db), + Definition::Const(it) => it.ty(db), + Definition::Static(it) => it.ty(db), _ => return None, }; - enum FieldOrTupleIdx { - Field(Field), - TupleIdx(usize), - } - - impl FieldOrTupleIdx { - fn name(&self, db: &RootDatabase) -> String { - match *self { - FieldOrTupleIdx::Field(f) => f - .name(db) - .as_str() - .map(|s| s.to_owned()) - .unwrap_or_else(|| format!("{:#?}", f.name(db).as_tuple_index().unwrap())), - FieldOrTupleIdx::TupleIdx(i) => format!(".{i}").to_owned(), - } - } - - fn index(&self) -> usize { - match *self { - FieldOrTupleIdx::Field(f) => f.index(), - FieldOrTupleIdx::TupleIdx(i) => i, - } - } - } - fn read_layout( nodes: &mut Vec, db: &RootDatabase, @@ -100,12 +102,12 @@ pub(crate) fn view_memory_layout( ) .collect::>(); - fields.sort_by_key(|(f, _)| layout.field_offset(f.index()).unwrap_or(u64::MAX)); - if fields.len() == 0 { return; } + fields.sort_by_key(|(f, _)| layout.field_offset(f.index()).unwrap()); + let children_start = nodes.len(); nodes[parent_idx].children_start = children_start as i64; nodes[parent_idx].children_len = fields.len() as u64; @@ -148,8 +150,21 @@ pub(crate) fn view_memory_layout( ty.layout(db) .map(|layout| { let item_name = match def { - Definition::Local(l) => l.name(db).as_str().unwrap().to_owned(), - _ => "[ROOT]".to_owned(), + // def is a datatype + Definition::Adt(_) + | Definition::TypeAlias(_) + | Definition::BuiltinType(_) + | Definition::SelfType(_) => "[ROOT]".to_owned(), + + // def is an item + def => def + .name(db) + .map(|n| { + n.as_str() + .map(|s| s.to_owned()) + .unwrap_or_else(|| format!(".{}", n.as_tuple_index().unwrap())) + }) + .unwrap_or("[ROOT]".to_owned()), }; let typename = ty.display(db).to_string(); @@ -170,3 +185,189 @@ pub(crate) fn view_memory_layout( }) .ok() } + +#[cfg(test)] +mod tests { + use super::*; + + use crate::fixture; + + fn make_memory_layout(ra_fixture: &str) -> Option { + let (analysis, position, _) = fixture::annotations(ra_fixture); + + view_memory_layout(&analysis.db, position) + } + + fn check_item_info(node: &MemoryLayoutNode, item_name: &str, check_typename: bool) { + assert_eq!(node.item_name, item_name); + assert_eq!(node.size, core::mem::size_of::() as u64); + assert_eq!(node.alignment, core::mem::align_of::() as u64); + if check_typename { + assert_eq!(node.typename, std::any::type_name::()); + } + } + + #[test] + fn view_memory_layout_none() { + assert!(make_memory_layout(r#"$0"#).is_none()); + assert!(make_memory_layout(r#"stru$0ct Blah {}"#).is_none()); + } + + #[test] + fn view_memory_layout_primitive() { + let ml = make_memory_layout( + r#" +fn main() { + let foo$0 = 109; // default i32 +} +"#, + ) + .unwrap(); + + assert_eq!(ml.nodes.len(), 1); + assert_eq!(ml.nodes[0].parent_idx, -1); + assert_eq!(ml.nodes[0].children_start, -1); + check_item_info::(&ml.nodes[0], "foo", true); + assert_eq!(ml.nodes[0].offset, 0); + } + + #[test] + fn view_memory_layout_constant() { + let ml = make_memory_layout( + r#" +const BLAH$0: bool = 0; +"#, + ) + .unwrap(); + + assert_eq!(ml.nodes.len(), 1); + assert_eq!(ml.nodes[0].parent_idx, -1); + assert_eq!(ml.nodes[0].children_start, -1); + check_item_info::(&ml.nodes[0], "BLAH", true); + assert_eq!(ml.nodes[0].offset, 0); + } + + #[test] + fn view_memory_layout_static() { + let ml = make_memory_layout( + r#" +static BLAH$0: bool = 0; +"#, + ) + .unwrap(); + + assert_eq!(ml.nodes.len(), 1); + assert_eq!(ml.nodes[0].parent_idx, -1); + assert_eq!(ml.nodes[0].children_start, -1); + check_item_info::(&ml.nodes[0], "BLAH", true); + assert_eq!(ml.nodes[0].offset, 0); + } + + #[test] + fn view_memory_layout_tuple() { + let ml = make_memory_layout( + r#" +fn main() { + let x$0 = (101.0, 111u8, 119i64); +} + "#, + ) + .unwrap(); + + assert_eq!(ml.nodes.len(), 4); + assert_eq!(ml.nodes[0].children_start, 1); + assert_eq!(ml.nodes[0].children_len, 3); + check_item_info::<(f64, u8, i64)>(&ml.nodes[0], "x", true); + } + + #[test] + fn view_memory_layout_struct() { + let ml = make_memory_layout( + r#" +#[repr(C)] +struct Blah$0 { + a: u32, + b: (i32, u8), + c: i8, +} +"#, + ) + .unwrap(); + + #[repr(C)] // repr C makes this testable, rustc doesn't enforce a layout otherwise ;-; + struct Blah { + a: u32, + b: (i32, u8), + c: i8, + } + + assert_eq!(ml.nodes.len(), 6); + check_item_info::(&ml.nodes[0], "[ROOT]", false); + assert_eq!(ml.nodes[0].offset, 0); + + check_item_info::(&ml.nodes[1], "a", true); + assert_eq!(ml.nodes[1].offset, 0); + + check_item_info::<(i32, u8)>(&ml.nodes[2], "b", true); + assert_eq!(ml.nodes[2].offset, 4); + + check_item_info::(&ml.nodes[3], "c", true); + assert_eq!(ml.nodes[3].offset, 12); + } + + #[test] + fn view_memory_layout_member() { + let ml = make_memory_layout( + r#" +struct Oof { + a$0: bool +} +"#, + ) + .unwrap(); + + assert_eq!(ml.nodes.len(), 1); + assert_eq!(ml.nodes[0].parent_idx, -1); + assert_eq!(ml.nodes[0].children_start, -1); + check_item_info::(&ml.nodes[0], "a", true); + // NOTE: this should not give the memory layout relative to the parent structure, but the type referred to by the member variable alone. + assert_eq!(ml.nodes[0].offset, 0); + } + + #[test] + fn view_memory_layout_alias() { + let ml_a = make_memory_layout( + r#" +struct X { + a: u32, + b: i8, + c: (f32, f32), +} + +type Foo$0 = X; + "#, + ) + .unwrap(); + let ml_b = make_memory_layout( + r#" +struct X$0 { + a: u32, + b: i8, + c: (f32, f32), +} + "#, + ) + .unwrap(); + + ml_a.nodes.iter().zip(ml_b.nodes.iter()).for_each(|(a, b)| { + assert_eq!(a.item_name, b.item_name); + assert_eq!(a.typename, b.typename); + assert_eq!(a.size, b.size); + assert_eq!(a.alignment, b.alignment); + assert_eq!(a.offset, b.offset); + assert_eq!(a.parent_idx, b.parent_idx); + assert_eq!(a.children_start, b.children_start); + assert_eq!(a.children_len, b.children_len); + }) + } +} diff --git a/docs/dev/lsp-extensions.md b/docs/dev/lsp-extensions.md index bc58aa7220..8655e95467 100644 --- a/docs/dev/lsp-extensions.md +++ b/docs/dev/lsp-extensions.md @@ -1,5 +1,5 @@