aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_ide_api
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_ide_api')
-rw-r--r--crates/ra_ide_api/src/inlay_hints.rs180
-rw-r--r--crates/ra_ide_api/src/lib.rs7
2 files changed, 187 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..174662beb
--- /dev/null
+++ b/crates/ra_ide_api/src/inlay_hints.rs
@@ -0,0 +1,180 @@
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 LetBindingType,
13 ClosureParameterType,
14}
15
16#[derive(Debug)]
17pub struct InlayHint {
18 pub range: TextRange,
19 pub kind: InlayKind,
20 pub label: SmolStr,
21}
22
23pub(crate) fn inlay_hints(db: &RootDatabase, file_id: FileId, file: &SourceFile) -> Vec<InlayHint> {
24 file.syntax()
25 .descendants()
26 .map(|node| get_inlay_hints(db, file_id, &node).unwrap_or_default())
27 .flatten()
28 .collect()
29}
30
31fn get_inlay_hints(
32 db: &RootDatabase,
33 file_id: FileId,
34 node: &SyntaxNode,
35) -> Option<Vec<InlayHint>> {
36 visitor()
37 .visit(|let_statement: ast::LetStmt| {
38 let let_syntax = let_statement.syntax();
39
40 if let_statement.ascribed_type().is_some() {
41 return None;
42 }
43
44 let let_pat = let_statement.pat()?;
45 let inlay_type_string = get_node_displayable_type(db, file_id, let_syntax, &let_pat)?
46 .display(db)
47 .to_string()
48 .into();
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 kind: InlayKind::LetBindingType,
59 label: inlay_type_string,
60 }])
61 })
62 .visit(|closure_parameter: ast::LambdaExpr| match closure_parameter.param_list() {
63 Some(param_list) => Some(
64 param_list
65 .params()
66 .filter(|closure_param| closure_param.ascribed_type().is_none())
67 .filter_map(|closure_param| {
68 let closure_param_syntax = closure_param.syntax();
69 let inlay_type_string = get_node_displayable_type(
70 db,
71 file_id,
72 closure_param_syntax,
73 &closure_param.pat()?,
74 )?
75 .display(db)
76 .to_string()
77 .into();
78
79 Some(InlayHint {
80 range: closure_param_syntax.text_range(),
81 kind: InlayKind::ClosureParameterType,
82 label: 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 kind: LetBindingType,
150 label: "i32",
151 },
152 InlayHint {
153 range: [121; 125),
154 kind: LetBindingType,
155 label: "OuterStruct",
156 },
157 InlayHint {
158 range: [297; 305),
159 kind: LetBindingType,
160 label: "i32",
161 },
162 InlayHint {
163 range: [417; 426),
164 kind: LetBindingType,
165 label: "u32",
166 },
167 InlayHint {
168 range: [496; 502),
169 kind: LetBindingType,
170 label: "(i32, char)",
171 },
172 InlayHint {
173 range: [524; 528),
174 kind: LetBindingType,
175 label: "(i32, char)",
176 },
177]"#
178 );
179 }
180}
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);