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