diff options
Diffstat (limited to 'crates')
22 files changed, 1238 insertions, 95 deletions
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/src/references.rs b/crates/ide/src/references.rs index 55f95ebae..5d0449e56 100644 --- a/crates/ide/src/references.rs +++ b/crates/ide/src/references.rs | |||
@@ -1224,4 +1224,29 @@ fn test() {} | |||
1224 | "#]], | 1224 | "#]], |
1225 | ); | 1225 | ); |
1226 | } | 1226 | } |
1227 | |||
1228 | #[test] | ||
1229 | fn test_const_in_pattern() { | ||
1230 | check( | ||
1231 | r#" | ||
1232 | const A$0: i32 = 42; | ||
1233 | |||
1234 | fn main() { | ||
1235 | match A { | ||
1236 | A => (), | ||
1237 | _ => (), | ||
1238 | } | ||
1239 | if let A = A {} | ||
1240 | } | ||
1241 | "#, | ||
1242 | expect![[r#" | ||
1243 | A Const FileId(0) 0..18 6..7 | ||
1244 | |||
1245 | FileId(0) 42..43 | ||
1246 | FileId(0) 54..55 | ||
1247 | FileId(0) 97..98 | ||
1248 | FileId(0) 101..102 | ||
1249 | "#]], | ||
1250 | ); | ||
1251 | } | ||
1227 | } | 1252 | } |
diff --git a/crates/ide_assists/src/handlers/apply_demorgan.rs b/crates/ide_assists/src/handlers/apply_demorgan.rs index ed4d11455..6997ea048 100644 --- a/crates/ide_assists/src/handlers/apply_demorgan.rs +++ b/crates/ide_assists/src/handlers/apply_demorgan.rs | |||
@@ -7,18 +7,17 @@ use crate::{utils::invert_boolean_expression, AssistContext, AssistId, AssistKin | |||
7 | // Apply https://en.wikipedia.org/wiki/De_Morgan%27s_laws[De Morgan's law]. | 7 | // Apply https://en.wikipedia.org/wiki/De_Morgan%27s_laws[De Morgan's law]. |
8 | // This transforms expressions of the form `!l || !r` into `!(l && r)`. | 8 | // This transforms expressions of the form `!l || !r` into `!(l && r)`. |
9 | // This also works with `&&`. This assist can only be applied with the cursor | 9 | // This also works with `&&`. This assist can only be applied with the cursor |
10 | // on either `||` or `&&`, with both operands being a negation of some kind. | 10 | // on either `||` or `&&`. |
11 | // This means something of the form `!x` or `x != y`. | ||
12 | // | 11 | // |
13 | // ``` | 12 | // ``` |
14 | // fn main() { | 13 | // fn main() { |
15 | // if x != 4 ||$0 !y {} | 14 | // if x != 4 ||$0 y < 3.14 {} |
16 | // } | 15 | // } |
17 | // ``` | 16 | // ``` |
18 | // -> | 17 | // -> |
19 | // ``` | 18 | // ``` |
20 | // fn main() { | 19 | // fn main() { |
21 | // if !(x == 4 && y) {} | 20 | // if !(x == 4 && !(y < 3.14)) {} |
22 | // } | 21 | // } |
23 | // ``` | 22 | // ``` |
24 | pub(crate) fn apply_demorgan(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { | 23 | pub(crate) fn apply_demorgan(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { |
@@ -33,11 +32,11 @@ pub(crate) fn apply_demorgan(acc: &mut Assists, ctx: &AssistContext) -> Option<( | |||
33 | 32 | ||
34 | let lhs = expr.lhs()?; | 33 | let lhs = expr.lhs()?; |
35 | let lhs_range = lhs.syntax().text_range(); | 34 | let lhs_range = lhs.syntax().text_range(); |
36 | let not_lhs = invert_boolean_expression(lhs); | 35 | let not_lhs = invert_boolean_expression(&ctx.sema, lhs); |
37 | 36 | ||
38 | let rhs = expr.rhs()?; | 37 | let rhs = expr.rhs()?; |
39 | let rhs_range = rhs.syntax().text_range(); | 38 | let rhs_range = rhs.syntax().text_range(); |
40 | let not_rhs = invert_boolean_expression(rhs); | 39 | let not_rhs = invert_boolean_expression(&ctx.sema, rhs); |
41 | 40 | ||
42 | acc.add( | 41 | acc.add( |
43 | AssistId("apply_demorgan", AssistKind::RefactorRewrite), | 42 | AssistId("apply_demorgan", AssistKind::RefactorRewrite), |
@@ -62,10 +61,77 @@ fn opposite_logic_op(kind: ast::BinOp) -> Option<&'static str> { | |||
62 | 61 | ||
63 | #[cfg(test)] | 62 | #[cfg(test)] |
64 | mod tests { | 63 | mod tests { |
64 | use ide_db::helpers::FamousDefs; | ||
65 | |||
65 | use super::*; | 66 | use super::*; |
66 | 67 | ||
67 | use crate::tests::{check_assist, check_assist_not_applicable}; | 68 | use crate::tests::{check_assist, check_assist_not_applicable}; |
68 | 69 | ||
70 | const ORDABLE_FIXTURE: &'static str = r" | ||
71 | //- /lib.rs deps:core crate:ordable | ||
72 | struct NonOrderable; | ||
73 | struct Orderable; | ||
74 | impl core::cmp::Ord for Orderable {} | ||
75 | "; | ||
76 | |||
77 | fn check(ra_fixture_before: &str, ra_fixture_after: &str) { | ||
78 | let before = &format!( | ||
79 | "//- /main.rs crate:main deps:core,ordable\n{}\n{}{}", | ||
80 | ra_fixture_before, | ||
81 | FamousDefs::FIXTURE, | ||
82 | ORDABLE_FIXTURE | ||
83 | ); | ||
84 | check_assist(apply_demorgan, before, &format!("{}\n", ra_fixture_after)); | ||
85 | } | ||
86 | |||
87 | #[test] | ||
88 | fn demorgan_handles_leq() { | ||
89 | check( | ||
90 | r"use ordable::Orderable; | ||
91 | fn f() { | ||
92 | Orderable < Orderable &&$0 Orderable <= Orderable | ||
93 | }", | ||
94 | r"use ordable::Orderable; | ||
95 | fn f() { | ||
96 | !(Orderable >= Orderable || Orderable > Orderable) | ||
97 | }", | ||
98 | ); | ||
99 | check( | ||
100 | r"use ordable::NonOrderable; | ||
101 | fn f() { | ||
102 | NonOrderable < NonOrderable &&$0 NonOrderable <= NonOrderable | ||
103 | }", | ||
104 | r"use ordable::NonOrderable; | ||
105 | fn f() { | ||
106 | !(!(NonOrderable < NonOrderable) || !(NonOrderable <= NonOrderable)) | ||
107 | }", | ||
108 | ); | ||
109 | } | ||
110 | |||
111 | #[test] | ||
112 | fn demorgan_handles_geq() { | ||
113 | check( | ||
114 | r"use ordable::Orderable; | ||
115 | fn f() { | ||
116 | Orderable > Orderable &&$0 Orderable >= Orderable | ||
117 | }", | ||
118 | r"use ordable::Orderable; | ||
119 | fn f() { | ||
120 | !(Orderable <= Orderable || Orderable < Orderable) | ||
121 | }", | ||
122 | ); | ||
123 | check( | ||
124 | r"use ordable::NonOrderable; | ||
125 | fn f() { | ||
126 | Orderable > Orderable &&$0 Orderable >= Orderable | ||
127 | }", | ||
128 | r"use ordable::NonOrderable; | ||
129 | fn f() { | ||
130 | !(!(Orderable > Orderable) || !(Orderable >= Orderable)) | ||
131 | }", | ||
132 | ); | ||
133 | } | ||
134 | |||
69 | #[test] | 135 | #[test] |
70 | fn demorgan_turns_and_into_or() { | 136 | fn demorgan_turns_and_into_or() { |
71 | check_assist(apply_demorgan, "fn f() { !x &&$0 !x }", "fn f() { !(x || x) }") | 137 | check_assist(apply_demorgan, "fn f() { !x &&$0 !x }", "fn f() { !(x || x) }") |
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/early_return.rs b/crates/ide_assists/src/handlers/early_return.rs index 6b87c3c05..9e0918477 100644 --- a/crates/ide_assists/src/handlers/early_return.rs +++ b/crates/ide_assists/src/handlers/early_return.rs | |||
@@ -111,7 +111,7 @@ pub(crate) fn convert_to_guarded_return(acc: &mut Assists, ctx: &AssistContext) | |||
111 | let new_expr = { | 111 | let new_expr = { |
112 | let then_branch = | 112 | let then_branch = |
113 | make::block_expr(once(make::expr_stmt(early_expression).into()), None); | 113 | make::block_expr(once(make::expr_stmt(early_expression).into()), None); |
114 | let cond = invert_boolean_expression(cond_expr); | 114 | let cond = invert_boolean_expression(&ctx.sema, cond_expr); |
115 | make::expr_if(make::condition(cond, None), then_branch, None) | 115 | make::expr_if(make::condition(cond, None), then_branch, None) |
116 | .indent(if_indent_level) | 116 | .indent(if_indent_level) |
117 | }; | 117 | }; |
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/handlers/invert_if.rs b/crates/ide_assists/src/handlers/invert_if.rs index 5b69dafd4..b131dc205 100644 --- a/crates/ide_assists/src/handlers/invert_if.rs +++ b/crates/ide_assists/src/handlers/invert_if.rs | |||
@@ -50,7 +50,7 @@ pub(crate) fn invert_if(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { | |||
50 | }; | 50 | }; |
51 | 51 | ||
52 | acc.add(AssistId("invert_if", AssistKind::RefactorRewrite), "Invert if", if_range, |edit| { | 52 | acc.add(AssistId("invert_if", AssistKind::RefactorRewrite), "Invert if", if_range, |edit| { |
53 | let flip_cond = invert_boolean_expression(cond.clone()); | 53 | let flip_cond = invert_boolean_expression(&ctx.sema, cond.clone()); |
54 | edit.replace_ast(cond, flip_cond); | 54 | edit.replace_ast(cond, flip_cond); |
55 | 55 | ||
56 | let else_node = else_block.syntax(); | 56 | let else_node = else_block.syntax(); |
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 0516deaff..7f6dbbccf 100644 --- a/crates/ide_assists/src/tests/generated.rs +++ b/crates/ide_assists/src/tests/generated.rs | |||
@@ -147,12 +147,12 @@ fn doctest_apply_demorgan() { | |||
147 | "apply_demorgan", | 147 | "apply_demorgan", |
148 | r#####" | 148 | r#####" |
149 | fn main() { | 149 | fn main() { |
150 | if x != 4 ||$0 !y {} | 150 | if x != 4 ||$0 y < 3.14 {} |
151 | } | 151 | } |
152 | "#####, | 152 | "#####, |
153 | r#####" | 153 | r#####" |
154 | fn main() { | 154 | fn main() { |
155 | if !(x == 4 && y) {} | 155 | if !(x == 4 && !(y < 3.14)) {} |
156 | } | 156 | } |
157 | "#####, | 157 | "#####, |
158 | ) | 158 | ) |
@@ -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 0074da741..880ab6fe3 100644 --- a/crates/ide_assists/src/utils.rs +++ b/crates/ide_assists/src/utils.rs | |||
@@ -3,8 +3,11 @@ | |||
3 | use std::ops; | 3 | use std::ops; |
4 | 4 | ||
5 | use ast::TypeBoundsOwner; | 5 | use ast::TypeBoundsOwner; |
6 | use hir::{Adt, HasSource}; | 6 | use hir::{Adt, HasSource, Semantics}; |
7 | use ide_db::{helpers::SnippetCap, RootDatabase}; | 7 | use ide_db::{ |
8 | helpers::{FamousDefs, SnippetCap}, | ||
9 | RootDatabase, | ||
10 | }; | ||
8 | use itertools::Itertools; | 11 | use itertools::Itertools; |
9 | use stdx::format_to; | 12 | use stdx::format_to; |
10 | use syntax::{ | 13 | use syntax::{ |
@@ -18,7 +21,7 @@ use syntax::{ | |||
18 | }; | 21 | }; |
19 | 22 | ||
20 | use crate::{ | 23 | use crate::{ |
21 | assist_context::AssistContext, | 24 | assist_context::{AssistBuilder, AssistContext}, |
22 | ast_transform::{self, AstTransform, QualifyPaths, SubstituteTypeParams}, | 25 | ast_transform::{self, AstTransform, QualifyPaths, SubstituteTypeParams}, |
23 | }; | 26 | }; |
24 | 27 | ||
@@ -205,23 +208,36 @@ pub(crate) fn vis_offset(node: &SyntaxNode) -> TextSize { | |||
205 | .unwrap_or_else(|| node.text_range().start()) | 208 | .unwrap_or_else(|| node.text_range().start()) |
206 | } | 209 | } |
207 | 210 | ||
208 | pub(crate) fn invert_boolean_expression(expr: ast::Expr) -> ast::Expr { | 211 | pub(crate) fn invert_boolean_expression( |
209 | if let Some(expr) = invert_special_case(&expr) { | 212 | sema: &Semantics<RootDatabase>, |
213 | expr: ast::Expr, | ||
214 | ) -> ast::Expr { | ||
215 | if let Some(expr) = invert_special_case(sema, &expr) { | ||
210 | return expr; | 216 | return expr; |
211 | } | 217 | } |
212 | make::expr_prefix(T![!], expr) | 218 | make::expr_prefix(T![!], expr) |
213 | } | 219 | } |
214 | 220 | ||
215 | fn invert_special_case(expr: &ast::Expr) -> Option<ast::Expr> { | 221 | fn invert_special_case(sema: &Semantics<RootDatabase>, expr: &ast::Expr) -> Option<ast::Expr> { |
216 | match expr { | 222 | match expr { |
217 | ast::Expr::BinExpr(bin) => match bin.op_kind()? { | 223 | ast::Expr::BinExpr(bin) => match bin.op_kind()? { |
218 | ast::BinOp::NegatedEqualityTest => bin.replace_op(T![==]).map(|it| it.into()), | 224 | ast::BinOp::NegatedEqualityTest => bin.replace_op(T![==]).map(|it| it.into()), |
219 | ast::BinOp::EqualityTest => bin.replace_op(T![!=]).map(|it| it.into()), | 225 | ast::BinOp::EqualityTest => bin.replace_op(T![!=]).map(|it| it.into()), |
220 | // Parenthesize composite boolean expressions before prefixing `!` | 226 | // Swap `<` with `>=`, `<=` with `>`, ... if operands `impl Ord` |
221 | ast::BinOp::BooleanAnd | ast::BinOp::BooleanOr => { | 227 | ast::BinOp::LesserTest if bin_impls_ord(sema, bin) => { |
222 | Some(make::expr_prefix(T![!], make::expr_paren(expr.clone()))) | 228 | bin.replace_op(T![>=]).map(|it| it.into()) |
229 | } | ||
230 | ast::BinOp::LesserEqualTest if bin_impls_ord(sema, bin) => { | ||
231 | bin.replace_op(T![>]).map(|it| it.into()) | ||
232 | } | ||
233 | ast::BinOp::GreaterTest if bin_impls_ord(sema, bin) => { | ||
234 | bin.replace_op(T![<=]).map(|it| it.into()) | ||
235 | } | ||
236 | ast::BinOp::GreaterEqualTest if bin_impls_ord(sema, bin) => { | ||
237 | bin.replace_op(T![<]).map(|it| it.into()) | ||
223 | } | 238 | } |
224 | _ => None, | 239 | // Parenthesize other expressions before prefixing `!` |
240 | _ => Some(make::expr_prefix(T![!], make::expr_paren(expr.clone()))), | ||
225 | }, | 241 | }, |
226 | ast::Expr::MethodCallExpr(mce) => { | 242 | ast::Expr::MethodCallExpr(mce) => { |
227 | let receiver = mce.receiver()?; | 243 | let receiver = mce.receiver()?; |
@@ -250,6 +266,22 @@ fn invert_special_case(expr: &ast::Expr) -> Option<ast::Expr> { | |||
250 | } | 266 | } |
251 | } | 267 | } |
252 | 268 | ||
269 | fn bin_impls_ord(sema: &Semantics<RootDatabase>, bin: &ast::BinExpr) -> bool { | ||
270 | match ( | ||
271 | bin.lhs().and_then(|lhs| sema.type_of_expr(&lhs)), | ||
272 | bin.rhs().and_then(|rhs| sema.type_of_expr(&rhs)), | ||
273 | ) { | ||
274 | (Some(lhs_ty), Some(rhs_ty)) if lhs_ty == rhs_ty => { | ||
275 | let krate = sema.scope(bin.syntax()).module().map(|it| it.krate()); | ||
276 | let ord_trait = FamousDefs(sema, krate).core_cmp_Ord(); | ||
277 | ord_trait.map_or(false, |ord_trait| { | ||
278 | lhs_ty.autoderef(sema.db).any(|ty| ty.impls_trait(sema.db, ord_trait, &[])) | ||
279 | }) | ||
280 | } | ||
281 | _ => false, | ||
282 | } | ||
283 | } | ||
284 | |||
253 | pub(crate) fn next_prev() -> impl Iterator<Item = Direction> { | 285 | pub(crate) fn next_prev() -> impl Iterator<Item = Direction> { |
254 | [Direction::Next, Direction::Prev].iter().copied() | 286 | [Direction::Next, Direction::Prev].iter().copied() |
255 | } | 287 | } |
@@ -432,3 +464,25 @@ fn generate_impl_text_inner(adt: &ast::Adt, trait_text: Option<&str>, code: &str | |||
432 | 464 | ||
433 | buf | 465 | buf |
434 | } | 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/ide_db/src/helpers.rs b/crates/ide_db/src/helpers.rs index bc7aee110..f9de8ce0e 100644 --- a/crates/ide_db/src/helpers.rs +++ b/crates/ide_db/src/helpers.rs | |||
@@ -45,6 +45,10 @@ impl FamousDefs<'_, '_> { | |||
45 | self.find_crate("core") | 45 | self.find_crate("core") |
46 | } | 46 | } |
47 | 47 | ||
48 | pub fn core_cmp_Ord(&self) -> Option<Trait> { | ||
49 | self.find_trait("core:cmp:Ord") | ||
50 | } | ||
51 | |||
48 | pub fn core_convert_From(&self) -> Option<Trait> { | 52 | pub fn core_convert_From(&self) -> Option<Trait> { |
49 | self.find_trait("core:convert:From") | 53 | self.find_trait("core:convert:From") |
50 | } | 54 | } |
diff --git a/crates/ide_db/src/helpers/famous_defs_fixture.rs b/crates/ide_db/src/helpers/famous_defs_fixture.rs index 5e88de64d..bb4e9666b 100644 --- a/crates/ide_db/src/helpers/famous_defs_fixture.rs +++ b/crates/ide_db/src/helpers/famous_defs_fixture.rs | |||
@@ -1,5 +1,15 @@ | |||
1 | //- /libcore.rs crate:core | 1 | //- /libcore.rs crate:core |
2 | //! Signatures of traits, types and functions from the core lib for use in tests. | 2 | //! Signatures of traits, types and functions from the core lib for use in tests. |
3 | pub mod cmp { | ||
4 | |||
5 | pub trait Ord { | ||
6 | fn cmp(&self, other: &Self) -> Ordering; | ||
7 | fn max(self, other: Self) -> Self; | ||
8 | fn min(self, other: Self) -> Self; | ||
9 | fn clamp(self, min: Self, max: Self) -> Self; | ||
10 | } | ||
11 | } | ||
12 | |||
3 | pub mod convert { | 13 | pub mod convert { |
4 | pub trait From<T> { | 14 | pub trait From<T> { |
5 | fn from(t: T) -> Self; | 15 | fn from(t: T) -> Self; |
@@ -109,6 +119,7 @@ pub mod option { | |||
109 | 119 | ||
110 | pub mod prelude { | 120 | pub mod prelude { |
111 | pub use crate::{ | 121 | pub use crate::{ |
122 | cmp::Ord, | ||
112 | convert::From, | 123 | convert::From, |
113 | default::Default, | 124 | default::Default, |
114 | iter::{IntoIterator, Iterator}, | 125 | iter::{IntoIterator, Iterator}, |
diff --git a/crates/ide_db/src/search.rs b/crates/ide_db/src/search.rs index ba8bea002..ddcfbd3f3 100644 --- a/crates/ide_db/src/search.rs +++ b/crates/ide_db/src/search.rs | |||
@@ -416,10 +416,11 @@ impl<'a> FindUsages<'a> { | |||
416 | sink: &mut dyn FnMut(FileId, FileReference) -> bool, | 416 | sink: &mut dyn FnMut(FileId, FileReference) -> bool, |
417 | ) -> bool { | 417 | ) -> bool { |
418 | match NameClass::classify(self.sema, name) { | 418 | match NameClass::classify(self.sema, name) { |
419 | Some(NameClass::PatFieldShorthand { local_def: _, field_ref }) => { | 419 | Some(NameClass::PatFieldShorthand { local_def: _, field_ref }) |
420 | if !matches!(self.def, Definition::Field(_) if &field_ref == self.def) { | 420 | if matches!( |
421 | return false; | 421 | self.def, Definition::Field(_) if &field_ref == self.def |
422 | } | 422 | ) => |
423 | { | ||
423 | let FileRange { file_id, range } = self.sema.original_range(name.syntax()); | 424 | let FileRange { file_id, range } = self.sema.original_range(name.syntax()); |
424 | let reference = FileReference { | 425 | let reference = FileReference { |
425 | range, | 426 | range, |
@@ -429,6 +430,12 @@ impl<'a> FindUsages<'a> { | |||
429 | }; | 430 | }; |
430 | sink(file_id, reference) | 431 | sink(file_id, reference) |
431 | } | 432 | } |
433 | Some(NameClass::ConstReference(def)) if *self.def == def => { | ||
434 | let FileRange { file_id, range } = self.sema.original_range(name.syntax()); | ||
435 | let reference = | ||
436 | FileReference { range, name: ast::NameLike::Name(name.clone()), access: None }; | ||
437 | sink(file_id, reference) | ||
438 | } | ||
432 | _ => false, // not a usage | 439 | _ => false, // not a usage |
433 | } | 440 | } |
434 | } | 441 | } |
diff --git a/crates/mbe/Cargo.toml b/crates/mbe/Cargo.toml index ef0907194..0abba3584 100644 --- a/crates/mbe/Cargo.toml +++ b/crates/mbe/Cargo.toml | |||
@@ -19,3 +19,6 @@ parser = { path = "../parser", version = "0.0.0" } | |||
19 | tt = { path = "../tt", version = "0.0.0" } | 19 | tt = { path = "../tt", version = "0.0.0" } |
20 | test_utils = { path = "../test_utils", version = "0.0.0" } | 20 | test_utils = { path = "../test_utils", version = "0.0.0" } |
21 | 21 | ||
22 | [dev-dependencies] | ||
23 | profile = { path = "../profile", version = "0.0.0" } | ||
24 | |||
diff --git a/crates/mbe/src/benchmark.rs b/crates/mbe/src/benchmark.rs new file mode 100644 index 000000000..0d0acd589 --- /dev/null +++ b/crates/mbe/src/benchmark.rs | |||
@@ -0,0 +1,211 @@ | |||
1 | //! This module add real world mbe example for benchmark tests | ||
2 | |||
3 | use rustc_hash::FxHashMap; | ||
4 | use syntax::{ | ||
5 | ast::{self, NameOwner}, | ||
6 | AstNode, SmolStr, | ||
7 | }; | ||
8 | use test_utils::{bench, bench_fixture, skip_slow_tests}; | ||
9 | |||
10 | use crate::{ | ||
11 | ast_to_token_tree, | ||
12 | parser::{Op, RepeatKind, Separator}, | ||
13 | MacroRules, | ||
14 | }; | ||
15 | |||
16 | #[test] | ||
17 | fn benchmark_parse_macro_rules() { | ||
18 | if skip_slow_tests() { | ||
19 | return; | ||
20 | } | ||
21 | let rules = macro_rules_fixtures_tt(); | ||
22 | let hash: usize = { | ||
23 | let _pt = bench("mbe parse macro rules"); | ||
24 | rules.values().map(|it| MacroRules::parse(it).unwrap().rules.len()).sum() | ||
25 | }; | ||
26 | assert_eq!(hash, 1144); | ||
27 | } | ||
28 | |||
29 | #[test] | ||
30 | fn benchmark_expand_macro_rules() { | ||
31 | if skip_slow_tests() { | ||
32 | return; | ||
33 | } | ||
34 | let rules = macro_rules_fixtures(); | ||
35 | let invocations = invocation_fixtures(&rules); | ||
36 | |||
37 | let hash: usize = { | ||
38 | let _pt = bench("mbe expand macro rules"); | ||
39 | invocations | ||
40 | .into_iter() | ||
41 | .map(|(id, tt)| { | ||
42 | let res = rules[&id].expand(&tt); | ||
43 | if res.err.is_some() { | ||
44 | // FIXME: | ||
45 | // Currently `invocation_fixtures` will generate some correct invocations but | ||
46 | // cannot be expanded by mbe. We ignore errors here. | ||
47 | // See: https://github.com/rust-analyzer/rust-analyzer/issues/4777 | ||
48 | eprintln!("err from {} {:?}", id, res.err); | ||
49 | } | ||
50 | res.value.token_trees.len() | ||
51 | }) | ||
52 | .sum() | ||
53 | }; | ||
54 | assert_eq!(hash, 66995); | ||
55 | } | ||
56 | |||
57 | fn macro_rules_fixtures() -> FxHashMap<String, MacroRules> { | ||
58 | macro_rules_fixtures_tt() | ||
59 | .into_iter() | ||
60 | .map(|(id, tt)| (id, MacroRules::parse(&tt).unwrap())) | ||
61 | .collect() | ||
62 | } | ||
63 | |||
64 | fn macro_rules_fixtures_tt() -> FxHashMap<String, tt::Subtree> { | ||
65 | let fixture = bench_fixture::numerous_macro_rules(); | ||
66 | let source_file = ast::SourceFile::parse(&fixture).ok().unwrap(); | ||
67 | |||
68 | source_file | ||
69 | .syntax() | ||
70 | .descendants() | ||
71 | .filter_map(ast::MacroRules::cast) | ||
72 | .map(|rule| { | ||
73 | let id = rule.name().unwrap().to_string(); | ||
74 | let (def_tt, _) = ast_to_token_tree(&rule.token_tree().unwrap()).unwrap(); | ||
75 | (id, def_tt) | ||
76 | }) | ||
77 | .collect() | ||
78 | } | ||
79 | |||
80 | // Generate random invocation fixtures from rules | ||
81 | fn invocation_fixtures(rules: &FxHashMap<String, MacroRules>) -> Vec<(String, tt::Subtree)> { | ||
82 | let mut seed = 123456789; | ||
83 | let mut res = Vec::new(); | ||
84 | |||
85 | for (name, it) in rules { | ||
86 | for rule in &it.rules { | ||
87 | // Generate twice | ||
88 | for _ in 0..2 { | ||
89 | let mut subtree = tt::Subtree::default(); | ||
90 | for op in rule.lhs.iter() { | ||
91 | collect_from_op(op, &mut subtree, &mut seed); | ||
92 | } | ||
93 | res.push((name.clone(), subtree)); | ||
94 | } | ||
95 | } | ||
96 | } | ||
97 | return res; | ||
98 | |||
99 | fn collect_from_op(op: &Op, parent: &mut tt::Subtree, seed: &mut usize) { | ||
100 | return match op { | ||
101 | Op::Var { kind, .. } => match kind.as_ref().map(|it| it.as_str()) { | ||
102 | Some("ident") => parent.token_trees.push(make_ident("foo")), | ||
103 | Some("ty") => parent.token_trees.push(make_ident("Foo")), | ||
104 | Some("tt") => parent.token_trees.push(make_ident("foo")), | ||
105 | Some("vis") => parent.token_trees.push(make_ident("pub")), | ||
106 | Some("pat") => parent.token_trees.push(make_ident("foo")), | ||
107 | Some("path") => parent.token_trees.push(make_ident("foo")), | ||
108 | Some("literal") => parent.token_trees.push(make_literal("1")), | ||
109 | Some("expr") => parent.token_trees.push(make_ident("foo").into()), | ||
110 | Some("lifetime") => { | ||
111 | parent.token_trees.push(make_punct('\'')); | ||
112 | parent.token_trees.push(make_ident("a")); | ||
113 | } | ||
114 | Some("block") => { | ||
115 | parent.token_trees.push(make_subtree(tt::DelimiterKind::Brace, None)) | ||
116 | } | ||
117 | Some("item") => { | ||
118 | parent.token_trees.push(make_ident("fn")); | ||
119 | parent.token_trees.push(make_ident("foo")); | ||
120 | parent.token_trees.push(make_subtree(tt::DelimiterKind::Parenthesis, None)); | ||
121 | parent.token_trees.push(make_subtree(tt::DelimiterKind::Brace, None)); | ||
122 | } | ||
123 | Some("meta") => { | ||
124 | parent.token_trees.push(make_ident("foo")); | ||
125 | parent.token_trees.push(make_subtree(tt::DelimiterKind::Parenthesis, None)); | ||
126 | } | ||
127 | |||
128 | None => (), | ||
129 | Some(kind) => panic!("Unhandled kind {}", kind), | ||
130 | }, | ||
131 | Op::Leaf(leaf) => parent.token_trees.push(leaf.clone().into()), | ||
132 | Op::Repeat { tokens, kind, separator } => { | ||
133 | let max = 10; | ||
134 | let cnt = match kind { | ||
135 | RepeatKind::ZeroOrMore => rand(seed) % max, | ||
136 | RepeatKind::OneOrMore => 1 + rand(seed) % max, | ||
137 | RepeatKind::ZeroOrOne => rand(seed) % 2, | ||
138 | }; | ||
139 | for i in 0..cnt { | ||
140 | for it in tokens.iter() { | ||
141 | collect_from_op(it, parent, seed); | ||
142 | } | ||
143 | if i + 1 != cnt { | ||
144 | if let Some(sep) = separator { | ||
145 | match sep { | ||
146 | Separator::Literal(it) => parent | ||
147 | .token_trees | ||
148 | .push(tt::Leaf::Literal(it.clone().into()).into()), | ||
149 | Separator::Ident(it) => parent | ||
150 | .token_trees | ||
151 | .push(tt::Leaf::Ident(it.clone().into()).into()), | ||
152 | Separator::Puncts(puncts) => { | ||
153 | for it in puncts { | ||
154 | parent | ||
155 | .token_trees | ||
156 | .push(tt::Leaf::Punct(it.clone().into()).into()) | ||
157 | } | ||
158 | } | ||
159 | }; | ||
160 | } | ||
161 | } | ||
162 | } | ||
163 | } | ||
164 | Op::Subtree { tokens, delimiter } => { | ||
165 | let mut subtree = | ||
166 | tt::Subtree { delimiter: delimiter.clone(), token_trees: Vec::new() }; | ||
167 | tokens.iter().for_each(|it| { | ||
168 | collect_from_op(it, &mut subtree, seed); | ||
169 | }); | ||
170 | parent.token_trees.push(subtree.into()); | ||
171 | } | ||
172 | }; | ||
173 | |||
174 | // Simple linear congruential generator for determistic result | ||
175 | fn rand(seed: &mut usize) -> usize { | ||
176 | let a = 1664525; | ||
177 | let c = 1013904223; | ||
178 | *seed = usize::wrapping_add(usize::wrapping_mul(*seed, a), c); | ||
179 | return *seed; | ||
180 | }; | ||
181 | fn make_ident(ident: &str) -> tt::TokenTree { | ||
182 | tt::Leaf::Ident(tt::Ident { id: tt::TokenId::unspecified(), text: SmolStr::new(ident) }) | ||
183 | .into() | ||
184 | } | ||
185 | fn make_punct(char: char) -> tt::TokenTree { | ||
186 | tt::Leaf::Punct(tt::Punct { | ||
187 | id: tt::TokenId::unspecified(), | ||
188 | char, | ||
189 | spacing: tt::Spacing::Alone, | ||
190 | }) | ||
191 | .into() | ||
192 | } | ||
193 | fn make_literal(lit: &str) -> tt::TokenTree { | ||
194 | tt::Leaf::Literal(tt::Literal { | ||
195 | id: tt::TokenId::unspecified(), | ||
196 | text: SmolStr::new(lit), | ||
197 | }) | ||
198 | .into() | ||
199 | } | ||
200 | fn make_subtree( | ||
201 | kind: tt::DelimiterKind, | ||
202 | token_trees: Option<Vec<tt::TokenTree>>, | ||
203 | ) -> tt::TokenTree { | ||
204 | tt::Subtree { | ||
205 | delimiter: Some(tt::Delimiter { id: tt::TokenId::unspecified(), kind }), | ||
206 | token_trees: token_trees.unwrap_or_default(), | ||
207 | } | ||
208 | .into() | ||
209 | } | ||
210 | } | ||
211 | } | ||
diff --git a/crates/mbe/src/lib.rs b/crates/mbe/src/lib.rs index d80bd7a33..6b4a4eb53 100644 --- a/crates/mbe/src/lib.rs +++ b/crates/mbe/src/lib.rs | |||
@@ -12,6 +12,9 @@ mod subtree_source; | |||
12 | #[cfg(test)] | 12 | #[cfg(test)] |
13 | mod tests; | 13 | mod tests; |
14 | 14 | ||
15 | #[cfg(test)] | ||
16 | mod benchmark; | ||
17 | |||
15 | use std::fmt; | 18 | use std::fmt; |
16 | 19 | ||
17 | use test_utils::mark; | 20 | use test_utils::mark; |
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" |
diff --git a/crates/syntax/src/ast/make.rs b/crates/syntax/src/ast/make.rs index 5eee33545..b6c5de658 100644 --- a/crates/syntax/src/ast/make.rs +++ b/crates/syntax/src/ast/make.rs | |||
@@ -527,8 +527,11 @@ pub mod tokens { | |||
527 | 527 | ||
528 | use crate::{ast, AstNode, Parse, SourceFile, SyntaxKind::*, SyntaxToken}; | 528 | use crate::{ast, AstNode, Parse, SourceFile, SyntaxKind::*, SyntaxToken}; |
529 | 529 | ||
530 | pub(super) static SOURCE_FILE: Lazy<Parse<SourceFile>> = | 530 | pub(super) static SOURCE_FILE: Lazy<Parse<SourceFile>> = Lazy::new(|| { |
531 | Lazy::new(|| SourceFile::parse("const C: <()>::Item = (1 != 1, 2 == 2, !true, *p)\n;\n\n")); | 531 | SourceFile::parse( |
532 | "const C: <()>::Item = (1 != 1, 2 == 2, 3 < 3, 4 <= 4, 5 > 5, 6 >= 6, !true, *p)\n;\n\n", | ||
533 | ) | ||
534 | }); | ||
532 | 535 | ||
533 | pub fn single_space() -> SyntaxToken { | 536 | pub fn single_space() -> SyntaxToken { |
534 | SOURCE_FILE | 537 | SOURCE_FILE |
diff --git a/crates/test_utils/src/bench_fixture.rs b/crates/test_utils/src/bench_fixture.rs index aa1bea9bb..d775e2cc9 100644 --- a/crates/test_utils/src/bench_fixture.rs +++ b/crates/test_utils/src/bench_fixture.rs | |||
@@ -35,3 +35,8 @@ pub fn glorious_old_parser() -> String { | |||
35 | let path = project_dir().join("bench_data/glorious_old_parser"); | 35 | let path = project_dir().join("bench_data/glorious_old_parser"); |
36 | fs::read_to_string(&path).unwrap() | 36 | fs::read_to_string(&path).unwrap() |
37 | } | 37 | } |
38 | |||
39 | pub fn numerous_macro_rules() -> String { | ||
40 | let path = project_dir().join("bench_data/numerous_macro_rules"); | ||
41 | fs::read_to_string(&path).unwrap() | ||
42 | } | ||