5007: SSR: Allow matching within macro calls r=matklad a=davidlattimore

#3186 

Co-authored-by: David Lattimore <dml@google.com>
This commit is contained in:
bors[bot] 2020-06-27 09:43:08 +00:00 committed by GitHub
commit dd3f9eaceb
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 80 additions and 3 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

@ -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");
@ -355,6 +365,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 +569,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())}"#,
)
}