1use 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}