diff options
-rw-r--r-- | Cargo.lock | 60 | ||||
-rw-r--r-- | crates/hir_expand/src/name.rs | 1 | ||||
-rw-r--r-- | crates/ide_assists/src/handlers/auto_import.rs | 6 | ||||
-rw-r--r-- | crates/ide_assists/src/handlers/convert_for_to_iter_for_each.rs | 301 | ||||
-rw-r--r-- | crates/ide_assists/src/handlers/flip_comma.rs | 26 | ||||
-rw-r--r-- | crates/ide_assists/src/handlers/generate_enum_is_method.rs (renamed from crates/ide_assists/src/handlers/generate_enum_match_method.rs) | 128 | ||||
-rw-r--r-- | crates/ide_assists/src/handlers/generate_enum_projection_method.rs | 331 | ||||
-rw-r--r-- | crates/ide_assists/src/lib.rs | 9 | ||||
-rw-r--r-- | crates/ide_assists/src/tests/generated.rs | 85 | ||||
-rw-r--r-- | crates/ide_assists/src/utils.rs | 24 | ||||
-rw-r--r-- | crates/syntax/Cargo.toml | 2 |
11 files changed, 857 insertions, 116 deletions
diff --git a/Cargo.lock b/Cargo.lock index a6038d36a..dbd7a746e 100644 --- a/Cargo.lock +++ b/Cargo.lock | |||
@@ -13,9 +13,9 @@ dependencies = [ | |||
13 | 13 | ||
14 | [[package]] | 14 | [[package]] |
15 | name = "adler" | 15 | name = "adler" |
16 | version = "0.2.3" | 16 | version = "1.0.1" |
17 | source = "registry+https://github.com/rust-lang/crates.io-index" | 17 | source = "registry+https://github.com/rust-lang/crates.io-index" |
18 | checksum = "ee2a4ec343196209d6594e19543ae87a39f96d5534d7174822a3ad825dd6ed7e" | 18 | checksum = "bedc89c5c7b5550ffb9372eb5c5ffc7f9f705cc3f4a128bd4669b9745f555093" |
19 | 19 | ||
20 | [[package]] | 20 | [[package]] |
21 | name = "always-assert" | 21 | name = "always-assert" |
@@ -273,28 +273,26 @@ dependencies = [ | |||
273 | 273 | ||
274 | [[package]] | 274 | [[package]] |
275 | name = "crossbeam-epoch" | 275 | name = "crossbeam-epoch" |
276 | version = "0.9.2" | 276 | version = "0.9.3" |
277 | source = "registry+https://github.com/rust-lang/crates.io-index" | 277 | source = "registry+https://github.com/rust-lang/crates.io-index" |
278 | checksum = "d60ab4a8dba064f2fbb5aa270c28da5cf4bbd0e72dae1140a6b0353a779dbe00" | 278 | checksum = "2584f639eb95fea8c798496315b297cf81b9b58b6d30ab066a75455333cf4b12" |
279 | dependencies = [ | 279 | dependencies = [ |
280 | "cfg-if", | 280 | "cfg-if", |
281 | "crossbeam-utils", | 281 | "crossbeam-utils", |
282 | "lazy_static", | 282 | "lazy_static", |
283 | "loom", | ||
284 | "memoffset", | 283 | "memoffset", |
285 | "scopeguard", | 284 | "scopeguard", |
286 | ] | 285 | ] |
287 | 286 | ||
288 | [[package]] | 287 | [[package]] |
289 | name = "crossbeam-utils" | 288 | name = "crossbeam-utils" |
290 | version = "0.8.2" | 289 | version = "0.8.3" |
291 | source = "registry+https://github.com/rust-lang/crates.io-index" | 290 | source = "registry+https://github.com/rust-lang/crates.io-index" |
292 | checksum = "bae8f328835f8f5a6ceb6a7842a7f2d0c03692adb5c889347235d59194731fe3" | 291 | checksum = "e7e9d99fa91428effe99c5c6d4634cdeba32b8cf784fc428a2a687f61a952c49" |
293 | dependencies = [ | 292 | dependencies = [ |
294 | "autocfg", | 293 | "autocfg", |
295 | "cfg-if", | 294 | "cfg-if", |
296 | "lazy_static", | 295 | "lazy_static", |
297 | "loom", | ||
298 | ] | 296 | ] |
299 | 297 | ||
300 | [[package]] | 298 | [[package]] |
@@ -438,19 +436,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" | |||
438 | checksum = "d79238883cf0307100b90aba4a755d8051a3182305dfe7f649a1e9dc0517006f" | 436 | checksum = "d79238883cf0307100b90aba4a755d8051a3182305dfe7f649a1e9dc0517006f" |
439 | 437 | ||
440 | [[package]] | 438 | [[package]] |
441 | name = "generator" | ||
442 | version = "0.6.24" | ||
443 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
444 | checksum = "a9fed24fd1e18827652b4d55652899a1e9da8e54d91624dc3437a5bc3a9f9a9c" | ||
445 | dependencies = [ | ||
446 | "cc", | ||
447 | "libc", | ||
448 | "log", | ||
449 | "rustversion", | ||
450 | "winapi", | ||
451 | ] | ||
452 | |||
453 | [[package]] | ||
454 | name = "gimli" | 439 | name = "gimli" |
455 | version = "0.23.0" | 440 | version = "0.23.0" |
456 | source = "registry+https://github.com/rust-lang/crates.io-index" | 441 | source = "registry+https://github.com/rust-lang/crates.io-index" |
@@ -845,17 +830,6 @@ dependencies = [ | |||
845 | ] | 830 | ] |
846 | 831 | ||
847 | [[package]] | 832 | [[package]] |
848 | name = "loom" | ||
849 | version = "0.4.0" | ||
850 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
851 | checksum = "d44c73b4636e497b4917eb21c33539efa3816741a2d3ff26c6316f1b529481a4" | ||
852 | dependencies = [ | ||
853 | "cfg-if", | ||
854 | "generator", | ||
855 | "scoped-tls", | ||
856 | ] | ||
857 | |||
858 | [[package]] | ||
859 | name = "lsp-server" | 833 | name = "lsp-server" |
860 | version = "0.5.0" | 834 | version = "0.5.0" |
861 | source = "registry+https://github.com/rust-lang/crates.io-index" | 835 | source = "registry+https://github.com/rust-lang/crates.io-index" |
@@ -944,9 +918,9 @@ dependencies = [ | |||
944 | 918 | ||
945 | [[package]] | 919 | [[package]] |
946 | name = "miniz_oxide" | 920 | name = "miniz_oxide" |
947 | version = "0.4.3" | 921 | version = "0.4.4" |
948 | source = "registry+https://github.com/rust-lang/crates.io-index" | 922 | source = "registry+https://github.com/rust-lang/crates.io-index" |
949 | checksum = "0f2d26ec3309788e423cfbf68ad1800f061638098d76a83681af979dc4eda19d" | 923 | checksum = "a92518e98c078586bc6c934028adcca4c92a53d6a958196de835170a01d84e4b" |
950 | dependencies = [ | 924 | dependencies = [ |
951 | "adler", | 925 | "adler", |
952 | "autocfg", | 926 | "autocfg", |
@@ -1040,9 +1014,9 @@ checksum = "a9a7ab5d64814df0fe4a4b5ead45ed6c5f181ee3ff04ba344313a6c80446c5d4" | |||
1040 | 1014 | ||
1041 | [[package]] | 1015 | [[package]] |
1042 | name = "once_cell" | 1016 | name = "once_cell" |
1043 | version = "1.6.0" | 1017 | version = "1.7.0" |
1044 | source = "registry+https://github.com/rust-lang/crates.io-index" | 1018 | source = "registry+https://github.com/rust-lang/crates.io-index" |
1045 | checksum = "4ad167a2f54e832b82dbe003a046280dceffe5227b5f79e08e363a29638cfddd" | 1019 | checksum = "10acf907b94fc1b1a152d08ef97e7759650268cf986bf127f387e602b02c7e5a" |
1046 | 1020 | ||
1047 | [[package]] | 1021 | [[package]] |
1048 | name = "oorandom" | 1022 | name = "oorandom" |
@@ -1400,9 +1374,9 @@ dependencies = [ | |||
1400 | 1374 | ||
1401 | [[package]] | 1375 | [[package]] |
1402 | name = "rustc-ap-rustc_lexer" | 1376 | name = "rustc-ap-rustc_lexer" |
1403 | version = "707.0.0" | 1377 | version = "708.0.0" |
1404 | source = "registry+https://github.com/rust-lang/crates.io-index" | 1378 | source = "registry+https://github.com/rust-lang/crates.io-index" |
1405 | checksum = "e27206aceb3b2153a3c367727d534362a1ce63a43ece8fd878195738a83aaf6f" | 1379 | checksum = "2706fc7106c75eaea49efe9f35f719a6fdfdb95212122ec2b543659406bae7ea" |
1406 | dependencies = [ | 1380 | dependencies = [ |
1407 | "unicode-xid", | 1381 | "unicode-xid", |
1408 | ] | 1382 | ] |
@@ -1420,12 +1394,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" | |||
1420 | checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" | 1394 | checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" |
1421 | 1395 | ||
1422 | [[package]] | 1396 | [[package]] |
1423 | name = "rustversion" | ||
1424 | version = "1.0.4" | ||
1425 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
1426 | checksum = "cb5d2a036dc6d2d8fd16fde3498b04306e29bd193bf306a57427019b823d5acd" | ||
1427 | |||
1428 | [[package]] | ||
1429 | name = "ryu" | 1397 | name = "ryu" |
1430 | version = "1.0.5" | 1398 | version = "1.0.5" |
1431 | source = "registry+https://github.com/rust-lang/crates.io-index" | 1399 | source = "registry+https://github.com/rust-lang/crates.io-index" |
@@ -1717,9 +1685,9 @@ dependencies = [ | |||
1717 | 1685 | ||
1718 | [[package]] | 1686 | [[package]] |
1719 | name = "tracing" | 1687 | name = "tracing" |
1720 | version = "0.1.24" | 1688 | version = "0.1.25" |
1721 | source = "registry+https://github.com/rust-lang/crates.io-index" | 1689 | source = "registry+https://github.com/rust-lang/crates.io-index" |
1722 | checksum = "f77d3842f76ca899ff2dbcf231c5c65813dea431301d6eb686279c15c4464f12" | 1690 | checksum = "01ebdc2bb4498ab1ab5f5b73c5803825e60199229ccba0698170e3be0e7f959f" |
1723 | dependencies = [ | 1691 | dependencies = [ |
1724 | "cfg-if", | 1692 | "cfg-if", |
1725 | "pin-project-lite", | 1693 | "pin-project-lite", |
diff --git a/crates/hir_expand/src/name.rs b/crates/hir_expand/src/name.rs index c7609e90d..c94fb580a 100644 --- a/crates/hir_expand/src/name.rs +++ b/crates/hir_expand/src/name.rs | |||
@@ -189,6 +189,7 @@ pub mod known { | |||
189 | // Components of known path (function name) | 189 | // Components of known path (function name) |
190 | filter_map, | 190 | filter_map, |
191 | next, | 191 | next, |
192 | iter_mut, | ||
192 | // Builtin macros | 193 | // Builtin macros |
193 | file, | 194 | file, |
194 | column, | 195 | column, |
diff --git a/crates/ide_assists/src/handlers/auto_import.rs b/crates/ide_assists/src/handlers/auto_import.rs index e93901cb3..dc38f90e9 100644 --- a/crates/ide_assists/src/handlers/auto_import.rs +++ b/crates/ide_assists/src/handlers/auto_import.rs | |||
@@ -33,9 +33,9 @@ use crate::{AssistContext, AssistId, AssistKind, Assists, GroupLabel}; | |||
33 | // use super::AssistContext; | 33 | // use super::AssistContext; |
34 | // ``` | 34 | // ``` |
35 | // | 35 | // |
36 | // .Merge Behaviour | 36 | // .Merge Behavior |
37 | // | 37 | // |
38 | // It is possible to configure how use-trees are merged with the `importMergeBehaviour` setting. | 38 | // It is possible to configure how use-trees are merged with the `importMergeBehavior` setting. |
39 | // It has the following configurations: | 39 | // It has the following configurations: |
40 | // | 40 | // |
41 | // - `full`: This setting will cause auto-import to always completely merge use-trees that share the | 41 | // - `full`: This setting will cause auto-import to always completely merge use-trees that share the |
@@ -46,7 +46,7 @@ use crate::{AssistContext, AssistId, AssistKind, Assists, GroupLabel}; | |||
46 | // - `none`: This setting will cause auto-import to never merge use-trees keeping them as simple | 46 | // - `none`: This setting will cause auto-import to never merge use-trees keeping them as simple |
47 | // paths. | 47 | // paths. |
48 | // | 48 | // |
49 | // In `VS Code` the configuration for this is `rust-analyzer.assist.importMergeBehaviour`. | 49 | // In `VS Code` the configuration for this is `rust-analyzer.assist.importMergeBehavior`. |
50 | // | 50 | // |
51 | // .Import Prefix | 51 | // .Import Prefix |
52 | // | 52 | // |
diff --git a/crates/ide_assists/src/handlers/convert_for_to_iter_for_each.rs b/crates/ide_assists/src/handlers/convert_for_to_iter_for_each.rs new file mode 100644 index 000000000..9fddf889c --- /dev/null +++ b/crates/ide_assists/src/handlers/convert_for_to_iter_for_each.rs | |||
@@ -0,0 +1,301 @@ | |||
1 | use ast::LoopBodyOwner; | ||
2 | use hir::known; | ||
3 | use ide_db::helpers::FamousDefs; | ||
4 | use stdx::format_to; | ||
5 | use syntax::{ast, AstNode}; | ||
6 | |||
7 | use crate::{AssistContext, AssistId, AssistKind, Assists}; | ||
8 | |||
9 | // Assist: convert_for_to_iter_for_each | ||
10 | // | ||
11 | // Converts a for loop into a for_each loop on the Iterator. | ||
12 | // | ||
13 | // ``` | ||
14 | // fn main() { | ||
15 | // let x = vec![1, 2, 3]; | ||
16 | // for $0v in x { | ||
17 | // let y = v * 2; | ||
18 | // } | ||
19 | // } | ||
20 | // ``` | ||
21 | // -> | ||
22 | // ``` | ||
23 | // fn main() { | ||
24 | // let x = vec![1, 2, 3]; | ||
25 | // x.into_iter().for_each(|v| { | ||
26 | // let y = v * 2; | ||
27 | // }); | ||
28 | // } | ||
29 | // ``` | ||
30 | pub(crate) fn convert_for_to_iter_for_each(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { | ||
31 | let for_loop = ctx.find_node_at_offset::<ast::ForExpr>()?; | ||
32 | let iterable = for_loop.iterable()?; | ||
33 | let pat = for_loop.pat()?; | ||
34 | let body = for_loop.loop_body()?; | ||
35 | |||
36 | acc.add( | ||
37 | AssistId("convert_for_to_iter_for_each", AssistKind::RefactorRewrite), | ||
38 | "Convert a for loop into an Iterator::for_each", | ||
39 | for_loop.syntax().text_range(), | ||
40 | |builder| { | ||
41 | let mut buf = String::new(); | ||
42 | |||
43 | if let Some((expr_behind_ref, method)) = | ||
44 | is_ref_and_impls_iter_method(&ctx.sema, &iterable) | ||
45 | { | ||
46 | // We have either "for x in &col" and col implements a method called iter | ||
47 | // or "for x in &mut col" and col implements a method called iter_mut | ||
48 | format_to!(buf, "{}.{}()", expr_behind_ref, method); | ||
49 | } else if impls_core_iter(&ctx.sema, &iterable) { | ||
50 | format_to!(buf, "{}", iterable); | ||
51 | } else { | ||
52 | if let ast::Expr::RefExpr(_) = iterable { | ||
53 | format_to!(buf, "({}).into_iter()", iterable); | ||
54 | } else { | ||
55 | format_to!(buf, "{}.into_iter()", iterable); | ||
56 | } | ||
57 | } | ||
58 | |||
59 | format_to!(buf, ".for_each(|{}| {});", pat, body); | ||
60 | |||
61 | builder.replace(for_loop.syntax().text_range(), buf) | ||
62 | }, | ||
63 | ) | ||
64 | } | ||
65 | |||
66 | /// If iterable is a reference where the expression behind the reference implements a method | ||
67 | /// returning an Iterator called iter or iter_mut (depending on the type of reference) then return | ||
68 | /// the expression behind the reference and the method name | ||
69 | fn is_ref_and_impls_iter_method( | ||
70 | sema: &hir::Semantics<ide_db::RootDatabase>, | ||
71 | iterable: &ast::Expr, | ||
72 | ) -> Option<(ast::Expr, hir::Name)> { | ||
73 | let ref_expr = match iterable { | ||
74 | ast::Expr::RefExpr(r) => r, | ||
75 | _ => return None, | ||
76 | }; | ||
77 | let wanted_method = if ref_expr.mut_token().is_some() { known::iter_mut } else { known::iter }; | ||
78 | let expr_behind_ref = ref_expr.expr()?; | ||
79 | let typ = sema.type_of_expr(&expr_behind_ref)?; | ||
80 | let scope = sema.scope(iterable.syntax()); | ||
81 | let krate = scope.module()?.krate(); | ||
82 | let traits_in_scope = scope.traits_in_scope(); | ||
83 | let iter_trait = FamousDefs(sema, Some(krate)).core_iter_Iterator()?; | ||
84 | let has_wanted_method = typ.iterate_method_candidates( | ||
85 | sema.db, | ||
86 | krate, | ||
87 | &traits_in_scope, | ||
88 | Some(&wanted_method), | ||
89 | |_, func| { | ||
90 | if func.ret_type(sema.db).impls_trait(sema.db, iter_trait, &[]) { | ||
91 | return Some(()); | ||
92 | } | ||
93 | None | ||
94 | }, | ||
95 | ); | ||
96 | has_wanted_method.and(Some((expr_behind_ref, wanted_method))) | ||
97 | } | ||
98 | |||
99 | /// Whether iterable implements core::Iterator | ||
100 | fn impls_core_iter(sema: &hir::Semantics<ide_db::RootDatabase>, iterable: &ast::Expr) -> bool { | ||
101 | let it_typ = if let Some(i) = sema.type_of_expr(iterable) { | ||
102 | i | ||
103 | } else { | ||
104 | return false; | ||
105 | }; | ||
106 | let module = if let Some(m) = sema.scope(iterable.syntax()).module() { | ||
107 | m | ||
108 | } else { | ||
109 | return false; | ||
110 | }; | ||
111 | let krate = module.krate(); | ||
112 | if let Some(iter_trait) = FamousDefs(sema, Some(krate)).core_iter_Iterator() { | ||
113 | return it_typ.impls_trait(sema.db, iter_trait, &[]); | ||
114 | } | ||
115 | false | ||
116 | } | ||
117 | |||
118 | #[cfg(test)] | ||
119 | mod tests { | ||
120 | use crate::tests::{check_assist, check_assist_not_applicable}; | ||
121 | |||
122 | use super::*; | ||
123 | |||
124 | const EMPTY_ITER_FIXTURE: &'static str = r" | ||
125 | //- /lib.rs deps:core crate:empty_iter | ||
126 | pub struct EmptyIter; | ||
127 | impl Iterator for EmptyIter { | ||
128 | type Item = usize; | ||
129 | fn next(&mut self) -> Option<Self::Item> { None } | ||
130 | } | ||
131 | |||
132 | pub struct Empty; | ||
133 | impl Empty { | ||
134 | pub fn iter(&self) -> EmptyIter { EmptyIter } | ||
135 | pub fn iter_mut(&self) -> EmptyIter { EmptyIter } | ||
136 | } | ||
137 | |||
138 | pub struct NoIterMethod; | ||
139 | "; | ||
140 | |||
141 | fn check_assist_with_fixtures(before: &str, after: &str) { | ||
142 | let before = &format!( | ||
143 | "//- /main.rs crate:main deps:core,empty_iter{}{}{}", | ||
144 | before, | ||
145 | FamousDefs::FIXTURE, | ||
146 | EMPTY_ITER_FIXTURE | ||
147 | ); | ||
148 | check_assist(convert_for_to_iter_for_each, before, after); | ||
149 | } | ||
150 | |||
151 | #[test] | ||
152 | fn test_not_for() { | ||
153 | check_assist_not_applicable( | ||
154 | convert_for_to_iter_for_each, | ||
155 | r" | ||
156 | let mut x = vec![1, 2, 3]; | ||
157 | x.iter_mut().$0for_each(|v| *v *= 2); | ||
158 | ", | ||
159 | ) | ||
160 | } | ||
161 | |||
162 | #[test] | ||
163 | fn test_simple_for() { | ||
164 | check_assist( | ||
165 | convert_for_to_iter_for_each, | ||
166 | r" | ||
167 | fn main() { | ||
168 | let x = vec![1, 2, 3]; | ||
169 | for $0v in x { | ||
170 | v *= 2; | ||
171 | } | ||
172 | }", | ||
173 | r" | ||
174 | fn main() { | ||
175 | let x = vec![1, 2, 3]; | ||
176 | x.into_iter().for_each(|v| { | ||
177 | v *= 2; | ||
178 | }); | ||
179 | }", | ||
180 | ) | ||
181 | } | ||
182 | |||
183 | #[test] | ||
184 | fn test_for_borrowed() { | ||
185 | check_assist_with_fixtures( | ||
186 | r" | ||
187 | use empty_iter::*; | ||
188 | fn main() { | ||
189 | let x = Empty; | ||
190 | for $0v in &x { | ||
191 | let a = v * 2; | ||
192 | } | ||
193 | } | ||
194 | ", | ||
195 | r" | ||
196 | use empty_iter::*; | ||
197 | fn main() { | ||
198 | let x = Empty; | ||
199 | x.iter().for_each(|v| { | ||
200 | let a = v * 2; | ||
201 | }); | ||
202 | } | ||
203 | ", | ||
204 | ) | ||
205 | } | ||
206 | |||
207 | #[test] | ||
208 | fn test_for_borrowed_no_iter_method() { | ||
209 | check_assist_with_fixtures( | ||
210 | r" | ||
211 | use empty_iter::*; | ||
212 | fn main() { | ||
213 | let x = NoIterMethod; | ||
214 | for $0v in &x { | ||
215 | let a = v * 2; | ||
216 | } | ||
217 | } | ||
218 | ", | ||
219 | r" | ||
220 | use empty_iter::*; | ||
221 | fn main() { | ||
222 | let x = NoIterMethod; | ||
223 | (&x).into_iter().for_each(|v| { | ||
224 | let a = v * 2; | ||
225 | }); | ||
226 | } | ||
227 | ", | ||
228 | ) | ||
229 | } | ||
230 | |||
231 | #[test] | ||
232 | fn test_for_borrowed_mut() { | ||
233 | check_assist_with_fixtures( | ||
234 | r" | ||
235 | use empty_iter::*; | ||
236 | fn main() { | ||
237 | let x = Empty; | ||
238 | for $0v in &mut x { | ||
239 | let a = v * 2; | ||
240 | } | ||
241 | } | ||
242 | ", | ||
243 | r" | ||
244 | use empty_iter::*; | ||
245 | fn main() { | ||
246 | let x = Empty; | ||
247 | x.iter_mut().for_each(|v| { | ||
248 | let a = v * 2; | ||
249 | }); | ||
250 | } | ||
251 | ", | ||
252 | ) | ||
253 | } | ||
254 | |||
255 | #[test] | ||
256 | fn test_for_borrowed_mut_behind_var() { | ||
257 | check_assist( | ||
258 | convert_for_to_iter_for_each, | ||
259 | r" | ||
260 | fn main() { | ||
261 | let x = vec![1, 2, 3]; | ||
262 | let y = &mut x; | ||
263 | for $0v in y { | ||
264 | *v *= 2; | ||
265 | } | ||
266 | }", | ||
267 | r" | ||
268 | fn main() { | ||
269 | let x = vec![1, 2, 3]; | ||
270 | let y = &mut x; | ||
271 | y.into_iter().for_each(|v| { | ||
272 | *v *= 2; | ||
273 | }); | ||
274 | }", | ||
275 | ) | ||
276 | } | ||
277 | |||
278 | #[test] | ||
279 | fn test_already_impls_iterator() { | ||
280 | check_assist_with_fixtures( | ||
281 | r#" | ||
282 | use empty_iter::*; | ||
283 | fn main() { | ||
284 | let x = Empty; | ||
285 | for$0 a in x.iter().take(1) { | ||
286 | println!("{}", a); | ||
287 | } | ||
288 | } | ||
289 | "#, | ||
290 | r#" | ||
291 | use empty_iter::*; | ||
292 | fn main() { | ||
293 | let x = Empty; | ||
294 | x.iter().take(1).for_each(|a| { | ||
295 | println!("{}", a); | ||
296 | }); | ||
297 | } | ||
298 | "#, | ||
299 | ); | ||
300 | } | ||
301 | } | ||
diff --git a/crates/ide_assists/src/handlers/flip_comma.rs b/crates/ide_assists/src/handlers/flip_comma.rs index 18cf64a34..7f5e75d34 100644 --- a/crates/ide_assists/src/handlers/flip_comma.rs +++ b/crates/ide_assists/src/handlers/flip_comma.rs | |||
@@ -1,4 +1,4 @@ | |||
1 | use syntax::{algo::non_trivia_sibling, Direction, T}; | 1 | use syntax::{algo::non_trivia_sibling, Direction, SyntaxKind, T}; |
2 | 2 | ||
3 | use crate::{AssistContext, AssistId, AssistKind, Assists}; | 3 | use crate::{AssistContext, AssistId, AssistKind, Assists}; |
4 | 4 | ||
@@ -28,6 +28,12 @@ pub(crate) fn flip_comma(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { | |||
28 | return None; | 28 | return None; |
29 | } | 29 | } |
30 | 30 | ||
31 | // Don't apply a "flip" inside the macro call | ||
32 | // since macro input are just mere tokens | ||
33 | if comma.ancestors().any(|it| it.kind() == SyntaxKind::MACRO_CALL) { | ||
34 | return None; | ||
35 | } | ||
36 | |||
31 | acc.add( | 37 | acc.add( |
32 | AssistId("flip_comma", AssistKind::RefactorRewrite), | 38 | AssistId("flip_comma", AssistKind::RefactorRewrite), |
33 | "Flip comma", | 39 | "Flip comma", |
@@ -43,7 +49,7 @@ pub(crate) fn flip_comma(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { | |||
43 | mod tests { | 49 | mod tests { |
44 | use super::*; | 50 | use super::*; |
45 | 51 | ||
46 | use crate::tests::{check_assist, check_assist_target}; | 52 | use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target}; |
47 | 53 | ||
48 | #[test] | 54 | #[test] |
49 | fn flip_comma_works_for_function_parameters() { | 55 | fn flip_comma_works_for_function_parameters() { |
@@ -81,4 +87,20 @@ mod tests { | |||
81 | ",", | 87 | ",", |
82 | ); | 88 | ); |
83 | } | 89 | } |
90 | |||
91 | #[test] | ||
92 | fn flip_comma_works() { | ||
93 | check_assist( | ||
94 | flip_comma, | ||
95 | r#"fn main() {((1, 2),$0 (3, 4));}"#, | ||
96 | r#"fn main() {((3, 4), (1, 2));}"#, | ||
97 | ) | ||
98 | } | ||
99 | |||
100 | #[test] | ||
101 | fn flip_comma_not_applicable_for_macro_input() { | ||
102 | // "Flip comma" assist shouldn't be applicable inside the macro call | ||
103 | // See https://github.com/rust-analyzer/rust-analyzer/issues/7693 | ||
104 | check_assist_not_applicable(flip_comma, r#"bar!(a,$0 b)"#); | ||
105 | } | ||
84 | } | 106 | } |
diff --git a/crates/ide_assists/src/handlers/generate_enum_match_method.rs b/crates/ide_assists/src/handlers/generate_enum_is_method.rs index aeb887e71..7e181a480 100644 --- a/crates/ide_assists/src/handlers/generate_enum_match_method.rs +++ b/crates/ide_assists/src/handlers/generate_enum_is_method.rs | |||
@@ -1,14 +1,13 @@ | |||
1 | use stdx::{format_to, to_lower_snake_case}; | 1 | use stdx::to_lower_snake_case; |
2 | use syntax::ast::VisibilityOwner; | 2 | use syntax::ast::VisibilityOwner; |
3 | use syntax::ast::{self, AstNode, NameOwner}; | 3 | use syntax::ast::{self, AstNode, NameOwner}; |
4 | use test_utils::mark; | ||
5 | 4 | ||
6 | use crate::{ | 5 | use crate::{ |
7 | utils::{find_impl_block_end, find_struct_impl, generate_impl_text}, | 6 | utils::{add_method_to_adt, find_struct_impl}, |
8 | AssistContext, AssistId, AssistKind, Assists, | 7 | AssistContext, AssistId, AssistKind, Assists, |
9 | }; | 8 | }; |
10 | 9 | ||
11 | // Assist: generate_enum_match_method | 10 | // Assist: generate_enum_is_method |
12 | // | 11 | // |
13 | // Generate an `is_` method for an enum variant. | 12 | // Generate an `is_` method for an enum variant. |
14 | // | 13 | // |
@@ -34,79 +33,52 @@ use crate::{ | |||
34 | // } | 33 | // } |
35 | // } | 34 | // } |
36 | // ``` | 35 | // ``` |
37 | pub(crate) fn generate_enum_match_method(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { | 36 | pub(crate) fn generate_enum_is_method(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { |
38 | let variant = ctx.find_node_at_offset::<ast::Variant>()?; | 37 | let variant = ctx.find_node_at_offset::<ast::Variant>()?; |
39 | let variant_name = variant.name()?; | 38 | let variant_name = variant.name()?; |
40 | let parent_enum = variant.parent_enum(); | 39 | let parent_enum = ast::Adt::Enum(variant.parent_enum()); |
41 | if !matches!(variant.kind(), ast::StructKind::Unit) { | 40 | let pattern_suffix = match variant.kind() { |
42 | mark::hit!(test_gen_enum_match_on_non_unit_variant_not_implemented); | 41 | ast::StructKind::Record(_) => " { .. }", |
43 | return None; | 42 | ast::StructKind::Tuple(_) => "(..)", |
44 | } | 43 | ast::StructKind::Unit => "", |
44 | }; | ||
45 | 45 | ||
46 | let enum_lowercase_name = to_lower_snake_case(&parent_enum.name()?.to_string()); | 46 | let enum_lowercase_name = to_lower_snake_case(&parent_enum.name()?.to_string()); |
47 | let fn_name = to_lower_snake_case(&variant_name.to_string()); | 47 | let fn_name = format!("is_{}", &to_lower_snake_case(variant_name.text())); |
48 | 48 | ||
49 | // Return early if we've found an existing new fn | 49 | // Return early if we've found an existing new fn |
50 | let impl_def = find_struct_impl( | 50 | let impl_def = find_struct_impl(&ctx, &parent_enum, &fn_name)?; |
51 | &ctx, | ||
52 | &ast::Adt::Enum(parent_enum.clone()), | ||
53 | format!("is_{}", fn_name).as_str(), | ||
54 | )?; | ||
55 | 51 | ||
56 | let target = variant.syntax().text_range(); | 52 | let target = variant.syntax().text_range(); |
57 | acc.add( | 53 | acc.add( |
58 | AssistId("generate_enum_match_method", AssistKind::Generate), | 54 | AssistId("generate_enum_is_method", AssistKind::Generate), |
59 | "Generate an `is_` method for an enum variant", | 55 | "Generate an `is_` method for an enum variant", |
60 | target, | 56 | target, |
61 | |builder| { | 57 | |builder| { |
62 | let mut buf = String::with_capacity(512); | ||
63 | |||
64 | if impl_def.is_some() { | ||
65 | buf.push('\n'); | ||
66 | } | ||
67 | |||
68 | let vis = parent_enum.visibility().map_or(String::new(), |v| format!("{} ", v)); | 58 | let vis = parent_enum.visibility().map_or(String::new(), |v| format!("{} ", v)); |
69 | format_to!( | 59 | let method = format!( |
70 | buf, | ||
71 | " /// Returns `true` if the {} is [`{}`]. | 60 | " /// Returns `true` if the {} is [`{}`]. |
72 | {}fn is_{}(&self) -> bool {{ | 61 | {}fn {}(&self) -> bool {{ |
73 | matches!(self, Self::{}) | 62 | matches!(self, Self::{}{}) |
74 | }}", | 63 | }}", |
75 | enum_lowercase_name, | 64 | enum_lowercase_name, variant_name, vis, fn_name, variant_name, pattern_suffix, |
76 | variant_name, | ||
77 | vis, | ||
78 | fn_name, | ||
79 | variant_name | ||
80 | ); | 65 | ); |
81 | 66 | ||
82 | let start_offset = impl_def | 67 | add_method_to_adt(builder, &parent_enum, impl_def, &method); |
83 | .and_then(|impl_def| find_impl_block_end(impl_def, &mut buf)) | ||
84 | .unwrap_or_else(|| { | ||
85 | buf = generate_impl_text(&ast::Adt::Enum(parent_enum.clone()), &buf); | ||
86 | parent_enum.syntax().text_range().end() | ||
87 | }); | ||
88 | |||
89 | builder.insert(start_offset, buf); | ||
90 | }, | 68 | }, |
91 | ) | 69 | ) |
92 | } | 70 | } |
93 | 71 | ||
94 | #[cfg(test)] | 72 | #[cfg(test)] |
95 | mod tests { | 73 | mod tests { |
96 | use test_utils::mark; | ||
97 | |||
98 | use crate::tests::{check_assist, check_assist_not_applicable}; | 74 | use crate::tests::{check_assist, check_assist_not_applicable}; |
99 | 75 | ||
100 | use super::*; | 76 | use super::*; |
101 | 77 | ||
102 | fn check_not_applicable(ra_fixture: &str) { | ||
103 | check_assist_not_applicable(generate_enum_match_method, ra_fixture) | ||
104 | } | ||
105 | |||
106 | #[test] | 78 | #[test] |
107 | fn test_generate_enum_match_from_variant() { | 79 | fn test_generate_enum_is_from_variant() { |
108 | check_assist( | 80 | check_assist( |
109 | generate_enum_match_method, | 81 | generate_enum_is_method, |
110 | r#" | 82 | r#" |
111 | enum Variant { | 83 | enum Variant { |
112 | Undefined, | 84 | Undefined, |
@@ -129,8 +101,9 @@ impl Variant { | |||
129 | } | 101 | } |
130 | 102 | ||
131 | #[test] | 103 | #[test] |
132 | fn test_generate_enum_match_already_implemented() { | 104 | fn test_generate_enum_is_already_implemented() { |
133 | check_not_applicable( | 105 | check_assist_not_applicable( |
106 | generate_enum_is_method, | ||
134 | r#" | 107 | r#" |
135 | enum Variant { | 108 | enum Variant { |
136 | Undefined, | 109 | Undefined, |
@@ -147,22 +120,59 @@ impl Variant { | |||
147 | } | 120 | } |
148 | 121 | ||
149 | #[test] | 122 | #[test] |
150 | fn test_add_from_impl_no_element() { | 123 | fn test_generate_enum_is_from_tuple_variant() { |
151 | mark::check!(test_gen_enum_match_on_non_unit_variant_not_implemented); | 124 | check_assist( |
152 | check_not_applicable( | 125 | generate_enum_is_method, |
153 | r#" | 126 | r#" |
154 | enum Variant { | 127 | enum Variant { |
155 | Undefined, | 128 | Undefined, |
156 | Minor(u32)$0, | 129 | Minor(u32)$0, |
157 | Major, | 130 | Major, |
158 | }"#, | 131 | }"#, |
132 | r#"enum Variant { | ||
133 | Undefined, | ||
134 | Minor(u32), | ||
135 | Major, | ||
136 | } | ||
137 | |||
138 | impl Variant { | ||
139 | /// Returns `true` if the variant is [`Minor`]. | ||
140 | fn is_minor(&self) -> bool { | ||
141 | matches!(self, Self::Minor(..)) | ||
142 | } | ||
143 | }"#, | ||
144 | ); | ||
145 | } | ||
146 | |||
147 | #[test] | ||
148 | fn test_generate_enum_is_from_record_variant() { | ||
149 | check_assist( | ||
150 | generate_enum_is_method, | ||
151 | r#" | ||
152 | enum Variant { | ||
153 | Undefined, | ||
154 | Minor { foo: i32 }$0, | ||
155 | Major, | ||
156 | }"#, | ||
157 | r#"enum Variant { | ||
158 | Undefined, | ||
159 | Minor { foo: i32 }, | ||
160 | Major, | ||
161 | } | ||
162 | |||
163 | impl Variant { | ||
164 | /// Returns `true` if the variant is [`Minor`]. | ||
165 | fn is_minor(&self) -> bool { | ||
166 | matches!(self, Self::Minor { .. }) | ||
167 | } | ||
168 | }"#, | ||
159 | ); | 169 | ); |
160 | } | 170 | } |
161 | 171 | ||
162 | #[test] | 172 | #[test] |
163 | fn test_generate_enum_match_from_variant_with_one_variant() { | 173 | fn test_generate_enum_is_from_variant_with_one_variant() { |
164 | check_assist( | 174 | check_assist( |
165 | generate_enum_match_method, | 175 | generate_enum_is_method, |
166 | r#"enum Variant { Undefi$0ned }"#, | 176 | r#"enum Variant { Undefi$0ned }"#, |
167 | r#" | 177 | r#" |
168 | enum Variant { Undefined } | 178 | enum Variant { Undefined } |
@@ -177,9 +187,9 @@ impl Variant { | |||
177 | } | 187 | } |
178 | 188 | ||
179 | #[test] | 189 | #[test] |
180 | fn test_generate_enum_match_from_variant_with_visibility_marker() { | 190 | fn test_generate_enum_is_from_variant_with_visibility_marker() { |
181 | check_assist( | 191 | check_assist( |
182 | generate_enum_match_method, | 192 | generate_enum_is_method, |
183 | r#" | 193 | r#" |
184 | pub(crate) enum Variant { | 194 | pub(crate) enum Variant { |
185 | Undefined, | 195 | Undefined, |
@@ -202,9 +212,9 @@ impl Variant { | |||
202 | } | 212 | } |
203 | 213 | ||
204 | #[test] | 214 | #[test] |
205 | fn test_multiple_generate_enum_match_from_variant() { | 215 | fn test_multiple_generate_enum_is_from_variant() { |
206 | check_assist( | 216 | check_assist( |
207 | generate_enum_match_method, | 217 | generate_enum_is_method, |
208 | r#" | 218 | r#" |
209 | enum Variant { | 219 | enum Variant { |
210 | Undefined, | 220 | Undefined, |
diff --git a/crates/ide_assists/src/handlers/generate_enum_projection_method.rs b/crates/ide_assists/src/handlers/generate_enum_projection_method.rs new file mode 100644 index 000000000..871bcab50 --- /dev/null +++ b/crates/ide_assists/src/handlers/generate_enum_projection_method.rs | |||
@@ -0,0 +1,331 @@ | |||
1 | use itertools::Itertools; | ||
2 | use stdx::to_lower_snake_case; | ||
3 | use syntax::ast::VisibilityOwner; | ||
4 | use syntax::ast::{self, AstNode, NameOwner}; | ||
5 | |||
6 | use crate::{ | ||
7 | utils::{add_method_to_adt, find_struct_impl}, | ||
8 | AssistContext, AssistId, AssistKind, Assists, | ||
9 | }; | ||
10 | |||
11 | // Assist: generate_enum_try_into_method | ||
12 | // | ||
13 | // Generate an `try_into_` method for an enum variant. | ||
14 | // | ||
15 | // ``` | ||
16 | // enum Value { | ||
17 | // Number(i32), | ||
18 | // Text(String)$0, | ||
19 | // } | ||
20 | // ``` | ||
21 | // -> | ||
22 | // ``` | ||
23 | // enum Value { | ||
24 | // Number(i32), | ||
25 | // Text(String), | ||
26 | // } | ||
27 | // | ||
28 | // impl Value { | ||
29 | // fn try_into_text(self) -> Result<String, Self> { | ||
30 | // if let Self::Text(v) = self { | ||
31 | // Ok(v) | ||
32 | // } else { | ||
33 | // Err(self) | ||
34 | // } | ||
35 | // } | ||
36 | // } | ||
37 | // ``` | ||
38 | pub(crate) fn generate_enum_try_into_method(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { | ||
39 | generate_enum_projection_method( | ||
40 | acc, | ||
41 | ctx, | ||
42 | "generate_enum_try_into_method", | ||
43 | "Generate an `try_into_` method for an enum variant", | ||
44 | ProjectionProps { | ||
45 | fn_name_prefix: "try_into", | ||
46 | self_param: "self", | ||
47 | return_prefix: "Result<", | ||
48 | return_suffix: ", Self>", | ||
49 | happy_case: "Ok", | ||
50 | sad_case: "Err(self)", | ||
51 | }, | ||
52 | ) | ||
53 | } | ||
54 | |||
55 | // Assist: generate_enum_as_method | ||
56 | // | ||
57 | // Generate an `as_` method for an enum variant. | ||
58 | // | ||
59 | // ``` | ||
60 | // enum Value { | ||
61 | // Number(i32), | ||
62 | // Text(String)$0, | ||
63 | // } | ||
64 | // ``` | ||
65 | // -> | ||
66 | // ``` | ||
67 | // enum Value { | ||
68 | // Number(i32), | ||
69 | // Text(String), | ||
70 | // } | ||
71 | // | ||
72 | // impl Value { | ||
73 | // fn as_text(&self) -> Option<&String> { | ||
74 | // if let Self::Text(v) = self { | ||
75 | // Some(v) | ||
76 | // } else { | ||
77 | // None | ||
78 | // } | ||
79 | // } | ||
80 | // } | ||
81 | // ``` | ||
82 | pub(crate) fn generate_enum_as_method(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { | ||
83 | generate_enum_projection_method( | ||
84 | acc, | ||
85 | ctx, | ||
86 | "generate_enum_as_method", | ||
87 | "Generate an `as_` method for an enum variant", | ||
88 | ProjectionProps { | ||
89 | fn_name_prefix: "as", | ||
90 | self_param: "&self", | ||
91 | return_prefix: "Option<&", | ||
92 | return_suffix: ">", | ||
93 | happy_case: "Some", | ||
94 | sad_case: "None", | ||
95 | }, | ||
96 | ) | ||
97 | } | ||
98 | |||
99 | struct ProjectionProps { | ||
100 | fn_name_prefix: &'static str, | ||
101 | self_param: &'static str, | ||
102 | return_prefix: &'static str, | ||
103 | return_suffix: &'static str, | ||
104 | happy_case: &'static str, | ||
105 | sad_case: &'static str, | ||
106 | } | ||
107 | |||
108 | fn generate_enum_projection_method( | ||
109 | acc: &mut Assists, | ||
110 | ctx: &AssistContext, | ||
111 | assist_id: &'static str, | ||
112 | assist_description: &str, | ||
113 | props: ProjectionProps, | ||
114 | ) -> Option<()> { | ||
115 | let variant = ctx.find_node_at_offset::<ast::Variant>()?; | ||
116 | let variant_name = variant.name()?; | ||
117 | let parent_enum = ast::Adt::Enum(variant.parent_enum()); | ||
118 | |||
119 | let (pattern_suffix, field_type, bound_name) = match variant.kind() { | ||
120 | ast::StructKind::Record(record) => { | ||
121 | let (field,) = record.fields().collect_tuple()?; | ||
122 | let name = field.name()?.to_string(); | ||
123 | let ty = field.ty()?; | ||
124 | let pattern_suffix = format!(" {{ {} }}", name); | ||
125 | (pattern_suffix, ty, name) | ||
126 | } | ||
127 | ast::StructKind::Tuple(tuple) => { | ||
128 | let (field,) = tuple.fields().collect_tuple()?; | ||
129 | let ty = field.ty()?; | ||
130 | ("(v)".to_owned(), ty, "v".to_owned()) | ||
131 | } | ||
132 | ast::StructKind::Unit => return None, | ||
133 | }; | ||
134 | |||
135 | let fn_name = format!("{}_{}", props.fn_name_prefix, &to_lower_snake_case(variant_name.text())); | ||
136 | |||
137 | // Return early if we've found an existing new fn | ||
138 | let impl_def = find_struct_impl(&ctx, &parent_enum, &fn_name)?; | ||
139 | |||
140 | let target = variant.syntax().text_range(); | ||
141 | acc.add(AssistId(assist_id, AssistKind::Generate), assist_description, target, |builder| { | ||
142 | let vis = parent_enum.visibility().map_or(String::new(), |v| format!("{} ", v)); | ||
143 | let method = format!( | ||
144 | " {0}fn {1}({2}) -> {3}{4}{5} {{ | ||
145 | if let Self::{6}{7} = self {{ | ||
146 | {8}({9}) | ||
147 | }} else {{ | ||
148 | {10} | ||
149 | }} | ||
150 | }}", | ||
151 | vis, | ||
152 | fn_name, | ||
153 | props.self_param, | ||
154 | props.return_prefix, | ||
155 | field_type.syntax(), | ||
156 | props.return_suffix, | ||
157 | variant_name, | ||
158 | pattern_suffix, | ||
159 | props.happy_case, | ||
160 | bound_name, | ||
161 | props.sad_case, | ||
162 | ); | ||
163 | |||
164 | add_method_to_adt(builder, &parent_enum, impl_def, &method); | ||
165 | }) | ||
166 | } | ||
167 | |||
168 | #[cfg(test)] | ||
169 | mod tests { | ||
170 | use crate::tests::{check_assist, check_assist_not_applicable}; | ||
171 | |||
172 | use super::*; | ||
173 | |||
174 | #[test] | ||
175 | fn test_generate_enum_try_into_tuple_variant() { | ||
176 | check_assist( | ||
177 | generate_enum_try_into_method, | ||
178 | r#" | ||
179 | enum Value { | ||
180 | Number(i32), | ||
181 | Text(String)$0, | ||
182 | }"#, | ||
183 | r#"enum Value { | ||
184 | Number(i32), | ||
185 | Text(String), | ||
186 | } | ||
187 | |||
188 | impl Value { | ||
189 | fn try_into_text(self) -> Result<String, Self> { | ||
190 | if let Self::Text(v) = self { | ||
191 | Ok(v) | ||
192 | } else { | ||
193 | Err(self) | ||
194 | } | ||
195 | } | ||
196 | }"#, | ||
197 | ); | ||
198 | } | ||
199 | |||
200 | #[test] | ||
201 | fn test_generate_enum_try_into_already_implemented() { | ||
202 | check_assist_not_applicable( | ||
203 | generate_enum_try_into_method, | ||
204 | r#"enum Value { | ||
205 | Number(i32), | ||
206 | Text(String)$0, | ||
207 | } | ||
208 | |||
209 | impl Value { | ||
210 | fn try_into_text(self) -> Result<String, Self> { | ||
211 | if let Self::Text(v) = self { | ||
212 | Ok(v) | ||
213 | } else { | ||
214 | Err(self) | ||
215 | } | ||
216 | } | ||
217 | }"#, | ||
218 | ); | ||
219 | } | ||
220 | |||
221 | #[test] | ||
222 | fn test_generate_enum_try_into_unit_variant() { | ||
223 | check_assist_not_applicable( | ||
224 | generate_enum_try_into_method, | ||
225 | r#"enum Value { | ||
226 | Number(i32), | ||
227 | Text(String), | ||
228 | Unit$0, | ||
229 | }"#, | ||
230 | ); | ||
231 | } | ||
232 | |||
233 | #[test] | ||
234 | fn test_generate_enum_try_into_record_with_multiple_fields() { | ||
235 | check_assist_not_applicable( | ||
236 | generate_enum_try_into_method, | ||
237 | r#"enum Value { | ||
238 | Number(i32), | ||
239 | Text(String), | ||
240 | Both { first: i32, second: String }$0, | ||
241 | }"#, | ||
242 | ); | ||
243 | } | ||
244 | |||
245 | #[test] | ||
246 | fn test_generate_enum_try_into_tuple_with_multiple_fields() { | ||
247 | check_assist_not_applicable( | ||
248 | generate_enum_try_into_method, | ||
249 | r#"enum Value { | ||
250 | Number(i32), | ||
251 | Text(String, String)$0, | ||
252 | }"#, | ||
253 | ); | ||
254 | } | ||
255 | |||
256 | #[test] | ||
257 | fn test_generate_enum_try_into_record_variant() { | ||
258 | check_assist( | ||
259 | generate_enum_try_into_method, | ||
260 | r#"enum Value { | ||
261 | Number(i32), | ||
262 | Text { text: String }$0, | ||
263 | }"#, | ||
264 | r#"enum Value { | ||
265 | Number(i32), | ||
266 | Text { text: String }, | ||
267 | } | ||
268 | |||
269 | impl Value { | ||
270 | fn try_into_text(self) -> Result<String, Self> { | ||
271 | if let Self::Text { text } = self { | ||
272 | Ok(text) | ||
273 | } else { | ||
274 | Err(self) | ||
275 | } | ||
276 | } | ||
277 | }"#, | ||
278 | ); | ||
279 | } | ||
280 | |||
281 | #[test] | ||
282 | fn test_generate_enum_as_tuple_variant() { | ||
283 | check_assist( | ||
284 | generate_enum_as_method, | ||
285 | r#" | ||
286 | enum Value { | ||
287 | Number(i32), | ||
288 | Text(String)$0, | ||
289 | }"#, | ||
290 | r#"enum Value { | ||
291 | Number(i32), | ||
292 | Text(String), | ||
293 | } | ||
294 | |||
295 | impl Value { | ||
296 | fn as_text(&self) -> Option<&String> { | ||
297 | if let Self::Text(v) = self { | ||
298 | Some(v) | ||
299 | } else { | ||
300 | None | ||
301 | } | ||
302 | } | ||
303 | }"#, | ||
304 | ); | ||
305 | } | ||
306 | |||
307 | #[test] | ||
308 | fn test_generate_enum_as_record_variant() { | ||
309 | check_assist( | ||
310 | generate_enum_as_method, | ||
311 | r#"enum Value { | ||
312 | Number(i32), | ||
313 | Text { text: String }$0, | ||
314 | }"#, | ||
315 | r#"enum Value { | ||
316 | Number(i32), | ||
317 | Text { text: String }, | ||
318 | } | ||
319 | |||
320 | impl Value { | ||
321 | fn as_text(&self) -> Option<&String> { | ||
322 | if let Self::Text { text } = self { | ||
323 | Some(text) | ||
324 | } else { | ||
325 | None | ||
326 | } | ||
327 | } | ||
328 | }"#, | ||
329 | ); | ||
330 | } | ||
331 | } | ||
diff --git a/crates/ide_assists/src/lib.rs b/crates/ide_assists/src/lib.rs index 7067cf8b6..4c067d451 100644 --- a/crates/ide_assists/src/lib.rs +++ b/crates/ide_assists/src/lib.rs | |||
@@ -114,6 +114,7 @@ mod handlers { | |||
114 | mod apply_demorgan; | 114 | mod apply_demorgan; |
115 | mod auto_import; | 115 | mod auto_import; |
116 | mod change_visibility; | 116 | mod change_visibility; |
117 | mod convert_for_to_iter_for_each; | ||
117 | mod convert_integer_literal; | 118 | mod convert_integer_literal; |
118 | mod early_return; | 119 | mod early_return; |
119 | mod expand_glob_import; | 120 | mod expand_glob_import; |
@@ -127,7 +128,8 @@ mod handlers { | |||
127 | mod flip_trait_bound; | 128 | mod flip_trait_bound; |
128 | mod generate_default_from_enum_variant; | 129 | mod generate_default_from_enum_variant; |
129 | mod generate_derive; | 130 | mod generate_derive; |
130 | mod generate_enum_match_method; | 131 | mod generate_enum_is_method; |
132 | mod generate_enum_projection_method; | ||
131 | mod generate_from_impl_for_enum; | 133 | mod generate_from_impl_for_enum; |
132 | mod generate_function; | 134 | mod generate_function; |
133 | mod generate_getter; | 135 | mod generate_getter; |
@@ -175,6 +177,7 @@ mod handlers { | |||
175 | apply_demorgan::apply_demorgan, | 177 | apply_demorgan::apply_demorgan, |
176 | auto_import::auto_import, | 178 | auto_import::auto_import, |
177 | change_visibility::change_visibility, | 179 | change_visibility::change_visibility, |
180 | convert_for_to_iter_for_each::convert_for_to_iter_for_each, | ||
178 | convert_integer_literal::convert_integer_literal, | 181 | convert_integer_literal::convert_integer_literal, |
179 | early_return::convert_to_guarded_return, | 182 | early_return::convert_to_guarded_return, |
180 | expand_glob_import::expand_glob_import, | 183 | expand_glob_import::expand_glob_import, |
@@ -187,7 +190,9 @@ mod handlers { | |||
187 | flip_trait_bound::flip_trait_bound, | 190 | flip_trait_bound::flip_trait_bound, |
188 | generate_default_from_enum_variant::generate_default_from_enum_variant, | 191 | generate_default_from_enum_variant::generate_default_from_enum_variant, |
189 | generate_derive::generate_derive, | 192 | generate_derive::generate_derive, |
190 | generate_enum_match_method::generate_enum_match_method, | 193 | generate_enum_is_method::generate_enum_is_method, |
194 | generate_enum_projection_method::generate_enum_try_into_method, | ||
195 | generate_enum_projection_method::generate_enum_as_method, | ||
191 | generate_from_impl_for_enum::generate_from_impl_for_enum, | 196 | generate_from_impl_for_enum::generate_from_impl_for_enum, |
192 | generate_function::generate_function, | 197 | generate_function::generate_function, |
193 | generate_getter::generate_getter, | 198 | generate_getter::generate_getter, |
diff --git a/crates/ide_assists/src/tests/generated.rs b/crates/ide_assists/src/tests/generated.rs index 701091a6b..7f6dbbccf 100644 --- a/crates/ide_assists/src/tests/generated.rs +++ b/crates/ide_assists/src/tests/generated.rs | |||
@@ -193,6 +193,29 @@ pub(crate) fn frobnicate() {} | |||
193 | } | 193 | } |
194 | 194 | ||
195 | #[test] | 195 | #[test] |
196 | fn doctest_convert_for_to_iter_for_each() { | ||
197 | check_doc_test( | ||
198 | "convert_for_to_iter_for_each", | ||
199 | r#####" | ||
200 | fn main() { | ||
201 | let x = vec![1, 2, 3]; | ||
202 | for $0v in x { | ||
203 | let y = v * 2; | ||
204 | } | ||
205 | } | ||
206 | "#####, | ||
207 | r#####" | ||
208 | fn main() { | ||
209 | let x = vec![1, 2, 3]; | ||
210 | x.into_iter().for_each(|v| { | ||
211 | let y = v * 2; | ||
212 | }); | ||
213 | } | ||
214 | "#####, | ||
215 | ) | ||
216 | } | ||
217 | |||
218 | #[test] | ||
196 | fn doctest_convert_integer_literal() { | 219 | fn doctest_convert_integer_literal() { |
197 | check_doc_test( | 220 | check_doc_test( |
198 | "convert_integer_literal", | 221 | "convert_integer_literal", |
@@ -460,9 +483,38 @@ struct Point { | |||
460 | } | 483 | } |
461 | 484 | ||
462 | #[test] | 485 | #[test] |
463 | fn doctest_generate_enum_match_method() { | 486 | fn doctest_generate_enum_as_method() { |
487 | check_doc_test( | ||
488 | "generate_enum_as_method", | ||
489 | r#####" | ||
490 | enum Value { | ||
491 | Number(i32), | ||
492 | Text(String)$0, | ||
493 | } | ||
494 | "#####, | ||
495 | r#####" | ||
496 | enum Value { | ||
497 | Number(i32), | ||
498 | Text(String), | ||
499 | } | ||
500 | |||
501 | impl Value { | ||
502 | fn as_text(&self) -> Option<&String> { | ||
503 | if let Self::Text(v) = self { | ||
504 | Some(v) | ||
505 | } else { | ||
506 | None | ||
507 | } | ||
508 | } | ||
509 | } | ||
510 | "#####, | ||
511 | ) | ||
512 | } | ||
513 | |||
514 | #[test] | ||
515 | fn doctest_generate_enum_is_method() { | ||
464 | check_doc_test( | 516 | check_doc_test( |
465 | "generate_enum_match_method", | 517 | "generate_enum_is_method", |
466 | r#####" | 518 | r#####" |
467 | enum Version { | 519 | enum Version { |
468 | Undefined, | 520 | Undefined, |
@@ -488,6 +540,35 @@ impl Version { | |||
488 | } | 540 | } |
489 | 541 | ||
490 | #[test] | 542 | #[test] |
543 | fn doctest_generate_enum_try_into_method() { | ||
544 | check_doc_test( | ||
545 | "generate_enum_try_into_method", | ||
546 | r#####" | ||
547 | enum Value { | ||
548 | Number(i32), | ||
549 | Text(String)$0, | ||
550 | } | ||
551 | "#####, | ||
552 | r#####" | ||
553 | enum Value { | ||
554 | Number(i32), | ||
555 | Text(String), | ||
556 | } | ||
557 | |||
558 | impl Value { | ||
559 | fn try_into_text(self) -> Result<String, Self> { | ||
560 | if let Self::Text(v) = self { | ||
561 | Ok(v) | ||
562 | } else { | ||
563 | Err(self) | ||
564 | } | ||
565 | } | ||
566 | } | ||
567 | "#####, | ||
568 | ) | ||
569 | } | ||
570 | |||
571 | #[test] | ||
491 | fn doctest_generate_from_impl_for_enum() { | 572 | fn doctest_generate_from_impl_for_enum() { |
492 | check_doc_test( | 573 | check_doc_test( |
493 | "generate_from_impl_for_enum", | 574 | "generate_from_impl_for_enum", |
diff --git a/crates/ide_assists/src/utils.rs b/crates/ide_assists/src/utils.rs index 276792bc1..880ab6fe3 100644 --- a/crates/ide_assists/src/utils.rs +++ b/crates/ide_assists/src/utils.rs | |||
@@ -21,7 +21,7 @@ use syntax::{ | |||
21 | }; | 21 | }; |
22 | 22 | ||
23 | use crate::{ | 23 | use crate::{ |
24 | assist_context::AssistContext, | 24 | assist_context::{AssistBuilder, AssistContext}, |
25 | ast_transform::{self, AstTransform, QualifyPaths, SubstituteTypeParams}, | 25 | ast_transform::{self, AstTransform, QualifyPaths, SubstituteTypeParams}, |
26 | }; | 26 | }; |
27 | 27 | ||
@@ -464,3 +464,25 @@ fn generate_impl_text_inner(adt: &ast::Adt, trait_text: Option<&str>, code: &str | |||
464 | 464 | ||
465 | buf | 465 | buf |
466 | } | 466 | } |
467 | |||
468 | pub(crate) fn add_method_to_adt( | ||
469 | builder: &mut AssistBuilder, | ||
470 | adt: &ast::Adt, | ||
471 | impl_def: Option<ast::Impl>, | ||
472 | method: &str, | ||
473 | ) { | ||
474 | let mut buf = String::with_capacity(method.len() + 2); | ||
475 | if impl_def.is_some() { | ||
476 | buf.push('\n'); | ||
477 | } | ||
478 | buf.push_str(method); | ||
479 | |||
480 | let start_offset = impl_def | ||
481 | .and_then(|impl_def| find_impl_block_end(impl_def, &mut buf)) | ||
482 | .unwrap_or_else(|| { | ||
483 | buf = generate_impl_text(&adt, &buf); | ||
484 | adt.syntax().text_range().end() | ||
485 | }); | ||
486 | |||
487 | builder.insert(start_offset, buf); | ||
488 | } | ||
diff --git a/crates/syntax/Cargo.toml b/crates/syntax/Cargo.toml index e41171b57..d836c5d1a 100644 --- a/crates/syntax/Cargo.toml +++ b/crates/syntax/Cargo.toml | |||
@@ -13,7 +13,7 @@ doctest = false | |||
13 | [dependencies] | 13 | [dependencies] |
14 | itertools = "0.10.0" | 14 | itertools = "0.10.0" |
15 | rowan = "0.12.2" | 15 | rowan = "0.12.2" |
16 | rustc_lexer = { version = "707.0.0", package = "rustc-ap-rustc_lexer" } | 16 | rustc_lexer = { version = "708.0.0", package = "rustc-ap-rustc_lexer" } |
17 | rustc-hash = "1.1.0" | 17 | rustc-hash = "1.1.0" |
18 | arrayvec = "0.5.1" | 18 | arrayvec = "0.5.1" |
19 | once_cell = "1.3.1" | 19 | once_cell = "1.3.1" |