@@ -4,7 +4,7 @@ use ide_db::{
4
4
base_db:: FileRange , famous_defs:: FamousDefs , syntax_helpers:: node_ext:: walk_ty, RootDatabase ,
5
5
} ;
6
6
use itertools:: Itertools ;
7
- use rustc_hash:: FxHashSet ;
7
+ use rustc_hash:: FxHashMap ;
8
8
use stdx:: to_lower_snake_case;
9
9
use syntax:: {
10
10
ast:: { self , AstNode , HasArgList , HasGenericParams , HasName , UnaryOp } ,
@@ -20,13 +20,19 @@ pub struct InlayHintsConfig {
20
20
pub parameter_hints : bool ,
21
21
pub chaining_hints : bool ,
22
22
pub closure_return_type_hints : bool ,
23
- // FIXME: ternary option here, on off non-noisy
24
- pub lifetime_elision_hints : bool ,
23
+ pub lifetime_elision_hints : LifetimeElisionHints ,
25
24
pub param_names_for_lifetime_elision_hints : bool ,
26
25
pub hide_named_constructor_hints : bool ,
27
26
pub max_length : Option < usize > ,
28
27
}
29
28
29
+ #[ derive( Clone , Debug , PartialEq , Eq ) ]
30
+ pub enum LifetimeElisionHints {
31
+ Always ,
32
+ SkipTrivial ,
33
+ Never ,
34
+ }
35
+
30
36
#[ derive( Clone , Debug , PartialEq , Eq ) ]
31
37
pub enum InlayKind {
32
38
TypeHint ,
@@ -58,6 +64,7 @@ pub struct InlayHint {
58
64
// Optionally, one can enable additional hints for
59
65
//
60
66
// * return types of closure expressions with blocks
67
+ // * elided lifetimes
61
68
//
62
69
// **Note:** VS Code does not have native support for inlay hints https://github.com/microsoft/vscode/issues/16221[yet] and the hints are implemented using decorations.
63
70
// This approach has limitations, the caret movement and bracket highlighting near the edges of the hint may be weird:
@@ -132,24 +139,24 @@ fn lifetime_hints(
132
139
config : & InlayHintsConfig ,
133
140
func : ast:: Fn ,
134
141
) -> Option < ( ) > {
135
- if ! config. lifetime_elision_hints {
142
+ if config. lifetime_elision_hints == LifetimeElisionHints :: Never {
136
143
return None ;
137
144
}
138
145
let param_list = func. param_list ( ) ?;
139
146
let generic_param_list = func. generic_param_list ( ) ;
140
147
let ret_type = func. ret_type ( ) ;
141
148
let self_param = param_list. self_param ( ) . filter ( |it| it. amp_token ( ) . is_some ( ) ) ;
142
149
143
- let used_names: FxHashSet < SmolStr > = generic_param_list
150
+ let mut used_names: FxHashMap < SmolStr , usize > = generic_param_list
144
151
. iter ( )
145
- . filter ( |_| ! config. param_names_for_lifetime_elision_hints )
152
+ . filter ( |_| config. param_names_for_lifetime_elision_hints )
146
153
. flat_map ( |gpl| gpl. lifetime_params ( ) )
147
154
. filter_map ( |param| param. lifetime ( ) )
148
- . map ( |lt| SmolStr :: from ( lt. text ( ) . as_str ( ) ) )
155
+ . filter_map ( |lt| Some ( ( SmolStr :: from ( lt. text ( ) . as_str ( ) . get ( 1 .. ) ? ) , 0 ) ) )
149
156
. collect ( ) ;
150
157
151
158
let mut allocated_lifetimes = vec ! [ ] ;
152
- let mut gen_name = {
159
+ let mut gen_idx_name = {
153
160
let mut gen = ( 0u8 ..) . map ( |idx| match idx {
154
161
idx if idx < 10 => SmolStr :: from_iter ( [ '\'' , ( idx + 48 ) as char ] ) ,
155
162
idx => format ! ( "'{idx}" ) . into ( ) ,
@@ -158,19 +165,27 @@ fn lifetime_hints(
158
165
} ;
159
166
160
167
let mut potential_lt_refs: Vec < _ > = vec ! [ ] ;
161
- param_list. params ( ) . filter_map ( |it| Some ( ( it. pat ( ) , it. ty ( ) ?) ) ) . for_each ( |( pat, ty) | {
162
- // FIXME: check path types
163
- walk_ty ( & ty, & mut |ty| match ty {
164
- ast:: Type :: RefType ( r) => potential_lt_refs. push ( (
165
- pat. as_ref ( ) . and_then ( |it| match it {
166
- ast:: Pat :: IdentPat ( p) => p. name ( ) ,
167
- _ => None ,
168
- } ) ,
169
- r,
170
- ) ) ,
171
- _ => ( ) ,
168
+ param_list
169
+ . params ( )
170
+ . filter_map ( |it| {
171
+ Some ( (
172
+ config. param_names_for_lifetime_elision_hints . then ( || it. pat ( ) ) . flatten ( ) ,
173
+ it. ty ( ) ?,
174
+ ) )
172
175
} )
173
- } ) ;
176
+ . for_each ( |( pat, ty) | {
177
+ // FIXME: check path types
178
+ walk_ty ( & ty, & mut |ty| match ty {
179
+ ast:: Type :: RefType ( r) => potential_lt_refs. push ( (
180
+ pat. as_ref ( ) . and_then ( |it| match it {
181
+ ast:: Pat :: IdentPat ( p) => p. name ( ) ,
182
+ _ => None ,
183
+ } ) ,
184
+ r,
185
+ ) ) ,
186
+ _ => ( ) ,
187
+ } )
188
+ } ) ;
174
189
175
190
enum LifetimeKind {
176
191
Elided ,
@@ -195,25 +210,28 @@ fn lifetime_hints(
195
210
if let Some ( self_param) = & self_param {
196
211
if is_elided ( self_param. lifetime ( ) ) {
197
212
allocated_lifetimes. push ( if config. param_names_for_lifetime_elision_hints {
213
+ // self can't be used as a lifetime, so no need to check for collisions
198
214
"'self" . into ( )
199
215
} else {
200
- gen_name ( )
216
+ gen_idx_name ( )
201
217
} ) ;
202
218
}
203
219
}
204
220
potential_lt_refs. iter ( ) . for_each ( |( name, it) | {
205
221
if is_elided ( it. lifetime ( ) ) {
206
- allocated_lifetimes. push (
207
- name. as_ref ( )
208
- . filter ( |it| {
209
- config. param_names_for_lifetime_elision_hints
210
- && !used_names. contains ( it. text ( ) . as_str ( ) )
211
- } )
212
- . map_or_else (
213
- || gen_name ( ) ,
214
- |it| SmolStr :: from_iter ( [ "\' " , it. text ( ) . as_str ( ) ] ) ,
215
- ) ,
216
- ) ;
222
+ let name = match name {
223
+ Some ( it) => {
224
+ if let Some ( c) = used_names. get_mut ( it. text ( ) . as_str ( ) ) {
225
+ * c += 1 ;
226
+ SmolStr :: from ( format ! ( "'{text}{c}" , text = it. text( ) . as_str( ) ) )
227
+ } else {
228
+ used_names. insert ( it. text ( ) . as_str ( ) . into ( ) , 0 ) ;
229
+ SmolStr :: from_iter ( [ "\' " , it. text ( ) . as_str ( ) ] )
230
+ }
231
+ }
232
+ _ => gen_idx_name ( ) ,
233
+ } ;
234
+ allocated_lifetimes. push ( name) ;
217
235
}
218
236
} ) ;
219
237
@@ -236,8 +254,21 @@ fn lifetime_hints(
236
254
}
237
255
} ;
238
256
239
- // apply hints
257
+ if allocated_lifetimes. is_empty ( ) && output. is_none ( ) {
258
+ return None ;
259
+ }
240
260
261
+ let skip_due_trivial_single = config. lifetime_elision_hints
262
+ == LifetimeElisionHints :: SkipTrivial
263
+ && ( allocated_lifetimes. len ( ) == 1 )
264
+ && generic_param_list. as_ref ( ) . map_or ( true , |it| it. lifetime_params ( ) . next ( ) . is_none ( ) ) ;
265
+
266
+ if skip_due_trivial_single {
267
+ cov_mark:: hit!( lifetime_hints_single) ;
268
+ return None ;
269
+ }
270
+
271
+ // apply hints
241
272
// apply output if required
242
273
match ( & output, ret_type) {
243
274
( Some ( output_lt) , Some ( r) ) => {
@@ -800,14 +831,14 @@ mod tests {
800
831
use syntax:: { TextRange , TextSize } ;
801
832
use test_utils:: extract_annotations;
802
833
803
- use crate :: { fixture, inlay_hints:: InlayHintsConfig } ;
834
+ use crate :: { fixture, inlay_hints:: InlayHintsConfig , LifetimeElisionHints } ;
804
835
805
836
const DISABLED_CONFIG : InlayHintsConfig = InlayHintsConfig {
806
837
render_colons : false ,
807
838
type_hints : false ,
808
839
parameter_hints : false ,
809
840
chaining_hints : false ,
810
- lifetime_elision_hints : false ,
841
+ lifetime_elision_hints : LifetimeElisionHints :: Never ,
811
842
hide_named_constructor_hints : false ,
812
843
closure_return_type_hints : false ,
813
844
param_names_for_lifetime_elision_hints : false ,
@@ -818,7 +849,7 @@ mod tests {
818
849
parameter_hints : true ,
819
850
chaining_hints : true ,
820
851
closure_return_type_hints : true ,
821
- lifetime_elision_hints : true ,
852
+ lifetime_elision_hints : LifetimeElisionHints :: Always ,
822
853
..DISABLED_CONFIG
823
854
} ;
824
855
@@ -2037,6 +2068,47 @@ impl () {
2037
2068
// ^^^<'0, '1>
2038
2069
// ^'0 ^'1 ^'0
2039
2070
}
2071
+ "# ,
2072
+ ) ;
2073
+ }
2074
+
2075
+ #[ test]
2076
+ fn hints_lifetimes_named ( ) {
2077
+ check_with_config (
2078
+ InlayHintsConfig { param_names_for_lifetime_elision_hints : true , ..TEST_CONFIG } ,
2079
+ r#"
2080
+ fn nested_in<'named>(named: & &X< &()>) {}
2081
+ // ^'named1, 'named2, 'named3, $
2082
+ //^'named1 ^'named2 ^'named3
2083
+ "# ,
2084
+ ) ;
2085
+ }
2086
+
2087
+ #[ test]
2088
+ fn hints_lifetimes_skingle_skip ( ) {
2089
+ cov_mark:: check!( lifetime_hints_single) ;
2090
+ check_with_config (
2091
+ InlayHintsConfig {
2092
+ lifetime_elision_hints : LifetimeElisionHints :: SkipTrivial ,
2093
+ ..TEST_CONFIG
2094
+ } ,
2095
+ r#"
2096
+ fn single(a: &()) -> &() {}
2097
+
2098
+ fn double(a: &(), b: &()) {}
2099
+ // ^^^^^^<'0, '1>
2100
+ // ^'0 ^'1
2101
+ fn partial<'a>(a: &'a (), b: &()) {}
2102
+ //^'0, $ ^'0
2103
+ fn partial2<'a>(a: &'a ()) -> &() {}
2104
+ //^'a
2105
+
2106
+ impl () {
2107
+ fn foo(&self) -> &() {}
2108
+ fn foo(&self, a: &()) -> &() {}
2109
+ // ^^^<'0, '1>
2110
+ // ^'0 ^'1 ^'0
2111
+ }
2040
2112
"# ,
2041
2113
) ;
2042
2114
}
0 commit comments