diff options
Diffstat (limited to 'crates/ra_ssr/src/matching.rs')
-rw-r--r-- | crates/ra_ssr/src/matching.rs | 207 |
1 files changed, 158 insertions, 49 deletions
diff --git a/crates/ra_ssr/src/matching.rs b/crates/ra_ssr/src/matching.rs index 50b29eab2..74e15c631 100644 --- a/crates/ra_ssr/src/matching.rs +++ b/crates/ra_ssr/src/matching.rs | |||
@@ -2,8 +2,9 @@ | |||
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::{Constraint, NodeKind, 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; |
@@ -48,9 +49,11 @@ pub struct Match { | |||
48 | pub(crate) matched_node: SyntaxNode, | 49 | pub(crate) matched_node: SyntaxNode, |
49 | pub(crate) placeholder_values: FxHashMap<Var, PlaceholderMatch>, | 50 | pub(crate) placeholder_values: FxHashMap<Var, PlaceholderMatch>, |
50 | pub(crate) ignored_comments: Vec<ast::Comment>, | 51 | pub(crate) ignored_comments: Vec<ast::Comment>, |
51 | // 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, |
52 | // if/when we do replacement. | 53 | /// The depth of matched_node. |
53 | 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>, | ||
54 | } | 57 | } |
55 | 58 | ||
56 | /// Represents a `$var` in an SSR query. | 59 | /// Represents a `$var` in an SSR query. |
@@ -86,7 +89,7 @@ pub(crate) struct MatchFailed { | |||
86 | /// parent module, we don't populate nested matches. | 89 | /// parent module, we don't populate nested matches. |
87 | pub(crate) fn get_match( | 90 | pub(crate) fn get_match( |
88 | debug_active: bool, | 91 | debug_active: bool, |
89 | rule: &SsrRule, | 92 | rule: &ResolvedRule, |
90 | code: &SyntaxNode, | 93 | code: &SyntaxNode, |
91 | restrict_range: &Option<FileRange>, | 94 | restrict_range: &Option<FileRange>, |
92 | sema: &Semantics<ra_ide_db::RootDatabase>, | 95 | sema: &Semantics<ra_ide_db::RootDatabase>, |
@@ -102,7 +105,7 @@ struct Matcher<'db, 'sema> { | |||
102 | /// 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 |
103 | /// rejected. | 106 | /// rejected. |
104 | restrict_range: Option<FileRange>, | 107 | restrict_range: Option<FileRange>, |
105 | rule: &'sema SsrRule, | 108 | rule: &'sema ResolvedRule, |
106 | } | 109 | } |
107 | 110 | ||
108 | /// Which phase of matching we're currently performing. We do two phases because most attempted | 111 | /// Which phase of matching we're currently performing. We do two phases because most attempted |
@@ -117,26 +120,35 @@ enum Phase<'a> { | |||
117 | 120 | ||
118 | impl<'db, 'sema> Matcher<'db, 'sema> { | 121 | impl<'db, 'sema> Matcher<'db, 'sema> { |
119 | fn try_match( | 122 | fn try_match( |
120 | rule: &'sema SsrRule, | 123 | rule: &ResolvedRule, |
121 | code: &SyntaxNode, | 124 | code: &SyntaxNode, |
122 | restrict_range: &Option<FileRange>, | 125 | restrict_range: &Option<FileRange>, |
123 | sema: &'sema Semantics<'db, ra_ide_db::RootDatabase>, | 126 | sema: &'sema Semantics<'db, ra_ide_db::RootDatabase>, |
124 | ) -> Result<Match, MatchFailed> { | 127 | ) -> Result<Match, MatchFailed> { |
125 | let match_state = Matcher { sema, restrict_range: restrict_range.clone(), rule }; | 128 | let match_state = Matcher { sema, restrict_range: restrict_range.clone(), rule }; |
126 | let pattern_tree = rule.pattern.tree_for_kind(code.kind())?; | ||
127 | // 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. |
128 | match_state.attempt_match_node(&mut Phase::First, &pattern_tree, code)?; | 130 | match_state.attempt_match_node(&mut Phase::First, &rule.pattern.node, code)?; |
129 | match_state.validate_range(&sema.original_range(code))?; | 131 | match_state.validate_range(&sema.original_range(code))?; |
130 | let mut the_match = Match { | 132 | let mut the_match = Match { |
131 | range: sema.original_range(code), | 133 | range: sema.original_range(code), |
132 | matched_node: code.clone(), | 134 | matched_node: code.clone(), |
133 | placeholder_values: FxHashMap::default(), | 135 | placeholder_values: FxHashMap::default(), |
134 | ignored_comments: Vec::new(), | 136 | ignored_comments: Vec::new(), |
135 | template: rule.template.clone(), | 137 | rule_index: rule.index, |
138 | depth: 0, | ||
139 | rendered_template_paths: FxHashMap::default(), | ||
136 | }; | 140 | }; |
137 | // 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 |
138 | // 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. |
139 | match_state.attempt_match_node(&mut Phase::Second(&mut the_match), &pattern_tree, code)?; | 143 | match_state.attempt_match_node( |
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 | } | ||
140 | Ok(the_match) | 152 | Ok(the_match) |
141 | } | 153 | } |
142 | 154 | ||
@@ -177,10 +189,17 @@ impl<'db, 'sema> Matcher<'db, 'sema> { | |||
177 | } | 189 | } |
178 | return Ok(()); | 190 | return Ok(()); |
179 | } | 191 | } |
180 | // 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 | } | ||
181 | if pattern.kind() != code.kind() { | 200 | if pattern.kind() != code.kind() { |
182 | fail_match!( | 201 | fail_match!( |
183 | "Pattern had a `{}` ({:?}), code had `{}` ({:?})", | 202 | "Pattern had `{}` ({:?}), code had `{}` ({:?})", |
184 | pattern.text(), | 203 | pattern.text(), |
185 | pattern.kind(), | 204 | pattern.kind(), |
186 | code.text(), | 205 | code.text(), |
@@ -190,10 +209,11 @@ impl<'db, 'sema> Matcher<'db, 'sema> { | |||
190 | // 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 |
191 | // matching. | 210 | // matching. |
192 | match code.kind() { | 211 | match code.kind() { |
193 | SyntaxKind::RECORD_FIELD_LIST => { | 212 | SyntaxKind::RECORD_EXPR_FIELD_LIST => { |
194 | self.attempt_match_record_field_list(phase, pattern, code) | 213 | self.attempt_match_record_field_list(phase, pattern, code) |
195 | } | 214 | } |
196 | SyntaxKind::TOKEN_TREE => self.attempt_match_token_tree(phase, pattern, code), | 215 | SyntaxKind::TOKEN_TREE => self.attempt_match_token_tree(phase, pattern, code), |
216 | SyntaxKind::PATH => self.attempt_match_path(phase, pattern, code), | ||
197 | _ => self.attempt_match_node_children(phase, pattern, code), | 217 | _ => self.attempt_match_node_children(phase, pattern, code), |
198 | } | 218 | } |
199 | } | 219 | } |
@@ -310,6 +330,64 @@ impl<'db, 'sema> Matcher<'db, 'sema> { | |||
310 | Ok(()) | 330 | Ok(()) |
311 | } | 331 | } |
312 | 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 | |||
313 | /// 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 |
314 | /// them. | 392 | /// them. |
315 | fn attempt_match_record_field_list( | 393 | fn attempt_match_record_field_list( |
@@ -321,7 +399,7 @@ impl<'db, 'sema> Matcher<'db, 'sema> { | |||
321 | // Build a map keyed by field name. | 399 | // Build a map keyed by field name. |
322 | let mut fields_by_name = FxHashMap::default(); | 400 | let mut fields_by_name = FxHashMap::default(); |
323 | for child in code.children() { | 401 | for child in code.children() { |
324 | if let Some(record) = ast::RecordField::cast(child.clone()) { | 402 | if let Some(record) = ast::RecordExprField::cast(child.clone()) { |
325 | if let Some(name) = record.field_name() { | 403 | if let Some(name) = record.field_name() { |
326 | fields_by_name.insert(name.text().clone(), child.clone()); | 404 | fields_by_name.insert(name.text().clone(), child.clone()); |
327 | } | 405 | } |
@@ -443,9 +521,61 @@ impl<'db, 'sema> Matcher<'db, 'sema> { | |||
443 | Ok(()) | 521 | Ok(()) |
444 | } | 522 | } |
445 | 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 | |||
446 | fn get_placeholder(&self, element: &SyntaxElement) -> Option<&Placeholder> { | 555 | fn get_placeholder(&self, element: &SyntaxElement) -> Option<&Placeholder> { |
447 | only_ident(element.clone()) | 556 | only_ident(element.clone()).and_then(|ident| self.rule.get_placeholder(&ident)) |
448 | .and_then(|ident| self.rule.pattern.placeholders_by_stand_in.get(ident.text())) | 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(()) | ||
449 | } | 579 | } |
450 | } | 580 | } |
451 | 581 | ||
@@ -510,28 +640,6 @@ impl PlaceholderMatch { | |||
510 | } | 640 | } |
511 | } | 641 | } |
512 | 642 | ||
513 | impl SsrPattern { | ||
514 | pub(crate) fn tree_for_kind(&self, kind: SyntaxKind) -> Result<&SyntaxNode, MatchFailed> { | ||
515 | let (tree, kind_name) = if ast::Expr::can_cast(kind) { | ||
516 | (&self.expr, "expression") | ||
517 | } else if ast::TypeRef::can_cast(kind) { | ||
518 | (&self.type_ref, "type reference") | ||
519 | } else if ast::ModuleItem::can_cast(kind) { | ||
520 | (&self.item, "item") | ||
521 | } else if ast::Path::can_cast(kind) { | ||
522 | (&self.path, "path") | ||
523 | } else if ast::Pat::can_cast(kind) { | ||
524 | (&self.pattern, "pattern") | ||
525 | } else { | ||
526 | fail_match!("Matching nodes of kind {:?} is not supported", kind); | ||
527 | }; | ||
528 | match tree { | ||
529 | Some(tree) => Ok(tree), | ||
530 | None => fail_match!("Pattern cannot be parsed as a {}", kind_name), | ||
531 | } | ||
532 | } | ||
533 | } | ||
534 | |||
535 | impl NodeKind { | 643 | impl NodeKind { |
536 | fn matches(&self, node: &SyntaxNode) -> Result<(), MatchFailed> { | 644 | fn matches(&self, node: &SyntaxNode) -> Result<(), MatchFailed> { |
537 | let ok = match self { | 645 | let ok = match self { |
@@ -596,13 +704,12 @@ mod tests { | |||
596 | #[test] | 704 | #[test] |
597 | fn parse_match_replace() { | 705 | fn parse_match_replace() { |
598 | let rule: SsrRule = "foo($x) ==>> bar($x)".parse().unwrap(); | 706 | let rule: SsrRule = "foo($x) ==>> bar($x)".parse().unwrap(); |
599 | let input = "fn foo() {} fn main() { foo(1+2); }"; | 707 | let input = "fn foo() {} fn bar() {} fn main() { foo(1+2); }"; |
600 | 708 | ||
601 | use ra_db::fixture::WithFixture; | 709 | let (db, position, selections) = crate::tests::single_file(input); |
602 | let (db, file_id) = ra_ide_db::RootDatabase::with_single_file(input); | 710 | let mut match_finder = MatchFinder::in_context(&db, position, selections); |
603 | let mut match_finder = MatchFinder::new(&db); | 711 | match_finder.add_rule(rule).unwrap(); |
604 | match_finder.add_rule(rule); | 712 | let matches = match_finder.matches(); |
605 | let matches = match_finder.find_matches_in_file(file_id); | ||
606 | assert_eq!(matches.matches.len(), 1); | 713 | assert_eq!(matches.matches.len(), 1); |
607 | assert_eq!(matches.matches[0].matched_node.text(), "foo(1+2)"); | 714 | assert_eq!(matches.matches[0].matched_node.text(), "foo(1+2)"); |
608 | assert_eq!(matches.matches[0].placeholder_values.len(), 1); | 715 | assert_eq!(matches.matches[0].placeholder_values.len(), 1); |
@@ -615,9 +722,11 @@ mod tests { | |||
615 | "1+2" | 722 | "1+2" |
616 | ); | 723 | ); |
617 | 724 | ||
618 | 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]; | ||
619 | let mut after = input.to_string(); | 728 | let mut after = input.to_string(); |
620 | edit.apply(&mut after); | 729 | edit.edit.apply(&mut after); |
621 | assert_eq!(after, "fn foo() {} fn main() { bar(1+2); }"); | 730 | assert_eq!(after, "fn foo() {} fn bar() {} fn main() { bar(1+2); }"); |
622 | } | 731 | } |
623 | } | 732 | } |