aboutsummaryrefslogtreecommitdiff
path: root/crates
diff options
context:
space:
mode:
Diffstat (limited to 'crates')
-rw-r--r--crates/hir_expand/src/name.rs1
-rw-r--r--crates/ide/src/references.rs25
-rw-r--r--crates/ide_assists/src/handlers/apply_demorgan.rs78
-rw-r--r--crates/ide_assists/src/handlers/auto_import.rs6
-rw-r--r--crates/ide_assists/src/handlers/convert_for_to_iter_for_each.rs301
-rw-r--r--crates/ide_assists/src/handlers/early_return.rs2
-rw-r--r--crates/ide_assists/src/handlers/flip_comma.rs26
-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.rs331
-rw-r--r--crates/ide_assists/src/handlers/invert_if.rs2
-rw-r--r--crates/ide_assists/src/lib.rs9
-rw-r--r--crates/ide_assists/src/tests/generated.rs89
-rw-r--r--crates/ide_assists/src/utils.rs74
-rw-r--r--crates/ide_db/src/helpers.rs4
-rw-r--r--crates/ide_db/src/helpers/famous_defs_fixture.rs11
-rw-r--r--crates/ide_db/src/search.rs15
-rw-r--r--crates/mbe/Cargo.toml3
-rw-r--r--crates/mbe/src/benchmark.rs211
-rw-r--r--crates/mbe/src/lib.rs3
-rw-r--r--crates/syntax/Cargo.toml2
-rw-r--r--crates/syntax/src/ast/make.rs7
-rw-r--r--crates/test_utils/src/bench_fixture.rs5
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#"
1232const A$0: i32 = 42;
1233
1234fn 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// ```
24pub(crate) fn apply_demorgan(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { 23pub(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)]
64mod tests { 63mod 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
72struct NonOrderable;
73struct Orderable;
74impl 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;
91fn f() {
92 Orderable < Orderable &&$0 Orderable <= Orderable
93}",
94 r"use ordable::Orderable;
95fn f() {
96 !(Orderable >= Orderable || Orderable > Orderable)
97}",
98 );
99 check(
100 r"use ordable::NonOrderable;
101fn f() {
102 NonOrderable < NonOrderable &&$0 NonOrderable <= NonOrderable
103}",
104 r"use ordable::NonOrderable;
105fn f() {
106 !(!(NonOrderable < NonOrderable) || !(NonOrderable <= NonOrderable))
107}",
108 );
109 }
110
111 #[test]
112 fn demorgan_handles_geq() {
113 check(
114 r"use ordable::Orderable;
115fn f() {
116 Orderable > Orderable &&$0 Orderable >= Orderable
117}",
118 r"use ordable::Orderable;
119fn f() {
120 !(Orderable <= Orderable || Orderable < Orderable)
121}",
122 );
123 check(
124 r"use ordable::NonOrderable;
125fn f() {
126 Orderable > Orderable &&$0 Orderable >= Orderable
127}",
128 r"use ordable::NonOrderable;
129fn 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 @@
1use ast::LoopBodyOwner;
2use hir::known;
3use ide_db::helpers::FamousDefs;
4use stdx::format_to;
5use syntax::{ast, AstNode};
6
7use 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// ```
30pub(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
69fn 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
100fn 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)]
119mod 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
126pub struct EmptyIter;
127impl Iterator for EmptyIter {
128 type Item = usize;
129 fn next(&mut self) -> Option<Self::Item> { None }
130}
131
132pub struct Empty;
133impl Empty {
134 pub fn iter(&self) -> EmptyIter { EmptyIter }
135 pub fn iter_mut(&self) -> EmptyIter { EmptyIter }
136}
137
138pub 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"
156let mut x = vec![1, 2, 3];
157x.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"
167fn main() {
168 let x = vec![1, 2, 3];
169 for $0v in x {
170 v *= 2;
171 }
172}",
173 r"
174fn 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"
187use empty_iter::*;
188fn main() {
189 let x = Empty;
190 for $0v in &x {
191 let a = v * 2;
192 }
193}
194",
195 r"
196use empty_iter::*;
197fn 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"
211use empty_iter::*;
212fn main() {
213 let x = NoIterMethod;
214 for $0v in &x {
215 let a = v * 2;
216 }
217}
218",
219 r"
220use empty_iter::*;
221fn 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"
235use empty_iter::*;
236fn main() {
237 let x = Empty;
238 for $0v in &mut x {
239 let a = v * 2;
240 }
241}
242",
243 r"
244use empty_iter::*;
245fn 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"
260fn 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"
268fn 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#"
282use empty_iter::*;
283fn main() {
284 let x = Empty;
285 for$0 a in x.iter().take(1) {
286 println!("{}", a);
287 }
288}
289"#,
290 r#"
291use empty_iter::*;
292fn 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 @@
1use syntax::{algo::non_trivia_sibling, Direction, T}; 1use syntax::{algo::non_trivia_sibling, Direction, SyntaxKind, T};
2 2
3use crate::{AssistContext, AssistId, AssistKind, Assists}; 3use 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<()> {
43mod tests { 49mod 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 @@
1use stdx::{format_to, to_lower_snake_case}; 1use stdx::to_lower_snake_case;
2use syntax::ast::VisibilityOwner; 2use syntax::ast::VisibilityOwner;
3use syntax::ast::{self, AstNode, NameOwner}; 3use syntax::ast::{self, AstNode, NameOwner};
4use test_utils::mark;
5 4
6use crate::{ 5use 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// ```
37pub(crate) fn generate_enum_match_method(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { 36pub(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)]
95mod tests { 73mod 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#"
111enum Variant { 83enum 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#"
135enum Variant { 108enum 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#"
154enum Variant { 127enum 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
138impl 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#"
152enum 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
163impl 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#"
168enum Variant { Undefined } 178enum 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#"
184pub(crate) enum Variant { 194pub(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#"
209enum Variant { 219enum 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 @@
1use itertools::Itertools;
2use stdx::to_lower_snake_case;
3use syntax::ast::VisibilityOwner;
4use syntax::ast::{self, AstNode, NameOwner};
5
6use 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// ```
38pub(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// ```
82pub(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
99struct 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
108fn 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)]
169mod 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#"
179enum Value {
180 Number(i32),
181 Text(String)$0,
182}"#,
183 r#"enum Value {
184 Number(i32),
185 Text(String),
186}
187
188impl 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
209impl 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
269impl 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#"
286enum Value {
287 Number(i32),
288 Text(String)$0,
289}"#,
290 r#"enum Value {
291 Number(i32),
292 Text(String),
293}
294
295impl 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
320impl 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#####"
149fn main() { 149fn main() {
150 if x != 4 ||$0 !y {} 150 if x != 4 ||$0 y < 3.14 {}
151} 151}
152"#####, 152"#####,
153 r#####" 153 r#####"
154fn main() { 154fn 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]
196fn doctest_convert_for_to_iter_for_each() {
197 check_doc_test(
198 "convert_for_to_iter_for_each",
199 r#####"
200fn main() {
201 let x = vec![1, 2, 3];
202 for $0v in x {
203 let y = v * 2;
204 }
205}
206"#####,
207 r#####"
208fn 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]
196fn doctest_convert_integer_literal() { 219fn 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]
463fn doctest_generate_enum_match_method() { 486fn doctest_generate_enum_as_method() {
487 check_doc_test(
488 "generate_enum_as_method",
489 r#####"
490enum Value {
491 Number(i32),
492 Text(String)$0,
493}
494"#####,
495 r#####"
496enum Value {
497 Number(i32),
498 Text(String),
499}
500
501impl 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]
515fn 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#####"
467enum Version { 519enum Version {
468 Undefined, 520 Undefined,
@@ -488,6 +540,35 @@ impl Version {
488} 540}
489 541
490#[test] 542#[test]
543fn doctest_generate_enum_try_into_method() {
544 check_doc_test(
545 "generate_enum_try_into_method",
546 r#####"
547enum Value {
548 Number(i32),
549 Text(String)$0,
550}
551"#####,
552 r#####"
553enum Value {
554 Number(i32),
555 Text(String),
556}
557
558impl 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]
491fn doctest_generate_from_impl_for_enum() { 572fn 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 @@
3use std::ops; 3use std::ops;
4 4
5use ast::TypeBoundsOwner; 5use ast::TypeBoundsOwner;
6use hir::{Adt, HasSource}; 6use hir::{Adt, HasSource, Semantics};
7use ide_db::{helpers::SnippetCap, RootDatabase}; 7use ide_db::{
8 helpers::{FamousDefs, SnippetCap},
9 RootDatabase,
10};
8use itertools::Itertools; 11use itertools::Itertools;
9use stdx::format_to; 12use stdx::format_to;
10use syntax::{ 13use syntax::{
@@ -18,7 +21,7 @@ use syntax::{
18}; 21};
19 22
20use crate::{ 23use 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
208pub(crate) fn invert_boolean_expression(expr: ast::Expr) -> ast::Expr { 211pub(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
215fn invert_special_case(expr: &ast::Expr) -> Option<ast::Expr> { 221fn 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
269fn 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
253pub(crate) fn next_prev() -> impl Iterator<Item = Direction> { 285pub(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
468pub(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.
3pub 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
3pub mod convert { 13pub 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
110pub mod prelude { 120pub 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" }
19tt = { path = "../tt", version = "0.0.0" } 19tt = { path = "../tt", version = "0.0.0" }
20test_utils = { path = "../test_utils", version = "0.0.0" } 20test_utils = { path = "../test_utils", version = "0.0.0" }
21 21
22[dev-dependencies]
23profile = { 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
3use rustc_hash::FxHashMap;
4use syntax::{
5 ast::{self, NameOwner},
6 AstNode, SmolStr,
7};
8use test_utils::{bench, bench_fixture, skip_slow_tests};
9
10use crate::{
11 ast_to_token_tree,
12 parser::{Op, RepeatKind, Separator},
13 MacroRules,
14};
15
16#[test]
17fn 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]
30fn 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
57fn 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
64fn 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
81fn 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)]
13mod tests; 13mod tests;
14 14
15#[cfg(test)]
16mod benchmark;
17
15use std::fmt; 18use std::fmt;
16 19
17use test_utils::mark; 20use 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]
14itertools = "0.10.0" 14itertools = "0.10.0"
15rowan = "0.12.2" 15rowan = "0.12.2"
16rustc_lexer = { version = "707.0.0", package = "rustc-ap-rustc_lexer" } 16rustc_lexer = { version = "708.0.0", package = "rustc-ap-rustc_lexer" }
17rustc-hash = "1.1.0" 17rustc-hash = "1.1.0"
18arrayvec = "0.5.1" 18arrayvec = "0.5.1"
19once_cell = "1.3.1" 19once_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
39pub fn numerous_macro_rules() -> String {
40 let path = project_dir().join("bench_data/numerous_macro_rules");
41 fs::read_to_string(&path).unwrap()
42}