Skip to content

Commit 3dac31f

Browse files
SSR: Allow function calls to match method calls
This differs from how this used to work before I removed it in that: a) It's only one direction. Function calls in the pattern can match method calls in the code, but not the other way around. b) We now check that the function call in the pattern resolves to the same function as the method call in the code. The lack of (b) was the reason I felt the need to remove the feature before.
1 parent 8d09ab8 commit 3dac31f

File tree

6 files changed

+169
-25
lines changed

6 files changed

+169
-25
lines changed

crates/ra_ide/src/ssr.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@ use ra_ssr::{MatchFinder, SsrError, SsrRule};
2121
// replacement occurs. For example if our replacement template is `foo::Bar` and we match some
2222
// code in the `foo` module, we'll insert just `Bar`.
2323
//
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+
//
2427
// Placeholders may be given constraints by writing them as `${<name>:<constraint1>:<constraint2>...}`.
2528
//
2629
// Supported constraints:

crates/ra_ssr/src/lib.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -202,8 +202,12 @@ impl<'db> MatchFinder<'db> {
202202
// For now we ignore rules that have a different kind than our node, otherwise
203203
// we get lots of noise. If at some point we add support for restricting rules
204204
// to a particular kind of thing (e.g. only match type references), then we can
205-
// relax this.
206-
if rule.pattern.node.kind() != node.kind() {
205+
// relax this. We special-case expressions, since function calls can match
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+
{
207211
continue;
208212
}
209213
out.push(MatchDebugInfo {

crates/ra_ssr/src/matching.rs

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -189,10 +189,17 @@ impl<'db, 'sema> Matcher<'db, 'sema> {
189189
}
190190
return Ok(());
191191
}
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+
}
193200
if pattern.kind() != code.kind() {
194201
fail_match!(
195-
"Pattern had a `{}` ({:?}), code had `{}` ({:?})",
202+
"Pattern had `{}` ({:?}), code had `{}` ({:?})",
196203
pattern.text(),
197204
pattern.kind(),
198205
code.text(),
@@ -514,6 +521,37 @@ impl<'db, 'sema> Matcher<'db, 'sema> {
514521
Ok(())
515522
}
516523

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+
517555
fn get_placeholder(&self, element: &SyntaxElement) -> Option<&Placeholder> {
518556
only_ident(element.clone()).and_then(|ident| self.rule.get_placeholder(&ident))
519557
}

crates/ra_ssr/src/resolving.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,12 @@ pub(crate) struct ResolvedPattern {
1818
pub(crate) node: SyntaxNode,
1919
// Paths in `node` that we've resolved.
2020
pub(crate) resolved_paths: FxHashMap<SyntaxNode, ResolvedPath>,
21+
pub(crate) ufcs_function_calls: FxHashMap<SyntaxNode, hir::Function>,
2122
}
2223

2324
pub(crate) struct ResolvedPath {
2425
pub(crate) resolution: hir::PathResolution,
26+
/// The depth of the ast::Path that was resolved within the pattern.
2527
pub(crate) depth: u32,
2628
}
2729

@@ -64,10 +66,26 @@ impl Resolver<'_, '_> {
6466
fn resolve_pattern_tree(&self, pattern: SyntaxNode) -> Result<ResolvedPattern, SsrError> {
6567
let mut resolved_paths = FxHashMap::default();
6668
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();
6784
Ok(ResolvedPattern {
6885
node: pattern,
6986
resolved_paths,
7087
placeholders_by_stand_in: self.placeholders_by_stand_in.clone(),
88+
ufcs_function_calls,
7189
})
7290
}
7391

crates/ra_ssr/src/search.rs

Lines changed: 44 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -46,35 +46,58 @@ impl<'db> MatchFinder<'db> {
4646
usage_cache: &mut UsageCache,
4747
matches_out: &mut Vec<Match>,
4848
) {
49-
if let Some(first_path) = pick_path_for_usages(pattern) {
50-
let definition: Definition = first_path.resolution.clone().into();
49+
if let Some(resolved_path) = pick_path_for_usages(pattern) {
50+
let definition: Definition = resolved_path.resolution.clone().into();
5151
for reference in self.find_usages(usage_cache, definition) {
52-
let file = self.sema.parse(reference.file_range.file_id);
53-
if let Some(path) = self.sema.find_node_at_offset_with_descend::<ast::Path>(
54-
file.syntax(),
55-
reference.file_range.range.start(),
56-
) {
57-
if let Some(node_to_match) = self
58-
.sema
59-
.ancestors_with_macros(path.syntax().clone())
60-
.skip(first_path.depth as usize)
61-
.next()
52+
if let Some(node_to_match) = self.find_node_to_match(resolved_path, reference) {
53+
if !is_search_permitted_ancestors(&node_to_match) {
54+
mark::hit!(use_declaration_with_braces);
55+
continue;
56+
}
57+
if let Ok(m) =
58+
matching::get_match(false, rule, &node_to_match, &None, &self.sema)
6259
{
63-
if !is_search_permitted_ancestors(&node_to_match) {
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-
}
60+
matches_out.push(m);
7261
}
7362
}
7463
}
7564
}
7665
}
7766

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+
78101
fn find_usages<'a>(
79102
&self,
80103
usage_cache: &'a mut UsageCache,

crates/ra_ssr/src/tests.rs

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -827,3 +827,61 @@ fn use_declaration_with_braces() {
827827
"]],
828828
)
829829
}
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+
}

0 commit comments

Comments
 (0)