Skip to content

Commit 2faa2cb

Browse files
committed
Auto merge of #17544 - MikeWalrus:inlay-hint-generic-param-name, r=Veykril
feat: add inlay hints for generic parameters fixes #11091 By default, only hints for const generic parameters are shown, and this can be configured through `rust-analyzer.inlayHints.genericParameterHints.enable`. Probably needs more testing.
2 parents 5445aef + 7da4615 commit 2faa2cb

File tree

10 files changed

+430
-14
lines changed

10 files changed

+430
-14
lines changed

src/tools/rust-analyzer/crates/ide/src/inlay_hints.rs

+21-1
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ mod closure_captures;
2929
mod closure_ret;
3030
mod discriminant;
3131
mod fn_lifetime_fn;
32+
mod generic_param;
3233
mod implicit_drop;
3334
mod implicit_static;
3435
mod param_name;
@@ -40,6 +41,7 @@ pub struct InlayHintsConfig {
4041
pub type_hints: bool,
4142
pub discriminant_hints: DiscriminantHints,
4243
pub parameter_hints: bool,
44+
pub generic_parameter_hints: GenericParameterHints,
4345
pub chaining_hints: bool,
4446
pub adjustment_hints: AdjustmentHints,
4547
pub adjustment_hints_mode: AdjustmentHintsMode,
@@ -94,6 +96,13 @@ pub enum DiscriminantHints {
9496
Fieldless,
9597
}
9698

99+
#[derive(Clone, Debug, PartialEq, Eq)]
100+
pub struct GenericParameterHints {
101+
pub type_hints: bool,
102+
pub lifetime_hints: bool,
103+
pub const_hints: bool,
104+
}
105+
97106
#[derive(Clone, Debug, PartialEq, Eq)]
98107
pub enum LifetimeElisionHints {
99108
Always,
@@ -127,6 +136,7 @@ pub enum InlayKind {
127136
GenericParamList,
128137
Lifetime,
129138
Parameter,
139+
GenericParameter,
130140
Type,
131141
Drop,
132142
RangeExclusive,
@@ -447,13 +457,15 @@ fn ty_to_text_edit(
447457
//
448458
// * types of local variables
449459
// * names of function arguments
460+
// * names of const generic parameters
450461
// * types of chained expressions
451462
//
452463
// Optionally, one can enable additional hints for
453464
//
454465
// * return types of closure expressions
455466
// * elided lifetimes
456467
// * compiler inserted reborrows
468+
// * names of generic type and lifetime parameters
457469
//
458470
// Note: inlay hints for function argument names are heuristically omitted to reduce noise and will not appear if
459471
// any of the
@@ -543,6 +555,9 @@ fn hints(
543555
node: SyntaxNode,
544556
) {
545557
closing_brace::hints(hints, sema, config, file_id, node.clone());
558+
if let Some(any_has_generic_args) = ast::AnyHasGenericArgs::cast(node.clone()) {
559+
generic_param::hints(hints, sema, config, any_has_generic_args);
560+
}
546561
match_ast! {
547562
match node {
548563
ast::Expr(expr) => {
@@ -645,13 +660,18 @@ mod tests {
645660
use crate::DiscriminantHints;
646661
use crate::{fixture, inlay_hints::InlayHintsConfig, LifetimeElisionHints};
647662

648-
use super::{ClosureReturnTypeHints, InlayFieldsToResolve};
663+
use super::{ClosureReturnTypeHints, GenericParameterHints, InlayFieldsToResolve};
649664

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

0 commit comments

Comments
 (0)