diff options
-rw-r--r-- | Cargo.lock | 1 | ||||
-rw-r--r-- | crates/ssr/Cargo.toml | 1 | ||||
-rw-r--r-- | crates/ssr/src/lib.rs | 5 | ||||
-rw-r--r-- | crates/ssr/src/matching.rs | 95 | ||||
-rw-r--r-- | crates/ssr/src/parsing.rs | 24 | ||||
-rw-r--r-- | crates/ssr/src/replacing.rs | 50 | ||||
-rw-r--r-- | crates/ssr/src/tests.rs | 109 |
7 files changed, 247 insertions, 38 deletions
diff --git a/Cargo.lock b/Cargo.lock index 7abfeaaeb..ffa385106 100644 --- a/Cargo.lock +++ b/Cargo.lock | |||
@@ -1455,6 +1455,7 @@ dependencies = [ | |||
1455 | "expect", | 1455 | "expect", |
1456 | "hir", | 1456 | "hir", |
1457 | "ide_db", | 1457 | "ide_db", |
1458 | "itertools", | ||
1458 | "rustc-hash", | 1459 | "rustc-hash", |
1459 | "syntax", | 1460 | "syntax", |
1460 | "test_utils", | 1461 | "test_utils", |
diff --git a/crates/ssr/Cargo.toml b/crates/ssr/Cargo.toml index 56c1f7761..7c2090de3 100644 --- a/crates/ssr/Cargo.toml +++ b/crates/ssr/Cargo.toml | |||
@@ -12,6 +12,7 @@ doctest = false | |||
12 | 12 | ||
13 | [dependencies] | 13 | [dependencies] |
14 | rustc-hash = "1.1.0" | 14 | rustc-hash = "1.1.0" |
15 | itertools = "0.9.0" | ||
15 | 16 | ||
16 | text_edit = { path = "../text_edit" } | 17 | text_edit = { path = "../text_edit" } |
17 | syntax = { path = "../syntax" } | 18 | syntax = { path = "../syntax" } |
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 ffc7202ae..8bb5ced90 100644 --- a/crates/ssr/src/matching.rs +++ b/crates/ssr/src/matching.rs | |||
@@ -2,7 +2,7 @@ | |||
2 | //! process of matching, placeholder values are recorded. | 2 | //! process of matching, placeholder values are recorded. |
3 | 3 | ||
4 | use crate::{ | 4 | use crate::{ |
5 | parsing::{Constraint, NodeKind, Placeholder}, | 5 | parsing::{Constraint, NodeKind, Placeholder, Var}, |
6 | resolving::{ResolvedPattern, ResolvedRule, UfcsCallInfo}, | 6 | resolving::{ResolvedPattern, ResolvedRule, UfcsCallInfo}, |
7 | SsrMatches, | 7 | SsrMatches, |
8 | }; | 8 | }; |
@@ -56,10 +56,6 @@ pub struct Match { | |||
56 | pub(crate) rendered_template_paths: FxHashMap<SyntaxNode, hir::ModPath>, | 56 | pub(crate) rendered_template_paths: FxHashMap<SyntaxNode, hir::ModPath>, |
57 | } | 57 | } |
58 | 58 | ||
59 | /// Represents a `$var` in an SSR query. | ||
60 | #[derive(Debug, Clone, PartialEq, Eq, Hash)] | ||
61 | pub(crate) struct Var(pub String); | ||
62 | |||
63 | /// Information about a placeholder bound in a match. | 59 | /// Information about a placeholder bound in a match. |
64 | #[derive(Debug)] | 60 | #[derive(Debug)] |
65 | pub(crate) struct PlaceholderMatch { | 61 | pub(crate) struct PlaceholderMatch { |
@@ -69,6 +65,10 @@ pub(crate) struct PlaceholderMatch { | |||
69 | pub(crate) range: FileRange, | 65 | pub(crate) range: FileRange, |
70 | /// More matches, found within `node`. | 66 | /// More matches, found within `node`. |
71 | 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, | ||
72 | } | 72 | } |
73 | 73 | ||
74 | #[derive(Debug)] | 74 | #[derive(Debug)] |
@@ -173,7 +173,7 @@ impl<'db, 'sema> Matcher<'db, 'sema> { | |||
173 | code: &SyntaxNode, | 173 | code: &SyntaxNode, |
174 | ) -> Result<(), MatchFailed> { | 174 | ) -> Result<(), MatchFailed> { |
175 | // Handle placeholders. | 175 | // Handle placeholders. |
176 | if let Some(placeholder) = self.get_placeholder(&SyntaxElement::Node(pattern.clone())) { | 176 | if let Some(placeholder) = self.get_placeholder_for_node(pattern) { |
177 | for constraint in &placeholder.constraints { | 177 | for constraint in &placeholder.constraints { |
178 | self.check_constraint(constraint, code)?; | 178 | self.check_constraint(constraint, code)?; |
179 | } | 179 | } |
@@ -183,8 +183,8 @@ impl<'db, 'sema> Matcher<'db, 'sema> { | |||
183 | // probably can't fail range validation, but just to be safe... | 183 | // probably can't fail range validation, but just to be safe... |
184 | self.validate_range(&original_range)?; | 184 | self.validate_range(&original_range)?; |
185 | matches_out.placeholder_values.insert( | 185 | matches_out.placeholder_values.insert( |
186 | Var(placeholder.ident.to_string()), | 186 | placeholder.ident.clone(), |
187 | PlaceholderMatch::new(code, original_range), | 187 | PlaceholderMatch::new(Some(code), original_range), |
188 | ); | 188 | ); |
189 | } | 189 | } |
190 | return Ok(()); | 190 | return Ok(()); |
@@ -487,7 +487,7 @@ impl<'db, 'sema> Matcher<'db, 'sema> { | |||
487 | } | 487 | } |
488 | if let Phase::Second(match_out) = phase { | 488 | if let Phase::Second(match_out) = phase { |
489 | match_out.placeholder_values.insert( | 489 | match_out.placeholder_values.insert( |
490 | Var(placeholder.ident.to_string()), | 490 | placeholder.ident.clone(), |
491 | PlaceholderMatch::from_range(FileRange { | 491 | PlaceholderMatch::from_range(FileRange { |
492 | file_id: self.sema.original_range(code).file_id, | 492 | file_id: self.sema.original_range(code).file_id, |
493 | range: first_matched_token | 493 | range: first_matched_token |
@@ -536,18 +536,40 @@ impl<'db, 'sema> Matcher<'db, 'sema> { | |||
536 | if pattern_ufcs.function != code_resolved_function { | 536 | if pattern_ufcs.function != code_resolved_function { |
537 | fail_match!("Method call resolved to a different function"); | 537 | fail_match!("Method call resolved to a different function"); |
538 | } | 538 | } |
539 | if code_resolved_function.has_self_param(self.sema.db) { | ||
540 | if let (Some(pattern_type), Some(expr)) = (&pattern_ufcs.qualifier_type, &code.expr()) { | ||
541 | self.check_expr_type(pattern_type, expr)?; | ||
542 | } | ||
543 | } | ||
544 | // Check arguments. | 539 | // Check arguments. |
545 | let mut pattern_args = pattern_ufcs | 540 | let mut pattern_args = pattern_ufcs |
546 | .call_expr | 541 | .call_expr |
547 | .arg_list() | 542 | .arg_list() |
548 | .ok_or_else(|| match_error!("Pattern function call has no args"))? | 543 | .ok_or_else(|| match_error!("Pattern function call has no args"))? |
549 | .args(); | 544 | .args(); |
550 | 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 | } | ||
551 | let mut code_args = | 573 | let mut code_args = |
552 | 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(); |
553 | loop { | 575 | loop { |
@@ -575,26 +597,35 @@ impl<'db, 'sema> Matcher<'db, 'sema> { | |||
575 | 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()) |
576 | } | 598 | } |
577 | 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. | ||
578 | fn check_expr_type( | 602 | fn check_expr_type( |
579 | &self, | 603 | &self, |
580 | pattern_type: &hir::Type, | 604 | pattern_type: &hir::Type, |
581 | expr: &ast::Expr, | 605 | expr: &ast::Expr, |
582 | ) -> Result<(), MatchFailed> { | 606 | ) -> Result<usize, MatchFailed> { |
583 | use hir::HirDisplay; | 607 | use hir::HirDisplay; |
584 | 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(|| { |
585 | match_error!("Failed to get receiver type for `{}`", expr.syntax().text()) | 609 | match_error!("Failed to get receiver type for `{}`", expr.syntax().text()) |
586 | })?; | 610 | })?; |
587 | if !code_type | 611 | // Temporary needed to make the borrow checker happy. |
612 | let res = code_type | ||
588 | .autoderef(self.sema.db) | 613 | .autoderef(self.sema.db) |
589 | .any(|deref_code_type| *pattern_type == deref_code_type) | 614 | .enumerate() |
590 | { | 615 | .find(|(_, deref_code_type)| pattern_type == deref_code_type) |
591 | fail_match!( | 616 | .map(|(count, _)| count) |
592 | "Pattern type `{}` didn't match code type `{}`", | 617 | .ok_or_else(|| { |
593 | pattern_type.display(self.sema.db), | 618 | match_error!( |
594 | code_type.display(self.sema.db) | 619 | "Pattern type `{}` didn't match code type `{}`", |
595 | ); | 620 | pattern_type.display(self.sema.db), |
596 | } | 621 | code_type.display(self.sema.db) |
597 | 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())) | ||
598 | } | 629 | } |
599 | 630 | ||
600 | fn get_placeholder(&self, element: &SyntaxElement) -> Option<&Placeholder> { | 631 | fn get_placeholder(&self, element: &SyntaxElement) -> Option<&Placeholder> { |
@@ -676,12 +707,18 @@ fn recording_match_fail_reasons() -> bool { | |||
676 | } | 707 | } |
677 | 708 | ||
678 | impl PlaceholderMatch { | 709 | impl PlaceholderMatch { |
679 | fn new(node: &SyntaxNode, range: FileRange) -> Self { | 710 | fn new(node: Option<&SyntaxNode>, range: FileRange) -> Self { |
680 | 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 | } | ||
681 | } | 718 | } |
682 | 719 | ||
683 | fn from_range(range: FileRange) -> Self { | 720 | fn from_range(range: FileRange) -> Self { |
684 | Self { node: None, range, inner_matches: SsrMatches::default() } | 721 | Self::new(None, range) |
685 | } | 722 | } |
686 | } | 723 | } |
687 | 724 | ||
diff --git a/crates/ssr/src/parsing.rs b/crates/ssr/src/parsing.rs index 9570e96e3..05b66dcd7 100644 --- a/crates/ssr/src/parsing.rs +++ b/crates/ssr/src/parsing.rs | |||
@@ -8,7 +8,7 @@ | |||
8 | use crate::errors::bail; | 8 | use crate::errors::bail; |
9 | use crate::{SsrError, SsrPattern, SsrRule}; | 9 | use crate::{SsrError, SsrPattern, SsrRule}; |
10 | use rustc_hash::{FxHashMap, FxHashSet}; | 10 | use rustc_hash::{FxHashMap, FxHashSet}; |
11 | use std::str::FromStr; | 11 | use std::{fmt::Display, str::FromStr}; |
12 | use syntax::{ast, AstNode, SmolStr, SyntaxKind, SyntaxNode, T}; | 12 | use syntax::{ast, AstNode, SmolStr, SyntaxKind, SyntaxNode, T}; |
13 | use test_utils::mark; | 13 | use test_utils::mark; |
14 | 14 | ||
@@ -34,12 +34,16 @@ pub(crate) enum PatternElement { | |||
34 | #[derive(Clone, Debug, PartialEq, Eq)] | 34 | #[derive(Clone, Debug, PartialEq, Eq)] |
35 | pub(crate) struct Placeholder { | 35 | pub(crate) struct Placeholder { |
36 | /// The name of this placeholder. e.g. for "$a", this would be "a" | 36 | /// The name of this placeholder. e.g. for "$a", this would be "a" |
37 | pub(crate) ident: SmolStr, | 37 | pub(crate) ident: Var, |
38 | /// A unique name used in place of this placeholder when we parse the pattern as Rust code. | 38 | /// A unique name used in place of this placeholder when we parse the pattern as Rust code. |
39 | stand_in_name: String, | 39 | stand_in_name: String, |
40 | pub(crate) constraints: Vec<Constraint>, | 40 | pub(crate) constraints: Vec<Constraint>, |
41 | } | 41 | } |
42 | 42 | ||
43 | /// Represents a `$var` in an SSR query. | ||
44 | #[derive(Debug, Clone, PartialEq, Eq, Hash)] | ||
45 | pub(crate) struct Var(pub String); | ||
46 | |||
43 | #[derive(Clone, Debug, PartialEq, Eq)] | 47 | #[derive(Clone, Debug, PartialEq, Eq)] |
44 | pub(crate) enum Constraint { | 48 | pub(crate) enum Constraint { |
45 | Kind(NodeKind), | 49 | Kind(NodeKind), |
@@ -205,7 +209,7 @@ fn parse_pattern(pattern_str: &str) -> Result<Vec<PatternElement>, SsrError> { | |||
205 | if token.kind == T![$] { | 209 | if token.kind == T![$] { |
206 | let placeholder = parse_placeholder(&mut tokens)?; | 210 | let placeholder = parse_placeholder(&mut tokens)?; |
207 | if !placeholder_names.insert(placeholder.ident.clone()) { | 211 | if !placeholder_names.insert(placeholder.ident.clone()) { |
208 | bail!("Name `{}` repeats more than once", placeholder.ident); | 212 | bail!("Placeholder `{}` repeats more than once", placeholder.ident); |
209 | } | 213 | } |
210 | res.push(PatternElement::Placeholder(placeholder)); | 214 | res.push(PatternElement::Placeholder(placeholder)); |
211 | } else { | 215 | } else { |
@@ -228,7 +232,7 @@ fn validate_rule(rule: &SsrRule) -> Result<(), SsrError> { | |||
228 | for p in &rule.template.tokens { | 232 | for p in &rule.template.tokens { |
229 | if let PatternElement::Placeholder(placeholder) = p { | 233 | if let PatternElement::Placeholder(placeholder) = p { |
230 | if !defined_placeholders.contains(&placeholder.ident) { | 234 | if !defined_placeholders.contains(&placeholder.ident) { |
231 | undefined.push(format!("${}", placeholder.ident)); | 235 | undefined.push(placeholder.ident.to_string()); |
232 | } | 236 | } |
233 | if !placeholder.constraints.is_empty() { | 237 | if !placeholder.constraints.is_empty() { |
234 | bail!("Replacement placeholders cannot have constraints"); | 238 | bail!("Replacement placeholders cannot have constraints"); |
@@ -344,7 +348,17 @@ impl NodeKind { | |||
344 | 348 | ||
345 | impl Placeholder { | 349 | impl Placeholder { |
346 | fn new(name: SmolStr, constraints: Vec<Constraint>) -> Self { | 350 | fn new(name: SmolStr, constraints: Vec<Constraint>) -> Self { |
347 | Self { stand_in_name: format!("__placeholder_{}", name), constraints, ident: name } | 351 | Self { |
352 | stand_in_name: format!("__placeholder_{}", name), | ||
353 | constraints, | ||
354 | ident: Var(name.to_string()), | ||
355 | } | ||
356 | } | ||
357 | } | ||
358 | |||
359 | impl Display for Var { | ||
360 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | ||
361 | write!(f, "${}", self.0) | ||
348 | } | 362 | } |
349 | } | 363 | } |
350 | 364 | ||
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) { |
diff --git a/crates/ssr/src/tests.rs b/crates/ssr/src/tests.rs index 0d0a00090..e45c88864 100644 --- a/crates/ssr/src/tests.rs +++ b/crates/ssr/src/tests.rs | |||
@@ -31,7 +31,7 @@ fn parser_two_delimiters() { | |||
31 | fn parser_repeated_name() { | 31 | fn parser_repeated_name() { |
32 | assert_eq!( | 32 | assert_eq!( |
33 | parse_error_text("foo($a, $a) ==>>"), | 33 | parse_error_text("foo($a, $a) ==>>"), |
34 | "Parse error: Name `a` repeats more than once" | 34 | "Parse error: Placeholder `$a` repeats more than once" |
35 | ); | 35 | ); |
36 | } | 36 | } |
37 | 37 | ||
@@ -1172,3 +1172,110 @@ 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 | mark::check!(replace_autoref_autoderef_capture); | ||
1183 | let code = r#" | ||
1184 | struct Foo {} | ||
1185 | impl Foo { | ||
1186 | fn foo(&self) {} | ||
1187 | fn foo2(&self) {} | ||
1188 | } | ||
1189 | fn bar(_: &Foo) {} | ||
1190 | fn main() { | ||
1191 | let f = Foo {}; | ||
1192 | let fr = &f; | ||
1193 | let fr2 = &fr; | ||
1194 | let fr3 = &fr2; | ||
1195 | f.foo(); | ||
1196 | fr.foo(); | ||
1197 | fr2.foo(); | ||
1198 | fr3.foo(); | ||
1199 | } | ||
1200 | "#; | ||
1201 | assert_ssr_transform( | ||
1202 | "Foo::foo($a) ==>> bar($a)", | ||
1203 | code, | ||
1204 | expect![[r#" | ||
1205 | struct Foo {} | ||
1206 | impl Foo { | ||
1207 | fn foo(&self) {} | ||
1208 | fn foo2(&self) {} | ||
1209 | } | ||
1210 | fn bar(_: &Foo) {} | ||
1211 | fn main() { | ||
1212 | let f = Foo {}; | ||
1213 | let fr = &f; | ||
1214 | let fr2 = &fr; | ||
1215 | let fr3 = &fr2; | ||
1216 | bar(&f); | ||
1217 | bar(&*fr); | ||
1218 | bar(&**fr2); | ||
1219 | bar(&***fr3); | ||
1220 | } | ||
1221 | "#]], | ||
1222 | ); | ||
1223 | // If the placeholder is used as the receiver of another method call, then we don't need to | ||
1224 | // explicitly autoderef or autoref. | ||
1225 | assert_ssr_transform( | ||
1226 | "Foo::foo($a) ==>> $a.foo2()", | ||
1227 | code, | ||
1228 | expect![[r#" | ||
1229 | struct Foo {} | ||
1230 | impl Foo { | ||
1231 | fn foo(&self) {} | ||
1232 | fn foo2(&self) {} | ||
1233 | } | ||
1234 | fn bar(_: &Foo) {} | ||
1235 | fn main() { | ||
1236 | let f = Foo {}; | ||
1237 | let fr = &f; | ||
1238 | let fr2 = &fr; | ||
1239 | let fr3 = &fr2; | ||
1240 | f.foo2(); | ||
1241 | fr.foo2(); | ||
1242 | fr2.foo2(); | ||
1243 | fr3.foo2(); | ||
1244 | } | ||
1245 | "#]], | ||
1246 | ); | ||
1247 | } | ||
1248 | |||
1249 | #[test] | ||
1250 | fn replace_autoref_mut() { | ||
1251 | let code = r#" | ||
1252 | struct Foo {} | ||
1253 | impl Foo { | ||
1254 | fn foo(&mut self) {} | ||
1255 | } | ||
1256 | fn bar(_: &mut Foo) {} | ||
1257 | fn main() { | ||
1258 | let mut f = Foo {}; | ||
1259 | f.foo(); | ||
1260 | let fr = &mut f; | ||
1261 | fr.foo(); | ||
1262 | } | ||
1263 | "#; | ||
1264 | assert_ssr_transform( | ||
1265 | "Foo::foo($a) ==>> bar($a)", | ||
1266 | code, | ||
1267 | expect![[r#" | ||
1268 | struct Foo {} | ||
1269 | impl Foo { | ||
1270 | fn foo(&mut self) {} | ||
1271 | } | ||
1272 | fn bar(_: &mut Foo) {} | ||
1273 | fn main() { | ||
1274 | let mut f = Foo {}; | ||
1275 | bar(&mut f); | ||
1276 | let fr = &mut f; | ||
1277 | bar(&mut *fr); | ||
1278 | } | ||
1279 | "#]], | ||
1280 | ); | ||
1281 | } | ||