ide/inlay_hints/
generic_param.rs

1//! Implementation of inlay hints for generic parameters.
2use either::Either;
3use ide_db::{active_parameter::generic_def_for_node, famous_defs::FamousDefs};
4use syntax::{
5    AstNode,
6    ast::{self, AnyHasGenericArgs, HasGenericArgs, HasName},
7};
8
9use crate::{
10    InlayHint, InlayHintLabel, InlayHintsConfig, InlayKind,
11    inlay_hints::{GenericParameterHints, param_name},
12};
13
14use super::param_name::is_argument_similar_to_param_name;
15
16pub(crate) fn hints(
17    acc: &mut Vec<InlayHint>,
18    FamousDefs(sema, krate): &FamousDefs<'_, '_>,
19    config: &InlayHintsConfig<'_>,
20    node: AnyHasGenericArgs,
21) -> Option<()> {
22    let GenericParameterHints { type_hints, lifetime_hints, const_hints } =
23        config.generic_parameter_hints;
24    if !(type_hints || lifetime_hints || const_hints) {
25        return None;
26    }
27
28    let generic_arg_list = node.generic_arg_list()?;
29
30    let (generic_def, _, _, _) =
31        generic_def_for_node(sema, &generic_arg_list, &node.syntax().first_token()?)?;
32
33    let mut args = generic_arg_list.generic_args().peekable();
34    let start_with_lifetime = matches!(args.peek()?, ast::GenericArg::LifetimeArg(_));
35    let params = generic_def.params(sema.db).into_iter().filter(|p| {
36        if let hir::GenericParam::TypeParam(it) = p
37            && it.is_implicit(sema.db)
38        {
39            return false;
40        }
41        if !start_with_lifetime {
42            return !matches!(p, hir::GenericParam::LifetimeParam(_));
43        }
44        true
45    });
46
47    let hints = params.zip(args).filter_map(|(param, arg)| {
48        if matches!(arg, ast::GenericArg::AssocTypeArg(_)) {
49            return None;
50        }
51
52        let allowed = match (param, &arg) {
53            (hir::GenericParam::TypeParam(_), ast::GenericArg::TypeArg(_)) => type_hints,
54            (hir::GenericParam::ConstParam(_), ast::GenericArg::ConstArg(_)) => const_hints,
55            (hir::GenericParam::LifetimeParam(_), ast::GenericArg::LifetimeArg(_)) => {
56                lifetime_hints
57            }
58            _ => false,
59        };
60        if !allowed {
61            return None;
62        }
63
64        let param_name = param.name(sema.db);
65
66        let should_hide = {
67            let param_name = param_name.as_str();
68            get_segment_representation(&arg).map_or(false, |seg| match seg {
69                Either::Left(Either::Left(argument)) => {
70                    is_argument_similar_to_param_name(&argument, param_name)
71                }
72                Either::Left(Either::Right(argument)) => argument
73                    .segment()
74                    .and_then(|it| it.name_ref())
75                    .is_some_and(|it| it.text().eq_ignore_ascii_case(param_name)),
76                Either::Right(lifetime) => lifetime.text().eq_ignore_ascii_case(param_name),
77            })
78        };
79
80        if should_hide {
81            return None;
82        }
83
84        let range = sema.original_range_opt(arg.syntax())?.range;
85
86        let colon = if config.render_colons { ":" } else { "" };
87        let label = InlayHintLabel::simple(
88            format!("{}{colon}", param_name.display(sema.db, krate.edition(sema.db))),
89            None,
90            config.lazy_location_opt(|| {
91                let source_syntax = match param {
92                    hir::GenericParam::TypeParam(it) => {
93                        sema.source(it.merge()).map(|it| it.value.syntax().clone())
94                    }
95                    hir::GenericParam::ConstParam(it) => {
96                        let syntax = sema.source(it.merge())?.value.syntax().clone();
97                        let const_param = ast::ConstParam::cast(syntax)?;
98                        const_param.name().map(|it| it.syntax().clone())
99                    }
100                    hir::GenericParam::LifetimeParam(it) => {
101                        sema.source(it).map(|it| it.value.syntax().clone())
102                    }
103                };
104                let linked_location = source_syntax.and_then(|it| sema.original_range_opt(&it));
105                linked_location.map(|frange| ide_db::FileRange {
106                    file_id: frange.file_id.file_id(sema.db),
107                    range: frange.range,
108                })
109            }),
110        );
111
112        Some(InlayHint {
113            range,
114            position: crate::InlayHintPosition::Before,
115            pad_left: false,
116            pad_right: true,
117            kind: InlayKind::GenericParameter,
118            label,
119            text_edit: None,
120            resolve_parent: Some(node.syntax().text_range()),
121        })
122    });
123
124    acc.extend(hints);
125    Some(())
126}
127
128fn get_segment_representation(
129    arg: &ast::GenericArg,
130) -> Option<Either<Either<Vec<ast::NameRef>, ast::Path>, ast::Lifetime>> {
131    return match arg {
132        ast::GenericArg::AssocTypeArg(_) => None,
133        ast::GenericArg::ConstArg(const_arg) => {
134            param_name::get_segment_representation(&const_arg.expr()?).map(Either::Left)
135        }
136        ast::GenericArg::LifetimeArg(lifetime_arg) => {
137            let lifetime = lifetime_arg.lifetime()?;
138            Some(Either::Right(lifetime))
139        }
140        ast::GenericArg::TypeArg(type_arg) => {
141            let ty = type_arg.ty()?;
142            type_path(&ty).map(Either::Right).map(Either::Left)
143        }
144    };
145
146    fn type_path(ty: &ast::Type) -> Option<ast::Path> {
147        match ty {
148            ast::Type::ArrayType(it) => type_path(&it.ty()?),
149            ast::Type::ForType(it) => type_path(&it.ty()?),
150            ast::Type::ParenType(it) => type_path(&it.ty()?),
151            ast::Type::PathType(path_type) => path_type.path(),
152            ast::Type::PtrType(it) => type_path(&it.ty()?),
153            ast::Type::RefType(it) => type_path(&it.ty()?),
154            ast::Type::SliceType(it) => type_path(&it.ty()?),
155            ast::Type::MacroType(macro_type) => macro_type.macro_call()?.path(),
156            _ => None,
157        }
158    }
159}
160
161#[cfg(test)]
162mod tests {
163    use crate::{
164        InlayHintsConfig,
165        inlay_hints::{
166            GenericParameterHints,
167            tests::{DISABLED_CONFIG, check_with_config},
168        },
169    };
170
171    #[track_caller]
172    fn generic_param_name_hints_always(#[rust_analyzer::rust_fixture] ra_fixture: &str) {
173        check_with_config(
174            InlayHintsConfig {
175                generic_parameter_hints: GenericParameterHints {
176                    type_hints: true,
177                    lifetime_hints: true,
178                    const_hints: true,
179                },
180                ..DISABLED_CONFIG
181            },
182            ra_fixture,
183        );
184    }
185
186    #[track_caller]
187    fn generic_param_name_hints_const_only(#[rust_analyzer::rust_fixture] ra_fixture: &str) {
188        check_with_config(
189            InlayHintsConfig {
190                generic_parameter_hints: GenericParameterHints {
191                    type_hints: false,
192                    lifetime_hints: false,
193                    const_hints: true,
194                },
195                ..DISABLED_CONFIG
196            },
197            ra_fixture,
198        );
199    }
200
201    #[test]
202    fn type_only() {
203        generic_param_name_hints_always(
204            r#"
205struct A<X, Y> {
206    x: X,
207    y: Y,
208}
209
210fn foo(a: A<usize,  u32>) {}
211          //^^^^^ X ^^^ Y
212"#,
213        )
214    }
215
216    #[test]
217    fn lifetime_and_type() {
218        generic_param_name_hints_always(
219            r#"
220struct A<'a, X> {
221    x: &'a X
222}
223
224fn foo<'b>(a: A<'b,  u32>) {}
225              //^^ 'a^^^ X
226"#,
227        )
228    }
229
230    #[test]
231    fn omit_lifetime() {
232        generic_param_name_hints_always(
233            r#"
234struct A<'a, X> {
235    x: &'a X
236}
237
238fn foo() {
239    let x: i32 = 1;
240    let a: A<i32> = A { x: &x };
241          // ^^^ X
242}
243"#,
244        )
245    }
246
247    #[test]
248    fn const_only() {
249        generic_param_name_hints_always(
250            r#"
251struct A<const X: usize, const Y: usize> {};
252
253fn foo(a: A<12, 2>) {}
254          //^^ X^ Y
255"#,
256        )
257    }
258
259    #[test]
260    fn lifetime_and_type_and_const() {
261        generic_param_name_hints_always(
262            r#"
263struct A<'a, X, const LEN: usize> {
264    x: &'a [X; LEN],
265}
266
267fn foo<'b>(a: A<
268    'b,
269 // ^^ 'a
270    u32,
271 // ^^^ X
272    3
273 // ^ LEN
274    >) {}
275"#,
276        )
277    }
278
279    #[test]
280    fn const_only_config() {
281        generic_param_name_hints_const_only(
282            r#"
283struct A<'a, X, const LEN: usize> {
284    x: &'a [X; LEN],
285}
286
287fn foo<'b>(a: A<
288    'b,
289    u32,
290    3
291 // ^ LEN
292    >) {}
293"#,
294        )
295    }
296
297    #[test]
298    fn assoc_type() {
299        generic_param_name_hints_always(
300            r#"
301trait Trait<T> {
302    type Assoc1;
303    type Assoc2;
304}
305
306fn foo() -> impl Trait<i32, Assoc1 = u32, Assoc2 = u32> {}
307                    // ^^^ T
308"#,
309        )
310    }
311
312    #[test]
313    fn hide_similar() {
314        generic_param_name_hints_always(
315            r#"
316struct A<'a, X, const N: usize> {
317    x: &'a [X; N],
318}
319
320const N: usize = 3;
321
322mod m {
323    type X = u32;
324}
325
326fn foo<'a>(a: A<'a, m::X, N>) {}
327"#,
328        )
329    }
330
331    #[test]
332    fn mismatching_args() {
333        generic_param_name_hints_always(
334            r#"
335struct A<X, const N: usize> {
336    x: [X; N]
337}
338
339type InvalidType = A<3, i32>;
340"#,
341        )
342    }
343}