1use std::ops::Not;
7
8use either::Either;
9use hir::{
10 Adjust, Adjustment, AutoBorrow, DisplayTarget, HirDisplay, Mutability, OverloadedDeref,
11 PointerCast, Safety,
12};
13use ide_db::famous_defs::FamousDefs;
14
15use ide_db::text_edit::TextEditBuilder;
16use syntax::ast::{self, AstNode, prec::ExprPrecedence};
17
18use crate::{
19 AdjustmentHints, AdjustmentHintsMode, InlayHint, InlayHintLabel, InlayHintLabelPart,
20 InlayHintPosition, InlayHintsConfig, InlayKind, InlayTooltip,
21};
22
23pub(super) fn hints(
24 acc: &mut Vec<InlayHint>,
25 FamousDefs(sema, _): &FamousDefs<'_, '_>,
26 config: &InlayHintsConfig<'_>,
27 display_target: DisplayTarget,
28 expr: &ast::Expr,
29) -> Option<()> {
30 if config.adjustment_hints_hide_outside_unsafe && !sema.is_inside_unsafe(expr) {
31 return None;
32 }
33
34 if config.adjustment_hints == AdjustmentHints::Never {
35 return None;
36 }
37
38 if let ast::Expr::ParenExpr(_) = expr {
40 return None;
41 }
42 if let ast::Expr::BlockExpr(b) = expr
43 && !b.is_standalone()
44 {
45 return None;
46 }
47
48 let descended = sema.descend_node_into_attributes(expr.clone()).pop();
49 let desc_expr = descended.as_ref().unwrap_or(expr);
50 let mut adjustments = sema.expr_adjustments(desc_expr).filter(|it| !it.is_empty())?;
51
52 if config.adjustment_hints_disable_reborrows {
53 let mut i = 0;
55 while i < adjustments.len().saturating_sub(1) {
56 let [current, next, ..] = &adjustments[i..] else { unreachable!() };
57 if matches!(current.kind, Adjust::Deref(None))
58 && matches!(next.kind, Adjust::Borrow(AutoBorrow::Ref(_)))
59 {
60 adjustments.splice(i..i + 2, []);
61 } else {
62 i += 1;
63 }
64 }
65 }
66
67 if let ast::Expr::BlockExpr(_) | ast::Expr::IfExpr(_) | ast::Expr::MatchExpr(_) = desc_expr {
68 if matches!(
70 &*adjustments,
71 [Adjustment { kind: Adjust::Deref(_), source, .. }, Adjustment { kind: Adjust::Borrow(_), target, .. }]
72 if source == target
73 ) {
74 return None;
75 }
76 }
77
78 let (postfix, needs_outer_parens, needs_inner_parens) =
79 mode_and_needs_parens_for_adjustment_hints(expr, config.adjustment_hints_mode);
80
81 let range = expr.syntax().text_range();
82 let mut pre = InlayHint {
83 range,
84 position: InlayHintPosition::Before,
85 pad_left: false,
86 pad_right: false,
87 kind: InlayKind::Adjustment,
88 label: InlayHintLabel::default(),
89 text_edit: None,
90 resolve_parent: Some(range),
91 };
92 let mut post = InlayHint {
93 range,
94 position: InlayHintPosition::After,
95 pad_left: false,
96 pad_right: false,
97 kind: InlayKind::Adjustment,
98 label: InlayHintLabel::default(),
99 text_edit: None,
100 resolve_parent: Some(range),
101 };
102
103 if needs_outer_parens || (postfix && needs_inner_parens) {
104 pre.label.append_str("(");
105 }
106
107 if postfix && needs_inner_parens {
108 post.label.append_str(")");
109 }
110
111 let mut iter = if postfix {
112 Either::Left(adjustments.into_iter())
113 } else {
114 Either::Right(adjustments.into_iter().rev())
115 };
116 let iter: &mut dyn Iterator<Item = _> = iter.as_mut().either(|it| it as _, |it| it as _);
117
118 let mut has_adjustments = false;
119 let mut allow_edit = !postfix;
120 for Adjustment { source, target, kind } in iter {
121 if source == target {
122 cov_mark::hit!(same_type_adjustment);
123 continue;
124 }
125 has_adjustments = true;
126
127 let (text, coercion, detailed_tooltip) = match kind {
128 Adjust::NeverToAny if config.adjustment_hints == AdjustmentHints::Always => {
129 allow_edit = false;
130 (
131 "<never-to-any>",
132 "never to any",
133 "Coerces the never type `!` into any other type. This happens in code paths that never return, like after `panic!()` or `return`.",
134 )
135 }
136 Adjust::Deref(None) => (
137 "*",
138 "dereference",
139 "Built-in dereference of a reference to access the underlying value. The compiler inserts `*` to get the value from `&T`.",
140 ),
141 Adjust::Deref(Some(OverloadedDeref(Mutability::Shared))) => (
142 "*",
143 "`Deref` dereference",
144 "Dereference via the `Deref` trait. Used for types like `Box<T>` or `Rc<T>` so they act like plain `T`.",
145 ),
146 Adjust::Deref(Some(OverloadedDeref(Mutability::Mut))) => (
147 "*",
148 "`DerefMut` dereference",
149 "Mutable dereference using the `DerefMut` trait. Enables smart pointers to give mutable access to their inner values.",
150 ),
151 Adjust::Borrow(AutoBorrow::Ref(Mutability::Shared)) => (
152 "&",
153 "shared borrow",
154 "Inserts `&` to create a shared reference. Lets you use a value without moving or cloning it.",
155 ),
156 Adjust::Borrow(AutoBorrow::Ref(Mutability::Mut)) => (
157 "&mut ",
158 "mutable borrow",
159 "Inserts `&mut` to create a unique, mutable reference. Lets you modify a value without taking ownership.",
160 ),
161 Adjust::Borrow(AutoBorrow::RawPtr(Mutability::Shared)) => (
162 "&raw const ",
163 "const raw pointer",
164 "Converts a reference to a raw const pointer `*const T`. Often used when working with FFI or unsafe code.",
165 ),
166 Adjust::Borrow(AutoBorrow::RawPtr(Mutability::Mut)) => (
167 "&raw mut ",
168 "mut raw pointer",
169 "Converts a mutable reference to a raw mutable pointer `*mut T`. Allows mutation in unsafe contexts.",
170 ),
171 Adjust::Pointer(cast) if config.adjustment_hints == AdjustmentHints::Always => {
174 allow_edit = false;
175 match cast {
176 PointerCast::ReifyFnPointer => (
177 "<fn-item-to-fn-pointer>",
178 "fn item to fn pointer",
179 "Converts a named function to a function pointer `fn()`. Useful when passing functions as values.",
180 ),
181 PointerCast::UnsafeFnPointer => (
182 "<safe-fn-pointer-to-unsafe-fn-pointer>",
183 "safe fn pointer to unsafe fn pointer",
184 "Coerces a safe function pointer to an unsafe one. Allows calling it in an unsafe context.",
185 ),
186 PointerCast::ClosureFnPointer(Safety::Unsafe) => (
187 "<closure-to-unsafe-fn-pointer>",
188 "closure to unsafe fn pointer",
189 "Converts a non-capturing closure to an unsafe function pointer. Required for use in `extern` or unsafe APIs.",
190 ),
191 PointerCast::ClosureFnPointer(Safety::Safe) => (
192 "<closure-to-fn-pointer>",
193 "closure to fn pointer",
194 "Converts a non-capturing closure to a function pointer. Lets closures behave like plain functions.",
195 ),
196 PointerCast::MutToConstPointer => (
197 "<mut-ptr-to-const-ptr>",
198 "mut ptr to const ptr",
199 "Coerces `*mut T` to `*const T`. Safe because const pointers restrict what you can do.",
200 ),
201 PointerCast::ArrayToPointer => (
202 "<array-ptr-to-element-ptr>",
203 "array to pointer",
204 "Converts an array to a pointer to its first element. Similar to how arrays decay to pointers in C.",
205 ),
206 PointerCast::Unsize => (
207 "<unsize>",
208 "unsize coercion",
209 "Converts a sized type to an unsized one. Used for things like turning arrays into slices or concrete types into trait objects.",
210 ),
211 }
212 }
213 _ => continue,
214 };
215 let label = InlayHintLabelPart {
216 text: if postfix { format!(".{}", text.trim_end()) } else { text.to_owned() },
217 linked_location: None,
218 tooltip: Some(config.lazy_tooltip(|| {
219 InlayTooltip::Markdown(format!(
220 "`{}` → `{}`\n\n**{}**\n\n{}",
221 source.display(sema.db, display_target),
222 target.display(sema.db, display_target),
223 coercion,
224 detailed_tooltip
225 ))
226 })),
227 };
228 if postfix { &mut post } else { &mut pre }.label.append_part(label);
229 }
230 if !has_adjustments {
231 return None;
232 }
233
234 if !postfix && needs_inner_parens {
235 pre.label.append_str("(");
236 }
237 if needs_outer_parens || (!postfix && needs_inner_parens) {
238 post.label.append_str(")");
239 }
240
241 let mut pre = pre.label.parts.is_empty().not().then_some(pre);
242 let mut post = post.label.parts.is_empty().not().then_some(post);
243 if pre.is_none() && post.is_none() {
244 return None;
245 }
246 if allow_edit {
247 let edit = Some(config.lazy_text_edit(|| {
248 let mut b = TextEditBuilder::default();
249 if let Some(pre) = &pre {
250 b.insert(
251 pre.range.start(),
252 pre.label.parts.iter().map(|part| &*part.text).collect::<String>(),
253 );
254 }
255 if let Some(post) = &post {
256 b.insert(
257 post.range.end(),
258 post.label.parts.iter().map(|part| &*part.text).collect::<String>(),
259 );
260 }
261 b.finish()
262 }));
263 match (&mut pre, &mut post) {
264 (Some(pre), Some(post)) => {
265 pre.text_edit = edit.clone();
266 post.text_edit = edit;
267 }
268 (Some(pre), None) => pre.text_edit = edit,
269 (None, Some(post)) => post.text_edit = edit,
270 (None, None) => (),
271 }
272 }
273 acc.extend(pre);
274 acc.extend(post);
275 Some(())
276}
277
278fn mode_and_needs_parens_for_adjustment_hints(
281 expr: &ast::Expr,
282 mode: AdjustmentHintsMode,
283) -> (bool, bool, bool) {
284 use {AdjustmentHintsMode::*, std::cmp::Ordering::*};
285
286 match mode {
287 Prefix | Postfix => {
288 let postfix = matches!(mode, Postfix);
289 let (inside, outside) = needs_parens_for_adjustment_hints(expr, postfix);
290 (postfix, inside, outside)
291 }
292 PreferPrefix | PreferPostfix => {
293 let prefer_postfix = matches!(mode, PreferPostfix);
294
295 let (pre_inside, pre_outside) = needs_parens_for_adjustment_hints(expr, false);
296 let prefix = (false, pre_inside, pre_outside);
297 let pre_count = pre_inside as u8 + pre_outside as u8;
298
299 let (post_inside, post_outside) = needs_parens_for_adjustment_hints(expr, true);
300 let postfix = (true, post_inside, post_outside);
301 let post_count = post_inside as u8 + post_outside as u8;
302
303 match pre_count.cmp(&post_count) {
304 Less => prefix,
305 Greater => postfix,
306 Equal if prefer_postfix => postfix,
307 Equal => prefix,
308 }
309 }
310 }
311}
312
313fn needs_parens_for_adjustment_hints(expr: &ast::Expr, postfix: bool) -> (bool, bool) {
316 let prec = expr.precedence();
317 if postfix {
318 let needs_inner_parens = prec.needs_parentheses_in(ExprPrecedence::Postfix);
319 let needs_outer_parens = false;
321 (needs_outer_parens, needs_inner_parens)
322 } else {
323 let needs_inner_parens = prec.needs_parentheses_in(ExprPrecedence::Prefix);
324 let parent = expr
325 .syntax()
326 .parent()
327 .and_then(ast::Expr::cast)
328 .filter(|it| !matches!(it, ast::Expr::ParenExpr(_)))
330 .map(|it| it.precedence())
331 .filter(|&prec| prec != ExprPrecedence::Unambiguous);
332
333 let needs_outer_parens = parent
336 .is_some_and(|parent_prec| ExprPrecedence::Prefix.needs_parentheses_in(parent_prec));
337 (needs_outer_parens, needs_inner_parens)
338 }
339}
340
341#[cfg(test)]
342mod tests {
343 use crate::{
344 AdjustmentHints, AdjustmentHintsMode, InlayHintsConfig,
345 inlay_hints::tests::{DISABLED_CONFIG, check_with_config},
346 };
347
348 #[test]
349 fn adjustment_hints_prefix() {
350 check_with_config(
351 InlayHintsConfig { adjustment_hints: AdjustmentHints::Always, ..DISABLED_CONFIG },
352 r#"
353//- minicore: coerce_unsized, fn, eq, index, dispatch_from_dyn, builtin_impls
354fn main() {
355 let _: u32 = loop {};
356 //^^^^^^^<never-to-any>
357 let _: &u32 = &mut 0;
358 //^^^^^^&*
359 let _: &mut u32 = &mut 0;
360 //^^^^^^&mut *
361 let _: *const u32 = &mut 0;
362 //^^^^^^&raw const *
363 let _: *mut u32 = &mut 0;
364 //^^^^^^&raw mut *
365 let _: fn() = main;
366 //^^^^<fn-item-to-fn-pointer>
367 let _: unsafe fn() = main;
368 //^^^^<safe-fn-pointer-to-unsafe-fn-pointer><fn-item-to-fn-pointer>
369 let _: unsafe fn() = main as fn();
370 //^^^^^^^^^^^^<safe-fn-pointer-to-unsafe-fn-pointer>(
371 //^^^^^^^^^^^^)
372 //^^^^<fn-item-to-fn-pointer>
373 let _: fn() = || {};
374 //^^^^^<closure-to-fn-pointer>
375 let _: unsafe fn() = || {};
376 //^^^^^<closure-to-unsafe-fn-pointer>
377 let _: *const u32 = &mut 0u32 as *mut u32;
378 //^^^^^^^^^^^^^^^^^^^^^<mut-ptr-to-const-ptr>(
379 //^^^^^^^^^^^^^^^^^^^^^)
380 //^^^^^^^^^&raw mut *
381 let _: &mut [_] = &mut [0; 0];
382 //^^^^^^^^^^^<unsize>&mut *
383
384 Struct.consume();
385 Struct.by_ref();
386 //^^^^^^(&
387 //^^^^^^)
388 Struct.by_ref_mut();
389 //^^^^^^(&mut $
390 //^^^^^^)
391
392 (&Struct).consume();
393 //^^^^^^^*
394 (&Struct).by_ref();
395 //^^^^^^^&*
396
397 (&mut Struct).consume();
398 //^^^^^^^^^^^*
399 (&mut Struct).by_ref();
400 //^^^^^^^^^^^&*
401 (&mut Struct).by_ref_mut();
402 //^^^^^^^^^^^&mut *
403
404 // Check that block-like expressions don't duplicate hints
405 let _: &mut [u32] = (&mut []);
406 //^^^^^^^<unsize>&mut *
407 let _: &mut [u32] = { &mut [] };
408 //^^^^^^^<unsize>&mut *
409 let _: &mut [u32] = unsafe { &mut [] };
410 //^^^^^^^<unsize>&mut *
411 let _: &mut [u32] = if true {
412 &mut []
413 //^^^^^^^<unsize>&mut *
414 } else {
415 loop {}
416 //^^^^^^^<never-to-any>
417 };
418 let _: &mut [u32] = match () { () => &mut [] };
419 //^^^^^^^<unsize>&mut *
420
421 let _: &mut dyn Fn() = &mut || ();
422 //^^^^^^^^^^<unsize>&mut *
423 () == ();
424 // ^^&
425 // ^^&
426 (()) == {()};
427 // ^^&
428 // ^^^^&
429 let closure: &dyn Fn = &|| ();
430 //^^^^^^<unsize>&*
431 closure();
432 Struct[0];
433 //^^^^^^(&
434 //^^^^^^)
435 &mut Struct[0];
436 //^^^^^^(&mut $
437 //^^^^^^)
438 let _: (&mut (),) = (&mut (),);
439 //^^^^^^^&mut *
440}
441
442#[derive(Copy, Clone)]
443struct Struct;
444impl Struct {
445 fn consume(self) {}
446 fn by_ref(&self) {}
447 fn by_ref_mut(&mut self) {}
448}
449struct StructMut;
450impl core::ops::Index<usize> for Struct {
451 type Output = ();
452}
453impl core::ops::IndexMut for Struct {}
454"#,
455 );
456 }
457
458 #[test]
459 fn adjustment_hints_postfix() {
460 check_with_config(
461 InlayHintsConfig {
462 adjustment_hints: AdjustmentHints::Always,
463 adjustment_hints_mode: AdjustmentHintsMode::Postfix,
464 ..DISABLED_CONFIG
465 },
466 r#"
467//- minicore: coerce_unsized, fn, eq, index, dispatch_from_dyn, builtin_impls
468fn main() {
469 Struct.consume();
470 Struct.by_ref();
471 //^^^^^^.&
472 Struct.by_ref_mut();
473 //^^^^^^.&mut
474
475 (&Struct).consume();
476 //^^^^^^^(
477 //^^^^^^^).*
478 (&Struct).by_ref();
479 //^^^^^^^(
480 //^^^^^^^).*.&
481
482 (&mut Struct).consume();
483 //^^^^^^^^^^^(
484 //^^^^^^^^^^^).*
485 (&mut Struct).by_ref();
486 //^^^^^^^^^^^(
487 //^^^^^^^^^^^).*.&
488 (&mut Struct).by_ref_mut();
489 //^^^^^^^^^^^(
490 //^^^^^^^^^^^).*.&mut
491
492 // Check that block-like expressions don't duplicate hints
493 let _: &mut [u32] = (&mut []);
494 //^^^^^^^(
495 //^^^^^^^).*.&mut.<unsize>
496 let _: &mut [u32] = { &mut [] };
497 //^^^^^^^(
498 //^^^^^^^).*.&mut.<unsize>
499 let _: &mut [u32] = unsafe { &mut [] };
500 //^^^^^^^(
501 //^^^^^^^).*.&mut.<unsize>
502 let _: &mut [u32] = if true {
503 &mut []
504 //^^^^^^^(
505 //^^^^^^^).*.&mut.<unsize>
506 } else {
507 loop {}
508 //^^^^^^^.<never-to-any>
509 };
510 let _: &mut [u32] = match () { () => &mut [] };
511 //^^^^^^^(
512 //^^^^^^^).*.&mut.<unsize>
513
514 let _: &mut dyn Fn() = &mut || ();
515 //^^^^^^^^^^(
516 //^^^^^^^^^^).*.&mut.<unsize>
517 () == ();
518 // ^^.&
519 // ^^.&
520 (()) == {()};
521 // ^^.&
522 // ^^^^.&
523 let closure: &dyn Fn = &|| ();
524 //^^^^^^(
525 //^^^^^^).*.&.<unsize>
526 closure();
527 Struct[0];
528 //^^^^^^.&
529 &mut Struct[0];
530 //^^^^^^.&mut
531 let _: (&mut (),) = (&mut (),);
532 //^^^^^^^(
533 //^^^^^^^).*.&mut
534}
535
536#[derive(Copy, Clone)]
537struct Struct;
538impl Struct {
539 fn consume(self) {}
540 fn by_ref(&self) {}
541 fn by_ref_mut(&mut self) {}
542}
543struct StructMut;
544impl core::ops::Index<usize> for Struct {
545 type Output = ();
546}
547impl core::ops::IndexMut for Struct {}
548"#,
549 );
550 }
551
552 #[test]
553 fn adjustment_hints_prefer_prefix() {
554 check_with_config(
555 InlayHintsConfig {
556 adjustment_hints: AdjustmentHints::Always,
557 adjustment_hints_mode: AdjustmentHintsMode::PreferPrefix,
558 ..DISABLED_CONFIG
559 },
560 r#"
561fn main() {
562 let _: u32 = loop {};
563 //^^^^^^^<never-to-any>
564
565 Struct.by_ref();
566 //^^^^^^.&
567
568 let (): () = return ();
569 //^^^^^^^^^<never-to-any>
570
571 struct Struct;
572 impl Struct { fn by_ref(&self) {} }
573}
574 "#,
575 )
576 }
577
578 #[test]
579 fn adjustment_hints_prefer_postfix() {
580 check_with_config(
581 InlayHintsConfig {
582 adjustment_hints: AdjustmentHints::Always,
583 adjustment_hints_mode: AdjustmentHintsMode::PreferPostfix,
584 ..DISABLED_CONFIG
585 },
586 r#"
587fn main() {
588 let _: u32 = loop {};
589 //^^^^^^^.<never-to-any>
590
591 Struct.by_ref();
592 //^^^^^^.&
593
594 let (): () = return ();
595 //^^^^^^^^^<never-to-any>
596
597 struct Struct;
598 impl Struct { fn by_ref(&self) {} }
599}
600 "#,
601 )
602 }
603
604 #[test]
605 fn never_to_never_is_never_shown() {
606 cov_mark::check!(same_type_adjustment);
607 check_with_config(
608 InlayHintsConfig { adjustment_hints: AdjustmentHints::Always, ..DISABLED_CONFIG },
609 r#"
610fn never() -> ! {
611 return loop {};
612}
613
614fn or_else() {
615 let () = () else { return };
616}
617 "#,
618 )
619 }
620
621 #[test]
622 fn adjustment_hints_unsafe_only() {
623 check_with_config(
624 InlayHintsConfig {
625 adjustment_hints: AdjustmentHints::Always,
626 adjustment_hints_hide_outside_unsafe: true,
627 ..DISABLED_CONFIG
628 },
629 r#"
630unsafe fn enabled() {
631 f(&&());
632 //^^^^&**
633}
634
635fn disabled() {
636 f(&&());
637}
638
639fn mixed() {
640 f(&&());
641
642 unsafe {
643 f(&&());
644 //^^^^&**
645 }
646}
647
648const _: () = {
649 f(&&());
650
651 unsafe {
652 f(&&());
653 //^^^^&**
654 }
655};
656
657static STATIC: () = {
658 f(&&());
659
660 unsafe {
661 f(&&());
662 //^^^^&**
663 }
664};
665
666enum E {
667 Disable = { f(&&()); 0 },
668 Enable = unsafe { f(&&()); 1 },
669 //^^^^&**
670}
671
672const fn f(_: &()) {}
673 "#,
674 )
675 }
676
677 #[test]
678 fn adjustment_hints_unsafe_only_with_item() {
679 check_with_config(
680 InlayHintsConfig {
681 adjustment_hints: AdjustmentHints::Always,
682 adjustment_hints_hide_outside_unsafe: true,
683 ..DISABLED_CONFIG
684 },
685 r#"
686fn a() {
687 struct Struct;
688 impl Struct {
689 fn by_ref(&self) {}
690 }
691
692 _ = Struct.by_ref();
693
694 _ = unsafe { Struct.by_ref() };
695 //^^^^^^(&
696 //^^^^^^)
697}
698 "#,
699 );
700 }
701
702 #[test]
703 fn let_stmt_explicit_ty() {
704 check_with_config(
705 InlayHintsConfig { adjustment_hints: AdjustmentHints::Always, ..DISABLED_CONFIG },
706 r#"
707fn main() {
708 let () = return;
709 //^^^^^^<never-to-any>
710 let (): () = return;
711 //^^^^^^<never-to-any>
712}
713 "#,
714 )
715 }
716
717 #[test]
719 fn adjustment_hints_method_call_on_impl_trait_self() {
720 check_with_config(
721 InlayHintsConfig { adjustment_hints: AdjustmentHints::Always, ..DISABLED_CONFIG },
722 r#"
723//- minicore: slice, coerce_unsized
724trait T<RHS = Self> {}
725
726fn hello(it: &&[impl T]) {
727 it.len();
728 //^^(&**
729 //^^)
730}
731"#,
732 );
733 }
734
735 #[test]
736 fn disable_reborrows() {
737 check_with_config(
738 InlayHintsConfig {
739 adjustment_hints: AdjustmentHints::Always,
740 adjustment_hints_disable_reborrows: true,
741 ..DISABLED_CONFIG
742 },
743 r#"
744#![rustc_coherence_is_core]
745
746trait ToOwned {
747 type Owned;
748 fn to_owned(&self) -> Self::Owned;
749}
750
751struct String;
752impl ToOwned for str {
753 type Owned = String;
754 fn to_owned(&self) -> Self::Owned { String }
755}
756
757fn a(s: &String) {}
758
759fn main() {
760 let s = "".to_owned();
761 a(&s)
762}
763"#,
764 );
765 }
766}