aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--crates/ra_ide/src/inlay_hints.rs235
1 files changed, 159 insertions, 76 deletions
diff --git a/crates/ra_ide/src/inlay_hints.rs b/crates/ra_ide/src/inlay_hints.rs
index 2353ad71f..293944206 100644
--- a/crates/ra_ide/src/inlay_hints.rs
+++ b/crates/ra_ide/src/inlay_hints.rs
@@ -70,6 +70,45 @@ pub(crate) fn inlay_hints(
70 res 70 res
71} 71}
72 72
73fn get_chaining_hints(
74 acc: &mut Vec<InlayHint>,
75 sema: &Semantics<RootDatabase>,
76 options: &InlayHintsOptions,
77 expr: ast::Expr,
78) -> Option<()> {
79 if !options.chaining_hints {
80 return None;
81 }
82
83 let ty = sema.type_of_expr(&expr)?;
84 let label = ty.display_truncated(sema.db, options.max_length).to_string();
85 if ty.is_unknown() {
86 return None;
87 }
88
89 let mut tokens = expr.syntax()
90 .siblings_with_tokens(Direction::Next)
91 .filter_map(NodeOrToken::into_token)
92 .filter(|t| match t.kind() {
93 SyntaxKind::WHITESPACE if !t.text().contains('\n') => false,
94 SyntaxKind::COMMENT => false,
95 _ => true,
96 });
97
98 // Chaining can be defined as an expression whose next sibling tokens are newline and dot
99 // Ignoring extra whitespace and comments
100 let next = tokens.next()?.kind();
101 let next_next = tokens.next()?.kind();
102 if next == SyntaxKind::WHITESPACE && next_next == SyntaxKind::DOT {
103 acc.push(InlayHint {
104 range: expr.syntax().text_range(),
105 kind: InlayKind::ChainingHint,
106 label: label.into(),
107 });
108 }
109 Some(())
110}
111
73fn get_param_name_hints( 112fn get_param_name_hints(
74 acc: &mut Vec<InlayHint>, 113 acc: &mut Vec<InlayHint>,
75 sema: &Semantics<RootDatabase>, 114 sema: &Semantics<RootDatabase>,
@@ -233,45 +272,6 @@ fn get_fn_signature(sema: &Semantics<RootDatabase>, expr: &ast::Expr) -> Option<
233 } 272 }
234} 273}
235 274
236fn get_chaining_hints(
237 acc: &mut Vec<InlayHint>,
238 sema: &Semantics<RootDatabase>,
239 options: &InlayHintsOptions,
240 expr: ast::Expr,
241) -> Option<()> {
242 if !options.chaining_hints {
243 return None;
244 }
245
246 let ty = sema.type_of_expr(&expr)?;
247 let label = ty.display_truncated(sema.db, options.max_length).to_string();
248 if ty.is_unknown() {
249 return None;
250 }
251
252 let mut tokens = expr.syntax()
253 .siblings_with_tokens(Direction::Next)
254 .filter_map(NodeOrToken::into_token)
255 .filter(|t| match t.kind() {
256 SyntaxKind::WHITESPACE if !t.text().contains('\n') => false,
257 SyntaxKind::COMMENT => false,
258 _ => true,
259 });
260
261 // Chaining can be defined as an expression whose next sibling tokens are newline and dot
262 // Ignoring extra whitespace and comments
263 let next = tokens.next()?.kind();
264 let next_next = tokens.next()?.kind();
265 if next == SyntaxKind::WHITESPACE && next_next == SyntaxKind::DOT {
266 acc.push(InlayHint {
267 range: expr.syntax().text_range(),
268 kind: InlayKind::ChainingHint,
269 label: label.into(),
270 });
271 }
272 Some(())
273}
274
275#[cfg(test)] 275#[cfg(test)]
276mod tests { 276mod tests {
277 use crate::inlay_hints::InlayHintsOptions; 277 use crate::inlay_hints::InlayHintsOptions;
@@ -280,43 +280,6 @@ mod tests {
280 use crate::mock_analysis::single_file; 280 use crate::mock_analysis::single_file;
281 281
282 #[test] 282 #[test]
283 fn generic_chaining_hints() {
284 let (analysis, file_id) = single_file(
285 r#"
286 struct A<T>(T);
287 struct B<T>(T);
288 struct C<T>(T);
289 struct X<T,R>(T, R);
290
291 impl<T> A<T> {
292 fn new(t: T) -> Self { A(t) }
293 fn into_b(self) -> B<T> { B(self.0) }
294 }
295 impl<T> B<T> {
296 fn into_c(self) -> C<T> { C(self.0) }
297 }
298 fn test() {
299 let c = A::new(X(42, true))
300 .into_b() // All the from A -> B -> C
301 .into_c();
302 }"#,
303 );
304 assert_debug_snapshot!(analysis.inlay_hints(file_id, &InlayHintsOptions{ parameter_hints: false, type_hints: false, chaining_hints: true, max_length: None}).unwrap(), @r###"
305 [
306 InlayHint {
307 range: [416; 465),
308 kind: ChainingHint,
309 label: "B<X<i32, bool>>",
310 },
311 InlayHint {
312 range: [416; 435),
313 kind: ChainingHint,
314 label: "A<X<i32, bool>>",
315 },
316 ]"###);
317 }
318
319 #[test]
320 fn param_hints_only() { 283 fn param_hints_only() {
321 let (analysis, file_id) = single_file( 284 let (analysis, file_id) = single_file(
322 r#" 285 r#"
@@ -1139,4 +1102,124 @@ fn main() {
1139 "### 1102 "###
1140 ); 1103 );
1141 } 1104 }
1105
1106 #[test]
1107 fn chaining_hints_ignore_comments() {
1108 let (analysis, file_id) = single_file(
1109 r#"
1110 struct A(B);
1111 impl A { fn into_b(self) -> B { self.0 } }
1112 struct B(C)
1113 impl B { fn into_c(self) -> C { self.0 } }
1114 struct C;
1115
1116 fn main() {
1117 let c = A(B(C))
1118 .into_b() // This is a comment
1119 .into_c();
1120 }"#,
1121 );
1122 assert_debug_snapshot!(analysis.inlay_hints(file_id, &InlayHintsOptions{ parameter_hints: false, type_hints: false, chaining_hints: true, max_length: None}).unwrap(), @r###"
1123 [
1124 InlayHint {
1125 range: [231; 268),
1126 kind: ChainingHint,
1127 label: "B",
1128 },
1129 InlayHint {
1130 range: [231; 238),
1131 kind: ChainingHint,
1132 label: "A",
1133 },
1134 ]"###);
1135 }
1136
1137 #[test]
1138 fn chaining_hints_without_newlines() {
1139 let (analysis, file_id) = single_file(
1140 r#"
1141 struct A(B);
1142 impl A { fn into_b(self) -> B { self.0 } }
1143 struct B(C)
1144 impl B { fn into_c(self) -> C { self.0 } }
1145 struct C;
1146
1147 fn main() {
1148 let c = A(B(C)).into_b().into_c();
1149 }"#,
1150 );
1151 assert_debug_snapshot!(analysis.inlay_hints(file_id, &InlayHintsOptions{ parameter_hints: false, type_hints: false, chaining_hints: true, max_length: None}).unwrap(), @r###"[]"###);
1152 }
1153
1154 #[test]
1155 fn struct_access_chaining_hints() {
1156 let (analysis, file_id) = single_file(
1157 r#"
1158 struct A { pub b: B }
1159 struct B { pub c: C }
1160 struct C(pub bool);
1161
1162 fn main() {
1163 let x = A { b: B { c: C(true) } }
1164 .b
1165 .c
1166 .0;
1167 }"#,
1168 );
1169 assert_debug_snapshot!(analysis.inlay_hints(file_id, &InlayHintsOptions{ parameter_hints: false, type_hints: false, chaining_hints: true, max_length: None}).unwrap(), @r###"
1170 [
1171 InlayHint {
1172 range: [150; 221),
1173 kind: ChainingHint,
1174 label: "C",
1175 },
1176 InlayHint {
1177 range: [150; 198),
1178 kind: ChainingHint,
1179 label: "B",
1180 },
1181 InlayHint {
1182 range: [150; 175),
1183 kind: ChainingHint,
1184 label: "A",
1185 },
1186 ]"###);
1187 }
1188
1189 #[test]
1190 fn generic_chaining_hints() {
1191 let (analysis, file_id) = single_file(
1192 r#"
1193 struct A<T>(T);
1194 struct B<T>(T);
1195 struct C<T>(T);
1196 struct X<T,R>(T, R);
1197
1198 impl<T> A<T> {
1199 fn new(t: T) -> Self { A(t) }
1200 fn into_b(self) -> B<T> { B(self.0) }
1201 }
1202 impl<T> B<T> {
1203 fn into_c(self) -> C<T> { C(self.0) }
1204 }
1205 fn main() {
1206 let c = A::new(X(42, true))
1207 .into_b()
1208 .into_c();
1209 }"#,
1210 );
1211 assert_debug_snapshot!(analysis.inlay_hints(file_id, &InlayHintsOptions{ parameter_hints: false, type_hints: false, chaining_hints: true, max_length: None}).unwrap(), @r###"
1212 [
1213 InlayHint {
1214 range: [416; 465),
1215 kind: ChainingHint,
1216 label: "B<X<i32, bool>>",
1217 },
1218 InlayHint {
1219 range: [416; 435),
1220 kind: ChainingHint,
1221 label: "A<X<i32, bool>>",
1222 },
1223 ]"###);
1224 }
1142} 1225}