@@ -5,7 +5,7 @@ use ra_ide_db::RootDatabase;
5
5
use ra_prof:: profile;
6
6
use ra_syntax:: {
7
7
ast:: { self , ArgListOwner , AstNode , TypeAscriptionOwner } ,
8
- match_ast, SmolStr , TextRange ,
8
+ match_ast, SmolStr , TextRange , NodeOrToken , SyntaxKind , Direction
9
9
} ;
10
10
11
11
use crate :: { FileId , FunctionSignature } ;
@@ -14,19 +14,26 @@ use crate::{FileId, FunctionSignature};
14
14
pub struct InlayHintsOptions {
15
15
pub type_hints : bool ,
16
16
pub parameter_hints : bool ,
17
+ pub chaining_hints : bool ,
17
18
pub max_length : Option < usize > ,
18
19
}
19
20
20
21
impl Default for InlayHintsOptions {
21
22
fn default ( ) -> Self {
22
- Self { type_hints : true , parameter_hints : true , max_length : None }
23
+ Self {
24
+ type_hints : true ,
25
+ parameter_hints : true ,
26
+ chaining_hints : true ,
27
+ max_length : None
28
+ }
23
29
}
24
30
}
25
31
26
32
#[ derive( Clone , Debug , PartialEq , Eq ) ]
27
33
pub enum InlayKind {
28
34
TypeHint ,
29
35
ParameterHint ,
36
+ ChainingHint ,
30
37
}
31
38
32
39
#[ derive( Debug ) ]
@@ -47,6 +54,10 @@ pub(crate) fn inlay_hints(
47
54
48
55
let mut res = Vec :: new ( ) ;
49
56
for node in file. syntax ( ) . descendants ( ) {
57
+ if let Some ( expr) = ast:: Expr :: cast ( node. clone ( ) ) {
58
+ get_chaining_hints ( & mut res, & sema, options, expr) ;
59
+ }
60
+
50
61
match_ast ! {
51
62
match node {
52
63
ast:: CallExpr ( it) => { get_param_name_hints( & mut res, & sema, options, ast:: Expr :: from( it) ) ; } ,
@@ -222,13 +233,89 @@ fn get_fn_signature(sema: &Semantics<RootDatabase>, expr: &ast::Expr) -> Option<
222
233
}
223
234
}
224
235
236
+ fn get_chaining_hints (
237
+ acc : & mut Vec < InlayHint > ,
238
+ sema : & Semantics < RootDatabase > ,
239
+ options : & InlayHintsOptions ,
240
+ expr : ast:: Expr ,
241
+ ) -> Option < ( ) > {
242
+ if !options. chaining_hints {
243
+ return None ;
244
+ }
245
+
246
+ let ty = sema. type_of_expr ( & expr) ?;
247
+ let label = ty. display_truncated ( sema. db , options. max_length ) . to_string ( ) ;
248
+ if ty. is_unknown ( ) {
249
+ return None ;
250
+ }
251
+
252
+ let mut tokens = expr. syntax ( )
253
+ . siblings_with_tokens ( Direction :: Next )
254
+ . filter_map ( NodeOrToken :: into_token)
255
+ . filter ( |t| match t. kind ( ) {
256
+ SyntaxKind :: WHITESPACE if !t. text ( ) . contains ( '\n' ) => false ,
257
+ SyntaxKind :: COMMENT => false ,
258
+ _ => true ,
259
+ } ) ;
260
+
261
+ // Chaining can be defined as an expression whose next sibling tokens are newline and dot
262
+ // Ignoring extra whitespace and comments
263
+ let next = tokens. next ( ) ?. kind ( ) ;
264
+ let next_next = tokens. next ( ) ?. kind ( ) ;
265
+ if next == SyntaxKind :: WHITESPACE && next_next == SyntaxKind :: DOT {
266
+ acc. push ( InlayHint {
267
+ range : expr. syntax ( ) . text_range ( ) ,
268
+ kind : InlayKind :: ChainingHint ,
269
+ label : label. into ( ) ,
270
+ } ) ;
271
+ }
272
+ Some ( ( ) )
273
+ }
274
+
225
275
#[ cfg( test) ]
226
276
mod tests {
227
277
use crate :: inlay_hints:: InlayHintsOptions ;
228
278
use insta:: assert_debug_snapshot;
229
279
230
280
use crate :: mock_analysis:: single_file;
231
281
282
+ #[ test]
283
+ fn generic_chaining_hints ( ) {
284
+ let ( analysis, file_id) = single_file (
285
+ r#"
286
+ struct A<T>(T);
287
+ struct B<T>(T);
288
+ struct C<T>(T);
289
+ struct X<T,R>(T, R);
290
+
291
+ impl<T> A<T> {
292
+ fn new(t: T) -> Self { A(t) }
293
+ fn into_b(self) -> B<T> { B(self.0) }
294
+ }
295
+ impl<T> B<T> {
296
+ fn into_c(self) -> C<T> { C(self.0) }
297
+ }
298
+ fn test() {
299
+ let c = A::new(X(42, true))
300
+ .into_b() // All the from A -> B -> C
301
+ .into_c();
302
+ }"# ,
303
+ ) ;
304
+ assert_debug_snapshot ! ( analysis. inlay_hints( file_id, & InlayHintsOptions { parameter_hints: false , type_hints: false , chaining_hints: true , max_length: None } ) . unwrap( ) , @r###"
305
+ [
306
+ InlayHint {
307
+ range: [416; 465),
308
+ kind: ChainingHint,
309
+ label: "B<X<i32, bool>>",
310
+ },
311
+ InlayHint {
312
+ range: [416; 435),
313
+ kind: ChainingHint,
314
+ label: "A<X<i32, bool>>",
315
+ },
316
+ ]"### ) ;
317
+ }
318
+
232
319
#[ test]
233
320
fn param_hints_only ( ) {
234
321
let ( analysis, file_id) = single_file (
@@ -238,7 +325,7 @@ mod tests {
238
325
let _x = foo(4, 4);
239
326
}"# ,
240
327
) ;
241
- assert_debug_snapshot ! ( analysis. inlay_hints( file_id, & InlayHintsOptions { parameter_hints: true , type_hints: false , max_length: None } ) . unwrap( ) , @r###"
328
+ assert_debug_snapshot ! ( analysis. inlay_hints( file_id, & InlayHintsOptions { parameter_hints: true , type_hints: false , chaining_hints : false , max_length: None } ) . unwrap( ) , @r###"
242
329
[
243
330
InlayHint {
244
331
range: [106; 107),
@@ -262,7 +349,7 @@ mod tests {
262
349
let _x = foo(4, 4);
263
350
}"# ,
264
351
) ;
265
- assert_debug_snapshot ! ( analysis. inlay_hints( file_id, & InlayHintsOptions { type_hints: false , parameter_hints: false , max_length: None } ) . unwrap( ) , @r###"[]"### ) ;
352
+ assert_debug_snapshot ! ( analysis. inlay_hints( file_id, & InlayHintsOptions { type_hints: false , parameter_hints: false , chaining_hints : false , max_length: None } ) . unwrap( ) , @r###"[]"### ) ;
266
353
}
267
354
268
355
#[ test]
@@ -274,7 +361,7 @@ mod tests {
274
361
let _x = foo(4, 4);
275
362
}"# ,
276
363
) ;
277
- assert_debug_snapshot ! ( analysis. inlay_hints( file_id, & InlayHintsOptions { type_hints: true , parameter_hints: false , max_length: None } ) . unwrap( ) , @r###"
364
+ assert_debug_snapshot ! ( analysis. inlay_hints( file_id, & InlayHintsOptions { type_hints: true , parameter_hints: false , chaining_hints : false , max_length: None } ) . unwrap( ) , @r###"
278
365
[
279
366
InlayHint {
280
367
range: [97; 99),
0 commit comments