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/src/replacing.rs | |
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/src/replacing.rs')
-rw-r--r-- | crates/ssr/src/replacing.rs | 46 |
1 files changed, 46 insertions, 0 deletions
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) { |