From f4dc54958257ad33fe182d600c418591341c86dd Mon Sep 17 00:00:00 2001 From: David Lattimore Date: Tue, 23 Jun 2020 18:59:18 +1000 Subject: [PATCH] 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 da26ee66941..8f149e3db65 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 bdaba9f1b61..85420ed3ca5 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 3ee1e74e959..d7e6d817a0f 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())}"#, + ) +}