5197: SSR internal refactorings r=davidlattimore a=davidlattimore

- Extract error code out to a separate module
- Improve error reporting when a test fails
- Refactor matching code
- Update tests so that all paths in search patterns can be resolved

Co-authored-by: David Lattimore <dml@google.com>
This commit is contained in:
bors[bot] 2020-07-04 00:13:11 +00:00 committed by GitHub
commit 212fa29a69
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 244 additions and 192 deletions

View file

@ -0,0 +1,29 @@
//! Code relating to errors produced by SSR.
/// Constructs an SsrError taking arguments like the format macro.
macro_rules! _error {
($fmt:expr) => {$crate::SsrError::new(format!($fmt))};
($fmt:expr, $($arg:tt)+) => {$crate::SsrError::new(format!($fmt, $($arg)+))}
}
pub(crate) use _error as error;
/// Returns from the current function with an error, supplied by arguments as for format!
macro_rules! _bail {
($($tokens:tt)*) => {return Err(crate::errors::error!($($tokens)*))}
}
pub(crate) use _bail as bail;
#[derive(Debug, PartialEq)]
pub struct SsrError(pub(crate) String);
impl std::fmt::Display for SsrError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "Parse error: {}", self.0)
}
}
impl SsrError {
pub(crate) fn new(message: impl Into<String>) -> SsrError {
SsrError(message.into())
}
}

View file

@ -6,9 +6,12 @@
mod matching;
mod parsing;
mod replacing;
#[macro_use]
mod errors;
#[cfg(test)]
mod tests;
pub use crate::errors::SsrError;
pub use crate::matching::Match;
use crate::matching::{record_match_fails_reasons_scope, MatchFailureReason};
use hir::Semantics;
@ -41,9 +44,6 @@ pub struct SsrPattern {
pattern: Option<SyntaxNode>,
}
#[derive(Debug, PartialEq)]
pub struct SsrError(String);
#[derive(Debug, Default)]
pub struct SsrMatches {
pub matches: Vec<Match>,
@ -201,9 +201,8 @@ impl<'db> MatchFinder<'db> {
);
}
}
} else {
self.output_debug_for_nodes_at_range(&node, range, restrict_range, out);
}
self.output_debug_for_nodes_at_range(&node, range, restrict_range, out);
}
}
}
@ -216,33 +215,28 @@ pub struct MatchDebugInfo {
matched: Result<Match, MatchFailureReason>,
}
impl std::fmt::Display for SsrError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "Parse error: {}", self.0)
}
}
impl std::fmt::Debug for MatchDebugInfo {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "========= PATTERN ==========\n")?;
match &self.pattern {
Ok(pattern) => {
write!(f, "{:#?}", pattern)?;
}
Err(err) => {
write!(f, "{}", err.reason)?;
}
match &self.matched {
Ok(_) => writeln!(f, "Node matched")?,
Err(reason) => writeln!(f, "Node failed to match because: {}", reason.reason)?,
}
write!(
writeln!(
f,
"\n============ AST ===========\n\
{:#?}\n============================\n",
"============ AST ===========\n\
{:#?}",
self.node
)?;
match &self.matched {
Ok(_) => write!(f, "Node matched")?,
Err(reason) => write!(f, "Node failed to match because: {}", reason.reason)?,
writeln!(f, "========= PATTERN ==========")?;
match &self.pattern {
Ok(pattern) => {
writeln!(f, "{:#?}", pattern)?;
}
Err(err) => {
writeln!(f, "{}", err.reason)?;
}
}
writeln!(f, "============================")?;
Ok(())
}
}

View file

@ -92,58 +92,52 @@ pub(crate) fn get_match(
sema: &Semantics<ra_ide_db::RootDatabase>,
) -> Result<Match, MatchFailed> {
record_match_fails_reasons_scope(debug_active, || {
MatchState::try_match(rule, code, restrict_range, sema)
Matcher::try_match(rule, code, restrict_range, sema)
})
}
/// Inputs to matching. This cannot be part of `MatchState`, since we mutate `MatchState` and in at
/// least one case need to hold a borrow of a placeholder from the input pattern while calling a
/// mutable `MatchState` method.
struct MatchInputs<'pattern> {
ssr_pattern: &'pattern SsrPattern,
}
/// State used while attempting to match our search pattern against a particular node of the AST.
struct MatchState<'db, 'sema> {
/// Checks if our search pattern matches a particular node of the AST.
struct Matcher<'db, 'sema> {
sema: &'sema Semantics<'db, ra_ide_db::RootDatabase>,
/// If any placeholders come from anywhere outside of this range, then the match will be
/// rejected.
restrict_range: Option<FileRange>,
/// The match that we're building. We do two passes for a successful match. On the first pass,
/// this is None so that we can avoid doing things like storing copies of what placeholders
/// matched to. If that pass succeeds, then we do a second pass where we collect those details.
/// This means that if we have a pattern like `$a.foo()` we won't do an insert into the
/// placeholders map for every single method call in the codebase. Instead we'll discard all the
/// method calls that aren't calls to `foo` on the first pass and only insert into the
/// placeholders map on the second pass. Likewise for ignored comments.
match_out: Option<Match>,
rule: &'sema SsrRule,
}
impl<'db, 'sema> MatchState<'db, 'sema> {
/// Which phase of matching we're currently performing. We do two phases because most attempted
/// matches will fail and it means we can defer more expensive checks to the second phase.
enum Phase<'a> {
/// On the first phase, we perform cheap checks. No state is mutated and nothing is recorded.
First,
/// On the second phase, we construct the `Match`. Things like what placeholders bind to is
/// recorded.
Second(&'a mut Match),
}
impl<'db, 'sema> Matcher<'db, 'sema> {
fn try_match(
rule: &SsrRule,
rule: &'sema SsrRule,
code: &SyntaxNode,
restrict_range: &Option<FileRange>,
sema: &'sema Semantics<'db, ra_ide_db::RootDatabase>,
) -> Result<Match, MatchFailed> {
let mut match_state =
MatchState { sema, restrict_range: restrict_range.clone(), match_out: None };
let match_inputs = MatchInputs { ssr_pattern: &rule.pattern };
let match_state = Matcher { sema, restrict_range: restrict_range.clone(), rule };
let pattern_tree = rule.pattern.tree_for_kind(code.kind())?;
// First pass at matching, where we check that node types and idents match.
match_state.attempt_match_node(&match_inputs, &pattern_tree, code)?;
match_state.attempt_match_node(&mut Phase::First, &pattern_tree, code)?;
match_state.validate_range(&sema.original_range(code))?;
match_state.match_out = Some(Match {
let mut the_match = Match {
range: sema.original_range(code),
matched_node: code.clone(),
placeholder_values: FxHashMap::default(),
ignored_comments: Vec::new(),
template: rule.template.clone(),
});
};
// Second matching pass, where we record placeholder matches, ignored comments and maybe do
// any other more expensive checks that we didn't want to do on the first pass.
match_state.attempt_match_node(&match_inputs, &pattern_tree, code)?;
Ok(match_state.match_out.unwrap())
match_state.attempt_match_node(&mut Phase::Second(&mut the_match), &pattern_tree, code)?;
Ok(the_match)
}
/// Checks that `range` is within the permitted range if any. This is applicable when we're
@ -161,27 +155,22 @@ impl<'db, 'sema> MatchState<'db, 'sema> {
}
fn attempt_match_node(
&mut self,
match_inputs: &MatchInputs,
&self,
phase: &mut Phase,
pattern: &SyntaxNode,
code: &SyntaxNode,
) -> Result<(), MatchFailed> {
// Handle placeholders.
if let Some(placeholder) =
match_inputs.get_placeholder(&SyntaxElement::Node(pattern.clone()))
{
if let Some(placeholder) = self.get_placeholder(&SyntaxElement::Node(pattern.clone())) {
for constraint in &placeholder.constraints {
self.check_constraint(constraint, code)?;
}
if self.match_out.is_none() {
return Ok(());
}
let original_range = self.sema.original_range(code);
// We validated the range for the node when we started the match, so the placeholder
// probably can't fail range validation, but just to be safe...
self.validate_range(&original_range)?;
if let Some(match_out) = &mut self.match_out {
match_out.placeholder_values.insert(
if let Phase::Second(matches_out) = phase {
let original_range = self.sema.original_range(code);
// We validated the range for the node when we started the match, so the placeholder
// probably can't fail range validation, but just to be safe...
self.validate_range(&original_range)?;
matches_out.placeholder_values.insert(
Var(placeholder.ident.to_string()),
PlaceholderMatch::new(code, original_range),
);
@ -190,41 +179,47 @@ impl<'db, 'sema> MatchState<'db, 'sema> {
}
// Non-placeholders.
if pattern.kind() != code.kind() {
fail_match!("Pattern had a {:?}, code had {:?}", pattern.kind(), code.kind());
fail_match!(
"Pattern had a `{}` ({:?}), code had `{}` ({:?})",
pattern.text(),
pattern.kind(),
code.text(),
code.kind()
);
}
// Some kinds of nodes have special handling. For everything else, we fall back to default
// matching.
match code.kind() {
SyntaxKind::RECORD_FIELD_LIST => {
self.attempt_match_record_field_list(match_inputs, pattern, code)
self.attempt_match_record_field_list(phase, pattern, code)
}
SyntaxKind::TOKEN_TREE => self.attempt_match_token_tree(match_inputs, pattern, code),
_ => self.attempt_match_node_children(match_inputs, pattern, code),
SyntaxKind::TOKEN_TREE => self.attempt_match_token_tree(phase, pattern, code),
_ => self.attempt_match_node_children(phase, pattern, code),
}
}
fn attempt_match_node_children(
&mut self,
match_inputs: &MatchInputs,
&self,
phase: &mut Phase,
pattern: &SyntaxNode,
code: &SyntaxNode,
) -> Result<(), MatchFailed> {
self.attempt_match_sequences(
match_inputs,
phase,
PatternIterator::new(pattern),
code.children_with_tokens(),
)
}
fn attempt_match_sequences(
&mut self,
match_inputs: &MatchInputs,
&self,
phase: &mut Phase,
pattern_it: PatternIterator,
mut code_it: SyntaxElementChildren,
) -> Result<(), MatchFailed> {
let mut pattern_it = pattern_it.peekable();
loop {
match self.next_non_trivial(&mut code_it) {
match phase.next_non_trivial(&mut code_it) {
None => {
if let Some(p) = pattern_it.next() {
fail_match!("Part of the pattern was unmatched: {:?}", p);
@ -232,11 +227,11 @@ impl<'db, 'sema> MatchState<'db, 'sema> {
return Ok(());
}
Some(SyntaxElement::Token(c)) => {
self.attempt_match_token(&mut pattern_it, &c)?;
self.attempt_match_token(phase, &mut pattern_it, &c)?;
}
Some(SyntaxElement::Node(c)) => match pattern_it.next() {
Some(SyntaxElement::Node(p)) => {
self.attempt_match_node(match_inputs, &p, &c)?;
self.attempt_match_node(phase, &p, &c)?;
}
Some(p) => fail_match!("Pattern wanted '{}', code has {}", p, c.text()),
None => fail_match!("Pattern reached end, code has {}", c.text()),
@ -246,11 +241,12 @@ impl<'db, 'sema> MatchState<'db, 'sema> {
}
fn attempt_match_token(
&mut self,
&self,
phase: &mut Phase,
pattern: &mut Peekable<PatternIterator>,
code: &ra_syntax::SyntaxToken,
) -> Result<(), MatchFailed> {
self.record_ignored_comments(code);
phase.record_ignored_comments(code);
// Ignore whitespace and comments.
if code.kind().is_trivia() {
return Ok(());
@ -317,8 +313,8 @@ impl<'db, 'sema> MatchState<'db, 'sema> {
/// We want to allow the records to match in any order, so we have special matching logic for
/// them.
fn attempt_match_record_field_list(
&mut self,
match_inputs: &MatchInputs,
&self,
phase: &mut Phase,
pattern: &SyntaxNode,
code: &SyntaxNode,
) -> Result<(), MatchFailed> {
@ -334,11 +330,11 @@ impl<'db, 'sema> MatchState<'db, 'sema> {
for p in pattern.children_with_tokens() {
if let SyntaxElement::Node(p) = p {
if let Some(name_element) = p.first_child_or_token() {
if match_inputs.get_placeholder(&name_element).is_some() {
if self.get_placeholder(&name_element).is_some() {
// If the pattern is using placeholders for field names then order
// independence doesn't make sense. Fall back to regular ordered
// matching.
return self.attempt_match_node_children(match_inputs, pattern, code);
return self.attempt_match_node_children(phase, pattern, code);
}
if let Some(ident) = only_ident(name_element) {
let code_record = fields_by_name.remove(ident.text()).ok_or_else(|| {
@ -347,7 +343,7 @@ impl<'db, 'sema> MatchState<'db, 'sema> {
ident
)
})?;
self.attempt_match_node(match_inputs, &p, &code_record)?;
self.attempt_match_node(phase, &p, &code_record)?;
}
}
}
@ -367,16 +363,15 @@ impl<'db, 'sema> MatchState<'db, 'sema> {
/// 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,
&self,
phase: &mut Phase,
pattern: &SyntaxNode,
code: &ra_syntax::SyntaxNode,
) -> Result<(), MatchFailed> {
let mut pattern = PatternIterator::new(pattern).peekable();
let mut children = code.children_with_tokens();
while let Some(child) = children.next() {
if let Some(placeholder) = pattern.peek().and_then(|p| match_inputs.get_placeholder(p))
{
if let Some(placeholder) = pattern.peek().and_then(|p| self.get_placeholder(p)) {
pattern.next();
let next_pattern_token = pattern
.peek()
@ -402,7 +397,7 @@ impl<'db, 'sema> MatchState<'db, 'sema> {
if Some(first_token.to_string()) == next_pattern_token {
if let Some(SyntaxElement::Node(p)) = pattern.next() {
// We have a subtree that starts with the next token in our pattern.
self.attempt_match_token_tree(match_inputs, &p, &n)?;
self.attempt_match_token_tree(phase, &p, &n)?;
break;
}
}
@ -411,7 +406,7 @@ impl<'db, 'sema> MatchState<'db, 'sema> {
};
last_matched_token = next;
}
if let Some(match_out) = &mut self.match_out {
if let Phase::Second(match_out) = phase {
match_out.placeholder_values.insert(
Var(placeholder.ident.to_string()),
PlaceholderMatch::from_range(FileRange {
@ -427,11 +422,11 @@ impl<'db, 'sema> MatchState<'db, 'sema> {
// Match literal (non-placeholder) tokens.
match child {
SyntaxElement::Token(token) => {
self.attempt_match_token(&mut pattern, &token)?;
self.attempt_match_token(phase, &mut pattern, &token)?;
}
SyntaxElement::Node(node) => match pattern.next() {
Some(SyntaxElement::Node(p)) => {
self.attempt_match_token_tree(match_inputs, &p, &node)?;
self.attempt_match_token_tree(phase, &p, &node)?;
}
Some(SyntaxElement::Token(p)) => fail_match!(
"Pattern has token '{}', code has subtree '{}'",
@ -448,6 +443,13 @@ impl<'db, 'sema> MatchState<'db, 'sema> {
Ok(())
}
fn get_placeholder(&self, element: &SyntaxElement) -> Option<&Placeholder> {
only_ident(element.clone())
.and_then(|ident| self.rule.pattern.placeholders_by_stand_in.get(ident.text()))
}
}
impl Phase<'_> {
fn next_non_trivial(&mut self, code_it: &mut SyntaxElementChildren) -> Option<SyntaxElement> {
loop {
let c = code_it.next();
@ -463,7 +465,7 @@ impl<'db, 'sema> MatchState<'db, 'sema> {
fn record_ignored_comments(&mut self, token: &SyntaxToken) {
if token.kind() == SyntaxKind::COMMENT {
if let Some(match_out) = &mut self.match_out {
if let Phase::Second(match_out) = self {
if let Some(comment) = ast::Comment::cast(token.clone()) {
match_out.ignored_comments.push(comment);
}
@ -472,13 +474,6 @@ impl<'db, 'sema> MatchState<'db, 'sema> {
}
}
impl MatchInputs<'_> {
fn get_placeholder(&self, element: &SyntaxElement) -> Option<&Placeholder> {
only_ident(element.clone())
.and_then(|ident| self.ssr_pattern.placeholders_by_stand_in.get(ident.text()))
}
}
fn is_closing_token(kind: SyntaxKind) -> bool {
kind == SyntaxKind::R_PAREN || kind == SyntaxKind::R_CURLY || kind == SyntaxKind::R_BRACK
}
@ -596,12 +591,12 @@ impl PatternIterator {
#[cfg(test)]
mod tests {
use super::*;
use crate::MatchFinder;
use crate::{MatchFinder, SsrRule};
#[test]
fn parse_match_replace() {
let rule: SsrRule = "foo($x) ==>> bar($x)".parse().unwrap();
let input = "fn main() { foo(1+2); }";
let input = "fn foo() {} fn main() { foo(1+2); }";
use ra_db::fixture::WithFixture;
let (db, file_id) = ra_ide_db::RootDatabase::with_single_file(input);
@ -623,6 +618,6 @@ mod tests {
let edit = crate::replacing::matches_to_edit(&matches, input);
let mut after = input.to_string();
edit.apply(&mut after);
assert_eq!(after, "fn main() { bar(1+2); }");
assert_eq!(after, "fn foo() {} fn main() { bar(1+2); }");
}
}

View file

@ -5,17 +5,12 @@
//! search patterns, we go further and parse the pattern as each kind of thing that we can match.
//! e.g. expressions, type references etc.
use crate::errors::bail;
use crate::{SsrError, SsrPattern, SsrRule};
use ra_syntax::{ast, AstNode, SmolStr, SyntaxKind, T};
use rustc_hash::{FxHashMap, FxHashSet};
use std::str::FromStr;
/// Returns from the current function with an error, supplied by arguments as for format!
macro_rules! bail {
($e:expr) => {return Err($crate::SsrError::new($e))};
($fmt:expr, $($arg:tt)+) => {return Err($crate::SsrError::new(format!($fmt, $($arg)+)))}
}
#[derive(Clone, Debug)]
pub(crate) struct SsrTemplate {
pub(crate) tokens: Vec<PatternElement>,
@ -246,7 +241,7 @@ fn parse_placeholder(tokens: &mut std::vec::IntoIter<Token>) -> Result<Placehold
}
}
_ => {
bail!("Placeholders should either be $name or ${name:constraints}");
bail!("Placeholders should either be $name or ${{name:constraints}}");
}
}
}
@ -289,7 +284,7 @@ fn expect_token(tokens: &mut std::vec::IntoIter<Token>, expected: &str) -> Resul
}
bail!("Expected {} found {}", expected, t.text);
}
bail!("Expected {} found end of stream");
bail!("Expected {} found end of stream", expected);
}
impl NodeKind {
@ -307,12 +302,6 @@ impl Placeholder {
}
}
impl SsrError {
fn new(message: impl Into<String>) -> SsrError {
SsrError(message.into())
}
}
#[cfg(test)]
mod tests {
use super::*;

View file

@ -91,6 +91,18 @@ fn assert_ssr_transforms(rules: &[&str], input: &str, result: &str) {
}
}
fn print_match_debug_info(match_finder: &MatchFinder, file_id: FileId, snippet: &str) {
let debug_info = match_finder.debug_where_text_equal(file_id, snippet);
println!(
"Match debug info: {} nodes had text exactly equal to '{}'",
debug_info.len(),
snippet
);
for (index, d) in debug_info.iter().enumerate() {
println!("Node #{}\n{:#?}\n", index, d);
}
}
fn assert_matches(pattern: &str, code: &str, expected: &[&str]) {
let (db, file_id) = single_file(code);
let mut match_finder = MatchFinder::new(&db);
@ -103,17 +115,20 @@ fn assert_matches(pattern: &str, code: &str, expected: &[&str]) {
.map(|m| m.matched_text())
.collect();
if matched_strings != expected && !expected.is_empty() {
let debug_info = match_finder.debug_where_text_equal(file_id, &expected[0]);
eprintln!("Test is about to fail. Some possibly useful info: {} nodes had text exactly equal to '{}'", debug_info.len(), &expected[0]);
for d in debug_info {
eprintln!("{:#?}", d);
}
print_match_debug_info(&match_finder, file_id, &expected[0]);
}
assert_eq!(matched_strings, expected);
}
fn assert_no_match(pattern: &str, code: &str) {
assert_matches(pattern, code, &[]);
let (db, file_id) = single_file(code);
let mut match_finder = MatchFinder::new(&db);
match_finder.add_search_pattern(pattern.parse().unwrap());
let matches = match_finder.find_matches_in_file(file_id).flattened().matches;
if !matches.is_empty() {
print_match_debug_info(&match_finder, file_id, &matches[0].matched_text());
panic!("Got {} matches when we expected none: {:#?}", matches.len(), matches);
}
}
fn assert_match_failure_reason(pattern: &str, code: &str, snippet: &str, expected_reason: &str) {
@ -133,8 +148,8 @@ fn assert_match_failure_reason(pattern: &str, code: &str, snippet: &str, expecte
fn ssr_function_to_method() {
assert_ssr_transform(
"my_function($a, $b) ==>> ($a).my_method($b)",
"loop { my_function( other_func(x, y), z + w) }",
"loop { (other_func(x, y)).my_method(z + w) }",
"fn my_function() {} fn main() { loop { my_function( other_func(x, y), z + w) } }",
"fn my_function() {} fn main() { loop { (other_func(x, y)).my_method(z + w) } }",
)
}
@ -142,8 +157,8 @@ fn ssr_function_to_method() {
fn ssr_nested_function() {
assert_ssr_transform(
"foo($a, $b, $c) ==>> bar($c, baz($a, $b))",
"fn main { foo (x + value.method(b), x+y-z, true && false) }",
"fn main { bar(true && false, baz(x + value.method(b), x+y-z)) }",
"fn foo() {} fn main { foo (x + value.method(b), x+y-z, true && false) }",
"fn foo() {} fn main { bar(true && false, baz(x + value.method(b), x+y-z)) }",
)
}
@ -151,8 +166,8 @@ fn ssr_nested_function() {
fn ssr_expected_spacing() {
assert_ssr_transform(
"foo($x) + bar() ==>> bar($x)",
"fn main() { foo(5) + bar() }",
"fn main() { bar(5) }",
"fn foo() {} fn bar() {} fn main() { foo(5) + bar() }",
"fn foo() {} fn bar() {} fn main() { bar(5) }",
);
}
@ -160,8 +175,8 @@ fn ssr_expected_spacing() {
fn ssr_with_extra_space() {
assert_ssr_transform(
"foo($x ) + bar() ==>> bar($x)",
"fn main() { foo( 5 ) +bar( ) }",
"fn main() { bar(5) }",
"fn foo() {} fn bar() {} fn main() { foo( 5 ) +bar( ) }",
"fn foo() {} fn bar() {} fn main() { bar(5) }",
);
}
@ -169,8 +184,8 @@ fn ssr_with_extra_space() {
fn ssr_keeps_nested_comment() {
assert_ssr_transform(
"foo($x) ==>> bar($x)",
"fn main() { foo(other(5 /* using 5 */)) }",
"fn main() { bar(other(5 /* using 5 */)) }",
"fn foo() {} fn main() { foo(other(5 /* using 5 */)) }",
"fn foo() {} fn main() { bar(other(5 /* using 5 */)) }",
)
}
@ -178,8 +193,8 @@ fn ssr_keeps_nested_comment() {
fn ssr_keeps_comment() {
assert_ssr_transform(
"foo($x) ==>> bar($x)",
"fn main() { foo(5 /* using 5 */) }",
"fn main() { bar(5)/* using 5 */ }",
"fn foo() {} fn main() { foo(5 /* using 5 */) }",
"fn foo() {} fn main() { bar(5)/* using 5 */ }",
)
}
@ -187,8 +202,8 @@ fn ssr_keeps_comment() {
fn ssr_struct_lit() {
assert_ssr_transform(
"foo{a: $a, b: $b} ==>> foo::new($a, $b)",
"fn main() { foo{b:2, a:1} }",
"fn main() { foo::new(1, 2) }",
"fn foo() {} fn main() { foo{b:2, a:1} }",
"fn foo() {} fn main() { foo::new(1, 2) }",
)
}
@ -210,16 +225,18 @@ fn match_fn_definition() {
#[test]
fn match_struct_definition() {
assert_matches(
"struct $n {$f: Option<String>}",
"struct Bar {} struct Foo {name: Option<String>}",
&["struct Foo {name: Option<String>}"],
);
let code = r#"
struct Option<T> {}
struct Bar {}
struct Foo {name: Option<String>}"#;
assert_matches("struct $n {$f: Option<String>}", code, &["struct Foo {name: Option<String>}"]);
}
#[test]
fn match_expr() {
let code = "fn f() -> i32 {foo(40 + 2, 42)}";
let code = r#"
fn foo() {}
fn f() -> i32 {foo(40 + 2, 42)}"#;
assert_matches("foo($a, $b)", code, &["foo(40 + 2, 42)"]);
assert_no_match("foo($a, $b, $c)", code);
assert_no_match("foo($a)", code);
@ -248,7 +265,9 @@ fn match_nested_method_calls_with_macro_call() {
#[test]
fn match_complex_expr() {
let code = "fn f() -> i32 {foo(bar(40, 2), 42)}";
let code = r#"
fn foo() {} fn bar() {}
fn f() -> i32 {foo(bar(40, 2), 42)}"#;
assert_matches("foo($a, $b)", code, &["foo(bar(40, 2), 42)"]);
assert_no_match("foo($a, $b, $c)", code);
assert_no_match("foo($a)", code);
@ -259,53 +278,62 @@ fn match_complex_expr() {
#[test]
fn match_with_trailing_commas() {
// Code has comma, pattern doesn't.
assert_matches("foo($a, $b)", "fn f() {foo(1, 2,);}", &["foo(1, 2,)"]);
assert_matches("Foo{$a, $b}", "fn f() {Foo{1, 2,};}", &["Foo{1, 2,}"]);
assert_matches("foo($a, $b)", "fn foo() {} fn f() {foo(1, 2,);}", &["foo(1, 2,)"]);
assert_matches("Foo{$a, $b}", "struct Foo {} fn f() {Foo{1, 2,};}", &["Foo{1, 2,}"]);
// Pattern has comma, code doesn't.
assert_matches("foo($a, $b,)", "fn f() {foo(1, 2);}", &["foo(1, 2)"]);
assert_matches("Foo{$a, $b,}", "fn f() {Foo{1, 2};}", &["Foo{1, 2}"]);
assert_matches("foo($a, $b,)", "fn foo() {} fn f() {foo(1, 2);}", &["foo(1, 2)"]);
assert_matches("Foo{$a, $b,}", "struct Foo {} fn f() {Foo{1, 2};}", &["Foo{1, 2}"]);
}
#[test]
fn match_type() {
assert_matches("i32", "fn f() -> i32 {1 + 2}", &["i32"]);
assert_matches("Option<$a>", "fn f() -> Option<i32> {42}", &["Option<i32>"]);
assert_no_match("Option<$a>", "fn f() -> Result<i32, ()> {42}");
assert_matches(
"Option<$a>",
"struct Option<T> {} fn f() -> Option<i32> {42}",
&["Option<i32>"],
);
assert_no_match(
"Option<$a>",
"struct Option<T> {} struct Result<T, E> {} fn f() -> Result<i32, ()> {42}",
);
}
#[test]
fn match_struct_instantiation() {
assert_matches(
"Foo {bar: 1, baz: 2}",
"fn f() {Foo {bar: 1, baz: 2}}",
&["Foo {bar: 1, baz: 2}"],
);
let code = r#"
struct Foo {bar: i32, baz: i32}
fn f() {Foo {bar: 1, baz: 2}}"#;
assert_matches("Foo {bar: 1, baz: 2}", code, &["Foo {bar: 1, baz: 2}"]);
// Now with placeholders for all parts of the struct.
assert_matches(
"Foo {$a: $b, $c: $d}",
"fn f() {Foo {bar: 1, baz: 2}}",
&["Foo {bar: 1, baz: 2}"],
);
assert_matches("Foo {}", "fn f() {Foo {}}", &["Foo {}"]);
assert_matches("Foo {$a: $b, $c: $d}", code, &["Foo {bar: 1, baz: 2}"]);
assert_matches("Foo {}", "struct Foo {} fn f() {Foo {}}", &["Foo {}"]);
}
#[test]
fn match_path() {
assert_matches("foo::bar", "fn f() {foo::bar(42)}", &["foo::bar"]);
assert_matches("$a::bar", "fn f() {foo::bar(42)}", &["foo::bar"]);
assert_matches("foo::$b", "fn f() {foo::bar(42)}", &["foo::bar"]);
let code = r#"
mod foo {
fn bar() {}
}
fn f() {foo::bar(42)}"#;
assert_matches("foo::bar", code, &["foo::bar"]);
assert_matches("$a::bar", code, &["foo::bar"]);
assert_matches("foo::$b", code, &["foo::bar"]);
}
#[test]
fn match_pattern() {
assert_matches("Some($a)", "fn f() {if let Some(x) = foo() {}}", &["Some(x)"]);
assert_matches("Some($a)", "struct Some(); fn f() {if let Some(x) = foo() {}}", &["Some(x)"]);
}
#[test]
fn literal_constraint() {
mark::check!(literal_constraint);
let code = r#"
enum Option<T> { Some(T), None }
use Option::Some;
fn f1() {
let x1 = Some(42);
let x2 = Some("foo");
@ -322,24 +350,36 @@ fn literal_constraint() {
fn match_reordered_struct_instantiation() {
assert_matches(
"Foo {aa: 1, b: 2, ccc: 3}",
"fn f() {Foo {b: 2, ccc: 3, aa: 1}}",
"struct Foo {} fn f() {Foo {b: 2, ccc: 3, aa: 1}}",
&["Foo {b: 2, ccc: 3, aa: 1}"],
);
assert_no_match("Foo {a: 1}", "fn f() {Foo {b: 1}}");
assert_no_match("Foo {a: 1}", "fn f() {Foo {a: 2}}");
assert_no_match("Foo {a: 1, b: 2}", "fn f() {Foo {a: 1}}");
assert_no_match("Foo {a: 1, b: 2}", "fn f() {Foo {b: 2}}");
assert_no_match("Foo {a: 1, }", "fn f() {Foo {a: 1, b: 2}}");
assert_no_match("Foo {a: 1, z: 9}", "fn f() {Foo {a: 1}}");
assert_no_match("Foo {a: 1}", "struct Foo {} fn f() {Foo {b: 1}}");
assert_no_match("Foo {a: 1}", "struct Foo {} fn f() {Foo {a: 2}}");
assert_no_match("Foo {a: 1, b: 2}", "struct Foo {} fn f() {Foo {a: 1}}");
assert_no_match("Foo {a: 1, b: 2}", "struct Foo {} fn f() {Foo {b: 2}}");
assert_no_match("Foo {a: 1, }", "struct Foo {} fn f() {Foo {a: 1, b: 2}}");
assert_no_match("Foo {a: 1, z: 9}", "struct Foo {} fn f() {Foo {a: 1}}");
}
#[test]
fn match_macro_invocation() {
assert_matches("foo!($a)", "fn() {foo(foo!(foo()))}", &["foo!(foo())"]);
assert_matches("foo!(41, $a, 43)", "fn() {foo!(41, 42, 43)}", &["foo!(41, 42, 43)"]);
assert_no_match("foo!(50, $a, 43)", "fn() {foo!(41, 42, 43}");
assert_no_match("foo!(41, $a, 50)", "fn() {foo!(41, 42, 43}");
assert_matches("foo!($a())", "fn() {foo!(bar())}", &["foo!(bar())"]);
assert_matches(
"foo!($a)",
"macro_rules! foo {() => {}} fn() {foo(foo!(foo()))}",
&["foo!(foo())"],
);
assert_matches(
"foo!(41, $a, 43)",
"macro_rules! foo {() => {}} fn() {foo!(41, 42, 43)}",
&["foo!(41, 42, 43)"],
);
assert_no_match("foo!(50, $a, 43)", "macro_rules! foo {() => {}} fn() {foo!(41, 42, 43}");
assert_no_match("foo!(41, $a, 50)", "macro_rules! foo {() => {}} fn() {foo!(41, 42, 43}");
assert_matches(
"foo!($a())",
"macro_rules! foo {() => {}} fn() {foo!(bar())}",
&["foo!(bar())"],
);
}
// When matching within a macro expansion, we only allow matches of nodes that originated from
@ -374,15 +414,19 @@ fn no_match_split_expression() {
#[test]
fn replace_function_call() {
assert_ssr_transform("foo() ==>> bar()", "fn f1() {foo(); foo();}", "fn f1() {bar(); bar();}");
assert_ssr_transform(
"foo() ==>> bar()",
"fn foo() {} fn f1() {foo(); foo();}",
"fn foo() {} fn f1() {bar(); bar();}",
);
}
#[test]
fn replace_function_call_with_placeholders() {
assert_ssr_transform(
"foo($a, $b) ==>> bar($b, $a)",
"fn f1() {foo(5, 42)}",
"fn f1() {bar(42, 5)}",
"fn foo() {} fn f1() {foo(5, 42)}",
"fn foo() {} fn f1() {bar(42, 5)}",
);
}
@ -390,8 +434,8 @@ fn replace_function_call_with_placeholders() {
fn replace_nested_function_calls() {
assert_ssr_transform(
"foo($a) ==>> bar($a)",
"fn f1() {foo(foo(42))}",
"fn f1() {bar(bar(42))}",
"fn foo() {} fn f1() {foo(foo(42))}",
"fn foo() {} fn f1() {bar(bar(42))}",
);
}
@ -399,8 +443,8 @@ fn replace_nested_function_calls() {
fn replace_type() {
assert_ssr_transform(
"Result<(), $a> ==>> Option<$a>",
"fn f1() -> Result<(), Vec<Error>> {foo()}",
"fn f1() -> Option<Vec<Error>> {foo()}",
"struct Result<T, E> {} fn f1() -> Result<(), Vec<Error>> {foo()}",
"struct Result<T, E> {} fn f1() -> Option<Vec<Error>> {foo()}",
);
}
@ -408,8 +452,8 @@ fn replace_type() {
fn replace_struct_init() {
assert_ssr_transform(
"Foo {a: $a, b: $b} ==>> Foo::new($a, $b)",
"fn f1() {Foo{b: 1, a: 2}}",
"fn f1() {Foo::new(2, 1)}",
"struct Foo {} fn f1() {Foo{b: 1, a: 2}}",
"struct Foo {} fn f1() {Foo::new(2, 1)}",
);
}
@ -417,13 +461,13 @@ fn replace_struct_init() {
fn replace_macro_invocations() {
assert_ssr_transform(
"try!($a) ==>> $a?",
"fn f1() -> Result<(), E> {bar(try!(foo()));}",
"fn f1() -> Result<(), E> {bar(foo()?);}",
"macro_rules! try {() => {}} fn f1() -> Result<(), E> {bar(try!(foo()));}",
"macro_rules! try {() => {}} fn f1() -> Result<(), E> {bar(foo()?);}",
);
assert_ssr_transform(
"foo!($a($b)) ==>> foo($b, $a)",
"fn f1() {foo!(abc(def() + 2));}",
"fn f1() {foo(def() + 2, abc);}",
"macro_rules! foo {() => {}} fn f1() {foo!(abc(def() + 2));}",
"macro_rules! foo {() => {}} fn f1() {foo(def() + 2, abc);}",
);
}
@ -512,6 +556,7 @@ fn preserves_whitespace_within_macro_expansion() {
#[test]
fn match_failure_reasons() {
let code = r#"
fn bar() {}
macro_rules! foo {
($a:expr) => {
1 + $a + 2