aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--crates/ra_ide/src/ssr.rs3
-rw-r--r--crates/ra_ssr/src/lib.rs8
-rw-r--r--crates/ra_ssr/src/matching.rs42
-rw-r--r--crates/ra_ssr/src/resolving.rs18
-rw-r--r--crates/ra_ssr/src/search.rs65
-rw-r--r--crates/ra_ssr/src/tests.rs58
6 files changed, 169 insertions, 25 deletions
diff --git a/crates/ra_ide/src/ssr.rs b/crates/ra_ide/src/ssr.rs
index 2f40bac08..95d8f79b8 100644
--- a/crates/ra_ide/src/ssr.rs
+++ b/crates/ra_ide/src/ssr.rs
@@ -21,6 +21,9 @@ use ra_ssr::{MatchFinder, SsrError, SsrRule};
21// replacement occurs. For example if our replacement template is `foo::Bar` and we match some 21// replacement occurs. For example if our replacement template is `foo::Bar` and we match some
22// code in the `foo` module, we'll insert just `Bar`. 22// code in the `foo` module, we'll insert just `Bar`.
23// 23//
24// Method calls should generally be written in UFCS form. e.g. `foo::Bar::baz($s, $a)` will match
25// `$s.baz($a)`, provided the method call `baz` resolves to the method `foo::Bar::baz`.
26//
24// Placeholders may be given constraints by writing them as `${<name>:<constraint1>:<constraint2>...}`. 27// Placeholders may be given constraints by writing them as `${<name>:<constraint1>:<constraint2>...}`.
25// 28//
26// Supported constraints: 29// Supported constraints:
diff --git a/crates/ra_ssr/src/lib.rs b/crates/ra_ssr/src/lib.rs
index 5a71d4f31..2fb326b45 100644
--- a/crates/ra_ssr/src/lib.rs
+++ b/crates/ra_ssr/src/lib.rs
@@ -202,8 +202,12 @@ impl<'db> MatchFinder<'db> {
202 // For now we ignore rules that have a different kind than our node, otherwise 202 // For now we ignore rules that have a different kind than our node, otherwise
203 // we get lots of noise. If at some point we add support for restricting rules 203 // we get lots of noise. If at some point we add support for restricting rules
204 // to a particular kind of thing (e.g. only match type references), then we can 204 // to a particular kind of thing (e.g. only match type references), then we can
205 // relax this. 205 // relax this. We special-case expressions, since function calls can match
206 if rule.pattern.node.kind() != node.kind() { 206 // method calls.
207 if rule.pattern.node.kind() != node.kind()
208 && !(ast::Expr::can_cast(rule.pattern.node.kind())
209 && ast::Expr::can_cast(node.kind()))
210 {
207 continue; 211 continue;
208 } 212 }
209 out.push(MatchDebugInfo { 213 out.push(MatchDebugInfo {
diff --git a/crates/ra_ssr/src/matching.rs b/crates/ra_ssr/src/matching.rs
index f3cc60c29..4862622bd 100644
--- a/crates/ra_ssr/src/matching.rs
+++ b/crates/ra_ssr/src/matching.rs
@@ -189,10 +189,17 @@ impl<'db, 'sema> Matcher<'db, 'sema> {
189 } 189 }
190 return Ok(()); 190 return Ok(());
191 } 191 }
192 // 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 }
193 if pattern.kind() != code.kind() { 200 if pattern.kind() != code.kind() {
194 fail_match!( 201 fail_match!(
195 "Pattern had a `{}` ({:?}), code had `{}` ({:?})", 202 "Pattern had `{}` ({:?}), code had `{}` ({:?})",
196 pattern.text(), 203 pattern.text(),
197 pattern.kind(), 204 pattern.kind(),
198 code.text(), 205 code.text(),
@@ -514,6 +521,37 @@ impl<'db, 'sema> Matcher<'db, 'sema> {
514 Ok(()) 521 Ok(())
515 } 522 }
516 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
517 fn get_placeholder(&self, element: &SyntaxElement) -> Option<&Placeholder> { 555 fn get_placeholder(&self, element: &SyntaxElement) -> Option<&Placeholder> {
518 only_ident(element.clone()).and_then(|ident| self.rule.get_placeholder(&ident)) 556 only_ident(element.clone()).and_then(|ident| self.rule.get_placeholder(&ident))
519 } 557 }
diff --git a/crates/ra_ssr/src/resolving.rs b/crates/ra_ssr/src/resolving.rs
index 63fd217ce..75f556785 100644
--- a/crates/ra_ssr/src/resolving.rs
+++ b/crates/ra_ssr/src/resolving.rs
@@ -18,10 +18,12 @@ pub(crate) struct ResolvedPattern {
18 pub(crate) node: SyntaxNode, 18 pub(crate) node: SyntaxNode,
19 // Paths in `node` that we've resolved. 19 // Paths in `node` that we've resolved.
20 pub(crate) resolved_paths: FxHashMap<SyntaxNode, ResolvedPath>, 20 pub(crate) resolved_paths: FxHashMap<SyntaxNode, ResolvedPath>,
21 pub(crate) ufcs_function_calls: FxHashMap<SyntaxNode, hir::Function>,
21} 22}
22 23
23pub(crate) struct ResolvedPath { 24pub(crate) struct ResolvedPath {
24 pub(crate) resolution: hir::PathResolution, 25 pub(crate) resolution: hir::PathResolution,
26 /// The depth of the ast::Path that was resolved within the pattern.
25 pub(crate) depth: u32, 27 pub(crate) depth: u32,
26} 28}
27 29
@@ -64,10 +66,26 @@ impl Resolver<'_, '_> {
64 fn resolve_pattern_tree(&self, pattern: SyntaxNode) -> Result<ResolvedPattern, SsrError> { 66 fn resolve_pattern_tree(&self, pattern: SyntaxNode) -> Result<ResolvedPattern, SsrError> {
65 let mut resolved_paths = FxHashMap::default(); 67 let mut resolved_paths = FxHashMap::default();
66 self.resolve(pattern.clone(), 0, &mut resolved_paths)?; 68 self.resolve(pattern.clone(), 0, &mut resolved_paths)?;
69 let ufcs_function_calls = resolved_paths
70 .iter()
71 .filter_map(|(path_node, resolved)| {
72 if let Some(grandparent) = path_node.parent().and_then(|parent| parent.parent()) {
73 if grandparent.kind() == SyntaxKind::CALL_EXPR {
74 if let hir::PathResolution::AssocItem(hir::AssocItem::Function(function)) =
75 &resolved.resolution
76 {
77 return Some((grandparent, *function));
78 }
79 }
80 }
81 None
82 })
83 .collect();
67 Ok(ResolvedPattern { 84 Ok(ResolvedPattern {
68 node: pattern, 85 node: pattern,
69 resolved_paths, 86 resolved_paths,
70 placeholders_by_stand_in: self.placeholders_by_stand_in.clone(), 87 placeholders_by_stand_in: self.placeholders_by_stand_in.clone(),
88 ufcs_function_calls,
71 }) 89 })
72 } 90 }
73 91
diff --git a/crates/ra_ssr/src/search.rs b/crates/ra_ssr/src/search.rs
index 141e7d026..bcf0f0468 100644
--- a/crates/ra_ssr/src/search.rs
+++ b/crates/ra_ssr/src/search.rs
@@ -46,35 +46,58 @@ impl<'db> MatchFinder<'db> {
46 usage_cache: &mut UsageCache, 46 usage_cache: &mut UsageCache,
47 matches_out: &mut Vec<Match>, 47 matches_out: &mut Vec<Match>,
48 ) { 48 ) {
49 if let Some(first_path) = pick_path_for_usages(pattern) { 49 if let Some(resolved_path) = pick_path_for_usages(pattern) {
50 let definition: Definition = first_path.resolution.clone().into(); 50 let definition: Definition = resolved_path.resolution.clone().into();
51 for reference in self.find_usages(usage_cache, definition) { 51 for reference in self.find_usages(usage_cache, definition) {
52 let file = self.sema.parse(reference.file_range.file_id); 52 if let Some(node_to_match) = self.find_node_to_match(resolved_path, reference) {
53 if let Some(path) = self.sema.find_node_at_offset_with_descend::<ast::Path>( 53 if !is_search_permitted_ancestors(&node_to_match) {
54 file.syntax(), 54 mark::hit!(use_declaration_with_braces);
55 reference.file_range.range.start(), 55 continue;
56 ) { 56 }
57 if let Some(node_to_match) = self 57 if let Ok(m) =
58 .sema 58 matching::get_match(false, rule, &node_to_match, &None, &self.sema)
59 .ancestors_with_macros(path.syntax().clone())
60 .skip(first_path.depth as usize)
61 .next()
62 { 59 {
63 if !is_search_permitted_ancestors(&node_to_match) { 60 matches_out.push(m);
64 mark::hit!(use_declaration_with_braces);
65 continue;
66 }
67 if let Ok(m) =
68 matching::get_match(false, rule, &node_to_match, &None, &self.sema)
69 {
70 matches_out.push(m);
71 }
72 } 61 }
73 } 62 }
74 } 63 }
75 } 64 }
76 } 65 }
77 66
67 fn find_node_to_match(
68 &self,
69 resolved_path: &ResolvedPath,
70 reference: &Reference,
71 ) -> Option<SyntaxNode> {
72 let file = self.sema.parse(reference.file_range.file_id);
73 let depth = resolved_path.depth as usize;
74 let offset = reference.file_range.range.start();
75 if let Some(path) =
76 self.sema.find_node_at_offset_with_descend::<ast::Path>(file.syntax(), offset)
77 {
78 self.sema.ancestors_with_macros(path.syntax().clone()).skip(depth).next()
79 } else if let Some(path) =
80 self.sema.find_node_at_offset_with_descend::<ast::MethodCallExpr>(file.syntax(), offset)
81 {
82 // If the pattern contained a path and we found a reference to that path that wasn't
83 // itself a path, but was a method call, then we need to adjust how far up to try
84 // matching by how deep the path was within a CallExpr. The structure would have been
85 // CallExpr, PathExpr, Path - i.e. a depth offset of 2. We don't need to check if the
86 // path was part of a CallExpr because if it wasn't then all that will happen is we'll
87 // fail to match, which is the desired behavior.
88 const PATH_DEPTH_IN_CALL_EXPR: usize = 2;
89 if depth < PATH_DEPTH_IN_CALL_EXPR {
90 return None;
91 }
92 self.sema
93 .ancestors_with_macros(path.syntax().clone())
94 .skip(depth - PATH_DEPTH_IN_CALL_EXPR)
95 .next()
96 } else {
97 None
98 }
99 }
100
78 fn find_usages<'a>( 101 fn find_usages<'a>(
79 &self, 102 &self,
80 usage_cache: &'a mut UsageCache, 103 usage_cache: &'a mut UsageCache,
diff --git a/crates/ra_ssr/src/tests.rs b/crates/ra_ssr/src/tests.rs
index f564c6129..b38807c0f 100644
--- a/crates/ra_ssr/src/tests.rs
+++ b/crates/ra_ssr/src/tests.rs
@@ -827,3 +827,61 @@ fn use_declaration_with_braces() {
827 "]], 827 "]],
828 ) 828 )
829} 829}
830
831#[test]
832fn ufcs_matches_method_call() {
833 let code = r#"
834 struct Foo {}
835 impl Foo {
836 fn new(_: i32) -> Foo { Foo {} }
837 fn do_stuff(&self, _: i32) {}
838 }
839 struct Bar {}
840 impl Bar {
841 fn new(_: i32) -> Bar { Bar {} }
842 fn do_stuff(&self, v: i32) {}
843 }
844 fn main() {
845 let b = Bar {};
846 let f = Foo {};
847 b.do_stuff(1);
848 f.do_stuff(2);
849 Foo::new(4).do_stuff(3);
850 // Too many / too few args - should never match
851 f.do_stuff(2, 10);
852 f.do_stuff();
853 }
854 "#;
855 assert_matches("Foo::do_stuff($a, $b)", code, &["f.do_stuff(2)", "Foo::new(4).do_stuff(3)"]);
856 // The arguments needs special handling in the case of a function call matching a method call
857 // and the first argument is different.
858 assert_matches("Foo::do_stuff($a, 2)", code, &["f.do_stuff(2)"]);
859 assert_matches("Foo::do_stuff(Foo::new(4), $b)", code, &["Foo::new(4).do_stuff(3)"]);
860
861 assert_ssr_transform(
862 "Foo::do_stuff(Foo::new($a), $b) ==>> Bar::new($b).do_stuff($a)",
863 code,
864 expect![[r#"
865 struct Foo {}
866 impl Foo {
867 fn new(_: i32) -> Foo { Foo {} }
868 fn do_stuff(&self, _: i32) {}
869 }
870 struct Bar {}
871 impl Bar {
872 fn new(_: i32) -> Bar { Bar {} }
873 fn do_stuff(&self, v: i32) {}
874 }
875 fn main() {
876 let b = Bar {};
877 let f = Foo {};
878 b.do_stuff(1);
879 f.do_stuff(2);
880 Bar::new(3).do_stuff(4);
881 // Too many / too few args - should never match
882 f.do_stuff(2, 10);
883 f.do_stuff();
884 }
885 "#]],
886 );
887}