diff options
Diffstat (limited to 'crates/ra_ssr/src/matching.rs')
-rw-r--r-- | crates/ra_ssr/src/matching.rs | 381 |
1 files changed, 260 insertions, 121 deletions
diff --git a/crates/ra_ssr/src/matching.rs b/crates/ra_ssr/src/matching.rs index bb87bda43..74e15c631 100644 --- a/crates/ra_ssr/src/matching.rs +++ b/crates/ra_ssr/src/matching.rs | |||
@@ -2,17 +2,17 @@ | |||
2 | //! process of matching, placeholder values are recorded. | 2 | //! process of matching, placeholder values are recorded. |
3 | 3 | ||
4 | use crate::{ | 4 | use crate::{ |
5 | parsing::{Placeholder, SsrTemplate}, | 5 | parsing::{Constraint, NodeKind, Placeholder}, |
6 | SsrMatches, SsrPattern, SsrRule, | 6 | resolving::{ResolvedPattern, ResolvedRule}, |
7 | SsrMatches, | ||
7 | }; | 8 | }; |
8 | use hir::Semantics; | 9 | use hir::Semantics; |
9 | use ra_db::FileRange; | 10 | use ra_db::FileRange; |
10 | use ra_syntax::ast::{AstNode, AstToken}; | 11 | use ra_syntax::ast::{AstNode, AstToken}; |
11 | use ra_syntax::{ | 12 | use ra_syntax::{ast, SyntaxElement, SyntaxElementChildren, SyntaxKind, SyntaxNode, SyntaxToken}; |
12 | ast, SyntaxElement, SyntaxElementChildren, SyntaxKind, SyntaxNode, SyntaxToken, TextRange, | ||
13 | }; | ||
14 | use rustc_hash::FxHashMap; | 13 | use rustc_hash::FxHashMap; |
15 | use std::{cell::Cell, iter::Peekable}; | 14 | use std::{cell::Cell, iter::Peekable}; |
15 | use test_utils::mark; | ||
16 | 16 | ||
17 | // Creates a match error. If we're currently attempting to match some code that we thought we were | 17 | // Creates a match error. If we're currently attempting to match some code that we thought we were |
18 | // going to match, as indicated by the --debug-snippet flag, then populate the reason field. | 18 | // going to match, as indicated by the --debug-snippet flag, then populate the reason field. |
@@ -44,14 +44,16 @@ macro_rules! fail_match { | |||
44 | 44 | ||
45 | /// Information about a match that was found. | 45 | /// Information about a match that was found. |
46 | #[derive(Debug)] | 46 | #[derive(Debug)] |
47 | pub(crate) struct Match { | 47 | pub struct Match { |
48 | pub(crate) range: TextRange, | 48 | pub(crate) range: FileRange, |
49 | pub(crate) matched_node: SyntaxNode, | 49 | pub(crate) matched_node: SyntaxNode, |
50 | pub(crate) placeholder_values: FxHashMap<Var, PlaceholderMatch>, | 50 | pub(crate) placeholder_values: FxHashMap<Var, PlaceholderMatch>, |
51 | pub(crate) ignored_comments: Vec<ast::Comment>, | 51 | pub(crate) ignored_comments: Vec<ast::Comment>, |
52 | // A copy of the template for the rule that produced this match. We store this on the match for | 52 | pub(crate) rule_index: usize, |
53 | // if/when we do replacement. | 53 | /// The depth of matched_node. |
54 | pub(crate) template: SsrTemplate, | 54 | pub(crate) depth: usize, |
55 | // Each path in the template rendered for the module in which the match was found. | ||
56 | pub(crate) rendered_template_paths: FxHashMap<SyntaxNode, hir::ModPath>, | ||
55 | } | 57 | } |
56 | 58 | ||
57 | /// Represents a `$var` in an SSR query. | 59 | /// Represents a `$var` in an SSR query. |
@@ -87,64 +89,67 @@ pub(crate) struct MatchFailed { | |||
87 | /// parent module, we don't populate nested matches. | 89 | /// parent module, we don't populate nested matches. |
88 | pub(crate) fn get_match( | 90 | pub(crate) fn get_match( |
89 | debug_active: bool, | 91 | debug_active: bool, |
90 | rule: &SsrRule, | 92 | rule: &ResolvedRule, |
91 | code: &SyntaxNode, | 93 | code: &SyntaxNode, |
92 | restrict_range: &Option<FileRange>, | 94 | restrict_range: &Option<FileRange>, |
93 | sema: &Semantics<ra_ide_db::RootDatabase>, | 95 | sema: &Semantics<ra_ide_db::RootDatabase>, |
94 | ) -> Result<Match, MatchFailed> { | 96 | ) -> Result<Match, MatchFailed> { |
95 | record_match_fails_reasons_scope(debug_active, || { | 97 | record_match_fails_reasons_scope(debug_active, || { |
96 | MatchState::try_match(rule, code, restrict_range, sema) | 98 | Matcher::try_match(rule, code, restrict_range, sema) |
97 | }) | 99 | }) |
98 | } | 100 | } |
99 | 101 | ||
100 | /// Inputs to matching. This cannot be part of `MatchState`, since we mutate `MatchState` and in at | 102 | /// Checks if our search pattern matches a particular node of the AST. |
101 | /// least one case need to hold a borrow of a placeholder from the input pattern while calling a | 103 | struct Matcher<'db, 'sema> { |
102 | /// mutable `MatchState` method. | ||
103 | struct MatchInputs<'pattern> { | ||
104 | ssr_pattern: &'pattern SsrPattern, | ||
105 | } | ||
106 | |||
107 | /// State used while attempting to match our search pattern against a particular node of the AST. | ||
108 | struct MatchState<'db, 'sema> { | ||
109 | sema: &'sema Semantics<'db, ra_ide_db::RootDatabase>, | 104 | sema: &'sema Semantics<'db, ra_ide_db::RootDatabase>, |
110 | /// If any placeholders come from anywhere outside of this range, then the match will be | 105 | /// If any placeholders come from anywhere outside of this range, then the match will be |
111 | /// rejected. | 106 | /// rejected. |
112 | restrict_range: Option<FileRange>, | 107 | restrict_range: Option<FileRange>, |
113 | /// The match that we're building. We do two passes for a successful match. On the first pass, | 108 | rule: &'sema ResolvedRule, |
114 | /// this is None so that we can avoid doing things like storing copies of what placeholders | 109 | } |
115 | /// matched to. If that pass succeeds, then we do a second pass where we collect those details. | 110 | |
116 | /// This means that if we have a pattern like `$a.foo()` we won't do an insert into the | 111 | /// Which phase of matching we're currently performing. We do two phases because most attempted |
117 | /// placeholders map for every single method call in the codebase. Instead we'll discard all the | 112 | /// matches will fail and it means we can defer more expensive checks to the second phase. |
118 | /// method calls that aren't calls to `foo` on the first pass and only insert into the | 113 | enum Phase<'a> { |
119 | /// placeholders map on the second pass. Likewise for ignored comments. | 114 | /// On the first phase, we perform cheap checks. No state is mutated and nothing is recorded. |
120 | match_out: Option<Match>, | 115 | First, |
116 | /// On the second phase, we construct the `Match`. Things like what placeholders bind to is | ||
117 | /// recorded. | ||
118 | Second(&'a mut Match), | ||
121 | } | 119 | } |
122 | 120 | ||
123 | impl<'db, 'sema> MatchState<'db, 'sema> { | 121 | impl<'db, 'sema> Matcher<'db, 'sema> { |
124 | fn try_match( | 122 | fn try_match( |
125 | rule: &SsrRule, | 123 | rule: &ResolvedRule, |
126 | code: &SyntaxNode, | 124 | code: &SyntaxNode, |
127 | restrict_range: &Option<FileRange>, | 125 | restrict_range: &Option<FileRange>, |
128 | sema: &'sema Semantics<'db, ra_ide_db::RootDatabase>, | 126 | sema: &'sema Semantics<'db, ra_ide_db::RootDatabase>, |
129 | ) -> Result<Match, MatchFailed> { | 127 | ) -> Result<Match, MatchFailed> { |
130 | let mut match_state = | 128 | let match_state = Matcher { sema, restrict_range: restrict_range.clone(), rule }; |
131 | MatchState { sema, restrict_range: restrict_range.clone(), match_out: None }; | ||
132 | let match_inputs = MatchInputs { ssr_pattern: &rule.pattern }; | ||
133 | let pattern_tree = rule.pattern.tree_for_kind(code.kind())?; | ||
134 | // First pass at matching, where we check that node types and idents match. | 129 | // First pass at matching, where we check that node types and idents match. |
135 | match_state.attempt_match_node(&match_inputs, &pattern_tree, code)?; | 130 | match_state.attempt_match_node(&mut Phase::First, &rule.pattern.node, code)?; |
136 | match_state.validate_range(&sema.original_range(code))?; | 131 | match_state.validate_range(&sema.original_range(code))?; |
137 | match_state.match_out = Some(Match { | 132 | let mut the_match = Match { |
138 | range: sema.original_range(code).range, | 133 | range: sema.original_range(code), |
139 | matched_node: code.clone(), | 134 | matched_node: code.clone(), |
140 | placeholder_values: FxHashMap::default(), | 135 | placeholder_values: FxHashMap::default(), |
141 | ignored_comments: Vec::new(), | 136 | ignored_comments: Vec::new(), |
142 | template: rule.template.clone(), | 137 | rule_index: rule.index, |
143 | }); | 138 | depth: 0, |
139 | rendered_template_paths: FxHashMap::default(), | ||
140 | }; | ||
144 | // Second matching pass, where we record placeholder matches, ignored comments and maybe do | 141 | // Second matching pass, where we record placeholder matches, ignored comments and maybe do |
145 | // any other more expensive checks that we didn't want to do on the first pass. | 142 | // any other more expensive checks that we didn't want to do on the first pass. |
146 | match_state.attempt_match_node(&match_inputs, &pattern_tree, code)?; | 143 | match_state.attempt_match_node( |
147 | Ok(match_state.match_out.unwrap()) | 144 | &mut Phase::Second(&mut the_match), |
145 | &rule.pattern.node, | ||
146 | code, | ||
147 | )?; | ||
148 | the_match.depth = sema.ancestors_with_macros(the_match.matched_node.clone()).count(); | ||
149 | if let Some(template) = &rule.template { | ||
150 | the_match.render_template_paths(template, sema)?; | ||
151 | } | ||
152 | Ok(the_match) | ||
148 | } | 153 | } |
149 | 154 | ||
150 | /// Checks that `range` is within the permitted range if any. This is applicable when we're | 155 | /// Checks that `range` is within the permitted range if any. This is applicable when we're |
@@ -162,79 +167,91 @@ impl<'db, 'sema> MatchState<'db, 'sema> { | |||
162 | } | 167 | } |
163 | 168 | ||
164 | fn attempt_match_node( | 169 | fn attempt_match_node( |
165 | &mut self, | 170 | &self, |
166 | match_inputs: &MatchInputs, | 171 | phase: &mut Phase, |
167 | pattern: &SyntaxNode, | 172 | pattern: &SyntaxNode, |
168 | code: &SyntaxNode, | 173 | code: &SyntaxNode, |
169 | ) -> Result<(), MatchFailed> { | 174 | ) -> Result<(), MatchFailed> { |
170 | // Handle placeholders. | 175 | // Handle placeholders. |
171 | if let Some(placeholder) = | 176 | if let Some(placeholder) = self.get_placeholder(&SyntaxElement::Node(pattern.clone())) { |
172 | match_inputs.get_placeholder(&SyntaxElement::Node(pattern.clone())) | 177 | for constraint in &placeholder.constraints { |
173 | { | 178 | self.check_constraint(constraint, code)?; |
174 | if self.match_out.is_none() { | ||
175 | return Ok(()); | ||
176 | } | 179 | } |
177 | let original_range = self.sema.original_range(code); | 180 | if let Phase::Second(matches_out) = phase { |
178 | // We validated the range for the node when we started the match, so the placeholder | 181 | let original_range = self.sema.original_range(code); |
179 | // probably can't fail range validation, but just to be safe... | 182 | // We validated the range for the node when we started the match, so the placeholder |
180 | self.validate_range(&original_range)?; | 183 | // probably can't fail range validation, but just to be safe... |
181 | if let Some(match_out) = &mut self.match_out { | 184 | self.validate_range(&original_range)?; |
182 | match_out.placeholder_values.insert( | 185 | matches_out.placeholder_values.insert( |
183 | Var(placeholder.ident.to_string()), | 186 | Var(placeholder.ident.to_string()), |
184 | PlaceholderMatch::new(code, original_range), | 187 | PlaceholderMatch::new(code, original_range), |
185 | ); | 188 | ); |
186 | } | 189 | } |
187 | return Ok(()); | 190 | return Ok(()); |
188 | } | 191 | } |
189 | // Non-placeholders. | 192 | // We allow a UFCS call to match a method call, provided they resolve to the same function. |
193 | if let Some(pattern_function) = self.rule.pattern.ufcs_function_calls.get(pattern) { | ||
194 | if let (Some(pattern), Some(code)) = | ||
195 | (ast::CallExpr::cast(pattern.clone()), ast::MethodCallExpr::cast(code.clone())) | ||
196 | { | ||
197 | return self.attempt_match_ufcs(phase, &pattern, &code, *pattern_function); | ||
198 | } | ||
199 | } | ||
190 | if pattern.kind() != code.kind() { | 200 | if pattern.kind() != code.kind() { |
191 | fail_match!("Pattern had a {:?}, code had {:?}", pattern.kind(), code.kind()); | 201 | fail_match!( |
202 | "Pattern had `{}` ({:?}), code had `{}` ({:?})", | ||
203 | pattern.text(), | ||
204 | pattern.kind(), | ||
205 | code.text(), | ||
206 | code.kind() | ||
207 | ); | ||
192 | } | 208 | } |
193 | // Some kinds of nodes have special handling. For everything else, we fall back to default | 209 | // Some kinds of nodes have special handling. For everything else, we fall back to default |
194 | // matching. | 210 | // matching. |
195 | match code.kind() { | 211 | match code.kind() { |
196 | SyntaxKind::RECORD_FIELD_LIST => { | 212 | SyntaxKind::RECORD_EXPR_FIELD_LIST => { |
197 | self.attempt_match_record_field_list(match_inputs, pattern, code) | 213 | self.attempt_match_record_field_list(phase, pattern, code) |
198 | } | 214 | } |
199 | SyntaxKind::TOKEN_TREE => self.attempt_match_token_tree(match_inputs, pattern, code), | 215 | SyntaxKind::TOKEN_TREE => self.attempt_match_token_tree(phase, pattern, code), |
200 | _ => self.attempt_match_node_children(match_inputs, pattern, code), | 216 | SyntaxKind::PATH => self.attempt_match_path(phase, pattern, code), |
217 | _ => self.attempt_match_node_children(phase, pattern, code), | ||
201 | } | 218 | } |
202 | } | 219 | } |
203 | 220 | ||
204 | fn attempt_match_node_children( | 221 | fn attempt_match_node_children( |
205 | &mut self, | 222 | &self, |
206 | match_inputs: &MatchInputs, | 223 | phase: &mut Phase, |
207 | pattern: &SyntaxNode, | 224 | pattern: &SyntaxNode, |
208 | code: &SyntaxNode, | 225 | code: &SyntaxNode, |
209 | ) -> Result<(), MatchFailed> { | 226 | ) -> Result<(), MatchFailed> { |
210 | self.attempt_match_sequences( | 227 | self.attempt_match_sequences( |
211 | match_inputs, | 228 | phase, |
212 | PatternIterator::new(pattern), | 229 | PatternIterator::new(pattern), |
213 | code.children_with_tokens(), | 230 | code.children_with_tokens(), |
214 | ) | 231 | ) |
215 | } | 232 | } |
216 | 233 | ||
217 | fn attempt_match_sequences( | 234 | fn attempt_match_sequences( |
218 | &mut self, | 235 | &self, |
219 | match_inputs: &MatchInputs, | 236 | phase: &mut Phase, |
220 | pattern_it: PatternIterator, | 237 | pattern_it: PatternIterator, |
221 | mut code_it: SyntaxElementChildren, | 238 | mut code_it: SyntaxElementChildren, |
222 | ) -> Result<(), MatchFailed> { | 239 | ) -> Result<(), MatchFailed> { |
223 | let mut pattern_it = pattern_it.peekable(); | 240 | let mut pattern_it = pattern_it.peekable(); |
224 | loop { | 241 | loop { |
225 | match self.next_non_trivial(&mut code_it) { | 242 | match phase.next_non_trivial(&mut code_it) { |
226 | None => { | 243 | None => { |
227 | if let Some(p) = pattern_it.next() { | 244 | if let Some(p) = pattern_it.next() { |
228 | fail_match!("Part of the pattern was unmached: {:?}", p); | 245 | fail_match!("Part of the pattern was unmatched: {:?}", p); |
229 | } | 246 | } |
230 | return Ok(()); | 247 | return Ok(()); |
231 | } | 248 | } |
232 | Some(SyntaxElement::Token(c)) => { | 249 | Some(SyntaxElement::Token(c)) => { |
233 | self.attempt_match_token(&mut pattern_it, &c)?; | 250 | self.attempt_match_token(phase, &mut pattern_it, &c)?; |
234 | } | 251 | } |
235 | Some(SyntaxElement::Node(c)) => match pattern_it.next() { | 252 | Some(SyntaxElement::Node(c)) => match pattern_it.next() { |
236 | Some(SyntaxElement::Node(p)) => { | 253 | Some(SyntaxElement::Node(p)) => { |
237 | self.attempt_match_node(match_inputs, &p, &c)?; | 254 | self.attempt_match_node(phase, &p, &c)?; |
238 | } | 255 | } |
239 | Some(p) => fail_match!("Pattern wanted '{}', code has {}", p, c.text()), | 256 | Some(p) => fail_match!("Pattern wanted '{}', code has {}", p, c.text()), |
240 | None => fail_match!("Pattern reached end, code has {}", c.text()), | 257 | None => fail_match!("Pattern reached end, code has {}", c.text()), |
@@ -244,11 +261,12 @@ impl<'db, 'sema> MatchState<'db, 'sema> { | |||
244 | } | 261 | } |
245 | 262 | ||
246 | fn attempt_match_token( | 263 | fn attempt_match_token( |
247 | &mut self, | 264 | &self, |
265 | phase: &mut Phase, | ||
248 | pattern: &mut Peekable<PatternIterator>, | 266 | pattern: &mut Peekable<PatternIterator>, |
249 | code: &ra_syntax::SyntaxToken, | 267 | code: &ra_syntax::SyntaxToken, |
250 | ) -> Result<(), MatchFailed> { | 268 | ) -> Result<(), MatchFailed> { |
251 | self.record_ignored_comments(code); | 269 | phase.record_ignored_comments(code); |
252 | // Ignore whitespace and comments. | 270 | // Ignore whitespace and comments. |
253 | if code.kind().is_trivia() { | 271 | if code.kind().is_trivia() { |
254 | return Ok(()); | 272 | return Ok(()); |
@@ -294,18 +312,94 @@ impl<'db, 'sema> MatchState<'db, 'sema> { | |||
294 | Ok(()) | 312 | Ok(()) |
295 | } | 313 | } |
296 | 314 | ||
315 | fn check_constraint( | ||
316 | &self, | ||
317 | constraint: &Constraint, | ||
318 | code: &SyntaxNode, | ||
319 | ) -> Result<(), MatchFailed> { | ||
320 | match constraint { | ||
321 | Constraint::Kind(kind) => { | ||
322 | kind.matches(code)?; | ||
323 | } | ||
324 | Constraint::Not(sub) => { | ||
325 | if self.check_constraint(&*sub, code).is_ok() { | ||
326 | fail_match!("Constraint {:?} failed for '{}'", constraint, code.text()); | ||
327 | } | ||
328 | } | ||
329 | } | ||
330 | Ok(()) | ||
331 | } | ||
332 | |||
333 | /// Paths are matched based on whether they refer to the same thing, even if they're written | ||
334 | /// differently. | ||
335 | fn attempt_match_path( | ||
336 | &self, | ||
337 | phase: &mut Phase, | ||
338 | pattern: &SyntaxNode, | ||
339 | code: &SyntaxNode, | ||
340 | ) -> Result<(), MatchFailed> { | ||
341 | if let Some(pattern_resolved) = self.rule.pattern.resolved_paths.get(pattern) { | ||
342 | let pattern_path = ast::Path::cast(pattern.clone()).unwrap(); | ||
343 | let code_path = ast::Path::cast(code.clone()).unwrap(); | ||
344 | if let (Some(pattern_segment), Some(code_segment)) = | ||
345 | (pattern_path.segment(), code_path.segment()) | ||
346 | { | ||
347 | // Match everything within the segment except for the name-ref, which is handled | ||
348 | // separately via comparing what the path resolves to below. | ||
349 | self.attempt_match_opt( | ||
350 | phase, | ||
351 | pattern_segment.type_arg_list(), | ||
352 | code_segment.type_arg_list(), | ||
353 | )?; | ||
354 | self.attempt_match_opt( | ||
355 | phase, | ||
356 | pattern_segment.param_list(), | ||
357 | code_segment.param_list(), | ||
358 | )?; | ||
359 | } | ||
360 | if matches!(phase, Phase::Second(_)) { | ||
361 | let resolution = self | ||
362 | .sema | ||
363 | .resolve_path(&code_path) | ||
364 | .ok_or_else(|| match_error!("Failed to resolve path `{}`", code.text()))?; | ||
365 | if pattern_resolved.resolution != resolution { | ||
366 | fail_match!("Pattern had path `{}` code had `{}`", pattern.text(), code.text()); | ||
367 | } | ||
368 | } | ||
369 | } else { | ||
370 | return self.attempt_match_node_children(phase, pattern, code); | ||
371 | } | ||
372 | Ok(()) | ||
373 | } | ||
374 | |||
375 | fn attempt_match_opt<T: AstNode>( | ||
376 | &self, | ||
377 | phase: &mut Phase, | ||
378 | pattern: Option<T>, | ||
379 | code: Option<T>, | ||
380 | ) -> Result<(), MatchFailed> { | ||
381 | match (pattern, code) { | ||
382 | (Some(p), Some(c)) => self.attempt_match_node(phase, &p.syntax(), &c.syntax()), | ||
383 | (None, None) => Ok(()), | ||
384 | (Some(p), None) => fail_match!("Pattern `{}` had nothing to match", p.syntax().text()), | ||
385 | (None, Some(c)) => { | ||
386 | fail_match!("Nothing in pattern to match code `{}`", c.syntax().text()) | ||
387 | } | ||
388 | } | ||
389 | } | ||
390 | |||
297 | /// We want to allow the records to match in any order, so we have special matching logic for | 391 | /// We want to allow the records to match in any order, so we have special matching logic for |
298 | /// them. | 392 | /// them. |
299 | fn attempt_match_record_field_list( | 393 | fn attempt_match_record_field_list( |
300 | &mut self, | 394 | &self, |
301 | match_inputs: &MatchInputs, | 395 | phase: &mut Phase, |
302 | pattern: &SyntaxNode, | 396 | pattern: &SyntaxNode, |
303 | code: &SyntaxNode, | 397 | code: &SyntaxNode, |
304 | ) -> Result<(), MatchFailed> { | 398 | ) -> Result<(), MatchFailed> { |
305 | // Build a map keyed by field name. | 399 | // Build a map keyed by field name. |
306 | let mut fields_by_name = FxHashMap::default(); | 400 | let mut fields_by_name = FxHashMap::default(); |
307 | for child in code.children() { | 401 | for child in code.children() { |
308 | if let Some(record) = ast::RecordField::cast(child.clone()) { | 402 | if let Some(record) = ast::RecordExprField::cast(child.clone()) { |
309 | if let Some(name) = record.field_name() { | 403 | if let Some(name) = record.field_name() { |
310 | fields_by_name.insert(name.text().clone(), child.clone()); | 404 | fields_by_name.insert(name.text().clone(), child.clone()); |
311 | } | 405 | } |
@@ -314,11 +408,11 @@ impl<'db, 'sema> MatchState<'db, 'sema> { | |||
314 | for p in pattern.children_with_tokens() { | 408 | for p in pattern.children_with_tokens() { |
315 | if let SyntaxElement::Node(p) = p { | 409 | if let SyntaxElement::Node(p) = p { |
316 | if let Some(name_element) = p.first_child_or_token() { | 410 | if let Some(name_element) = p.first_child_or_token() { |
317 | if match_inputs.get_placeholder(&name_element).is_some() { | 411 | if self.get_placeholder(&name_element).is_some() { |
318 | // If the pattern is using placeholders for field names then order | 412 | // If the pattern is using placeholders for field names then order |
319 | // independence doesn't make sense. Fall back to regular ordered | 413 | // independence doesn't make sense. Fall back to regular ordered |
320 | // matching. | 414 | // matching. |
321 | return self.attempt_match_node_children(match_inputs, pattern, code); | 415 | return self.attempt_match_node_children(phase, pattern, code); |
322 | } | 416 | } |
323 | if let Some(ident) = only_ident(name_element) { | 417 | if let Some(ident) = only_ident(name_element) { |
324 | let code_record = fields_by_name.remove(ident.text()).ok_or_else(|| { | 418 | let code_record = fields_by_name.remove(ident.text()).ok_or_else(|| { |
@@ -327,7 +421,7 @@ impl<'db, 'sema> MatchState<'db, 'sema> { | |||
327 | ident | 421 | ident |
328 | ) | 422 | ) |
329 | })?; | 423 | })?; |
330 | self.attempt_match_node(match_inputs, &p, &code_record)?; | 424 | self.attempt_match_node(phase, &p, &code_record)?; |
331 | } | 425 | } |
332 | } | 426 | } |
333 | } | 427 | } |
@@ -347,16 +441,15 @@ impl<'db, 'sema> MatchState<'db, 'sema> { | |||
347 | /// pattern matches the macro invocation. For matches within the macro call, we'll already have | 441 | /// pattern matches the macro invocation. For matches within the macro call, we'll already have |
348 | /// expanded the macro. | 442 | /// expanded the macro. |
349 | fn attempt_match_token_tree( | 443 | fn attempt_match_token_tree( |
350 | &mut self, | 444 | &self, |
351 | match_inputs: &MatchInputs, | 445 | phase: &mut Phase, |
352 | pattern: &SyntaxNode, | 446 | pattern: &SyntaxNode, |
353 | code: &ra_syntax::SyntaxNode, | 447 | code: &ra_syntax::SyntaxNode, |
354 | ) -> Result<(), MatchFailed> { | 448 | ) -> Result<(), MatchFailed> { |
355 | let mut pattern = PatternIterator::new(pattern).peekable(); | 449 | let mut pattern = PatternIterator::new(pattern).peekable(); |
356 | let mut children = code.children_with_tokens(); | 450 | let mut children = code.children_with_tokens(); |
357 | while let Some(child) = children.next() { | 451 | while let Some(child) = children.next() { |
358 | if let Some(placeholder) = pattern.peek().and_then(|p| match_inputs.get_placeholder(p)) | 452 | if let Some(placeholder) = pattern.peek().and_then(|p| self.get_placeholder(p)) { |
359 | { | ||
360 | pattern.next(); | 453 | pattern.next(); |
361 | let next_pattern_token = pattern | 454 | let next_pattern_token = pattern |
362 | .peek() | 455 | .peek() |
@@ -382,7 +475,7 @@ impl<'db, 'sema> MatchState<'db, 'sema> { | |||
382 | if Some(first_token.to_string()) == next_pattern_token { | 475 | if Some(first_token.to_string()) == next_pattern_token { |
383 | if let Some(SyntaxElement::Node(p)) = pattern.next() { | 476 | if let Some(SyntaxElement::Node(p)) = pattern.next() { |
384 | // We have a subtree that starts with the next token in our pattern. | 477 | // We have a subtree that starts with the next token in our pattern. |
385 | self.attempt_match_token_tree(match_inputs, &p, &n)?; | 478 | self.attempt_match_token_tree(phase, &p, &n)?; |
386 | break; | 479 | break; |
387 | } | 480 | } |
388 | } | 481 | } |
@@ -391,7 +484,7 @@ impl<'db, 'sema> MatchState<'db, 'sema> { | |||
391 | }; | 484 | }; |
392 | last_matched_token = next; | 485 | last_matched_token = next; |
393 | } | 486 | } |
394 | if let Some(match_out) = &mut self.match_out { | 487 | if let Phase::Second(match_out) = phase { |
395 | match_out.placeholder_values.insert( | 488 | match_out.placeholder_values.insert( |
396 | Var(placeholder.ident.to_string()), | 489 | Var(placeholder.ident.to_string()), |
397 | PlaceholderMatch::from_range(FileRange { | 490 | PlaceholderMatch::from_range(FileRange { |
@@ -407,11 +500,11 @@ impl<'db, 'sema> MatchState<'db, 'sema> { | |||
407 | // Match literal (non-placeholder) tokens. | 500 | // Match literal (non-placeholder) tokens. |
408 | match child { | 501 | match child { |
409 | SyntaxElement::Token(token) => { | 502 | SyntaxElement::Token(token) => { |
410 | self.attempt_match_token(&mut pattern, &token)?; | 503 | self.attempt_match_token(phase, &mut pattern, &token)?; |
411 | } | 504 | } |
412 | SyntaxElement::Node(node) => match pattern.next() { | 505 | SyntaxElement::Node(node) => match pattern.next() { |
413 | Some(SyntaxElement::Node(p)) => { | 506 | Some(SyntaxElement::Node(p)) => { |
414 | self.attempt_match_token_tree(match_inputs, &p, &node)?; | 507 | self.attempt_match_token_tree(phase, &p, &node)?; |
415 | } | 508 | } |
416 | Some(SyntaxElement::Token(p)) => fail_match!( | 509 | Some(SyntaxElement::Token(p)) => fail_match!( |
417 | "Pattern has token '{}', code has subtree '{}'", | 510 | "Pattern has token '{}', code has subtree '{}'", |
@@ -428,6 +521,65 @@ impl<'db, 'sema> MatchState<'db, 'sema> { | |||
428 | Ok(()) | 521 | Ok(()) |
429 | } | 522 | } |
430 | 523 | ||
524 | fn attempt_match_ufcs( | ||
525 | &self, | ||
526 | phase: &mut Phase, | ||
527 | pattern: &ast::CallExpr, | ||
528 | code: &ast::MethodCallExpr, | ||
529 | pattern_function: hir::Function, | ||
530 | ) -> Result<(), MatchFailed> { | ||
531 | use ast::ArgListOwner; | ||
532 | let code_resolved_function = self | ||
533 | .sema | ||
534 | .resolve_method_call(code) | ||
535 | .ok_or_else(|| match_error!("Failed to resolve method call"))?; | ||
536 | if pattern_function != code_resolved_function { | ||
537 | fail_match!("Method call resolved to a different function"); | ||
538 | } | ||
539 | // Check arguments. | ||
540 | let mut pattern_args = pattern | ||
541 | .arg_list() | ||
542 | .ok_or_else(|| match_error!("Pattern function call has no args"))? | ||
543 | .args(); | ||
544 | self.attempt_match_opt(phase, pattern_args.next(), code.expr())?; | ||
545 | let mut code_args = | ||
546 | code.arg_list().ok_or_else(|| match_error!("Code method call has no args"))?.args(); | ||
547 | loop { | ||
548 | match (pattern_args.next(), code_args.next()) { | ||
549 | (None, None) => return Ok(()), | ||
550 | (p, c) => self.attempt_match_opt(phase, p, c)?, | ||
551 | } | ||
552 | } | ||
553 | } | ||
554 | |||
555 | fn get_placeholder(&self, element: &SyntaxElement) -> Option<&Placeholder> { | ||
556 | only_ident(element.clone()).and_then(|ident| self.rule.get_placeholder(&ident)) | ||
557 | } | ||
558 | } | ||
559 | |||
560 | impl Match { | ||
561 | fn render_template_paths( | ||
562 | &mut self, | ||
563 | template: &ResolvedPattern, | ||
564 | sema: &Semantics<ra_ide_db::RootDatabase>, | ||
565 | ) -> Result<(), MatchFailed> { | ||
566 | let module = sema | ||
567 | .scope(&self.matched_node) | ||
568 | .module() | ||
569 | .ok_or_else(|| match_error!("Matched node isn't in a module"))?; | ||
570 | for (path, resolved_path) in &template.resolved_paths { | ||
571 | if let hir::PathResolution::Def(module_def) = resolved_path.resolution { | ||
572 | let mod_path = module.find_use_path(sema.db, module_def).ok_or_else(|| { | ||
573 | match_error!("Failed to render template path `{}` at match location") | ||
574 | })?; | ||
575 | self.rendered_template_paths.insert(path.clone(), mod_path); | ||
576 | } | ||
577 | } | ||
578 | Ok(()) | ||
579 | } | ||
580 | } | ||
581 | |||
582 | impl Phase<'_> { | ||
431 | fn next_non_trivial(&mut self, code_it: &mut SyntaxElementChildren) -> Option<SyntaxElement> { | 583 | fn next_non_trivial(&mut self, code_it: &mut SyntaxElementChildren) -> Option<SyntaxElement> { |
432 | loop { | 584 | loop { |
433 | let c = code_it.next(); | 585 | let c = code_it.next(); |
@@ -443,7 +595,7 @@ impl<'db, 'sema> MatchState<'db, 'sema> { | |||
443 | 595 | ||
444 | fn record_ignored_comments(&mut self, token: &SyntaxToken) { | 596 | fn record_ignored_comments(&mut self, token: &SyntaxToken) { |
445 | if token.kind() == SyntaxKind::COMMENT { | 597 | if token.kind() == SyntaxKind::COMMENT { |
446 | if let Some(match_out) = &mut self.match_out { | 598 | if let Phase::Second(match_out) = self { |
447 | if let Some(comment) = ast::Comment::cast(token.clone()) { | 599 | if let Some(comment) = ast::Comment::cast(token.clone()) { |
448 | match_out.ignored_comments.push(comment); | 600 | match_out.ignored_comments.push(comment); |
449 | } | 601 | } |
@@ -452,13 +604,6 @@ impl<'db, 'sema> MatchState<'db, 'sema> { | |||
452 | } | 604 | } |
453 | } | 605 | } |
454 | 606 | ||
455 | impl MatchInputs<'_> { | ||
456 | fn get_placeholder(&self, element: &SyntaxElement) -> Option<&Placeholder> { | ||
457 | only_ident(element.clone()) | ||
458 | .and_then(|ident| self.ssr_pattern.placeholders_by_stand_in.get(ident.text())) | ||
459 | } | ||
460 | } | ||
461 | |||
462 | fn is_closing_token(kind: SyntaxKind) -> bool { | 607 | fn is_closing_token(kind: SyntaxKind) -> bool { |
463 | kind == SyntaxKind::R_PAREN || kind == SyntaxKind::R_CURLY || kind == SyntaxKind::R_BRACK | 608 | kind == SyntaxKind::R_PAREN || kind == SyntaxKind::R_CURLY || kind == SyntaxKind::R_BRACK |
464 | } | 609 | } |
@@ -495,25 +640,18 @@ impl PlaceholderMatch { | |||
495 | } | 640 | } |
496 | } | 641 | } |
497 | 642 | ||
498 | impl SsrPattern { | 643 | impl NodeKind { |
499 | pub(crate) fn tree_for_kind(&self, kind: SyntaxKind) -> Result<&SyntaxNode, MatchFailed> { | 644 | fn matches(&self, node: &SyntaxNode) -> Result<(), MatchFailed> { |
500 | let (tree, kind_name) = if ast::Expr::can_cast(kind) { | 645 | let ok = match self { |
501 | (&self.expr, "expression") | 646 | Self::Literal => { |
502 | } else if ast::TypeRef::can_cast(kind) { | 647 | mark::hit!(literal_constraint); |
503 | (&self.type_ref, "type reference") | 648 | ast::Literal::can_cast(node.kind()) |
504 | } else if ast::ModuleItem::can_cast(kind) { | 649 | } |
505 | (&self.item, "item") | ||
506 | } else if ast::Path::can_cast(kind) { | ||
507 | (&self.path, "path") | ||
508 | } else if ast::Pat::can_cast(kind) { | ||
509 | (&self.pattern, "pattern") | ||
510 | } else { | ||
511 | fail_match!("Matching nodes of kind {:?} is not supported", kind); | ||
512 | }; | 650 | }; |
513 | match tree { | 651 | if !ok { |
514 | Some(tree) => Ok(tree), | 652 | fail_match!("Code '{}' isn't of kind {:?}", node.text(), self); |
515 | None => fail_match!("Pattern cannot be parsed as a {}", kind_name), | ||
516 | } | 653 | } |
654 | Ok(()) | ||
517 | } | 655 | } |
518 | } | 656 | } |
519 | 657 | ||
@@ -561,18 +699,17 @@ impl PatternIterator { | |||
561 | #[cfg(test)] | 699 | #[cfg(test)] |
562 | mod tests { | 700 | mod tests { |
563 | use super::*; | 701 | use super::*; |
564 | use crate::MatchFinder; | 702 | use crate::{MatchFinder, SsrRule}; |
565 | 703 | ||
566 | #[test] | 704 | #[test] |
567 | fn parse_match_replace() { | 705 | fn parse_match_replace() { |
568 | let rule: SsrRule = "foo($x) ==>> bar($x)".parse().unwrap(); | 706 | let rule: SsrRule = "foo($x) ==>> bar($x)".parse().unwrap(); |
569 | let input = "fn main() { foo(1+2); }"; | 707 | let input = "fn foo() {} fn bar() {} fn main() { foo(1+2); }"; |
570 | 708 | ||
571 | use ra_db::fixture::WithFixture; | 709 | let (db, position, selections) = crate::tests::single_file(input); |
572 | let (db, file_id) = ra_ide_db::RootDatabase::with_single_file(input); | 710 | let mut match_finder = MatchFinder::in_context(&db, position, selections); |
573 | let mut match_finder = MatchFinder::new(&db); | 711 | match_finder.add_rule(rule).unwrap(); |
574 | match_finder.add_rule(rule); | 712 | let matches = match_finder.matches(); |
575 | let matches = match_finder.find_matches_in_file(file_id); | ||
576 | assert_eq!(matches.matches.len(), 1); | 713 | assert_eq!(matches.matches.len(), 1); |
577 | assert_eq!(matches.matches[0].matched_node.text(), "foo(1+2)"); | 714 | assert_eq!(matches.matches[0].matched_node.text(), "foo(1+2)"); |
578 | assert_eq!(matches.matches[0].placeholder_values.len(), 1); | 715 | assert_eq!(matches.matches[0].placeholder_values.len(), 1); |
@@ -585,9 +722,11 @@ mod tests { | |||
585 | "1+2" | 722 | "1+2" |
586 | ); | 723 | ); |
587 | 724 | ||
588 | let edit = crate::replacing::matches_to_edit(&matches, input); | 725 | let edits = match_finder.edits(); |
726 | assert_eq!(edits.len(), 1); | ||
727 | let edit = &edits[0]; | ||
589 | let mut after = input.to_string(); | 728 | let mut after = input.to_string(); |
590 | edit.apply(&mut after); | 729 | edit.edit.apply(&mut after); |
591 | assert_eq!(after, "fn main() { bar(1+2); }"); | 730 | assert_eq!(after, "fn foo() {} fn bar() {} fn main() { bar(1+2); }"); |
592 | } | 731 | } |
593 | } | 732 | } |