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