aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--crates/ra_ide_api/src/inlay_hints.rs186
-rw-r--r--crates/ra_ide_api/src/lib.rs7
2 files changed, 193 insertions, 0 deletions
diff --git a/crates/ra_ide_api/src/inlay_hints.rs b/crates/ra_ide_api/src/inlay_hints.rs
new file mode 100644
index 000000000..7d49ebcf4
--- /dev/null
+++ b/crates/ra_ide_api/src/inlay_hints.rs
@@ -0,0 +1,186 @@
1use crate::{db::RootDatabase, FileId};
2use hir::{HirDisplay, Ty};
3use ra_syntax::ast::Pat;
4use ra_syntax::{
5 algo::visit::{visitor, Visitor},
6 ast::{self, PatKind, TypeAscriptionOwner},
7 AstNode, SmolStr, SourceFile, SyntaxNode, TextRange,
8};
9
10#[derive(Debug, PartialEq, Eq)]
11pub enum InlayKind {
12 LetBinding,
13 ClosureParameter,
14}
15
16#[derive(Debug)]
17pub struct InlayHint {
18 pub range: TextRange,
19 pub text: SmolStr,
20 pub inlay_kind: InlayKind,
21 pub inlay_type_string: String,
22}
23
24pub(crate) fn inlay_hints(db: &RootDatabase, file_id: FileId, file: &SourceFile) -> Vec<InlayHint> {
25 file.syntax()
26 .descendants()
27 .map(|node| get_inlay_hints(db, file_id, &node).unwrap_or_default())
28 .flatten()
29 .collect()
30}
31
32fn get_inlay_hints(
33 db: &RootDatabase,
34 file_id: FileId,
35 node: &SyntaxNode,
36) -> Option<Vec<InlayHint>> {
37 visitor()
38 .visit(|let_statement: ast::LetStmt| {
39 let let_syntax = let_statement.syntax();
40
41 if let_statement.ascribed_type().is_some() {
42 return None;
43 }
44
45 let let_pat = let_statement.pat()?;
46 let inlay_type_string = get_node_displayable_type(db, file_id, let_syntax, &let_pat)?
47 .display(db)
48 .to_string();;
49
50 let pat_range = match let_pat.kind() {
51 PatKind::BindPat(bind_pat) => bind_pat.syntax().text_range(),
52 PatKind::TuplePat(tuple_pat) => tuple_pat.syntax().text_range(),
53 _ => return None,
54 };
55
56 Some(vec![InlayHint {
57 range: pat_range,
58 text: let_syntax.text().to_smol_string(),
59 inlay_kind: InlayKind::LetBinding,
60 inlay_type_string,
61 }])
62 })
63 .visit(|closure_parameter: ast::LambdaExpr| match closure_parameter.param_list() {
64 Some(param_list) => Some(
65 param_list
66 .params()
67 .filter(|closure_param| closure_param.ascribed_type().is_none())
68 .filter_map(|closure_param| {
69 let closure_param_syntax = closure_param.syntax();
70 let inlay_type_string = get_node_displayable_type(
71 db,
72 file_id,
73 closure_param_syntax,
74 &closure_param.pat()?,
75 )?
76 .display(db)
77 .to_string();
78 Some(InlayHint {
79 range: closure_param_syntax.text_range(),
80 text: closure_param_syntax.text().to_smol_string(),
81 inlay_kind: InlayKind::ClosureParameter,
82 inlay_type_string,
83 })
84 })
85 .collect(),
86 ),
87 None => None,
88 })
89 .accept(&node)?
90}
91
92fn get_node_displayable_type(
93 db: &RootDatabase,
94 file_id: FileId,
95 node_syntax: &SyntaxNode,
96 node_pat: &Pat,
97) -> Option<Ty> {
98 let analyzer = hir::SourceAnalyzer::new(db, file_id, node_syntax, None);
99 analyzer.type_of_pat(db, node_pat).and_then(|resolved_type| {
100 if let Ty::Apply(_) = resolved_type {
101 Some(resolved_type)
102 } else {
103 None
104 }
105 })
106}
107
108#[cfg(test)]
109mod tests {
110 use crate::mock_analysis::single_file;
111 use insta::assert_debug_snapshot_matches;
112
113 #[test]
114 fn test_inlay_hints() {
115 let (analysis, file_id) = single_file(
116 r#"
117struct OuterStruct {}
118
119fn main() {
120 struct InnerStruct {}
121
122 let test = 54;
123 let test = InnerStruct {};
124 let test = OuterStruct {};
125 let test = vec![222];
126 let mut test = Vec::new();
127 test.push(333);
128 let test = test.into_iter().map(|i| i * i).collect::<Vec<_>>();
129 let mut test = 33;
130 let _ = 22;
131 let test: Vec<_> = (0..3).collect();
132
133 let _ = (0..23).map(|i: u32| {
134 let i_squared = i * i;
135 i_squared
136 });
137
138 let test: i32 = 33;
139
140 let (x, c) = (42, 'a');
141 let test = (42, 'a');
142}
143"#,
144 );
145
146 assert_debug_snapshot_matches!(analysis.inlay_hints(file_id).unwrap(), @r#"[
147 InlayHint {
148 range: [71; 75),
149 text: "let test = 54;",
150 inlay_kind: LetBinding,
151 inlay_type_string: "i32",
152 },
153 InlayHint {
154 range: [121; 125),
155 text: "let test = OuterStruct {};",
156 inlay_kind: LetBinding,
157 inlay_type_string: "OuterStruct",
158 },
159 InlayHint {
160 range: [297; 305),
161 text: "let mut test = 33;",
162 inlay_kind: LetBinding,
163 inlay_type_string: "i32",
164 },
165 InlayHint {
166 range: [417; 426),
167 text: "let i_squared = i * i;",
168 inlay_kind: LetBinding,
169 inlay_type_string: "u32",
170 },
171 InlayHint {
172 range: [496; 502),
173 text: "let (x, c) = (42, \'a\');",
174 inlay_kind: LetBinding,
175 inlay_type_string: "(i32, char)",
176 },
177 InlayHint {
178 range: [524; 528),
179 text: "let test = (42, \'a\');",
180 inlay_kind: LetBinding,
181 inlay_type_string: "(i32, char)",
182 },
183]"#
184 );
185 }
186}
diff --git a/crates/ra_ide_api/src/lib.rs b/crates/ra_ide_api/src/lib.rs
index c54d574bc..16ffb03ce 100644
--- a/crates/ra_ide_api/src/lib.rs
+++ b/crates/ra_ide_api/src/lib.rs
@@ -38,6 +38,7 @@ mod join_lines;
38mod typing; 38mod typing;
39mod matching_brace; 39mod matching_brace;
40mod display; 40mod display;
41mod inlay_hints;
41 42
42#[cfg(test)] 43#[cfg(test)]
43mod marks; 44mod marks;
@@ -64,6 +65,7 @@ pub use crate::{
64 display::{file_structure, FunctionSignature, NavigationTarget, StructureNode}, 65 display::{file_structure, FunctionSignature, NavigationTarget, StructureNode},
65 folding_ranges::{Fold, FoldKind}, 66 folding_ranges::{Fold, FoldKind},
66 hover::HoverResult, 67 hover::HoverResult,
68 inlay_hints::{InlayHint, InlayKind},
67 line_index::{LineCol, LineIndex}, 69 line_index::{LineCol, LineIndex},
68 line_index_utils::translate_offset_with_edit, 70 line_index_utils::translate_offset_with_edit,
69 references::ReferenceSearchResult, 71 references::ReferenceSearchResult,
@@ -396,6 +398,11 @@ impl Analysis {
396 file_structure(&parse.tree()) 398 file_structure(&parse.tree())
397 } 399 }
398 400
401 /// Returns a list of the places in the file where type hints can be displayed.
402 pub fn inlay_hints(&self, file_id: FileId) -> Cancelable<Vec<InlayHint>> {
403 self.with_db(|db| inlay_hints::inlay_hints(db, file_id, &db.parse(file_id).tree()))
404 }
405
399 /// Returns the set of folding ranges. 406 /// Returns the set of folding ranges.
400 pub fn folding_ranges(&self, file_id: FileId) -> Vec<Fold> { 407 pub fn folding_ranges(&self, file_id: FileId) -> Vec<Fold> {
401 let parse = self.db.parse(file_id); 408 let parse = self.db.parse(file_id);