diff options
author | bors[bot] <26634292+bors[bot]@users.noreply.github.com> | 2020-08-18 11:52:27 +0100 |
---|---|---|
committer | GitHub <[email protected]> | 2020-08-18 11:52:27 +0100 |
commit | a95c5e21219cd6569e2630637fd4268e24c0ca03 (patch) | |
tree | 8f8d7348d4916479396abb58adc6891163cb9cbc /crates/ssr/src/replacing.rs | |
parent | 5d97db8d480d58559e7490e1efdb0e45d1a8832f (diff) | |
parent | 29e6238cb7330f7d29f33ff03a4ccc0a0cec9f4d (diff) |
Merge #5758
5758: SSR: Explicitly autoderef and ref placeholders as needed r=matklad a=davidlattimore
Structural search replace now inserts *, & and &mut in the replacement to match any auto[de]ref in the matched code.
e.g. `$a.foo() ==>> bar($a)` might convert `x.foo()` to `bar(&mut x)`
Co-authored-by: David Lattimore <[email protected]>
Diffstat (limited to 'crates/ssr/src/replacing.rs')
-rw-r--r-- | crates/ssr/src/replacing.rs | 50 |
1 files changed, 48 insertions, 2 deletions
diff --git a/crates/ssr/src/replacing.rs b/crates/ssr/src/replacing.rs index 8f8fe6149..29284e3f1 100644 --- a/crates/ssr/src/replacing.rs +++ b/crates/ssr/src/replacing.rs | |||
@@ -1,10 +1,11 @@ | |||
1 | //! Code for applying replacement templates for matches that have previously been found. | 1 | //! Code for applying replacement templates for matches that have previously been found. |
2 | 2 | ||
3 | use crate::matching::Var; | ||
4 | use crate::{resolving::ResolvedRule, Match, SsrMatches}; | 3 | use crate::{resolving::ResolvedRule, Match, SsrMatches}; |
4 | use itertools::Itertools; | ||
5 | use rustc_hash::{FxHashMap, FxHashSet}; | 5 | use rustc_hash::{FxHashMap, FxHashSet}; |
6 | use syntax::ast::{self, AstToken}; | 6 | use syntax::ast::{self, AstToken}; |
7 | use syntax::{SyntaxElement, SyntaxKind, SyntaxNode, SyntaxToken, TextRange, TextSize}; | 7 | use syntax::{SyntaxElement, SyntaxKind, SyntaxNode, SyntaxToken, TextRange, TextSize}; |
8 | use test_utils::mark; | ||
8 | use text_edit::TextEdit; | 9 | use text_edit::TextEdit; |
9 | 10 | ||
10 | /// Returns a text edit that will replace each match in `matches` with its corresponding replacement | 11 | /// Returns a text edit that will replace each match in `matches` with its corresponding replacement |
@@ -114,11 +115,33 @@ impl ReplacementRenderer<'_> { | |||
114 | fn render_token(&mut self, token: &SyntaxToken) { | 115 | fn render_token(&mut self, token: &SyntaxToken) { |
115 | if let Some(placeholder) = self.rule.get_placeholder(&token) { | 116 | if let Some(placeholder) = self.rule.get_placeholder(&token) { |
116 | if let Some(placeholder_value) = | 117 | if let Some(placeholder_value) = |
117 | self.match_info.placeholder_values.get(&Var(placeholder.ident.to_string())) | 118 | self.match_info.placeholder_values.get(&placeholder.ident) |
118 | { | 119 | { |
119 | let range = &placeholder_value.range.range; | 120 | let range = &placeholder_value.range.range; |
120 | let mut matched_text = | 121 | let mut matched_text = |
121 | self.file_src[usize::from(range.start())..usize::from(range.end())].to_owned(); | 122 | self.file_src[usize::from(range.start())..usize::from(range.end())].to_owned(); |
123 | // If a method call is performed directly on the placeholder, then autoderef and | ||
124 | // autoref will apply, so we can just substitute whatever the placeholder matched to | ||
125 | // directly. If we're not applying a method call, then we need to add explicitly | ||
126 | // deref and ref in order to match whatever was being done implicitly at the match | ||
127 | // site. | ||
128 | if !token_is_method_call_receiver(token) | ||
129 | && (placeholder_value.autoderef_count > 0 | ||
130 | || placeholder_value.autoref_kind != ast::SelfParamKind::Owned) | ||
131 | { | ||
132 | mark::hit!(replace_autoref_autoderef_capture); | ||
133 | let ref_kind = match placeholder_value.autoref_kind { | ||
134 | ast::SelfParamKind::Owned => "", | ||
135 | ast::SelfParamKind::Ref => "&", | ||
136 | ast::SelfParamKind::MutRef => "&mut ", | ||
137 | }; | ||
138 | matched_text = format!( | ||
139 | "{}{}{}", | ||
140 | ref_kind, | ||
141 | "*".repeat(placeholder_value.autoderef_count), | ||
142 | matched_text | ||
143 | ); | ||
144 | } | ||
122 | let edit = matches_to_edit_at_offset( | 145 | let edit = matches_to_edit_at_offset( |
123 | &placeholder_value.inner_matches, | 146 | &placeholder_value.inner_matches, |
124 | self.file_src, | 147 | self.file_src, |
@@ -179,6 +202,29 @@ impl ReplacementRenderer<'_> { | |||
179 | } | 202 | } |
180 | } | 203 | } |
181 | 204 | ||
205 | /// Returns whether token is the receiver of a method call. Note, being within the receiver of a | ||
206 | /// method call doesn't count. e.g. if the token is `$a`, then `$a.foo()` will return true, while | ||
207 | /// `($a + $b).foo()` or `x.foo($a)` will return false. | ||
208 | fn token_is_method_call_receiver(token: &SyntaxToken) -> bool { | ||
209 | use syntax::ast::AstNode; | ||
210 | // Find the first method call among the ancestors of `token`, then check if the only token | ||
211 | // within the receiver is `token`. | ||
212 | if let Some(receiver) = | ||
213 | token.ancestors().find_map(ast::MethodCallExpr::cast).and_then(|call| call.expr()) | ||
214 | { | ||
215 | let tokens = receiver.syntax().descendants_with_tokens().filter_map(|node_or_token| { | ||
216 | match node_or_token { | ||
217 | SyntaxElement::Token(t) => Some(t), | ||
218 | _ => None, | ||
219 | } | ||
220 | }); | ||
221 | if let Some((only_token,)) = tokens.collect_tuple() { | ||
222 | return only_token == *token; | ||
223 | } | ||
224 | } | ||
225 | false | ||
226 | } | ||
227 | |||
182 | fn parse_as_kind(code: &str, kind: SyntaxKind) -> Option<SyntaxNode> { | 228 | fn parse_as_kind(code: &str, kind: SyntaxKind) -> Option<SyntaxNode> { |
183 | use syntax::ast::AstNode; | 229 | use syntax::ast::AstNode; |
184 | if ast::Expr::can_cast(kind) { | 230 | if ast::Expr::can_cast(kind) { |