ide/inlay_hints/
closure_ret.rs

1//! Implementation of "closure return type" inlay hints.
2//!
3//! Tests live in [`bind_pat`][super::bind_pat] module.
4use hir::DisplayTarget;
5use ide_db::{famous_defs::FamousDefs, text_edit::TextEditBuilder};
6use syntax::ast::{self, AstNode};
7
8use crate::{
9    ClosureReturnTypeHints, InlayHint, InlayHintPosition, InlayHintsConfig, InlayKind,
10    inlay_hints::{closure_has_block_body, label_of_ty, ty_to_text_edit},
11};
12
13pub(super) fn hints(
14    acc: &mut Vec<InlayHint>,
15    famous_defs @ FamousDefs(sema, _): &FamousDefs<'_, '_>,
16    config: &InlayHintsConfig<'_>,
17    display_target: DisplayTarget,
18    closure: ast::ClosureExpr,
19) -> Option<()> {
20    if config.closure_return_type_hints == ClosureReturnTypeHints::Never {
21        return None;
22    }
23
24    let ret_type = closure.ret_type().map(|rt| (rt.thin_arrow_token(), rt.ty().is_some()));
25    let arrow = match ret_type {
26        Some((_, true)) => return None,
27        Some((arrow, _)) => arrow,
28        None => None,
29    };
30
31    let has_block_body = closure_has_block_body(&closure);
32    if !has_block_body && config.closure_return_type_hints == ClosureReturnTypeHints::WithBlock {
33        return None;
34    }
35
36    let param_list = closure.param_list()?;
37
38    let resolve_parent = Some(closure.syntax().text_range());
39    let descended_closure = sema.descend_node_into_attributes(closure.clone()).pop()?;
40    let ty = sema.type_of_expr(&ast::Expr::ClosureExpr(descended_closure.clone()))?.adjusted();
41    let callable = ty.as_callable(sema.db)?;
42    let ty = callable.return_type();
43    if arrow.is_none() && ty.is_unit() {
44        return None;
45    }
46
47    let mut label = label_of_ty(famous_defs, config, &ty, display_target)?;
48
49    if arrow.is_none() {
50        label.prepend_str(" -> ");
51    }
52
53    let offset_to_insert_ty =
54        arrow.as_ref().map_or_else(|| param_list.syntax().text_range(), |t| t.text_range()).end();
55
56    // Insert braces if necessary
57    let insert_braces = |builder: &mut TextEditBuilder| {
58        if !has_block_body && let Some(range) = closure.body().map(|b| b.syntax().text_range()) {
59            builder.insert(range.start(), "{ ".to_owned());
60            builder.insert(range.end(), " }".to_owned());
61        }
62    };
63
64    let text_edit = ty_to_text_edit(
65        sema,
66        config,
67        descended_closure.syntax(),
68        &ty,
69        offset_to_insert_ty,
70        &insert_braces,
71        if arrow.is_none() { " -> " } else { "" },
72    );
73
74    acc.push(InlayHint {
75        range: param_list.syntax().text_range(),
76        kind: InlayKind::Type,
77        label,
78        text_edit,
79        position: InlayHintPosition::After,
80        pad_left: false,
81        pad_right: false,
82        resolve_parent,
83    });
84    Some(())
85}
86
87#[cfg(test)]
88mod tests {
89    use crate::inlay_hints::tests::{DISABLED_CONFIG, check_with_config};
90
91    use super::*;
92
93    #[test]
94    fn return_type_hints_for_closure_without_block() {
95        check_with_config(
96            InlayHintsConfig {
97                closure_return_type_hints: ClosureReturnTypeHints::Always,
98                ..DISABLED_CONFIG
99            },
100            r#"
101fn main() {
102    let a = || { 0 };
103          //^^ -> i32
104    let b = || 0;
105          //^^ -> i32
106}"#,
107        );
108    }
109}