diff options
Diffstat (limited to 'crates/ra_ide/src/inlay_hints.rs')
-rw-r--r-- | crates/ra_ide/src/inlay_hints.rs | 918 |
1 files changed, 0 insertions, 918 deletions
diff --git a/crates/ra_ide/src/inlay_hints.rs b/crates/ra_ide/src/inlay_hints.rs deleted file mode 100644 index 4bbbcd258..000000000 --- a/crates/ra_ide/src/inlay_hints.rs +++ /dev/null | |||
@@ -1,918 +0,0 @@ | |||
1 | use hir::{Adt, Callable, HirDisplay, Semantics, Type}; | ||
2 | use ra_ide_db::RootDatabase; | ||
3 | use ra_prof::profile; | ||
4 | use ra_syntax::{ | ||
5 | ast::{self, ArgListOwner, AstNode}, | ||
6 | match_ast, Direction, NodeOrToken, SmolStr, SyntaxKind, TextRange, T, | ||
7 | }; | ||
8 | use stdx::to_lower_snake_case; | ||
9 | |||
10 | use crate::FileId; | ||
11 | use ast::NameOwner; | ||
12 | use either::Either; | ||
13 | |||
14 | #[derive(Clone, Debug, PartialEq, Eq)] | ||
15 | pub struct InlayHintsConfig { | ||
16 | pub type_hints: bool, | ||
17 | pub parameter_hints: bool, | ||
18 | pub chaining_hints: bool, | ||
19 | pub max_length: Option<usize>, | ||
20 | } | ||
21 | |||
22 | impl Default for InlayHintsConfig { | ||
23 | fn default() -> Self { | ||
24 | Self { type_hints: true, parameter_hints: true, chaining_hints: true, max_length: None } | ||
25 | } | ||
26 | } | ||
27 | |||
28 | #[derive(Clone, Debug, PartialEq, Eq)] | ||
29 | pub enum InlayKind { | ||
30 | TypeHint, | ||
31 | ParameterHint, | ||
32 | ChainingHint, | ||
33 | } | ||
34 | |||
35 | #[derive(Debug)] | ||
36 | pub struct InlayHint { | ||
37 | pub range: TextRange, | ||
38 | pub kind: InlayKind, | ||
39 | pub label: SmolStr, | ||
40 | } | ||
41 | |||
42 | // Feature: Inlay Hints | ||
43 | // | ||
44 | // rust-analyzer shows additional information inline with the source code. | ||
45 | // Editors usually render this using read-only virtual text snippets interspersed with code. | ||
46 | // | ||
47 | // rust-analyzer shows hits for | ||
48 | // | ||
49 | // * types of local variables | ||
50 | // * names of function arguments | ||
51 | // * types of chained expressions | ||
52 | // | ||
53 | // **Note:** VS Code does not have native support for inlay hints https://github.com/microsoft/vscode/issues/16221[yet] and the hints are implemented using decorations. | ||
54 | // This approach has limitations, the caret movement and bracket highlighting near the edges of the hint may be weird: | ||
55 | // https://github.com/rust-analyzer/rust-analyzer/issues/1623[1], https://github.com/rust-analyzer/rust-analyzer/issues/3453[2]. | ||
56 | // | ||
57 | // |=== | ||
58 | // | Editor | Action Name | ||
59 | // | ||
60 | // | VS Code | **Rust Analyzer: Toggle inlay hints* | ||
61 | // |=== | ||
62 | pub(crate) fn inlay_hints( | ||
63 | db: &RootDatabase, | ||
64 | file_id: FileId, | ||
65 | config: &InlayHintsConfig, | ||
66 | ) -> Vec<InlayHint> { | ||
67 | let _p = profile("inlay_hints"); | ||
68 | let sema = Semantics::new(db); | ||
69 | let file = sema.parse(file_id); | ||
70 | |||
71 | let mut res = Vec::new(); | ||
72 | for node in file.syntax().descendants() { | ||
73 | if let Some(expr) = ast::Expr::cast(node.clone()) { | ||
74 | get_chaining_hints(&mut res, &sema, config, expr); | ||
75 | } | ||
76 | |||
77 | match_ast! { | ||
78 | match node { | ||
79 | ast::CallExpr(it) => { get_param_name_hints(&mut res, &sema, config, ast::Expr::from(it)); }, | ||
80 | ast::MethodCallExpr(it) => { get_param_name_hints(&mut res, &sema, config, ast::Expr::from(it)); }, | ||
81 | ast::BindPat(it) => { get_bind_pat_hints(&mut res, &sema, config, it); }, | ||
82 | _ => (), | ||
83 | } | ||
84 | } | ||
85 | } | ||
86 | res | ||
87 | } | ||
88 | |||
89 | fn get_chaining_hints( | ||
90 | acc: &mut Vec<InlayHint>, | ||
91 | sema: &Semantics<RootDatabase>, | ||
92 | config: &InlayHintsConfig, | ||
93 | expr: ast::Expr, | ||
94 | ) -> Option<()> { | ||
95 | if !config.chaining_hints { | ||
96 | return None; | ||
97 | } | ||
98 | |||
99 | if matches!(expr, ast::Expr::RecordExpr(_)) { | ||
100 | return None; | ||
101 | } | ||
102 | |||
103 | let mut tokens = expr | ||
104 | .syntax() | ||
105 | .siblings_with_tokens(Direction::Next) | ||
106 | .filter_map(NodeOrToken::into_token) | ||
107 | .filter(|t| match t.kind() { | ||
108 | SyntaxKind::WHITESPACE if !t.text().contains('\n') => false, | ||
109 | SyntaxKind::COMMENT => false, | ||
110 | _ => true, | ||
111 | }); | ||
112 | |||
113 | // Chaining can be defined as an expression whose next sibling tokens are newline and dot | ||
114 | // Ignoring extra whitespace and comments | ||
115 | let next = tokens.next()?.kind(); | ||
116 | let next_next = tokens.next()?.kind(); | ||
117 | if next == SyntaxKind::WHITESPACE && next_next == T![.] { | ||
118 | let ty = sema.type_of_expr(&expr)?; | ||
119 | if ty.is_unknown() { | ||
120 | return None; | ||
121 | } | ||
122 | if matches!(expr, ast::Expr::PathExpr(_)) { | ||
123 | if let Some(Adt::Struct(st)) = ty.as_adt() { | ||
124 | if st.fields(sema.db).is_empty() { | ||
125 | return None; | ||
126 | } | ||
127 | } | ||
128 | } | ||
129 | let label = ty.display_truncated(sema.db, config.max_length).to_string(); | ||
130 | acc.push(InlayHint { | ||
131 | range: expr.syntax().text_range(), | ||
132 | kind: InlayKind::ChainingHint, | ||
133 | label: label.into(), | ||
134 | }); | ||
135 | } | ||
136 | Some(()) | ||
137 | } | ||
138 | |||
139 | fn get_param_name_hints( | ||
140 | acc: &mut Vec<InlayHint>, | ||
141 | sema: &Semantics<RootDatabase>, | ||
142 | config: &InlayHintsConfig, | ||
143 | expr: ast::Expr, | ||
144 | ) -> Option<()> { | ||
145 | if !config.parameter_hints { | ||
146 | return None; | ||
147 | } | ||
148 | |||
149 | let args = match &expr { | ||
150 | ast::Expr::CallExpr(expr) => expr.arg_list()?.args(), | ||
151 | ast::Expr::MethodCallExpr(expr) => expr.arg_list()?.args(), | ||
152 | _ => return None, | ||
153 | }; | ||
154 | |||
155 | let callable = get_callable(sema, &expr)?; | ||
156 | let hints = callable | ||
157 | .params(sema.db) | ||
158 | .into_iter() | ||
159 | .zip(args) | ||
160 | .filter_map(|((param, _ty), arg)| match param? { | ||
161 | Either::Left(self_param) => Some((self_param.to_string(), arg)), | ||
162 | Either::Right(pat) => { | ||
163 | let param_name = match pat { | ||
164 | ast::Pat::BindPat(it) => it.name()?.to_string(), | ||
165 | it => it.to_string(), | ||
166 | }; | ||
167 | Some((param_name, arg)) | ||
168 | } | ||
169 | }) | ||
170 | .filter(|(param_name, arg)| should_show_param_name_hint(sema, &callable, ¶m_name, &arg)) | ||
171 | .map(|(param_name, arg)| InlayHint { | ||
172 | range: arg.syntax().text_range(), | ||
173 | kind: InlayKind::ParameterHint, | ||
174 | label: param_name.into(), | ||
175 | }); | ||
176 | |||
177 | acc.extend(hints); | ||
178 | Some(()) | ||
179 | } | ||
180 | |||
181 | fn get_bind_pat_hints( | ||
182 | acc: &mut Vec<InlayHint>, | ||
183 | sema: &Semantics<RootDatabase>, | ||
184 | config: &InlayHintsConfig, | ||
185 | pat: ast::BindPat, | ||
186 | ) -> Option<()> { | ||
187 | if !config.type_hints { | ||
188 | return None; | ||
189 | } | ||
190 | |||
191 | let ty = sema.type_of_pat(&pat.clone().into())?; | ||
192 | |||
193 | if should_not_display_type_hint(sema.db, &pat, &ty) { | ||
194 | return None; | ||
195 | } | ||
196 | |||
197 | acc.push(InlayHint { | ||
198 | range: pat.syntax().text_range(), | ||
199 | kind: InlayKind::TypeHint, | ||
200 | label: ty.display_truncated(sema.db, config.max_length).to_string().into(), | ||
201 | }); | ||
202 | Some(()) | ||
203 | } | ||
204 | |||
205 | fn pat_is_enum_variant(db: &RootDatabase, bind_pat: &ast::BindPat, pat_ty: &Type) -> bool { | ||
206 | if let Some(Adt::Enum(enum_data)) = pat_ty.as_adt() { | ||
207 | let pat_text = bind_pat.to_string(); | ||
208 | enum_data | ||
209 | .variants(db) | ||
210 | .into_iter() | ||
211 | .map(|variant| variant.name(db).to_string()) | ||
212 | .any(|enum_name| enum_name == pat_text) | ||
213 | } else { | ||
214 | false | ||
215 | } | ||
216 | } | ||
217 | |||
218 | fn should_not_display_type_hint(db: &RootDatabase, bind_pat: &ast::BindPat, pat_ty: &Type) -> bool { | ||
219 | if pat_ty.is_unknown() { | ||
220 | return true; | ||
221 | } | ||
222 | |||
223 | if let Some(Adt::Struct(s)) = pat_ty.as_adt() { | ||
224 | if s.fields(db).is_empty() && s.name(db).to_string() == bind_pat.to_string() { | ||
225 | return true; | ||
226 | } | ||
227 | } | ||
228 | |||
229 | for node in bind_pat.syntax().ancestors() { | ||
230 | match_ast! { | ||
231 | match node { | ||
232 | ast::LetStmt(it) => { | ||
233 | return it.ty().is_some() | ||
234 | }, | ||
235 | ast::Param(it) => { | ||
236 | return it.ty().is_some() | ||
237 | }, | ||
238 | ast::MatchArm(_it) => { | ||
239 | return pat_is_enum_variant(db, bind_pat, pat_ty); | ||
240 | }, | ||
241 | ast::IfExpr(it) => { | ||
242 | return it.condition().and_then(|condition| condition.pat()).is_some() | ||
243 | && pat_is_enum_variant(db, bind_pat, pat_ty); | ||
244 | }, | ||
245 | ast::WhileExpr(it) => { | ||
246 | return it.condition().and_then(|condition| condition.pat()).is_some() | ||
247 | && pat_is_enum_variant(db, bind_pat, pat_ty); | ||
248 | }, | ||
249 | _ => (), | ||
250 | } | ||
251 | } | ||
252 | } | ||
253 | false | ||
254 | } | ||
255 | |||
256 | fn should_show_param_name_hint( | ||
257 | sema: &Semantics<RootDatabase>, | ||
258 | callable: &Callable, | ||
259 | param_name: &str, | ||
260 | argument: &ast::Expr, | ||
261 | ) -> bool { | ||
262 | let param_name = param_name.trim_start_matches('_'); | ||
263 | let fn_name = match callable.kind() { | ||
264 | hir::CallableKind::Function(it) => Some(it.name(sema.db).to_string()), | ||
265 | hir::CallableKind::TupleStruct(_) | ||
266 | | hir::CallableKind::TupleEnumVariant(_) | ||
267 | | hir::CallableKind::Closure => None, | ||
268 | }; | ||
269 | if param_name.is_empty() | ||
270 | || Some(param_name) == fn_name.as_ref().map(|s| s.trim_start_matches('_')) | ||
271 | || is_argument_similar_to_param_name(sema, argument, param_name) | ||
272 | || param_name.starts_with("ra_fixture") | ||
273 | { | ||
274 | return false; | ||
275 | } | ||
276 | |||
277 | // avoid displaying hints for common functions like map, filter, etc. | ||
278 | // or other obvious words used in std | ||
279 | !(callable.n_params() == 1 && is_obvious_param(param_name)) | ||
280 | } | ||
281 | |||
282 | fn is_argument_similar_to_param_name( | ||
283 | sema: &Semantics<RootDatabase>, | ||
284 | argument: &ast::Expr, | ||
285 | param_name: &str, | ||
286 | ) -> bool { | ||
287 | if is_enum_name_similar_to_param_name(sema, argument, param_name) { | ||
288 | return true; | ||
289 | } | ||
290 | match get_string_representation(argument) { | ||
291 | None => false, | ||
292 | Some(repr) => { | ||
293 | let argument_string = repr.trim_start_matches('_'); | ||
294 | argument_string.starts_with(param_name) || argument_string.ends_with(param_name) | ||
295 | } | ||
296 | } | ||
297 | } | ||
298 | |||
299 | fn is_enum_name_similar_to_param_name( | ||
300 | sema: &Semantics<RootDatabase>, | ||
301 | argument: &ast::Expr, | ||
302 | param_name: &str, | ||
303 | ) -> bool { | ||
304 | match sema.type_of_expr(argument).and_then(|t| t.as_adt()) { | ||
305 | Some(Adt::Enum(e)) => to_lower_snake_case(&e.name(sema.db).to_string()) == param_name, | ||
306 | _ => false, | ||
307 | } | ||
308 | } | ||
309 | |||
310 | fn get_string_representation(expr: &ast::Expr) -> Option<String> { | ||
311 | match expr { | ||
312 | ast::Expr::MethodCallExpr(method_call_expr) => { | ||
313 | Some(method_call_expr.name_ref()?.to_string()) | ||
314 | } | ||
315 | ast::Expr::RefExpr(ref_expr) => get_string_representation(&ref_expr.expr()?), | ||
316 | _ => Some(expr.to_string()), | ||
317 | } | ||
318 | } | ||
319 | |||
320 | fn is_obvious_param(param_name: &str) -> bool { | ||
321 | let is_obvious_param_name = | ||
322 | matches!(param_name, "predicate" | "value" | "pat" | "rhs" | "other"); | ||
323 | param_name.len() == 1 || is_obvious_param_name | ||
324 | } | ||
325 | |||
326 | fn get_callable(sema: &Semantics<RootDatabase>, expr: &ast::Expr) -> Option<Callable> { | ||
327 | match expr { | ||
328 | ast::Expr::CallExpr(expr) => sema.type_of_expr(&expr.expr()?)?.as_callable(sema.db), | ||
329 | ast::Expr::MethodCallExpr(expr) => sema.resolve_method_call_as_callable(expr), | ||
330 | _ => None, | ||
331 | } | ||
332 | } | ||
333 | |||
334 | #[cfg(test)] | ||
335 | mod tests { | ||
336 | use expect::{expect, Expect}; | ||
337 | use test_utils::extract_annotations; | ||
338 | |||
339 | use crate::{inlay_hints::InlayHintsConfig, mock_analysis::single_file}; | ||
340 | |||
341 | fn check(ra_fixture: &str) { | ||
342 | check_with_config(InlayHintsConfig::default(), ra_fixture); | ||
343 | } | ||
344 | |||
345 | fn check_with_config(config: InlayHintsConfig, ra_fixture: &str) { | ||
346 | let (analysis, file_id) = single_file(ra_fixture); | ||
347 | let expected = extract_annotations(&*analysis.file_text(file_id).unwrap()); | ||
348 | let inlay_hints = analysis.inlay_hints(file_id, &config).unwrap(); | ||
349 | let actual = | ||
350 | inlay_hints.into_iter().map(|it| (it.range, it.label.to_string())).collect::<Vec<_>>(); | ||
351 | assert_eq!(expected, actual, "\nExpected:\n{:#?}\n\nActual:\n{:#?}", expected, actual); | ||
352 | } | ||
353 | |||
354 | fn check_expect(config: InlayHintsConfig, ra_fixture: &str, expect: Expect) { | ||
355 | let (analysis, file_id) = single_file(ra_fixture); | ||
356 | let inlay_hints = analysis.inlay_hints(file_id, &config).unwrap(); | ||
357 | expect.assert_debug_eq(&inlay_hints) | ||
358 | } | ||
359 | |||
360 | #[test] | ||
361 | fn param_hints_only() { | ||
362 | check_with_config( | ||
363 | InlayHintsConfig { | ||
364 | parameter_hints: true, | ||
365 | type_hints: false, | ||
366 | chaining_hints: false, | ||
367 | max_length: None, | ||
368 | }, | ||
369 | r#" | ||
370 | fn foo(a: i32, b: i32) -> i32 { a + b } | ||
371 | fn main() { | ||
372 | let _x = foo( | ||
373 | 4, | ||
374 | //^ a | ||
375 | 4, | ||
376 | //^ b | ||
377 | ); | ||
378 | }"#, | ||
379 | ); | ||
380 | } | ||
381 | |||
382 | #[test] | ||
383 | fn hints_disabled() { | ||
384 | check_with_config( | ||
385 | InlayHintsConfig { | ||
386 | type_hints: false, | ||
387 | parameter_hints: false, | ||
388 | chaining_hints: false, | ||
389 | max_length: None, | ||
390 | }, | ||
391 | r#" | ||
392 | fn foo(a: i32, b: i32) -> i32 { a + b } | ||
393 | fn main() { | ||
394 | let _x = foo(4, 4); | ||
395 | }"#, | ||
396 | ); | ||
397 | } | ||
398 | |||
399 | #[test] | ||
400 | fn type_hints_only() { | ||
401 | check_with_config( | ||
402 | InlayHintsConfig { | ||
403 | type_hints: true, | ||
404 | parameter_hints: false, | ||
405 | chaining_hints: false, | ||
406 | max_length: None, | ||
407 | }, | ||
408 | r#" | ||
409 | fn foo(a: i32, b: i32) -> i32 { a + b } | ||
410 | fn main() { | ||
411 | let _x = foo(4, 4); | ||
412 | //^^ i32 | ||
413 | }"#, | ||
414 | ); | ||
415 | } | ||
416 | |||
417 | #[test] | ||
418 | fn default_generic_types_should_not_be_displayed() { | ||
419 | check( | ||
420 | r#" | ||
421 | struct Test<K, T = u8> { k: K, t: T } | ||
422 | |||
423 | fn main() { | ||
424 | let zz = Test { t: 23u8, k: 33 }; | ||
425 | //^^ Test<i32> | ||
426 | let zz_ref = &zz; | ||
427 | //^^^^^^ &Test<i32> | ||
428 | let test = || zz; | ||
429 | //^^^^ || -> Test<i32> | ||
430 | }"#, | ||
431 | ); | ||
432 | } | ||
433 | |||
434 | #[test] | ||
435 | fn let_statement() { | ||
436 | check( | ||
437 | r#" | ||
438 | #[derive(PartialEq)] | ||
439 | enum Option<T> { None, Some(T) } | ||
440 | |||
441 | #[derive(PartialEq)] | ||
442 | struct Test { a: Option<u32>, b: u8 } | ||
443 | |||
444 | fn main() { | ||
445 | struct InnerStruct {} | ||
446 | |||
447 | let test = 54; | ||
448 | //^^^^ i32 | ||
449 | let test: i32 = 33; | ||
450 | let mut test = 33; | ||
451 | //^^^^^^^^ i32 | ||
452 | let _ = 22; | ||
453 | let test = "test"; | ||
454 | //^^^^ &str | ||
455 | let test = InnerStruct {}; | ||
456 | |||
457 | let test = unresolved(); | ||
458 | |||
459 | let test = (42, 'a'); | ||
460 | //^^^^ (i32, char) | ||
461 | let (a, (b, (c,)) = (2, (3, (9.2,)); | ||
462 | //^ i32 ^ i32 ^ f64 | ||
463 | let &x = &92; | ||
464 | //^ i32 | ||
465 | }"#, | ||
466 | ); | ||
467 | } | ||
468 | |||
469 | #[test] | ||
470 | fn closure_parameters() { | ||
471 | check( | ||
472 | r#" | ||
473 | fn main() { | ||
474 | let mut start = 0; | ||
475 | //^^^^^^^^^ i32 | ||
476 | (0..2).for_each(|increment| { start += increment; }); | ||
477 | //^^^^^^^^^ i32 | ||
478 | |||
479 | let multiply = | ||
480 | //^^^^^^^^ |…| -> i32 | ||
481 | | a, b| a * b | ||
482 | //^ i32 ^ i32 | ||
483 | ; | ||
484 | |||
485 | let _: i32 = multiply(1, 2); | ||
486 | let multiply_ref = &multiply; | ||
487 | //^^^^^^^^^^^^ &|…| -> i32 | ||
488 | |||
489 | let return_42 = || 42; | ||
490 | //^^^^^^^^^ || -> i32 | ||
491 | }"#, | ||
492 | ); | ||
493 | } | ||
494 | |||
495 | #[test] | ||
496 | fn for_expression() { | ||
497 | check( | ||
498 | r#" | ||
499 | fn main() { | ||
500 | let mut start = 0; | ||
501 | //^^^^^^^^^ i32 | ||
502 | for increment in 0..2 { start += increment; } | ||
503 | //^^^^^^^^^ i32 | ||
504 | }"#, | ||
505 | ); | ||
506 | } | ||
507 | |||
508 | #[test] | ||
509 | fn if_expr() { | ||
510 | check( | ||
511 | r#" | ||
512 | enum Option<T> { None, Some(T) } | ||
513 | use Option::*; | ||
514 | |||
515 | struct Test { a: Option<u32>, b: u8 } | ||
516 | |||
517 | fn main() { | ||
518 | let test = Some(Test { a: Some(3), b: 1 }); | ||
519 | //^^^^ Option<Test> | ||
520 | if let None = &test {}; | ||
521 | if let test = &test {}; | ||
522 | //^^^^ &Option<Test> | ||
523 | if let Some(test) = &test {}; | ||
524 | //^^^^ &Test | ||
525 | if let Some(Test { a, b }) = &test {}; | ||
526 | //^ &Option<u32> ^ &u8 | ||
527 | if let Some(Test { a: x, b: y }) = &test {}; | ||
528 | //^ &Option<u32> ^ &u8 | ||
529 | if let Some(Test { a: Some(x), b: y }) = &test {}; | ||
530 | //^ &u32 ^ &u8 | ||
531 | if let Some(Test { a: None, b: y }) = &test {}; | ||
532 | //^ &u8 | ||
533 | if let Some(Test { b: y, .. }) = &test {}; | ||
534 | //^ &u8 | ||
535 | if test == None {} | ||
536 | }"#, | ||
537 | ); | ||
538 | } | ||
539 | |||
540 | #[test] | ||
541 | fn while_expr() { | ||
542 | check( | ||
543 | r#" | ||
544 | enum Option<T> { None, Some(T) } | ||
545 | use Option::*; | ||
546 | |||
547 | struct Test { a: Option<u32>, b: u8 } | ||
548 | |||
549 | fn main() { | ||
550 | let test = Some(Test { a: Some(3), b: 1 }); | ||
551 | //^^^^ Option<Test> | ||
552 | while let Some(Test { a: Some(x), b: y }) = &test {}; | ||
553 | //^ &u32 ^ &u8 | ||
554 | }"#, | ||
555 | ); | ||
556 | } | ||
557 | |||
558 | #[test] | ||
559 | fn match_arm_list() { | ||
560 | check( | ||
561 | r#" | ||
562 | enum Option<T> { None, Some(T) } | ||
563 | use Option::*; | ||
564 | |||
565 | struct Test { a: Option<u32>, b: u8 } | ||
566 | |||
567 | fn main() { | ||
568 | match Some(Test { a: Some(3), b: 1 }) { | ||
569 | None => (), | ||
570 | test => (), | ||
571 | //^^^^ Option<Test> | ||
572 | Some(Test { a: Some(x), b: y }) => (), | ||
573 | //^ u32 ^ u8 | ||
574 | _ => {} | ||
575 | } | ||
576 | }"#, | ||
577 | ); | ||
578 | } | ||
579 | |||
580 | #[test] | ||
581 | fn hint_truncation() { | ||
582 | check_with_config( | ||
583 | InlayHintsConfig { max_length: Some(8), ..Default::default() }, | ||
584 | r#" | ||
585 | struct Smol<T>(T); | ||
586 | |||
587 | struct VeryLongOuterName<T>(T); | ||
588 | |||
589 | fn main() { | ||
590 | let a = Smol(0u32); | ||
591 | //^ Smol<u32> | ||
592 | let b = VeryLongOuterName(0usize); | ||
593 | //^ VeryLongOuterName<…> | ||
594 | let c = Smol(Smol(0u32)) | ||
595 | //^ Smol<Smol<…>> | ||
596 | }"#, | ||
597 | ); | ||
598 | } | ||
599 | |||
600 | #[test] | ||
601 | fn function_call_parameter_hint() { | ||
602 | check( | ||
603 | r#" | ||
604 | enum Option<T> { None, Some(T) } | ||
605 | use Option::*; | ||
606 | |||
607 | struct FileId {} | ||
608 | struct SmolStr {} | ||
609 | |||
610 | struct TextRange {} | ||
611 | struct SyntaxKind {} | ||
612 | struct NavigationTarget {} | ||
613 | |||
614 | struct Test {} | ||
615 | |||
616 | impl Test { | ||
617 | fn method(&self, mut param: i32) -> i32 { param * 2 } | ||
618 | |||
619 | fn from_syntax( | ||
620 | file_id: FileId, | ||
621 | name: SmolStr, | ||
622 | focus_range: Option<TextRange>, | ||
623 | full_range: TextRange, | ||
624 | kind: SyntaxKind, | ||
625 | docs: Option<String>, | ||
626 | ) -> NavigationTarget { | ||
627 | NavigationTarget {} | ||
628 | } | ||
629 | } | ||
630 | |||
631 | fn test_func(mut foo: i32, bar: i32, msg: &str, _: i32, last: i32) -> i32 { | ||
632 | foo + bar | ||
633 | } | ||
634 | |||
635 | fn main() { | ||
636 | let not_literal = 1; | ||
637 | //^^^^^^^^^^^ i32 | ||
638 | let _: i32 = test_func(1, 2, "hello", 3, not_literal); | ||
639 | //^ foo ^ bar ^^^^^^^ msg ^^^^^^^^^^^ last | ||
640 | let t: Test = Test {}; | ||
641 | t.method(123); | ||
642 | //^^^ param | ||
643 | Test::method(&t, 3456); | ||
644 | //^^ &self ^^^^ param | ||
645 | Test::from_syntax( | ||
646 | FileId {}, | ||
647 | //^^^^^^^^^ file_id | ||
648 | "impl".into(), | ||
649 | //^^^^^^^^^^^^^ name | ||
650 | None, | ||
651 | //^^^^ focus_range | ||
652 | TextRange {}, | ||
653 | //^^^^^^^^^^^^ full_range | ||
654 | SyntaxKind {}, | ||
655 | //^^^^^^^^^^^^^ kind | ||
656 | None, | ||
657 | //^^^^ docs | ||
658 | ); | ||
659 | }"#, | ||
660 | ); | ||
661 | } | ||
662 | |||
663 | #[test] | ||
664 | fn omitted_parameters_hints_heuristics() { | ||
665 | check_with_config( | ||
666 | InlayHintsConfig { max_length: Some(8), ..Default::default() }, | ||
667 | r#" | ||
668 | fn map(f: i32) {} | ||
669 | fn filter(predicate: i32) {} | ||
670 | |||
671 | struct TestVarContainer { | ||
672 | test_var: i32, | ||
673 | } | ||
674 | |||
675 | impl TestVarContainer { | ||
676 | fn test_var(&self) -> i32 { | ||
677 | self.test_var | ||
678 | } | ||
679 | } | ||
680 | |||
681 | struct Test {} | ||
682 | |||
683 | impl Test { | ||
684 | fn map(self, f: i32) -> Self { | ||
685 | self | ||
686 | } | ||
687 | |||
688 | fn filter(self, predicate: i32) -> Self { | ||
689 | self | ||
690 | } | ||
691 | |||
692 | fn field(self, value: i32) -> Self { | ||
693 | self | ||
694 | } | ||
695 | |||
696 | fn no_hints_expected(&self, _: i32, test_var: i32) {} | ||
697 | |||
698 | fn frob(&self, frob: bool) {} | ||
699 | } | ||
700 | |||
701 | struct Param {} | ||
702 | |||
703 | fn different_order(param: &Param) {} | ||
704 | fn different_order_mut(param: &mut Param) {} | ||
705 | fn has_underscore(_param: bool) {} | ||
706 | fn enum_matches_param_name(completion_kind: CompletionKind) {} | ||
707 | |||
708 | fn twiddle(twiddle: bool) {} | ||
709 | fn doo(_doo: bool) {} | ||
710 | |||
711 | enum CompletionKind { | ||
712 | Keyword, | ||
713 | } | ||
714 | |||
715 | fn main() { | ||
716 | let container: TestVarContainer = TestVarContainer { test_var: 42 }; | ||
717 | let test: Test = Test {}; | ||
718 | |||
719 | map(22); | ||
720 | filter(33); | ||
721 | |||
722 | let test_processed: Test = test.map(1).filter(2).field(3); | ||
723 | |||
724 | let test_var: i32 = 55; | ||
725 | test_processed.no_hints_expected(22, test_var); | ||
726 | test_processed.no_hints_expected(33, container.test_var); | ||
727 | test_processed.no_hints_expected(44, container.test_var()); | ||
728 | test_processed.frob(false); | ||
729 | |||
730 | twiddle(true); | ||
731 | doo(true); | ||
732 | |||
733 | let mut param_begin: Param = Param {}; | ||
734 | different_order(¶m_begin); | ||
735 | different_order(&mut param_begin); | ||
736 | |||
737 | let param: bool = true; | ||
738 | has_underscore(param); | ||
739 | |||
740 | enum_matches_param_name(CompletionKind::Keyword); | ||
741 | |||
742 | let a: f64 = 7.0; | ||
743 | let b: f64 = 4.0; | ||
744 | let _: f64 = a.div_euclid(b); | ||
745 | let _: f64 = a.abs_sub(b); | ||
746 | }"#, | ||
747 | ); | ||
748 | } | ||
749 | |||
750 | #[test] | ||
751 | fn unit_structs_have_no_type_hints() { | ||
752 | check_with_config( | ||
753 | InlayHintsConfig { max_length: Some(8), ..Default::default() }, | ||
754 | r#" | ||
755 | enum Result<T, E> { Ok(T), Err(E) } | ||
756 | use Result::*; | ||
757 | |||
758 | struct SyntheticSyntax; | ||
759 | |||
760 | fn main() { | ||
761 | match Ok(()) { | ||
762 | Ok(_) => (), | ||
763 | Err(SyntheticSyntax) => (), | ||
764 | } | ||
765 | }"#, | ||
766 | ); | ||
767 | } | ||
768 | |||
769 | #[test] | ||
770 | fn chaining_hints_ignore_comments() { | ||
771 | check_expect( | ||
772 | InlayHintsConfig { | ||
773 | parameter_hints: false, | ||
774 | type_hints: false, | ||
775 | chaining_hints: true, | ||
776 | max_length: None, | ||
777 | }, | ||
778 | r#" | ||
779 | struct A(B); | ||
780 | impl A { fn into_b(self) -> B { self.0 } } | ||
781 | struct B(C); | ||
782 | impl B { fn into_c(self) -> C { self.0 } } | ||
783 | struct C; | ||
784 | |||
785 | fn main() { | ||
786 | let c = A(B(C)) | ||
787 | .into_b() // This is a comment | ||
788 | .into_c(); | ||
789 | } | ||
790 | "#, | ||
791 | expect![[r#" | ||
792 | [ | ||
793 | InlayHint { | ||
794 | range: 147..172, | ||
795 | kind: ChainingHint, | ||
796 | label: "B", | ||
797 | }, | ||
798 | InlayHint { | ||
799 | range: 147..154, | ||
800 | kind: ChainingHint, | ||
801 | label: "A", | ||
802 | }, | ||
803 | ] | ||
804 | "#]], | ||
805 | ); | ||
806 | } | ||
807 | |||
808 | #[test] | ||
809 | fn chaining_hints_without_newlines() { | ||
810 | check_with_config( | ||
811 | InlayHintsConfig { | ||
812 | parameter_hints: false, | ||
813 | type_hints: false, | ||
814 | chaining_hints: true, | ||
815 | max_length: None, | ||
816 | }, | ||
817 | r#" | ||
818 | struct A(B); | ||
819 | impl A { fn into_b(self) -> B { self.0 } } | ||
820 | struct B(C); | ||
821 | impl B { fn into_c(self) -> C { self.0 } } | ||
822 | struct C; | ||
823 | |||
824 | fn main() { | ||
825 | let c = A(B(C)).into_b().into_c(); | ||
826 | }"#, | ||
827 | ); | ||
828 | } | ||
829 | |||
830 | #[test] | ||
831 | fn struct_access_chaining_hints() { | ||
832 | check_expect( | ||
833 | InlayHintsConfig { | ||
834 | parameter_hints: false, | ||
835 | type_hints: false, | ||
836 | chaining_hints: true, | ||
837 | max_length: None, | ||
838 | }, | ||
839 | r#" | ||
840 | struct A { pub b: B } | ||
841 | struct B { pub c: C } | ||
842 | struct C(pub bool); | ||
843 | struct D; | ||
844 | |||
845 | impl D { | ||
846 | fn foo(&self) -> i32 { 42 } | ||
847 | } | ||
848 | |||
849 | fn main() { | ||
850 | let x = A { b: B { c: C(true) } } | ||
851 | .b | ||
852 | .c | ||
853 | .0; | ||
854 | let x = D | ||
855 | .foo(); | ||
856 | }"#, | ||
857 | expect![[r#" | ||
858 | [ | ||
859 | InlayHint { | ||
860 | range: 143..190, | ||
861 | kind: ChainingHint, | ||
862 | label: "C", | ||
863 | }, | ||
864 | InlayHint { | ||
865 | range: 143..179, | ||
866 | kind: ChainingHint, | ||
867 | label: "B", | ||
868 | }, | ||
869 | ] | ||
870 | "#]], | ||
871 | ); | ||
872 | } | ||
873 | |||
874 | #[test] | ||
875 | fn generic_chaining_hints() { | ||
876 | check_expect( | ||
877 | InlayHintsConfig { | ||
878 | parameter_hints: false, | ||
879 | type_hints: false, | ||
880 | chaining_hints: true, | ||
881 | max_length: None, | ||
882 | }, | ||
883 | r#" | ||
884 | struct A<T>(T); | ||
885 | struct B<T>(T); | ||
886 | struct C<T>(T); | ||
887 | struct X<T,R>(T, R); | ||
888 | |||
889 | impl<T> A<T> { | ||
890 | fn new(t: T) -> Self { A(t) } | ||
891 | fn into_b(self) -> B<T> { B(self.0) } | ||
892 | } | ||
893 | impl<T> B<T> { | ||
894 | fn into_c(self) -> C<T> { C(self.0) } | ||
895 | } | ||
896 | fn main() { | ||
897 | let c = A::new(X(42, true)) | ||
898 | .into_b() | ||
899 | .into_c(); | ||
900 | } | ||
901 | "#, | ||
902 | expect![[r#" | ||
903 | [ | ||
904 | InlayHint { | ||
905 | range: 246..283, | ||
906 | kind: ChainingHint, | ||
907 | label: "B<X<i32, bool>>", | ||
908 | }, | ||
909 | InlayHint { | ||
910 | range: 246..265, | ||
911 | kind: ChainingHint, | ||
912 | label: "A<X<i32, bool>>", | ||
913 | }, | ||
914 | ] | ||
915 | "#]], | ||
916 | ); | ||
917 | } | ||
918 | } | ||