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