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