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