aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_ssr/src/matching.rs
diff options
context:
space:
mode:
authorbors[bot] <26634292+bors[bot]@users.noreply.github.com>2020-07-24 13:46:55 +0100
committerGitHub <[email protected]>2020-07-24 13:46:55 +0100
commitc3defe2532ba6ffd12a13bcbc8fdeda037665efc (patch)
tree831bf4dd44ec83d927face4ba17e57dcdeab7fbe /crates/ra_ssr/src/matching.rs
parent0e5095d3cac11d4b569c6e1594bd07937556c812 (diff)
parent58680cb08ea535e1fb567416fa3466a744a01b99 (diff)
Merge #5518
5518: Use resolved paths in SSR rules r=matklad a=davidlattimore The main user-visible changes are: * SSR now matches paths based on whether they resolve to the same thing instead of whether they're written the same. * So `foo()` won't match `foo()` if it's a different function `foo()`, but will match `bar::foo()` if it's the same `foo`. * Paths in the replacement will now be rendered with appropriate qualification for their context. * For example `foo::Bar` will render as just `Bar` inside the module `foo`, but might render as `baz::foo::Bar` from elsewhere. * This means that all paths in the search pattern and replacement template must be able to be resolved. * It now also matters where you invoke SSR from, since paths are resolved relative to wherever that is. * Search now uses find-uses on paths to locate places to try matching. This means that when a path is present in the pattern, search will generally be pretty fast. * Function calls can now match method calls again, but this time only if they resolve to the same function. Co-authored-by: David Lattimore <[email protected]>
Diffstat (limited to 'crates/ra_ssr/src/matching.rs')
-rw-r--r--crates/ra_ssr/src/matching.rs203
1 files changed, 156 insertions, 47 deletions
diff --git a/crates/ra_ssr/src/matching.rs b/crates/ra_ssr/src/matching.rs
index 50b29eab2..4862622bd 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
4use crate::{ 4use crate::{
5 parsing::{Constraint, NodeKind, 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;
@@ -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.
87pub(crate) fn get_match( 90pub(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
118impl<'db, 'sema> Matcher<'db, 'sema> { 121impl<'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(),
@@ -194,6 +213,7 @@ impl<'db, 'sema> Matcher<'db, 'sema> {
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(
@@ -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
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(())
449 } 579 }
450} 580}
451 581
@@ -510,28 +640,6 @@ impl PlaceholderMatch {
510 } 640 }
511} 641}
512 642
513impl 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
535impl NodeKind { 643impl 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) = 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);
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}