aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_ide_api/src
diff options
context:
space:
mode:
authorbors[bot] <26634292+bors[bot]@users.noreply.github.com>2019-07-29 13:30:39 +0100
committerbors[bot] <26634292+bors[bot]@users.noreply.github.com>2019-07-29 13:30:39 +0100
commitbf1369cdef1b11ce7ba3a289ae6c4187b6fa5854 (patch)
tree8df5bee4253cc9f170e2f324062d274871c942bc /crates/ra_ide_api/src
parent62bdcc0fb2015116316d47d665d504cbe666aaed (diff)
parentb8f95f42e1777d3858c74b45ba8f7a5b5a3cdca8 (diff)
Merge #1606
1606: Add `if let`, `while let` and match arm inlay hints r=matklad a=SomeoneToIgnore <img width="693" alt="image" src="https://user-images.githubusercontent.com/2690773/62013363-152f1d80-b19a-11e9-90ea-07568757baa2.png"> Add more inline hints support. Looks like `while let` type inference support is missing currently, so the corresponding hint tests lack the actual results. I've also could not find a good way to distinguish between `a` and `b` pats in the following expressions: `if let Some(Test { a: None, b: y }) = &test {};` In this case we don't need to add a hint for first pat (`a: None`), since it's matched against the particular enum variant and need a hint for `y`, since it's a new variable. But both `a` and `b` are `BIND_PAT` with similar contents, so looks like there's nothing I can check for to find any differences. I don't display any hints for such cases now, to avoid confusion, but would be nice to know if there's a way to fix this behavior. Co-authored-by: Kirill Bulatov <[email protected]>
Diffstat (limited to 'crates/ra_ide_api/src')
-rw-r--r--crates/ra_ide_api/src/inlay_hints.rs352
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 @@
1use crate::{db::RootDatabase, FileId}; 1use crate::{db::RootDatabase, FileId};
2use hir::{HirDisplay, SourceAnalyzer, Ty}; 2use hir::{HirDisplay, SourceAnalyzer, Ty};
3use ra_syntax::ast::Pat;
4use ra_syntax::{ 3use 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#"
140struct OuterStruct {} 197#[derive(PartialEq)]
198enum CustomOption<T> {
199 None,
200 Some(T),
201}
202
203#[derive(PartialEq)]
204struct Test {
205 a: CustomOption<u32>,
206 b: u8,
207}
141 208
142fn main() { 209fn 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#"
291fn 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#"
318fn 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)]
346enum CustomOption<T> {
347 None,
348 Some(T),
349}
350
351#[derive(PartialEq)]
352struct Test {
353 a: CustomOption<u32>,
354 b: u8,
355}
356
357fn 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)]
407enum CustomOption<T> {
408 None,
409 Some(T),
410}
411
412#[derive(PartialEq)]
413struct Test {
414 a: CustomOption<u32>,
415 b: u8,
416}
417
418fn 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)]
448enum CustomOption<T> {
449 None,
450 Some(T),
451}
452
453#[derive(PartialEq)]
454struct Test {
455 a: CustomOption<u32>,
456 b: u8,
457}
458
459fn 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 );