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