Skip to content

Commit 91b2f0b

Browse files
Merge #5539
5539: SSR: Fix path resolution of locals in current scope r=matklad a=davidlattimore Co-authored-by: David Lattimore <[email protected]>
2 parents 401a9c2 + b3ca36b commit 91b2f0b

File tree

3 files changed

+103
-27
lines changed

3 files changed

+103
-27
lines changed

crates/ra_ssr/src/lib.rs

Lines changed: 5 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,7 @@ pub struct MatchFinder<'db> {
5151
/// Our source of information about the user's code.
5252
sema: Semantics<'db, ra_ide_db::RootDatabase>,
5353
rules: Vec<ResolvedRule>,
54-
scope: hir::SemanticsScope<'db>,
55-
hygiene: hir::Hygiene,
54+
resolution_scope: resolving::ResolutionScope<'db>,
5655
}
5756

5857
impl<'db> MatchFinder<'db> {
@@ -63,21 +62,8 @@ impl<'db> MatchFinder<'db> {
6362
lookup_context: FilePosition,
6463
) -> MatchFinder<'db> {
6564
let sema = Semantics::new(db);
66-
let file = sema.parse(lookup_context.file_id);
67-
// Find a node at the requested position, falling back to the whole file.
68-
let node = file
69-
.syntax()
70-
.token_at_offset(lookup_context.offset)
71-
.left_biased()
72-
.map(|token| token.parent())
73-
.unwrap_or_else(|| file.syntax().clone());
74-
let scope = sema.scope(&node);
75-
MatchFinder {
76-
sema: Semantics::new(db),
77-
rules: Vec::new(),
78-
scope,
79-
hygiene: hir::Hygiene::new(db, lookup_context.file_id.into()),
80-
}
65+
let resolution_scope = resolving::ResolutionScope::new(&sema, lookup_context);
66+
MatchFinder { sema: Semantics::new(db), rules: Vec::new(), resolution_scope }
8167
}
8268

8369
/// Constructs an instance using the start of the first file in `db` as the lookup context.
@@ -106,8 +92,7 @@ impl<'db> MatchFinder<'db> {
10692
for parsed_rule in rule.parsed_rules {
10793
self.rules.push(ResolvedRule::new(
10894
parsed_rule,
109-
&self.scope,
110-
&self.hygiene,
95+
&self.resolution_scope,
11196
self.rules.len(),
11297
)?);
11398
}
@@ -140,8 +125,7 @@ impl<'db> MatchFinder<'db> {
140125
for parsed_rule in pattern.parsed_rules {
141126
self.rules.push(ResolvedRule::new(
142127
parsed_rule,
143-
&self.scope,
144-
&self.hygiene,
128+
&self.resolution_scope,
145129
self.rules.len(),
146130
)?);
147131
}

crates/ra_ssr/src/resolving.rs

Lines changed: 61 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,16 @@
33
use crate::errors::error;
44
use crate::{parsing, SsrError};
55
use parsing::Placeholder;
6+
use ra_db::FilePosition;
67
use ra_syntax::{ast, SmolStr, SyntaxKind, SyntaxNode, SyntaxToken};
78
use rustc_hash::{FxHashMap, FxHashSet};
89
use test_utils::mark;
910

11+
pub(crate) struct ResolutionScope<'db> {
12+
scope: hir::SemanticsScope<'db>,
13+
hygiene: hir::Hygiene,
14+
}
15+
1016
pub(crate) struct ResolvedRule {
1117
pub(crate) pattern: ResolvedPattern,
1218
pub(crate) template: Option<ResolvedPattern>,
@@ -30,12 +36,11 @@ pub(crate) struct ResolvedPath {
3036
impl ResolvedRule {
3137
pub(crate) fn new(
3238
rule: parsing::ParsedRule,
33-
scope: &hir::SemanticsScope,
34-
hygiene: &hir::Hygiene,
39+
resolution_scope: &ResolutionScope,
3540
index: usize,
3641
) -> Result<ResolvedRule, SsrError> {
3742
let resolver =
38-
Resolver { scope, hygiene, placeholders_by_stand_in: rule.placeholders_by_stand_in };
43+
Resolver { resolution_scope, placeholders_by_stand_in: rule.placeholders_by_stand_in };
3944
let resolved_template = if let Some(template) = rule.template {
4045
Some(resolver.resolve_pattern_tree(template)?)
4146
} else {
@@ -57,8 +62,7 @@ impl ResolvedRule {
5762
}
5863

5964
struct Resolver<'a, 'db> {
60-
scope: &'a hir::SemanticsScope<'db>,
61-
hygiene: &'a hir::Hygiene,
65+
resolution_scope: &'a ResolutionScope<'db>,
6266
placeholders_by_stand_in: FxHashMap<SmolStr, parsing::Placeholder>,
6367
}
6468

@@ -104,6 +108,7 @@ impl Resolver<'_, '_> {
104108
&& !self.path_contains_placeholder(&path)
105109
{
106110
let resolution = self
111+
.resolution_scope
107112
.resolve_path(&path)
108113
.ok_or_else(|| error!("Failed to resolve path `{}`", node.text()))?;
109114
resolved_paths.insert(node, ResolvedPath { resolution, depth });
@@ -131,9 +136,32 @@ impl Resolver<'_, '_> {
131136
}
132137
false
133138
}
139+
}
140+
141+
impl<'db> ResolutionScope<'db> {
142+
pub(crate) fn new(
143+
sema: &hir::Semantics<'db, ra_ide_db::RootDatabase>,
144+
lookup_context: FilePosition,
145+
) -> ResolutionScope<'db> {
146+
use ra_syntax::ast::AstNode;
147+
let file = sema.parse(lookup_context.file_id);
148+
// Find a node at the requested position, falling back to the whole file.
149+
let node = file
150+
.syntax()
151+
.token_at_offset(lookup_context.offset)
152+
.left_biased()
153+
.map(|token| token.parent())
154+
.unwrap_or_else(|| file.syntax().clone());
155+
let node = pick_node_for_resolution(node);
156+
let scope = sema.scope(&node);
157+
ResolutionScope {
158+
scope,
159+
hygiene: hir::Hygiene::new(sema.db, lookup_context.file_id.into()),
160+
}
161+
}
134162

135163
fn resolve_path(&self, path: &ast::Path) -> Option<hir::PathResolution> {
136-
let hir_path = hir::Path::from_src(path.clone(), self.hygiene)?;
164+
let hir_path = hir::Path::from_src(path.clone(), &self.hygiene)?;
137165
// First try resolving the whole path. This will work for things like
138166
// `std::collections::HashMap`, but will fail for things like
139167
// `std::collections::HashMap::new`.
@@ -158,6 +186,33 @@ impl Resolver<'_, '_> {
158186
}
159187
}
160188

189+
/// Returns a suitable node for resolving paths in the current scope. If we create a scope based on
190+
/// a statement node, then we can't resolve local variables that were defined in the current scope
191+
/// (only in parent scopes). So we find another node, ideally a child of the statement where local
192+
/// variable resolution is permitted.
193+
fn pick_node_for_resolution(node: SyntaxNode) -> SyntaxNode {
194+
match node.kind() {
195+
SyntaxKind::EXPR_STMT => {
196+
if let Some(n) = node.first_child() {
197+
mark::hit!(cursor_after_semicolon);
198+
return n;
199+
}
200+
}
201+
SyntaxKind::LET_STMT | SyntaxKind::BIND_PAT => {
202+
if let Some(next) = node.next_sibling() {
203+
return pick_node_for_resolution(next);
204+
}
205+
}
206+
SyntaxKind::NAME => {
207+
if let Some(parent) = node.parent() {
208+
return pick_node_for_resolution(parent);
209+
}
210+
}
211+
_ => {}
212+
}
213+
node
214+
}
215+
161216
/// Returns whether `path` or any of its qualifiers contains type arguments.
162217
fn path_contains_type_arguments(path: Option<ast::Path>) -> bool {
163218
if let Some(path) = path {

crates/ra_ssr/src/tests.rs

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -885,3 +885,40 @@ fn ufcs_matches_method_call() {
885885
"#]],
886886
);
887887
}
888+
889+
#[test]
890+
fn replace_local_variable_reference() {
891+
// The pattern references a local variable `foo` in the block containing the cursor. We should
892+
// only replace references to this variable `foo`, not other variables that just happen to have
893+
// the same name.
894+
mark::check!(cursor_after_semicolon);
895+
assert_ssr_transform(
896+
"foo + $a ==>> $a - foo",
897+
r#"
898+
fn bar1() -> i32 {
899+
let mut res = 0;
900+
let foo = 5;
901+
res += foo + 1;
902+
let foo = 10;
903+
res += foo + 2;<|>
904+
res += foo + 3;
905+
let foo = 15;
906+
res += foo + 4;
907+
res
908+
}
909+
"#,
910+
expect![[r#"
911+
fn bar1() -> i32 {
912+
let mut res = 0;
913+
let foo = 5;
914+
res += foo + 1;
915+
let foo = 10;
916+
res += 2 - foo;
917+
res += 3 - foo;
918+
let foo = 15;
919+
res += foo + 4;
920+
res
921+
}
922+
"#]],
923+
)
924+
}

0 commit comments

Comments
 (0)