aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_ssr/src/matching.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_ssr/src/matching.rs')
-rw-r--r--crates/ra_ssr/src/matching.rs381
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
4use crate::{ 4use crate::{
5 parsing::{Placeholder, SsrTemplate}, 5 parsing::{Constraint, NodeKind, Placeholder},
6 SsrMatches, SsrPattern, SsrRule, 6 resolving::{ResolvedPattern, ResolvedRule},
7 SsrMatches,
7}; 8};
8use hir::Semantics; 9use hir::Semantics;
9use ra_db::FileRange; 10use ra_db::FileRange;
10use ra_syntax::ast::{AstNode, AstToken}; 11use ra_syntax::ast::{AstNode, AstToken};
11use ra_syntax::{ 12use ra_syntax::{ast, SyntaxElement, SyntaxElementChildren, SyntaxKind, SyntaxNode, SyntaxToken};
12 ast, SyntaxElement, SyntaxElementChildren, SyntaxKind, SyntaxNode, SyntaxToken, TextRange,
13};
14use rustc_hash::FxHashMap; 13use rustc_hash::FxHashMap;
15use std::{cell::Cell, iter::Peekable}; 14use std::{cell::Cell, iter::Peekable};
15use 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)]
47pub(crate) struct Match { 47pub 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.
88pub(crate) fn get_match( 90pub(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 103struct Matcher<'db, 'sema> {
102/// mutable `MatchState` method.
103struct 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.
108struct 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 113enum 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
123impl<'db, 'sema> MatchState<'db, 'sema> { 121impl<'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
560impl 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
582impl 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
455impl 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
462fn is_closing_token(kind: SyntaxKind) -> bool { 607fn 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
498impl SsrPattern { 643impl 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)]
562mod tests { 700mod 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}