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