Unnamed repository; edit this file 'description' to name the repository.
cleanup + docs + tests
| -rw-r--r-- | crates/ide-db/src/helpers.rs | 18 | ||||
| -rw-r--r-- | crates/ide/src/lib.rs | 5 | ||||
| -rw-r--r-- | crates/ide/src/static_index.rs | 17 | ||||
| -rw-r--r-- | crates/ide/src/view_memory_layout.rs | 277 | ||||
| -rw-r--r-- | docs/dev/lsp-extensions.md | 55 | ||||
| -rw-r--r-- | editors/code/src/commands.ts | 2 | ||||
| -rw-r--r-- | editors/code/src/lsp_ext.ts | 2 |
7 files changed, 318 insertions, 58 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<Name> { 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<Definition> { + 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<Option<RecursiveMemoryLayout>> { + pub fn get_recursive_memory_layout( + &self, + position: FilePosition, + ) -> Cancellable<Option<RecursiveMemoryLayout>> { 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<Definition> { - 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<MemoryLayoutNode>, } -fn get_definition(sema: &Semantics<'_, RootDatabase>, token: SyntaxToken) -> Option<Definition> { - 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); +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, } } - None } +// 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<MemoryLayoutNode>, db: &RootDatabase, @@ -100,12 +102,12 @@ pub(crate) fn view_memory_layout( ) .collect::<Vec<_>>(); - 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<RecursiveMemoryLayout> { + let (analysis, position, _) = fixture::annotations(ra_fixture); + + view_memory_layout(&analysis.db, position) + } + + fn check_item_info<T>(node: &MemoryLayoutNode, item_name: &str, check_typename: bool) { + assert_eq!(node.item_name, item_name); + assert_eq!(node.size, core::mem::size_of::<T>() as u64); + assert_eq!(node.alignment, core::mem::align_of::<T>() as u64); + if check_typename { + assert_eq!(node.typename, std::any::type_name::<T>()); + } + } + + #[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::<i32>(&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::<bool>(&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::<bool>(&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::<Blah>(&ml.nodes[0], "[ROOT]", false); + assert_eq!(ml.nodes[0].offset, 0); + + check_item_info::<u32>(&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::<i8>(&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::<bool>(&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 @@ <!--- -lsp_ext.rs hash: 2d60bbffe70ae198 +lsp_ext.rs hash: 12bf360ee77cc63d If you need to change the above hash to make the test pass, please check if you need to adjust this doc as well and ping this issue: @@ -886,3 +886,56 @@ export interface FetchDependencyListResult { } ``` Returns all crates from this workspace, so it can be used create a viewTree to help navigate the dependency tree. + +## View Recursive Memory Layout + +**Method:** `rust-analyzer/fetchDependencyList` + +**Request:** + +```typescript +/// Holds a location in a text document, the location may be a datatype or a variable and it will do its best to locate the desired type. +export interface ViewRecursiveMemoryLayoutParams { + textDocument: TextDocumentIdentifier; + position: Position; +} +``` + +**Response:** + +```typescript +export interface RecursiveMemoryLayoutNode = { + /// Name of the item, or [ROOT], `.n` for tuples + item_name: string; + /// Full name of the type (type aliases are ignored) + typename: string; + /// Size of the type in bytes + size: number; + /// Alignment of the type in bytes + alignment: number; + /// Offset of the type relative to its parent (or 0 if its the root) + offset: number; + /// Index of the node's parent (or -1 if its the root) + parent_idx: number; + /// Index of the node's children (or -1 if it does not have children) + children_start: number; + /// Number of child nodes (unspecified it does not have children) + children_len: number; +}; + +export interface RecursiveMemoryLayout = { + nodes: RecursiveMemoryLayoutNode[]; +}; +``` + +Returns a vector of nodes representing items in the datatype as a tree, `RecursiveMemoryLayout::nodes[0]` is the root node. + +If `RecursiveMemoryLayout::nodes::length == 0` we could not find a suitable type. + +Generic Types do not give anything because they are incomplete. Fully specified generic types do not give anything if they are selected directly but do work when a child of other types [this is consistent with other behavior](https://github.com/rust-lang/rust-analyzer/issues/15010). + +### Unresolved questions: + +- How should enums/unions be represented? currently they do not produce any children because they have multiple distinct sets of children. +- Should niches be represented? currently they are not reported. +- A visual representation of the memory layout is not specified, see the provided implementation for an example, however it may not translate well to terminal based editors or other such things. diff --git a/editors/code/src/commands.ts b/editors/code/src/commands.ts index 052e391824..17202e5345 100644 --- a/editors/code/src/commands.ts +++ b/editors/code/src/commands.ts @@ -1142,8 +1142,6 @@ export function viewMemoryLayout(ctx: CtxInit): Cmd { position, }); - // if (expanded == null) return "Not available"; - const document = vscode.window.createWebviewPanel( "memory_layout", "[Memory Layout]", diff --git a/editors/code/src/lsp_ext.ts b/editors/code/src/lsp_ext.ts index 8a0c55a942..9ab1a99376 100644 --- a/editors/code/src/lsp_ext.ts +++ b/editors/code/src/lsp_ext.ts @@ -218,7 +218,5 @@ export type RecursiveMemoryLayoutNode = { children_len: number; }; export type RecursiveMemoryLayout = { - name: string; - expansion: string; nodes: RecursiveMemoryLayoutNode[]; }; |