mirror of
https://github.com/rust-lang/rust-analyzer
synced 2025-01-13 21:54:42 +00:00
Merge #5197
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:
commit
212fa29a69
5 changed files with 244 additions and 192 deletions
29
crates/ra_ssr/src/errors.rs
Normal file
29
crates/ra_ssr/src/errors.rs
Normal 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())
|
||||
}
|
||||
}
|
|
@ -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(())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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); }");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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::*;
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue