diff options
author | bors[bot] <26634292+bors[bot]@users.noreply.github.com> | 2020-07-04 01:13:11 +0100 |
---|---|---|
committer | GitHub <[email protected]> | 2020-07-04 01:13:11 +0100 |
commit | 212fa29a69af5f0f0de30a1d450bb4cd978a0c3e (patch) | |
tree | d6e217123706604766365d4630ee21ef2d34a8bb /crates/ra_ssr/src | |
parent | fe16e1da695589d55f13b36644b7a56be7d09a48 (diff) | |
parent | a354e5b5cf433a8f1236c85f30cd19829a374f6d (diff) |
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 <[email protected]>
Diffstat (limited to 'crates/ra_ssr/src')
-rw-r--r-- | crates/ra_ssr/src/errors.rs | 29 | ||||
-rw-r--r-- | crates/ra_ssr/src/lib.rs | 42 | ||||
-rw-r--r-- | crates/ra_ssr/src/matching.rs | 155 | ||||
-rw-r--r-- | crates/ra_ssr/src/parsing.rs | 17 | ||||
-rw-r--r-- | crates/ra_ssr/src/tests.rs | 191 |
5 files changed, 243 insertions, 191 deletions
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 @@ | |||
1 | //! Code relating to errors produced by SSR. | ||
2 | |||
3 | /// Constructs an SsrError taking arguments like the format macro. | ||
4 | macro_rules! _error { | ||
5 | ($fmt:expr) => {$crate::SsrError::new(format!($fmt))}; | ||
6 | ($fmt:expr, $($arg:tt)+) => {$crate::SsrError::new(format!($fmt, $($arg)+))} | ||
7 | } | ||
8 | pub(crate) use _error as error; | ||
9 | |||
10 | /// Returns from the current function with an error, supplied by arguments as for format! | ||
11 | macro_rules! _bail { | ||
12 | ($($tokens:tt)*) => {return Err(crate::errors::error!($($tokens)*))} | ||
13 | } | ||
14 | pub(crate) use _bail as bail; | ||
15 | |||
16 | #[derive(Debug, PartialEq)] | ||
17 | pub struct SsrError(pub(crate) String); | ||
18 | |||
19 | impl std::fmt::Display for SsrError { | ||
20 | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { | ||
21 | write!(f, "Parse error: {}", self.0) | ||
22 | } | ||
23 | } | ||
24 | |||
25 | impl SsrError { | ||
26 | pub(crate) fn new(message: impl Into<String>) -> SsrError { | ||
27 | SsrError(message.into()) | ||
28 | } | ||
29 | } | ||
diff --git a/crates/ra_ssr/src/lib.rs b/crates/ra_ssr/src/lib.rs index 422e15ee6..cca4576ce 100644 --- a/crates/ra_ssr/src/lib.rs +++ b/crates/ra_ssr/src/lib.rs | |||
@@ -6,9 +6,12 @@ | |||
6 | mod matching; | 6 | mod matching; |
7 | mod parsing; | 7 | mod parsing; |
8 | mod replacing; | 8 | mod replacing; |
9 | #[macro_use] | ||
10 | mod errors; | ||
9 | #[cfg(test)] | 11 | #[cfg(test)] |
10 | mod tests; | 12 | mod tests; |
11 | 13 | ||
14 | pub use crate::errors::SsrError; | ||
12 | pub use crate::matching::Match; | 15 | pub use crate::matching::Match; |
13 | use crate::matching::{record_match_fails_reasons_scope, MatchFailureReason}; | 16 | use crate::matching::{record_match_fails_reasons_scope, MatchFailureReason}; |
14 | use hir::Semantics; | 17 | use hir::Semantics; |
@@ -41,9 +44,6 @@ pub struct SsrPattern { | |||
41 | pattern: Option<SyntaxNode>, | 44 | pattern: Option<SyntaxNode>, |
42 | } | 45 | } |
43 | 46 | ||
44 | #[derive(Debug, PartialEq)] | ||
45 | pub struct SsrError(String); | ||
46 | |||
47 | #[derive(Debug, Default)] | 47 | #[derive(Debug, Default)] |
48 | pub struct SsrMatches { | 48 | pub struct SsrMatches { |
49 | pub matches: Vec<Match>, | 49 | pub matches: Vec<Match>, |
@@ -201,9 +201,8 @@ impl<'db> MatchFinder<'db> { | |||
201 | ); | 201 | ); |
202 | } | 202 | } |
203 | } | 203 | } |
204 | } else { | ||
205 | self.output_debug_for_nodes_at_range(&node, range, restrict_range, out); | ||
206 | } | 204 | } |
205 | self.output_debug_for_nodes_at_range(&node, range, restrict_range, out); | ||
207 | } | 206 | } |
208 | } | 207 | } |
209 | } | 208 | } |
@@ -216,33 +215,28 @@ pub struct MatchDebugInfo { | |||
216 | matched: Result<Match, MatchFailureReason>, | 215 | matched: Result<Match, MatchFailureReason>, |
217 | } | 216 | } |
218 | 217 | ||
219 | impl std::fmt::Display for SsrError { | ||
220 | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { | ||
221 | write!(f, "Parse error: {}", self.0) | ||
222 | } | ||
223 | } | ||
224 | |||
225 | impl std::fmt::Debug for MatchDebugInfo { | 218 | impl std::fmt::Debug for MatchDebugInfo { |
226 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | 219 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { |
227 | write!(f, "========= PATTERN ==========\n")?; | 220 | match &self.matched { |
221 | Ok(_) => writeln!(f, "Node matched")?, | ||
222 | Err(reason) => writeln!(f, "Node failed to match because: {}", reason.reason)?, | ||
223 | } | ||
224 | writeln!( | ||
225 | f, | ||
226 | "============ AST ===========\n\ | ||
227 | {:#?}", | ||
228 | self.node | ||
229 | )?; | ||
230 | writeln!(f, "========= PATTERN ==========")?; | ||
228 | match &self.pattern { | 231 | match &self.pattern { |
229 | Ok(pattern) => { | 232 | Ok(pattern) => { |
230 | write!(f, "{:#?}", pattern)?; | 233 | writeln!(f, "{:#?}", pattern)?; |
231 | } | 234 | } |
232 | Err(err) => { | 235 | Err(err) => { |
233 | write!(f, "{}", err.reason)?; | 236 | writeln!(f, "{}", err.reason)?; |
234 | } | 237 | } |
235 | } | 238 | } |
236 | write!( | 239 | writeln!(f, "============================")?; |
237 | f, | ||
238 | "\n============ AST ===========\n\ | ||
239 | {:#?}\n============================\n", | ||
240 | self.node | ||
241 | )?; | ||
242 | match &self.matched { | ||
243 | Ok(_) => write!(f, "Node matched")?, | ||
244 | Err(reason) => write!(f, "Node failed to match because: {}", reason.reason)?, | ||
245 | } | ||
246 | Ok(()) | 240 | Ok(()) |
247 | } | 241 | } |
248 | } | 242 | } |
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( | |||
92 | sema: &Semantics<ra_ide_db::RootDatabase>, | 92 | sema: &Semantics<ra_ide_db::RootDatabase>, |
93 | ) -> Result<Match, MatchFailed> { | 93 | ) -> Result<Match, MatchFailed> { |
94 | record_match_fails_reasons_scope(debug_active, || { | 94 | record_match_fails_reasons_scope(debug_active, || { |
95 | MatchState::try_match(rule, code, restrict_range, sema) | 95 | Matcher::try_match(rule, code, restrict_range, sema) |
96 | }) | 96 | }) |
97 | } | 97 | } |
98 | 98 | ||
99 | /// Inputs to matching. This cannot be part of `MatchState`, since we mutate `MatchState` and in at | 99 | /// Checks if our search pattern matches a particular node of the AST. |
100 | /// least one case need to hold a borrow of a placeholder from the input pattern while calling a | 100 | struct Matcher<'db, 'sema> { |
101 | /// mutable `MatchState` method. | ||
102 | struct MatchInputs<'pattern> { | ||
103 | ssr_pattern: &'pattern SsrPattern, | ||
104 | } | ||
105 | |||
106 | /// State used while attempting to match our search pattern against a particular node of the AST. | ||
107 | struct MatchState<'db, 'sema> { | ||
108 | sema: &'sema Semantics<'db, ra_ide_db::RootDatabase>, | 101 | sema: &'sema Semantics<'db, ra_ide_db::RootDatabase>, |
109 | /// If any placeholders come from anywhere outside of this range, then the match will be | 102 | /// If any placeholders come from anywhere outside of this range, then the match will be |
110 | /// rejected. | 103 | /// rejected. |
111 | restrict_range: Option<FileRange>, | 104 | restrict_range: Option<FileRange>, |
112 | /// The match that we're building. We do two passes for a successful match. On the first pass, | 105 | rule: &'sema SsrRule, |
113 | /// this is None so that we can avoid doing things like storing copies of what placeholders | 106 | } |
114 | /// matched to. If that pass succeeds, then we do a second pass where we collect those details. | 107 | |
115 | /// This means that if we have a pattern like `$a.foo()` we won't do an insert into the | 108 | /// Which phase of matching we're currently performing. We do two phases because most attempted |
116 | /// placeholders map for every single method call in the codebase. Instead we'll discard all the | 109 | /// matches will fail and it means we can defer more expensive checks to the second phase. |
117 | /// method calls that aren't calls to `foo` on the first pass and only insert into the | 110 | enum Phase<'a> { |
118 | /// placeholders map on the second pass. Likewise for ignored comments. | 111 | /// On the first phase, we perform cheap checks. No state is mutated and nothing is recorded. |
119 | match_out: Option<Match>, | 112 | First, |
113 | /// On the second phase, we construct the `Match`. Things like what placeholders bind to is | ||
114 | /// recorded. | ||
115 | Second(&'a mut Match), | ||
120 | } | 116 | } |
121 | 117 | ||
122 | impl<'db, 'sema> MatchState<'db, 'sema> { | 118 | impl<'db, 'sema> Matcher<'db, 'sema> { |
123 | fn try_match( | 119 | fn try_match( |
124 | rule: &SsrRule, | 120 | rule: &'sema SsrRule, |
125 | code: &SyntaxNode, | 121 | code: &SyntaxNode, |
126 | restrict_range: &Option<FileRange>, | 122 | restrict_range: &Option<FileRange>, |
127 | sema: &'sema Semantics<'db, ra_ide_db::RootDatabase>, | 123 | sema: &'sema Semantics<'db, ra_ide_db::RootDatabase>, |
128 | ) -> Result<Match, MatchFailed> { | 124 | ) -> Result<Match, MatchFailed> { |
129 | let mut match_state = | 125 | let match_state = Matcher { sema, restrict_range: restrict_range.clone(), rule }; |
130 | MatchState { sema, restrict_range: restrict_range.clone(), match_out: None }; | ||
131 | let match_inputs = MatchInputs { ssr_pattern: &rule.pattern }; | ||
132 | let pattern_tree = rule.pattern.tree_for_kind(code.kind())?; | 126 | let pattern_tree = rule.pattern.tree_for_kind(code.kind())?; |
133 | // First pass at matching, where we check that node types and idents match. | 127 | // First pass at matching, where we check that node types and idents match. |
134 | match_state.attempt_match_node(&match_inputs, &pattern_tree, code)?; | 128 | match_state.attempt_match_node(&mut Phase::First, &pattern_tree, code)?; |
135 | match_state.validate_range(&sema.original_range(code))?; | 129 | match_state.validate_range(&sema.original_range(code))?; |
136 | match_state.match_out = Some(Match { | 130 | let mut the_match = Match { |
137 | range: sema.original_range(code), | 131 | range: sema.original_range(code), |
138 | matched_node: code.clone(), | 132 | matched_node: code.clone(), |
139 | placeholder_values: FxHashMap::default(), | 133 | placeholder_values: FxHashMap::default(), |
140 | ignored_comments: Vec::new(), | 134 | ignored_comments: Vec::new(), |
141 | template: rule.template.clone(), | 135 | template: rule.template.clone(), |
142 | }); | 136 | }; |
143 | // Second matching pass, where we record placeholder matches, ignored comments and maybe do | 137 | // Second matching pass, where we record placeholder matches, ignored comments and maybe do |
144 | // any other more expensive checks that we didn't want to do on the first pass. | 138 | // any other more expensive checks that we didn't want to do on the first pass. |
145 | match_state.attempt_match_node(&match_inputs, &pattern_tree, code)?; | 139 | match_state.attempt_match_node(&mut Phase::Second(&mut the_match), &pattern_tree, code)?; |
146 | Ok(match_state.match_out.unwrap()) | 140 | Ok(the_match) |
147 | } | 141 | } |
148 | 142 | ||
149 | /// Checks that `range` is within the permitted range if any. This is applicable when we're | 143 | /// 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> { | |||
161 | } | 155 | } |
162 | 156 | ||
163 | fn attempt_match_node( | 157 | fn attempt_match_node( |
164 | &mut self, | 158 | &self, |
165 | match_inputs: &MatchInputs, | 159 | phase: &mut Phase, |
166 | pattern: &SyntaxNode, | 160 | pattern: &SyntaxNode, |
167 | code: &SyntaxNode, | 161 | code: &SyntaxNode, |
168 | ) -> Result<(), MatchFailed> { | 162 | ) -> Result<(), MatchFailed> { |
169 | // Handle placeholders. | 163 | // Handle placeholders. |
170 | if let Some(placeholder) = | 164 | if let Some(placeholder) = self.get_placeholder(&SyntaxElement::Node(pattern.clone())) { |
171 | match_inputs.get_placeholder(&SyntaxElement::Node(pattern.clone())) | ||
172 | { | ||
173 | for constraint in &placeholder.constraints { | 165 | for constraint in &placeholder.constraints { |
174 | self.check_constraint(constraint, code)?; | 166 | self.check_constraint(constraint, code)?; |
175 | } | 167 | } |
176 | if self.match_out.is_none() { | 168 | if let Phase::Second(matches_out) = phase { |
177 | return Ok(()); | 169 | let original_range = self.sema.original_range(code); |
178 | } | 170 | // We validated the range for the node when we started the match, so the placeholder |
179 | let original_range = self.sema.original_range(code); | 171 | // probably can't fail range validation, but just to be safe... |
180 | // We validated the range for the node when we started the match, so the placeholder | 172 | self.validate_range(&original_range)?; |
181 | // probably can't fail range validation, but just to be safe... | 173 | matches_out.placeholder_values.insert( |
182 | self.validate_range(&original_range)?; | ||
183 | if let Some(match_out) = &mut self.match_out { | ||
184 | match_out.placeholder_values.insert( | ||
185 | Var(placeholder.ident.to_string()), | 174 | Var(placeholder.ident.to_string()), |
186 | PlaceholderMatch::new(code, original_range), | 175 | PlaceholderMatch::new(code, original_range), |
187 | ); | 176 | ); |
@@ -190,41 +179,47 @@ impl<'db, 'sema> MatchState<'db, 'sema> { | |||
190 | } | 179 | } |
191 | // Non-placeholders. | 180 | // Non-placeholders. |
192 | if pattern.kind() != code.kind() { | 181 | if pattern.kind() != code.kind() { |
193 | fail_match!("Pattern had a {:?}, code had {:?}", pattern.kind(), code.kind()); | 182 | fail_match!( |
183 | "Pattern had a `{}` ({:?}), code had `{}` ({:?})", | ||
184 | pattern.text(), | ||
185 | pattern.kind(), | ||
186 | code.text(), | ||
187 | code.kind() | ||
188 | ); | ||
194 | } | 189 | } |
195 | // Some kinds of nodes have special handling. For everything else, we fall back to default | 190 | // Some kinds of nodes have special handling. For everything else, we fall back to default |
196 | // matching. | 191 | // matching. |
197 | match code.kind() { | 192 | match code.kind() { |
198 | SyntaxKind::RECORD_FIELD_LIST => { | 193 | SyntaxKind::RECORD_FIELD_LIST => { |
199 | self.attempt_match_record_field_list(match_inputs, pattern, code) | 194 | self.attempt_match_record_field_list(phase, pattern, code) |
200 | } | 195 | } |
201 | SyntaxKind::TOKEN_TREE => self.attempt_match_token_tree(match_inputs, pattern, code), | 196 | SyntaxKind::TOKEN_TREE => self.attempt_match_token_tree(phase, pattern, code), |
202 | _ => self.attempt_match_node_children(match_inputs, pattern, code), | 197 | _ => self.attempt_match_node_children(phase, pattern, code), |
203 | } | 198 | } |
204 | } | 199 | } |
205 | 200 | ||
206 | fn attempt_match_node_children( | 201 | fn attempt_match_node_children( |
207 | &mut self, | 202 | &self, |
208 | match_inputs: &MatchInputs, | 203 | phase: &mut Phase, |
209 | pattern: &SyntaxNode, | 204 | pattern: &SyntaxNode, |
210 | code: &SyntaxNode, | 205 | code: &SyntaxNode, |
211 | ) -> Result<(), MatchFailed> { | 206 | ) -> Result<(), MatchFailed> { |
212 | self.attempt_match_sequences( | 207 | self.attempt_match_sequences( |
213 | match_inputs, | 208 | phase, |
214 | PatternIterator::new(pattern), | 209 | PatternIterator::new(pattern), |
215 | code.children_with_tokens(), | 210 | code.children_with_tokens(), |
216 | ) | 211 | ) |
217 | } | 212 | } |
218 | 213 | ||
219 | fn attempt_match_sequences( | 214 | fn attempt_match_sequences( |
220 | &mut self, | 215 | &self, |
221 | match_inputs: &MatchInputs, | 216 | phase: &mut Phase, |
222 | pattern_it: PatternIterator, | 217 | pattern_it: PatternIterator, |
223 | mut code_it: SyntaxElementChildren, | 218 | mut code_it: SyntaxElementChildren, |
224 | ) -> Result<(), MatchFailed> { | 219 | ) -> Result<(), MatchFailed> { |
225 | let mut pattern_it = pattern_it.peekable(); | 220 | let mut pattern_it = pattern_it.peekable(); |
226 | loop { | 221 | loop { |
227 | match self.next_non_trivial(&mut code_it) { | 222 | match phase.next_non_trivial(&mut code_it) { |
228 | None => { | 223 | None => { |
229 | if let Some(p) = pattern_it.next() { | 224 | if let Some(p) = pattern_it.next() { |
230 | fail_match!("Part of the pattern was unmatched: {:?}", p); | 225 | fail_match!("Part of the pattern was unmatched: {:?}", p); |
@@ -232,11 +227,11 @@ impl<'db, 'sema> MatchState<'db, 'sema> { | |||
232 | return Ok(()); | 227 | return Ok(()); |
233 | } | 228 | } |
234 | Some(SyntaxElement::Token(c)) => { | 229 | Some(SyntaxElement::Token(c)) => { |
235 | self.attempt_match_token(&mut pattern_it, &c)?; | 230 | self.attempt_match_token(phase, &mut pattern_it, &c)?; |
236 | } | 231 | } |
237 | Some(SyntaxElement::Node(c)) => match pattern_it.next() { | 232 | Some(SyntaxElement::Node(c)) => match pattern_it.next() { |
238 | Some(SyntaxElement::Node(p)) => { | 233 | Some(SyntaxElement::Node(p)) => { |
239 | self.attempt_match_node(match_inputs, &p, &c)?; | 234 | self.attempt_match_node(phase, &p, &c)?; |
240 | } | 235 | } |
241 | Some(p) => fail_match!("Pattern wanted '{}', code has {}", p, c.text()), | 236 | Some(p) => fail_match!("Pattern wanted '{}', code has {}", p, c.text()), |
242 | None => fail_match!("Pattern reached end, code has {}", c.text()), | 237 | None => fail_match!("Pattern reached end, code has {}", c.text()), |
@@ -246,11 +241,12 @@ impl<'db, 'sema> MatchState<'db, 'sema> { | |||
246 | } | 241 | } |
247 | 242 | ||
248 | fn attempt_match_token( | 243 | fn attempt_match_token( |
249 | &mut self, | 244 | &self, |
245 | phase: &mut Phase, | ||
250 | pattern: &mut Peekable<PatternIterator>, | 246 | pattern: &mut Peekable<PatternIterator>, |
251 | code: &ra_syntax::SyntaxToken, | 247 | code: &ra_syntax::SyntaxToken, |
252 | ) -> Result<(), MatchFailed> { | 248 | ) -> Result<(), MatchFailed> { |
253 | self.record_ignored_comments(code); | 249 | phase.record_ignored_comments(code); |
254 | // Ignore whitespace and comments. | 250 | // Ignore whitespace and comments. |
255 | if code.kind().is_trivia() { | 251 | if code.kind().is_trivia() { |
256 | return Ok(()); | 252 | return Ok(()); |
@@ -317,8 +313,8 @@ impl<'db, 'sema> MatchState<'db, 'sema> { | |||
317 | /// We want to allow the records to match in any order, so we have special matching logic for | 313 | /// We want to allow the records to match in any order, so we have special matching logic for |
318 | /// them. | 314 | /// them. |
319 | fn attempt_match_record_field_list( | 315 | fn attempt_match_record_field_list( |
320 | &mut self, | 316 | &self, |
321 | match_inputs: &MatchInputs, | 317 | phase: &mut Phase, |
322 | pattern: &SyntaxNode, | 318 | pattern: &SyntaxNode, |
323 | code: &SyntaxNode, | 319 | code: &SyntaxNode, |
324 | ) -> Result<(), MatchFailed> { | 320 | ) -> Result<(), MatchFailed> { |
@@ -334,11 +330,11 @@ impl<'db, 'sema> MatchState<'db, 'sema> { | |||
334 | for p in pattern.children_with_tokens() { | 330 | for p in pattern.children_with_tokens() { |
335 | if let SyntaxElement::Node(p) = p { | 331 | if let SyntaxElement::Node(p) = p { |
336 | if let Some(name_element) = p.first_child_or_token() { | 332 | if let Some(name_element) = p.first_child_or_token() { |
337 | if match_inputs.get_placeholder(&name_element).is_some() { | 333 | if self.get_placeholder(&name_element).is_some() { |
338 | // If the pattern is using placeholders for field names then order | 334 | // If the pattern is using placeholders for field names then order |
339 | // independence doesn't make sense. Fall back to regular ordered | 335 | // independence doesn't make sense. Fall back to regular ordered |
340 | // matching. | 336 | // matching. |
341 | return self.attempt_match_node_children(match_inputs, pattern, code); | 337 | return self.attempt_match_node_children(phase, pattern, code); |
342 | } | 338 | } |
343 | if let Some(ident) = only_ident(name_element) { | 339 | if let Some(ident) = only_ident(name_element) { |
344 | let code_record = fields_by_name.remove(ident.text()).ok_or_else(|| { | 340 | let code_record = fields_by_name.remove(ident.text()).ok_or_else(|| { |
@@ -347,7 +343,7 @@ impl<'db, 'sema> MatchState<'db, 'sema> { | |||
347 | ident | 343 | ident |
348 | ) | 344 | ) |
349 | })?; | 345 | })?; |
350 | self.attempt_match_node(match_inputs, &p, &code_record)?; | 346 | self.attempt_match_node(phase, &p, &code_record)?; |
351 | } | 347 | } |
352 | } | 348 | } |
353 | } | 349 | } |
@@ -367,16 +363,15 @@ impl<'db, 'sema> MatchState<'db, 'sema> { | |||
367 | /// pattern matches the macro invocation. For matches within the macro call, we'll already have | 363 | /// pattern matches the macro invocation. For matches within the macro call, we'll already have |
368 | /// expanded the macro. | 364 | /// expanded the macro. |
369 | fn attempt_match_token_tree( | 365 | fn attempt_match_token_tree( |
370 | &mut self, | 366 | &self, |
371 | match_inputs: &MatchInputs, | 367 | phase: &mut Phase, |
372 | pattern: &SyntaxNode, | 368 | pattern: &SyntaxNode, |
373 | code: &ra_syntax::SyntaxNode, | 369 | code: &ra_syntax::SyntaxNode, |
374 | ) -> Result<(), MatchFailed> { | 370 | ) -> Result<(), MatchFailed> { |
375 | let mut pattern = PatternIterator::new(pattern).peekable(); | 371 | let mut pattern = PatternIterator::new(pattern).peekable(); |
376 | let mut children = code.children_with_tokens(); | 372 | let mut children = code.children_with_tokens(); |
377 | while let Some(child) = children.next() { | 373 | while let Some(child) = children.next() { |
378 | if let Some(placeholder) = pattern.peek().and_then(|p| match_inputs.get_placeholder(p)) | 374 | if let Some(placeholder) = pattern.peek().and_then(|p| self.get_placeholder(p)) { |
379 | { | ||
380 | pattern.next(); | 375 | pattern.next(); |
381 | let next_pattern_token = pattern | 376 | let next_pattern_token = pattern |
382 | .peek() | 377 | .peek() |
@@ -402,7 +397,7 @@ impl<'db, 'sema> MatchState<'db, 'sema> { | |||
402 | if Some(first_token.to_string()) == next_pattern_token { | 397 | if Some(first_token.to_string()) == next_pattern_token { |
403 | if let Some(SyntaxElement::Node(p)) = pattern.next() { | 398 | if let Some(SyntaxElement::Node(p)) = pattern.next() { |
404 | // We have a subtree that starts with the next token in our pattern. | 399 | // We have a subtree that starts with the next token in our pattern. |
405 | self.attempt_match_token_tree(match_inputs, &p, &n)?; | 400 | self.attempt_match_token_tree(phase, &p, &n)?; |
406 | break; | 401 | break; |
407 | } | 402 | } |
408 | } | 403 | } |
@@ -411,7 +406,7 @@ impl<'db, 'sema> MatchState<'db, 'sema> { | |||
411 | }; | 406 | }; |
412 | last_matched_token = next; | 407 | last_matched_token = next; |
413 | } | 408 | } |
414 | if let Some(match_out) = &mut self.match_out { | 409 | if let Phase::Second(match_out) = phase { |
415 | match_out.placeholder_values.insert( | 410 | match_out.placeholder_values.insert( |
416 | Var(placeholder.ident.to_string()), | 411 | Var(placeholder.ident.to_string()), |
417 | PlaceholderMatch::from_range(FileRange { | 412 | PlaceholderMatch::from_range(FileRange { |
@@ -427,11 +422,11 @@ impl<'db, 'sema> MatchState<'db, 'sema> { | |||
427 | // Match literal (non-placeholder) tokens. | 422 | // Match literal (non-placeholder) tokens. |
428 | match child { | 423 | match child { |
429 | SyntaxElement::Token(token) => { | 424 | SyntaxElement::Token(token) => { |
430 | self.attempt_match_token(&mut pattern, &token)?; | 425 | self.attempt_match_token(phase, &mut pattern, &token)?; |
431 | } | 426 | } |
432 | SyntaxElement::Node(node) => match pattern.next() { | 427 | SyntaxElement::Node(node) => match pattern.next() { |
433 | Some(SyntaxElement::Node(p)) => { | 428 | Some(SyntaxElement::Node(p)) => { |
434 | self.attempt_match_token_tree(match_inputs, &p, &node)?; | 429 | self.attempt_match_token_tree(phase, &p, &node)?; |
435 | } | 430 | } |
436 | Some(SyntaxElement::Token(p)) => fail_match!( | 431 | Some(SyntaxElement::Token(p)) => fail_match!( |
437 | "Pattern has token '{}', code has subtree '{}'", | 432 | "Pattern has token '{}', code has subtree '{}'", |
@@ -448,6 +443,13 @@ impl<'db, 'sema> MatchState<'db, 'sema> { | |||
448 | Ok(()) | 443 | Ok(()) |
449 | } | 444 | } |
450 | 445 | ||
446 | fn get_placeholder(&self, element: &SyntaxElement) -> Option<&Placeholder> { | ||
447 | only_ident(element.clone()) | ||
448 | .and_then(|ident| self.rule.pattern.placeholders_by_stand_in.get(ident.text())) | ||
449 | } | ||
450 | } | ||
451 | |||
452 | impl Phase<'_> { | ||
451 | fn next_non_trivial(&mut self, code_it: &mut SyntaxElementChildren) -> Option<SyntaxElement> { | 453 | fn next_non_trivial(&mut self, code_it: &mut SyntaxElementChildren) -> Option<SyntaxElement> { |
452 | loop { | 454 | loop { |
453 | let c = code_it.next(); | 455 | let c = code_it.next(); |
@@ -463,7 +465,7 @@ impl<'db, 'sema> MatchState<'db, 'sema> { | |||
463 | 465 | ||
464 | fn record_ignored_comments(&mut self, token: &SyntaxToken) { | 466 | fn record_ignored_comments(&mut self, token: &SyntaxToken) { |
465 | if token.kind() == SyntaxKind::COMMENT { | 467 | if token.kind() == SyntaxKind::COMMENT { |
466 | if let Some(match_out) = &mut self.match_out { | 468 | if let Phase::Second(match_out) = self { |
467 | if let Some(comment) = ast::Comment::cast(token.clone()) { | 469 | if let Some(comment) = ast::Comment::cast(token.clone()) { |
468 | match_out.ignored_comments.push(comment); | 470 | match_out.ignored_comments.push(comment); |
469 | } | 471 | } |
@@ -472,13 +474,6 @@ impl<'db, 'sema> MatchState<'db, 'sema> { | |||
472 | } | 474 | } |
473 | } | 475 | } |
474 | 476 | ||
475 | impl MatchInputs<'_> { | ||
476 | fn get_placeholder(&self, element: &SyntaxElement) -> Option<&Placeholder> { | ||
477 | only_ident(element.clone()) | ||
478 | .and_then(|ident| self.ssr_pattern.placeholders_by_stand_in.get(ident.text())) | ||
479 | } | ||
480 | } | ||
481 | |||
482 | fn is_closing_token(kind: SyntaxKind) -> bool { | 477 | fn is_closing_token(kind: SyntaxKind) -> bool { |
483 | kind == SyntaxKind::R_PAREN || kind == SyntaxKind::R_CURLY || kind == SyntaxKind::R_BRACK | 478 | kind == SyntaxKind::R_PAREN || kind == SyntaxKind::R_CURLY || kind == SyntaxKind::R_BRACK |
484 | } | 479 | } |
@@ -596,12 +591,12 @@ impl PatternIterator { | |||
596 | #[cfg(test)] | 591 | #[cfg(test)] |
597 | mod tests { | 592 | mod tests { |
598 | use super::*; | 593 | use super::*; |
599 | use crate::MatchFinder; | 594 | use crate::{MatchFinder, SsrRule}; |
600 | 595 | ||
601 | #[test] | 596 | #[test] |
602 | fn parse_match_replace() { | 597 | fn parse_match_replace() { |
603 | let rule: SsrRule = "foo($x) ==>> bar($x)".parse().unwrap(); | 598 | let rule: SsrRule = "foo($x) ==>> bar($x)".parse().unwrap(); |
604 | let input = "fn main() { foo(1+2); }"; | 599 | let input = "fn foo() {} fn main() { foo(1+2); }"; |
605 | 600 | ||
606 | use ra_db::fixture::WithFixture; | 601 | use ra_db::fixture::WithFixture; |
607 | let (db, file_id) = ra_ide_db::RootDatabase::with_single_file(input); | 602 | let (db, file_id) = ra_ide_db::RootDatabase::with_single_file(input); |
@@ -623,6 +618,6 @@ mod tests { | |||
623 | let edit = crate::replacing::matches_to_edit(&matches, input); | 618 | let edit = crate::replacing::matches_to_edit(&matches, input); |
624 | let mut after = input.to_string(); | 619 | let mut after = input.to_string(); |
625 | edit.apply(&mut after); | 620 | edit.apply(&mut after); |
626 | assert_eq!(after, "fn main() { bar(1+2); }"); | 621 | assert_eq!(after, "fn foo() {} fn main() { bar(1+2); }"); |
627 | } | 622 | } |
628 | } | 623 | } |
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 @@ | |||
5 | //! search patterns, we go further and parse the pattern as each kind of thing that we can match. | 5 | //! search patterns, we go further and parse the pattern as each kind of thing that we can match. |
6 | //! e.g. expressions, type references etc. | 6 | //! e.g. expressions, type references etc. |
7 | 7 | ||
8 | use crate::errors::bail; | ||
8 | use crate::{SsrError, SsrPattern, SsrRule}; | 9 | use crate::{SsrError, SsrPattern, SsrRule}; |
9 | use ra_syntax::{ast, AstNode, SmolStr, SyntaxKind, T}; | 10 | use ra_syntax::{ast, AstNode, SmolStr, SyntaxKind, T}; |
10 | use rustc_hash::{FxHashMap, FxHashSet}; | 11 | use rustc_hash::{FxHashMap, FxHashSet}; |
11 | use std::str::FromStr; | 12 | use std::str::FromStr; |
12 | 13 | ||
13 | /// Returns from the current function with an error, supplied by arguments as for format! | ||
14 | macro_rules! bail { | ||
15 | ($e:expr) => {return Err($crate::SsrError::new($e))}; | ||
16 | ($fmt:expr, $($arg:tt)+) => {return Err($crate::SsrError::new(format!($fmt, $($arg)+)))} | ||
17 | } | ||
18 | |||
19 | #[derive(Clone, Debug)] | 14 | #[derive(Clone, Debug)] |
20 | pub(crate) struct SsrTemplate { | 15 | pub(crate) struct SsrTemplate { |
21 | pub(crate) tokens: Vec<PatternElement>, | 16 | pub(crate) tokens: Vec<PatternElement>, |
@@ -246,7 +241,7 @@ fn parse_placeholder(tokens: &mut std::vec::IntoIter<Token>) -> Result<Placehold | |||
246 | } | 241 | } |
247 | } | 242 | } |
248 | _ => { | 243 | _ => { |
249 | bail!("Placeholders should either be $name or ${name:constraints}"); | 244 | bail!("Placeholders should either be $name or ${{name:constraints}}"); |
250 | } | 245 | } |
251 | } | 246 | } |
252 | } | 247 | } |
@@ -289,7 +284,7 @@ fn expect_token(tokens: &mut std::vec::IntoIter<Token>, expected: &str) -> Resul | |||
289 | } | 284 | } |
290 | bail!("Expected {} found {}", expected, t.text); | 285 | bail!("Expected {} found {}", expected, t.text); |
291 | } | 286 | } |
292 | bail!("Expected {} found end of stream"); | 287 | bail!("Expected {} found end of stream", expected); |
293 | } | 288 | } |
294 | 289 | ||
295 | impl NodeKind { | 290 | impl NodeKind { |
@@ -307,12 +302,6 @@ impl Placeholder { | |||
307 | } | 302 | } |
308 | } | 303 | } |
309 | 304 | ||
310 | impl SsrError { | ||
311 | fn new(message: impl Into<String>) -> SsrError { | ||
312 | SsrError(message.into()) | ||
313 | } | ||
314 | } | ||
315 | |||
316 | #[cfg(test)] | 305 | #[cfg(test)] |
317 | mod tests { | 306 | mod tests { |
318 | use super::*; | 307 | use super::*; |
diff --git a/crates/ra_ssr/src/tests.rs b/crates/ra_ssr/src/tests.rs index 9568d4432..f20ae2cdf 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) { | |||
91 | } | 91 | } |
92 | } | 92 | } |
93 | 93 | ||
94 | fn print_match_debug_info(match_finder: &MatchFinder, file_id: FileId, snippet: &str) { | ||
95 | let debug_info = match_finder.debug_where_text_equal(file_id, snippet); | ||
96 | println!( | ||
97 | "Match debug info: {} nodes had text exactly equal to '{}'", | ||
98 | debug_info.len(), | ||
99 | snippet | ||
100 | ); | ||
101 | for (index, d) in debug_info.iter().enumerate() { | ||
102 | println!("Node #{}\n{:#?}\n", index, d); | ||
103 | } | ||
104 | } | ||
105 | |||
94 | fn assert_matches(pattern: &str, code: &str, expected: &[&str]) { | 106 | fn assert_matches(pattern: &str, code: &str, expected: &[&str]) { |
95 | let (db, file_id) = single_file(code); | 107 | let (db, file_id) = single_file(code); |
96 | let mut match_finder = MatchFinder::new(&db); | 108 | let mut match_finder = MatchFinder::new(&db); |
@@ -103,17 +115,20 @@ fn assert_matches(pattern: &str, code: &str, expected: &[&str]) { | |||
103 | .map(|m| m.matched_text()) | 115 | .map(|m| m.matched_text()) |
104 | .collect(); | 116 | .collect(); |
105 | if matched_strings != expected && !expected.is_empty() { | 117 | if matched_strings != expected && !expected.is_empty() { |
106 | let debug_info = match_finder.debug_where_text_equal(file_id, &expected[0]); | 118 | print_match_debug_info(&match_finder, file_id, &expected[0]); |
107 | eprintln!("Test is about to fail. Some possibly useful info: {} nodes had text exactly equal to '{}'", debug_info.len(), &expected[0]); | ||
108 | for d in debug_info { | ||
109 | eprintln!("{:#?}", d); | ||
110 | } | ||
111 | } | 119 | } |
112 | assert_eq!(matched_strings, expected); | 120 | assert_eq!(matched_strings, expected); |
113 | } | 121 | } |
114 | 122 | ||
115 | fn assert_no_match(pattern: &str, code: &str) { | 123 | fn assert_no_match(pattern: &str, code: &str) { |
116 | assert_matches(pattern, code, &[]); | 124 | let (db, file_id) = single_file(code); |
125 | let mut match_finder = MatchFinder::new(&db); | ||
126 | match_finder.add_search_pattern(pattern.parse().unwrap()); | ||
127 | let matches = match_finder.find_matches_in_file(file_id).flattened().matches; | ||
128 | if !matches.is_empty() { | ||
129 | print_match_debug_info(&match_finder, file_id, &matches[0].matched_text()); | ||
130 | panic!("Got {} matches when we expected none: {:#?}", matches.len(), matches); | ||
131 | } | ||
117 | } | 132 | } |
118 | 133 | ||
119 | fn assert_match_failure_reason(pattern: &str, code: &str, snippet: &str, expected_reason: &str) { | 134 | 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 | |||
133 | fn ssr_function_to_method() { | 148 | fn ssr_function_to_method() { |
134 | assert_ssr_transform( | 149 | assert_ssr_transform( |
135 | "my_function($a, $b) ==>> ($a).my_method($b)", | 150 | "my_function($a, $b) ==>> ($a).my_method($b)", |
136 | "loop { my_function( other_func(x, y), z + w) }", | 151 | "fn my_function() {} fn main() { loop { my_function( other_func(x, y), z + w) } }", |
137 | "loop { (other_func(x, y)).my_method(z + w) }", | 152 | "fn my_function() {} fn main() { loop { (other_func(x, y)).my_method(z + w) } }", |
138 | ) | 153 | ) |
139 | } | 154 | } |
140 | 155 | ||
@@ -142,8 +157,8 @@ fn ssr_function_to_method() { | |||
142 | fn ssr_nested_function() { | 157 | fn ssr_nested_function() { |
143 | assert_ssr_transform( | 158 | assert_ssr_transform( |
144 | "foo($a, $b, $c) ==>> bar($c, baz($a, $b))", | 159 | "foo($a, $b, $c) ==>> bar($c, baz($a, $b))", |
145 | "fn main { foo (x + value.method(b), x+y-z, true && false) }", | 160 | "fn foo() {} fn main { foo (x + value.method(b), x+y-z, true && false) }", |
146 | "fn main { bar(true && false, baz(x + value.method(b), x+y-z)) }", | 161 | "fn foo() {} fn main { bar(true && false, baz(x + value.method(b), x+y-z)) }", |
147 | ) | 162 | ) |
148 | } | 163 | } |
149 | 164 | ||
@@ -151,8 +166,8 @@ fn ssr_nested_function() { | |||
151 | fn ssr_expected_spacing() { | 166 | fn ssr_expected_spacing() { |
152 | assert_ssr_transform( | 167 | assert_ssr_transform( |
153 | "foo($x) + bar() ==>> bar($x)", | 168 | "foo($x) + bar() ==>> bar($x)", |
154 | "fn main() { foo(5) + bar() }", | 169 | "fn foo() {} fn bar() {} fn main() { foo(5) + bar() }", |
155 | "fn main() { bar(5) }", | 170 | "fn foo() {} fn bar() {} fn main() { bar(5) }", |
156 | ); | 171 | ); |
157 | } | 172 | } |
158 | 173 | ||
@@ -160,8 +175,8 @@ fn ssr_expected_spacing() { | |||
160 | fn ssr_with_extra_space() { | 175 | fn ssr_with_extra_space() { |
161 | assert_ssr_transform( | 176 | assert_ssr_transform( |
162 | "foo($x ) + bar() ==>> bar($x)", | 177 | "foo($x ) + bar() ==>> bar($x)", |
163 | "fn main() { foo( 5 ) +bar( ) }", | 178 | "fn foo() {} fn bar() {} fn main() { foo( 5 ) +bar( ) }", |
164 | "fn main() { bar(5) }", | 179 | "fn foo() {} fn bar() {} fn main() { bar(5) }", |
165 | ); | 180 | ); |
166 | } | 181 | } |
167 | 182 | ||
@@ -169,8 +184,8 @@ fn ssr_with_extra_space() { | |||
169 | fn ssr_keeps_nested_comment() { | 184 | fn ssr_keeps_nested_comment() { |
170 | assert_ssr_transform( | 185 | assert_ssr_transform( |
171 | "foo($x) ==>> bar($x)", | 186 | "foo($x) ==>> bar($x)", |
172 | "fn main() { foo(other(5 /* using 5 */)) }", | 187 | "fn foo() {} fn main() { foo(other(5 /* using 5 */)) }", |
173 | "fn main() { bar(other(5 /* using 5 */)) }", | 188 | "fn foo() {} fn main() { bar(other(5 /* using 5 */)) }", |
174 | ) | 189 | ) |
175 | } | 190 | } |
176 | 191 | ||
@@ -178,8 +193,8 @@ fn ssr_keeps_nested_comment() { | |||
178 | fn ssr_keeps_comment() { | 193 | fn ssr_keeps_comment() { |
179 | assert_ssr_transform( | 194 | assert_ssr_transform( |
180 | "foo($x) ==>> bar($x)", | 195 | "foo($x) ==>> bar($x)", |
181 | "fn main() { foo(5 /* using 5 */) }", | 196 | "fn foo() {} fn main() { foo(5 /* using 5 */) }", |
182 | "fn main() { bar(5)/* using 5 */ }", | 197 | "fn foo() {} fn main() { bar(5)/* using 5 */ }", |
183 | ) | 198 | ) |
184 | } | 199 | } |
185 | 200 | ||
@@ -187,8 +202,8 @@ fn ssr_keeps_comment() { | |||
187 | fn ssr_struct_lit() { | 202 | fn ssr_struct_lit() { |
188 | assert_ssr_transform( | 203 | assert_ssr_transform( |
189 | "foo{a: $a, b: $b} ==>> foo::new($a, $b)", | 204 | "foo{a: $a, b: $b} ==>> foo::new($a, $b)", |
190 | "fn main() { foo{b:2, a:1} }", | 205 | "fn foo() {} fn main() { foo{b:2, a:1} }", |
191 | "fn main() { foo::new(1, 2) }", | 206 | "fn foo() {} fn main() { foo::new(1, 2) }", |
192 | ) | 207 | ) |
193 | } | 208 | } |
194 | 209 | ||
@@ -210,16 +225,18 @@ fn match_fn_definition() { | |||
210 | 225 | ||
211 | #[test] | 226 | #[test] |
212 | fn match_struct_definition() { | 227 | fn match_struct_definition() { |
213 | assert_matches( | 228 | let code = r#" |
214 | "struct $n {$f: Option<String>}", | 229 | struct Option<T> {} |
215 | "struct Bar {} struct Foo {name: Option<String>}", | 230 | struct Bar {} |
216 | &["struct Foo {name: Option<String>}"], | 231 | struct Foo {name: Option<String>}"#; |
217 | ); | 232 | assert_matches("struct $n {$f: Option<String>}", code, &["struct Foo {name: Option<String>}"]); |
218 | } | 233 | } |
219 | 234 | ||
220 | #[test] | 235 | #[test] |
221 | fn match_expr() { | 236 | fn match_expr() { |
222 | let code = "fn f() -> i32 {foo(40 + 2, 42)}"; | 237 | let code = r#" |
238 | fn foo() {} | ||
239 | fn f() -> i32 {foo(40 + 2, 42)}"#; | ||
223 | assert_matches("foo($a, $b)", code, &["foo(40 + 2, 42)"]); | 240 | assert_matches("foo($a, $b)", code, &["foo(40 + 2, 42)"]); |
224 | assert_no_match("foo($a, $b, $c)", code); | 241 | assert_no_match("foo($a, $b, $c)", code); |
225 | assert_no_match("foo($a)", code); | 242 | assert_no_match("foo($a)", code); |
@@ -248,7 +265,9 @@ fn match_nested_method_calls_with_macro_call() { | |||
248 | 265 | ||
249 | #[test] | 266 | #[test] |
250 | fn match_complex_expr() { | 267 | fn match_complex_expr() { |
251 | let code = "fn f() -> i32 {foo(bar(40, 2), 42)}"; | 268 | let code = r#" |
269 | fn foo() {} fn bar() {} | ||
270 | fn f() -> i32 {foo(bar(40, 2), 42)}"#; | ||
252 | assert_matches("foo($a, $b)", code, &["foo(bar(40, 2), 42)"]); | 271 | assert_matches("foo($a, $b)", code, &["foo(bar(40, 2), 42)"]); |
253 | assert_no_match("foo($a, $b, $c)", code); | 272 | assert_no_match("foo($a, $b, $c)", code); |
254 | assert_no_match("foo($a)", code); | 273 | assert_no_match("foo($a)", code); |
@@ -259,53 +278,62 @@ fn match_complex_expr() { | |||
259 | #[test] | 278 | #[test] |
260 | fn match_with_trailing_commas() { | 279 | fn match_with_trailing_commas() { |
261 | // Code has comma, pattern doesn't. | 280 | // Code has comma, pattern doesn't. |
262 | assert_matches("foo($a, $b)", "fn f() {foo(1, 2,);}", &["foo(1, 2,)"]); | 281 | assert_matches("foo($a, $b)", "fn foo() {} fn f() {foo(1, 2,);}", &["foo(1, 2,)"]); |
263 | assert_matches("Foo{$a, $b}", "fn f() {Foo{1, 2,};}", &["Foo{1, 2,}"]); | 282 | assert_matches("Foo{$a, $b}", "struct Foo {} fn f() {Foo{1, 2,};}", &["Foo{1, 2,}"]); |
264 | 283 | ||
265 | // Pattern has comma, code doesn't. | 284 | // Pattern has comma, code doesn't. |
266 | assert_matches("foo($a, $b,)", "fn f() {foo(1, 2);}", &["foo(1, 2)"]); | 285 | assert_matches("foo($a, $b,)", "fn foo() {} fn f() {foo(1, 2);}", &["foo(1, 2)"]); |
267 | assert_matches("Foo{$a, $b,}", "fn f() {Foo{1, 2};}", &["Foo{1, 2}"]); | 286 | assert_matches("Foo{$a, $b,}", "struct Foo {} fn f() {Foo{1, 2};}", &["Foo{1, 2}"]); |
268 | } | 287 | } |
269 | 288 | ||
270 | #[test] | 289 | #[test] |
271 | fn match_type() { | 290 | fn match_type() { |
272 | assert_matches("i32", "fn f() -> i32 {1 + 2}", &["i32"]); | 291 | assert_matches("i32", "fn f() -> i32 {1 + 2}", &["i32"]); |
273 | assert_matches("Option<$a>", "fn f() -> Option<i32> {42}", &["Option<i32>"]); | 292 | assert_matches( |
274 | assert_no_match("Option<$a>", "fn f() -> Result<i32, ()> {42}"); | 293 | "Option<$a>", |
294 | "struct Option<T> {} fn f() -> Option<i32> {42}", | ||
295 | &["Option<i32>"], | ||
296 | ); | ||
297 | assert_no_match( | ||
298 | "Option<$a>", | ||
299 | "struct Option<T> {} struct Result<T, E> {} fn f() -> Result<i32, ()> {42}", | ||
300 | ); | ||
275 | } | 301 | } |
276 | 302 | ||
277 | #[test] | 303 | #[test] |
278 | fn match_struct_instantiation() { | 304 | fn match_struct_instantiation() { |
279 | assert_matches( | 305 | let code = r#" |
280 | "Foo {bar: 1, baz: 2}", | 306 | struct Foo {bar: i32, baz: i32} |
281 | "fn f() {Foo {bar: 1, baz: 2}}", | 307 | fn f() {Foo {bar: 1, baz: 2}}"#; |
282 | &["Foo {bar: 1, baz: 2}"], | 308 | assert_matches("Foo {bar: 1, baz: 2}", code, &["Foo {bar: 1, baz: 2}"]); |
283 | ); | ||
284 | // Now with placeholders for all parts of the struct. | 309 | // Now with placeholders for all parts of the struct. |
285 | assert_matches( | 310 | assert_matches("Foo {$a: $b, $c: $d}", code, &["Foo {bar: 1, baz: 2}"]); |
286 | "Foo {$a: $b, $c: $d}", | 311 | assert_matches("Foo {}", "struct Foo {} fn f() {Foo {}}", &["Foo {}"]); |
287 | "fn f() {Foo {bar: 1, baz: 2}}", | ||
288 | &["Foo {bar: 1, baz: 2}"], | ||
289 | ); | ||
290 | assert_matches("Foo {}", "fn f() {Foo {}}", &["Foo {}"]); | ||
291 | } | 312 | } |
292 | 313 | ||
293 | #[test] | 314 | #[test] |
294 | fn match_path() { | 315 | fn match_path() { |
295 | assert_matches("foo::bar", "fn f() {foo::bar(42)}", &["foo::bar"]); | 316 | let code = r#" |
296 | assert_matches("$a::bar", "fn f() {foo::bar(42)}", &["foo::bar"]); | 317 | mod foo { |
297 | assert_matches("foo::$b", "fn f() {foo::bar(42)}", &["foo::bar"]); | 318 | fn bar() {} |
319 | } | ||
320 | fn f() {foo::bar(42)}"#; | ||
321 | assert_matches("foo::bar", code, &["foo::bar"]); | ||
322 | assert_matches("$a::bar", code, &["foo::bar"]); | ||
323 | assert_matches("foo::$b", code, &["foo::bar"]); | ||
298 | } | 324 | } |
299 | 325 | ||
300 | #[test] | 326 | #[test] |
301 | fn match_pattern() { | 327 | fn match_pattern() { |
302 | assert_matches("Some($a)", "fn f() {if let Some(x) = foo() {}}", &["Some(x)"]); | 328 | assert_matches("Some($a)", "struct Some(); fn f() {if let Some(x) = foo() {}}", &["Some(x)"]); |
303 | } | 329 | } |
304 | 330 | ||
305 | #[test] | 331 | #[test] |
306 | fn literal_constraint() { | 332 | fn literal_constraint() { |
307 | mark::check!(literal_constraint); | 333 | mark::check!(literal_constraint); |
308 | let code = r#" | 334 | let code = r#" |
335 | enum Option<T> { Some(T), None } | ||
336 | use Option::Some; | ||
309 | fn f1() { | 337 | fn f1() { |
310 | let x1 = Some(42); | 338 | let x1 = Some(42); |
311 | let x2 = Some("foo"); | 339 | let x2 = Some("foo"); |
@@ -322,24 +350,36 @@ fn literal_constraint() { | |||
322 | fn match_reordered_struct_instantiation() { | 350 | fn match_reordered_struct_instantiation() { |
323 | assert_matches( | 351 | assert_matches( |
324 | "Foo {aa: 1, b: 2, ccc: 3}", | 352 | "Foo {aa: 1, b: 2, ccc: 3}", |
325 | "fn f() {Foo {b: 2, ccc: 3, aa: 1}}", | 353 | "struct Foo {} fn f() {Foo {b: 2, ccc: 3, aa: 1}}", |
326 | &["Foo {b: 2, ccc: 3, aa: 1}"], | 354 | &["Foo {b: 2, ccc: 3, aa: 1}"], |
327 | ); | 355 | ); |
328 | assert_no_match("Foo {a: 1}", "fn f() {Foo {b: 1}}"); | 356 | assert_no_match("Foo {a: 1}", "struct Foo {} fn f() {Foo {b: 1}}"); |
329 | assert_no_match("Foo {a: 1}", "fn f() {Foo {a: 2}}"); | 357 | assert_no_match("Foo {a: 1}", "struct Foo {} fn f() {Foo {a: 2}}"); |
330 | assert_no_match("Foo {a: 1, b: 2}", "fn f() {Foo {a: 1}}"); | 358 | assert_no_match("Foo {a: 1, b: 2}", "struct Foo {} fn f() {Foo {a: 1}}"); |
331 | assert_no_match("Foo {a: 1, b: 2}", "fn f() {Foo {b: 2}}"); | 359 | assert_no_match("Foo {a: 1, b: 2}", "struct Foo {} fn f() {Foo {b: 2}}"); |
332 | assert_no_match("Foo {a: 1, }", "fn f() {Foo {a: 1, b: 2}}"); | 360 | assert_no_match("Foo {a: 1, }", "struct Foo {} fn f() {Foo {a: 1, b: 2}}"); |
333 | assert_no_match("Foo {a: 1, z: 9}", "fn f() {Foo {a: 1}}"); | 361 | assert_no_match("Foo {a: 1, z: 9}", "struct Foo {} fn f() {Foo {a: 1}}"); |
334 | } | 362 | } |
335 | 363 | ||
336 | #[test] | 364 | #[test] |
337 | fn match_macro_invocation() { | 365 | fn match_macro_invocation() { |
338 | assert_matches("foo!($a)", "fn() {foo(foo!(foo()))}", &["foo!(foo())"]); | 366 | assert_matches( |
339 | assert_matches("foo!(41, $a, 43)", "fn() {foo!(41, 42, 43)}", &["foo!(41, 42, 43)"]); | 367 | "foo!($a)", |
340 | assert_no_match("foo!(50, $a, 43)", "fn() {foo!(41, 42, 43}"); | 368 | "macro_rules! foo {() => {}} fn() {foo(foo!(foo()))}", |
341 | assert_no_match("foo!(41, $a, 50)", "fn() {foo!(41, 42, 43}"); | 369 | &["foo!(foo())"], |
342 | assert_matches("foo!($a())", "fn() {foo!(bar())}", &["foo!(bar())"]); | 370 | ); |
371 | assert_matches( | ||
372 | "foo!(41, $a, 43)", | ||
373 | "macro_rules! foo {() => {}} fn() {foo!(41, 42, 43)}", | ||
374 | &["foo!(41, 42, 43)"], | ||
375 | ); | ||
376 | assert_no_match("foo!(50, $a, 43)", "macro_rules! foo {() => {}} fn() {foo!(41, 42, 43}"); | ||
377 | assert_no_match("foo!(41, $a, 50)", "macro_rules! foo {() => {}} fn() {foo!(41, 42, 43}"); | ||
378 | assert_matches( | ||
379 | "foo!($a())", | ||
380 | "macro_rules! foo {() => {}} fn() {foo!(bar())}", | ||
381 | &["foo!(bar())"], | ||
382 | ); | ||
343 | } | 383 | } |
344 | 384 | ||
345 | // When matching within a macro expansion, we only allow matches of nodes that originated from | 385 | // When matching within a macro expansion, we only allow matches of nodes that originated from |
@@ -374,15 +414,19 @@ fn no_match_split_expression() { | |||
374 | 414 | ||
375 | #[test] | 415 | #[test] |
376 | fn replace_function_call() { | 416 | fn replace_function_call() { |
377 | assert_ssr_transform("foo() ==>> bar()", "fn f1() {foo(); foo();}", "fn f1() {bar(); bar();}"); | 417 | assert_ssr_transform( |
418 | "foo() ==>> bar()", | ||
419 | "fn foo() {} fn f1() {foo(); foo();}", | ||
420 | "fn foo() {} fn f1() {bar(); bar();}", | ||
421 | ); | ||
378 | } | 422 | } |
379 | 423 | ||
380 | #[test] | 424 | #[test] |
381 | fn replace_function_call_with_placeholders() { | 425 | fn replace_function_call_with_placeholders() { |
382 | assert_ssr_transform( | 426 | assert_ssr_transform( |
383 | "foo($a, $b) ==>> bar($b, $a)", | 427 | "foo($a, $b) ==>> bar($b, $a)", |
384 | "fn f1() {foo(5, 42)}", | 428 | "fn foo() {} fn f1() {foo(5, 42)}", |
385 | "fn f1() {bar(42, 5)}", | 429 | "fn foo() {} fn f1() {bar(42, 5)}", |
386 | ); | 430 | ); |
387 | } | 431 | } |
388 | 432 | ||
@@ -390,8 +434,8 @@ fn replace_function_call_with_placeholders() { | |||
390 | fn replace_nested_function_calls() { | 434 | fn replace_nested_function_calls() { |
391 | assert_ssr_transform( | 435 | assert_ssr_transform( |
392 | "foo($a) ==>> bar($a)", | 436 | "foo($a) ==>> bar($a)", |
393 | "fn f1() {foo(foo(42))}", | 437 | "fn foo() {} fn f1() {foo(foo(42))}", |
394 | "fn f1() {bar(bar(42))}", | 438 | "fn foo() {} fn f1() {bar(bar(42))}", |
395 | ); | 439 | ); |
396 | } | 440 | } |
397 | 441 | ||
@@ -399,8 +443,8 @@ fn replace_nested_function_calls() { | |||
399 | fn replace_type() { | 443 | fn replace_type() { |
400 | assert_ssr_transform( | 444 | assert_ssr_transform( |
401 | "Result<(), $a> ==>> Option<$a>", | 445 | "Result<(), $a> ==>> Option<$a>", |
402 | "fn f1() -> Result<(), Vec<Error>> {foo()}", | 446 | "struct Result<T, E> {} fn f1() -> Result<(), Vec<Error>> {foo()}", |
403 | "fn f1() -> Option<Vec<Error>> {foo()}", | 447 | "struct Result<T, E> {} fn f1() -> Option<Vec<Error>> {foo()}", |
404 | ); | 448 | ); |
405 | } | 449 | } |
406 | 450 | ||
@@ -408,8 +452,8 @@ fn replace_type() { | |||
408 | fn replace_struct_init() { | 452 | fn replace_struct_init() { |
409 | assert_ssr_transform( | 453 | assert_ssr_transform( |
410 | "Foo {a: $a, b: $b} ==>> Foo::new($a, $b)", | 454 | "Foo {a: $a, b: $b} ==>> Foo::new($a, $b)", |
411 | "fn f1() {Foo{b: 1, a: 2}}", | 455 | "struct Foo {} fn f1() {Foo{b: 1, a: 2}}", |
412 | "fn f1() {Foo::new(2, 1)}", | 456 | "struct Foo {} fn f1() {Foo::new(2, 1)}", |
413 | ); | 457 | ); |
414 | } | 458 | } |
415 | 459 | ||
@@ -417,13 +461,13 @@ fn replace_struct_init() { | |||
417 | fn replace_macro_invocations() { | 461 | fn replace_macro_invocations() { |
418 | assert_ssr_transform( | 462 | assert_ssr_transform( |
419 | "try!($a) ==>> $a?", | 463 | "try!($a) ==>> $a?", |
420 | "fn f1() -> Result<(), E> {bar(try!(foo()));}", | 464 | "macro_rules! try {() => {}} fn f1() -> Result<(), E> {bar(try!(foo()));}", |
421 | "fn f1() -> Result<(), E> {bar(foo()?);}", | 465 | "macro_rules! try {() => {}} fn f1() -> Result<(), E> {bar(foo()?);}", |
422 | ); | 466 | ); |
423 | assert_ssr_transform( | 467 | assert_ssr_transform( |
424 | "foo!($a($b)) ==>> foo($b, $a)", | 468 | "foo!($a($b)) ==>> foo($b, $a)", |
425 | "fn f1() {foo!(abc(def() + 2));}", | 469 | "macro_rules! foo {() => {}} fn f1() {foo!(abc(def() + 2));}", |
426 | "fn f1() {foo(def() + 2, abc);}", | 470 | "macro_rules! foo {() => {}} fn f1() {foo(def() + 2, abc);}", |
427 | ); | 471 | ); |
428 | } | 472 | } |
429 | 473 | ||
@@ -512,6 +556,7 @@ fn preserves_whitespace_within_macro_expansion() { | |||
512 | #[test] | 556 | #[test] |
513 | fn match_failure_reasons() { | 557 | fn match_failure_reasons() { |
514 | let code = r#" | 558 | let code = r#" |
559 | fn bar() {} | ||
515 | macro_rules! foo { | 560 | macro_rules! foo { |
516 | ($a:expr) => { | 561 | ($a:expr) => { |
517 | 1 + $a + 2 | 562 | 1 + $a + 2 |