aboutsummaryrefslogtreecommitdiff
path: root/crates/ide/src
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ide/src')
-rw-r--r--crates/ide/src/annotations.rs937
-rw-r--r--crates/ide/src/call_hierarchy.rs8
-rw-r--r--crates/ide/src/diagnostics.rs70
-rw-r--r--crates/ide/src/diagnostics/fixes.rs35
-rw-r--r--crates/ide/src/display/navigation_target.rs53
-rw-r--r--crates/ide/src/display/short_label.rs16
-rw-r--r--crates/ide/src/doc_links.rs36
-rw-r--r--crates/ide/src/extend_selection.rs12
-rw-r--r--crates/ide/src/file_structure.rs3
-rw-r--r--crates/ide/src/fixture.rs16
-rw-r--r--crates/ide/src/fn_references.rs2
-rw-r--r--crates/ide/src/goto_definition.rs105
-rw-r--r--crates/ide/src/goto_implementation.rs10
-rw-r--r--crates/ide/src/hover.rs128
-rw-r--r--crates/ide/src/inlay_hints.rs61
-rw-r--r--crates/ide/src/join_lines.rs45
-rw-r--r--crates/ide/src/lib.rs47
-rw-r--r--crates/ide/src/parent_module.rs71
-rw-r--r--crates/ide/src/references.rs686
-rw-r--r--crates/ide/src/references/rename.rs932
-rw-r--r--crates/ide/src/runnables.rs236
-rw-r--r--crates/ide/src/status.rs2
-rw-r--r--crates/ide/src/syntax_highlighting.rs6
-rw-r--r--crates/ide/src/syntax_highlighting/format.rs5
-rw-r--r--crates/ide/src/syntax_highlighting/highlight.rs4
-rw-r--r--crates/ide/src/syntax_highlighting/inject.rs2
-rw-r--r--crates/ide/src/syntax_highlighting/tags.rs2
-rw-r--r--crates/ide/src/syntax_highlighting/test_data/highlighting.html4
-rw-r--r--crates/ide/src/syntax_highlighting/tests.rs55
-rw-r--r--crates/ide/src/syntax_tree.rs366
-rw-r--r--crates/ide/src/view_hir.rs2
31 files changed, 2749 insertions, 1208 deletions
diff --git a/crates/ide/src/annotations.rs b/crates/ide/src/annotations.rs
new file mode 100644
index 000000000..2e8e82b70
--- /dev/null
+++ b/crates/ide/src/annotations.rs
@@ -0,0 +1,937 @@
1use hir::Semantics;
2use ide_db::{
3 base_db::{FileId, FilePosition, FileRange, SourceDatabase},
4 RootDatabase, SymbolKind,
5};
6use syntax::TextRange;
7
8use crate::{
9 file_structure::file_structure,
10 fn_references::find_all_methods,
11 goto_implementation::goto_implementation,
12 references::find_all_refs,
13 runnables::{runnables, Runnable},
14 NavigationTarget, RunnableKind,
15};
16
17// Feature: Annotations
18//
19// Provides user with annotations above items for looking up references or impl blocks
20// and running/debugging binaries.
21#[derive(Debug)]
22pub struct Annotation {
23 pub range: TextRange,
24 pub kind: AnnotationKind,
25}
26
27#[derive(Debug)]
28pub enum AnnotationKind {
29 Runnable { debug: bool, runnable: Runnable },
30 HasImpls { position: FilePosition, data: Option<Vec<NavigationTarget>> },
31 HasReferences { position: FilePosition, data: Option<Vec<FileRange>> },
32}
33
34pub struct AnnotationConfig {
35 pub binary_target: bool,
36 pub annotate_runnables: bool,
37 pub annotate_impls: bool,
38 pub annotate_references: bool,
39 pub annotate_method_references: bool,
40 pub run: bool,
41 pub debug: bool,
42}
43
44pub(crate) fn annotations(
45 db: &RootDatabase,
46 file_id: FileId,
47 config: AnnotationConfig,
48) -> Vec<Annotation> {
49 let mut annotations = Vec::default();
50
51 if config.annotate_runnables {
52 for runnable in runnables(db, file_id) {
53 if should_skip_runnable(&runnable.kind, config.binary_target) {
54 continue;
55 }
56
57 let action = runnable.action();
58 let range = runnable.nav.full_range;
59
60 if config.run {
61 annotations.push(Annotation {
62 range,
63
64 // FIXME: This one allocates without reason if run is enabled, but debug is disabled
65 kind: AnnotationKind::Runnable { debug: false, runnable: runnable.clone() },
66 });
67 }
68
69 if action.debugee && config.debug {
70 annotations.push(Annotation {
71 range,
72 kind: AnnotationKind::Runnable { debug: true, runnable },
73 });
74 }
75 }
76 }
77
78 file_structure(&db.parse(file_id).tree())
79 .into_iter()
80 .filter(|node| {
81 matches!(
82 node.kind,
83 SymbolKind::Trait
84 | SymbolKind::Struct
85 | SymbolKind::Enum
86 | SymbolKind::Union
87 | SymbolKind::Const
88 )
89 })
90 .for_each(|node| {
91 if config.annotate_impls && node.kind != SymbolKind::Const {
92 annotations.push(Annotation {
93 range: node.node_range,
94 kind: AnnotationKind::HasImpls {
95 position: FilePosition { file_id, offset: node.navigation_range.start() },
96 data: None,
97 },
98 });
99 }
100
101 if config.annotate_references {
102 annotations.push(Annotation {
103 range: node.node_range,
104 kind: AnnotationKind::HasReferences {
105 position: FilePosition { file_id, offset: node.navigation_range.start() },
106 data: None,
107 },
108 });
109 }
110 });
111
112 if config.annotate_method_references {
113 annotations.extend(find_all_methods(db, file_id).into_iter().map(|method| Annotation {
114 range: method.range,
115 kind: AnnotationKind::HasReferences {
116 position: FilePosition { file_id, offset: method.range.start() },
117 data: None,
118 },
119 }));
120 }
121
122 annotations
123}
124
125pub(crate) fn resolve_annotation(db: &RootDatabase, mut annotation: Annotation) -> Annotation {
126 match annotation.kind {
127 AnnotationKind::HasImpls { position, ref mut data } => {
128 *data = goto_implementation(db, position).map(|range| range.info);
129 }
130 AnnotationKind::HasReferences { position, ref mut data } => {
131 *data = find_all_refs(&Semantics::new(db), position, None).map(|result| {
132 result
133 .references
134 .into_iter()
135 .map(|(file_id, access)| {
136 access.into_iter().map(move |(range, _)| FileRange { file_id, range })
137 })
138 .flatten()
139 .collect()
140 });
141 }
142 _ => {}
143 };
144
145 annotation
146}
147
148fn should_skip_runnable(kind: &RunnableKind, binary_target: bool) -> bool {
149 match kind {
150 RunnableKind::Bin => !binary_target,
151 _ => false,
152 }
153}
154
155#[cfg(test)]
156mod tests {
157 use expect_test::{expect, Expect};
158
159 use crate::{fixture, Annotation, AnnotationConfig};
160
161 fn check(ra_fixture: &str, expect: Expect) {
162 let (analysis, file_id) = fixture::file(ra_fixture);
163
164 let annotations: Vec<Annotation> = analysis
165 .annotations(
166 file_id,
167 AnnotationConfig {
168 binary_target: true,
169 annotate_runnables: true,
170 annotate_impls: true,
171 annotate_references: true,
172 annotate_method_references: true,
173 run: true,
174 debug: true,
175 },
176 )
177 .unwrap()
178 .into_iter()
179 .map(|annotation| analysis.resolve_annotation(annotation).unwrap())
180 .collect();
181
182 expect.assert_debug_eq(&annotations);
183 }
184
185 #[test]
186 fn const_annotations() {
187 check(
188 r#"
189const DEMO: i32 = 123;
190
191const UNUSED: i32 = 123;
192
193fn main() {
194 let hello = DEMO;
195}
196 "#,
197 expect![[r#"
198 [
199 Annotation {
200 range: 50..85,
201 kind: Runnable {
202 debug: false,
203 runnable: Runnable {
204 nav: NavigationTarget {
205 file_id: FileId(
206 0,
207 ),
208 full_range: 50..85,
209 focus_range: 53..57,
210 name: "main",
211 kind: Function,
212 },
213 kind: Bin,
214 cfg: None,
215 },
216 },
217 },
218 Annotation {
219 range: 50..85,
220 kind: Runnable {
221 debug: true,
222 runnable: Runnable {
223 nav: NavigationTarget {
224 file_id: FileId(
225 0,
226 ),
227 full_range: 50..85,
228 focus_range: 53..57,
229 name: "main",
230 kind: Function,
231 },
232 kind: Bin,
233 cfg: None,
234 },
235 },
236 },
237 Annotation {
238 range: 0..22,
239 kind: HasReferences {
240 position: FilePosition {
241 file_id: FileId(
242 0,
243 ),
244 offset: 6,
245 },
246 data: Some(
247 [
248 FileRange {
249 file_id: FileId(
250 0,
251 ),
252 range: 78..82,
253 },
254 ],
255 ),
256 },
257 },
258 Annotation {
259 range: 24..48,
260 kind: HasReferences {
261 position: FilePosition {
262 file_id: FileId(
263 0,
264 ),
265 offset: 30,
266 },
267 data: Some(
268 [],
269 ),
270 },
271 },
272 Annotation {
273 range: 53..57,
274 kind: HasReferences {
275 position: FilePosition {
276 file_id: FileId(
277 0,
278 ),
279 offset: 53,
280 },
281 data: Some(
282 [],
283 ),
284 },
285 },
286 ]
287 "#]],
288 );
289 }
290
291 #[test]
292 fn struct_references_annotations() {
293 check(
294 r#"
295struct Test;
296
297fn main() {
298 let test = Test;
299}
300 "#,
301 expect![[r#"
302 [
303 Annotation {
304 range: 14..48,
305 kind: Runnable {
306 debug: false,
307 runnable: Runnable {
308 nav: NavigationTarget {
309 file_id: FileId(
310 0,
311 ),
312 full_range: 14..48,
313 focus_range: 17..21,
314 name: "main",
315 kind: Function,
316 },
317 kind: Bin,
318 cfg: None,
319 },
320 },
321 },
322 Annotation {
323 range: 14..48,
324 kind: Runnable {
325 debug: true,
326 runnable: Runnable {
327 nav: NavigationTarget {
328 file_id: FileId(
329 0,
330 ),
331 full_range: 14..48,
332 focus_range: 17..21,
333 name: "main",
334 kind: Function,
335 },
336 kind: Bin,
337 cfg: None,
338 },
339 },
340 },
341 Annotation {
342 range: 0..12,
343 kind: HasImpls {
344 position: FilePosition {
345 file_id: FileId(
346 0,
347 ),
348 offset: 7,
349 },
350 data: Some(
351 [],
352 ),
353 },
354 },
355 Annotation {
356 range: 0..12,
357 kind: HasReferences {
358 position: FilePosition {
359 file_id: FileId(
360 0,
361 ),
362 offset: 7,
363 },
364 data: Some(
365 [
366 FileRange {
367 file_id: FileId(
368 0,
369 ),
370 range: 41..45,
371 },
372 ],
373 ),
374 },
375 },
376 Annotation {
377 range: 17..21,
378 kind: HasReferences {
379 position: FilePosition {
380 file_id: FileId(
381 0,
382 ),
383 offset: 17,
384 },
385 data: Some(
386 [],
387 ),
388 },
389 },
390 ]
391 "#]],
392 );
393 }
394
395 #[test]
396 fn struct_and_trait_impls_annotations() {
397 check(
398 r#"
399struct Test;
400
401trait MyCoolTrait {}
402
403impl MyCoolTrait for Test {}
404
405fn main() {
406 let test = Test;
407}
408 "#,
409 expect![[r#"
410 [
411 Annotation {
412 range: 66..100,
413 kind: Runnable {
414 debug: false,
415 runnable: Runnable {
416 nav: NavigationTarget {
417 file_id: FileId(
418 0,
419 ),
420 full_range: 66..100,
421 focus_range: 69..73,
422 name: "main",
423 kind: Function,
424 },
425 kind: Bin,
426 cfg: None,
427 },
428 },
429 },
430 Annotation {
431 range: 66..100,
432 kind: Runnable {
433 debug: true,
434 runnable: Runnable {
435 nav: NavigationTarget {
436 file_id: FileId(
437 0,
438 ),
439 full_range: 66..100,
440 focus_range: 69..73,
441 name: "main",
442 kind: Function,
443 },
444 kind: Bin,
445 cfg: None,
446 },
447 },
448 },
449 Annotation {
450 range: 0..12,
451 kind: HasImpls {
452 position: FilePosition {
453 file_id: FileId(
454 0,
455 ),
456 offset: 7,
457 },
458 data: Some(
459 [
460 NavigationTarget {
461 file_id: FileId(
462 0,
463 ),
464 full_range: 36..64,
465 focus_range: 57..61,
466 name: "impl",
467 kind: Impl,
468 },
469 ],
470 ),
471 },
472 },
473 Annotation {
474 range: 0..12,
475 kind: HasReferences {
476 position: FilePosition {
477 file_id: FileId(
478 0,
479 ),
480 offset: 7,
481 },
482 data: Some(
483 [
484 FileRange {
485 file_id: FileId(
486 0,
487 ),
488 range: 57..61,
489 },
490 FileRange {
491 file_id: FileId(
492 0,
493 ),
494 range: 93..97,
495 },
496 ],
497 ),
498 },
499 },
500 Annotation {
501 range: 14..34,
502 kind: HasImpls {
503 position: FilePosition {
504 file_id: FileId(
505 0,
506 ),
507 offset: 20,
508 },
509 data: Some(
510 [
511 NavigationTarget {
512 file_id: FileId(
513 0,
514 ),
515 full_range: 36..64,
516 focus_range: 57..61,
517 name: "impl",
518 kind: Impl,
519 },
520 ],
521 ),
522 },
523 },
524 Annotation {
525 range: 14..34,
526 kind: HasReferences {
527 position: FilePosition {
528 file_id: FileId(
529 0,
530 ),
531 offset: 20,
532 },
533 data: Some(
534 [
535 FileRange {
536 file_id: FileId(
537 0,
538 ),
539 range: 41..52,
540 },
541 ],
542 ),
543 },
544 },
545 Annotation {
546 range: 69..73,
547 kind: HasReferences {
548 position: FilePosition {
549 file_id: FileId(
550 0,
551 ),
552 offset: 69,
553 },
554 data: Some(
555 [],
556 ),
557 },
558 },
559 ]
560 "#]],
561 );
562 }
563
564 #[test]
565 fn runnable_annotation() {
566 check(
567 r#"
568fn main() {}
569 "#,
570 expect![[r#"
571 [
572 Annotation {
573 range: 0..12,
574 kind: Runnable {
575 debug: false,
576 runnable: Runnable {
577 nav: NavigationTarget {
578 file_id: FileId(
579 0,
580 ),
581 full_range: 0..12,
582 focus_range: 3..7,
583 name: "main",
584 kind: Function,
585 },
586 kind: Bin,
587 cfg: None,
588 },
589 },
590 },
591 Annotation {
592 range: 0..12,
593 kind: Runnable {
594 debug: true,
595 runnable: Runnable {
596 nav: NavigationTarget {
597 file_id: FileId(
598 0,
599 ),
600 full_range: 0..12,
601 focus_range: 3..7,
602 name: "main",
603 kind: Function,
604 },
605 kind: Bin,
606 cfg: None,
607 },
608 },
609 },
610 Annotation {
611 range: 3..7,
612 kind: HasReferences {
613 position: FilePosition {
614 file_id: FileId(
615 0,
616 ),
617 offset: 3,
618 },
619 data: Some(
620 [],
621 ),
622 },
623 },
624 ]
625 "#]],
626 );
627 }
628
629 #[test]
630 fn method_annotations() {
631 check(
632 r#"
633struct Test;
634
635impl Test {
636 fn self_by_ref(&self) {}
637}
638
639fn main() {
640 Test.self_by_ref();
641}
642 "#,
643 expect![[r#"
644 [
645 Annotation {
646 range: 58..95,
647 kind: Runnable {
648 debug: false,
649 runnable: Runnable {
650 nav: NavigationTarget {
651 file_id: FileId(
652 0,
653 ),
654 full_range: 58..95,
655 focus_range: 61..65,
656 name: "main",
657 kind: Function,
658 },
659 kind: Bin,
660 cfg: None,
661 },
662 },
663 },
664 Annotation {
665 range: 58..95,
666 kind: Runnable {
667 debug: true,
668 runnable: Runnable {
669 nav: NavigationTarget {
670 file_id: FileId(
671 0,
672 ),
673 full_range: 58..95,
674 focus_range: 61..65,
675 name: "main",
676 kind: Function,
677 },
678 kind: Bin,
679 cfg: None,
680 },
681 },
682 },
683 Annotation {
684 range: 0..12,
685 kind: HasImpls {
686 position: FilePosition {
687 file_id: FileId(
688 0,
689 ),
690 offset: 7,
691 },
692 data: Some(
693 [
694 NavigationTarget {
695 file_id: FileId(
696 0,
697 ),
698 full_range: 14..56,
699 focus_range: 19..23,
700 name: "impl",
701 kind: Impl,
702 },
703 ],
704 ),
705 },
706 },
707 Annotation {
708 range: 0..12,
709 kind: HasReferences {
710 position: FilePosition {
711 file_id: FileId(
712 0,
713 ),
714 offset: 7,
715 },
716 data: Some(
717 [
718 FileRange {
719 file_id: FileId(
720 0,
721 ),
722 range: 19..23,
723 },
724 FileRange {
725 file_id: FileId(
726 0,
727 ),
728 range: 74..78,
729 },
730 ],
731 ),
732 },
733 },
734 Annotation {
735 range: 33..44,
736 kind: HasReferences {
737 position: FilePosition {
738 file_id: FileId(
739 0,
740 ),
741 offset: 33,
742 },
743 data: Some(
744 [
745 FileRange {
746 file_id: FileId(
747 0,
748 ),
749 range: 79..90,
750 },
751 ],
752 ),
753 },
754 },
755 Annotation {
756 range: 61..65,
757 kind: HasReferences {
758 position: FilePosition {
759 file_id: FileId(
760 0,
761 ),
762 offset: 61,
763 },
764 data: Some(
765 [],
766 ),
767 },
768 },
769 ]
770 "#]],
771 );
772 }
773
774 #[test]
775 fn test_annotations() {
776 check(
777 r#"
778fn main() {}
779
780mod tests {
781 #[test]
782 fn my_cool_test() {}
783}
784 "#,
785 expect![[r#"
786 [
787 Annotation {
788 range: 0..12,
789 kind: Runnable {
790 debug: false,
791 runnable: Runnable {
792 nav: NavigationTarget {
793 file_id: FileId(
794 0,
795 ),
796 full_range: 0..12,
797 focus_range: 3..7,
798 name: "main",
799 kind: Function,
800 },
801 kind: Bin,
802 cfg: None,
803 },
804 },
805 },
806 Annotation {
807 range: 0..12,
808 kind: Runnable {
809 debug: true,
810 runnable: Runnable {
811 nav: NavigationTarget {
812 file_id: FileId(
813 0,
814 ),
815 full_range: 0..12,
816 focus_range: 3..7,
817 name: "main",
818 kind: Function,
819 },
820 kind: Bin,
821 cfg: None,
822 },
823 },
824 },
825 Annotation {
826 range: 14..64,
827 kind: Runnable {
828 debug: false,
829 runnable: Runnable {
830 nav: NavigationTarget {
831 file_id: FileId(
832 0,
833 ),
834 full_range: 14..64,
835 focus_range: 18..23,
836 name: "tests",
837 kind: Module,
838 },
839 kind: TestMod {
840 path: "tests",
841 },
842 cfg: None,
843 },
844 },
845 },
846 Annotation {
847 range: 14..64,
848 kind: Runnable {
849 debug: true,
850 runnable: Runnable {
851 nav: NavigationTarget {
852 file_id: FileId(
853 0,
854 ),
855 full_range: 14..64,
856 focus_range: 18..23,
857 name: "tests",
858 kind: Module,
859 },
860 kind: TestMod {
861 path: "tests",
862 },
863 cfg: None,
864 },
865 },
866 },
867 Annotation {
868 range: 30..62,
869 kind: Runnable {
870 debug: false,
871 runnable: Runnable {
872 nav: NavigationTarget {
873 file_id: FileId(
874 0,
875 ),
876 full_range: 30..62,
877 focus_range: 45..57,
878 name: "my_cool_test",
879 kind: Function,
880 },
881 kind: Test {
882 test_id: Path(
883 "tests::my_cool_test",
884 ),
885 attr: TestAttr {
886 ignore: false,
887 },
888 },
889 cfg: None,
890 },
891 },
892 },
893 Annotation {
894 range: 30..62,
895 kind: Runnable {
896 debug: true,
897 runnable: Runnable {
898 nav: NavigationTarget {
899 file_id: FileId(
900 0,
901 ),
902 full_range: 30..62,
903 focus_range: 45..57,
904 name: "my_cool_test",
905 kind: Function,
906 },
907 kind: Test {
908 test_id: Path(
909 "tests::my_cool_test",
910 ),
911 attr: TestAttr {
912 ignore: false,
913 },
914 },
915 cfg: None,
916 },
917 },
918 },
919 Annotation {
920 range: 3..7,
921 kind: HasReferences {
922 position: FilePosition {
923 file_id: FileId(
924 0,
925 ),
926 offset: 3,
927 },
928 data: Some(
929 [],
930 ),
931 },
932 },
933 ]
934 "#]],
935 );
936 }
937}
diff --git a/crates/ide/src/call_hierarchy.rs b/crates/ide/src/call_hierarchy.rs
index e8999a7f3..b848945d7 100644
--- a/crates/ide/src/call_hierarchy.rs
+++ b/crates/ide/src/call_hierarchy.rs
@@ -47,11 +47,11 @@ pub(crate) fn incoming_calls(db: &RootDatabase, position: FilePosition) -> Optio
47 47
48 let mut calls = CallLocations::default(); 48 let mut calls = CallLocations::default();
49 49
50 for (&file_id, references) in refs.info.references().iter() { 50 for (file_id, references) in refs.references {
51 let file = sema.parse(file_id); 51 let file = sema.parse(file_id);
52 let file = file.syntax(); 52 let file = file.syntax();
53 for reference in references { 53 for (r_range, _) in references {
54 let token = file.token_at_offset(reference.range.start()).next()?; 54 let token = file.token_at_offset(r_range.start()).next()?;
55 let token = sema.descend_into_macros(token); 55 let token = sema.descend_into_macros(token);
56 let syntax = token.parent(); 56 let syntax = token.parent();
57 57
@@ -61,7 +61,7 @@ pub(crate) fn incoming_calls(db: &RootDatabase, position: FilePosition) -> Optio
61 let def = sema.to_def(&fn_)?; 61 let def = sema.to_def(&fn_)?;
62 def.try_to_nav(sema.db) 62 def.try_to_nav(sema.db)
63 }) { 63 }) {
64 let relative_range = reference.range; 64 let relative_range = r_range;
65 calls.add(&nav, relative_range); 65 calls.add(&nav, relative_range);
66 } 66 }
67 } 67 }
diff --git a/crates/ide/src/diagnostics.rs b/crates/ide/src/diagnostics.rs
index b35bc2bae..fe32f39b6 100644
--- a/crates/ide/src/diagnostics.rs
+++ b/crates/ide/src/diagnostics.rs
@@ -10,15 +10,16 @@ mod field_shorthand;
10use std::cell::RefCell; 10use std::cell::RefCell;
11 11
12use hir::{ 12use hir::{
13 db::AstDatabase,
13 diagnostics::{Diagnostic as _, DiagnosticCode, DiagnosticSinkBuilder}, 14 diagnostics::{Diagnostic as _, DiagnosticCode, DiagnosticSinkBuilder},
14 Semantics, 15 InFile, Semantics,
15}; 16};
16use ide_db::{base_db::SourceDatabase, RootDatabase}; 17use ide_db::{base_db::SourceDatabase, RootDatabase};
17use itertools::Itertools; 18use itertools::Itertools;
18use rustc_hash::FxHashSet; 19use rustc_hash::FxHashSet;
19use syntax::{ 20use syntax::{
20 ast::{self, AstNode}, 21 ast::{self, AstNode},
21 SyntaxNode, TextRange, 22 SyntaxNode, SyntaxNodePtr, TextRange,
22}; 23};
23use text_edit::TextEdit; 24use text_edit::TextEdit;
24 25
@@ -136,6 +137,9 @@ pub(crate) fn diagnostics(
136 .on::<hir::diagnostics::IncorrectCase, _>(|d| { 137 .on::<hir::diagnostics::IncorrectCase, _>(|d| {
137 res.borrow_mut().push(warning_with_fix(d, &sema)); 138 res.borrow_mut().push(warning_with_fix(d, &sema));
138 }) 139 })
140 .on::<hir::diagnostics::ReplaceFilterMapNextWithFindMap, _>(|d| {
141 res.borrow_mut().push(warning_with_fix(d, &sema));
142 })
139 .on::<hir::diagnostics::InactiveCode, _>(|d| { 143 .on::<hir::diagnostics::InactiveCode, _>(|d| {
140 // If there's inactive code somewhere in a macro, don't propagate to the call-site. 144 // If there's inactive code somewhere in a macro, don't propagate to the call-site.
141 if d.display_source().file_id.expansion_info(db).is_some() { 145 if d.display_source().file_id.expansion_info(db).is_some() {
@@ -144,20 +148,38 @@ pub(crate) fn diagnostics(
144 148
145 // Override severity and mark as unused. 149 // Override severity and mark as unused.
146 res.borrow_mut().push( 150 res.borrow_mut().push(
147 Diagnostic::hint(sema.diagnostics_display_range(d).range, d.message()) 151 Diagnostic::hint(
148 .with_unused(true) 152 sema.diagnostics_display_range(d.display_source()).range,
149 .with_code(Some(d.code())), 153 d.message(),
154 )
155 .with_unused(true)
156 .with_code(Some(d.code())),
150 ); 157 );
151 }) 158 })
152 .on::<hir::diagnostics::UnresolvedProcMacro, _>(|d| { 159 .on::<hir::diagnostics::UnresolvedProcMacro, _>(|d| {
153 // Use more accurate position if available. 160 // Use more accurate position if available.
154 let display_range = 161 let display_range = d
155 d.precise_location.unwrap_or_else(|| sema.diagnostics_display_range(d).range); 162 .precise_location
163 .unwrap_or_else(|| sema.diagnostics_display_range(d.display_source()).range);
156 164
157 // FIXME: it would be nice to tell the user whether proc macros are currently disabled 165 // FIXME: it would be nice to tell the user whether proc macros are currently disabled
158 res.borrow_mut() 166 res.borrow_mut()
159 .push(Diagnostic::hint(display_range, d.message()).with_code(Some(d.code()))); 167 .push(Diagnostic::hint(display_range, d.message()).with_code(Some(d.code())));
160 }) 168 })
169 .on::<hir::diagnostics::UnresolvedMacroCall, _>(|d| {
170 let last_path_segment = sema.db.parse_or_expand(d.file).and_then(|root| {
171 d.node
172 .to_node(&root)
173 .path()
174 .and_then(|it| it.segment())
175 .and_then(|it| it.name_ref())
176 .map(|it| InFile::new(d.file, SyntaxNodePtr::new(it.syntax())))
177 });
178 let diagnostics = last_path_segment.unwrap_or_else(|| d.display_source());
179 let display_range = sema.diagnostics_display_range(diagnostics).range;
180 res.borrow_mut()
181 .push(Diagnostic::error(display_range, d.message()).with_code(Some(d.code())));
182 })
161 // Only collect experimental diagnostics when they're enabled. 183 // Only collect experimental diagnostics when they're enabled.
162 .filter(|diag| !(diag.is_experimental() && config.disable_experimental)) 184 .filter(|diag| !(diag.is_experimental() && config.disable_experimental))
163 .filter(|diag| !config.disabled.contains(diag.code().as_str())); 185 .filter(|diag| !config.disabled.contains(diag.code().as_str()));
@@ -167,8 +189,11 @@ pub(crate) fn diagnostics(
167 // Diagnostics not handled above get no fix and default treatment. 189 // Diagnostics not handled above get no fix and default treatment.
168 .build(|d| { 190 .build(|d| {
169 res.borrow_mut().push( 191 res.borrow_mut().push(
170 Diagnostic::error(sema.diagnostics_display_range(d).range, d.message()) 192 Diagnostic::error(
171 .with_code(Some(d.code())), 193 sema.diagnostics_display_range(d.display_source()).range,
194 d.message(),
195 )
196 .with_code(Some(d.code())),
172 ); 197 );
173 }); 198 });
174 199
@@ -180,13 +205,13 @@ pub(crate) fn diagnostics(
180} 205}
181 206
182fn diagnostic_with_fix<D: DiagnosticWithFix>(d: &D, sema: &Semantics<RootDatabase>) -> Diagnostic { 207fn diagnostic_with_fix<D: DiagnosticWithFix>(d: &D, sema: &Semantics<RootDatabase>) -> Diagnostic {
183 Diagnostic::error(sema.diagnostics_display_range(d).range, d.message()) 208 Diagnostic::error(sema.diagnostics_display_range(d.display_source()).range, d.message())
184 .with_fix(d.fix(&sema)) 209 .with_fix(d.fix(&sema))
185 .with_code(Some(d.code())) 210 .with_code(Some(d.code()))
186} 211}
187 212
188fn warning_with_fix<D: DiagnosticWithFix>(d: &D, sema: &Semantics<RootDatabase>) -> Diagnostic { 213fn warning_with_fix<D: DiagnosticWithFix>(d: &D, sema: &Semantics<RootDatabase>) -> Diagnostic {
189 Diagnostic::hint(sema.diagnostics_display_range(d).range, d.message()) 214 Diagnostic::hint(sema.diagnostics_display_range(d.display_source()).range, d.message())
190 .with_fix(d.fix(&sema)) 215 .with_fix(d.fix(&sema))
191 .with_code(Some(d.code())) 216 .with_code(Some(d.code()))
192} 217}
@@ -643,6 +668,29 @@ fn test_fn() {
643 } 668 }
644 669
645 #[test] 670 #[test]
671 fn test_unresolved_macro_range() {
672 check_expect(
673 r#"foo::bar!(92);"#,
674 expect![[r#"
675 [
676 Diagnostic {
677 message: "unresolved macro call",
678 range: 5..8,
679 severity: Error,
680 fix: None,
681 unused: false,
682 code: Some(
683 DiagnosticCode(
684 "unresolved-macro-call",
685 ),
686 ),
687 },
688 ]
689 "#]],
690 );
691 }
692
693 #[test]
646 fn range_mapping_out_of_macros() { 694 fn range_mapping_out_of_macros() {
647 // FIXME: this is very wrong, but somewhat tricky to fix. 695 // FIXME: this is very wrong, but somewhat tricky to fix.
648 check_fix( 696 check_fix(
diff --git a/crates/ide/src/diagnostics/fixes.rs b/crates/ide/src/diagnostics/fixes.rs
index e4335119b..cbfc66ab3 100644
--- a/crates/ide/src/diagnostics/fixes.rs
+++ b/crates/ide/src/diagnostics/fixes.rs
@@ -4,7 +4,7 @@ use hir::{
4 db::AstDatabase, 4 db::AstDatabase,
5 diagnostics::{ 5 diagnostics::{
6 Diagnostic, IncorrectCase, MissingFields, MissingOkOrSomeInTailExpr, NoSuchField, 6 Diagnostic, IncorrectCase, MissingFields, MissingOkOrSomeInTailExpr, NoSuchField,
7 RemoveThisSemicolon, UnresolvedModule, 7 RemoveThisSemicolon, ReplaceFilterMapNextWithFindMap, UnresolvedModule,
8 }, 8 },
9 HasSource, HirDisplay, InFile, Semantics, VariantDef, 9 HasSource, HirDisplay, InFile, Semantics, VariantDef,
10}; 10};
@@ -15,8 +15,8 @@ use ide_db::{
15}; 15};
16use syntax::{ 16use syntax::{
17 algo, 17 algo,
18 ast::{self, edit::IndentLevel, make}, 18 ast::{self, edit::IndentLevel, make, ArgListOwner},
19 AstNode, 19 AstNode, TextRange,
20}; 20};
21use text_edit::TextEdit; 21use text_edit::TextEdit;
22 22
@@ -140,7 +140,34 @@ impl DiagnosticWithFix for IncorrectCase {
140 rename_with_semantics(sema, file_position, &self.suggested_text).ok()?; 140 rename_with_semantics(sema, file_position, &self.suggested_text).ok()?;
141 141
142 let label = format!("Rename to {}", self.suggested_text); 142 let label = format!("Rename to {}", self.suggested_text);
143 Some(Fix::new(&label, rename_changes.info, rename_changes.range)) 143 Some(Fix::new(&label, rename_changes, frange.range))
144 }
145}
146
147impl DiagnosticWithFix for ReplaceFilterMapNextWithFindMap {
148 fn fix(&self, sema: &Semantics<RootDatabase>) -> Option<Fix> {
149 let root = sema.db.parse_or_expand(self.file)?;
150 let next_expr = self.next_expr.to_node(&root);
151 let next_call = ast::MethodCallExpr::cast(next_expr.syntax().clone())?;
152
153 let filter_map_call = ast::MethodCallExpr::cast(next_call.receiver()?.syntax().clone())?;
154 let filter_map_name_range = filter_map_call.name_ref()?.ident_token()?.text_range();
155 let filter_map_args = filter_map_call.arg_list()?;
156
157 let range_to_replace =
158 TextRange::new(filter_map_name_range.start(), next_expr.syntax().text_range().end());
159 let replacement = format!("find_map{}", filter_map_args.syntax().text());
160 let trigger_range = next_expr.syntax().text_range();
161
162 let edit = TextEdit::replace(range_to_replace, replacement);
163
164 let source_change = SourceChange::from_text_edit(self.file.original_file(sema.db), edit);
165
166 Some(Fix::new(
167 "Replace filter_map(..).next() with find_map()",
168 source_change,
169 trigger_range,
170 ))
144 } 171 }
145} 172}
146 173
diff --git a/crates/ide/src/display/navigation_target.rs b/crates/ide/src/display/navigation_target.rs
index 00e601244..198243466 100644
--- a/crates/ide/src/display/navigation_target.rs
+++ b/crates/ide/src/display/navigation_target.rs
@@ -7,6 +7,7 @@ use hir::{AssocItem, Documentation, FieldSource, HasAttrs, HasSource, InFile, Mo
7use ide_db::{ 7use ide_db::{
8 base_db::{FileId, FileRange, SourceDatabase}, 8 base_db::{FileId, FileRange, SourceDatabase},
9 symbol_index::FileSymbolKind, 9 symbol_index::FileSymbolKind,
10 SymbolKind,
10}; 11};
11use ide_db::{defs::Definition, RootDatabase}; 12use ide_db::{defs::Definition, RootDatabase};
12use syntax::{ 13use syntax::{
@@ -18,30 +19,6 @@ use crate::FileSymbol;
18 19
19use super::short_label::ShortLabel; 20use super::short_label::ShortLabel;
20 21
21#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
22pub enum SymbolKind {
23 Module,
24 Impl,
25 Field,
26 TypeParam,
27 ConstParam,
28 LifetimeParam,
29 ValueParam,
30 SelfParam,
31 Local,
32 Label,
33 Function,
34 Const,
35 Static,
36 Struct,
37 Enum,
38 Variant,
39 Union,
40 TypeAlias,
41 Trait,
42 Macro,
43}
44
45/// `NavigationTarget` represents and element in the editor's UI which you can 22/// `NavigationTarget` represents and element in the editor's UI which you can
46/// click on to navigate to a particular piece of code. 23/// click on to navigate to a particular piece of code.
47/// 24///
@@ -108,12 +85,16 @@ impl NavigationTarget {
108 let name = module.name(db).map(|it| it.to_string().into()).unwrap_or_default(); 85 let name = module.name(db).map(|it| it.to_string().into()).unwrap_or_default();
109 if let Some(src) = module.declaration_source(db) { 86 if let Some(src) = module.declaration_source(db) {
110 let node = src.as_ref().map(|it| it.syntax()); 87 let node = src.as_ref().map(|it| it.syntax());
111 let frange = node.original_file_range(db); 88 let full_range = node.original_file_range(db);
89 let focus_range = src
90 .value
91 .name()
92 .map(|name| src.with_value(name.syntax()).original_file_range(db).range);
112 let mut res = NavigationTarget::from_syntax( 93 let mut res = NavigationTarget::from_syntax(
113 frange.file_id, 94 full_range.file_id,
114 name, 95 name,
115 None, 96 focus_range,
116 frange.range, 97 full_range.range,
117 SymbolKind::Module, 98 SymbolKind::Module,
118 ); 99 );
119 res.docs = module.attrs(db).docs(); 100 res.docs = module.attrs(db).docs();
@@ -153,8 +134,7 @@ impl NavigationTarget {
153 node: InFile<&dyn ast::NameOwner>, 134 node: InFile<&dyn ast::NameOwner>,
154 kind: SymbolKind, 135 kind: SymbolKind,
155 ) -> NavigationTarget { 136 ) -> NavigationTarget {
156 let name = 137 let name = node.value.name().map(|it| it.text().into()).unwrap_or_else(|| "_".into());
157 node.value.name().map(|it| it.text().clone()).unwrap_or_else(|| SmolStr::new("_"));
158 let focus_range = 138 let focus_range =
159 node.value.name().map(|it| node.with_value(it.syntax()).original_file_range(db).range); 139 node.value.name().map(|it| node.with_value(it.syntax()).original_file_range(db).range);
160 let frange = node.map(|it| it.syntax()).original_file_range(db); 140 let frange = node.map(|it| it.syntax()).original_file_range(db);
@@ -197,6 +177,7 @@ impl ToNav for FileSymbol {
197 FileSymbolKind::Const => SymbolKind::Const, 177 FileSymbolKind::Const => SymbolKind::Const,
198 FileSymbolKind::Static => SymbolKind::Static, 178 FileSymbolKind::Static => SymbolKind::Static,
199 FileSymbolKind::Macro => SymbolKind::Macro, 179 FileSymbolKind::Macro => SymbolKind::Macro,
180 FileSymbolKind::Union => SymbolKind::Union,
200 }), 181 }),
201 full_range: self.range, 182 full_range: self.range,
202 focus_range: self.name_range, 183 focus_range: self.name_range,
@@ -295,6 +276,7 @@ impl ToNav for hir::Module {
295 ModuleSource::Module(node) => { 276 ModuleSource::Module(node) => {
296 (node.syntax(), node.name().map(|it| it.syntax().text_range())) 277 (node.syntax(), node.name().map(|it| it.syntax().text_range()))
297 } 278 }
279 ModuleSource::BlockExpr(node) => (node.syntax(), None),
298 }; 280 };
299 let frange = src.with_value(syntax).original_file_range(db); 281 let frange = src.with_value(syntax).original_file_range(db);
300 NavigationTarget::from_syntax(frange.file_id, name, focus, frange.range, SymbolKind::Module) 282 NavigationTarget::from_syntax(frange.file_id, name, focus, frange.range, SymbolKind::Module)
@@ -457,13 +439,16 @@ impl TryToNav for hir::TypeParam {
457 fn try_to_nav(&self, db: &RootDatabase) -> Option<NavigationTarget> { 439 fn try_to_nav(&self, db: &RootDatabase) -> Option<NavigationTarget> {
458 let src = self.source(db)?; 440 let src = self.source(db)?;
459 let full_range = match &src.value { 441 let full_range = match &src.value {
460 Either::Left(it) => it.syntax().text_range(), 442 Either::Left(it) => it
443 .name()
444 .map_or_else(|| it.syntax().text_range(), |name| name.syntax().text_range()),
461 Either::Right(it) => it.syntax().text_range(), 445 Either::Right(it) => it.syntax().text_range(),
462 }; 446 };
463 let focus_range = match &src.value { 447 let focus_range = match &src.value {
464 Either::Left(_) => None, 448 Either::Left(it) => it.name(),
465 Either::Right(it) => it.name().map(|it| it.syntax().text_range()), 449 Either::Right(it) => it.name(),
466 }; 450 }
451 .map(|it| it.syntax().text_range());
467 Some(NavigationTarget { 452 Some(NavigationTarget {
468 file_id: src.file_id.original_file(db), 453 file_id: src.file_id.original_file(db),
469 name: self.name(db).to_string().into(), 454 name: self.name(db).to_string().into(),
diff --git a/crates/ide/src/display/short_label.rs b/crates/ide/src/display/short_label.rs
index 990f740b8..84b8883de 100644
--- a/crates/ide/src/display/short_label.rs
+++ b/crates/ide/src/display/short_label.rs
@@ -53,9 +53,19 @@ impl ShortLabel for ast::SourceFile {
53 } 53 }
54} 54}
55 55
56impl ShortLabel for ast::BlockExpr {
57 fn short_label(&self) -> Option<String> {
58 None
59 }
60}
61
56impl ShortLabel for ast::TypeAlias { 62impl ShortLabel for ast::TypeAlias {
57 fn short_label(&self) -> Option<String> { 63 fn short_label(&self) -> Option<String> {
58 short_label_from_node(self, "type ") 64 let mut buf = short_label_from_node(self, "type ")?;
65 if let Some(type_ref) = self.ty() {
66 format_to!(buf, " = {}", type_ref.syntax());
67 }
68 Some(buf)
59 } 69 }
60} 70}
61 71
@@ -90,7 +100,7 @@ impl ShortLabel for ast::Variant {
90impl ShortLabel for ast::ConstParam { 100impl ShortLabel for ast::ConstParam {
91 fn short_label(&self) -> Option<String> { 101 fn short_label(&self) -> Option<String> {
92 let mut buf = "const ".to_owned(); 102 let mut buf = "const ".to_owned();
93 buf.push_str(self.name()?.text().as_str()); 103 buf.push_str(self.name()?.text());
94 if let Some(type_ref) = self.ty() { 104 if let Some(type_ref) = self.ty() {
95 format_to!(buf, ": {}", type_ref.syntax()); 105 format_to!(buf, ": {}", type_ref.syntax());
96 } 106 }
@@ -117,6 +127,6 @@ where
117{ 127{
118 let mut buf = node.visibility().map(|v| format!("{} ", v.syntax())).unwrap_or_default(); 128 let mut buf = node.visibility().map(|v| format!("{} ", v.syntax())).unwrap_or_default();
119 buf.push_str(label); 129 buf.push_str(label);
120 buf.push_str(node.name()?.text().as_str()); 130 buf.push_str(node.name()?.text());
121 Some(buf) 131 Some(buf)
122} 132}
diff --git a/crates/ide/src/doc_links.rs b/crates/ide/src/doc_links.rs
index de10406bc..7bdd3cca3 100644
--- a/crates/ide/src/doc_links.rs
+++ b/crates/ide/src/doc_links.rs
@@ -9,8 +9,7 @@ use url::Url;
9 9
10use hir::{ 10use hir::{
11 db::{DefDatabase, HirDatabase}, 11 db::{DefDatabase, HirDatabase},
12 Adt, AsAssocItem, AsName, AssocItem, AssocItemContainer, Crate, Field, HasAttrs, ItemInNs, 12 Adt, AsAssocItem, AssocItem, AssocItemContainer, Crate, Field, HasAttrs, ItemInNs, ModuleDef,
13 ModuleDef,
14}; 13};
15use ide_db::{ 14use ide_db::{
16 defs::{Definition, NameClass, NameRefClass}, 15 defs::{Definition, NameClass, NameRefClass},
@@ -221,14 +220,31 @@ fn rewrite_intra_doc_link(
221 }?; 220 }?;
222 let krate = resolved.module(db)?.krate(); 221 let krate = resolved.module(db)?.krate();
223 let canonical_path = resolved.canonical_path(db)?; 222 let canonical_path = resolved.canonical_path(db)?;
224 let new_target = get_doc_url(db, &krate)? 223 let mut new_url = get_doc_url(db, &krate)?
225 .join(&format!("{}/", krate.display_name(db)?)) 224 .join(&format!("{}/", krate.display_name(db)?))
226 .ok()? 225 .ok()?
227 .join(&canonical_path.replace("::", "/")) 226 .join(&canonical_path.replace("::", "/"))
228 .ok()? 227 .ok()?
229 .join(&get_symbol_filename(db, &resolved)?) 228 .join(&get_symbol_filename(db, &resolved)?)
230 .ok()? 229 .ok()?;
231 .into_string(); 230
231 if let ModuleDef::Trait(t) = resolved {
232 let items = t.items(db);
233 if let Some(field_or_assoc_item) = items.iter().find_map(|assoc_item| {
234 if let Some(name) = assoc_item.name(db) {
235 if *link == format!("{}::{}", canonical_path, name) {
236 return Some(FieldOrAssocItem::AssocItem(*assoc_item));
237 }
238 }
239 None
240 }) {
241 if let Some(fragment) = get_symbol_fragment(db, &field_or_assoc_item) {
242 new_url = new_url.join(&fragment).ok()?;
243 }
244 };
245 }
246
247 let new_target = new_url.into_string();
232 let new_title = strip_prefixes_suffixes(title); 248 let new_title = strip_prefixes_suffixes(title);
233 Some((new_target, new_title.to_string())) 249 Some((new_target, new_title.to_string()))
234} 250}
@@ -412,7 +428,7 @@ fn get_symbol_filename(db: &dyn HirDatabase, definition: &ModuleDef) -> Option<S
412 ModuleDef::Module(_) => "index.html".to_string(), 428 ModuleDef::Module(_) => "index.html".to_string(),
413 ModuleDef::Trait(t) => format!("trait.{}.html", t.name(db)), 429 ModuleDef::Trait(t) => format!("trait.{}.html", t.name(db)),
414 ModuleDef::TypeAlias(t) => format!("type.{}.html", t.name(db)), 430 ModuleDef::TypeAlias(t) => format!("type.{}.html", t.name(db)),
415 ModuleDef::BuiltinType(t) => format!("primitive.{}.html", t.as_name()), 431 ModuleDef::BuiltinType(t) => format!("primitive.{}.html", t.name()),
416 ModuleDef::Function(f) => format!("fn.{}.html", f.name(db)), 432 ModuleDef::Function(f) => format!("fn.{}.html", f.name(db)),
417 ModuleDef::Variant(ev) => { 433 ModuleDef::Variant(ev) => {
418 format!("enum.{}.html#variant.{}", ev.parent_enum(db).name(db), ev.name(db)) 434 format!("enum.{}.html#variant.{}", ev.parent_enum(db).name(db), ev.name(db))
@@ -438,10 +454,10 @@ fn get_symbol_fragment(db: &dyn HirDatabase, field_or_assoc: &FieldOrAssocItem)
438 FieldOrAssocItem::Field(field) => format!("#structfield.{}", field.name(db)), 454 FieldOrAssocItem::Field(field) => format!("#structfield.{}", field.name(db)),
439 FieldOrAssocItem::AssocItem(assoc) => match assoc { 455 FieldOrAssocItem::AssocItem(assoc) => match assoc {
440 AssocItem::Function(function) => { 456 AssocItem::Function(function) => {
441 let is_trait_method = matches!( 457 let is_trait_method = function
442 function.as_assoc_item(db).map(|assoc| assoc.container(db)), 458 .as_assoc_item(db)
443 Some(AssocItemContainer::Trait(..)) 459 .and_then(|assoc| assoc.containing_trait(db))
444 ); 460 .is_some();
445 // This distinction may get more complicated when specialization is available. 461 // This distinction may get more complicated when specialization is available.
446 // Rustdoc makes this decision based on whether a method 'has defaultness'. 462 // Rustdoc makes this decision based on whether a method 'has defaultness'.
447 // Currently this is only the case for provided trait methods. 463 // Currently this is only the case for provided trait methods.
diff --git a/crates/ide/src/extend_selection.rs b/crates/ide/src/extend_selection.rs
index 17a540972..b540d04fe 100644
--- a/crates/ide/src/extend_selection.rs
+++ b/crates/ide/src/extend_selection.rs
@@ -12,15 +12,17 @@ use syntax::{
12 12
13use crate::FileRange; 13use crate::FileRange;
14 14
15// Feature: Extend Selection 15// Feature: Expand and Shrink Selection
16// 16//
17// Extends the current selection to the encompassing syntactic construct 17// Extends or shrinks the current selection to the encompassing syntactic construct
18// (expression, statement, item, module, etc). It works with multiple cursors. 18// (expression, statement, item, module, etc). It works with multiple cursors.
19// 19//
20// This is a standard LSP feature and not a protocol extension.
21//
20// |=== 22// |===
21// | Editor | Shortcut 23// | Editor | Shortcut
22// 24//
23// | VS Code | kbd:[Ctrl+Shift+→] 25// | VS Code | kbd:[Alt+Shift+→], kbd:[Alt+Shift+←]
24// |=== 26// |===
25pub(crate) fn extend_selection(db: &RootDatabase, frange: FileRange) -> TextRange { 27pub(crate) fn extend_selection(db: &RootDatabase, frange: FileRange) -> TextRange {
26 let sema = Semantics::new(db); 28 let sema = Semantics::new(db);
@@ -213,8 +215,8 @@ fn extend_ws(root: &SyntaxNode, ws: SyntaxToken, offset: TextSize) -> TextRange
213 let ws_text = ws.text(); 215 let ws_text = ws.text();
214 let suffix = TextRange::new(offset, ws.text_range().end()) - ws.text_range().start(); 216 let suffix = TextRange::new(offset, ws.text_range().end()) - ws.text_range().start();
215 let prefix = TextRange::new(ws.text_range().start(), offset) - ws.text_range().start(); 217 let prefix = TextRange::new(ws.text_range().start(), offset) - ws.text_range().start();
216 let ws_suffix = &ws_text.as_str()[suffix]; 218 let ws_suffix = &ws_text[suffix];
217 let ws_prefix = &ws_text.as_str()[prefix]; 219 let ws_prefix = &ws_text[prefix];
218 if ws_text.contains('\n') && !ws_suffix.contains('\n') { 220 if ws_text.contains('\n') && !ws_suffix.contains('\n') {
219 if let Some(node) = ws.next_sibling_or_token() { 221 if let Some(node) = ws.next_sibling_or_token() {
220 let start = match ws_prefix.rfind('\n') { 222 let start = match ws_prefix.rfind('\n') {
diff --git a/crates/ide/src/file_structure.rs b/crates/ide/src/file_structure.rs
index 32556dad3..26793bdb4 100644
--- a/crates/ide/src/file_structure.rs
+++ b/crates/ide/src/file_structure.rs
@@ -1,10 +1,9 @@
1use ide_db::SymbolKind;
1use syntax::{ 2use syntax::{
2 ast::{self, AttrsOwner, GenericParamsOwner, NameOwner}, 3 ast::{self, AttrsOwner, GenericParamsOwner, NameOwner},
3 match_ast, AstNode, SourceFile, SyntaxNode, TextRange, WalkEvent, 4 match_ast, AstNode, SourceFile, SyntaxNode, TextRange, WalkEvent,
4}; 5};
5 6
6use crate::SymbolKind;
7
8#[derive(Debug, Clone)] 7#[derive(Debug, Clone)]
9pub struct StructureNode { 8pub struct StructureNode {
10 pub parent: Option<usize>, 9 pub parent: Option<usize>,
diff --git a/crates/ide/src/fixture.rs b/crates/ide/src/fixture.rs
index cc8218885..cc6641ba1 100644
--- a/crates/ide/src/fixture.rs
+++ b/crates/ide/src/fixture.rs
@@ -1,5 +1,6 @@
1//! Utilities for creating `Analysis` instances for tests. 1//! Utilities for creating `Analysis` instances for tests.
2use ide_db::base_db::fixture::ChangeFixture; 2use ide_db::base_db::fixture::ChangeFixture;
3use syntax::{TextRange, TextSize};
3use test_utils::{extract_annotations, RangeOrOffset}; 4use test_utils::{extract_annotations, RangeOrOffset};
4 5
5use crate::{Analysis, AnalysisHost, FileId, FilePosition, FileRange}; 6use crate::{Analysis, AnalysisHost, FileId, FilePosition, FileRange};
@@ -68,3 +69,18 @@ pub(crate) fn annotations(ra_fixture: &str) -> (Analysis, FilePosition, Vec<(Fil
68 .collect(); 69 .collect();
69 (host.analysis(), FilePosition { file_id, offset }, annotations) 70 (host.analysis(), FilePosition { file_id, offset }, annotations)
70} 71}
72
73pub(crate) fn nav_target_annotation(ra_fixture: &str) -> (Analysis, FilePosition, FileRange) {
74 let (analysis, position, mut annotations) = annotations(ra_fixture);
75 let (mut expected, data) = annotations.pop().unwrap();
76 assert!(annotations.is_empty());
77 match data.as_str() {
78 "" => (),
79 "file" => {
80 expected.range =
81 TextRange::up_to(TextSize::of(&*analysis.file_text(expected.file_id).unwrap()))
82 }
83 data => panic!("bad data: {}", data),
84 }
85 (analysis, position, expected)
86}
diff --git a/crates/ide/src/fn_references.rs b/crates/ide/src/fn_references.rs
index f6e5a522b..1a99a1f37 100644
--- a/crates/ide/src/fn_references.rs
+++ b/crates/ide/src/fn_references.rs
@@ -1,8 +1,8 @@
1//! This module implements a methods and free functions search in the specified file. 1//! This module implements a methods and free functions search in the specified file.
2//! We have to skip tests, so cannot reuse file_structure module. 2//! We have to skip tests, so cannot reuse file_structure module.
3 3
4use assists::utils::test_related_attribute;
5use hir::Semantics; 4use hir::Semantics;
5use ide_assists::utils::test_related_attribute;
6use ide_db::RootDatabase; 6use ide_db::RootDatabase;
7use syntax::{ast, ast::NameOwner, AstNode, SyntaxNode}; 7use syntax::{ast, ast::NameOwner, AstNode, SyntaxNode};
8 8
diff --git a/crates/ide/src/goto_definition.rs b/crates/ide/src/goto_definition.rs
index a1d2bce1d..abed1969e 100644
--- a/crates/ide/src/goto_definition.rs
+++ b/crates/ide/src/goto_definition.rs
@@ -2,16 +2,14 @@ use either::Either;
2use hir::{HasAttrs, ModuleDef, Semantics}; 2use hir::{HasAttrs, ModuleDef, Semantics};
3use ide_db::{ 3use ide_db::{
4 defs::{Definition, NameClass, NameRefClass}, 4 defs::{Definition, NameClass, NameRefClass},
5 symbol_index, RootDatabase, 5 RootDatabase,
6}; 6};
7use syntax::{ 7use syntax::{
8 ast, match_ast, AstNode, AstToken, SyntaxKind::*, SyntaxToken, TextSize, TokenAtOffset, T, 8 ast, match_ast, AstNode, AstToken, SyntaxKind::*, SyntaxToken, TextSize, TokenAtOffset, T,
9}; 9};
10 10
11use crate::{ 11use crate::{
12 display::{ToNav, TryToNav}, 12 display::TryToNav, doc_links::extract_definitions_from_markdown, runnables::doc_owner_to_def,
13 doc_links::extract_definitions_from_markdown,
14 runnables::doc_owner_to_def,
15 FilePosition, NavigationTarget, RangeInfo, 13 FilePosition, NavigationTarget, RangeInfo,
16}; 14};
17 15
@@ -33,33 +31,31 @@ pub(crate) fn goto_definition(
33 let original_token = pick_best(file.token_at_offset(position.offset))?; 31 let original_token = pick_best(file.token_at_offset(position.offset))?;
34 let token = sema.descend_into_macros(original_token.clone()); 32 let token = sema.descend_into_macros(original_token.clone());
35 let parent = token.parent(); 33 let parent = token.parent();
36 if let Some(comment) = ast::Comment::cast(token.clone()) { 34 if let Some(comment) = ast::Comment::cast(token) {
37 let nav = def_for_doc_comment(&sema, position, &comment)?.try_to_nav(db)?; 35 let nav = def_for_doc_comment(&sema, position, &comment)?.try_to_nav(db)?;
38 return Some(RangeInfo::new(original_token.text_range(), vec![nav])); 36 return Some(RangeInfo::new(original_token.text_range(), vec![nav]));
39 } 37 }
40 38
41 let nav_targets = match_ast! { 39 let nav = match_ast! {
42 match parent { 40 match parent {
43 ast::NameRef(name_ref) => { 41 ast::NameRef(name_ref) => {
44 reference_definition(&sema, Either::Right(&name_ref)).to_vec() 42 reference_definition(&sema, Either::Right(&name_ref))
45 }, 43 },
46 ast::Name(name) => { 44 ast::Name(name) => {
47 let def = NameClass::classify(&sema, &name)?.referenced_or_defined(sema.db); 45 let def = NameClass::classify(&sema, &name)?.referenced_or_defined(sema.db);
48 let nav = def.try_to_nav(sema.db)?; 46 def.try_to_nav(sema.db)
49 vec![nav]
50 }, 47 },
51 ast::Lifetime(lt) => if let Some(name_class) = NameClass::classify_lifetime(&sema, &lt) { 48 ast::Lifetime(lt) => if let Some(name_class) = NameClass::classify_lifetime(&sema, &lt) {
52 let def = name_class.referenced_or_defined(sema.db); 49 let def = name_class.referenced_or_defined(sema.db);
53 let nav = def.try_to_nav(sema.db)?; 50 def.try_to_nav(sema.db)
54 vec![nav]
55 } else { 51 } else {
56 reference_definition(&sema, Either::Left(&lt)).to_vec() 52 reference_definition(&sema, Either::Left(&lt))
57 }, 53 },
58 _ => return None, 54 _ => return None,
59 } 55 }
60 }; 56 };
61 57
62 Some(RangeInfo::new(original_token.text_range(), nav_targets)) 58 Some(RangeInfo::new(original_token.text_range(), nav.into_iter().collect()))
63} 59}
64 60
65fn def_for_doc_comment( 61fn def_for_doc_comment(
@@ -120,63 +116,26 @@ fn pick_best(tokens: TokenAtOffset<SyntaxToken>) -> Option<SyntaxToken> {
120 } 116 }
121} 117}
122 118
123#[derive(Debug)]
124pub(crate) enum ReferenceResult {
125 Exact(NavigationTarget),
126 Approximate(Vec<NavigationTarget>),
127}
128
129impl ReferenceResult {
130 fn to_vec(self) -> Vec<NavigationTarget> {
131 match self {
132 ReferenceResult::Exact(target) => vec![target],
133 ReferenceResult::Approximate(vec) => vec,
134 }
135 }
136}
137
138pub(crate) fn reference_definition( 119pub(crate) fn reference_definition(
139 sema: &Semantics<RootDatabase>, 120 sema: &Semantics<RootDatabase>,
140 name_ref: Either<&ast::Lifetime, &ast::NameRef>, 121 name_ref: Either<&ast::Lifetime, &ast::NameRef>,
141) -> ReferenceResult { 122) -> Option<NavigationTarget> {
142 let name_kind = name_ref.either( 123 let name_kind = name_ref.either(
143 |lifetime| NameRefClass::classify_lifetime(sema, lifetime), 124 |lifetime| NameRefClass::classify_lifetime(sema, lifetime),
144 |name_ref| NameRefClass::classify(sema, name_ref), 125 |name_ref| NameRefClass::classify(sema, name_ref),
145 ); 126 )?;
146 if let Some(def) = name_kind { 127 let def = name_kind.referenced(sema.db);
147 let def = def.referenced(sema.db); 128 def.try_to_nav(sema.db)
148 return match def.try_to_nav(sema.db) {
149 Some(nav) => ReferenceResult::Exact(nav),
150 None => ReferenceResult::Approximate(Vec::new()),
151 };
152 }
153
154 // Fallback index based approach:
155 let name = name_ref.either(ast::Lifetime::text, ast::NameRef::text);
156 let navs =
157 symbol_index::index_resolve(sema.db, name).into_iter().map(|s| s.to_nav(sema.db)).collect();
158 ReferenceResult::Approximate(navs)
159} 129}
160 130
161#[cfg(test)] 131#[cfg(test)]
162mod tests { 132mod tests {
163 use ide_db::base_db::FileRange; 133 use ide_db::base_db::FileRange;
164 use syntax::{TextRange, TextSize};
165 134
166 use crate::fixture; 135 use crate::fixture;
167 136
168 fn check(ra_fixture: &str) { 137 fn check(ra_fixture: &str) {
169 let (analysis, position, mut annotations) = fixture::annotations(ra_fixture); 138 let (analysis, position, expected) = fixture::nav_target_annotation(ra_fixture);
170 let (mut expected, data) = annotations.pop().unwrap();
171 match data.as_str() {
172 "" => (),
173 "file" => {
174 expected.range =
175 TextRange::up_to(TextSize::of(&*analysis.file_text(expected.file_id).unwrap()))
176 }
177 data => panic!("bad data: {}", data),
178 }
179
180 let mut navs = 139 let mut navs =
181 analysis.goto_definition(position).unwrap().expect("no definition found").info; 140 analysis.goto_definition(position).unwrap().expect("no definition found").info;
182 if navs.len() == 0 { 141 if navs.len() == 0 {
@@ -192,12 +151,12 @@ mod tests {
192 fn goto_def_for_extern_crate() { 151 fn goto_def_for_extern_crate() {
193 check( 152 check(
194 r#" 153 r#"
195 //- /main.rs crate:main deps:std 154//- /main.rs crate:main deps:std
196 extern crate std$0; 155extern crate std$0;
197 //- /std/lib.rs crate:std 156//- /std/lib.rs crate:std
198 // empty 157// empty
199 //^ file 158//^ file
200 "#, 159"#,
201 ) 160 )
202 } 161 }
203 162
@@ -205,12 +164,12 @@ mod tests {
205 fn goto_def_for_renamed_extern_crate() { 164 fn goto_def_for_renamed_extern_crate() {
206 check( 165 check(
207 r#" 166 r#"
208 //- /main.rs crate:main deps:std 167//- /main.rs crate:main deps:std
209 extern crate std as abc$0; 168extern crate std as abc$0;
210 //- /std/lib.rs crate:std 169//- /std/lib.rs crate:std
211 // empty 170// empty
212 //^ file 171//^ file
213 "#, 172"#,
214 ) 173 )
215 } 174 }
216 175
@@ -297,13 +256,13 @@ fn bar() {
297 fn goto_def_for_macros_from_other_crates() { 256 fn goto_def_for_macros_from_other_crates() {
298 check( 257 check(
299 r#" 258 r#"
300//- /lib.rs 259//- /lib.rs crate:main deps:foo
301use foo::foo; 260use foo::foo;
302fn bar() { 261fn bar() {
303 $0foo!(); 262 $0foo!();
304} 263}
305 264
306//- /foo/lib.rs 265//- /foo/lib.rs crate:foo
307#[macro_export] 266#[macro_export]
308macro_rules! foo { () => { () } } 267macro_rules! foo { () => { () } }
309 //^^^ 268 //^^^
@@ -315,10 +274,10 @@ macro_rules! foo { () => { () } }
315 fn goto_def_for_macros_in_use_tree() { 274 fn goto_def_for_macros_in_use_tree() {
316 check( 275 check(
317 r#" 276 r#"
318//- /lib.rs 277//- /lib.rs crate:main deps:foo
319use foo::foo$0; 278use foo::foo$0;
320 279
321//- /foo/lib.rs 280//- /foo/lib.rs crate:foo
322#[macro_export] 281#[macro_export]
323macro_rules! foo { () => { () } } 282macro_rules! foo { () => { () } }
324 //^^^ 283 //^^^
@@ -976,10 +935,10 @@ type Alias<T> = T$0;
976 fn goto_def_for_macro_container() { 935 fn goto_def_for_macro_container() {
977 check( 936 check(
978 r#" 937 r#"
979//- /lib.rs 938//- /lib.rs crate:main deps:foo
980foo::module$0::mac!(); 939foo::module$0::mac!();
981 940
982//- /foo/lib.rs 941//- /foo/lib.rs crate:foo
983pub mod module { 942pub mod module {
984 //^^^^^^ 943 //^^^^^^
985 #[macro_export] 944 #[macro_export]
diff --git a/crates/ide/src/goto_implementation.rs b/crates/ide/src/goto_implementation.rs
index 761a98b2c..3990305fc 100644
--- a/crates/ide/src/goto_implementation.rs
+++ b/crates/ide/src/goto_implementation.rs
@@ -23,7 +23,7 @@ pub(crate) fn goto_implementation(
23 23
24 let krate = sema.to_module_def(position.file_id)?.krate(); 24 let krate = sema.to_module_def(position.file_id)?.krate();
25 25
26 if let Some(nominal_def) = find_node_at_offset::<ast::AdtDef>(&syntax, position.offset) { 26 if let Some(nominal_def) = find_node_at_offset::<ast::Adt>(&syntax, position.offset) {
27 return Some(RangeInfo::new( 27 return Some(RangeInfo::new(
28 nominal_def.syntax().text_range(), 28 nominal_def.syntax().text_range(),
29 impls_for_def(&sema, &nominal_def, krate)?, 29 impls_for_def(&sema, &nominal_def, krate)?,
@@ -40,13 +40,13 @@ pub(crate) fn goto_implementation(
40 40
41fn impls_for_def( 41fn impls_for_def(
42 sema: &Semantics<RootDatabase>, 42 sema: &Semantics<RootDatabase>,
43 node: &ast::AdtDef, 43 node: &ast::Adt,
44 krate: Crate, 44 krate: Crate,
45) -> Option<Vec<NavigationTarget>> { 45) -> Option<Vec<NavigationTarget>> {
46 let ty = match node { 46 let ty = match node {
47 ast::AdtDef::Struct(def) => sema.to_def(def)?.ty(sema.db), 47 ast::Adt::Struct(def) => sema.to_def(def)?.ty(sema.db),
48 ast::AdtDef::Enum(def) => sema.to_def(def)?.ty(sema.db), 48 ast::Adt::Enum(def) => sema.to_def(def)?.ty(sema.db),
49 ast::AdtDef::Union(def) => sema.to_def(def)?.ty(sema.db), 49 ast::Adt::Union(def) => sema.to_def(def)?.ty(sema.db),
50 }; 50 };
51 51
52 let impls = Impl::all_in_crate(sema.db, krate); 52 let impls = Impl::all_in_crate(sema.db, krate);
diff --git a/crates/ide/src/hover.rs b/crates/ide/src/hover.rs
index 2024acd94..20b799490 100644
--- a/crates/ide/src/hover.rs
+++ b/crates/ide/src/hover.rs
@@ -2,8 +2,8 @@ use hir::{
2 Adt, AsAssocItem, AssocItemContainer, FieldSource, GenericParam, HasAttrs, HasSource, 2 Adt, AsAssocItem, AssocItemContainer, FieldSource, GenericParam, HasAttrs, HasSource,
3 HirDisplay, Module, ModuleDef, ModuleSource, Semantics, 3 HirDisplay, Module, ModuleDef, ModuleSource, Semantics,
4}; 4};
5use ide_db::base_db::SourceDatabase;
6use ide_db::{ 5use ide_db::{
6 base_db::SourceDatabase,
7 defs::{Definition, NameClass, NameRefClass}, 7 defs::{Definition, NameClass, NameRefClass},
8 RootDatabase, 8 RootDatabase,
9}; 9};
@@ -94,7 +94,12 @@ pub(crate) fn hover(
94 let node = token.parent(); 94 let node = token.parent();
95 let definition = match_ast! { 95 let definition = match_ast! {
96 match node { 96 match node {
97 ast::Name(name) => NameClass::classify(&sema, &name).and_then(|d| d.defined(sema.db)), 97 // we don't use NameClass::referenced_or_defined here as we do not want to resolve
98 // field pattern shorthands to their definition
99 ast::Name(name) => NameClass::classify(&sema, &name).and_then(|class| match class {
100 NameClass::ConstReference(def) => Some(def),
101 def => def.defined(sema.db),
102 }),
98 ast::NameRef(name_ref) => NameRefClass::classify(&sema, &name_ref).map(|d| d.referenced(sema.db)), 103 ast::NameRef(name_ref) => NameRefClass::classify(&sema, &name_ref).map(|d| d.referenced(sema.db)),
99 ast::Lifetime(lifetime) => NameClass::classify_lifetime(&sema, &lifetime) 104 ast::Lifetime(lifetime) => NameClass::classify_lifetime(&sema, &lifetime)
100 .map_or_else(|| NameRefClass::classify_lifetime(&sema, &lifetime).map(|d| d.referenced(sema.db)), |d| d.defined(sema.db)), 105 .map_or_else(|| NameRefClass::classify_lifetime(&sema, &lifetime).map(|d| d.referenced(sema.db)), |d| d.defined(sema.db)),
@@ -182,12 +187,7 @@ fn runnable_action(
182) -> Option<HoverAction> { 187) -> Option<HoverAction> {
183 match def { 188 match def {
184 Definition::ModuleDef(it) => match it { 189 Definition::ModuleDef(it) => match it {
185 ModuleDef::Module(it) => match it.definition_source(sema.db).value { 190 ModuleDef::Module(it) => runnable_mod(&sema, it).map(|it| HoverAction::Runnable(it)),
186 ModuleSource::Module(it) => {
187 runnable_mod(&sema, it).map(|it| HoverAction::Runnable(it))
188 }
189 _ => None,
190 },
191 ModuleDef::Function(func) => { 191 ModuleDef::Function(func) => {
192 let src = func.source(sema.db)?; 192 let src = func.source(sema.db)?;
193 if src.file_id != file_id.into() { 193 if src.file_id != file_id.into() {
@@ -326,6 +326,7 @@ fn hover_for_definition(db: &RootDatabase, def: Definition) -> Option<Markup> {
326 match it.definition_source(db).value { 326 match it.definition_source(db).value {
327 ModuleSource::Module(it) => it.short_label(), 327 ModuleSource::Module(it) => it.short_label(),
328 ModuleSource::SourceFile(it) => it.short_label(), 328 ModuleSource::SourceFile(it) => it.short_label(),
329 ModuleSource::BlockExpr(it) => it.short_label(),
329 }, 330 },
330 mod_path, 331 mod_path,
331 ), 332 ),
@@ -338,7 +339,7 @@ fn hover_for_definition(db: &RootDatabase, def: Definition) -> Option<Markup> {
338 ModuleDef::Static(it) => from_def_source(db, it, mod_path), 339 ModuleDef::Static(it) => from_def_source(db, it, mod_path),
339 ModuleDef::Trait(it) => from_def_source(db, it, mod_path), 340 ModuleDef::Trait(it) => from_def_source(db, it, mod_path),
340 ModuleDef::TypeAlias(it) => from_def_source(db, it, mod_path), 341 ModuleDef::TypeAlias(it) => from_def_source(db, it, mod_path),
341 ModuleDef::BuiltinType(it) => Some(Markup::fenced_block(&it)), 342 ModuleDef::BuiltinType(it) => Some(Markup::fenced_block(&it.name())),
342 }, 343 },
343 Definition::Local(it) => Some(Markup::fenced_block(&it.ty(db).display(db))), 344 Definition::Local(it) => Some(Markup::fenced_block(&it.ty(db).display(db))),
344 Definition::SelfType(impl_def) => { 345 Definition::SelfType(impl_def) => {
@@ -1830,6 +1831,35 @@ pub struct B$0ar
1830 "#]], 1831 "#]],
1831 ); 1832 );
1832 } 1833 }
1834 #[test]
1835 fn test_hover_intra_link_reference_to_trait_method() {
1836 check(
1837 r#"
1838pub trait Foo {
1839 fn buzz() -> usize;
1840}
1841/// [Foo][buzz]
1842///
1843/// [buzz]: Foo::buzz
1844pub struct B$0ar
1845"#,
1846 expect![[r#"
1847 *Bar*
1848
1849 ```rust
1850 test
1851 ```
1852
1853 ```rust
1854 pub struct Bar
1855 ```
1856
1857 ---
1858
1859 [Foo](https://docs.rs/test/*/test/trait.Foo.html#tymethod.buzz)
1860 "#]],
1861 );
1862 }
1833 1863
1834 #[test] 1864 #[test]
1835 fn test_hover_external_url() { 1865 fn test_hover_external_url() {
@@ -3387,7 +3417,7 @@ impl<T> Foo<T$0> {}
3387 ``` 3417 ```
3388 "#]], 3418 "#]],
3389 ); 3419 );
3390 // lifetimes aren't being substituted yet 3420 // lifetimes bounds arent being tracked yet
3391 check( 3421 check(
3392 r#" 3422 r#"
3393struct Foo<T>(T); 3423struct Foo<T>(T);
@@ -3397,7 +3427,7 @@ impl<T: 'static> Foo<T$0> {}
3397 *T* 3427 *T*
3398 3428
3399 ```rust 3429 ```rust
3400 T: {error} 3430 T
3401 ``` 3431 ```
3402 "#]], 3432 "#]],
3403 ); 3433 );
@@ -3419,4 +3449,80 @@ impl<const LEN: usize> Foo<LEN$0> {}
3419 "#]], 3449 "#]],
3420 ); 3450 );
3421 } 3451 }
3452
3453 #[test]
3454 fn hover_const_pat() {
3455 check(
3456 r#"
3457/// This is a doc
3458const FOO: usize = 3;
3459fn foo() {
3460 match 5 {
3461 FOO$0 => (),
3462 _ => ()
3463 }
3464}
3465"#,
3466 expect![[r#"
3467 *FOO*
3468
3469 ```rust
3470 test
3471 ```
3472
3473 ```rust
3474 const FOO: usize = 3
3475 ```
3476
3477 ---
3478
3479 This is a doc
3480 "#]],
3481 );
3482 }
3483
3484 #[test]
3485 fn hover_mod_def() {
3486 check(
3487 r#"
3488//- /main.rs
3489mod foo$0;
3490//- /foo.rs
3491//! For the horde!
3492"#,
3493 expect![[r#"
3494 *foo*
3495 For the horde!
3496 "#]],
3497 );
3498 }
3499
3500 #[test]
3501 fn hover_self_in_use() {
3502 check(
3503 r#"
3504//! This should not appear
3505mod foo {
3506 /// But this should appear
3507 pub mod bar {}
3508}
3509use foo::bar::{self$0};
3510"#,
3511 expect![[r#"
3512 *self*
3513
3514 ```rust
3515 test::foo
3516 ```
3517
3518 ```rust
3519 pub mod bar
3520 ```
3521
3522 ---
3523
3524 But this should appear
3525 "#]],
3526 );
3527 }
3422} 3528}
diff --git a/crates/ide/src/inlay_hints.rs b/crates/ide/src/inlay_hints.rs
index a2039fcc7..4ceb20742 100644
--- a/crates/ide/src/inlay_hints.rs
+++ b/crates/ide/src/inlay_hints.rs
@@ -109,26 +109,31 @@ fn get_chaining_hints(
109 // Chaining can be defined as an expression whose next sibling tokens are newline and dot 109 // Chaining can be defined as an expression whose next sibling tokens are newline and dot
110 // Ignoring extra whitespace and comments 110 // Ignoring extra whitespace and comments
111 let next = tokens.next()?.kind(); 111 let next = tokens.next()?.kind();
112 let next_next = tokens.next()?.kind(); 112 if next == SyntaxKind::WHITESPACE {
113 if next == SyntaxKind::WHITESPACE && next_next == T![.] { 113 let mut next_next = tokens.next()?.kind();
114 let ty = sema.type_of_expr(&expr)?; 114 while next_next == SyntaxKind::WHITESPACE {
115 if ty.is_unknown() { 115 next_next = tokens.next()?.kind();
116 return None;
117 } 116 }
118 if matches!(expr, ast::Expr::PathExpr(_)) { 117 if next_next == T![.] {
119 if let Some(hir::Adt::Struct(st)) = ty.as_adt() { 118 let ty = sema.type_of_expr(&expr)?;
120 if st.fields(sema.db).is_empty() { 119 if ty.is_unknown() {
121 return None; 120 return None;
121 }
122 if matches!(expr, ast::Expr::PathExpr(_)) {
123 if let Some(hir::Adt::Struct(st)) = ty.as_adt() {
124 if st.fields(sema.db).is_empty() {
125 return None;
126 }
122 } 127 }
123 } 128 }
129 acc.push(InlayHint {
130 range: expr.syntax().text_range(),
131 kind: InlayKind::ChainingHint,
132 label: hint_iterator(sema, &famous_defs, config, &ty).unwrap_or_else(|| {
133 ty.display_truncated(sema.db, config.max_length).to_string().into()
134 }),
135 });
124 } 136 }
125 acc.push(InlayHint {
126 range: expr.syntax().text_range(),
127 kind: InlayKind::ChainingHint,
128 label: hint_iterator(sema, &famous_defs, config, &ty).unwrap_or_else(|| {
129 ty.display_truncated(sema.db, config.max_length).to_string().into()
130 }),
131 });
132 } 137 }
133 Some(()) 138 Some(())
134} 139}
@@ -411,13 +416,16 @@ fn get_string_representation(expr: &ast::Expr) -> Option<String> {
411 match expr { 416 match expr {
412 ast::Expr::MethodCallExpr(method_call_expr) => { 417 ast::Expr::MethodCallExpr(method_call_expr) => {
413 let name_ref = method_call_expr.name_ref()?; 418 let name_ref = method_call_expr.name_ref()?;
414 match name_ref.text().as_str() { 419 match name_ref.text() {
415 "clone" => method_call_expr.receiver().map(|rec| rec.to_string()), 420 "clone" => method_call_expr.receiver().map(|rec| rec.to_string()),
416 name_ref => Some(name_ref.to_owned()), 421 name_ref => Some(name_ref.to_owned()),
417 } 422 }
418 } 423 }
424 ast::Expr::FieldExpr(field_expr) => Some(field_expr.name_ref()?.to_string()),
425 ast::Expr::PathExpr(path_expr) => Some(path_expr.to_string()),
426 ast::Expr::PrefixExpr(prefix_expr) => get_string_representation(&prefix_expr.expr()?),
419 ast::Expr::RefExpr(ref_expr) => get_string_representation(&ref_expr.expr()?), 427 ast::Expr::RefExpr(ref_expr) => get_string_representation(&ref_expr.expr()?),
420 _ => Some(expr.to_string()), 428 _ => None,
421 } 429 }
422} 430}
423 431
@@ -651,6 +659,7 @@ fn main() {
651 let test = "test"; 659 let test = "test";
652 //^^^^ &str 660 //^^^^ &str
653 let test = InnerStruct {}; 661 let test = InnerStruct {};
662 //^^^^ InnerStruct
654 663
655 let test = unresolved(); 664 let test = unresolved();
656 665
@@ -979,6 +988,7 @@ struct C;
979fn main() { 988fn main() {
980 let c = A(B(C)) 989 let c = A(B(C))
981 .into_b() // This is a comment 990 .into_b() // This is a comment
991 // This is another comment
982 .into_c(); 992 .into_c();
983} 993}
984"#, 994"#,
@@ -1438,4 +1448,19 @@ fn main() {
1438"#, 1448"#,
1439 ) 1449 )
1440 } 1450 }
1451
1452 #[test]
1453 fn param_name_hints_show_for_literals() {
1454 check(
1455 r#"pub fn test(a: i32, b: i32) -> [i32; 2] { [a, b] }
1456fn main() {
1457 test(
1458 0x0fab272b,
1459 //^^^^^^^^^^ a
1460 0x0fab272b
1461 //^^^^^^^^^^ b
1462 );
1463}"#,
1464 )
1465 }
1441} 1466}
diff --git a/crates/ide/src/join_lines.rs b/crates/ide/src/join_lines.rs
index 981467c8d..2c077ed1f 100644
--- a/crates/ide/src/join_lines.rs
+++ b/crates/ide/src/join_lines.rs
@@ -1,4 +1,4 @@
1use assists::utils::extract_trivial_expression; 1use ide_assists::utils::extract_trivial_expression;
2use itertools::Itertools; 2use itertools::Itertools;
3use syntax::{ 3use syntax::{
4 algo::non_trivia_sibling, 4 algo::non_trivia_sibling,
@@ -59,7 +59,7 @@ fn remove_newline(edit: &mut TextEditBuilder, token: &SyntaxToken, offset: TextS
59 // The node is either the first or the last in the file 59 // The node is either the first or the last in the file
60 let suff = &token.text()[TextRange::new( 60 let suff = &token.text()[TextRange::new(
61 offset - token.text_range().start() + TextSize::of('\n'), 61 offset - token.text_range().start() + TextSize::of('\n'),
62 TextSize::of(token.text().as_str()), 62 TextSize::of(token.text()),
63 )]; 63 )];
64 let spaces = suff.bytes().take_while(|&b| b == b' ').count(); 64 let spaces = suff.bytes().take_while(|&b| b == b' ').count();
65 65
@@ -270,27 +270,28 @@ fn foo() {
270 270
271 #[test] 271 #[test]
272 fn test_join_lines_diverging_block() { 272 fn test_join_lines_diverging_block() {
273 let before = r" 273 check_join_lines(
274 fn foo() { 274 r"
275 loop { 275fn foo() {
276 match x { 276 loop {
277 92 => $0{ 277 match x {
278 continue; 278 92 => $0{
279 } 279 continue;
280 }
281 }
282 }
283 ";
284 let after = r"
285 fn foo() {
286 loop {
287 match x {
288 92 => $0continue,
289 }
290 }
291 } 280 }
292 "; 281 }
293 check_join_lines(before, after); 282 }
283}
284 ",
285 r"
286fn foo() {
287 loop {
288 match x {
289 92 => $0continue,
290 }
291 }
292}
293 ",
294 );
294 } 295 }
295 296
296 #[test] 297 #[test]
diff --git a/crates/ide/src/lib.rs b/crates/ide/src/lib.rs
index 6c94c26b5..b600178ee 100644
--- a/crates/ide/src/lib.rs
+++ b/crates/ide/src/lib.rs
@@ -22,6 +22,7 @@ mod markup;
22mod prime_caches; 22mod prime_caches;
23mod display; 23mod display;
24 24
25mod annotations;
25mod call_hierarchy; 26mod call_hierarchy;
26mod diagnostics; 27mod diagnostics;
27mod expand_macro; 28mod expand_macro;
@@ -63,9 +64,10 @@ use syntax::SourceFile;
63use crate::display::ToNav; 64use crate::display::ToNav;
64 65
65pub use crate::{ 66pub use crate::{
67 annotations::{Annotation, AnnotationConfig, AnnotationKind},
66 call_hierarchy::CallItem, 68 call_hierarchy::CallItem,
67 diagnostics::{Diagnostic, DiagnosticsConfig, Fix, Severity}, 69 diagnostics::{Diagnostic, DiagnosticsConfig, Fix, Severity},
68 display::navigation_target::{NavigationTarget, SymbolKind}, 70 display::navigation_target::NavigationTarget,
69 expand_macro::ExpandedMacro, 71 expand_macro::ExpandedMacro,
70 file_structure::StructureNode, 72 file_structure::StructureNode,
71 folding_ranges::{Fold, FoldKind}, 73 folding_ranges::{Fold, FoldKind},
@@ -73,19 +75,19 @@ pub use crate::{
73 inlay_hints::{InlayHint, InlayHintsConfig, InlayKind}, 75 inlay_hints::{InlayHint, InlayHintsConfig, InlayKind},
74 markup::Markup, 76 markup::Markup,
75 prime_caches::PrimeCachesProgress, 77 prime_caches::PrimeCachesProgress,
76 references::{rename::RenameError, Declaration, ReferenceSearchResult}, 78 references::{rename::RenameError, ReferenceSearchResult},
77 runnables::{Runnable, RunnableKind, TestId}, 79 runnables::{Runnable, RunnableKind, TestId},
78 syntax_highlighting::{ 80 syntax_highlighting::{
79 tags::{Highlight, HlMod, HlMods, HlPunct, HlTag}, 81 tags::{Highlight, HlMod, HlMods, HlPunct, HlTag},
80 HlRange, 82 HlRange,
81 }, 83 },
82}; 84};
83pub use assists::{Assist, AssistConfig, AssistId, AssistKind}; 85pub use hir::{Documentation, Semantics};
84pub use completion::{ 86pub use ide_assists::{Assist, AssistConfig, AssistId, AssistKind};
87pub use ide_completion::{
85 CompletionConfig, CompletionItem, CompletionItemKind, CompletionScore, ImportEdit, 88 CompletionConfig, CompletionItem, CompletionItemKind, CompletionScore, ImportEdit,
86 InsertTextFormat, 89 InsertTextFormat,
87}; 90};
88pub use hir::{Documentation, Semantics};
89pub use ide_db::{ 91pub use ide_db::{
90 base_db::{ 92 base_db::{
91 Canceled, Change, CrateGraph, CrateId, Edition, FileId, FilePosition, FileRange, 93 Canceled, Change, CrateGraph, CrateId, Edition, FileId, FilePosition, FileRange,
@@ -93,13 +95,13 @@ pub use ide_db::{
93 }, 95 },
94 call_info::CallInfo, 96 call_info::CallInfo,
95 label::Label, 97 label::Label,
96 line_index::{LineCol, LineIndex}, 98 line_index::{LineCol, LineColUtf16, LineIndex},
97 search::{FileReference, ReferenceAccess, ReferenceKind, SearchScope}, 99 search::{ReferenceAccess, SearchScope},
98 source_change::{FileSystemEdit, SourceChange}, 100 source_change::{FileSystemEdit, SourceChange},
99 symbol_index::Query, 101 symbol_index::Query,
100 RootDatabase, 102 RootDatabase,
101}; 103};
102pub use ssr::SsrError; 104pub use ide_ssr::SsrError;
103pub use syntax::{TextRange, TextSize}; 105pub use syntax::{TextRange, TextSize};
104pub use text_edit::{Indel, TextEdit}; 106pub use text_edit::{Indel, TextEdit};
105 107
@@ -369,9 +371,7 @@ impl Analysis {
369 position: FilePosition, 371 position: FilePosition,
370 search_scope: Option<SearchScope>, 372 search_scope: Option<SearchScope>,
371 ) -> Cancelable<Option<ReferenceSearchResult>> { 373 ) -> Cancelable<Option<ReferenceSearchResult>> {
372 self.with_db(|db| { 374 self.with_db(|db| references::find_all_refs(&Semantics::new(db), position, search_scope))
373 references::find_all_refs(&Semantics::new(db), position, search_scope).map(|it| it.info)
374 })
375 } 375 }
376 376
377 /// Finds all methods and free functions for the file. Does not return tests! 377 /// Finds all methods and free functions for the file. Does not return tests!
@@ -468,7 +468,7 @@ impl Analysis {
468 config: &CompletionConfig, 468 config: &CompletionConfig,
469 position: FilePosition, 469 position: FilePosition,
470 ) -> Cancelable<Option<Vec<CompletionItem>>> { 470 ) -> Cancelable<Option<Vec<CompletionItem>>> {
471 self.with_db(|db| completion::completions(db, config, position).map(Into::into)) 471 self.with_db(|db| ide_completion::completions(db, config, position).map(Into::into))
472 } 472 }
473 473
474 /// Resolves additional completion data at the position given. 474 /// Resolves additional completion data at the position given.
@@ -478,15 +478,17 @@ impl Analysis {
478 position: FilePosition, 478 position: FilePosition,
479 full_import_path: &str, 479 full_import_path: &str,
480 imported_name: String, 480 imported_name: String,
481 import_for_trait_assoc_item: bool,
481 ) -> Cancelable<Vec<TextEdit>> { 482 ) -> Cancelable<Vec<TextEdit>> {
482 Ok(self 483 Ok(self
483 .with_db(|db| { 484 .with_db(|db| {
484 completion::resolve_completion_edits( 485 ide_completion::resolve_completion_edits(
485 db, 486 db,
486 config, 487 config,
487 position, 488 position,
488 full_import_path, 489 full_import_path,
489 imported_name, 490 imported_name,
491 import_for_trait_assoc_item,
490 ) 492 )
491 })? 493 })?
492 .unwrap_or_default()) 494 .unwrap_or_default())
@@ -520,7 +522,7 @@ impl Analysis {
520 &self, 522 &self,
521 position: FilePosition, 523 position: FilePosition,
522 new_name: &str, 524 new_name: &str,
523 ) -> Cancelable<Result<RangeInfo<SourceChange>, RenameError>> { 525 ) -> Cancelable<Result<SourceChange, RenameError>> {
524 self.with_db(|db| references::rename::rename(db, position, new_name)) 526 self.with_db(|db| references::rename::rename(db, position, new_name))
525 } 527 }
526 528
@@ -547,14 +549,27 @@ impl Analysis {
547 selections: Vec<FileRange>, 549 selections: Vec<FileRange>,
548 ) -> Cancelable<Result<SourceChange, SsrError>> { 550 ) -> Cancelable<Result<SourceChange, SsrError>> {
549 self.with_db(|db| { 551 self.with_db(|db| {
550 let rule: ssr::SsrRule = query.parse()?; 552 let rule: ide_ssr::SsrRule = query.parse()?;
551 let mut match_finder = ssr::MatchFinder::in_context(db, resolve_context, selections); 553 let mut match_finder =
554 ide_ssr::MatchFinder::in_context(db, resolve_context, selections);
552 match_finder.add_rule(rule)?; 555 match_finder.add_rule(rule)?;
553 let edits = if parse_only { Default::default() } else { match_finder.edits() }; 556 let edits = if parse_only { Default::default() } else { match_finder.edits() };
554 Ok(SourceChange::from(edits)) 557 Ok(SourceChange::from(edits))
555 }) 558 })
556 } 559 }
557 560
561 pub fn annotations(
562 &self,
563 file_id: FileId,
564 config: AnnotationConfig,
565 ) -> Cancelable<Vec<Annotation>> {
566 self.with_db(|db| annotations::annotations(db, file_id, config))
567 }
568
569 pub fn resolve_annotation(&self, annotation: Annotation) -> Cancelable<Annotation> {
570 self.with_db(|db| annotations::resolve_annotation(db, annotation))
571 }
572
558 /// Performs an operation on that may be Canceled. 573 /// Performs an operation on that may be Canceled.
559 fn with_db<F, T>(&self, f: F) -> Cancelable<T> 574 fn with_db<F, T>(&self, f: F) -> Cancelable<T>
560 where 575 where
diff --git a/crates/ide/src/parent_module.rs b/crates/ide/src/parent_module.rs
index d343638fb..ddbaf22b7 100644
--- a/crates/ide/src/parent_module.rs
+++ b/crates/ide/src/parent_module.rs
@@ -63,57 +63,62 @@ pub(crate) fn crate_for(db: &RootDatabase, file_id: FileId) -> Vec<CrateId> {
63 63
64#[cfg(test)] 64#[cfg(test)]
65mod tests { 65mod tests {
66 use ide_db::base_db::FileRange;
66 use test_utils::mark; 67 use test_utils::mark;
67 68
68 use crate::fixture::{self}; 69 use crate::fixture;
70
71 fn check(ra_fixture: &str) {
72 let (analysis, position, expected) = fixture::nav_target_annotation(ra_fixture);
73 let mut navs = analysis.parent_module(position).unwrap();
74 assert_eq!(navs.len(), 1);
75 let nav = navs.pop().unwrap();
76 assert_eq!(expected, FileRange { file_id: nav.file_id, range: nav.focus_or_full_range() });
77 }
69 78
70 #[test] 79 #[test]
71 fn test_resolve_parent_module() { 80 fn test_resolve_parent_module() {
72 let (analysis, pos) = fixture::position( 81 check(
73 " 82 r#"
74 //- /lib.rs 83//- /lib.rs
75 mod foo; 84mod foo;
76 //- /foo.rs 85 //^^^
77 $0// empty 86
78 ", 87//- /foo.rs
88$0// empty
89"#,
79 ); 90 );
80 let nav = analysis.parent_module(pos).unwrap().pop().unwrap();
81 nav.assert_match("foo Module FileId(0) 0..8");
82 } 91 }
83 92
84 #[test] 93 #[test]
85 fn test_resolve_parent_module_on_module_decl() { 94 fn test_resolve_parent_module_on_module_decl() {
86 mark::check!(test_resolve_parent_module_on_module_decl); 95 mark::check!(test_resolve_parent_module_on_module_decl);
87 let (analysis, pos) = fixture::position( 96 check(
88 " 97 r#"
89 //- /lib.rs 98//- /lib.rs
90 mod foo; 99mod foo;
91 100 //^^^
92 //- /foo.rs 101//- /foo.rs
93 mod $0bar; 102mod $0bar;
94 103
95 //- /foo/bar.rs 104//- /foo/bar.rs
96 // empty 105// empty
97 ", 106"#,
98 ); 107 );
99 let nav = analysis.parent_module(pos).unwrap().pop().unwrap();
100 nav.assert_match("foo Module FileId(0) 0..8");
101 } 108 }
102 109
103 #[test] 110 #[test]
104 fn test_resolve_parent_module_for_inline() { 111 fn test_resolve_parent_module_for_inline() {
105 let (analysis, pos) = fixture::position( 112 check(
106 " 113 r#"
107 //- /lib.rs 114//- /lib.rs
108 mod foo { 115mod foo {
109 mod bar { 116 mod bar {
110 mod baz { $0 } 117 mod baz { $0 }
111 } 118 } //^^^
112 } 119}
113 ", 120"#,
114 ); 121 );
115 let nav = analysis.parent_module(pos).unwrap().pop().unwrap();
116 nav.assert_match("baz Module FileId(0) 32..44");
117 } 122 }
118 123
119 #[test] 124 #[test]
diff --git a/crates/ide/src/references.rs b/crates/ide/src/references.rs
index df9c31aef..fef70533d 100644
--- a/crates/ide/src/references.rs
+++ b/crates/ide/src/references.rs
@@ -11,171 +11,120 @@
11 11
12pub(crate) mod rename; 12pub(crate) mod rename;
13 13
14use either::Either; 14use hir::{PathResolution, Semantics};
15use hir::Semantics;
16use ide_db::{ 15use ide_db::{
17 base_db::FileId, 16 base_db::FileId,
18 defs::{Definition, NameClass, NameRefClass}, 17 defs::{Definition, NameClass, NameRefClass},
19 search::{FileReference, ReferenceAccess, ReferenceKind, SearchScope, UsageSearchResult}, 18 search::{ReferenceAccess, SearchScope},
20 RootDatabase, 19 RootDatabase,
21}; 20};
21use rustc_hash::FxHashMap;
22use syntax::{ 22use syntax::{
23 algo::find_node_at_offset, 23 algo::find_node_at_offset,
24 ast::{self, NameOwner}, 24 ast::{self, NameOwner},
25 AstNode, SyntaxNode, TextRange, TokenAtOffset, T, 25 match_ast, AstNode, SyntaxNode, TextRange, T,
26}; 26};
27 27
28use crate::{display::TryToNav, FilePosition, FileRange, NavigationTarget, RangeInfo}; 28use crate::{display::TryToNav, FilePosition, NavigationTarget};
29 29
30#[derive(Debug, Clone)] 30#[derive(Debug, Clone)]
31pub struct ReferenceSearchResult { 31pub struct ReferenceSearchResult {
32 declaration: Declaration, 32 pub declaration: Declaration,
33 references: UsageSearchResult, 33 pub references: FxHashMap<FileId, Vec<(TextRange, Option<ReferenceAccess>)>>,
34} 34}
35 35
36#[derive(Debug, Clone)] 36#[derive(Debug, Clone)]
37pub struct Declaration { 37pub struct Declaration {
38 pub nav: NavigationTarget, 38 pub nav: NavigationTarget,
39 pub kind: ReferenceKind,
40 pub access: Option<ReferenceAccess>, 39 pub access: Option<ReferenceAccess>,
41} 40}
42 41
43impl ReferenceSearchResult { 42// Feature: Find All References
44 pub fn declaration(&self) -> &Declaration { 43//
45 &self.declaration 44// Shows all references of the item at the cursor location
46 } 45//
47 46// |===
48 pub fn decl_target(&self) -> &NavigationTarget { 47// | Editor | Shortcut
49 &self.declaration.nav 48//
50 } 49// | VS Code | kbd:[Shift+Alt+F12]
51 50// |===
52 pub fn references(&self) -> &UsageSearchResult {
53 &self.references
54 }
55
56 pub fn references_with_declaration(mut self) -> UsageSearchResult {
57 let decl_ref = FileReference {
58 range: self.declaration.nav.focus_or_full_range(),
59 kind: self.declaration.kind,
60 access: self.declaration.access,
61 };
62 let file_id = self.declaration.nav.file_id;
63 self.references.references.entry(file_id).or_default().push(decl_ref);
64 self.references
65 }
66
67 /// Total number of references
68 /// At least 1 since all valid references should
69 /// Have a declaration
70 pub fn len(&self) -> usize {
71 self.references.len() + 1
72 }
73}
74
75// allow turning ReferenceSearchResult into an iterator
76// over References
77impl IntoIterator for ReferenceSearchResult {
78 type Item = (FileId, Vec<FileReference>);
79 type IntoIter = std::collections::hash_map::IntoIter<FileId, Vec<FileReference>>;
80
81 fn into_iter(self) -> Self::IntoIter {
82 self.references_with_declaration().into_iter()
83 }
84}
85
86pub(crate) fn find_all_refs( 51pub(crate) fn find_all_refs(
87 sema: &Semantics<RootDatabase>, 52 sema: &Semantics<RootDatabase>,
88 position: FilePosition, 53 position: FilePosition,
89 search_scope: Option<SearchScope>, 54 search_scope: Option<SearchScope>,
90) -> Option<RangeInfo<ReferenceSearchResult>> { 55) -> Option<ReferenceSearchResult> {
91 let _p = profile::span("find_all_refs"); 56 let _p = profile::span("find_all_refs");
92 let syntax = sema.parse(position.file_id).syntax().clone(); 57 let syntax = sema.parse(position.file_id).syntax().clone();
93 58
94 let (opt_name, search_kind) = if let Some(name) = 59 let (def, is_literal_search) =
95 get_struct_def_name_for_struct_literal_search(&sema, &syntax, position) 60 if let Some(name) = get_name_of_item_declaration(&syntax, position) {
96 { 61 (NameClass::classify(sema, &name)?.referenced_or_defined(sema.db), true)
97 (Some(name), ReferenceKind::StructLiteral) 62 } else {
98 } else if let Some(name) = get_enum_def_name_for_struct_literal_search(&sema, &syntax, position) 63 (find_def(&sema, &syntax, position)?, false)
99 { 64 };
100 (Some(name), ReferenceKind::EnumLiteral)
101 } else {
102 (
103 sema.find_node_at_offset_with_descend::<ast::Name>(&syntax, position.offset),
104 ReferenceKind::Other,
105 )
106 };
107
108 let RangeInfo { range, info: def } = find_name(&sema, &syntax, position, opt_name)?;
109 65
110 let mut usages = def.usages(sema).set_scope(search_scope).all(); 66 let mut usages = def.usages(sema).set_scope(search_scope).all();
111 usages 67 if is_literal_search {
112 .references 68 // filter for constructor-literals
113 .values_mut() 69 let refs = usages.references.values_mut();
114 .for_each(|it| it.retain(|r| search_kind == ReferenceKind::Other || search_kind == r.kind)); 70 match def {
115 usages.references.retain(|_, it| !it.is_empty()); 71 Definition::ModuleDef(hir::ModuleDef::Adt(hir::Adt::Enum(enum_))) => {
116 72 refs.for_each(|it| {
117 let nav = def.try_to_nav(sema.db)?; 73 it.retain(|reference| {
118 let decl_range = nav.focus_or_full_range(); 74 reference
119 75 .name
120 let mut kind = ReferenceKind::Other; 76 .as_name_ref()
121 if let Definition::Local(local) = def { 77 .map_or(false, |name_ref| is_enum_lit_name_ref(sema, enum_, name_ref))
122 match local.source(sema.db).value { 78 })
123 Either::Left(pat) => { 79 });
124 if matches!( 80 usages.references.retain(|_, it| !it.is_empty());
125 pat.syntax().parent().and_then(ast::RecordPatField::cast),
126 Some(pat_field) if pat_field.name_ref().is_none()
127 ) {
128 kind = ReferenceKind::FieldShorthandForLocal;
129 }
130 } 81 }
131 Either::Right(_) => kind = ReferenceKind::SelfParam, 82 Definition::ModuleDef(hir::ModuleDef::Adt(_))
83 | Definition::ModuleDef(hir::ModuleDef::Variant(_)) => {
84 refs.for_each(|it| {
85 it.retain(|reference| {
86 reference.name.as_name_ref().map_or(false, is_lit_name_ref)
87 })
88 });
89 usages.references.retain(|_, it| !it.is_empty());
90 }
91 _ => {}
132 } 92 }
133 } else if matches!( 93 }
134 def, 94 let nav = def.try_to_nav(sema.db)?;
135 Definition::GenericParam(hir::GenericParam::LifetimeParam(_)) | Definition::Label(_) 95 let decl_range = nav.focus_or_full_range();
136 ) {
137 kind = ReferenceKind::Lifetime;
138 };
139 96
140 let declaration = Declaration { nav, kind, access: decl_access(&def, &syntax, decl_range) }; 97 let declaration = Declaration { nav, access: decl_access(&def, &syntax, decl_range) };
98 let references = usages
99 .into_iter()
100 .map(|(file_id, refs)| {
101 (file_id, refs.into_iter().map(|file_ref| (file_ref.range, file_ref.access)).collect())
102 })
103 .collect();
141 104
142 Some(RangeInfo::new(range, ReferenceSearchResult { declaration, references: usages })) 105 Some(ReferenceSearchResult { declaration, references })
143} 106}
144 107
145fn find_name( 108fn find_def(
146 sema: &Semantics<RootDatabase>, 109 sema: &Semantics<RootDatabase>,
147 syntax: &SyntaxNode, 110 syntax: &SyntaxNode,
148 position: FilePosition, 111 position: FilePosition,
149 opt_name: Option<ast::Name>, 112) -> Option<Definition> {
150) -> Option<RangeInfo<Definition>> { 113 let def = match sema.find_node_at_offset_with_descend(syntax, position.offset)? {
151 if let Some(name) = opt_name { 114 ast::NameLike::NameRef(name_ref) => {
152 let def = NameClass::classify(sema, &name)?.referenced_or_defined(sema.db); 115 NameRefClass::classify(sema, &name_ref)?.referenced(sema.db)
153 let FileRange { range, .. } = sema.original_range(name.syntax());
154 return Some(RangeInfo::new(range, def));
155 }
156
157 let (FileRange { range, .. }, def) = if let Some(lifetime) =
158 sema.find_node_at_offset_with_descend::<ast::Lifetime>(&syntax, position.offset)
159 {
160 if let Some(def) = NameRefClass::classify_lifetime(sema, &lifetime)
161 .map(|class| NameRefClass::referenced(class, sema.db))
162 {
163 (sema.original_range(lifetime.syntax()), def)
164 } else {
165 (
166 sema.original_range(lifetime.syntax()),
167 NameClass::classify_lifetime(sema, &lifetime)?.referenced_or_defined(sema.db),
168 )
169 } 116 }
170 } else { 117 ast::NameLike::Name(name) => {
171 let name_ref = 118 NameClass::classify(sema, &name)?.referenced_or_defined(sema.db)
172 sema.find_node_at_offset_with_descend::<ast::NameRef>(&syntax, position.offset)?; 119 }
173 ( 120 ast::NameLike::Lifetime(lifetime) => NameRefClass::classify_lifetime(sema, &lifetime)
174 sema.original_range(name_ref.syntax()), 121 .map(|class| class.referenced(sema.db))
175 NameRefClass::classify(sema, &name_ref)?.referenced(sema.db), 122 .or_else(|| {
176 ) 123 NameClass::classify_lifetime(sema, &lifetime)
124 .map(|class| class.referenced_or_defined(sema.db))
125 })?,
177 }; 126 };
178 Some(RangeInfo::new(range, def)) 127 Some(def)
179} 128}
180 129
181fn decl_access(def: &Definition, syntax: &SyntaxNode, range: TextRange) -> Option<ReferenceAccess> { 130fn decl_access(def: &Definition, syntax: &SyntaxNode, range: TextRange) -> Option<ReferenceAccess> {
@@ -197,58 +146,85 @@ fn decl_access(def: &Definition, syntax: &SyntaxNode, range: TextRange) -> Optio
197 None 146 None
198} 147}
199 148
200fn get_struct_def_name_for_struct_literal_search( 149fn get_name_of_item_declaration(syntax: &SyntaxNode, position: FilePosition) -> Option<ast::Name> {
201 sema: &Semantics<RootDatabase>, 150 let token = syntax.token_at_offset(position.offset).right_biased()?;
202 syntax: &SyntaxNode, 151 let kind = token.kind();
203 position: FilePosition, 152 if kind == T![;] {
204) -> Option<ast::Name> { 153 ast::Struct::cast(token.parent())
205 if let TokenAtOffset::Between(ref left, ref right) = syntax.token_at_offset(position.offset) { 154 .filter(|struct_| struct_.field_list().is_none())
206 if right.kind() != T!['{'] && right.kind() != T!['('] { 155 .and_then(|struct_| struct_.name())
207 return None; 156 } else if kind == T!['{'] {
208 } 157 match_ast! {
209 if let Some(name) = 158 match (token.parent()) {
210 sema.find_node_at_offset_with_descend::<ast::Name>(&syntax, left.text_range().start()) 159 ast::RecordFieldList(rfl) => match_ast! {
211 { 160 match (rfl.syntax().parent()?) {
212 return name.syntax().ancestors().find_map(ast::Struct::cast).and_then(|l| l.name()); 161 ast::Variant(it) => it.name(),
162 ast::Struct(it) => it.name(),
163 ast::Union(it) => it.name(),
164 _ => None,
165 }
166 },
167 ast::VariantList(vl) => ast::Enum::cast(vl.syntax().parent()?)?.name(),
168 _ => None,
169 }
213 } 170 }
214 if sema 171 } else if kind == T!['('] {
215 .find_node_at_offset_with_descend::<ast::GenericParamList>( 172 let tfl = ast::TupleFieldList::cast(token.parent())?;
216 &syntax, 173 match_ast! {
217 left.text_range().start(), 174 match (tfl.syntax().parent()?) {
218 ) 175 ast::Variant(it) => it.name(),
219 .is_some() 176 ast::Struct(it) => it.name(),
220 { 177 _ => None,
221 return left.ancestors().find_map(ast::Struct::cast).and_then(|l| l.name()); 178 }
222 } 179 }
180 } else {
181 None
223 } 182 }
224 None
225} 183}
226 184
227fn get_enum_def_name_for_struct_literal_search( 185fn is_enum_lit_name_ref(
228 sema: &Semantics<RootDatabase>, 186 sema: &Semantics<RootDatabase>,
229 syntax: &SyntaxNode, 187 enum_: hir::Enum,
230 position: FilePosition, 188 name_ref: &ast::NameRef,
231) -> Option<ast::Name> { 189) -> bool {
232 if let TokenAtOffset::Between(ref left, ref right) = syntax.token_at_offset(position.offset) { 190 let path_is_variant_of_enum = |path: ast::Path| {
233 if right.kind() != T!['{'] && right.kind() != T!['('] { 191 matches!(
234 return None; 192 sema.resolve_path(&path),
235 } 193 Some(PathResolution::Def(hir::ModuleDef::Variant(variant)))
236 if let Some(name) = 194 if variant.parent_enum(sema.db) == enum_
237 sema.find_node_at_offset_with_descend::<ast::Name>(&syntax, left.text_range().start()) 195 )
238 { 196 };
239 return name.syntax().ancestors().find_map(ast::Enum::cast).and_then(|l| l.name()); 197 name_ref
240 } 198 .syntax()
241 if sema 199 .ancestors()
242 .find_node_at_offset_with_descend::<ast::GenericParamList>( 200 .find_map(|ancestor| {
243 &syntax, 201 match_ast! {
244 left.text_range().start(), 202 match ancestor {
245 ) 203 ast::PathExpr(path_expr) => path_expr.path().map(path_is_variant_of_enum),
246 .is_some() 204 ast::RecordExpr(record_expr) => record_expr.path().map(path_is_variant_of_enum),
247 { 205 _ => None,
248 return left.ancestors().find_map(ast::Enum::cast).and_then(|l| l.name()); 206 }
207 }
208 })
209 .unwrap_or(false)
210}
211
212fn path_ends_with(path: Option<ast::Path>, name_ref: &ast::NameRef) -> bool {
213 path.and_then(|path| path.segment())
214 .and_then(|segment| segment.name_ref())
215 .map_or(false, |segment| segment == *name_ref)
216}
217
218fn is_lit_name_ref(name_ref: &ast::NameRef) -> bool {
219 name_ref.syntax().ancestors().find_map(|ancestor| {
220 match_ast! {
221 match ancestor {
222 ast::PathExpr(path_expr) => Some(path_ends_with(path_expr.path(), name_ref)),
223 ast::RecordExpr(record_expr) => Some(path_ends_with(record_expr.path(), name_ref)),
224 _ => None,
225 }
249 } 226 }
250 } 227 }).unwrap_or(false)
251 None
252} 228}
253 229
254#[cfg(test)] 230#[cfg(test)]
@@ -275,9 +251,9 @@ fn main() {
275} 251}
276"#, 252"#,
277 expect![[r#" 253 expect![[r#"
278 Foo Struct FileId(0) 0..26 7..10 Other 254 Foo Struct FileId(0) 0..26 7..10
279 255
280 FileId(0) 101..104 StructLiteral 256 FileId(0) 101..104
281 "#]], 257 "#]],
282 ); 258 );
283 } 259 }
@@ -293,10 +269,10 @@ struct Foo$0 {}
293} 269}
294"#, 270"#,
295 expect![[r#" 271 expect![[r#"
296 Foo Struct FileId(0) 0..13 7..10 Other 272 Foo Struct FileId(0) 0..13 7..10
297 273
298 FileId(0) 41..44 Other 274 FileId(0) 41..44
299 FileId(0) 54..57 StructLiteral 275 FileId(0) 54..57
300 "#]], 276 "#]],
301 ); 277 );
302 } 278 }
@@ -312,9 +288,9 @@ struct Foo<T> $0{}
312} 288}
313"#, 289"#,
314 expect![[r#" 290 expect![[r#"
315 Foo Struct FileId(0) 0..16 7..10 Other 291 Foo Struct FileId(0) 0..16 7..10
316 292
317 FileId(0) 64..67 StructLiteral 293 FileId(0) 64..67
318 "#]], 294 "#]],
319 ); 295 );
320 } 296 }
@@ -331,9 +307,30 @@ fn main() {
331} 307}
332"#, 308"#,
333 expect![[r#" 309 expect![[r#"
334 Foo Struct FileId(0) 0..16 7..10 Other 310 Foo Struct FileId(0) 0..16 7..10
311
312 FileId(0) 54..57
313 "#]],
314 );
315 }
316
317 #[test]
318 fn test_struct_literal_for_union() {
319 check(
320 r#"
321union Foo $0{
322 x: u32
323}
324
325fn main() {
326 let f: Foo;
327 f = Foo { x: 1 };
328}
329"#,
330 expect![[r#"
331 Foo Union FileId(0) 0..24 6..9
335 332
336 FileId(0) 54..57 StructLiteral 333 FileId(0) 62..65
337 "#]], 334 "#]],
338 ); 335 );
339 } 336 }
@@ -344,17 +341,65 @@ fn main() {
344 r#" 341 r#"
345enum Foo $0{ 342enum Foo $0{
346 A, 343 A,
347 B, 344 B(),
345 C{},
348} 346}
349fn main() { 347fn main() {
350 let f: Foo; 348 let f: Foo;
351 f = Foo::A; 349 f = Foo::A;
350 f = Foo::B();
351 f = Foo::C{};
352}
353"#,
354 expect![[r#"
355 Foo Enum FileId(0) 0..37 5..8
356
357 FileId(0) 74..77
358 FileId(0) 90..93
359 FileId(0) 108..111
360 "#]],
361 );
362 }
363
364 #[test]
365 fn test_variant_record_after_space() {
366 check(
367 r#"
368enum Foo {
369 A $0{ n: i32 },
370 B,
371}
372fn main() {
373 let f: Foo;
374 f = Foo::B;
375 f = Foo::A { n: 92 };
352} 376}
353"#, 377"#,
354 expect![[r#" 378 expect![[r#"
355 Foo Enum FileId(0) 0..26 5..8 Other 379 A Variant FileId(0) 15..27 15..16
356 380
357 FileId(0) 63..66 EnumLiteral 381 FileId(0) 95..96
382 "#]],
383 );
384 }
385 #[test]
386 fn test_variant_tuple_before_paren() {
387 check(
388 r#"
389enum Foo {
390 A$0(i32),
391 B,
392}
393fn main() {
394 let f: Foo;
395 f = Foo::B;
396 f = Foo::A(92);
397}
398"#,
399 expect![[r#"
400 A Variant FileId(0) 15..21 15..16
401
402 FileId(0) 89..90
358 "#]], 403 "#]],
359 ); 404 );
360 } 405 }
@@ -373,10 +418,10 @@ fn main() {
373} 418}
374"#, 419"#,
375 expect![[r#" 420 expect![[r#"
376 Foo Enum FileId(0) 0..26 5..8 Other 421 Foo Enum FileId(0) 0..26 5..8
377 422
378 FileId(0) 50..53 Other 423 FileId(0) 50..53
379 FileId(0) 63..66 EnumLiteral 424 FileId(0) 63..66
380 "#]], 425 "#]],
381 ); 426 );
382 } 427 }
@@ -395,9 +440,9 @@ fn main() {
395} 440}
396"#, 441"#,
397 expect![[r#" 442 expect![[r#"
398 Foo Enum FileId(0) 0..32 5..8 Other 443 Foo Enum FileId(0) 0..32 5..8
399 444
400 FileId(0) 73..76 EnumLiteral 445 FileId(0) 73..76
401 "#]], 446 "#]],
402 ); 447 );
403 } 448 }
@@ -416,9 +461,9 @@ fn main() {
416} 461}
417"#, 462"#,
418 expect![[r#" 463 expect![[r#"
419 Foo Enum FileId(0) 0..33 5..8 Other 464 Foo Enum FileId(0) 0..33 5..8
420 465
421 FileId(0) 70..73 EnumLiteral 466 FileId(0) 70..73
422 "#]], 467 "#]],
423 ); 468 );
424 } 469 }
@@ -439,12 +484,12 @@ fn main() {
439 i = 5; 484 i = 5;
440}"#, 485}"#,
441 expect![[r#" 486 expect![[r#"
442 i Local FileId(0) 20..25 24..25 Other Write 487 i Local FileId(0) 20..25 24..25 Write
443 488
444 FileId(0) 50..51 Other Write 489 FileId(0) 50..51 Write
445 FileId(0) 54..55 Other Read 490 FileId(0) 54..55 Read
446 FileId(0) 76..77 Other Write 491 FileId(0) 76..77 Write
447 FileId(0) 94..95 Other Write 492 FileId(0) 94..95 Write
448 "#]], 493 "#]],
449 ); 494 );
450 } 495 }
@@ -463,10 +508,10 @@ fn bar() {
463} 508}
464"#, 509"#,
465 expect![[r#" 510 expect![[r#"
466 spam Local FileId(0) 19..23 19..23 Other 511 spam Local FileId(0) 19..23 19..23
467 512
468 FileId(0) 34..38 Other Read 513 FileId(0) 34..38 Read
469 FileId(0) 41..45 Other Read 514 FileId(0) 41..45 Read
470 "#]], 515 "#]],
471 ); 516 );
472 } 517 }
@@ -478,9 +523,9 @@ fn bar() {
478fn foo(i : u32) -> u32 { i$0 } 523fn foo(i : u32) -> u32 { i$0 }
479"#, 524"#,
480 expect![[r#" 525 expect![[r#"
481 i ValueParam FileId(0) 7..8 7..8 Other 526 i ValueParam FileId(0) 7..8 7..8
482 527
483 FileId(0) 25..26 Other Read 528 FileId(0) 25..26 Read
484 "#]], 529 "#]],
485 ); 530 );
486 } 531 }
@@ -492,9 +537,9 @@ fn foo(i : u32) -> u32 { i$0 }
492fn foo(i$0 : u32) -> u32 { i } 537fn foo(i$0 : u32) -> u32 { i }
493"#, 538"#,
494 expect![[r#" 539 expect![[r#"
495 i ValueParam FileId(0) 7..8 7..8 Other 540 i ValueParam FileId(0) 7..8 7..8
496 541
497 FileId(0) 25..26 Other Read 542 FileId(0) 25..26 Read
498 "#]], 543 "#]],
499 ); 544 );
500 } 545 }
@@ -513,9 +558,9 @@ fn main(s: Foo) {
513} 558}
514"#, 559"#,
515 expect![[r#" 560 expect![[r#"
516 spam Field FileId(0) 17..30 21..25 Other 561 spam Field FileId(0) 17..30 21..25
517 562
518 FileId(0) 67..71 Other Read 563 FileId(0) 67..71 Read
519 "#]], 564 "#]],
520 ); 565 );
521 } 566 }
@@ -530,7 +575,7 @@ impl Foo {
530} 575}
531"#, 576"#,
532 expect![[r#" 577 expect![[r#"
533 f Function FileId(0) 27..43 30..31 Other 578 f Function FileId(0) 27..43 30..31
534 579
535 "#]], 580 "#]],
536 ); 581 );
@@ -547,7 +592,7 @@ enum Foo {
547} 592}
548"#, 593"#,
549 expect![[r#" 594 expect![[r#"
550 B Variant FileId(0) 22..23 22..23 Other 595 B Variant FileId(0) 22..23 22..23
551 596
552 "#]], 597 "#]],
553 ); 598 );
@@ -564,7 +609,7 @@ enum Foo {
564} 609}
565"#, 610"#,
566 expect![[r#" 611 expect![[r#"
567 field Field FileId(0) 26..35 26..31 Other 612 field Field FileId(0) 26..35 26..31
568 613
569 "#]], 614 "#]],
570 ); 615 );
@@ -605,10 +650,10 @@ fn f() {
605} 650}
606"#, 651"#,
607 expect![[r#" 652 expect![[r#"
608 Foo Struct FileId(1) 17..51 28..31 Other 653 Foo Struct FileId(1) 17..51 28..31
609 654
610 FileId(0) 53..56 StructLiteral 655 FileId(0) 53..56
611 FileId(2) 79..82 StructLiteral 656 FileId(2) 79..82
612 "#]], 657 "#]],
613 ); 658 );
614 } 659 }
@@ -635,9 +680,9 @@ pub struct Foo {
635} 680}
636"#, 681"#,
637 expect![[r#" 682 expect![[r#"
638 foo Module FileId(1) 0..35 Other 683 foo Module FileId(1) 0..35
639 684
640 FileId(0) 14..17 Other 685 FileId(0) 14..17
641 "#]], 686 "#]],
642 ); 687 );
643 } 688 }
@@ -663,10 +708,10 @@ pub(super) struct Foo$0 {
663} 708}
664"#, 709"#,
665 expect![[r#" 710 expect![[r#"
666 Foo Struct FileId(2) 0..41 18..21 Other 711 Foo Struct FileId(2) 0..41 18..21
667 712
668 FileId(1) 20..23 Other 713 FileId(1) 20..23
669 FileId(1) 47..50 StructLiteral 714 FileId(1) 47..50
670 "#]], 715 "#]],
671 ); 716 );
672 } 717 }
@@ -691,10 +736,10 @@ pub(super) struct Foo$0 {
691 code, 736 code,
692 None, 737 None,
693 expect![[r#" 738 expect![[r#"
694 quux Function FileId(0) 19..35 26..30 Other 739 quux Function FileId(0) 19..35 26..30
695 740
696 FileId(1) 16..20 StructLiteral 741 FileId(1) 16..20
697 FileId(2) 16..20 StructLiteral 742 FileId(2) 16..20
698 "#]], 743 "#]],
699 ); 744 );
700 745
@@ -702,9 +747,9 @@ pub(super) struct Foo$0 {
702 code, 747 code,
703 Some(SearchScope::single_file(FileId(2))), 748 Some(SearchScope::single_file(FileId(2))),
704 expect![[r#" 749 expect![[r#"
705 quux Function FileId(0) 19..35 26..30 Other 750 quux Function FileId(0) 19..35 26..30
706 751
707 FileId(2) 16..20 StructLiteral 752 FileId(2) 16..20
708 "#]], 753 "#]],
709 ); 754 );
710 } 755 }
@@ -722,10 +767,10 @@ fn foo() {
722} 767}
723"#, 768"#,
724 expect![[r#" 769 expect![[r#"
725 m1 Macro FileId(0) 0..46 29..31 Other 770 m1 Macro FileId(0) 0..46 29..31
726 771
727 FileId(0) 63..65 StructLiteral 772 FileId(0) 63..65
728 FileId(0) 73..75 StructLiteral 773 FileId(0) 73..75
729 "#]], 774 "#]],
730 ); 775 );
731 } 776 }
@@ -740,10 +785,10 @@ fn foo() {
740} 785}
741"#, 786"#,
742 expect![[r#" 787 expect![[r#"
743 i Local FileId(0) 19..24 23..24 Other Write 788 i Local FileId(0) 19..24 23..24 Write
744 789
745 FileId(0) 34..35 Other Write 790 FileId(0) 34..35 Write
746 FileId(0) 38..39 Other Read 791 FileId(0) 38..39 Read
747 "#]], 792 "#]],
748 ); 793 );
749 } 794 }
@@ -762,10 +807,10 @@ fn foo() {
762} 807}
763"#, 808"#,
764 expect![[r#" 809 expect![[r#"
765 f Field FileId(0) 15..21 15..16 Other 810 f Field FileId(0) 15..21 15..16
766 811
767 FileId(0) 55..56 RecordFieldExprOrPat Read 812 FileId(0) 55..56 Read
768 FileId(0) 68..69 Other Write 813 FileId(0) 68..69 Write
769 "#]], 814 "#]],
770 ); 815 );
771 } 816 }
@@ -780,9 +825,9 @@ fn foo() {
780} 825}
781"#, 826"#,
782 expect![[r#" 827 expect![[r#"
783 i Local FileId(0) 19..20 19..20 Other 828 i Local FileId(0) 19..20 19..20
784 829
785 FileId(0) 26..27 Other Write 830 FileId(0) 26..27 Write
786 "#]], 831 "#]],
787 ); 832 );
788 } 833 }
@@ -804,9 +849,9 @@ fn main() {
804} 849}
805"#, 850"#,
806 expect![[r#" 851 expect![[r#"
807 new Function FileId(0) 54..81 61..64 Other 852 new Function FileId(0) 54..81 61..64
808 853
809 FileId(0) 126..129 StructLiteral 854 FileId(0) 126..129
810 "#]], 855 "#]],
811 ); 856 );
812 } 857 }
@@ -826,10 +871,10 @@ use crate::f;
826fn g() { f(); } 871fn g() { f(); }
827"#, 872"#,
828 expect![[r#" 873 expect![[r#"
829 f Function FileId(0) 22..31 25..26 Other 874 f Function FileId(0) 22..31 25..26
830 875
831 FileId(1) 11..12 Other 876 FileId(1) 11..12
832 FileId(1) 24..25 StructLiteral 877 FileId(1) 24..25
833 "#]], 878 "#]],
834 ); 879 );
835 } 880 }
@@ -849,9 +894,9 @@ fn f(s: S) {
849} 894}
850"#, 895"#,
851 expect![[r#" 896 expect![[r#"
852 field Field FileId(0) 15..24 15..20 Other 897 field Field FileId(0) 15..24 15..20
853 898
854 FileId(0) 68..73 FieldShorthandForField Read 899 FileId(0) 68..73 Read
855 "#]], 900 "#]],
856 ); 901 );
857 } 902 }
@@ -873,9 +918,9 @@ fn f(e: En) {
873} 918}
874"#, 919"#,
875 expect![[r#" 920 expect![[r#"
876 field Field FileId(0) 32..41 32..37 Other 921 field Field FileId(0) 32..41 32..37
877 922
878 FileId(0) 102..107 FieldShorthandForField Read 923 FileId(0) 102..107 Read
879 "#]], 924 "#]],
880 ); 925 );
881 } 926 }
@@ -897,9 +942,9 @@ fn f() -> m::En {
897} 942}
898"#, 943"#,
899 expect![[r#" 944 expect![[r#"
900 field Field FileId(0) 56..65 56..61 Other 945 field Field FileId(0) 56..65 56..61
901 946
902 FileId(0) 125..130 RecordFieldExprOrPat Read 947 FileId(0) 125..130 Read
903 "#]], 948 "#]],
904 ); 949 );
905 } 950 }
@@ -922,10 +967,10 @@ impl Foo {
922} 967}
923"#, 968"#,
924 expect![[r#" 969 expect![[r#"
925 self SelfParam FileId(0) 47..51 47..51 SelfParam 970 self SelfParam FileId(0) 47..51 47..51
926 971
927 FileId(0) 71..75 Other Read 972 FileId(0) 71..75 Read
928 FileId(0) 152..156 Other Read 973 FileId(0) 152..156 Read
929 "#]], 974 "#]],
930 ); 975 );
931 } 976 }
@@ -943,9 +988,9 @@ impl Foo {
943} 988}
944"#, 989"#,
945 expect![[r#" 990 expect![[r#"
946 self SelfParam FileId(0) 47..51 47..51 SelfParam 991 self SelfParam FileId(0) 47..51 47..51
947 992
948 FileId(0) 63..67 Other Read 993 FileId(0) 63..67 Read
949 "#]], 994 "#]],
950 ); 995 );
951 } 996 }
@@ -961,7 +1006,7 @@ impl Foo {
961 let mut actual = String::new(); 1006 let mut actual = String::new();
962 { 1007 {
963 let decl = refs.declaration; 1008 let decl = refs.declaration;
964 format_to!(actual, "{} {:?}", decl.nav.debug_render(), decl.kind); 1009 format_to!(actual, "{}", decl.nav.debug_render());
965 if let Some(access) = decl.access { 1010 if let Some(access) = decl.access {
966 format_to!(actual, " {:?}", access) 1011 format_to!(actual, " {:?}", access)
967 } 1012 }
@@ -969,9 +1014,9 @@ impl Foo {
969 } 1014 }
970 1015
971 for (file_id, references) in refs.references { 1016 for (file_id, references) in refs.references {
972 for r in references { 1017 for (range, access) in references {
973 format_to!(actual, "{:?} {:?} {:?}", file_id, r.range, r.kind); 1018 format_to!(actual, "{:?} {:?}", file_id, range);
974 if let Some(access) = r.access { 1019 if let Some(access) = access {
975 format_to!(actual, " {:?}", access); 1020 format_to!(actual, " {:?}", access);
976 } 1021 }
977 actual += "\n"; 1022 actual += "\n";
@@ -992,13 +1037,13 @@ fn foo<'a, 'b: 'a>(x: &'a$0 ()) -> &'a () where &'a (): Foo<'a> {
992} 1037}
993"#, 1038"#,
994 expect![[r#" 1039 expect![[r#"
995 'a LifetimeParam FileId(0) 55..57 55..57 Lifetime 1040 'a LifetimeParam FileId(0) 55..57 55..57
996 1041
997 FileId(0) 63..65 Lifetime 1042 FileId(0) 63..65
998 FileId(0) 71..73 Lifetime 1043 FileId(0) 71..73
999 FileId(0) 82..84 Lifetime 1044 FileId(0) 82..84
1000 FileId(0) 95..97 Lifetime 1045 FileId(0) 95..97
1001 FileId(0) 106..108 Lifetime 1046 FileId(0) 106..108
1002 "#]], 1047 "#]],
1003 ); 1048 );
1004 } 1049 }
@@ -1010,10 +1055,10 @@ fn foo<'a, 'b: 'a>(x: &'a$0 ()) -> &'a () where &'a (): Foo<'a> {
1010type Foo<'a, T> where T: 'a$0 = &'a T; 1055type Foo<'a, T> where T: 'a$0 = &'a T;
1011"#, 1056"#,
1012 expect![[r#" 1057 expect![[r#"
1013 'a LifetimeParam FileId(0) 9..11 9..11 Lifetime 1058 'a LifetimeParam FileId(0) 9..11 9..11
1014 1059
1015 FileId(0) 25..27 Lifetime 1060 FileId(0) 25..27
1016 FileId(0) 31..33 Lifetime 1061 FileId(0) 31..33
1017 "#]], 1062 "#]],
1018 ); 1063 );
1019 } 1064 }
@@ -1032,11 +1077,11 @@ impl<'a> Foo<'a> for &'a () {
1032} 1077}
1033"#, 1078"#,
1034 expect![[r#" 1079 expect![[r#"
1035 'a LifetimeParam FileId(0) 47..49 47..49 Lifetime 1080 'a LifetimeParam FileId(0) 47..49 47..49
1036 1081
1037 FileId(0) 55..57 Lifetime 1082 FileId(0) 55..57
1038 FileId(0) 64..66 Lifetime 1083 FileId(0) 64..66
1039 FileId(0) 89..91 Lifetime 1084 FileId(0) 89..91
1040 "#]], 1085 "#]],
1041 ); 1086 );
1042 } 1087 }
@@ -1052,9 +1097,9 @@ fn main() {
1052} 1097}
1053"#, 1098"#,
1054 expect![[r#" 1099 expect![[r#"
1055 a Local FileId(0) 59..60 59..60 Other 1100 a Local FileId(0) 59..60 59..60
1056 1101
1057 FileId(0) 80..81 Other Read 1102 FileId(0) 80..81 Read
1058 "#]], 1103 "#]],
1059 ); 1104 );
1060 } 1105 }
@@ -1070,9 +1115,9 @@ fn main() {
1070} 1115}
1071"#, 1116"#,
1072 expect![[r#" 1117 expect![[r#"
1073 a Local FileId(0) 59..60 59..60 Other 1118 a Local FileId(0) 59..60 59..60
1074 1119
1075 FileId(0) 80..81 Other Read 1120 FileId(0) 80..81 Read
1076 "#]], 1121 "#]],
1077 ); 1122 );
1078 } 1123 }
@@ -1091,10 +1136,10 @@ fn foo<'a>() -> &'a () {
1091} 1136}
1092"#, 1137"#,
1093 expect![[r#" 1138 expect![[r#"
1094 'a Label FileId(0) 29..32 29..31 Lifetime 1139 'a Label FileId(0) 29..32 29..31
1095 1140
1096 FileId(0) 80..82 Lifetime 1141 FileId(0) 80..82
1097 FileId(0) 108..110 Lifetime 1142 FileId(0) 108..110
1098 "#]], 1143 "#]],
1099 ); 1144 );
1100 } 1145 }
@@ -1108,9 +1153,108 @@ fn foo<const FOO$0: usize>() -> usize {
1108} 1153}
1109"#, 1154"#,
1110 expect![[r#" 1155 expect![[r#"
1111 FOO ConstParam FileId(0) 7..23 13..16 Other 1156 FOO ConstParam FileId(0) 7..23 13..16
1157
1158 FileId(0) 42..45
1159 "#]],
1160 );
1161 }
1162
1163 #[test]
1164 fn test_find_self_ty_in_trait_def() {
1165 check(
1166 r#"
1167trait Foo {
1168 fn f() -> Self$0;
1169}
1170"#,
1171 expect![[r#"
1172 Self TypeParam FileId(0) 6..9 6..9
1173
1174 FileId(0) 26..30
1175 "#]],
1176 );
1177 }
1178
1179 #[test]
1180 fn test_self_variant_with_payload() {
1181 check(
1182 r#"
1183enum Foo { Bar() }
1184
1185impl Foo {
1186 fn foo(self) {
1187 match self {
1188 Self::Bar$0() => (),
1189 }
1190 }
1191}
1192
1193"#,
1194 expect![[r#"
1195 Bar Variant FileId(0) 11..16 11..14
1196
1197 FileId(0) 89..92
1198 "#]],
1199 );
1200 }
1201
1202 #[test]
1203 fn test_attr_differs_from_fn_with_same_name() {
1204 check(
1205 r#"
1206#[test]
1207fn test$0() {
1208 test();
1209}
1210"#,
1211 expect![[r#"
1212 test Function FileId(0) 0..33 11..15
1213
1214 FileId(0) 24..28
1215 "#]],
1216 );
1217 }
1218
1219 #[test]
1220 fn test_attr_matches_proc_macro_fn() {
1221 check(
1222 r#"
1223#[proc_macro_attribute]
1224fn my_proc_macro() {}
1225
1226#[my_proc_macro$0]
1227fn test() {}
1228"#,
1229 expect![[r#"
1230 my_proc_macro Function FileId(0) 0..45 27..40
1231
1232 FileId(0) 49..62
1233 "#]],
1234 );
1235 }
1236
1237 #[test]
1238 fn test_const_in_pattern() {
1239 check(
1240 r#"
1241const A$0: i32 = 42;
1242
1243fn main() {
1244 match A {
1245 A => (),
1246 _ => (),
1247 }
1248 if let A = A {}
1249}
1250"#,
1251 expect![[r#"
1252 A Const FileId(0) 0..18 6..7
1112 1253
1113 FileId(0) 42..45 Other 1254 FileId(0) 42..43
1255 FileId(0) 54..55
1256 FileId(0) 97..98
1257 FileId(0) 101..102
1114 "#]], 1258 "#]],
1115 ); 1259 );
1116 } 1260 }
diff --git a/crates/ide/src/references/rename.rs b/crates/ide/src/references/rename.rs
index 4df189c98..22ddeeae3 100644
--- a/crates/ide/src/references/rename.rs
+++ b/crates/ide/src/references/rename.rs
@@ -1,25 +1,23 @@
1//! FIXME: write short doc here 1//! FIXME: write short doc here
2use std::fmt::{self, Display}; 2use std::fmt::{self, Display};
3 3
4use hir::{Module, ModuleDef, ModuleSource, Semantics}; 4use either::Either;
5use hir::{HasSource, InFile, Module, ModuleDef, ModuleSource, Semantics};
5use ide_db::{ 6use ide_db::{
6 base_db::{AnchoredPathBuf, FileId, FileRange}, 7 base_db::{AnchoredPathBuf, FileId},
7 defs::{Definition, NameClass, NameRefClass}, 8 defs::{Definition, NameClass, NameRefClass},
8 search::FileReference, 9 search::FileReference,
9 RootDatabase, 10 RootDatabase,
10}; 11};
12use stdx::never;
11use syntax::{ 13use syntax::{
12 algo::find_node_at_offset,
13 ast::{self, NameOwner}, 14 ast::{self, NameOwner},
14 lex_single_syntax_kind, match_ast, AstNode, SyntaxKind, SyntaxNode, T, 15 lex_single_syntax_kind, AstNode, SyntaxKind, SyntaxNode, T,
15}; 16};
16use test_utils::mark; 17use test_utils::mark;
17use text_edit::TextEdit; 18use text_edit::TextEdit;
18 19
19use crate::{ 20use crate::{display::TryToNav, FilePosition, FileSystemEdit, RangeInfo, SourceChange, TextRange};
20 FilePosition, FileSystemEdit, RangeInfo, ReferenceKind, ReferenceSearchResult, SourceChange,
21 TextRange,
22};
23 21
24type RenameResult<T> = Result<T, RenameError>; 22type RenameResult<T> = Result<T, RenameError>;
25#[derive(Debug)] 23#[derive(Debug)]
@@ -40,6 +38,8 @@ macro_rules! bail {
40 ($($tokens:tt)*) => {return Err(format_err!($($tokens)*))} 38 ($($tokens:tt)*) => {return Err(format_err!($($tokens)*))}
41} 39}
42 40
41/// Prepares a rename. The sole job of this function is to return the TextRange of the thing that is
42/// being targeted for a rename.
43pub(crate) fn prepare_rename( 43pub(crate) fn prepare_rename(
44 db: &RootDatabase, 44 db: &RootDatabase,
45 position: FilePosition, 45 position: FilePosition,
@@ -47,20 +47,32 @@ pub(crate) fn prepare_rename(
47 let sema = Semantics::new(db); 47 let sema = Semantics::new(db);
48 let source_file = sema.parse(position.file_id); 48 let source_file = sema.parse(position.file_id);
49 let syntax = source_file.syntax(); 49 let syntax = source_file.syntax();
50 if let Some(module) = find_module_at_offset(&sema, position, syntax) { 50 let range = match &sema
51 rename_mod(&sema, position, module, "dummy") 51 .find_node_at_offset_with_descend(&syntax, position.offset)
52 } else { 52 .ok_or_else(|| format_err!("No references found at position"))?
53 let RangeInfo { range, .. } = find_all_refs(&sema, position)?; 53 {
54 Ok(RangeInfo::new(range, SourceChange::default())) 54 ast::NameLike::Name(it) => it.syntax(),
55 } 55 ast::NameLike::NameRef(it) => it.syntax(),
56 .map(|info| RangeInfo::new(info.range, ())) 56 ast::NameLike::Lifetime(it) => it.syntax(),
57} 57 }
58 58 .text_range();
59 Ok(RangeInfo::new(range, ()))
60}
61
62// Feature: Rename
63//
64// Renames the item below the cursor and all of its references
65//
66// |===
67// | Editor | Shortcut
68//
69// | VS Code | kbd:[F2]
70// |===
59pub(crate) fn rename( 71pub(crate) fn rename(
60 db: &RootDatabase, 72 db: &RootDatabase,
61 position: FilePosition, 73 position: FilePosition,
62 new_name: &str, 74 new_name: &str,
63) -> RenameResult<RangeInfo<SourceChange>> { 75) -> RenameResult<SourceChange> {
64 let sema = Semantics::new(db); 76 let sema = Semantics::new(db);
65 rename_with_semantics(&sema, position, new_name) 77 rename_with_semantics(&sema, position, new_name)
66} 78}
@@ -69,14 +81,14 @@ pub(crate) fn rename_with_semantics(
69 sema: &Semantics<RootDatabase>, 81 sema: &Semantics<RootDatabase>,
70 position: FilePosition, 82 position: FilePosition,
71 new_name: &str, 83 new_name: &str,
72) -> RenameResult<RangeInfo<SourceChange>> { 84) -> RenameResult<SourceChange> {
73 let source_file = sema.parse(position.file_id); 85 let source_file = sema.parse(position.file_id);
74 let syntax = source_file.syntax(); 86 let syntax = source_file.syntax();
75 87
76 if let Some(module) = find_module_at_offset(&sema, position, syntax) { 88 let def = find_definition(sema, syntax, position)?;
77 rename_mod(&sema, position, module, new_name) 89 match def {
78 } else { 90 Definition::ModuleDef(ModuleDef::Module(module)) => rename_mod(&sema, module, new_name),
79 rename_reference(&sema, position, new_name) 91 def => rename_reference(sema, def, new_name),
80 } 92 }
81} 93}
82 94
@@ -87,12 +99,7 @@ pub(crate) fn will_rename_file(
87) -> Option<SourceChange> { 99) -> Option<SourceChange> {
88 let sema = Semantics::new(db); 100 let sema = Semantics::new(db);
89 let module = sema.to_module_def(file_id)?; 101 let module = sema.to_module_def(file_id)?;
90 102 let mut change = rename_mod(&sema, module, new_name_stem).ok()?;
91 let decl = module.declaration_source(db)?;
92 let range = decl.value.name()?.syntax().text_range();
93
94 let position = FilePosition { file_id: decl.file_id.original_file(db), offset: range.start() };
95 let mut change = rename_mod(&sema, position, module, new_name_stem).ok()?.info;
96 change.file_system_edits.clear(); 103 change.file_system_edits.clear();
97 Some(change) 104 Some(change)
98} 105}
@@ -124,179 +131,232 @@ fn check_identifier(new_name: &str) -> RenameResult<IdentifierKind> {
124 } 131 }
125} 132}
126 133
127fn find_module_at_offset( 134fn find_definition(
128 sema: &Semantics<RootDatabase>, 135 sema: &Semantics<RootDatabase>,
129 position: FilePosition,
130 syntax: &SyntaxNode, 136 syntax: &SyntaxNode,
131) -> Option<Module> {
132 let ident = syntax.token_at_offset(position.offset).find(|t| t.kind() == SyntaxKind::IDENT)?;
133
134 let module = match_ast! {
135 match (ident.parent()) {
136 ast::NameRef(name_ref) => {
137 match NameRefClass::classify(sema, &name_ref)? {
138 NameRefClass::Definition(Definition::ModuleDef(ModuleDef::Module(module))) => module,
139 _ => return None,
140 }
141 },
142 ast::Name(name) => {
143 match NameClass::classify(&sema, &name)? {
144 NameClass::Definition(Definition::ModuleDef(ModuleDef::Module(module))) => module,
145 _ => return None,
146 }
147 },
148 _ => return None,
149 }
150 };
151
152 Some(module)
153}
154
155fn find_all_refs(
156 sema: &Semantics<RootDatabase>,
157 position: FilePosition, 137 position: FilePosition,
158) -> RenameResult<RangeInfo<ReferenceSearchResult>> { 138) -> RenameResult<Definition> {
159 crate::references::find_all_refs(sema, position, None) 139 match sema
160 .ok_or_else(|| format_err!("No references found at position")) 140 .find_node_at_offset_with_descend(syntax, position.offset)
141 .ok_or_else(|| format_err!("No references found at position"))?
142 {
143 // renaming aliases would rename the item being aliased as the HIR doesn't track aliases yet
144 ast::NameLike::Name(name)
145 if name.syntax().parent().map_or(false, |it| ast::Rename::can_cast(it.kind())) =>
146 {
147 bail!("Renaming aliases is currently unsupported")
148 }
149 ast::NameLike::Name(name) => {
150 NameClass::classify(sema, &name).map(|class| class.referenced_or_defined(sema.db))
151 }
152 ast::NameLike::NameRef(name_ref) => {
153 NameRefClass::classify(sema, &name_ref).map(|class| class.referenced(sema.db))
154 }
155 ast::NameLike::Lifetime(lifetime) => NameRefClass::classify_lifetime(sema, &lifetime)
156 .map(|class| NameRefClass::referenced(class, sema.db))
157 .or_else(|| {
158 NameClass::classify_lifetime(sema, &lifetime)
159 .map(|it| it.referenced_or_defined(sema.db))
160 }),
161 }
162 .ok_or_else(|| format_err!("No references found at position"))
161} 163}
162 164
163fn source_edit_from_references( 165fn source_edit_from_references(
164 sema: &Semantics<RootDatabase>, 166 _sema: &Semantics<RootDatabase>,
165 file_id: FileId, 167 file_id: FileId,
166 references: &[FileReference], 168 references: &[FileReference],
169 def: Definition,
167 new_name: &str, 170 new_name: &str,
168) -> (FileId, TextEdit) { 171) -> (FileId, TextEdit) {
169 let mut edit = TextEdit::builder(); 172 let mut edit = TextEdit::builder();
170 for reference in references { 173 for reference in references {
171 let mut replacement_text = String::new(); 174 let (range, replacement) = match &reference.name {
172 let range = match reference.kind { 175 // if the ranges differ then the node is inside a macro call, we can't really attempt
173 ReferenceKind::FieldShorthandForField => { 176 // to make special rewrites like shorthand syntax and such, so just rename the node in
174 mark::hit!(test_rename_struct_field_for_shorthand); 177 // the macro input
175 replacement_text.push_str(new_name); 178 ast::NameLike::NameRef(name_ref)
176 replacement_text.push_str(": "); 179 if name_ref.syntax().text_range() == reference.range =>
177 TextRange::new(reference.range.start(), reference.range.start()) 180 {
178 } 181 source_edit_from_name_ref(name_ref, new_name, def)
179 ReferenceKind::FieldShorthandForLocal => {
180 mark::hit!(test_rename_local_for_field_shorthand);
181 replacement_text.push_str(": ");
182 replacement_text.push_str(new_name);
183 TextRange::new(reference.range.end(), reference.range.end())
184 } 182 }
185 ReferenceKind::RecordFieldExprOrPat => { 183 ast::NameLike::Name(name) if name.syntax().text_range() == reference.range => {
186 mark::hit!(test_rename_field_expr_pat); 184 source_edit_from_name(name, new_name)
187 replacement_text.push_str(new_name);
188 edit_text_range_for_record_field_expr_or_pat(
189 sema,
190 FileRange { file_id, range: reference.range },
191 new_name,
192 )
193 } 185 }
194 _ => { 186 _ => None,
195 replacement_text.push_str(new_name); 187 }
196 reference.range 188 .unwrap_or_else(|| (reference.range, new_name.to_string()));
197 } 189 edit.replace(range, replacement);
198 };
199 edit.replace(range, replacement_text);
200 } 190 }
201 (file_id, edit.finish()) 191 (file_id, edit.finish())
202} 192}
203 193
204fn edit_text_range_for_record_field_expr_or_pat( 194fn source_edit_from_name(name: &ast::Name, new_name: &str) -> Option<(TextRange, String)> {
205 sema: &Semantics<RootDatabase>, 195 if let Some(_) = ast::RecordPatField::for_field_name(name) {
206 file_range: FileRange, 196 if let Some(ident_pat) = name.syntax().parent().and_then(ast::IdentPat::cast) {
197 return Some((
198 TextRange::empty(ident_pat.syntax().text_range().start()),
199 format!("{}: ", new_name),
200 ));
201 }
202 }
203 None
204}
205
206fn source_edit_from_name_ref(
207 name_ref: &ast::NameRef,
207 new_name: &str, 208 new_name: &str,
208) -> TextRange { 209 def: Definition,
209 let source_file = sema.parse(file_range.file_id); 210) -> Option<(TextRange, String)> {
210 let file_syntax = source_file.syntax(); 211 if let Some(record_field) = ast::RecordExprField::for_name_ref(name_ref) {
211 let original_range = file_range.range; 212 let rcf_name_ref = record_field.name_ref();
212 213 let rcf_expr = record_field.expr();
213 syntax::algo::find_node_at_range::<ast::RecordExprField>(file_syntax, original_range) 214 match (rcf_name_ref, rcf_expr.and_then(|it| it.name_ref())) {
214 .and_then(|field_expr| match field_expr.expr().and_then(|e| e.name_ref()) { 215 // field: init-expr, check if we can use a field init shorthand
215 Some(name) if &name.to_string() == new_name => Some(field_expr.syntax().text_range()), 216 (Some(field_name), Some(init)) => {
216 _ => None, 217 if field_name == *name_ref {
217 }) 218 if init.text() == new_name {
218 .or_else(|| { 219 mark::hit!(test_rename_field_put_init_shorthand);
219 syntax::algo::find_node_at_range::<ast::RecordPatField>(file_syntax, original_range) 220 // same names, we can use a shorthand here instead.
220 .and_then(|field_pat| match field_pat.pat() { 221 // we do not want to erase attributes hence this range start
221 Some(ast::Pat::IdentPat(pat)) 222 let s = field_name.syntax().text_range().start();
222 if pat.name().map(|n| n.to_string()).as_deref() == Some(new_name) => 223 let e = record_field.syntax().text_range().end();
223 { 224 return Some((TextRange::new(s, e), new_name.to_owned()));
224 Some(field_pat.syntax().text_range())
225 } 225 }
226 _ => None, 226 } else if init == *name_ref {
227 }) 227 if field_name.text() == new_name {
228 }) 228 mark::hit!(test_rename_local_put_init_shorthand);
229 .unwrap_or(original_range) 229 // same names, we can use a shorthand here instead.
230 // we do not want to erase attributes hence this range start
231 let s = field_name.syntax().text_range().start();
232 let e = record_field.syntax().text_range().end();
233 return Some((TextRange::new(s, e), new_name.to_owned()));
234 }
235 }
236 None
237 }
238 // init shorthand
239 // FIXME: instead of splitting the shorthand, recursively trigger a rename of the
240 // other name https://github.com/rust-analyzer/rust-analyzer/issues/6547
241 (None, Some(_)) if matches!(def, Definition::Field(_)) => {
242 mark::hit!(test_rename_field_in_field_shorthand);
243 let s = name_ref.syntax().text_range().start();
244 Some((TextRange::empty(s), format!("{}: ", new_name)))
245 }
246 (None, Some(_)) if matches!(def, Definition::Local(_)) => {
247 mark::hit!(test_rename_local_in_field_shorthand);
248 let s = name_ref.syntax().text_range().end();
249 Some((TextRange::empty(s), format!(": {}", new_name)))
250 }
251 _ => None,
252 }
253 } else if let Some(record_field) = ast::RecordPatField::for_field_name_ref(name_ref) {
254 let rcf_name_ref = record_field.name_ref();
255 let rcf_pat = record_field.pat();
256 match (rcf_name_ref, rcf_pat) {
257 // field: rename
258 (Some(field_name), Some(ast::Pat::IdentPat(pat))) if field_name == *name_ref => {
259 // field name is being renamed
260 if pat.name().map_or(false, |it| it.text() == new_name) {
261 mark::hit!(test_rename_field_put_init_shorthand_pat);
262 // same names, we can use a shorthand here instead/
263 // we do not want to erase attributes hence this range start
264 let s = field_name.syntax().text_range().start();
265 let e = record_field.syntax().text_range().end();
266 Some((TextRange::new(s, e), pat.to_string()))
267 } else {
268 None
269 }
270 }
271 _ => None,
272 }
273 } else {
274 None
275 }
230} 276}
231 277
232fn rename_mod( 278fn rename_mod(
233 sema: &Semantics<RootDatabase>, 279 sema: &Semantics<RootDatabase>,
234 position: FilePosition,
235 module: Module, 280 module: Module,
236 new_name: &str, 281 new_name: &str,
237) -> RenameResult<RangeInfo<SourceChange>> { 282) -> RenameResult<SourceChange> {
238 if IdentifierKind::Ident != check_identifier(new_name)? { 283 if IdentifierKind::Ident != check_identifier(new_name)? {
239 bail!("Invalid name `{0}`: cannot rename module to {0}", new_name); 284 bail!("Invalid name `{0}`: cannot rename module to {0}", new_name);
240 } 285 }
241 286
242 let mut source_change = SourceChange::default(); 287 let mut source_change = SourceChange::default();
243 288
244 let src = module.definition_source(sema.db); 289 let InFile { file_id, value: def_source } = module.definition_source(sema.db);
245 let file_id = src.file_id.original_file(sema.db); 290 let file_id = file_id.original_file(sema.db);
246 match src.value { 291 if let ModuleSource::SourceFile(..) = def_source {
247 ModuleSource::SourceFile(..) => { 292 // mod is defined in path/to/dir/mod.rs
248 // mod is defined in path/to/dir/mod.rs 293 let path = if module.is_mod_rs(sema.db) {
249 let path = if module.is_mod_rs(sema.db) { 294 format!("../{}/mod.rs", new_name)
250 format!("../{}/mod.rs", new_name) 295 } else {
251 } else { 296 format!("{}.rs", new_name)
252 format!("{}.rs", new_name) 297 };
253 }; 298 let dst = AnchoredPathBuf { anchor: file_id, path };
254 let dst = AnchoredPathBuf { anchor: file_id, path }; 299 let move_file = FileSystemEdit::MoveFile { src: file_id, dst };
255 let move_file = FileSystemEdit::MoveFile { src: file_id, dst }; 300 source_change.push_file_system_edit(move_file);
256 source_change.push_file_system_edit(move_file); 301 }
257 } 302
258 ModuleSource::Module(..) => {} 303 if let Some(InFile { file_id, value: decl_source }) = module.declaration_source(sema.db) {
259 } 304 let file_id = file_id.original_file(sema.db);
260 305 match decl_source.name() {
261 if let Some(src) = module.declaration_source(sema.db) { 306 Some(name) => source_change.insert_source_edit(
262 let file_id = src.file_id.original_file(sema.db); 307 file_id,
263 let name = src.value.name().unwrap(); 308 TextEdit::replace(name.syntax().text_range(), new_name.to_string()),
264 source_change.insert_source_edit( 309 ),
265 file_id, 310 _ => unreachable!(),
266 TextEdit::replace(name.syntax().text_range(), new_name.into()), 311 };
267 );
268 } 312 }
269 313 let def = Definition::ModuleDef(ModuleDef::Module(module));
270 let RangeInfo { range, info: refs } = find_all_refs(sema, position)?; 314 let usages = def.usages(sema).all();
271 let ref_edits = refs.references().iter().map(|(&file_id, references)| { 315 let ref_edits = usages.iter().map(|(&file_id, references)| {
272 source_edit_from_references(sema, file_id, references, new_name) 316 source_edit_from_references(sema, file_id, references, def, new_name)
273 }); 317 });
274 source_change.extend(ref_edits); 318 source_change.extend(ref_edits);
275 319
276 Ok(RangeInfo::new(range, source_change)) 320 Ok(source_change)
277} 321}
278 322
279fn rename_to_self( 323fn rename_to_self(sema: &Semantics<RootDatabase>, local: hir::Local) -> RenameResult<SourceChange> {
280 sema: &Semantics<RootDatabase>, 324 if never!(local.is_self(sema.db)) {
281 position: FilePosition, 325 bail!("rename_to_self invoked on self");
282) -> Result<RangeInfo<SourceChange>, RenameError> { 326 }
283 let source_file = sema.parse(position.file_id); 327
284 let syn = source_file.syntax(); 328 let fn_def = match local.parent(sema.db) {
329 hir::DefWithBody::Function(func) => func,
330 _ => bail!("Cannot rename non-param local to self"),
331 };
285 332
286 let (fn_def, fn_ast) = find_node_at_offset::<ast::Fn>(syn, position.offset) 333 // FIXME: reimplement this on the hir instead
287 .and_then(|fn_ast| sema.to_def(&fn_ast).zip(Some(fn_ast))) 334 // as of the time of this writing params in hir don't keep their names
288 .ok_or_else(|| format_err!("No surrounding method declaration found"))?; 335 let fn_ast = fn_def
289 let param_range = fn_ast 336 .source(sema.db)
337 .ok_or_else(|| format_err!("Cannot rename non-param local to self"))?
338 .value;
339
340 let first_param_range = fn_ast
290 .param_list() 341 .param_list()
291 .and_then(|p| p.params().next()) 342 .and_then(|p| p.params().next())
292 .ok_or_else(|| format_err!("Method has no parameters"))? 343 .ok_or_else(|| format_err!("Method has no parameters"))?
293 .syntax() 344 .syntax()
294 .text_range(); 345 .text_range();
295 if !param_range.contains(position.offset) { 346 let InFile { file_id, value: local_source } = local.source(sema.db);
296 bail!("Only the first parameter can be self"); 347 match local_source {
348 either::Either::Left(pat)
349 if !first_param_range.contains_range(pat.syntax().text_range()) =>
350 {
351 bail!("Only the first parameter can be self");
352 }
353 _ => (),
297 } 354 }
298 355
299 let impl_block = find_node_at_offset::<ast::Impl>(syn, position.offset) 356 let impl_block = fn_ast
357 .syntax()
358 .ancestors()
359 .find_map(|node| ast::Impl::cast(node))
300 .and_then(|def| sema.to_def(&def)) 360 .and_then(|def| sema.to_def(&def))
301 .ok_or_else(|| format_err!("No impl block found for function"))?; 361 .ok_or_else(|| format_err!("No impl block found for function"))?;
302 if fn_def.self_param(sema.db).is_some() { 362 if fn_def.self_param(sema.db).is_some() {
@@ -320,25 +380,21 @@ fn rename_to_self(
320 bail!("Parameter type differs from impl block type"); 380 bail!("Parameter type differs from impl block type");
321 } 381 }
322 382
323 let RangeInfo { range, info: refs } = find_all_refs(sema, position)?; 383 let def = Definition::Local(local);
324 384 let usages = def.usages(sema).all();
325 let mut source_change = SourceChange::default(); 385 let mut source_change = SourceChange::default();
326 source_change.extend(refs.references().iter().map(|(&file_id, references)| { 386 source_change.extend(usages.iter().map(|(&file_id, references)| {
327 source_edit_from_references(sema, file_id, references, "self") 387 source_edit_from_references(sema, file_id, references, def, "self")
328 })); 388 }));
329 source_change.insert_source_edit( 389 source_change.insert_source_edit(
330 position.file_id, 390 file_id.original_file(sema.db),
331 TextEdit::replace(param_range, String::from(self_param)), 391 TextEdit::replace(first_param_range, String::from(self_param)),
332 ); 392 );
333 393
334 Ok(RangeInfo::new(range, source_change)) 394 Ok(source_change)
335} 395}
336 396
337fn text_edit_from_self_param( 397fn text_edit_from_self_param(self_param: &ast::SelfParam, new_name: &str) -> Option<TextEdit> {
338 syn: &SyntaxNode,
339 self_param: &ast::SelfParam,
340 new_name: &str,
341) -> Option<TextEdit> {
342 fn target_type_name(impl_def: &ast::Impl) -> Option<String> { 398 fn target_type_name(impl_def: &ast::Impl) -> Option<String> {
343 if let Some(ast::Type::PathType(p)) = impl_def.self_ty() { 399 if let Some(ast::Type::PathType(p)) = impl_def.self_ty() {
344 return Some(p.path()?.segment()?.name_ref()?.text().to_string()); 400 return Some(p.path()?.segment()?.name_ref()?.text().to_string());
@@ -346,7 +402,7 @@ fn text_edit_from_self_param(
346 None 402 None
347 } 403 }
348 404
349 let impl_def = find_node_at_offset::<ast::Impl>(syn, self_param.syntax().text_range().start())?; 405 let impl_def = self_param.syntax().ancestors().find_map(|it| ast::Impl::cast(it))?;
350 let type_name = target_type_name(&impl_def)?; 406 let type_name = target_type_name(&impl_def)?;
351 407
352 let mut replacement_text = String::from(new_name); 408 let mut replacement_text = String::from(new_name);
@@ -363,94 +419,119 @@ fn text_edit_from_self_param(
363 419
364fn rename_self_to_param( 420fn rename_self_to_param(
365 sema: &Semantics<RootDatabase>, 421 sema: &Semantics<RootDatabase>,
366 position: FilePosition, 422 local: hir::Local,
367 new_name: &str, 423 new_name: &str,
368 ident_kind: IdentifierKind, 424 identifier_kind: IdentifierKind,
369 range: TextRange, 425) -> RenameResult<SourceChange> {
370 refs: ReferenceSearchResult, 426 let (file_id, self_param) = match local.source(sema.db) {
371) -> Result<RangeInfo<SourceChange>, RenameError> { 427 InFile { file_id, value: Either::Right(self_param) } => (file_id, self_param),
372 match ident_kind { 428 _ => {
373 IdentifierKind::Lifetime => bail!("Invalid name `{}`: not an identifier", new_name), 429 never!(true, "rename_self_to_param invoked on a non-self local");
374 IdentifierKind::ToSelf => { 430 bail!("rename_self_to_param invoked on a non-self local");
375 // no-op
376 mark::hit!(rename_self_to_self);
377 return Ok(RangeInfo::new(range, SourceChange::default()));
378 } 431 }
379 _ => (), 432 };
380 }
381 let source_file = sema.parse(position.file_id);
382 let syn = source_file.syntax();
383
384 let fn_def = find_node_at_offset::<ast::Fn>(syn, position.offset)
385 .ok_or_else(|| format_err!("No surrounding method declaration found"))?;
386
387 let mut source_change = SourceChange::default();
388 if let Some(self_param) = fn_def.param_list().and_then(|it| it.self_param()) {
389 if self_param
390 .syntax()
391 .text_range()
392 .contains_range(refs.declaration().nav.focus_or_full_range())
393 {
394 let edit = text_edit_from_self_param(syn, &self_param, new_name)
395 .ok_or_else(|| format_err!("No target type found"))?;
396 source_change.insert_source_edit(position.file_id, edit);
397
398 source_change.extend(refs.references().iter().map(|(&file_id, references)| {
399 source_edit_from_references(sema, file_id, &references, new_name)
400 }));
401
402 if source_change.source_file_edits.len() > 1 && ident_kind == IdentifierKind::Underscore
403 {
404 bail!("Cannot rename reference to `_` as it is being referenced multiple times");
405 }
406 433
407 return Ok(RangeInfo::new(range, source_change)); 434 let def = Definition::Local(local);
408 } 435 let usages = def.usages(sema).all();
436 let edit = text_edit_from_self_param(&self_param, new_name)
437 .ok_or_else(|| format_err!("No target type found"))?;
438 if usages.len() > 1 && identifier_kind == IdentifierKind::Underscore {
439 bail!("Cannot rename reference to `_` as it is being referenced multiple times");
409 } 440 }
410 Err(format_err!("Method has no self param")) 441 let mut source_change = SourceChange::default();
442 source_change.insert_source_edit(file_id.original_file(sema.db), edit);
443 source_change.extend(usages.iter().map(|(&file_id, references)| {
444 source_edit_from_references(sema, file_id, &references, def, new_name)
445 }));
446 Ok(source_change)
411} 447}
412 448
413fn rename_reference( 449fn rename_reference(
414 sema: &Semantics<RootDatabase>, 450 sema: &Semantics<RootDatabase>,
415 position: FilePosition, 451 def: Definition,
416 new_name: &str, 452 new_name: &str,
417) -> Result<RangeInfo<SourceChange>, RenameError> { 453) -> RenameResult<SourceChange> {
418 let ident_kind = check_identifier(new_name)?; 454 let ident_kind = check_identifier(new_name)?;
419 let RangeInfo { range, info: refs } = find_all_refs(sema, position)?;
420 455
421 match (ident_kind, &refs.declaration.kind) { 456 let def_is_lbl_or_lt = matches!(
422 (IdentifierKind::ToSelf, ReferenceKind::Lifetime) 457 def,
423 | (IdentifierKind::Underscore, ReferenceKind::Lifetime) 458 Definition::GenericParam(hir::GenericParam::LifetimeParam(_)) | Definition::Label(_)
424 | (IdentifierKind::Ident, ReferenceKind::Lifetime) => { 459 );
460 match (ident_kind, def) {
461 (IdentifierKind::ToSelf, _)
462 | (IdentifierKind::Underscore, _)
463 | (IdentifierKind::Ident, _)
464 if def_is_lbl_or_lt =>
465 {
425 mark::hit!(rename_not_a_lifetime_ident_ref); 466 mark::hit!(rename_not_a_lifetime_ident_ref);
426 bail!("Invalid name `{}`: not a lifetime identifier", new_name) 467 bail!("Invalid name `{}`: not a lifetime identifier", new_name)
427 } 468 }
428 (IdentifierKind::Lifetime, ReferenceKind::Lifetime) => mark::hit!(rename_lifetime), 469 (IdentifierKind::Lifetime, _) if def_is_lbl_or_lt => mark::hit!(rename_lifetime),
429 (IdentifierKind::Lifetime, _) => { 470 (IdentifierKind::Lifetime, _) => {
430 mark::hit!(rename_not_an_ident_ref); 471 mark::hit!(rename_not_an_ident_ref);
431 bail!("Invalid name `{}`: not an identifier", new_name) 472 bail!("Invalid name `{}`: not an identifier", new_name)
432 } 473 }
433 (_, ReferenceKind::SelfParam) => { 474 (IdentifierKind::ToSelf, Definition::Local(local)) if local.is_self(sema.db) => {
475 // no-op
476 mark::hit!(rename_self_to_self);
477 return Ok(SourceChange::default());
478 }
479 (ident_kind, Definition::Local(local)) if local.is_self(sema.db) => {
434 mark::hit!(rename_self_to_param); 480 mark::hit!(rename_self_to_param);
435 return rename_self_to_param(sema, position, new_name, ident_kind, range, refs); 481 return rename_self_to_param(sema, local, new_name, ident_kind);
436 } 482 }
437 (IdentifierKind::ToSelf, _) => { 483 (IdentifierKind::ToSelf, Definition::Local(local)) => {
438 mark::hit!(rename_to_self); 484 mark::hit!(rename_to_self);
439 return rename_to_self(sema, position); 485 return rename_to_self(sema, local);
440 }
441 (IdentifierKind::Underscore, _) if !refs.references.is_empty() => {
442 mark::hit!(rename_underscore_multiple);
443 bail!("Cannot rename reference to `_` as it is being referenced multiple times")
444 } 486 }
487 (IdentifierKind::ToSelf, _) => bail!("Invalid name `{}`: not an identifier", new_name),
445 (IdentifierKind::Ident, _) | (IdentifierKind::Underscore, _) => mark::hit!(rename_ident), 488 (IdentifierKind::Ident, _) | (IdentifierKind::Underscore, _) => mark::hit!(rename_ident),
446 } 489 }
447 490
491 let usages = def.usages(sema).all();
492 if !usages.is_empty() && ident_kind == IdentifierKind::Underscore {
493 mark::hit!(rename_underscore_multiple);
494 bail!("Cannot rename reference to `_` as it is being referenced multiple times");
495 }
448 let mut source_change = SourceChange::default(); 496 let mut source_change = SourceChange::default();
449 source_change.extend(refs.into_iter().map(|(file_id, references)| { 497 source_change.extend(usages.iter().map(|(&file_id, references)| {
450 source_edit_from_references(sema, file_id, &references, new_name) 498 source_edit_from_references(sema, file_id, &references, def, new_name)
451 })); 499 }));
452 500
453 Ok(RangeInfo::new(range, source_change)) 501 let (file_id, edit) = source_edit_from_def(sema, def, new_name)?;
502 source_change.insert_source_edit(file_id, edit);
503 Ok(source_change)
504}
505
506fn source_edit_from_def(
507 sema: &Semantics<RootDatabase>,
508 def: Definition,
509 new_name: &str,
510) -> RenameResult<(FileId, TextEdit)> {
511 let nav = def.try_to_nav(sema.db).unwrap();
512
513 let mut replacement_text = String::new();
514 let mut repl_range = nav.focus_or_full_range();
515 if let Definition::Local(local) = def {
516 if let Either::Left(pat) = local.source(sema.db).value {
517 if matches!(
518 pat.syntax().parent().and_then(ast::RecordPatField::cast),
519 Some(pat_field) if pat_field.name_ref().is_none()
520 ) {
521 replacement_text.push_str(": ");
522 replacement_text.push_str(new_name);
523 repl_range = TextRange::new(
524 pat.syntax().text_range().end(),
525 pat.syntax().text_range().end(),
526 );
527 }
528 }
529 }
530 if replacement_text.is_empty() {
531 replacement_text.push_str(new_name);
532 }
533 let edit = TextEdit::replace(repl_range, replacement_text);
534 Ok((nav.file_id, edit))
454} 535}
455 536
456#[cfg(test)] 537#[cfg(test)]
@@ -472,7 +553,7 @@ mod tests {
472 Ok(source_change) => { 553 Ok(source_change) => {
473 let mut text_edit_builder = TextEdit::builder(); 554 let mut text_edit_builder = TextEdit::builder();
474 let mut file_id: Option<FileId> = None; 555 let mut file_id: Option<FileId> = None;
475 for edit in source_change.info.source_file_edits { 556 for edit in source_change.source_file_edits {
476 file_id = Some(edit.0); 557 file_id = Some(edit.0);
477 for indel in edit.1.into_iter() { 558 for indel in edit.1.into_iter() {
478 text_edit_builder.replace(indel.delete, indel.insert); 559 text_edit_builder.replace(indel.delete, indel.insert);
@@ -502,10 +583,8 @@ mod tests {
502 583
503 fn check_expect(new_name: &str, ra_fixture: &str, expect: Expect) { 584 fn check_expect(new_name: &str, ra_fixture: &str, expect: Expect) {
504 let (analysis, position) = fixture::position(ra_fixture); 585 let (analysis, position) = fixture::position(ra_fixture);
505 let source_change = analysis 586 let source_change =
506 .rename(position, new_name) 587 analysis.rename(position, new_name).unwrap().expect("Expect returned a RenameError");
507 .unwrap()
508 .expect("Expect returned RangeInfo to be Some, but was None");
509 expect.assert_debug_eq(&source_change) 588 expect.assert_debug_eq(&source_change)
510 } 589 }
511 590
@@ -749,8 +828,8 @@ impl Foo {
749 } 828 }
750 829
751 #[test] 830 #[test]
752 fn test_rename_struct_field_for_shorthand() { 831 fn test_rename_field_in_field_shorthand() {
753 mark::check!(test_rename_struct_field_for_shorthand); 832 mark::check!(test_rename_field_in_field_shorthand);
754 check( 833 check(
755 "j", 834 "j",
756 r#" 835 r#"
@@ -775,8 +854,8 @@ impl Foo {
775 } 854 }
776 855
777 #[test] 856 #[test]
778 fn test_rename_local_for_field_shorthand() { 857 fn test_rename_local_in_field_shorthand() {
779 mark::check!(test_rename_local_for_field_shorthand); 858 mark::check!(test_rename_local_in_field_shorthand);
780 check( 859 check(
781 "j", 860 "j",
782 r#" 861 r#"
@@ -871,36 +950,33 @@ mod foo$0;
871// empty 950// empty
872"#, 951"#,
873 expect![[r#" 952 expect![[r#"
874 RangeInfo { 953 SourceChange {
875 range: 4..7, 954 source_file_edits: {
876 info: SourceChange { 955 FileId(
877 source_file_edits: { 956 1,
878 FileId( 957 ): TextEdit {
879 1, 958 indels: [
880 ): TextEdit { 959 Indel {
881 indels: [ 960 insert: "foo2",
882 Indel { 961 delete: 4..7,
883 insert: "foo2", 962 },
884 delete: 4..7, 963 ],
885 },
886 ],
887 },
888 }, 964 },
889 file_system_edits: [ 965 },
890 MoveFile { 966 file_system_edits: [
891 src: FileId( 967 MoveFile {
968 src: FileId(
969 2,
970 ),
971 dst: AnchoredPathBuf {
972 anchor: FileId(
892 2, 973 2,
893 ), 974 ),
894 dst: AnchoredPathBuf { 975 path: "foo2.rs",
895 anchor: FileId(
896 2,
897 ),
898 path: "foo2.rs",
899 },
900 }, 976 },
901 ], 977 },
902 is_snippet: false, 978 ],
903 }, 979 is_snippet: false,
904 } 980 }
905 "#]], 981 "#]],
906 ); 982 );
@@ -923,46 +999,43 @@ pub struct FooContent;
923use crate::foo$0::FooContent; 999use crate::foo$0::FooContent;
924"#, 1000"#,
925 expect![[r#" 1001 expect![[r#"
926 RangeInfo { 1002 SourceChange {
927 range: 11..14, 1003 source_file_edits: {
928 info: SourceChange { 1004 FileId(
929 source_file_edits: { 1005 0,
930 FileId( 1006 ): TextEdit {
931 0, 1007 indels: [
932 ): TextEdit { 1008 Indel {
933 indels: [ 1009 insert: "quux",
934 Indel { 1010 delete: 8..11,
935 insert: "quux", 1011 },
936 delete: 8..11, 1012 ],
937 },
938 ],
939 },
940 FileId(
941 2,
942 ): TextEdit {
943 indels: [
944 Indel {
945 insert: "quux",
946 delete: 11..14,
947 },
948 ],
949 },
950 }, 1013 },
951 file_system_edits: [ 1014 FileId(
952 MoveFile { 1015 2,
953 src: FileId( 1016 ): TextEdit {
1017 indels: [
1018 Indel {
1019 insert: "quux",
1020 delete: 11..14,
1021 },
1022 ],
1023 },
1024 },
1025 file_system_edits: [
1026 MoveFile {
1027 src: FileId(
1028 1,
1029 ),
1030 dst: AnchoredPathBuf {
1031 anchor: FileId(
954 1, 1032 1,
955 ), 1033 ),
956 dst: AnchoredPathBuf { 1034 path: "quux.rs",
957 anchor: FileId(
958 1,
959 ),
960 path: "quux.rs",
961 },
962 }, 1035 },
963 ], 1036 },
964 is_snippet: false, 1037 ],
965 }, 1038 is_snippet: false,
966 } 1039 }
967 "#]], 1040 "#]],
968 ); 1041 );
@@ -979,36 +1052,33 @@ mod fo$0o;
979// empty 1052// empty
980"#, 1053"#,
981 expect![[r#" 1054 expect![[r#"
982 RangeInfo { 1055 SourceChange {
983 range: 4..7, 1056 source_file_edits: {
984 info: SourceChange { 1057 FileId(
985 source_file_edits: { 1058 0,
986 FileId( 1059 ): TextEdit {
987 0, 1060 indels: [
988 ): TextEdit { 1061 Indel {
989 indels: [ 1062 insert: "foo2",
990 Indel { 1063 delete: 4..7,
991 insert: "foo2", 1064 },
992 delete: 4..7, 1065 ],
993 },
994 ],
995 },
996 }, 1066 },
997 file_system_edits: [ 1067 },
998 MoveFile { 1068 file_system_edits: [
999 src: FileId( 1069 MoveFile {
1070 src: FileId(
1071 1,
1072 ),
1073 dst: AnchoredPathBuf {
1074 anchor: FileId(
1000 1, 1075 1,
1001 ), 1076 ),
1002 dst: AnchoredPathBuf { 1077 path: "../foo2/mod.rs",
1003 anchor: FileId(
1004 1,
1005 ),
1006 path: "../foo2/mod.rs",
1007 },
1008 }, 1078 },
1009 ], 1079 },
1010 is_snippet: false, 1080 ],
1011 }, 1081 is_snippet: false,
1012 } 1082 }
1013 "#]], 1083 "#]],
1014 ); 1084 );
@@ -1026,36 +1096,33 @@ mod outer { mod fo$0o; }
1026// empty 1096// empty
1027"#, 1097"#,
1028 expect![[r#" 1098 expect![[r#"
1029 RangeInfo { 1099 SourceChange {
1030 range: 16..19, 1100 source_file_edits: {
1031 info: SourceChange { 1101 FileId(
1032 source_file_edits: { 1102 0,
1033 FileId( 1103 ): TextEdit {
1034 0, 1104 indels: [
1035 ): TextEdit { 1105 Indel {
1036 indels: [ 1106 insert: "bar",
1037 Indel { 1107 delete: 16..19,
1038 insert: "bar", 1108 },
1039 delete: 16..19, 1109 ],
1040 },
1041 ],
1042 },
1043 }, 1110 },
1044 file_system_edits: [ 1111 },
1045 MoveFile { 1112 file_system_edits: [
1046 src: FileId( 1113 MoveFile {
1114 src: FileId(
1115 1,
1116 ),
1117 dst: AnchoredPathBuf {
1118 anchor: FileId(
1047 1, 1119 1,
1048 ), 1120 ),
1049 dst: AnchoredPathBuf { 1121 path: "bar.rs",
1050 anchor: FileId(
1051 1,
1052 ),
1053 path: "bar.rs",
1054 },
1055 }, 1122 },
1056 ], 1123 },
1057 is_snippet: false, 1124 ],
1058 }, 1125 is_snippet: false,
1059 } 1126 }
1060 "#]], 1127 "#]],
1061 ); 1128 );
@@ -1096,46 +1163,43 @@ pub mod foo$0;
1096// pub fn fun() {} 1163// pub fn fun() {}
1097"#, 1164"#,
1098 expect![[r#" 1165 expect![[r#"
1099 RangeInfo { 1166 SourceChange {
1100 range: 8..11, 1167 source_file_edits: {
1101 info: SourceChange { 1168 FileId(
1102 source_file_edits: { 1169 0,
1103 FileId( 1170 ): TextEdit {
1104 0, 1171 indels: [
1105 ): TextEdit { 1172 Indel {
1106 indels: [ 1173 insert: "foo2",
1107 Indel { 1174 delete: 27..30,
1108 insert: "foo2", 1175 },
1109 delete: 27..30, 1176 ],
1110 }, 1177 },
1111 ], 1178 FileId(
1112 }, 1179 1,
1113 FileId( 1180 ): TextEdit {
1114 1, 1181 indels: [
1115 ): TextEdit { 1182 Indel {
1116 indels: [ 1183 insert: "foo2",
1117 Indel { 1184 delete: 8..11,
1118 insert: "foo2", 1185 },
1119 delete: 8..11, 1186 ],
1120 },
1121 ],
1122 },
1123 }, 1187 },
1124 file_system_edits: [ 1188 },
1125 MoveFile { 1189 file_system_edits: [
1126 src: FileId( 1190 MoveFile {
1191 src: FileId(
1192 2,
1193 ),
1194 dst: AnchoredPathBuf {
1195 anchor: FileId(
1127 2, 1196 2,
1128 ), 1197 ),
1129 dst: AnchoredPathBuf { 1198 path: "foo2.rs",
1130 anchor: FileId(
1131 2,
1132 ),
1133 path: "foo2.rs",
1134 },
1135 }, 1199 },
1136 ], 1200 },
1137 is_snippet: false, 1201 ],
1138 }, 1202 is_snippet: false,
1139 } 1203 }
1140 "#]], 1204 "#]],
1141 ); 1205 );
@@ -1389,8 +1453,8 @@ impl Foo {
1389 } 1453 }
1390 1454
1391 #[test] 1455 #[test]
1392 fn test_initializer_use_field_init_shorthand() { 1456 fn test_rename_field_put_init_shorthand() {
1393 mark::check!(test_rename_field_expr_pat); 1457 mark::check!(test_rename_field_put_init_shorthand);
1394 check( 1458 check(
1395 "bar", 1459 "bar",
1396 r#" 1460 r#"
@@ -1411,23 +1475,46 @@ fn foo(bar: i32) -> Foo {
1411 } 1475 }
1412 1476
1413 #[test] 1477 #[test]
1414 fn test_struct_field_destructure_into_shorthand() { 1478 fn test_rename_local_put_init_shorthand() {
1479 mark::check!(test_rename_local_put_init_shorthand);
1480 check(
1481 "i",
1482 r#"
1483struct Foo { i: i32 }
1484
1485fn foo(bar$0: i32) -> Foo {
1486 Foo { i: bar }
1487}
1488"#,
1489 r#"
1490struct Foo { i: i32 }
1491
1492fn foo(i: i32) -> Foo {
1493 Foo { i }
1494}
1495"#,
1496 );
1497 }
1498
1499 #[test]
1500 fn test_struct_field_pat_into_shorthand() {
1501 mark::check!(test_rename_field_put_init_shorthand_pat);
1415 check( 1502 check(
1416 "baz", 1503 "baz",
1417 r#" 1504 r#"
1418struct Foo { i$0: i32 } 1505struct Foo { i$0: i32 }
1419 1506
1420fn foo(foo: Foo) { 1507fn foo(foo: Foo) {
1421 let Foo { i: baz } = foo; 1508 let Foo { i: ref baz @ qux } = foo;
1422 let _ = baz; 1509 let _ = qux;
1423} 1510}
1424"#, 1511"#,
1425 r#" 1512 r#"
1426struct Foo { baz: i32 } 1513struct Foo { baz: i32 }
1427 1514
1428fn foo(foo: Foo) { 1515fn foo(foo: Foo) {
1429 let Foo { baz } = foo; 1516 let Foo { ref baz @ qux } = foo;
1430 let _ = baz; 1517 let _ = qux;
1431} 1518}
1432"#, 1519"#,
1433 ); 1520 );
@@ -1501,6 +1588,27 @@ fn foo(Foo { i: bar }: foo) -> i32 {
1501 } 1588 }
1502 1589
1503 #[test] 1590 #[test]
1591 fn test_struct_field_complex_ident_pat() {
1592 check(
1593 "baz",
1594 r#"
1595struct Foo { i$0: i32 }
1596
1597fn foo(foo: Foo) {
1598 let Foo { ref i } = foo;
1599}
1600"#,
1601 r#"
1602struct Foo { baz: i32 }
1603
1604fn foo(foo: Foo) {
1605 let Foo { baz: ref i } = foo;
1606}
1607"#,
1608 );
1609 }
1610
1611 #[test]
1504 fn test_rename_lifetimes() { 1612 fn test_rename_lifetimes() {
1505 mark::check!(rename_lifetime); 1613 mark::check!(rename_lifetime);
1506 check( 1614 check(
@@ -1607,4 +1715,38 @@ impl Foo {
1607"#, 1715"#,
1608 ) 1716 )
1609 } 1717 }
1718
1719 #[test]
1720 fn test_rename_field_in_pat_in_macro_doesnt_shorthand() {
1721 // ideally we would be able to make this emit a short hand, but I doubt this is easily possible
1722 check(
1723 "baz",
1724 r#"
1725macro_rules! foo {
1726 ($pattern:pat) => {
1727 let $pattern = loop {};
1728 };
1729}
1730struct Foo {
1731 bar$0: u32,
1732}
1733fn foo() {
1734 foo!(Foo { bar: baz });
1735}
1736"#,
1737 r#"
1738macro_rules! foo {
1739 ($pattern:pat) => {
1740 let $pattern = loop {};
1741 };
1742}
1743struct Foo {
1744 baz: u32,
1745}
1746fn foo() {
1747 foo!(Foo { baz: baz });
1748}
1749"#,
1750 )
1751 }
1610} 1752}
diff --git a/crates/ide/src/runnables.rs b/crates/ide/src/runnables.rs
index f5ee7de86..1e7baed20 100644
--- a/crates/ide/src/runnables.rs
+++ b/crates/ide/src/runnables.rs
@@ -1,18 +1,19 @@
1use std::fmt; 1use std::fmt;
2 2
3use assists::utils::test_related_attribute;
4use cfg::CfgExpr; 3use cfg::CfgExpr;
5use hir::{AsAssocItem, HasAttrs, HasSource, Semantics}; 4use hir::{AsAssocItem, HasAttrs, HasSource, Semantics};
6use ide_db::{defs::Definition, RootDatabase}; 5use ide_assists::utils::test_related_attribute;
6use ide_db::{defs::Definition, RootDatabase, SymbolKind};
7use itertools::Itertools; 7use itertools::Itertools;
8use syntax::{ 8use syntax::{
9 ast::{self, AstNode, AttrsOwner, ModuleItemOwner}, 9 ast::{self, AstNode, AttrsOwner},
10 match_ast, SyntaxNode, 10 match_ast, SyntaxNode,
11}; 11};
12use test_utils::mark;
12 13
13use crate::{ 14use crate::{
14 display::{ToNav, TryToNav}, 15 display::{ToNav, TryToNav},
15 FileId, NavigationTarget, SymbolKind, 16 FileId, NavigationTarget,
16}; 17};
17 18
18#[derive(Debug, Clone)] 19#[derive(Debug, Clone)]
@@ -95,27 +96,45 @@ impl Runnable {
95// |=== 96// |===
96pub(crate) fn runnables(db: &RootDatabase, file_id: FileId) -> Vec<Runnable> { 97pub(crate) fn runnables(db: &RootDatabase, file_id: FileId) -> Vec<Runnable> {
97 let sema = Semantics::new(db); 98 let sema = Semantics::new(db);
98 let source_file = sema.parse(file_id); 99 let module = match sema.to_module_def(file_id) {
99 source_file 100 None => return Vec::new(),
100 .syntax() 101 Some(it) => it,
101 .descendants() 102 };
102 .filter_map(|item| { 103
103 let runnable = match_ast! { 104 let mut res = Vec::new();
104 match item { 105 runnables_mod(&sema, &mut res, module);
105 ast::Fn(func) => { 106 res
106 let def = sema.to_def(&func)?; 107}
107 runnable_fn(&sema, def) 108
108 }, 109fn runnables_mod(sema: &Semantics<RootDatabase>, acc: &mut Vec<Runnable>, module: hir::Module) {
109 ast::Module(it) => runnable_mod(&sema, it), 110 acc.extend(module.declarations(sema.db).into_iter().filter_map(|def| {
110 _ => None, 111 let runnable = match def {
111 } 112 hir::ModuleDef::Module(it) => runnable_mod(&sema, it),
112 }; 113 hir::ModuleDef::Function(it) => runnable_fn(&sema, it),
113 runnable.or_else(|| match doc_owner_to_def(&sema, item)? { 114 _ => None,
114 Definition::ModuleDef(def) => module_def_doctest(&sema, def), 115 };
115 _ => None, 116 runnable.or_else(|| module_def_doctest(&sema, def))
116 }) 117 }));
117 }) 118
118 .collect() 119 acc.extend(module.impl_defs(sema.db).into_iter().flat_map(|it| it.items(sema.db)).filter_map(
120 |def| match def {
121 hir::AssocItem::Function(it) => {
122 runnable_fn(&sema, it).or_else(|| module_def_doctest(&sema, it.into()))
123 }
124 hir::AssocItem::Const(it) => module_def_doctest(&sema, it.into()),
125 hir::AssocItem::TypeAlias(it) => module_def_doctest(&sema, it.into()),
126 },
127 ));
128
129 for def in module.declarations(sema.db) {
130 if let hir::ModuleDef::Module(submodule) = def {
131 match submodule.definition_source(sema.db).value {
132 hir::ModuleSource::Module(_) => runnables_mod(sema, acc, submodule),
133 hir::ModuleSource::SourceFile(_) => mark::hit!(dont_recurse_in_outline_submodules),
134 hir::ModuleSource::BlockExpr(_) => {} // inner items aren't runnable
135 }
136 }
137 }
119} 138}
120 139
121pub(crate) fn runnable_fn(sema: &Semantics<RootDatabase>, def: hir::Function) -> Option<Runnable> { 140pub(crate) fn runnable_fn(sema: &Semantics<RootDatabase>, def: hir::Function) -> Option<Runnable> {
@@ -150,26 +169,16 @@ pub(crate) fn runnable_fn(sema: &Semantics<RootDatabase>, def: hir::Function) ->
150 Some(Runnable { nav, kind, cfg }) 169 Some(Runnable { nav, kind, cfg })
151} 170}
152 171
153pub(crate) fn runnable_mod( 172pub(crate) fn runnable_mod(sema: &Semantics<RootDatabase>, def: hir::Module) -> Option<Runnable> {
154 sema: &Semantics<RootDatabase>, 173 if !has_test_function_or_multiple_test_submodules(sema, &def) {
155 module: ast::Module,
156) -> Option<Runnable> {
157 if !has_test_function_or_multiple_test_submodules(&module) {
158 return None; 174 return None;
159 } 175 }
160 let module_def = sema.to_def(&module)?; 176 let path =
177 def.path_to_root(sema.db).into_iter().rev().filter_map(|it| it.name(sema.db)).join("::");
161 178
162 let path = module_def
163 .path_to_root(sema.db)
164 .into_iter()
165 .rev()
166 .filter_map(|it| it.name(sema.db))
167 .join("::");
168
169 let def = sema.to_def(&module)?;
170 let attrs = def.attrs(sema.db); 179 let attrs = def.attrs(sema.db);
171 let cfg = attrs.cfg(); 180 let cfg = attrs.cfg();
172 let nav = module_def.to_nav(sema.db); 181 let nav = def.to_nav(sema.db);
173 Some(Runnable { nav, kind: RunnableKind::TestMod { path }, cfg }) 182 Some(Runnable { nav, kind: RunnableKind::TestMod { path }, cfg })
174} 183}
175 184
@@ -289,35 +298,37 @@ fn has_runnable_doc_test(attrs: &hir::Attrs) -> bool {
289 298
290// We could create runnables for modules with number_of_test_submodules > 0, 299// We could create runnables for modules with number_of_test_submodules > 0,
291// but that bloats the runnables for no real benefit, since all tests can be run by the submodule already 300// but that bloats the runnables for no real benefit, since all tests can be run by the submodule already
292fn has_test_function_or_multiple_test_submodules(module: &ast::Module) -> bool { 301fn has_test_function_or_multiple_test_submodules(
293 if let Some(item_list) = module.item_list() { 302 sema: &Semantics<RootDatabase>,
294 let mut number_of_test_submodules = 0; 303 module: &hir::Module,
295 304) -> bool {
296 for item in item_list.items() { 305 let mut number_of_test_submodules = 0;
297 match item { 306
298 ast::Item::Fn(f) => { 307 for item in module.declarations(sema.db) {
299 if test_related_attribute(&f).is_some() { 308 match item {
309 hir::ModuleDef::Function(f) => {
310 if let Some(it) = f.source(sema.db) {
311 if test_related_attribute(&it.value).is_some() {
300 return true; 312 return true;
301 } 313 }
302 } 314 }
303 ast::Item::Module(submodule) => { 315 }
304 if has_test_function_or_multiple_test_submodules(&submodule) { 316 hir::ModuleDef::Module(submodule) => {
305 number_of_test_submodules += 1; 317 if has_test_function_or_multiple_test_submodules(sema, &submodule) {
306 } 318 number_of_test_submodules += 1;
307 } 319 }
308 _ => (),
309 } 320 }
321 _ => (),
310 } 322 }
311
312 number_of_test_submodules > 1
313 } else {
314 false
315 } 323 }
324
325 number_of_test_submodules > 1
316} 326}
317 327
318#[cfg(test)] 328#[cfg(test)]
319mod tests { 329mod tests {
320 use expect_test::{expect, Expect}; 330 use expect_test::{expect, Expect};
331 use test_utils::mark;
321 332
322 use crate::fixture; 333 use crate::fixture;
323 334
@@ -753,6 +764,21 @@ mod root_tests {
753 file_id: FileId( 764 file_id: FileId(
754 0, 765 0,
755 ), 766 ),
767 full_range: 202..286,
768 focus_range: 206..220,
769 name: "nested_tests_2",
770 kind: Module,
771 },
772 kind: TestMod {
773 path: "root_tests::nested_tests_0::nested_tests_2",
774 },
775 cfg: None,
776 },
777 Runnable {
778 nav: NavigationTarget {
779 file_id: FileId(
780 0,
781 ),
756 full_range: 84..126, 782 full_range: 84..126,
757 focus_range: 107..121, 783 focus_range: 107..121,
758 name: "nested_test_11", 784 name: "nested_test_11",
@@ -793,21 +819,6 @@ mod root_tests {
793 file_id: FileId( 819 file_id: FileId(
794 0, 820 0,
795 ), 821 ),
796 full_range: 202..286,
797 focus_range: 206..220,
798 name: "nested_tests_2",
799 kind: Module,
800 },
801 kind: TestMod {
802 path: "root_tests::nested_tests_0::nested_tests_2",
803 },
804 cfg: None,
805 },
806 Runnable {
807 nav: NavigationTarget {
808 file_id: FileId(
809 0,
810 ),
811 full_range: 235..276, 822 full_range: 235..276,
812 focus_range: 258..271, 823 focus_range: 258..271,
813 name: "nested_test_2", 824 name: "nested_test_2",
@@ -982,4 +993,85 @@ impl Foo {
982 "#]], 993 "#]],
983 ); 994 );
984 } 995 }
996
997 #[test]
998 fn test_runnables_in_macro() {
999 check(
1000 r#"
1001//- /lib.rs
1002$0
1003macro_rules! gen {
1004 () => {
1005 #[test]
1006 fn foo_test() {
1007 }
1008 }
1009}
1010mod tests {
1011 gen!();
1012}
1013"#,
1014 &[&TEST, &TEST],
1015 expect![[r#"
1016 [
1017 Runnable {
1018 nav: NavigationTarget {
1019 file_id: FileId(
1020 0,
1021 ),
1022 full_range: 90..115,
1023 focus_range: 94..99,
1024 name: "tests",
1025 kind: Module,
1026 },
1027 kind: TestMod {
1028 path: "tests",
1029 },
1030 cfg: None,
1031 },
1032 Runnable {
1033 nav: NavigationTarget {
1034 file_id: FileId(
1035 0,
1036 ),
1037 full_range: 106..113,
1038 focus_range: 106..113,
1039 name: "foo_test",
1040 kind: Function,
1041 },
1042 kind: Test {
1043 test_id: Path(
1044 "tests::foo_test",
1045 ),
1046 attr: TestAttr {
1047 ignore: false,
1048 },
1049 },
1050 cfg: None,
1051 },
1052 ]
1053 "#]],
1054 );
1055 }
1056
1057 #[test]
1058 fn dont_recurse_in_outline_submodules() {
1059 mark::check!(dont_recurse_in_outline_submodules);
1060 check(
1061 r#"
1062//- /lib.rs
1063$0
1064mod m;
1065//- /m.rs
1066mod tests {
1067 #[test]
1068 fn t() {}
1069}
1070"#,
1071 &[],
1072 expect![[r#"
1073 []
1074 "#]],
1075 );
1076 }
985} 1077}
diff --git a/crates/ide/src/status.rs b/crates/ide/src/status.rs
index e10d7c3a4..137c38c0d 100644
--- a/crates/ide/src/status.rs
+++ b/crates/ide/src/status.rs
@@ -38,6 +38,7 @@ pub(crate) fn status(db: &RootDatabase, file_id: Option<FileId>) -> String {
38 format_to!(buf, "{}\n", syntax_tree_stats(db)); 38 format_to!(buf, "{}\n", syntax_tree_stats(db));
39 format_to!(buf, "{} (macros)\n", macro_syntax_tree_stats(db)); 39 format_to!(buf, "{} (macros)\n", macro_syntax_tree_stats(db));
40 format_to!(buf, "{} total\n", memory_usage()); 40 format_to!(buf, "{} total\n", memory_usage());
41 format_to!(buf, "\ncounts:\n{}", profile::countme::get_all());
41 42
42 if let Some(file_id) = file_id { 43 if let Some(file_id) = file_id {
43 format_to!(buf, "\nfile info:\n"); 44 format_to!(buf, "\nfile info:\n");
@@ -60,6 +61,7 @@ pub(crate) fn status(db: &RootDatabase, file_id: Option<FileId>) -> String {
60 None => format_to!(buf, "does not belong to any crate"), 61 None => format_to!(buf, "does not belong to any crate"),
61 } 62 }
62 } 63 }
64
63 buf 65 buf
64} 66}
65 67
diff --git a/crates/ide/src/syntax_highlighting.rs b/crates/ide/src/syntax_highlighting.rs
index f2d4da78d..9bed329d8 100644
--- a/crates/ide/src/syntax_highlighting.rs
+++ b/crates/ide/src/syntax_highlighting.rs
@@ -13,7 +13,7 @@ mod html;
13mod tests; 13mod tests;
14 14
15use hir::{Name, Semantics}; 15use hir::{Name, Semantics};
16use ide_db::RootDatabase; 16use ide_db::{RootDatabase, SymbolKind};
17use rustc_hash::FxHashMap; 17use rustc_hash::FxHashMap;
18use syntax::{ 18use syntax::{
19 ast::{self, HasFormatSpecifier}, 19 ast::{self, HasFormatSpecifier},
@@ -27,7 +27,7 @@ use crate::{
27 format::highlight_format_string, highlights::Highlights, 27 format::highlight_format_string, highlights::Highlights,
28 macro_rules::MacroRulesHighlighter, tags::Highlight, 28 macro_rules::MacroRulesHighlighter, tags::Highlight,
29 }, 29 },
30 FileId, HlMod, HlTag, SymbolKind, 30 FileId, HlMod, HlTag,
31}; 31};
32 32
33pub(crate) use html::highlight_as_html; 33pub(crate) use html::highlight_as_html;
@@ -72,7 +72,7 @@ pub(crate) fn highlight(
72 } 72 }
73 }; 73 };
74 74
75 let mut hl = highlights::Highlights::new(range_to_highlight); 75 let mut hl = highlights::Highlights::new(root.text_range());
76 traverse(&mut hl, &sema, &root, range_to_highlight, syntactic_name_ref_highlighting); 76 traverse(&mut hl, &sema, &root, range_to_highlight, syntactic_name_ref_highlighting);
77 hl.to_vec() 77 hl.to_vec()
78} 78}
diff --git a/crates/ide/src/syntax_highlighting/format.rs b/crates/ide/src/syntax_highlighting/format.rs
index a74ca844b..8c67a0863 100644
--- a/crates/ide/src/syntax_highlighting/format.rs
+++ b/crates/ide/src/syntax_highlighting/format.rs
@@ -1,10 +1,11 @@
1//! Syntax highlighting for format macro strings. 1//! Syntax highlighting for format macro strings.
2use ide_db::SymbolKind;
2use syntax::{ 3use syntax::{
3 ast::{self, FormatSpecifier, HasFormatSpecifier}, 4 ast::{self, FormatSpecifier, HasFormatSpecifier},
4 AstNode, AstToken, TextRange, 5 AstNode, AstToken, TextRange,
5}; 6};
6 7
7use crate::{syntax_highlighting::highlights::Highlights, HlRange, HlTag, SymbolKind}; 8use crate::{syntax_highlighting::highlights::Highlights, HlRange, HlTag};
8 9
9pub(super) fn highlight_format_string( 10pub(super) fn highlight_format_string(
10 stack: &mut Highlights, 11 stack: &mut Highlights,
@@ -30,7 +31,7 @@ fn is_format_string(string: &ast::String) -> Option<()> {
30 let parent = string.syntax().parent(); 31 let parent = string.syntax().parent();
31 32
32 let name = parent.parent().and_then(ast::MacroCall::cast)?.path()?.segment()?.name_ref()?; 33 let name = parent.parent().and_then(ast::MacroCall::cast)?.path()?.segment()?.name_ref()?;
33 if !matches!(name.text().as_str(), "format_args" | "format_args_nl") { 34 if !matches!(name.text(), "format_args" | "format_args_nl") {
34 return None; 35 return None;
35 } 36 }
36 37
diff --git a/crates/ide/src/syntax_highlighting/highlight.rs b/crates/ide/src/syntax_highlighting/highlight.rs
index 8625ef5df..24fcbb584 100644
--- a/crates/ide/src/syntax_highlighting/highlight.rs
+++ b/crates/ide/src/syntax_highlighting/highlight.rs
@@ -3,7 +3,7 @@
3use hir::{AsAssocItem, Semantics, VariantDef}; 3use hir::{AsAssocItem, Semantics, VariantDef};
4use ide_db::{ 4use ide_db::{
5 defs::{Definition, NameClass, NameRefClass}, 5 defs::{Definition, NameClass, NameRefClass},
6 RootDatabase, 6 RootDatabase, SymbolKind,
7}; 7};
8use rustc_hash::FxHashMap; 8use rustc_hash::FxHashMap;
9use syntax::{ 9use syntax::{
@@ -12,7 +12,7 @@ use syntax::{
12 SyntaxNode, SyntaxToken, T, 12 SyntaxNode, SyntaxToken, T,
13}; 13};
14 14
15use crate::{syntax_highlighting::tags::HlPunct, Highlight, HlMod, HlTag, SymbolKind}; 15use crate::{syntax_highlighting::tags::HlPunct, Highlight, HlMod, HlTag};
16 16
17pub(super) fn element( 17pub(super) fn element(
18 sema: &Semantics<RootDatabase>, 18 sema: &Semantics<RootDatabase>,
diff --git a/crates/ide/src/syntax_highlighting/inject.rs b/crates/ide/src/syntax_highlighting/inject.rs
index 281461493..8cdc3688f 100644
--- a/crates/ide/src/syntax_highlighting/inject.rs
+++ b/crates/ide/src/syntax_highlighting/inject.rs
@@ -116,7 +116,7 @@ pub(super) fn doc_comment(hl: &mut Highlights, node: &SyntaxNode) {
116 None => (), 116 None => (),
117 } 117 }
118 118
119 let line: &str = comment.text().as_str(); 119 let line: &str = comment.text();
120 let range = comment.syntax().text_range(); 120 let range = comment.syntax().text_range();
121 121
122 let mut pos = TextSize::of(comment.prefix()); 122 let mut pos = TextSize::of(comment.prefix());
diff --git a/crates/ide/src/syntax_highlighting/tags.rs b/crates/ide/src/syntax_highlighting/tags.rs
index 8dd05ac52..3c02fdb11 100644
--- a/crates/ide/src/syntax_highlighting/tags.rs
+++ b/crates/ide/src/syntax_highlighting/tags.rs
@@ -3,7 +3,7 @@
3 3
4use std::{fmt, ops}; 4use std::{fmt, ops};
5 5
6use crate::SymbolKind; 6use ide_db::SymbolKind;
7 7
8#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] 8#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
9pub struct Highlight { 9pub struct Highlight {
diff --git a/crates/ide/src/syntax_highlighting/test_data/highlighting.html b/crates/ide/src/syntax_highlighting/test_data/highlighting.html
index 237149566..2f983c0b8 100644
--- a/crates/ide/src/syntax_highlighting/test_data/highlighting.html
+++ b/crates/ide/src/syntax_highlighting/test_data/highlighting.html
@@ -108,6 +108,10 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
108 <span class="brace">}</span> 108 <span class="brace">}</span>
109<span class="brace">}</span> 109<span class="brace">}</span>
110 110
111<span class="keyword">fn</span> <span class="function declaration">str</span><span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="brace">{</span>
112 <span class="function">str</span><span class="parenthesis">(</span><span class="parenthesis">)</span><span class="semicolon">;</span>
113<span class="brace">}</span>
114
111<span class="keyword">static</span> <span class="keyword">mut</span> <span class="static declaration mutable unsafe">STATIC_MUT</span><span class="colon">:</span> <span class="builtin_type">i32</span> <span class="operator">=</span> <span class="numeric_literal">0</span><span class="semicolon">;</span> 115<span class="keyword">static</span> <span class="keyword">mut</span> <span class="static declaration mutable unsafe">STATIC_MUT</span><span class="colon">:</span> <span class="builtin_type">i32</span> <span class="operator">=</span> <span class="numeric_literal">0</span><span class="semicolon">;</span>
112 116
113<span class="keyword">fn</span> <span class="function declaration">foo</span><span class="angle">&lt;</span><span class="lifetime declaration">'a</span><span class="comma">,</span> <span class="type_param declaration">T</span><span class="angle">&gt;</span><span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="operator">-&gt;</span> <span class="type_param">T</span> <span class="brace">{</span> 117<span class="keyword">fn</span> <span class="function declaration">foo</span><span class="angle">&lt;</span><span class="lifetime declaration">'a</span><span class="comma">,</span> <span class="type_param declaration">T</span><span class="angle">&gt;</span><span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="operator">-&gt;</span> <span class="type_param">T</span> <span class="brace">{</span>
diff --git a/crates/ide/src/syntax_highlighting/tests.rs b/crates/ide/src/syntax_highlighting/tests.rs
index a62704c39..9d0cd1af5 100644
--- a/crates/ide/src/syntax_highlighting/tests.rs
+++ b/crates/ide/src/syntax_highlighting/tests.rs
@@ -1,9 +1,8 @@
1use std::fs;
2
3use expect_test::{expect_file, ExpectFile}; 1use expect_test::{expect_file, ExpectFile};
4use test_utils::project_dir; 2use ide_db::SymbolKind;
3use test_utils::{bench, bench_fixture, skip_slow_tests};
5 4
6use crate::{fixture, FileRange, TextRange}; 5use crate::{fixture, FileRange, HlTag, TextRange};
7 6
8#[test] 7#[test]
9fn test_highlighting() { 8fn test_highlighting() {
@@ -81,6 +80,10 @@ impl FooCopy {
81 } 80 }
82} 81}
83 82
83fn str() {
84 str();
85}
86
84static mut STATIC_MUT: i32 = 0; 87static mut STATIC_MUT: i32 = 0;
85 88
86fn foo<'a, T>() -> T { 89fn foo<'a, T>() -> T {
@@ -224,15 +227,45 @@ fn bar() {
224} 227}
225 228
226#[test] 229#[test]
227fn accidentally_quadratic() { 230fn benchmark_syntax_highlighting_long_struct() {
228 let file = project_dir().join("crates/syntax/test_data/accidentally_quadratic"); 231 if skip_slow_tests() {
229 let src = fs::read_to_string(file).unwrap(); 232 return;
233 }
230 234
231 let (analysis, file_id) = fixture::file(&src); 235 let fixture = bench_fixture::big_struct();
236 let (analysis, file_id) = fixture::file(&fixture);
232 237
233 // let t = std::time::Instant::now(); 238 let hash = {
234 let _ = analysis.highlight(file_id).unwrap(); 239 let _pt = bench("syntax highlighting long struct");
235 // eprintln!("elapsed: {:?}", t.elapsed()); 240 analysis
241 .highlight(file_id)
242 .unwrap()
243 .iter()
244 .filter(|it| it.highlight.tag == HlTag::Symbol(SymbolKind::Struct))
245 .count()
246 };
247 assert_eq!(hash, 2001);
248}
249
250#[test]
251fn benchmark_syntax_highlighting_parser() {
252 if skip_slow_tests() {
253 return;
254 }
255
256 let fixture = bench_fixture::glorious_old_parser();
257 let (analysis, file_id) = fixture::file(&fixture);
258
259 let hash = {
260 let _pt = bench("syntax highlighting parser");
261 analysis
262 .highlight(file_id)
263 .unwrap()
264 .iter()
265 .filter(|it| it.highlight.tag == HlTag::Symbol(SymbolKind::Function))
266 .count()
267 };
268 assert_eq!(hash, 1629);
236} 269}
237 270
238#[test] 271#[test]
diff --git a/crates/ide/src/syntax_tree.rs b/crates/ide/src/syntax_tree.rs
index 4c63d3023..f979ba434 100644
--- a/crates/ide/src/syntax_tree.rs
+++ b/crates/ide/src/syntax_tree.rs
@@ -100,147 +100,137 @@ fn syntax_tree_for_token(node: &SyntaxToken, text_range: TextRange) -> Option<St
100 100
101#[cfg(test)] 101#[cfg(test)]
102mod tests { 102mod tests {
103 use test_utils::assert_eq_text; 103 use expect_test::expect;
104 104
105 use crate::fixture; 105 use crate::fixture;
106 106
107 fn check(ra_fixture: &str, expect: expect_test::Expect) {
108 let (analysis, file_id) = fixture::file(ra_fixture);
109 let syn = analysis.syntax_tree(file_id, None).unwrap();
110 expect.assert_eq(&syn)
111 }
112 fn check_range(ra_fixture: &str, expect: expect_test::Expect) {
113 let (analysis, frange) = fixture::range(ra_fixture);
114 let syn = analysis.syntax_tree(frange.file_id, Some(frange.range)).unwrap();
115 expect.assert_eq(&syn)
116 }
117
107 #[test] 118 #[test]
108 fn test_syntax_tree_without_range() { 119 fn test_syntax_tree_without_range() {
109 // Basic syntax 120 // Basic syntax
110 let (analysis, file_id) = fixture::file(r#"fn foo() {}"#); 121 check(
111 let syn = analysis.syntax_tree(file_id, None).unwrap(); 122 r#"fn foo() {}"#,
112 123 expect![[r#"
113 assert_eq_text!( 124 [email protected]
114 r#" 125 [email protected]
115[email protected] 126 [email protected] "fn"
116 [email protected] 127 [email protected] " "
117 [email protected] "fn" 128 [email protected]
118 [email protected] " " 129 [email protected] "foo"
119 [email protected] 130 [email protected]
120 [email protected] "foo" 131 [email protected] "("
121 [email protected] 132 [email protected] ")"
122 [email protected] "(" 133 [email protected] " "
123 [email protected] ")" 134 [email protected]
124 [email protected] " " 135 [email protected] "{"
125 [email protected] 136 [email protected] "}"
126 [email protected] "{" 137 "#]],
127 [email protected] "}"
128"#
129 .trim(),
130 syn.trim()
131 ); 138 );
132 139
133 let (analysis, file_id) = fixture::file( 140 check(
134 r#" 141 r#"
135fn test() { 142fn test() {
136 assert!(" 143 assert!("
137 fn foo() { 144 fn foo() {
138 } 145 }
139 ", ""); 146 ", "");
140}"# 147}"#,
141 .trim(), 148 expect![[r#"
142 ); 149 [email protected]
143 let syn = analysis.syntax_tree(file_id, None).unwrap(); 150 [email protected]
144 151 [email protected] "fn"
145 assert_eq_text!( 152 [email protected] " "
146 r#" 153 [email protected]
147[email protected] 154 [email protected] "test"
148 [email protected] 155 [email protected]
149 [email protected] "fn" 156 [email protected] "("
150 [email protected] " " 157 [email protected] ")"
151 [email protected] 158 [email protected] " "
152 [email protected] "test" 159 [email protected]
153 [email protected] 160 [email protected] "{"
154 [email protected] "(" 161 [email protected] "\n "
155 [email protected] ")" 162 [email protected]
156 [email protected] " " 163 [email protected]
157 [email protected] 164 [email protected]
158 [email protected] "{" 165 [email protected]
159 [email protected] "\n " 166 [email protected]
160 [email protected] 167 [email protected] "assert"
161 [email protected] 168 [email protected] "!"
162 [email protected] 169 [email protected]
163 [email protected] 170 [email protected] "("
164 [email protected] 171 [email protected] "\"\n fn foo() {\n ..."
165 [email protected] "assert" 172 [email protected] ","
166 [email protected] "!" 173 [email protected] " "
167 [email protected] 174 [email protected] "\"\""
168 [email protected] "(" 175 [email protected] ")"
169 [email protected] "\"\n fn foo() {\n ..." 176 [email protected] ";"
170 [email protected] "," 177 [email protected] "\n"
171 [email protected] " " 178 [email protected] "}"
172 [email protected] "\"\"" 179 "#]],
173 [email protected] ")" 180 )
174 [email protected] ";"
175 [email protected] "\n"
176 [email protected] "}"
177"#
178 .trim(),
179 syn.trim()
180 );
181 } 181 }
182 182
183 #[test] 183 #[test]
184 fn test_syntax_tree_with_range() { 184 fn test_syntax_tree_with_range() {
185 let (analysis, range) = fixture::range(r#"$0fn foo() {}$0"#.trim()); 185 check_range(
186 let syn = analysis.syntax_tree(range.file_id, Some(range.range)).unwrap(); 186 r#"$0fn foo() {}$0"#,
187 187 expect![[r#"
188 assert_eq_text!( 188 [email protected]
189 r#" 189 [email protected] "fn"
190[email protected] 190 [email protected] " "
191 [email protected] "fn" 191 [email protected]
192 [email protected] " " 192 [email protected] "foo"
193 [email protected] 193 [email protected]
194 [email protected] "foo" 194 [email protected] "("
195 [email protected] 195 [email protected] ")"
196 [email protected] "(" 196 [email protected] " "
197 [email protected] ")" 197 [email protected]
198 [email protected] " " 198 [email protected] "{"
199 [email protected] 199 [email protected] "}"
200 [email protected] "{" 200 "#]],
201 [email protected] "}"
202"#
203 .trim(),
204 syn.trim()
205 ); 201 );
206 202
207 let (analysis, range) = fixture::range( 203 check_range(
208 r#"fn test() { 204 r#"
205fn test() {
209 $0assert!(" 206 $0assert!("
210 fn foo() { 207 fn foo() {
211 } 208 }
212 ", "");$0 209 ", "");$0
213}"# 210}"#,
214 .trim(), 211 expect![[r#"
215 ); 212 [email protected]
216 let syn = analysis.syntax_tree(range.file_id, Some(range.range)).unwrap(); 213 [email protected]
217 214 [email protected]
218 assert_eq_text!( 215 [email protected]
219 r#" 216 [email protected]
220[email protected] 217 [email protected] "assert"
221 [email protected] 218 [email protected] "!"
222 [email protected] 219 [email protected]
223 [email protected] 220 [email protected] "("
224 [email protected] 221 [email protected] "\"\n fn foo() {\n ..."
225 [email protected] "assert" 222 [email protected] ","
226 [email protected] "!" 223 [email protected] " "
227 [email protected] 224 [email protected] "\"\""
228 [email protected] "(" 225 [email protected] ")"
229 [email protected] "\"\n fn foo() {\n ..." 226 [email protected] ";"
230 [email protected] "," 227 "#]],
231 [email protected] " "
232 [email protected] "\"\""
233 [email protected] ")"
234 [email protected] ";"
235"#
236 .trim(),
237 syn.trim()
238 ); 228 );
239 } 229 }
240 230
241 #[test] 231 #[test]
242 fn test_syntax_tree_inside_string() { 232 fn test_syntax_tree_inside_string() {
243 let (analysis, range) = fixture::range( 233 check_range(
244 r#"fn test() { 234 r#"fn test() {
245 assert!(" 235 assert!("
246$0fn foo() { 236$0fn foo() {
@@ -248,33 +238,27 @@ $0fn foo() {
248fn bar() { 238fn bar() {
249} 239}
250 ", ""); 240 ", "");
251}"# 241}"#,
252 .trim(), 242 expect![[r#"
253 ); 243 [email protected]
254 let syn = analysis.syntax_tree(range.file_id, Some(range.range)).unwrap(); 244 [email protected]
255 assert_eq_text!( 245 [email protected] "fn"
256 r#" 246 [email protected] " "
257[email protected] 247 [email protected]
258 [email protected] 248 [email protected] "foo"
259 [email protected] "fn" 249 [email protected]
260 [email protected] " " 250 [email protected] "("
261 [email protected] 251 [email protected] ")"
262 [email protected] "foo" 252 [email protected] " "
263 [email protected] 253 [email protected]
264 [email protected] "(" 254 [email protected] "{"
265 [email protected] ")" 255 [email protected] "\n"
266 [email protected] " " 256 [email protected] "}"
267 [email protected] 257 "#]],
268 [email protected] "{"
269 [email protected] "\n"
270 [email protected] "}"
271"#
272 .trim(),
273 syn.trim()
274 ); 258 );
275 259
276 // With a raw string 260 // With a raw string
277 let (analysis, range) = fixture::range( 261 check_range(
278 r###"fn test() { 262 r###"fn test() {
279 assert!(r#" 263 assert!(r#"
280$0fn foo() { 264$0fn foo() {
@@ -282,76 +266,64 @@ $0fn foo() {
282fn bar() { 266fn bar() {
283} 267}
284 "#, ""); 268 "#, "");
285}"### 269}"###,
286 .trim(), 270 expect![[r#"
287 ); 271 [email protected]
288 let syn = analysis.syntax_tree(range.file_id, Some(range.range)).unwrap(); 272 [email protected]
289 assert_eq_text!( 273 [email protected] "fn"
290 r#" 274 [email protected] " "
291[email protected] 275 [email protected]
292 [email protected] 276 [email protected] "foo"
293 [email protected] "fn" 277 [email protected]
294 [email protected] " " 278 [email protected] "("
295 [email protected] 279 [email protected] ")"
296 [email protected] "foo" 280 [email protected] " "
297 [email protected] 281 [email protected]
298 [email protected] "(" 282 [email protected] "{"
299 [email protected] ")" 283 [email protected] "\n"
300 [email protected] " " 284 [email protected] "}"
301 [email protected] 285 "#]],
302 [email protected] "{"
303 [email protected] "\n"
304 [email protected] "}"
305"#
306 .trim(),
307 syn.trim()
308 ); 286 );
309 287
310 // With a raw string 288 // With a raw string
311 let (analysis, range) = fixture::range( 289 check_range(
312 r###"fn test() { 290 r###"fn test() {
313 assert!(r$0#" 291 assert!(r$0#"
314fn foo() { 292fn foo() {
315} 293}
316fn bar() { 294fn bar() {
317}"$0#, ""); 295}"$0#, "");
318}"### 296}"###,
319 .trim(), 297 expect![[r#"
320 ); 298 [email protected]
321 let syn = analysis.syntax_tree(range.file_id, Some(range.range)).unwrap(); 299 [email protected]
322 assert_eq_text!( 300 [email protected] "fn"
323 r#" 301 [email protected] " "
324[email protected] 302 [email protected]
325 [email protected] 303 [email protected] "foo"
326 [email protected] "fn" 304 [email protected]
327 [email protected] " " 305 [email protected] "("
328 [email protected] 306 [email protected] ")"
329 [email protected] "foo" 307 [email protected] " "
330 [email protected] 308 [email protected]
331 [email protected] "(" 309 [email protected] "{"
332 [email protected] ")" 310 [email protected] "\n"
333 [email protected] " " 311 [email protected] "}"
334 [email protected] 312 [email protected] "\n"
335 [email protected] "{" 313 [email protected]
336 [email protected] "\n" 314 [email protected] "fn"
337 [email protected] "}" 315 [email protected] " "
338 [email protected] "\n" 316 [email protected]
339 [email protected] 317 [email protected] "bar"
340 [email protected] "fn" 318 [email protected]
341 [email protected] " " 319 [email protected] "("
342 [email protected] 320 [email protected] ")"
343 [email protected] "bar" 321 [email protected] " "
344 [email protected] 322 [email protected]
345 [email protected] "(" 323 [email protected] "{"
346 [email protected] ")" 324 [email protected] "\n"
347 [email protected] " " 325 [email protected] "}"
348 [email protected] 326 "#]],
349 [email protected] "{"
350 [email protected] "\n"
351 [email protected] "}"
352"#
353 .trim(),
354 syn.trim()
355 ); 327 );
356 } 328 }
357} 329}
diff --git a/crates/ide/src/view_hir.rs b/crates/ide/src/view_hir.rs
index cfcfb7cfb..f8f3fae3d 100644
--- a/crates/ide/src/view_hir.rs
+++ b/crates/ide/src/view_hir.rs
@@ -11,7 +11,7 @@ use syntax::{algo::find_node_at_offset, ast, AstNode};
11// | VS Code | **Rust Analyzer: View Hir** 11// | VS Code | **Rust Analyzer: View Hir**
12// |=== 12// |===
13pub(crate) fn view_hir(db: &RootDatabase, position: FilePosition) -> String { 13pub(crate) fn view_hir(db: &RootDatabase, position: FilePosition) -> String {
14 body_hir(db, position).unwrap_or("Not inside a function body".to_string()) 14 body_hir(db, position).unwrap_or_else(|| "Not inside a function body".to_string())
15} 15}
16 16
17fn body_hir(db: &RootDatabase, position: FilePosition) -> Option<String> { 17fn body_hir(db: &RootDatabase, position: FilePosition) -> Option<String> {