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 hir::attach_db(sema.db, || {
220 InlayTooltip::Markdown(format!(
221 "`{}` → `{}`\n\n**{}**\n\n{}",
222 source.display(sema.db, display_target),
223 target.display(sema.db, display_target),
224 coercion,
225 detailed_tooltip
226 ))
227 })
228 })),
229 };
230 if postfix { &mut post } else { &mut pre }.label.append_part(label);
231 }
232 if !has_adjustments {
233 return None;
234 }
235
236 if !postfix && needs_inner_parens {
237 pre.label.append_str("(");
238 }
239 if needs_outer_parens || (!postfix && needs_inner_parens) {
240 post.label.append_str(")");
241 }
242
243 let mut pre = pre.label.parts.is_empty().not().then_some(pre);
244 let mut post = post.label.parts.is_empty().not().then_some(post);
245 if pre.is_none() && post.is_none() {
246 return None;
247 }
248 if allow_edit {
249 let edit = Some(config.lazy_text_edit(|| {
250 let mut b = TextEditBuilder::default();
251 if let Some(pre) = &pre {
252 b.insert(
253 pre.range.start(),
254 pre.label.parts.iter().map(|part| &*part.text).collect::<String>(),
255 );
256 }
257 if let Some(post) = &post {
258 b.insert(
259 post.range.end(),
260 post.label.parts.iter().map(|part| &*part.text).collect::<String>(),
261 );
262 }
263 b.finish()
264 }));
265 match (&mut pre, &mut post) {
266 (Some(pre), Some(post)) => {
267 pre.text_edit = edit.clone();
268 post.text_edit = edit;
269 }
270 (Some(pre), None) => pre.text_edit = edit,
271 (None, Some(post)) => post.text_edit = edit,
272 (None, None) => (),
273 }
274 }
275 acc.extend(pre);
276 acc.extend(post);
277 Some(())
278}
279
280fn mode_and_needs_parens_for_adjustment_hints(
283 expr: &ast::Expr,
284 mode: AdjustmentHintsMode,
285) -> (bool, bool, bool) {
286 use {AdjustmentHintsMode::*, std::cmp::Ordering::*};
287
288 match mode {
289 Prefix | Postfix => {
290 let postfix = matches!(mode, Postfix);
291 let (inside, outside) = needs_parens_for_adjustment_hints(expr, postfix);
292 (postfix, inside, outside)
293 }
294 PreferPrefix | PreferPostfix => {
295 let prefer_postfix = matches!(mode, PreferPostfix);
296
297 let (pre_inside, pre_outside) = needs_parens_for_adjustment_hints(expr, false);
298 let prefix = (false, pre_inside, pre_outside);
299 let pre_count = pre_inside as u8 + pre_outside as u8;
300
301 let (post_inside, post_outside) = needs_parens_for_adjustment_hints(expr, true);
302 let postfix = (true, post_inside, post_outside);
303 let post_count = post_inside as u8 + post_outside as u8;
304
305 match pre_count.cmp(&post_count) {
306 Less => prefix,
307 Greater => postfix,
308 Equal if prefer_postfix => postfix,
309 Equal => prefix,
310 }
311 }
312 }
313}
314
315fn needs_parens_for_adjustment_hints(expr: &ast::Expr, postfix: bool) -> (bool, bool) {
318 let prec = expr.precedence();
319 if postfix {
320 let needs_inner_parens = prec.needs_parentheses_in(ExprPrecedence::Postfix);
321 let needs_outer_parens = false;
323 (needs_outer_parens, needs_inner_parens)
324 } else {
325 let needs_inner_parens = prec.needs_parentheses_in(ExprPrecedence::Prefix);
326 let parent = expr
327 .syntax()
328 .parent()
329 .and_then(ast::Expr::cast)
330 .filter(|it| !matches!(it, ast::Expr::ParenExpr(_)))
332 .map(|it| it.precedence())
333 .filter(|&prec| prec != ExprPrecedence::Unambiguous);
334
335 let needs_outer_parens = parent
338 .is_some_and(|parent_prec| ExprPrecedence::Prefix.needs_parentheses_in(parent_prec));
339 (needs_outer_parens, needs_inner_parens)
340 }
341}
342
343#[cfg(test)]
344mod tests {
345 use crate::{
346 AdjustmentHints, AdjustmentHintsMode, InlayHintsConfig,
347 inlay_hints::tests::{DISABLED_CONFIG, check_with_config},
348 };
349
350 #[test]
351 fn adjustment_hints_prefix() {
352 check_with_config(
353 InlayHintsConfig { adjustment_hints: AdjustmentHints::Always, ..DISABLED_CONFIG },
354 r#"
355//- minicore: coerce_unsized, fn, eq, index, dispatch_from_dyn, builtin_impls
356fn main() {
357 let _: u32 = loop {};
358 //^^^^^^^<never-to-any>
359 let _: &u32 = &mut 0;
360 //^^^^^^&*
361 let _: &mut u32 = &mut 0;
362 //^^^^^^&mut *
363 let _: *const u32 = &mut 0;
364 //^^^^^^&raw const *
365 let _: *mut u32 = &mut 0;
366 //^^^^^^&raw mut *
367 let _: fn() = main;
368 //^^^^<fn-item-to-fn-pointer>
369 let _: unsafe fn() = main;
370 //^^^^<safe-fn-pointer-to-unsafe-fn-pointer><fn-item-to-fn-pointer>
371 let _: unsafe fn() = main as fn();
372 //^^^^^^^^^^^^<safe-fn-pointer-to-unsafe-fn-pointer>(
373 //^^^^^^^^^^^^)
374 //^^^^<fn-item-to-fn-pointer>
375 let _: fn() = || {};
376 //^^^^^<closure-to-fn-pointer>
377 let _: unsafe fn() = || {};
378 //^^^^^<closure-to-unsafe-fn-pointer>
379 let _: *const u32 = &mut 0u32 as *mut u32;
380 //^^^^^^^^^^^^^^^^^^^^^<mut-ptr-to-const-ptr>(
381 //^^^^^^^^^^^^^^^^^^^^^)
382 //^^^^^^^^^&raw mut *
383 let _: &mut [_] = &mut [0; 0];
384 //^^^^^^^^^^^<unsize>&mut *
385
386 Struct.consume();
387 Struct.by_ref();
388 //^^^^^^(&
389 //^^^^^^)
390 Struct.by_ref_mut();
391 //^^^^^^(&mut $
392 //^^^^^^)
393
394 (&Struct).consume();
395 //^^^^^^^*
396 (&Struct).by_ref();
397 //^^^^^^^&*
398
399 (&mut Struct).consume();
400 //^^^^^^^^^^^*
401 (&mut Struct).by_ref();
402 //^^^^^^^^^^^&*
403 (&mut Struct).by_ref_mut();
404 //^^^^^^^^^^^&mut *
405
406 // Check that block-like expressions don't duplicate hints
407 let _: &mut [u32] = (&mut []);
408 //^^^^^^^<unsize>&mut *
409 let _: &mut [u32] = { &mut [] };
410 //^^^^^^^<unsize>&mut *
411 let _: &mut [u32] = unsafe { &mut [] };
412 //^^^^^^^<unsize>&mut *
413 let _: &mut [u32] = if true {
414 &mut []
415 //^^^^^^^<unsize>&mut *
416 } else {
417 loop {}
418 //^^^^^^^<never-to-any>
419 };
420 let _: &mut [u32] = match () { () => &mut [] };
421 //^^^^^^^<unsize>&mut *
422
423 let _: &mut dyn Fn() = &mut || ();
424 //^^^^^^^^^^<unsize>&mut *
425 () == ();
426 // ^^&
427 // ^^&
428 (()) == {()};
429 // ^^&
430 // ^^^^&
431 let closure: &dyn Fn = &|| ();
432 //^^^^^^<unsize>&*
433 closure();
434 Struct[0];
435 //^^^^^^(&
436 //^^^^^^)
437 &mut Struct[0];
438 //^^^^^^(&mut $
439 //^^^^^^)
440 let _: (&mut (),) = (&mut (),);
441 //^^^^^^^&mut *
442}
443
444#[derive(Copy, Clone)]
445struct Struct;
446impl Struct {
447 fn consume(self) {}
448 fn by_ref(&self) {}
449 fn by_ref_mut(&mut self) {}
450}
451struct StructMut;
452impl core::ops::Index<usize> for Struct {
453 type Output = ();
454}
455impl core::ops::IndexMut for Struct {}
456"#,
457 );
458 }
459
460 #[test]
461 fn adjustment_hints_postfix() {
462 check_with_config(
463 InlayHintsConfig {
464 adjustment_hints: AdjustmentHints::Always,
465 adjustment_hints_mode: AdjustmentHintsMode::Postfix,
466 ..DISABLED_CONFIG
467 },
468 r#"
469//- minicore: coerce_unsized, fn, eq, index, dispatch_from_dyn, builtin_impls
470fn main() {
471 Struct.consume();
472 Struct.by_ref();
473 //^^^^^^.&
474 Struct.by_ref_mut();
475 //^^^^^^.&mut
476
477 (&Struct).consume();
478 //^^^^^^^(
479 //^^^^^^^).*
480 (&Struct).by_ref();
481 //^^^^^^^(
482 //^^^^^^^).*.&
483
484 (&mut Struct).consume();
485 //^^^^^^^^^^^(
486 //^^^^^^^^^^^).*
487 (&mut Struct).by_ref();
488 //^^^^^^^^^^^(
489 //^^^^^^^^^^^).*.&
490 (&mut Struct).by_ref_mut();
491 //^^^^^^^^^^^(
492 //^^^^^^^^^^^).*.&mut
493
494 // Check that block-like expressions don't duplicate hints
495 let _: &mut [u32] = (&mut []);
496 //^^^^^^^(
497 //^^^^^^^).*.&mut.<unsize>
498 let _: &mut [u32] = { &mut [] };
499 //^^^^^^^(
500 //^^^^^^^).*.&mut.<unsize>
501 let _: &mut [u32] = unsafe { &mut [] };
502 //^^^^^^^(
503 //^^^^^^^).*.&mut.<unsize>
504 let _: &mut [u32] = if true {
505 &mut []
506 //^^^^^^^(
507 //^^^^^^^).*.&mut.<unsize>
508 } else {
509 loop {}
510 //^^^^^^^.<never-to-any>
511 };
512 let _: &mut [u32] = match () { () => &mut [] };
513 //^^^^^^^(
514 //^^^^^^^).*.&mut.<unsize>
515
516 let _: &mut dyn Fn() = &mut || ();
517 //^^^^^^^^^^(
518 //^^^^^^^^^^).*.&mut.<unsize>
519 () == ();
520 // ^^.&
521 // ^^.&
522 (()) == {()};
523 // ^^.&
524 // ^^^^.&
525 let closure: &dyn Fn = &|| ();
526 //^^^^^^(
527 //^^^^^^).*.&.<unsize>
528 closure();
529 Struct[0];
530 //^^^^^^.&
531 &mut Struct[0];
532 //^^^^^^.&mut
533 let _: (&mut (),) = (&mut (),);
534 //^^^^^^^(
535 //^^^^^^^).*.&mut
536}
537
538#[derive(Copy, Clone)]
539struct Struct;
540impl Struct {
541 fn consume(self) {}
542 fn by_ref(&self) {}
543 fn by_ref_mut(&mut self) {}
544}
545struct StructMut;
546impl core::ops::Index<usize> for Struct {
547 type Output = ();
548}
549impl core::ops::IndexMut for Struct {}
550"#,
551 );
552 }
553
554 #[test]
555 fn adjustment_hints_prefer_prefix() {
556 check_with_config(
557 InlayHintsConfig {
558 adjustment_hints: AdjustmentHints::Always,
559 adjustment_hints_mode: AdjustmentHintsMode::PreferPrefix,
560 ..DISABLED_CONFIG
561 },
562 r#"
563fn main() {
564 let _: u32 = loop {};
565 //^^^^^^^<never-to-any>
566
567 Struct.by_ref();
568 //^^^^^^.&
569
570 let (): () = return ();
571 //^^^^^^^^^<never-to-any>
572
573 struct Struct;
574 impl Struct { fn by_ref(&self) {} }
575}
576 "#,
577 )
578 }
579
580 #[test]
581 fn adjustment_hints_prefer_postfix() {
582 check_with_config(
583 InlayHintsConfig {
584 adjustment_hints: AdjustmentHints::Always,
585 adjustment_hints_mode: AdjustmentHintsMode::PreferPostfix,
586 ..DISABLED_CONFIG
587 },
588 r#"
589fn main() {
590 let _: u32 = loop {};
591 //^^^^^^^.<never-to-any>
592
593 Struct.by_ref();
594 //^^^^^^.&
595
596 let (): () = return ();
597 //^^^^^^^^^<never-to-any>
598
599 struct Struct;
600 impl Struct { fn by_ref(&self) {} }
601}
602 "#,
603 )
604 }
605
606 #[test]
607 fn never_to_never_is_never_shown() {
608 cov_mark::check!(same_type_adjustment);
609 check_with_config(
610 InlayHintsConfig { adjustment_hints: AdjustmentHints::Always, ..DISABLED_CONFIG },
611 r#"
612fn never() -> ! {
613 return loop {};
614}
615
616fn or_else() {
617 let () = () else { return };
618}
619 "#,
620 )
621 }
622
623 #[test]
624 fn adjustment_hints_unsafe_only() {
625 check_with_config(
626 InlayHintsConfig {
627 adjustment_hints: AdjustmentHints::Always,
628 adjustment_hints_hide_outside_unsafe: true,
629 ..DISABLED_CONFIG
630 },
631 r#"
632unsafe fn enabled() {
633 f(&&());
634 //^^^^&**
635}
636
637fn disabled() {
638 f(&&());
639}
640
641fn mixed() {
642 f(&&());
643
644 unsafe {
645 f(&&());
646 //^^^^&**
647 }
648}
649
650const _: () = {
651 f(&&());
652
653 unsafe {
654 f(&&());
655 //^^^^&**
656 }
657};
658
659static STATIC: () = {
660 f(&&());
661
662 unsafe {
663 f(&&());
664 //^^^^&**
665 }
666};
667
668enum E {
669 Disable = { f(&&()); 0 },
670 Enable = unsafe { f(&&()); 1 },
671 //^^^^&**
672}
673
674const fn f(_: &()) {}
675 "#,
676 )
677 }
678
679 #[test]
680 fn adjustment_hints_unsafe_only_with_item() {
681 check_with_config(
682 InlayHintsConfig {
683 adjustment_hints: AdjustmentHints::Always,
684 adjustment_hints_hide_outside_unsafe: true,
685 ..DISABLED_CONFIG
686 },
687 r#"
688fn a() {
689 struct Struct;
690 impl Struct {
691 fn by_ref(&self) {}
692 }
693
694 _ = Struct.by_ref();
695
696 _ = unsafe { Struct.by_ref() };
697 //^^^^^^(&
698 //^^^^^^)
699}
700 "#,
701 );
702 }
703
704 #[test]
705 fn let_stmt_explicit_ty() {
706 check_with_config(
707 InlayHintsConfig { adjustment_hints: AdjustmentHints::Always, ..DISABLED_CONFIG },
708 r#"
709fn main() {
710 let () = return;
711 //^^^^^^<never-to-any>
712 let (): () = return;
713 //^^^^^^<never-to-any>
714}
715 "#,
716 )
717 }
718
719 #[test]
721 fn adjustment_hints_method_call_on_impl_trait_self() {
722 check_with_config(
723 InlayHintsConfig { adjustment_hints: AdjustmentHints::Always, ..DISABLED_CONFIG },
724 r#"
725//- minicore: slice, coerce_unsized
726trait T<RHS = Self> {}
727
728fn hello(it: &&[impl T]) {
729 it.len();
730 //^^(&**
731 //^^)
732}
733"#,
734 );
735 }
736
737 #[test]
738 fn disable_reborrows() {
739 check_with_config(
740 InlayHintsConfig {
741 adjustment_hints: AdjustmentHints::Always,
742 adjustment_hints_disable_reborrows: true,
743 ..DISABLED_CONFIG
744 },
745 r#"
746#![rustc_coherence_is_core]
747
748trait ToOwned {
749 type Owned;
750 fn to_owned(&self) -> Self::Owned;
751}
752
753struct String;
754impl ToOwned for str {
755 type Owned = String;
756 fn to_owned(&self) -> Self::Owned { String }
757}
758
759fn a(s: &String) {}
760
761fn main() {
762 let s = "".to_owned();
763 a(&s)
764}
765"#,
766 );
767 }
768}