]> git.lizzy.rs Git - rust.git/blob - crates/ide/src/inlay_hints/chaining.rs
Auto merge of #13764 - WaffleLapkin:badassexprs, r=Veykril
[rust.git] / crates / ide / src / inlay_hints / chaining.rs
1 //! Implementation of "chaining" inlay hints.
2 use hir::{HirDisplay, Semantics};
3 use ide_db::{famous_defs::FamousDefs, RootDatabase};
4 use syntax::{
5     ast::{self, AstNode},
6     Direction, NodeOrToken, SyntaxKind, T,
7 };
8
9 use crate::{
10     inlay_hints::hint_iterator, FileId, InlayHint, InlayHintsConfig, InlayKind, InlayTooltip,
11 };
12
13 pub(super) fn hints(
14     acc: &mut Vec<InlayHint>,
15     sema: &Semantics<'_, RootDatabase>,
16     famous_defs: &FamousDefs<'_, '_>,
17     config: &InlayHintsConfig,
18     file_id: FileId,
19     expr: &ast::Expr,
20 ) -> Option<()> {
21     if !config.chaining_hints {
22         return None;
23     }
24
25     if matches!(expr, ast::Expr::RecordExpr(_)) {
26         return None;
27     }
28
29     let descended = sema.descend_node_into_attributes(expr.clone()).pop();
30     let desc_expr = descended.as_ref().unwrap_or(expr);
31
32     let mut tokens = expr
33         .syntax()
34         .siblings_with_tokens(Direction::Next)
35         .filter_map(NodeOrToken::into_token)
36         .filter(|t| match t.kind() {
37             SyntaxKind::WHITESPACE if !t.text().contains('\n') => false,
38             SyntaxKind::COMMENT => false,
39             _ => true,
40         });
41
42     // Chaining can be defined as an expression whose next sibling tokens are newline and dot
43     // Ignoring extra whitespace and comments
44     let next = tokens.next()?.kind();
45     if next == SyntaxKind::WHITESPACE {
46         let mut next_next = tokens.next()?.kind();
47         while next_next == SyntaxKind::WHITESPACE {
48             next_next = tokens.next()?.kind();
49         }
50         if next_next == T![.] {
51             let ty = sema.type_of_expr(desc_expr)?.original;
52             if ty.is_unknown() {
53                 return None;
54             }
55             if matches!(expr, ast::Expr::PathExpr(_)) {
56                 if let Some(hir::Adt::Struct(st)) = ty.as_adt() {
57                     if st.fields(sema.db).is_empty() {
58                         return None;
59                     }
60                 }
61             }
62             acc.push(InlayHint {
63                 range: expr.syntax().text_range(),
64                 kind: InlayKind::ChainingHint,
65                 label: hint_iterator(sema, &famous_defs, config, &ty)
66                     .unwrap_or_else(|| ty.display_truncated(sema.db, config.max_length).to_string())
67                     .into(),
68                 tooltip: Some(InlayTooltip::HoverRanged(file_id, expr.syntax().text_range())),
69             });
70         }
71     }
72     Some(())
73 }
74
75 #[cfg(test)]
76 mod tests {
77     use expect_test::expect;
78
79     use crate::{
80         inlay_hints::tests::{check_expect, check_with_config, DISABLED_CONFIG, TEST_CONFIG},
81         InlayHintsConfig,
82     };
83
84     #[track_caller]
85     fn check_chains(ra_fixture: &str) {
86         check_with_config(InlayHintsConfig { chaining_hints: true, ..DISABLED_CONFIG }, ra_fixture);
87     }
88
89     #[test]
90     fn chaining_hints_ignore_comments() {
91         check_expect(
92             InlayHintsConfig { type_hints: false, chaining_hints: true, ..DISABLED_CONFIG },
93             r#"
94 struct A(B);
95 impl A { fn into_b(self) -> B { self.0 } }
96 struct B(C);
97 impl B { fn into_c(self) -> C { self.0 } }
98 struct C;
99
100 fn main() {
101     let c = A(B(C))
102         .into_b() // This is a comment
103         // This is another comment
104         .into_c();
105 }
106 "#,
107             expect![[r#"
108                 [
109                     InlayHint {
110                         range: 147..172,
111                         kind: ChainingHint,
112                         label: [
113                             "B",
114                         ],
115                         tooltip: Some(
116                             HoverRanged(
117                                 FileId(
118                                     0,
119                                 ),
120                                 147..172,
121                             ),
122                         ),
123                     },
124                     InlayHint {
125                         range: 147..154,
126                         kind: ChainingHint,
127                         label: [
128                             "A",
129                         ],
130                         tooltip: Some(
131                             HoverRanged(
132                                 FileId(
133                                     0,
134                                 ),
135                                 147..154,
136                             ),
137                         ),
138                     },
139                 ]
140             "#]],
141         );
142     }
143
144     #[test]
145     fn chaining_hints_without_newlines() {
146         check_chains(
147             r#"
148 struct A(B);
149 impl A { fn into_b(self) -> B { self.0 } }
150 struct B(C);
151 impl B { fn into_c(self) -> C { self.0 } }
152 struct C;
153
154 fn main() {
155     let c = A(B(C)).into_b().into_c();
156 }"#,
157         );
158     }
159
160     #[test]
161     fn struct_access_chaining_hints() {
162         check_expect(
163             InlayHintsConfig { chaining_hints: true, ..DISABLED_CONFIG },
164             r#"
165 struct A { pub b: B }
166 struct B { pub c: C }
167 struct C(pub bool);
168 struct D;
169
170 impl D {
171     fn foo(&self) -> i32 { 42 }
172 }
173
174 fn main() {
175     let x = A { b: B { c: C(true) } }
176         .b
177         .c
178         .0;
179     let x = D
180         .foo();
181 }"#,
182             expect![[r#"
183                 [
184                     InlayHint {
185                         range: 143..190,
186                         kind: ChainingHint,
187                         label: [
188                             "C",
189                         ],
190                         tooltip: Some(
191                             HoverRanged(
192                                 FileId(
193                                     0,
194                                 ),
195                                 143..190,
196                             ),
197                         ),
198                     },
199                     InlayHint {
200                         range: 143..179,
201                         kind: ChainingHint,
202                         label: [
203                             "B",
204                         ],
205                         tooltip: Some(
206                             HoverRanged(
207                                 FileId(
208                                     0,
209                                 ),
210                                 143..179,
211                             ),
212                         ),
213                     },
214                 ]
215             "#]],
216         );
217     }
218
219     #[test]
220     fn generic_chaining_hints() {
221         check_expect(
222             InlayHintsConfig { chaining_hints: true, ..DISABLED_CONFIG },
223             r#"
224 struct A<T>(T);
225 struct B<T>(T);
226 struct C<T>(T);
227 struct X<T,R>(T, R);
228
229 impl<T> A<T> {
230     fn new(t: T) -> Self { A(t) }
231     fn into_b(self) -> B<T> { B(self.0) }
232 }
233 impl<T> B<T> {
234     fn into_c(self) -> C<T> { C(self.0) }
235 }
236 fn main() {
237     let c = A::new(X(42, true))
238         .into_b()
239         .into_c();
240 }
241 "#,
242             expect![[r#"
243                 [
244                     InlayHint {
245                         range: 246..283,
246                         kind: ChainingHint,
247                         label: [
248                             "B<X<i32, bool>>",
249                         ],
250                         tooltip: Some(
251                             HoverRanged(
252                                 FileId(
253                                     0,
254                                 ),
255                                 246..283,
256                             ),
257                         ),
258                     },
259                     InlayHint {
260                         range: 246..265,
261                         kind: ChainingHint,
262                         label: [
263                             "A<X<i32, bool>>",
264                         ],
265                         tooltip: Some(
266                             HoverRanged(
267                                 FileId(
268                                     0,
269                                 ),
270                                 246..265,
271                             ),
272                         ),
273                     },
274                 ]
275             "#]],
276         );
277     }
278
279     #[test]
280     fn shorten_iterator_chaining_hints() {
281         check_expect(
282             InlayHintsConfig { chaining_hints: true, ..DISABLED_CONFIG },
283             r#"
284 //- minicore: iterators
285 use core::iter;
286
287 struct MyIter;
288
289 impl Iterator for MyIter {
290     type Item = ();
291     fn next(&mut self) -> Option<Self::Item> {
292         None
293     }
294 }
295
296 fn main() {
297     let _x = MyIter.by_ref()
298         .take(5)
299         .by_ref()
300         .take(5)
301         .by_ref();
302 }
303 "#,
304             expect![[r#"
305                 [
306                     InlayHint {
307                         range: 174..241,
308                         kind: ChainingHint,
309                         label: [
310                             "impl Iterator<Item = ()>",
311                         ],
312                         tooltip: Some(
313                             HoverRanged(
314                                 FileId(
315                                     0,
316                                 ),
317                                 174..241,
318                             ),
319                         ),
320                     },
321                     InlayHint {
322                         range: 174..224,
323                         kind: ChainingHint,
324                         label: [
325                             "impl Iterator<Item = ()>",
326                         ],
327                         tooltip: Some(
328                             HoverRanged(
329                                 FileId(
330                                     0,
331                                 ),
332                                 174..224,
333                             ),
334                         ),
335                     },
336                     InlayHint {
337                         range: 174..206,
338                         kind: ChainingHint,
339                         label: [
340                             "impl Iterator<Item = ()>",
341                         ],
342                         tooltip: Some(
343                             HoverRanged(
344                                 FileId(
345                                     0,
346                                 ),
347                                 174..206,
348                             ),
349                         ),
350                     },
351                     InlayHint {
352                         range: 174..189,
353                         kind: ChainingHint,
354                         label: [
355                             "&mut MyIter",
356                         ],
357                         tooltip: Some(
358                             HoverRanged(
359                                 FileId(
360                                     0,
361                                 ),
362                                 174..189,
363                             ),
364                         ),
365                     },
366                 ]
367             "#]],
368         );
369     }
370
371     #[test]
372     fn hints_in_attr_call() {
373         check_expect(
374             TEST_CONFIG,
375             r#"
376 //- proc_macros: identity, input_replace
377 struct Struct;
378 impl Struct {
379     fn chain(self) -> Self {
380         self
381     }
382 }
383 #[proc_macros::identity]
384 fn main() {
385     let strukt = Struct;
386     strukt
387         .chain()
388         .chain()
389         .chain();
390     Struct::chain(strukt);
391 }
392 "#,
393             expect![[r#"
394                 [
395                     InlayHint {
396                         range: 124..130,
397                         kind: TypeHint,
398                         label: [
399                             "Struct",
400                         ],
401                         tooltip: Some(
402                             HoverRanged(
403                                 FileId(
404                                     0,
405                                 ),
406                                 124..130,
407                             ),
408                         ),
409                     },
410                     InlayHint {
411                         range: 145..185,
412                         kind: ChainingHint,
413                         label: [
414                             "Struct",
415                         ],
416                         tooltip: Some(
417                             HoverRanged(
418                                 FileId(
419                                     0,
420                                 ),
421                                 145..185,
422                             ),
423                         ),
424                     },
425                     InlayHint {
426                         range: 145..168,
427                         kind: ChainingHint,
428                         label: [
429                             "Struct",
430                         ],
431                         tooltip: Some(
432                             HoverRanged(
433                                 FileId(
434                                     0,
435                                 ),
436                                 145..168,
437                             ),
438                         ),
439                     },
440                     InlayHint {
441                         range: 222..228,
442                         kind: ParameterHint,
443                         label: [
444                             "self",
445                         ],
446                         tooltip: Some(
447                             HoverOffset(
448                                 FileId(
449                                     0,
450                                 ),
451                                 42,
452                             ),
453                         ),
454                     },
455                 ]
456             "#]],
457         );
458     }
459 }