SSR: Allow matching within macro calls

This commit is contained in:
David Lattimore 2020-06-23 18:59:18 +10:00
parent 9a4d02faf9
commit f4dc549582
3 changed files with 69 additions and 2 deletions

View file

@ -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(&macro_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);
}

View file

@ -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,

View file

@ -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())}"#,
)
}