aboutsummaryrefslogtreecommitdiff
path: root/crates/ssr/src/replacing.rs
diff options
context:
space:
mode:
authorbors[bot] <26634292+bors[bot]@users.noreply.github.com>2020-08-18 11:52:27 +0100
committerGitHub <[email protected]>2020-08-18 11:52:27 +0100
commita95c5e21219cd6569e2630637fd4268e24c0ca03 (patch)
tree8f8d7348d4916479396abb58adc6891163cb9cbc /crates/ssr/src/replacing.rs
parent5d97db8d480d58559e7490e1efdb0e45d1a8832f (diff)
parent29e6238cb7330f7d29f33ff03a4ccc0a0cec9f4d (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.rs50
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
3use crate::matching::Var;
4use crate::{resolving::ResolvedRule, Match, SsrMatches}; 3use crate::{resolving::ResolvedRule, Match, SsrMatches};
4use itertools::Itertools;
5use rustc_hash::{FxHashMap, FxHashSet}; 5use rustc_hash::{FxHashMap, FxHashSet};
6use syntax::ast::{self, AstToken}; 6use syntax::ast::{self, AstToken};
7use syntax::{SyntaxElement, SyntaxKind, SyntaxNode, SyntaxToken, TextRange, TextSize}; 7use syntax::{SyntaxElement, SyntaxKind, SyntaxNode, SyntaxToken, TextRange, TextSize};
8use test_utils::mark;
8use text_edit::TextEdit; 9use 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.
208fn 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
182fn parse_as_kind(code: &str, kind: SyntaxKind) -> Option<SyntaxNode> { 228fn 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) {