]> git.proxmox.com Git - rustc.git/blob - src/tools/rust-analyzer/crates/ide/src/inlay_hints/adjustment.rs
New upstream version 1.70.0+dfsg1
[rustc.git] / src / tools / rust-analyzer / crates / ide / src / inlay_hints / adjustment.rs
1 //! Implementation of "adjustment" inlay hints:
2 //! ```no_run
3 //! let _: u32 = /* <never-to-any> */ loop {};
4 //! let _: &u32 = /* &* */ &mut 0;
5 //! ```
6 use hir::{Adjust, Adjustment, AutoBorrow, HirDisplay, Mutability, PointerCast, Safety, Semantics};
7 use ide_db::RootDatabase;
8
9 use stdx::never;
10 use syntax::{
11 ast::{self, make, AstNode},
12 ted,
13 };
14
15 use crate::{
16 AdjustmentHints, AdjustmentHintsMode, InlayHint, InlayHintLabel, InlayHintsConfig, InlayKind,
17 InlayTooltip,
18 };
19
20 pub(super) fn hints(
21 acc: &mut Vec<InlayHint>,
22 sema: &Semantics<'_, RootDatabase>,
23 config: &InlayHintsConfig,
24 expr: &ast::Expr,
25 ) -> Option<()> {
26 if config.adjustment_hints_hide_outside_unsafe && !sema.is_inside_unsafe(expr) {
27 return None;
28 }
29
30 if config.adjustment_hints == AdjustmentHints::Never {
31 return None;
32 }
33
34 // ParenExpr resolve to their contained expressions HIR so they will dupe these hints
35 if let ast::Expr::ParenExpr(_) = expr {
36 return None;
37 }
38 if let ast::Expr::BlockExpr(b) = expr {
39 if !b.is_standalone() {
40 return None;
41 }
42 }
43
44 let descended = sema.descend_node_into_attributes(expr.clone()).pop();
45 let desc_expr = descended.as_ref().unwrap_or(expr);
46 let adjustments = sema.expr_adjustments(desc_expr).filter(|it| !it.is_empty())?;
47
48 if let ast::Expr::BlockExpr(_) | ast::Expr::IfExpr(_) | ast::Expr::MatchExpr(_) = desc_expr {
49 if let [Adjustment { kind: Adjust::Deref(_), source, .. }, Adjustment { kind: Adjust::Borrow(_), source: _, target }] =
50 &*adjustments
51 {
52 // Don't show unnecessary reborrows for these, they will just repeat the inner ones again
53 if source == target {
54 return None;
55 }
56 }
57 }
58
59 let (postfix, needs_outer_parens, needs_inner_parens) =
60 mode_and_needs_parens_for_adjustment_hints(expr, config.adjustment_hints_mode);
61
62 if needs_outer_parens {
63 acc.push(InlayHint::opening_paren(expr.syntax().text_range()));
64 }
65
66 if postfix && needs_inner_parens {
67 acc.push(InlayHint::opening_paren(expr.syntax().text_range()));
68 acc.push(InlayHint::closing_paren(expr.syntax().text_range()));
69 }
70
71 let (mut tmp0, mut tmp1);
72 let iter: &mut dyn Iterator<Item = _> = if postfix {
73 tmp0 = adjustments.into_iter();
74 &mut tmp0
75 } else {
76 tmp1 = adjustments.into_iter().rev();
77 &mut tmp1
78 };
79
80 for Adjustment { source, target, kind } in iter {
81 if source == target {
82 cov_mark::hit!(same_type_adjustment);
83 continue;
84 }
85
86 // FIXME: Add some nicer tooltips to each of these
87 let (text, coercion) = match kind {
88 Adjust::NeverToAny if config.adjustment_hints == AdjustmentHints::Always => {
89 ("<never-to-any>", "never to any")
90 }
91 Adjust::Deref(_) => ("*", "dereference"),
92 Adjust::Borrow(AutoBorrow::Ref(Mutability::Shared)) => ("&", "borrow"),
93 Adjust::Borrow(AutoBorrow::Ref(Mutability::Mut)) => ("&mut ", "unique borrow"),
94 Adjust::Borrow(AutoBorrow::RawPtr(Mutability::Shared)) => {
95 ("&raw const ", "const pointer borrow")
96 }
97 Adjust::Borrow(AutoBorrow::RawPtr(Mutability::Mut)) => {
98 ("&raw mut ", "mut pointer borrow")
99 }
100 // some of these could be represented via `as` casts, but that's not too nice and
101 // handling everything as a prefix expr makes the `(` and `)` insertion easier
102 Adjust::Pointer(cast) if config.adjustment_hints == AdjustmentHints::Always => {
103 match cast {
104 PointerCast::ReifyFnPointer => {
105 ("<fn-item-to-fn-pointer>", "fn item to fn pointer")
106 }
107 PointerCast::UnsafeFnPointer => (
108 "<safe-fn-pointer-to-unsafe-fn-pointer>",
109 "safe fn pointer to unsafe fn pointer",
110 ),
111 PointerCast::ClosureFnPointer(Safety::Unsafe) => {
112 ("<closure-to-unsafe-fn-pointer>", "closure to unsafe fn pointer")
113 }
114 PointerCast::ClosureFnPointer(Safety::Safe) => {
115 ("<closure-to-fn-pointer>", "closure to fn pointer")
116 }
117 PointerCast::MutToConstPointer => {
118 ("<mut-ptr-to-const-ptr>", "mut ptr to const ptr")
119 }
120 PointerCast::ArrayToPointer => ("<array-ptr-to-element-ptr>", ""),
121 PointerCast::Unsize => ("<unsize>", "unsize"),
122 }
123 }
124 _ => continue,
125 };
126 acc.push(InlayHint {
127 range: expr.syntax().text_range(),
128 kind: if postfix { InlayKind::AdjustmentPostfix } else { InlayKind::Adjustment },
129 label: InlayHintLabel::simple(
130 if postfix { format!(".{}", text.trim_end()) } else { text.to_owned() },
131 Some(InlayTooltip::Markdown(format!(
132 "`{}` → `{}` ({coercion} coercion)",
133 source.display(sema.db),
134 target.display(sema.db),
135 ))),
136 None,
137 ),
138 });
139 }
140 if !postfix && needs_inner_parens {
141 acc.push(InlayHint::opening_paren(expr.syntax().text_range()));
142 acc.push(InlayHint::closing_paren(expr.syntax().text_range()));
143 }
144 if needs_outer_parens {
145 acc.push(InlayHint::closing_paren(expr.syntax().text_range()));
146 }
147 Some(())
148 }
149
150 /// Returns whatever the hint should be postfix and if we need to add paretheses on the inside and/or outside of `expr`,
151 /// if we are going to add (`postfix`) adjustments hints to it.
152 fn mode_and_needs_parens_for_adjustment_hints(
153 expr: &ast::Expr,
154 mode: AdjustmentHintsMode,
155 ) -> (bool, bool, bool) {
156 use {std::cmp::Ordering::*, AdjustmentHintsMode::*};
157
158 match mode {
159 Prefix | Postfix => {
160 let postfix = matches!(mode, Postfix);
161 let (inside, outside) = needs_parens_for_adjustment_hints(expr, postfix);
162 (postfix, inside, outside)
163 }
164 PreferPrefix | PreferPostfix => {
165 let prefer_postfix = matches!(mode, PreferPostfix);
166
167 let (pre_inside, pre_outside) = needs_parens_for_adjustment_hints(expr, false);
168 let prefix = (false, pre_inside, pre_outside);
169 let pre_count = pre_inside as u8 + pre_outside as u8;
170
171 let (post_inside, post_outside) = needs_parens_for_adjustment_hints(expr, true);
172 let postfix = (true, post_inside, post_outside);
173 let post_count = post_inside as u8 + post_outside as u8;
174
175 match pre_count.cmp(&post_count) {
176 Less => prefix,
177 Greater => postfix,
178 Equal if prefer_postfix => postfix,
179 Equal => prefix,
180 }
181 }
182 }
183 }
184
185 /// Returns whatever we need to add paretheses on the inside and/or outside of `expr`,
186 /// if we are going to add (`postfix`) adjustments hints to it.
187 fn needs_parens_for_adjustment_hints(expr: &ast::Expr, postfix: bool) -> (bool, bool) {
188 // This is a very miserable pile of hacks...
189 //
190 // `Expr::needs_parens_in` requires that the expression is the child of the other expression,
191 // that is supposed to be its parent.
192 //
193 // But we want to check what would happen if we add `*`/`.*` to the inner expression.
194 // To check for inner we need `` expr.needs_parens_in(`*expr`) ``,
195 // to check for outer we need `` `*expr`.needs_parens_in(parent) ``,
196 // where "expr" is the `expr` parameter, `*expr` is the editted `expr`,
197 // and "parent" is the parent of the original expression...
198 //
199 // For this we utilize mutable mutable trees, which is a HACK, but it works.
200 //
201 // FIXME: comeup with a better API for `needs_parens_in`, so that we don't have to do *this*
202
203 // Make `&expr`/`expr?`
204 let dummy_expr = {
205 // `make::*` function go through a string, so they parse wrongly.
206 // for example `` make::expr_try(`|| a`) `` would result in a
207 // `|| (a?)` and not `(|| a)?`.
208 //
209 // Thus we need dummy parens to preserve the relationship we want.
210 // The parens are then simply ignored by the following code.
211 let dummy_paren = make::expr_paren(expr.clone());
212 if postfix {
213 make::expr_try(dummy_paren)
214 } else {
215 make::expr_ref(dummy_paren, false)
216 }
217 };
218
219 // Do the dark mutable tree magic.
220 // This essentially makes `dummy_expr` and `expr` switch places (families),
221 // so that `expr`'s parent is not `dummy_expr`'s parent.
222 let dummy_expr = dummy_expr.clone_for_update();
223 let expr = expr.clone_for_update();
224 ted::replace(expr.syntax(), dummy_expr.syntax());
225
226 let parent = dummy_expr.syntax().parent();
227 let Some(expr) = (|| {
228 if postfix {
229 let ast::Expr::TryExpr(e) = &dummy_expr else { return None };
230 let Some(ast::Expr::ParenExpr(e)) = e.expr() else { return None };
231
232 e.expr()
233 } else {
234 let ast::Expr::RefExpr(e) = &dummy_expr else { return None };
235 let Some(ast::Expr::ParenExpr(e)) = e.expr() else { return None };
236
237 e.expr()
238 }
239 })() else {
240 never!("broken syntax tree?\n{:?}\n{:?}", expr, dummy_expr);
241 return (true, true)
242 };
243
244 // At this point
245 // - `parent` is the parrent of the original expression
246 // - `dummy_expr` is the original expression wrapped in the operator we want (`*`/`.*`)
247 // - `expr` is the clone of the original expression (with `dummy_expr` as the parent)
248
249 let needs_outer_parens = parent.map_or(false, |p| dummy_expr.needs_parens_in(p));
250 let needs_inner_parens = expr.needs_parens_in(dummy_expr.syntax().clone());
251
252 (needs_outer_parens, needs_inner_parens)
253 }
254
255 #[cfg(test)]
256 mod tests {
257 use crate::{
258 inlay_hints::tests::{check_with_config, DISABLED_CONFIG},
259 AdjustmentHints, AdjustmentHintsMode, InlayHintsConfig,
260 };
261
262 #[test]
263 fn adjustment_hints() {
264 check_with_config(
265 InlayHintsConfig { adjustment_hints: AdjustmentHints::Always, ..DISABLED_CONFIG },
266 r#"
267 //- minicore: coerce_unsized, fn, eq
268 fn main() {
269 let _: u32 = loop {};
270 //^^^^^^^<never-to-any>
271 let _: &u32 = &mut 0;
272 //^^^^^^&
273 //^^^^^^*
274 let _: &mut u32 = &mut 0;
275 //^^^^^^&mut $
276 //^^^^^^*
277 let _: *const u32 = &mut 0;
278 //^^^^^^&raw const $
279 //^^^^^^*
280 let _: *mut u32 = &mut 0;
281 //^^^^^^&raw mut $
282 //^^^^^^*
283 let _: fn() = main;
284 //^^^^<fn-item-to-fn-pointer>
285 let _: unsafe fn() = main;
286 //^^^^<safe-fn-pointer-to-unsafe-fn-pointer>
287 //^^^^<fn-item-to-fn-pointer>
288 let _: unsafe fn() = main as fn();
289 //^^^^^^^^^^^^<safe-fn-pointer-to-unsafe-fn-pointer>
290 //^^^^^^^^^^^^(
291 //^^^^^^^^^^^^)
292 let _: fn() = || {};
293 //^^^^^<closure-to-fn-pointer>
294 let _: unsafe fn() = || {};
295 //^^^^^<closure-to-unsafe-fn-pointer>
296 let _: *const u32 = &mut 0u32 as *mut u32;
297 //^^^^^^^^^^^^^^^^^^^^^<mut-ptr-to-const-ptr>
298 //^^^^^^^^^^^^^^^^^^^^^(
299 //^^^^^^^^^^^^^^^^^^^^^)
300 let _: &mut [_] = &mut [0; 0];
301 //^^^^^^^^^^^<unsize>
302 //^^^^^^^^^^^&mut $
303 //^^^^^^^^^^^*
304
305 Struct.consume();
306 Struct.by_ref();
307 //^^^^^^(
308 //^^^^^^&
309 //^^^^^^)
310 Struct.by_ref_mut();
311 //^^^^^^(
312 //^^^^^^&mut $
313 //^^^^^^)
314
315 (&Struct).consume();
316 //^^^^^^^*
317 (&Struct).by_ref();
318
319 (&mut Struct).consume();
320 //^^^^^^^^^^^*
321 (&mut Struct).by_ref();
322 //^^^^^^^^^^^&
323 //^^^^^^^^^^^*
324 (&mut Struct).by_ref_mut();
325
326 // Check that block-like expressions don't duplicate hints
327 let _: &mut [u32] = (&mut []);
328 //^^^^^^^<unsize>
329 //^^^^^^^&mut $
330 //^^^^^^^*
331 let _: &mut [u32] = { &mut [] };
332 //^^^^^^^<unsize>
333 //^^^^^^^&mut $
334 //^^^^^^^*
335 let _: &mut [u32] = unsafe { &mut [] };
336 //^^^^^^^<unsize>
337 //^^^^^^^&mut $
338 //^^^^^^^*
339 let _: &mut [u32] = if true {
340 &mut []
341 //^^^^^^^<unsize>
342 //^^^^^^^&mut $
343 //^^^^^^^*
344 } else {
345 loop {}
346 //^^^^^^^<never-to-any>
347 };
348 let _: &mut [u32] = match () { () => &mut [] };
349 //^^^^^^^<unsize>
350 //^^^^^^^&mut $
351 //^^^^^^^*
352
353 let _: &mut dyn Fn() = &mut || ();
354 //^^^^^^^^^^<unsize>
355 //^^^^^^^^^^&mut $
356 //^^^^^^^^^^*
357 () == ();
358 // ^^&
359 // ^^&
360 (()) == {()};
361 // ^^&
362 // ^^^^&
363 }
364
365 #[derive(Copy, Clone)]
366 struct Struct;
367 impl Struct {
368 fn consume(self) {}
369 fn by_ref(&self) {}
370 fn by_ref_mut(&mut self) {}
371 }
372 "#,
373 )
374 }
375
376 #[test]
377 fn adjustment_hints_postfix() {
378 check_with_config(
379 InlayHintsConfig {
380 adjustment_hints: AdjustmentHints::Always,
381 adjustment_hints_mode: AdjustmentHintsMode::Postfix,
382 ..DISABLED_CONFIG
383 },
384 r#"
385 //- minicore: coerce_unsized, fn, eq
386 fn main() {
387
388 Struct.consume();
389 Struct.by_ref();
390 //^^^^^^.&
391 Struct.by_ref_mut();
392 //^^^^^^.&mut
393
394 (&Struct).consume();
395 //^^^^^^^(
396 //^^^^^^^)
397 //^^^^^^^.*
398 (&Struct).by_ref();
399
400 (&mut Struct).consume();
401 //^^^^^^^^^^^(
402 //^^^^^^^^^^^)
403 //^^^^^^^^^^^.*
404 (&mut Struct).by_ref();
405 //^^^^^^^^^^^(
406 //^^^^^^^^^^^)
407 //^^^^^^^^^^^.*
408 //^^^^^^^^^^^.&
409 (&mut Struct).by_ref_mut();
410
411 // Check that block-like expressions don't duplicate hints
412 let _: &mut [u32] = (&mut []);
413 //^^^^^^^(
414 //^^^^^^^)
415 //^^^^^^^.*
416 //^^^^^^^.&mut
417 //^^^^^^^.<unsize>
418 let _: &mut [u32] = { &mut [] };
419 //^^^^^^^(
420 //^^^^^^^)
421 //^^^^^^^.*
422 //^^^^^^^.&mut
423 //^^^^^^^.<unsize>
424 let _: &mut [u32] = unsafe { &mut [] };
425 //^^^^^^^(
426 //^^^^^^^)
427 //^^^^^^^.*
428 //^^^^^^^.&mut
429 //^^^^^^^.<unsize>
430 let _: &mut [u32] = if true {
431 &mut []
432 //^^^^^^^(
433 //^^^^^^^)
434 //^^^^^^^.*
435 //^^^^^^^.&mut
436 //^^^^^^^.<unsize>
437 } else {
438 loop {}
439 //^^^^^^^.<never-to-any>
440 };
441 let _: &mut [u32] = match () { () => &mut [] };
442 //^^^^^^^(
443 //^^^^^^^)
444 //^^^^^^^.*
445 //^^^^^^^.&mut
446 //^^^^^^^.<unsize>
447
448 let _: &mut dyn Fn() = &mut || ();
449 //^^^^^^^^^^(
450 //^^^^^^^^^^)
451 //^^^^^^^^^^.*
452 //^^^^^^^^^^.&mut
453 //^^^^^^^^^^.<unsize>
454 () == ();
455 // ^^.&
456 // ^^.&
457 (()) == {()};
458 // ^^.&
459 // ^^^^.&
460 }
461
462 #[derive(Copy, Clone)]
463 struct Struct;
464 impl Struct {
465 fn consume(self) {}
466 fn by_ref(&self) {}
467 fn by_ref_mut(&mut self) {}
468 }
469 "#,
470 );
471 }
472
473 #[test]
474 fn adjustment_hints_prefer_prefix() {
475 check_with_config(
476 InlayHintsConfig {
477 adjustment_hints: AdjustmentHints::Always,
478 adjustment_hints_mode: AdjustmentHintsMode::PreferPrefix,
479 ..DISABLED_CONFIG
480 },
481 r#"
482 fn main() {
483 let _: u32 = loop {};
484 //^^^^^^^<never-to-any>
485
486 Struct.by_ref();
487 //^^^^^^.&
488
489 let (): () = return ();
490 //^^^^^^^^^<never-to-any>
491
492 struct Struct;
493 impl Struct { fn by_ref(&self) {} }
494 }
495 "#,
496 )
497 }
498
499 #[test]
500 fn adjustment_hints_prefer_postfix() {
501 check_with_config(
502 InlayHintsConfig {
503 adjustment_hints: AdjustmentHints::Always,
504 adjustment_hints_mode: AdjustmentHintsMode::PreferPostfix,
505 ..DISABLED_CONFIG
506 },
507 r#"
508 fn main() {
509 let _: u32 = loop {};
510 //^^^^^^^.<never-to-any>
511
512 Struct.by_ref();
513 //^^^^^^.&
514
515 let (): () = return ();
516 //^^^^^^^^^<never-to-any>
517
518 struct Struct;
519 impl Struct { fn by_ref(&self) {} }
520 }
521 "#,
522 )
523 }
524
525 #[test]
526 fn never_to_never_is_never_shown() {
527 cov_mark::check!(same_type_adjustment);
528 check_with_config(
529 InlayHintsConfig { adjustment_hints: AdjustmentHints::Always, ..DISABLED_CONFIG },
530 r#"
531 fn never() -> ! {
532 return loop {};
533 }
534
535 fn or_else() {
536 let () = () else { return };
537 }
538 "#,
539 )
540 }
541
542 #[test]
543 fn adjustment_hints_unsafe_only() {
544 check_with_config(
545 InlayHintsConfig {
546 adjustment_hints: AdjustmentHints::Always,
547 adjustment_hints_hide_outside_unsafe: true,
548 ..DISABLED_CONFIG
549 },
550 r#"
551 unsafe fn enabled() {
552 f(&&());
553 //^^^^&
554 //^^^^*
555 //^^^^*
556 }
557
558 fn disabled() {
559 f(&&());
560 }
561
562 fn mixed() {
563 f(&&());
564
565 unsafe {
566 f(&&());
567 //^^^^&
568 //^^^^*
569 //^^^^*
570 }
571 }
572
573 const _: () = {
574 f(&&());
575
576 unsafe {
577 f(&&());
578 //^^^^&
579 //^^^^*
580 //^^^^*
581 }
582 };
583
584 static STATIC: () = {
585 f(&&());
586
587 unsafe {
588 f(&&());
589 //^^^^&
590 //^^^^*
591 //^^^^*
592 }
593 };
594
595 enum E {
596 Disable = { f(&&()); 0 },
597 Enable = unsafe { f(&&()); 1 },
598 //^^^^&
599 //^^^^*
600 //^^^^*
601 }
602
603 const fn f(_: &()) {}
604 "#,
605 )
606 }
607
608 #[test]
609 fn adjustment_hints_unsafe_only_with_item() {
610 check_with_config(
611 InlayHintsConfig {
612 adjustment_hints: AdjustmentHints::Always,
613 adjustment_hints_hide_outside_unsafe: true,
614 ..DISABLED_CONFIG
615 },
616 r#"
617 fn a() {
618 struct Struct;
619 impl Struct {
620 fn by_ref(&self) {}
621 }
622
623 _ = Struct.by_ref();
624
625 _ = unsafe { Struct.by_ref() };
626 //^^^^^^(
627 //^^^^^^&
628 //^^^^^^)
629 }
630 "#,
631 );
632 }
633
634 #[test]
635 fn let_stmt_explicit_ty() {
636 check_with_config(
637 InlayHintsConfig { adjustment_hints: AdjustmentHints::Always, ..DISABLED_CONFIG },
638 r#"
639 fn main() {
640 let () = return;
641 //^^^^^^<never-to-any>
642 let (): () = return;
643 //^^^^^^<never-to-any>
644 }
645 "#,
646 )
647 }
648 }