aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_ssr
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_ssr')
-rw-r--r--crates/ra_ssr/src/lib.rs18
-rw-r--r--crates/ra_ssr/src/matching.rs4
-rw-r--r--crates/ra_ssr/src/tests.rs61
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;
12use crate::matching::Match; 12use crate::matching::Match;
13use hir::Semantics; 13use hir::Semantics;
14use ra_db::{FileId, FileRange}; 14use ra_db::{FileId, FileRange};
15use ra_syntax::{AstNode, SmolStr, SyntaxNode}; 15use ra_syntax::{ast, AstNode, SmolStr, SyntaxNode};
16use ra_text_edit::TextEdit; 16use ra_text_edit::TextEdit;
17use rustc_hash::FxHashMap; 17use 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(&macro_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
212fn normalize_code(code: &str) -> String {
213 let (db, file_id) = single_file(code);
214 db.file_text(file_id).to_string()
215}
216
212fn assert_ssr_transforms(rules: &[&str], input: &str, result: &str) { 217fn 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]
370fn 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]
359fn match_complex_expr() { 381fn 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]
574fn 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]
594fn 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}