@@ -4,7 +4,7 @@ use ide_db::{base_db::FileRange, famous_defs::FamousDefs, RootDatabase};
4
4
use itertools:: Itertools ;
5
5
use stdx:: to_lower_snake_case;
6
6
use syntax:: {
7
- ast:: { self , AstNode , HasArgList , HasName , UnaryOp } ,
7
+ ast:: { self , AstNode , HasArgList , HasGenericParams , HasName , UnaryOp } ,
8
8
match_ast, Direction , NodeOrToken , SmolStr , SyntaxKind , SyntaxNode , TextRange , T ,
9
9
} ;
10
10
@@ -17,6 +17,7 @@ pub struct InlayHintsConfig {
17
17
pub parameter_hints : bool ,
18
18
pub chaining_hints : bool ,
19
19
pub closure_return_type_hints : bool ,
20
+ pub lifetime_elision_hints : bool ,
20
21
pub hide_named_constructor_hints : bool ,
21
22
pub max_length : Option < usize > ,
22
23
}
@@ -27,6 +28,8 @@ pub enum InlayKind {
27
28
ParameterHint ,
28
29
ClosureReturnTypeHint ,
29
30
ChainingHint ,
31
+ GenericParamListHint ,
32
+ LifetimeHint ,
30
33
}
31
34
32
35
#[ derive( Debug ) ]
@@ -41,12 +44,16 @@ pub struct InlayHint {
41
44
// rust-analyzer shows additional information inline with the source code.
42
45
// Editors usually render this using read-only virtual text snippets interspersed with code.
43
46
//
44
- // rust-analyzer shows hints for
47
+ // rust-analyzer by default shows hints for
45
48
//
46
49
// * types of local variables
47
50
// * names of function arguments
48
51
// * types of chained expressions
49
52
//
53
+ // Optionally, one can enable additional hints for
54
+ //
55
+ // * return types of closure expressions with blocks
56
+ //
50
57
// **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.
51
58
// This approach has limitations, the caret movement and bracket highlighting near the edges of the hint may be weird:
52
59
// https://github.com/rust-analyzer/rust-analyzer/issues/1623[1], https://github.com/rust-analyzer/rust-analyzer/issues/3453[2].
@@ -108,11 +115,172 @@ fn hints(
108
115
}
109
116
_ => ( ) ,
110
117
}
111
- } else if let Some ( it) = ast:: IdentPat :: cast ( node) {
118
+ } else if let Some ( it) = ast:: IdentPat :: cast ( node. clone ( ) ) {
112
119
bind_pat_hints ( hints, sema, config, & it) ;
120
+ } else if let Some ( it) = ast:: Fn :: cast ( node) {
121
+ lifetime_hints ( hints, config, it) ;
113
122
}
114
123
}
115
124
125
+ fn lifetime_hints (
126
+ acc : & mut Vec < InlayHint > ,
127
+ config : & InlayHintsConfig ,
128
+ func : ast:: Fn ,
129
+ ) -> Option < ( ) > {
130
+ if !config. lifetime_elision_hints {
131
+ return None ;
132
+ }
133
+ let param_list = func. param_list ( ) ?;
134
+ let generic_param_list = func. generic_param_list ( ) ;
135
+ let ret_type = func. ret_type ( ) ;
136
+ let self_param = param_list. self_param ( ) ;
137
+
138
+ let mut allocated_lifetimes = vec ! [ ] ;
139
+ let mut gen_name = {
140
+ let mut iter = 'a' ..;
141
+ let allocated_lifetimes = & mut allocated_lifetimes;
142
+ move || {
143
+ if let Some ( it) = iter. next ( ) {
144
+ allocated_lifetimes. push ( SmolStr :: from_iter ( [ '\'' , it] ) )
145
+ }
146
+ }
147
+ } ;
148
+
149
+ let potential_lt_refs: Vec < _ > = param_list
150
+ . params ( )
151
+ . filter_map ( |it| {
152
+ let ty = it. ty ( ) ?;
153
+ // FIXME: look into the nested types here and check path types
154
+ match ty {
155
+ ast:: Type :: RefType ( r) => Some ( r) ,
156
+ _ => None ,
157
+ }
158
+ } )
159
+ . collect ( ) ;
160
+
161
+ enum LifetimeKind {
162
+ Elided ,
163
+ Named ( SmolStr ) ,
164
+ Static ,
165
+ }
166
+
167
+ let fetch_lt_text = |lt : Option < ast:: Lifetime > | match lt {
168
+ Some ( lt) => match lt. text ( ) . as_str ( ) {
169
+ "'_" => LifetimeKind :: Elided ,
170
+ "'static" => LifetimeKind :: Static ,
171
+ name => LifetimeKind :: Named ( name. into ( ) ) ,
172
+ } ,
173
+ None => LifetimeKind :: Elided ,
174
+ } ;
175
+ let is_elided = |lt : Option < ast:: Lifetime > | match lt {
176
+ Some ( lt) => matches ! ( lt. text( ) . as_str( ) , "'_" ) ,
177
+ None => true ,
178
+ } ;
179
+
180
+ // allocate names
181
+ if let Some ( self_param) = & self_param {
182
+ if is_elided ( self_param. lifetime ( ) ) {
183
+ gen_name ( ) ;
184
+ }
185
+ }
186
+ potential_lt_refs. iter ( ) . for_each ( |it| {
187
+ // FIXME: look into the nested types here and check path types
188
+ if is_elided ( it. lifetime ( ) ) {
189
+ gen_name ( ) ;
190
+ }
191
+ } ) ;
192
+
193
+ // fetch output lifetime if elision rule applies
194
+
195
+ let output = if let Some ( self_param) = & self_param {
196
+ match fetch_lt_text ( self_param. lifetime ( ) ) {
197
+ LifetimeKind :: Elided => allocated_lifetimes. get ( 0 ) . cloned ( ) ,
198
+ LifetimeKind :: Named ( name) => Some ( name) ,
199
+ LifetimeKind :: Static => None ,
200
+ }
201
+ } else {
202
+ match potential_lt_refs. as_slice ( ) {
203
+ [ r] => match fetch_lt_text ( r. lifetime ( ) ) {
204
+ LifetimeKind :: Elided => allocated_lifetimes. get ( 0 ) . cloned ( ) ,
205
+ LifetimeKind :: Named ( name) => Some ( name) ,
206
+ LifetimeKind :: Static => None ,
207
+ } ,
208
+ [ ..] => None ,
209
+ }
210
+ } ;
211
+
212
+ // apply hints
213
+
214
+ // apply output if required
215
+ match ( & output, ret_type) {
216
+ ( Some ( output_lt) , Some ( r) ) => {
217
+ if let Some ( ast:: Type :: RefType ( t) ) = r. ty ( ) {
218
+ if t. lifetime ( ) . is_none ( ) {
219
+ let amp = t. amp_token ( ) ?;
220
+ acc. push ( InlayHint {
221
+ range : amp. text_range ( ) ,
222
+ kind : InlayKind :: LifetimeHint ,
223
+ label : output_lt. clone ( ) ,
224
+ } ) ;
225
+ }
226
+ }
227
+ }
228
+ _ => ( ) ,
229
+ }
230
+
231
+ let mut idx = if let Some ( self_param) = & self_param {
232
+ if is_elided ( self_param. lifetime ( ) ) {
233
+ if let Some ( amp) = self_param. amp_token ( ) {
234
+ let lt = allocated_lifetimes[ 0 ] . clone ( ) ;
235
+ acc. push ( InlayHint {
236
+ range : amp. text_range ( ) ,
237
+ kind : InlayKind :: LifetimeHint ,
238
+ label : lt,
239
+ } ) ;
240
+ }
241
+ 1
242
+ } else {
243
+ 0
244
+ }
245
+ } else {
246
+ 0
247
+ } ;
248
+
249
+ for p in potential_lt_refs. iter ( ) {
250
+ if is_elided ( p. lifetime ( ) ) {
251
+ let t = p. amp_token ( ) ?;
252
+ let lt = allocated_lifetimes[ idx] . clone ( ) ;
253
+ acc. push ( InlayHint { range : t. text_range ( ) , kind : InlayKind :: LifetimeHint , label : lt } ) ;
254
+ idx += 1 ;
255
+ }
256
+ }
257
+
258
+ // generate generic param list things
259
+ match ( generic_param_list, allocated_lifetimes. as_slice ( ) ) {
260
+ ( _, [ ] ) => ( ) ,
261
+ ( Some ( gpl) , allocated_lifetimes) => {
262
+ let angle_tok = gpl. l_angle_token ( ) ?;
263
+ let is_empty = gpl. generic_params ( ) . next ( ) . is_none ( ) ;
264
+ acc. push ( InlayHint {
265
+ range : angle_tok. text_range ( ) ,
266
+ kind : InlayKind :: GenericParamListHint ,
267
+ label : format ! (
268
+ "{}{}" ,
269
+ allocated_lifetimes. iter( ) . format( ", " ) ,
270
+ if is_empty { "" } else { ", " }
271
+ )
272
+ . into ( ) ,
273
+ } ) ;
274
+ }
275
+ ( None , allocated_lifetimes) => acc. push ( InlayHint {
276
+ range : func. name ( ) ?. syntax ( ) . text_range ( ) ,
277
+ kind : InlayKind :: GenericParamListHint ,
278
+ label : format ! ( "<{}>" , allocated_lifetimes. iter( ) . format( ", " ) , ) . into ( ) ,
279
+ } ) ,
280
+ }
281
+ Some ( ( ) )
282
+ }
283
+
116
284
fn closure_ret_hints (
117
285
acc : & mut Vec < InlayHint > ,
118
286
sema : & Semantics < RootDatabase > ,
@@ -600,6 +768,7 @@ fn get_callable(
600
768
mod tests {
601
769
use expect_test:: { expect, Expect } ;
602
770
use ide_db:: base_db:: FileRange ;
771
+ use itertools:: Itertools ;
603
772
use syntax:: { TextRange , TextSize } ;
604
773
use test_utils:: extract_annotations;
605
774
@@ -610,6 +779,7 @@ mod tests {
610
779
type_hints : false ,
611
780
parameter_hints : false ,
612
781
chaining_hints : false ,
782
+ lifetime_elision_hints : false ,
613
783
hide_named_constructor_hints : false ,
614
784
closure_return_type_hints : false ,
615
785
max_length : None ,
@@ -619,6 +789,7 @@ mod tests {
619
789
parameter_hints : true ,
620
790
chaining_hints : true ,
621
791
closure_return_type_hints : true ,
792
+ lifetime_elision_hints : true ,
622
793
..DISABLED_CONFIG
623
794
} ;
624
795
@@ -648,10 +819,15 @@ mod tests {
648
819
#[ track_caller]
649
820
fn check_with_config ( config : InlayHintsConfig , ra_fixture : & str ) {
650
821
let ( analysis, file_id) = fixture:: file ( ra_fixture) ;
651
- let expected = extract_annotations ( & * analysis. file_text ( file_id) . unwrap ( ) ) ;
822
+ let mut expected = extract_annotations ( & * analysis. file_text ( file_id) . unwrap ( ) ) ;
652
823
let inlay_hints = analysis. inlay_hints ( & config, file_id, None ) . unwrap ( ) ;
653
- let actual =
654
- inlay_hints. into_iter ( ) . map ( |it| ( it. range , it. label . to_string ( ) ) ) . collect :: < Vec < _ > > ( ) ;
824
+ let actual = inlay_hints
825
+ . into_iter ( )
826
+ . map ( |it| ( it. range , it. label . to_string ( ) ) )
827
+ . sorted_by_key ( |( range, _) | range. start ( ) )
828
+ . collect :: < Vec < _ > > ( ) ;
829
+ expected. sort_by_key ( |( range, _) | range. start ( ) ) ;
830
+
655
831
assert_eq ! ( expected, actual, "\n Expected:\n {:#?}\n \n Actual:\n {:#?}" , expected, actual) ;
656
832
}
657
833
@@ -1825,4 +2001,42 @@ fn main() {
1825
2001
"# ] ] ,
1826
2002
) ;
1827
2003
}
2004
+
2005
+ #[ test]
2006
+ fn hints_sssin_attr_call ( ) {
2007
+ check (
2008
+ r#"
2009
+ fn empty() {}
2010
+
2011
+ fn no_gpl(a: &()) {}
2012
+ //^^^^^^<'a>
2013
+ // ^'a
2014
+ fn empty_gpl<>(a: &()) {}
2015
+ // ^'a ^'a
2016
+ fn partial<'b>(a: &(), b: &'b ()) {}
2017
+ // ^'a, $ ^'a
2018
+ fn partial<'b>(a: &'b (), b: &()) {}
2019
+ // ^'a, $ ^'a
2020
+
2021
+ fn single_ret(a: &()) -> &() {}
2022
+ // ^^^^^^^^^^<'a>
2023
+ // ^'a ^'a
2024
+ fn full_mul(a: &(), b: &()) {}
2025
+ // ^^^^^^^^<'a, 'b>
2026
+ // ^'a ^'b
2027
+
2028
+ fn foo<'c>(a: &'c ()) -> &() {}
2029
+ // ^'c
2030
+
2031
+ impl () {
2032
+ fn foo(&self) -> &() {}
2033
+ // ^^^<'a>
2034
+ // ^'a ^'a
2035
+ fn foo(&self, a: &()) -> &() {}
2036
+ // ^^^<'a, 'b>
2037
+ // ^'a ^'b ^'a$
2038
+ }
2039
+ "# ,
2040
+ ) ;
2041
+ }
1828
2042
}
0 commit comments