diff options
Diffstat (limited to 'crates/ra_ide/src/inlay_hints.rs')
-rw-r--r-- | crates/ra_ide/src/inlay_hints.rs | 174 |
1 files changed, 82 insertions, 92 deletions
diff --git a/crates/ra_ide/src/inlay_hints.rs b/crates/ra_ide/src/inlay_hints.rs index 62d364bfa..f2e4f7ee5 100644 --- a/crates/ra_ide/src/inlay_hints.rs +++ b/crates/ra_ide/src/inlay_hints.rs | |||
@@ -1,14 +1,16 @@ | |||
1 | use hir::{Adt, HirDisplay, Semantics, Type}; | 1 | use hir::{Adt, Callable, HirDisplay, Semantics, Type}; |
2 | use ra_ide_db::RootDatabase; | 2 | use ra_ide_db::RootDatabase; |
3 | use ra_prof::profile; | 3 | use ra_prof::profile; |
4 | use ra_syntax::{ | 4 | use ra_syntax::{ |
5 | ast::{self, ArgListOwner, AstNode, TypeAscriptionOwner}, | 5 | ast::{self, ArgListOwner, AstNode, TypeAscriptionOwner}, |
6 | match_ast, Direction, NodeOrToken, SmolStr, SyntaxKind, TextRange, T, | 6 | match_ast, Direction, NodeOrToken, SmolStr, SyntaxKind, TextRange, T, |
7 | }; | 7 | }; |
8 | |||
9 | use crate::{FileId, FunctionSignature}; | ||
10 | use stdx::to_lower_snake_case; | 8 | use stdx::to_lower_snake_case; |
11 | 9 | ||
10 | use crate::FileId; | ||
11 | use ast::NameOwner; | ||
12 | use either::Either; | ||
13 | |||
12 | #[derive(Clone, Debug, PartialEq, Eq)] | 14 | #[derive(Clone, Debug, PartialEq, Eq)] |
13 | pub struct InlayHintsConfig { | 15 | pub struct InlayHintsConfig { |
14 | pub type_hints: bool, | 16 | pub type_hints: bool, |
@@ -150,19 +152,22 @@ fn get_param_name_hints( | |||
150 | _ => return None, | 152 | _ => return None, |
151 | }; | 153 | }; |
152 | 154 | ||
153 | let fn_signature = get_fn_signature(sema, &expr)?; | 155 | let callable = get_callable(sema, &expr)?; |
154 | let n_params_to_skip = | 156 | let hints = callable |
155 | if fn_signature.has_self_param && matches!(&expr, ast::Expr::MethodCallExpr(_)) { | 157 | .params(sema.db) |
156 | 1 | 158 | .into_iter() |
157 | } else { | ||
158 | 0 | ||
159 | }; | ||
160 | let hints = fn_signature | ||
161 | .parameter_names | ||
162 | .iter() | ||
163 | .skip(n_params_to_skip) | ||
164 | .zip(args) | 159 | .zip(args) |
165 | .filter(|(param, arg)| should_show_param_name_hint(sema, &fn_signature, param, &arg)) | 160 | .filter_map(|((param, _ty), arg)| match param? { |
161 | Either::Left(self_param) => Some((self_param.to_string(), arg)), | ||
162 | Either::Right(pat) => { | ||
163 | let param_name = match pat { | ||
164 | ast::Pat::BindPat(it) => it.name()?.to_string(), | ||
165 | it => it.to_string(), | ||
166 | }; | ||
167 | Some((param_name, arg)) | ||
168 | } | ||
169 | }) | ||
170 | .filter(|(param_name, arg)| should_show_param_name_hint(sema, &callable, ¶m_name, &arg)) | ||
166 | .map(|(param_name, arg)| InlayHint { | 171 | .map(|(param_name, arg)| InlayHint { |
167 | range: arg.syntax().text_range(), | 172 | range: arg.syntax().text_range(), |
168 | kind: InlayKind::ParameterHint, | 173 | kind: InlayKind::ParameterHint, |
@@ -250,28 +255,28 @@ fn should_not_display_type_hint(db: &RootDatabase, bind_pat: &ast::BindPat, pat_ | |||
250 | 255 | ||
251 | fn should_show_param_name_hint( | 256 | fn should_show_param_name_hint( |
252 | sema: &Semantics<RootDatabase>, | 257 | sema: &Semantics<RootDatabase>, |
253 | fn_signature: &FunctionSignature, | 258 | callable: &Callable, |
254 | param_name: &str, | 259 | param_name: &str, |
255 | argument: &ast::Expr, | 260 | argument: &ast::Expr, |
256 | ) -> bool { | 261 | ) -> bool { |
257 | let param_name = param_name.trim_start_matches('_'); | 262 | let param_name = param_name.trim_start_matches('_'); |
263 | let fn_name = match callable.kind() { | ||
264 | hir::CallableKind::Function(it) => Some(it.name(sema.db).to_string()), | ||
265 | hir::CallableKind::TupleStruct(_) | ||
266 | | hir::CallableKind::TupleEnumVariant(_) | ||
267 | | hir::CallableKind::Closure => None, | ||
268 | }; | ||
258 | if param_name.is_empty() | 269 | if param_name.is_empty() |
259 | || Some(param_name) == fn_signature.name.as_ref().map(|s| s.trim_start_matches('_')) | 270 | || Some(param_name) == fn_name.as_ref().map(|s| s.trim_start_matches('_')) |
260 | || is_argument_similar_to_param_name(sema, argument, param_name) | 271 | || is_argument_similar_to_param_name(sema, argument, param_name) |
261 | || param_name.starts_with("ra_fixture") | 272 | || param_name.starts_with("ra_fixture") |
262 | { | 273 | { |
263 | return false; | 274 | return false; |
264 | } | 275 | } |
265 | 276 | ||
266 | let parameters_len = if fn_signature.has_self_param { | ||
267 | fn_signature.parameters.len() - 1 | ||
268 | } else { | ||
269 | fn_signature.parameters.len() | ||
270 | }; | ||
271 | |||
272 | // avoid displaying hints for common functions like map, filter, etc. | 277 | // avoid displaying hints for common functions like map, filter, etc. |
273 | // or other obvious words used in std | 278 | // or other obvious words used in std |
274 | !(parameters_len == 1 && is_obvious_param(param_name)) | 279 | !(callable.n_params() == 1 && is_obvious_param(param_name)) |
275 | } | 280 | } |
276 | 281 | ||
277 | fn is_argument_similar_to_param_name( | 282 | fn is_argument_similar_to_param_name( |
@@ -318,27 +323,10 @@ fn is_obvious_param(param_name: &str) -> bool { | |||
318 | param_name.len() == 1 || is_obvious_param_name | 323 | param_name.len() == 1 || is_obvious_param_name |
319 | } | 324 | } |
320 | 325 | ||
321 | fn get_fn_signature(sema: &Semantics<RootDatabase>, expr: &ast::Expr) -> Option<FunctionSignature> { | 326 | fn get_callable(sema: &Semantics<RootDatabase>, expr: &ast::Expr) -> Option<Callable> { |
322 | match expr { | 327 | match expr { |
323 | ast::Expr::CallExpr(expr) => { | 328 | ast::Expr::CallExpr(expr) => sema.type_of_expr(&expr.expr()?)?.as_callable(sema.db), |
324 | // FIXME: Type::as_callable is broken for closures | 329 | ast::Expr::MethodCallExpr(expr) => sema.resolve_method_call_as_callable(expr), |
325 | let callable_def = sema.type_of_expr(&expr.expr()?)?.as_callable()?; | ||
326 | match callable_def { | ||
327 | hir::CallableDef::FunctionId(it) => { | ||
328 | Some(FunctionSignature::from_hir(sema.db, it.into())) | ||
329 | } | ||
330 | hir::CallableDef::StructId(it) => { | ||
331 | FunctionSignature::from_struct(sema.db, it.into()) | ||
332 | } | ||
333 | hir::CallableDef::EnumVariantId(it) => { | ||
334 | FunctionSignature::from_enum_variant(sema.db, it.into()) | ||
335 | } | ||
336 | } | ||
337 | } | ||
338 | ast::Expr::MethodCallExpr(expr) => { | ||
339 | let fn_def = sema.resolve_method_call(&expr)?; | ||
340 | Some(FunctionSignature::from_hir(sema.db, fn_def)) | ||
341 | } | ||
342 | _ => None, | 330 | _ => None, |
343 | } | 331 | } |
344 | } | 332 | } |
@@ -351,19 +339,19 @@ mod tests { | |||
351 | use crate::{inlay_hints::InlayHintsConfig, mock_analysis::single_file}; | 339 | use crate::{inlay_hints::InlayHintsConfig, mock_analysis::single_file}; |
352 | 340 | ||
353 | fn check(ra_fixture: &str) { | 341 | fn check(ra_fixture: &str) { |
354 | check_with_config(ra_fixture, InlayHintsConfig::default()); | 342 | check_with_config(InlayHintsConfig::default(), ra_fixture); |
355 | } | 343 | } |
356 | 344 | ||
357 | fn check_with_config(ra_fixture: &str, config: InlayHintsConfig) { | 345 | fn check_with_config(config: InlayHintsConfig, ra_fixture: &str) { |
358 | let (analysis, file_id) = single_file(ra_fixture); | 346 | let (analysis, file_id) = single_file(ra_fixture); |
359 | let expected = extract_annotations(&*analysis.file_text(file_id).unwrap()); | 347 | let expected = extract_annotations(&*analysis.file_text(file_id).unwrap()); |
360 | let inlay_hints = analysis.inlay_hints(file_id, &config).unwrap(); | 348 | let inlay_hints = analysis.inlay_hints(file_id, &config).unwrap(); |
361 | let actual = | 349 | let actual = |
362 | inlay_hints.into_iter().map(|it| (it.range, it.label.to_string())).collect::<Vec<_>>(); | 350 | inlay_hints.into_iter().map(|it| (it.range, it.label.to_string())).collect::<Vec<_>>(); |
363 | assert_eq!(expected, actual); | 351 | assert_eq!(expected, actual, "\nExpected:\n{:#?}\n\nActual:\n{:#?}", expected, actual); |
364 | } | 352 | } |
365 | 353 | ||
366 | fn check_expect(ra_fixture: &str, config: InlayHintsConfig, expect: Expect) { | 354 | fn check_expect(config: InlayHintsConfig, ra_fixture: &str, expect: Expect) { |
367 | let (analysis, file_id) = single_file(ra_fixture); | 355 | let (analysis, file_id) = single_file(ra_fixture); |
368 | let inlay_hints = analysis.inlay_hints(file_id, &config).unwrap(); | 356 | let inlay_hints = analysis.inlay_hints(file_id, &config).unwrap(); |
369 | expect.assert_debug_eq(&inlay_hints) | 357 | expect.assert_debug_eq(&inlay_hints) |
@@ -372,6 +360,12 @@ mod tests { | |||
372 | #[test] | 360 | #[test] |
373 | fn param_hints_only() { | 361 | fn param_hints_only() { |
374 | check_with_config( | 362 | check_with_config( |
363 | InlayHintsConfig { | ||
364 | parameter_hints: true, | ||
365 | type_hints: false, | ||
366 | chaining_hints: false, | ||
367 | max_length: None, | ||
368 | }, | ||
375 | r#" | 369 | r#" |
376 | fn foo(a: i32, b: i32) -> i32 { a + b } | 370 | fn foo(a: i32, b: i32) -> i32 { a + b } |
377 | fn main() { | 371 | fn main() { |
@@ -382,47 +376,41 @@ fn main() { | |||
382 | //^ b | 376 | //^ b |
383 | ); | 377 | ); |
384 | }"#, | 378 | }"#, |
385 | InlayHintsConfig { | ||
386 | parameter_hints: true, | ||
387 | type_hints: false, | ||
388 | chaining_hints: false, | ||
389 | max_length: None, | ||
390 | }, | ||
391 | ); | 379 | ); |
392 | } | 380 | } |
393 | 381 | ||
394 | #[test] | 382 | #[test] |
395 | fn hints_disabled() { | 383 | fn hints_disabled() { |
396 | check_with_config( | 384 | check_with_config( |
397 | r#" | ||
398 | fn foo(a: i32, b: i32) -> i32 { a + b } | ||
399 | fn main() { | ||
400 | let _x = foo(4, 4); | ||
401 | }"#, | ||
402 | InlayHintsConfig { | 385 | InlayHintsConfig { |
403 | type_hints: false, | 386 | type_hints: false, |
404 | parameter_hints: false, | 387 | parameter_hints: false, |
405 | chaining_hints: false, | 388 | chaining_hints: false, |
406 | max_length: None, | 389 | max_length: None, |
407 | }, | 390 | }, |
391 | r#" | ||
392 | fn foo(a: i32, b: i32) -> i32 { a + b } | ||
393 | fn main() { | ||
394 | let _x = foo(4, 4); | ||
395 | }"#, | ||
408 | ); | 396 | ); |
409 | } | 397 | } |
410 | 398 | ||
411 | #[test] | 399 | #[test] |
412 | fn type_hints_only() { | 400 | fn type_hints_only() { |
413 | check_with_config( | 401 | check_with_config( |
414 | r#" | ||
415 | fn foo(a: i32, b: i32) -> i32 { a + b } | ||
416 | fn main() { | ||
417 | let _x = foo(4, 4); | ||
418 | //^^ i32 | ||
419 | }"#, | ||
420 | InlayHintsConfig { | 402 | InlayHintsConfig { |
421 | type_hints: true, | 403 | type_hints: true, |
422 | parameter_hints: false, | 404 | parameter_hints: false, |
423 | chaining_hints: false, | 405 | chaining_hints: false, |
424 | max_length: None, | 406 | max_length: None, |
425 | }, | 407 | }, |
408 | r#" | ||
409 | fn foo(a: i32, b: i32) -> i32 { a + b } | ||
410 | fn main() { | ||
411 | let _x = foo(4, 4); | ||
412 | //^^ i32 | ||
413 | }"#, | ||
426 | ); | 414 | ); |
427 | } | 415 | } |
428 | 416 | ||
@@ -437,6 +425,8 @@ fn main() { | |||
437 | //^^ Test<i32> | 425 | //^^ Test<i32> |
438 | let zz_ref = &zz; | 426 | let zz_ref = &zz; |
439 | //^^^^^^ &Test<i32> | 427 | //^^^^^^ &Test<i32> |
428 | let test = || zz; | ||
429 | //^^^^ || -> Test<i32> | ||
440 | }"#, | 430 | }"#, |
441 | ); | 431 | ); |
442 | } | 432 | } |
@@ -590,6 +580,7 @@ fn main() { | |||
590 | #[test] | 580 | #[test] |
591 | fn hint_truncation() { | 581 | fn hint_truncation() { |
592 | check_with_config( | 582 | check_with_config( |
583 | InlayHintsConfig { max_length: Some(8), ..Default::default() }, | ||
593 | r#" | 584 | r#" |
594 | struct Smol<T>(T); | 585 | struct Smol<T>(T); |
595 | 586 | ||
@@ -603,7 +594,6 @@ fn main() { | |||
603 | let c = Smol(Smol(0u32)) | 594 | let c = Smol(Smol(0u32)) |
604 | //^ Smol<Smol<…>> | 595 | //^ Smol<Smol<…>> |
605 | }"#, | 596 | }"#, |
606 | InlayHintsConfig { max_length: Some(8), ..Default::default() }, | ||
607 | ); | 597 | ); |
608 | } | 598 | } |
609 | 599 | ||
@@ -673,6 +663,7 @@ fn main() { | |||
673 | #[test] | 663 | #[test] |
674 | fn omitted_parameters_hints_heuristics() { | 664 | fn omitted_parameters_hints_heuristics() { |
675 | check_with_config( | 665 | check_with_config( |
666 | InlayHintsConfig { max_length: Some(8), ..Default::default() }, | ||
676 | r#" | 667 | r#" |
677 | fn map(f: i32) {} | 668 | fn map(f: i32) {} |
678 | fn filter(predicate: i32) {} | 669 | fn filter(predicate: i32) {} |
@@ -753,13 +744,13 @@ fn main() { | |||
753 | let _: f64 = a.div_euclid(b); | 744 | let _: f64 = a.div_euclid(b); |
754 | let _: f64 = a.abs_sub(b); | 745 | let _: f64 = a.abs_sub(b); |
755 | }"#, | 746 | }"#, |
756 | InlayHintsConfig { max_length: Some(8), ..Default::default() }, | ||
757 | ); | 747 | ); |
758 | } | 748 | } |
759 | 749 | ||
760 | #[test] | 750 | #[test] |
761 | fn unit_structs_have_no_type_hints() { | 751 | fn unit_structs_have_no_type_hints() { |
762 | check_with_config( | 752 | check_with_config( |
753 | InlayHintsConfig { max_length: Some(8), ..Default::default() }, | ||
763 | r#" | 754 | r#" |
764 | enum Result<T, E> { Ok(T), Err(E) } | 755 | enum Result<T, E> { Ok(T), Err(E) } |
765 | use Result::*; | 756 | use Result::*; |
@@ -772,13 +763,18 @@ fn main() { | |||
772 | Err(SyntheticSyntax) => (), | 763 | Err(SyntheticSyntax) => (), |
773 | } | 764 | } |
774 | }"#, | 765 | }"#, |
775 | InlayHintsConfig { max_length: Some(8), ..Default::default() }, | ||
776 | ); | 766 | ); |
777 | } | 767 | } |
778 | 768 | ||
779 | #[test] | 769 | #[test] |
780 | fn chaining_hints_ignore_comments() { | 770 | fn chaining_hints_ignore_comments() { |
781 | check_expect( | 771 | check_expect( |
772 | InlayHintsConfig { | ||
773 | parameter_hints: false, | ||
774 | type_hints: false, | ||
775 | chaining_hints: true, | ||
776 | max_length: None, | ||
777 | }, | ||
782 | r#" | 778 | r#" |
783 | struct A(B); | 779 | struct A(B); |
784 | impl A { fn into_b(self) -> B { self.0 } } | 780 | impl A { fn into_b(self) -> B { self.0 } } |
@@ -792,12 +788,6 @@ fn main() { | |||
792 | .into_c(); | 788 | .into_c(); |
793 | } | 789 | } |
794 | "#, | 790 | "#, |
795 | InlayHintsConfig { | ||
796 | parameter_hints: false, | ||
797 | type_hints: false, | ||
798 | chaining_hints: true, | ||
799 | max_length: None, | ||
800 | }, | ||
801 | expect![[r#" | 791 | expect![[r#" |
802 | [ | 792 | [ |
803 | InlayHint { | 793 | InlayHint { |
@@ -818,6 +808,12 @@ fn main() { | |||
818 | #[test] | 808 | #[test] |
819 | fn chaining_hints_without_newlines() { | 809 | fn chaining_hints_without_newlines() { |
820 | check_with_config( | 810 | check_with_config( |
811 | InlayHintsConfig { | ||
812 | parameter_hints: false, | ||
813 | type_hints: false, | ||
814 | chaining_hints: true, | ||
815 | max_length: None, | ||
816 | }, | ||
821 | r#" | 817 | r#" |
822 | struct A(B); | 818 | struct A(B); |
823 | impl A { fn into_b(self) -> B { self.0 } } | 819 | impl A { fn into_b(self) -> B { self.0 } } |
@@ -828,18 +824,18 @@ struct C; | |||
828 | fn main() { | 824 | fn main() { |
829 | let c = A(B(C)).into_b().into_c(); | 825 | let c = A(B(C)).into_b().into_c(); |
830 | }"#, | 826 | }"#, |
831 | InlayHintsConfig { | ||
832 | parameter_hints: false, | ||
833 | type_hints: false, | ||
834 | chaining_hints: true, | ||
835 | max_length: None, | ||
836 | }, | ||
837 | ); | 827 | ); |
838 | } | 828 | } |
839 | 829 | ||
840 | #[test] | 830 | #[test] |
841 | fn struct_access_chaining_hints() { | 831 | fn struct_access_chaining_hints() { |
842 | check_expect( | 832 | check_expect( |
833 | InlayHintsConfig { | ||
834 | parameter_hints: false, | ||
835 | type_hints: false, | ||
836 | chaining_hints: true, | ||
837 | max_length: None, | ||
838 | }, | ||
843 | r#" | 839 | r#" |
844 | struct A { pub b: B } | 840 | struct A { pub b: B } |
845 | struct B { pub c: C } | 841 | struct B { pub c: C } |
@@ -858,12 +854,6 @@ fn main() { | |||
858 | let x = D | 854 | let x = D |
859 | .foo(); | 855 | .foo(); |
860 | }"#, | 856 | }"#, |
861 | InlayHintsConfig { | ||
862 | parameter_hints: false, | ||
863 | type_hints: false, | ||
864 | chaining_hints: true, | ||
865 | max_length: None, | ||
866 | }, | ||
867 | expect![[r#" | 857 | expect![[r#" |
868 | [ | 858 | [ |
869 | InlayHint { | 859 | InlayHint { |
@@ -884,6 +874,12 @@ fn main() { | |||
884 | #[test] | 874 | #[test] |
885 | fn generic_chaining_hints() { | 875 | fn generic_chaining_hints() { |
886 | check_expect( | 876 | check_expect( |
877 | InlayHintsConfig { | ||
878 | parameter_hints: false, | ||
879 | type_hints: false, | ||
880 | chaining_hints: true, | ||
881 | max_length: None, | ||
882 | }, | ||
887 | r#" | 883 | r#" |
888 | struct A<T>(T); | 884 | struct A<T>(T); |
889 | struct B<T>(T); | 885 | struct B<T>(T); |
@@ -903,12 +899,6 @@ fn main() { | |||
903 | .into_c(); | 899 | .into_c(); |
904 | } | 900 | } |
905 | "#, | 901 | "#, |
906 | InlayHintsConfig { | ||
907 | parameter_hints: false, | ||
908 | type_hints: false, | ||
909 | chaining_hints: true, | ||
910 | max_length: None, | ||
911 | }, | ||
912 | expect![[r#" | 902 | expect![[r#" |
913 | [ | 903 | [ |
914 | InlayHint { | 904 | InlayHint { |