diff options
Diffstat (limited to 'crates/ra_ide/src/inlay_hints.rs')
-rw-r--r-- | crates/ra_ide/src/inlay_hints.rs | 207 |
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 | ||
3 | use hir::{HirDisplay, SourceAnalyzer}; | 3 | use hir::{HirDisplay, SourceAnalyzer, SourceBinder}; |
4 | use once_cell::unsync::Lazy; | 4 | use once_cell::unsync::Lazy; |
5 | use ra_prof::profile; | 5 | use ra_prof::profile; |
6 | use ra_syntax::{ | 6 | use 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 | ||
11 | use crate::{db::RootDatabase, FileId}; | 11 | use crate::{db::RootDatabase, FileId, FunctionSignature}; |
12 | 12 | ||
13 | #[derive(Debug, PartialEq, Eq)] | 13 | #[derive(Debug, PartialEq, Eq)] |
14 | pub enum InlayKind { | 14 | pub 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 | ||
38 | fn get_inlay_hints( | 40 | fn 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 | |||
98 | fn 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 | |||
135 | fn 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 | ||
95 | fn get_pat_type_hints( | 163 | fn 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 | ||
122 | fn get_leaf_pats(root_pat: ast::Pat) -> Vec<ast::Pat> { | 192 | fn 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#" | ||
683 | struct Test {} | ||
684 | |||
685 | impl Test { | ||
686 | fn method(&self, param: i32) -> i32 { | ||
687 | param * 2 | ||
688 | } | ||
689 | } | ||
690 | |||
691 | fn test_func(foo: i32, bar: i32, msg: &str, _: i32, last: i32) -> i32 { | ||
692 | foo + bar | ||
693 | } | ||
694 | |||
695 | fn 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 | } |