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.rs174
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 @@
1use hir::{Adt, HirDisplay, Semantics, Type}; 1use hir::{Adt, Callable, HirDisplay, Semantics, Type};
2use ra_ide_db::RootDatabase; 2use ra_ide_db::RootDatabase;
3use ra_prof::profile; 3use ra_prof::profile;
4use ra_syntax::{ 4use 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
9use crate::{FileId, FunctionSignature};
10use stdx::to_lower_snake_case; 8use stdx::to_lower_snake_case;
11 9
10use crate::FileId;
11use ast::NameOwner;
12use either::Either;
13
12#[derive(Clone, Debug, PartialEq, Eq)] 14#[derive(Clone, Debug, PartialEq, Eq)]
13pub struct InlayHintsConfig { 15pub 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, &param_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
251fn should_show_param_name_hint( 256fn 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
277fn is_argument_similar_to_param_name( 282fn 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
321fn get_fn_signature(sema: &Semantics<RootDatabase>, expr: &ast::Expr) -> Option<FunctionSignature> { 326fn 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#"
376fn foo(a: i32, b: i32) -> i32 { a + b } 370fn foo(a: i32, b: i32) -> i32 { a + b }
377fn main() { 371fn 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#"
398fn foo(a: i32, b: i32) -> i32 { a + b }
399fn 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#"
392fn foo(a: i32, b: i32) -> i32 { a + b }
393fn 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#"
415fn foo(a: i32, b: i32) -> i32 { a + b }
416fn 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#"
409fn foo(a: i32, b: i32) -> i32 { a + b }
410fn 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#"
594struct Smol<T>(T); 585struct 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#"
677fn map(f: i32) {} 668fn map(f: i32) {}
678fn filter(predicate: i32) {} 669fn 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#"
764enum Result<T, E> { Ok(T), Err(E) } 755enum Result<T, E> { Ok(T), Err(E) }
765use Result::*; 756use 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#"
783struct A(B); 779struct A(B);
784impl A { fn into_b(self) -> B { self.0 } } 780impl 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#"
822struct A(B); 818struct A(B);
823impl A { fn into_b(self) -> B { self.0 } } 819impl A { fn into_b(self) -> B { self.0 } }
@@ -828,18 +824,18 @@ struct C;
828fn main() { 824fn 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#"
844struct A { pub b: B } 840struct A { pub b: B }
845struct B { pub c: C } 841struct 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#"
888struct A<T>(T); 884struct A<T>(T);
889struct B<T>(T); 885struct 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 {