diff options
author | Benjamin Coenen <[email protected]> | 2020-05-10 11:31:55 +0100 |
---|---|---|
committer | Benjamin Coenen <[email protected]> | 2020-05-10 11:31:55 +0100 |
commit | e80903a96564c2239489a8c630a4748bf21a3659 (patch) | |
tree | 12b31a1fd12deb2120065cea5a558425c8c1984f | |
parent | 6203e9c4faee288f16d93dbb7dd0f1f8df487d83 (diff) | |
parent | 4578154b608fa075595103d0c933da60d55b25c8 (diff) |
Merge branch 'master' of github.com:rust-analyzer/rust-analyzer into feat_4348
79 files changed, 1920 insertions, 1058 deletions
diff --git a/Cargo.lock b/Cargo.lock index 5d50a766f..41855f22e 100644 --- a/Cargo.lock +++ b/Cargo.lock | |||
@@ -68,9 +68,9 @@ dependencies = [ | |||
68 | 68 | ||
69 | [[package]] | 69 | [[package]] |
70 | name = "base64" | 70 | name = "base64" |
71 | version = "0.12.0" | 71 | version = "0.12.1" |
72 | source = "registry+https://github.com/rust-lang/crates.io-index" | 72 | source = "registry+https://github.com/rust-lang/crates.io-index" |
73 | checksum = "7d5ca2cd0adc3f48f9e9ea5a6bbdf9ccc0bfade884847e484d452414c7ccffb3" | 73 | checksum = "53d1ccbaf7d9ec9537465a97bf19edc1a4e158ecb49fc16178202238c569cc42" |
74 | 74 | ||
75 | [[package]] | 75 | [[package]] |
76 | name = "bitflags" | 76 | name = "bitflags" |
@@ -342,9 +342,9 @@ dependencies = [ | |||
342 | 342 | ||
343 | [[package]] | 343 | [[package]] |
344 | name = "filetime" | 344 | name = "filetime" |
345 | version = "0.2.9" | 345 | version = "0.2.10" |
346 | source = "registry+https://github.com/rust-lang/crates.io-index" | 346 | source = "registry+https://github.com/rust-lang/crates.io-index" |
347 | checksum = "f59efc38004c988e4201d11d263b8171f49a2e7ec0bdbb71773433f271504a5e" | 347 | checksum = "affc17579b132fc2461adf7c575cc6e8b134ebca52c51f5411388965227dc695" |
348 | dependencies = [ | 348 | dependencies = [ |
349 | "cfg-if", | 349 | "cfg-if", |
350 | "libc", | 350 | "libc", |
@@ -465,6 +465,15 @@ dependencies = [ | |||
465 | ] | 465 | ] |
466 | 466 | ||
467 | [[package]] | 467 | [[package]] |
468 | name = "home" | ||
469 | version = "0.5.3" | ||
470 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
471 | checksum = "2456aef2e6b6a9784192ae780c0f15bc57df0e918585282325e8c8ac27737654" | ||
472 | dependencies = [ | ||
473 | "winapi 0.3.8", | ||
474 | ] | ||
475 | |||
476 | [[package]] | ||
468 | name = "idna" | 477 | name = "idna" |
469 | version = "0.2.0" | 478 | version = "0.2.0" |
470 | source = "registry+https://github.com/rust-lang/crates.io-index" | 479 | source = "registry+https://github.com/rust-lang/crates.io-index" |
@@ -610,18 +619,18 @@ checksum = "99e85c08494b21a9054e7fe1374a732aeadaff3980b6990b94bfd3a70f690005" | |||
610 | 619 | ||
611 | [[package]] | 620 | [[package]] |
612 | name = "libloading" | 621 | name = "libloading" |
613 | version = "0.6.1" | 622 | version = "0.6.2" |
614 | source = "registry+https://github.com/rust-lang/crates.io-index" | 623 | source = "registry+https://github.com/rust-lang/crates.io-index" |
615 | checksum = "3c4f51b790f5bdb65acb4cc94bb81d7b2ee60348a5431ac1467d390b017600b0" | 624 | checksum = "2cadb8e769f070c45df05c78c7520eb4cd17061d4ab262e43cfc68b4d00ac71c" |
616 | dependencies = [ | 625 | dependencies = [ |
617 | "winapi 0.3.8", | 626 | "winapi 0.3.8", |
618 | ] | 627 | ] |
619 | 628 | ||
620 | [[package]] | 629 | [[package]] |
621 | name = "linked-hash-map" | 630 | name = "linked-hash-map" |
622 | version = "0.5.2" | 631 | version = "0.5.3" |
623 | source = "registry+https://github.com/rust-lang/crates.io-index" | 632 | source = "registry+https://github.com/rust-lang/crates.io-index" |
624 | checksum = "ae91b68aebc4ddb91978b11a1b02ddd8602a05ec19002801c5666000e05e0f83" | 633 | checksum = "8dd5a6d5999d9907cda8ed67bbd137d3af8085216c2ac62de5be860bd41f304a" |
625 | 634 | ||
626 | [[package]] | 635 | [[package]] |
627 | name = "lock_api" | 636 | name = "lock_api" |
@@ -824,9 +833,9 @@ dependencies = [ | |||
824 | 833 | ||
825 | [[package]] | 834 | [[package]] |
826 | name = "paste" | 835 | name = "paste" |
827 | version = "0.1.11" | 836 | version = "0.1.12" |
828 | source = "registry+https://github.com/rust-lang/crates.io-index" | 837 | source = "registry+https://github.com/rust-lang/crates.io-index" |
829 | checksum = "a3c897744f63f34f7ae3a024d9162bb5001f4ad661dd24bea0dc9f075d2de1c6" | 838 | checksum = "0a229b1c58c692edcaa5b9b0948084f130f55d2dcc15b02fcc5340b2b4521476" |
830 | dependencies = [ | 839 | dependencies = [ |
831 | "paste-impl", | 840 | "paste-impl", |
832 | "proc-macro-hack", | 841 | "proc-macro-hack", |
@@ -834,9 +843,9 @@ dependencies = [ | |||
834 | 843 | ||
835 | [[package]] | 844 | [[package]] |
836 | name = "paste-impl" | 845 | name = "paste-impl" |
837 | version = "0.1.11" | 846 | version = "0.1.12" |
838 | source = "registry+https://github.com/rust-lang/crates.io-index" | 847 | source = "registry+https://github.com/rust-lang/crates.io-index" |
839 | checksum = "66fd6f92e3594f2dd7b3fc23e42d82e292f7bcda6d8e5dcd167072327234ab89" | 848 | checksum = "2e0bf239e447e67ff6d16a8bb5e4d4bd2343acf5066061c0e8e06ac5ba8ca68c" |
840 | dependencies = [ | 849 | dependencies = [ |
841 | "proc-macro-hack", | 850 | "proc-macro-hack", |
842 | "proc-macro2", | 851 | "proc-macro2", |
@@ -886,9 +895,9 @@ checksum = "0d659fe7c6d27f25e9d80a1a094c223f5246f6a6596453e09d7229bf42750b63" | |||
886 | 895 | ||
887 | [[package]] | 896 | [[package]] |
888 | name = "proc-macro2" | 897 | name = "proc-macro2" |
889 | version = "1.0.10" | 898 | version = "1.0.12" |
890 | source = "registry+https://github.com/rust-lang/crates.io-index" | 899 | source = "registry+https://github.com/rust-lang/crates.io-index" |
891 | checksum = "df246d292ff63439fea9bc8c0a270bed0e390d5ebd4db4ba15aba81111b5abe3" | 900 | checksum = "8872cf6f48eee44265156c111456a700ab3483686b3f96df4cf5481c89157319" |
892 | dependencies = [ | 901 | dependencies = [ |
893 | "unicode-xid", | 902 | "unicode-xid", |
894 | ] | 903 | ] |
@@ -958,6 +967,7 @@ dependencies = [ | |||
958 | "jod-thread", | 967 | "jod-thread", |
959 | "log", | 968 | "log", |
960 | "lsp-types", | 969 | "lsp-types", |
970 | "ra_toolchain", | ||
961 | "serde_json", | 971 | "serde_json", |
962 | ] | 972 | ] |
963 | 973 | ||
@@ -1163,6 +1173,7 @@ dependencies = [ | |||
1163 | "ra_cfg", | 1173 | "ra_cfg", |
1164 | "ra_db", | 1174 | "ra_db", |
1165 | "ra_proc_macro", | 1175 | "ra_proc_macro", |
1176 | "ra_toolchain", | ||
1166 | "rustc-hash", | 1177 | "rustc-hash", |
1167 | "serde", | 1178 | "serde", |
1168 | "serde_json", | 1179 | "serde_json", |
@@ -1195,6 +1206,13 @@ dependencies = [ | |||
1195 | ] | 1206 | ] |
1196 | 1207 | ||
1197 | [[package]] | 1208 | [[package]] |
1209 | name = "ra_toolchain" | ||
1210 | version = "0.1.0" | ||
1211 | dependencies = [ | ||
1212 | "home", | ||
1213 | ] | ||
1214 | |||
1215 | [[package]] | ||
1198 | name = "ra_tt" | 1216 | name = "ra_tt" |
1199 | version = "0.1.0" | 1217 | version = "0.1.0" |
1200 | dependencies = [ | 1218 | dependencies = [ |
@@ -1581,9 +1599,9 @@ checksum = "ab16ced94dbd8a46c82fd81e3ed9a8727dac2977ea869d217bcc4ea1f122e81f" | |||
1581 | 1599 | ||
1582 | [[package]] | 1600 | [[package]] |
1583 | name = "syn" | 1601 | name = "syn" |
1584 | version = "1.0.18" | 1602 | version = "1.0.19" |
1585 | source = "registry+https://github.com/rust-lang/crates.io-index" | 1603 | source = "registry+https://github.com/rust-lang/crates.io-index" |
1586 | checksum = "410a7488c0a728c7ceb4ad59b9567eb4053d02e8cc7f5c0e0eeeb39518369213" | 1604 | checksum = "e8e5aa70697bb26ee62214ae3288465ecec0000f05182f039b477001f08f5ae7" |
1587 | dependencies = [ | 1605 | dependencies = [ |
1588 | "proc-macro2", | 1606 | "proc-macro2", |
1589 | "quote", | 1607 | "quote", |
diff --git a/crates/ra_assists/src/assist_ctx.rs b/crates/ra_assists/src/assist_context.rs index 871671de2..a680f752b 100644 --- a/crates/ra_assists/src/assist_ctx.rs +++ b/crates/ra_assists/src/assist_context.rs | |||
@@ -1,4 +1,6 @@ | |||
1 | //! This module defines `AssistCtx` -- the API surface that is exposed to assists. | 1 | //! See `AssistContext` |
2 | |||
3 | use algo::find_covering_element; | ||
2 | use hir::Semantics; | 4 | use hir::Semantics; |
3 | use ra_db::{FileId, FileRange}; | 5 | use ra_db::{FileId, FileRange}; |
4 | use ra_fmt::{leading_indent, reindent}; | 6 | use ra_fmt::{leading_indent, reindent}; |
@@ -7,54 +9,25 @@ use ra_ide_db::{ | |||
7 | RootDatabase, | 9 | RootDatabase, |
8 | }; | 10 | }; |
9 | use ra_syntax::{ | 11 | use ra_syntax::{ |
10 | algo::{self, find_covering_element, find_node_at_offset, SyntaxRewriter}, | 12 | algo::{self, find_node_at_offset, SyntaxRewriter}, |
11 | AstNode, SourceFile, SyntaxElement, SyntaxKind, SyntaxNode, SyntaxToken, TextRange, TextSize, | 13 | AstNode, SourceFile, SyntaxElement, SyntaxKind, SyntaxNode, SyntaxToken, TextRange, TextSize, |
12 | TokenAtOffset, | 14 | TokenAtOffset, |
13 | }; | 15 | }; |
14 | use ra_text_edit::TextEditBuilder; | 16 | use ra_text_edit::TextEditBuilder; |
15 | 17 | ||
16 | use crate::{AssistId, AssistLabel, GroupLabel, ResolvedAssist}; | 18 | use crate::{Assist, AssistId, GroupLabel, ResolvedAssist}; |
17 | |||
18 | #[derive(Clone, Debug)] | ||
19 | pub(crate) struct Assist(pub(crate) Vec<AssistInfo>); | ||
20 | |||
21 | #[derive(Clone, Debug)] | ||
22 | pub(crate) struct AssistInfo { | ||
23 | pub(crate) label: AssistLabel, | ||
24 | pub(crate) group_label: Option<GroupLabel>, | ||
25 | pub(crate) source_change: Option<SourceChange>, | ||
26 | } | ||
27 | |||
28 | impl AssistInfo { | ||
29 | fn new(label: AssistLabel) -> AssistInfo { | ||
30 | AssistInfo { label, group_label: None, source_change: None } | ||
31 | } | ||
32 | |||
33 | fn resolved(self, source_change: SourceChange) -> AssistInfo { | ||
34 | AssistInfo { source_change: Some(source_change), ..self } | ||
35 | } | ||
36 | |||
37 | fn with_group(self, group_label: GroupLabel) -> AssistInfo { | ||
38 | AssistInfo { group_label: Some(group_label), ..self } | ||
39 | } | ||
40 | |||
41 | pub(crate) fn into_resolved(self) -> Option<ResolvedAssist> { | ||
42 | let label = self.label; | ||
43 | self.source_change.map(|source_change| ResolvedAssist { label, source_change }) | ||
44 | } | ||
45 | } | ||
46 | 19 | ||
47 | /// `AssistCtx` allows to apply an assist or check if it could be applied. | 20 | /// `AssistContext` allows to apply an assist or check if it could be applied. |
48 | /// | 21 | /// |
49 | /// Assists use a somewhat over-engineered approach, given the current needs. The | 22 | /// Assists use a somewhat over-engineered approach, given the current needs. |
50 | /// assists workflow consists of two phases. In the first phase, a user asks for | 23 | /// The assists workflow consists of two phases. In the first phase, a user asks |
51 | /// the list of available assists. In the second phase, the user picks a | 24 | /// for the list of available assists. In the second phase, the user picks a |
52 | /// particular assist and it gets applied. | 25 | /// particular assist and it gets applied. |
53 | /// | 26 | /// |
54 | /// There are two peculiarities here: | 27 | /// There are two peculiarities here: |
55 | /// | 28 | /// |
56 | /// * first, we ideally avoid computing more things then necessary to answer | 29 | /// * first, we ideally avoid computing more things then necessary to answer "is |
57 | /// "is assist applicable" in the first phase. | 30 | /// assist applicable" in the first phase. |
58 | /// * second, when we are applying assist, we don't have a guarantee that there | 31 | /// * second, when we are applying assist, we don't have a guarantee that there |
59 | /// weren't any changes between the point when user asked for assists and when | 32 | /// weren't any changes between the point when user asked for assists and when |
60 | /// they applied a particular assist. So, when applying assist, we need to do | 33 | /// they applied a particular assist. So, when applying assist, we need to do |
@@ -63,152 +36,157 @@ impl AssistInfo { | |||
63 | /// To avoid repeating the same code twice for both "check" and "apply" | 36 | /// To avoid repeating the same code twice for both "check" and "apply" |
64 | /// functions, we use an approach reminiscent of that of Django's function based | 37 | /// functions, we use an approach reminiscent of that of Django's function based |
65 | /// views dealing with forms. Each assist receives a runtime parameter, | 38 | /// views dealing with forms. Each assist receives a runtime parameter, |
66 | /// `should_compute_edit`. It first check if an edit is applicable (potentially | 39 | /// `resolve`. It first check if an edit is applicable (potentially computing |
67 | /// computing info required to compute the actual edit). If it is applicable, | 40 | /// info required to compute the actual edit). If it is applicable, and |
68 | /// and `should_compute_edit` is `true`, it then computes the actual edit. | 41 | /// `resolve` is `true`, it then computes the actual edit. |
69 | /// | 42 | /// |
70 | /// So, to implement the original assists workflow, we can first apply each edit | 43 | /// So, to implement the original assists workflow, we can first apply each edit |
71 | /// with `should_compute_edit = false`, and then applying the selected edit | 44 | /// with `resolve = false`, and then applying the selected edit again, with |
72 | /// again, with `should_compute_edit = true` this time. | 45 | /// `resolve = true` this time. |
73 | /// | 46 | /// |
74 | /// Note, however, that we don't actually use such two-phase logic at the | 47 | /// Note, however, that we don't actually use such two-phase logic at the |
75 | /// moment, because the LSP API is pretty awkward in this place, and it's much | 48 | /// moment, because the LSP API is pretty awkward in this place, and it's much |
76 | /// easier to just compute the edit eagerly :-) | 49 | /// easier to just compute the edit eagerly :-) |
77 | #[derive(Clone)] | 50 | pub(crate) struct AssistContext<'a> { |
78 | pub(crate) struct AssistCtx<'a> { | 51 | pub(crate) sema: Semantics<'a, RootDatabase>, |
79 | pub(crate) sema: &'a Semantics<'a, RootDatabase>, | ||
80 | pub(crate) db: &'a RootDatabase, | 52 | pub(crate) db: &'a RootDatabase, |
81 | pub(crate) frange: FileRange, | 53 | pub(crate) frange: FileRange, |
82 | source_file: SourceFile, | 54 | source_file: SourceFile, |
83 | should_compute_edit: bool, | ||
84 | } | 55 | } |
85 | 56 | ||
86 | impl<'a> AssistCtx<'a> { | 57 | impl<'a> AssistContext<'a> { |
87 | pub fn new( | 58 | pub fn new(sema: Semantics<'a, RootDatabase>, frange: FileRange) -> AssistContext<'a> { |
88 | sema: &'a Semantics<'a, RootDatabase>, | ||
89 | frange: FileRange, | ||
90 | should_compute_edit: bool, | ||
91 | ) -> AssistCtx<'a> { | ||
92 | let source_file = sema.parse(frange.file_id); | 59 | let source_file = sema.parse(frange.file_id); |
93 | AssistCtx { sema, db: sema.db, frange, source_file, should_compute_edit } | 60 | let db = sema.db; |
61 | AssistContext { sema, db, frange, source_file } | ||
94 | } | 62 | } |
95 | 63 | ||
96 | pub(crate) fn add_assist( | 64 | // NB, this ignores active selection. |
97 | self, | 65 | pub(crate) fn offset(&self) -> TextSize { |
98 | id: AssistId, | 66 | self.frange.range.start() |
99 | label: impl Into<String>, | ||
100 | target: TextRange, | ||
101 | f: impl FnOnce(&mut ActionBuilder), | ||
102 | ) -> Option<Assist> { | ||
103 | let label = AssistLabel::new(id, label.into(), None, target); | ||
104 | let change_label = label.label.clone(); | ||
105 | let mut info = AssistInfo::new(label); | ||
106 | if self.should_compute_edit { | ||
107 | let source_change = { | ||
108 | let mut edit = ActionBuilder::new(&self); | ||
109 | f(&mut edit); | ||
110 | edit.build(change_label) | ||
111 | }; | ||
112 | info = info.resolved(source_change) | ||
113 | }; | ||
114 | |||
115 | Some(Assist(vec![info])) | ||
116 | } | ||
117 | |||
118 | pub(crate) fn add_assist_group(self, group_name: impl Into<String>) -> AssistGroup<'a> { | ||
119 | let group = GroupLabel(group_name.into()); | ||
120 | AssistGroup { ctx: self, group, assists: Vec::new() } | ||
121 | } | 67 | } |
122 | 68 | ||
123 | pub(crate) fn token_at_offset(&self) -> TokenAtOffset<SyntaxToken> { | 69 | pub(crate) fn token_at_offset(&self) -> TokenAtOffset<SyntaxToken> { |
124 | self.source_file.syntax().token_at_offset(self.frange.range.start()) | 70 | self.source_file.syntax().token_at_offset(self.offset()) |
125 | } | 71 | } |
126 | |||
127 | pub(crate) fn find_token_at_offset(&self, kind: SyntaxKind) -> Option<SyntaxToken> { | 72 | pub(crate) fn find_token_at_offset(&self, kind: SyntaxKind) -> Option<SyntaxToken> { |
128 | self.token_at_offset().find(|it| it.kind() == kind) | 73 | self.token_at_offset().find(|it| it.kind() == kind) |
129 | } | 74 | } |
130 | |||
131 | pub(crate) fn find_node_at_offset<N: AstNode>(&self) -> Option<N> { | 75 | pub(crate) fn find_node_at_offset<N: AstNode>(&self) -> Option<N> { |
132 | find_node_at_offset(self.source_file.syntax(), self.frange.range.start()) | 76 | find_node_at_offset(self.source_file.syntax(), self.offset()) |
133 | } | 77 | } |
134 | |||
135 | pub(crate) fn find_node_at_offset_with_descend<N: AstNode>(&self) -> Option<N> { | 78 | pub(crate) fn find_node_at_offset_with_descend<N: AstNode>(&self) -> Option<N> { |
136 | self.sema | 79 | self.sema.find_node_at_offset_with_descend(self.source_file.syntax(), self.offset()) |
137 | .find_node_at_offset_with_descend(self.source_file.syntax(), self.frange.range.start()) | ||
138 | } | 80 | } |
139 | |||
140 | pub(crate) fn covering_element(&self) -> SyntaxElement { | 81 | pub(crate) fn covering_element(&self) -> SyntaxElement { |
141 | find_covering_element(self.source_file.syntax(), self.frange.range) | 82 | find_covering_element(self.source_file.syntax(), self.frange.range) |
142 | } | 83 | } |
84 | // FIXME: remove | ||
143 | pub(crate) fn covering_node_for_range(&self, range: TextRange) -> SyntaxElement { | 85 | pub(crate) fn covering_node_for_range(&self, range: TextRange) -> SyntaxElement { |
144 | find_covering_element(self.source_file.syntax(), range) | 86 | find_covering_element(self.source_file.syntax(), range) |
145 | } | 87 | } |
146 | } | 88 | } |
147 | 89 | ||
148 | pub(crate) struct AssistGroup<'a> { | 90 | pub(crate) struct Assists { |
149 | ctx: AssistCtx<'a>, | 91 | resolve: bool, |
150 | group: GroupLabel, | 92 | file: FileId, |
151 | assists: Vec<AssistInfo>, | 93 | buf: Vec<(Assist, Option<SourceChange>)>, |
152 | } | 94 | } |
153 | 95 | ||
154 | impl<'a> AssistGroup<'a> { | 96 | impl Assists { |
155 | pub(crate) fn add_assist( | 97 | pub(crate) fn new_resolved(ctx: &AssistContext) -> Assists { |
98 | Assists { resolve: true, file: ctx.frange.file_id, buf: Vec::new() } | ||
99 | } | ||
100 | pub(crate) fn new_unresolved(ctx: &AssistContext) -> Assists { | ||
101 | Assists { resolve: false, file: ctx.frange.file_id, buf: Vec::new() } | ||
102 | } | ||
103 | |||
104 | pub(crate) fn finish_unresolved(self) -> Vec<Assist> { | ||
105 | assert!(!self.resolve); | ||
106 | self.finish() | ||
107 | .into_iter() | ||
108 | .map(|(label, edit)| { | ||
109 | assert!(edit.is_none()); | ||
110 | label | ||
111 | }) | ||
112 | .collect() | ||
113 | } | ||
114 | |||
115 | pub(crate) fn finish_resolved(self) -> Vec<ResolvedAssist> { | ||
116 | assert!(self.resolve); | ||
117 | self.finish() | ||
118 | .into_iter() | ||
119 | .map(|(label, edit)| ResolvedAssist { assist: label, source_change: edit.unwrap() }) | ||
120 | .collect() | ||
121 | } | ||
122 | |||
123 | pub(crate) fn add( | ||
156 | &mut self, | 124 | &mut self, |
157 | id: AssistId, | 125 | id: AssistId, |
158 | label: impl Into<String>, | 126 | label: impl Into<String>, |
159 | target: TextRange, | 127 | target: TextRange, |
160 | f: impl FnOnce(&mut ActionBuilder), | 128 | f: impl FnOnce(&mut AssistBuilder), |
161 | ) { | 129 | ) -> Option<()> { |
162 | let label = AssistLabel::new(id, label.into(), Some(self.group.clone()), target); | 130 | let label = Assist::new(id, label.into(), None, target); |
131 | self.add_impl(label, f) | ||
132 | } | ||
133 | pub(crate) fn add_group( | ||
134 | &mut self, | ||
135 | group: &GroupLabel, | ||
136 | id: AssistId, | ||
137 | label: impl Into<String>, | ||
138 | target: TextRange, | ||
139 | f: impl FnOnce(&mut AssistBuilder), | ||
140 | ) -> Option<()> { | ||
141 | let label = Assist::new(id, label.into(), Some(group.clone()), target); | ||
142 | self.add_impl(label, f) | ||
143 | } | ||
144 | fn add_impl(&mut self, label: Assist, f: impl FnOnce(&mut AssistBuilder)) -> Option<()> { | ||
163 | let change_label = label.label.clone(); | 145 | let change_label = label.label.clone(); |
164 | let mut info = AssistInfo::new(label).with_group(self.group.clone()); | 146 | let source_change = if self.resolve { |
165 | if self.ctx.should_compute_edit { | 147 | let mut builder = AssistBuilder::new(self.file); |
166 | let source_change = { | 148 | f(&mut builder); |
167 | let mut edit = ActionBuilder::new(&self.ctx); | 149 | Some(builder.finish(change_label)) |
168 | f(&mut edit); | 150 | } else { |
169 | edit.build(change_label) | 151 | None |
170 | }; | ||
171 | info = info.resolved(source_change) | ||
172 | }; | 152 | }; |
173 | 153 | ||
174 | self.assists.push(info) | 154 | self.buf.push((label, source_change)); |
155 | Some(()) | ||
175 | } | 156 | } |
176 | 157 | ||
177 | pub(crate) fn finish(self) -> Option<Assist> { | 158 | fn finish(mut self) -> Vec<(Assist, Option<SourceChange>)> { |
178 | if self.assists.is_empty() { | 159 | self.buf.sort_by_key(|(label, _edit)| label.target.len()); |
179 | None | 160 | self.buf |
180 | } else { | ||
181 | Some(Assist(self.assists)) | ||
182 | } | ||
183 | } | 161 | } |
184 | } | 162 | } |
185 | 163 | ||
186 | pub(crate) struct ActionBuilder<'a, 'b> { | 164 | pub(crate) struct AssistBuilder { |
187 | edit: TextEditBuilder, | 165 | edit: TextEditBuilder, |
188 | cursor_position: Option<TextSize>, | 166 | cursor_position: Option<TextSize>, |
189 | file: FileId, | 167 | file: FileId, |
190 | ctx: &'a AssistCtx<'b>, | ||
191 | } | 168 | } |
192 | 169 | ||
193 | impl<'a, 'b> ActionBuilder<'a, 'b> { | 170 | impl AssistBuilder { |
194 | fn new(ctx: &'a AssistCtx<'b>) -> Self { | 171 | pub(crate) fn new(file: FileId) -> AssistBuilder { |
195 | Self { | 172 | AssistBuilder { edit: TextEditBuilder::default(), cursor_position: None, file } |
196 | edit: TextEditBuilder::default(), | ||
197 | cursor_position: None, | ||
198 | file: ctx.frange.file_id, | ||
199 | ctx, | ||
200 | } | ||
201 | } | 173 | } |
202 | 174 | ||
203 | pub(crate) fn ctx(&self) -> &AssistCtx<'b> { | 175 | /// Remove specified `range` of text. |
204 | &self.ctx | 176 | pub(crate) fn delete(&mut self, range: TextRange) { |
177 | self.edit.delete(range) | ||
178 | } | ||
179 | /// Append specified `text` at the given `offset` | ||
180 | pub(crate) fn insert(&mut self, offset: TextSize, text: impl Into<String>) { | ||
181 | self.edit.insert(offset, text.into()) | ||
205 | } | 182 | } |
206 | |||
207 | /// Replaces specified `range` of text with a given string. | 183 | /// Replaces specified `range` of text with a given string. |
208 | pub(crate) fn replace(&mut self, range: TextRange, replace_with: impl Into<String>) { | 184 | pub(crate) fn replace(&mut self, range: TextRange, replace_with: impl Into<String>) { |
209 | self.edit.replace(range, replace_with.into()) | 185 | self.edit.replace(range, replace_with.into()) |
210 | } | 186 | } |
211 | 187 | pub(crate) fn replace_ast<N: AstNode>(&mut self, old: N, new: N) { | |
188 | algo::diff(old.syntax(), new.syntax()).into_text_edit(&mut self.edit) | ||
189 | } | ||
212 | /// Replaces specified `node` of text with a given string, reindenting the | 190 | /// Replaces specified `node` of text with a given string, reindenting the |
213 | /// string to maintain `node`'s existing indent. | 191 | /// string to maintain `node`'s existing indent. |
214 | // FIXME: remove in favor of ra_syntax::edit::IndentLevel::increase_indent | 192 | // FIXME: remove in favor of ra_syntax::edit::IndentLevel::increase_indent |
@@ -223,42 +201,28 @@ impl<'a, 'b> ActionBuilder<'a, 'b> { | |||
223 | } | 201 | } |
224 | self.replace(node.text_range(), replace_with) | 202 | self.replace(node.text_range(), replace_with) |
225 | } | 203 | } |
226 | 204 | pub(crate) fn rewrite(&mut self, rewriter: SyntaxRewriter) { | |
227 | /// Remove specified `range` of text. | 205 | let node = rewriter.rewrite_root().unwrap(); |
228 | #[allow(unused)] | 206 | let new = rewriter.rewrite(&node); |
229 | pub(crate) fn delete(&mut self, range: TextRange) { | 207 | algo::diff(&node, &new).into_text_edit(&mut self.edit) |
230 | self.edit.delete(range) | ||
231 | } | ||
232 | |||
233 | /// Append specified `text` at the given `offset` | ||
234 | pub(crate) fn insert(&mut self, offset: TextSize, text: impl Into<String>) { | ||
235 | self.edit.insert(offset, text.into()) | ||
236 | } | 208 | } |
237 | 209 | ||
238 | /// Specify desired position of the cursor after the assist is applied. | 210 | /// Specify desired position of the cursor after the assist is applied. |
239 | pub(crate) fn set_cursor(&mut self, offset: TextSize) { | 211 | pub(crate) fn set_cursor(&mut self, offset: TextSize) { |
240 | self.cursor_position = Some(offset) | 212 | self.cursor_position = Some(offset) |
241 | } | 213 | } |
214 | // FIXME: better API | ||
215 | pub(crate) fn set_file(&mut self, assist_file: FileId) { | ||
216 | self.file = assist_file; | ||
217 | } | ||
242 | 218 | ||
219 | // FIXME: kill this API | ||
243 | /// Get access to the raw `TextEditBuilder`. | 220 | /// Get access to the raw `TextEditBuilder`. |
244 | pub(crate) fn text_edit_builder(&mut self) -> &mut TextEditBuilder { | 221 | pub(crate) fn text_edit_builder(&mut self) -> &mut TextEditBuilder { |
245 | &mut self.edit | 222 | &mut self.edit |
246 | } | 223 | } |
247 | 224 | ||
248 | pub(crate) fn replace_ast<N: AstNode>(&mut self, old: N, new: N) { | 225 | fn finish(self, change_label: String) -> SourceChange { |
249 | algo::diff(old.syntax(), new.syntax()).into_text_edit(&mut self.edit) | ||
250 | } | ||
251 | pub(crate) fn rewrite(&mut self, rewriter: SyntaxRewriter) { | ||
252 | let node = rewriter.rewrite_root().unwrap(); | ||
253 | let new = rewriter.rewrite(&node); | ||
254 | algo::diff(&node, &new).into_text_edit(&mut self.edit) | ||
255 | } | ||
256 | |||
257 | pub(crate) fn set_file(&mut self, assist_file: FileId) { | ||
258 | self.file = assist_file; | ||
259 | } | ||
260 | |||
261 | fn build(self, change_label: String) -> SourceChange { | ||
262 | let edit = self.edit.finish(); | 226 | let edit = self.edit.finish(); |
263 | if edit.is_empty() && self.cursor_position.is_none() { | 227 | if edit.is_empty() && self.cursor_position.is_none() { |
264 | panic!("Only call `add_assist` if the assist can be applied") | 228 | panic!("Only call `add_assist` if the assist can be applied") |
diff --git a/crates/ra_assists/src/handlers/add_custom_impl.rs b/crates/ra_assists/src/handlers/add_custom_impl.rs index 869d4dc04..795a225a4 100644 --- a/crates/ra_assists/src/handlers/add_custom_impl.rs +++ b/crates/ra_assists/src/handlers/add_custom_impl.rs | |||
@@ -6,7 +6,10 @@ use ra_syntax::{ | |||
6 | }; | 6 | }; |
7 | use stdx::SepBy; | 7 | use stdx::SepBy; |
8 | 8 | ||
9 | use crate::{Assist, AssistCtx, AssistId}; | 9 | use crate::{ |
10 | assist_context::{AssistContext, Assists}, | ||
11 | AssistId, | ||
12 | }; | ||
10 | 13 | ||
11 | // Assist: add_custom_impl | 14 | // Assist: add_custom_impl |
12 | // | 15 | // |
@@ -25,7 +28,7 @@ use crate::{Assist, AssistCtx, AssistId}; | |||
25 | // | 28 | // |
26 | // } | 29 | // } |
27 | // ``` | 30 | // ``` |
28 | pub(crate) fn add_custom_impl(ctx: AssistCtx) -> Option<Assist> { | 31 | pub(crate) fn add_custom_impl(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { |
29 | let input = ctx.find_node_at_offset::<ast::AttrInput>()?; | 32 | let input = ctx.find_node_at_offset::<ast::AttrInput>()?; |
30 | let attr = input.syntax().parent().and_then(ast::Attr::cast)?; | 33 | let attr = input.syntax().parent().and_then(ast::Attr::cast)?; |
31 | 34 | ||
@@ -49,7 +52,7 @@ pub(crate) fn add_custom_impl(ctx: AssistCtx) -> Option<Assist> { | |||
49 | format!("Add custom impl '{}' for '{}'", trait_token.text().as_str(), annotated_name); | 52 | format!("Add custom impl '{}' for '{}'", trait_token.text().as_str(), annotated_name); |
50 | 53 | ||
51 | let target = attr.syntax().text_range(); | 54 | let target = attr.syntax().text_range(); |
52 | ctx.add_assist(AssistId("add_custom_impl"), label, target, |edit| { | 55 | acc.add(AssistId("add_custom_impl"), label, target, |edit| { |
53 | let new_attr_input = input | 56 | let new_attr_input = input |
54 | .syntax() | 57 | .syntax() |
55 | .descendants_with_tokens() | 58 | .descendants_with_tokens() |
diff --git a/crates/ra_assists/src/handlers/add_derive.rs b/crates/ra_assists/src/handlers/add_derive.rs index 2a6bb1cae..fb08c19e9 100644 --- a/crates/ra_assists/src/handlers/add_derive.rs +++ b/crates/ra_assists/src/handlers/add_derive.rs | |||
@@ -4,7 +4,7 @@ use ra_syntax::{ | |||
4 | TextSize, | 4 | TextSize, |
5 | }; | 5 | }; |
6 | 6 | ||
7 | use crate::{Assist, AssistCtx, AssistId}; | 7 | use crate::{AssistContext, AssistId, Assists}; |
8 | 8 | ||
9 | // Assist: add_derive | 9 | // Assist: add_derive |
10 | // | 10 | // |
@@ -24,11 +24,11 @@ use crate::{Assist, AssistCtx, AssistId}; | |||
24 | // y: u32, | 24 | // y: u32, |
25 | // } | 25 | // } |
26 | // ``` | 26 | // ``` |
27 | pub(crate) fn add_derive(ctx: AssistCtx) -> Option<Assist> { | 27 | pub(crate) fn add_derive(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { |
28 | let nominal = ctx.find_node_at_offset::<ast::NominalDef>()?; | 28 | let nominal = ctx.find_node_at_offset::<ast::NominalDef>()?; |
29 | let node_start = derive_insertion_offset(&nominal)?; | 29 | let node_start = derive_insertion_offset(&nominal)?; |
30 | let target = nominal.syntax().text_range(); | 30 | let target = nominal.syntax().text_range(); |
31 | ctx.add_assist(AssistId("add_derive"), "Add `#[derive]`", target, |edit| { | 31 | acc.add(AssistId("add_derive"), "Add `#[derive]`", target, |edit| { |
32 | let derive_attr = nominal | 32 | let derive_attr = nominal |
33 | .attrs() | 33 | .attrs() |
34 | .filter_map(|x| x.as_simple_call()) | 34 | .filter_map(|x| x.as_simple_call()) |
@@ -57,9 +57,10 @@ fn derive_insertion_offset(nominal: &ast::NominalDef) -> Option<TextSize> { | |||
57 | 57 | ||
58 | #[cfg(test)] | 58 | #[cfg(test)] |
59 | mod tests { | 59 | mod tests { |
60 | use super::*; | ||
61 | use crate::tests::{check_assist, check_assist_target}; | 60 | use crate::tests::{check_assist, check_assist_target}; |
62 | 61 | ||
62 | use super::*; | ||
63 | |||
63 | #[test] | 64 | #[test] |
64 | fn add_derive_new() { | 65 | fn add_derive_new() { |
65 | check_assist( | 66 | check_assist( |
diff --git a/crates/ra_assists/src/handlers/add_explicit_type.rs b/crates/ra_assists/src/handlers/add_explicit_type.rs index a59ec16b2..146cc75df 100644 --- a/crates/ra_assists/src/handlers/add_explicit_type.rs +++ b/crates/ra_assists/src/handlers/add_explicit_type.rs | |||
@@ -4,7 +4,7 @@ use ra_syntax::{ | |||
4 | TextRange, | 4 | TextRange, |
5 | }; | 5 | }; |
6 | 6 | ||
7 | use crate::{Assist, AssistCtx, AssistId}; | 7 | use crate::{AssistContext, AssistId, Assists}; |
8 | 8 | ||
9 | // Assist: add_explicit_type | 9 | // Assist: add_explicit_type |
10 | // | 10 | // |
@@ -21,8 +21,9 @@ use crate::{Assist, AssistCtx, AssistId}; | |||
21 | // let x: i32 = 92; | 21 | // let x: i32 = 92; |
22 | // } | 22 | // } |
23 | // ``` | 23 | // ``` |
24 | pub(crate) fn add_explicit_type(ctx: AssistCtx) -> Option<Assist> { | 24 | pub(crate) fn add_explicit_type(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { |
25 | let stmt = ctx.find_node_at_offset::<LetStmt>()?; | 25 | let stmt = ctx.find_node_at_offset::<LetStmt>()?; |
26 | let module = ctx.sema.scope(stmt.syntax()).module()?; | ||
26 | let expr = stmt.initializer()?; | 27 | let expr = stmt.initializer()?; |
27 | let pat = stmt.pat()?; | 28 | let pat = stmt.pat()?; |
28 | // Must be a binding | 29 | // Must be a binding |
@@ -57,17 +58,17 @@ pub(crate) fn add_explicit_type(ctx: AssistCtx) -> Option<Assist> { | |||
57 | return None; | 58 | return None; |
58 | } | 59 | } |
59 | 60 | ||
60 | let db = ctx.db; | 61 | let inferred_type = ty.display_source_code(ctx.db, module.into()).ok()?; |
61 | let new_type_string = ty.display_truncated(db, None).to_string(); | 62 | acc.add( |
62 | ctx.add_assist( | ||
63 | AssistId("add_explicit_type"), | 63 | AssistId("add_explicit_type"), |
64 | format!("Insert explicit type '{}'", new_type_string), | 64 | format!("Insert explicit type '{}'", inferred_type), |
65 | pat_range, | 65 | pat_range, |
66 | |edit| { | 66 | |builder| match ascribed_ty { |
67 | if let Some(ascribed_ty) = ascribed_ty { | 67 | Some(ascribed_ty) => { |
68 | edit.replace(ascribed_ty.syntax().text_range(), new_type_string); | 68 | builder.replace(ascribed_ty.syntax().text_range(), inferred_type); |
69 | } else { | 69 | } |
70 | edit.insert(name_range.end(), format!(": {}", new_type_string)); | 70 | None => { |
71 | builder.insert(name_range.end(), format!(": {}", inferred_type)); | ||
71 | } | 72 | } |
72 | }, | 73 | }, |
73 | ) | 74 | ) |
@@ -208,7 +209,7 @@ struct Test<K, T = u8> { | |||
208 | } | 209 | } |
209 | 210 | ||
210 | fn main() { | 211 | fn main() { |
211 | let test<|>: Test<i32> = Test { t: 23, k: 33 }; | 212 | let test<|>: Test<i32, u8> = Test { t: 23, k: 33 }; |
212 | }"#, | 213 | }"#, |
213 | ); | 214 | ); |
214 | } | 215 | } |
diff --git a/crates/ra_assists/src/handlers/add_from_impl_for_enum.rs b/crates/ra_assists/src/handlers/add_from_impl_for_enum.rs index 81deb3dfa..6a49b7dbd 100644 --- a/crates/ra_assists/src/handlers/add_from_impl_for_enum.rs +++ b/crates/ra_assists/src/handlers/add_from_impl_for_enum.rs | |||
@@ -1,13 +1,10 @@ | |||
1 | use ra_ide_db::RootDatabase; | 1 | use ra_ide_db::RootDatabase; |
2 | use ra_syntax::{ | 2 | use ra_syntax::ast::{self, AstNode, NameOwner}; |
3 | ast::{self, AstNode, NameOwner}, | ||
4 | TextSize, | ||
5 | }; | ||
6 | use stdx::format_to; | 3 | use stdx::format_to; |
7 | |||
8 | use crate::{utils::FamousDefs, Assist, AssistCtx, AssistId}; | ||
9 | use test_utils::tested_by; | 4 | use test_utils::tested_by; |
10 | 5 | ||
6 | use crate::{utils::FamousDefs, AssistContext, AssistId, Assists}; | ||
7 | |||
11 | // Assist add_from_impl_for_enum | 8 | // Assist add_from_impl_for_enum |
12 | // | 9 | // |
13 | // Adds a From impl for an enum variant with one tuple field | 10 | // Adds a From impl for an enum variant with one tuple field |
@@ -25,7 +22,7 @@ use test_utils::tested_by; | |||
25 | // } | 22 | // } |
26 | // } | 23 | // } |
27 | // ``` | 24 | // ``` |
28 | pub(crate) fn add_from_impl_for_enum(ctx: AssistCtx) -> Option<Assist> { | 25 | pub(crate) fn add_from_impl_for_enum(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { |
29 | let variant = ctx.find_node_at_offset::<ast::EnumVariant>()?; | 26 | let variant = ctx.find_node_at_offset::<ast::EnumVariant>()?; |
30 | let variant_name = variant.name()?; | 27 | let variant_name = variant.name()?; |
31 | let enum_name = variant.parent_enum().name()?; | 28 | let enum_name = variant.parent_enum().name()?; |
@@ -42,13 +39,13 @@ pub(crate) fn add_from_impl_for_enum(ctx: AssistCtx) -> Option<Assist> { | |||
42 | _ => return None, | 39 | _ => return None, |
43 | }; | 40 | }; |
44 | 41 | ||
45 | if existing_from_impl(ctx.sema, &variant).is_some() { | 42 | if existing_from_impl(&ctx.sema, &variant).is_some() { |
46 | tested_by!(test_add_from_impl_already_exists); | 43 | tested_by!(test_add_from_impl_already_exists); |
47 | return None; | 44 | return None; |
48 | } | 45 | } |
49 | 46 | ||
50 | let target = variant.syntax().text_range(); | 47 | let target = variant.syntax().text_range(); |
51 | ctx.add_assist( | 48 | acc.add( |
52 | AssistId("add_from_impl_for_enum"), | 49 | AssistId("add_from_impl_for_enum"), |
53 | "Add From impl for this enum variant", | 50 | "Add From impl for this enum variant", |
54 | target, | 51 | target, |
@@ -69,7 +66,6 @@ impl From<{0}> for {1} {{ | |||
69 | variant_name | 66 | variant_name |
70 | ); | 67 | ); |
71 | edit.insert(start_offset, buf); | 68 | edit.insert(start_offset, buf); |
72 | edit.set_cursor(start_offset + TextSize::of("\n\n")); | ||
73 | }, | 69 | }, |
74 | ) | 70 | ) |
75 | } | 71 | } |
@@ -97,19 +93,20 @@ fn existing_from_impl( | |||
97 | 93 | ||
98 | #[cfg(test)] | 94 | #[cfg(test)] |
99 | mod tests { | 95 | mod tests { |
100 | use super::*; | 96 | use test_utils::covers; |
101 | 97 | ||
102 | use crate::tests::{check_assist, check_assist_not_applicable}; | 98 | use crate::tests::{check_assist, check_assist_not_applicable}; |
103 | use test_utils::covers; | 99 | |
100 | use super::*; | ||
104 | 101 | ||
105 | #[test] | 102 | #[test] |
106 | fn test_add_from_impl_for_enum() { | 103 | fn test_add_from_impl_for_enum() { |
107 | check_assist( | 104 | check_assist( |
108 | add_from_impl_for_enum, | 105 | add_from_impl_for_enum, |
109 | "enum A { <|>One(u32) }", | 106 | "enum A { <|>One(u32) }", |
110 | r#"enum A { One(u32) } | 107 | r#"enum A { <|>One(u32) } |
111 | 108 | ||
112 | <|>impl From<u32> for A { | 109 | impl From<u32> for A { |
113 | fn from(v: u32) -> Self { | 110 | fn from(v: u32) -> Self { |
114 | A::One(v) | 111 | A::One(v) |
115 | } | 112 | } |
@@ -121,10 +118,10 @@ mod tests { | |||
121 | fn test_add_from_impl_for_enum_complicated_path() { | 118 | fn test_add_from_impl_for_enum_complicated_path() { |
122 | check_assist( | 119 | check_assist( |
123 | add_from_impl_for_enum, | 120 | add_from_impl_for_enum, |
124 | "enum A { <|>One(foo::bar::baz::Boo) }", | 121 | r#"enum A { <|>One(foo::bar::baz::Boo) }"#, |
125 | r#"enum A { One(foo::bar::baz::Boo) } | 122 | r#"enum A { <|>One(foo::bar::baz::Boo) } |
126 | 123 | ||
127 | <|>impl From<foo::bar::baz::Boo> for A { | 124 | impl From<foo::bar::baz::Boo> for A { |
128 | fn from(v: foo::bar::baz::Boo) -> Self { | 125 | fn from(v: foo::bar::baz::Boo) -> Self { |
129 | A::One(v) | 126 | A::One(v) |
130 | } | 127 | } |
@@ -184,9 +181,9 @@ impl From<String> for A { | |||
184 | pub trait From<T> { | 181 | pub trait From<T> { |
185 | fn from(T) -> Self; | 182 | fn from(T) -> Self; |
186 | }"#, | 183 | }"#, |
187 | r#"enum A { One(u32), Two(String), } | 184 | r#"enum A { <|>One(u32), Two(String), } |
188 | 185 | ||
189 | <|>impl From<u32> for A { | 186 | impl From<u32> for A { |
190 | fn from(v: u32) -> Self { | 187 | fn from(v: u32) -> Self { |
191 | A::One(v) | 188 | A::One(v) |
192 | } | 189 | } |
diff --git a/crates/ra_assists/src/handlers/add_function.rs b/crates/ra_assists/src/handlers/add_function.rs index 278079665..de016ae4e 100644 --- a/crates/ra_assists/src/handlers/add_function.rs +++ b/crates/ra_assists/src/handlers/add_function.rs | |||
@@ -1,14 +1,17 @@ | |||
1 | use hir::HirDisplay; | ||
2 | use ra_db::FileId; | ||
1 | use ra_syntax::{ | 3 | use ra_syntax::{ |
2 | ast::{self, AstNode}, | 4 | ast::{ |
5 | self, | ||
6 | edit::{AstNodeEdit, IndentLevel}, | ||
7 | ArgListOwner, AstNode, ModuleItemOwner, | ||
8 | }, | ||
3 | SyntaxKind, SyntaxNode, TextSize, | 9 | SyntaxKind, SyntaxNode, TextSize, |
4 | }; | 10 | }; |
5 | |||
6 | use crate::{Assist, AssistCtx, AssistId}; | ||
7 | use ast::{edit::IndentLevel, ArgListOwner, ModuleItemOwner}; | ||
8 | use hir::HirDisplay; | ||
9 | use ra_db::FileId; | ||
10 | use rustc_hash::{FxHashMap, FxHashSet}; | 11 | use rustc_hash::{FxHashMap, FxHashSet}; |
11 | 12 | ||
13 | use crate::{AssistContext, AssistId, Assists}; | ||
14 | |||
12 | // Assist: add_function | 15 | // Assist: add_function |
13 | // | 16 | // |
14 | // Adds a stub function with a signature matching the function under the cursor. | 17 | // Adds a stub function with a signature matching the function under the cursor. |
@@ -34,7 +37,7 @@ use rustc_hash::{FxHashMap, FxHashSet}; | |||
34 | // } | 37 | // } |
35 | // | 38 | // |
36 | // ``` | 39 | // ``` |
37 | pub(crate) fn add_function(ctx: AssistCtx) -> Option<Assist> { | 40 | pub(crate) fn add_function(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { |
38 | let path_expr: ast::PathExpr = ctx.find_node_at_offset()?; | 41 | let path_expr: ast::PathExpr = ctx.find_node_at_offset()?; |
39 | let call = path_expr.syntax().parent().and_then(ast::CallExpr::cast)?; | 42 | let call = path_expr.syntax().parent().and_then(ast::CallExpr::cast)?; |
40 | let path = path_expr.path()?; | 43 | let path = path_expr.path()?; |
@@ -44,22 +47,18 @@ pub(crate) fn add_function(ctx: AssistCtx) -> Option<Assist> { | |||
44 | return None; | 47 | return None; |
45 | } | 48 | } |
46 | 49 | ||
47 | let target_module = if let Some(qualifier) = path.qualifier() { | 50 | let target_module = match path.qualifier() { |
48 | if let Some(hir::PathResolution::Def(hir::ModuleDef::Module(module))) = | 51 | Some(qualifier) => match ctx.sema.resolve_path(&qualifier) { |
49 | ctx.sema.resolve_path(&qualifier) | 52 | Some(hir::PathResolution::Def(hir::ModuleDef::Module(module))) => Some(module), |
50 | { | 53 | _ => return None, |
51 | Some(module.definition_source(ctx.sema.db)) | 54 | }, |
52 | } else { | 55 | None => None, |
53 | return None; | ||
54 | } | ||
55 | } else { | ||
56 | None | ||
57 | }; | 56 | }; |
58 | 57 | ||
59 | let function_builder = FunctionBuilder::from_call(&ctx, &call, &path, target_module)?; | 58 | let function_builder = FunctionBuilder::from_call(&ctx, &call, &path, target_module)?; |
60 | 59 | ||
61 | let target = call.syntax().text_range(); | 60 | let target = call.syntax().text_range(); |
62 | ctx.add_assist(AssistId("add_function"), "Add function", target, |edit| { | 61 | acc.add(AssistId("add_function"), "Add function", target, |edit| { |
63 | let function_template = function_builder.render(); | 62 | let function_template = function_builder.render(); |
64 | edit.set_file(function_template.file); | 63 | edit.set_file(function_template.file); |
65 | edit.set_cursor(function_template.cursor_offset); | 64 | edit.set_cursor(function_template.cursor_offset); |
@@ -84,25 +83,29 @@ struct FunctionBuilder { | |||
84 | } | 83 | } |
85 | 84 | ||
86 | impl FunctionBuilder { | 85 | impl FunctionBuilder { |
87 | /// Prepares a generated function that matches `call` in `generate_in` | 86 | /// Prepares a generated function that matches `call`. |
88 | /// (or as close to `call` as possible, if `generate_in` is `None`) | 87 | /// The function is generated in `target_module` or next to `call` |
89 | fn from_call( | 88 | fn from_call( |
90 | ctx: &AssistCtx, | 89 | ctx: &AssistContext, |
91 | call: &ast::CallExpr, | 90 | call: &ast::CallExpr, |
92 | path: &ast::Path, | 91 | path: &ast::Path, |
93 | target_module: Option<hir::InFile<hir::ModuleSource>>, | 92 | target_module: Option<hir::Module>, |
94 | ) -> Option<Self> { | 93 | ) -> Option<Self> { |
95 | let needs_pub = target_module.is_some(); | ||
96 | let mut file = ctx.frange.file_id; | 94 | let mut file = ctx.frange.file_id; |
97 | let target = if let Some(target_module) = target_module { | 95 | let target = match &target_module { |
98 | let (in_file, target) = next_space_for_fn_in_module(ctx.sema.db, target_module)?; | 96 | Some(target_module) => { |
99 | file = in_file; | 97 | let module_source = target_module.definition_source(ctx.db); |
100 | target | 98 | let (in_file, target) = next_space_for_fn_in_module(ctx.sema.db, &module_source)?; |
101 | } else { | 99 | file = in_file; |
102 | next_space_for_fn_after_call_site(&call)? | 100 | target |
101 | } | ||
102 | None => next_space_for_fn_after_call_site(&call)?, | ||
103 | }; | 103 | }; |
104 | let needs_pub = target_module.is_some(); | ||
105 | let target_module = target_module.or_else(|| ctx.sema.scope(target.syntax()).module())?; | ||
104 | let fn_name = fn_name(&path)?; | 106 | let fn_name = fn_name(&path)?; |
105 | let (type_params, params) = fn_args(ctx, &call)?; | 107 | let (type_params, params) = fn_args(ctx, target_module, &call)?; |
108 | |||
106 | Some(Self { target, fn_name, type_params, params, file, needs_pub }) | 109 | Some(Self { target, fn_name, type_params, params, file, needs_pub }) |
107 | } | 110 | } |
108 | 111 | ||
@@ -117,17 +120,16 @@ impl FunctionBuilder { | |||
117 | let (fn_def, insert_offset) = match self.target { | 120 | let (fn_def, insert_offset) = match self.target { |
118 | GeneratedFunctionTarget::BehindItem(it) => { | 121 | GeneratedFunctionTarget::BehindItem(it) => { |
119 | let with_leading_blank_line = ast::make::add_leading_newlines(2, fn_def); | 122 | let with_leading_blank_line = ast::make::add_leading_newlines(2, fn_def); |
120 | let indented = IndentLevel::from_node(&it).increase_indent(with_leading_blank_line); | 123 | let indented = with_leading_blank_line.indent(IndentLevel::from_node(&it)); |
121 | (indented, it.text_range().end()) | 124 | (indented, it.text_range().end()) |
122 | } | 125 | } |
123 | GeneratedFunctionTarget::InEmptyItemList(it) => { | 126 | GeneratedFunctionTarget::InEmptyItemList(it) => { |
124 | let indent_once = IndentLevel(1); | 127 | let indent_once = IndentLevel(1); |
125 | let indent = IndentLevel::from_node(it.syntax()); | 128 | let indent = IndentLevel::from_node(it.syntax()); |
126 | |||
127 | let fn_def = ast::make::add_leading_newlines(1, fn_def); | 129 | let fn_def = ast::make::add_leading_newlines(1, fn_def); |
128 | let fn_def = indent_once.increase_indent(fn_def); | 130 | let fn_def = fn_def.indent(indent_once); |
129 | let fn_def = ast::make::add_trailing_newlines(1, fn_def); | 131 | let fn_def = ast::make::add_trailing_newlines(1, fn_def); |
130 | let fn_def = indent.increase_indent(fn_def); | 132 | let fn_def = fn_def.indent(indent); |
131 | (fn_def, it.syntax().text_range().start() + TextSize::of('{')) | 133 | (fn_def, it.syntax().text_range().start() + TextSize::of('{')) |
132 | } | 134 | } |
133 | }; | 135 | }; |
@@ -145,6 +147,15 @@ enum GeneratedFunctionTarget { | |||
145 | InEmptyItemList(ast::ItemList), | 147 | InEmptyItemList(ast::ItemList), |
146 | } | 148 | } |
147 | 149 | ||
150 | impl GeneratedFunctionTarget { | ||
151 | fn syntax(&self) -> &SyntaxNode { | ||
152 | match self { | ||
153 | GeneratedFunctionTarget::BehindItem(it) => it, | ||
154 | GeneratedFunctionTarget::InEmptyItemList(it) => it.syntax(), | ||
155 | } | ||
156 | } | ||
157 | } | ||
158 | |||
148 | fn fn_name(call: &ast::Path) -> Option<ast::Name> { | 159 | fn fn_name(call: &ast::Path) -> Option<ast::Name> { |
149 | let name = call.segment()?.syntax().to_string(); | 160 | let name = call.segment()?.syntax().to_string(); |
150 | Some(ast::make::name(&name)) | 161 | Some(ast::make::name(&name)) |
@@ -152,18 +163,18 @@ fn fn_name(call: &ast::Path) -> Option<ast::Name> { | |||
152 | 163 | ||
153 | /// Computes the type variables and arguments required for the generated function | 164 | /// Computes the type variables and arguments required for the generated function |
154 | fn fn_args( | 165 | fn fn_args( |
155 | ctx: &AssistCtx, | 166 | ctx: &AssistContext, |
167 | target_module: hir::Module, | ||
156 | call: &ast::CallExpr, | 168 | call: &ast::CallExpr, |
157 | ) -> Option<(Option<ast::TypeParamList>, ast::ParamList)> { | 169 | ) -> Option<(Option<ast::TypeParamList>, ast::ParamList)> { |
158 | let mut arg_names = Vec::new(); | 170 | let mut arg_names = Vec::new(); |
159 | let mut arg_types = Vec::new(); | 171 | let mut arg_types = Vec::new(); |
160 | for arg in call.arg_list()?.args() { | 172 | for arg in call.arg_list()?.args() { |
161 | let arg_name = match fn_arg_name(&arg) { | 173 | arg_names.push(match fn_arg_name(&arg) { |
162 | Some(name) => name, | 174 | Some(name) => name, |
163 | None => String::from("arg"), | 175 | None => String::from("arg"), |
164 | }; | 176 | }); |
165 | arg_names.push(arg_name); | 177 | arg_types.push(match fn_arg_type(ctx, target_module, &arg) { |
166 | arg_types.push(match fn_arg_type(ctx, &arg) { | ||
167 | Some(ty) => ty, | 178 | Some(ty) => ty, |
168 | None => String::from("()"), | 179 | None => String::from("()"), |
169 | }); | 180 | }); |
@@ -219,12 +230,21 @@ fn fn_arg_name(fn_arg: &ast::Expr) -> Option<String> { | |||
219 | } | 230 | } |
220 | } | 231 | } |
221 | 232 | ||
222 | fn fn_arg_type(ctx: &AssistCtx, fn_arg: &ast::Expr) -> Option<String> { | 233 | fn fn_arg_type( |
234 | ctx: &AssistContext, | ||
235 | target_module: hir::Module, | ||
236 | fn_arg: &ast::Expr, | ||
237 | ) -> Option<String> { | ||
223 | let ty = ctx.sema.type_of_expr(fn_arg)?; | 238 | let ty = ctx.sema.type_of_expr(fn_arg)?; |
224 | if ty.is_unknown() { | 239 | if ty.is_unknown() { |
225 | return None; | 240 | return None; |
226 | } | 241 | } |
227 | Some(ty.display(ctx.sema.db).to_string()) | 242 | |
243 | if let Ok(rendered) = ty.display_source_code(ctx.db, target_module.into()) { | ||
244 | Some(rendered) | ||
245 | } else { | ||
246 | None | ||
247 | } | ||
228 | } | 248 | } |
229 | 249 | ||
230 | /// Returns the position inside the current mod or file | 250 | /// Returns the position inside the current mod or file |
@@ -253,10 +273,10 @@ fn next_space_for_fn_after_call_site(expr: &ast::CallExpr) -> Option<GeneratedFu | |||
253 | 273 | ||
254 | fn next_space_for_fn_in_module( | 274 | fn next_space_for_fn_in_module( |
255 | db: &dyn hir::db::AstDatabase, | 275 | db: &dyn hir::db::AstDatabase, |
256 | module: hir::InFile<hir::ModuleSource>, | 276 | module_source: &hir::InFile<hir::ModuleSource>, |
257 | ) -> Option<(FileId, GeneratedFunctionTarget)> { | 277 | ) -> Option<(FileId, GeneratedFunctionTarget)> { |
258 | let file = module.file_id.original_file(db); | 278 | let file = module_source.file_id.original_file(db); |
259 | let assist_item = match module.value { | 279 | let assist_item = match &module_source.value { |
260 | hir::ModuleSource::SourceFile(it) => { | 280 | hir::ModuleSource::SourceFile(it) => { |
261 | if let Some(last_item) = it.items().last() { | 281 | if let Some(last_item) = it.items().last() { |
262 | GeneratedFunctionTarget::BehindItem(last_item.syntax().clone()) | 282 | GeneratedFunctionTarget::BehindItem(last_item.syntax().clone()) |
@@ -600,8 +620,33 @@ fn bar(foo: impl Foo) { | |||
600 | } | 620 | } |
601 | 621 | ||
602 | #[test] | 622 | #[test] |
603 | #[ignore] | 623 | fn borrowed_arg() { |
604 | // FIXME print paths properly to make this test pass | 624 | check_assist( |
625 | add_function, | ||
626 | r" | ||
627 | struct Baz; | ||
628 | fn baz() -> Baz { todo!() } | ||
629 | |||
630 | fn foo() { | ||
631 | bar<|>(&baz()) | ||
632 | } | ||
633 | ", | ||
634 | r" | ||
635 | struct Baz; | ||
636 | fn baz() -> Baz { todo!() } | ||
637 | |||
638 | fn foo() { | ||
639 | bar(&baz()) | ||
640 | } | ||
641 | |||
642 | fn bar(baz: &Baz) { | ||
643 | <|>todo!() | ||
644 | } | ||
645 | ", | ||
646 | ) | ||
647 | } | ||
648 | |||
649 | #[test] | ||
605 | fn add_function_with_qualified_path_arg() { | 650 | fn add_function_with_qualified_path_arg() { |
606 | check_assist( | 651 | check_assist( |
607 | add_function, | 652 | add_function, |
@@ -610,10 +655,8 @@ mod Baz { | |||
610 | pub struct Bof; | 655 | pub struct Bof; |
611 | pub fn baz() -> Bof { Bof } | 656 | pub fn baz() -> Bof { Bof } |
612 | } | 657 | } |
613 | mod Foo { | 658 | fn foo() { |
614 | fn foo() { | 659 | <|>bar(Baz::baz()) |
615 | <|>bar(super::Baz::baz()) | ||
616 | } | ||
617 | } | 660 | } |
618 | ", | 661 | ", |
619 | r" | 662 | r" |
@@ -621,14 +664,12 @@ mod Baz { | |||
621 | pub struct Bof; | 664 | pub struct Bof; |
622 | pub fn baz() -> Bof { Bof } | 665 | pub fn baz() -> Bof { Bof } |
623 | } | 666 | } |
624 | mod Foo { | 667 | fn foo() { |
625 | fn foo() { | 668 | bar(Baz::baz()) |
626 | bar(super::Baz::baz()) | 669 | } |
627 | } | ||
628 | 670 | ||
629 | fn bar(baz: super::Baz::Bof) { | 671 | fn bar(baz: Baz::Bof) { |
630 | <|>todo!() | 672 | <|>todo!() |
631 | } | ||
632 | } | 673 | } |
633 | ", | 674 | ", |
634 | ) | 675 | ) |
@@ -810,6 +851,40 @@ fn foo() { | |||
810 | } | 851 | } |
811 | 852 | ||
812 | #[test] | 853 | #[test] |
854 | #[ignore] | ||
855 | // Ignored until local imports are supported. | ||
856 | // See https://github.com/rust-analyzer/rust-analyzer/issues/1165 | ||
857 | fn qualified_path_uses_correct_scope() { | ||
858 | check_assist( | ||
859 | add_function, | ||
860 | " | ||
861 | mod foo { | ||
862 | pub struct Foo; | ||
863 | } | ||
864 | fn bar() { | ||
865 | use foo::Foo; | ||
866 | let foo = Foo; | ||
867 | baz<|>(foo) | ||
868 | } | ||
869 | ", | ||
870 | " | ||
871 | mod foo { | ||
872 | pub struct Foo; | ||
873 | } | ||
874 | fn bar() { | ||
875 | use foo::Foo; | ||
876 | let foo = Foo; | ||
877 | baz(foo) | ||
878 | } | ||
879 | |||
880 | fn baz(foo: foo::Foo) { | ||
881 | <|>todo!() | ||
882 | } | ||
883 | ", | ||
884 | ) | ||
885 | } | ||
886 | |||
887 | #[test] | ||
813 | fn add_function_in_module_containing_other_items() { | 888 | fn add_function_in_module_containing_other_items() { |
814 | check_assist( | 889 | check_assist( |
815 | add_function, | 890 | add_function, |
@@ -921,21 +996,6 @@ fn bar(baz: ()) {} | |||
921 | } | 996 | } |
922 | 997 | ||
923 | #[test] | 998 | #[test] |
924 | fn add_function_not_applicable_if_function_path_not_singleton() { | ||
925 | // In the future this assist could be extended to generate functions | ||
926 | // if the path is in the same crate (or even the same workspace). | ||
927 | // For the beginning, I think this is fine. | ||
928 | check_assist_not_applicable( | ||
929 | add_function, | ||
930 | r" | ||
931 | fn foo() { | ||
932 | other_crate::bar<|>(); | ||
933 | } | ||
934 | ", | ||
935 | ) | ||
936 | } | ||
937 | |||
938 | #[test] | ||
939 | #[ignore] | 999 | #[ignore] |
940 | fn create_method_with_no_args() { | 1000 | fn create_method_with_no_args() { |
941 | check_assist( | 1001 | check_assist( |
diff --git a/crates/ra_assists/src/handlers/add_impl.rs b/crates/ra_assists/src/handlers/add_impl.rs index 557344ebb..df114a0d8 100644 --- a/crates/ra_assists/src/handlers/add_impl.rs +++ b/crates/ra_assists/src/handlers/add_impl.rs | |||
@@ -4,7 +4,7 @@ use ra_syntax::{ | |||
4 | }; | 4 | }; |
5 | use stdx::{format_to, SepBy}; | 5 | use stdx::{format_to, SepBy}; |
6 | 6 | ||
7 | use crate::{Assist, AssistCtx, AssistId}; | 7 | use crate::{AssistContext, AssistId, Assists}; |
8 | 8 | ||
9 | // Assist: add_impl | 9 | // Assist: add_impl |
10 | // | 10 | // |
@@ -25,43 +25,36 @@ use crate::{Assist, AssistCtx, AssistId}; | |||
25 | // | 25 | // |
26 | // } | 26 | // } |
27 | // ``` | 27 | // ``` |
28 | pub(crate) fn add_impl(ctx: AssistCtx) -> Option<Assist> { | 28 | pub(crate) fn add_impl(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { |
29 | let nominal = ctx.find_node_at_offset::<ast::NominalDef>()?; | 29 | let nominal = ctx.find_node_at_offset::<ast::NominalDef>()?; |
30 | let name = nominal.name()?; | 30 | let name = nominal.name()?; |
31 | let target = nominal.syntax().text_range(); | 31 | let target = nominal.syntax().text_range(); |
32 | ctx.add_assist( | 32 | acc.add(AssistId("add_impl"), format!("Implement {}", name.text().as_str()), target, |edit| { |
33 | AssistId("add_impl"), | 33 | let type_params = nominal.type_param_list(); |
34 | format!("Implement {}", name.text().as_str()), | 34 | let start_offset = nominal.syntax().text_range().end(); |
35 | target, | 35 | let mut buf = String::new(); |
36 | |edit| { | 36 | buf.push_str("\n\nimpl"); |
37 | let type_params = nominal.type_param_list(); | 37 | if let Some(type_params) = &type_params { |
38 | let start_offset = nominal.syntax().text_range().end(); | 38 | format_to!(buf, "{}", type_params.syntax()); |
39 | let mut buf = String::new(); | 39 | } |
40 | buf.push_str("\n\nimpl"); | 40 | buf.push_str(" "); |
41 | if let Some(type_params) = &type_params { | 41 | buf.push_str(name.text().as_str()); |
42 | format_to!(buf, "{}", type_params.syntax()); | 42 | if let Some(type_params) = type_params { |
43 | } | 43 | let lifetime_params = type_params |
44 | buf.push_str(" "); | 44 | .lifetime_params() |
45 | buf.push_str(name.text().as_str()); | 45 | .filter_map(|it| it.lifetime_token()) |
46 | if let Some(type_params) = type_params { | 46 | .map(|it| it.text().clone()); |
47 | let lifetime_params = type_params | 47 | let type_params = |
48 | .lifetime_params() | 48 | type_params.type_params().filter_map(|it| it.name()).map(|it| it.text().clone()); |
49 | .filter_map(|it| it.lifetime_token()) | ||
50 | .map(|it| it.text().clone()); | ||
51 | let type_params = type_params | ||
52 | .type_params() | ||
53 | .filter_map(|it| it.name()) | ||
54 | .map(|it| it.text().clone()); | ||
55 | 49 | ||
56 | let generic_params = lifetime_params.chain(type_params).sep_by(", "); | 50 | let generic_params = lifetime_params.chain(type_params).sep_by(", "); |
57 | format_to!(buf, "<{}>", generic_params) | 51 | format_to!(buf, "<{}>", generic_params) |
58 | } | 52 | } |
59 | buf.push_str(" {\n"); | 53 | buf.push_str(" {\n"); |
60 | edit.set_cursor(start_offset + TextSize::of(&buf)); | 54 | edit.set_cursor(start_offset + TextSize::of(&buf)); |
61 | buf.push_str("\n}"); | 55 | buf.push_str("\n}"); |
62 | edit.insert(start_offset, buf); | 56 | edit.insert(start_offset, buf); |
63 | }, | 57 | }) |
64 | ) | ||
65 | } | 58 | } |
66 | 59 | ||
67 | #[cfg(test)] | 60 | #[cfg(test)] |
diff --git a/crates/ra_assists/src/handlers/add_missing_impl_members.rs b/crates/ra_assists/src/handlers/add_missing_impl_members.rs index 7df786590..c1ce87914 100644 --- a/crates/ra_assists/src/handlers/add_missing_impl_members.rs +++ b/crates/ra_assists/src/handlers/add_missing_impl_members.rs | |||
@@ -2,16 +2,17 @@ use hir::HasSource; | |||
2 | use ra_syntax::{ | 2 | use ra_syntax::{ |
3 | ast::{ | 3 | ast::{ |
4 | self, | 4 | self, |
5 | edit::{self, IndentLevel}, | 5 | edit::{self, AstNodeEdit, IndentLevel}, |
6 | make, AstNode, NameOwner, | 6 | make, AstNode, NameOwner, |
7 | }, | 7 | }, |
8 | SmolStr, | 8 | SmolStr, |
9 | }; | 9 | }; |
10 | 10 | ||
11 | use crate::{ | 11 | use crate::{ |
12 | assist_context::{AssistContext, Assists}, | ||
12 | ast_transform::{self, AstTransform, QualifyPaths, SubstituteTypeParams}, | 13 | ast_transform::{self, AstTransform, QualifyPaths, SubstituteTypeParams}, |
13 | utils::{get_missing_assoc_items, resolve_target_trait}, | 14 | utils::{get_missing_assoc_items, resolve_target_trait}, |
14 | Assist, AssistCtx, AssistId, | 15 | AssistId, |
15 | }; | 16 | }; |
16 | 17 | ||
17 | #[derive(PartialEq)] | 18 | #[derive(PartialEq)] |
@@ -50,8 +51,9 @@ enum AddMissingImplMembersMode { | |||
50 | // | 51 | // |
51 | // } | 52 | // } |
52 | // ``` | 53 | // ``` |
53 | pub(crate) fn add_missing_impl_members(ctx: AssistCtx) -> Option<Assist> { | 54 | pub(crate) fn add_missing_impl_members(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { |
54 | add_missing_impl_members_inner( | 55 | add_missing_impl_members_inner( |
56 | acc, | ||
55 | ctx, | 57 | ctx, |
56 | AddMissingImplMembersMode::NoDefaultMethods, | 58 | AddMissingImplMembersMode::NoDefaultMethods, |
57 | "add_impl_missing_members", | 59 | "add_impl_missing_members", |
@@ -91,8 +93,9 @@ pub(crate) fn add_missing_impl_members(ctx: AssistCtx) -> Option<Assist> { | |||
91 | // | 93 | // |
92 | // } | 94 | // } |
93 | // ``` | 95 | // ``` |
94 | pub(crate) fn add_missing_default_members(ctx: AssistCtx) -> Option<Assist> { | 96 | pub(crate) fn add_missing_default_members(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { |
95 | add_missing_impl_members_inner( | 97 | add_missing_impl_members_inner( |
98 | acc, | ||
96 | ctx, | 99 | ctx, |
97 | AddMissingImplMembersMode::DefaultMethodsOnly, | 100 | AddMissingImplMembersMode::DefaultMethodsOnly, |
98 | "add_impl_default_members", | 101 | "add_impl_default_members", |
@@ -101,11 +104,12 @@ pub(crate) fn add_missing_default_members(ctx: AssistCtx) -> Option<Assist> { | |||
101 | } | 104 | } |
102 | 105 | ||
103 | fn add_missing_impl_members_inner( | 106 | fn add_missing_impl_members_inner( |
104 | ctx: AssistCtx, | 107 | acc: &mut Assists, |
108 | ctx: &AssistContext, | ||
105 | mode: AddMissingImplMembersMode, | 109 | mode: AddMissingImplMembersMode, |
106 | assist_id: &'static str, | 110 | assist_id: &'static str, |
107 | label: &'static str, | 111 | label: &'static str, |
108 | ) -> Option<Assist> { | 112 | ) -> Option<()> { |
109 | let _p = ra_prof::profile("add_missing_impl_members_inner"); | 113 | let _p = ra_prof::profile("add_missing_impl_members_inner"); |
110 | let impl_def = ctx.find_node_at_offset::<ast::ImplDef>()?; | 114 | let impl_def = ctx.find_node_at_offset::<ast::ImplDef>()?; |
111 | let impl_item_list = impl_def.item_list()?; | 115 | let impl_item_list = impl_def.item_list()?; |
@@ -142,12 +146,11 @@ fn add_missing_impl_members_inner( | |||
142 | return None; | 146 | return None; |
143 | } | 147 | } |
144 | 148 | ||
145 | let sema = ctx.sema; | ||
146 | let target = impl_def.syntax().text_range(); | 149 | let target = impl_def.syntax().text_range(); |
147 | ctx.add_assist(AssistId(assist_id), label, target, |edit| { | 150 | acc.add(AssistId(assist_id), label, target, |edit| { |
148 | let n_existing_items = impl_item_list.assoc_items().count(); | 151 | let n_existing_items = impl_item_list.assoc_items().count(); |
149 | let source_scope = sema.scope_for_def(trait_); | 152 | let source_scope = ctx.sema.scope_for_def(trait_); |
150 | let target_scope = sema.scope(impl_item_list.syntax()); | 153 | let target_scope = ctx.sema.scope(impl_item_list.syntax()); |
151 | let ast_transform = QualifyPaths::new(&target_scope, &source_scope) | 154 | let ast_transform = QualifyPaths::new(&target_scope, &source_scope) |
152 | .or(SubstituteTypeParams::for_trait_impl(&source_scope, trait_, impl_def)); | 155 | .or(SubstituteTypeParams::for_trait_impl(&source_scope, trait_, impl_def)); |
153 | let items = missing_items | 156 | let items = missing_items |
@@ -170,13 +173,11 @@ fn add_missing_impl_members_inner( | |||
170 | } | 173 | } |
171 | 174 | ||
172 | fn add_body(fn_def: ast::FnDef) -> ast::FnDef { | 175 | fn add_body(fn_def: ast::FnDef) -> ast::FnDef { |
173 | if fn_def.body().is_none() { | 176 | if fn_def.body().is_some() { |
174 | let body = make::block_expr(None, Some(make::expr_todo())); | 177 | return fn_def; |
175 | let body = IndentLevel(1).increase_indent(body); | ||
176 | fn_def.with_body(body) | ||
177 | } else { | ||
178 | fn_def | ||
179 | } | 178 | } |
179 | let body = make::block_expr(None, Some(make::expr_todo())).indent(IndentLevel(1)); | ||
180 | fn_def.with_body(body) | ||
180 | } | 181 | } |
181 | 182 | ||
182 | #[cfg(test)] | 183 | #[cfg(test)] |
diff --git a/crates/ra_assists/src/handlers/add_new.rs b/crates/ra_assists/src/handlers/add_new.rs index 1c3f8435a..fe7451dcf 100644 --- a/crates/ra_assists/src/handlers/add_new.rs +++ b/crates/ra_assists/src/handlers/add_new.rs | |||
@@ -7,7 +7,7 @@ use ra_syntax::{ | |||
7 | }; | 7 | }; |
8 | use stdx::{format_to, SepBy}; | 8 | use stdx::{format_to, SepBy}; |
9 | 9 | ||
10 | use crate::{Assist, AssistCtx, AssistId}; | 10 | use crate::{AssistContext, AssistId, Assists}; |
11 | 11 | ||
12 | // Assist: add_new | 12 | // Assist: add_new |
13 | // | 13 | // |
@@ -29,7 +29,7 @@ use crate::{Assist, AssistCtx, AssistId}; | |||
29 | // } | 29 | // } |
30 | // | 30 | // |
31 | // ``` | 31 | // ``` |
32 | pub(crate) fn add_new(ctx: AssistCtx) -> Option<Assist> { | 32 | pub(crate) fn add_new(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { |
33 | let strukt = ctx.find_node_at_offset::<ast::StructDef>()?; | 33 | let strukt = ctx.find_node_at_offset::<ast::StructDef>()?; |
34 | 34 | ||
35 | // We want to only apply this to non-union structs with named fields | 35 | // We want to only apply this to non-union structs with named fields |
@@ -42,7 +42,7 @@ pub(crate) fn add_new(ctx: AssistCtx) -> Option<Assist> { | |||
42 | let impl_def = find_struct_impl(&ctx, &strukt)?; | 42 | let impl_def = find_struct_impl(&ctx, &strukt)?; |
43 | 43 | ||
44 | let target = strukt.syntax().text_range(); | 44 | let target = strukt.syntax().text_range(); |
45 | ctx.add_assist(AssistId("add_new"), "Add default constructor", target, |edit| { | 45 | acc.add(AssistId("add_new"), "Add default constructor", target, |edit| { |
46 | let mut buf = String::with_capacity(512); | 46 | let mut buf = String::with_capacity(512); |
47 | 47 | ||
48 | if impl_def.is_some() { | 48 | if impl_def.is_some() { |
@@ -123,7 +123,7 @@ fn generate_impl_text(strukt: &ast::StructDef, code: &str) -> String { | |||
123 | // | 123 | // |
124 | // FIXME: change the new fn checking to a more semantic approach when that's more | 124 | // FIXME: change the new fn checking to a more semantic approach when that's more |
125 | // viable (e.g. we process proc macros, etc) | 125 | // viable (e.g. we process proc macros, etc) |
126 | fn find_struct_impl(ctx: &AssistCtx, strukt: &ast::StructDef) -> Option<Option<ast::ImplDef>> { | 126 | fn find_struct_impl(ctx: &AssistContext, strukt: &ast::StructDef) -> Option<Option<ast::ImplDef>> { |
127 | let db = ctx.db; | 127 | let db = ctx.db; |
128 | let module = strukt.syntax().ancestors().find(|node| { | 128 | let module = strukt.syntax().ancestors().find(|node| { |
129 | ast::Module::can_cast(node.kind()) || ast::SourceFile::can_cast(node.kind()) | 129 | ast::Module::can_cast(node.kind()) || ast::SourceFile::can_cast(node.kind()) |
diff --git a/crates/ra_assists/src/handlers/apply_demorgan.rs b/crates/ra_assists/src/handlers/apply_demorgan.rs index a5b26e5b9..0feba5e11 100644 --- a/crates/ra_assists/src/handlers/apply_demorgan.rs +++ b/crates/ra_assists/src/handlers/apply_demorgan.rs | |||
@@ -1,6 +1,6 @@ | |||
1 | use ra_syntax::ast::{self, AstNode}; | 1 | use ra_syntax::ast::{self, AstNode}; |
2 | 2 | ||
3 | use crate::{utils::invert_boolean_expression, Assist, AssistCtx, AssistId}; | 3 | use crate::{utils::invert_boolean_expression, AssistContext, AssistId, Assists}; |
4 | 4 | ||
5 | // Assist: apply_demorgan | 5 | // Assist: apply_demorgan |
6 | // | 6 | // |
@@ -21,7 +21,7 @@ use crate::{utils::invert_boolean_expression, Assist, AssistCtx, AssistId}; | |||
21 | // if !(x == 4 && y) {} | 21 | // if !(x == 4 && y) {} |
22 | // } | 22 | // } |
23 | // ``` | 23 | // ``` |
24 | pub(crate) fn apply_demorgan(ctx: AssistCtx) -> Option<Assist> { | 24 | pub(crate) fn apply_demorgan(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { |
25 | let expr = ctx.find_node_at_offset::<ast::BinExpr>()?; | 25 | let expr = ctx.find_node_at_offset::<ast::BinExpr>()?; |
26 | let op = expr.op_kind()?; | 26 | let op = expr.op_kind()?; |
27 | let op_range = expr.op_token()?.text_range(); | 27 | let op_range = expr.op_token()?.text_range(); |
@@ -39,7 +39,7 @@ pub(crate) fn apply_demorgan(ctx: AssistCtx) -> Option<Assist> { | |||
39 | let rhs_range = rhs.syntax().text_range(); | 39 | let rhs_range = rhs.syntax().text_range(); |
40 | let not_rhs = invert_boolean_expression(rhs); | 40 | let not_rhs = invert_boolean_expression(rhs); |
41 | 41 | ||
42 | ctx.add_assist(AssistId("apply_demorgan"), "Apply De Morgan's law", op_range, |edit| { | 42 | acc.add(AssistId("apply_demorgan"), "Apply De Morgan's law", op_range, |edit| { |
43 | edit.replace(op_range, opposite_op); | 43 | edit.replace(op_range, opposite_op); |
44 | edit.replace(lhs_range, format!("!({}", not_lhs.syntax().text())); | 44 | edit.replace(lhs_range, format!("!({}", not_lhs.syntax().text())); |
45 | edit.replace(rhs_range, format!("{})", not_rhs.syntax().text())); | 45 | edit.replace(rhs_range, format!("{})", not_rhs.syntax().text())); |
diff --git a/crates/ra_assists/src/handlers/auto_import.rs b/crates/ra_assists/src/handlers/auto_import.rs index 2224b9714..78d23150d 100644 --- a/crates/ra_assists/src/handlers/auto_import.rs +++ b/crates/ra_assists/src/handlers/auto_import.rs | |||
@@ -1,5 +1,6 @@ | |||
1 | use std::collections::BTreeSet; | 1 | use std::collections::BTreeSet; |
2 | 2 | ||
3 | use either::Either; | ||
3 | use hir::{ | 4 | use hir::{ |
4 | AsAssocItem, AssocItemContainer, ModPath, Module, ModuleDef, PathResolution, Semantics, Trait, | 5 | AsAssocItem, AssocItemContainer, ModPath, Module, ModuleDef, PathResolution, Semantics, Trait, |
5 | Type, | 6 | Type, |
@@ -12,12 +13,7 @@ use ra_syntax::{ | |||
12 | }; | 13 | }; |
13 | use rustc_hash::FxHashSet; | 14 | use rustc_hash::FxHashSet; |
14 | 15 | ||
15 | use crate::{ | 16 | use crate::{utils::insert_use_statement, AssistContext, AssistId, Assists, GroupLabel}; |
16 | assist_ctx::{Assist, AssistCtx}, | ||
17 | utils::insert_use_statement, | ||
18 | AssistId, | ||
19 | }; | ||
20 | use either::Either; | ||
21 | 17 | ||
22 | // Assist: auto_import | 18 | // Assist: auto_import |
23 | // | 19 | // |
@@ -38,7 +34,7 @@ use either::Either; | |||
38 | // } | 34 | // } |
39 | // # pub mod std { pub mod collections { pub struct HashMap { } } } | 35 | // # pub mod std { pub mod collections { pub struct HashMap { } } } |
40 | // ``` | 36 | // ``` |
41 | pub(crate) fn auto_import(ctx: AssistCtx) -> Option<Assist> { | 37 | pub(crate) fn auto_import(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { |
42 | let auto_import_assets = AutoImportAssets::new(&ctx)?; | 38 | let auto_import_assets = AutoImportAssets::new(&ctx)?; |
43 | let proposed_imports = auto_import_assets.search_for_imports(ctx.db); | 39 | let proposed_imports = auto_import_assets.search_for_imports(ctx.db); |
44 | if proposed_imports.is_empty() { | 40 | if proposed_imports.is_empty() { |
@@ -46,13 +42,19 @@ pub(crate) fn auto_import(ctx: AssistCtx) -> Option<Assist> { | |||
46 | } | 42 | } |
47 | 43 | ||
48 | let range = ctx.sema.original_range(&auto_import_assets.syntax_under_caret).range; | 44 | let range = ctx.sema.original_range(&auto_import_assets.syntax_under_caret).range; |
49 | let mut group = ctx.add_assist_group(auto_import_assets.get_import_group_message()); | 45 | let group = auto_import_assets.get_import_group_message(); |
50 | for import in proposed_imports { | 46 | for import in proposed_imports { |
51 | group.add_assist(AssistId("auto_import"), format!("Import `{}`", &import), range, |edit| { | 47 | acc.add_group( |
52 | insert_use_statement(&auto_import_assets.syntax_under_caret, &import, edit); | 48 | &group, |
53 | }); | 49 | AssistId("auto_import"), |
50 | format!("Import `{}`", &import), | ||
51 | range, | ||
52 | |builder| { | ||
53 | insert_use_statement(&auto_import_assets.syntax_under_caret, &import, ctx, builder); | ||
54 | }, | ||
55 | ); | ||
54 | } | 56 | } |
55 | group.finish() | 57 | Some(()) |
56 | } | 58 | } |
57 | 59 | ||
58 | #[derive(Debug)] | 60 | #[derive(Debug)] |
@@ -63,7 +65,7 @@ struct AutoImportAssets { | |||
63 | } | 65 | } |
64 | 66 | ||
65 | impl AutoImportAssets { | 67 | impl AutoImportAssets { |
66 | fn new(ctx: &AssistCtx) -> Option<Self> { | 68 | fn new(ctx: &AssistContext) -> Option<Self> { |
67 | if let Some(path_under_caret) = ctx.find_node_at_offset_with_descend::<ast::Path>() { | 69 | if let Some(path_under_caret) = ctx.find_node_at_offset_with_descend::<ast::Path>() { |
68 | Self::for_regular_path(path_under_caret, &ctx) | 70 | Self::for_regular_path(path_under_caret, &ctx) |
69 | } else { | 71 | } else { |
@@ -71,7 +73,7 @@ impl AutoImportAssets { | |||
71 | } | 73 | } |
72 | } | 74 | } |
73 | 75 | ||
74 | fn for_method_call(method_call: ast::MethodCallExpr, ctx: &AssistCtx) -> Option<Self> { | 76 | fn for_method_call(method_call: ast::MethodCallExpr, ctx: &AssistContext) -> Option<Self> { |
75 | let syntax_under_caret = method_call.syntax().to_owned(); | 77 | let syntax_under_caret = method_call.syntax().to_owned(); |
76 | let module_with_name_to_import = ctx.sema.scope(&syntax_under_caret).module()?; | 78 | let module_with_name_to_import = ctx.sema.scope(&syntax_under_caret).module()?; |
77 | Some(Self { | 79 | Some(Self { |
@@ -81,7 +83,7 @@ impl AutoImportAssets { | |||
81 | }) | 83 | }) |
82 | } | 84 | } |
83 | 85 | ||
84 | fn for_regular_path(path_under_caret: ast::Path, ctx: &AssistCtx) -> Option<Self> { | 86 | fn for_regular_path(path_under_caret: ast::Path, ctx: &AssistContext) -> Option<Self> { |
85 | let syntax_under_caret = path_under_caret.syntax().to_owned(); | 87 | let syntax_under_caret = path_under_caret.syntax().to_owned(); |
86 | if syntax_under_caret.ancestors().find_map(ast::UseItem::cast).is_some() { | 88 | if syntax_under_caret.ancestors().find_map(ast::UseItem::cast).is_some() { |
87 | return None; | 89 | return None; |
@@ -104,8 +106,8 @@ impl AutoImportAssets { | |||
104 | } | 106 | } |
105 | } | 107 | } |
106 | 108 | ||
107 | fn get_import_group_message(&self) -> String { | 109 | fn get_import_group_message(&self) -> GroupLabel { |
108 | match &self.import_candidate { | 110 | let name = match &self.import_candidate { |
109 | ImportCandidate::UnqualifiedName(name) => format!("Import {}", name), | 111 | ImportCandidate::UnqualifiedName(name) => format!("Import {}", name), |
110 | ImportCandidate::QualifierStart(qualifier_start) => { | 112 | ImportCandidate::QualifierStart(qualifier_start) => { |
111 | format!("Import {}", qualifier_start) | 113 | format!("Import {}", qualifier_start) |
@@ -116,7 +118,8 @@ impl AutoImportAssets { | |||
116 | ImportCandidate::TraitMethod(_, trait_method_name) => { | 118 | ImportCandidate::TraitMethod(_, trait_method_name) => { |
117 | format!("Import a trait for method {}", trait_method_name) | 119 | format!("Import a trait for method {}", trait_method_name) |
118 | } | 120 | } |
119 | } | 121 | }; |
122 | GroupLabel(name) | ||
120 | } | 123 | } |
121 | 124 | ||
122 | fn search_for_imports(&self, db: &RootDatabase) -> BTreeSet<ModPath> { | 125 | fn search_for_imports(&self, db: &RootDatabase) -> BTreeSet<ModPath> { |
@@ -383,7 +386,7 @@ mod tests { | |||
383 | } | 386 | } |
384 | ", | 387 | ", |
385 | r" | 388 | r" |
386 | use PubMod1::PubStruct; | 389 | use PubMod3::PubStruct; |
387 | 390 | ||
388 | PubSt<|>ruct | 391 | PubSt<|>ruct |
389 | 392 | ||
diff --git a/crates/ra_assists/src/handlers/change_return_type_to_result.rs b/crates/ra_assists/src/handlers/change_return_type_to_result.rs index 1e8d986cd..5c907097e 100644 --- a/crates/ra_assists/src/handlers/change_return_type_to_result.rs +++ b/crates/ra_assists/src/handlers/change_return_type_to_result.rs | |||
@@ -1,11 +1,11 @@ | |||
1 | use ra_syntax::{ | 1 | use ra_syntax::{ |
2 | ast, AstNode, | 2 | ast::{self, BlockExpr, Expr, LoopBodyOwner}, |
3 | AstNode, | ||
3 | SyntaxKind::{COMMENT, WHITESPACE}, | 4 | SyntaxKind::{COMMENT, WHITESPACE}, |
4 | SyntaxNode, TextSize, | 5 | SyntaxNode, TextSize, |
5 | }; | 6 | }; |
6 | 7 | ||
7 | use crate::{Assist, AssistCtx, AssistId}; | 8 | use crate::{AssistContext, AssistId, Assists}; |
8 | use ast::{BlockExpr, Expr, LoopBodyOwner}; | ||
9 | 9 | ||
10 | // Assist: change_return_type_to_result | 10 | // Assist: change_return_type_to_result |
11 | // | 11 | // |
@@ -18,7 +18,7 @@ use ast::{BlockExpr, Expr, LoopBodyOwner}; | |||
18 | // ``` | 18 | // ``` |
19 | // fn foo() -> Result<i32, > { Ok(42i32) } | 19 | // fn foo() -> Result<i32, > { Ok(42i32) } |
20 | // ``` | 20 | // ``` |
21 | pub(crate) fn change_return_type_to_result(ctx: AssistCtx) -> Option<Assist> { | 21 | pub(crate) fn change_return_type_to_result(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { |
22 | let fn_def = ctx.find_node_at_offset::<ast::FnDef>(); | 22 | let fn_def = ctx.find_node_at_offset::<ast::FnDef>(); |
23 | let fn_def = &mut fn_def?; | 23 | let fn_def = &mut fn_def?; |
24 | let ret_type = &fn_def.ret_type()?.type_ref()?; | 24 | let ret_type = &fn_def.ret_type()?.type_ref()?; |
@@ -33,7 +33,7 @@ pub(crate) fn change_return_type_to_result(ctx: AssistCtx) -> Option<Assist> { | |||
33 | return None; | 33 | return None; |
34 | } | 34 | } |
35 | 35 | ||
36 | ctx.add_assist( | 36 | acc.add( |
37 | AssistId("change_return_type_to_result"), | 37 | AssistId("change_return_type_to_result"), |
38 | "Change return type to Result", | 38 | "Change return type to Result", |
39 | ret_type.syntax().text_range(), | 39 | ret_type.syntax().text_range(), |
diff --git a/crates/ra_assists/src/handlers/change_visibility.rs b/crates/ra_assists/src/handlers/change_visibility.rs index 489db83e6..e631766ef 100644 --- a/crates/ra_assists/src/handlers/change_visibility.rs +++ b/crates/ra_assists/src/handlers/change_visibility.rs | |||
@@ -7,10 +7,10 @@ use ra_syntax::{ | |||
7 | }, | 7 | }, |
8 | SyntaxNode, TextSize, T, | 8 | SyntaxNode, TextSize, T, |
9 | }; | 9 | }; |
10 | |||
11 | use crate::{Assist, AssistCtx, AssistId}; | ||
12 | use test_utils::tested_by; | 10 | use test_utils::tested_by; |
13 | 11 | ||
12 | use crate::{AssistContext, AssistId, Assists}; | ||
13 | |||
14 | // Assist: change_visibility | 14 | // Assist: change_visibility |
15 | // | 15 | // |
16 | // Adds or changes existing visibility specifier. | 16 | // Adds or changes existing visibility specifier. |
@@ -22,14 +22,14 @@ use test_utils::tested_by; | |||
22 | // ``` | 22 | // ``` |
23 | // pub(crate) fn frobnicate() {} | 23 | // pub(crate) fn frobnicate() {} |
24 | // ``` | 24 | // ``` |
25 | pub(crate) fn change_visibility(ctx: AssistCtx) -> Option<Assist> { | 25 | pub(crate) fn change_visibility(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { |
26 | if let Some(vis) = ctx.find_node_at_offset::<ast::Visibility>() { | 26 | if let Some(vis) = ctx.find_node_at_offset::<ast::Visibility>() { |
27 | return change_vis(ctx, vis); | 27 | return change_vis(acc, vis); |
28 | } | 28 | } |
29 | add_vis(ctx) | 29 | add_vis(acc, ctx) |
30 | } | 30 | } |
31 | 31 | ||
32 | fn add_vis(ctx: AssistCtx) -> Option<Assist> { | 32 | fn add_vis(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { |
33 | let item_keyword = ctx.token_at_offset().find(|leaf| match leaf.kind() { | 33 | let item_keyword = ctx.token_at_offset().find(|leaf| match leaf.kind() { |
34 | T![const] | T![fn] | T![mod] | T![struct] | T![enum] | T![trait] => true, | 34 | T![const] | T![fn] | T![mod] | T![struct] | T![enum] | T![trait] => true, |
35 | _ => false, | 35 | _ => false, |
@@ -66,15 +66,10 @@ fn add_vis(ctx: AssistCtx) -> Option<Assist> { | |||
66 | return None; | 66 | return None; |
67 | }; | 67 | }; |
68 | 68 | ||
69 | ctx.add_assist( | 69 | acc.add(AssistId("change_visibility"), "Change visibility to pub(crate)", target, |edit| { |
70 | AssistId("change_visibility"), | 70 | edit.insert(offset, "pub(crate) "); |
71 | "Change visibility to pub(crate)", | 71 | edit.set_cursor(offset); |
72 | target, | 72 | }) |
73 | |edit| { | ||
74 | edit.insert(offset, "pub(crate) "); | ||
75 | edit.set_cursor(offset); | ||
76 | }, | ||
77 | ) | ||
78 | } | 73 | } |
79 | 74 | ||
80 | fn vis_offset(node: &SyntaxNode) -> TextSize { | 75 | fn vis_offset(node: &SyntaxNode) -> TextSize { |
@@ -88,10 +83,10 @@ fn vis_offset(node: &SyntaxNode) -> TextSize { | |||
88 | .unwrap_or_else(|| node.text_range().start()) | 83 | .unwrap_or_else(|| node.text_range().start()) |
89 | } | 84 | } |
90 | 85 | ||
91 | fn change_vis(ctx: AssistCtx, vis: ast::Visibility) -> Option<Assist> { | 86 | fn change_vis(acc: &mut Assists, vis: ast::Visibility) -> Option<()> { |
92 | if vis.syntax().text() == "pub" { | 87 | if vis.syntax().text() == "pub" { |
93 | let target = vis.syntax().text_range(); | 88 | let target = vis.syntax().text_range(); |
94 | return ctx.add_assist( | 89 | return acc.add( |
95 | AssistId("change_visibility"), | 90 | AssistId("change_visibility"), |
96 | "Change Visibility to pub(crate)", | 91 | "Change Visibility to pub(crate)", |
97 | target, | 92 | target, |
@@ -103,7 +98,7 @@ fn change_vis(ctx: AssistCtx, vis: ast::Visibility) -> Option<Assist> { | |||
103 | } | 98 | } |
104 | if vis.syntax().text() == "pub(crate)" { | 99 | if vis.syntax().text() == "pub(crate)" { |
105 | let target = vis.syntax().text_range(); | 100 | let target = vis.syntax().text_range(); |
106 | return ctx.add_assist( | 101 | return acc.add( |
107 | AssistId("change_visibility"), | 102 | AssistId("change_visibility"), |
108 | "Change visibility to pub", | 103 | "Change visibility to pub", |
109 | target, | 104 | target, |
diff --git a/crates/ra_assists/src/handlers/early_return.rs b/crates/ra_assists/src/handlers/early_return.rs index 4bd6040b2..66b296081 100644 --- a/crates/ra_assists/src/handlers/early_return.rs +++ b/crates/ra_assists/src/handlers/early_return.rs | |||
@@ -2,14 +2,18 @@ use std::{iter::once, ops::RangeInclusive}; | |||
2 | 2 | ||
3 | use ra_syntax::{ | 3 | use ra_syntax::{ |
4 | algo::replace_children, | 4 | algo::replace_children, |
5 | ast::{self, edit::IndentLevel, make}, | 5 | ast::{ |
6 | self, | ||
7 | edit::{AstNodeEdit, IndentLevel}, | ||
8 | make, | ||
9 | }, | ||
6 | AstNode, | 10 | AstNode, |
7 | SyntaxKind::{FN_DEF, LOOP_EXPR, L_CURLY, R_CURLY, WHILE_EXPR, WHITESPACE}, | 11 | SyntaxKind::{FN_DEF, LOOP_EXPR, L_CURLY, R_CURLY, WHILE_EXPR, WHITESPACE}, |
8 | SyntaxNode, | 12 | SyntaxNode, |
9 | }; | 13 | }; |
10 | 14 | ||
11 | use crate::{ | 15 | use crate::{ |
12 | assist_ctx::{Assist, AssistCtx}, | 16 | assist_context::{AssistContext, Assists}, |
13 | utils::invert_boolean_expression, | 17 | utils::invert_boolean_expression, |
14 | AssistId, | 18 | AssistId, |
15 | }; | 19 | }; |
@@ -36,7 +40,7 @@ use crate::{ | |||
36 | // bar(); | 40 | // bar(); |
37 | // } | 41 | // } |
38 | // ``` | 42 | // ``` |
39 | pub(crate) fn convert_to_guarded_return(ctx: AssistCtx) -> Option<Assist> { | 43 | pub(crate) fn convert_to_guarded_return(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { |
40 | let if_expr: ast::IfExpr = ctx.find_node_at_offset()?; | 44 | let if_expr: ast::IfExpr = ctx.find_node_at_offset()?; |
41 | if if_expr.else_branch().is_some() { | 45 | if if_expr.else_branch().is_some() { |
42 | return None; | 46 | return None; |
@@ -93,96 +97,90 @@ pub(crate) fn convert_to_guarded_return(ctx: AssistCtx) -> Option<Assist> { | |||
93 | } | 97 | } |
94 | 98 | ||
95 | then_block.syntax().last_child_or_token().filter(|t| t.kind() == R_CURLY)?; | 99 | then_block.syntax().last_child_or_token().filter(|t| t.kind() == R_CURLY)?; |
96 | let cursor_position = ctx.frange.range.start(); | 100 | let cursor_position = ctx.offset(); |
97 | 101 | ||
98 | let target = if_expr.syntax().text_range(); | 102 | let target = if_expr.syntax().text_range(); |
99 | ctx.add_assist( | 103 | acc.add(AssistId("convert_to_guarded_return"), "Convert to guarded return", target, |edit| { |
100 | AssistId("convert_to_guarded_return"), | 104 | let if_indent_level = IndentLevel::from_node(&if_expr.syntax()); |
101 | "Convert to guarded return", | 105 | let new_block = match if_let_pat { |
102 | target, | 106 | None => { |
103 | |edit| { | 107 | // If. |
104 | let if_indent_level = IndentLevel::from_node(&if_expr.syntax()); | 108 | let new_expr = { |
105 | let new_block = match if_let_pat { | 109 | let then_branch = |
106 | None => { | 110 | make::block_expr(once(make::expr_stmt(early_expression).into()), None); |
107 | // If. | 111 | let cond = invert_boolean_expression(cond_expr); |
108 | let new_expr = { | 112 | make::expr_if(make::condition(cond, None), then_branch).indent(if_indent_level) |
109 | let then_branch = | 113 | }; |
110 | make::block_expr(once(make::expr_stmt(early_expression).into()), None); | 114 | replace(new_expr.syntax(), &then_block, &parent_block, &if_expr) |
111 | let cond = invert_boolean_expression(cond_expr); | 115 | } |
112 | let e = make::expr_if(make::condition(cond, None), then_branch); | 116 | Some((path, bound_ident)) => { |
113 | if_indent_level.increase_indent(e) | 117 | // If-let. |
114 | }; | 118 | let match_expr = { |
115 | replace(new_expr.syntax(), &then_block, &parent_block, &if_expr) | 119 | let happy_arm = { |
116 | } | 120 | let pat = make::tuple_struct_pat( |
117 | Some((path, bound_ident)) => { | 121 | path, |
118 | // If-let. | 122 | once(make::bind_pat(make::name("it")).into()), |
119 | let match_expr = { | ||
120 | let happy_arm = { | ||
121 | let pat = make::tuple_struct_pat( | ||
122 | path, | ||
123 | once(make::bind_pat(make::name("it")).into()), | ||
124 | ); | ||
125 | let expr = { | ||
126 | let name_ref = make::name_ref("it"); | ||
127 | let segment = make::path_segment(name_ref); | ||
128 | let path = make::path_unqualified(segment); | ||
129 | make::expr_path(path) | ||
130 | }; | ||
131 | make::match_arm(once(pat.into()), expr) | ||
132 | }; | ||
133 | |||
134 | let sad_arm = make::match_arm( | ||
135 | // FIXME: would be cool to use `None` or `Err(_)` if appropriate | ||
136 | once(make::placeholder_pat().into()), | ||
137 | early_expression, | ||
138 | ); | 123 | ); |
139 | 124 | let expr = { | |
140 | make::expr_match(cond_expr, make::match_arm_list(vec![happy_arm, sad_arm])) | 125 | let name_ref = make::name_ref("it"); |
126 | let segment = make::path_segment(name_ref); | ||
127 | let path = make::path_unqualified(segment); | ||
128 | make::expr_path(path) | ||
129 | }; | ||
130 | make::match_arm(once(pat.into()), expr) | ||
141 | }; | 131 | }; |
142 | 132 | ||
143 | let let_stmt = make::let_stmt( | 133 | let sad_arm = make::match_arm( |
144 | make::bind_pat(make::name(&bound_ident.syntax().to_string())).into(), | 134 | // FIXME: would be cool to use `None` or `Err(_)` if appropriate |
145 | Some(match_expr), | 135 | once(make::placeholder_pat().into()), |
136 | early_expression, | ||
146 | ); | 137 | ); |
147 | let let_stmt = if_indent_level.increase_indent(let_stmt); | 138 | |
148 | replace(let_stmt.syntax(), &then_block, &parent_block, &if_expr) | 139 | make::expr_match(cond_expr, make::match_arm_list(vec![happy_arm, sad_arm])) |
149 | } | 140 | }; |
150 | }; | 141 | |
151 | edit.replace_ast(parent_block, ast::BlockExpr::cast(new_block).unwrap()); | 142 | let let_stmt = make::let_stmt( |
152 | edit.set_cursor(cursor_position); | 143 | make::bind_pat(make::name(&bound_ident.syntax().to_string())).into(), |
153 | 144 | Some(match_expr), | |
154 | fn replace( | ||
155 | new_expr: &SyntaxNode, | ||
156 | then_block: &ast::BlockExpr, | ||
157 | parent_block: &ast::BlockExpr, | ||
158 | if_expr: &ast::IfExpr, | ||
159 | ) -> SyntaxNode { | ||
160 | let then_block_items = IndentLevel::from(1).decrease_indent(then_block.clone()); | ||
161 | let end_of_then = then_block_items.syntax().last_child_or_token().unwrap(); | ||
162 | let end_of_then = | ||
163 | if end_of_then.prev_sibling_or_token().map(|n| n.kind()) == Some(WHITESPACE) { | ||
164 | end_of_then.prev_sibling_or_token().unwrap() | ||
165 | } else { | ||
166 | end_of_then | ||
167 | }; | ||
168 | let mut then_statements = new_expr.children_with_tokens().chain( | ||
169 | then_block_items | ||
170 | .syntax() | ||
171 | .children_with_tokens() | ||
172 | .skip(1) | ||
173 | .take_while(|i| *i != end_of_then), | ||
174 | ); | 145 | ); |
175 | replace_children( | 146 | let let_stmt = let_stmt.indent(if_indent_level); |
176 | &parent_block.syntax(), | 147 | replace(let_stmt.syntax(), &then_block, &parent_block, &if_expr) |
177 | RangeInclusive::new( | ||
178 | if_expr.clone().syntax().clone().into(), | ||
179 | if_expr.syntax().clone().into(), | ||
180 | ), | ||
181 | &mut then_statements, | ||
182 | ) | ||
183 | } | 148 | } |
184 | }, | 149 | }; |
185 | ) | 150 | edit.replace_ast(parent_block, ast::BlockExpr::cast(new_block).unwrap()); |
151 | edit.set_cursor(cursor_position); | ||
152 | |||
153 | fn replace( | ||
154 | new_expr: &SyntaxNode, | ||
155 | then_block: &ast::BlockExpr, | ||
156 | parent_block: &ast::BlockExpr, | ||
157 | if_expr: &ast::IfExpr, | ||
158 | ) -> SyntaxNode { | ||
159 | let then_block_items = then_block.dedent(IndentLevel::from(1)); | ||
160 | let end_of_then = then_block_items.syntax().last_child_or_token().unwrap(); | ||
161 | let end_of_then = | ||
162 | if end_of_then.prev_sibling_or_token().map(|n| n.kind()) == Some(WHITESPACE) { | ||
163 | end_of_then.prev_sibling_or_token().unwrap() | ||
164 | } else { | ||
165 | end_of_then | ||
166 | }; | ||
167 | let mut then_statements = new_expr.children_with_tokens().chain( | ||
168 | then_block_items | ||
169 | .syntax() | ||
170 | .children_with_tokens() | ||
171 | .skip(1) | ||
172 | .take_while(|i| *i != end_of_then), | ||
173 | ); | ||
174 | replace_children( | ||
175 | &parent_block.syntax(), | ||
176 | RangeInclusive::new( | ||
177 | if_expr.clone().syntax().clone().into(), | ||
178 | if_expr.syntax().clone().into(), | ||
179 | ), | ||
180 | &mut then_statements, | ||
181 | ) | ||
182 | } | ||
183 | }) | ||
186 | } | 184 | } |
187 | 185 | ||
188 | #[cfg(test)] | 186 | #[cfg(test)] |
diff --git a/crates/ra_assists/src/handlers/fill_match_arms.rs b/crates/ra_assists/src/handlers/fill_match_arms.rs index 7c8f8bdf2..13c1e7e80 100644 --- a/crates/ra_assists/src/handlers/fill_match_arms.rs +++ b/crates/ra_assists/src/handlers/fill_match_arms.rs | |||
@@ -5,7 +5,7 @@ use itertools::Itertools; | |||
5 | use ra_ide_db::RootDatabase; | 5 | use ra_ide_db::RootDatabase; |
6 | use ra_syntax::ast::{self, make, AstNode, MatchArm, NameOwner, Pat}; | 6 | use ra_syntax::ast::{self, make, AstNode, MatchArm, NameOwner, Pat}; |
7 | 7 | ||
8 | use crate::{Assist, AssistCtx, AssistId}; | 8 | use crate::{AssistContext, AssistId, Assists}; |
9 | 9 | ||
10 | // Assist: fill_match_arms | 10 | // Assist: fill_match_arms |
11 | // | 11 | // |
@@ -31,7 +31,7 @@ use crate::{Assist, AssistCtx, AssistId}; | |||
31 | // } | 31 | // } |
32 | // } | 32 | // } |
33 | // ``` | 33 | // ``` |
34 | pub(crate) fn fill_match_arms(ctx: AssistCtx) -> Option<Assist> { | 34 | pub(crate) fn fill_match_arms(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { |
35 | let match_expr = ctx.find_node_at_offset::<ast::MatchExpr>()?; | 35 | let match_expr = ctx.find_node_at_offset::<ast::MatchExpr>()?; |
36 | let match_arm_list = match_expr.match_arm_list()?; | 36 | let match_arm_list = match_expr.match_arm_list()?; |
37 | 37 | ||
@@ -93,7 +93,7 @@ pub(crate) fn fill_match_arms(ctx: AssistCtx) -> Option<Assist> { | |||
93 | } | 93 | } |
94 | 94 | ||
95 | let target = match_expr.syntax().text_range(); | 95 | let target = match_expr.syntax().text_range(); |
96 | ctx.add_assist(AssistId("fill_match_arms"), "Fill match arms", target, |edit| { | 96 | acc.add(AssistId("fill_match_arms"), "Fill match arms", target, |edit| { |
97 | let new_arm_list = match_arm_list.remove_placeholder().append_arms(missing_arms); | 97 | let new_arm_list = match_arm_list.remove_placeholder().append_arms(missing_arms); |
98 | edit.set_cursor(expr.syntax().text_range().start()); | 98 | edit.set_cursor(expr.syntax().text_range().start()); |
99 | edit.replace_ast(match_arm_list, new_arm_list); | 99 | edit.replace_ast(match_arm_list, new_arm_list); |
diff --git a/crates/ra_assists/src/handlers/flip_binexpr.rs b/crates/ra_assists/src/handlers/flip_binexpr.rs index cb7264d7b..692ba4895 100644 --- a/crates/ra_assists/src/handlers/flip_binexpr.rs +++ b/crates/ra_assists/src/handlers/flip_binexpr.rs | |||
@@ -1,6 +1,6 @@ | |||
1 | use ra_syntax::ast::{AstNode, BinExpr, BinOp}; | 1 | use ra_syntax::ast::{AstNode, BinExpr, BinOp}; |
2 | 2 | ||
3 | use crate::{Assist, AssistCtx, AssistId}; | 3 | use crate::{AssistContext, AssistId, Assists}; |
4 | 4 | ||
5 | // Assist: flip_binexpr | 5 | // Assist: flip_binexpr |
6 | // | 6 | // |
@@ -17,7 +17,7 @@ use crate::{Assist, AssistCtx, AssistId}; | |||
17 | // let _ = 2 + 90; | 17 | // let _ = 2 + 90; |
18 | // } | 18 | // } |
19 | // ``` | 19 | // ``` |
20 | pub(crate) fn flip_binexpr(ctx: AssistCtx) -> Option<Assist> { | 20 | pub(crate) fn flip_binexpr(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { |
21 | let expr = ctx.find_node_at_offset::<BinExpr>()?; | 21 | let expr = ctx.find_node_at_offset::<BinExpr>()?; |
22 | let lhs = expr.lhs()?.syntax().clone(); | 22 | let lhs = expr.lhs()?.syntax().clone(); |
23 | let rhs = expr.rhs()?.syntax().clone(); | 23 | let rhs = expr.rhs()?.syntax().clone(); |
@@ -33,7 +33,7 @@ pub(crate) fn flip_binexpr(ctx: AssistCtx) -> Option<Assist> { | |||
33 | return None; | 33 | return None; |
34 | } | 34 | } |
35 | 35 | ||
36 | ctx.add_assist(AssistId("flip_binexpr"), "Flip binary expression", op_range, |edit| { | 36 | acc.add(AssistId("flip_binexpr"), "Flip binary expression", op_range, |edit| { |
37 | if let FlipAction::FlipAndReplaceOp(new_op) = action { | 37 | if let FlipAction::FlipAndReplaceOp(new_op) = action { |
38 | edit.replace(op_range, new_op); | 38 | edit.replace(op_range, new_op); |
39 | } | 39 | } |
diff --git a/crates/ra_assists/src/handlers/flip_comma.rs b/crates/ra_assists/src/handlers/flip_comma.rs index 24982ae22..dfe2a7fed 100644 --- a/crates/ra_assists/src/handlers/flip_comma.rs +++ b/crates/ra_assists/src/handlers/flip_comma.rs | |||
@@ -1,6 +1,6 @@ | |||
1 | use ra_syntax::{algo::non_trivia_sibling, Direction, T}; | 1 | use ra_syntax::{algo::non_trivia_sibling, Direction, T}; |
2 | 2 | ||
3 | use crate::{Assist, AssistCtx, AssistId}; | 3 | use crate::{AssistContext, AssistId, Assists}; |
4 | 4 | ||
5 | // Assist: flip_comma | 5 | // Assist: flip_comma |
6 | // | 6 | // |
@@ -17,7 +17,7 @@ use crate::{Assist, AssistCtx, AssistId}; | |||
17 | // ((3, 4), (1, 2)); | 17 | // ((3, 4), (1, 2)); |
18 | // } | 18 | // } |
19 | // ``` | 19 | // ``` |
20 | pub(crate) fn flip_comma(ctx: AssistCtx) -> Option<Assist> { | 20 | pub(crate) fn flip_comma(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { |
21 | let comma = ctx.find_token_at_offset(T![,])?; | 21 | let comma = ctx.find_token_at_offset(T![,])?; |
22 | let prev = non_trivia_sibling(comma.clone().into(), Direction::Prev)?; | 22 | let prev = non_trivia_sibling(comma.clone().into(), Direction::Prev)?; |
23 | let next = non_trivia_sibling(comma.clone().into(), Direction::Next)?; | 23 | let next = non_trivia_sibling(comma.clone().into(), Direction::Next)?; |
@@ -28,7 +28,7 @@ pub(crate) fn flip_comma(ctx: AssistCtx) -> Option<Assist> { | |||
28 | return None; | 28 | return None; |
29 | } | 29 | } |
30 | 30 | ||
31 | ctx.add_assist(AssistId("flip_comma"), "Flip comma", comma.text_range(), |edit| { | 31 | acc.add(AssistId("flip_comma"), "Flip comma", comma.text_range(), |edit| { |
32 | edit.replace(prev.text_range(), next.to_string()); | 32 | edit.replace(prev.text_range(), next.to_string()); |
33 | edit.replace(next.text_range(), prev.to_string()); | 33 | edit.replace(next.text_range(), prev.to_string()); |
34 | }) | 34 | }) |
diff --git a/crates/ra_assists/src/handlers/flip_trait_bound.rs b/crates/ra_assists/src/handlers/flip_trait_bound.rs index 6a3b2df67..8a08702ab 100644 --- a/crates/ra_assists/src/handlers/flip_trait_bound.rs +++ b/crates/ra_assists/src/handlers/flip_trait_bound.rs | |||
@@ -4,7 +4,7 @@ use ra_syntax::{ | |||
4 | Direction, T, | 4 | Direction, T, |
5 | }; | 5 | }; |
6 | 6 | ||
7 | use crate::{Assist, AssistCtx, AssistId}; | 7 | use crate::{AssistContext, AssistId, Assists}; |
8 | 8 | ||
9 | // Assist: flip_trait_bound | 9 | // Assist: flip_trait_bound |
10 | // | 10 | // |
@@ -17,7 +17,7 @@ use crate::{Assist, AssistCtx, AssistId}; | |||
17 | // ``` | 17 | // ``` |
18 | // fn foo<T: Copy + Clone>() { } | 18 | // fn foo<T: Copy + Clone>() { } |
19 | // ``` | 19 | // ``` |
20 | pub(crate) fn flip_trait_bound(ctx: AssistCtx) -> Option<Assist> { | 20 | pub(crate) fn flip_trait_bound(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { |
21 | // We want to replicate the behavior of `flip_binexpr` by only suggesting | 21 | // We want to replicate the behavior of `flip_binexpr` by only suggesting |
22 | // the assist when the cursor is on a `+` | 22 | // the assist when the cursor is on a `+` |
23 | let plus = ctx.find_token_at_offset(T![+])?; | 23 | let plus = ctx.find_token_at_offset(T![+])?; |
@@ -33,7 +33,7 @@ pub(crate) fn flip_trait_bound(ctx: AssistCtx) -> Option<Assist> { | |||
33 | ); | 33 | ); |
34 | 34 | ||
35 | let target = plus.text_range(); | 35 | let target = plus.text_range(); |
36 | ctx.add_assist(AssistId("flip_trait_bound"), "Flip trait bounds", target, |edit| { | 36 | acc.add(AssistId("flip_trait_bound"), "Flip trait bounds", target, |edit| { |
37 | edit.replace(before.text_range(), after.to_string()); | 37 | edit.replace(before.text_range(), after.to_string()); |
38 | edit.replace(after.text_range(), before.to_string()); | 38 | edit.replace(after.text_range(), before.to_string()); |
39 | }) | 39 | }) |
diff --git a/crates/ra_assists/src/handlers/inline_local_variable.rs b/crates/ra_assists/src/handlers/inline_local_variable.rs index e5765c845..5b26814d3 100644 --- a/crates/ra_assists/src/handlers/inline_local_variable.rs +++ b/crates/ra_assists/src/handlers/inline_local_variable.rs | |||
@@ -5,7 +5,10 @@ use ra_syntax::{ | |||
5 | }; | 5 | }; |
6 | use test_utils::tested_by; | 6 | use test_utils::tested_by; |
7 | 7 | ||
8 | use crate::{assist_ctx::ActionBuilder, Assist, AssistCtx, AssistId}; | 8 | use crate::{ |
9 | assist_context::{AssistContext, Assists}, | ||
10 | AssistId, | ||
11 | }; | ||
9 | 12 | ||
10 | // Assist: inline_local_variable | 13 | // Assist: inline_local_variable |
11 | // | 14 | // |
@@ -23,7 +26,7 @@ use crate::{assist_ctx::ActionBuilder, Assist, AssistCtx, AssistId}; | |||
23 | // (1 + 2) * 4; | 26 | // (1 + 2) * 4; |
24 | // } | 27 | // } |
25 | // ``` | 28 | // ``` |
26 | pub(crate) fn inline_local_variable(ctx: AssistCtx) -> Option<Assist> { | 29 | pub(crate) fn inline_local_variable(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { |
27 | let let_stmt = ctx.find_node_at_offset::<ast::LetStmt>()?; | 30 | let let_stmt = ctx.find_node_at_offset::<ast::LetStmt>()?; |
28 | let bind_pat = match let_stmt.pat()? { | 31 | let bind_pat = match let_stmt.pat()? { |
29 | ast::Pat::BindPat(pat) => pat, | 32 | ast::Pat::BindPat(pat) => pat, |
@@ -33,7 +36,7 @@ pub(crate) fn inline_local_variable(ctx: AssistCtx) -> Option<Assist> { | |||
33 | tested_by!(test_not_inline_mut_variable); | 36 | tested_by!(test_not_inline_mut_variable); |
34 | return None; | 37 | return None; |
35 | } | 38 | } |
36 | if !bind_pat.syntax().text_range().contains_inclusive(ctx.frange.range.start()) { | 39 | if !bind_pat.syntax().text_range().contains_inclusive(ctx.offset()) { |
37 | tested_by!(not_applicable_outside_of_bind_pat); | 40 | tested_by!(not_applicable_outside_of_bind_pat); |
38 | return None; | 41 | return None; |
39 | } | 42 | } |
@@ -107,20 +110,14 @@ pub(crate) fn inline_local_variable(ctx: AssistCtx) -> Option<Assist> { | |||
107 | let init_in_paren = format!("({})", &init_str); | 110 | let init_in_paren = format!("({})", &init_str); |
108 | 111 | ||
109 | let target = bind_pat.syntax().text_range(); | 112 | let target = bind_pat.syntax().text_range(); |
110 | ctx.add_assist( | 113 | acc.add(AssistId("inline_local_variable"), "Inline variable", target, move |builder| { |
111 | AssistId("inline_local_variable"), | 114 | builder.delete(delete_range); |
112 | "Inline variable", | 115 | for (desc, should_wrap) in refs.iter().zip(wrap_in_parens) { |
113 | target, | 116 | let replacement = if should_wrap { init_in_paren.clone() } else { init_str.clone() }; |
114 | move |edit: &mut ActionBuilder| { | 117 | builder.replace(desc.file_range.range, replacement) |
115 | edit.delete(delete_range); | 118 | } |
116 | for (desc, should_wrap) in refs.iter().zip(wrap_in_parens) { | 119 | builder.set_cursor(delete_range.start()) |
117 | let replacement = | 120 | }) |
118 | if should_wrap { init_in_paren.clone() } else { init_str.clone() }; | ||
119 | edit.replace(desc.file_range.range, replacement) | ||
120 | } | ||
121 | edit.set_cursor(delete_range.start()) | ||
122 | }, | ||
123 | ) | ||
124 | } | 121 | } |
125 | 122 | ||
126 | #[cfg(test)] | 123 | #[cfg(test)] |
diff --git a/crates/ra_assists/src/handlers/introduce_variable.rs b/crates/ra_assists/src/handlers/introduce_variable.rs index 3c340ff3b..fdf3ada0d 100644 --- a/crates/ra_assists/src/handlers/introduce_variable.rs +++ b/crates/ra_assists/src/handlers/introduce_variable.rs | |||
@@ -9,7 +9,7 @@ use ra_syntax::{ | |||
9 | use stdx::format_to; | 9 | use stdx::format_to; |
10 | use test_utils::tested_by; | 10 | use test_utils::tested_by; |
11 | 11 | ||
12 | use crate::{Assist, AssistCtx, AssistId}; | 12 | use crate::{AssistContext, AssistId, Assists}; |
13 | 13 | ||
14 | // Assist: introduce_variable | 14 | // Assist: introduce_variable |
15 | // | 15 | // |
@@ -27,7 +27,7 @@ use crate::{Assist, AssistCtx, AssistId}; | |||
27 | // var_name * 4; | 27 | // var_name * 4; |
28 | // } | 28 | // } |
29 | // ``` | 29 | // ``` |
30 | pub(crate) fn introduce_variable(ctx: AssistCtx) -> Option<Assist> { | 30 | pub(crate) fn introduce_variable(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { |
31 | if ctx.frange.range.is_empty() { | 31 | if ctx.frange.range.is_empty() { |
32 | return None; | 32 | return None; |
33 | } | 33 | } |
@@ -43,7 +43,7 @@ pub(crate) fn introduce_variable(ctx: AssistCtx) -> Option<Assist> { | |||
43 | return None; | 43 | return None; |
44 | } | 44 | } |
45 | let target = expr.syntax().text_range(); | 45 | let target = expr.syntax().text_range(); |
46 | ctx.add_assist(AssistId("introduce_variable"), "Extract into variable", target, move |edit| { | 46 | acc.add(AssistId("introduce_variable"), "Extract into variable", target, move |edit| { |
47 | let mut buf = String::new(); | 47 | let mut buf = String::new(); |
48 | 48 | ||
49 | let cursor_offset = if wrap_in_block { | 49 | let cursor_offset = if wrap_in_block { |
diff --git a/crates/ra_assists/src/handlers/invert_if.rs b/crates/ra_assists/src/handlers/invert_if.rs index b16271443..527c7caef 100644 --- a/crates/ra_assists/src/handlers/invert_if.rs +++ b/crates/ra_assists/src/handlers/invert_if.rs | |||
@@ -3,7 +3,11 @@ use ra_syntax::{ | |||
3 | T, | 3 | T, |
4 | }; | 4 | }; |
5 | 5 | ||
6 | use crate::{utils::invert_boolean_expression, Assist, AssistCtx, AssistId}; | 6 | use crate::{ |
7 | assist_context::{AssistContext, Assists}, | ||
8 | utils::invert_boolean_expression, | ||
9 | AssistId, | ||
10 | }; | ||
7 | 11 | ||
8 | // Assist: invert_if | 12 | // Assist: invert_if |
9 | // | 13 | // |
@@ -24,7 +28,7 @@ use crate::{utils::invert_boolean_expression, Assist, AssistCtx, AssistId}; | |||
24 | // } | 28 | // } |
25 | // ``` | 29 | // ``` |
26 | 30 | ||
27 | pub(crate) fn invert_if(ctx: AssistCtx) -> Option<Assist> { | 31 | pub(crate) fn invert_if(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { |
28 | let if_keyword = ctx.find_token_at_offset(T![if])?; | 32 | let if_keyword = ctx.find_token_at_offset(T![if])?; |
29 | let expr = ast::IfExpr::cast(if_keyword.parent())?; | 33 | let expr = ast::IfExpr::cast(if_keyword.parent())?; |
30 | let if_range = if_keyword.text_range(); | 34 | let if_range = if_keyword.text_range(); |
@@ -40,21 +44,21 @@ pub(crate) fn invert_if(ctx: AssistCtx) -> Option<Assist> { | |||
40 | 44 | ||
41 | let cond = expr.condition()?.expr()?; | 45 | let cond = expr.condition()?.expr()?; |
42 | let then_node = expr.then_branch()?.syntax().clone(); | 46 | let then_node = expr.then_branch()?.syntax().clone(); |
47 | let else_block = match expr.else_branch()? { | ||
48 | ast::ElseBranch::Block(it) => it, | ||
49 | ast::ElseBranch::IfExpr(_) => return None, | ||
50 | }; | ||
43 | 51 | ||
44 | if let ast::ElseBranch::Block(else_block) = expr.else_branch()? { | 52 | let cond_range = cond.syntax().text_range(); |
45 | let cond_range = cond.syntax().text_range(); | 53 | let flip_cond = invert_boolean_expression(cond); |
46 | let flip_cond = invert_boolean_expression(cond); | 54 | let else_node = else_block.syntax(); |
47 | let else_node = else_block.syntax(); | 55 | let else_range = else_node.text_range(); |
48 | let else_range = else_node.text_range(); | 56 | let then_range = then_node.text_range(); |
49 | let then_range = then_node.text_range(); | 57 | acc.add(AssistId("invert_if"), "Invert if", if_range, |edit| { |
50 | return ctx.add_assist(AssistId("invert_if"), "Invert if", if_range, |edit| { | 58 | edit.replace(cond_range, flip_cond.syntax().text()); |
51 | edit.replace(cond_range, flip_cond.syntax().text()); | 59 | edit.replace(else_range, then_node.text()); |
52 | edit.replace(else_range, then_node.text()); | 60 | edit.replace(then_range, else_node.text()); |
53 | edit.replace(then_range, else_node.text()); | 61 | }) |
54 | }); | ||
55 | } | ||
56 | |||
57 | None | ||
58 | } | 62 | } |
59 | 63 | ||
60 | #[cfg(test)] | 64 | #[cfg(test)] |
diff --git a/crates/ra_assists/src/handlers/merge_imports.rs b/crates/ra_assists/src/handlers/merge_imports.rs index de74d83d8..ac3e53c27 100644 --- a/crates/ra_assists/src/handlers/merge_imports.rs +++ b/crates/ra_assists/src/handlers/merge_imports.rs | |||
@@ -6,7 +6,10 @@ use ra_syntax::{ | |||
6 | AstNode, Direction, InsertPosition, SyntaxElement, T, | 6 | AstNode, Direction, InsertPosition, SyntaxElement, T, |
7 | }; | 7 | }; |
8 | 8 | ||
9 | use crate::{Assist, AssistCtx, AssistId}; | 9 | use crate::{ |
10 | assist_context::{AssistContext, Assists}, | ||
11 | AssistId, | ||
12 | }; | ||
10 | 13 | ||
11 | // Assist: merge_imports | 14 | // Assist: merge_imports |
12 | // | 15 | // |
@@ -20,10 +23,10 @@ use crate::{Assist, AssistCtx, AssistId}; | |||
20 | // ``` | 23 | // ``` |
21 | // use std::{fmt::Formatter, io}; | 24 | // use std::{fmt::Formatter, io}; |
22 | // ``` | 25 | // ``` |
23 | pub(crate) fn merge_imports(ctx: AssistCtx) -> Option<Assist> { | 26 | pub(crate) fn merge_imports(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { |
24 | let tree: ast::UseTree = ctx.find_node_at_offset()?; | 27 | let tree: ast::UseTree = ctx.find_node_at_offset()?; |
25 | let mut rewriter = SyntaxRewriter::default(); | 28 | let mut rewriter = SyntaxRewriter::default(); |
26 | let mut offset = ctx.frange.range.start(); | 29 | let mut offset = ctx.offset(); |
27 | 30 | ||
28 | if let Some(use_item) = tree.syntax().parent().and_then(ast::UseItem::cast) { | 31 | if let Some(use_item) = tree.syntax().parent().and_then(ast::UseItem::cast) { |
29 | let (merged, to_delete) = next_prev() | 32 | let (merged, to_delete) = next_prev() |
@@ -53,10 +56,10 @@ pub(crate) fn merge_imports(ctx: AssistCtx) -> Option<Assist> { | |||
53 | }; | 56 | }; |
54 | 57 | ||
55 | let target = tree.syntax().text_range(); | 58 | let target = tree.syntax().text_range(); |
56 | ctx.add_assist(AssistId("merge_imports"), "Merge imports", target, |edit| { | 59 | acc.add(AssistId("merge_imports"), "Merge imports", target, |builder| { |
57 | edit.rewrite(rewriter); | 60 | builder.rewrite(rewriter); |
58 | // FIXME: we only need because our diff is imprecise | 61 | // FIXME: we only need because our diff is imprecise |
59 | edit.set_cursor(offset); | 62 | builder.set_cursor(offset); |
60 | }) | 63 | }) |
61 | } | 64 | } |
62 | 65 | ||
diff --git a/crates/ra_assists/src/handlers/merge_match_arms.rs b/crates/ra_assists/src/handlers/merge_match_arms.rs index 7c4d9d55d..d4e38aa6a 100644 --- a/crates/ra_assists/src/handlers/merge_match_arms.rs +++ b/crates/ra_assists/src/handlers/merge_match_arms.rs | |||
@@ -6,7 +6,7 @@ use ra_syntax::{ | |||
6 | Direction, TextSize, | 6 | Direction, TextSize, |
7 | }; | 7 | }; |
8 | 8 | ||
9 | use crate::{Assist, AssistCtx, AssistId, TextRange}; | 9 | use crate::{AssistContext, AssistId, Assists, TextRange}; |
10 | 10 | ||
11 | // Assist: merge_match_arms | 11 | // Assist: merge_match_arms |
12 | // | 12 | // |
@@ -32,7 +32,7 @@ use crate::{Assist, AssistCtx, AssistId, TextRange}; | |||
32 | // } | 32 | // } |
33 | // } | 33 | // } |
34 | // ``` | 34 | // ``` |
35 | pub(crate) fn merge_match_arms(ctx: AssistCtx) -> Option<Assist> { | 35 | pub(crate) fn merge_match_arms(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { |
36 | let current_arm = ctx.find_node_at_offset::<ast::MatchArm>()?; | 36 | let current_arm = ctx.find_node_at_offset::<ast::MatchArm>()?; |
37 | // Don't try to handle arms with guards for now - can add support for this later | 37 | // Don't try to handle arms with guards for now - can add support for this later |
38 | if current_arm.guard().is_some() { | 38 | if current_arm.guard().is_some() { |
@@ -45,7 +45,7 @@ pub(crate) fn merge_match_arms(ctx: AssistCtx) -> Option<Assist> { | |||
45 | InExpr(TextSize), | 45 | InExpr(TextSize), |
46 | InPat(TextSize), | 46 | InPat(TextSize), |
47 | } | 47 | } |
48 | let cursor_pos = ctx.frange.range.start(); | 48 | let cursor_pos = ctx.offset(); |
49 | let cursor_pos = if current_expr.syntax().text_range().contains(cursor_pos) { | 49 | let cursor_pos = if current_expr.syntax().text_range().contains(cursor_pos) { |
50 | CursorPos::InExpr(current_text_range.end() - cursor_pos) | 50 | CursorPos::InExpr(current_text_range.end() - cursor_pos) |
51 | } else { | 51 | } else { |
@@ -70,7 +70,7 @@ pub(crate) fn merge_match_arms(ctx: AssistCtx) -> Option<Assist> { | |||
70 | return None; | 70 | return None; |
71 | } | 71 | } |
72 | 72 | ||
73 | ctx.add_assist(AssistId("merge_match_arms"), "Merge match arms", current_text_range, |edit| { | 73 | acc.add(AssistId("merge_match_arms"), "Merge match arms", current_text_range, |edit| { |
74 | let pats = if arms_to_merge.iter().any(contains_placeholder) { | 74 | let pats = if arms_to_merge.iter().any(contains_placeholder) { |
75 | "_".into() | 75 | "_".into() |
76 | } else { | 76 | } else { |
diff --git a/crates/ra_assists/src/handlers/move_bounds.rs b/crates/ra_assists/src/handlers/move_bounds.rs index 44e50cb6e..a41aacfc3 100644 --- a/crates/ra_assists/src/handlers/move_bounds.rs +++ b/crates/ra_assists/src/handlers/move_bounds.rs | |||
@@ -5,7 +5,7 @@ use ra_syntax::{ | |||
5 | T, | 5 | T, |
6 | }; | 6 | }; |
7 | 7 | ||
8 | use crate::{Assist, AssistCtx, AssistId}; | 8 | use crate::{AssistContext, AssistId, Assists}; |
9 | 9 | ||
10 | // Assist: move_bounds_to_where_clause | 10 | // Assist: move_bounds_to_where_clause |
11 | // | 11 | // |
@@ -22,7 +22,7 @@ use crate::{Assist, AssistCtx, AssistId}; | |||
22 | // f(x) | 22 | // f(x) |
23 | // } | 23 | // } |
24 | // ``` | 24 | // ``` |
25 | pub(crate) fn move_bounds_to_where_clause(ctx: AssistCtx) -> Option<Assist> { | 25 | pub(crate) fn move_bounds_to_where_clause(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { |
26 | let type_param_list = ctx.find_node_at_offset::<ast::TypeParamList>()?; | 26 | let type_param_list = ctx.find_node_at_offset::<ast::TypeParamList>()?; |
27 | 27 | ||
28 | let mut type_params = type_param_list.type_params(); | 28 | let mut type_params = type_param_list.type_params(); |
@@ -50,36 +50,29 @@ pub(crate) fn move_bounds_to_where_clause(ctx: AssistCtx) -> Option<Assist> { | |||
50 | }; | 50 | }; |
51 | 51 | ||
52 | let target = type_param_list.syntax().text_range(); | 52 | let target = type_param_list.syntax().text_range(); |
53 | ctx.add_assist( | 53 | acc.add(AssistId("move_bounds_to_where_clause"), "Move to where clause", target, |edit| { |
54 | AssistId("move_bounds_to_where_clause"), | 54 | let new_params = type_param_list |
55 | "Move to where clause", | 55 | .type_params() |
56 | target, | 56 | .filter(|it| it.type_bound_list().is_some()) |
57 | |edit| { | 57 | .map(|type_param| { |
58 | let new_params = type_param_list | 58 | let without_bounds = type_param.remove_bounds(); |
59 | .type_params() | 59 | (type_param, without_bounds) |
60 | .filter(|it| it.type_bound_list().is_some()) | 60 | }); |
61 | .map(|type_param| { | 61 | |
62 | let without_bounds = type_param.remove_bounds(); | 62 | let new_type_param_list = type_param_list.replace_descendants(new_params); |
63 | (type_param, without_bounds) | 63 | edit.replace_ast(type_param_list.clone(), new_type_param_list); |
64 | }); | 64 | |
65 | 65 | let where_clause = { | |
66 | let new_type_param_list = type_param_list.replace_descendants(new_params); | 66 | let predicates = type_param_list.type_params().filter_map(build_predicate); |
67 | edit.replace_ast(type_param_list.clone(), new_type_param_list); | 67 | make::where_clause(predicates) |
68 | 68 | }; | |
69 | let where_clause = { | 69 | |
70 | let predicates = type_param_list.type_params().filter_map(build_predicate); | 70 | let to_insert = match anchor.prev_sibling_or_token() { |
71 | make::where_clause(predicates) | 71 | Some(ref elem) if elem.kind() == WHITESPACE => format!("{} ", where_clause.syntax()), |
72 | }; | 72 | _ => format!(" {}", where_clause.syntax()), |
73 | 73 | }; | |
74 | let to_insert = match anchor.prev_sibling_or_token() { | 74 | edit.insert(anchor.text_range().start(), to_insert); |
75 | Some(ref elem) if elem.kind() == WHITESPACE => { | 75 | }) |
76 | format!("{} ", where_clause.syntax()) | ||
77 | } | ||
78 | _ => format!(" {}", where_clause.syntax()), | ||
79 | }; | ||
80 | edit.insert(anchor.text_range().start(), to_insert); | ||
81 | }, | ||
82 | ) | ||
83 | } | 76 | } |
84 | 77 | ||
85 | fn build_predicate(param: ast::TypeParam) -> Option<ast::WherePred> { | 78 | fn build_predicate(param: ast::TypeParam) -> Option<ast::WherePred> { |
diff --git a/crates/ra_assists/src/handlers/move_guard.rs b/crates/ra_assists/src/handlers/move_guard.rs index 29bc9a9ff..fc0335b57 100644 --- a/crates/ra_assists/src/handlers/move_guard.rs +++ b/crates/ra_assists/src/handlers/move_guard.rs | |||
@@ -4,7 +4,7 @@ use ra_syntax::{ | |||
4 | TextSize, | 4 | TextSize, |
5 | }; | 5 | }; |
6 | 6 | ||
7 | use crate::{Assist, AssistCtx, AssistId}; | 7 | use crate::{AssistContext, AssistId, Assists}; |
8 | 8 | ||
9 | // Assist: move_guard_to_arm_body | 9 | // Assist: move_guard_to_arm_body |
10 | // | 10 | // |
@@ -31,7 +31,7 @@ use crate::{Assist, AssistCtx, AssistId}; | |||
31 | // } | 31 | // } |
32 | // } | 32 | // } |
33 | // ``` | 33 | // ``` |
34 | pub(crate) fn move_guard_to_arm_body(ctx: AssistCtx) -> Option<Assist> { | 34 | pub(crate) fn move_guard_to_arm_body(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { |
35 | let match_arm = ctx.find_node_at_offset::<MatchArm>()?; | 35 | let match_arm = ctx.find_node_at_offset::<MatchArm>()?; |
36 | let guard = match_arm.guard()?; | 36 | let guard = match_arm.guard()?; |
37 | let space_before_guard = guard.syntax().prev_sibling_or_token(); | 37 | let space_before_guard = guard.syntax().prev_sibling_or_token(); |
@@ -41,7 +41,7 @@ pub(crate) fn move_guard_to_arm_body(ctx: AssistCtx) -> Option<Assist> { | |||
41 | let buf = format!("if {} {{ {} }}", guard_conditions.syntax().text(), arm_expr.syntax().text()); | 41 | let buf = format!("if {} {{ {} }}", guard_conditions.syntax().text(), arm_expr.syntax().text()); |
42 | 42 | ||
43 | let target = guard.syntax().text_range(); | 43 | let target = guard.syntax().text_range(); |
44 | ctx.add_assist(AssistId("move_guard_to_arm_body"), "Move guard to arm body", target, |edit| { | 44 | acc.add(AssistId("move_guard_to_arm_body"), "Move guard to arm body", target, |edit| { |
45 | let offseting_amount = match space_before_guard.and_then(|it| it.into_token()) { | 45 | let offseting_amount = match space_before_guard.and_then(|it| it.into_token()) { |
46 | Some(tok) => { | 46 | Some(tok) => { |
47 | if ast::Whitespace::cast(tok.clone()).is_some() { | 47 | if ast::Whitespace::cast(tok.clone()).is_some() { |
@@ -88,7 +88,7 @@ pub(crate) fn move_guard_to_arm_body(ctx: AssistCtx) -> Option<Assist> { | |||
88 | // } | 88 | // } |
89 | // } | 89 | // } |
90 | // ``` | 90 | // ``` |
91 | pub(crate) fn move_arm_cond_to_match_guard(ctx: AssistCtx) -> Option<Assist> { | 91 | pub(crate) fn move_arm_cond_to_match_guard(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { |
92 | let match_arm: MatchArm = ctx.find_node_at_offset::<MatchArm>()?; | 92 | let match_arm: MatchArm = ctx.find_node_at_offset::<MatchArm>()?; |
93 | let match_pat = match_arm.pat()?; | 93 | let match_pat = match_arm.pat()?; |
94 | 94 | ||
@@ -109,7 +109,7 @@ pub(crate) fn move_arm_cond_to_match_guard(ctx: AssistCtx) -> Option<Assist> { | |||
109 | let buf = format!(" if {}", cond.syntax().text()); | 109 | let buf = format!(" if {}", cond.syntax().text()); |
110 | 110 | ||
111 | let target = if_expr.syntax().text_range(); | 111 | let target = if_expr.syntax().text_range(); |
112 | ctx.add_assist( | 112 | acc.add( |
113 | AssistId("move_arm_cond_to_match_guard"), | 113 | AssistId("move_arm_cond_to_match_guard"), |
114 | "Move condition to match guard", | 114 | "Move condition to match guard", |
115 | target, | 115 | target, |
diff --git a/crates/ra_assists/src/handlers/raw_string.rs b/crates/ra_assists/src/handlers/raw_string.rs index 155c679b4..c20ffe0b3 100644 --- a/crates/ra_assists/src/handlers/raw_string.rs +++ b/crates/ra_assists/src/handlers/raw_string.rs | |||
@@ -5,7 +5,7 @@ use ra_syntax::{ | |||
5 | TextSize, | 5 | TextSize, |
6 | }; | 6 | }; |
7 | 7 | ||
8 | use crate::{Assist, AssistCtx, AssistId}; | 8 | use crate::{AssistContext, AssistId, Assists}; |
9 | 9 | ||
10 | // Assist: make_raw_string | 10 | // Assist: make_raw_string |
11 | // | 11 | // |
@@ -22,11 +22,11 @@ use crate::{Assist, AssistCtx, AssistId}; | |||
22 | // r#"Hello, World!"#; | 22 | // r#"Hello, World!"#; |
23 | // } | 23 | // } |
24 | // ``` | 24 | // ``` |
25 | pub(crate) fn make_raw_string(ctx: AssistCtx) -> Option<Assist> { | 25 | pub(crate) fn make_raw_string(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { |
26 | let token = ctx.find_token_at_offset(STRING).and_then(ast::String::cast)?; | 26 | let token = ctx.find_token_at_offset(STRING).and_then(ast::String::cast)?; |
27 | let value = token.value()?; | 27 | let value = token.value()?; |
28 | let target = token.syntax().text_range(); | 28 | let target = token.syntax().text_range(); |
29 | ctx.add_assist(AssistId("make_raw_string"), "Rewrite as raw string", target, |edit| { | 29 | acc.add(AssistId("make_raw_string"), "Rewrite as raw string", target, |edit| { |
30 | let max_hash_streak = count_hashes(&value); | 30 | let max_hash_streak = count_hashes(&value); |
31 | let mut hashes = String::with_capacity(max_hash_streak + 1); | 31 | let mut hashes = String::with_capacity(max_hash_streak + 1); |
32 | for _ in 0..hashes.capacity() { | 32 | for _ in 0..hashes.capacity() { |
@@ -51,11 +51,11 @@ pub(crate) fn make_raw_string(ctx: AssistCtx) -> Option<Assist> { | |||
51 | // "Hello, \"World!\""; | 51 | // "Hello, \"World!\""; |
52 | // } | 52 | // } |
53 | // ``` | 53 | // ``` |
54 | pub(crate) fn make_usual_string(ctx: AssistCtx) -> Option<Assist> { | 54 | pub(crate) fn make_usual_string(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { |
55 | let token = ctx.find_token_at_offset(RAW_STRING).and_then(ast::RawString::cast)?; | 55 | let token = ctx.find_token_at_offset(RAW_STRING).and_then(ast::RawString::cast)?; |
56 | let value = token.value()?; | 56 | let value = token.value()?; |
57 | let target = token.syntax().text_range(); | 57 | let target = token.syntax().text_range(); |
58 | ctx.add_assist(AssistId("make_usual_string"), "Rewrite as regular string", target, |edit| { | 58 | acc.add(AssistId("make_usual_string"), "Rewrite as regular string", target, |edit| { |
59 | // parse inside string to escape `"` | 59 | // parse inside string to escape `"` |
60 | let escaped = value.escape_default().to_string(); | 60 | let escaped = value.escape_default().to_string(); |
61 | edit.replace(token.syntax().text_range(), format!("\"{}\"", escaped)); | 61 | edit.replace(token.syntax().text_range(), format!("\"{}\"", escaped)); |
@@ -77,10 +77,10 @@ pub(crate) fn make_usual_string(ctx: AssistCtx) -> Option<Assist> { | |||
77 | // r##"Hello, World!"##; | 77 | // r##"Hello, World!"##; |
78 | // } | 78 | // } |
79 | // ``` | 79 | // ``` |
80 | pub(crate) fn add_hash(ctx: AssistCtx) -> Option<Assist> { | 80 | pub(crate) fn add_hash(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { |
81 | let token = ctx.find_token_at_offset(RAW_STRING)?; | 81 | let token = ctx.find_token_at_offset(RAW_STRING)?; |
82 | let target = token.text_range(); | 82 | let target = token.text_range(); |
83 | ctx.add_assist(AssistId("add_hash"), "Add # to raw string", target, |edit| { | 83 | acc.add(AssistId("add_hash"), "Add # to raw string", target, |edit| { |
84 | edit.insert(token.text_range().start() + TextSize::of('r'), "#"); | 84 | edit.insert(token.text_range().start() + TextSize::of('r'), "#"); |
85 | edit.insert(token.text_range().end(), "#"); | 85 | edit.insert(token.text_range().end(), "#"); |
86 | }) | 86 | }) |
@@ -101,7 +101,7 @@ pub(crate) fn add_hash(ctx: AssistCtx) -> Option<Assist> { | |||
101 | // r"Hello, World!"; | 101 | // r"Hello, World!"; |
102 | // } | 102 | // } |
103 | // ``` | 103 | // ``` |
104 | pub(crate) fn remove_hash(ctx: AssistCtx) -> Option<Assist> { | 104 | pub(crate) fn remove_hash(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { |
105 | let token = ctx.find_token_at_offset(RAW_STRING)?; | 105 | let token = ctx.find_token_at_offset(RAW_STRING)?; |
106 | let text = token.text().as_str(); | 106 | let text = token.text().as_str(); |
107 | if text.starts_with("r\"") { | 107 | if text.starts_with("r\"") { |
@@ -109,7 +109,7 @@ pub(crate) fn remove_hash(ctx: AssistCtx) -> Option<Assist> { | |||
109 | return None; | 109 | return None; |
110 | } | 110 | } |
111 | let target = token.text_range(); | 111 | let target = token.text_range(); |
112 | ctx.add_assist(AssistId("remove_hash"), "Remove hash from raw string", target, |edit| { | 112 | acc.add(AssistId("remove_hash"), "Remove hash from raw string", target, |edit| { |
113 | let result = &text[2..text.len() - 1]; | 113 | let result = &text[2..text.len() - 1]; |
114 | let result = if result.starts_with('\"') { | 114 | let result = if result.starts_with('\"') { |
115 | // FIXME: this logic is wrong, not only the last has has to handled specially | 115 | // FIXME: this logic is wrong, not only the last has has to handled specially |
diff --git a/crates/ra_assists/src/handlers/remove_dbg.rs b/crates/ra_assists/src/handlers/remove_dbg.rs index e6e02f2ae..8eef578cf 100644 --- a/crates/ra_assists/src/handlers/remove_dbg.rs +++ b/crates/ra_assists/src/handlers/remove_dbg.rs | |||
@@ -3,7 +3,7 @@ use ra_syntax::{ | |||
3 | TextSize, T, | 3 | TextSize, T, |
4 | }; | 4 | }; |
5 | 5 | ||
6 | use crate::{Assist, AssistCtx, AssistId}; | 6 | use crate::{AssistContext, AssistId, Assists}; |
7 | 7 | ||
8 | // Assist: remove_dbg | 8 | // Assist: remove_dbg |
9 | // | 9 | // |
@@ -20,7 +20,7 @@ use crate::{Assist, AssistCtx, AssistId}; | |||
20 | // 92; | 20 | // 92; |
21 | // } | 21 | // } |
22 | // ``` | 22 | // ``` |
23 | pub(crate) fn remove_dbg(ctx: AssistCtx) -> Option<Assist> { | 23 | pub(crate) fn remove_dbg(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { |
24 | let macro_call = ctx.find_node_at_offset::<ast::MacroCall>()?; | 24 | let macro_call = ctx.find_node_at_offset::<ast::MacroCall>()?; |
25 | 25 | ||
26 | if !is_valid_macrocall(¯o_call, "dbg")? { | 26 | if !is_valid_macrocall(¯o_call, "dbg")? { |
@@ -58,7 +58,7 @@ pub(crate) fn remove_dbg(ctx: AssistCtx) -> Option<Assist> { | |||
58 | }; | 58 | }; |
59 | 59 | ||
60 | let target = macro_call.syntax().text_range(); | 60 | let target = macro_call.syntax().text_range(); |
61 | ctx.add_assist(AssistId("remove_dbg"), "Remove dbg!()", target, |edit| { | 61 | acc.add(AssistId("remove_dbg"), "Remove dbg!()", target, |edit| { |
62 | edit.replace(macro_range, macro_content); | 62 | edit.replace(macro_range, macro_content); |
63 | edit.set_cursor(cursor_pos); | 63 | edit.set_cursor(cursor_pos); |
64 | }) | 64 | }) |
diff --git a/crates/ra_assists/src/handlers/remove_mut.rs b/crates/ra_assists/src/handlers/remove_mut.rs index 9f72f879d..dce546db7 100644 --- a/crates/ra_assists/src/handlers/remove_mut.rs +++ b/crates/ra_assists/src/handlers/remove_mut.rs | |||
@@ -1,6 +1,6 @@ | |||
1 | use ra_syntax::{SyntaxKind, TextRange, T}; | 1 | use ra_syntax::{SyntaxKind, TextRange, T}; |
2 | 2 | ||
3 | use crate::{Assist, AssistCtx, AssistId}; | 3 | use crate::{AssistContext, AssistId, Assists}; |
4 | 4 | ||
5 | // Assist: remove_mut | 5 | // Assist: remove_mut |
6 | // | 6 | // |
@@ -17,7 +17,7 @@ use crate::{Assist, AssistCtx, AssistId}; | |||
17 | // fn feed(&self, amount: u32) {} | 17 | // fn feed(&self, amount: u32) {} |
18 | // } | 18 | // } |
19 | // ``` | 19 | // ``` |
20 | pub(crate) fn remove_mut(ctx: AssistCtx) -> Option<Assist> { | 20 | pub(crate) fn remove_mut(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { |
21 | let mut_token = ctx.find_token_at_offset(T![mut])?; | 21 | let mut_token = ctx.find_token_at_offset(T![mut])?; |
22 | let delete_from = mut_token.text_range().start(); | 22 | let delete_from = mut_token.text_range().start(); |
23 | let delete_to = match mut_token.next_token() { | 23 | let delete_to = match mut_token.next_token() { |
@@ -26,7 +26,7 @@ pub(crate) fn remove_mut(ctx: AssistCtx) -> Option<Assist> { | |||
26 | }; | 26 | }; |
27 | 27 | ||
28 | let target = mut_token.text_range(); | 28 | let target = mut_token.text_range(); |
29 | ctx.add_assist(AssistId("remove_mut"), "Remove `mut` keyword", target, |edit| { | 29 | acc.add(AssistId("remove_mut"), "Remove `mut` keyword", target, |edit| { |
30 | edit.set_cursor(delete_from); | 30 | edit.set_cursor(delete_from); |
31 | edit.delete(TextRange::new(delete_from, delete_to)); | 31 | edit.delete(TextRange::new(delete_from, delete_to)); |
32 | }) | 32 | }) |
diff --git a/crates/ra_assists/src/handlers/reorder_fields.rs b/crates/ra_assists/src/handlers/reorder_fields.rs index 0b930dea2..757f6406e 100644 --- a/crates/ra_assists/src/handlers/reorder_fields.rs +++ b/crates/ra_assists/src/handlers/reorder_fields.rs | |||
@@ -3,18 +3,9 @@ use std::collections::HashMap; | |||
3 | use hir::{Adt, ModuleDef, PathResolution, Semantics, Struct}; | 3 | use hir::{Adt, ModuleDef, PathResolution, Semantics, Struct}; |
4 | use itertools::Itertools; | 4 | use itertools::Itertools; |
5 | use ra_ide_db::RootDatabase; | 5 | use ra_ide_db::RootDatabase; |
6 | use ra_syntax::{ | 6 | use ra_syntax::{algo, ast, match_ast, AstNode, SyntaxKind, SyntaxKind::*, SyntaxNode}; |
7 | algo, | 7 | |
8 | ast::{self, Path, RecordLit, RecordPat}, | 8 | use crate::{AssistContext, AssistId, Assists}; |
9 | match_ast, AstNode, SyntaxKind, | ||
10 | SyntaxKind::*, | ||
11 | SyntaxNode, | ||
12 | }; | ||
13 | |||
14 | use crate::{ | ||
15 | assist_ctx::{Assist, AssistCtx}, | ||
16 | AssistId, | ||
17 | }; | ||
18 | 9 | ||
19 | // Assist: reorder_fields | 10 | // Assist: reorder_fields |
20 | // | 11 | // |
@@ -31,13 +22,13 @@ use crate::{ | |||
31 | // const test: Foo = Foo {foo: 1, bar: 0} | 22 | // const test: Foo = Foo {foo: 1, bar: 0} |
32 | // ``` | 23 | // ``` |
33 | // | 24 | // |
34 | pub(crate) fn reorder_fields(ctx: AssistCtx) -> Option<Assist> { | 25 | pub(crate) fn reorder_fields(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { |
35 | reorder::<RecordLit>(ctx.clone()).or_else(|| reorder::<RecordPat>(ctx)) | 26 | reorder::<ast::RecordLit>(acc, ctx.clone()).or_else(|| reorder::<ast::RecordPat>(acc, ctx)) |
36 | } | 27 | } |
37 | 28 | ||
38 | fn reorder<R: AstNode>(ctx: AssistCtx) -> Option<Assist> { | 29 | fn reorder<R: AstNode>(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { |
39 | let record = ctx.find_node_at_offset::<R>()?; | 30 | let record = ctx.find_node_at_offset::<R>()?; |
40 | let path = record.syntax().children().find_map(Path::cast)?; | 31 | let path = record.syntax().children().find_map(ast::Path::cast)?; |
41 | 32 | ||
42 | let ranks = compute_fields_ranks(&path, &ctx)?; | 33 | let ranks = compute_fields_ranks(&path, &ctx)?; |
43 | 34 | ||
@@ -51,7 +42,7 @@ fn reorder<R: AstNode>(ctx: AssistCtx) -> Option<Assist> { | |||
51 | } | 42 | } |
52 | 43 | ||
53 | let target = record.syntax().text_range(); | 44 | let target = record.syntax().text_range(); |
54 | ctx.add_assist(AssistId("reorder_fields"), "Reorder record fields", target, |edit| { | 45 | acc.add(AssistId("reorder_fields"), "Reorder record fields", target, |edit| { |
55 | for (old, new) in fields.iter().zip(&sorted_fields) { | 46 | for (old, new) in fields.iter().zip(&sorted_fields) { |
56 | algo::diff(old, new).into_text_edit(edit.text_edit_builder()); | 47 | algo::diff(old, new).into_text_edit(edit.text_edit_builder()); |
57 | } | 48 | } |
@@ -96,9 +87,9 @@ fn struct_definition(path: &ast::Path, sema: &Semantics<RootDatabase>) -> Option | |||
96 | } | 87 | } |
97 | } | 88 | } |
98 | 89 | ||
99 | fn compute_fields_ranks(path: &Path, ctx: &AssistCtx) -> Option<HashMap<String, usize>> { | 90 | fn compute_fields_ranks(path: &ast::Path, ctx: &AssistContext) -> Option<HashMap<String, usize>> { |
100 | Some( | 91 | Some( |
101 | struct_definition(path, ctx.sema)? | 92 | struct_definition(path, &ctx.sema)? |
102 | .fields(ctx.db) | 93 | .fields(ctx.db) |
103 | .iter() | 94 | .iter() |
104 | .enumerate() | 95 | .enumerate() |
diff --git a/crates/ra_assists/src/handlers/replace_if_let_with_match.rs b/crates/ra_assists/src/handlers/replace_if_let_with_match.rs index 2eb8348f8..65f5fc6ab 100644 --- a/crates/ra_assists/src/handlers/replace_if_let_with_match.rs +++ b/crates/ra_assists/src/handlers/replace_if_let_with_match.rs | |||
@@ -1,10 +1,14 @@ | |||
1 | use ra_fmt::unwrap_trivial_block; | 1 | use ra_fmt::unwrap_trivial_block; |
2 | use ra_syntax::{ | 2 | use ra_syntax::{ |
3 | ast::{self, edit::IndentLevel, make}, | 3 | ast::{ |
4 | self, | ||
5 | edit::{AstNodeEdit, IndentLevel}, | ||
6 | make, | ||
7 | }, | ||
4 | AstNode, | 8 | AstNode, |
5 | }; | 9 | }; |
6 | 10 | ||
7 | use crate::{utils::TryEnum, Assist, AssistCtx, AssistId}; | 11 | use crate::{utils::TryEnum, AssistContext, AssistId, Assists}; |
8 | 12 | ||
9 | // Assist: replace_if_let_with_match | 13 | // Assist: replace_if_let_with_match |
10 | // | 14 | // |
@@ -32,7 +36,7 @@ use crate::{utils::TryEnum, Assist, AssistCtx, AssistId}; | |||
32 | // } | 36 | // } |
33 | // } | 37 | // } |
34 | // ``` | 38 | // ``` |
35 | pub(crate) fn replace_if_let_with_match(ctx: AssistCtx) -> Option<Assist> { | 39 | pub(crate) fn replace_if_let_with_match(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { |
36 | let if_expr: ast::IfExpr = ctx.find_node_at_offset()?; | 40 | let if_expr: ast::IfExpr = ctx.find_node_at_offset()?; |
37 | let cond = if_expr.condition()?; | 41 | let cond = if_expr.condition()?; |
38 | let pat = cond.pat()?; | 42 | let pat = cond.pat()?; |
@@ -43,36 +47,30 @@ pub(crate) fn replace_if_let_with_match(ctx: AssistCtx) -> Option<Assist> { | |||
43 | ast::ElseBranch::IfExpr(_) => return None, | 47 | ast::ElseBranch::IfExpr(_) => return None, |
44 | }; | 48 | }; |
45 | 49 | ||
46 | let sema = ctx.sema; | ||
47 | let target = if_expr.syntax().text_range(); | 50 | let target = if_expr.syntax().text_range(); |
48 | ctx.add_assist( | 51 | acc.add(AssistId("replace_if_let_with_match"), "Replace with match", target, move |edit| { |
49 | AssistId("replace_if_let_with_match"), | 52 | let match_expr = { |
50 | "Replace with match", | 53 | let then_arm = { |
51 | target, | 54 | let then_expr = unwrap_trivial_block(then_block); |
52 | move |edit| { | 55 | make::match_arm(vec![pat.clone()], then_expr) |
53 | let match_expr = { | ||
54 | let then_arm = { | ||
55 | let then_expr = unwrap_trivial_block(then_block); | ||
56 | make::match_arm(vec![pat.clone()], then_expr) | ||
57 | }; | ||
58 | let else_arm = { | ||
59 | let pattern = sema | ||
60 | .type_of_pat(&pat) | ||
61 | .and_then(|ty| TryEnum::from_ty(sema, &ty)) | ||
62 | .map(|it| it.sad_pattern()) | ||
63 | .unwrap_or_else(|| make::placeholder_pat().into()); | ||
64 | let else_expr = unwrap_trivial_block(else_block); | ||
65 | make::match_arm(vec![pattern], else_expr) | ||
66 | }; | ||
67 | make::expr_match(expr, make::match_arm_list(vec![then_arm, else_arm])) | ||
68 | }; | 56 | }; |
57 | let else_arm = { | ||
58 | let pattern = ctx | ||
59 | .sema | ||
60 | .type_of_pat(&pat) | ||
61 | .and_then(|ty| TryEnum::from_ty(&ctx.sema, &ty)) | ||
62 | .map(|it| it.sad_pattern()) | ||
63 | .unwrap_or_else(|| make::placeholder_pat().into()); | ||
64 | let else_expr = unwrap_trivial_block(else_block); | ||
65 | make::match_arm(vec![pattern], else_expr) | ||
66 | }; | ||
67 | make::expr_match(expr, make::match_arm_list(vec![then_arm, else_arm])) | ||
68 | .indent(IndentLevel::from_node(if_expr.syntax())) | ||
69 | }; | ||
69 | 70 | ||
70 | let match_expr = IndentLevel::from_node(if_expr.syntax()).increase_indent(match_expr); | 71 | edit.set_cursor(if_expr.syntax().text_range().start()); |
71 | 72 | edit.replace_ast::<ast::Expr>(if_expr.into(), match_expr); | |
72 | edit.set_cursor(if_expr.syntax().text_range().start()); | 73 | }) |
73 | edit.replace_ast::<ast::Expr>(if_expr.into(), match_expr); | ||
74 | }, | ||
75 | ) | ||
76 | } | 74 | } |
77 | 75 | ||
78 | #[cfg(test)] | 76 | #[cfg(test)] |
diff --git a/crates/ra_assists/src/handlers/replace_let_with_if_let.rs b/crates/ra_assists/src/handlers/replace_let_with_if_let.rs index a5509a567..482957dc6 100644 --- a/crates/ra_assists/src/handlers/replace_let_with_if_let.rs +++ b/crates/ra_assists/src/handlers/replace_let_with_if_let.rs | |||
@@ -9,11 +9,7 @@ use ra_syntax::{ | |||
9 | AstNode, T, | 9 | AstNode, T, |
10 | }; | 10 | }; |
11 | 11 | ||
12 | use crate::{ | 12 | use crate::{utils::TryEnum, AssistContext, AssistId, Assists}; |
13 | assist_ctx::{Assist, AssistCtx}, | ||
14 | utils::TryEnum, | ||
15 | AssistId, | ||
16 | }; | ||
17 | 13 | ||
18 | // Assist: replace_let_with_if_let | 14 | // Assist: replace_let_with_if_let |
19 | // | 15 | // |
@@ -39,16 +35,16 @@ use crate::{ | |||
39 | // | 35 | // |
40 | // fn compute() -> Option<i32> { None } | 36 | // fn compute() -> Option<i32> { None } |
41 | // ``` | 37 | // ``` |
42 | pub(crate) fn replace_let_with_if_let(ctx: AssistCtx) -> Option<Assist> { | 38 | pub(crate) fn replace_let_with_if_let(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { |
43 | let let_kw = ctx.find_token_at_offset(T![let])?; | 39 | let let_kw = ctx.find_token_at_offset(T![let])?; |
44 | let let_stmt = let_kw.ancestors().find_map(ast::LetStmt::cast)?; | 40 | let let_stmt = let_kw.ancestors().find_map(ast::LetStmt::cast)?; |
45 | let init = let_stmt.initializer()?; | 41 | let init = let_stmt.initializer()?; |
46 | let original_pat = let_stmt.pat()?; | 42 | let original_pat = let_stmt.pat()?; |
47 | let ty = ctx.sema.type_of_expr(&init)?; | 43 | let ty = ctx.sema.type_of_expr(&init)?; |
48 | let happy_variant = TryEnum::from_ty(ctx.sema, &ty).map(|it| it.happy_case()); | 44 | let happy_variant = TryEnum::from_ty(&ctx.sema, &ty).map(|it| it.happy_case()); |
49 | 45 | ||
50 | let target = let_kw.text_range(); | 46 | let target = let_kw.text_range(); |
51 | ctx.add_assist(AssistId("replace_let_with_if_let"), "Replace with if-let", target, |edit| { | 47 | acc.add(AssistId("replace_let_with_if_let"), "Replace with if-let", target, |edit| { |
52 | let with_placeholder: ast::Pat = match happy_variant { | 48 | let with_placeholder: ast::Pat = match happy_variant { |
53 | None => make::placeholder_pat().into(), | 49 | None => make::placeholder_pat().into(), |
54 | Some(var_name) => make::tuple_struct_pat( | 50 | Some(var_name) => make::tuple_struct_pat( |
@@ -57,8 +53,7 @@ pub(crate) fn replace_let_with_if_let(ctx: AssistCtx) -> Option<Assist> { | |||
57 | ) | 53 | ) |
58 | .into(), | 54 | .into(), |
59 | }; | 55 | }; |
60 | let block = | 56 | let block = make::block_expr(None, None).indent(IndentLevel::from_node(let_stmt.syntax())); |
61 | IndentLevel::from_node(let_stmt.syntax()).increase_indent(make::block_expr(None, None)); | ||
62 | let if_ = make::expr_if(make::condition(init, Some(with_placeholder)), block); | 57 | let if_ = make::expr_if(make::condition(init, Some(with_placeholder)), block); |
63 | let stmt = make::expr_stmt(if_); | 58 | let stmt = make::expr_stmt(if_); |
64 | 59 | ||
diff --git a/crates/ra_assists/src/handlers/replace_qualified_name_with_use.rs b/crates/ra_assists/src/handlers/replace_qualified_name_with_use.rs index fd41da64b..1a81d8a0e 100644 --- a/crates/ra_assists/src/handlers/replace_qualified_name_with_use.rs +++ b/crates/ra_assists/src/handlers/replace_qualified_name_with_use.rs | |||
@@ -1,11 +1,7 @@ | |||
1 | use hir; | 1 | use hir; |
2 | use ra_syntax::{ast, AstNode, SmolStr, TextRange}; | 2 | use ra_syntax::{ast, AstNode, SmolStr, TextRange}; |
3 | 3 | ||
4 | use crate::{ | 4 | use crate::{utils::insert_use_statement, AssistContext, AssistId, Assists}; |
5 | assist_ctx::{Assist, AssistCtx}, | ||
6 | utils::insert_use_statement, | ||
7 | AssistId, | ||
8 | }; | ||
9 | 5 | ||
10 | // Assist: replace_qualified_name_with_use | 6 | // Assist: replace_qualified_name_with_use |
11 | // | 7 | // |
@@ -20,7 +16,10 @@ use crate::{ | |||
20 | // | 16 | // |
21 | // fn process(map: HashMap<String, String>) {} | 17 | // fn process(map: HashMap<String, String>) {} |
22 | // ``` | 18 | // ``` |
23 | pub(crate) fn replace_qualified_name_with_use(ctx: AssistCtx) -> Option<Assist> { | 19 | pub(crate) fn replace_qualified_name_with_use( |
20 | acc: &mut Assists, | ||
21 | ctx: &AssistContext, | ||
22 | ) -> Option<()> { | ||
24 | let path: ast::Path = ctx.find_node_at_offset()?; | 23 | let path: ast::Path = ctx.find_node_at_offset()?; |
25 | // We don't want to mess with use statements | 24 | // We don't want to mess with use statements |
26 | if path.syntax().ancestors().find_map(ast::UseItem::cast).is_some() { | 25 | if path.syntax().ancestors().find_map(ast::UseItem::cast).is_some() { |
@@ -34,18 +33,18 @@ pub(crate) fn replace_qualified_name_with_use(ctx: AssistCtx) -> Option<Assist> | |||
34 | } | 33 | } |
35 | 34 | ||
36 | let target = path.syntax().text_range(); | 35 | let target = path.syntax().text_range(); |
37 | ctx.add_assist( | 36 | acc.add( |
38 | AssistId("replace_qualified_name_with_use"), | 37 | AssistId("replace_qualified_name_with_use"), |
39 | "Replace qualified path with use", | 38 | "Replace qualified path with use", |
40 | target, | 39 | target, |
41 | |edit| { | 40 | |builder| { |
42 | let path_to_import = hir_path.mod_path().clone(); | 41 | let path_to_import = hir_path.mod_path().clone(); |
43 | insert_use_statement(path.syntax(), &path_to_import, edit); | 42 | insert_use_statement(path.syntax(), &path_to_import, ctx, builder); |
44 | 43 | ||
45 | if let Some(last) = path.segment() { | 44 | if let Some(last) = path.segment() { |
46 | // Here we are assuming the assist will provide a correct use statement | 45 | // Here we are assuming the assist will provide a correct use statement |
47 | // so we can delete the path qualifier | 46 | // so we can delete the path qualifier |
48 | edit.delete(TextRange::new( | 47 | builder.delete(TextRange::new( |
49 | path.syntax().text_range().start(), | 48 | path.syntax().text_range().start(), |
50 | last.syntax().text_range().start(), | 49 | last.syntax().text_range().start(), |
51 | )); | 50 | )); |
diff --git a/crates/ra_assists/src/handlers/replace_unwrap_with_match.rs b/crates/ra_assists/src/handlers/replace_unwrap_with_match.rs index c6b73da67..c4b56f6e9 100644 --- a/crates/ra_assists/src/handlers/replace_unwrap_with_match.rs +++ b/crates/ra_assists/src/handlers/replace_unwrap_with_match.rs | |||
@@ -1,11 +1,15 @@ | |||
1 | use std::iter; | 1 | use std::iter; |
2 | 2 | ||
3 | use ra_syntax::{ | 3 | use ra_syntax::{ |
4 | ast::{self, edit::IndentLevel, make}, | 4 | ast::{ |
5 | self, | ||
6 | edit::{AstNodeEdit, IndentLevel}, | ||
7 | make, | ||
8 | }, | ||
5 | AstNode, | 9 | AstNode, |
6 | }; | 10 | }; |
7 | 11 | ||
8 | use crate::{utils::TryEnum, Assist, AssistCtx, AssistId}; | 12 | use crate::{utils::TryEnum, AssistContext, AssistId, Assists}; |
9 | 13 | ||
10 | // Assist: replace_unwrap_with_match | 14 | // Assist: replace_unwrap_with_match |
11 | // | 15 | // |
@@ -29,7 +33,7 @@ use crate::{utils::TryEnum, Assist, AssistCtx, AssistId}; | |||
29 | // }; | 33 | // }; |
30 | // } | 34 | // } |
31 | // ``` | 35 | // ``` |
32 | pub(crate) fn replace_unwrap_with_match(ctx: AssistCtx) -> Option<Assist> { | 36 | pub(crate) fn replace_unwrap_with_match(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { |
33 | let method_call: ast::MethodCallExpr = ctx.find_node_at_offset()?; | 37 | let method_call: ast::MethodCallExpr = ctx.find_node_at_offset()?; |
34 | let name = method_call.name_ref()?; | 38 | let name = method_call.name_ref()?; |
35 | if name.text() != "unwrap" { | 39 | if name.text() != "unwrap" { |
@@ -37,33 +41,26 @@ pub(crate) fn replace_unwrap_with_match(ctx: AssistCtx) -> Option<Assist> { | |||
37 | } | 41 | } |
38 | let caller = method_call.expr()?; | 42 | let caller = method_call.expr()?; |
39 | let ty = ctx.sema.type_of_expr(&caller)?; | 43 | let ty = ctx.sema.type_of_expr(&caller)?; |
40 | let happy_variant = TryEnum::from_ty(ctx.sema, &ty)?.happy_case(); | 44 | let happy_variant = TryEnum::from_ty(&ctx.sema, &ty)?.happy_case(); |
41 | let target = method_call.syntax().text_range(); | 45 | let target = method_call.syntax().text_range(); |
42 | ctx.add_assist( | 46 | acc.add(AssistId("replace_unwrap_with_match"), "Replace unwrap with match", target, |edit| { |
43 | AssistId("replace_unwrap_with_match"), | 47 | let ok_path = make::path_unqualified(make::path_segment(make::name_ref(happy_variant))); |
44 | "Replace unwrap with match", | 48 | let it = make::bind_pat(make::name("a")).into(); |
45 | target, | 49 | let ok_tuple = make::tuple_struct_pat(ok_path, iter::once(it)).into(); |
46 | |edit| { | ||
47 | let ok_path = make::path_unqualified(make::path_segment(make::name_ref(happy_variant))); | ||
48 | let it = make::bind_pat(make::name("a")).into(); | ||
49 | let ok_tuple = make::tuple_struct_pat(ok_path, iter::once(it)).into(); | ||
50 | 50 | ||
51 | let bind_path = make::path_unqualified(make::path_segment(make::name_ref("a"))); | 51 | let bind_path = make::path_unqualified(make::path_segment(make::name_ref("a"))); |
52 | let ok_arm = make::match_arm(iter::once(ok_tuple), make::expr_path(bind_path)); | 52 | let ok_arm = make::match_arm(iter::once(ok_tuple), make::expr_path(bind_path)); |
53 | 53 | ||
54 | let unreachable_call = make::unreachable_macro_call().into(); | 54 | let unreachable_call = make::unreachable_macro_call().into(); |
55 | let err_arm = | 55 | let err_arm = make::match_arm(iter::once(make::placeholder_pat().into()), unreachable_call); |
56 | make::match_arm(iter::once(make::placeholder_pat().into()), unreachable_call); | ||
57 | 56 | ||
58 | let match_arm_list = make::match_arm_list(vec![ok_arm, err_arm]); | 57 | let match_arm_list = make::match_arm_list(vec![ok_arm, err_arm]); |
59 | let match_expr = make::expr_match(caller.clone(), match_arm_list); | 58 | let match_expr = make::expr_match(caller.clone(), match_arm_list) |
60 | let match_expr = | 59 | .indent(IndentLevel::from_node(method_call.syntax())); |
61 | IndentLevel::from_node(method_call.syntax()).increase_indent(match_expr); | ||
62 | 60 | ||
63 | edit.set_cursor(caller.syntax().text_range().start()); | 61 | edit.set_cursor(caller.syntax().text_range().start()); |
64 | edit.replace_ast::<ast::Expr>(method_call.into(), match_expr); | 62 | edit.replace_ast::<ast::Expr>(method_call.into(), match_expr); |
65 | }, | 63 | }) |
66 | ) | ||
67 | } | 64 | } |
68 | 65 | ||
69 | #[cfg(test)] | 66 | #[cfg(test)] |
diff --git a/crates/ra_assists/src/handlers/split_import.rs b/crates/ra_assists/src/handlers/split_import.rs index d49563974..b2757e50c 100644 --- a/crates/ra_assists/src/handlers/split_import.rs +++ b/crates/ra_assists/src/handlers/split_import.rs | |||
@@ -2,7 +2,7 @@ use std::iter::successors; | |||
2 | 2 | ||
3 | use ra_syntax::{ast, AstNode, T}; | 3 | use ra_syntax::{ast, AstNode, T}; |
4 | 4 | ||
5 | use crate::{Assist, AssistCtx, AssistId}; | 5 | use crate::{AssistContext, AssistId, Assists}; |
6 | 6 | ||
7 | // Assist: split_import | 7 | // Assist: split_import |
8 | // | 8 | // |
@@ -15,7 +15,7 @@ use crate::{Assist, AssistCtx, AssistId}; | |||
15 | // ``` | 15 | // ``` |
16 | // use std::{collections::HashMap}; | 16 | // use std::{collections::HashMap}; |
17 | // ``` | 17 | // ``` |
18 | pub(crate) fn split_import(ctx: AssistCtx) -> Option<Assist> { | 18 | pub(crate) fn split_import(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { |
19 | let colon_colon = ctx.find_token_at_offset(T![::])?; | 19 | let colon_colon = ctx.find_token_at_offset(T![::])?; |
20 | let path = ast::Path::cast(colon_colon.parent())?.qualifier()?; | 20 | let path = ast::Path::cast(colon_colon.parent())?.qualifier()?; |
21 | let top_path = successors(Some(path.clone()), |it| it.parent_path()).last()?; | 21 | let top_path = successors(Some(path.clone()), |it| it.parent_path()).last()?; |
@@ -26,10 +26,10 @@ pub(crate) fn split_import(ctx: AssistCtx) -> Option<Assist> { | |||
26 | if new_tree == use_tree { | 26 | if new_tree == use_tree { |
27 | return None; | 27 | return None; |
28 | } | 28 | } |
29 | let cursor = ctx.frange.range.start(); | 29 | let cursor = ctx.offset(); |
30 | 30 | ||
31 | let target = colon_colon.text_range(); | 31 | let target = colon_colon.text_range(); |
32 | ctx.add_assist(AssistId("split_import"), "Split import", target, |edit| { | 32 | acc.add(AssistId("split_import"), "Split import", target, |edit| { |
33 | edit.replace_ast(use_tree, new_tree); | 33 | edit.replace_ast(use_tree, new_tree); |
34 | edit.set_cursor(cursor); | 34 | edit.set_cursor(cursor); |
35 | }) | 35 | }) |
diff --git a/crates/ra_assists/src/handlers/unwrap_block.rs b/crates/ra_assists/src/handlers/unwrap_block.rs index 6df927abb..eba0631a4 100644 --- a/crates/ra_assists/src/handlers/unwrap_block.rs +++ b/crates/ra_assists/src/handlers/unwrap_block.rs | |||
@@ -1,4 +1,4 @@ | |||
1 | use crate::{Assist, AssistCtx, AssistId}; | 1 | use crate::{AssistContext, AssistId, Assists}; |
2 | 2 | ||
3 | use ast::LoopBodyOwner; | 3 | use ast::LoopBodyOwner; |
4 | use ra_fmt::unwrap_trivial_block; | 4 | use ra_fmt::unwrap_trivial_block; |
@@ -21,7 +21,7 @@ use ra_syntax::{ast, match_ast, AstNode, TextRange, T}; | |||
21 | // println!("foo"); | 21 | // println!("foo"); |
22 | // } | 22 | // } |
23 | // ``` | 23 | // ``` |
24 | pub(crate) fn unwrap_block(ctx: AssistCtx) -> Option<Assist> { | 24 | pub(crate) fn unwrap_block(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { |
25 | let l_curly_token = ctx.find_token_at_offset(T!['{'])?; | 25 | let l_curly_token = ctx.find_token_at_offset(T!['{'])?; |
26 | let block = ast::BlockExpr::cast(l_curly_token.parent())?; | 26 | let block = ast::BlockExpr::cast(l_curly_token.parent())?; |
27 | let parent = block.syntax().parent()?; | 27 | let parent = block.syntax().parent()?; |
@@ -58,7 +58,7 @@ pub(crate) fn unwrap_block(ctx: AssistCtx) -> Option<Assist> { | |||
58 | }; | 58 | }; |
59 | 59 | ||
60 | let target = expr_to_unwrap.syntax().text_range(); | 60 | let target = expr_to_unwrap.syntax().text_range(); |
61 | ctx.add_assist(AssistId("unwrap_block"), "Unwrap block", target, |edit| { | 61 | acc.add(AssistId("unwrap_block"), "Unwrap block", target, |edit| { |
62 | edit.set_cursor(expr.syntax().text_range().start()); | 62 | edit.set_cursor(expr.syntax().text_range().start()); |
63 | 63 | ||
64 | let pat_start: &[_] = &[' ', '{', '\n']; | 64 | let pat_start: &[_] = &[' ', '{', '\n']; |
diff --git a/crates/ra_assists/src/lib.rs b/crates/ra_assists/src/lib.rs index 0473fd8c2..b6dc7cb1b 100644 --- a/crates/ra_assists/src/lib.rs +++ b/crates/ra_assists/src/lib.rs | |||
@@ -10,7 +10,7 @@ macro_rules! eprintln { | |||
10 | ($($tt:tt)*) => { stdx::eprintln!($($tt)*) }; | 10 | ($($tt:tt)*) => { stdx::eprintln!($($tt)*) }; |
11 | } | 11 | } |
12 | 12 | ||
13 | mod assist_ctx; | 13 | mod assist_context; |
14 | mod marks; | 14 | mod marks; |
15 | #[cfg(test)] | 15 | #[cfg(test)] |
16 | mod tests; | 16 | mod tests; |
@@ -22,15 +22,18 @@ use ra_db::FileRange; | |||
22 | use ra_ide_db::{source_change::SourceChange, RootDatabase}; | 22 | use ra_ide_db::{source_change::SourceChange, RootDatabase}; |
23 | use ra_syntax::TextRange; | 23 | use ra_syntax::TextRange; |
24 | 24 | ||
25 | pub(crate) use crate::assist_ctx::{Assist, AssistCtx}; | 25 | pub(crate) use crate::assist_context::{AssistContext, Assists}; |
26 | 26 | ||
27 | /// Unique identifier of the assist, should not be shown to the user | 27 | /// Unique identifier of the assist, should not be shown to the user |
28 | /// directly. | 28 | /// directly. |
29 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] | 29 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] |
30 | pub struct AssistId(pub &'static str); | 30 | pub struct AssistId(pub &'static str); |
31 | 31 | ||
32 | #[derive(Clone, Debug)] | ||
33 | pub struct GroupLabel(pub String); | ||
34 | |||
32 | #[derive(Debug, Clone)] | 35 | #[derive(Debug, Clone)] |
33 | pub struct AssistLabel { | 36 | pub struct Assist { |
34 | pub id: AssistId, | 37 | pub id: AssistId, |
35 | /// Short description of the assist, as shown in the UI. | 38 | /// Short description of the assist, as shown in the UI. |
36 | pub label: String, | 39 | pub label: String, |
@@ -40,74 +43,69 @@ pub struct AssistLabel { | |||
40 | pub target: TextRange, | 43 | pub target: TextRange, |
41 | } | 44 | } |
42 | 45 | ||
43 | #[derive(Clone, Debug)] | 46 | #[derive(Debug, Clone)] |
44 | pub struct GroupLabel(pub String); | 47 | pub struct ResolvedAssist { |
48 | pub assist: Assist, | ||
49 | pub source_change: SourceChange, | ||
50 | } | ||
51 | |||
52 | impl Assist { | ||
53 | /// Return all the assists applicable at the given position. | ||
54 | /// | ||
55 | /// Assists are returned in the "unresolved" state, that is only labels are | ||
56 | /// returned, without actual edits. | ||
57 | pub fn unresolved(db: &RootDatabase, range: FileRange) -> Vec<Assist> { | ||
58 | let sema = Semantics::new(db); | ||
59 | let ctx = AssistContext::new(sema, range); | ||
60 | let mut acc = Assists::new_unresolved(&ctx); | ||
61 | handlers::all().iter().for_each(|handler| { | ||
62 | handler(&mut acc, &ctx); | ||
63 | }); | ||
64 | acc.finish_unresolved() | ||
65 | } | ||
66 | |||
67 | /// Return all the assists applicable at the given position. | ||
68 | /// | ||
69 | /// Assists are returned in the "resolved" state, that is with edit fully | ||
70 | /// computed. | ||
71 | pub fn resolved(db: &RootDatabase, range: FileRange) -> Vec<ResolvedAssist> { | ||
72 | let sema = Semantics::new(db); | ||
73 | let ctx = AssistContext::new(sema, range); | ||
74 | let mut acc = Assists::new_resolved(&ctx); | ||
75 | handlers::all().iter().for_each(|handler| { | ||
76 | handler(&mut acc, &ctx); | ||
77 | }); | ||
78 | acc.finish_resolved() | ||
79 | } | ||
45 | 80 | ||
46 | impl AssistLabel { | ||
47 | pub(crate) fn new( | 81 | pub(crate) fn new( |
48 | id: AssistId, | 82 | id: AssistId, |
49 | label: String, | 83 | label: String, |
50 | group: Option<GroupLabel>, | 84 | group: Option<GroupLabel>, |
51 | target: TextRange, | 85 | target: TextRange, |
52 | ) -> AssistLabel { | 86 | ) -> Assist { |
53 | // FIXME: make fields private, so that this invariant can't be broken | 87 | // FIXME: make fields private, so that this invariant can't be broken |
54 | assert!(label.starts_with(|c: char| c.is_uppercase())); | 88 | assert!(label.starts_with(|c: char| c.is_uppercase())); |
55 | AssistLabel { id, label, group, target } | 89 | Assist { id, label, group, target } |
56 | } | 90 | } |
57 | } | 91 | } |
58 | 92 | ||
59 | #[derive(Debug, Clone)] | ||
60 | pub struct ResolvedAssist { | ||
61 | pub label: AssistLabel, | ||
62 | pub source_change: SourceChange, | ||
63 | } | ||
64 | |||
65 | /// Return all the assists applicable at the given position. | ||
66 | /// | ||
67 | /// Assists are returned in the "unresolved" state, that is only labels are | ||
68 | /// returned, without actual edits. | ||
69 | pub fn unresolved_assists(db: &RootDatabase, range: FileRange) -> Vec<AssistLabel> { | ||
70 | let sema = Semantics::new(db); | ||
71 | let ctx = AssistCtx::new(&sema, range, false); | ||
72 | handlers::all() | ||
73 | .iter() | ||
74 | .filter_map(|f| f(ctx.clone())) | ||
75 | .flat_map(|it| it.0) | ||
76 | .map(|a| a.label) | ||
77 | .collect() | ||
78 | } | ||
79 | |||
80 | /// Return all the assists applicable at the given position. | ||
81 | /// | ||
82 | /// Assists are returned in the "resolved" state, that is with edit fully | ||
83 | /// computed. | ||
84 | pub fn resolved_assists(db: &RootDatabase, range: FileRange) -> Vec<ResolvedAssist> { | ||
85 | let sema = Semantics::new(db); | ||
86 | let ctx = AssistCtx::new(&sema, range, true); | ||
87 | let mut a = handlers::all() | ||
88 | .iter() | ||
89 | .filter_map(|f| f(ctx.clone())) | ||
90 | .flat_map(|it| it.0) | ||
91 | .map(|it| it.into_resolved().unwrap()) | ||
92 | .collect::<Vec<_>>(); | ||
93 | a.sort_by_key(|it| it.label.target.len()); | ||
94 | a | ||
95 | } | ||
96 | |||
97 | mod handlers { | 93 | mod handlers { |
98 | use crate::{Assist, AssistCtx}; | 94 | use crate::{AssistContext, Assists}; |
99 | 95 | ||
100 | pub(crate) type Handler = fn(AssistCtx) -> Option<Assist>; | 96 | pub(crate) type Handler = fn(&mut Assists, &AssistContext) -> Option<()>; |
101 | 97 | ||
102 | mod add_custom_impl; | 98 | mod add_custom_impl; |
103 | mod add_derive; | 99 | mod add_derive; |
104 | mod add_explicit_type; | 100 | mod add_explicit_type; |
101 | mod add_from_impl_for_enum; | ||
105 | mod add_function; | 102 | mod add_function; |
106 | mod add_impl; | 103 | mod add_impl; |
107 | mod add_missing_impl_members; | 104 | mod add_missing_impl_members; |
108 | mod add_new; | 105 | mod add_new; |
109 | mod apply_demorgan; | 106 | mod apply_demorgan; |
110 | mod auto_import; | 107 | mod auto_import; |
108 | mod change_return_type_to_result; | ||
111 | mod change_visibility; | 109 | mod change_visibility; |
112 | mod early_return; | 110 | mod early_return; |
113 | mod fill_match_arms; | 111 | mod fill_match_arms; |
@@ -124,14 +122,12 @@ mod handlers { | |||
124 | mod raw_string; | 122 | mod raw_string; |
125 | mod remove_dbg; | 123 | mod remove_dbg; |
126 | mod remove_mut; | 124 | mod remove_mut; |
125 | mod reorder_fields; | ||
127 | mod replace_if_let_with_match; | 126 | mod replace_if_let_with_match; |
128 | mod replace_let_with_if_let; | 127 | mod replace_let_with_if_let; |
129 | mod replace_qualified_name_with_use; | 128 | mod replace_qualified_name_with_use; |
130 | mod replace_unwrap_with_match; | 129 | mod replace_unwrap_with_match; |
131 | mod split_import; | 130 | mod split_import; |
132 | mod change_return_type_to_result; | ||
133 | mod add_from_impl_for_enum; | ||
134 | mod reorder_fields; | ||
135 | mod unwrap_block; | 131 | mod unwrap_block; |
136 | 132 | ||
137 | pub(crate) fn all() -> &'static [Handler] { | 133 | pub(crate) fn all() -> &'static [Handler] { |
diff --git a/crates/ra_assists/src/tests.rs b/crates/ra_assists/src/tests.rs index 17e3ece9f..a3eacb8f1 100644 --- a/crates/ra_assists/src/tests.rs +++ b/crates/ra_assists/src/tests.rs | |||
@@ -11,7 +11,7 @@ use test_utils::{ | |||
11 | RangeOrOffset, | 11 | RangeOrOffset, |
12 | }; | 12 | }; |
13 | 13 | ||
14 | use crate::{handlers::Handler, resolved_assists, AssistCtx}; | 14 | use crate::{handlers::Handler, Assist, AssistContext, Assists}; |
15 | 15 | ||
16 | pub(crate) fn with_single_file(text: &str) -> (RootDatabase, FileId) { | 16 | pub(crate) fn with_single_file(text: &str) -> (RootDatabase, FileId) { |
17 | let (mut db, file_id) = RootDatabase::with_single_file(text); | 17 | let (mut db, file_id) = RootDatabase::with_single_file(text); |
@@ -41,16 +41,16 @@ fn check_doc_test(assist_id: &str, before: &str, after: &str) { | |||
41 | let (db, file_id) = crate::tests::with_single_file(&before); | 41 | let (db, file_id) = crate::tests::with_single_file(&before); |
42 | let frange = FileRange { file_id, range: selection.into() }; | 42 | let frange = FileRange { file_id, range: selection.into() }; |
43 | 43 | ||
44 | let mut assist = resolved_assists(&db, frange) | 44 | let mut assist = Assist::resolved(&db, frange) |
45 | .into_iter() | 45 | .into_iter() |
46 | .find(|assist| assist.label.id.0 == assist_id) | 46 | .find(|assist| assist.assist.id.0 == assist_id) |
47 | .unwrap_or_else(|| { | 47 | .unwrap_or_else(|| { |
48 | panic!( | 48 | panic!( |
49 | "\n\nAssist is not applicable: {}\nAvailable assists: {}", | 49 | "\n\nAssist is not applicable: {}\nAvailable assists: {}", |
50 | assist_id, | 50 | assist_id, |
51 | resolved_assists(&db, frange) | 51 | Assist::resolved(&db, frange) |
52 | .into_iter() | 52 | .into_iter() |
53 | .map(|assist| assist.label.id.0) | 53 | .map(|assist| assist.assist.id.0) |
54 | .collect::<Vec<_>>() | 54 | .collect::<Vec<_>>() |
55 | .join(", ") | 55 | .join(", ") |
56 | ) | 56 | ) |
@@ -71,7 +71,7 @@ enum ExpectedResult<'a> { | |||
71 | Target(&'a str), | 71 | Target(&'a str), |
72 | } | 72 | } |
73 | 73 | ||
74 | fn check(assist: Handler, before: &str, expected: ExpectedResult) { | 74 | fn check(handler: Handler, before: &str, expected: ExpectedResult) { |
75 | let (text_without_caret, file_with_caret_id, range_or_offset, db) = if before.contains("//-") { | 75 | let (text_without_caret, file_with_caret_id, range_or_offset, db) = if before.contains("//-") { |
76 | let (mut db, position) = RootDatabase::with_position(before); | 76 | let (mut db, position) = RootDatabase::with_position(before); |
77 | db.set_local_roots(Arc::new(vec![db.file_source_root(position.file_id)])); | 77 | db.set_local_roots(Arc::new(vec![db.file_source_root(position.file_id)])); |
@@ -90,17 +90,20 @@ fn check(assist: Handler, before: &str, expected: ExpectedResult) { | |||
90 | let frange = FileRange { file_id: file_with_caret_id, range: range_or_offset.into() }; | 90 | let frange = FileRange { file_id: file_with_caret_id, range: range_or_offset.into() }; |
91 | 91 | ||
92 | let sema = Semantics::new(&db); | 92 | let sema = Semantics::new(&db); |
93 | let assist_ctx = AssistCtx::new(&sema, frange, true); | 93 | let ctx = AssistContext::new(sema, frange); |
94 | 94 | let mut acc = Assists::new_resolved(&ctx); | |
95 | match (assist(assist_ctx), expected) { | 95 | handler(&mut acc, &ctx); |
96 | let mut res = acc.finish_resolved(); | ||
97 | let assist = res.pop(); | ||
98 | match (assist, expected) { | ||
96 | (Some(assist), ExpectedResult::After(after)) => { | 99 | (Some(assist), ExpectedResult::After(after)) => { |
97 | let mut action = assist.0[0].source_change.clone().unwrap(); | 100 | let mut source_change = assist.source_change; |
98 | let change = action.source_file_edits.pop().unwrap(); | 101 | let change = source_change.source_file_edits.pop().unwrap(); |
99 | 102 | ||
100 | let mut actual = db.file_text(change.file_id).as_ref().to_owned(); | 103 | let mut actual = db.file_text(change.file_id).as_ref().to_owned(); |
101 | change.edit.apply(&mut actual); | 104 | change.edit.apply(&mut actual); |
102 | 105 | ||
103 | match action.cursor_position { | 106 | match source_change.cursor_position { |
104 | None => { | 107 | None => { |
105 | if let RangeOrOffset::Offset(before_cursor_pos) = range_or_offset { | 108 | if let RangeOrOffset::Offset(before_cursor_pos) = range_or_offset { |
106 | let off = change | 109 | let off = change |
@@ -116,7 +119,7 @@ fn check(assist: Handler, before: &str, expected: ExpectedResult) { | |||
116 | assert_eq_text!(after, &actual); | 119 | assert_eq_text!(after, &actual); |
117 | } | 120 | } |
118 | (Some(assist), ExpectedResult::Target(target)) => { | 121 | (Some(assist), ExpectedResult::Target(target)) => { |
119 | let range = assist.0[0].label.target; | 122 | let range = assist.assist.target; |
120 | assert_eq_text!(&text_without_caret[range], target); | 123 | assert_eq_text!(&text_without_caret[range], target); |
121 | } | 124 | } |
122 | (Some(_), ExpectedResult::NotApplicable) => panic!("assist should not be applicable!"), | 125 | (Some(_), ExpectedResult::NotApplicable) => panic!("assist should not be applicable!"), |
@@ -133,14 +136,14 @@ fn assist_order_field_struct() { | |||
133 | let (before_cursor_pos, before) = extract_offset(before); | 136 | let (before_cursor_pos, before) = extract_offset(before); |
134 | let (db, file_id) = with_single_file(&before); | 137 | let (db, file_id) = with_single_file(&before); |
135 | let frange = FileRange { file_id, range: TextRange::empty(before_cursor_pos) }; | 138 | let frange = FileRange { file_id, range: TextRange::empty(before_cursor_pos) }; |
136 | let assists = resolved_assists(&db, frange); | 139 | let assists = Assist::resolved(&db, frange); |
137 | let mut assists = assists.iter(); | 140 | let mut assists = assists.iter(); |
138 | 141 | ||
139 | assert_eq!( | 142 | assert_eq!( |
140 | assists.next().expect("expected assist").label.label, | 143 | assists.next().expect("expected assist").assist.label, |
141 | "Change visibility to pub(crate)" | 144 | "Change visibility to pub(crate)" |
142 | ); | 145 | ); |
143 | assert_eq!(assists.next().expect("expected assist").label.label, "Add `#[derive]`"); | 146 | assert_eq!(assists.next().expect("expected assist").assist.label, "Add `#[derive]`"); |
144 | } | 147 | } |
145 | 148 | ||
146 | #[test] | 149 | #[test] |
@@ -156,9 +159,9 @@ fn assist_order_if_expr() { | |||
156 | let (range, before) = extract_range(before); | 159 | let (range, before) = extract_range(before); |
157 | let (db, file_id) = with_single_file(&before); | 160 | let (db, file_id) = with_single_file(&before); |
158 | let frange = FileRange { file_id, range }; | 161 | let frange = FileRange { file_id, range }; |
159 | let assists = resolved_assists(&db, frange); | 162 | let assists = Assist::resolved(&db, frange); |
160 | let mut assists = assists.iter(); | 163 | let mut assists = assists.iter(); |
161 | 164 | ||
162 | assert_eq!(assists.next().expect("expected assist").label.label, "Extract into variable"); | 165 | assert_eq!(assists.next().expect("expected assist").assist.label, "Extract into variable"); |
163 | assert_eq!(assists.next().expect("expected assist").label.label, "Replace with match"); | 166 | assert_eq!(assists.next().expect("expected assist").assist.label, "Replace with match"); |
164 | } | 167 | } |
diff --git a/crates/ra_assists/src/utils/insert_use.rs b/crates/ra_assists/src/utils/insert_use.rs index c1f447efe..1214e3cd4 100644 --- a/crates/ra_assists/src/utils/insert_use.rs +++ b/crates/ra_assists/src/utils/insert_use.rs | |||
@@ -2,7 +2,6 @@ | |||
2 | // FIXME: rewrite according to the plan, outlined in | 2 | // FIXME: rewrite according to the plan, outlined in |
3 | // https://github.com/rust-analyzer/rust-analyzer/issues/3301#issuecomment-592931553 | 3 | // https://github.com/rust-analyzer/rust-analyzer/issues/3301#issuecomment-592931553 |
4 | 4 | ||
5 | use crate::assist_ctx::ActionBuilder; | ||
6 | use hir::{self, ModPath}; | 5 | use hir::{self, ModPath}; |
7 | use ra_syntax::{ | 6 | use ra_syntax::{ |
8 | ast::{self, NameOwner}, | 7 | ast::{self, NameOwner}, |
@@ -12,6 +11,8 @@ use ra_syntax::{ | |||
12 | }; | 11 | }; |
13 | use ra_text_edit::TextEditBuilder; | 12 | use ra_text_edit::TextEditBuilder; |
14 | 13 | ||
14 | use crate::assist_context::{AssistBuilder, AssistContext}; | ||
15 | |||
15 | /// Creates and inserts a use statement for the given path to import. | 16 | /// Creates and inserts a use statement for the given path to import. |
16 | /// The use statement is inserted in the scope most appropriate to the | 17 | /// The use statement is inserted in the scope most appropriate to the |
17 | /// the cursor position given, additionally merged with the existing use imports. | 18 | /// the cursor position given, additionally merged with the existing use imports. |
@@ -19,10 +20,11 @@ pub(crate) fn insert_use_statement( | |||
19 | // Ideally the position of the cursor, used to | 20 | // Ideally the position of the cursor, used to |
20 | position: &SyntaxNode, | 21 | position: &SyntaxNode, |
21 | path_to_import: &ModPath, | 22 | path_to_import: &ModPath, |
22 | edit: &mut ActionBuilder, | 23 | ctx: &AssistContext, |
24 | builder: &mut AssistBuilder, | ||
23 | ) { | 25 | ) { |
24 | let target = path_to_import.to_string().split("::").map(SmolStr::new).collect::<Vec<_>>(); | 26 | let target = path_to_import.to_string().split("::").map(SmolStr::new).collect::<Vec<_>>(); |
25 | let container = edit.ctx().sema.ancestors_with_macros(position.clone()).find_map(|n| { | 27 | let container = ctx.sema.ancestors_with_macros(position.clone()).find_map(|n| { |
26 | if let Some(module) = ast::Module::cast(n.clone()) { | 28 | if let Some(module) = ast::Module::cast(n.clone()) { |
27 | return module.item_list().map(|it| it.syntax().clone()); | 29 | return module.item_list().map(|it| it.syntax().clone()); |
28 | } | 30 | } |
@@ -31,7 +33,7 @@ pub(crate) fn insert_use_statement( | |||
31 | 33 | ||
32 | if let Some(container) = container { | 34 | if let Some(container) = container { |
33 | let action = best_action_for_target(container, position.clone(), &target); | 35 | let action = best_action_for_target(container, position.clone(), &target); |
34 | make_assist(&action, &target, edit.text_edit_builder()); | 36 | make_assist(&action, &target, builder.text_edit_builder()); |
35 | } | 37 | } |
36 | } | 38 | } |
37 | 39 | ||
diff --git a/crates/ra_cfg/src/lib.rs b/crates/ra_cfg/src/lib.rs index 51d953f6e..57feabcb2 100644 --- a/crates/ra_cfg/src/lib.rs +++ b/crates/ra_cfg/src/lib.rs | |||
@@ -2,8 +2,6 @@ | |||
2 | 2 | ||
3 | mod cfg_expr; | 3 | mod cfg_expr; |
4 | 4 | ||
5 | use std::iter::IntoIterator; | ||
6 | |||
7 | use ra_syntax::SmolStr; | 5 | use ra_syntax::SmolStr; |
8 | use rustc_hash::FxHashSet; | 6 | use rustc_hash::FxHashSet; |
9 | 7 | ||
@@ -48,9 +46,4 @@ impl CfgOptions { | |||
48 | pub fn insert_key_value(&mut self, key: SmolStr, value: SmolStr) { | 46 | pub fn insert_key_value(&mut self, key: SmolStr, value: SmolStr) { |
49 | self.key_values.insert((key, value)); | 47 | self.key_values.insert((key, value)); |
50 | } | 48 | } |
51 | |||
52 | /// Shortcut to set features | ||
53 | pub fn insert_features(&mut self, iter: impl IntoIterator<Item = SmolStr>) { | ||
54 | iter.into_iter().for_each(|feat| self.insert_key_value("feature".into(), feat)); | ||
55 | } | ||
56 | } | 49 | } |
diff --git a/crates/ra_flycheck/Cargo.toml b/crates/ra_flycheck/Cargo.toml index 3d5093264..03e557148 100644 --- a/crates/ra_flycheck/Cargo.toml +++ b/crates/ra_flycheck/Cargo.toml | |||
@@ -14,6 +14,7 @@ log = "0.4.8" | |||
14 | cargo_metadata = "0.9.1" | 14 | cargo_metadata = "0.9.1" |
15 | serde_json = "1.0.48" | 15 | serde_json = "1.0.48" |
16 | jod-thread = "0.1.1" | 16 | jod-thread = "0.1.1" |
17 | ra_toolchain = { path = "../ra_toolchain" } | ||
17 | 18 | ||
18 | [dev-dependencies] | 19 | [dev-dependencies] |
19 | insta = "0.16.0" | 20 | insta = "0.16.0" |
diff --git a/crates/ra_flycheck/src/lib.rs b/crates/ra_flycheck/src/lib.rs index f27252949..68dcee285 100644 --- a/crates/ra_flycheck/src/lib.rs +++ b/crates/ra_flycheck/src/lib.rs | |||
@@ -4,7 +4,6 @@ | |||
4 | mod conv; | 4 | mod conv; |
5 | 5 | ||
6 | use std::{ | 6 | use std::{ |
7 | env, | ||
8 | io::{self, BufRead, BufReader}, | 7 | io::{self, BufRead, BufReader}, |
9 | path::PathBuf, | 8 | path::PathBuf, |
10 | process::{Command, Stdio}, | 9 | process::{Command, Stdio}, |
@@ -216,10 +215,10 @@ impl FlycheckThread { | |||
216 | 215 | ||
217 | let mut cmd = match &self.config { | 216 | let mut cmd = match &self.config { |
218 | FlycheckConfig::CargoCommand { command, all_targets, all_features, extra_args } => { | 217 | FlycheckConfig::CargoCommand { command, all_targets, all_features, extra_args } => { |
219 | let mut cmd = Command::new(cargo_binary()); | 218 | let mut cmd = Command::new(ra_toolchain::cargo()); |
220 | cmd.arg(command); | 219 | cmd.arg(command); |
221 | cmd.args(&["--workspace", "--message-format=json", "--manifest-path"]); | 220 | cmd.args(&["--workspace", "--message-format=json", "--manifest-path"]) |
222 | cmd.arg(self.workspace_root.join("Cargo.toml")); | 221 | .arg(self.workspace_root.join("Cargo.toml")); |
223 | if *all_targets { | 222 | if *all_targets { |
224 | cmd.arg("--all-targets"); | 223 | cmd.arg("--all-targets"); |
225 | } | 224 | } |
@@ -337,7 +336,3 @@ fn run_cargo( | |||
337 | 336 | ||
338 | Ok(()) | 337 | Ok(()) |
339 | } | 338 | } |
340 | |||
341 | fn cargo_binary() -> String { | ||
342 | env::var("CARGO").unwrap_or_else(|_| "cargo".to_string()) | ||
343 | } | ||
diff --git a/crates/ra_hir/src/code_model.rs b/crates/ra_hir/src/code_model.rs index 7e840add5..6d8444462 100644 --- a/crates/ra_hir/src/code_model.rs +++ b/crates/ra_hir/src/code_model.rs | |||
@@ -22,8 +22,11 @@ use hir_expand::{ | |||
22 | MacroDefId, MacroDefKind, | 22 | MacroDefId, MacroDefKind, |
23 | }; | 23 | }; |
24 | use hir_ty::{ | 24 | use hir_ty::{ |
25 | autoderef, display::HirFormatter, expr::ExprValidator, method_resolution, ApplicationTy, | 25 | autoderef, |
26 | Canonical, InEnvironment, Substs, TraitEnvironment, Ty, TyDefId, TypeCtor, | 26 | display::{HirDisplayError, HirFormatter}, |
27 | expr::ExprValidator, | ||
28 | method_resolution, ApplicationTy, Canonical, InEnvironment, Substs, TraitEnvironment, Ty, | ||
29 | TyDefId, TypeCtor, | ||
27 | }; | 30 | }; |
28 | use ra_db::{CrateId, CrateName, Edition, FileId}; | 31 | use ra_db::{CrateId, CrateName, Edition, FileId}; |
29 | use ra_prof::profile; | 32 | use ra_prof::profile; |
@@ -1341,7 +1344,7 @@ impl Type { | |||
1341 | } | 1344 | } |
1342 | 1345 | ||
1343 | impl HirDisplay for Type { | 1346 | impl HirDisplay for Type { |
1344 | fn hir_fmt(&self, f: &mut HirFormatter) -> std::fmt::Result { | 1347 | fn hir_fmt(&self, f: &mut HirFormatter) -> Result<(), HirDisplayError> { |
1345 | self.ty.value.hir_fmt(f) | 1348 | self.ty.value.hir_fmt(f) |
1346 | } | 1349 | } |
1347 | } | 1350 | } |
diff --git a/crates/ra_hir_ty/src/diagnostics.rs b/crates/ra_hir_ty/src/diagnostics.rs index c8fd54861..41ac70272 100644 --- a/crates/ra_hir_ty/src/diagnostics.rs +++ b/crates/ra_hir_ty/src/diagnostics.rs | |||
@@ -131,3 +131,31 @@ impl AstDiagnostic for MissingOkInTailExpr { | |||
131 | ast::Expr::cast(node).unwrap() | 131 | ast::Expr::cast(node).unwrap() |
132 | } | 132 | } |
133 | } | 133 | } |
134 | |||
135 | #[derive(Debug)] | ||
136 | pub struct BreakOutsideOfLoop { | ||
137 | pub file: HirFileId, | ||
138 | pub expr: AstPtr<ast::Expr>, | ||
139 | } | ||
140 | |||
141 | impl Diagnostic for BreakOutsideOfLoop { | ||
142 | fn message(&self) -> String { | ||
143 | "break outside of loop".to_string() | ||
144 | } | ||
145 | fn source(&self) -> InFile<SyntaxNodePtr> { | ||
146 | InFile { file_id: self.file, value: self.expr.clone().into() } | ||
147 | } | ||
148 | fn as_any(&self) -> &(dyn Any + Send + 'static) { | ||
149 | self | ||
150 | } | ||
151 | } | ||
152 | |||
153 | impl AstDiagnostic for BreakOutsideOfLoop { | ||
154 | type AST = ast::Expr; | ||
155 | |||
156 | fn ast(&self, db: &impl AstDatabase) -> Self::AST { | ||
157 | let root = db.parse_or_expand(self.file).unwrap(); | ||
158 | let node = self.source().value.to_node(&root); | ||
159 | ast::Expr::cast(node).unwrap() | ||
160 | } | ||
161 | } | ||
diff --git a/crates/ra_hir_ty/src/display.rs b/crates/ra_hir_ty/src/display.rs index d03bbd5a7..f5edaea8c 100644 --- a/crates/ra_hir_ty/src/display.rs +++ b/crates/ra_hir_ty/src/display.rs | |||
@@ -6,28 +6,42 @@ use crate::{ | |||
6 | db::HirDatabase, utils::generics, ApplicationTy, CallableDef, FnSig, GenericPredicate, | 6 | db::HirDatabase, utils::generics, ApplicationTy, CallableDef, FnSig, GenericPredicate, |
7 | Obligation, ProjectionTy, Substs, TraitRef, Ty, TypeCtor, | 7 | Obligation, ProjectionTy, Substs, TraitRef, Ty, TypeCtor, |
8 | }; | 8 | }; |
9 | use hir_def::{generics::TypeParamProvenance, AdtId, AssocContainerId, Lookup}; | 9 | use hir_def::{ |
10 | find_path, generics::TypeParamProvenance, item_scope::ItemInNs, AdtId, AssocContainerId, | ||
11 | Lookup, ModuleId, | ||
12 | }; | ||
10 | use hir_expand::name::Name; | 13 | use hir_expand::name::Name; |
11 | 14 | ||
12 | pub struct HirFormatter<'a, 'b> { | 15 | pub struct HirFormatter<'a> { |
13 | pub db: &'a dyn HirDatabase, | 16 | pub db: &'a dyn HirDatabase, |
14 | fmt: &'a mut fmt::Formatter<'b>, | 17 | fmt: &'a mut dyn fmt::Write, |
15 | buf: String, | 18 | buf: String, |
16 | curr_size: usize, | 19 | curr_size: usize, |
17 | pub(crate) max_size: Option<usize>, | 20 | pub(crate) max_size: Option<usize>, |
18 | omit_verbose_types: bool, | 21 | omit_verbose_types: bool, |
22 | display_target: DisplayTarget, | ||
19 | } | 23 | } |
20 | 24 | ||
21 | pub trait HirDisplay { | 25 | pub trait HirDisplay { |
22 | fn hir_fmt(&self, f: &mut HirFormatter) -> fmt::Result; | 26 | fn hir_fmt(&self, f: &mut HirFormatter) -> Result<(), HirDisplayError>; |
23 | 27 | ||
28 | /// Returns a `Display`able type that is human-readable. | ||
29 | /// Use this for showing types to the user (e.g. diagnostics) | ||
24 | fn display<'a>(&'a self, db: &'a dyn HirDatabase) -> HirDisplayWrapper<'a, Self> | 30 | fn display<'a>(&'a self, db: &'a dyn HirDatabase) -> HirDisplayWrapper<'a, Self> |
25 | where | 31 | where |
26 | Self: Sized, | 32 | Self: Sized, |
27 | { | 33 | { |
28 | HirDisplayWrapper(db, self, None, false) | 34 | HirDisplayWrapper { |
35 | db, | ||
36 | t: self, | ||
37 | max_size: None, | ||
38 | omit_verbose_types: false, | ||
39 | display_target: DisplayTarget::Diagnostics, | ||
40 | } | ||
29 | } | 41 | } |
30 | 42 | ||
43 | /// Returns a `Display`able type that is human-readable and tries to be succinct. | ||
44 | /// Use this for showing types to the user where space is constrained (e.g. doc popups) | ||
31 | fn display_truncated<'a>( | 45 | fn display_truncated<'a>( |
32 | &'a self, | 46 | &'a self, |
33 | db: &'a dyn HirDatabase, | 47 | db: &'a dyn HirDatabase, |
@@ -36,16 +50,46 @@ pub trait HirDisplay { | |||
36 | where | 50 | where |
37 | Self: Sized, | 51 | Self: Sized, |
38 | { | 52 | { |
39 | HirDisplayWrapper(db, self, max_size, true) | 53 | HirDisplayWrapper { |
54 | db, | ||
55 | t: self, | ||
56 | max_size, | ||
57 | omit_verbose_types: true, | ||
58 | display_target: DisplayTarget::Diagnostics, | ||
59 | } | ||
60 | } | ||
61 | |||
62 | /// Returns a String representation of `self` that can be inserted into the given module. | ||
63 | /// Use this when generating code (e.g. assists) | ||
64 | fn display_source_code<'a>( | ||
65 | &'a self, | ||
66 | db: &'a dyn HirDatabase, | ||
67 | module_id: ModuleId, | ||
68 | ) -> Result<String, DisplaySourceCodeError> { | ||
69 | let mut result = String::new(); | ||
70 | match self.hir_fmt(&mut HirFormatter { | ||
71 | db, | ||
72 | fmt: &mut result, | ||
73 | buf: String::with_capacity(20), | ||
74 | curr_size: 0, | ||
75 | max_size: None, | ||
76 | omit_verbose_types: false, | ||
77 | display_target: DisplayTarget::SourceCode { module_id }, | ||
78 | }) { | ||
79 | Ok(()) => {} | ||
80 | Err(HirDisplayError::FmtError) => panic!("Writing to String can't fail!"), | ||
81 | Err(HirDisplayError::DisplaySourceCodeError(e)) => return Err(e), | ||
82 | }; | ||
83 | Ok(result) | ||
40 | } | 84 | } |
41 | } | 85 | } |
42 | 86 | ||
43 | impl<'a, 'b> HirFormatter<'a, 'b> { | 87 | impl<'a> HirFormatter<'a> { |
44 | pub fn write_joined<T: HirDisplay>( | 88 | pub fn write_joined<T: HirDisplay>( |
45 | &mut self, | 89 | &mut self, |
46 | iter: impl IntoIterator<Item = T>, | 90 | iter: impl IntoIterator<Item = T>, |
47 | sep: &str, | 91 | sep: &str, |
48 | ) -> fmt::Result { | 92 | ) -> Result<(), HirDisplayError> { |
49 | let mut first = true; | 93 | let mut first = true; |
50 | for e in iter { | 94 | for e in iter { |
51 | if !first { | 95 | if !first { |
@@ -58,14 +102,14 @@ impl<'a, 'b> HirFormatter<'a, 'b> { | |||
58 | } | 102 | } |
59 | 103 | ||
60 | /// This allows using the `write!` macro directly with a `HirFormatter`. | 104 | /// This allows using the `write!` macro directly with a `HirFormatter`. |
61 | pub fn write_fmt(&mut self, args: fmt::Arguments) -> fmt::Result { | 105 | pub fn write_fmt(&mut self, args: fmt::Arguments) -> Result<(), HirDisplayError> { |
62 | // We write to a buffer first to track output size | 106 | // We write to a buffer first to track output size |
63 | self.buf.clear(); | 107 | self.buf.clear(); |
64 | fmt::write(&mut self.buf, args)?; | 108 | fmt::write(&mut self.buf, args)?; |
65 | self.curr_size += self.buf.len(); | 109 | self.curr_size += self.buf.len(); |
66 | 110 | ||
67 | // Then we write to the internal formatter from the buffer | 111 | // Then we write to the internal formatter from the buffer |
68 | self.fmt.write_str(&self.buf) | 112 | self.fmt.write_str(&self.buf).map_err(HirDisplayError::from) |
69 | } | 113 | } |
70 | 114 | ||
71 | pub fn should_truncate(&self) -> bool { | 115 | pub fn should_truncate(&self) -> bool { |
@@ -81,34 +125,76 @@ impl<'a, 'b> HirFormatter<'a, 'b> { | |||
81 | } | 125 | } |
82 | } | 126 | } |
83 | 127 | ||
84 | pub struct HirDisplayWrapper<'a, T>(&'a dyn HirDatabase, &'a T, Option<usize>, bool); | 128 | #[derive(Clone, Copy)] |
129 | enum DisplayTarget { | ||
130 | /// Display types for inlays, doc popups, autocompletion, etc... | ||
131 | /// Showing `{unknown}` or not qualifying paths is fine here. | ||
132 | /// There's no reason for this to fail. | ||
133 | Diagnostics, | ||
134 | /// Display types for inserting them in source files. | ||
135 | /// The generated code should compile, so paths need to be qualified. | ||
136 | SourceCode { module_id: ModuleId }, | ||
137 | } | ||
138 | |||
139 | #[derive(Debug)] | ||
140 | pub enum DisplaySourceCodeError { | ||
141 | PathNotFound, | ||
142 | } | ||
143 | |||
144 | pub enum HirDisplayError { | ||
145 | /// Errors that can occur when generating source code | ||
146 | DisplaySourceCodeError(DisplaySourceCodeError), | ||
147 | /// `FmtError` is required to be compatible with std::fmt::Display | ||
148 | FmtError, | ||
149 | } | ||
150 | impl From<fmt::Error> for HirDisplayError { | ||
151 | fn from(_: fmt::Error) -> Self { | ||
152 | Self::FmtError | ||
153 | } | ||
154 | } | ||
155 | |||
156 | pub struct HirDisplayWrapper<'a, T> { | ||
157 | db: &'a dyn HirDatabase, | ||
158 | t: &'a T, | ||
159 | max_size: Option<usize>, | ||
160 | omit_verbose_types: bool, | ||
161 | display_target: DisplayTarget, | ||
162 | } | ||
85 | 163 | ||
86 | impl<'a, T> fmt::Display for HirDisplayWrapper<'a, T> | 164 | impl<'a, T> fmt::Display for HirDisplayWrapper<'a, T> |
87 | where | 165 | where |
88 | T: HirDisplay, | 166 | T: HirDisplay, |
89 | { | 167 | { |
90 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | 168 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
91 | self.1.hir_fmt(&mut HirFormatter { | 169 | match self.t.hir_fmt(&mut HirFormatter { |
92 | db: self.0, | 170 | db: self.db, |
93 | fmt: f, | 171 | fmt: f, |
94 | buf: String::with_capacity(20), | 172 | buf: String::with_capacity(20), |
95 | curr_size: 0, | 173 | curr_size: 0, |
96 | max_size: self.2, | 174 | max_size: self.max_size, |
97 | omit_verbose_types: self.3, | 175 | omit_verbose_types: self.omit_verbose_types, |
98 | }) | 176 | display_target: self.display_target, |
177 | }) { | ||
178 | Ok(()) => Ok(()), | ||
179 | Err(HirDisplayError::FmtError) => Err(fmt::Error), | ||
180 | Err(HirDisplayError::DisplaySourceCodeError(_)) => { | ||
181 | // This should never happen | ||
182 | panic!("HirDisplay failed when calling Display::fmt!") | ||
183 | } | ||
184 | } | ||
99 | } | 185 | } |
100 | } | 186 | } |
101 | 187 | ||
102 | const TYPE_HINT_TRUNCATION: &str = "…"; | 188 | const TYPE_HINT_TRUNCATION: &str = "…"; |
103 | 189 | ||
104 | impl HirDisplay for &Ty { | 190 | impl HirDisplay for &Ty { |
105 | fn hir_fmt(&self, f: &mut HirFormatter) -> fmt::Result { | 191 | fn hir_fmt(&self, f: &mut HirFormatter) -> Result<(), HirDisplayError> { |
106 | HirDisplay::hir_fmt(*self, f) | 192 | HirDisplay::hir_fmt(*self, f) |
107 | } | 193 | } |
108 | } | 194 | } |
109 | 195 | ||
110 | impl HirDisplay for ApplicationTy { | 196 | impl HirDisplay for ApplicationTy { |
111 | fn hir_fmt(&self, f: &mut HirFormatter) -> fmt::Result { | 197 | fn hir_fmt(&self, f: &mut HirFormatter) -> Result<(), HirDisplayError> { |
112 | if f.should_truncate() { | 198 | if f.should_truncate() { |
113 | return write!(f, "{}", TYPE_HINT_TRUNCATION); | 199 | return write!(f, "{}", TYPE_HINT_TRUNCATION); |
114 | } | 200 | } |
@@ -191,12 +277,30 @@ impl HirDisplay for ApplicationTy { | |||
191 | } | 277 | } |
192 | } | 278 | } |
193 | TypeCtor::Adt(def_id) => { | 279 | TypeCtor::Adt(def_id) => { |
194 | let name = match def_id { | 280 | match f.display_target { |
195 | AdtId::StructId(it) => f.db.struct_data(it).name.clone(), | 281 | DisplayTarget::Diagnostics => { |
196 | AdtId::UnionId(it) => f.db.union_data(it).name.clone(), | 282 | let name = match def_id { |
197 | AdtId::EnumId(it) => f.db.enum_data(it).name.clone(), | 283 | AdtId::StructId(it) => f.db.struct_data(it).name.clone(), |
198 | }; | 284 | AdtId::UnionId(it) => f.db.union_data(it).name.clone(), |
199 | write!(f, "{}", name)?; | 285 | AdtId::EnumId(it) => f.db.enum_data(it).name.clone(), |
286 | }; | ||
287 | write!(f, "{}", name)?; | ||
288 | } | ||
289 | DisplayTarget::SourceCode { module_id } => { | ||
290 | if let Some(path) = find_path::find_path( | ||
291 | f.db.upcast(), | ||
292 | ItemInNs::Types(def_id.into()), | ||
293 | module_id, | ||
294 | ) { | ||
295 | write!(f, "{}", path)?; | ||
296 | } else { | ||
297 | return Err(HirDisplayError::DisplaySourceCodeError( | ||
298 | DisplaySourceCodeError::PathNotFound, | ||
299 | )); | ||
300 | } | ||
301 | } | ||
302 | } | ||
303 | |||
200 | if self.parameters.len() > 0 { | 304 | if self.parameters.len() > 0 { |
201 | let mut non_default_parameters = Vec::with_capacity(self.parameters.len()); | 305 | let mut non_default_parameters = Vec::with_capacity(self.parameters.len()); |
202 | let parameters_to_write = if f.omit_verbose_types() { | 306 | let parameters_to_write = if f.omit_verbose_types() { |
@@ -269,7 +373,7 @@ impl HirDisplay for ApplicationTy { | |||
269 | } | 373 | } |
270 | 374 | ||
271 | impl HirDisplay for ProjectionTy { | 375 | impl HirDisplay for ProjectionTy { |
272 | fn hir_fmt(&self, f: &mut HirFormatter) -> fmt::Result { | 376 | fn hir_fmt(&self, f: &mut HirFormatter) -> Result<(), HirDisplayError> { |
273 | if f.should_truncate() { | 377 | if f.should_truncate() { |
274 | return write!(f, "{}", TYPE_HINT_TRUNCATION); | 378 | return write!(f, "{}", TYPE_HINT_TRUNCATION); |
275 | } | 379 | } |
@@ -287,7 +391,7 @@ impl HirDisplay for ProjectionTy { | |||
287 | } | 391 | } |
288 | 392 | ||
289 | impl HirDisplay for Ty { | 393 | impl HirDisplay for Ty { |
290 | fn hir_fmt(&self, f: &mut HirFormatter) -> fmt::Result { | 394 | fn hir_fmt(&self, f: &mut HirFormatter) -> Result<(), HirDisplayError> { |
291 | if f.should_truncate() { | 395 | if f.should_truncate() { |
292 | return write!(f, "{}", TYPE_HINT_TRUNCATION); | 396 | return write!(f, "{}", TYPE_HINT_TRUNCATION); |
293 | } | 397 | } |
@@ -332,7 +436,7 @@ impl HirDisplay for Ty { | |||
332 | fn write_bounds_like_dyn_trait( | 436 | fn write_bounds_like_dyn_trait( |
333 | predicates: &[GenericPredicate], | 437 | predicates: &[GenericPredicate], |
334 | f: &mut HirFormatter, | 438 | f: &mut HirFormatter, |
335 | ) -> fmt::Result { | 439 | ) -> Result<(), HirDisplayError> { |
336 | // Note: This code is written to produce nice results (i.e. | 440 | // Note: This code is written to produce nice results (i.e. |
337 | // corresponding to surface Rust) for types that can occur in | 441 | // corresponding to surface Rust) for types that can occur in |
338 | // actual Rust. It will have weird results if the predicates | 442 | // actual Rust. It will have weird results if the predicates |
@@ -394,7 +498,7 @@ fn write_bounds_like_dyn_trait( | |||
394 | } | 498 | } |
395 | 499 | ||
396 | impl TraitRef { | 500 | impl TraitRef { |
397 | fn hir_fmt_ext(&self, f: &mut HirFormatter, use_as: bool) -> fmt::Result { | 501 | fn hir_fmt_ext(&self, f: &mut HirFormatter, use_as: bool) -> Result<(), HirDisplayError> { |
398 | if f.should_truncate() { | 502 | if f.should_truncate() { |
399 | return write!(f, "{}", TYPE_HINT_TRUNCATION); | 503 | return write!(f, "{}", TYPE_HINT_TRUNCATION); |
400 | } | 504 | } |
@@ -416,19 +520,19 @@ impl TraitRef { | |||
416 | } | 520 | } |
417 | 521 | ||
418 | impl HirDisplay for TraitRef { | 522 | impl HirDisplay for TraitRef { |
419 | fn hir_fmt(&self, f: &mut HirFormatter) -> fmt::Result { | 523 | fn hir_fmt(&self, f: &mut HirFormatter) -> Result<(), HirDisplayError> { |
420 | self.hir_fmt_ext(f, false) | 524 | self.hir_fmt_ext(f, false) |
421 | } | 525 | } |
422 | } | 526 | } |
423 | 527 | ||
424 | impl HirDisplay for &GenericPredicate { | 528 | impl HirDisplay for &GenericPredicate { |
425 | fn hir_fmt(&self, f: &mut HirFormatter) -> fmt::Result { | 529 | fn hir_fmt(&self, f: &mut HirFormatter) -> Result<(), HirDisplayError> { |
426 | HirDisplay::hir_fmt(*self, f) | 530 | HirDisplay::hir_fmt(*self, f) |
427 | } | 531 | } |
428 | } | 532 | } |
429 | 533 | ||
430 | impl HirDisplay for GenericPredicate { | 534 | impl HirDisplay for GenericPredicate { |
431 | fn hir_fmt(&self, f: &mut HirFormatter) -> fmt::Result { | 535 | fn hir_fmt(&self, f: &mut HirFormatter) -> Result<(), HirDisplayError> { |
432 | if f.should_truncate() { | 536 | if f.should_truncate() { |
433 | return write!(f, "{}", TYPE_HINT_TRUNCATION); | 537 | return write!(f, "{}", TYPE_HINT_TRUNCATION); |
434 | } | 538 | } |
@@ -452,15 +556,15 @@ impl HirDisplay for GenericPredicate { | |||
452 | } | 556 | } |
453 | 557 | ||
454 | impl HirDisplay for Obligation { | 558 | impl HirDisplay for Obligation { |
455 | fn hir_fmt(&self, f: &mut HirFormatter) -> fmt::Result { | 559 | fn hir_fmt(&self, f: &mut HirFormatter) -> Result<(), HirDisplayError> { |
456 | match self { | 560 | Ok(match self { |
457 | Obligation::Trait(tr) => write!(f, "Implements({})", tr.display(f.db)), | 561 | Obligation::Trait(tr) => write!(f, "Implements({})", tr.display(f.db))?, |
458 | Obligation::Projection(proj) => write!( | 562 | Obligation::Projection(proj) => write!( |
459 | f, | 563 | f, |
460 | "Normalize({} => {})", | 564 | "Normalize({} => {})", |
461 | proj.projection_ty.display(f.db), | 565 | proj.projection_ty.display(f.db), |
462 | proj.ty.display(f.db) | 566 | proj.ty.display(f.db) |
463 | ), | 567 | )?, |
464 | } | 568 | }) |
465 | } | 569 | } |
466 | } | 570 | } |
diff --git a/crates/ra_hir_ty/src/infer.rs b/crates/ra_hir_ty/src/infer.rs index bd4ef69a0..a21ad8d86 100644 --- a/crates/ra_hir_ty/src/infer.rs +++ b/crates/ra_hir_ty/src/infer.rs | |||
@@ -210,6 +210,13 @@ struct InferenceContext<'a> { | |||
210 | /// closures, but currently this is the only field that will change there, | 210 | /// closures, but currently this is the only field that will change there, |
211 | /// so it doesn't make sense. | 211 | /// so it doesn't make sense. |
212 | return_ty: Ty, | 212 | return_ty: Ty, |
213 | diverges: Diverges, | ||
214 | breakables: Vec<BreakableContext>, | ||
215 | } | ||
216 | |||
217 | #[derive(Clone, Debug)] | ||
218 | struct BreakableContext { | ||
219 | pub may_break: bool, | ||
213 | } | 220 | } |
214 | 221 | ||
215 | impl<'a> InferenceContext<'a> { | 222 | impl<'a> InferenceContext<'a> { |
@@ -224,6 +231,8 @@ impl<'a> InferenceContext<'a> { | |||
224 | owner, | 231 | owner, |
225 | body: db.body(owner), | 232 | body: db.body(owner), |
226 | resolver, | 233 | resolver, |
234 | diverges: Diverges::Maybe, | ||
235 | breakables: Vec::new(), | ||
227 | } | 236 | } |
228 | } | 237 | } |
229 | 238 | ||
@@ -666,15 +675,57 @@ impl Expectation { | |||
666 | } | 675 | } |
667 | } | 676 | } |
668 | 677 | ||
678 | #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] | ||
679 | enum Diverges { | ||
680 | Maybe, | ||
681 | Always, | ||
682 | } | ||
683 | |||
684 | impl Diverges { | ||
685 | fn is_always(self) -> bool { | ||
686 | self == Diverges::Always | ||
687 | } | ||
688 | } | ||
689 | |||
690 | impl std::ops::BitAnd for Diverges { | ||
691 | type Output = Self; | ||
692 | fn bitand(self, other: Self) -> Self { | ||
693 | std::cmp::min(self, other) | ||
694 | } | ||
695 | } | ||
696 | |||
697 | impl std::ops::BitOr for Diverges { | ||
698 | type Output = Self; | ||
699 | fn bitor(self, other: Self) -> Self { | ||
700 | std::cmp::max(self, other) | ||
701 | } | ||
702 | } | ||
703 | |||
704 | impl std::ops::BitAndAssign for Diverges { | ||
705 | fn bitand_assign(&mut self, other: Self) { | ||
706 | *self = *self & other; | ||
707 | } | ||
708 | } | ||
709 | |||
710 | impl std::ops::BitOrAssign for Diverges { | ||
711 | fn bitor_assign(&mut self, other: Self) { | ||
712 | *self = *self | other; | ||
713 | } | ||
714 | } | ||
715 | |||
669 | mod diagnostics { | 716 | mod diagnostics { |
670 | use hir_def::{expr::ExprId, FunctionId}; | 717 | use hir_def::{expr::ExprId, FunctionId}; |
671 | use hir_expand::diagnostics::DiagnosticSink; | 718 | use hir_expand::diagnostics::DiagnosticSink; |
672 | 719 | ||
673 | use crate::{db::HirDatabase, diagnostics::NoSuchField}; | 720 | use crate::{ |
721 | db::HirDatabase, | ||
722 | diagnostics::{BreakOutsideOfLoop, NoSuchField}, | ||
723 | }; | ||
674 | 724 | ||
675 | #[derive(Debug, PartialEq, Eq, Clone)] | 725 | #[derive(Debug, PartialEq, Eq, Clone)] |
676 | pub(super) enum InferenceDiagnostic { | 726 | pub(super) enum InferenceDiagnostic { |
677 | NoSuchField { expr: ExprId, field: usize }, | 727 | NoSuchField { expr: ExprId, field: usize }, |
728 | BreakOutsideOfLoop { expr: ExprId }, | ||
678 | } | 729 | } |
679 | 730 | ||
680 | impl InferenceDiagnostic { | 731 | impl InferenceDiagnostic { |
@@ -690,6 +741,13 @@ mod diagnostics { | |||
690 | let field = source_map.field_syntax(*expr, *field); | 741 | let field = source_map.field_syntax(*expr, *field); |
691 | sink.push(NoSuchField { file: field.file_id, field: field.value }) | 742 | sink.push(NoSuchField { file: field.file_id, field: field.value }) |
692 | } | 743 | } |
744 | InferenceDiagnostic::BreakOutsideOfLoop { expr } => { | ||
745 | let (_, source_map) = db.body_with_source_map(owner.into()); | ||
746 | let ptr = source_map | ||
747 | .expr_syntax(*expr) | ||
748 | .expect("break outside of loop in synthetic syntax"); | ||
749 | sink.push(BreakOutsideOfLoop { file: ptr.file_id, expr: ptr.value }) | ||
750 | } | ||
693 | } | 751 | } |
694 | } | 752 | } |
695 | } | 753 | } |
diff --git a/crates/ra_hir_ty/src/infer/coerce.rs b/crates/ra_hir_ty/src/infer/coerce.rs index 89200255a..173ec59ed 100644 --- a/crates/ra_hir_ty/src/infer/coerce.rs +++ b/crates/ra_hir_ty/src/infer/coerce.rs | |||
@@ -20,21 +20,35 @@ impl<'a> InferenceContext<'a> { | |||
20 | self.coerce_inner(from_ty, &to_ty) | 20 | self.coerce_inner(from_ty, &to_ty) |
21 | } | 21 | } |
22 | 22 | ||
23 | /// Merge two types from different branches, with possible implicit coerce. | 23 | /// Merge two types from different branches, with possible coercion. |
24 | /// | 24 | /// |
25 | /// Note that it is only possible that one type are coerced to another. | 25 | /// Mostly this means trying to coerce one to the other, but |
26 | /// Coercing both types to another least upper bound type is not possible in rustc, | 26 | /// - if we have two function types for different functions, we need to |
27 | /// which will simply result in "incompatible types" error. | 27 | /// coerce both to function pointers; |
28 | /// - if we were concerned with lifetime subtyping, we'd need to look for a | ||
29 | /// least upper bound. | ||
28 | pub(super) fn coerce_merge_branch(&mut self, ty1: &Ty, ty2: &Ty) -> Ty { | 30 | pub(super) fn coerce_merge_branch(&mut self, ty1: &Ty, ty2: &Ty) -> Ty { |
29 | if self.coerce(ty1, ty2) { | 31 | if self.coerce(ty1, ty2) { |
30 | ty2.clone() | 32 | ty2.clone() |
31 | } else if self.coerce(ty2, ty1) { | 33 | } else if self.coerce(ty2, ty1) { |
32 | ty1.clone() | 34 | ty1.clone() |
33 | } else { | 35 | } else { |
34 | tested_by!(coerce_merge_fail_fallback); | 36 | if let (ty_app!(TypeCtor::FnDef(_)), ty_app!(TypeCtor::FnDef(_))) = (ty1, ty2) { |
35 | // For incompatible types, we use the latter one as result | 37 | tested_by!(coerce_fn_reification); |
36 | // to be better recovery for `if` without `else`. | 38 | // Special case: two function types. Try to coerce both to |
37 | ty2.clone() | 39 | // pointers to have a chance at getting a match. See |
40 | // https://github.com/rust-lang/rust/blob/7b805396bf46dce972692a6846ce2ad8481c5f85/src/librustc_typeck/check/coercion.rs#L877-L916 | ||
41 | let sig1 = ty1.callable_sig(self.db).expect("FnDef without callable sig"); | ||
42 | let sig2 = ty2.callable_sig(self.db).expect("FnDef without callable sig"); | ||
43 | let ptr_ty1 = Ty::fn_ptr(sig1); | ||
44 | let ptr_ty2 = Ty::fn_ptr(sig2); | ||
45 | self.coerce_merge_branch(&ptr_ty1, &ptr_ty2) | ||
46 | } else { | ||
47 | tested_by!(coerce_merge_fail_fallback); | ||
48 | // For incompatible types, we use the latter one as result | ||
49 | // to be better recovery for `if` without `else`. | ||
50 | ty2.clone() | ||
51 | } | ||
38 | } | 52 | } |
39 | } | 53 | } |
40 | 54 | ||
@@ -84,9 +98,7 @@ impl<'a> InferenceContext<'a> { | |||
84 | match from_ty.callable_sig(self.db) { | 98 | match from_ty.callable_sig(self.db) { |
85 | None => return false, | 99 | None => return false, |
86 | Some(sig) => { | 100 | Some(sig) => { |
87 | let num_args = sig.params_and_return.len() as u16 - 1; | 101 | from_ty = Ty::fn_ptr(sig); |
88 | from_ty = | ||
89 | Ty::apply(TypeCtor::FnPtr { num_args }, Substs(sig.params_and_return)); | ||
90 | } | 102 | } |
91 | } | 103 | } |
92 | } | 104 | } |
diff --git a/crates/ra_hir_ty/src/infer/expr.rs b/crates/ra_hir_ty/src/infer/expr.rs index 83f946eee..0b67d216a 100644 --- a/crates/ra_hir_ty/src/infer/expr.rs +++ b/crates/ra_hir_ty/src/infer/expr.rs | |||
@@ -1,7 +1,7 @@ | |||
1 | //! Type inference for expressions. | 1 | //! Type inference for expressions. |
2 | 2 | ||
3 | use std::iter::{repeat, repeat_with}; | 3 | use std::iter::{repeat, repeat_with}; |
4 | use std::sync::Arc; | 4 | use std::{mem, sync::Arc}; |
5 | 5 | ||
6 | use hir_def::{ | 6 | use hir_def::{ |
7 | builtin_type::Signedness, | 7 | builtin_type::Signedness, |
@@ -21,11 +21,18 @@ use crate::{ | |||
21 | Ty, TypeCtor, Uncertain, | 21 | Ty, TypeCtor, Uncertain, |
22 | }; | 22 | }; |
23 | 23 | ||
24 | use super::{BindingMode, Expectation, InferenceContext, InferenceDiagnostic, TypeMismatch}; | 24 | use super::{ |
25 | BindingMode, BreakableContext, Diverges, Expectation, InferenceContext, InferenceDiagnostic, | ||
26 | TypeMismatch, | ||
27 | }; | ||
25 | 28 | ||
26 | impl<'a> InferenceContext<'a> { | 29 | impl<'a> InferenceContext<'a> { |
27 | pub(super) fn infer_expr(&mut self, tgt_expr: ExprId, expected: &Expectation) -> Ty { | 30 | pub(super) fn infer_expr(&mut self, tgt_expr: ExprId, expected: &Expectation) -> Ty { |
28 | let ty = self.infer_expr_inner(tgt_expr, expected); | 31 | let ty = self.infer_expr_inner(tgt_expr, expected); |
32 | if ty.is_never() { | ||
33 | // Any expression that produces a value of type `!` must have diverged | ||
34 | self.diverges = Diverges::Always; | ||
35 | } | ||
29 | let could_unify = self.unify(&ty, &expected.ty); | 36 | let could_unify = self.unify(&ty, &expected.ty); |
30 | if !could_unify { | 37 | if !could_unify { |
31 | self.result.type_mismatches.insert( | 38 | self.result.type_mismatches.insert( |
@@ -64,11 +71,18 @@ impl<'a> InferenceContext<'a> { | |||
64 | // if let is desugared to match, so this is always simple if | 71 | // if let is desugared to match, so this is always simple if |
65 | self.infer_expr(*condition, &Expectation::has_type(Ty::simple(TypeCtor::Bool))); | 72 | self.infer_expr(*condition, &Expectation::has_type(Ty::simple(TypeCtor::Bool))); |
66 | 73 | ||
74 | let condition_diverges = mem::replace(&mut self.diverges, Diverges::Maybe); | ||
75 | let mut both_arms_diverge = Diverges::Always; | ||
76 | |||
67 | let then_ty = self.infer_expr_inner(*then_branch, &expected); | 77 | let then_ty = self.infer_expr_inner(*then_branch, &expected); |
78 | both_arms_diverge &= mem::replace(&mut self.diverges, Diverges::Maybe); | ||
68 | let else_ty = match else_branch { | 79 | let else_ty = match else_branch { |
69 | Some(else_branch) => self.infer_expr_inner(*else_branch, &expected), | 80 | Some(else_branch) => self.infer_expr_inner(*else_branch, &expected), |
70 | None => Ty::unit(), | 81 | None => Ty::unit(), |
71 | }; | 82 | }; |
83 | both_arms_diverge &= self.diverges; | ||
84 | |||
85 | self.diverges = condition_diverges | both_arms_diverge; | ||
72 | 86 | ||
73 | self.coerce_merge_branch(&then_ty, &else_ty) | 87 | self.coerce_merge_branch(&then_ty, &else_ty) |
74 | } | 88 | } |
@@ -79,24 +93,43 @@ impl<'a> InferenceContext<'a> { | |||
79 | Ty::Unknown | 93 | Ty::Unknown |
80 | } | 94 | } |
81 | Expr::Loop { body } => { | 95 | Expr::Loop { body } => { |
96 | self.breakables.push(BreakableContext { may_break: false }); | ||
82 | self.infer_expr(*body, &Expectation::has_type(Ty::unit())); | 97 | self.infer_expr(*body, &Expectation::has_type(Ty::unit())); |
98 | |||
99 | let ctxt = self.breakables.pop().expect("breakable stack broken"); | ||
100 | if ctxt.may_break { | ||
101 | self.diverges = Diverges::Maybe; | ||
102 | } | ||
83 | // FIXME handle break with value | 103 | // FIXME handle break with value |
84 | Ty::simple(TypeCtor::Never) | 104 | if ctxt.may_break { |
105 | Ty::unit() | ||
106 | } else { | ||
107 | Ty::simple(TypeCtor::Never) | ||
108 | } | ||
85 | } | 109 | } |
86 | Expr::While { condition, body } => { | 110 | Expr::While { condition, body } => { |
111 | self.breakables.push(BreakableContext { may_break: false }); | ||
87 | // while let is desugared to a match loop, so this is always simple while | 112 | // while let is desugared to a match loop, so this is always simple while |
88 | self.infer_expr(*condition, &Expectation::has_type(Ty::simple(TypeCtor::Bool))); | 113 | self.infer_expr(*condition, &Expectation::has_type(Ty::simple(TypeCtor::Bool))); |
89 | self.infer_expr(*body, &Expectation::has_type(Ty::unit())); | 114 | self.infer_expr(*body, &Expectation::has_type(Ty::unit())); |
115 | let _ctxt = self.breakables.pop().expect("breakable stack broken"); | ||
116 | // the body may not run, so it diverging doesn't mean we diverge | ||
117 | self.diverges = Diverges::Maybe; | ||
90 | Ty::unit() | 118 | Ty::unit() |
91 | } | 119 | } |
92 | Expr::For { iterable, body, pat } => { | 120 | Expr::For { iterable, body, pat } => { |
93 | let iterable_ty = self.infer_expr(*iterable, &Expectation::none()); | 121 | let iterable_ty = self.infer_expr(*iterable, &Expectation::none()); |
94 | 122 | ||
123 | self.breakables.push(BreakableContext { may_break: false }); | ||
95 | let pat_ty = | 124 | let pat_ty = |
96 | self.resolve_associated_type(iterable_ty, self.resolve_into_iter_item()); | 125 | self.resolve_associated_type(iterable_ty, self.resolve_into_iter_item()); |
97 | 126 | ||
98 | self.infer_pat(*pat, &pat_ty, BindingMode::default()); | 127 | self.infer_pat(*pat, &pat_ty, BindingMode::default()); |
128 | |||
99 | self.infer_expr(*body, &Expectation::has_type(Ty::unit())); | 129 | self.infer_expr(*body, &Expectation::has_type(Ty::unit())); |
130 | let _ctxt = self.breakables.pop().expect("breakable stack broken"); | ||
131 | // the body may not run, so it diverging doesn't mean we diverge | ||
132 | self.diverges = Diverges::Maybe; | ||
100 | Ty::unit() | 133 | Ty::unit() |
101 | } | 134 | } |
102 | Expr::Lambda { body, args, ret_type, arg_types } => { | 135 | Expr::Lambda { body, args, ret_type, arg_types } => { |
@@ -132,10 +165,12 @@ impl<'a> InferenceContext<'a> { | |||
132 | // infer the body. | 165 | // infer the body. |
133 | self.coerce(&closure_ty, &expected.ty); | 166 | self.coerce(&closure_ty, &expected.ty); |
134 | 167 | ||
135 | let prev_ret_ty = std::mem::replace(&mut self.return_ty, ret_ty.clone()); | 168 | let prev_diverges = mem::replace(&mut self.diverges, Diverges::Maybe); |
169 | let prev_ret_ty = mem::replace(&mut self.return_ty, ret_ty.clone()); | ||
136 | 170 | ||
137 | self.infer_expr_coerce(*body, &Expectation::has_type(ret_ty)); | 171 | self.infer_expr_coerce(*body, &Expectation::has_type(ret_ty)); |
138 | 172 | ||
173 | self.diverges = prev_diverges; | ||
139 | self.return_ty = prev_ret_ty; | 174 | self.return_ty = prev_ret_ty; |
140 | 175 | ||
141 | closure_ty | 176 | closure_ty |
@@ -165,7 +200,11 @@ impl<'a> InferenceContext<'a> { | |||
165 | self.table.new_type_var() | 200 | self.table.new_type_var() |
166 | }; | 201 | }; |
167 | 202 | ||
203 | let matchee_diverges = self.diverges; | ||
204 | let mut all_arms_diverge = Diverges::Always; | ||
205 | |||
168 | for arm in arms { | 206 | for arm in arms { |
207 | self.diverges = Diverges::Maybe; | ||
169 | let _pat_ty = self.infer_pat(arm.pat, &input_ty, BindingMode::default()); | 208 | let _pat_ty = self.infer_pat(arm.pat, &input_ty, BindingMode::default()); |
170 | if let Some(guard_expr) = arm.guard { | 209 | if let Some(guard_expr) = arm.guard { |
171 | self.infer_expr( | 210 | self.infer_expr( |
@@ -175,9 +214,12 @@ impl<'a> InferenceContext<'a> { | |||
175 | } | 214 | } |
176 | 215 | ||
177 | let arm_ty = self.infer_expr_inner(arm.expr, &expected); | 216 | let arm_ty = self.infer_expr_inner(arm.expr, &expected); |
217 | all_arms_diverge &= self.diverges; | ||
178 | result_ty = self.coerce_merge_branch(&result_ty, &arm_ty); | 218 | result_ty = self.coerce_merge_branch(&result_ty, &arm_ty); |
179 | } | 219 | } |
180 | 220 | ||
221 | self.diverges = matchee_diverges | all_arms_diverge; | ||
222 | |||
181 | result_ty | 223 | result_ty |
182 | } | 224 | } |
183 | Expr::Path(p) => { | 225 | Expr::Path(p) => { |
@@ -191,6 +233,13 @@ impl<'a> InferenceContext<'a> { | |||
191 | // FIXME handle break with value | 233 | // FIXME handle break with value |
192 | self.infer_expr(*expr, &Expectation::none()); | 234 | self.infer_expr(*expr, &Expectation::none()); |
193 | } | 235 | } |
236 | if let Some(ctxt) = self.breakables.last_mut() { | ||
237 | ctxt.may_break = true; | ||
238 | } else { | ||
239 | self.push_diagnostic(InferenceDiagnostic::BreakOutsideOfLoop { | ||
240 | expr: tgt_expr, | ||
241 | }); | ||
242 | } | ||
194 | Ty::simple(TypeCtor::Never) | 243 | Ty::simple(TypeCtor::Never) |
195 | } | 244 | } |
196 | Expr::Return { expr } => { | 245 | Expr::Return { expr } => { |
@@ -501,8 +550,8 @@ impl<'a> InferenceContext<'a> { | |||
501 | } | 550 | } |
502 | Literal::ByteString(..) => { | 551 | Literal::ByteString(..) => { |
503 | let byte_type = Ty::simple(TypeCtor::Int(Uncertain::Known(IntTy::u8()))); | 552 | let byte_type = Ty::simple(TypeCtor::Int(Uncertain::Known(IntTy::u8()))); |
504 | let slice_type = Ty::apply_one(TypeCtor::Slice, byte_type); | 553 | let array_type = Ty::apply_one(TypeCtor::Array, byte_type); |
505 | Ty::apply_one(TypeCtor::Ref(Mutability::Shared), slice_type) | 554 | Ty::apply_one(TypeCtor::Ref(Mutability::Shared), array_type) |
506 | } | 555 | } |
507 | Literal::Char(..) => Ty::simple(TypeCtor::Char), | 556 | Literal::Char(..) => Ty::simple(TypeCtor::Char), |
508 | Literal::Int(_v, ty) => Ty::simple(TypeCtor::Int((*ty).into())), | 557 | Literal::Int(_v, ty) => Ty::simple(TypeCtor::Int((*ty).into())), |
@@ -522,7 +571,6 @@ impl<'a> InferenceContext<'a> { | |||
522 | tail: Option<ExprId>, | 571 | tail: Option<ExprId>, |
523 | expected: &Expectation, | 572 | expected: &Expectation, |
524 | ) -> Ty { | 573 | ) -> Ty { |
525 | let mut diverges = false; | ||
526 | for stmt in statements { | 574 | for stmt in statements { |
527 | match stmt { | 575 | match stmt { |
528 | Statement::Let { pat, type_ref, initializer } => { | 576 | Statement::Let { pat, type_ref, initializer } => { |
@@ -544,9 +592,7 @@ impl<'a> InferenceContext<'a> { | |||
544 | self.infer_pat(*pat, &ty, BindingMode::default()); | 592 | self.infer_pat(*pat, &ty, BindingMode::default()); |
545 | } | 593 | } |
546 | Statement::Expr(expr) => { | 594 | Statement::Expr(expr) => { |
547 | if let ty_app!(TypeCtor::Never) = self.infer_expr(*expr, &Expectation::none()) { | 595 | self.infer_expr(*expr, &Expectation::none()); |
548 | diverges = true; | ||
549 | } | ||
550 | } | 596 | } |
551 | } | 597 | } |
552 | } | 598 | } |
@@ -554,14 +600,22 @@ impl<'a> InferenceContext<'a> { | |||
554 | let ty = if let Some(expr) = tail { | 600 | let ty = if let Some(expr) = tail { |
555 | self.infer_expr_coerce(expr, expected) | 601 | self.infer_expr_coerce(expr, expected) |
556 | } else { | 602 | } else { |
557 | self.coerce(&Ty::unit(), expected.coercion_target()); | 603 | // Citing rustc: if there is no explicit tail expression, |
558 | Ty::unit() | 604 | // that is typically equivalent to a tail expression |
605 | // of `()` -- except if the block diverges. In that | ||
606 | // case, there is no value supplied from the tail | ||
607 | // expression (assuming there are no other breaks, | ||
608 | // this implies that the type of the block will be | ||
609 | // `!`). | ||
610 | if self.diverges.is_always() { | ||
611 | // we don't even make an attempt at coercion | ||
612 | self.table.new_maybe_never_type_var() | ||
613 | } else { | ||
614 | self.coerce(&Ty::unit(), expected.coercion_target()); | ||
615 | Ty::unit() | ||
616 | } | ||
559 | }; | 617 | }; |
560 | if diverges { | 618 | ty |
561 | Ty::simple(TypeCtor::Never) | ||
562 | } else { | ||
563 | ty | ||
564 | } | ||
565 | } | 619 | } |
566 | 620 | ||
567 | fn infer_method_call( | 621 | fn infer_method_call( |
diff --git a/crates/ra_hir_ty/src/lib.rs b/crates/ra_hir_ty/src/lib.rs index a6f56c661..e8f3482fe 100644 --- a/crates/ra_hir_ty/src/lib.rs +++ b/crates/ra_hir_ty/src/lib.rs | |||
@@ -683,6 +683,12 @@ impl Ty { | |||
683 | pub fn unit() -> Self { | 683 | pub fn unit() -> Self { |
684 | Ty::apply(TypeCtor::Tuple { cardinality: 0 }, Substs::empty()) | 684 | Ty::apply(TypeCtor::Tuple { cardinality: 0 }, Substs::empty()) |
685 | } | 685 | } |
686 | pub fn fn_ptr(sig: FnSig) -> Self { | ||
687 | Ty::apply( | ||
688 | TypeCtor::FnPtr { num_args: sig.params().len() as u16 }, | ||
689 | Substs(sig.params_and_return), | ||
690 | ) | ||
691 | } | ||
686 | 692 | ||
687 | pub fn as_reference(&self) -> Option<(&Ty, Mutability)> { | 693 | pub fn as_reference(&self) -> Option<(&Ty, Mutability)> { |
688 | match self { | 694 | match self { |
@@ -730,6 +736,10 @@ impl Ty { | |||
730 | } | 736 | } |
731 | } | 737 | } |
732 | 738 | ||
739 | pub fn is_never(&self) -> bool { | ||
740 | matches!(self, Ty::Apply(ApplicationTy { ctor: TypeCtor::Never, .. })) | ||
741 | } | ||
742 | |||
733 | /// If this is a `dyn Trait` type, this returns the `Trait` part. | 743 | /// If this is a `dyn Trait` type, this returns the `Trait` part. |
734 | pub fn dyn_trait_ref(&self) -> Option<&TraitRef> { | 744 | pub fn dyn_trait_ref(&self) -> Option<&TraitRef> { |
735 | match self { | 745 | match self { |
diff --git a/crates/ra_hir_ty/src/marks.rs b/crates/ra_hir_ty/src/marks.rs index de5cb1d6b..a39740143 100644 --- a/crates/ra_hir_ty/src/marks.rs +++ b/crates/ra_hir_ty/src/marks.rs | |||
@@ -7,5 +7,6 @@ test_utils::marks!( | |||
7 | impl_self_type_match_without_receiver | 7 | impl_self_type_match_without_receiver |
8 | match_ergonomics_ref | 8 | match_ergonomics_ref |
9 | coerce_merge_fail_fallback | 9 | coerce_merge_fail_fallback |
10 | coerce_fn_reification | ||
10 | trait_self_implements_self | 11 | trait_self_implements_self |
11 | ); | 12 | ); |
diff --git a/crates/ra_hir_ty/src/tests.rs b/crates/ra_hir_ty/src/tests.rs index d60732e19..1fe05c70c 100644 --- a/crates/ra_hir_ty/src/tests.rs +++ b/crates/ra_hir_ty/src/tests.rs | |||
@@ -6,6 +6,7 @@ mod patterns; | |||
6 | mod traits; | 6 | mod traits; |
7 | mod method_resolution; | 7 | mod method_resolution; |
8 | mod macros; | 8 | mod macros; |
9 | mod display_source_code; | ||
9 | 10 | ||
10 | use std::sync::Arc; | 11 | use std::sync::Arc; |
11 | 12 | ||
@@ -16,7 +17,7 @@ use hir_def::{ | |||
16 | item_scope::ItemScope, | 17 | item_scope::ItemScope, |
17 | keys, | 18 | keys, |
18 | nameres::CrateDefMap, | 19 | nameres::CrateDefMap, |
19 | AssocItemId, DefWithBodyId, LocalModuleId, Lookup, ModuleDefId, | 20 | AssocItemId, DefWithBodyId, LocalModuleId, Lookup, ModuleDefId, ModuleId, |
20 | }; | 21 | }; |
21 | use hir_expand::{db::AstDatabase, InFile}; | 22 | use hir_expand::{db::AstDatabase, InFile}; |
22 | use insta::assert_snapshot; | 23 | use insta::assert_snapshot; |
@@ -37,6 +38,18 @@ use crate::{ | |||
37 | // update the snapshots. | 38 | // update the snapshots. |
38 | 39 | ||
39 | fn type_at_pos(db: &TestDB, pos: FilePosition) -> String { | 40 | fn type_at_pos(db: &TestDB, pos: FilePosition) -> String { |
41 | type_at_pos_displayed(db, pos, |ty, _| ty.display(db).to_string()) | ||
42 | } | ||
43 | |||
44 | fn displayed_source_at_pos(db: &TestDB, pos: FilePosition) -> String { | ||
45 | type_at_pos_displayed(db, pos, |ty, module_id| ty.display_source_code(db, module_id).unwrap()) | ||
46 | } | ||
47 | |||
48 | fn type_at_pos_displayed( | ||
49 | db: &TestDB, | ||
50 | pos: FilePosition, | ||
51 | display_fn: impl FnOnce(&Ty, ModuleId) -> String, | ||
52 | ) -> String { | ||
40 | let file = db.parse(pos.file_id).ok().unwrap(); | 53 | let file = db.parse(pos.file_id).ok().unwrap(); |
41 | let expr = algo::find_node_at_offset::<ast::Expr>(file.syntax(), pos.offset).unwrap(); | 54 | let expr = algo::find_node_at_offset::<ast::Expr>(file.syntax(), pos.offset).unwrap(); |
42 | let fn_def = expr.syntax().ancestors().find_map(ast::FnDef::cast).unwrap(); | 55 | let fn_def = expr.syntax().ancestors().find_map(ast::FnDef::cast).unwrap(); |
@@ -49,7 +62,7 @@ fn type_at_pos(db: &TestDB, pos: FilePosition) -> String { | |||
49 | if let Some(expr_id) = source_map.node_expr(InFile::new(pos.file_id.into(), &expr)) { | 62 | if let Some(expr_id) = source_map.node_expr(InFile::new(pos.file_id.into(), &expr)) { |
50 | let infer = db.infer(func.into()); | 63 | let infer = db.infer(func.into()); |
51 | let ty = &infer[expr_id]; | 64 | let ty = &infer[expr_id]; |
52 | return ty.display(db).to_string(); | 65 | return display_fn(ty, module); |
53 | } | 66 | } |
54 | panic!("Can't find expression") | 67 | panic!("Can't find expression") |
55 | } | 68 | } |
@@ -518,3 +531,21 @@ fn missing_record_pat_field_no_diagnostic_if_not_exhaustive() { | |||
518 | 531 | ||
519 | assert_snapshot!(diagnostics, @""); | 532 | assert_snapshot!(diagnostics, @""); |
520 | } | 533 | } |
534 | |||
535 | #[test] | ||
536 | fn break_outside_of_loop() { | ||
537 | let diagnostics = TestDB::with_files( | ||
538 | r" | ||
539 | //- /lib.rs | ||
540 | fn foo() { | ||
541 | break; | ||
542 | } | ||
543 | ", | ||
544 | ) | ||
545 | .diagnostics() | ||
546 | .0; | ||
547 | |||
548 | assert_snapshot!(diagnostics, @r###""break": break outside of loop | ||
549 | "### | ||
550 | ); | ||
551 | } | ||
diff --git a/crates/ra_hir_ty/src/tests/coercion.rs b/crates/ra_hir_ty/src/tests/coercion.rs index e6fb3e123..6dc4b2cd1 100644 --- a/crates/ra_hir_ty/src/tests/coercion.rs +++ b/crates/ra_hir_ty/src/tests/coercion.rs | |||
@@ -384,7 +384,7 @@ fn foo() -> u32 { | |||
384 | } | 384 | } |
385 | "#, true), | 385 | "#, true), |
386 | @r###" | 386 | @r###" |
387 | 17..40 '{ ...own; }': ! | 387 | 17..40 '{ ...own; }': u32 |
388 | 23..37 'return unknown': ! | 388 | 23..37 'return unknown': ! |
389 | 30..37 'unknown': u32 | 389 | 30..37 'unknown': u32 |
390 | "### | 390 | "### |
@@ -514,7 +514,7 @@ fn foo() { | |||
514 | 27..103 '{ ... }': &u32 | 514 | 27..103 '{ ... }': &u32 |
515 | 37..82 'if tru... }': () | 515 | 37..82 'if tru... }': () |
516 | 40..44 'true': bool | 516 | 40..44 'true': bool |
517 | 45..82 '{ ... }': ! | 517 | 45..82 '{ ... }': () |
518 | 59..71 'return &1u32': ! | 518 | 59..71 'return &1u32': ! |
519 | 66..71 '&1u32': &u32 | 519 | 66..71 '&1u32': &u32 |
520 | 67..71 '1u32': u32 | 520 | 67..71 '1u32': u32 |
@@ -546,6 +546,48 @@ fn test() { | |||
546 | } | 546 | } |
547 | 547 | ||
548 | #[test] | 548 | #[test] |
549 | fn coerce_fn_items_in_match_arms() { | ||
550 | covers!(coerce_fn_reification); | ||
551 | assert_snapshot!( | ||
552 | infer_with_mismatches(r#" | ||
553 | fn foo1(x: u32) -> isize { 1 } | ||
554 | fn foo2(x: u32) -> isize { 2 } | ||
555 | fn foo3(x: u32) -> isize { 3 } | ||
556 | fn test() { | ||
557 | let x = match 1 { | ||
558 | 1 => foo1, | ||
559 | 2 => foo2, | ||
560 | _ => foo3, | ||
561 | }; | ||
562 | } | ||
563 | "#, true), | ||
564 | @r###" | ||
565 | 9..10 'x': u32 | ||
566 | 26..31 '{ 1 }': isize | ||
567 | 28..29 '1': isize | ||
568 | 40..41 'x': u32 | ||
569 | 57..62 '{ 2 }': isize | ||
570 | 59..60 '2': isize | ||
571 | 71..72 'x': u32 | ||
572 | 88..93 '{ 3 }': isize | ||
573 | 90..91 '3': isize | ||
574 | 104..193 '{ ... }; }': () | ||
575 | 114..115 'x': fn(u32) -> isize | ||
576 | 118..190 'match ... }': fn(u32) -> isize | ||
577 | 124..125 '1': i32 | ||
578 | 136..137 '1': i32 | ||
579 | 136..137 '1': i32 | ||
580 | 141..145 'foo1': fn foo1(u32) -> isize | ||
581 | 155..156 '2': i32 | ||
582 | 155..156 '2': i32 | ||
583 | 160..164 'foo2': fn foo2(u32) -> isize | ||
584 | 174..175 '_': i32 | ||
585 | 179..183 'foo3': fn foo3(u32) -> isize | ||
586 | "### | ||
587 | ); | ||
588 | } | ||
589 | |||
590 | #[test] | ||
549 | fn coerce_closure_to_fn_ptr() { | 591 | fn coerce_closure_to_fn_ptr() { |
550 | assert_snapshot!( | 592 | assert_snapshot!( |
551 | infer_with_mismatches(r#" | 593 | infer_with_mismatches(r#" |
diff --git a/crates/ra_hir_ty/src/tests/display_source_code.rs b/crates/ra_hir_ty/src/tests/display_source_code.rs new file mode 100644 index 000000000..ca1748615 --- /dev/null +++ b/crates/ra_hir_ty/src/tests/display_source_code.rs | |||
@@ -0,0 +1,23 @@ | |||
1 | use super::displayed_source_at_pos; | ||
2 | use crate::test_db::TestDB; | ||
3 | use ra_db::fixture::WithFixture; | ||
4 | |||
5 | #[test] | ||
6 | fn qualify_path_to_submodule() { | ||
7 | let (db, pos) = TestDB::with_position( | ||
8 | r#" | ||
9 | //- /main.rs | ||
10 | |||
11 | mod foo { | ||
12 | pub struct Foo; | ||
13 | } | ||
14 | |||
15 | fn bar() { | ||
16 | let foo: foo::Foo = foo::Foo; | ||
17 | foo<|> | ||
18 | } | ||
19 | |||
20 | "#, | ||
21 | ); | ||
22 | assert_eq!("foo::Foo", displayed_source_at_pos(&db, pos)); | ||
23 | } | ||
diff --git a/crates/ra_hir_ty/src/tests/macros.rs b/crates/ra_hir_ty/src/tests/macros.rs index 07398ddcc..4c6099aa2 100644 --- a/crates/ra_hir_ty/src/tests/macros.rs +++ b/crates/ra_hir_ty/src/tests/macros.rs | |||
@@ -197,7 +197,7 @@ fn spam() { | |||
197 | !0..6 '1isize': isize | 197 | !0..6 '1isize': isize |
198 | !0..6 '1isize': isize | 198 | !0..6 '1isize': isize |
199 | !0..6 '1isize': isize | 199 | !0..6 '1isize': isize |
200 | 54..457 '{ ...!(); }': ! | 200 | 54..457 '{ ...!(); }': () |
201 | 88..109 'spam!(...am!())': {unknown} | 201 | 88..109 'spam!(...am!())': {unknown} |
202 | 115..134 'for _ ...!() {}': () | 202 | 115..134 'for _ ...!() {}': () |
203 | 119..120 '_': {unknown} | 203 | 119..120 '_': {unknown} |
diff --git a/crates/ra_hir_ty/src/tests/method_resolution.rs b/crates/ra_hir_ty/src/tests/method_resolution.rs index ab87f598a..67f964ab5 100644 --- a/crates/ra_hir_ty/src/tests/method_resolution.rs +++ b/crates/ra_hir_ty/src/tests/method_resolution.rs | |||
@@ -17,8 +17,8 @@ impl<T> [T] { | |||
17 | #[lang = "slice_alloc"] | 17 | #[lang = "slice_alloc"] |
18 | impl<T> [T] {} | 18 | impl<T> [T] {} |
19 | 19 | ||
20 | fn test() { | 20 | fn test(x: &[u8]) { |
21 | <[_]>::foo(b"foo"); | 21 | <[_]>::foo(x); |
22 | } | 22 | } |
23 | "#), | 23 | "#), |
24 | @r###" | 24 | @r###" |
@@ -26,10 +26,11 @@ fn test() { | |||
26 | 56..79 '{ ... }': T | 26 | 56..79 '{ ... }': T |
27 | 66..73 'loop {}': ! | 27 | 66..73 'loop {}': ! |
28 | 71..73 '{}': () | 28 | 71..73 '{}': () |
29 | 133..160 '{ ...o"); }': () | 29 | 131..132 'x': &[u8] |
30 | 139..149 '<[_]>::foo': fn foo<u8>(&[u8]) -> u8 | 30 | 141..163 '{ ...(x); }': () |
31 | 139..157 '<[_]>:..."foo")': u8 | 31 | 147..157 '<[_]>::foo': fn foo<u8>(&[u8]) -> u8 |
32 | 150..156 'b"foo"': &[u8] | 32 | 147..160 '<[_]>::foo(x)': u8 |
33 | 158..159 'x': &[u8] | ||
33 | "### | 34 | "### |
34 | ); | 35 | ); |
35 | } | 36 | } |
diff --git a/crates/ra_hir_ty/src/tests/never_type.rs b/crates/ra_hir_ty/src/tests/never_type.rs index a77209480..082c47208 100644 --- a/crates/ra_hir_ty/src/tests/never_type.rs +++ b/crates/ra_hir_ty/src/tests/never_type.rs | |||
@@ -1,4 +1,6 @@ | |||
1 | use super::type_at; | 1 | use insta::assert_snapshot; |
2 | |||
3 | use super::{infer_with_mismatches, type_at}; | ||
2 | 4 | ||
3 | #[test] | 5 | #[test] |
4 | fn infer_never1() { | 6 | fn infer_never1() { |
@@ -261,3 +263,176 @@ fn test(a: i32) { | |||
261 | ); | 263 | ); |
262 | assert_eq!(t, "f64"); | 264 | assert_eq!(t, "f64"); |
263 | } | 265 | } |
266 | |||
267 | #[test] | ||
268 | fn diverging_expression_1() { | ||
269 | let t = infer_with_mismatches( | ||
270 | r#" | ||
271 | //- /main.rs | ||
272 | fn test1() { | ||
273 | let x: u32 = return; | ||
274 | } | ||
275 | fn test2() { | ||
276 | let x: u32 = { return; }; | ||
277 | } | ||
278 | fn test3() { | ||
279 | let x: u32 = loop {}; | ||
280 | } | ||
281 | fn test4() { | ||
282 | let x: u32 = { loop {} }; | ||
283 | } | ||
284 | fn test5() { | ||
285 | let x: u32 = { if true { loop {}; } else { loop {}; } }; | ||
286 | } | ||
287 | fn test6() { | ||
288 | let x: u32 = { let y: u32 = { loop {}; }; }; | ||
289 | } | ||
290 | "#, | ||
291 | true, | ||
292 | ); | ||
293 | assert_snapshot!(t, @r###" | ||
294 | 25..53 '{ ...urn; }': () | ||
295 | 35..36 'x': u32 | ||
296 | 44..50 'return': ! | ||
297 | 65..98 '{ ...; }; }': () | ||
298 | 75..76 'x': u32 | ||
299 | 84..95 '{ return; }': u32 | ||
300 | 86..92 'return': ! | ||
301 | 110..139 '{ ... {}; }': () | ||
302 | 120..121 'x': u32 | ||
303 | 129..136 'loop {}': ! | ||
304 | 134..136 '{}': () | ||
305 | 151..184 '{ ...} }; }': () | ||
306 | 161..162 'x': u32 | ||
307 | 170..181 '{ loop {} }': u32 | ||
308 | 172..179 'loop {}': ! | ||
309 | 177..179 '{}': () | ||
310 | 196..260 '{ ...} }; }': () | ||
311 | 206..207 'x': u32 | ||
312 | 215..257 '{ if t...}; } }': u32 | ||
313 | 217..255 'if tru... {}; }': u32 | ||
314 | 220..224 'true': bool | ||
315 | 225..237 '{ loop {}; }': u32 | ||
316 | 227..234 'loop {}': ! | ||
317 | 232..234 '{}': () | ||
318 | 243..255 '{ loop {}; }': u32 | ||
319 | 245..252 'loop {}': ! | ||
320 | 250..252 '{}': () | ||
321 | 272..324 '{ ...; }; }': () | ||
322 | 282..283 'x': u32 | ||
323 | 291..321 '{ let ...; }; }': u32 | ||
324 | 297..298 'y': u32 | ||
325 | 306..318 '{ loop {}; }': u32 | ||
326 | 308..315 'loop {}': ! | ||
327 | 313..315 '{}': () | ||
328 | "###); | ||
329 | } | ||
330 | |||
331 | #[test] | ||
332 | fn diverging_expression_2() { | ||
333 | let t = infer_with_mismatches( | ||
334 | r#" | ||
335 | //- /main.rs | ||
336 | fn test1() { | ||
337 | // should give type mismatch | ||
338 | let x: u32 = { loop {}; "foo" }; | ||
339 | } | ||
340 | "#, | ||
341 | true, | ||
342 | ); | ||
343 | assert_snapshot!(t, @r###" | ||
344 | 25..98 '{ ..." }; }': () | ||
345 | 68..69 'x': u32 | ||
346 | 77..95 '{ loop...foo" }': &str | ||
347 | 79..86 'loop {}': ! | ||
348 | 84..86 '{}': () | ||
349 | 88..93 '"foo"': &str | ||
350 | 77..95: expected u32, got &str | ||
351 | 88..93: expected u32, got &str | ||
352 | "###); | ||
353 | } | ||
354 | |||
355 | #[test] | ||
356 | fn diverging_expression_3_break() { | ||
357 | let t = infer_with_mismatches( | ||
358 | r#" | ||
359 | //- /main.rs | ||
360 | fn test1() { | ||
361 | // should give type mismatch | ||
362 | let x: u32 = { loop { break; } }; | ||
363 | } | ||
364 | fn test2() { | ||
365 | // should give type mismatch | ||
366 | let x: u32 = { for a in b { break; }; }; | ||
367 | // should give type mismatch as well | ||
368 | let x: u32 = { for a in b {}; }; | ||
369 | // should give type mismatch as well | ||
370 | let x: u32 = { for a in b { return; }; }; | ||
371 | } | ||
372 | fn test3() { | ||
373 | // should give type mismatch | ||
374 | let x: u32 = { while true { break; }; }; | ||
375 | // should give type mismatch as well -- there's an implicit break, even if it's never hit | ||
376 | let x: u32 = { while true {}; }; | ||
377 | // should give type mismatch as well | ||
378 | let x: u32 = { while true { return; }; }; | ||
379 | } | ||
380 | "#, | ||
381 | true, | ||
382 | ); | ||
383 | assert_snapshot!(t, @r###" | ||
384 | 25..99 '{ ...} }; }': () | ||
385 | 68..69 'x': u32 | ||
386 | 77..96 '{ loop...k; } }': () | ||
387 | 79..94 'loop { break; }': () | ||
388 | 84..94 '{ break; }': () | ||
389 | 86..91 'break': ! | ||
390 | 77..96: expected u32, got () | ||
391 | 79..94: expected u32, got () | ||
392 | 111..357 '{ ...; }; }': () | ||
393 | 154..155 'x': u32 | ||
394 | 163..189 '{ for ...; }; }': () | ||
395 | 165..186 'for a ...eak; }': () | ||
396 | 169..170 'a': {unknown} | ||
397 | 174..175 'b': {unknown} | ||
398 | 176..186 '{ break; }': () | ||
399 | 178..183 'break': ! | ||
400 | 240..241 'x': u32 | ||
401 | 249..267 '{ for ... {}; }': () | ||
402 | 251..264 'for a in b {}': () | ||
403 | 255..256 'a': {unknown} | ||
404 | 260..261 'b': {unknown} | ||
405 | 262..264 '{}': () | ||
406 | 318..319 'x': u32 | ||
407 | 327..354 '{ for ...; }; }': () | ||
408 | 329..351 'for a ...urn; }': () | ||
409 | 333..334 'a': {unknown} | ||
410 | 338..339 'b': {unknown} | ||
411 | 340..351 '{ return; }': () | ||
412 | 342..348 'return': ! | ||
413 | 163..189: expected u32, got () | ||
414 | 249..267: expected u32, got () | ||
415 | 327..354: expected u32, got () | ||
416 | 369..668 '{ ...; }; }': () | ||
417 | 412..413 'x': u32 | ||
418 | 421..447 '{ whil...; }; }': () | ||
419 | 423..444 'while ...eak; }': () | ||
420 | 429..433 'true': bool | ||
421 | 434..444 '{ break; }': () | ||
422 | 436..441 'break': ! | ||
423 | 551..552 'x': u32 | ||
424 | 560..578 '{ whil... {}; }': () | ||
425 | 562..575 'while true {}': () | ||
426 | 568..572 'true': bool | ||
427 | 573..575 '{}': () | ||
428 | 629..630 'x': u32 | ||
429 | 638..665 '{ whil...; }; }': () | ||
430 | 640..662 'while ...urn; }': () | ||
431 | 646..650 'true': bool | ||
432 | 651..662 '{ return; }': () | ||
433 | 653..659 'return': ! | ||
434 | 421..447: expected u32, got () | ||
435 | 560..578: expected u32, got () | ||
436 | 638..665: expected u32, got () | ||
437 | "###); | ||
438 | } | ||
diff --git a/crates/ra_hir_ty/src/tests/simple.rs b/crates/ra_hir_ty/src/tests/simple.rs index 3d3088965..3820175f6 100644 --- a/crates/ra_hir_ty/src/tests/simple.rs +++ b/crates/ra_hir_ty/src/tests/simple.rs | |||
@@ -179,7 +179,7 @@ fn test(a: u32, b: isize, c: !, d: &str) { | |||
179 | 17..18 'b': isize | 179 | 17..18 'b': isize |
180 | 27..28 'c': ! | 180 | 27..28 'c': ! |
181 | 33..34 'd': &str | 181 | 33..34 'd': &str |
182 | 42..121 '{ ...f32; }': ! | 182 | 42..121 '{ ...f32; }': () |
183 | 48..49 'a': u32 | 183 | 48..49 'a': u32 |
184 | 55..56 'b': isize | 184 | 55..56 'b': isize |
185 | 62..63 'c': ! | 185 | 62..63 'c': ! |
@@ -414,7 +414,7 @@ fn test() { | |||
414 | 27..31 '5f32': f32 | 414 | 27..31 '5f32': f32 |
415 | 37..41 '5f64': f64 | 415 | 37..41 '5f64': f64 |
416 | 47..54 '"hello"': &str | 416 | 47..54 '"hello"': &str |
417 | 60..68 'b"bytes"': &[u8] | 417 | 60..68 'b"bytes"': &[u8; _] |
418 | 74..77 ''c'': char | 418 | 74..77 ''c'': char |
419 | 83..87 'b'b'': u8 | 419 | 83..87 'b'b'': u8 |
420 | 93..97 '3.14': f64 | 420 | 93..97 '3.14': f64 |
@@ -422,7 +422,7 @@ fn test() { | |||
422 | 113..118 'false': bool | 422 | 113..118 'false': bool |
423 | 124..128 'true': bool | 423 | 124..128 'true': bool |
424 | 134..202 'r#" ... "#': &str | 424 | 134..202 'r#" ... "#': &str |
425 | 208..218 'br#"yolo"#': &[u8] | 425 | 208..218 'br#"yolo"#': &[u8; _] |
426 | "### | 426 | "### |
427 | ); | 427 | ); |
428 | } | 428 | } |
@@ -935,7 +935,7 @@ fn foo() { | |||
935 | 29..33 'true': bool | 935 | 29..33 'true': bool |
936 | 34..51 '{ ... }': i32 | 936 | 34..51 '{ ... }': i32 |
937 | 44..45 '1': i32 | 937 | 44..45 '1': i32 |
938 | 57..80 '{ ... }': ! | 938 | 57..80 '{ ... }': i32 |
939 | 67..73 'return': ! | 939 | 67..73 'return': ! |
940 | 90..93 '_x2': i32 | 940 | 90..93 '_x2': i32 |
941 | 96..149 'if tru... }': i32 | 941 | 96..149 'if tru... }': i32 |
@@ -951,7 +951,7 @@ fn foo() { | |||
951 | 186..190 'true': bool | 951 | 186..190 'true': bool |
952 | 194..195 '3': i32 | 952 | 194..195 '3': i32 |
953 | 205..206 '_': bool | 953 | 205..206 '_': bool |
954 | 210..241 '{ ... }': ! | 954 | 210..241 '{ ... }': i32 |
955 | 224..230 'return': ! | 955 | 224..230 'return': ! |
956 | 257..260 '_x4': i32 | 956 | 257..260 '_x4': i32 |
957 | 263..320 'match ... }': i32 | 957 | 263..320 'match ... }': i32 |
@@ -1687,7 +1687,7 @@ fn foo() -> u32 { | |||
1687 | 17..59 '{ ...; }; }': () | 1687 | 17..59 '{ ...; }; }': () |
1688 | 27..28 'x': || -> usize | 1688 | 27..28 'x': || -> usize |
1689 | 31..56 '|| -> ...n 1; }': || -> usize | 1689 | 31..56 '|| -> ...n 1; }': || -> usize |
1690 | 43..56 '{ return 1; }': ! | 1690 | 43..56 '{ return 1; }': usize |
1691 | 45..53 'return 1': ! | 1691 | 45..53 'return 1': ! |
1692 | 52..53 '1': usize | 1692 | 52..53 '1': usize |
1693 | "### | 1693 | "### |
@@ -1706,7 +1706,7 @@ fn foo() -> u32 { | |||
1706 | 17..48 '{ ...; }; }': () | 1706 | 17..48 '{ ...; }; }': () |
1707 | 27..28 'x': || -> () | 1707 | 27..28 'x': || -> () |
1708 | 31..45 '|| { return; }': || -> () | 1708 | 31..45 '|| { return; }': || -> () |
1709 | 34..45 '{ return; }': ! | 1709 | 34..45 '{ return; }': () |
1710 | 36..42 'return': ! | 1710 | 36..42 'return': ! |
1711 | "### | 1711 | "### |
1712 | ); | 1712 | ); |
diff --git a/crates/ra_ide/src/display/function_signature.rs b/crates/ra_ide/src/display/function_signature.rs index db3907fe6..9572debd8 100644 --- a/crates/ra_ide/src/display/function_signature.rs +++ b/crates/ra_ide/src/display/function_signature.rs | |||
@@ -1,5 +1,7 @@ | |||
1 | //! FIXME: write short doc here | 1 | //! FIXME: write short doc here |
2 | 2 | ||
3 | // FIXME: this modules relies on strings and AST way too much, and it should be | ||
4 | // rewritten (matklad 2020-05-07) | ||
3 | use std::{ | 5 | use std::{ |
4 | convert::From, | 6 | convert::From, |
5 | fmt::{self, Display}, | 7 | fmt::{self, Display}, |
@@ -82,8 +84,8 @@ impl FunctionSignature { | |||
82 | let ty = field.signature_ty(db); | 84 | let ty = field.signature_ty(db); |
83 | let raw_param = format!("{}", ty.display(db)); | 85 | let raw_param = format!("{}", ty.display(db)); |
84 | 86 | ||
85 | if let Some(param_type) = raw_param.split(':').nth(1) { | 87 | if let Some(param_type) = raw_param.split(':').nth(1).and_then(|it| it.get(1..)) { |
86 | parameter_types.push(param_type[1..].to_string()); | 88 | parameter_types.push(param_type.to_string()); |
87 | } else { | 89 | } else { |
88 | // useful when you have tuple struct | 90 | // useful when you have tuple struct |
89 | parameter_types.push(raw_param.clone()); | 91 | parameter_types.push(raw_param.clone()); |
@@ -127,8 +129,8 @@ impl FunctionSignature { | |||
127 | for field in variant.fields(db).into_iter() { | 129 | for field in variant.fields(db).into_iter() { |
128 | let ty = field.signature_ty(db); | 130 | let ty = field.signature_ty(db); |
129 | let raw_param = format!("{}", ty.display(db)); | 131 | let raw_param = format!("{}", ty.display(db)); |
130 | if let Some(param_type) = raw_param.split(':').nth(1) { | 132 | if let Some(param_type) = raw_param.split(':').nth(1).and_then(|it| it.get(1..)) { |
131 | parameter_types.push(param_type[1..].to_string()); | 133 | parameter_types.push(param_type.to_string()); |
132 | } else { | 134 | } else { |
133 | // The unwrap_or_else is useful when you have tuple | 135 | // The unwrap_or_else is useful when you have tuple |
134 | parameter_types.push(raw_param); | 136 | parameter_types.push(raw_param); |
@@ -195,14 +197,23 @@ impl From<&'_ ast::FnDef> for FunctionSignature { | |||
195 | let raw_param = self_param.syntax().text().to_string(); | 197 | let raw_param = self_param.syntax().text().to_string(); |
196 | 198 | ||
197 | res_types.push( | 199 | res_types.push( |
198 | raw_param.split(':').nth(1).unwrap_or_else(|| " Self")[1..].to_string(), | 200 | raw_param |
201 | .split(':') | ||
202 | .nth(1) | ||
203 | .and_then(|it| it.get(1..)) | ||
204 | .unwrap_or_else(|| "Self") | ||
205 | .to_string(), | ||
199 | ); | 206 | ); |
200 | res.push(raw_param); | 207 | res.push(raw_param); |
201 | } | 208 | } |
202 | 209 | ||
203 | res.extend(param_list.params().map(|param| param.syntax().text().to_string())); | 210 | res.extend(param_list.params().map(|param| param.syntax().text().to_string())); |
204 | res_types.extend(param_list.params().map(|param| { | 211 | res_types.extend(param_list.params().map(|param| { |
205 | param.syntax().text().to_string().split(':').nth(1).unwrap()[1..].to_string() | 212 | let param_text = param.syntax().text().to_string(); |
213 | match param_text.split(':').nth(1).and_then(|it| it.get(1..)) { | ||
214 | Some(it) => it.to_string(), | ||
215 | None => param_text, | ||
216 | } | ||
206 | })); | 217 | })); |
207 | } | 218 | } |
208 | (has_self_param, res, res_types) | 219 | (has_self_param, res, res_types) |
diff --git a/crates/ra_ide/src/lib.rs b/crates/ra_ide/src/lib.rs index 737f87109..915199bd8 100644 --- a/crates/ra_ide/src/lib.rs +++ b/crates/ra_ide/src/lib.rs | |||
@@ -472,12 +472,12 @@ impl Analysis { | |||
472 | /// position. | 472 | /// position. |
473 | pub fn assists(&self, frange: FileRange) -> Cancelable<Vec<Assist>> { | 473 | pub fn assists(&self, frange: FileRange) -> Cancelable<Vec<Assist>> { |
474 | self.with_db(|db| { | 474 | self.with_db(|db| { |
475 | ra_assists::resolved_assists(db, frange) | 475 | ra_assists::Assist::resolved(db, frange) |
476 | .into_iter() | 476 | .into_iter() |
477 | .map(|assist| Assist { | 477 | .map(|assist| Assist { |
478 | id: assist.label.id, | 478 | id: assist.assist.id, |
479 | label: assist.label.label, | 479 | label: assist.assist.label, |
480 | group_label: assist.label.group.map(|it| it.0), | 480 | group_label: assist.assist.group.map(|it| it.0), |
481 | source_change: assist.source_change, | 481 | source_change: assist.source_change, |
482 | }) | 482 | }) |
483 | .collect() | 483 | .collect() |
diff --git a/crates/ra_ide/src/references/rename.rs b/crates/ra_ide/src/references/rename.rs index 0398d53bc..2cbb82c1a 100644 --- a/crates/ra_ide/src/references/rename.rs +++ b/crates/ra_ide/src/references/rename.rs | |||
@@ -712,6 +712,68 @@ mod tests { | |||
712 | "###); | 712 | "###); |
713 | } | 713 | } |
714 | 714 | ||
715 | #[test] | ||
716 | fn test_enum_variant_from_module_1() { | ||
717 | test_rename( | ||
718 | r#" | ||
719 | mod foo { | ||
720 | pub enum Foo { | ||
721 | Bar<|>, | ||
722 | } | ||
723 | } | ||
724 | |||
725 | fn func(f: foo::Foo) { | ||
726 | match f { | ||
727 | foo::Foo::Bar => {} | ||
728 | } | ||
729 | } | ||
730 | "#, | ||
731 | "Baz", | ||
732 | r#" | ||
733 | mod foo { | ||
734 | pub enum Foo { | ||
735 | Baz, | ||
736 | } | ||
737 | } | ||
738 | |||
739 | fn func(f: foo::Foo) { | ||
740 | match f { | ||
741 | foo::Foo::Baz => {} | ||
742 | } | ||
743 | } | ||
744 | "#, | ||
745 | ); | ||
746 | } | ||
747 | |||
748 | #[test] | ||
749 | fn test_enum_variant_from_module_2() { | ||
750 | test_rename( | ||
751 | r#" | ||
752 | mod foo { | ||
753 | pub struct Foo { | ||
754 | pub bar<|>: uint, | ||
755 | } | ||
756 | } | ||
757 | |||
758 | fn foo(f: foo::Foo) { | ||
759 | let _ = f.bar; | ||
760 | } | ||
761 | "#, | ||
762 | "baz", | ||
763 | r#" | ||
764 | mod foo { | ||
765 | pub struct Foo { | ||
766 | pub baz: uint, | ||
767 | } | ||
768 | } | ||
769 | |||
770 | fn foo(f: foo::Foo) { | ||
771 | let _ = f.baz; | ||
772 | } | ||
773 | "#, | ||
774 | ); | ||
775 | } | ||
776 | |||
715 | fn test_rename(text: &str, new_name: &str, expected: &str) { | 777 | fn test_rename(text: &str, new_name: &str, expected: &str) { |
716 | let (analysis, position) = single_file_with_position(text); | 778 | let (analysis, position) = single_file_with_position(text); |
717 | let source_change = analysis.rename(position, new_name).unwrap(); | 779 | let source_change = analysis.rename(position, new_name).unwrap(); |
diff --git a/crates/ra_ide_db/src/defs.rs b/crates/ra_ide_db/src/defs.rs index 40d0e77b5..f990e3bb9 100644 --- a/crates/ra_ide_db/src/defs.rs +++ b/crates/ra_ide_db/src/defs.rs | |||
@@ -6,7 +6,7 @@ | |||
6 | // FIXME: this badly needs rename/rewrite (matklad, 2020-02-06). | 6 | // FIXME: this badly needs rename/rewrite (matklad, 2020-02-06). |
7 | 7 | ||
8 | use hir::{ | 8 | use hir::{ |
9 | Field, HasVisibility, ImplDef, Local, MacroDef, Module, ModuleDef, Name, PathResolution, | 9 | Adt, Field, HasVisibility, ImplDef, Local, MacroDef, Module, ModuleDef, Name, PathResolution, |
10 | Semantics, TypeParam, Visibility, | 10 | Semantics, TypeParam, Visibility, |
11 | }; | 11 | }; |
12 | use ra_prof::profile; | 12 | use ra_prof::profile; |
@@ -47,7 +47,13 @@ impl Definition { | |||
47 | match self { | 47 | match self { |
48 | Definition::Macro(_) => None, | 48 | Definition::Macro(_) => None, |
49 | Definition::Field(sf) => Some(sf.visibility(db)), | 49 | Definition::Field(sf) => Some(sf.visibility(db)), |
50 | Definition::ModuleDef(def) => module?.visibility_of(db, def), | 50 | Definition::ModuleDef(def) => match def { |
51 | ModuleDef::EnumVariant(id) => { | ||
52 | let parent = id.parent_enum(db); | ||
53 | module?.visibility_of(db, &ModuleDef::Adt(Adt::Enum(parent))) | ||
54 | } | ||
55 | _ => module?.visibility_of(db, def), | ||
56 | }, | ||
51 | Definition::SelfType(_) => None, | 57 | Definition::SelfType(_) => None, |
52 | Definition::Local(_) => None, | 58 | Definition::Local(_) => None, |
53 | Definition::TypeParam(_) => None, | 59 | Definition::TypeParam(_) => None, |
diff --git a/crates/ra_project_model/Cargo.toml b/crates/ra_project_model/Cargo.toml index 5e651fe70..a32a5daab 100644 --- a/crates/ra_project_model/Cargo.toml +++ b/crates/ra_project_model/Cargo.toml | |||
@@ -14,8 +14,9 @@ rustc-hash = "1.1.0" | |||
14 | cargo_metadata = "0.9.1" | 14 | cargo_metadata = "0.9.1" |
15 | 15 | ||
16 | ra_arena = { path = "../ra_arena" } | 16 | ra_arena = { path = "../ra_arena" } |
17 | ra_db = { path = "../ra_db" } | ||
18 | ra_cfg = { path = "../ra_cfg" } | 17 | ra_cfg = { path = "../ra_cfg" } |
18 | ra_db = { path = "../ra_db" } | ||
19 | ra_toolchain = { path = "../ra_toolchain" } | ||
19 | ra_proc_macro = { path = "../ra_proc_macro" } | 20 | ra_proc_macro = { path = "../ra_proc_macro" } |
20 | 21 | ||
21 | serde = { version = "1.0.106", features = ["derive"] } | 22 | serde = { version = "1.0.106", features = ["derive"] } |
diff --git a/crates/ra_project_model/src/cargo_workspace.rs b/crates/ra_project_model/src/cargo_workspace.rs index 59f46a2a0..082af4f96 100644 --- a/crates/ra_project_model/src/cargo_workspace.rs +++ b/crates/ra_project_model/src/cargo_workspace.rs | |||
@@ -1,7 +1,6 @@ | |||
1 | //! FIXME: write short doc here | 1 | //! FIXME: write short doc here |
2 | 2 | ||
3 | use std::{ | 3 | use std::{ |
4 | env, | ||
5 | ffi::OsStr, | 4 | ffi::OsStr, |
6 | ops, | 5 | ops, |
7 | path::{Path, PathBuf}, | 6 | path::{Path, PathBuf}, |
@@ -87,6 +86,7 @@ pub struct PackageData { | |||
87 | pub dependencies: Vec<PackageDependency>, | 86 | pub dependencies: Vec<PackageDependency>, |
88 | pub edition: Edition, | 87 | pub edition: Edition, |
89 | pub features: Vec<String>, | 88 | pub features: Vec<String>, |
89 | pub cfgs: Vec<String>, | ||
90 | pub out_dir: Option<PathBuf>, | 90 | pub out_dir: Option<PathBuf>, |
91 | pub proc_macro_dylib_path: Option<PathBuf>, | 91 | pub proc_macro_dylib_path: Option<PathBuf>, |
92 | } | 92 | } |
@@ -145,12 +145,8 @@ impl CargoWorkspace { | |||
145 | cargo_toml: &Path, | 145 | cargo_toml: &Path, |
146 | cargo_features: &CargoConfig, | 146 | cargo_features: &CargoConfig, |
147 | ) -> Result<CargoWorkspace> { | 147 | ) -> Result<CargoWorkspace> { |
148 | let _ = Command::new(cargo_binary()) | ||
149 | .arg("--version") | ||
150 | .output() | ||
151 | .context("failed to run `cargo --version`, is `cargo` in PATH?")?; | ||
152 | |||
153 | let mut meta = MetadataCommand::new(); | 148 | let mut meta = MetadataCommand::new(); |
149 | meta.cargo_path(ra_toolchain::cargo()); | ||
154 | meta.manifest_path(cargo_toml); | 150 | meta.manifest_path(cargo_toml); |
155 | if cargo_features.all_features { | 151 | if cargo_features.all_features { |
156 | meta.features(CargoOpt::AllFeatures); | 152 | meta.features(CargoOpt::AllFeatures); |
@@ -172,10 +168,12 @@ impl CargoWorkspace { | |||
172 | })?; | 168 | })?; |
173 | 169 | ||
174 | let mut out_dir_by_id = FxHashMap::default(); | 170 | let mut out_dir_by_id = FxHashMap::default(); |
171 | let mut cfgs = FxHashMap::default(); | ||
175 | let mut proc_macro_dylib_paths = FxHashMap::default(); | 172 | let mut proc_macro_dylib_paths = FxHashMap::default(); |
176 | if cargo_features.load_out_dirs_from_check { | 173 | if cargo_features.load_out_dirs_from_check { |
177 | let resources = load_extern_resources(cargo_toml, cargo_features)?; | 174 | let resources = load_extern_resources(cargo_toml, cargo_features)?; |
178 | out_dir_by_id = resources.out_dirs; | 175 | out_dir_by_id = resources.out_dirs; |
176 | cfgs = resources.cfgs; | ||
179 | proc_macro_dylib_paths = resources.proc_dylib_paths; | 177 | proc_macro_dylib_paths = resources.proc_dylib_paths; |
180 | } | 178 | } |
181 | 179 | ||
@@ -201,6 +199,7 @@ impl CargoWorkspace { | |||
201 | edition, | 199 | edition, |
202 | dependencies: Vec::new(), | 200 | dependencies: Vec::new(), |
203 | features: Vec::new(), | 201 | features: Vec::new(), |
202 | cfgs: cfgs.get(&id).cloned().unwrap_or_default(), | ||
204 | out_dir: out_dir_by_id.get(&id).cloned(), | 203 | out_dir: out_dir_by_id.get(&id).cloned(), |
205 | proc_macro_dylib_path: proc_macro_dylib_paths.get(&id).cloned(), | 204 | proc_macro_dylib_path: proc_macro_dylib_paths.get(&id).cloned(), |
206 | }); | 205 | }); |
@@ -282,13 +281,14 @@ impl CargoWorkspace { | |||
282 | pub struct ExternResources { | 281 | pub struct ExternResources { |
283 | out_dirs: FxHashMap<PackageId, PathBuf>, | 282 | out_dirs: FxHashMap<PackageId, PathBuf>, |
284 | proc_dylib_paths: FxHashMap<PackageId, PathBuf>, | 283 | proc_dylib_paths: FxHashMap<PackageId, PathBuf>, |
284 | cfgs: FxHashMap<PackageId, Vec<String>>, | ||
285 | } | 285 | } |
286 | 286 | ||
287 | pub fn load_extern_resources( | 287 | pub fn load_extern_resources( |
288 | cargo_toml: &Path, | 288 | cargo_toml: &Path, |
289 | cargo_features: &CargoConfig, | 289 | cargo_features: &CargoConfig, |
290 | ) -> Result<ExternResources> { | 290 | ) -> Result<ExternResources> { |
291 | let mut cmd = Command::new(cargo_binary()); | 291 | let mut cmd = Command::new(ra_toolchain::cargo()); |
292 | cmd.args(&["check", "--message-format=json", "--manifest-path"]).arg(cargo_toml); | 292 | cmd.args(&["check", "--message-format=json", "--manifest-path"]).arg(cargo_toml); |
293 | if cargo_features.all_features { | 293 | if cargo_features.all_features { |
294 | cmd.arg("--all-features"); | 294 | cmd.arg("--all-features"); |
@@ -307,8 +307,14 @@ pub fn load_extern_resources( | |||
307 | for message in cargo_metadata::parse_messages(output.stdout.as_slice()) { | 307 | for message in cargo_metadata::parse_messages(output.stdout.as_slice()) { |
308 | if let Ok(message) = message { | 308 | if let Ok(message) = message { |
309 | match message { | 309 | match message { |
310 | Message::BuildScriptExecuted(BuildScript { package_id, out_dir, .. }) => { | 310 | Message::BuildScriptExecuted(BuildScript { package_id, out_dir, cfgs, .. }) => { |
311 | res.out_dirs.insert(package_id, out_dir); | 311 | res.out_dirs.insert(package_id.clone(), out_dir); |
312 | res.cfgs.insert( | ||
313 | package_id, | ||
314 | // FIXME: Current `cargo_metadata` uses `PathBuf` instead of `String`, | ||
315 | // change when https://github.com/oli-obk/cargo_metadata/pulls/112 reaches crates.io | ||
316 | cfgs.iter().filter_map(|c| c.to_str().map(|s| s.to_owned())).collect(), | ||
317 | ); | ||
312 | } | 318 | } |
313 | 319 | ||
314 | Message::CompilerArtifact(message) => { | 320 | Message::CompilerArtifact(message) => { |
@@ -336,7 +342,3 @@ fn is_dylib(path: &Path) -> bool { | |||
336 | Some(ext) => matches!(ext.as_str(), "dll" | "dylib" | "so"), | 342 | Some(ext) => matches!(ext.as_str(), "dll" | "dylib" | "so"), |
337 | } | 343 | } |
338 | } | 344 | } |
339 | |||
340 | fn cargo_binary() -> String { | ||
341 | env::var("CARGO").unwrap_or_else(|_| "cargo".to_string()) | ||
342 | } | ||
diff --git a/crates/ra_project_model/src/lib.rs b/crates/ra_project_model/src/lib.rs index c2b33c1dc..4f098b706 100644 --- a/crates/ra_project_model/src/lib.rs +++ b/crates/ra_project_model/src/lib.rs | |||
@@ -8,7 +8,7 @@ use std::{ | |||
8 | fs::{read_dir, File, ReadDir}, | 8 | fs::{read_dir, File, ReadDir}, |
9 | io::{self, BufReader}, | 9 | io::{self, BufReader}, |
10 | path::{Path, PathBuf}, | 10 | path::{Path, PathBuf}, |
11 | process::Command, | 11 | process::{Command, Output}, |
12 | }; | 12 | }; |
13 | 13 | ||
14 | use anyhow::{bail, Context, Result}; | 14 | use anyhow::{bail, Context, Result}; |
@@ -88,46 +88,28 @@ impl ProjectRoot { | |||
88 | } | 88 | } |
89 | 89 | ||
90 | pub fn discover(path: &Path) -> io::Result<Vec<ProjectRoot>> { | 90 | pub fn discover(path: &Path) -> io::Result<Vec<ProjectRoot>> { |
91 | if let Some(project_json) = find_rust_project_json(path) { | 91 | if let Some(project_json) = find_in_parent_dirs(path, "rust-project.json") { |
92 | return Ok(vec![ProjectRoot::ProjectJson(project_json)]); | 92 | return Ok(vec![ProjectRoot::ProjectJson(project_json)]); |
93 | } | 93 | } |
94 | return find_cargo_toml(path) | 94 | return find_cargo_toml(path) |
95 | .map(|paths| paths.into_iter().map(ProjectRoot::CargoToml).collect()); | 95 | .map(|paths| paths.into_iter().map(ProjectRoot::CargoToml).collect()); |
96 | 96 | ||
97 | fn find_rust_project_json(path: &Path) -> Option<PathBuf> { | ||
98 | if path.ends_with("rust-project.json") { | ||
99 | return Some(path.to_path_buf()); | ||
100 | } | ||
101 | |||
102 | let mut curr = Some(path); | ||
103 | while let Some(path) = curr { | ||
104 | let candidate = path.join("rust-project.json"); | ||
105 | if candidate.exists() { | ||
106 | return Some(candidate); | ||
107 | } | ||
108 | curr = path.parent(); | ||
109 | } | ||
110 | |||
111 | None | ||
112 | } | ||
113 | |||
114 | fn find_cargo_toml(path: &Path) -> io::Result<Vec<PathBuf>> { | 97 | fn find_cargo_toml(path: &Path) -> io::Result<Vec<PathBuf>> { |
115 | if path.ends_with("Cargo.toml") { | 98 | match find_in_parent_dirs(path, "Cargo.toml") { |
116 | return Ok(vec![path.to_path_buf()]); | 99 | Some(it) => Ok(vec![it]), |
100 | None => Ok(find_cargo_toml_in_child_dir(read_dir(path)?)), | ||
117 | } | 101 | } |
102 | } | ||
118 | 103 | ||
119 | if let Some(p) = find_cargo_toml_in_parent_dir(path) { | 104 | fn find_in_parent_dirs(path: &Path, target_file_name: &str) -> Option<PathBuf> { |
120 | return Ok(vec![p]); | 105 | if path.ends_with(target_file_name) { |
106 | return Some(path.to_owned()); | ||
121 | } | 107 | } |
122 | 108 | ||
123 | let entities = read_dir(path)?; | ||
124 | Ok(find_cargo_toml_in_child_dir(entities)) | ||
125 | } | ||
126 | |||
127 | fn find_cargo_toml_in_parent_dir(path: &Path) -> Option<PathBuf> { | ||
128 | let mut curr = Some(path); | 109 | let mut curr = Some(path); |
110 | |||
129 | while let Some(path) = curr { | 111 | while let Some(path) = curr { |
130 | let candidate = path.join("Cargo.toml"); | 112 | let candidate = path.join(target_file_name); |
131 | if candidate.exists() { | 113 | if candidate.exists() { |
132 | return Some(candidate); | 114 | return Some(candidate); |
133 | } | 115 | } |
@@ -139,14 +121,11 @@ impl ProjectRoot { | |||
139 | 121 | ||
140 | fn find_cargo_toml_in_child_dir(entities: ReadDir) -> Vec<PathBuf> { | 122 | fn find_cargo_toml_in_child_dir(entities: ReadDir) -> Vec<PathBuf> { |
141 | // Only one level down to avoid cycles the easy way and stop a runaway scan with large projects | 123 | // Only one level down to avoid cycles the easy way and stop a runaway scan with large projects |
142 | let mut valid_canditates = vec![]; | 124 | entities |
143 | for entity in entities.filter_map(Result::ok) { | 125 | .filter_map(Result::ok) |
144 | let candidate = entity.path().join("Cargo.toml"); | 126 | .map(|it| it.path().join("Cargo.toml")) |
145 | if candidate.exists() { | 127 | .filter(|it| it.exists()) |
146 | valid_canditates.push(candidate) | 128 | .collect() |
147 | } | ||
148 | } | ||
149 | valid_canditates | ||
150 | } | 129 | } |
151 | } | 130 | } |
152 | } | 131 | } |
@@ -398,7 +377,18 @@ impl ProjectWorkspace { | |||
398 | let edition = cargo[pkg].edition; | 377 | let edition = cargo[pkg].edition; |
399 | let cfg_options = { | 378 | let cfg_options = { |
400 | let mut opts = default_cfg_options.clone(); | 379 | let mut opts = default_cfg_options.clone(); |
401 | opts.insert_features(cargo[pkg].features.iter().map(Into::into)); | 380 | for feature in cargo[pkg].features.iter() { |
381 | opts.insert_key_value("feature".into(), feature.into()); | ||
382 | } | ||
383 | for cfg in cargo[pkg].cfgs.iter() { | ||
384 | match cfg.find('=') { | ||
385 | Some(split) => opts.insert_key_value( | ||
386 | cfg[..split].into(), | ||
387 | cfg[split + 1..].trim_matches('"').into(), | ||
388 | ), | ||
389 | None => opts.insert_atom(cfg.into()), | ||
390 | }; | ||
391 | } | ||
402 | opts | 392 | opts |
403 | }; | 393 | }; |
404 | let mut env = Env::default(); | 394 | let mut env = Env::default(); |
@@ -556,25 +546,18 @@ pub fn get_rustc_cfg_options(target: Option<&String>) -> CfgOptions { | |||
556 | } | 546 | } |
557 | } | 547 | } |
558 | 548 | ||
559 | match (|| -> Result<String> { | 549 | let rustc_cfgs = || -> Result<String> { |
560 | // `cfg(test)` and `cfg(debug_assertion)` are handled outside, so we suppress them here. | 550 | // `cfg(test)` and `cfg(debug_assertion)` are handled outside, so we suppress them here. |
561 | let mut cmd = Command::new("rustc"); | 551 | let mut cmd = Command::new(ra_toolchain::rustc()); |
562 | cmd.args(&["--print", "cfg", "-O"]); | 552 | cmd.args(&["--print", "cfg", "-O"]); |
563 | if let Some(target) = target { | 553 | if let Some(target) = target { |
564 | cmd.args(&["--target", target.as_str()]); | 554 | cmd.args(&["--target", target.as_str()]); |
565 | } | 555 | } |
566 | let output = cmd.output().context("Failed to get output from rustc --print cfg -O")?; | 556 | let output = output(cmd)?; |
567 | if !output.status.success() { | ||
568 | bail!( | ||
569 | "rustc --print cfg -O exited with exit code ({})", | ||
570 | output | ||
571 | .status | ||
572 | .code() | ||
573 | .map_or(String::from("no exit code"), |code| format!("{}", code)) | ||
574 | ); | ||
575 | } | ||
576 | Ok(String::from_utf8(output.stdout)?) | 557 | Ok(String::from_utf8(output.stdout)?) |
577 | })() { | 558 | }(); |
559 | |||
560 | match rustc_cfgs { | ||
578 | Ok(rustc_cfgs) => { | 561 | Ok(rustc_cfgs) => { |
579 | for line in rustc_cfgs.lines() { | 562 | for line in rustc_cfgs.lines() { |
580 | match line.find('=') { | 563 | match line.find('=') { |
@@ -587,8 +570,21 @@ pub fn get_rustc_cfg_options(target: Option<&String>) -> CfgOptions { | |||
587 | } | 570 | } |
588 | } | 571 | } |
589 | } | 572 | } |
590 | Err(e) => log::error!("failed to get rustc cfgs: {}", e), | 573 | Err(e) => log::error!("failed to get rustc cfgs: {:#}", e), |
591 | } | 574 | } |
592 | 575 | ||
593 | cfg_options | 576 | cfg_options |
594 | } | 577 | } |
578 | |||
579 | fn output(mut cmd: Command) -> Result<Output> { | ||
580 | let output = cmd.output().with_context(|| format!("{:?} failed", cmd))?; | ||
581 | if !output.status.success() { | ||
582 | match String::from_utf8(output.stderr) { | ||
583 | Ok(stderr) if !stderr.is_empty() => { | ||
584 | bail!("{:?} failed, {}\nstderr:\n{}", cmd, output.status, stderr) | ||
585 | } | ||
586 | _ => bail!("{:?} failed, {}", cmd, output.status), | ||
587 | } | ||
588 | } | ||
589 | Ok(output) | ||
590 | } | ||
diff --git a/crates/ra_project_model/src/sysroot.rs b/crates/ra_project_model/src/sysroot.rs index 55ff5ad80..a8a196e64 100644 --- a/crates/ra_project_model/src/sysroot.rs +++ b/crates/ra_project_model/src/sysroot.rs | |||
@@ -1,14 +1,16 @@ | |||
1 | //! FIXME: write short doc here | 1 | //! FIXME: write short doc here |
2 | 2 | ||
3 | use anyhow::{bail, Context, Result}; | ||
4 | use std::{ | 3 | use std::{ |
5 | env, ops, | 4 | env, ops, |
6 | path::{Path, PathBuf}, | 5 | path::{Path, PathBuf}, |
7 | process::{Command, Output}, | 6 | process::Command, |
8 | }; | 7 | }; |
9 | 8 | ||
9 | use anyhow::{bail, Result}; | ||
10 | use ra_arena::{Arena, Idx}; | 10 | use ra_arena::{Arena, Idx}; |
11 | 11 | ||
12 | use crate::output; | ||
13 | |||
12 | #[derive(Default, Debug, Clone)] | 14 | #[derive(Default, Debug, Clone)] |
13 | pub struct Sysroot { | 15 | pub struct Sysroot { |
14 | crates: Arena<SysrootCrateData>, | 16 | crates: Arena<SysrootCrateData>, |
@@ -84,43 +86,22 @@ impl Sysroot { | |||
84 | } | 86 | } |
85 | } | 87 | } |
86 | 88 | ||
87 | fn create_command_text(program: &str, args: &[&str]) -> String { | ||
88 | format!("{} {}", program, args.join(" ")) | ||
89 | } | ||
90 | |||
91 | fn run_command_in_cargo_dir(cargo_toml: &Path, program: &str, args: &[&str]) -> Result<Output> { | ||
92 | let output = Command::new(program) | ||
93 | .current_dir(cargo_toml.parent().unwrap()) | ||
94 | .args(args) | ||
95 | .output() | ||
96 | .context(format!("{} failed", create_command_text(program, args)))?; | ||
97 | if !output.status.success() { | ||
98 | match output.status.code() { | ||
99 | Some(code) => bail!( | ||
100 | "failed to run the command: '{}' exited with code {}", | ||
101 | create_command_text(program, args), | ||
102 | code | ||
103 | ), | ||
104 | None => bail!( | ||
105 | "failed to run the command: '{}' terminated by signal", | ||
106 | create_command_text(program, args) | ||
107 | ), | ||
108 | }; | ||
109 | } | ||
110 | Ok(output) | ||
111 | } | ||
112 | |||
113 | fn get_or_install_rust_src(cargo_toml: &Path) -> Result<PathBuf> { | 89 | fn get_or_install_rust_src(cargo_toml: &Path) -> Result<PathBuf> { |
114 | if let Ok(path) = env::var("RUST_SRC_PATH") { | 90 | if let Ok(path) = env::var("RUST_SRC_PATH") { |
115 | return Ok(path.into()); | 91 | return Ok(path.into()); |
116 | } | 92 | } |
117 | let rustc_output = run_command_in_cargo_dir(cargo_toml, "rustc", &["--print", "sysroot"])?; | 93 | let current_dir = cargo_toml.parent().unwrap(); |
94 | let mut rustc = Command::new(ra_toolchain::rustc()); | ||
95 | rustc.current_dir(current_dir).args(&["--print", "sysroot"]); | ||
96 | let rustc_output = output(rustc)?; | ||
118 | let stdout = String::from_utf8(rustc_output.stdout)?; | 97 | let stdout = String::from_utf8(rustc_output.stdout)?; |
119 | let sysroot_path = Path::new(stdout.trim()); | 98 | let sysroot_path = Path::new(stdout.trim()); |
120 | let src_path = sysroot_path.join("lib/rustlib/src/rust/src"); | 99 | let src_path = sysroot_path.join("lib/rustlib/src/rust/src"); |
121 | 100 | ||
122 | if !src_path.exists() { | 101 | if !src_path.exists() { |
123 | run_command_in_cargo_dir(cargo_toml, "rustup", &["component", "add", "rust-src"])?; | 102 | let mut rustup = Command::new(ra_toolchain::rustup()); |
103 | rustup.current_dir(current_dir).args(&["component", "add", "rust-src"]); | ||
104 | let _output = output(rustup)?; | ||
124 | } | 105 | } |
125 | if !src_path.exists() { | 106 | if !src_path.exists() { |
126 | bail!( | 107 | bail!( |
diff --git a/crates/ra_syntax/src/ast/edit.rs b/crates/ra_syntax/src/ast/edit.rs index 3e6dd6061..24a1e1d91 100644 --- a/crates/ra_syntax/src/ast/edit.rs +++ b/crates/ra_syntax/src/ast/edit.rs | |||
@@ -453,11 +453,7 @@ impl IndentLevel { | |||
453 | IndentLevel(0) | 453 | IndentLevel(0) |
454 | } | 454 | } |
455 | 455 | ||
456 | pub fn increase_indent<N: AstNode>(self, node: N) -> N { | 456 | fn increase_indent(self, node: SyntaxNode) -> SyntaxNode { |
457 | N::cast(self._increase_indent(node.syntax().clone())).unwrap() | ||
458 | } | ||
459 | |||
460 | fn _increase_indent(self, node: SyntaxNode) -> SyntaxNode { | ||
461 | let mut rewriter = SyntaxRewriter::default(); | 457 | let mut rewriter = SyntaxRewriter::default(); |
462 | node.descendants_with_tokens() | 458 | node.descendants_with_tokens() |
463 | .filter_map(|el| el.into_token()) | 459 | .filter_map(|el| el.into_token()) |
@@ -478,11 +474,7 @@ impl IndentLevel { | |||
478 | rewriter.rewrite(&node) | 474 | rewriter.rewrite(&node) |
479 | } | 475 | } |
480 | 476 | ||
481 | pub fn decrease_indent<N: AstNode>(self, node: N) -> N { | 477 | fn decrease_indent(self, node: SyntaxNode) -> SyntaxNode { |
482 | N::cast(self._decrease_indent(node.syntax().clone())).unwrap() | ||
483 | } | ||
484 | |||
485 | fn _decrease_indent(self, node: SyntaxNode) -> SyntaxNode { | ||
486 | let mut rewriter = SyntaxRewriter::default(); | 478 | let mut rewriter = SyntaxRewriter::default(); |
487 | node.descendants_with_tokens() | 479 | node.descendants_with_tokens() |
488 | .filter_map(|el| el.into_token()) | 480 | .filter_map(|el| el.into_token()) |
@@ -521,7 +513,7 @@ fn prev_tokens(token: SyntaxToken) -> impl Iterator<Item = SyntaxToken> { | |||
521 | iter::successors(Some(token), |token| token.prev_token()) | 513 | iter::successors(Some(token), |token| token.prev_token()) |
522 | } | 514 | } |
523 | 515 | ||
524 | pub trait AstNodeEdit: AstNode + Sized { | 516 | pub trait AstNodeEdit: AstNode + Clone + Sized { |
525 | #[must_use] | 517 | #[must_use] |
526 | fn insert_children( | 518 | fn insert_children( |
527 | &self, | 519 | &self, |
@@ -558,9 +550,17 @@ pub trait AstNodeEdit: AstNode + Sized { | |||
558 | } | 550 | } |
559 | rewriter.rewrite_ast(self) | 551 | rewriter.rewrite_ast(self) |
560 | } | 552 | } |
553 | #[must_use] | ||
554 | fn indent(&self, indent: IndentLevel) -> Self { | ||
555 | Self::cast(indent.increase_indent(self.syntax().clone())).unwrap() | ||
556 | } | ||
557 | #[must_use] | ||
558 | fn dedent(&self, indent: IndentLevel) -> Self { | ||
559 | Self::cast(indent.decrease_indent(self.syntax().clone())).unwrap() | ||
560 | } | ||
561 | } | 561 | } |
562 | 562 | ||
563 | impl<N: AstNode> AstNodeEdit for N {} | 563 | impl<N: AstNode + Clone> AstNodeEdit for N {} |
564 | 564 | ||
565 | fn single_node(element: impl Into<SyntaxElement>) -> RangeInclusive<SyntaxElement> { | 565 | fn single_node(element: impl Into<SyntaxElement>) -> RangeInclusive<SyntaxElement> { |
566 | let element = element.into(); | 566 | let element = element.into(); |
@@ -580,7 +580,7 @@ fn test_increase_indent() { | |||
580 | _ => (), | 580 | _ => (), |
581 | }" | 581 | }" |
582 | ); | 582 | ); |
583 | let indented = IndentLevel(2).increase_indent(arm_list); | 583 | let indented = arm_list.indent(IndentLevel(2)); |
584 | assert_eq!( | 584 | assert_eq!( |
585 | indented.syntax().to_string(), | 585 | indented.syntax().to_string(), |
586 | "{ | 586 | "{ |
diff --git a/crates/ra_toolchain/Cargo.toml b/crates/ra_toolchain/Cargo.toml new file mode 100644 index 000000000..1873fbe16 --- /dev/null +++ b/crates/ra_toolchain/Cargo.toml | |||
@@ -0,0 +1,8 @@ | |||
1 | [package] | ||
2 | edition = "2018" | ||
3 | name = "ra_toolchain" | ||
4 | version = "0.1.0" | ||
5 | authors = ["rust-analyzer developers"] | ||
6 | |||
7 | [dependencies] | ||
8 | home = "0.5.3" | ||
diff --git a/crates/ra_toolchain/src/lib.rs b/crates/ra_toolchain/src/lib.rs new file mode 100644 index 000000000..3c307a0ea --- /dev/null +++ b/crates/ra_toolchain/src/lib.rs | |||
@@ -0,0 +1,64 @@ | |||
1 | //! This crate contains a single public function | ||
2 | //! [`get_path_for_executable`](fn.get_path_for_executable.html). | ||
3 | //! See docs there for more information. | ||
4 | use std::{env, iter, path::PathBuf}; | ||
5 | |||
6 | pub fn cargo() -> PathBuf { | ||
7 | get_path_for_executable("cargo") | ||
8 | } | ||
9 | |||
10 | pub fn rustc() -> PathBuf { | ||
11 | get_path_for_executable("rustc") | ||
12 | } | ||
13 | |||
14 | pub fn rustup() -> PathBuf { | ||
15 | get_path_for_executable("rustup") | ||
16 | } | ||
17 | |||
18 | /// Return a `PathBuf` to use for the given executable. | ||
19 | /// | ||
20 | /// E.g., `get_path_for_executable("cargo")` may return just `cargo` if that | ||
21 | /// gives a valid Cargo executable; or it may return a full path to a valid | ||
22 | /// Cargo. | ||
23 | fn get_path_for_executable(executable_name: &'static str) -> PathBuf { | ||
24 | // The current implementation checks three places for an executable to use: | ||
25 | // 1) Appropriate environment variable (erroring if this is set but not a usable executable) | ||
26 | // example: for cargo, this checks $CARGO environment variable; for rustc, $RUSTC; etc | ||
27 | // 2) `<executable_name>` | ||
28 | // example: for cargo, this tries just `cargo`, which will succeed if `cargo` is on the $PATH | ||
29 | // 3) `~/.cargo/bin/<executable_name>` | ||
30 | // example: for cargo, this tries ~/.cargo/bin/cargo | ||
31 | // It seems that this is a reasonable place to try for cargo, rustc, and rustup | ||
32 | let env_var = executable_name.to_ascii_uppercase(); | ||
33 | if let Some(path) = env::var_os(&env_var) { | ||
34 | return path.into(); | ||
35 | } | ||
36 | |||
37 | if lookup_in_path(executable_name) { | ||
38 | return executable_name.into(); | ||
39 | } | ||
40 | |||
41 | if let Some(mut path) = home::home_dir() { | ||
42 | path.push(".cargo"); | ||
43 | path.push("bin"); | ||
44 | path.push(executable_name); | ||
45 | if path.is_file() { | ||
46 | return path; | ||
47 | } | ||
48 | } | ||
49 | executable_name.into() | ||
50 | } | ||
51 | |||
52 | fn lookup_in_path(exec: &str) -> bool { | ||
53 | let paths = env::var_os("PATH").unwrap_or_default(); | ||
54 | let mut candidates = env::split_paths(&paths).flat_map(|path| { | ||
55 | let candidate = path.join(&exec); | ||
56 | let with_exe = if env::consts::EXE_EXTENSION == "" { | ||
57 | None | ||
58 | } else { | ||
59 | Some(candidate.with_extension(env::consts::EXE_EXTENSION)) | ||
60 | }; | ||
61 | iter::once(candidate).chain(with_exe) | ||
62 | }); | ||
63 | candidates.any(|it| it.is_file()) | ||
64 | } | ||
diff --git a/crates/rust-analyzer/src/main_loop.rs b/crates/rust-analyzer/src/main_loop.rs index b77f0c5a9..17b0b95b9 100644 --- a/crates/rust-analyzer/src/main_loop.rs +++ b/crates/rust-analyzer/src/main_loop.rs | |||
@@ -96,23 +96,21 @@ pub fn main_loop(ws_roots: Vec<PathBuf>, config: Config, connection: Connection) | |||
96 | let mut world_state = { | 96 | let mut world_state = { |
97 | let workspaces = { | 97 | let workspaces = { |
98 | // FIXME: support dynamic workspace loading. | 98 | // FIXME: support dynamic workspace loading. |
99 | let mut visited = FxHashSet::default(); | 99 | let project_roots: FxHashSet<_> = ws_roots |
100 | let project_roots = ws_roots | ||
101 | .iter() | 100 | .iter() |
102 | .filter_map(|it| ra_project_model::ProjectRoot::discover(it).ok()) | 101 | .filter_map(|it| ra_project_model::ProjectRoot::discover(it).ok()) |
103 | .flatten() | 102 | .flatten() |
104 | .filter(|it| visited.insert(it.clone())) | 103 | .collect(); |
105 | .collect::<Vec<_>>(); | ||
106 | 104 | ||
107 | if project_roots.is_empty() && config.notifications.cargo_toml_not_found { | 105 | if project_roots.is_empty() && config.notifications.cargo_toml_not_found { |
108 | show_message( | 106 | show_message( |
109 | req::MessageType::Error, | 107 | req::MessageType::Error, |
110 | format!( | 108 | format!( |
111 | "rust-analyzer failed to discover workspace, no Cargo.toml found, dirs searched: {}", | 109 | "rust-analyzer failed to discover workspace, no Cargo.toml found, dirs searched: {}", |
112 | ws_roots.iter().format_with(", ", |it, f| f(&it.display())) | 110 | ws_roots.iter().format_with(", ", |it, f| f(&it.display())) |
113 | ), | 111 | ), |
114 | &connection.sender, | 112 | &connection.sender, |
115 | ); | 113 | ); |
116 | }; | 114 | }; |
117 | 115 | ||
118 | project_roots | 116 | project_roots |
diff --git a/crates/rust-analyzer/src/main_loop/handlers.rs b/crates/rust-analyzer/src/main_loop/handlers.rs index 15e8305f8..f4353af64 100644 --- a/crates/rust-analyzer/src/main_loop/handlers.rs +++ b/crates/rust-analyzer/src/main_loop/handlers.rs | |||
@@ -42,6 +42,7 @@ use crate::{ | |||
42 | world::WorldSnapshot, | 42 | world::WorldSnapshot, |
43 | LspError, Result, | 43 | LspError, Result, |
44 | }; | 44 | }; |
45 | use ra_project_model::TargetKind; | ||
45 | 46 | ||
46 | pub fn handle_analyzer_status(world: WorldSnapshot, _: ()) -> Result<String> { | 47 | pub fn handle_analyzer_status(world: WorldSnapshot, _: ()) -> Result<String> { |
47 | let _p = profile("handle_analyzer_status"); | 48 | let _p = profile("handle_analyzer_status"); |
@@ -384,16 +385,27 @@ pub fn handle_runnables( | |||
384 | let offset = params.position.map(|it| it.conv_with(&line_index)); | 385 | let offset = params.position.map(|it| it.conv_with(&line_index)); |
385 | let mut res = Vec::new(); | 386 | let mut res = Vec::new(); |
386 | let workspace_root = world.workspace_root_for(file_id); | 387 | let workspace_root = world.workspace_root_for(file_id); |
388 | let cargo_spec = CargoTargetSpec::for_file(&world, file_id)?; | ||
387 | for runnable in world.analysis().runnables(file_id)? { | 389 | for runnable in world.analysis().runnables(file_id)? { |
388 | if let Some(offset) = offset { | 390 | if let Some(offset) = offset { |
389 | if !runnable.range.contains_inclusive(offset) { | 391 | if !runnable.range.contains_inclusive(offset) { |
390 | continue; | 392 | continue; |
391 | } | 393 | } |
392 | } | 394 | } |
395 | // Do not suggest binary run on other target than binary | ||
396 | if let RunnableKind::Bin = runnable.kind { | ||
397 | if let Some(spec) = &cargo_spec { | ||
398 | match spec.target_kind { | ||
399 | TargetKind::Bin => {} | ||
400 | _ => continue, | ||
401 | } | ||
402 | } | ||
403 | } | ||
393 | res.push(to_lsp_runnable(&world, file_id, runnable)?); | 404 | res.push(to_lsp_runnable(&world, file_id, runnable)?); |
394 | } | 405 | } |
406 | |||
395 | // Add `cargo check` and `cargo test` for the whole package | 407 | // Add `cargo check` and `cargo test` for the whole package |
396 | match CargoTargetSpec::for_file(&world, file_id)? { | 408 | match cargo_spec { |
397 | Some(spec) => { | 409 | Some(spec) => { |
398 | for &cmd in ["check", "test"].iter() { | 410 | for &cmd in ["check", "test"].iter() { |
399 | res.push(req::Runnable { | 411 | res.push(req::Runnable { |
@@ -831,13 +843,23 @@ pub fn handle_code_lens( | |||
831 | 843 | ||
832 | let mut lenses: Vec<CodeLens> = Default::default(); | 844 | let mut lenses: Vec<CodeLens> = Default::default(); |
833 | 845 | ||
846 | let cargo_spec = CargoTargetSpec::for_file(&world, file_id)?; | ||
834 | // Gather runnables | 847 | // Gather runnables |
835 | for runnable in world.analysis().runnables(file_id)? { | 848 | for runnable in world.analysis().runnables(file_id)? { |
836 | let title = match &runnable.kind { | 849 | let title = match &runnable.kind { |
837 | RunnableKind::Test { .. } | RunnableKind::TestMod { .. } => "▶️\u{fe0e}Run Test", | 850 | RunnableKind::Test { .. } | RunnableKind::TestMod { .. } => "▶️\u{fe0e}Run Test", |
838 | RunnableKind::DocTest { .. } => "▶️\u{fe0e}Run Doctest", | 851 | RunnableKind::DocTest { .. } => "▶️\u{fe0e}Run Doctest", |
839 | RunnableKind::Bench { .. } => "Run Bench", | 852 | RunnableKind::Bench { .. } => "Run Bench", |
840 | RunnableKind::Bin => "Run", | 853 | RunnableKind::Bin => { |
854 | // Do not suggest binary run on other target than binary | ||
855 | match &cargo_spec { | ||
856 | Some(spec) => match spec.target_kind { | ||
857 | TargetKind::Bin => "Run", | ||
858 | _ => continue, | ||
859 | }, | ||
860 | None => continue, | ||
861 | } | ||
862 | } | ||
841 | } | 863 | } |
842 | .to_string(); | 864 | .to_string(); |
843 | let mut r = to_lsp_runnable(&world, file_id, runnable)?; | 865 | let mut r = to_lsp_runnable(&world, file_id, runnable)?; |
diff --git a/crates/rust-analyzer/tests/heavy_tests/main.rs b/crates/rust-analyzer/tests/heavy_tests/main.rs index 1efa5dd63..e459e3a3c 100644 --- a/crates/rust-analyzer/tests/heavy_tests/main.rs +++ b/crates/rust-analyzer/tests/heavy_tests/main.rs | |||
@@ -9,7 +9,8 @@ use lsp_types::{ | |||
9 | }; | 9 | }; |
10 | use rust_analyzer::req::{ | 10 | use rust_analyzer::req::{ |
11 | CodeActionParams, CodeActionRequest, Completion, CompletionParams, DidOpenTextDocument, | 11 | CodeActionParams, CodeActionRequest, Completion, CompletionParams, DidOpenTextDocument, |
12 | Formatting, GotoDefinition, HoverRequest, OnEnter, Runnables, RunnablesParams, | 12 | Formatting, GotoDefinition, GotoTypeDefinition, HoverRequest, OnEnter, Runnables, |
13 | RunnablesParams, | ||
13 | }; | 14 | }; |
14 | use serde_json::json; | 15 | use serde_json::json; |
15 | use tempfile::TempDir; | 16 | use tempfile::TempDir; |
@@ -574,7 +575,7 @@ version = \"0.0.0\" | |||
574 | } | 575 | } |
575 | 576 | ||
576 | #[test] | 577 | #[test] |
577 | fn resolve_include_concat_env() { | 578 | fn out_dirs_check() { |
578 | if skip_slow_tests() { | 579 | if skip_slow_tests() { |
579 | return; | 580 | return; |
580 | } | 581 | } |
@@ -597,11 +598,28 @@ fn main() { | |||
597 | r#"pub fn message() -> &'static str { "Hello, World!" }"#, | 598 | r#"pub fn message() -> &'static str { "Hello, World!" }"#, |
598 | ) | 599 | ) |
599 | .unwrap(); | 600 | .unwrap(); |
601 | println!("cargo:rustc-cfg=atom_cfg"); | ||
602 | println!("cargo:rustc-cfg=featlike=\"set\""); | ||
600 | println!("cargo:rerun-if-changed=build.rs"); | 603 | println!("cargo:rerun-if-changed=build.rs"); |
601 | } | 604 | } |
602 | //- src/main.rs | 605 | //- src/main.rs |
603 | include!(concat!(env!("OUT_DIR"), "/hello.rs")); | 606 | include!(concat!(env!("OUT_DIR"), "/hello.rs")); |
604 | 607 | ||
608 | #[cfg(atom_cfg)] | ||
609 | struct A; | ||
610 | #[cfg(bad_atom_cfg)] | ||
611 | struct A; | ||
612 | #[cfg(featlike = "set")] | ||
613 | struct B; | ||
614 | #[cfg(featlike = "not_set")] | ||
615 | struct B; | ||
616 | |||
617 | fn main() { | ||
618 | let va = A; | ||
619 | let vb = B; | ||
620 | message(); | ||
621 | } | ||
622 | |||
605 | fn main() { message(); } | 623 | fn main() { message(); } |
606 | "###, | 624 | "###, |
607 | ) | 625 | ) |
@@ -613,12 +631,98 @@ fn main() { message(); } | |||
613 | let res = server.send_request::<GotoDefinition>(GotoDefinitionParams { | 631 | let res = server.send_request::<GotoDefinition>(GotoDefinitionParams { |
614 | text_document_position_params: TextDocumentPositionParams::new( | 632 | text_document_position_params: TextDocumentPositionParams::new( |
615 | server.doc_id("src/main.rs"), | 633 | server.doc_id("src/main.rs"), |
616 | Position::new(2, 15), | 634 | Position::new(14, 8), |
617 | ), | 635 | ), |
618 | work_done_progress_params: Default::default(), | 636 | work_done_progress_params: Default::default(), |
619 | partial_result_params: Default::default(), | 637 | partial_result_params: Default::default(), |
620 | }); | 638 | }); |
621 | assert!(format!("{}", res).contains("hello.rs")); | 639 | assert!(format!("{}", res).contains("hello.rs")); |
640 | server.request::<GotoTypeDefinition>( | ||
641 | GotoDefinitionParams { | ||
642 | text_document_position_params: TextDocumentPositionParams::new( | ||
643 | server.doc_id("src/main.rs"), | ||
644 | Position::new(12, 9), | ||
645 | ), | ||
646 | work_done_progress_params: Default::default(), | ||
647 | partial_result_params: Default::default(), | ||
648 | }, | ||
649 | json!([{ | ||
650 | "originSelectionRange": { | ||
651 | "end": { | ||
652 | "character": 10, | ||
653 | "line": 12 | ||
654 | }, | ||
655 | "start": { | ||
656 | "character": 8, | ||
657 | "line": 12 | ||
658 | } | ||
659 | }, | ||
660 | "targetRange": { | ||
661 | "end": { | ||
662 | "character": 9, | ||
663 | "line": 3 | ||
664 | }, | ||
665 | "start": { | ||
666 | "character": 0, | ||
667 | "line": 2 | ||
668 | } | ||
669 | }, | ||
670 | "targetSelectionRange": { | ||
671 | "end": { | ||
672 | "character": 8, | ||
673 | "line": 3 | ||
674 | }, | ||
675 | "start": { | ||
676 | "character": 7, | ||
677 | "line": 3 | ||
678 | } | ||
679 | }, | ||
680 | "targetUri": "file:///[..]src/main.rs" | ||
681 | }]), | ||
682 | ); | ||
683 | server.request::<GotoTypeDefinition>( | ||
684 | GotoDefinitionParams { | ||
685 | text_document_position_params: TextDocumentPositionParams::new( | ||
686 | server.doc_id("src/main.rs"), | ||
687 | Position::new(13, 9), | ||
688 | ), | ||
689 | work_done_progress_params: Default::default(), | ||
690 | partial_result_params: Default::default(), | ||
691 | }, | ||
692 | json!([{ | ||
693 | "originSelectionRange": { | ||
694 | "end": { | ||
695 | "character": 10, | ||
696 | "line": 13 | ||
697 | }, | ||
698 | "start": { | ||
699 | "character": 8, | ||
700 | "line":13 | ||
701 | } | ||
702 | }, | ||
703 | "targetRange": { | ||
704 | "end": { | ||
705 | "character": 9, | ||
706 | "line": 7 | ||
707 | }, | ||
708 | "start": { | ||
709 | "character": 0, | ||
710 | "line":6 | ||
711 | } | ||
712 | }, | ||
713 | "targetSelectionRange": { | ||
714 | "end": { | ||
715 | "character": 8, | ||
716 | "line": 7 | ||
717 | }, | ||
718 | "start": { | ||
719 | "character": 7, | ||
720 | "line": 7 | ||
721 | } | ||
722 | }, | ||
723 | "targetUri": "file:///[..]src/main.rs" | ||
724 | }]), | ||
725 | ); | ||
622 | } | 726 | } |
623 | 727 | ||
624 | #[test] | 728 | #[test] |
diff --git a/crates/test_utils/src/lib.rs b/crates/test_utils/src/lib.rs index b13e13af2..b1e3c328f 100644 --- a/crates/test_utils/src/lib.rs +++ b/crates/test_utils/src/lib.rs | |||
@@ -270,7 +270,7 @@ fn parse_fixture_checks_further_indented_metadata() { | |||
270 | } | 270 | } |
271 | 271 | ||
272 | #[test] | 272 | #[test] |
273 | fn parse_fixture_can_handle_unindented_first_line() { | 273 | fn parse_fixture_can_handle_dedented_first_line() { |
274 | let fixture = "//- /lib.rs | 274 | let fixture = "//- /lib.rs |
275 | mod foo; | 275 | mod foo; |
276 | //- /foo.rs | 276 | //- /foo.rs |
diff --git a/editors/code/package.json b/editors/code/package.json index 12a08ba40..c6fc13519 100644 --- a/editors/code/package.json +++ b/editors/code/package.json | |||
@@ -300,6 +300,11 @@ | |||
300 | "default": true, | 300 | "default": true, |
301 | "markdownDescription": "Check with all features (will be passed as `--all-features`)" | 301 | "markdownDescription": "Check with all features (will be passed as `--all-features`)" |
302 | }, | 302 | }, |
303 | "rust-analyzer.inlayHints.enable": { | ||
304 | "type": "boolean", | ||
305 | "default": true, | ||
306 | "description": "Disable all inlay hints" | ||
307 | }, | ||
303 | "rust-analyzer.inlayHints.typeHints": { | 308 | "rust-analyzer.inlayHints.typeHints": { |
304 | "type": "boolean", | 309 | "type": "boolean", |
305 | "default": true, | 310 | "default": true, |
@@ -418,6 +423,16 @@ | |||
418 | "default": { | 423 | "default": { |
419 | "/rustc/<id>": "${env:USERPROFILE}/.rustup/toolchains/<toolchain-id>/lib/rustlib/src/rust" | 424 | "/rustc/<id>": "${env:USERPROFILE}/.rustup/toolchains/<toolchain-id>/lib/rustlib/src/rust" |
420 | } | 425 | } |
426 | }, | ||
427 | "rust-analyzer.debug.openDebugPane": { | ||
428 | "description": "Whether to open up the Debug Pane on debugging start.", | ||
429 | "type": "boolean", | ||
430 | "default": false | ||
431 | }, | ||
432 | "rust-analyzer.debug.engineSettings": { | ||
433 | "type": "object", | ||
434 | "default": {}, | ||
435 | "description": "Optional settings passed to the debug engine. Example:\n{ \"lldb\": { \"terminal\":\"external\"} }" | ||
421 | } | 436 | } |
422 | } | 437 | } |
423 | }, | 438 | }, |
@@ -589,6 +604,9 @@ | |||
589 | "union": [ | 604 | "union": [ |
590 | "entity.name.union" | 605 | "entity.name.union" |
591 | ], | 606 | ], |
607 | "struct": [ | ||
608 | "entity.name.type.struct" | ||
609 | ], | ||
592 | "keyword.unsafe": [ | 610 | "keyword.unsafe": [ |
593 | "keyword.other.unsafe" | 611 | "keyword.other.unsafe" |
594 | ], | 612 | ], |
diff --git a/editors/code/src/cargo.ts b/editors/code/src/cargo.ts index a328ba9bd..2a2c2e0e1 100644 --- a/editors/code/src/cargo.ts +++ b/editors/code/src/cargo.ts | |||
@@ -1,6 +1,9 @@ | |||
1 | import * as cp from 'child_process'; | 1 | import * as cp from 'child_process'; |
2 | import * as os from 'os'; | ||
3 | import * as path from 'path'; | ||
2 | import * as readline from 'readline'; | 4 | import * as readline from 'readline'; |
3 | import { OutputChannel } from 'vscode'; | 5 | import { OutputChannel } from 'vscode'; |
6 | import { isValidExecutable } from './util'; | ||
4 | 7 | ||
5 | interface CompilationArtifact { | 8 | interface CompilationArtifact { |
6 | fileName: string; | 9 | fileName: string; |
@@ -10,17 +13,9 @@ interface CompilationArtifact { | |||
10 | } | 13 | } |
11 | 14 | ||
12 | export class Cargo { | 15 | export class Cargo { |
13 | rootFolder: string; | 16 | constructor(readonly rootFolder: string, readonly output: OutputChannel) { } |
14 | env?: Record<string, string>; | ||
15 | output: OutputChannel; | ||
16 | |||
17 | public constructor(cargoTomlFolder: string, output: OutputChannel, env: Record<string, string> | undefined = undefined) { | ||
18 | this.rootFolder = cargoTomlFolder; | ||
19 | this.output = output; | ||
20 | this.env = env; | ||
21 | } | ||
22 | 17 | ||
23 | public async artifactsFromArgs(cargoArgs: string[]): Promise<CompilationArtifact[]> { | 18 | private async artifactsFromArgs(cargoArgs: string[]): Promise<CompilationArtifact[]> { |
24 | const artifacts: CompilationArtifact[] = []; | 19 | const artifacts: CompilationArtifact[] = []; |
25 | 20 | ||
26 | try { | 21 | try { |
@@ -37,17 +32,13 @@ export class Cargo { | |||
37 | isTest: message.profile.test | 32 | isTest: message.profile.test |
38 | }); | 33 | }); |
39 | } | 34 | } |
40 | } | 35 | } else if (message.reason === 'compiler-message') { |
41 | else if (message.reason === 'compiler-message') { | ||
42 | this.output.append(message.message.rendered); | 36 | this.output.append(message.message.rendered); |
43 | } | 37 | } |
44 | }, | 38 | }, |
45 | stderr => { | 39 | stderr => this.output.append(stderr), |
46 | this.output.append(stderr); | ||
47 | } | ||
48 | ); | 40 | ); |
49 | } | 41 | } catch (err) { |
50 | catch (err) { | ||
51 | this.output.show(true); | 42 | this.output.show(true); |
52 | throw new Error(`Cargo invocation has failed: ${err}`); | 43 | throw new Error(`Cargo invocation has failed: ${err}`); |
53 | } | 44 | } |
@@ -55,9 +46,8 @@ export class Cargo { | |||
55 | return artifacts; | 46 | return artifacts; |
56 | } | 47 | } |
57 | 48 | ||
58 | public async executableFromArgs(args: string[]): Promise<string> { | 49 | async executableFromArgs(args: readonly string[]): Promise<string> { |
59 | const cargoArgs = [...args]; // to remain args unchanged | 50 | const cargoArgs = [...args, "--message-format=json"]; |
60 | cargoArgs.push("--message-format=json"); | ||
61 | 51 | ||
62 | const artifacts = await this.artifactsFromArgs(cargoArgs); | 52 | const artifacts = await this.artifactsFromArgs(cargoArgs); |
63 | 53 | ||
@@ -70,24 +60,27 @@ export class Cargo { | |||
70 | return artifacts[0].fileName; | 60 | return artifacts[0].fileName; |
71 | } | 61 | } |
72 | 62 | ||
73 | runCargo( | 63 | private runCargo( |
74 | cargoArgs: string[], | 64 | cargoArgs: string[], |
75 | onStdoutJson: (obj: any) => void, | 65 | onStdoutJson: (obj: any) => void, |
76 | onStderrString: (data: string) => void | 66 | onStderrString: (data: string) => void |
77 | ): Promise<number> { | 67 | ): Promise<number> { |
78 | return new Promise<number>((resolve, reject) => { | 68 | return new Promise((resolve, reject) => { |
79 | const cargo = cp.spawn('cargo', cargoArgs, { | 69 | let cargoPath; |
70 | try { | ||
71 | cargoPath = getCargoPathOrFail(); | ||
72 | } catch (err) { | ||
73 | return reject(err); | ||
74 | } | ||
75 | |||
76 | const cargo = cp.spawn(cargoPath, cargoArgs, { | ||
80 | stdio: ['ignore', 'pipe', 'pipe'], | 77 | stdio: ['ignore', 'pipe', 'pipe'], |
81 | cwd: this.rootFolder, | 78 | cwd: this.rootFolder |
82 | env: this.env, | ||
83 | }); | 79 | }); |
84 | 80 | ||
85 | cargo.on('error', err => { | 81 | cargo.on('error', err => reject(new Error(`could not launch cargo: ${err}`))); |
86 | reject(new Error(`could not launch cargo: ${err}`)); | 82 | |
87 | }); | 83 | cargo.stderr.on('data', chunk => onStderrString(chunk.toString())); |
88 | cargo.stderr.on('data', chunk => { | ||
89 | onStderrString(chunk.toString()); | ||
90 | }); | ||
91 | 84 | ||
92 | const rl = readline.createInterface({ input: cargo.stdout }); | 85 | const rl = readline.createInterface({ input: cargo.stdout }); |
93 | rl.on('line', line => { | 86 | rl.on('line', line => { |
@@ -103,4 +96,28 @@ export class Cargo { | |||
103 | }); | 96 | }); |
104 | }); | 97 | }); |
105 | } | 98 | } |
106 | } \ No newline at end of file | 99 | } |
100 | |||
101 | // Mirrors `ra_env::get_path_for_executable` implementation | ||
102 | function getCargoPathOrFail(): string { | ||
103 | const envVar = process.env.CARGO; | ||
104 | const executableName = "cargo"; | ||
105 | |||
106 | if (envVar) { | ||
107 | if (isValidExecutable(envVar)) return envVar; | ||
108 | |||
109 | throw new Error(`\`${envVar}\` environment variable points to something that's not a valid executable`); | ||
110 | } | ||
111 | |||
112 | if (isValidExecutable(executableName)) return executableName; | ||
113 | |||
114 | const standardLocation = path.join(os.homedir(), '.cargo', 'bin', executableName); | ||
115 | |||
116 | if (isValidExecutable(standardLocation)) return standardLocation; | ||
117 | |||
118 | throw new Error( | ||
119 | `Failed to find \`${executableName}\` executable. ` + | ||
120 | `Make sure \`${executableName}\` is in \`$PATH\`, ` + | ||
121 | `or set \`${envVar}\` to point to a valid executable.` | ||
122 | ); | ||
123 | } | ||
diff --git a/editors/code/src/commands/runnables.ts b/editors/code/src/commands/runnables.ts index d77e8188c..ae328d2a4 100644 --- a/editors/code/src/commands/runnables.ts +++ b/editors/code/src/commands/runnables.ts | |||
@@ -64,29 +64,20 @@ export function runSingle(ctx: Ctx): Cmd { | |||
64 | }; | 64 | }; |
65 | } | 65 | } |
66 | 66 | ||
67 | function getLldbDebugConfig(config: ra.Runnable, sourceFileMap: Record<string, string>): vscode.DebugConfiguration { | 67 | function getLldbDebugConfig(config: ra.Runnable, executable: string, sourceFileMap?: Record<string, string>): vscode.DebugConfiguration { |
68 | return { | 68 | return { |
69 | type: "lldb", | 69 | type: "lldb", |
70 | request: "launch", | 70 | request: "launch", |
71 | name: config.label, | 71 | name: config.label, |
72 | cargo: { | 72 | program: executable, |
73 | args: config.args, | ||
74 | }, | ||
75 | args: config.extraArgs, | 73 | args: config.extraArgs, |
76 | cwd: config.cwd, | 74 | cwd: config.cwd, |
77 | sourceMap: sourceFileMap | 75 | sourceMap: sourceFileMap, |
76 | sourceLanguages: ["rust"] | ||
78 | }; | 77 | }; |
79 | } | 78 | } |
80 | 79 | ||
81 | const debugOutput = vscode.window.createOutputChannel("Debug"); | 80 | function getCppvsDebugConfig(config: ra.Runnable, executable: string, sourceFileMap?: Record<string, string>): vscode.DebugConfiguration { |
82 | |||
83 | async function getCppvsDebugConfig(config: ra.Runnable, sourceFileMap: Record<string, string>): Promise<vscode.DebugConfiguration> { | ||
84 | debugOutput.clear(); | ||
85 | |||
86 | const cargo = new Cargo(config.cwd || '.', debugOutput); | ||
87 | const executable = await cargo.executableFromArgs(config.args); | ||
88 | |||
89 | // if we are here, there were no compilation errors. | ||
90 | return { | 81 | return { |
91 | type: (os.platform() === "win32") ? "cppvsdbg" : 'cppdbg', | 82 | type: (os.platform() === "win32") ? "cppvsdbg" : 'cppdbg', |
92 | request: "launch", | 83 | request: "launch", |
@@ -98,36 +89,62 @@ async function getCppvsDebugConfig(config: ra.Runnable, sourceFileMap: Record<st | |||
98 | }; | 89 | }; |
99 | } | 90 | } |
100 | 91 | ||
92 | const debugOutput = vscode.window.createOutputChannel("Debug"); | ||
93 | |||
94 | async function getDebugExecutable(config: ra.Runnable): Promise<string> { | ||
95 | const cargo = new Cargo(config.cwd || '.', debugOutput); | ||
96 | const executable = await cargo.executableFromArgs(config.args); | ||
97 | |||
98 | // if we are here, there were no compilation errors. | ||
99 | return executable; | ||
100 | } | ||
101 | |||
102 | type DebugConfigProvider = (config: ra.Runnable, executable: string, sourceFileMap?: Record<string, string>) => vscode.DebugConfiguration; | ||
103 | |||
101 | export function debugSingle(ctx: Ctx): Cmd { | 104 | export function debugSingle(ctx: Ctx): Cmd { |
102 | return async (config: ra.Runnable) => { | 105 | return async (config: ra.Runnable) => { |
103 | const editor = ctx.activeRustEditor; | 106 | const editor = ctx.activeRustEditor; |
104 | if (!editor) return; | 107 | if (!editor) return; |
105 | 108 | ||
106 | const lldbId = "vadimcn.vscode-lldb"; | 109 | const knownEngines: Record<string, DebugConfigProvider> = { |
107 | const cpptoolsId = "ms-vscode.cpptools"; | 110 | "vadimcn.vscode-lldb": getLldbDebugConfig, |
111 | "ms-vscode.cpptools": getCppvsDebugConfig | ||
112 | }; | ||
113 | const debugOptions = ctx.config.debug; | ||
108 | 114 | ||
109 | const debugEngineId = ctx.config.debug.engine; | ||
110 | let debugEngine = null; | 115 | let debugEngine = null; |
111 | if (debugEngineId === "auto") { | 116 | if (debugOptions.engine === "auto") { |
112 | debugEngine = vscode.extensions.getExtension(lldbId); | 117 | for (var engineId in knownEngines) { |
113 | if (!debugEngine) { | 118 | debugEngine = vscode.extensions.getExtension(engineId); |
114 | debugEngine = vscode.extensions.getExtension(cpptoolsId); | 119 | if (debugEngine) break; |
115 | } | 120 | } |
116 | } | 121 | } |
117 | else { | 122 | else { |
118 | debugEngine = vscode.extensions.getExtension(debugEngineId); | 123 | debugEngine = vscode.extensions.getExtension(debugOptions.engine); |
119 | } | 124 | } |
120 | 125 | ||
121 | if (!debugEngine) { | 126 | if (!debugEngine) { |
122 | vscode.window.showErrorMessage(`Install [CodeLLDB](https://marketplace.visualstudio.com/items?itemName=${lldbId})` | 127 | vscode.window.showErrorMessage(`Install [CodeLLDB](https://marketplace.visualstudio.com/items?itemName=vadimcn.vscode-lldb)` |
123 | + ` or [MS C++ tools](https://marketplace.visualstudio.com/items?itemName=${cpptoolsId}) extension for debugging.`); | 128 | + ` or [MS C++ tools](https://marketplace.visualstudio.com/items?itemName=ms-vscode.cpptools) extension for debugging.`); |
124 | return; | 129 | return; |
125 | } | 130 | } |
126 | 131 | ||
127 | const debugConfig = lldbId === debugEngine.id | 132 | debugOutput.clear(); |
128 | ? getLldbDebugConfig(config, ctx.config.debug.sourceFileMap) | 133 | if (ctx.config.debug.openUpDebugPane) { |
129 | : await getCppvsDebugConfig(config, ctx.config.debug.sourceFileMap); | 134 | debugOutput.show(true); |
135 | } | ||
136 | |||
137 | const executable = await getDebugExecutable(config); | ||
138 | const debugConfig = knownEngines[debugEngine.id](config, executable, debugOptions.sourceFileMap); | ||
139 | if (debugConfig.type in debugOptions.engineSettings) { | ||
140 | const settingsMap = (debugOptions.engineSettings as any)[debugConfig.type]; | ||
141 | for (var key in settingsMap) { | ||
142 | debugConfig[key] = settingsMap[key]; | ||
143 | } | ||
144 | } | ||
130 | 145 | ||
146 | debugOutput.appendLine("Launching debug configuration:"); | ||
147 | debugOutput.appendLine(JSON.stringify(debugConfig, null, 2)); | ||
131 | return vscode.debug.startDebugging(undefined, debugConfig); | 148 | return vscode.debug.startDebugging(undefined, debugConfig); |
132 | }; | 149 | }; |
133 | } | 150 | } |
diff --git a/editors/code/src/config.ts b/editors/code/src/config.ts index 110e54180..be2e27aec 100644 --- a/editors/code/src/config.ts +++ b/editors/code/src/config.ts | |||
@@ -94,6 +94,7 @@ export class Config { | |||
94 | 94 | ||
95 | get inlayHints() { | 95 | get inlayHints() { |
96 | return { | 96 | return { |
97 | enable: this.get<boolean>("inlayHints.enable"), | ||
97 | typeHints: this.get<boolean>("inlayHints.typeHints"), | 98 | typeHints: this.get<boolean>("inlayHints.typeHints"), |
98 | parameterHints: this.get<boolean>("inlayHints.parameterHints"), | 99 | parameterHints: this.get<boolean>("inlayHints.parameterHints"), |
99 | chainingHints: this.get<boolean>("inlayHints.chainingHints"), | 100 | chainingHints: this.get<boolean>("inlayHints.chainingHints"), |
@@ -108,10 +109,14 @@ export class Config { | |||
108 | } | 109 | } |
109 | 110 | ||
110 | get debug() { | 111 | get debug() { |
112 | // "/rustc/<id>" used by suggestions only. | ||
113 | const { ["/rustc/<id>"]: _, ...sourceFileMap } = this.get<Record<string, string>>("debug.sourceFileMap"); | ||
114 | |||
111 | return { | 115 | return { |
112 | engine: this.get<string>("debug.engine"), | 116 | engine: this.get<string>("debug.engine"), |
113 | sourceFileMap: this.get<Record<string, string>>("debug.sourceFileMap"), | 117 | engineSettings: this.get<object>("debug.engineSettings"), |
118 | openUpDebugPane: this.get<boolean>("debug.openUpDebugPane"), | ||
119 | sourceFileMap: sourceFileMap, | ||
114 | }; | 120 | }; |
115 | } | 121 | } |
116 | |||
117 | } | 122 | } |
diff --git a/editors/code/src/inlay_hints.ts b/editors/code/src/inlay_hints.ts index a09531797..a2b07d003 100644 --- a/editors/code/src/inlay_hints.ts +++ b/editors/code/src/inlay_hints.ts | |||
@@ -10,13 +10,13 @@ export function activateInlayHints(ctx: Ctx) { | |||
10 | const maybeUpdater = { | 10 | const maybeUpdater = { |
11 | updater: null as null | HintsUpdater, | 11 | updater: null as null | HintsUpdater, |
12 | async onConfigChange() { | 12 | async onConfigChange() { |
13 | if ( | 13 | const anyEnabled = ctx.config.inlayHints.typeHints |
14 | !ctx.config.inlayHints.typeHints && | 14 | || ctx.config.inlayHints.parameterHints |
15 | !ctx.config.inlayHints.parameterHints && | 15 | || ctx.config.inlayHints.chainingHints; |
16 | !ctx.config.inlayHints.chainingHints | 16 | const enabled = ctx.config.inlayHints.enable && anyEnabled; |
17 | ) { | 17 | |
18 | return this.dispose(); | 18 | if (!enabled) return this.dispose(); |
19 | } | 19 | |
20 | await sleep(100); | 20 | await sleep(100); |
21 | if (this.updater) { | 21 | if (this.updater) { |
22 | this.updater.syncCacheAndRenderHints(); | 22 | this.updater.syncCacheAndRenderHints(); |
diff --git a/editors/code/src/main.ts b/editors/code/src/main.ts index efd56a84b..9b020d001 100644 --- a/editors/code/src/main.ts +++ b/editors/code/src/main.ts | |||
@@ -8,10 +8,9 @@ import { activateInlayHints } from './inlay_hints'; | |||
8 | import { activateStatusDisplay } from './status_display'; | 8 | import { activateStatusDisplay } from './status_display'; |
9 | import { Ctx } from './ctx'; | 9 | import { Ctx } from './ctx'; |
10 | import { Config, NIGHTLY_TAG } from './config'; | 10 | import { Config, NIGHTLY_TAG } from './config'; |
11 | import { log, assert } from './util'; | 11 | import { log, assert, isValidExecutable } from './util'; |
12 | import { PersistentState } from './persistent_state'; | 12 | import { PersistentState } from './persistent_state'; |
13 | import { fetchRelease, download } from './net'; | 13 | import { fetchRelease, download } from './net'; |
14 | import { spawnSync } from 'child_process'; | ||
15 | import { activateTaskProvider } from './tasks'; | 14 | import { activateTaskProvider } from './tasks'; |
16 | 15 | ||
17 | let ctx: Ctx | undefined; | 16 | let ctx: Ctx | undefined; |
@@ -179,10 +178,7 @@ async function bootstrapServer(config: Config, state: PersistentState): Promise< | |||
179 | 178 | ||
180 | log.debug("Using server binary at", path); | 179 | log.debug("Using server binary at", path); |
181 | 180 | ||
182 | const res = spawnSync(path, ["--version"], { encoding: 'utf8' }); | 181 | if (!isValidExecutable(path)) { |
183 | log.debug("Checked binary availability via --version", res); | ||
184 | log.debug(res, "--version output:", res.output); | ||
185 | if (res.status !== 0) { | ||
186 | throw new Error(`Failed to execute ${path} --version`); | 182 | throw new Error(`Failed to execute ${path} --version`); |
187 | } | 183 | } |
188 | 184 | ||
diff --git a/editors/code/src/util.ts b/editors/code/src/util.ts index 6f91f81d6..127a9e911 100644 --- a/editors/code/src/util.ts +++ b/editors/code/src/util.ts | |||
@@ -1,6 +1,7 @@ | |||
1 | import * as lc from "vscode-languageclient"; | 1 | import * as lc from "vscode-languageclient"; |
2 | import * as vscode from "vscode"; | 2 | import * as vscode from "vscode"; |
3 | import { strict as nativeAssert } from "assert"; | 3 | import { strict as nativeAssert } from "assert"; |
4 | import { spawnSync } from "child_process"; | ||
4 | 5 | ||
5 | export function assert(condition: boolean, explanation: string): asserts condition { | 6 | export function assert(condition: boolean, explanation: string): asserts condition { |
6 | try { | 7 | try { |
@@ -82,3 +83,13 @@ export function isRustDocument(document: vscode.TextDocument): document is RustD | |||
82 | export function isRustEditor(editor: vscode.TextEditor): editor is RustEditor { | 83 | export function isRustEditor(editor: vscode.TextEditor): editor is RustEditor { |
83 | return isRustDocument(editor.document); | 84 | return isRustDocument(editor.document); |
84 | } | 85 | } |
86 | |||
87 | export function isValidExecutable(path: string): boolean { | ||
88 | log.debug("Checking availability of a binary at", path); | ||
89 | |||
90 | const res = spawnSync(path, ["--version"], { encoding: 'utf8' }); | ||
91 | |||
92 | log.debug(res, "--version output:", res.output); | ||
93 | |||
94 | return res.status === 0; | ||
95 | } | ||