mirror of
https://github.com/rust-lang/rust-analyzer
synced 2025-01-26 11:55:04 +00:00
Merge #5539
5539: SSR: Fix path resolution of locals in current scope r=matklad a=davidlattimore Co-authored-by: David Lattimore <dml@google.com>
This commit is contained in:
commit
91b2f0baaf
3 changed files with 103 additions and 27 deletions
|
@ -51,8 +51,7 @@ pub struct MatchFinder<'db> {
|
||||||
/// Our source of information about the user's code.
|
/// Our source of information about the user's code.
|
||||||
sema: Semantics<'db, ra_ide_db::RootDatabase>,
|
sema: Semantics<'db, ra_ide_db::RootDatabase>,
|
||||||
rules: Vec<ResolvedRule>,
|
rules: Vec<ResolvedRule>,
|
||||||
scope: hir::SemanticsScope<'db>,
|
resolution_scope: resolving::ResolutionScope<'db>,
|
||||||
hygiene: hir::Hygiene,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'db> MatchFinder<'db> {
|
impl<'db> MatchFinder<'db> {
|
||||||
|
@ -63,21 +62,8 @@ impl<'db> MatchFinder<'db> {
|
||||||
lookup_context: FilePosition,
|
lookup_context: FilePosition,
|
||||||
) -> MatchFinder<'db> {
|
) -> MatchFinder<'db> {
|
||||||
let sema = Semantics::new(db);
|
let sema = Semantics::new(db);
|
||||||
let file = sema.parse(lookup_context.file_id);
|
let resolution_scope = resolving::ResolutionScope::new(&sema, lookup_context);
|
||||||
// Find a node at the requested position, falling back to the whole file.
|
MatchFinder { sema: Semantics::new(db), rules: Vec::new(), resolution_scope }
|
||||||
let node = file
|
|
||||||
.syntax()
|
|
||||||
.token_at_offset(lookup_context.offset)
|
|
||||||
.left_biased()
|
|
||||||
.map(|token| token.parent())
|
|
||||||
.unwrap_or_else(|| file.syntax().clone());
|
|
||||||
let scope = sema.scope(&node);
|
|
||||||
MatchFinder {
|
|
||||||
sema: Semantics::new(db),
|
|
||||||
rules: Vec::new(),
|
|
||||||
scope,
|
|
||||||
hygiene: hir::Hygiene::new(db, lookup_context.file_id.into()),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Constructs an instance using the start of the first file in `db` as the lookup context.
|
/// Constructs an instance using the start of the first file in `db` as the lookup context.
|
||||||
|
@ -106,8 +92,7 @@ impl<'db> MatchFinder<'db> {
|
||||||
for parsed_rule in rule.parsed_rules {
|
for parsed_rule in rule.parsed_rules {
|
||||||
self.rules.push(ResolvedRule::new(
|
self.rules.push(ResolvedRule::new(
|
||||||
parsed_rule,
|
parsed_rule,
|
||||||
&self.scope,
|
&self.resolution_scope,
|
||||||
&self.hygiene,
|
|
||||||
self.rules.len(),
|
self.rules.len(),
|
||||||
)?);
|
)?);
|
||||||
}
|
}
|
||||||
|
@ -140,8 +125,7 @@ impl<'db> MatchFinder<'db> {
|
||||||
for parsed_rule in pattern.parsed_rules {
|
for parsed_rule in pattern.parsed_rules {
|
||||||
self.rules.push(ResolvedRule::new(
|
self.rules.push(ResolvedRule::new(
|
||||||
parsed_rule,
|
parsed_rule,
|
||||||
&self.scope,
|
&self.resolution_scope,
|
||||||
&self.hygiene,
|
|
||||||
self.rules.len(),
|
self.rules.len(),
|
||||||
)?);
|
)?);
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,10 +3,16 @@
|
||||||
use crate::errors::error;
|
use crate::errors::error;
|
||||||
use crate::{parsing, SsrError};
|
use crate::{parsing, SsrError};
|
||||||
use parsing::Placeholder;
|
use parsing::Placeholder;
|
||||||
|
use ra_db::FilePosition;
|
||||||
use ra_syntax::{ast, SmolStr, SyntaxKind, SyntaxNode, SyntaxToken};
|
use ra_syntax::{ast, SmolStr, SyntaxKind, SyntaxNode, SyntaxToken};
|
||||||
use rustc_hash::{FxHashMap, FxHashSet};
|
use rustc_hash::{FxHashMap, FxHashSet};
|
||||||
use test_utils::mark;
|
use test_utils::mark;
|
||||||
|
|
||||||
|
pub(crate) struct ResolutionScope<'db> {
|
||||||
|
scope: hir::SemanticsScope<'db>,
|
||||||
|
hygiene: hir::Hygiene,
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) struct ResolvedRule {
|
pub(crate) struct ResolvedRule {
|
||||||
pub(crate) pattern: ResolvedPattern,
|
pub(crate) pattern: ResolvedPattern,
|
||||||
pub(crate) template: Option<ResolvedPattern>,
|
pub(crate) template: Option<ResolvedPattern>,
|
||||||
|
@ -30,12 +36,11 @@ pub(crate) struct ResolvedPath {
|
||||||
impl ResolvedRule {
|
impl ResolvedRule {
|
||||||
pub(crate) fn new(
|
pub(crate) fn new(
|
||||||
rule: parsing::ParsedRule,
|
rule: parsing::ParsedRule,
|
||||||
scope: &hir::SemanticsScope,
|
resolution_scope: &ResolutionScope,
|
||||||
hygiene: &hir::Hygiene,
|
|
||||||
index: usize,
|
index: usize,
|
||||||
) -> Result<ResolvedRule, SsrError> {
|
) -> Result<ResolvedRule, SsrError> {
|
||||||
let resolver =
|
let resolver =
|
||||||
Resolver { scope, hygiene, placeholders_by_stand_in: rule.placeholders_by_stand_in };
|
Resolver { resolution_scope, placeholders_by_stand_in: rule.placeholders_by_stand_in };
|
||||||
let resolved_template = if let Some(template) = rule.template {
|
let resolved_template = if let Some(template) = rule.template {
|
||||||
Some(resolver.resolve_pattern_tree(template)?)
|
Some(resolver.resolve_pattern_tree(template)?)
|
||||||
} else {
|
} else {
|
||||||
|
@ -57,8 +62,7 @@ impl ResolvedRule {
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Resolver<'a, 'db> {
|
struct Resolver<'a, 'db> {
|
||||||
scope: &'a hir::SemanticsScope<'db>,
|
resolution_scope: &'a ResolutionScope<'db>,
|
||||||
hygiene: &'a hir::Hygiene,
|
|
||||||
placeholders_by_stand_in: FxHashMap<SmolStr, parsing::Placeholder>,
|
placeholders_by_stand_in: FxHashMap<SmolStr, parsing::Placeholder>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -104,6 +108,7 @@ impl Resolver<'_, '_> {
|
||||||
&& !self.path_contains_placeholder(&path)
|
&& !self.path_contains_placeholder(&path)
|
||||||
{
|
{
|
||||||
let resolution = self
|
let resolution = self
|
||||||
|
.resolution_scope
|
||||||
.resolve_path(&path)
|
.resolve_path(&path)
|
||||||
.ok_or_else(|| error!("Failed to resolve path `{}`", node.text()))?;
|
.ok_or_else(|| error!("Failed to resolve path `{}`", node.text()))?;
|
||||||
resolved_paths.insert(node, ResolvedPath { resolution, depth });
|
resolved_paths.insert(node, ResolvedPath { resolution, depth });
|
||||||
|
@ -131,9 +136,32 @@ impl Resolver<'_, '_> {
|
||||||
}
|
}
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'db> ResolutionScope<'db> {
|
||||||
|
pub(crate) fn new(
|
||||||
|
sema: &hir::Semantics<'db, ra_ide_db::RootDatabase>,
|
||||||
|
lookup_context: FilePosition,
|
||||||
|
) -> ResolutionScope<'db> {
|
||||||
|
use ra_syntax::ast::AstNode;
|
||||||
|
let file = sema.parse(lookup_context.file_id);
|
||||||
|
// Find a node at the requested position, falling back to the whole file.
|
||||||
|
let node = file
|
||||||
|
.syntax()
|
||||||
|
.token_at_offset(lookup_context.offset)
|
||||||
|
.left_biased()
|
||||||
|
.map(|token| token.parent())
|
||||||
|
.unwrap_or_else(|| file.syntax().clone());
|
||||||
|
let node = pick_node_for_resolution(node);
|
||||||
|
let scope = sema.scope(&node);
|
||||||
|
ResolutionScope {
|
||||||
|
scope,
|
||||||
|
hygiene: hir::Hygiene::new(sema.db, lookup_context.file_id.into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn resolve_path(&self, path: &ast::Path) -> Option<hir::PathResolution> {
|
fn resolve_path(&self, path: &ast::Path) -> Option<hir::PathResolution> {
|
||||||
let hir_path = hir::Path::from_src(path.clone(), self.hygiene)?;
|
let hir_path = hir::Path::from_src(path.clone(), &self.hygiene)?;
|
||||||
// First try resolving the whole path. This will work for things like
|
// First try resolving the whole path. This will work for things like
|
||||||
// `std::collections::HashMap`, but will fail for things like
|
// `std::collections::HashMap`, but will fail for things like
|
||||||
// `std::collections::HashMap::new`.
|
// `std::collections::HashMap::new`.
|
||||||
|
@ -158,6 +186,33 @@ impl Resolver<'_, '_> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns a suitable node for resolving paths in the current scope. If we create a scope based on
|
||||||
|
/// a statement node, then we can't resolve local variables that were defined in the current scope
|
||||||
|
/// (only in parent scopes). So we find another node, ideally a child of the statement where local
|
||||||
|
/// variable resolution is permitted.
|
||||||
|
fn pick_node_for_resolution(node: SyntaxNode) -> SyntaxNode {
|
||||||
|
match node.kind() {
|
||||||
|
SyntaxKind::EXPR_STMT => {
|
||||||
|
if let Some(n) = node.first_child() {
|
||||||
|
mark::hit!(cursor_after_semicolon);
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SyntaxKind::LET_STMT | SyntaxKind::BIND_PAT => {
|
||||||
|
if let Some(next) = node.next_sibling() {
|
||||||
|
return pick_node_for_resolution(next);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SyntaxKind::NAME => {
|
||||||
|
if let Some(parent) = node.parent() {
|
||||||
|
return pick_node_for_resolution(parent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
node
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns whether `path` or any of its qualifiers contains type arguments.
|
/// Returns whether `path` or any of its qualifiers contains type arguments.
|
||||||
fn path_contains_type_arguments(path: Option<ast::Path>) -> bool {
|
fn path_contains_type_arguments(path: Option<ast::Path>) -> bool {
|
||||||
if let Some(path) = path {
|
if let Some(path) = path {
|
||||||
|
|
|
@ -885,3 +885,40 @@ fn ufcs_matches_method_call() {
|
||||||
"#]],
|
"#]],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn replace_local_variable_reference() {
|
||||||
|
// The pattern references a local variable `foo` in the block containing the cursor. We should
|
||||||
|
// only replace references to this variable `foo`, not other variables that just happen to have
|
||||||
|
// the same name.
|
||||||
|
mark::check!(cursor_after_semicolon);
|
||||||
|
assert_ssr_transform(
|
||||||
|
"foo + $a ==>> $a - foo",
|
||||||
|
r#"
|
||||||
|
fn bar1() -> i32 {
|
||||||
|
let mut res = 0;
|
||||||
|
let foo = 5;
|
||||||
|
res += foo + 1;
|
||||||
|
let foo = 10;
|
||||||
|
res += foo + 2;<|>
|
||||||
|
res += foo + 3;
|
||||||
|
let foo = 15;
|
||||||
|
res += foo + 4;
|
||||||
|
res
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
expect![[r#"
|
||||||
|
fn bar1() -> i32 {
|
||||||
|
let mut res = 0;
|
||||||
|
let foo = 5;
|
||||||
|
res += foo + 1;
|
||||||
|
let foo = 10;
|
||||||
|
res += 2 - foo;
|
||||||
|
res += 3 - foo;
|
||||||
|
let foo = 15;
|
||||||
|
res += foo + 4;
|
||||||
|
res
|
||||||
|
}
|
||||||
|
"#]],
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue