From a5ef644a16f6d187fb6b612d30f19bf4f20fc6c4 Mon Sep 17 00:00:00 2001 From: David Lattimore Date: Tue, 23 Jun 2020 22:03:39 +1000 Subject: SSR: Extract error code out to a separate module This is to make reusing it outside of parsing easier in a subsequent change. --- crates/ra_ssr/src/errors.rs | 29 +++++++++++++++++++++++++++++ crates/ra_ssr/src/lib.rs | 12 +++--------- crates/ra_ssr/src/parsing.rs | 17 +++-------------- 3 files changed, 35 insertions(+), 23 deletions(-) create mode 100644 crates/ra_ssr/src/errors.rs (limited to 'crates/ra_ssr/src') diff --git a/crates/ra_ssr/src/errors.rs b/crates/ra_ssr/src/errors.rs new file mode 100644 index 000000000..c02bacae6 --- /dev/null +++ b/crates/ra_ssr/src/errors.rs @@ -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) -> SsrError { + SsrError(message.into()) + } +} diff --git a/crates/ra_ssr/src/lib.rs b/crates/ra_ssr/src/lib.rs index 422e15ee6..d6862356d 100644 --- a/crates/ra_ssr/src/lib.rs +++ b/crates/ra_ssr/src/lib.rs @@ -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, } -#[derive(Debug, PartialEq)] -pub struct SsrError(String); - #[derive(Debug, Default)] pub struct SsrMatches { pub matches: Vec, @@ -216,12 +216,6 @@ pub struct MatchDebugInfo { matched: Result, } -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")?; diff --git a/crates/ra_ssr/src/parsing.rs b/crates/ra_ssr/src/parsing.rs index 5ea125616..4aee97bb2 100644 --- a/crates/ra_ssr/src/parsing.rs +++ b/crates/ra_ssr/src/parsing.rs @@ -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, @@ -246,7 +241,7 @@ fn parse_placeholder(tokens: &mut std::vec::IntoIter) -> Result { - 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, 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) -> SsrError { - SsrError(message.into()) - } -} - #[cfg(test)] mod tests { use super::*; -- cgit v1.2.3 From 4a8679824b9ddf950e6754231755d5d065692c47 Mon Sep 17 00:00:00 2001 From: David Lattimore Date: Fri, 3 Jul 2020 13:15:00 +1000 Subject: SSR: Improve error reporting when a test fails --- crates/ra_ssr/src/lib.rs | 30 +++++++++++++++--------------- crates/ra_ssr/src/tests.rs | 27 +++++++++++++++++++++------ 2 files changed, 36 insertions(+), 21 deletions(-) (limited to 'crates/ra_ssr/src') diff --git a/crates/ra_ssr/src/lib.rs b/crates/ra_ssr/src/lib.rs index d6862356d..cca4576ce 100644 --- a/crates/ra_ssr/src/lib.rs +++ b/crates/ra_ssr/src/lib.rs @@ -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); } } } @@ -218,25 +217,26 @@ pub struct MatchDebugInfo { impl std::fmt::Debug for MatchDebugInfo { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "========= PATTERN ==========\n")?; + match &self.matched { + Ok(_) => writeln!(f, "Node matched")?, + Err(reason) => writeln!(f, "Node failed to match because: {}", reason.reason)?, + } + writeln!( + f, + "============ AST ===========\n\ + {:#?}", + self.node + )?; + writeln!(f, "========= PATTERN ==========")?; match &self.pattern { Ok(pattern) => { - write!(f, "{:#?}", pattern)?; + writeln!(f, "{:#?}", pattern)?; } Err(err) => { - write!(f, "{}", err.reason)?; + writeln!(f, "{}", err.reason)?; } } - write!( - f, - "\n============ AST ===========\n\ - {:#?}\n============================\n", - self.node - )?; - match &self.matched { - Ok(_) => write!(f, "Node matched")?, - Err(reason) => write!(f, "Node failed to match because: {}", reason.reason)?, - } + writeln!(f, "============================")?; Ok(()) } } diff --git a/crates/ra_ssr/src/tests.rs b/crates/ra_ssr/src/tests.rs index 9568d4432..9628dcbac 100644 --- a/crates/ra_ssr/src/tests.rs +++ b/crates/ra_ssr/src/tests.rs @@ -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) { -- cgit v1.2.3 From 69051d2f9dcf008124918ef34d2f11221a6a20f0 Mon Sep 17 00:00:00 2001 From: David Lattimore Date: Fri, 3 Jul 2020 13:09:14 +1000 Subject: SSR: Refactor matching code. Mutable state is now stored in the enum Phase. MatchState, since it now has no mutable state is renamed Matcher. MatchInputs is merged into Matcher --- crates/ra_ssr/src/matching.rs | 155 ++++++++++++++++++++---------------------- 1 file changed, 75 insertions(+), 80 deletions(-) (limited to 'crates/ra_ssr/src') diff --git a/crates/ra_ssr/src/matching.rs b/crates/ra_ssr/src/matching.rs index ce53d46d2..50b29eab2 100644 --- a/crates/ra_ssr/src/matching.rs +++ b/crates/ra_ssr/src/matching.rs @@ -92,58 +92,52 @@ pub(crate) fn get_match( sema: &Semantics, ) -> Result { 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, - /// 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, + rule: &'sema SsrRule, +} + +/// 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> MatchState<'db, 'sema> { +impl<'db, 'sema> Matcher<'db, 'sema> { fn try_match( - rule: &SsrRule, + rule: &'sema SsrRule, code: &SyntaxNode, restrict_range: &Option, sema: &'sema Semantics<'db, ra_ide_db::RootDatabase>, ) -> Result { - 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, 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 { 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); }"); } } -- cgit v1.2.3 From a354e5b5cf433a8f1236c85f30cd19829a374f6d Mon Sep 17 00:00:00 2001 From: David Lattimore Date: Fri, 3 Jul 2020 13:04:52 +1000 Subject: SSR: Update tests so that all paths in patterns can be resolved --- crates/ra_ssr/src/tests.rs | 164 +++++++++++++++++++++++++++------------------ 1 file changed, 97 insertions(+), 67 deletions(-) (limited to 'crates/ra_ssr/src') diff --git a/crates/ra_ssr/src/tests.rs b/crates/ra_ssr/src/tests.rs index 9628dcbac..f20ae2cdf 100644 --- a/crates/ra_ssr/src/tests.rs +++ b/crates/ra_ssr/src/tests.rs @@ -148,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) } }", ) } @@ -157,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)) }", ) } @@ -166,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) }", ); } @@ -175,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) }", ); } @@ -184,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 */)) }", ) } @@ -193,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 */ }", ) } @@ -202,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) }", ) } @@ -225,16 +225,18 @@ fn match_fn_definition() { #[test] fn match_struct_definition() { - assert_matches( - "struct $n {$f: Option}", - "struct Bar {} struct Foo {name: Option}", - &["struct Foo {name: Option}"], - ); + let code = r#" + struct Option {} + struct Bar {} + struct Foo {name: Option}"#; + assert_matches("struct $n {$f: Option}", code, &["struct Foo {name: Option}"]); } #[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); @@ -263,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); @@ -274,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 {42}", &["Option"]); - assert_no_match("Option<$a>", "fn f() -> Result {42}"); + assert_matches( + "Option<$a>", + "struct Option {} fn f() -> Option {42}", + &["Option"], + ); + assert_no_match( + "Option<$a>", + "struct Option {} struct Result {} fn f() -> Result {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 { Some(T), None } + use Option::Some; fn f1() { let x1 = Some(42); let x2 = Some("foo"); @@ -337,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 @@ -389,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)}", ); } @@ -405,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))}", ); } @@ -414,8 +443,8 @@ fn replace_nested_function_calls() { fn replace_type() { assert_ssr_transform( "Result<(), $a> ==>> Option<$a>", - "fn f1() -> Result<(), Vec> {foo()}", - "fn f1() -> Option> {foo()}", + "struct Result {} fn f1() -> Result<(), Vec> {foo()}", + "struct Result {} fn f1() -> Option> {foo()}", ); } @@ -423,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)}", ); } @@ -432,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);}", ); } @@ -527,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 -- cgit v1.2.3