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