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