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:
bors[bot] 2020-08-12 13:50:34 +00:00 committed by GitHub
commit 1e8b2c498a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 93 additions and 6 deletions

View file

@ -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.

View file

@ -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)),
)

View file

@ -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