From f4dc54958257ad33fe182d600c418591341c86dd Mon Sep 17 00:00:00 2001 From: David Lattimore Date: Tue, 23 Jun 2020 18:59:18 +1000 Subject: SSR: Allow matching within macro calls --- crates/ra_ssr/src/lib.rs | 18 +++++++++++++++- crates/ra_ssr/src/matching.rs | 4 +++- crates/ra_ssr/src/tests.rs | 49 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 69 insertions(+), 2 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; use crate::matching::Match; use hir::Semantics; use ra_db::{FileId, FileRange}; -use ra_syntax::{AstNode, SmolStr, SyntaxNode}; +use ra_syntax::{ast, AstNode, SmolStr, SyntaxNode}; use ra_text_edit::TextEdit; use rustc_hash::FxHashMap; @@ -107,6 +107,22 @@ impl<'db> MatchFinder<'db> { return; } } + // If we've got a macro call, we already tried matching it pre-expansion, which is the only + // way to match the whole macro, now try expanding it and matching the expansion. + if let Some(macro_call) = ast::MacroCall::cast(code.clone()) { + if let Some(expanded) = self.sema.expand(¯o_call) { + if let Some(tt) = macro_call.token_tree() { + // When matching within a macro expansion, we only want to allow matches of + // nodes that originated entirely from within the token tree of the macro call. + // i.e. we don't want to match something that came from the macro itself. + self.find_matches( + &expanded, + &Some(self.sema.original_range(tt.syntax())), + matches_out, + ); + } + } + } for child in code.children() { self.find_matches(&child, restrict_range, matches_out); } 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> { } /// Outside of token trees, a placeholder can only match a single AST node, whereas in a token - /// tree it can match a sequence of tokens. + /// tree it can match a sequence of tokens. Note, that this code will only be used when the + /// pattern matches the macro invocation. For matches within the macro call, we'll already have + /// expanded the macro. fn attempt_match_token_tree( &mut self, match_inputs: &MatchInputs, diff --git a/crates/ra_ssr/src/tests.rs b/crates/ra_ssr/src/tests.rs index 3ee1e74e9..d7e6d817a 100644 --- a/crates/ra_ssr/src/tests.rs +++ b/crates/ra_ssr/src/tests.rs @@ -355,6 +355,18 @@ fn match_nested_method_calls() { ); } +// Make sure that our node matching semantics don't differ within macro calls. +#[test] +fn match_nested_method_calls_with_macro_call() { + assert_matches( + "$a.z().z().z()", + r#" + macro_rules! m1 { ($a:expr) => {$a}; } + fn f() {m1!(h().i().j().z().z().z().d().e())}"#, + &["h().i().j().z().z().z()"], + ); +} + #[test] fn match_complex_expr() { let code = "fn f() -> i32 {foo(bar(40, 2), 42)}"; @@ -547,3 +559,40 @@ fn multiple_rules() { "fn f() -> i32 {add_one(add(3, 2))}", ) } + +#[test] +fn match_within_macro_invocation() { + let code = r#" + macro_rules! foo { + ($a:stmt; $b:expr) => { + $b + }; + } + struct A {} + impl A { + fn bar() {} + } + fn f1() { + let aaa = A {}; + foo!(macro_ignores_this(); aaa.bar()); + } + "#; + assert_matches("$a.bar()", code, &["aaa.bar()"]); +} + +#[test] +fn replace_within_macro_expansion() { + assert_ssr_transform( + "$a.foo() ==>> bar($a)", + r#" + macro_rules! macro1 { + ($a:expr) => {$a} + } + fn f() {macro1!(5.x().foo().o2())}"#, + r#" + macro_rules! macro1 { + ($a:expr) => {$a} + } + fn f() {macro1!(bar(5.x()).o2())}"#, + ) +} -- cgit v1.2.3 From fc46c12e3641c5dcd18f4b32f85b2d8f33000cf6 Mon Sep 17 00:00:00 2001 From: David Lattimore Date: Sat, 27 Jun 2020 11:31:15 +1000 Subject: Fix test following change to fixture parsing (d016cb486738c1ab2574a322924183fa8a870b06) --- crates/ra_ssr/src/tests.rs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/crates/ra_ssr/src/tests.rs b/crates/ra_ssr/src/tests.rs index d7e6d817a..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) { assert_ssr_transforms(&[rule], input, result); } +fn normalize_code(code: &str) -> String { + let (db, file_id) = single_file(code); + db.file_text(file_id).to_string() +} + fn assert_ssr_transforms(rules: &[&str], input: &str, result: &str) { let (db, file_id) = single_file(input); let mut match_finder = MatchFinder::new(&db); @@ -217,8 +222,13 @@ fn assert_ssr_transforms(rules: &[&str], input: &str, result: &str) { match_finder.add_rule(rule); } if let Some(edits) = match_finder.edits_for_file(file_id) { - let mut after = input.to_string(); + // Note, db.file_text is not necessarily the same as `input`, since fixture parsing alters + // stuff. + let mut after = db.file_text(file_id).to_string(); edits.apply(&mut after); + // Likewise, we need to make sure that whatever transformations fixture parsing applies, + // also get appplied to our expected result. + let result = normalize_code(result); assert_eq!(after, result); } else { panic!("No edits were made"); -- cgit v1.2.3