diff options
author | David Lattimore <[email protected]> | 2020-08-05 22:26:28 +0100 |
---|---|---|
committer | David Lattimore <[email protected]> | 2020-08-14 12:26:25 +0100 |
commit | a4a504e1328111c184603ddc0b2c113ad5a5c555 (patch) | |
tree | bb6b430c8b393dfb4fd92df21b5b52defeda1b54 /crates/ssr | |
parent | c84f98385a28eeb7595f38b7cfaf861a6e06f4ea (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')
-rw-r--r-- | crates/ssr/src/lib.rs | 5 | ||||
-rw-r--r-- | crates/ssr/src/matching.rs | 90 | ||||
-rw-r--r-- | crates/ssr/src/replacing.rs | 46 | ||||
-rw-r--r-- | crates/ssr/src/tests.rs | 106 |
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 | ||
673 | impl PlaceholderMatch { | 709 | impl 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. | ||
205 | fn 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 | |||
181 | fn parse_as_kind(code: &str, kind: SyntaxKind) -> Option<SyntaxNode> { | 227 | fn 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] | ||
1177 | fn 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] | ||
1249 | fn 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 | } | ||