ide/inlay_hints/
bounds.rs

1//! Implementation of trait bound hints.
2//!
3//! Currently this renders the implied `Sized` bound.
4use ide_db::{FileRange, famous_defs::FamousDefs};
5
6use syntax::ast::{self, AstNode, HasTypeBounds};
7
8use crate::{
9    InlayHint, InlayHintLabel, InlayHintLabelPart, InlayHintPosition, InlayHintsConfig, InlayKind,
10    TryToNav,
11};
12
13pub(super) fn hints(
14    acc: &mut Vec<InlayHint>,
15    famous_defs @ FamousDefs(sema, _): &FamousDefs<'_, '_>,
16    config: &InlayHintsConfig<'_>,
17    params: ast::GenericParamList,
18) -> Option<()> {
19    if !config.sized_bound {
20        return None;
21    }
22
23    let sized_trait = famous_defs.core_marker_Sized();
24
25    for param in params.type_or_const_params() {
26        match param {
27            ast::TypeOrConstParam::Type(type_param) => {
28                let c = type_param.colon_token().map(|it| it.text_range());
29                let has_bounds =
30                    type_param.type_bound_list().is_some_and(|it| it.bounds().next().is_some());
31                acc.push(InlayHint {
32                    range: c.unwrap_or_else(|| type_param.syntax().text_range()),
33                    kind: InlayKind::Type,
34                    label: {
35                        let mut hint = InlayHintLabel::default();
36                        if c.is_none() {
37                            hint.parts.push(InlayHintLabelPart {
38                                text: ": ".to_owned(),
39                                linked_location: None,
40                                tooltip: None,
41                            });
42                        }
43                        hint.parts.push(InlayHintLabelPart {
44                            text: "Sized".to_owned(),
45                            linked_location: sized_trait.and_then(|it| {
46                                config.lazy_location_opt(|| {
47                                    it.try_to_nav(sema).map(|it| {
48                                        let n = it.call_site();
49                                        FileRange {
50                                            file_id: n.file_id,
51                                            range: n.focus_or_full_range(),
52                                        }
53                                    })
54                                })
55                            }),
56                            tooltip: None,
57                        });
58                        if has_bounds {
59                            hint.parts.push(InlayHintLabelPart {
60                                text: " +".to_owned(),
61                                linked_location: None,
62                                tooltip: None,
63                            });
64                        }
65                        hint
66                    },
67                    text_edit: None,
68                    position: InlayHintPosition::After,
69                    pad_left: c.is_some(),
70                    pad_right: has_bounds,
71                    resolve_parent: Some(params.syntax().text_range()),
72                });
73            }
74            ast::TypeOrConstParam::Const(_) => (),
75        }
76    }
77
78    Some(())
79}
80
81#[cfg(test)]
82mod tests {
83    use expect_test::expect;
84
85    use crate::inlay_hints::InlayHintsConfig;
86
87    use crate::inlay_hints::tests::{DISABLED_CONFIG, check_expect, check_with_config};
88
89    #[track_caller]
90    fn check(#[rust_analyzer::rust_fixture] ra_fixture: &str) {
91        check_with_config(InlayHintsConfig { sized_bound: true, ..DISABLED_CONFIG }, ra_fixture);
92    }
93
94    #[test]
95    fn smoke() {
96        check(
97            r#"
98fn foo<T>() {}
99    // ^ : Sized
100"#,
101        );
102    }
103
104    #[test]
105    fn with_colon() {
106        check(
107            r#"
108fn foo<T:>() {}
109     // ^ Sized
110"#,
111        );
112    }
113
114    #[test]
115    fn with_colon_and_bounds() {
116        check(
117            r#"
118fn foo<T: 'static>() {}
119     // ^ Sized +
120"#,
121        );
122    }
123
124    #[test]
125    fn location_works() {
126        check_expect(
127            InlayHintsConfig { sized_bound: true, ..DISABLED_CONFIG },
128            r#"
129//- minicore: sized
130fn foo<T>() {}
131"#,
132            expect![[r#"
133                [
134                    (
135                        7..8,
136                        [
137                            ": ",
138                            InlayHintLabelPart {
139                                text: "Sized",
140                                linked_location: Some(
141                                    Computed(
142                                        FileRangeWrapper {
143                                            file_id: FileId(
144                                                1,
145                                            ),
146                                            range: 446..451,
147                                        },
148                                    ),
149                                ),
150                                tooltip: "",
151                            },
152                        ],
153                    ),
154                ]
155            "#]],
156        );
157    }
158}