mirror of
https://github.com/rust-lang/rust-analyzer.git
synced 2025-09-28 04:44:57 +00:00
SSR: Matching trait associated constants, types and functions
This fixes matching of things like `HashMap::default()` by resolving `HashMap` instead of `default` (which resolves to `Default::default`). Same for associated constants and types that are part of a trait implementation. However, we still don't support matching calls to trait methods.
This commit is contained in:
parent
5af32aeb2b
commit
21d2cebcf1
3 changed files with 93 additions and 6 deletions
|
@ -21,8 +21,8 @@ use ra_ssr::{MatchFinder, SsrError, SsrRule};
|
||||||
// replacement occurs. For example if our replacement template is `foo::Bar` and we match some
|
// replacement occurs. For example if our replacement template is `foo::Bar` and we match some
|
||||||
// code in the `foo` module, we'll insert just `Bar`.
|
// code in the `foo` module, we'll insert just `Bar`.
|
||||||
//
|
//
|
||||||
// Method calls should generally be written in UFCS form. e.g. `foo::Bar::baz($s, $a)` will match
|
// Inherent method calls should generally be written in UFCS form. e.g. `foo::Bar::baz($s, $a)` will
|
||||||
// `$s.baz($a)`, provided the method call `baz` resolves to the method `foo::Bar::baz`.
|
// match `$s.baz($a)`, provided the method call `baz` resolves to the method `foo::Bar::baz`.
|
||||||
//
|
//
|
||||||
// The scope of the search / replace will be restricted to the current selection if any, otherwise
|
// The scope of the search / replace will be restricted to the current selection if any, otherwise
|
||||||
// it will apply to the whole workspace.
|
// it will apply to the whole workspace.
|
||||||
|
|
|
@ -5,7 +5,7 @@ use crate::{parsing, SsrError};
|
||||||
use parsing::Placeholder;
|
use parsing::Placeholder;
|
||||||
use ra_db::FilePosition;
|
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;
|
||||||
use test_utils::mark;
|
use test_utils::mark;
|
||||||
|
|
||||||
pub(crate) struct ResolutionScope<'db> {
|
pub(crate) struct ResolutionScope<'db> {
|
||||||
|
@ -111,10 +111,12 @@ impl Resolver<'_, '_> {
|
||||||
.resolution_scope
|
.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()))?;
|
||||||
|
if self.ok_to_use_path_resolution(&resolution) {
|
||||||
resolved_paths.insert(node, ResolvedPath { resolution, depth });
|
resolved_paths.insert(node, ResolvedPath { resolution, depth });
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
for node in node.children() {
|
for node in node.children() {
|
||||||
self.resolve(node, depth + 1, resolved_paths)?;
|
self.resolve(node, depth + 1, resolved_paths)?;
|
||||||
}
|
}
|
||||||
|
@ -136,6 +138,27 @@ impl Resolver<'_, '_> {
|
||||||
}
|
}
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn ok_to_use_path_resolution(&self, resolution: &hir::PathResolution) -> bool {
|
||||||
|
match resolution {
|
||||||
|
hir::PathResolution::AssocItem(hir::AssocItem::Function(function)) => {
|
||||||
|
if function.has_self_param(self.resolution_scope.scope.db) {
|
||||||
|
// If we don't use this path resolution, then we won't be able to match method
|
||||||
|
// calls. e.g. `Foo::bar($s)` should match `x.bar()`.
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
mark::hit!(replace_associated_trait_default_function_call);
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
hir::PathResolution::AssocItem(_) => {
|
||||||
|
// Not a function. Could be a constant or an associated type.
|
||||||
|
mark::hit!(replace_associated_trait_constant);
|
||||||
|
false
|
||||||
|
}
|
||||||
|
_ => true,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'db> ResolutionScope<'db> {
|
impl<'db> ResolutionScope<'db> {
|
||||||
|
@ -176,7 +199,7 @@ impl<'db> ResolutionScope<'db> {
|
||||||
adt.ty(self.scope.db).iterate_path_candidates(
|
adt.ty(self.scope.db).iterate_path_candidates(
|
||||||
self.scope.db,
|
self.scope.db,
|
||||||
self.scope.module()?.krate(),
|
self.scope.module()?.krate(),
|
||||||
&FxHashSet::default(),
|
&self.scope.traits_in_scope(),
|
||||||
Some(hir_path.segments().last()?.name),
|
Some(hir_path.segments().last()?.name),
|
||||||
|_ty, assoc_item| Some(hir::PathResolution::AssocItem(assoc_item)),
|
|_ty, assoc_item| Some(hir::PathResolution::AssocItem(assoc_item)),
|
||||||
)
|
)
|
||||||
|
|
|
@ -549,6 +549,70 @@ fn replace_associated_function_call() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn replace_associated_trait_default_function_call() {
|
||||||
|
mark::check!(replace_associated_trait_default_function_call);
|
||||||
|
assert_ssr_transform(
|
||||||
|
"Bar2::foo() ==>> Bar2::foo2()",
|
||||||
|
r#"
|
||||||
|
trait Foo { fn foo() {} }
|
||||||
|
pub struct Bar {}
|
||||||
|
impl Foo for Bar {}
|
||||||
|
pub struct Bar2 {}
|
||||||
|
impl Foo for Bar2 {}
|
||||||
|
impl Bar2 { fn foo2() {} }
|
||||||
|
fn main() {
|
||||||
|
Bar::foo();
|
||||||
|
Bar2::foo();
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
expect![[r#"
|
||||||
|
trait Foo { fn foo() {} }
|
||||||
|
pub struct Bar {}
|
||||||
|
impl Foo for Bar {}
|
||||||
|
pub struct Bar2 {}
|
||||||
|
impl Foo for Bar2 {}
|
||||||
|
impl Bar2 { fn foo2() {} }
|
||||||
|
fn main() {
|
||||||
|
Bar::foo();
|
||||||
|
Bar2::foo2();
|
||||||
|
}
|
||||||
|
"#]],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn replace_associated_trait_constant() {
|
||||||
|
mark::check!(replace_associated_trait_constant);
|
||||||
|
assert_ssr_transform(
|
||||||
|
"Bar2::VALUE ==>> Bar2::VALUE_2222",
|
||||||
|
r#"
|
||||||
|
trait Foo { const VALUE: i32; const VALUE_2222: i32; }
|
||||||
|
pub struct Bar {}
|
||||||
|
impl Foo for Bar { const VALUE: i32 = 1; const VALUE_2222: i32 = 2; }
|
||||||
|
pub struct Bar2 {}
|
||||||
|
impl Foo for Bar2 { const VALUE: i32 = 1; const VALUE_2222: i32 = 2; }
|
||||||
|
impl Bar2 { fn foo2() {} }
|
||||||
|
fn main() {
|
||||||
|
Bar::VALUE;
|
||||||
|
Bar2::VALUE;
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
expect![[r#"
|
||||||
|
trait Foo { const VALUE: i32; const VALUE_2222: i32; }
|
||||||
|
pub struct Bar {}
|
||||||
|
impl Foo for Bar { const VALUE: i32 = 1; const VALUE_2222: i32 = 2; }
|
||||||
|
pub struct Bar2 {}
|
||||||
|
impl Foo for Bar2 { const VALUE: i32 = 1; const VALUE_2222: i32 = 2; }
|
||||||
|
impl Bar2 { fn foo2() {} }
|
||||||
|
fn main() {
|
||||||
|
Bar::VALUE;
|
||||||
|
Bar2::VALUE_2222;
|
||||||
|
}
|
||||||
|
"#]],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn replace_path_in_different_contexts() {
|
fn replace_path_in_different_contexts() {
|
||||||
// Note the <|> inside module a::b which marks the point where the rule is interpreted. We
|
// Note the <|> inside module a::b which marks the point where the rule is interpreted. We
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue