diff options
-rw-r--r-- | crates/ra_ide/src/ssr.rs | 3 | ||||
-rw-r--r-- | crates/ra_ssr/src/lib.rs | 8 | ||||
-rw-r--r-- | crates/ra_ssr/src/matching.rs | 42 | ||||
-rw-r--r-- | crates/ra_ssr/src/resolving.rs | 18 | ||||
-rw-r--r-- | crates/ra_ssr/src/search.rs | 65 | ||||
-rw-r--r-- | crates/ra_ssr/src/tests.rs | 58 |
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 | ||
23 | pub(crate) struct ResolvedPath { | 24 | pub(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] | ||
832 | fn 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 | } | ||