1use std::fmt;
2
3use hir::{DisplayTarget, Field, HirDisplay, Layout, Semantics, Type};
4use ide_db::{
5 RootDatabase,
6 defs::Definition,
7 helpers::{get_definition, pick_best_token},
8};
9use syntax::{AstNode, SyntaxKind};
10
11use crate::FilePosition;
12
13pub struct MemoryLayoutNode {
14 pub item_name: String,
15 pub typename: String,
16 pub size: u64,
17 pub alignment: u64,
18 pub offset: u64,
19 pub parent_idx: i64,
20 pub children_start: i64,
21 pub children_len: u64,
22}
23
24pub struct RecursiveMemoryLayout {
25 pub nodes: Vec<MemoryLayoutNode>,
26}
27
28impl fmt::Display for RecursiveMemoryLayout {
30 fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
31 fn process(
32 fmt: &mut fmt::Formatter<'_>,
33 nodes: &Vec<MemoryLayoutNode>,
34 idx: usize,
35 depth: usize,
36 ) -> fmt::Result {
37 let mut out = "\t".repeat(depth);
38 let node = &nodes[idx];
39 out += &format!(
40 "{}: {} (size: {}, align: {}, field offset: {})\n",
41 node.item_name, node.typename, node.size, node.alignment, node.offset
42 );
43 write!(fmt, "{out}")?;
44 if node.children_start != -1 {
45 for j in nodes[idx].children_start
46 ..(nodes[idx].children_start + nodes[idx].children_len as i64)
47 {
48 process(fmt, nodes, j as usize, depth + 1)?;
49 }
50 }
51 Ok(())
52 }
53
54 process(fmt, &self.nodes, 0, 0)
55 }
56}
57
58#[derive(Copy, Clone)]
59enum FieldOrTupleIdx {
60 Field(Field),
61 TupleIdx(usize),
62}
63
64impl FieldOrTupleIdx {
65 fn name(&self, db: &RootDatabase) -> String {
66 match *self {
67 FieldOrTupleIdx::Field(f) => f.name(db).as_str().to_owned(),
68 FieldOrTupleIdx::TupleIdx(i) => format!(".{i}"),
69 }
70 }
71}
72
73pub(crate) fn view_memory_layout(
81 db: &RootDatabase,
82 position: FilePosition,
83) -> Option<RecursiveMemoryLayout> {
84 let sema = Semantics::new(db);
85 let file = sema.parse_guess_edition(position.file_id);
86 let display_target = sema.first_crate(position.file_id)?.to_display_target(db);
87 let token =
88 pick_best_token(file.syntax().token_at_offset(position.offset), |kind| match kind {
89 SyntaxKind::IDENT => 3,
90 _ => 0,
91 })?;
92
93 let def = get_definition(&sema, token)?;
94
95 let ty = match def {
96 Definition::Adt(it) => it.ty(db),
97 Definition::TypeAlias(it) => it.ty(db),
98 Definition::BuiltinType(it) => it.ty(db),
99 Definition::SelfType(it) => it.self_ty(db),
100 Definition::Local(it) => it.ty(db),
101 Definition::Field(it) => it.ty(db).to_type(db),
102 Definition::Const(it) => it.ty(db),
103 Definition::Static(it) => it.ty(db),
104 _ => return None,
105 };
106
107 fn read_layout(
108 nodes: &mut Vec<MemoryLayoutNode>,
109 db: &RootDatabase,
110 ty: &Type<'_>,
111 layout: &Layout,
112 parent_idx: usize,
113 display_target: DisplayTarget,
114 ) {
115 let mut fields = ty
116 .fields(db)
117 .into_iter()
118 .map(|(f, ty)| (FieldOrTupleIdx::Field(f), ty))
119 .chain(
120 ty.tuple_fields(db)
121 .into_iter()
122 .enumerate()
123 .map(|(i, ty)| (FieldOrTupleIdx::TupleIdx(i), ty)),
124 )
125 .collect::<Vec<_>>();
126
127 if fields.is_empty() {
128 return;
129 }
130
131 fields.sort_by_key(|&(f, _)| match f {
132 FieldOrTupleIdx::Field(f) => layout.field_offset(f).unwrap_or(0),
133 FieldOrTupleIdx::TupleIdx(f) => layout.tuple_field_offset(f).unwrap_or(0),
134 });
135
136 let children_start = nodes.len();
137 nodes[parent_idx].children_start = children_start as i64;
138 nodes[parent_idx].children_len = fields.len() as u64;
139
140 for (field, child_ty) in fields.iter() {
141 if let Ok(child_layout) = child_ty.layout(db) {
142 nodes.push(MemoryLayoutNode {
143 item_name: field.name(db),
144 typename: { child_ty.display(db, display_target).to_string() },
145 size: child_layout.size(),
146 alignment: child_layout.align(),
147 offset: match *field {
148 FieldOrTupleIdx::Field(f) => layout.field_offset(f).unwrap_or(0),
149 FieldOrTupleIdx::TupleIdx(f) => layout.tuple_field_offset(f).unwrap_or(0),
150 },
151 parent_idx: parent_idx as i64,
152 children_start: -1,
153 children_len: 0,
154 });
155 } else {
156 nodes.push(MemoryLayoutNode {
157 item_name: field.name(db)
158 + format!("(no layout data: {:?})", child_ty.layout(db).unwrap_err())
159 .as_ref(),
160 typename: child_ty.display(db, display_target).to_string(),
161 size: 0,
162 offset: 0,
163 alignment: 0,
164 parent_idx: parent_idx as i64,
165 children_start: -1,
166 children_len: 0,
167 });
168 }
169 }
170
171 for (i, (_, child_ty)) in fields.iter().enumerate() {
172 if let Ok(child_layout) = child_ty.layout(db) {
173 read_layout(nodes, db, child_ty, &child_layout, children_start + i, display_target);
174 }
175 }
176 }
177
178 ty.layout(db)
179 .map(|layout| {
180 let item_name = match def {
181 Definition::Adt(_)
183 | Definition::TypeAlias(_)
184 | Definition::BuiltinType(_)
185 | Definition::SelfType(_) => "[ROOT]".to_owned(),
186
187 def => def.name(db).map(|n| n.as_str().to_owned()).unwrap_or("[ROOT]".to_owned()),
189 };
190
191 let typename = ty.display(db, display_target).to_string();
192
193 let mut nodes = vec![MemoryLayoutNode {
194 item_name,
195 typename,
196 size: layout.size(),
197 offset: 0,
198 alignment: layout.align(),
199 parent_idx: -1,
200 children_start: -1,
201 children_len: 0,
202 }];
203 read_layout(&mut nodes, db, &ty, &layout, 0, display_target);
204
205 RecursiveMemoryLayout { nodes }
206 })
207 .ok()
208}
209
210#[cfg(test)]
211mod tests {
212 use super::*;
213
214 use crate::fixture;
215 use expect_test::expect;
216
217 fn make_memory_layout(
218 #[rust_analyzer::rust_fixture] ra_fixture: &str,
219 ) -> Option<RecursiveMemoryLayout> {
220 let (analysis, position, _) = fixture::annotations(ra_fixture);
221
222 hir::attach_db(&analysis.db, || view_memory_layout(&analysis.db, position))
223 }
224
225 #[test]
226 fn view_memory_layout_none() {
227 assert!(make_memory_layout(r#"$0"#).is_none());
228 assert!(make_memory_layout(r#"stru$0ct Blah {}"#).is_none());
229 }
230
231 #[test]
232 fn view_memory_layout_primitive() {
233 expect![[r#"
234 foo: i32 (size: 4, align: 4, field offset: 0)
235 "#]]
236 .assert_eq(
237 &make_memory_layout(
238 r#"
239fn main() {
240 let foo$0 = 109; // default i32
241}
242"#,
243 )
244 .unwrap()
245 .to_string(),
246 );
247 }
248
249 #[test]
250 fn view_memory_layout_constant() {
251 expect![[r#"
252 BLAH: bool (size: 1, align: 1, field offset: 0)
253 "#]]
254 .assert_eq(
255 &make_memory_layout(
256 r#"
257const BLAH$0: bool = 0;
258"#,
259 )
260 .unwrap()
261 .to_string(),
262 );
263 }
264
265 #[test]
266 fn view_memory_layout_static() {
267 expect![[r#"
268 BLAH: bool (size: 1, align: 1, field offset: 0)
269 "#]]
270 .assert_eq(
271 &make_memory_layout(
272 r#"
273static BLAH$0: bool = 0;
274"#,
275 )
276 .unwrap()
277 .to_string(),
278 );
279 }
280
281 #[test]
282 fn view_memory_layout_tuple() {
283 expect![[r#"
284 x: (f64, u8, i64) (size: 24, align: 8, field offset: 0)
285 .0: f64 (size: 8, align: 8, field offset: 0)
286 .1: u8 (size: 1, align: 1, field offset: 8)
287 .2: i64 (size: 8, align: 8, field offset: 16)
288 "#]]
289 .assert_eq(
290 &make_memory_layout(
291 r#"
292fn main() {
293 let x$0 = (101.0, 111u8, 119i64);
294}
295"#,
296 )
297 .unwrap()
298 .to_string(),
299 );
300 }
301
302 #[test]
303 fn view_memory_layout_c_struct() {
304 expect![[r#"
305 [ROOT]: Blah (size: 16, align: 4, field offset: 0)
306 a: u32 (size: 4, align: 4, field offset: 0)
307 b: (i32, u8) (size: 8, align: 4, field offset: 4)
308 .0: i32 (size: 4, align: 4, field offset: 0)
309 .1: u8 (size: 1, align: 1, field offset: 4)
310 c: i8 (size: 1, align: 1, field offset: 12)
311 "#]]
312 .assert_eq(
313 &make_memory_layout(
314 r#"
315#[repr(C)]
316struct Blah$0 {
317 a: u32,
318 b: (i32, u8),
319 c: i8,
320}
321"#,
322 )
323 .unwrap()
324 .to_string(),
325 );
326 }
327
328 #[test]
329 fn view_memory_layout_struct() {
330 expect![[r#"
331 [ROOT]: Blah (size: 16, align: 4, field offset: 0)
332 b: (i32, u8) (size: 8, align: 4, field offset: 0)
333 .0: i32 (size: 4, align: 4, field offset: 0)
334 .1: u8 (size: 1, align: 1, field offset: 4)
335 a: u32 (size: 4, align: 4, field offset: 8)
336 c: i8 (size: 1, align: 1, field offset: 12)
337 "#]]
338 .assert_eq(
339 &make_memory_layout(
340 r#"
341struct Blah$0 {
342 a: u32,
343 b: (i32, u8),
344 c: i8,
345}
346"#,
347 )
348 .unwrap()
349 .to_string(),
350 );
351 }
352
353 #[test]
354 fn view_memory_layout_member() {
355 expect![[r#"
356 a: bool (size: 1, align: 1, field offset: 0)
357 "#]]
358 .assert_eq(
359 &make_memory_layout(
360 r#"
361#[repr(C)]
362struct Oof {
363 a$0: bool,
364}
365"#,
366 )
367 .unwrap()
368 .to_string(),
369 );
370 }
371
372 #[test]
373 fn view_memory_layout_alias() {
374 let ml_a = make_memory_layout(
375 r#"
376struct X {
377 a: u32,
378 b: i8,
379 c: (f32, f32),
380}
381
382type Foo$0 = X;
383"#,
384 )
385 .unwrap();
386
387 let ml_b = make_memory_layout(
388 r#"
389struct X$0 {
390 a: u32,
391 b: i8,
392 c: (f32, f32),
393}
394"#,
395 )
396 .unwrap();
397
398 assert_eq!(ml_a.to_string(), ml_b.to_string());
399 }
400}