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.rs207
1 files changed, 172 insertions, 35 deletions
diff --git a/crates/ra_ide/src/inlay_hints.rs b/crates/ra_ide/src/inlay_hints.rs
index 977aafc51..1b631c7cd 100644
--- a/crates/ra_ide/src/inlay_hints.rs
+++ b/crates/ra_ide/src/inlay_hints.rs
@@ -1,18 +1,19 @@
1//! FIXME: write short doc here 1//! FIXME: write short doc here
2 2
3use hir::{HirDisplay, SourceAnalyzer}; 3use hir::{HirDisplay, SourceAnalyzer, SourceBinder};
4use once_cell::unsync::Lazy; 4use once_cell::unsync::Lazy;
5use ra_prof::profile; 5use ra_prof::profile;
6use ra_syntax::{ 6use ra_syntax::{
7 ast::{self, AstNode, TypeAscriptionOwner}, 7 ast::{self, ArgListOwner, AstNode, TypeAscriptionOwner},
8 match_ast, SmolStr, SourceFile, SyntaxKind, SyntaxNode, TextRange, 8 match_ast, SmolStr, SourceFile, SyntaxKind, SyntaxNode, TextRange,
9}; 9};
10 10
11use crate::{db::RootDatabase, FileId}; 11use crate::{db::RootDatabase, FileId, FunctionSignature};
12 12
13#[derive(Debug, PartialEq, Eq)] 13#[derive(Debug, PartialEq, Eq)]
14pub enum InlayKind { 14pub enum InlayKind {
15 TypeHint, 15 TypeHint,
16 ParameterHint,
16} 17}
17 18
18#[derive(Debug)] 19#[derive(Debug)]
@@ -28,22 +29,24 @@ pub(crate) fn inlay_hints(
28 file: &SourceFile, 29 file: &SourceFile,
29 max_inlay_hint_length: Option<usize>, 30 max_inlay_hint_length: Option<usize>,
30) -> Vec<InlayHint> { 31) -> Vec<InlayHint> {
31 file.syntax() 32 let mut sb = SourceBinder::new(db);
32 .descendants() 33 let mut res = Vec::new();
33 .flat_map(|node| get_inlay_hints(db, file_id, &node, max_inlay_hint_length)) 34 for node in file.syntax().descendants() {
34 .flatten() 35 get_inlay_hints(&mut res, &mut sb, file_id, &node, max_inlay_hint_length);
35 .collect() 36 }
37 res
36} 38}
37 39
38fn get_inlay_hints( 40fn get_inlay_hints(
39 db: &RootDatabase, 41 acc: &mut Vec<InlayHint>,
42 sb: &mut SourceBinder<RootDatabase>,
40 file_id: FileId, 43 file_id: FileId,
41 node: &SyntaxNode, 44 node: &SyntaxNode,
42 max_inlay_hint_length: Option<usize>, 45 max_inlay_hint_length: Option<usize>,
43) -> Option<Vec<InlayHint>> { 46) -> Option<()> {
44 let _p = profile("get_inlay_hints"); 47 let _p = profile("get_inlay_hints");
45 let analyzer = 48 let db = sb.db;
46 Lazy::new(|| SourceAnalyzer::new(db, hir::InFile::new(file_id.into(), node), None)); 49 let analyzer = Lazy::new(move || sb.analyze(hir::InFile::new(file_id.into(), node), None));
47 match_ast! { 50 match_ast! {
48 match node { 51 match node {
49 ast::LetStmt(it) => { 52 ast::LetStmt(it) => {
@@ -51,7 +54,7 @@ fn get_inlay_hints(
51 return None; 54 return None;
52 } 55 }
53 let pat = it.pat()?; 56 let pat = it.pat()?;
54 Some(get_pat_type_hints(db, &analyzer, pat, false, max_inlay_hint_length)) 57 get_pat_type_hints(acc, db, &analyzer, pat, false, max_inlay_hint_length);
55 }, 58 },
56 ast::LambdaExpr(it) => { 59 ast::LambdaExpr(it) => {
57 it.param_list().map(|param_list| { 60 it.param_list().map(|param_list| {
@@ -59,49 +62,115 @@ fn get_inlay_hints(
59 .params() 62 .params()
60 .filter(|closure_param| closure_param.ascribed_type().is_none()) 63 .filter(|closure_param| closure_param.ascribed_type().is_none())
61 .filter_map(|closure_param| closure_param.pat()) 64 .filter_map(|closure_param| closure_param.pat())
62 .map(|root_pat| get_pat_type_hints(db, &analyzer, root_pat, false, max_inlay_hint_length)) 65 .for_each(|root_pat| get_pat_type_hints(acc, db, &analyzer, root_pat, false, max_inlay_hint_length))
63 .flatten() 66 });
64 .collect()
65 })
66 }, 67 },
67 ast::ForExpr(it) => { 68 ast::ForExpr(it) => {
68 let pat = it.pat()?; 69 let pat = it.pat()?;
69 Some(get_pat_type_hints(db, &analyzer, pat, false, max_inlay_hint_length)) 70 get_pat_type_hints(acc, db, &analyzer, pat, false, max_inlay_hint_length);
70 }, 71 },
71 ast::IfExpr(it) => { 72 ast::IfExpr(it) => {
72 let pat = it.condition()?.pat()?; 73 let pat = it.condition()?.pat()?;
73 Some(get_pat_type_hints(db, &analyzer, pat, true, max_inlay_hint_length)) 74 get_pat_type_hints(acc, db, &analyzer, pat, true, max_inlay_hint_length);
74 }, 75 },
75 ast::WhileExpr(it) => { 76 ast::WhileExpr(it) => {
76 let pat = it.condition()?.pat()?; 77 let pat = it.condition()?.pat()?;
77 Some(get_pat_type_hints(db, &analyzer, pat, true, max_inlay_hint_length)) 78 get_pat_type_hints(acc, db, &analyzer, pat, true, max_inlay_hint_length);
78 }, 79 },
79 ast::MatchArmList(it) => { 80 ast::MatchArmList(it) => {
80 Some( 81 it.arms()
81 it 82 .map(|match_arm| match_arm.pats())
82 .arms() 83 .flatten()
83 .map(|match_arm| match_arm.pats()) 84 .for_each(|root_pat| get_pat_type_hints(acc, db, &analyzer, root_pat, true, max_inlay_hint_length));
84 .flatten() 85 },
85 .map(|root_pat| get_pat_type_hints(db, &analyzer, root_pat, true, max_inlay_hint_length)) 86 ast::CallExpr(it) => {
86 .flatten() 87 get_param_name_hints(acc, db, &analyzer, ast::Expr::from(it));
87 .collect(), 88 },
88 ) 89 ast::MethodCallExpr(it) => {
89 }, 90 get_param_name_hints(acc, db, &analyzer, ast::Expr::from(it));
90 _ => None, 91 },
92 _ => (),
91 } 93 }
94 };
95 Some(())
96}
97
98fn get_param_name_hints(
99 acc: &mut Vec<InlayHint>,
100 db: &RootDatabase,
101 analyzer: &SourceAnalyzer,
102 expr: ast::Expr,
103) -> Option<()> {
104 let args = match &expr {
105 ast::Expr::CallExpr(expr) => expr.arg_list()?.args(),
106 ast::Expr::MethodCallExpr(expr) => expr.arg_list()?.args(),
107 _ => return None,
108 };
109
110 let mut parameters = get_fn_signature(db, analyzer, &expr)?.parameter_names.into_iter();
111
112 if let ast::Expr::MethodCallExpr(_) = &expr {
113 parameters.next();
114 };
115
116 let hints = parameters
117 .zip(args)
118 .filter_map(|(param, arg)| {
119 if arg.syntax().kind() == SyntaxKind::LITERAL {
120 Some((arg.syntax().text_range(), param))
121 } else {
122 None
123 }
124 })
125 .map(|(range, param_name)| InlayHint {
126 range,
127 kind: InlayKind::ParameterHint,
128 label: param_name.into(),
129 });
130
131 acc.extend(hints);
132 Some(())
133}
134
135fn get_fn_signature(
136 db: &RootDatabase,
137 analyzer: &SourceAnalyzer,
138 expr: &ast::Expr,
139) -> Option<FunctionSignature> {
140 match expr {
141 ast::Expr::CallExpr(expr) => {
142 // FIXME: Type::as_callable is broken for closures
143 let callable_def = analyzer.type_of(db, &expr.expr()?)?.as_callable()?;
144 match callable_def {
145 hir::CallableDef::FunctionId(it) => {
146 let fn_def = it.into();
147 Some(FunctionSignature::from_hir(db, fn_def))
148 }
149 hir::CallableDef::StructId(it) => FunctionSignature::from_struct(db, it.into()),
150 hir::CallableDef::EnumVariantId(it) => {
151 FunctionSignature::from_enum_variant(db, it.into())
152 }
153 }
154 }
155 ast::Expr::MethodCallExpr(expr) => {
156 let fn_def = analyzer.resolve_method_call(&expr)?;
157 Some(FunctionSignature::from_hir(db, fn_def))
158 }
159 _ => None,
92 } 160 }
93} 161}
94 162
95fn get_pat_type_hints( 163fn get_pat_type_hints(
164 acc: &mut Vec<InlayHint>,
96 db: &RootDatabase, 165 db: &RootDatabase,
97 analyzer: &SourceAnalyzer, 166 analyzer: &SourceAnalyzer,
98 root_pat: ast::Pat, 167 root_pat: ast::Pat,
99 skip_root_pat_hint: bool, 168 skip_root_pat_hint: bool,
100 max_inlay_hint_length: Option<usize>, 169 max_inlay_hint_length: Option<usize>,
101) -> Vec<InlayHint> { 170) {
102 let original_pat = &root_pat.clone(); 171 let original_pat = &root_pat.clone();
103 172
104 get_leaf_pats(root_pat) 173 let hints = get_leaf_pats(root_pat)
105 .into_iter() 174 .into_iter()
106 .filter(|pat| !skip_root_pat_hint || pat != original_pat) 175 .filter(|pat| !skip_root_pat_hint || pat != original_pat)
107 .filter_map(|pat| { 176 .filter_map(|pat| {
@@ -115,8 +184,9 @@ fn get_pat_type_hints(
115 range, 184 range,
116 kind: InlayKind::TypeHint, 185 kind: InlayKind::TypeHint,
117 label: pat_type.display_truncated(db, max_inlay_hint_length).to_string().into(), 186 label: pat_type.display_truncated(db, max_inlay_hint_length).to_string().into(),
118 }) 187 });
119 .collect() 188
189 acc.extend(hints);
120} 190}
121 191
122fn get_leaf_pats(root_pat: ast::Pat) -> Vec<ast::Pat> { 192fn get_leaf_pats(root_pat: ast::Pat) -> Vec<ast::Pat> {
@@ -605,4 +675,71 @@ fn main() {
605 "### 675 "###
606 ); 676 );
607 } 677 }
678
679 #[test]
680 fn function_call_parameter_hint() {
681 let (analysis, file_id) = single_file(
682 r#"
683struct Test {}
684
685impl Test {
686 fn method(&self, param: i32) -> i32 {
687 param * 2
688 }
689}
690
691fn test_func(foo: i32, bar: i32, msg: &str, _: i32, last: i32) -> i32 {
692 foo + bar
693}
694
695fn main() {
696 let not_literal = 1;
697 let _: i32 = test_func(1, 2, "hello", 3, not_literal);
698 let t: Test = Test {};
699 t.method(123);
700 Test::method(&t, 3456);
701}"#,
702 );
703
704 assert_debug_snapshot!(analysis.inlay_hints(file_id, None).unwrap(), @r###"
705 [
706 InlayHint {
707 range: [207; 218),
708 kind: TypeHint,
709 label: "i32",
710 },
711 InlayHint {
712 range: [251; 252),
713 kind: ParameterHint,
714 label: "foo",
715 },
716 InlayHint {
717 range: [254; 255),
718 kind: ParameterHint,
719 label: "bar",
720 },
721 InlayHint {
722 range: [257; 264),
723 kind: ParameterHint,
724 label: "msg",
725 },
726 InlayHint {
727 range: [266; 267),
728 kind: ParameterHint,
729 label: "_",
730 },
731 InlayHint {
732 range: [323; 326),
733 kind: ParameterHint,
734 label: "param",
735 },
736 InlayHint {
737 range: [350; 354),
738 kind: ParameterHint,
739 label: "param",
740 },
741 ]
742 "###
743 );
744 }
608} 745}