]> git.lizzy.rs Git - rust.git/blob - src/tools/rust-analyzer/crates/ide/src/annotations.rs
Rollup merge of #99614 - RalfJung:transmute-is-not-memcpy, r=thomcc
[rust.git] / src / tools / rust-analyzer / crates / ide / src / annotations.rs
1 use hir::{HasSource, InFile, Semantics};
2 use ide_db::{
3     base_db::{FileId, FilePosition, FileRange},
4     defs::Definition,
5     helpers::visit_file_defs,
6     RootDatabase,
7 };
8 use syntax::{ast::HasName, AstNode, TextRange};
9
10 use crate::{
11     fn_references::find_all_methods,
12     goto_implementation::goto_implementation,
13     references::find_all_refs,
14     runnables::{runnables, Runnable},
15     NavigationTarget, RunnableKind,
16 };
17
18 // Feature: Annotations
19 //
20 // Provides user with annotations above items for looking up references or impl blocks
21 // and running/debugging binaries.
22 //
23 // image::https://user-images.githubusercontent.com/48062697/113020672-b7c34f00-917a-11eb-8f6e-858735660a0e.png[]
24 #[derive(Debug)]
25 pub struct Annotation {
26     pub range: TextRange,
27     pub kind: AnnotationKind,
28 }
29
30 #[derive(Debug)]
31 pub enum AnnotationKind {
32     Runnable(Runnable),
33     HasImpls { file_id: FileId, data: Option<Vec<NavigationTarget>> },
34     HasReferences { file_id: FileId, data: Option<Vec<FileRange>> },
35 }
36
37 pub struct AnnotationConfig {
38     pub binary_target: bool,
39     pub annotate_runnables: bool,
40     pub annotate_impls: bool,
41     pub annotate_references: bool,
42     pub annotate_method_references: bool,
43     pub annotate_enum_variant_references: bool,
44 }
45
46 pub(crate) fn annotations(
47     db: &RootDatabase,
48     config: &AnnotationConfig,
49     file_id: FileId,
50 ) -> Vec<Annotation> {
51     let mut annotations = Vec::default();
52
53     if config.annotate_runnables {
54         for runnable in runnables(db, file_id) {
55             if should_skip_runnable(&runnable.kind, config.binary_target) {
56                 continue;
57             }
58
59             let range = runnable.nav.focus_or_full_range();
60
61             annotations.push(Annotation { range, kind: AnnotationKind::Runnable(runnable) });
62         }
63     }
64
65     visit_file_defs(&Semantics::new(db), file_id, &mut |def| {
66         let range = match def {
67             Definition::Const(konst) if config.annotate_references => {
68                 konst.source(db).and_then(|node| name_range(db, node, file_id))
69             }
70             Definition::Trait(trait_) if config.annotate_references || config.annotate_impls => {
71                 trait_.source(db).and_then(|node| name_range(db, node, file_id))
72             }
73             Definition::Adt(adt) => match adt {
74                 hir::Adt::Enum(enum_) => {
75                     if config.annotate_enum_variant_references {
76                         enum_
77                             .variants(db)
78                             .into_iter()
79                             .map(|variant| {
80                                 variant.source(db).and_then(|node| name_range(db, node, file_id))
81                             })
82                             .flatten()
83                             .for_each(|range| {
84                                 annotations.push(Annotation {
85                                     range,
86                                     kind: AnnotationKind::HasReferences { file_id, data: None },
87                                 })
88                             })
89                     }
90                     if config.annotate_references || config.annotate_impls {
91                         enum_.source(db).and_then(|node| name_range(db, node, file_id))
92                     } else {
93                         None
94                     }
95                 }
96                 _ => {
97                     if config.annotate_references || config.annotate_impls {
98                         adt.source(db).and_then(|node| name_range(db, node, file_id))
99                     } else {
100                         None
101                     }
102                 }
103             },
104             _ => None,
105         };
106
107         let range = match range {
108             Some(range) => range,
109             None => return,
110         };
111
112         if config.annotate_impls && !matches!(def, Definition::Const(_)) {
113             annotations
114                 .push(Annotation { range, kind: AnnotationKind::HasImpls { file_id, data: None } });
115         }
116         if config.annotate_references {
117             annotations.push(Annotation {
118                 range,
119                 kind: AnnotationKind::HasReferences { file_id, data: None },
120             });
121         }
122
123         fn name_range<T: HasName>(
124             db: &RootDatabase,
125             node: InFile<T>,
126             source_file_id: FileId,
127         ) -> Option<TextRange> {
128             if let Some(InFile { file_id, value }) = node.original_ast_node(db) {
129                 if file_id == source_file_id.into() {
130                     return value.name().map(|it| it.syntax().text_range());
131                 }
132             }
133             None
134         }
135     });
136
137     if config.annotate_method_references {
138         annotations.extend(find_all_methods(db, file_id).into_iter().map(
139             |FileRange { file_id, range }| Annotation {
140                 range,
141                 kind: AnnotationKind::HasReferences { file_id, data: None },
142             },
143         ));
144     }
145
146     annotations
147 }
148
149 pub(crate) fn resolve_annotation(db: &RootDatabase, mut annotation: Annotation) -> Annotation {
150     match annotation.kind {
151         AnnotationKind::HasImpls { file_id, ref mut data } => {
152             *data =
153                 goto_implementation(db, FilePosition { file_id, offset: annotation.range.start() })
154                     .map(|range| range.info);
155         }
156         AnnotationKind::HasReferences { file_id, ref mut data } => {
157             *data = find_all_refs(
158                 &Semantics::new(db),
159                 FilePosition { file_id, offset: annotation.range.start() },
160                 None,
161             )
162             .map(|result| {
163                 result
164                     .into_iter()
165                     .flat_map(|res| res.references)
166                     .flat_map(|(file_id, access)| {
167                         access.into_iter().map(move |(range, _)| FileRange { file_id, range })
168                     })
169                     .collect()
170             });
171         }
172         _ => {}
173     };
174
175     annotation
176 }
177
178 fn should_skip_runnable(kind: &RunnableKind, binary_target: bool) -> bool {
179     match kind {
180         RunnableKind::Bin => !binary_target,
181         _ => false,
182     }
183 }
184
185 #[cfg(test)]
186 mod tests {
187     use expect_test::{expect, Expect};
188
189     use crate::{fixture, Annotation, AnnotationConfig};
190
191     fn check(ra_fixture: &str, expect: Expect) {
192         let (analysis, file_id) = fixture::file(ra_fixture);
193
194         let annotations: Vec<Annotation> = analysis
195             .annotations(
196                 &AnnotationConfig {
197                     binary_target: true,
198                     annotate_runnables: true,
199                     annotate_impls: true,
200                     annotate_references: true,
201                     annotate_method_references: true,
202                     annotate_enum_variant_references: true,
203                 },
204                 file_id,
205             )
206             .unwrap()
207             .into_iter()
208             .map(|annotation| analysis.resolve_annotation(annotation).unwrap())
209             .collect();
210
211         expect.assert_debug_eq(&annotations);
212     }
213
214     #[test]
215     fn const_annotations() {
216         check(
217             r#"
218 const DEMO: i32 = 123;
219
220 const UNUSED: i32 = 123;
221
222 fn main() {
223     let hello = DEMO;
224 }
225             "#,
226             expect![[r#"
227                 [
228                     Annotation {
229                         range: 53..57,
230                         kind: Runnable(
231                             Runnable {
232                                 use_name_in_title: false,
233                                 nav: NavigationTarget {
234                                     file_id: FileId(
235                                         0,
236                                     ),
237                                     full_range: 50..85,
238                                     focus_range: 53..57,
239                                     name: "main",
240                                     kind: Function,
241                                 },
242                                 kind: Bin,
243                                 cfg: None,
244                             },
245                         ),
246                     },
247                     Annotation {
248                         range: 6..10,
249                         kind: HasReferences {
250                             file_id: FileId(
251                                 0,
252                             ),
253                             data: Some(
254                                 [
255                                     FileRange {
256                                         file_id: FileId(
257                                             0,
258                                         ),
259                                         range: 78..82,
260                                     },
261                                 ],
262                             ),
263                         },
264                     },
265                     Annotation {
266                         range: 30..36,
267                         kind: HasReferences {
268                             file_id: FileId(
269                                 0,
270                             ),
271                             data: Some(
272                                 [],
273                             ),
274                         },
275                     },
276                     Annotation {
277                         range: 53..57,
278                         kind: HasReferences {
279                             file_id: FileId(
280                                 0,
281                             ),
282                             data: Some(
283                                 [],
284                             ),
285                         },
286                     },
287                 ]
288             "#]],
289         );
290     }
291
292     #[test]
293     fn struct_references_annotations() {
294         check(
295             r#"
296 struct Test;
297
298 fn main() {
299     let test = Test;
300 }
301             "#,
302             expect![[r#"
303                 [
304                     Annotation {
305                         range: 17..21,
306                         kind: Runnable(
307                             Runnable {
308                                 use_name_in_title: false,
309                                 nav: NavigationTarget {
310                                     file_id: FileId(
311                                         0,
312                                     ),
313                                     full_range: 14..48,
314                                     focus_range: 17..21,
315                                     name: "main",
316                                     kind: Function,
317                                 },
318                                 kind: Bin,
319                                 cfg: None,
320                             },
321                         ),
322                     },
323                     Annotation {
324                         range: 7..11,
325                         kind: HasImpls {
326                             file_id: FileId(
327                                 0,
328                             ),
329                             data: Some(
330                                 [],
331                             ),
332                         },
333                     },
334                     Annotation {
335                         range: 7..11,
336                         kind: HasReferences {
337                             file_id: FileId(
338                                 0,
339                             ),
340                             data: Some(
341                                 [
342                                     FileRange {
343                                         file_id: FileId(
344                                             0,
345                                         ),
346                                         range: 41..45,
347                                     },
348                                 ],
349                             ),
350                         },
351                     },
352                     Annotation {
353                         range: 17..21,
354                         kind: HasReferences {
355                             file_id: FileId(
356                                 0,
357                             ),
358                             data: Some(
359                                 [],
360                             ),
361                         },
362                     },
363                 ]
364             "#]],
365         );
366     }
367
368     #[test]
369     fn struct_and_trait_impls_annotations() {
370         check(
371             r#"
372 struct Test;
373
374 trait MyCoolTrait {}
375
376 impl MyCoolTrait for Test {}
377
378 fn main() {
379     let test = Test;
380 }
381             "#,
382             expect![[r#"
383                 [
384                     Annotation {
385                         range: 69..73,
386                         kind: Runnable(
387                             Runnable {
388                                 use_name_in_title: false,
389                                 nav: NavigationTarget {
390                                     file_id: FileId(
391                                         0,
392                                     ),
393                                     full_range: 66..100,
394                                     focus_range: 69..73,
395                                     name: "main",
396                                     kind: Function,
397                                 },
398                                 kind: Bin,
399                                 cfg: None,
400                             },
401                         ),
402                     },
403                     Annotation {
404                         range: 7..11,
405                         kind: HasImpls {
406                             file_id: FileId(
407                                 0,
408                             ),
409                             data: Some(
410                                 [
411                                     NavigationTarget {
412                                         file_id: FileId(
413                                             0,
414                                         ),
415                                         full_range: 36..64,
416                                         focus_range: 57..61,
417                                         name: "impl",
418                                         kind: Impl,
419                                     },
420                                 ],
421                             ),
422                         },
423                     },
424                     Annotation {
425                         range: 7..11,
426                         kind: HasReferences {
427                             file_id: FileId(
428                                 0,
429                             ),
430                             data: Some(
431                                 [
432                                     FileRange {
433                                         file_id: FileId(
434                                             0,
435                                         ),
436                                         range: 57..61,
437                                     },
438                                     FileRange {
439                                         file_id: FileId(
440                                             0,
441                                         ),
442                                         range: 93..97,
443                                     },
444                                 ],
445                             ),
446                         },
447                     },
448                     Annotation {
449                         range: 20..31,
450                         kind: HasImpls {
451                             file_id: FileId(
452                                 0,
453                             ),
454                             data: Some(
455                                 [
456                                     NavigationTarget {
457                                         file_id: FileId(
458                                             0,
459                                         ),
460                                         full_range: 36..64,
461                                         focus_range: 57..61,
462                                         name: "impl",
463                                         kind: Impl,
464                                     },
465                                 ],
466                             ),
467                         },
468                     },
469                     Annotation {
470                         range: 20..31,
471                         kind: HasReferences {
472                             file_id: FileId(
473                                 0,
474                             ),
475                             data: Some(
476                                 [
477                                     FileRange {
478                                         file_id: FileId(
479                                             0,
480                                         ),
481                                         range: 41..52,
482                                     },
483                                 ],
484                             ),
485                         },
486                     },
487                     Annotation {
488                         range: 69..73,
489                         kind: HasReferences {
490                             file_id: FileId(
491                                 0,
492                             ),
493                             data: Some(
494                                 [],
495                             ),
496                         },
497                     },
498                 ]
499             "#]],
500         );
501     }
502
503     #[test]
504     fn runnable_annotation() {
505         check(
506             r#"
507 fn main() {}
508             "#,
509             expect![[r#"
510                 [
511                     Annotation {
512                         range: 3..7,
513                         kind: Runnable(
514                             Runnable {
515                                 use_name_in_title: false,
516                                 nav: NavigationTarget {
517                                     file_id: FileId(
518                                         0,
519                                     ),
520                                     full_range: 0..12,
521                                     focus_range: 3..7,
522                                     name: "main",
523                                     kind: Function,
524                                 },
525                                 kind: Bin,
526                                 cfg: None,
527                             },
528                         ),
529                     },
530                     Annotation {
531                         range: 3..7,
532                         kind: HasReferences {
533                             file_id: FileId(
534                                 0,
535                             ),
536                             data: Some(
537                                 [],
538                             ),
539                         },
540                     },
541                 ]
542             "#]],
543         );
544     }
545
546     #[test]
547     fn method_annotations() {
548         check(
549             r#"
550 struct Test;
551
552 impl Test {
553     fn self_by_ref(&self) {}
554 }
555
556 fn main() {
557     Test.self_by_ref();
558 }
559             "#,
560             expect![[r#"
561                 [
562                     Annotation {
563                         range: 61..65,
564                         kind: Runnable(
565                             Runnable {
566                                 use_name_in_title: false,
567                                 nav: NavigationTarget {
568                                     file_id: FileId(
569                                         0,
570                                     ),
571                                     full_range: 58..95,
572                                     focus_range: 61..65,
573                                     name: "main",
574                                     kind: Function,
575                                 },
576                                 kind: Bin,
577                                 cfg: None,
578                             },
579                         ),
580                     },
581                     Annotation {
582                         range: 7..11,
583                         kind: HasImpls {
584                             file_id: FileId(
585                                 0,
586                             ),
587                             data: Some(
588                                 [
589                                     NavigationTarget {
590                                         file_id: FileId(
591                                             0,
592                                         ),
593                                         full_range: 14..56,
594                                         focus_range: 19..23,
595                                         name: "impl",
596                                         kind: Impl,
597                                     },
598                                 ],
599                             ),
600                         },
601                     },
602                     Annotation {
603                         range: 7..11,
604                         kind: HasReferences {
605                             file_id: FileId(
606                                 0,
607                             ),
608                             data: Some(
609                                 [
610                                     FileRange {
611                                         file_id: FileId(
612                                             0,
613                                         ),
614                                         range: 19..23,
615                                     },
616                                     FileRange {
617                                         file_id: FileId(
618                                             0,
619                                         ),
620                                         range: 74..78,
621                                     },
622                                 ],
623                             ),
624                         },
625                     },
626                     Annotation {
627                         range: 33..44,
628                         kind: HasReferences {
629                             file_id: FileId(
630                                 0,
631                             ),
632                             data: Some(
633                                 [
634                                     FileRange {
635                                         file_id: FileId(
636                                             0,
637                                         ),
638                                         range: 79..90,
639                                     },
640                                 ],
641                             ),
642                         },
643                     },
644                     Annotation {
645                         range: 61..65,
646                         kind: HasReferences {
647                             file_id: FileId(
648                                 0,
649                             ),
650                             data: Some(
651                                 [],
652                             ),
653                         },
654                     },
655                 ]
656             "#]],
657         );
658     }
659
660     #[test]
661     fn test_annotations() {
662         check(
663             r#"
664 fn main() {}
665
666 mod tests {
667     #[test]
668     fn my_cool_test() {}
669 }
670             "#,
671             expect![[r#"
672                 [
673                     Annotation {
674                         range: 3..7,
675                         kind: Runnable(
676                             Runnable {
677                                 use_name_in_title: false,
678                                 nav: NavigationTarget {
679                                     file_id: FileId(
680                                         0,
681                                     ),
682                                     full_range: 0..12,
683                                     focus_range: 3..7,
684                                     name: "main",
685                                     kind: Function,
686                                 },
687                                 kind: Bin,
688                                 cfg: None,
689                             },
690                         ),
691                     },
692                     Annotation {
693                         range: 18..23,
694                         kind: Runnable(
695                             Runnable {
696                                 use_name_in_title: false,
697                                 nav: NavigationTarget {
698                                     file_id: FileId(
699                                         0,
700                                     ),
701                                     full_range: 14..64,
702                                     focus_range: 18..23,
703                                     name: "tests",
704                                     kind: Module,
705                                     description: "mod tests",
706                                 },
707                                 kind: TestMod {
708                                     path: "tests",
709                                 },
710                                 cfg: None,
711                             },
712                         ),
713                     },
714                     Annotation {
715                         range: 45..57,
716                         kind: Runnable(
717                             Runnable {
718                                 use_name_in_title: false,
719                                 nav: NavigationTarget {
720                                     file_id: FileId(
721                                         0,
722                                     ),
723                                     full_range: 30..62,
724                                     focus_range: 45..57,
725                                     name: "my_cool_test",
726                                     kind: Function,
727                                 },
728                                 kind: Test {
729                                     test_id: Path(
730                                         "tests::my_cool_test",
731                                     ),
732                                     attr: TestAttr {
733                                         ignore: false,
734                                     },
735                                 },
736                                 cfg: None,
737                             },
738                         ),
739                     },
740                     Annotation {
741                         range: 3..7,
742                         kind: HasReferences {
743                             file_id: FileId(
744                                 0,
745                             ),
746                             data: Some(
747                                 [],
748                             ),
749                         },
750                     },
751                 ]
752             "#]],
753         );
754     }
755
756     #[test]
757     fn test_no_annotations_outside_module_tree() {
758         check(
759             r#"
760 //- /foo.rs
761 struct Foo;
762 //- /lib.rs
763 // this file comes last since `check` checks the first file only
764 "#,
765             expect![[r#"
766                 []
767             "#]],
768         );
769     }
770
771     #[test]
772     fn test_no_annotations_macro_struct_def() {
773         check(
774             r#"
775 //- /lib.rs
776 macro_rules! m {
777     () => {
778         struct A {}
779     };
780 }
781
782 m!();
783 "#,
784             expect![[r#"
785                 []
786             "#]],
787         );
788     }
789 }