mirror of
https://github.com/rust-lang/rust-analyzer
synced 2024-11-10 15:14:32 +00:00
Merge #5007
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:
commit
dd3f9eaceb
3 changed files with 80 additions and 3 deletions
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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())}"#,
|
||||
)
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue