Skip to content

Commit 6bbeffc

Browse files
SSR: Allow self in patterns.
It's now consistent with other variables in that if the pattern references self, only the `self` in scope where the rule is invoked will be accepted. Since `self` doesn't work the same as other paths, this is implemented by restricting the search to just the current function. Prior to this change (since path resolution was implemented), having self in a pattern would just result in no matches.
1 parent 5af32ae commit 6bbeffc

File tree

4 files changed

+74
-6
lines changed

4 files changed

+74
-6
lines changed

crates/ra_ssr/src/lib.rs

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -66,12 +66,7 @@ impl<'db> MatchFinder<'db> {
6666
restrict_ranges.retain(|range| !range.range.is_empty());
6767
let sema = Semantics::new(db);
6868
let resolution_scope = resolving::ResolutionScope::new(&sema, lookup_context);
69-
MatchFinder {
70-
sema: Semantics::new(db),
71-
rules: Vec::new(),
72-
resolution_scope,
73-
restrict_ranges,
74-
}
69+
MatchFinder { sema, rules: Vec::new(), resolution_scope, restrict_ranges }
7570
}
7671

7772
/// Constructs an instance using the start of the first file in `db` as the lookup context.

crates/ra_ssr/src/resolving.rs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ use test_utils::mark;
1111
pub(crate) struct ResolutionScope<'db> {
1212
scope: hir::SemanticsScope<'db>,
1313
hygiene: hir::Hygiene,
14+
node: SyntaxNode,
1415
}
1516

1617
pub(crate) struct ResolvedRule {
@@ -25,6 +26,7 @@ pub(crate) struct ResolvedPattern {
2526
// Paths in `node` that we've resolved.
2627
pub(crate) resolved_paths: FxHashMap<SyntaxNode, ResolvedPath>,
2728
pub(crate) ufcs_function_calls: FxHashMap<SyntaxNode, hir::Function>,
29+
pub(crate) contains_self: bool,
2830
}
2931

3032
pub(crate) struct ResolvedPath {
@@ -68,6 +70,7 @@ struct Resolver<'a, 'db> {
6870

6971
impl Resolver<'_, '_> {
7072
fn resolve_pattern_tree(&self, pattern: SyntaxNode) -> Result<ResolvedPattern, SsrError> {
73+
use ra_syntax::{SyntaxElement, T};
7174
let mut resolved_paths = FxHashMap::default();
7275
self.resolve(pattern.clone(), 0, &mut resolved_paths)?;
7376
let ufcs_function_calls = resolved_paths
@@ -85,11 +88,17 @@ impl Resolver<'_, '_> {
8588
None
8689
})
8790
.collect();
91+
let contains_self =
92+
pattern.descendants_with_tokens().any(|node_or_token| match node_or_token {
93+
SyntaxElement::Token(t) => t.kind() == T![self],
94+
_ => false,
95+
});
8896
Ok(ResolvedPattern {
8997
node: pattern,
9098
resolved_paths,
9199
placeholders_by_stand_in: self.placeholders_by_stand_in.clone(),
92100
ufcs_function_calls,
101+
contains_self,
93102
})
94103
}
95104

@@ -101,6 +110,10 @@ impl Resolver<'_, '_> {
101110
) -> Result<(), SsrError> {
102111
use ra_syntax::ast::AstNode;
103112
if let Some(path) = ast::Path::cast(node.clone()) {
113+
if is_self(&path) {
114+
// Self cannot be resolved like other paths.
115+
return Ok(());
116+
}
104117
// Check if this is an appropriate place in the path to resolve. If the path is
105118
// something like `a::B::<i32>::c` then we want to resolve `a::B`. If the path contains
106119
// a placeholder. e.g. `a::$b::c` then we want to resolve `a`.
@@ -157,6 +170,18 @@ impl<'db> ResolutionScope<'db> {
157170
ResolutionScope {
158171
scope,
159172
hygiene: hir::Hygiene::new(sema.db, resolve_context.file_id.into()),
173+
node,
174+
}
175+
}
176+
177+
/// Returns the function in which SSR was invoked, if any.
178+
pub(crate) fn current_function(&self) -> Option<SyntaxNode> {
179+
let mut node = self.node.clone();
180+
loop {
181+
if node.kind() == SyntaxKind::FN {
182+
return Some(node);
183+
}
184+
node = node.parent()?;
160185
}
161186
}
162187

@@ -186,6 +211,10 @@ impl<'db> ResolutionScope<'db> {
186211
}
187212
}
188213

214+
fn is_self(path: &ast::Path) -> bool {
215+
path.segment().map(|segment| segment.self_token().is_some()).unwrap_or(false)
216+
}
217+
189218
/// Returns a suitable node for resolving paths in the current scope. If we create a scope based on
190219
/// a statement node, then we can't resolve local variables that were defined in the current scope
191220
/// (only in parent scopes). So we find another node, ideally a child of the statement where local

crates/ra_ssr/src/search.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,15 @@ impl<'db> MatchFinder<'db> {
3333
usage_cache: &mut UsageCache,
3434
matches_out: &mut Vec<Match>,
3535
) {
36+
if rule.pattern.contains_self {
37+
// If the pattern contains `self` we restrict the scope of the search to just the
38+
// current method. No other method can reference the same `self`. This makes the
39+
// behavior of `self` consistent with other variables.
40+
if let Some(current_function) = self.resolution_scope.current_function() {
41+
self.slow_scan_node(&current_function, rule, &None, matches_out);
42+
}
43+
return;
44+
}
3645
if pick_path_for_usages(&rule.pattern).is_none() {
3746
self.slow_scan(rule, matches_out);
3847
return;

crates/ra_ssr/src/tests.rs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1044,3 +1044,38 @@ fn replace_nonpath_within_selection() {
10441044
}"#]],
10451045
);
10461046
}
1047+
1048+
#[test]
1049+
fn replace_self() {
1050+
// `foo(self)` occurs twice in the code, however only the first occurrence is the `self` that's
1051+
// in scope where the rule is invoked.
1052+
assert_ssr_transform(
1053+
"foo(self) ==>> bar(self)",
1054+
r#"
1055+
struct S1 {}
1056+
fn foo(_: &S1) {}
1057+
fn bar(_: &S1) {}
1058+
impl S1 {
1059+
fn f1(&self) {
1060+
foo(self)<|>
1061+
}
1062+
fn f2(&self) {
1063+
foo(self)
1064+
}
1065+
}
1066+
"#,
1067+
expect![[r#"
1068+
struct S1 {}
1069+
fn foo(_: &S1) {}
1070+
fn bar(_: &S1) {}
1071+
impl S1 {
1072+
fn f1(&self) {
1073+
bar(self)
1074+
}
1075+
fn f2(&self) {
1076+
foo(self)
1077+
}
1078+
}
1079+
"#]],
1080+
);
1081+
}

0 commit comments

Comments
 (0)