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: ImplicitDropHints,
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 ImplicitDropHints {
460 Always,
461 WithDropGlue,
462 Never,
463}
464
465#[derive(Clone, Debug, PartialEq, Eq)]
466pub enum AdjustmentHints {
467 Always,
468 BorrowsOnly,
469 Never,
470}
471
472#[derive(Copy, Clone, Debug, PartialEq, Eq)]
473pub enum AdjustmentHintsMode {
474 Prefix,
475 Postfix,
476 PreferPrefix,
477 PreferPostfix,
478}
479
480#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
481pub enum InlayKind {
482 Adjustment,
483 BindingMode,
484 Chaining,
485 ClosingBrace,
486 ClosureCapture,
487 Discriminant,
488 GenericParamList,
489 Lifetime,
490 Parameter,
491 GenericParameter,
492 Type,
493 Dyn,
494 Drop,
495 RangeExclusive,
496 ExternUnsafety,
497}
498
499#[derive(Debug, Hash)]
500pub enum InlayHintPosition {
501 Before,
502 After,
503}
504
505#[derive(Debug, UpmapFromRaFixture)]
506pub struct InlayHint {
507 pub range: TextRange,
509 pub position: InlayHintPosition,
510 pub pad_left: bool,
511 pub pad_right: bool,
512 pub kind: InlayKind,
514 pub label: InlayHintLabel,
516 pub text_edit: Option<LazyProperty<TextEdit>>,
518 pub resolve_parent: Option<TextRange>,
521}
522
523#[derive(Clone, Debug, Default, UpmapFromRaFixture)]
525pub enum LazyProperty<T> {
526 Computed(T),
527 #[default]
528 Lazy,
529}
530
531impl<T> LazyProperty<T> {
532 pub fn computed(self) -> Option<T> {
533 match self {
534 LazyProperty::Computed(it) => Some(it),
535 _ => None,
536 }
537 }
538
539 pub fn is_lazy(&self) -> bool {
540 matches!(self, Self::Lazy)
541 }
542}
543
544impl std::hash::Hash for InlayHint {
545 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
546 self.range.hash(state);
547 self.position.hash(state);
548 self.pad_left.hash(state);
549 self.pad_right.hash(state);
550 self.kind.hash(state);
551 self.label.hash(state);
552 mem::discriminant(&self.text_edit).hash(state);
553 }
554}
555
556impl InlayHint {
557 fn closing_paren_after(kind: InlayKind, range: TextRange) -> InlayHint {
558 InlayHint {
559 range,
560 kind,
561 label: InlayHintLabel::from(")"),
562 text_edit: None,
563 position: InlayHintPosition::After,
564 pad_left: false,
565 pad_right: false,
566 resolve_parent: None,
567 }
568 }
569}
570
571#[derive(Debug, Hash)]
572pub enum InlayTooltip {
573 String(String),
574 Markdown(String),
575}
576
577#[derive(Default, Hash, UpmapFromRaFixture)]
578pub struct InlayHintLabel {
579 pub parts: SmallVec<[InlayHintLabelPart; 1]>,
580}
581
582impl InlayHintLabel {
583 pub fn simple(
584 s: impl Into<String>,
585 tooltip: Option<LazyProperty<InlayTooltip>>,
586 linked_location: Option<LazyProperty<FileRange>>,
587 ) -> InlayHintLabel {
588 InlayHintLabel {
589 parts: smallvec![InlayHintLabelPart { text: s.into(), linked_location, tooltip }],
590 }
591 }
592
593 pub fn prepend_str(&mut self, s: &str) {
594 match &mut *self.parts {
595 [InlayHintLabelPart { text, linked_location: None, tooltip: None }, ..] => {
596 text.insert_str(0, s)
597 }
598 _ => self.parts.insert(
599 0,
600 InlayHintLabelPart { text: s.into(), linked_location: None, tooltip: None },
601 ),
602 }
603 }
604
605 pub fn append_str(&mut self, s: &str) {
606 match &mut *self.parts {
607 [.., InlayHintLabelPart { text, linked_location: None, tooltip: None }] => {
608 text.push_str(s)
609 }
610 _ => self.parts.push(InlayHintLabelPart {
611 text: s.into(),
612 linked_location: None,
613 tooltip: None,
614 }),
615 }
616 }
617
618 pub fn append_part(&mut self, part: InlayHintLabelPart) {
619 if part.linked_location.is_none()
620 && part.tooltip.is_none()
621 && let Some(InlayHintLabelPart { text, linked_location: None, tooltip: None }) =
622 self.parts.last_mut()
623 {
624 text.push_str(&part.text);
625 return;
626 }
627 self.parts.push(part);
628 }
629}
630
631impl From<String> for InlayHintLabel {
632 fn from(s: String) -> Self {
633 Self {
634 parts: smallvec![InlayHintLabelPart { text: s, linked_location: None, tooltip: None }],
635 }
636 }
637}
638
639impl From<&str> for InlayHintLabel {
640 fn from(s: &str) -> Self {
641 Self {
642 parts: smallvec![InlayHintLabelPart {
643 text: s.into(),
644 linked_location: None,
645 tooltip: None
646 }],
647 }
648 }
649}
650
651impl fmt::Display for InlayHintLabel {
652 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
653 write!(f, "{}", self.parts.iter().map(|part| &part.text).format(""))
654 }
655}
656
657impl fmt::Debug for InlayHintLabel {
658 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
659 f.debug_list().entries(&self.parts).finish()
660 }
661}
662
663#[derive(UpmapFromRaFixture)]
664pub struct InlayHintLabelPart {
665 pub text: String,
666 pub linked_location: Option<LazyProperty<FileRange>>,
672 pub tooltip: Option<LazyProperty<InlayTooltip>>,
675}
676
677impl std::hash::Hash for InlayHintLabelPart {
678 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
679 self.text.hash(state);
680 self.linked_location.is_some().hash(state);
681 self.tooltip.is_some().hash(state);
682 }
683}
684
685impl fmt::Debug for InlayHintLabelPart {
686 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
687 match self {
688 Self { text, linked_location: None, tooltip: None | Some(LazyProperty::Lazy) } => {
689 text.fmt(f)
690 }
691 Self { text, linked_location, tooltip } => f
692 .debug_struct("InlayHintLabelPart")
693 .field("text", text)
694 .field("linked_location", linked_location)
695 .field(
696 "tooltip",
697 &tooltip.as_ref().map_or("", |it| match it {
698 LazyProperty::Computed(
699 InlayTooltip::String(it) | InlayTooltip::Markdown(it),
700 ) => it,
701 LazyProperty::Lazy => "",
702 }),
703 )
704 .finish(),
705 }
706 }
707}
708
709#[derive(Debug)]
710struct InlayHintLabelBuilder<'a, 'db> {
711 sema: &'a Semantics<'db, RootDatabase>,
712 result: InlayHintLabel,
713 last_part: String,
714 resolve: bool,
715 location: Option<LazyProperty<FileRange>>,
716}
717
718impl fmt::Write for InlayHintLabelBuilder<'_, '_> {
719 fn write_str(&mut self, s: &str) -> fmt::Result {
720 self.last_part.write_str(s)
721 }
722}
723
724impl HirWrite for InlayHintLabelBuilder<'_, '_> {
725 fn start_location_link(&mut self, def: ModuleDefId) {
726 never!(self.location.is_some(), "location link is already started");
727 self.make_new_part();
728
729 self.location = Some(if self.resolve {
730 LazyProperty::Lazy
731 } else {
732 LazyProperty::Computed({
733 let Some(location) = ModuleDef::from(def).try_to_nav(self.sema) else { return };
734 let location = location.call_site();
735 FileRange { file_id: location.file_id, range: location.focus_or_full_range() }
736 })
737 });
738 }
739
740 fn start_location_link_generic(&mut self, def: GenericParamId) {
741 never!(self.location.is_some(), "location link is already started");
742 self.make_new_part();
743
744 self.location = Some(if self.resolve {
745 LazyProperty::Lazy
746 } else {
747 LazyProperty::Computed({
748 let Some(location) = GenericParam::from(def).try_to_nav(self.sema) else { return };
749 let location = location.call_site();
750 FileRange { file_id: location.file_id, range: location.focus_or_full_range() }
751 })
752 });
753 }
754
755 fn end_location_link(&mut self) {
756 self.make_new_part();
757 }
758}
759
760impl InlayHintLabelBuilder<'_, '_> {
761 fn make_new_part(&mut self) {
762 let text = take(&mut self.last_part);
763 if !text.is_empty() {
764 self.result.parts.push(InlayHintLabelPart {
765 text,
766 linked_location: self.location.take(),
767 tooltip: None,
768 });
769 }
770 }
771
772 fn finish(mut self) -> InlayHintLabel {
773 self.make_new_part();
774 self.result
775 }
776}
777
778fn label_of_ty<'db>(
779 famous_defs @ FamousDefs(sema, _): &FamousDefs<'_, 'db>,
780 config: &InlayHintsConfig<'_>,
781 ty: &hir::Type<'db>,
782 display_target: DisplayTarget,
783) -> Option<InlayHintLabel> {
784 fn rec<'db>(
785 sema: &Semantics<'db, RootDatabase>,
786 famous_defs: &FamousDefs<'_, 'db>,
787 mut max_length: Option<usize>,
788 ty: &hir::Type<'db>,
789 label_builder: &mut InlayHintLabelBuilder<'_, '_>,
790 config: &InlayHintsConfig<'_>,
791 display_target: DisplayTarget,
792 ) -> Result<(), HirDisplayError> {
793 let iter_item_type = hint_iterator(sema, famous_defs, ty);
794 match iter_item_type {
795 Some((iter_trait, item, ty)) => {
796 const LABEL_START: &str = "impl ";
797 const LABEL_ITERATOR: &str = "Iterator";
798 const LABEL_MIDDLE: &str = "<";
799 const LABEL_ITEM: &str = "Item";
800 const LABEL_MIDDLE2: &str = " = ";
801 const LABEL_END: &str = ">";
802
803 max_length = max_length.map(|len| {
804 len.saturating_sub(
805 LABEL_START.len()
806 + LABEL_ITERATOR.len()
807 + LABEL_MIDDLE.len()
808 + LABEL_MIDDLE2.len()
809 + LABEL_END.len(),
810 )
811 });
812
813 let module_def_location = |label_builder: &mut InlayHintLabelBuilder<'_, '_>,
814 def: ModuleDef,
815 name| {
816 let def = def.try_into();
817 if let Ok(def) = def {
818 label_builder.start_location_link(def);
819 }
820 #[expect(
821 clippy::question_mark,
822 reason = "false positive; replacing with `?` leads to 'type annotations needed' error"
823 )]
824 if let Err(err) = label_builder.write_str(name) {
825 return Err(err);
826 }
827 if def.is_ok() {
828 label_builder.end_location_link();
829 }
830 Ok(())
831 };
832
833 label_builder.write_str(LABEL_START)?;
834 module_def_location(label_builder, ModuleDef::from(iter_trait), LABEL_ITERATOR)?;
835 label_builder.write_str(LABEL_MIDDLE)?;
836 module_def_location(label_builder, ModuleDef::from(item), LABEL_ITEM)?;
837 label_builder.write_str(LABEL_MIDDLE2)?;
838 rec(sema, famous_defs, max_length, &ty, label_builder, config, display_target)?;
839 label_builder.write_str(LABEL_END)?;
840 Ok(())
841 }
842 None => ty
843 .display_truncated(sema.db, max_length, display_target)
844 .with_closure_style(config.closure_style)
845 .write_to(label_builder),
846 }
847 }
848
849 let mut label_builder = InlayHintLabelBuilder {
850 sema,
851 last_part: String::new(),
852 location: None,
853 result: InlayHintLabel::default(),
854 resolve: config.fields_to_resolve.resolve_label_location,
855 };
856 let _ =
857 rec(sema, famous_defs, config.max_length, ty, &mut label_builder, config, display_target);
858 let r = label_builder.finish();
859 Some(r)
860}
861
862fn hint_iterator<'db>(
864 sema: &Semantics<'db, RootDatabase>,
865 famous_defs: &FamousDefs<'_, 'db>,
866 ty: &hir::Type<'db>,
867) -> Option<(hir::Trait, hir::TypeAlias, hir::Type<'db>)> {
868 let db = sema.db;
869 let strukt = ty.strip_references().as_adt()?;
870 let krate = strukt.module(db).krate(db);
871 if krate != famous_defs.core()? {
872 return None;
873 }
874 let iter_trait = famous_defs.core_iter_Iterator()?;
875 let iter_mod = famous_defs.core_iter()?;
876
877 if !(strukt.visibility(db) == hir::Visibility::Public
879 && strukt.module(db).path_to_root(db).contains(&iter_mod))
880 {
881 return None;
882 }
883
884 if ty.impls_trait(db, iter_trait, &[]) {
885 let assoc_type_item = iter_trait.items(db).into_iter().find_map(|item| match item {
886 hir::AssocItem::TypeAlias(alias) if alias.name(db) == sym::Item => Some(alias),
887 _ => None,
888 })?;
889 if let Some(ty) = ty.normalize_trait_assoc_type(db, &[], assoc_type_item) {
890 return Some((iter_trait, assoc_type_item, ty));
891 }
892 }
893
894 None
895}
896
897fn ty_to_text_edit(
898 sema: &Semantics<'_, RootDatabase>,
899 config: &InlayHintsConfig<'_>,
900 node_for_hint: &SyntaxNode,
901 ty: &hir::Type<'_>,
902 offset_to_insert_ty: TextSize,
903 additional_edits: &dyn Fn(&mut TextEditBuilder),
904 prefix: impl Into<String>,
905) -> Option<LazyProperty<TextEdit>> {
906 let rendered = sema
908 .scope(node_for_hint)
909 .and_then(|scope| ty.display_source_code(scope.db, scope.module().into(), false).ok())?;
910 Some(config.lazy_text_edit(|| {
911 let mut builder = TextEdit::builder();
912 builder.insert(offset_to_insert_ty, prefix.into());
913 builder.insert(offset_to_insert_ty, rendered);
914
915 additional_edits(&mut builder);
916
917 builder.finish()
918 }))
919}
920
921fn closure_has_block_body(closure: &ast::ClosureExpr) -> bool {
922 matches!(closure.body(), Some(ast::Expr::BlockExpr(_)))
923}
924
925#[cfg(test)]
926mod tests {
927
928 use expect_test::Expect;
929 use hir::ClosureStyle;
930 use ide_db::ra_fixture::RaFixtureConfig;
931 use itertools::Itertools;
932 use test_utils::extract_annotations;
933
934 use crate::DiscriminantHints;
935 use crate::inlay_hints::{AdjustmentHints, AdjustmentHintsMode};
936 use crate::{LifetimeElisionHints, fixture, inlay_hints::InlayHintsConfig};
937
938 use super::{
939 ClosureReturnTypeHints, GenericParameterHints, ImplicitDropHints, InlayFieldsToResolve,
940 TypeHintsPlacement,
941 };
942
943 pub(super) const DISABLED_CONFIG: InlayHintsConfig<'_> = InlayHintsConfig {
944 discriminant_hints: DiscriminantHints::Never,
945 render_colons: false,
946 type_hints: false,
947 type_hints_placement: TypeHintsPlacement::Inline,
948 parameter_hints: false,
949 parameter_hints_for_missing_arguments: false,
950 sized_bound: false,
951 generic_parameter_hints: GenericParameterHints {
952 type_hints: false,
953 lifetime_hints: false,
954 const_hints: false,
955 },
956 chaining_hints: false,
957 lifetime_elision_hints: LifetimeElisionHints::Never,
958 closure_return_type_hints: ClosureReturnTypeHints::Never,
959 closure_capture_hints: false,
960 adjustment_hints: AdjustmentHints::Never,
961 adjustment_hints_disable_reborrows: false,
962 adjustment_hints_mode: AdjustmentHintsMode::Prefix,
963 adjustment_hints_hide_outside_unsafe: false,
964 binding_mode_hints: false,
965 hide_inferred_type_hints: false,
966 hide_named_constructor_hints: false,
967 hide_closure_initialization_hints: false,
968 hide_closure_parameter_hints: false,
969 closure_style: ClosureStyle::ImplFn,
970 param_names_for_lifetime_elision_hints: false,
971 max_length: None,
972 closing_brace_hints_min_lines: None,
973 fields_to_resolve: InlayFieldsToResolve::empty(),
974 implicit_drop_hints: ImplicitDropHints::Never,
975 implied_dyn_trait_hints: false,
976 range_exclusive_hints: false,
977 ra_fixture: RaFixtureConfig::default(),
978 };
979 pub(super) const TEST_CONFIG: InlayHintsConfig<'_> = InlayHintsConfig {
980 type_hints: true,
981 type_hints_placement: TypeHintsPlacement::Inline,
982 parameter_hints: true,
983 chaining_hints: true,
984 closure_return_type_hints: ClosureReturnTypeHints::WithBlock,
985 binding_mode_hints: true,
986 lifetime_elision_hints: LifetimeElisionHints::Always,
987 ..DISABLED_CONFIG
988 };
989
990 #[track_caller]
991 pub(super) fn check(#[rust_analyzer::rust_fixture] ra_fixture: &str) {
992 check_with_config(TEST_CONFIG, ra_fixture);
993 }
994
995 #[track_caller]
996 pub(super) fn check_with_config(
997 config: InlayHintsConfig<'_>,
998 #[rust_analyzer::rust_fixture] ra_fixture: &str,
999 ) {
1000 let (analysis, file_id) = fixture::file(ra_fixture);
1001 let mut expected = extract_annotations(&analysis.file_text(file_id).unwrap());
1002 let inlay_hints = analysis.inlay_hints(&config, file_id, None).unwrap();
1003 let actual = inlay_hints
1004 .into_iter()
1005 .map(|it| (it.range, it.label.to_string().trim_start().to_owned()))
1007 .sorted_by_key(|(range, _)| range.start())
1008 .collect::<Vec<_>>();
1009 expected.sort_by_key(|(range, _)| range.start());
1010
1011 assert_eq!(expected, actual, "\nExpected:\n{expected:#?}\n\nActual:\n{actual:#?}");
1012 }
1013
1014 #[track_caller]
1015 pub(super) fn check_expect(
1016 config: InlayHintsConfig<'_>,
1017 #[rust_analyzer::rust_fixture] ra_fixture: &str,
1018 expect: Expect,
1019 ) {
1020 let (analysis, file_id) = fixture::file(ra_fixture);
1021 let inlay_hints = analysis.inlay_hints(&config, file_id, None).unwrap();
1022 let filtered =
1023 inlay_hints.into_iter().map(|hint| (hint.range, hint.label)).collect::<Vec<_>>();
1024 expect.assert_debug_eq(&filtered)
1025 }
1026
1027 #[track_caller]
1030 pub(super) fn check_edit(
1031 config: InlayHintsConfig<'_>,
1032 #[rust_analyzer::rust_fixture] ra_fixture: &str,
1033 expect: Expect,
1034 ) {
1035 let (analysis, file_id) = fixture::file(ra_fixture);
1036 let inlay_hints = analysis.inlay_hints(&config, file_id, None).unwrap();
1037
1038 let edits = inlay_hints
1039 .into_iter()
1040 .filter_map(|hint| hint.text_edit?.computed())
1041 .reduce(|mut acc, next| {
1042 acc.union(next).expect("merging text edits failed");
1043 acc
1044 })
1045 .expect("no edit returned");
1046
1047 let mut actual = analysis.file_text(file_id).unwrap().to_string();
1048 edits.apply(&mut actual);
1049 expect.assert_eq(&actual);
1050 }
1051
1052 #[track_caller]
1053 pub(super) fn check_no_edit(
1054 config: InlayHintsConfig<'_>,
1055 #[rust_analyzer::rust_fixture] ra_fixture: &str,
1056 ) {
1057 let (analysis, file_id) = fixture::file(ra_fixture);
1058 let inlay_hints = analysis.inlay_hints(&config, file_id, None).unwrap();
1059
1060 let edits: Vec<_> =
1061 inlay_hints.into_iter().filter_map(|hint| hint.text_edit?.computed()).collect();
1062
1063 assert!(edits.is_empty(), "unexpected edits: {edits:?}");
1064 }
1065
1066 #[test]
1067 fn hints_disabled() {
1068 check_with_config(
1069 InlayHintsConfig { render_colons: true, ..DISABLED_CONFIG },
1070 r#"
1071fn foo(a: i32, b: i32) -> i32 { a + b }
1072fn main() {
1073 let _x = foo(4, 4);
1074}"#,
1075 );
1076 }
1077
1078 #[test]
1079 fn regression_18840() {
1080 check(
1081 r#"
1082//- proc_macros: issue_18840
1083#[proc_macros::issue_18840]
1084fn foo() {
1085 let
1086 loop {}
1087}
1088"#,
1089 );
1090 }
1091
1092 #[test]
1093 fn regression_18898() {
1094 check(
1095 r#"
1096//- proc_macros: issue_18898
1097#[proc_macros::issue_18898]
1098fn foo() {
1099 let
1100}
1101"#,
1102 );
1103 }
1104
1105 #[test]
1106 fn closure_dependency_cycle_no_panic() {
1107 check(
1108 r#"
1109//- minicore: fn
1110fn foo() {
1111 let closure;
1112 // ^^^^^^^ impl FnOnce()
1113 closure = || {
1114 closure();
1115 };
1116}
1117
1118fn bar() {
1119 let closure1;
1120 // ^^^^^^^^ impl FnOnce()
1121 let closure2;
1122 // ^^^^^^^^ impl FnOnce()
1123 closure1 = || {
1124 closure2();
1125 };
1126 closure2 = || {
1127 closure1();
1128 };
1129}
1130 "#,
1131 );
1132 }
1133
1134 #[test]
1135 fn regression_19610() {
1136 check(
1137 r#"
1138trait Trait {
1139 type Assoc;
1140}
1141struct Foo<A>(A);
1142impl<A: Trait<Assoc = impl Trait>> Foo<A> {
1143 fn foo<'a, 'b>(_: &'a [i32], _: &'b [i32]) {}
1144}
1145
1146fn bar() {
1147 Foo::foo(&[1], &[2]);
1148}
1149"#,
1150 );
1151 }
1152
1153 #[test]
1154 fn regression_20239() {
1155 check_with_config(
1156 InlayHintsConfig { parameter_hints: true, type_hints: true, ..DISABLED_CONFIG },
1157 r#"
1158//- minicore: fn
1159trait Iterator {
1160 type Item;
1161 fn map<B, F: FnMut(Self::Item) -> B>(self, f: F);
1162}
1163trait ToString {
1164 fn to_string(&self);
1165}
1166
1167fn check_tostr_eq<L, R>(left: L, right: R)
1168where
1169 L: Iterator,
1170 L::Item: ToString,
1171 R: Iterator,
1172 R::Item: ToString,
1173{
1174 left.map(|s| s.to_string());
1175 // ^ impl ToString
1176 right.map(|s| s.to_string());
1177 // ^ impl ToString
1178}
1179 "#,
1180 );
1181 }
1182}