diff options
author | bors[bot] <26634292+bors[bot]@users.noreply.github.com> | 2020-06-27 10:43:08 +0100 |
---|---|---|
committer | GitHub <[email protected]> | 2020-06-27 10:43:08 +0100 |
commit | dd3f9eaceb17f5686f6377250cbe510b348af318 (patch) | |
tree | f8e5b4a36265a1f7af8891cd1f2dae593275ef06 | |
parent | 656cbc68a1f500510cd721cee2c3a515da53dc31 (diff) | |
parent | fc46c12e3641c5dcd18f4b32f85b2d8f33000cf6 (diff) |
Merge #5007
5007: SSR: Allow matching within macro calls r=matklad a=davidlattimore
#3186
Co-authored-by: David Lattimore <[email protected]>
-rw-r--r-- | crates/ra_ssr/src/lib.rs | 18 | ||||
-rw-r--r-- | crates/ra_ssr/src/matching.rs | 4 | ||||
-rw-r--r-- | crates/ra_ssr/src/tests.rs | 61 |
3 files changed, 80 insertions, 3 deletions
diff --git a/crates/ra_ssr/src/lib.rs b/crates/ra_ssr/src/lib.rs index da26ee669..8f149e3db 100644 --- a/crates/ra_ssr/src/lib.rs +++ b/crates/ra_ssr/src/lib.rs | |||
@@ -12,7 +12,7 @@ mod tests; | |||
12 | use crate::matching::Match; | 12 | use crate::matching::Match; |
13 | use hir::Semantics; | 13 | use hir::Semantics; |
14 | use ra_db::{FileId, FileRange}; | 14 | use ra_db::{FileId, FileRange}; |
15 | use ra_syntax::{AstNode, SmolStr, SyntaxNode}; | 15 | use ra_syntax::{ast, AstNode, SmolStr, SyntaxNode}; |
16 | use ra_text_edit::TextEdit; | 16 | use ra_text_edit::TextEdit; |
17 | use rustc_hash::FxHashMap; | 17 | use rustc_hash::FxHashMap; |
18 | 18 | ||
@@ -107,6 +107,22 @@ impl<'db> MatchFinder<'db> { | |||
107 | return; | 107 | return; |
108 | } | 108 | } |
109 | } | 109 | } |
110 | // If we've got a macro call, we already tried matching it pre-expansion, which is the only | ||
111 | // way to match the whole macro, now try expanding it and matching the expansion. | ||
112 | if let Some(macro_call) = ast::MacroCall::cast(code.clone()) { | ||
113 | if let Some(expanded) = self.sema.expand(¯o_call) { | ||
114 | if let Some(tt) = macro_call.token_tree() { | ||
115 | // When matching within a macro expansion, we only want to allow matches of | ||
116 | // nodes that originated entirely from within the token tree of the macro call. | ||
117 | // i.e. we don't want to match something that came from the macro itself. | ||
118 | self.find_matches( | ||
119 | &expanded, | ||
120 | &Some(self.sema.original_range(tt.syntax())), | ||
121 | matches_out, | ||
122 | ); | ||
123 | } | ||
124 | } | ||
125 | } | ||
110 | for child in code.children() { | 126 | for child in code.children() { |
111 | self.find_matches(&child, restrict_range, matches_out); | 127 | self.find_matches(&child, restrict_range, matches_out); |
112 | } | 128 | } |
diff --git a/crates/ra_ssr/src/matching.rs b/crates/ra_ssr/src/matching.rs index bdaba9f1b..85420ed3c 100644 --- a/crates/ra_ssr/src/matching.rs +++ b/crates/ra_ssr/src/matching.rs | |||
@@ -343,7 +343,9 @@ impl<'db, 'sema> MatchState<'db, 'sema> { | |||
343 | } | 343 | } |
344 | 344 | ||
345 | /// Outside of token trees, a placeholder can only match a single AST node, whereas in a token | 345 | /// Outside of token trees, a placeholder can only match a single AST node, whereas in a token |
346 | /// tree it can match a sequence of tokens. | 346 | /// tree it can match a sequence of tokens. Note, that this code will only be used when the |
347 | /// pattern matches the macro invocation. For matches within the macro call, we'll already have | ||
348 | /// expanded the macro. | ||
347 | fn attempt_match_token_tree( | 349 | fn attempt_match_token_tree( |
348 | &mut self, | 350 | &mut self, |
349 | match_inputs: &MatchInputs, | 351 | match_inputs: &MatchInputs, |
diff --git a/crates/ra_ssr/src/tests.rs b/crates/ra_ssr/src/tests.rs index 3ee1e74e9..7a3141be8 100644 --- a/crates/ra_ssr/src/tests.rs +++ b/crates/ra_ssr/src/tests.rs | |||
@@ -209,6 +209,11 @@ fn assert_ssr_transform(rule: &str, input: &str, result: &str) { | |||
209 | assert_ssr_transforms(&[rule], input, result); | 209 | assert_ssr_transforms(&[rule], input, result); |
210 | } | 210 | } |
211 | 211 | ||
212 | fn normalize_code(code: &str) -> String { | ||
213 | let (db, file_id) = single_file(code); | ||
214 | db.file_text(file_id).to_string() | ||
215 | } | ||
216 | |||
212 | fn assert_ssr_transforms(rules: &[&str], input: &str, result: &str) { | 217 | fn assert_ssr_transforms(rules: &[&str], input: &str, result: &str) { |
213 | let (db, file_id) = single_file(input); | 218 | let (db, file_id) = single_file(input); |
214 | let mut match_finder = MatchFinder::new(&db); | 219 | let mut match_finder = MatchFinder::new(&db); |
@@ -217,8 +222,13 @@ fn assert_ssr_transforms(rules: &[&str], input: &str, result: &str) { | |||
217 | match_finder.add_rule(rule); | 222 | match_finder.add_rule(rule); |
218 | } | 223 | } |
219 | if let Some(edits) = match_finder.edits_for_file(file_id) { | 224 | if let Some(edits) = match_finder.edits_for_file(file_id) { |
220 | let mut after = input.to_string(); | 225 | // Note, db.file_text is not necessarily the same as `input`, since fixture parsing alters |
226 | // stuff. | ||
227 | let mut after = db.file_text(file_id).to_string(); | ||
221 | edits.apply(&mut after); | 228 | edits.apply(&mut after); |
229 | // Likewise, we need to make sure that whatever transformations fixture parsing applies, | ||
230 | // also get appplied to our expected result. | ||
231 | let result = normalize_code(result); | ||
222 | assert_eq!(after, result); | 232 | assert_eq!(after, result); |
223 | } else { | 233 | } else { |
224 | panic!("No edits were made"); | 234 | panic!("No edits were made"); |
@@ -355,6 +365,18 @@ fn match_nested_method_calls() { | |||
355 | ); | 365 | ); |
356 | } | 366 | } |
357 | 367 | ||
368 | // Make sure that our node matching semantics don't differ within macro calls. | ||
369 | #[test] | ||
370 | fn match_nested_method_calls_with_macro_call() { | ||
371 | assert_matches( | ||
372 | "$a.z().z().z()", | ||
373 | r#" | ||
374 | macro_rules! m1 { ($a:expr) => {$a}; } | ||
375 | fn f() {m1!(h().i().j().z().z().z().d().e())}"#, | ||
376 | &["h().i().j().z().z().z()"], | ||
377 | ); | ||
378 | } | ||
379 | |||
358 | #[test] | 380 | #[test] |
359 | fn match_complex_expr() { | 381 | fn match_complex_expr() { |
360 | let code = "fn f() -> i32 {foo(bar(40, 2), 42)}"; | 382 | let code = "fn f() -> i32 {foo(bar(40, 2), 42)}"; |
@@ -547,3 +569,40 @@ fn multiple_rules() { | |||
547 | "fn f() -> i32 {add_one(add(3, 2))}", | 569 | "fn f() -> i32 {add_one(add(3, 2))}", |
548 | ) | 570 | ) |
549 | } | 571 | } |
572 | |||
573 | #[test] | ||
574 | fn match_within_macro_invocation() { | ||
575 | let code = r#" | ||
576 | macro_rules! foo { | ||
577 | ($a:stmt; $b:expr) => { | ||
578 | $b | ||
579 | }; | ||
580 | } | ||
581 | struct A {} | ||
582 | impl A { | ||
583 | fn bar() {} | ||
584 | } | ||
585 | fn f1() { | ||
586 | let aaa = A {}; | ||
587 | foo!(macro_ignores_this(); aaa.bar()); | ||
588 | } | ||
589 | "#; | ||
590 | assert_matches("$a.bar()", code, &["aaa.bar()"]); | ||
591 | } | ||
592 | |||
593 | #[test] | ||
594 | fn replace_within_macro_expansion() { | ||
595 | assert_ssr_transform( | ||
596 | "$a.foo() ==>> bar($a)", | ||
597 | r#" | ||
598 | macro_rules! macro1 { | ||
599 | ($a:expr) => {$a} | ||
600 | } | ||
601 | fn f() {macro1!(5.x().foo().o2())}"#, | ||
602 | r#" | ||
603 | macro_rules! macro1 { | ||
604 | ($a:expr) => {$a} | ||
605 | } | ||
606 | fn f() {macro1!(bar(5.x()).o2())}"#, | ||
607 | ) | ||
608 | } | ||