aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_ide/src/inlay_hints.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_ide/src/inlay_hints.rs')
-rw-r--r--crates/ra_ide/src/inlay_hints.rs543
1 files changed, 543 insertions, 0 deletions
diff --git a/crates/ra_ide/src/inlay_hints.rs b/crates/ra_ide/src/inlay_hints.rs
new file mode 100644
index 000000000..45149bf0c
--- /dev/null
+++ b/crates/ra_ide/src/inlay_hints.rs
@@ -0,0 +1,543 @@
1//! FIXME: write short doc here
2
3use crate::{db::RootDatabase, FileId};
4use hir::{HirDisplay, SourceAnalyzer};
5use ra_syntax::{
6 ast::{self, AstNode, TypeAscriptionOwner},
7 match_ast, SmolStr, SourceFile, SyntaxKind, SyntaxNode, TextRange,
8};
9
10#[derive(Debug, PartialEq, Eq)]
11pub enum InlayKind {
12 TypeHint,
13}
14
15#[derive(Debug)]
16pub struct InlayHint {
17 pub range: TextRange,
18 pub kind: InlayKind,
19 pub label: SmolStr,
20}
21
22pub(crate) fn inlay_hints(
23 db: &RootDatabase,
24 file_id: FileId,
25 file: &SourceFile,
26 max_inlay_hint_length: Option<usize>,
27) -> Vec<InlayHint> {
28 file.syntax()
29 .descendants()
30 .map(|node| get_inlay_hints(db, file_id, &node, max_inlay_hint_length).unwrap_or_default())
31 .flatten()
32 .collect()
33}
34
35fn get_inlay_hints(
36 db: &RootDatabase,
37 file_id: FileId,
38 node: &SyntaxNode,
39 max_inlay_hint_length: Option<usize>,
40) -> Option<Vec<InlayHint>> {
41 let analyzer = SourceAnalyzer::new(db, hir::Source::new(file_id.into(), node), None);
42 match_ast! {
43 match node {
44 ast::LetStmt(it) => {
45 if it.ascribed_type().is_some() {
46 return None;
47 }
48 let pat = it.pat()?;
49 Some(get_pat_type_hints(db, &analyzer, pat, false, max_inlay_hint_length))
50 },
51 ast::LambdaExpr(it) => {
52 it.param_list().map(|param_list| {
53 param_list
54 .params()
55 .filter(|closure_param| closure_param.ascribed_type().is_none())
56 .filter_map(|closure_param| closure_param.pat())
57 .map(|root_pat| get_pat_type_hints(db, &analyzer, root_pat, false, max_inlay_hint_length))
58 .flatten()
59 .collect()
60 })
61 },
62 ast::ForExpr(it) => {
63 let pat = it.pat()?;
64 Some(get_pat_type_hints(db, &analyzer, pat, false, max_inlay_hint_length))
65 },
66 ast::IfExpr(it) => {
67 let pat = it.condition()?.pat()?;
68 Some(get_pat_type_hints(db, &analyzer, pat, true, max_inlay_hint_length))
69 },
70 ast::WhileExpr(it) => {
71 let pat = it.condition()?.pat()?;
72 Some(get_pat_type_hints(db, &analyzer, pat, true, max_inlay_hint_length))
73 },
74 ast::MatchArmList(it) => {
75 Some(
76 it
77 .arms()
78 .map(|match_arm| match_arm.pats())
79 .flatten()
80 .map(|root_pat| get_pat_type_hints(db, &analyzer, root_pat, true, max_inlay_hint_length))
81 .flatten()
82 .collect(),
83 )
84 },
85 _ => None,
86 }
87 }
88}
89
90fn get_pat_type_hints(
91 db: &RootDatabase,
92 analyzer: &SourceAnalyzer,
93 root_pat: ast::Pat,
94 skip_root_pat_hint: bool,
95 max_inlay_hint_length: Option<usize>,
96) -> Vec<InlayHint> {
97 let original_pat = &root_pat.clone();
98
99 get_leaf_pats(root_pat)
100 .into_iter()
101 .filter(|pat| !skip_root_pat_hint || pat != original_pat)
102 .filter_map(|pat| {
103 let ty = analyzer.type_of_pat(db, &pat)?;
104 if ty.is_unknown() {
105 return None;
106 }
107 Some((pat.syntax().text_range(), ty))
108 })
109 .map(|(range, pat_type)| InlayHint {
110 range,
111 kind: InlayKind::TypeHint,
112 label: pat_type.display_truncated(db, max_inlay_hint_length).to_string().into(),
113 })
114 .collect()
115}
116
117fn get_leaf_pats(root_pat: ast::Pat) -> Vec<ast::Pat> {
118 let mut pats_to_process = std::collections::VecDeque::<ast::Pat>::new();
119 pats_to_process.push_back(root_pat);
120
121 let mut leaf_pats = Vec::new();
122
123 while let Some(maybe_leaf_pat) = pats_to_process.pop_front() {
124 match &maybe_leaf_pat {
125 ast::Pat::BindPat(bind_pat) => {
126 if let Some(pat) = bind_pat.pat() {
127 pats_to_process.push_back(pat);
128 } else {
129 leaf_pats.push(maybe_leaf_pat);
130 }
131 }
132 ast::Pat::TuplePat(tuple_pat) => {
133 for arg_pat in tuple_pat.args() {
134 pats_to_process.push_back(arg_pat);
135 }
136 }
137 ast::Pat::RecordPat(record_pat) => {
138 if let Some(pat_list) = record_pat.record_field_pat_list() {
139 pats_to_process.extend(
140 pat_list
141 .record_field_pats()
142 .filter_map(|record_field_pat| {
143 record_field_pat
144 .pat()
145 .filter(|pat| pat.syntax().kind() != SyntaxKind::BIND_PAT)
146 })
147 .chain(pat_list.bind_pats().map(|bind_pat| {
148 bind_pat.pat().unwrap_or_else(|| ast::Pat::from(bind_pat))
149 })),
150 );
151 }
152 }
153 ast::Pat::TupleStructPat(tuple_struct_pat) => {
154 for arg_pat in tuple_struct_pat.args() {
155 pats_to_process.push_back(arg_pat);
156 }
157 }
158 _ => (),
159 }
160 }
161 leaf_pats
162}
163
164#[cfg(test)]
165mod tests {
166 use crate::mock_analysis::single_file;
167 use insta::assert_debug_snapshot;
168
169 #[test]
170 fn let_statement() {
171 let (analysis, file_id) = single_file(
172 r#"
173#[derive(PartialEq)]
174enum CustomOption<T> {
175 None,
176 Some(T),
177}
178
179#[derive(PartialEq)]
180struct Test {
181 a: CustomOption<u32>,
182 b: u8,
183}
184
185fn main() {
186 struct InnerStruct {}
187
188 let test = 54;
189 let test: i32 = 33;
190 let mut test = 33;
191 let _ = 22;
192 let test = "test";
193 let test = InnerStruct {};
194
195 let test = vec![222];
196 let test: Vec<_> = (0..3).collect();
197 let test = (0..3).collect::<Vec<i128>>();
198 let test = (0..3).collect::<Vec<_>>();
199
200 let mut test = Vec::new();
201 test.push(333);
202
203 let test = (42, 'a');
204 let (a, (b, c, (d, e), f)) = (2, (3, 4, (6.6, 7.7), 5));
205}"#,
206 );
207
208 assert_debug_snapshot!(analysis.inlay_hints(file_id, None).unwrap(), @r###"
209 [
210 InlayHint {
211 range: [193; 197),
212 kind: TypeHint,
213 label: "i32",
214 },
215 InlayHint {
216 range: [236; 244),
217 kind: TypeHint,
218 label: "i32",
219 },
220 InlayHint {
221 range: [275; 279),
222 kind: TypeHint,
223 label: "&str",
224 },
225 InlayHint {
226 range: [539; 543),
227 kind: TypeHint,
228 label: "(i32, char)",
229 },
230 InlayHint {
231 range: [566; 567),
232 kind: TypeHint,
233 label: "i32",
234 },
235 InlayHint {
236 range: [570; 571),
237 kind: TypeHint,
238 label: "i32",
239 },
240 InlayHint {
241 range: [573; 574),
242 kind: TypeHint,
243 label: "i32",
244 },
245 InlayHint {
246 range: [584; 585),
247 kind: TypeHint,
248 label: "i32",
249 },
250 InlayHint {
251 range: [577; 578),
252 kind: TypeHint,
253 label: "f64",
254 },
255 InlayHint {
256 range: [580; 581),
257 kind: TypeHint,
258 label: "f64",
259 },
260 ]
261 "###
262 );
263 }
264
265 #[test]
266 fn closure_parameter() {
267 let (analysis, file_id) = single_file(
268 r#"
269fn main() {
270 let mut start = 0;
271 (0..2).for_each(|increment| {
272 start += increment;
273 })
274}"#,
275 );
276
277 assert_debug_snapshot!(analysis.inlay_hints(file_id, None).unwrap(), @r###"
278 [
279 InlayHint {
280 range: [21; 30),
281 kind: TypeHint,
282 label: "i32",
283 },
284 InlayHint {
285 range: [57; 66),
286 kind: TypeHint,
287 label: "i32",
288 },
289 ]
290 "###
291 );
292 }
293
294 #[test]
295 fn for_expression() {
296 let (analysis, file_id) = single_file(
297 r#"
298fn main() {
299 let mut start = 0;
300 for increment in 0..2 {
301 start += increment;
302 }
303}"#,
304 );
305
306 assert_debug_snapshot!(analysis.inlay_hints(file_id, None).unwrap(), @r###"
307 [
308 InlayHint {
309 range: [21; 30),
310 kind: TypeHint,
311 label: "i32",
312 },
313 InlayHint {
314 range: [44; 53),
315 kind: TypeHint,
316 label: "i32",
317 },
318 ]
319 "###
320 );
321 }
322
323 #[test]
324 fn if_expr() {
325 let (analysis, file_id) = single_file(
326 r#"
327#[derive(PartialEq)]
328enum CustomOption<T> {
329 None,
330 Some(T),
331}
332
333#[derive(PartialEq)]
334struct Test {
335 a: CustomOption<u32>,
336 b: u8,
337}
338
339fn main() {
340 let test = CustomOption::Some(Test { a: CustomOption::Some(3), b: 1 });
341 if let CustomOption::None = &test {};
342 if let test = &test {};
343 if let CustomOption::Some(test) = &test {};
344 if let CustomOption::Some(Test { a, b }) = &test {};
345 if let CustomOption::Some(Test { a: x, b: y }) = &test {};
346 if let CustomOption::Some(Test { a: CustomOption::Some(x), b: y }) = &test {};
347 if let CustomOption::Some(Test { a: CustomOption::None, b: y }) = &test {};
348 if let CustomOption::Some(Test { b: y, .. }) = &test {};
349
350 if test == CustomOption::None {}
351}"#,
352 );
353
354 assert_debug_snapshot!(analysis.inlay_hints(file_id, None).unwrap(), @r###"
355 [
356 InlayHint {
357 range: [166; 170),
358 kind: TypeHint,
359 label: "CustomOption<Test>",
360 },
361 InlayHint {
362 range: [334; 338),
363 kind: TypeHint,
364 label: "&Test",
365 },
366 InlayHint {
367 range: [389; 390),
368 kind: TypeHint,
369 label: "&CustomOption<u32>",
370 },
371 InlayHint {
372 range: [392; 393),
373 kind: TypeHint,
374 label: "&u8",
375 },
376 InlayHint {
377 range: [531; 532),
378 kind: TypeHint,
379 label: "&u32",
380 },
381 ]
382 "###
383 );
384 }
385
386 #[test]
387 fn while_expr() {
388 let (analysis, file_id) = single_file(
389 r#"
390#[derive(PartialEq)]
391enum CustomOption<T> {
392 None,
393 Some(T),
394}
395
396#[derive(PartialEq)]
397struct Test {
398 a: CustomOption<u32>,
399 b: u8,
400}
401
402fn main() {
403 let test = CustomOption::Some(Test { a: CustomOption::Some(3), b: 1 });
404 while let CustomOption::None = &test {};
405 while let test = &test {};
406 while let CustomOption::Some(test) = &test {};
407 while let CustomOption::Some(Test { a, b }) = &test {};
408 while let CustomOption::Some(Test { a: x, b: y }) = &test {};
409 while let CustomOption::Some(Test { a: CustomOption::Some(x), b: y }) = &test {};
410 while let CustomOption::Some(Test { a: CustomOption::None, b: y }) = &test {};
411 while let CustomOption::Some(Test { b: y, .. }) = &test {};
412
413 while test == CustomOption::None {}
414}"#,
415 );
416
417 assert_debug_snapshot!(analysis.inlay_hints(file_id, None).unwrap(), @r###"
418 [
419 InlayHint {
420 range: [166; 170),
421 kind: TypeHint,
422 label: "CustomOption<Test>",
423 },
424 InlayHint {
425 range: [343; 347),
426 kind: TypeHint,
427 label: "&Test",
428 },
429 InlayHint {
430 range: [401; 402),
431 kind: TypeHint,
432 label: "&CustomOption<u32>",
433 },
434 InlayHint {
435 range: [404; 405),
436 kind: TypeHint,
437 label: "&u8",
438 },
439 InlayHint {
440 range: [549; 550),
441 kind: TypeHint,
442 label: "&u32",
443 },
444 ]
445 "###
446 );
447 }
448
449 #[test]
450 fn match_arm_list() {
451 let (analysis, file_id) = single_file(
452 r#"
453#[derive(PartialEq)]
454enum CustomOption<T> {
455 None,
456 Some(T),
457}
458
459#[derive(PartialEq)]
460struct Test {
461 a: CustomOption<u32>,
462 b: u8,
463}
464
465fn main() {
466 match CustomOption::Some(Test { a: CustomOption::Some(3), b: 1 }) {
467 CustomOption::None => (),
468 test => (),
469 CustomOption::Some(test) => (),
470 CustomOption::Some(Test { a, b }) => (),
471 CustomOption::Some(Test { a: x, b: y }) => (),
472 CustomOption::Some(Test { a: CustomOption::Some(x), b: y }) => (),
473 CustomOption::Some(Test { a: CustomOption::None, b: y }) => (),
474 CustomOption::Some(Test { b: y, .. }) => (),
475 _ => {}
476 }
477}"#,
478 );
479
480 assert_debug_snapshot!(analysis.inlay_hints(file_id, None).unwrap(), @r###"
481 [
482 InlayHint {
483 range: [311; 315),
484 kind: TypeHint,
485 label: "Test",
486 },
487 InlayHint {
488 range: [358; 359),
489 kind: TypeHint,
490 label: "CustomOption<u32>",
491 },
492 InlayHint {
493 range: [361; 362),
494 kind: TypeHint,
495 label: "u8",
496 },
497 InlayHint {
498 range: [484; 485),
499 kind: TypeHint,
500 label: "u32",
501 },
502 ]
503 "###
504 );
505 }
506
507 #[test]
508 fn hint_truncation() {
509 let (analysis, file_id) = single_file(
510 r#"
511struct Smol<T>(T);
512
513struct VeryLongOuterName<T>(T);
514
515fn main() {
516 let a = Smol(0u32);
517 let b = VeryLongOuterName(0usize);
518 let c = Smol(Smol(0u32))
519}"#,
520 );
521
522 assert_debug_snapshot!(analysis.inlay_hints(file_id, Some(8)).unwrap(), @r###"
523 [
524 InlayHint {
525 range: [74; 75),
526 kind: TypeHint,
527 label: "Smol<u32>",
528 },
529 InlayHint {
530 range: [98; 99),
531 kind: TypeHint,
532 label: "VeryLongOuterName<…>",
533 },
534 InlayHint {
535 range: [137; 138),
536 kind: TypeHint,
537 label: "Smol<Smol<…>>",
538 },
539 ]
540 "###
541 );
542 }
543}