1use std::{
2 fmt::{self, Write},
3 mem::{self, take},
4};
5
6use either::Either;
7use hir::{
8 ClosureStyle, DisplayTarget, EditionedFileId, GenericParam, GenericParamId, HasVisibility,
9 HirDisplay, HirDisplayError, HirWrite, InRealFile, ModuleDef, ModuleDefId, Semantics, sym,
10};
11use ide_db::{
12 FileRange, MiniCore, RootDatabase, famous_defs::FamousDefs, text_edit::TextEditBuilder,
13};
14use ide_db::{FxHashSet, text_edit::TextEdit};
15use itertools::Itertools;
16use macros::UpmapFromRaFixture;
17use smallvec::{SmallVec, smallvec};
18use stdx::never;
19use syntax::{
20 SmolStr, SyntaxNode, TextRange, TextSize, WalkEvent,
21 ast::{self, AstNode, HasGenericParams},
22 format_smolstr, match_ast,
23};
24
25use crate::{FileId, navigation_target::TryToNav};
26
27mod adjustment;
28mod bind_pat;
29mod binding_mode;
30mod bounds;
31mod chaining;
32mod closing_brace;
33mod closure_captures;
34mod closure_ret;
35mod discriminant;
36mod extern_block;
37mod generic_param;
38mod implicit_drop;
39mod implicit_static;
40mod implied_dyn_trait;
41mod lifetime;
42mod param_name;
43mod placeholders;
44mod ra_fixture;
45mod range_exclusive;
46
47pub(crate) fn inlay_hints(
85 db: &RootDatabase,
86 file_id: FileId,
87 range_limit: Option<TextRange>,
88 config: &InlayHintsConfig<'_>,
89) -> Vec<InlayHint> {
90 let _p = tracing::info_span!("inlay_hints").entered();
91 let sema = Semantics::new(db);
92 let file_id = sema.attach_first_edition(file_id);
93 let file = sema.parse(file_id);
94 let file = file.syntax();
95
96 let mut acc = Vec::new();
97
98 let Some(scope) = sema.scope(file) else {
99 return acc;
100 };
101 let famous_defs = FamousDefs(&sema, scope.krate());
102 let display_target = famous_defs.1.to_display_target(sema.db);
103
104 let ctx = &mut InlayHintCtx::default();
105 let mut hints = |event| {
106 if let Some(node) = handle_event(ctx, event) {
107 hints(&mut acc, ctx, &famous_defs, config, file_id, display_target, node);
108 }
109 };
110 let mut preorder = file.preorder();
111 while let Some(event) = preorder.next() {
112 if matches!((&event, range_limit), (WalkEvent::Enter(node), Some(range)) if range.intersect(node.text_range()).is_none())
113 {
114 preorder.skip_subtree();
115 continue;
116 }
117 hints(event);
118 }
119 if let Some(range_limit) = range_limit {
120 acc.retain(|hint| range_limit.contains_range(hint.range));
121 }
122 acc
123}
124
125#[derive(Default)]
126struct InlayHintCtx {
127 lifetime_stacks: Vec<Vec<SmolStr>>,
128 extern_block_parent: Option<ast::ExternBlock>,
129}
130
131pub(crate) fn inlay_hints_resolve(
132 db: &RootDatabase,
133 file_id: FileId,
134 resolve_range: TextRange,
135 hash: u64,
136 config: &InlayHintsConfig<'_>,
137 hasher: impl Fn(&InlayHint) -> u64,
138) -> Option<InlayHint> {
139 let _p = tracing::info_span!("inlay_hints_resolve").entered();
140 let sema = Semantics::new(db);
141 let file_id = sema.attach_first_edition(file_id);
142 let file = sema.parse(file_id);
143 let file = file.syntax();
144
145 let scope = sema.scope(file)?;
146 let famous_defs = FamousDefs(&sema, scope.krate());
147 let mut acc = Vec::new();
148
149 let display_target = famous_defs.1.to_display_target(sema.db);
150
151 let ctx = &mut InlayHintCtx::default();
152 let mut hints = |event| {
153 if let Some(node) = handle_event(ctx, event) {
154 hints(&mut acc, ctx, &famous_defs, config, file_id, display_target, node);
155 }
156 };
157
158 let mut preorder = file.preorder();
159 while let Some(event) = preorder.next() {
160 if matches!(&event, WalkEvent::Enter(node) if resolve_range.intersect(node.text_range()).is_none())
162 {
163 preorder.skip_subtree();
164 continue;
165 }
166 hints(event);
167 }
168 acc.into_iter().find(|hint| hasher(hint) == hash)
169}
170
171fn handle_event(ctx: &mut InlayHintCtx, node: WalkEvent<SyntaxNode>) -> Option<SyntaxNode> {
172 match node {
173 WalkEvent::Enter(node) => {
174 if let Some(node) = ast::AnyHasGenericParams::cast(node.clone()) {
175 let params = node
176 .generic_param_list()
177 .map(|it| {
178 it.lifetime_params()
179 .filter_map(|it| {
180 it.lifetime().map(|it| format_smolstr!("{}", &it.text()[1..]))
181 })
182 .collect()
183 })
184 .unwrap_or_default();
185 ctx.lifetime_stacks.push(params);
186 }
187 if let Some(node) = ast::ExternBlock::cast(node.clone()) {
188 ctx.extern_block_parent = Some(node);
189 }
190 Some(node)
191 }
192 WalkEvent::Leave(n) => {
193 if ast::AnyHasGenericParams::can_cast(n.kind()) {
194 ctx.lifetime_stacks.pop();
195 }
196 if ast::ExternBlock::can_cast(n.kind()) {
197 ctx.extern_block_parent = None;
198 }
199 None
200 }
201 }
202}
203
204fn hints(
207 hints: &mut Vec<InlayHint>,
208 ctx: &mut InlayHintCtx,
209 famous_defs @ FamousDefs(sema, _krate): &FamousDefs<'_, '_>,
210 config: &InlayHintsConfig<'_>,
211 file_id: EditionedFileId,
212 display_target: DisplayTarget,
213 node: SyntaxNode,
214) {
215 closing_brace::hints(
216 hints,
217 sema,
218 config,
219 display_target,
220 InRealFile { file_id, value: node.clone() },
221 );
222 if let Some(any_has_generic_args) = ast::AnyHasGenericArgs::cast(node.clone()) {
223 generic_param::hints(hints, famous_defs, config, any_has_generic_args);
224 }
225
226 match_ast! {
227 match node {
228 ast::Expr(expr) => {
229 chaining::hints(hints, famous_defs, config, display_target, &expr);
230 adjustment::hints(hints, famous_defs, config, display_target, &expr);
231 match expr {
232 ast::Expr::CallExpr(it) => param_name::hints(hints, famous_defs, config, file_id, ast::Expr::from(it)),
233 ast::Expr::MethodCallExpr(it) => {
234 param_name::hints(hints, famous_defs, config, file_id, ast::Expr::from(it))
235 }
236 ast::Expr::ClosureExpr(it) => {
237 closure_captures::hints(hints, famous_defs, config, it.clone());
238 closure_ret::hints(hints, famous_defs, config, display_target, it)
239 },
240 ast::Expr::RangeExpr(it) => range_exclusive::hints(hints, famous_defs, config, it),
241 ast::Expr::Literal(it) => ra_fixture::hints(hints, famous_defs.0, file_id, config, it),
242 _ => Some(()),
243 }
244 },
245 ast::Pat(it) => {
246 binding_mode::hints(hints, famous_defs, config, &it);
247 match it {
248 ast::Pat::IdentPat(it) => {
249 bind_pat::hints(hints, famous_defs, config, display_target, &it);
250 }
251 ast::Pat::RangePat(it) => {
252 range_exclusive::hints(hints, famous_defs, config, it);
253 }
254 _ => {}
255 }
256 Some(())
257 },
258 ast::Item(it) => match it {
259 ast::Item::Fn(it) => {
260 implicit_drop::hints(hints, famous_defs, config, display_target, &it);
261 if let Some(extern_block) = &ctx.extern_block_parent {
262 extern_block::fn_hints(hints, famous_defs, config, &it, extern_block);
263 }
264 lifetime::fn_hints(hints, ctx, famous_defs, config, it)
265 },
266 ast::Item::Static(it) => {
267 if let Some(extern_block) = &ctx.extern_block_parent {
268 extern_block::static_hints(hints, famous_defs, config, &it, extern_block);
269 }
270 implicit_static::hints(hints, famous_defs, config, Either::Left(it))
271 },
272 ast::Item::Const(it) => implicit_static::hints(hints, famous_defs, config, Either::Right(it)),
273 ast::Item::Enum(it) => discriminant::enum_hints(hints, famous_defs, config, it),
274 ast::Item::ExternBlock(it) => extern_block::extern_block_hints(hints, famous_defs, config, it),
275 _ => None,
276 },
277 ast::Type(ty) => match ty {
279 ast::Type::FnPtrType(ptr) => lifetime::fn_ptr_hints(hints, ctx, famous_defs, config, ptr),
280 ast::Type::PathType(path) => {
281 lifetime::fn_path_hints(hints, ctx, famous_defs, config, &path);
282 implied_dyn_trait::hints(hints, famous_defs, config, Either::Left(path));
283 Some(())
284 },
285 ast::Type::DynTraitType(dyn_) => {
286 implied_dyn_trait::hints(hints, famous_defs, config, Either::Right(dyn_));
287 Some(())
288 },
289 ast::Type::InferType(placeholder) => {
290 placeholders::type_hints(hints, famous_defs, config, display_target, placeholder);
291 Some(())
292 },
293 _ => Some(()),
294 },
295 ast::GenericParamList(it) => bounds::hints(hints, famous_defs, config, it),
296 _ => Some(()),
297 }
298 };
299}
300
301#[derive(Clone, Debug)]
302pub struct InlayHintsConfig<'a> {
303 pub render_colons: bool,
304 pub type_hints: bool,
305 pub sized_bound: bool,
306 pub discriminant_hints: DiscriminantHints,
307 pub parameter_hints: bool,
308 pub parameter_hints_for_missing_arguments: bool,
309 pub generic_parameter_hints: GenericParameterHints,
310 pub chaining_hints: bool,
311 pub adjustment_hints: AdjustmentHints,
312 pub adjustment_hints_disable_reborrows: bool,
313 pub adjustment_hints_mode: AdjustmentHintsMode,
314 pub adjustment_hints_hide_outside_unsafe: bool,
315 pub closure_return_type_hints: ClosureReturnTypeHints,
316 pub closure_capture_hints: bool,
317 pub binding_mode_hints: bool,
318 pub implicit_drop_hints: bool,
319 pub implied_dyn_trait_hints: bool,
320 pub lifetime_elision_hints: LifetimeElisionHints,
321 pub param_names_for_lifetime_elision_hints: bool,
322 pub hide_inferred_type_hints: bool,
323 pub hide_named_constructor_hints: bool,
324 pub hide_closure_initialization_hints: bool,
325 pub hide_closure_parameter_hints: bool,
326 pub range_exclusive_hints: bool,
327 pub closure_style: ClosureStyle,
328 pub max_length: Option<usize>,
329 pub closing_brace_hints_min_lines: Option<usize>,
330 pub fields_to_resolve: InlayFieldsToResolve,
331 pub minicore: MiniCore<'a>,
332}
333
334impl InlayHintsConfig<'_> {
335 fn lazy_text_edit(&self, finish: impl FnOnce() -> TextEdit) -> LazyProperty<TextEdit> {
336 if self.fields_to_resolve.resolve_text_edits {
337 LazyProperty::Lazy
338 } else {
339 let edit = finish();
340 never!(edit.is_empty(), "inlay hint produced an empty text edit");
341 LazyProperty::Computed(edit)
342 }
343 }
344
345 fn lazy_tooltip(&self, finish: impl FnOnce() -> InlayTooltip) -> LazyProperty<InlayTooltip> {
346 if self.fields_to_resolve.resolve_hint_tooltip
347 && self.fields_to_resolve.resolve_label_tooltip
348 {
349 LazyProperty::Lazy
350 } else {
351 let tooltip = finish();
352 never!(
353 match &tooltip {
354 InlayTooltip::String(s) => s,
355 InlayTooltip::Markdown(s) => s,
356 }
357 .is_empty(),
358 "inlay hint produced an empty tooltip"
359 );
360 LazyProperty::Computed(tooltip)
361 }
362 }
363
364 fn lazy_location_opt(
367 &self,
368 finish: impl FnOnce() -> Option<FileRange>,
369 ) -> Option<LazyProperty<FileRange>> {
370 if self.fields_to_resolve.resolve_label_location {
371 Some(LazyProperty::Lazy)
372 } else {
373 finish().map(LazyProperty::Computed)
374 }
375 }
376}
377
378#[derive(Copy, Clone, Debug, PartialEq, Eq)]
379pub struct InlayFieldsToResolve {
380 pub resolve_text_edits: bool,
381 pub resolve_hint_tooltip: bool,
382 pub resolve_label_tooltip: bool,
383 pub resolve_label_location: bool,
384 pub resolve_label_command: bool,
385}
386
387impl InlayFieldsToResolve {
388 pub fn from_client_capabilities(client_capability_fields: &FxHashSet<&str>) -> Self {
389 Self {
390 resolve_text_edits: client_capability_fields.contains("textEdits"),
391 resolve_hint_tooltip: client_capability_fields.contains("tooltip"),
392 resolve_label_tooltip: client_capability_fields.contains("label.tooltip"),
393 resolve_label_location: client_capability_fields.contains("label.location"),
394 resolve_label_command: client_capability_fields.contains("label.command"),
395 }
396 }
397
398 pub const fn empty() -> Self {
399 Self {
400 resolve_text_edits: false,
401 resolve_hint_tooltip: false,
402 resolve_label_tooltip: false,
403 resolve_label_location: false,
404 resolve_label_command: false,
405 }
406 }
407}
408
409#[derive(Clone, Debug, PartialEq, Eq)]
410pub enum ClosureReturnTypeHints {
411 Always,
412 WithBlock,
413 Never,
414}
415
416#[derive(Clone, Debug, PartialEq, Eq)]
417pub enum DiscriminantHints {
418 Always,
419 Never,
420 Fieldless,
421}
422
423#[derive(Clone, Debug, PartialEq, Eq)]
424pub struct GenericParameterHints {
425 pub type_hints: bool,
426 pub lifetime_hints: bool,
427 pub const_hints: bool,
428}
429
430#[derive(Clone, Debug, PartialEq, Eq)]
431pub enum LifetimeElisionHints {
432 Always,
433 SkipTrivial,
434 Never,
435}
436
437#[derive(Clone, Debug, PartialEq, Eq)]
438pub enum AdjustmentHints {
439 Always,
440 BorrowsOnly,
441 Never,
442}
443
444#[derive(Copy, Clone, Debug, PartialEq, Eq)]
445pub enum AdjustmentHintsMode {
446 Prefix,
447 Postfix,
448 PreferPrefix,
449 PreferPostfix,
450}
451
452#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
453pub enum InlayKind {
454 Adjustment,
455 BindingMode,
456 Chaining,
457 ClosingBrace,
458 ClosureCapture,
459 Discriminant,
460 GenericParamList,
461 Lifetime,
462 Parameter,
463 GenericParameter,
464 Type,
465 Dyn,
466 Drop,
467 RangeExclusive,
468 ExternUnsafety,
469}
470
471#[derive(Debug, Hash)]
472pub enum InlayHintPosition {
473 Before,
474 After,
475}
476
477#[derive(Debug, UpmapFromRaFixture)]
478pub struct InlayHint {
479 pub range: TextRange,
481 pub position: InlayHintPosition,
482 pub pad_left: bool,
483 pub pad_right: bool,
484 pub kind: InlayKind,
486 pub label: InlayHintLabel,
488 pub text_edit: Option<LazyProperty<TextEdit>>,
490 pub resolve_parent: Option<TextRange>,
493}
494
495#[derive(Clone, Debug, Default, UpmapFromRaFixture)]
497pub enum LazyProperty<T> {
498 Computed(T),
499 #[default]
500 Lazy,
501}
502
503impl<T> LazyProperty<T> {
504 pub fn computed(self) -> Option<T> {
505 match self {
506 LazyProperty::Computed(it) => Some(it),
507 _ => None,
508 }
509 }
510
511 pub fn is_lazy(&self) -> bool {
512 matches!(self, Self::Lazy)
513 }
514}
515
516impl std::hash::Hash for InlayHint {
517 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
518 self.range.hash(state);
519 self.position.hash(state);
520 self.pad_left.hash(state);
521 self.pad_right.hash(state);
522 self.kind.hash(state);
523 self.label.hash(state);
524 mem::discriminant(&self.text_edit).hash(state);
525 }
526}
527
528impl InlayHint {
529 fn closing_paren_after(kind: InlayKind, range: TextRange) -> InlayHint {
530 InlayHint {
531 range,
532 kind,
533 label: InlayHintLabel::from(")"),
534 text_edit: None,
535 position: InlayHintPosition::After,
536 pad_left: false,
537 pad_right: false,
538 resolve_parent: None,
539 }
540 }
541}
542
543#[derive(Debug, Hash)]
544pub enum InlayTooltip {
545 String(String),
546 Markdown(String),
547}
548
549#[derive(Default, Hash, UpmapFromRaFixture)]
550pub struct InlayHintLabel {
551 pub parts: SmallVec<[InlayHintLabelPart; 1]>,
552}
553
554impl InlayHintLabel {
555 pub fn simple(
556 s: impl Into<String>,
557 tooltip: Option<LazyProperty<InlayTooltip>>,
558 linked_location: Option<LazyProperty<FileRange>>,
559 ) -> InlayHintLabel {
560 InlayHintLabel {
561 parts: smallvec![InlayHintLabelPart { text: s.into(), linked_location, tooltip }],
562 }
563 }
564
565 pub fn prepend_str(&mut self, s: &str) {
566 match &mut *self.parts {
567 [InlayHintLabelPart { text, linked_location: None, tooltip: None }, ..] => {
568 text.insert_str(0, s)
569 }
570 _ => self.parts.insert(
571 0,
572 InlayHintLabelPart { text: s.into(), linked_location: None, tooltip: None },
573 ),
574 }
575 }
576
577 pub fn append_str(&mut self, s: &str) {
578 match &mut *self.parts {
579 [.., InlayHintLabelPart { text, linked_location: None, tooltip: None }] => {
580 text.push_str(s)
581 }
582 _ => self.parts.push(InlayHintLabelPart {
583 text: s.into(),
584 linked_location: None,
585 tooltip: None,
586 }),
587 }
588 }
589
590 pub fn append_part(&mut self, part: InlayHintLabelPart) {
591 if part.linked_location.is_none()
592 && part.tooltip.is_none()
593 && let Some(InlayHintLabelPart { text, linked_location: None, tooltip: None }) =
594 self.parts.last_mut()
595 {
596 text.push_str(&part.text);
597 return;
598 }
599 self.parts.push(part);
600 }
601}
602
603impl From<String> for InlayHintLabel {
604 fn from(s: String) -> Self {
605 Self {
606 parts: smallvec![InlayHintLabelPart { text: s, linked_location: None, tooltip: None }],
607 }
608 }
609}
610
611impl From<&str> for InlayHintLabel {
612 fn from(s: &str) -> Self {
613 Self {
614 parts: smallvec![InlayHintLabelPart {
615 text: s.into(),
616 linked_location: None,
617 tooltip: None
618 }],
619 }
620 }
621}
622
623impl fmt::Display for InlayHintLabel {
624 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
625 write!(f, "{}", self.parts.iter().map(|part| &part.text).format(""))
626 }
627}
628
629impl fmt::Debug for InlayHintLabel {
630 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
631 f.debug_list().entries(&self.parts).finish()
632 }
633}
634
635#[derive(UpmapFromRaFixture)]
636pub struct InlayHintLabelPart {
637 pub text: String,
638 pub linked_location: Option<LazyProperty<FileRange>>,
644 pub tooltip: Option<LazyProperty<InlayTooltip>>,
647}
648
649impl std::hash::Hash for InlayHintLabelPart {
650 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
651 self.text.hash(state);
652 self.linked_location.is_some().hash(state);
653 self.tooltip.is_some().hash(state);
654 }
655}
656
657impl fmt::Debug for InlayHintLabelPart {
658 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
659 match self {
660 Self { text, linked_location: None, tooltip: None | Some(LazyProperty::Lazy) } => {
661 text.fmt(f)
662 }
663 Self { text, linked_location, tooltip } => f
664 .debug_struct("InlayHintLabelPart")
665 .field("text", text)
666 .field("linked_location", linked_location)
667 .field(
668 "tooltip",
669 &tooltip.as_ref().map_or("", |it| match it {
670 LazyProperty::Computed(
671 InlayTooltip::String(it) | InlayTooltip::Markdown(it),
672 ) => it,
673 LazyProperty::Lazy => "",
674 }),
675 )
676 .finish(),
677 }
678 }
679}
680
681#[derive(Debug)]
682struct InlayHintLabelBuilder<'a> {
683 sema: &'a Semantics<'a, RootDatabase>,
684 result: InlayHintLabel,
685 last_part: String,
686 resolve: bool,
687 location: Option<LazyProperty<FileRange>>,
688}
689
690impl fmt::Write for InlayHintLabelBuilder<'_> {
691 fn write_str(&mut self, s: &str) -> fmt::Result {
692 self.last_part.write_str(s)
693 }
694}
695
696impl HirWrite for InlayHintLabelBuilder<'_> {
697 fn start_location_link(&mut self, def: ModuleDefId) {
698 never!(self.location.is_some(), "location link is already started");
699 self.make_new_part();
700
701 self.location = Some(if self.resolve {
702 LazyProperty::Lazy
703 } else {
704 LazyProperty::Computed({
705 let Some(location) = ModuleDef::from(def).try_to_nav(self.sema) else { return };
706 let location = location.call_site();
707 FileRange { file_id: location.file_id, range: location.focus_or_full_range() }
708 })
709 });
710 }
711
712 fn start_location_link_generic(&mut self, def: GenericParamId) {
713 never!(self.location.is_some(), "location link is already started");
714 self.make_new_part();
715
716 self.location = Some(if self.resolve {
717 LazyProperty::Lazy
718 } else {
719 LazyProperty::Computed({
720 let Some(location) = GenericParam::from(def).try_to_nav(self.sema) else { return };
721 let location = location.call_site();
722 FileRange { file_id: location.file_id, range: location.focus_or_full_range() }
723 })
724 });
725 }
726
727 fn end_location_link(&mut self) {
728 self.make_new_part();
729 }
730}
731
732impl InlayHintLabelBuilder<'_> {
733 fn make_new_part(&mut self) {
734 let text = take(&mut self.last_part);
735 if !text.is_empty() {
736 self.result.parts.push(InlayHintLabelPart {
737 text,
738 linked_location: self.location.take(),
739 tooltip: None,
740 });
741 }
742 }
743
744 fn finish(mut self) -> InlayHintLabel {
745 self.make_new_part();
746 self.result
747 }
748}
749
750fn label_of_ty(
751 famous_defs @ FamousDefs(sema, _): &FamousDefs<'_, '_>,
752 config: &InlayHintsConfig<'_>,
753 ty: &hir::Type<'_>,
754 display_target: DisplayTarget,
755) -> Option<InlayHintLabel> {
756 fn rec(
757 sema: &Semantics<'_, RootDatabase>,
758 famous_defs: &FamousDefs<'_, '_>,
759 mut max_length: Option<usize>,
760 ty: &hir::Type<'_>,
761 label_builder: &mut InlayHintLabelBuilder<'_>,
762 config: &InlayHintsConfig<'_>,
763 display_target: DisplayTarget,
764 ) -> Result<(), HirDisplayError> {
765 let iter_item_type = hint_iterator(sema, famous_defs, ty);
766 match iter_item_type {
767 Some((iter_trait, item, ty)) => {
768 const LABEL_START: &str = "impl ";
769 const LABEL_ITERATOR: &str = "Iterator";
770 const LABEL_MIDDLE: &str = "<";
771 const LABEL_ITEM: &str = "Item";
772 const LABEL_MIDDLE2: &str = " = ";
773 const LABEL_END: &str = ">";
774
775 max_length = max_length.map(|len| {
776 len.saturating_sub(
777 LABEL_START.len()
778 + LABEL_ITERATOR.len()
779 + LABEL_MIDDLE.len()
780 + LABEL_MIDDLE2.len()
781 + LABEL_END.len(),
782 )
783 });
784
785 let module_def_location = |label_builder: &mut InlayHintLabelBuilder<'_>,
786 def: ModuleDef,
787 name| {
788 let def = def.try_into();
789 if let Ok(def) = def {
790 label_builder.start_location_link(def);
791 }
792 #[expect(
793 clippy::question_mark,
794 reason = "false positive; replacing with `?` leads to 'type annotations needed' error"
795 )]
796 if let Err(err) = label_builder.write_str(name) {
797 return Err(err);
798 }
799 if def.is_ok() {
800 label_builder.end_location_link();
801 }
802 Ok(())
803 };
804
805 label_builder.write_str(LABEL_START)?;
806 module_def_location(label_builder, ModuleDef::from(iter_trait), LABEL_ITERATOR)?;
807 label_builder.write_str(LABEL_MIDDLE)?;
808 module_def_location(label_builder, ModuleDef::from(item), LABEL_ITEM)?;
809 label_builder.write_str(LABEL_MIDDLE2)?;
810 rec(sema, famous_defs, max_length, &ty, label_builder, config, display_target)?;
811 label_builder.write_str(LABEL_END)?;
812 Ok(())
813 }
814 None => ty
815 .display_truncated(sema.db, max_length, display_target)
816 .with_closure_style(config.closure_style)
817 .write_to(label_builder),
818 }
819 }
820
821 let mut label_builder = InlayHintLabelBuilder {
822 sema,
823 last_part: String::new(),
824 location: None,
825 result: InlayHintLabel::default(),
826 resolve: config.fields_to_resolve.resolve_label_location,
827 };
828 let _ =
829 rec(sema, famous_defs, config.max_length, ty, &mut label_builder, config, display_target);
830 let r = label_builder.finish();
831 Some(r)
832}
833
834fn hint_iterator<'db>(
836 sema: &Semantics<'db, RootDatabase>,
837 famous_defs: &FamousDefs<'_, 'db>,
838 ty: &hir::Type<'db>,
839) -> Option<(hir::Trait, hir::TypeAlias, hir::Type<'db>)> {
840 let db = sema.db;
841 let strukt = ty.strip_references().as_adt()?;
842 let krate = strukt.module(db).krate(db);
843 if krate != famous_defs.core()? {
844 return None;
845 }
846 let iter_trait = famous_defs.core_iter_Iterator()?;
847 let iter_mod = famous_defs.core_iter()?;
848
849 if !(strukt.visibility(db) == hir::Visibility::Public
851 && strukt.module(db).path_to_root(db).contains(&iter_mod))
852 {
853 return None;
854 }
855
856 if ty.impls_trait(db, iter_trait, &[]) {
857 let assoc_type_item = iter_trait.items(db).into_iter().find_map(|item| match item {
858 hir::AssocItem::TypeAlias(alias) if alias.name(db) == sym::Item => Some(alias),
859 _ => None,
860 })?;
861 if let Some(ty) = ty.normalize_trait_assoc_type(db, &[], assoc_type_item) {
862 return Some((iter_trait, assoc_type_item, ty));
863 }
864 }
865
866 None
867}
868
869fn ty_to_text_edit(
870 sema: &Semantics<'_, RootDatabase>,
871 config: &InlayHintsConfig<'_>,
872 node_for_hint: &SyntaxNode,
873 ty: &hir::Type<'_>,
874 offset_to_insert_ty: TextSize,
875 additional_edits: &dyn Fn(&mut TextEditBuilder),
876 prefix: impl Into<String>,
877) -> Option<LazyProperty<TextEdit>> {
878 let rendered = sema
880 .scope(node_for_hint)
881 .and_then(|scope| ty.display_source_code(scope.db, scope.module().into(), false).ok())?;
882 Some(config.lazy_text_edit(|| {
883 let mut builder = TextEdit::builder();
884 builder.insert(offset_to_insert_ty, prefix.into());
885 builder.insert(offset_to_insert_ty, rendered);
886
887 additional_edits(&mut builder);
888
889 builder.finish()
890 }))
891}
892
893fn closure_has_block_body(closure: &ast::ClosureExpr) -> bool {
894 matches!(closure.body(), Some(ast::Expr::BlockExpr(_)))
895}
896
897#[cfg(test)]
898mod tests {
899
900 use expect_test::Expect;
901 use hir::ClosureStyle;
902 use ide_db::MiniCore;
903 use itertools::Itertools;
904 use test_utils::extract_annotations;
905
906 use crate::DiscriminantHints;
907 use crate::inlay_hints::{AdjustmentHints, AdjustmentHintsMode};
908 use crate::{LifetimeElisionHints, fixture, inlay_hints::InlayHintsConfig};
909
910 use super::{ClosureReturnTypeHints, GenericParameterHints, InlayFieldsToResolve};
911
912 pub(super) const DISABLED_CONFIG: InlayHintsConfig<'_> = InlayHintsConfig {
913 discriminant_hints: DiscriminantHints::Never,
914 render_colons: false,
915 type_hints: false,
916 parameter_hints: false,
917 parameter_hints_for_missing_arguments: false,
918 sized_bound: false,
919 generic_parameter_hints: GenericParameterHints {
920 type_hints: false,
921 lifetime_hints: false,
922 const_hints: false,
923 },
924 chaining_hints: false,
925 lifetime_elision_hints: LifetimeElisionHints::Never,
926 closure_return_type_hints: ClosureReturnTypeHints::Never,
927 closure_capture_hints: false,
928 adjustment_hints: AdjustmentHints::Never,
929 adjustment_hints_disable_reborrows: false,
930 adjustment_hints_mode: AdjustmentHintsMode::Prefix,
931 adjustment_hints_hide_outside_unsafe: false,
932 binding_mode_hints: false,
933 hide_inferred_type_hints: false,
934 hide_named_constructor_hints: false,
935 hide_closure_initialization_hints: false,
936 hide_closure_parameter_hints: false,
937 closure_style: ClosureStyle::ImplFn,
938 param_names_for_lifetime_elision_hints: false,
939 max_length: None,
940 closing_brace_hints_min_lines: None,
941 fields_to_resolve: InlayFieldsToResolve::empty(),
942 implicit_drop_hints: false,
943 implied_dyn_trait_hints: false,
944 range_exclusive_hints: false,
945 minicore: MiniCore::default(),
946 };
947 pub(super) const TEST_CONFIG: InlayHintsConfig<'_> = InlayHintsConfig {
948 type_hints: true,
949 parameter_hints: true,
950 chaining_hints: true,
951 closure_return_type_hints: ClosureReturnTypeHints::WithBlock,
952 binding_mode_hints: true,
953 lifetime_elision_hints: LifetimeElisionHints::Always,
954 ..DISABLED_CONFIG
955 };
956
957 #[track_caller]
958 pub(super) fn check(#[rust_analyzer::rust_fixture] ra_fixture: &str) {
959 check_with_config(TEST_CONFIG, ra_fixture);
960 }
961
962 #[track_caller]
963 pub(super) fn check_with_config(
964 config: InlayHintsConfig<'_>,
965 #[rust_analyzer::rust_fixture] ra_fixture: &str,
966 ) {
967 let (analysis, file_id) = fixture::file(ra_fixture);
968 let mut expected = extract_annotations(&analysis.file_text(file_id).unwrap());
969 let inlay_hints = analysis.inlay_hints(&config, file_id, None).unwrap();
970 let actual = inlay_hints
971 .into_iter()
972 .map(|it| (it.range, it.label.to_string().trim_start().to_owned()))
974 .sorted_by_key(|(range, _)| range.start())
975 .collect::<Vec<_>>();
976 expected.sort_by_key(|(range, _)| range.start());
977
978 assert_eq!(expected, actual, "\nExpected:\n{expected:#?}\n\nActual:\n{actual:#?}");
979 }
980
981 #[track_caller]
982 pub(super) fn check_expect(
983 config: InlayHintsConfig<'_>,
984 #[rust_analyzer::rust_fixture] ra_fixture: &str,
985 expect: Expect,
986 ) {
987 let (analysis, file_id) = fixture::file(ra_fixture);
988 let inlay_hints = analysis.inlay_hints(&config, file_id, None).unwrap();
989 let filtered =
990 inlay_hints.into_iter().map(|hint| (hint.range, hint.label)).collect::<Vec<_>>();
991 expect.assert_debug_eq(&filtered)
992 }
993
994 #[track_caller]
997 pub(super) fn check_edit(
998 config: InlayHintsConfig<'_>,
999 #[rust_analyzer::rust_fixture] ra_fixture: &str,
1000 expect: Expect,
1001 ) {
1002 let (analysis, file_id) = fixture::file(ra_fixture);
1003 let inlay_hints = analysis.inlay_hints(&config, file_id, None).unwrap();
1004
1005 let edits = inlay_hints
1006 .into_iter()
1007 .filter_map(|hint| hint.text_edit?.computed())
1008 .reduce(|mut acc, next| {
1009 acc.union(next).expect("merging text edits failed");
1010 acc
1011 })
1012 .expect("no edit returned");
1013
1014 let mut actual = analysis.file_text(file_id).unwrap().to_string();
1015 edits.apply(&mut actual);
1016 expect.assert_eq(&actual);
1017 }
1018
1019 #[track_caller]
1020 pub(super) fn check_no_edit(
1021 config: InlayHintsConfig<'_>,
1022 #[rust_analyzer::rust_fixture] ra_fixture: &str,
1023 ) {
1024 let (analysis, file_id) = fixture::file(ra_fixture);
1025 let inlay_hints = analysis.inlay_hints(&config, file_id, None).unwrap();
1026
1027 let edits: Vec<_> =
1028 inlay_hints.into_iter().filter_map(|hint| hint.text_edit?.computed()).collect();
1029
1030 assert!(edits.is_empty(), "unexpected edits: {edits:?}");
1031 }
1032
1033 #[test]
1034 fn hints_disabled() {
1035 check_with_config(
1036 InlayHintsConfig { render_colons: true, ..DISABLED_CONFIG },
1037 r#"
1038fn foo(a: i32, b: i32) -> i32 { a + b }
1039fn main() {
1040 let _x = foo(4, 4);
1041}"#,
1042 );
1043 }
1044
1045 #[test]
1046 fn regression_18840() {
1047 check(
1048 r#"
1049//- proc_macros: issue_18840
1050#[proc_macros::issue_18840]
1051fn foo() {
1052 let
1053 loop {}
1054}
1055"#,
1056 );
1057 }
1058
1059 #[test]
1060 fn regression_18898() {
1061 check(
1062 r#"
1063//- proc_macros: issue_18898
1064#[proc_macros::issue_18898]
1065fn foo() {
1066 let
1067}
1068"#,
1069 );
1070 }
1071
1072 #[test]
1073 fn closure_dependency_cycle_no_panic() {
1074 check(
1075 r#"
1076fn foo() {
1077 let closure;
1078 // ^^^^^^^ impl Fn()
1079 closure = || {
1080 closure();
1081 };
1082}
1083
1084fn bar() {
1085 let closure1;
1086 // ^^^^^^^^ impl Fn()
1087 let closure2;
1088 // ^^^^^^^^ impl Fn()
1089 closure1 = || {
1090 closure2();
1091 };
1092 closure2 = || {
1093 closure1();
1094 };
1095}
1096 "#,
1097 );
1098 }
1099
1100 #[test]
1101 fn regression_19610() {
1102 check(
1103 r#"
1104trait Trait {
1105 type Assoc;
1106}
1107struct Foo<A>(A);
1108impl<A: Trait<Assoc = impl Trait>> Foo<A> {
1109 fn foo<'a, 'b>(_: &'a [i32], _: &'b [i32]) {}
1110}
1111
1112fn bar() {
1113 Foo::foo(&[1], &[2]);
1114}
1115"#,
1116 );
1117 }
1118
1119 #[test]
1120 fn regression_20239() {
1121 check_with_config(
1122 InlayHintsConfig { parameter_hints: true, type_hints: true, ..DISABLED_CONFIG },
1123 r#"
1124//- minicore: fn
1125trait Iterator {
1126 type Item;
1127 fn map<B, F: FnMut(Self::Item) -> B>(self, f: F);
1128}
1129trait ToString {
1130 fn to_string(&self);
1131}
1132
1133fn check_tostr_eq<L, R>(left: L, right: R)
1134where
1135 L: Iterator,
1136 L::Item: ToString,
1137 R: Iterator,
1138 R::Item: ToString,
1139{
1140 left.map(|s| s.to_string());
1141 // ^ impl ToString
1142 right.map(|s| s.to_string());
1143 // ^ impl ToString
1144}
1145 "#,
1146 );
1147 }
1148}