mirror of
https://github.com/rust-lang/rust-analyzer
synced 2024-12-25 20:43:21 +00:00
Merge #5565
5565: SSR: Don't mix non-path-based rules with path-based r=matklad a=davidlattimore If any rules contain paths, then we reject any rules that don't contain paths. Allowing a mix leads to strange semantics, since the path-based rules only match things where the path refers to semantically the same thing, whereas the non-path-based rules could match anything. Specifically, if we have a rule like `foo ==>> bar` we only want to match the `foo` that is in the current scope, not any `foo`. However "foo" can be parsed as a pattern (BIND_PAT -> NAME -> IDENT). Allowing such a rule through would result in renaming everything called `foo` to `bar`. It'd also be slow, since without a path, we'd have to use the slow-scan search mechanism. Co-authored-by: David Lattimore <dml@google.com>
This commit is contained in:
commit
04d2b7b256
2 changed files with 62 additions and 1 deletions
|
@ -10,6 +10,7 @@ use crate::{SsrError, SsrPattern, SsrRule};
|
|||
use ra_syntax::{ast, AstNode, SmolStr, SyntaxKind, SyntaxNode, T};
|
||||
use rustc_hash::{FxHashMap, FxHashSet};
|
||||
use std::str::FromStr;
|
||||
use test_utils::mark;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct ParsedRule {
|
||||
|
@ -102,14 +103,35 @@ impl RuleBuilder {
|
|||
}
|
||||
}
|
||||
|
||||
fn build(self) -> Result<Vec<ParsedRule>, SsrError> {
|
||||
fn build(mut self) -> Result<Vec<ParsedRule>, SsrError> {
|
||||
if self.rules.is_empty() {
|
||||
bail!("Not a valid Rust expression, type, item, path or pattern");
|
||||
}
|
||||
// If any rules contain paths, then we reject any rules that don't contain paths. Allowing a
|
||||
// mix leads to strange semantics, since the path-based rules only match things where the
|
||||
// path refers to semantically the same thing, whereas the non-path-based rules could match
|
||||
// anything. Specifically, if we have a rule like `foo ==>> bar` we only want to match the
|
||||
// `foo` that is in the current scope, not any `foo`. However "foo" can be parsed as a
|
||||
// pattern (BIND_PAT -> NAME -> IDENT). Allowing such a rule through would result in
|
||||
// renaming everything called `foo` to `bar`. It'd also be slow, since without a path, we'd
|
||||
// have to use the slow-scan search mechanism.
|
||||
if self.rules.iter().any(|rule| contains_path(&rule.pattern)) {
|
||||
let old_len = self.rules.len();
|
||||
self.rules.retain(|rule| contains_path(&rule.pattern));
|
||||
if self.rules.len() < old_len {
|
||||
mark::hit!(pattern_is_a_single_segment_path);
|
||||
}
|
||||
}
|
||||
Ok(self.rules)
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns whether there are any paths in `node`.
|
||||
fn contains_path(node: &SyntaxNode) -> bool {
|
||||
node.kind() == SyntaxKind::PATH
|
||||
|| node.descendants().any(|node| node.kind() == SyntaxKind::PATH)
|
||||
}
|
||||
|
||||
impl FromStr for SsrRule {
|
||||
type Err = SsrError;
|
||||
|
||||
|
|
|
@ -886,6 +886,45 @@ fn ufcs_matches_method_call() {
|
|||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pattern_is_a_single_segment_path() {
|
||||
mark::check!(pattern_is_a_single_segment_path);
|
||||
// The first function should not be altered because the `foo` in scope at the cursor position is
|
||||
// a different `foo`. This case is special because "foo" can be parsed as a pattern (BIND_PAT ->
|
||||
// NAME -> IDENT), which contains no path. If we're not careful we'll end up matching the `foo`
|
||||
// in `let foo` from the first function. Whether we should match the `let foo` in the second
|
||||
// function is less clear. At the moment, we don't. Doing so sounds like a rename operation,
|
||||
// which isn't really what SSR is for, especially since the replacement `bar` must be able to be
|
||||
// resolved, which means if we rename `foo` we'll get a name collision.
|
||||
assert_ssr_transform(
|
||||
"foo ==>> bar",
|
||||
r#"
|
||||
fn f1() -> i32 {
|
||||
let foo = 1;
|
||||
let bar = 2;
|
||||
foo
|
||||
}
|
||||
fn f1() -> i32 {
|
||||
let foo = 1;
|
||||
let bar = 2;
|
||||
foo<|>
|
||||
}
|
||||
"#,
|
||||
expect![[r#"
|
||||
fn f1() -> i32 {
|
||||
let foo = 1;
|
||||
let bar = 2;
|
||||
foo
|
||||
}
|
||||
fn f1() -> i32 {
|
||||
let foo = 1;
|
||||
let bar = 2;
|
||||
bar
|
||||
}
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn replace_local_variable_reference() {
|
||||
// The pattern references a local variable `foo` in the block containing the cursor. We should
|
||||
|
|
Loading…
Reference in a new issue