aboutsummaryrefslogtreecommitdiff
path: root/crates/ssr/src
diff options
context:
space:
mode:
authorDavid Lattimore <[email protected]>2020-08-05 22:26:28 +0100
committerDavid Lattimore <[email protected]>2020-08-14 12:26:25 +0100
commita4a504e1328111c184603ddc0b2c113ad5a5c555 (patch)
treebb6b430c8b393dfb4fd92df21b5b52defeda1b54 /crates/ssr/src
parentc84f98385a28eeb7595f38b7cfaf861a6e06f4ea (diff)
SSR: Explicitly autoderef and ref placeholders as needed
Structured search replace now inserts *, & and &mut in the replacement to match any auto[de]ref in the matched code.
Diffstat (limited to 'crates/ssr/src')
-rw-r--r--crates/ssr/src/lib.rs5
-rw-r--r--crates/ssr/src/matching.rs90
-rw-r--r--crates/ssr/src/replacing.rs46
-rw-r--r--crates/ssr/src/tests.rs106
4 files changed, 222 insertions, 25 deletions
diff --git a/crates/ssr/src/lib.rs b/crates/ssr/src/lib.rs
index 292bd5b9a..ba669fd56 100644
--- a/crates/ssr/src/lib.rs
+++ b/crates/ssr/src/lib.rs
@@ -21,7 +21,10 @@
21// code in the `foo` module, we'll insert just `Bar`. 21// code in the `foo` module, we'll insert just `Bar`.
22// 22//
23// Inherent method calls should generally be written in UFCS form. e.g. `foo::Bar::baz($s, $a)` will 23// Inherent method calls should generally be written in UFCS form. e.g. `foo::Bar::baz($s, $a)` will
24// match `$s.baz($a)`, provided the method call `baz` resolves to the method `foo::Bar::baz`. 24// match `$s.baz($a)`, provided the method call `baz` resolves to the method `foo::Bar::baz`. When a
25// placeholder is the receiver of a method call in the search pattern (e.g. `$s.foo()`), but not in
26// the replacement template (e.g. `bar($s)`), then *, & and &mut will be added as needed to mirror
27// whatever autoderef and autoref was happening implicitly in the matched code.
25// 28//
26// The scope of the search / replace will be restricted to the current selection if any, otherwise 29// The scope of the search / replace will be restricted to the current selection if any, otherwise
27// it will apply to the whole workspace. 30// it will apply to the whole workspace.
diff --git a/crates/ssr/src/matching.rs b/crates/ssr/src/matching.rs
index 7f0b5061e..8bb5ced90 100644
--- a/crates/ssr/src/matching.rs
+++ b/crates/ssr/src/matching.rs
@@ -65,6 +65,10 @@ pub(crate) struct PlaceholderMatch {
65 pub(crate) range: FileRange, 65 pub(crate) range: FileRange,
66 /// More matches, found within `node`. 66 /// More matches, found within `node`.
67 pub(crate) inner_matches: SsrMatches, 67 pub(crate) inner_matches: SsrMatches,
68 /// How many times the code that the placeholder matched needed to be dereferenced. Will only be
69 /// non-zero if the placeholder matched to the receiver of a method call.
70 pub(crate) autoderef_count: usize,
71 pub(crate) autoref_kind: ast::SelfParamKind,
68} 72}
69 73
70#[derive(Debug)] 74#[derive(Debug)]
@@ -169,7 +173,7 @@ impl<'db, 'sema> Matcher<'db, 'sema> {
169 code: &SyntaxNode, 173 code: &SyntaxNode,
170 ) -> Result<(), MatchFailed> { 174 ) -> Result<(), MatchFailed> {
171 // Handle placeholders. 175 // Handle placeholders.
172 if let Some(placeholder) = self.get_placeholder(&SyntaxElement::Node(pattern.clone())) { 176 if let Some(placeholder) = self.get_placeholder_for_node(pattern) {
173 for constraint in &placeholder.constraints { 177 for constraint in &placeholder.constraints {
174 self.check_constraint(constraint, code)?; 178 self.check_constraint(constraint, code)?;
175 } 179 }
@@ -178,9 +182,10 @@ impl<'db, 'sema> Matcher<'db, 'sema> {
178 // We validated the range for the node when we started the match, so the placeholder 182 // We validated the range for the node when we started the match, so the placeholder
179 // probably can't fail range validation, but just to be safe... 183 // probably can't fail range validation, but just to be safe...
180 self.validate_range(&original_range)?; 184 self.validate_range(&original_range)?;
181 matches_out 185 matches_out.placeholder_values.insert(
182 .placeholder_values 186 placeholder.ident.clone(),
183 .insert(placeholder.ident.clone(), PlaceholderMatch::new(code, original_range)); 187 PlaceholderMatch::new(Some(code), original_range),
188 );
184 } 189 }
185 return Ok(()); 190 return Ok(());
186 } 191 }
@@ -531,18 +536,40 @@ impl<'db, 'sema> Matcher<'db, 'sema> {
531 if pattern_ufcs.function != code_resolved_function { 536 if pattern_ufcs.function != code_resolved_function {
532 fail_match!("Method call resolved to a different function"); 537 fail_match!("Method call resolved to a different function");
533 } 538 }
534 if code_resolved_function.has_self_param(self.sema.db) {
535 if let (Some(pattern_type), Some(expr)) = (&pattern_ufcs.qualifier_type, &code.expr()) {
536 self.check_expr_type(pattern_type, expr)?;
537 }
538 }
539 // Check arguments. 539 // Check arguments.
540 let mut pattern_args = pattern_ufcs 540 let mut pattern_args = pattern_ufcs
541 .call_expr 541 .call_expr
542 .arg_list() 542 .arg_list()
543 .ok_or_else(|| match_error!("Pattern function call has no args"))? 543 .ok_or_else(|| match_error!("Pattern function call has no args"))?
544 .args(); 544 .args();
545 self.attempt_match_opt(phase, pattern_args.next(), code.expr())?; 545 // If the function we're calling takes a self parameter, then we store additional
546 // information on the placeholder match about autoderef and autoref. This allows us to use
547 // the placeholder in a context where autoderef and autoref don't apply.
548 if code_resolved_function.has_self_param(self.sema.db) {
549 if let (Some(pattern_type), Some(expr)) = (&pattern_ufcs.qualifier_type, &code.expr()) {
550 let deref_count = self.check_expr_type(pattern_type, expr)?;
551 let pattern_receiver = pattern_args.next();
552 self.attempt_match_opt(phase, pattern_receiver.clone(), code.expr())?;
553 if let Phase::Second(match_out) = phase {
554 if let Some(placeholder_value) = pattern_receiver
555 .and_then(|n| self.get_placeholder_for_node(n.syntax()))
556 .and_then(|placeholder| {
557 match_out.placeholder_values.get_mut(&placeholder.ident)
558 })
559 {
560 placeholder_value.autoderef_count = deref_count;
561 placeholder_value.autoref_kind = self
562 .sema
563 .resolve_method_call_as_callable(code)
564 .and_then(|callable| callable.receiver_param(self.sema.db))
565 .map(|self_param| self_param.kind())
566 .unwrap_or(ast::SelfParamKind::Owned);
567 }
568 }
569 }
570 } else {
571 self.attempt_match_opt(phase, pattern_args.next(), code.expr())?;
572 }
546 let mut code_args = 573 let mut code_args =
547 code.arg_list().ok_or_else(|| match_error!("Code method call has no args"))?.args(); 574 code.arg_list().ok_or_else(|| match_error!("Code method call has no args"))?.args();
548 loop { 575 loop {
@@ -570,26 +597,35 @@ impl<'db, 'sema> Matcher<'db, 'sema> {
570 self.attempt_match_node_children(phase, pattern_ufcs.call_expr.syntax(), code.syntax()) 597 self.attempt_match_node_children(phase, pattern_ufcs.call_expr.syntax(), code.syntax())
571 } 598 }
572 599
600 /// Verifies that `expr` matches `pattern_type`, possibly after dereferencing some number of
601 /// times. Returns the number of times it needed to be dereferenced.
573 fn check_expr_type( 602 fn check_expr_type(
574 &self, 603 &self,
575 pattern_type: &hir::Type, 604 pattern_type: &hir::Type,
576 expr: &ast::Expr, 605 expr: &ast::Expr,
577 ) -> Result<(), MatchFailed> { 606 ) -> Result<usize, MatchFailed> {
578 use hir::HirDisplay; 607 use hir::HirDisplay;
579 let code_type = self.sema.type_of_expr(&expr).ok_or_else(|| { 608 let code_type = self.sema.type_of_expr(&expr).ok_or_else(|| {
580 match_error!("Failed to get receiver type for `{}`", expr.syntax().text()) 609 match_error!("Failed to get receiver type for `{}`", expr.syntax().text())
581 })?; 610 })?;
582 if !code_type 611 // Temporary needed to make the borrow checker happy.
612 let res = code_type
583 .autoderef(self.sema.db) 613 .autoderef(self.sema.db)
584 .any(|deref_code_type| *pattern_type == deref_code_type) 614 .enumerate()
585 { 615 .find(|(_, deref_code_type)| pattern_type == deref_code_type)
586 fail_match!( 616 .map(|(count, _)| count)
587 "Pattern type `{}` didn't match code type `{}`", 617 .ok_or_else(|| {
588 pattern_type.display(self.sema.db), 618 match_error!(
589 code_type.display(self.sema.db) 619 "Pattern type `{}` didn't match code type `{}`",
590 ); 620 pattern_type.display(self.sema.db),
591 } 621 code_type.display(self.sema.db)
592 Ok(()) 622 )
623 });
624 res
625 }
626
627 fn get_placeholder_for_node(&self, node: &SyntaxNode) -> Option<&Placeholder> {
628 self.get_placeholder(&SyntaxElement::Node(node.clone()))
593 } 629 }
594 630
595 fn get_placeholder(&self, element: &SyntaxElement) -> Option<&Placeholder> { 631 fn get_placeholder(&self, element: &SyntaxElement) -> Option<&Placeholder> {
@@ -671,12 +707,18 @@ fn recording_match_fail_reasons() -> bool {
671} 707}
672 708
673impl PlaceholderMatch { 709impl PlaceholderMatch {
674 fn new(node: &SyntaxNode, range: FileRange) -> Self { 710 fn new(node: Option<&SyntaxNode>, range: FileRange) -> Self {
675 Self { node: Some(node.clone()), range, inner_matches: SsrMatches::default() } 711 Self {
712 node: node.cloned(),
713 range,
714 inner_matches: SsrMatches::default(),
715 autoderef_count: 0,
716 autoref_kind: ast::SelfParamKind::Owned,
717 }
676 } 718 }
677 719
678 fn from_range(range: FileRange) -> Self { 720 fn from_range(range: FileRange) -> Self {
679 Self { node: None, range, inner_matches: SsrMatches::default() } 721 Self::new(None, range)
680 } 722 }
681} 723}
682 724
diff --git a/crates/ssr/src/replacing.rs b/crates/ssr/src/replacing.rs
index 496a21e6e..21d0aa8a8 100644
--- a/crates/ssr/src/replacing.rs
+++ b/crates/ssr/src/replacing.rs
@@ -118,6 +118,27 @@ impl ReplacementRenderer<'_> {
118 let range = &placeholder_value.range.range; 118 let range = &placeholder_value.range.range;
119 let mut matched_text = 119 let mut matched_text =
120 self.file_src[usize::from(range.start())..usize::from(range.end())].to_owned(); 120 self.file_src[usize::from(range.start())..usize::from(range.end())].to_owned();
121 // If a method call is performed directly on the placeholder, then autoderef and
122 // autoref will apply, so we can just substitute whatever the placeholder matched to
123 // directly. If we're not applying a method call, then we need to add explicitly
124 // deref and ref in order to match whatever was being done implicitly at the match
125 // site.
126 if !token_is_method_call_receiver(token)
127 && (placeholder_value.autoderef_count > 0
128 || placeholder_value.autoref_kind != ast::SelfParamKind::Owned)
129 {
130 let ref_kind = match placeholder_value.autoref_kind {
131 ast::SelfParamKind::Owned => "",
132 ast::SelfParamKind::Ref => "&",
133 ast::SelfParamKind::MutRef => "&mut ",
134 };
135 matched_text = format!(
136 "{}{}{}",
137 ref_kind,
138 "*".repeat(placeholder_value.autoderef_count),
139 matched_text
140 );
141 }
121 let edit = matches_to_edit_at_offset( 142 let edit = matches_to_edit_at_offset(
122 &placeholder_value.inner_matches, 143 &placeholder_value.inner_matches,
123 self.file_src, 144 self.file_src,
@@ -178,6 +199,31 @@ impl ReplacementRenderer<'_> {
178 } 199 }
179} 200}
180 201
202/// Returns whether token is the receiver of a method call. Note, being within the receiver of a
203/// method call doesn't count. e.g. if the token is `$a`, then `$a.foo()` will return true, while
204/// `($a + $b).foo()` or `x.foo($a)` will return false.
205fn token_is_method_call_receiver(token: &SyntaxToken) -> bool {
206 use syntax::ast::AstNode;
207 // Find the first method call among the ancestors of `token`, then check if the only token
208 // within the receiver is `token`.
209 if let Some(receiver) = token
210 .ancestors()
211 .find(|node| node.kind() == SyntaxKind::METHOD_CALL_EXPR)
212 .and_then(|node| ast::MethodCallExpr::cast(node).unwrap().expr())
213 {
214 let mut tokens = receiver.syntax().descendants_with_tokens().filter_map(|node_or_token| {
215 match node_or_token {
216 SyntaxElement::Token(t) => Some(t),
217 _ => None,
218 }
219 });
220 if let (Some(only_token), None) = (tokens.next(), tokens.next()) {
221 return only_token == *token;
222 }
223 }
224 false
225}
226
181fn parse_as_kind(code: &str, kind: SyntaxKind) -> Option<SyntaxNode> { 227fn parse_as_kind(code: &str, kind: SyntaxKind) -> Option<SyntaxNode> {
182 use syntax::ast::AstNode; 228 use syntax::ast::AstNode;
183 if ast::Expr::can_cast(kind) { 229 if ast::Expr::can_cast(kind) {
diff --git a/crates/ssr/src/tests.rs b/crates/ssr/src/tests.rs
index 65cd38753..0ad3512ba 100644
--- a/crates/ssr/src/tests.rs
+++ b/crates/ssr/src/tests.rs
@@ -1172,3 +1172,109 @@ fn match_trait_method_call() {
1172 assert_matches("Bar::foo($a, $b)", code, &["v1.foo(1)", "Bar::foo(&v1, 3)", "v1_ref.foo(5)"]); 1172 assert_matches("Bar::foo($a, $b)", code, &["v1.foo(1)", "Bar::foo(&v1, 3)", "v1_ref.foo(5)"]);
1173 assert_matches("Bar2::foo($a, $b)", code, &["v2.foo(2)", "Bar2::foo(&v2, 4)", "v2_ref.foo(6)"]); 1173 assert_matches("Bar2::foo($a, $b)", code, &["v2.foo(2)", "Bar2::foo(&v2, 4)", "v2_ref.foo(6)"]);
1174} 1174}
1175
1176#[test]
1177fn replace_autoref_autoderef_capture() {
1178 // Here we have several calls to `$a.foo()`. In the first case autoref is applied, in the
1179 // second, we already have a reference, so it isn't. When $a is used in a context where autoref
1180 // doesn't apply, we need to prefix it with `&`. Finally, we have some cases where autoderef
1181 // needs to be applied.
1182 let code = r#"
1183 struct Foo {}
1184 impl Foo {
1185 fn foo(&self) {}
1186 fn foo2(&self) {}
1187 }
1188 fn bar(_: &Foo) {}
1189 fn main() {
1190 let f = Foo {};
1191 let fr = &f;
1192 let fr2 = &fr;
1193 let fr3 = &fr2;
1194 f.foo();
1195 fr.foo();
1196 fr2.foo();
1197 fr3.foo();
1198 }
1199 "#;
1200 assert_ssr_transform(
1201 "Foo::foo($a) ==>> bar($a)",
1202 code,
1203 expect![[r#"
1204 struct Foo {}
1205 impl Foo {
1206 fn foo(&self) {}
1207 fn foo2(&self) {}
1208 }
1209 fn bar(_: &Foo) {}
1210 fn main() {
1211 let f = Foo {};
1212 let fr = &f;
1213 let fr2 = &fr;
1214 let fr3 = &fr2;
1215 bar(&f);
1216 bar(&*fr);
1217 bar(&**fr2);
1218 bar(&***fr3);
1219 }
1220 "#]],
1221 );
1222 // If the placeholder is used as the receiver of another method call, then we don't need to
1223 // explicitly autoderef or autoref.
1224 assert_ssr_transform(
1225 "Foo::foo($a) ==>> $a.foo2()",
1226 code,
1227 expect![[r#"
1228 struct Foo {}
1229 impl Foo {
1230 fn foo(&self) {}
1231 fn foo2(&self) {}
1232 }
1233 fn bar(_: &Foo) {}
1234 fn main() {
1235 let f = Foo {};
1236 let fr = &f;
1237 let fr2 = &fr;
1238 let fr3 = &fr2;
1239 f.foo2();
1240 fr.foo2();
1241 fr2.foo2();
1242 fr3.foo2();
1243 }
1244 "#]],
1245 );
1246}
1247
1248#[test]
1249fn replace_autoref_mut() {
1250 let code = r#"
1251 struct Foo {}
1252 impl Foo {
1253 fn foo(&mut self) {}
1254 }
1255 fn bar(_: &mut Foo) {}
1256 fn main() {
1257 let mut f = Foo {};
1258 f.foo();
1259 let fr = &mut f;
1260 fr.foo();
1261 }
1262 "#;
1263 assert_ssr_transform(
1264 "Foo::foo($a) ==>> bar($a)",
1265 code,
1266 expect![[r#"
1267 struct Foo {}
1268 impl Foo {
1269 fn foo(&mut self) {}
1270 }
1271 fn bar(_: &mut Foo) {}
1272 fn main() {
1273 let mut f = Foo {};
1274 bar(&mut f);
1275 let fr = &mut f;
1276 bar(&mut *fr);
1277 }
1278 "#]],
1279 );
1280}