diff options
Diffstat (limited to 'crates/ra_ide/src/inlay_hints.rs')
-rw-r--r-- | crates/ra_ide/src/inlay_hints.rs | 922 |
1 files changed, 0 insertions, 922 deletions
diff --git a/crates/ra_ide/src/inlay_hints.rs b/crates/ra_ide/src/inlay_hints.rs deleted file mode 100644 index 1bacead63..000000000 --- a/crates/ra_ide/src/inlay_hints.rs +++ /dev/null | |||
@@ -1,922 +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::IdentPat(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::IdentPat(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::IdentPat, | ||
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::IdentPat, 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( | ||
219 | db: &RootDatabase, | ||
220 | bind_pat: &ast::IdentPat, | ||
221 | pat_ty: &Type, | ||
222 | ) -> bool { | ||
223 | if pat_ty.is_unknown() { | ||
224 | return true; | ||
225 | } | ||
226 | |||
227 | if let Some(Adt::Struct(s)) = pat_ty.as_adt() { | ||
228 | if s.fields(db).is_empty() && s.name(db).to_string() == bind_pat.to_string() { | ||
229 | return true; | ||
230 | } | ||
231 | } | ||
232 | |||
233 | for node in bind_pat.syntax().ancestors() { | ||
234 | match_ast! { | ||
235 | match node { | ||
236 | ast::LetStmt(it) => { | ||
237 | return it.ty().is_some() | ||
238 | }, | ||
239 | ast::Param(it) => { | ||
240 | return it.ty().is_some() | ||
241 | }, | ||
242 | ast::MatchArm(_it) => { | ||
243 | return pat_is_enum_variant(db, bind_pat, pat_ty); | ||
244 | }, | ||
245 | ast::IfExpr(it) => { | ||
246 | return it.condition().and_then(|condition| condition.pat()).is_some() | ||
247 | && pat_is_enum_variant(db, bind_pat, pat_ty); | ||
248 | }, | ||
249 | ast::WhileExpr(it) => { | ||
250 | return it.condition().and_then(|condition| condition.pat()).is_some() | ||
251 | && pat_is_enum_variant(db, bind_pat, pat_ty); | ||
252 | }, | ||
253 | _ => (), | ||
254 | } | ||
255 | } | ||
256 | } | ||
257 | false | ||
258 | } | ||
259 | |||
260 | fn should_show_param_name_hint( | ||
261 | sema: &Semantics<RootDatabase>, | ||
262 | callable: &Callable, | ||
263 | param_name: &str, | ||
264 | argument: &ast::Expr, | ||
265 | ) -> bool { | ||
266 | let param_name = param_name.trim_start_matches('_'); | ||
267 | let fn_name = match callable.kind() { | ||
268 | hir::CallableKind::Function(it) => Some(it.name(sema.db).to_string()), | ||
269 | hir::CallableKind::TupleStruct(_) | ||
270 | | hir::CallableKind::TupleEnumVariant(_) | ||
271 | | hir::CallableKind::Closure => None, | ||
272 | }; | ||
273 | if param_name.is_empty() | ||
274 | || Some(param_name) == fn_name.as_ref().map(|s| s.trim_start_matches('_')) | ||
275 | || is_argument_similar_to_param_name(sema, argument, param_name) | ||
276 | || param_name.starts_with("ra_fixture") | ||
277 | { | ||
278 | return false; | ||
279 | } | ||
280 | |||
281 | // avoid displaying hints for common functions like map, filter, etc. | ||
282 | // or other obvious words used in std | ||
283 | !(callable.n_params() == 1 && is_obvious_param(param_name)) | ||
284 | } | ||
285 | |||
286 | fn is_argument_similar_to_param_name( | ||
287 | sema: &Semantics<RootDatabase>, | ||
288 | argument: &ast::Expr, | ||
289 | param_name: &str, | ||
290 | ) -> bool { | ||
291 | if is_enum_name_similar_to_param_name(sema, argument, param_name) { | ||
292 | return true; | ||
293 | } | ||
294 | match get_string_representation(argument) { | ||
295 | None => false, | ||
296 | Some(repr) => { | ||
297 | let argument_string = repr.trim_start_matches('_'); | ||
298 | argument_string.starts_with(param_name) || argument_string.ends_with(param_name) | ||
299 | } | ||
300 | } | ||
301 | } | ||
302 | |||
303 | fn is_enum_name_similar_to_param_name( | ||
304 | sema: &Semantics<RootDatabase>, | ||
305 | argument: &ast::Expr, | ||
306 | param_name: &str, | ||
307 | ) -> bool { | ||
308 | match sema.type_of_expr(argument).and_then(|t| t.as_adt()) { | ||
309 | Some(Adt::Enum(e)) => to_lower_snake_case(&e.name(sema.db).to_string()) == param_name, | ||
310 | _ => false, | ||
311 | } | ||
312 | } | ||
313 | |||
314 | fn get_string_representation(expr: &ast::Expr) -> Option<String> { | ||
315 | match expr { | ||
316 | ast::Expr::MethodCallExpr(method_call_expr) => { | ||
317 | Some(method_call_expr.name_ref()?.to_string()) | ||
318 | } | ||
319 | ast::Expr::RefExpr(ref_expr) => get_string_representation(&ref_expr.expr()?), | ||
320 | _ => Some(expr.to_string()), | ||
321 | } | ||
322 | } | ||
323 | |||
324 | fn is_obvious_param(param_name: &str) -> bool { | ||
325 | let is_obvious_param_name = | ||
326 | matches!(param_name, "predicate" | "value" | "pat" | "rhs" | "other"); | ||
327 | param_name.len() == 1 || is_obvious_param_name | ||
328 | } | ||
329 | |||
330 | fn get_callable(sema: &Semantics<RootDatabase>, expr: &ast::Expr) -> Option<Callable> { | ||
331 | match expr { | ||
332 | ast::Expr::CallExpr(expr) => sema.type_of_expr(&expr.expr()?)?.as_callable(sema.db), | ||
333 | ast::Expr::MethodCallExpr(expr) => sema.resolve_method_call_as_callable(expr), | ||
334 | _ => None, | ||
335 | } | ||
336 | } | ||
337 | |||
338 | #[cfg(test)] | ||
339 | mod tests { | ||
340 | use expect::{expect, Expect}; | ||
341 | use test_utils::extract_annotations; | ||
342 | |||
343 | use crate::{inlay_hints::InlayHintsConfig, mock_analysis::single_file}; | ||
344 | |||
345 | fn check(ra_fixture: &str) { | ||
346 | check_with_config(InlayHintsConfig::default(), ra_fixture); | ||
347 | } | ||
348 | |||
349 | fn check_with_config(config: InlayHintsConfig, ra_fixture: &str) { | ||
350 | let (analysis, file_id) = single_file(ra_fixture); | ||
351 | let expected = extract_annotations(&*analysis.file_text(file_id).unwrap()); | ||
352 | let inlay_hints = analysis.inlay_hints(file_id, &config).unwrap(); | ||
353 | let actual = | ||
354 | inlay_hints.into_iter().map(|it| (it.range, it.label.to_string())).collect::<Vec<_>>(); | ||
355 | assert_eq!(expected, actual, "\nExpected:\n{:#?}\n\nActual:\n{:#?}", expected, actual); | ||
356 | } | ||
357 | |||
358 | fn check_expect(config: InlayHintsConfig, ra_fixture: &str, expect: Expect) { | ||
359 | let (analysis, file_id) = single_file(ra_fixture); | ||
360 | let inlay_hints = analysis.inlay_hints(file_id, &config).unwrap(); | ||
361 | expect.assert_debug_eq(&inlay_hints) | ||
362 | } | ||
363 | |||
364 | #[test] | ||
365 | fn param_hints_only() { | ||
366 | check_with_config( | ||
367 | InlayHintsConfig { | ||
368 | parameter_hints: true, | ||
369 | type_hints: false, | ||
370 | chaining_hints: false, | ||
371 | max_length: None, | ||
372 | }, | ||
373 | r#" | ||
374 | fn foo(a: i32, b: i32) -> i32 { a + b } | ||
375 | fn main() { | ||
376 | let _x = foo( | ||
377 | 4, | ||
378 | //^ a | ||
379 | 4, | ||
380 | //^ b | ||
381 | ); | ||
382 | }"#, | ||
383 | ); | ||
384 | } | ||
385 | |||
386 | #[test] | ||
387 | fn hints_disabled() { | ||
388 | check_with_config( | ||
389 | InlayHintsConfig { | ||
390 | type_hints: false, | ||
391 | parameter_hints: false, | ||
392 | chaining_hints: false, | ||
393 | max_length: None, | ||
394 | }, | ||
395 | r#" | ||
396 | fn foo(a: i32, b: i32) -> i32 { a + b } | ||
397 | fn main() { | ||
398 | let _x = foo(4, 4); | ||
399 | }"#, | ||
400 | ); | ||
401 | } | ||
402 | |||
403 | #[test] | ||
404 | fn type_hints_only() { | ||
405 | check_with_config( | ||
406 | InlayHintsConfig { | ||
407 | type_hints: true, | ||
408 | parameter_hints: false, | ||
409 | chaining_hints: false, | ||
410 | max_length: None, | ||
411 | }, | ||
412 | r#" | ||
413 | fn foo(a: i32, b: i32) -> i32 { a + b } | ||
414 | fn main() { | ||
415 | let _x = foo(4, 4); | ||
416 | //^^ i32 | ||
417 | }"#, | ||
418 | ); | ||
419 | } | ||
420 | |||
421 | #[test] | ||
422 | fn default_generic_types_should_not_be_displayed() { | ||
423 | check( | ||
424 | r#" | ||
425 | struct Test<K, T = u8> { k: K, t: T } | ||
426 | |||
427 | fn main() { | ||
428 | let zz = Test { t: 23u8, k: 33 }; | ||
429 | //^^ Test<i32> | ||
430 | let zz_ref = &zz; | ||
431 | //^^^^^^ &Test<i32> | ||
432 | let test = || zz; | ||
433 | //^^^^ || -> Test<i32> | ||
434 | }"#, | ||
435 | ); | ||
436 | } | ||
437 | |||
438 | #[test] | ||
439 | fn let_statement() { | ||
440 | check( | ||
441 | r#" | ||
442 | #[derive(PartialEq)] | ||
443 | enum Option<T> { None, Some(T) } | ||
444 | |||
445 | #[derive(PartialEq)] | ||
446 | struct Test { a: Option<u32>, b: u8 } | ||
447 | |||
448 | fn main() { | ||
449 | struct InnerStruct {} | ||
450 | |||
451 | let test = 54; | ||
452 | //^^^^ i32 | ||
453 | let test: i32 = 33; | ||
454 | let mut test = 33; | ||
455 | //^^^^^^^^ i32 | ||
456 | let _ = 22; | ||
457 | let test = "test"; | ||
458 | //^^^^ &str | ||
459 | let test = InnerStruct {}; | ||
460 | |||
461 | let test = unresolved(); | ||
462 | |||
463 | let test = (42, 'a'); | ||
464 | //^^^^ (i32, char) | ||
465 | let (a, (b, (c,)) = (2, (3, (9.2,)); | ||
466 | //^ i32 ^ i32 ^ f64 | ||
467 | let &x = &92; | ||
468 | //^ i32 | ||
469 | }"#, | ||
470 | ); | ||
471 | } | ||
472 | |||
473 | #[test] | ||
474 | fn closure_parameters() { | ||
475 | check( | ||
476 | r#" | ||
477 | fn main() { | ||
478 | let mut start = 0; | ||
479 | //^^^^^^^^^ i32 | ||
480 | (0..2).for_each(|increment| { start += increment; }); | ||
481 | //^^^^^^^^^ i32 | ||
482 | |||
483 | let multiply = | ||
484 | //^^^^^^^^ |…| -> i32 | ||
485 | | a, b| a * b | ||
486 | //^ i32 ^ i32 | ||
487 | ; | ||
488 | |||
489 | let _: i32 = multiply(1, 2); | ||
490 | let multiply_ref = &multiply; | ||
491 | //^^^^^^^^^^^^ &|…| -> i32 | ||
492 | |||
493 | let return_42 = || 42; | ||
494 | //^^^^^^^^^ || -> i32 | ||
495 | }"#, | ||
496 | ); | ||
497 | } | ||
498 | |||
499 | #[test] | ||
500 | fn for_expression() { | ||
501 | check( | ||
502 | r#" | ||
503 | fn main() { | ||
504 | let mut start = 0; | ||
505 | //^^^^^^^^^ i32 | ||
506 | for increment in 0..2 { start += increment; } | ||
507 | //^^^^^^^^^ i32 | ||
508 | }"#, | ||
509 | ); | ||
510 | } | ||
511 | |||
512 | #[test] | ||
513 | fn if_expr() { | ||
514 | check( | ||
515 | r#" | ||
516 | enum Option<T> { None, Some(T) } | ||
517 | use Option::*; | ||
518 | |||
519 | struct Test { a: Option<u32>, b: u8 } | ||
520 | |||
521 | fn main() { | ||
522 | let test = Some(Test { a: Some(3), b: 1 }); | ||
523 | //^^^^ Option<Test> | ||
524 | if let None = &test {}; | ||
525 | if let test = &test {}; | ||
526 | //^^^^ &Option<Test> | ||
527 | if let Some(test) = &test {}; | ||
528 | //^^^^ &Test | ||
529 | if let Some(Test { a, b }) = &test {}; | ||
530 | //^ &Option<u32> ^ &u8 | ||
531 | if let Some(Test { a: x, b: y }) = &test {}; | ||
532 | //^ &Option<u32> ^ &u8 | ||
533 | if let Some(Test { a: Some(x), b: y }) = &test {}; | ||
534 | //^ &u32 ^ &u8 | ||
535 | if let Some(Test { a: None, b: y }) = &test {}; | ||
536 | //^ &u8 | ||
537 | if let Some(Test { b: y, .. }) = &test {}; | ||
538 | //^ &u8 | ||
539 | if test == None {} | ||
540 | }"#, | ||
541 | ); | ||
542 | } | ||
543 | |||
544 | #[test] | ||
545 | fn while_expr() { | ||
546 | check( | ||
547 | r#" | ||
548 | enum Option<T> { None, Some(T) } | ||
549 | use Option::*; | ||
550 | |||
551 | struct Test { a: Option<u32>, b: u8 } | ||
552 | |||
553 | fn main() { | ||
554 | let test = Some(Test { a: Some(3), b: 1 }); | ||
555 | //^^^^ Option<Test> | ||
556 | while let Some(Test { a: Some(x), b: y }) = &test {}; | ||
557 | //^ &u32 ^ &u8 | ||
558 | }"#, | ||
559 | ); | ||
560 | } | ||
561 | |||
562 | #[test] | ||
563 | fn match_arm_list() { | ||
564 | check( | ||
565 | r#" | ||
566 | enum Option<T> { None, Some(T) } | ||
567 | use Option::*; | ||
568 | |||
569 | struct Test { a: Option<u32>, b: u8 } | ||
570 | |||
571 | fn main() { | ||
572 | match Some(Test { a: Some(3), b: 1 }) { | ||
573 | None => (), | ||
574 | test => (), | ||
575 | //^^^^ Option<Test> | ||
576 | Some(Test { a: Some(x), b: y }) => (), | ||
577 | //^ u32 ^ u8 | ||
578 | _ => {} | ||
579 | } | ||
580 | }"#, | ||
581 | ); | ||
582 | } | ||
583 | |||
584 | #[test] | ||
585 | fn hint_truncation() { | ||
586 | check_with_config( | ||
587 | InlayHintsConfig { max_length: Some(8), ..Default::default() }, | ||
588 | r#" | ||
589 | struct Smol<T>(T); | ||
590 | |||
591 | struct VeryLongOuterName<T>(T); | ||
592 | |||
593 | fn main() { | ||
594 | let a = Smol(0u32); | ||
595 | //^ Smol<u32> | ||
596 | let b = VeryLongOuterName(0usize); | ||
597 | //^ VeryLongOuterName<…> | ||
598 | let c = Smol(Smol(0u32)) | ||
599 | //^ Smol<Smol<…>> | ||
600 | }"#, | ||
601 | ); | ||
602 | } | ||
603 | |||
604 | #[test] | ||
605 | fn function_call_parameter_hint() { | ||
606 | check( | ||
607 | r#" | ||
608 | enum Option<T> { None, Some(T) } | ||
609 | use Option::*; | ||
610 | |||
611 | struct FileId {} | ||
612 | struct SmolStr {} | ||
613 | |||
614 | struct TextRange {} | ||
615 | struct SyntaxKind {} | ||
616 | struct NavigationTarget {} | ||
617 | |||
618 | struct Test {} | ||
619 | |||
620 | impl Test { | ||
621 | fn method(&self, mut param: i32) -> i32 { param * 2 } | ||
622 | |||
623 | fn from_syntax( | ||
624 | file_id: FileId, | ||
625 | name: SmolStr, | ||
626 | focus_range: Option<TextRange>, | ||
627 | full_range: TextRange, | ||
628 | kind: SyntaxKind, | ||
629 | docs: Option<String>, | ||
630 | ) -> NavigationTarget { | ||
631 | NavigationTarget {} | ||
632 | } | ||
633 | } | ||
634 | |||
635 | fn test_func(mut foo: i32, bar: i32, msg: &str, _: i32, last: i32) -> i32 { | ||
636 | foo + bar | ||
637 | } | ||
638 | |||
639 | fn main() { | ||
640 | let not_literal = 1; | ||
641 | //^^^^^^^^^^^ i32 | ||
642 | let _: i32 = test_func(1, 2, "hello", 3, not_literal); | ||
643 | //^ foo ^ bar ^^^^^^^ msg ^^^^^^^^^^^ last | ||
644 | let t: Test = Test {}; | ||
645 | t.method(123); | ||
646 | //^^^ param | ||
647 | Test::method(&t, 3456); | ||
648 | //^^ &self ^^^^ param | ||
649 | Test::from_syntax( | ||
650 | FileId {}, | ||
651 | //^^^^^^^^^ file_id | ||
652 | "impl".into(), | ||
653 | //^^^^^^^^^^^^^ name | ||
654 | None, | ||
655 | //^^^^ focus_range | ||
656 | TextRange {}, | ||
657 | //^^^^^^^^^^^^ full_range | ||
658 | SyntaxKind {}, | ||
659 | //^^^^^^^^^^^^^ kind | ||
660 | None, | ||
661 | //^^^^ docs | ||
662 | ); | ||
663 | }"#, | ||
664 | ); | ||
665 | } | ||
666 | |||
667 | #[test] | ||
668 | fn omitted_parameters_hints_heuristics() { | ||
669 | check_with_config( | ||
670 | InlayHintsConfig { max_length: Some(8), ..Default::default() }, | ||
671 | r#" | ||
672 | fn map(f: i32) {} | ||
673 | fn filter(predicate: i32) {} | ||
674 | |||
675 | struct TestVarContainer { | ||
676 | test_var: i32, | ||
677 | } | ||
678 | |||
679 | impl TestVarContainer { | ||
680 | fn test_var(&self) -> i32 { | ||
681 | self.test_var | ||
682 | } | ||
683 | } | ||
684 | |||
685 | struct Test {} | ||
686 | |||
687 | impl Test { | ||
688 | fn map(self, f: i32) -> Self { | ||
689 | self | ||
690 | } | ||
691 | |||
692 | fn filter(self, predicate: i32) -> Self { | ||
693 | self | ||
694 | } | ||
695 | |||
696 | fn field(self, value: i32) -> Self { | ||
697 | self | ||
698 | } | ||
699 | |||
700 | fn no_hints_expected(&self, _: i32, test_var: i32) {} | ||
701 | |||
702 | fn frob(&self, frob: bool) {} | ||
703 | } | ||
704 | |||
705 | struct Param {} | ||
706 | |||
707 | fn different_order(param: &Param) {} | ||
708 | fn different_order_mut(param: &mut Param) {} | ||
709 | fn has_underscore(_param: bool) {} | ||
710 | fn enum_matches_param_name(completion_kind: CompletionKind) {} | ||
711 | |||
712 | fn twiddle(twiddle: bool) {} | ||
713 | fn doo(_doo: bool) {} | ||
714 | |||
715 | enum CompletionKind { | ||
716 | Keyword, | ||
717 | } | ||
718 | |||
719 | fn main() { | ||
720 | let container: TestVarContainer = TestVarContainer { test_var: 42 }; | ||
721 | let test: Test = Test {}; | ||
722 | |||
723 | map(22); | ||
724 | filter(33); | ||
725 | |||
726 | let test_processed: Test = test.map(1).filter(2).field(3); | ||
727 | |||
728 | let test_var: i32 = 55; | ||
729 | test_processed.no_hints_expected(22, test_var); | ||
730 | test_processed.no_hints_expected(33, container.test_var); | ||
731 | test_processed.no_hints_expected(44, container.test_var()); | ||
732 | test_processed.frob(false); | ||
733 | |||
734 | twiddle(true); | ||
735 | doo(true); | ||
736 | |||
737 | let mut param_begin: Param = Param {}; | ||
738 | different_order(¶m_begin); | ||
739 | different_order(&mut param_begin); | ||
740 | |||
741 | let param: bool = true; | ||
742 | has_underscore(param); | ||
743 | |||
744 | enum_matches_param_name(CompletionKind::Keyword); | ||
745 | |||
746 | let a: f64 = 7.0; | ||
747 | let b: f64 = 4.0; | ||
748 | let _: f64 = a.div_euclid(b); | ||
749 | let _: f64 = a.abs_sub(b); | ||
750 | }"#, | ||
751 | ); | ||
752 | } | ||
753 | |||
754 | #[test] | ||
755 | fn unit_structs_have_no_type_hints() { | ||
756 | check_with_config( | ||
757 | InlayHintsConfig { max_length: Some(8), ..Default::default() }, | ||
758 | r#" | ||
759 | enum Result<T, E> { Ok(T), Err(E) } | ||
760 | use Result::*; | ||
761 | |||
762 | struct SyntheticSyntax; | ||
763 | |||
764 | fn main() { | ||
765 | match Ok(()) { | ||
766 | Ok(_) => (), | ||
767 | Err(SyntheticSyntax) => (), | ||
768 | } | ||
769 | }"#, | ||
770 | ); | ||
771 | } | ||
772 | |||
773 | #[test] | ||
774 | fn chaining_hints_ignore_comments() { | ||
775 | check_expect( | ||
776 | InlayHintsConfig { | ||
777 | parameter_hints: false, | ||
778 | type_hints: false, | ||
779 | chaining_hints: true, | ||
780 | max_length: None, | ||
781 | }, | ||
782 | r#" | ||
783 | struct A(B); | ||
784 | impl A { fn into_b(self) -> B { self.0 } } | ||
785 | struct B(C); | ||
786 | impl B { fn into_c(self) -> C { self.0 } } | ||
787 | struct C; | ||
788 | |||
789 | fn main() { | ||
790 | let c = A(B(C)) | ||
791 | .into_b() // This is a comment | ||
792 | .into_c(); | ||
793 | } | ||
794 | "#, | ||
795 | expect![[r#" | ||
796 | [ | ||
797 | InlayHint { | ||
798 | range: 147..172, | ||
799 | kind: ChainingHint, | ||
800 | label: "B", | ||
801 | }, | ||
802 | InlayHint { | ||
803 | range: 147..154, | ||
804 | kind: ChainingHint, | ||
805 | label: "A", | ||
806 | }, | ||
807 | ] | ||
808 | "#]], | ||
809 | ); | ||
810 | } | ||
811 | |||
812 | #[test] | ||
813 | fn chaining_hints_without_newlines() { | ||
814 | check_with_config( | ||
815 | InlayHintsConfig { | ||
816 | parameter_hints: false, | ||
817 | type_hints: false, | ||
818 | chaining_hints: true, | ||
819 | max_length: None, | ||
820 | }, | ||
821 | r#" | ||
822 | struct A(B); | ||
823 | impl A { fn into_b(self) -> B { self.0 } } | ||
824 | struct B(C); | ||
825 | impl B { fn into_c(self) -> C { self.0 } } | ||
826 | struct C; | ||
827 | |||
828 | fn main() { | ||
829 | let c = A(B(C)).into_b().into_c(); | ||
830 | }"#, | ||
831 | ); | ||
832 | } | ||
833 | |||
834 | #[test] | ||
835 | fn struct_access_chaining_hints() { | ||
836 | check_expect( | ||
837 | InlayHintsConfig { | ||
838 | parameter_hints: false, | ||
839 | type_hints: false, | ||
840 | chaining_hints: true, | ||
841 | max_length: None, | ||
842 | }, | ||
843 | r#" | ||
844 | struct A { pub b: B } | ||
845 | struct B { pub c: C } | ||
846 | struct C(pub bool); | ||
847 | struct D; | ||
848 | |||
849 | impl D { | ||
850 | fn foo(&self) -> i32 { 42 } | ||
851 | } | ||
852 | |||
853 | fn main() { | ||
854 | let x = A { b: B { c: C(true) } } | ||
855 | .b | ||
856 | .c | ||
857 | .0; | ||
858 | let x = D | ||
859 | .foo(); | ||
860 | }"#, | ||
861 | expect![[r#" | ||
862 | [ | ||
863 | InlayHint { | ||
864 | range: 143..190, | ||
865 | kind: ChainingHint, | ||
866 | label: "C", | ||
867 | }, | ||
868 | InlayHint { | ||
869 | range: 143..179, | ||
870 | kind: ChainingHint, | ||
871 | label: "B", | ||
872 | }, | ||
873 | ] | ||
874 | "#]], | ||
875 | ); | ||
876 | } | ||
877 | |||
878 | #[test] | ||
879 | fn generic_chaining_hints() { | ||
880 | check_expect( | ||
881 | InlayHintsConfig { | ||
882 | parameter_hints: false, | ||
883 | type_hints: false, | ||
884 | chaining_hints: true, | ||
885 | max_length: None, | ||
886 | }, | ||
887 | r#" | ||
888 | struct A<T>(T); | ||
889 | struct B<T>(T); | ||
890 | struct C<T>(T); | ||
891 | struct X<T,R>(T, R); | ||
892 | |||
893 | impl<T> A<T> { | ||
894 | fn new(t: T) -> Self { A(t) } | ||
895 | fn into_b(self) -> B<T> { B(self.0) } | ||
896 | } | ||
897 | impl<T> B<T> { | ||
898 | fn into_c(self) -> C<T> { C(self.0) } | ||
899 | } | ||
900 | fn main() { | ||
901 | let c = A::new(X(42, true)) | ||
902 | .into_b() | ||
903 | .into_c(); | ||
904 | } | ||
905 | "#, | ||
906 | expect![[r#" | ||
907 | [ | ||
908 | InlayHint { | ||
909 | range: 246..283, | ||
910 | kind: ChainingHint, | ||
911 | label: "B<X<i32, bool>>", | ||
912 | }, | ||
913 | InlayHint { | ||
914 | range: 246..265, | ||
915 | kind: ChainingHint, | ||
916 | label: "A<X<i32, bool>>", | ||
917 | }, | ||
918 | ] | ||
919 | "#]], | ||
920 | ); | ||
921 | } | ||
922 | } | ||