mirror of
https://github.com/rust-lang/rust-analyzer
synced 2024-12-26 13:03:31 +00:00
Merge #5637
5637: SSR: Matching trait associated constants, types and functions r=matklad a=davidlattimore 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. Co-authored-by: David Lattimore <dml@google.com>
This commit is contained in:
commit
1e8b2c498a
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
|
||||
// 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
|
||||
// `$s.baz($a)`, provided the method call `baz` resolves to the method `foo::Bar::baz`.
|
||||
// Inherent method calls should generally be written in UFCS form. e.g. `foo::Bar::baz($s, $a)` will
|
||||
// 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
|
||||
// it will apply to the whole workspace.
|
||||
|
|
|
@ -5,7 +5,7 @@ use crate::{parsing, SsrError};
|
|||
use parsing::Placeholder;
|
||||
use ra_db::FilePosition;
|
||||
use ra_syntax::{ast, SmolStr, SyntaxKind, SyntaxNode, SyntaxToken};
|
||||
use rustc_hash::{FxHashMap, FxHashSet};
|
||||
use rustc_hash::FxHashMap;
|
||||
use test_utils::mark;
|
||||
|
||||
pub(crate) struct ResolutionScope<'db> {
|
||||
|
@ -124,10 +124,12 @@ impl Resolver<'_, '_> {
|
|||
.resolution_scope
|
||||
.resolve_path(&path)
|
||||
.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 });
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
}
|
||||
for node in node.children() {
|
||||
self.resolve(node, depth + 1, resolved_paths)?;
|
||||
}
|
||||
|
@ -149,6 +151,27 @@ impl Resolver<'_, '_> {
|
|||
}
|
||||
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> {
|
||||
|
@ -195,7 +218,7 @@ impl<'db> ResolutionScope<'db> {
|
|||
adt.ty(self.scope.db).iterate_path_candidates(
|
||||
self.scope.db,
|
||||
self.scope.module()?.krate(),
|
||||
&FxHashSet::default(),
|
||||
&self.scope.traits_in_scope(),
|
||||
Some(hir_path.segments().last()?.name),
|
||||
|_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]
|
||||
fn replace_path_in_different_contexts() {
|
||||
// Note the <|> inside module a::b which marks the point where the rule is interpreted. We
|
||||
|
|
Loading…
Reference in a new issue