diff options
-rw-r--r-- | crates/ra_assists/src/assists/apply_demorgan.rs | 40 | ||||
-rw-r--r-- | crates/ra_assists/src/assists/invert_if.rs | 102 | ||||
-rw-r--r-- | crates/ra_assists/src/doc_tests/generated.rs | 17 | ||||
-rw-r--r-- | crates/ra_assists/src/lib.rs | 2 | ||||
-rw-r--r-- | crates/ra_hir_expand/src/builtin_macro.rs | 163 | ||||
-rw-r--r-- | crates/ra_hir_expand/src/db.rs | 6 | ||||
-rw-r--r-- | crates/ra_hir_expand/src/lib.rs | 7 | ||||
-rw-r--r-- | crates/ra_hir_expand/src/test_db.rs | 50 | ||||
-rw-r--r-- | crates/ra_ide_api/src/expand_macro.rs | 40 | ||||
-rw-r--r-- | crates/ra_syntax/src/ast/edit.rs | 12 | ||||
-rw-r--r-- | crates/ra_syntax/src/ast/expr_extensions.rs | 2 | ||||
-rw-r--r-- | crates/ra_syntax/src/ast/make.rs | 15 | ||||
-rw-r--r-- | docs/user/assists.md | 19 |
13 files changed, 389 insertions, 86 deletions
diff --git a/crates/ra_assists/src/assists/apply_demorgan.rs b/crates/ra_assists/src/assists/apply_demorgan.rs index 068da1774..7c57c0560 100644 --- a/crates/ra_assists/src/assists/apply_demorgan.rs +++ b/crates/ra_assists/src/assists/apply_demorgan.rs | |||
@@ -1,6 +1,6 @@ | |||
1 | use super::invert_if::invert_boolean_expression; | ||
1 | use hir::db::HirDatabase; | 2 | use hir::db::HirDatabase; |
2 | use ra_syntax::ast::{self, AstNode}; | 3 | use ra_syntax::ast::{self, AstNode}; |
3 | use ra_syntax::SyntaxNode; | ||
4 | 4 | ||
5 | use crate::{Assist, AssistCtx, AssistId}; | 5 | use crate::{Assist, AssistCtx, AssistId}; |
6 | 6 | ||
@@ -32,18 +32,18 @@ pub(crate) fn apply_demorgan(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> | |||
32 | if !cursor_in_range { | 32 | if !cursor_in_range { |
33 | return None; | 33 | return None; |
34 | } | 34 | } |
35 | let lhs = expr.lhs()?.syntax().clone(); | 35 | let lhs = expr.lhs()?; |
36 | let lhs_range = lhs.text_range(); | 36 | let lhs_range = lhs.syntax().text_range(); |
37 | let rhs = expr.rhs()?.syntax().clone(); | 37 | let rhs = expr.rhs()?; |
38 | let rhs_range = rhs.text_range(); | 38 | let rhs_range = rhs.syntax().text_range(); |
39 | let not_lhs = undo_negation(lhs)?; | 39 | let not_lhs = invert_boolean_expression(&lhs)?; |
40 | let not_rhs = undo_negation(rhs)?; | 40 | let not_rhs = invert_boolean_expression(&rhs)?; |
41 | 41 | ||
42 | ctx.add_assist(AssistId("apply_demorgan"), "apply demorgan's law", |edit| { | 42 | ctx.add_assist(AssistId("apply_demorgan"), "apply demorgan's law", |edit| { |
43 | edit.target(op_range); | 43 | edit.target(op_range); |
44 | edit.replace(op_range, opposite_op); | 44 | edit.replace(op_range, opposite_op); |
45 | edit.replace(lhs_range, format!("!({}", not_lhs)); | 45 | edit.replace(lhs_range, format!("!({}", not_lhs.syntax().text())); |
46 | edit.replace(rhs_range, format!("{})", not_rhs)); | 46 | edit.replace(rhs_range, format!("{})", not_rhs.syntax().text())); |
47 | }) | 47 | }) |
48 | } | 48 | } |
49 | 49 | ||
@@ -56,28 +56,6 @@ fn opposite_logic_op(kind: ast::BinOp) -> Option<&'static str> { | |||
56 | } | 56 | } |
57 | } | 57 | } |
58 | 58 | ||
59 | // This function tries to undo unary negation, or inequality | ||
60 | fn undo_negation(node: SyntaxNode) -> Option<String> { | ||
61 | match ast::Expr::cast(node)? { | ||
62 | ast::Expr::BinExpr(bin) => match bin.op_kind()? { | ||
63 | ast::BinOp::NegatedEqualityTest => { | ||
64 | let lhs = bin.lhs()?.syntax().text(); | ||
65 | let rhs = bin.rhs()?.syntax().text(); | ||
66 | Some(format!("{} == {}", lhs, rhs)) | ||
67 | } | ||
68 | _ => None, | ||
69 | }, | ||
70 | ast::Expr::PrefixExpr(pe) => match pe.op_kind()? { | ||
71 | ast::PrefixOp::Not => { | ||
72 | let child = pe.expr()?.syntax().text(); | ||
73 | Some(String::from(child)) | ||
74 | } | ||
75 | _ => None, | ||
76 | }, | ||
77 | _ => None, | ||
78 | } | ||
79 | } | ||
80 | |||
81 | #[cfg(test)] | 59 | #[cfg(test)] |
82 | mod tests { | 60 | mod tests { |
83 | use super::*; | 61 | use super::*; |
diff --git a/crates/ra_assists/src/assists/invert_if.rs b/crates/ra_assists/src/assists/invert_if.rs new file mode 100644 index 000000000..bababa3e2 --- /dev/null +++ b/crates/ra_assists/src/assists/invert_if.rs | |||
@@ -0,0 +1,102 @@ | |||
1 | use hir::db::HirDatabase; | ||
2 | use ra_syntax::ast::{self, AstNode}; | ||
3 | use ra_syntax::T; | ||
4 | |||
5 | use crate::{Assist, AssistCtx, AssistId}; | ||
6 | |||
7 | // Assist: invert_if | ||
8 | // | ||
9 | // Apply invert_if | ||
10 | // This transforms if expressions of the form `if !x {A} else {B}` into `if x {B} else {A}` | ||
11 | // This also works with `!=`. This assist can only be applied with the cursor | ||
12 | // on `if`. | ||
13 | // | ||
14 | // ``` | ||
15 | // fn main() { | ||
16 | // if<|> !y { A } else { B } | ||
17 | // } | ||
18 | // ``` | ||
19 | // -> | ||
20 | // ``` | ||
21 | // fn main() { | ||
22 | // if y { B } else { A } | ||
23 | // } | ||
24 | // ``` | ||
25 | |||
26 | pub(crate) fn invert_if(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { | ||
27 | let if_keyword = ctx.find_token_at_offset(T![if])?; | ||
28 | let expr = ast::IfExpr::cast(if_keyword.parent())?; | ||
29 | let if_range = if_keyword.text_range(); | ||
30 | let cursor_in_range = ctx.frange.range.is_subrange(&if_range); | ||
31 | if !cursor_in_range { | ||
32 | return None; | ||
33 | } | ||
34 | |||
35 | let cond = expr.condition()?.expr()?; | ||
36 | let then_node = expr.then_branch()?.syntax().clone(); | ||
37 | |||
38 | if let ast::ElseBranch::Block(else_block) = expr.else_branch()? { | ||
39 | let flip_cond = invert_boolean_expression(&cond)?; | ||
40 | let cond_range = cond.syntax().text_range(); | ||
41 | let else_node = else_block.syntax(); | ||
42 | let else_range = else_node.text_range(); | ||
43 | let then_range = then_node.text_range(); | ||
44 | return ctx.add_assist(AssistId("invert_if"), "invert if branches", |edit| { | ||
45 | edit.target(if_range); | ||
46 | edit.replace(cond_range, flip_cond.syntax().text()); | ||
47 | edit.replace(else_range, then_node.text()); | ||
48 | edit.replace(then_range, else_node.text()); | ||
49 | }); | ||
50 | } | ||
51 | |||
52 | None | ||
53 | } | ||
54 | |||
55 | pub(crate) fn invert_boolean_expression(expr: &ast::Expr) -> Option<ast::Expr> { | ||
56 | match expr { | ||
57 | ast::Expr::BinExpr(bin) => match bin.op_kind()? { | ||
58 | ast::BinOp::NegatedEqualityTest => bin.replace_op(T![==]).map(|it| it.into()), | ||
59 | _ => None, | ||
60 | }, | ||
61 | ast::Expr::PrefixExpr(pe) => match pe.op_kind()? { | ||
62 | ast::PrefixOp::Not => pe.expr(), | ||
63 | _ => None, | ||
64 | }, | ||
65 | _ => None, | ||
66 | } | ||
67 | } | ||
68 | |||
69 | #[cfg(test)] | ||
70 | mod tests { | ||
71 | use super::*; | ||
72 | |||
73 | use crate::helpers::{check_assist, check_assist_not_applicable}; | ||
74 | |||
75 | #[test] | ||
76 | fn invert_if_remove_inequality() { | ||
77 | check_assist( | ||
78 | invert_if, | ||
79 | "fn f() { i<|>f x != 3 { 1 } else { 3 + 2 } }", | ||
80 | "fn f() { i<|>f x == 3 { 3 + 2 } else { 1 } }", | ||
81 | ) | ||
82 | } | ||
83 | |||
84 | #[test] | ||
85 | fn invert_if_remove_not() { | ||
86 | check_assist( | ||
87 | invert_if, | ||
88 | "fn f() { <|>if !cond { 3 * 2 } else { 1 } }", | ||
89 | "fn f() { <|>if cond { 1 } else { 3 * 2 } }", | ||
90 | ) | ||
91 | } | ||
92 | |||
93 | #[test] | ||
94 | fn invert_if_doesnt_apply_with_cursor_not_on_if() { | ||
95 | check_assist_not_applicable(invert_if, "fn f() { if !<|>cond { 3 * 2 } else { 1 } }") | ||
96 | } | ||
97 | |||
98 | #[test] | ||
99 | fn invert_if_doesnt_apply_without_negated() { | ||
100 | check_assist_not_applicable(invert_if, "fn f() { i<|>f cond { 3 * 2 } else { 1 } }") | ||
101 | } | ||
102 | } | ||
diff --git a/crates/ra_assists/src/doc_tests/generated.rs b/crates/ra_assists/src/doc_tests/generated.rs index 176761efb..3c716c2d1 100644 --- a/crates/ra_assists/src/doc_tests/generated.rs +++ b/crates/ra_assists/src/doc_tests/generated.rs | |||
@@ -342,6 +342,23 @@ fn main() { | |||
342 | } | 342 | } |
343 | 343 | ||
344 | #[test] | 344 | #[test] |
345 | fn doctest_invert_if() { | ||
346 | check( | ||
347 | "invert_if", | ||
348 | r#####" | ||
349 | fn main() { | ||
350 | if<|> !y { A } else { B } | ||
351 | } | ||
352 | "#####, | ||
353 | r#####" | ||
354 | fn main() { | ||
355 | if y { B } else { A } | ||
356 | } | ||
357 | "#####, | ||
358 | ) | ||
359 | } | ||
360 | |||
361 | #[test] | ||
345 | fn doctest_make_raw_string() { | 362 | fn doctest_make_raw_string() { |
346 | check( | 363 | check( |
347 | "make_raw_string", | 364 | "make_raw_string", |
diff --git a/crates/ra_assists/src/lib.rs b/crates/ra_assists/src/lib.rs index f2f0dacbf..a372bd8b9 100644 --- a/crates/ra_assists/src/lib.rs +++ b/crates/ra_assists/src/lib.rs | |||
@@ -97,6 +97,7 @@ mod assists { | |||
97 | mod add_impl; | 97 | mod add_impl; |
98 | mod add_new; | 98 | mod add_new; |
99 | mod apply_demorgan; | 99 | mod apply_demorgan; |
100 | mod invert_if; | ||
100 | mod flip_comma; | 101 | mod flip_comma; |
101 | mod flip_binexpr; | 102 | mod flip_binexpr; |
102 | mod flip_trait_bound; | 103 | mod flip_trait_bound; |
@@ -122,6 +123,7 @@ mod assists { | |||
122 | add_impl::add_impl, | 123 | add_impl::add_impl, |
123 | add_new::add_new, | 124 | add_new::add_new, |
124 | apply_demorgan::apply_demorgan, | 125 | apply_demorgan::apply_demorgan, |
126 | invert_if::invert_if, | ||
125 | change_visibility::change_visibility, | 127 | change_visibility::change_visibility, |
126 | fill_match_arms::fill_match_arms, | 128 | fill_match_arms::fill_match_arms, |
127 | merge_match_arms::merge_match_arms, | 129 | merge_match_arms::merge_match_arms, |
diff --git a/crates/ra_hir_expand/src/builtin_macro.rs b/crates/ra_hir_expand/src/builtin_macro.rs index 4da56529d..c0e0436c0 100644 --- a/crates/ra_hir_expand/src/builtin_macro.rs +++ b/crates/ra_hir_expand/src/builtin_macro.rs | |||
@@ -8,47 +8,47 @@ use crate::{ | |||
8 | 8 | ||
9 | use crate::quote; | 9 | use crate::quote; |
10 | 10 | ||
11 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] | 11 | macro_rules! register_builtin { |
12 | pub enum BuiltinExpander { | 12 | ( $(($name:ident, $kind: ident) => $expand:ident),* ) => { |
13 | Column, | 13 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] |
14 | File, | 14 | pub enum BuiltinFnLikeExpander { |
15 | Line, | 15 | $($kind),* |
16 | Stringify, | 16 | } |
17 | } | ||
18 | 17 | ||
19 | impl BuiltinExpander { | 18 | impl BuiltinFnLikeExpander { |
20 | pub fn expand( | 19 | pub fn expand( |
21 | &self, | 20 | &self, |
22 | db: &dyn AstDatabase, | 21 | db: &dyn AstDatabase, |
23 | id: MacroCallId, | 22 | id: MacroCallId, |
24 | tt: &tt::Subtree, | 23 | tt: &tt::Subtree, |
25 | ) -> Result<tt::Subtree, mbe::ExpandError> { | 24 | ) -> Result<tt::Subtree, mbe::ExpandError> { |
26 | match self { | 25 | let expander = match *self { |
27 | BuiltinExpander::Column => column_expand(db, id, tt), | 26 | $( BuiltinFnLikeExpander::$kind => $expand, )* |
28 | BuiltinExpander::File => file_expand(db, id, tt), | 27 | }; |
29 | BuiltinExpander::Line => line_expand(db, id, tt), | 28 | expander(db, id, tt) |
30 | BuiltinExpander::Stringify => stringify_expand(db, id, tt), | 29 | } |
31 | } | 30 | } |
32 | } | 31 | |
32 | pub fn find_builtin_macro( | ||
33 | ident: &name::Name, | ||
34 | krate: CrateId, | ||
35 | ast_id: AstId<ast::MacroCall>, | ||
36 | ) -> Option<MacroDefId> { | ||
37 | let kind = match ident { | ||
38 | $( id if id == &name::$name => BuiltinFnLikeExpander::$kind, )* | ||
39 | _ => return None, | ||
40 | }; | ||
41 | |||
42 | Some(MacroDefId { krate, ast_id, kind: MacroDefKind::BuiltIn(kind) }) | ||
43 | } | ||
44 | }; | ||
33 | } | 45 | } |
34 | 46 | ||
35 | pub fn find_builtin_macro( | 47 | register_builtin! { |
36 | ident: &name::Name, | 48 | (COLUMN_MACRO, Column) => column_expand, |
37 | krate: CrateId, | 49 | (FILE_MACRO, File) => file_expand, |
38 | ast_id: AstId<ast::MacroCall>, | 50 | (LINE_MACRO, Line) => line_expand, |
39 | ) -> Option<MacroDefId> { | 51 | (STRINGIFY_MACRO, Stringify) => stringify_expand |
40 | // FIXME: Better registering method | ||
41 | if ident == &name::COLUMN_MACRO { | ||
42 | Some(MacroDefId { krate, ast_id, kind: MacroDefKind::BuiltIn(BuiltinExpander::Column) }) | ||
43 | } else if ident == &name::FILE_MACRO { | ||
44 | Some(MacroDefId { krate, ast_id, kind: MacroDefKind::BuiltIn(BuiltinExpander::File) }) | ||
45 | } else if ident == &name::LINE_MACRO { | ||
46 | Some(MacroDefId { krate, ast_id, kind: MacroDefKind::BuiltIn(BuiltinExpander::Line) }) | ||
47 | } else if ident == &name::STRINGIFY_MACRO { | ||
48 | Some(MacroDefId { krate, ast_id, kind: MacroDefKind::BuiltIn(BuiltinExpander::Stringify) }) | ||
49 | } else { | ||
50 | None | ||
51 | } | ||
52 | } | 52 | } |
53 | 53 | ||
54 | fn to_line_number(db: &dyn AstDatabase, file: HirFileId, pos: TextUnit) -> usize { | 54 | fn to_line_number(db: &dyn AstDatabase, file: HirFileId, pos: TextUnit) -> usize { |
@@ -171,3 +171,92 @@ fn file_expand( | |||
171 | 171 | ||
172 | Ok(expanded) | 172 | Ok(expanded) |
173 | } | 173 | } |
174 | |||
175 | #[cfg(test)] | ||
176 | mod tests { | ||
177 | use super::*; | ||
178 | use crate::{test_db::TestDB, MacroCallLoc}; | ||
179 | use ra_db::{fixture::WithFixture, SourceDatabase}; | ||
180 | |||
181 | fn expand_builtin_macro(s: &str, expander: BuiltinFnLikeExpander) -> String { | ||
182 | let (db, file_id) = TestDB::with_single_file(&s); | ||
183 | let parsed = db.parse(file_id); | ||
184 | let macro_calls: Vec<_> = | ||
185 | parsed.syntax_node().descendants().filter_map(|it| ast::MacroCall::cast(it)).collect(); | ||
186 | |||
187 | let ast_id_map = db.ast_id_map(file_id.into()); | ||
188 | |||
189 | // the first one should be a macro_rules | ||
190 | let def = MacroDefId { | ||
191 | krate: CrateId(0), | ||
192 | ast_id: AstId::new(file_id.into(), ast_id_map.ast_id(¯o_calls[0])), | ||
193 | kind: MacroDefKind::BuiltIn(expander), | ||
194 | }; | ||
195 | |||
196 | let loc = MacroCallLoc { | ||
197 | def, | ||
198 | ast_id: AstId::new(file_id.into(), ast_id_map.ast_id(¯o_calls[1])), | ||
199 | }; | ||
200 | |||
201 | let id = db.intern_macro(loc); | ||
202 | let parsed = db.parse_or_expand(id.as_file(MacroFileKind::Expr)).unwrap(); | ||
203 | |||
204 | parsed.text().to_string() | ||
205 | } | ||
206 | |||
207 | #[test] | ||
208 | fn test_column_expand() { | ||
209 | let expanded = expand_builtin_macro( | ||
210 | r#" | ||
211 | #[rustc_builtin_macro] | ||
212 | macro_rules! column {() => {}} | ||
213 | column!() | ||
214 | "#, | ||
215 | BuiltinFnLikeExpander::Column, | ||
216 | ); | ||
217 | |||
218 | assert_eq!(expanded, "9"); | ||
219 | } | ||
220 | |||
221 | #[test] | ||
222 | fn test_line_expand() { | ||
223 | let expanded = expand_builtin_macro( | ||
224 | r#" | ||
225 | #[rustc_builtin_macro] | ||
226 | macro_rules! line {() => {}} | ||
227 | line!() | ||
228 | "#, | ||
229 | BuiltinFnLikeExpander::Line, | ||
230 | ); | ||
231 | |||
232 | assert_eq!(expanded, "4"); | ||
233 | } | ||
234 | |||
235 | #[test] | ||
236 | fn test_stringify_expand() { | ||
237 | let expanded = expand_builtin_macro( | ||
238 | r#" | ||
239 | #[rustc_builtin_macro] | ||
240 | macro_rules! stringify {() => {}} | ||
241 | stringify!(a b c) | ||
242 | "#, | ||
243 | BuiltinFnLikeExpander::Stringify, | ||
244 | ); | ||
245 | |||
246 | assert_eq!(expanded, "\"a b c\""); | ||
247 | } | ||
248 | |||
249 | #[test] | ||
250 | fn test_file_expand() { | ||
251 | let expanded = expand_builtin_macro( | ||
252 | r#" | ||
253 | #[rustc_builtin_macro] | ||
254 | macro_rules! file {() => {}} | ||
255 | file!() | ||
256 | "#, | ||
257 | BuiltinFnLikeExpander::File, | ||
258 | ); | ||
259 | |||
260 | assert_eq!(expanded, "\"\""); | ||
261 | } | ||
262 | } | ||
diff --git a/crates/ra_hir_expand/src/db.rs b/crates/ra_hir_expand/src/db.rs index e1d93a8ef..8e46fa177 100644 --- a/crates/ra_hir_expand/src/db.rs +++ b/crates/ra_hir_expand/src/db.rs | |||
@@ -9,14 +9,14 @@ use ra_prof::profile; | |||
9 | use ra_syntax::{AstNode, Parse, SyntaxNode}; | 9 | use ra_syntax::{AstNode, Parse, SyntaxNode}; |
10 | 10 | ||
11 | use crate::{ | 11 | use crate::{ |
12 | ast_id_map::AstIdMap, BuiltinExpander, HirFileId, HirFileIdRepr, MacroCallId, MacroCallLoc, | 12 | ast_id_map::AstIdMap, BuiltinFnLikeExpander, HirFileId, HirFileIdRepr, MacroCallId, |
13 | MacroDefId, MacroDefKind, MacroFile, MacroFileKind, | 13 | MacroCallLoc, MacroDefId, MacroDefKind, MacroFile, MacroFileKind, |
14 | }; | 14 | }; |
15 | 15 | ||
16 | #[derive(Debug, Clone, Eq, PartialEq)] | 16 | #[derive(Debug, Clone, Eq, PartialEq)] |
17 | pub enum TokenExpander { | 17 | pub enum TokenExpander { |
18 | MacroRules(mbe::MacroRules), | 18 | MacroRules(mbe::MacroRules), |
19 | Builtin(BuiltinExpander), | 19 | Builtin(BuiltinFnLikeExpander), |
20 | } | 20 | } |
21 | 21 | ||
22 | impl TokenExpander { | 22 | impl TokenExpander { |
diff --git a/crates/ra_hir_expand/src/lib.rs b/crates/ra_hir_expand/src/lib.rs index 126d12fbb..4f3ccf1d0 100644 --- a/crates/ra_hir_expand/src/lib.rs +++ b/crates/ra_hir_expand/src/lib.rs | |||
@@ -24,7 +24,10 @@ use ra_syntax::{ | |||
24 | }; | 24 | }; |
25 | 25 | ||
26 | use crate::ast_id_map::FileAstId; | 26 | use crate::ast_id_map::FileAstId; |
27 | use crate::builtin_macro::BuiltinExpander; | 27 | use crate::builtin_macro::BuiltinFnLikeExpander; |
28 | |||
29 | #[cfg(test)] | ||
30 | mod test_db; | ||
28 | 31 | ||
29 | /// Input to the analyzer is a set of files, where each file is identified by | 32 | /// Input to the analyzer is a set of files, where each file is identified by |
30 | /// `FileId` and contains source code. However, another source of source code in | 33 | /// `FileId` and contains source code. However, another source of source code in |
@@ -135,7 +138,7 @@ pub struct MacroDefId { | |||
135 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] | 138 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] |
136 | pub enum MacroDefKind { | 139 | pub enum MacroDefKind { |
137 | Declarative, | 140 | Declarative, |
138 | BuiltIn(BuiltinExpander), | 141 | BuiltIn(BuiltinFnLikeExpander), |
139 | } | 142 | } |
140 | 143 | ||
141 | #[derive(Debug, Clone, PartialEq, Eq, Hash)] | 144 | #[derive(Debug, Clone, PartialEq, Eq, Hash)] |
diff --git a/crates/ra_hir_expand/src/test_db.rs b/crates/ra_hir_expand/src/test_db.rs new file mode 100644 index 000000000..d23e75d9e --- /dev/null +++ b/crates/ra_hir_expand/src/test_db.rs | |||
@@ -0,0 +1,50 @@ | |||
1 | //! Database used for testing `hir_expand`. | ||
2 | |||
3 | use std::{ | ||
4 | panic, | ||
5 | sync::{Arc, Mutex}, | ||
6 | }; | ||
7 | |||
8 | use ra_db::{salsa, CrateId, FileId, FileLoader, FileLoaderDelegate, RelativePath}; | ||
9 | |||
10 | #[salsa::database( | ||
11 | ra_db::SourceDatabaseExtStorage, | ||
12 | ra_db::SourceDatabaseStorage, | ||
13 | crate::db::AstDatabaseStorage | ||
14 | )] | ||
15 | #[derive(Debug, Default)] | ||
16 | pub struct TestDB { | ||
17 | runtime: salsa::Runtime<TestDB>, | ||
18 | events: Mutex<Option<Vec<salsa::Event<TestDB>>>>, | ||
19 | } | ||
20 | |||
21 | impl salsa::Database for TestDB { | ||
22 | fn salsa_runtime(&self) -> &salsa::Runtime<Self> { | ||
23 | &self.runtime | ||
24 | } | ||
25 | |||
26 | fn salsa_event(&self, event: impl Fn() -> salsa::Event<TestDB>) { | ||
27 | let mut events = self.events.lock().unwrap(); | ||
28 | if let Some(events) = &mut *events { | ||
29 | events.push(event()); | ||
30 | } | ||
31 | } | ||
32 | } | ||
33 | |||
34 | impl panic::RefUnwindSafe for TestDB {} | ||
35 | |||
36 | impl FileLoader for TestDB { | ||
37 | fn file_text(&self, file_id: FileId) -> Arc<String> { | ||
38 | FileLoaderDelegate(self).file_text(file_id) | ||
39 | } | ||
40 | fn resolve_relative_path( | ||
41 | &self, | ||
42 | anchor: FileId, | ||
43 | relative_path: &RelativePath, | ||
44 | ) -> Option<FileId> { | ||
45 | FileLoaderDelegate(self).resolve_relative_path(anchor, relative_path) | ||
46 | } | ||
47 | fn relevant_crates(&self, file_id: FileId) -> Arc<Vec<CrateId>> { | ||
48 | FileLoaderDelegate(self).relevant_crates(file_id) | ||
49 | } | ||
50 | } | ||
diff --git a/crates/ra_ide_api/src/expand_macro.rs b/crates/ra_ide_api/src/expand_macro.rs index 673301b10..0b540b8cd 100644 --- a/crates/ra_ide_api/src/expand_macro.rs +++ b/crates/ra_ide_api/src/expand_macro.rs | |||
@@ -47,15 +47,15 @@ fn expand_macro_recur( | |||
47 | 47 | ||
48 | for child in children.into_iter() { | 48 | for child in children.into_iter() { |
49 | let node = hir::Source::new(macro_file_id, &child); | 49 | let node = hir::Source::new(macro_file_id, &child); |
50 | let new_node = expand_macro_recur(db, source, node)?; | 50 | if let Some(new_node) = expand_macro_recur(db, source, node) { |
51 | 51 | // Replace the whole node if it is root | |
52 | // Replace the whole node if it is root | 52 | // `replace_descendants` will not replace the parent node |
53 | // `replace_descendants` will not replace the parent node | 53 | // but `SyntaxNode::descendants include itself |
54 | // but `SyntaxNode::descendants include itself | 54 | if expanded == *child.syntax() { |
55 | if expanded == *child.syntax() { | 55 | expanded = new_node; |
56 | expanded = new_node; | 56 | } else { |
57 | } else { | 57 | replaces.insert(child.syntax().clone().into(), new_node.into()); |
58 | replaces.insert(child.syntax().clone().into(), new_node.into()); | 58 | } |
59 | } | 59 | } |
60 | } | 60 | } |
61 | 61 | ||
@@ -247,4 +247,26 @@ fn some_thing() -> u32 { | |||
247 | assert_eq!(res.name, "match_ast"); | 247 | assert_eq!(res.name, "match_ast"); |
248 | assert_snapshot!(res.expansion, @r###"{}"###); | 248 | assert_snapshot!(res.expansion, @r###"{}"###); |
249 | } | 249 | } |
250 | |||
251 | #[test] | ||
252 | fn macro_expand_inner_macro_fail_to_expand() { | ||
253 | let res = check_expand_macro( | ||
254 | r#" | ||
255 | //- /lib.rs | ||
256 | macro_rules! bar { | ||
257 | (BAD) => {}; | ||
258 | } | ||
259 | macro_rules! foo { | ||
260 | () => {bar!()}; | ||
261 | } | ||
262 | |||
263 | fn main() { | ||
264 | let res = fo<|>o!(); | ||
265 | } | ||
266 | "#, | ||
267 | ); | ||
268 | |||
269 | assert_eq!(res.name, "foo"); | ||
270 | assert_snapshot!(res.expansion, @r###"bar!()"###); | ||
271 | } | ||
250 | } | 272 | } |
diff --git a/crates/ra_syntax/src/ast/edit.rs b/crates/ra_syntax/src/ast/edit.rs index 6f005a2d8..95bf9db14 100644 --- a/crates/ra_syntax/src/ast/edit.rs +++ b/crates/ra_syntax/src/ast/edit.rs | |||
@@ -13,11 +13,21 @@ use crate::{ | |||
13 | make::{self, tokens}, | 13 | make::{self, tokens}, |
14 | AstNode, TypeBoundsOwner, | 14 | AstNode, TypeBoundsOwner, |
15 | }, | 15 | }, |
16 | AstToken, Direction, InsertPosition, SmolStr, SyntaxElement, | 16 | AstToken, Direction, InsertPosition, SmolStr, SyntaxElement, SyntaxKind, |
17 | SyntaxKind::{ATTR, COMMENT, WHITESPACE}, | 17 | SyntaxKind::{ATTR, COMMENT, WHITESPACE}, |
18 | SyntaxNode, SyntaxToken, T, | 18 | SyntaxNode, SyntaxToken, T, |
19 | }; | 19 | }; |
20 | 20 | ||
21 | impl ast::BinExpr { | ||
22 | #[must_use] | ||
23 | pub fn replace_op(&self, op: SyntaxKind) -> Option<ast::BinExpr> { | ||
24 | let op_node: SyntaxElement = self.op_details()?.0.into(); | ||
25 | let to_insert: Option<SyntaxElement> = Some(tokens::op(op).into()); | ||
26 | let replace_range = RangeInclusive::new(op_node.clone(), op_node); | ||
27 | Some(replace_children(self, replace_range, to_insert.into_iter())) | ||
28 | } | ||
29 | } | ||
30 | |||
21 | impl ast::FnDef { | 31 | impl ast::FnDef { |
22 | #[must_use] | 32 | #[must_use] |
23 | pub fn with_body(&self, body: ast::Block) -> ast::FnDef { | 33 | pub fn with_body(&self, body: ast::Block) -> ast::FnDef { |
diff --git a/crates/ra_syntax/src/ast/expr_extensions.rs b/crates/ra_syntax/src/ast/expr_extensions.rs index 7c53aa934..2fd039837 100644 --- a/crates/ra_syntax/src/ast/expr_extensions.rs +++ b/crates/ra_syntax/src/ast/expr_extensions.rs | |||
@@ -127,7 +127,7 @@ pub enum BinOp { | |||
127 | } | 127 | } |
128 | 128 | ||
129 | impl ast::BinExpr { | 129 | impl ast::BinExpr { |
130 | fn op_details(&self) -> Option<(SyntaxToken, BinOp)> { | 130 | pub fn op_details(&self) -> Option<(SyntaxToken, BinOp)> { |
131 | self.syntax().children_with_tokens().filter_map(|it| it.into_token()).find_map(|c| { | 131 | self.syntax().children_with_tokens().filter_map(|it| it.into_token()).find_map(|c| { |
132 | let bin_op = match c.kind() { | 132 | let bin_op = match c.kind() { |
133 | T![||] => BinOp::BooleanOr, | 133 | T![||] => BinOp::BooleanOr, |
diff --git a/crates/ra_syntax/src/ast/make.rs b/crates/ra_syntax/src/ast/make.rs index 9749327fa..40db570da 100644 --- a/crates/ra_syntax/src/ast/make.rs +++ b/crates/ra_syntax/src/ast/make.rs | |||
@@ -173,10 +173,21 @@ fn ast_from_text<N: AstNode>(text: &str) -> N { | |||
173 | } | 173 | } |
174 | 174 | ||
175 | pub mod tokens { | 175 | pub mod tokens { |
176 | use crate::{AstNode, Parse, SourceFile, SyntaxKind::*, SyntaxToken, T}; | 176 | use crate::{AstNode, Parse, SourceFile, SyntaxKind, SyntaxKind::*, SyntaxToken, T}; |
177 | use once_cell::sync::Lazy; | 177 | use once_cell::sync::Lazy; |
178 | 178 | ||
179 | static SOURCE_FILE: Lazy<Parse<SourceFile>> = Lazy::new(|| SourceFile::parse(",\n; ;")); | 179 | static SOURCE_FILE: Lazy<Parse<SourceFile>> = |
180 | Lazy::new(|| SourceFile::parse("const C: () = (1 != 1, 2 == 2)\n;")); | ||
181 | |||
182 | pub fn op(op: SyntaxKind) -> SyntaxToken { | ||
183 | SOURCE_FILE | ||
184 | .tree() | ||
185 | .syntax() | ||
186 | .descendants_with_tokens() | ||
187 | .filter_map(|it| it.into_token()) | ||
188 | .find(|it| it.kind() == op) | ||
189 | .unwrap() | ||
190 | } | ||
180 | 191 | ||
181 | pub fn comma() -> SyntaxToken { | 192 | pub fn comma() -> SyntaxToken { |
182 | SOURCE_FILE | 193 | SOURCE_FILE |
diff --git a/docs/user/assists.md b/docs/user/assists.md index 8da7578e2..6f4c30bee 100644 --- a/docs/user/assists.md +++ b/docs/user/assists.md | |||
@@ -329,6 +329,25 @@ fn main() { | |||
329 | } | 329 | } |
330 | ``` | 330 | ``` |
331 | 331 | ||
332 | ## `invert_if` | ||
333 | |||
334 | Apply invert_if | ||
335 | This transforms if expressions of the form `if !x {A} else {B}` into `if x {B} else {A}` | ||
336 | This also works with `!=`. This assist can only be applied with the cursor | ||
337 | on `if`. | ||
338 | |||
339 | ```rust | ||
340 | // BEFORE | ||
341 | fn main() { | ||
342 | if┃ !y { A } else { B } | ||
343 | } | ||
344 | |||
345 | // AFTER | ||
346 | fn main() { | ||
347 | if y { B } else { A } | ||
348 | } | ||
349 | ``` | ||
350 | |||
332 | ## `make_raw_string` | 351 | ## `make_raw_string` |
333 | 352 | ||
334 | Adds `r#` to a plain string literal. | 353 | Adds `r#` to a plain string literal. |