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.attach_first_edition(file_id);
93 let file = sema.parse(file_id);
94 let file = file.syntax();
95
96 let mut acc = Vec::new();
97
98 let Some(scope) = sema.scope(file) else {
99 return acc;
100 };
101 let famous_defs = FamousDefs(&sema, scope.krate());
102 let display_target = famous_defs.1.to_display_target(sema.db);
103
104 let ctx = &mut InlayHintCtx::default();
105 let mut hints = |event| {
106 if let Some(node) = handle_event(ctx, event) {
107 hints(&mut acc, ctx, &famous_defs, config, file_id, display_target, node);
108 }
109 };
110 let mut preorder = file.preorder();
111 hir::attach_db(sema.db, || {
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 });
121 if let Some(range_limit) = range_limit {
122 acc.retain(|hint| range_limit.contains_range(hint.range));
123 }
124 acc
125}
126
127#[derive(Default)]
128struct InlayHintCtx {
129 lifetime_stacks: Vec<Vec<SmolStr>>,
130 extern_block_parent: Option<ast::ExternBlock>,
131}
132
133pub(crate) fn inlay_hints_resolve(
134 db: &RootDatabase,
135 file_id: FileId,
136 resolve_range: TextRange,
137 hash: u64,
138 config: &InlayHintsConfig<'_>,
139 hasher: impl Fn(&InlayHint) -> u64,
140) -> Option<InlayHint> {
141 let _p = tracing::info_span!("inlay_hints_resolve").entered();
142 let sema = Semantics::new(db);
143 let file_id = sema.attach_first_edition(file_id);
144 let file = sema.parse(file_id);
145 let file = file.syntax();
146
147 let scope = sema.scope(file)?;
148 let famous_defs = FamousDefs(&sema, scope.krate());
149 let mut acc = Vec::new();
150
151 let display_target = famous_defs.1.to_display_target(sema.db);
152
153 let ctx = &mut InlayHintCtx::default();
154 let mut hints = |event| {
155 if let Some(node) = handle_event(ctx, event) {
156 hints(&mut acc, ctx, &famous_defs, config, file_id, display_target, node);
157 }
158 };
159
160 let mut preorder = file.preorder();
161 while let Some(event) = preorder.next() {
162 if matches!(&event, WalkEvent::Enter(node) if resolve_range.intersect(node.text_range()).is_none())
164 {
165 preorder.skip_subtree();
166 continue;
167 }
168 hints(event);
169 }
170 acc.into_iter().find(|hint| hasher(hint) == hash)
171}
172
173fn handle_event(ctx: &mut InlayHintCtx, node: WalkEvent<SyntaxNode>) -> Option<SyntaxNode> {
174 match node {
175 WalkEvent::Enter(node) => {
176 if let Some(node) = ast::AnyHasGenericParams::cast(node.clone()) {
177 let params = node
178 .generic_param_list()
179 .map(|it| {
180 it.lifetime_params()
181 .filter_map(|it| {
182 it.lifetime().map(|it| format_smolstr!("{}", &it.text()[1..]))
183 })
184 .collect()
185 })
186 .unwrap_or_default();
187 ctx.lifetime_stacks.push(params);
188 }
189 if let Some(node) = ast::ExternBlock::cast(node.clone()) {
190 ctx.extern_block_parent = Some(node);
191 }
192 Some(node)
193 }
194 WalkEvent::Leave(n) => {
195 if ast::AnyHasGenericParams::can_cast(n.kind()) {
196 ctx.lifetime_stacks.pop();
197 }
198 if ast::ExternBlock::can_cast(n.kind()) {
199 ctx.extern_block_parent = None;
200 }
201 None
202 }
203 }
204}
205
206fn hints(
209 hints: &mut Vec<InlayHint>,
210 ctx: &mut InlayHintCtx,
211 famous_defs @ FamousDefs(sema, _krate): &FamousDefs<'_, '_>,
212 config: &InlayHintsConfig<'_>,
213 file_id: EditionedFileId,
214 display_target: DisplayTarget,
215 node: SyntaxNode,
216) {
217 closing_brace::hints(
218 hints,
219 sema,
220 config,
221 display_target,
222 InRealFile { file_id, value: node.clone() },
223 );
224 if let Some(any_has_generic_args) = ast::AnyHasGenericArgs::cast(node.clone()) {
225 generic_param::hints(hints, famous_defs, config, any_has_generic_args);
226 }
227
228 match_ast! {
229 match node {
230 ast::Expr(expr) => {
231 chaining::hints(hints, famous_defs, config, display_target, &expr);
232 adjustment::hints(hints, famous_defs, config, display_target, &expr);
233 match expr {
234 ast::Expr::CallExpr(it) => param_name::hints(hints, famous_defs, config, file_id, ast::Expr::from(it)),
235 ast::Expr::MethodCallExpr(it) => {
236 param_name::hints(hints, famous_defs, config, file_id, ast::Expr::from(it))
237 }
238 ast::Expr::ClosureExpr(it) => {
239 closure_captures::hints(hints, famous_defs, config, it.clone());
240 closure_ret::hints(hints, famous_defs, config, display_target, it)
241 },
242 ast::Expr::RangeExpr(it) => range_exclusive::hints(hints, famous_defs, config, it),
243 ast::Expr::Literal(it) => ra_fixture::hints(hints, famous_defs.0, file_id, config, it),
244 _ => Some(()),
245 }
246 },
247 ast::Pat(it) => {
248 binding_mode::hints(hints, famous_defs, config, &it);
249 match it {
250 ast::Pat::IdentPat(it) => {
251 bind_pat::hints(hints, famous_defs, config, display_target, &it);
252 }
253 ast::Pat::RangePat(it) => {
254 range_exclusive::hints(hints, famous_defs, config, it);
255 }
256 _ => {}
257 }
258 Some(())
259 },
260 ast::Item(it) => match it {
261 ast::Item::Fn(it) => {
262 implicit_drop::hints(hints, famous_defs, config, display_target, &it);
263 if let Some(extern_block) = &ctx.extern_block_parent {
264 extern_block::fn_hints(hints, famous_defs, config, &it, extern_block);
265 }
266 lifetime::fn_hints(hints, ctx, famous_defs, config, it)
267 },
268 ast::Item::Static(it) => {
269 if let Some(extern_block) = &ctx.extern_block_parent {
270 extern_block::static_hints(hints, famous_defs, config, &it, extern_block);
271 }
272 implicit_static::hints(hints, famous_defs, config, Either::Left(it))
273 },
274 ast::Item::Const(it) => implicit_static::hints(hints, famous_defs, config, Either::Right(it)),
275 ast::Item::Enum(it) => discriminant::enum_hints(hints, famous_defs, config, it),
276 ast::Item::ExternBlock(it) => extern_block::extern_block_hints(hints, famous_defs, config, it),
277 _ => None,
278 },
279 ast::Type(ty) => match ty {
281 ast::Type::FnPtrType(ptr) => lifetime::fn_ptr_hints(hints, ctx, famous_defs, config, ptr),
282 ast::Type::PathType(path) => {
283 lifetime::fn_path_hints(hints, ctx, famous_defs, config, &path);
284 implied_dyn_trait::hints(hints, famous_defs, config, Either::Left(path));
285 Some(())
286 },
287 ast::Type::DynTraitType(dyn_) => {
288 implied_dyn_trait::hints(hints, famous_defs, config, Either::Right(dyn_));
289 Some(())
290 },
291 ast::Type::InferType(placeholder) => {
292 placeholders::type_hints(hints, famous_defs, config, display_target, placeholder);
293 Some(())
294 },
295 _ => Some(()),
296 },
297 ast::GenericParamList(it) => bounds::hints(hints, famous_defs, config, it),
298 _ => Some(()),
299 }
300 };
301}
302
303#[derive(Clone, Debug)]
304pub struct InlayHintsConfig<'a> {
305 pub render_colons: bool,
306 pub type_hints: bool,
307 pub sized_bound: bool,
308 pub discriminant_hints: DiscriminantHints,
309 pub parameter_hints: bool,
310 pub parameter_hints_for_missing_arguments: bool,
311 pub generic_parameter_hints: GenericParameterHints,
312 pub chaining_hints: bool,
313 pub adjustment_hints: AdjustmentHints,
314 pub adjustment_hints_disable_reborrows: bool,
315 pub adjustment_hints_mode: AdjustmentHintsMode,
316 pub adjustment_hints_hide_outside_unsafe: bool,
317 pub closure_return_type_hints: ClosureReturnTypeHints,
318 pub closure_capture_hints: bool,
319 pub binding_mode_hints: bool,
320 pub implicit_drop_hints: bool,
321 pub implied_dyn_trait_hints: bool,
322 pub lifetime_elision_hints: LifetimeElisionHints,
323 pub param_names_for_lifetime_elision_hints: bool,
324 pub hide_inferred_type_hints: bool,
325 pub hide_named_constructor_hints: bool,
326 pub hide_closure_initialization_hints: bool,
327 pub hide_closure_parameter_hints: bool,
328 pub range_exclusive_hints: bool,
329 pub closure_style: ClosureStyle,
330 pub max_length: Option<usize>,
331 pub closing_brace_hints_min_lines: Option<usize>,
332 pub fields_to_resolve: InlayFieldsToResolve,
333 pub minicore: MiniCore<'a>,
334}
335
336impl InlayHintsConfig<'_> {
337 fn lazy_text_edit(&self, finish: impl FnOnce() -> TextEdit) -> LazyProperty<TextEdit> {
338 if self.fields_to_resolve.resolve_text_edits {
339 LazyProperty::Lazy
340 } else {
341 let edit = finish();
342 never!(edit.is_empty(), "inlay hint produced an empty text edit");
343 LazyProperty::Computed(edit)
344 }
345 }
346
347 fn lazy_tooltip(&self, finish: impl FnOnce() -> InlayTooltip) -> LazyProperty<InlayTooltip> {
348 if self.fields_to_resolve.resolve_hint_tooltip
349 && self.fields_to_resolve.resolve_label_tooltip
350 {
351 LazyProperty::Lazy
352 } else {
353 let tooltip = finish();
354 never!(
355 match &tooltip {
356 InlayTooltip::String(s) => s,
357 InlayTooltip::Markdown(s) => s,
358 }
359 .is_empty(),
360 "inlay hint produced an empty tooltip"
361 );
362 LazyProperty::Computed(tooltip)
363 }
364 }
365
366 fn lazy_location_opt(
369 &self,
370 finish: impl FnOnce() -> Option<FileRange>,
371 ) -> Option<LazyProperty<FileRange>> {
372 if self.fields_to_resolve.resolve_label_location {
373 Some(LazyProperty::Lazy)
374 } else {
375 finish().map(LazyProperty::Computed)
376 }
377 }
378}
379
380#[derive(Copy, Clone, Debug, PartialEq, Eq)]
381pub struct InlayFieldsToResolve {
382 pub resolve_text_edits: bool,
383 pub resolve_hint_tooltip: bool,
384 pub resolve_label_tooltip: bool,
385 pub resolve_label_location: bool,
386 pub resolve_label_command: bool,
387}
388
389impl InlayFieldsToResolve {
390 pub fn from_client_capabilities(client_capability_fields: &FxHashSet<&str>) -> Self {
391 Self {
392 resolve_text_edits: client_capability_fields.contains("textEdits"),
393 resolve_hint_tooltip: client_capability_fields.contains("tooltip"),
394 resolve_label_tooltip: client_capability_fields.contains("label.tooltip"),
395 resolve_label_location: client_capability_fields.contains("label.location"),
396 resolve_label_command: client_capability_fields.contains("label.command"),
397 }
398 }
399
400 pub const fn empty() -> Self {
401 Self {
402 resolve_text_edits: false,
403 resolve_hint_tooltip: false,
404 resolve_label_tooltip: false,
405 resolve_label_location: false,
406 resolve_label_command: false,
407 }
408 }
409}
410
411#[derive(Clone, Debug, PartialEq, Eq)]
412pub enum ClosureReturnTypeHints {
413 Always,
414 WithBlock,
415 Never,
416}
417
418#[derive(Clone, Debug, PartialEq, Eq)]
419pub enum DiscriminantHints {
420 Always,
421 Never,
422 Fieldless,
423}
424
425#[derive(Clone, Debug, PartialEq, Eq)]
426pub struct GenericParameterHints {
427 pub type_hints: bool,
428 pub lifetime_hints: bool,
429 pub const_hints: bool,
430}
431
432#[derive(Clone, Debug, PartialEq, Eq)]
433pub enum LifetimeElisionHints {
434 Always,
435 SkipTrivial,
436 Never,
437}
438
439#[derive(Clone, Debug, PartialEq, Eq)]
440pub enum AdjustmentHints {
441 Always,
442 BorrowsOnly,
443 Never,
444}
445
446#[derive(Copy, Clone, Debug, PartialEq, Eq)]
447pub enum AdjustmentHintsMode {
448 Prefix,
449 Postfix,
450 PreferPrefix,
451 PreferPostfix,
452}
453
454#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
455pub enum InlayKind {
456 Adjustment,
457 BindingMode,
458 Chaining,
459 ClosingBrace,
460 ClosureCapture,
461 Discriminant,
462 GenericParamList,
463 Lifetime,
464 Parameter,
465 GenericParameter,
466 Type,
467 Dyn,
468 Drop,
469 RangeExclusive,
470 ExternUnsafety,
471}
472
473#[derive(Debug, Hash)]
474pub enum InlayHintPosition {
475 Before,
476 After,
477}
478
479#[derive(Debug, UpmapFromRaFixture)]
480pub struct InlayHint {
481 pub range: TextRange,
483 pub position: InlayHintPosition,
484 pub pad_left: bool,
485 pub pad_right: bool,
486 pub kind: InlayKind,
488 pub label: InlayHintLabel,
490 pub text_edit: Option<LazyProperty<TextEdit>>,
492 pub resolve_parent: Option<TextRange>,
495}
496
497#[derive(Clone, Debug, Default, UpmapFromRaFixture)]
499pub enum LazyProperty<T> {
500 Computed(T),
501 #[default]
502 Lazy,
503}
504
505impl<T> LazyProperty<T> {
506 pub fn computed(self) -> Option<T> {
507 match self {
508 LazyProperty::Computed(it) => Some(it),
509 _ => None,
510 }
511 }
512
513 pub fn is_lazy(&self) -> bool {
514 matches!(self, Self::Lazy)
515 }
516}
517
518impl std::hash::Hash for InlayHint {
519 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
520 self.range.hash(state);
521 self.position.hash(state);
522 self.pad_left.hash(state);
523 self.pad_right.hash(state);
524 self.kind.hash(state);
525 self.label.hash(state);
526 mem::discriminant(&self.text_edit).hash(state);
527 }
528}
529
530impl InlayHint {
531 fn closing_paren_after(kind: InlayKind, range: TextRange) -> InlayHint {
532 InlayHint {
533 range,
534 kind,
535 label: InlayHintLabel::from(")"),
536 text_edit: None,
537 position: InlayHintPosition::After,
538 pad_left: false,
539 pad_right: false,
540 resolve_parent: None,
541 }
542 }
543}
544
545#[derive(Debug, Hash)]
546pub enum InlayTooltip {
547 String(String),
548 Markdown(String),
549}
550
551#[derive(Default, Hash, UpmapFromRaFixture)]
552pub struct InlayHintLabel {
553 pub parts: SmallVec<[InlayHintLabelPart; 1]>,
554}
555
556impl InlayHintLabel {
557 pub fn simple(
558 s: impl Into<String>,
559 tooltip: Option<LazyProperty<InlayTooltip>>,
560 linked_location: Option<LazyProperty<FileRange>>,
561 ) -> InlayHintLabel {
562 InlayHintLabel {
563 parts: smallvec![InlayHintLabelPart { text: s.into(), linked_location, tooltip }],
564 }
565 }
566
567 pub fn prepend_str(&mut self, s: &str) {
568 match &mut *self.parts {
569 [InlayHintLabelPart { text, linked_location: None, tooltip: None }, ..] => {
570 text.insert_str(0, s)
571 }
572 _ => self.parts.insert(
573 0,
574 InlayHintLabelPart { text: s.into(), linked_location: None, tooltip: None },
575 ),
576 }
577 }
578
579 pub fn append_str(&mut self, s: &str) {
580 match &mut *self.parts {
581 [.., InlayHintLabelPart { text, linked_location: None, tooltip: None }] => {
582 text.push_str(s)
583 }
584 _ => self.parts.push(InlayHintLabelPart {
585 text: s.into(),
586 linked_location: None,
587 tooltip: None,
588 }),
589 }
590 }
591
592 pub fn append_part(&mut self, part: InlayHintLabelPart) {
593 if part.linked_location.is_none()
594 && part.tooltip.is_none()
595 && let Some(InlayHintLabelPart { text, linked_location: None, tooltip: None }) =
596 self.parts.last_mut()
597 {
598 text.push_str(&part.text);
599 return;
600 }
601 self.parts.push(part);
602 }
603}
604
605impl From<String> for InlayHintLabel {
606 fn from(s: String) -> Self {
607 Self {
608 parts: smallvec![InlayHintLabelPart { text: s, linked_location: None, tooltip: None }],
609 }
610 }
611}
612
613impl From<&str> for InlayHintLabel {
614 fn from(s: &str) -> Self {
615 Self {
616 parts: smallvec![InlayHintLabelPart {
617 text: s.into(),
618 linked_location: None,
619 tooltip: None
620 }],
621 }
622 }
623}
624
625impl fmt::Display for InlayHintLabel {
626 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
627 write!(f, "{}", self.parts.iter().map(|part| &part.text).format(""))
628 }
629}
630
631impl fmt::Debug for InlayHintLabel {
632 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
633 f.debug_list().entries(&self.parts).finish()
634 }
635}
636
637#[derive(UpmapFromRaFixture)]
638pub struct InlayHintLabelPart {
639 pub text: String,
640 pub linked_location: Option<LazyProperty<FileRange>>,
646 pub tooltip: Option<LazyProperty<InlayTooltip>>,
649}
650
651impl std::hash::Hash for InlayHintLabelPart {
652 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
653 self.text.hash(state);
654 self.linked_location.is_some().hash(state);
655 self.tooltip.is_some().hash(state);
656 }
657}
658
659impl fmt::Debug for InlayHintLabelPart {
660 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
661 match self {
662 Self { text, linked_location: None, tooltip: None | Some(LazyProperty::Lazy) } => {
663 text.fmt(f)
664 }
665 Self { text, linked_location, tooltip } => f
666 .debug_struct("InlayHintLabelPart")
667 .field("text", text)
668 .field("linked_location", linked_location)
669 .field(
670 "tooltip",
671 &tooltip.as_ref().map_or("", |it| match it {
672 LazyProperty::Computed(
673 InlayTooltip::String(it) | InlayTooltip::Markdown(it),
674 ) => it,
675 LazyProperty::Lazy => "",
676 }),
677 )
678 .finish(),
679 }
680 }
681}
682
683#[derive(Debug)]
684struct InlayHintLabelBuilder<'a> {
685 sema: &'a Semantics<'a, RootDatabase>,
686 result: InlayHintLabel,
687 last_part: String,
688 resolve: bool,
689 location: Option<LazyProperty<FileRange>>,
690}
691
692impl fmt::Write for InlayHintLabelBuilder<'_> {
693 fn write_str(&mut self, s: &str) -> fmt::Result {
694 self.last_part.write_str(s)
695 }
696}
697
698impl HirWrite for InlayHintLabelBuilder<'_> {
699 fn start_location_link(&mut self, def: ModuleDefId) {
700 never!(self.location.is_some(), "location link is already started");
701 self.make_new_part();
702
703 self.location = Some(if self.resolve {
704 LazyProperty::Lazy
705 } else {
706 LazyProperty::Computed({
707 let Some(location) = ModuleDef::from(def).try_to_nav(self.sema) else { return };
708 let location = location.call_site();
709 FileRange { file_id: location.file_id, range: location.focus_or_full_range() }
710 })
711 });
712 }
713
714 fn end_location_link(&mut self) {
715 self.make_new_part();
716 }
717}
718
719impl InlayHintLabelBuilder<'_> {
720 fn make_new_part(&mut self) {
721 let text = take(&mut self.last_part);
722 if !text.is_empty() {
723 self.result.parts.push(InlayHintLabelPart {
724 text,
725 linked_location: self.location.take(),
726 tooltip: None,
727 });
728 }
729 }
730
731 fn finish(mut self) -> InlayHintLabel {
732 self.make_new_part();
733 self.result
734 }
735}
736
737fn label_of_ty(
738 famous_defs @ FamousDefs(sema, _): &FamousDefs<'_, '_>,
739 config: &InlayHintsConfig<'_>,
740 ty: &hir::Type<'_>,
741 display_target: DisplayTarget,
742) -> Option<InlayHintLabel> {
743 fn rec(
744 sema: &Semantics<'_, RootDatabase>,
745 famous_defs: &FamousDefs<'_, '_>,
746 mut max_length: Option<usize>,
747 ty: &hir::Type<'_>,
748 label_builder: &mut InlayHintLabelBuilder<'_>,
749 config: &InlayHintsConfig<'_>,
750 display_target: DisplayTarget,
751 ) -> Result<(), HirDisplayError> {
752 hir::attach_db(sema.db, || {
753 let iter_item_type = hint_iterator(sema, famous_defs, ty);
754 match iter_item_type {
755 Some((iter_trait, item, ty)) => {
756 const LABEL_START: &str = "impl ";
757 const LABEL_ITERATOR: &str = "Iterator";
758 const LABEL_MIDDLE: &str = "<";
759 const LABEL_ITEM: &str = "Item";
760 const LABEL_MIDDLE2: &str = " = ";
761 const LABEL_END: &str = ">";
762
763 max_length = max_length.map(|len| {
764 len.saturating_sub(
765 LABEL_START.len()
766 + LABEL_ITERATOR.len()
767 + LABEL_MIDDLE.len()
768 + LABEL_MIDDLE2.len()
769 + LABEL_END.len(),
770 )
771 });
772
773 label_builder.write_str(LABEL_START)?;
774 label_builder.start_location_link(ModuleDef::from(iter_trait).into());
775 label_builder.write_str(LABEL_ITERATOR)?;
776 label_builder.end_location_link();
777 label_builder.write_str(LABEL_MIDDLE)?;
778 label_builder.start_location_link(ModuleDef::from(item).into());
779 label_builder.write_str(LABEL_ITEM)?;
780 label_builder.end_location_link();
781 label_builder.write_str(LABEL_MIDDLE2)?;
782 rec(sema, famous_defs, max_length, &ty, label_builder, config, display_target)?;
783 label_builder.write_str(LABEL_END)?;
784 Ok(())
785 }
786 None => ty
787 .display_truncated(sema.db, max_length, display_target)
788 .with_closure_style(config.closure_style)
789 .write_to(label_builder),
790 }
791 })
792 }
793
794 let mut label_builder = InlayHintLabelBuilder {
795 sema,
796 last_part: String::new(),
797 location: None,
798 result: InlayHintLabel::default(),
799 resolve: config.fields_to_resolve.resolve_label_location,
800 };
801 let _ =
802 rec(sema, famous_defs, config.max_length, ty, &mut label_builder, config, display_target);
803 let r = label_builder.finish();
804 Some(r)
805}
806
807fn hint_iterator<'db>(
809 sema: &Semantics<'db, RootDatabase>,
810 famous_defs: &FamousDefs<'_, 'db>,
811 ty: &hir::Type<'db>,
812) -> Option<(hir::Trait, hir::TypeAlias, hir::Type<'db>)> {
813 let db = sema.db;
814 let strukt = ty.strip_references().as_adt()?;
815 let krate = strukt.module(db).krate(db);
816 if krate != famous_defs.core()? {
817 return None;
818 }
819 let iter_trait = famous_defs.core_iter_Iterator()?;
820 let iter_mod = famous_defs.core_iter()?;
821
822 if !(strukt.visibility(db) == hir::Visibility::Public
824 && strukt.module(db).path_to_root(db).contains(&iter_mod))
825 {
826 return None;
827 }
828
829 if ty.impls_trait(db, iter_trait, &[]) {
830 let assoc_type_item = iter_trait.items(db).into_iter().find_map(|item| match item {
831 hir::AssocItem::TypeAlias(alias) if alias.name(db) == sym::Item => Some(alias),
832 _ => None,
833 })?;
834 if let Some(ty) = ty.normalize_trait_assoc_type(db, &[], assoc_type_item) {
835 return Some((iter_trait, assoc_type_item, ty));
836 }
837 }
838
839 None
840}
841
842fn ty_to_text_edit(
843 sema: &Semantics<'_, RootDatabase>,
844 config: &InlayHintsConfig<'_>,
845 node_for_hint: &SyntaxNode,
846 ty: &hir::Type<'_>,
847 offset_to_insert_ty: TextSize,
848 additional_edits: &dyn Fn(&mut TextEditBuilder),
849 prefix: impl Into<String>,
850) -> Option<LazyProperty<TextEdit>> {
851 let rendered = sema
853 .scope(node_for_hint)
854 .and_then(|scope| ty.display_source_code(scope.db, scope.module().into(), false).ok())?;
855 Some(config.lazy_text_edit(|| {
856 let mut builder = TextEdit::builder();
857 builder.insert(offset_to_insert_ty, prefix.into());
858 builder.insert(offset_to_insert_ty, rendered);
859
860 additional_edits(&mut builder);
861
862 builder.finish()
863 }))
864}
865
866fn closure_has_block_body(closure: &ast::ClosureExpr) -> bool {
867 matches!(closure.body(), Some(ast::Expr::BlockExpr(_)))
868}
869
870#[cfg(test)]
871mod tests {
872
873 use expect_test::Expect;
874 use hir::ClosureStyle;
875 use ide_db::MiniCore;
876 use itertools::Itertools;
877 use test_utils::extract_annotations;
878
879 use crate::DiscriminantHints;
880 use crate::inlay_hints::{AdjustmentHints, AdjustmentHintsMode};
881 use crate::{LifetimeElisionHints, fixture, inlay_hints::InlayHintsConfig};
882
883 use super::{ClosureReturnTypeHints, GenericParameterHints, InlayFieldsToResolve};
884
885 pub(super) const DISABLED_CONFIG: InlayHintsConfig<'_> = InlayHintsConfig {
886 discriminant_hints: DiscriminantHints::Never,
887 render_colons: false,
888 type_hints: false,
889 parameter_hints: false,
890 parameter_hints_for_missing_arguments: false,
891 sized_bound: false,
892 generic_parameter_hints: GenericParameterHints {
893 type_hints: false,
894 lifetime_hints: false,
895 const_hints: false,
896 },
897 chaining_hints: false,
898 lifetime_elision_hints: LifetimeElisionHints::Never,
899 closure_return_type_hints: ClosureReturnTypeHints::Never,
900 closure_capture_hints: false,
901 adjustment_hints: AdjustmentHints::Never,
902 adjustment_hints_disable_reborrows: false,
903 adjustment_hints_mode: AdjustmentHintsMode::Prefix,
904 adjustment_hints_hide_outside_unsafe: false,
905 binding_mode_hints: false,
906 hide_inferred_type_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}