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