diff options
author | David Lattimore <[email protected]> | 2020-08-01 03:43:10 +0100 |
---|---|---|
committer | David Lattimore <[email protected]> | 2020-08-01 03:55:26 +0100 |
commit | 21d2cebcf1a417bce72da98aa638a20235c050db (patch) | |
tree | 5b89fe4b74fef8ce71da396d903f18e2a2ea9da2 | |
parent | 5af32aeb2b83c7ae8adf3e088bf4f3691aa45eb1 (diff) |
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.
-rw-r--r-- | crates/ra_ide/src/ssr.rs | 4 | ||||
-rw-r--r-- | crates/ra_ssr/src/resolving.rs | 31 | ||||
-rw-r--r-- | crates/ra_ssr/src/tests.rs | 64 |
3 files changed, 93 insertions, 6 deletions
diff --git a/crates/ra_ide/src/ssr.rs b/crates/ra_ide/src/ssr.rs index 4348b43be..8be862fd6 100644 --- a/crates/ra_ide/src/ssr.rs +++ b/crates/ra_ide/src/ssr.rs | |||
@@ -21,8 +21,8 @@ use ra_ssr::{MatchFinder, SsrError, SsrRule}; | |||
21 | // replacement occurs. For example if our replacement template is `foo::Bar` and we match some | 21 | // replacement occurs. For example if our replacement template is `foo::Bar` and we match some |
22 | // code in the `foo` module, we'll insert just `Bar`. | 22 | // code in the `foo` module, we'll insert just `Bar`. |
23 | // | 23 | // |
24 | // Method calls should generally be written in UFCS form. e.g. `foo::Bar::baz($s, $a)` will match | 24 | // Inherent method calls should generally be written in UFCS form. e.g. `foo::Bar::baz($s, $a)` will |
25 | // `$s.baz($a)`, provided the method call `baz` resolves to the method `foo::Bar::baz`. | 25 | // match `$s.baz($a)`, provided the method call `baz` resolves to the method `foo::Bar::baz`. |
26 | // | 26 | // |
27 | // The scope of the search / replace will be restricted to the current selection if any, otherwise | 27 | // The scope of the search / replace will be restricted to the current selection if any, otherwise |
28 | // it will apply to the whole workspace. | 28 | // it will apply to the whole workspace. |
diff --git a/crates/ra_ssr/src/resolving.rs b/crates/ra_ssr/src/resolving.rs index 6f62000f4..d5b65eaac 100644 --- a/crates/ra_ssr/src/resolving.rs +++ b/crates/ra_ssr/src/resolving.rs | |||
@@ -5,7 +5,7 @@ use crate::{parsing, SsrError}; | |||
5 | use parsing::Placeholder; | 5 | use parsing::Placeholder; |
6 | use ra_db::FilePosition; | 6 | use ra_db::FilePosition; |
7 | use ra_syntax::{ast, SmolStr, SyntaxKind, SyntaxNode, SyntaxToken}; | 7 | use ra_syntax::{ast, SmolStr, SyntaxKind, SyntaxNode, SyntaxToken}; |
8 | use rustc_hash::{FxHashMap, FxHashSet}; | 8 | use rustc_hash::FxHashMap; |
9 | use test_utils::mark; | 9 | use test_utils::mark; |
10 | 10 | ||
11 | pub(crate) struct ResolutionScope<'db> { | 11 | pub(crate) struct ResolutionScope<'db> { |
@@ -111,8 +111,10 @@ impl Resolver<'_, '_> { | |||
111 | .resolution_scope | 111 | .resolution_scope |
112 | .resolve_path(&path) | 112 | .resolve_path(&path) |
113 | .ok_or_else(|| error!("Failed to resolve path `{}`", node.text()))?; | 113 | .ok_or_else(|| error!("Failed to resolve path `{}`", node.text()))?; |
114 | resolved_paths.insert(node, ResolvedPath { resolution, depth }); | 114 | if self.ok_to_use_path_resolution(&resolution) { |
115 | return Ok(()); | 115 | resolved_paths.insert(node, ResolvedPath { resolution, depth }); |
116 | return Ok(()); | ||
117 | } | ||
116 | } | 118 | } |
117 | } | 119 | } |
118 | for node in node.children() { | 120 | for node in node.children() { |
@@ -136,6 +138,27 @@ impl Resolver<'_, '_> { | |||
136 | } | 138 | } |
137 | false | 139 | false |
138 | } | 140 | } |
141 | |||
142 | fn ok_to_use_path_resolution(&self, resolution: &hir::PathResolution) -> bool { | ||
143 | match resolution { | ||
144 | hir::PathResolution::AssocItem(hir::AssocItem::Function(function)) => { | ||
145 | if function.has_self_param(self.resolution_scope.scope.db) { | ||
146 | // If we don't use this path resolution, then we won't be able to match method | ||
147 | // calls. e.g. `Foo::bar($s)` should match `x.bar()`. | ||
148 | true | ||
149 | } else { | ||
150 | mark::hit!(replace_associated_trait_default_function_call); | ||
151 | false | ||
152 | } | ||
153 | } | ||
154 | hir::PathResolution::AssocItem(_) => { | ||
155 | // Not a function. Could be a constant or an associated type. | ||
156 | mark::hit!(replace_associated_trait_constant); | ||
157 | false | ||
158 | } | ||
159 | _ => true, | ||
160 | } | ||
161 | } | ||
139 | } | 162 | } |
140 | 163 | ||
141 | impl<'db> ResolutionScope<'db> { | 164 | impl<'db> ResolutionScope<'db> { |
@@ -176,7 +199,7 @@ impl<'db> ResolutionScope<'db> { | |||
176 | adt.ty(self.scope.db).iterate_path_candidates( | 199 | adt.ty(self.scope.db).iterate_path_candidates( |
177 | self.scope.db, | 200 | self.scope.db, |
178 | self.scope.module()?.krate(), | 201 | self.scope.module()?.krate(), |
179 | &FxHashSet::default(), | 202 | &self.scope.traits_in_scope(), |
180 | Some(hir_path.segments().last()?.name), | 203 | Some(hir_path.segments().last()?.name), |
181 | |_ty, assoc_item| Some(hir::PathResolution::AssocItem(assoc_item)), | 204 | |_ty, assoc_item| Some(hir::PathResolution::AssocItem(assoc_item)), |
182 | ) | 205 | ) |
diff --git a/crates/ra_ssr/src/tests.rs b/crates/ra_ssr/src/tests.rs index 2ae03c64c..0a49a46e3 100644 --- a/crates/ra_ssr/src/tests.rs +++ b/crates/ra_ssr/src/tests.rs | |||
@@ -550,6 +550,70 @@ fn replace_associated_function_call() { | |||
550 | } | 550 | } |
551 | 551 | ||
552 | #[test] | 552 | #[test] |
553 | fn replace_associated_trait_default_function_call() { | ||
554 | mark::check!(replace_associated_trait_default_function_call); | ||
555 | assert_ssr_transform( | ||
556 | "Bar2::foo() ==>> Bar2::foo2()", | ||
557 | r#" | ||
558 | trait Foo { fn foo() {} } | ||
559 | pub struct Bar {} | ||
560 | impl Foo for Bar {} | ||
561 | pub struct Bar2 {} | ||
562 | impl Foo for Bar2 {} | ||
563 | impl Bar2 { fn foo2() {} } | ||
564 | fn main() { | ||
565 | Bar::foo(); | ||
566 | Bar2::foo(); | ||
567 | } | ||
568 | "#, | ||
569 | expect![[r#" | ||
570 | trait Foo { fn foo() {} } | ||
571 | pub struct Bar {} | ||
572 | impl Foo for Bar {} | ||
573 | pub struct Bar2 {} | ||
574 | impl Foo for Bar2 {} | ||
575 | impl Bar2 { fn foo2() {} } | ||
576 | fn main() { | ||
577 | Bar::foo(); | ||
578 | Bar2::foo2(); | ||
579 | } | ||
580 | "#]], | ||
581 | ); | ||
582 | } | ||
583 | |||
584 | #[test] | ||
585 | fn replace_associated_trait_constant() { | ||
586 | mark::check!(replace_associated_trait_constant); | ||
587 | assert_ssr_transform( | ||
588 | "Bar2::VALUE ==>> Bar2::VALUE_2222", | ||
589 | r#" | ||
590 | trait Foo { const VALUE: i32; const VALUE_2222: i32; } | ||
591 | pub struct Bar {} | ||
592 | impl Foo for Bar { const VALUE: i32 = 1; const VALUE_2222: i32 = 2; } | ||
593 | pub struct Bar2 {} | ||
594 | impl Foo for Bar2 { const VALUE: i32 = 1; const VALUE_2222: i32 = 2; } | ||
595 | impl Bar2 { fn foo2() {} } | ||
596 | fn main() { | ||
597 | Bar::VALUE; | ||
598 | Bar2::VALUE; | ||
599 | } | ||
600 | "#, | ||
601 | expect![[r#" | ||
602 | trait Foo { const VALUE: i32; const VALUE_2222: i32; } | ||
603 | pub struct Bar {} | ||
604 | impl Foo for Bar { const VALUE: i32 = 1; const VALUE_2222: i32 = 2; } | ||
605 | pub struct Bar2 {} | ||
606 | impl Foo for Bar2 { const VALUE: i32 = 1; const VALUE_2222: i32 = 2; } | ||
607 | impl Bar2 { fn foo2() {} } | ||
608 | fn main() { | ||
609 | Bar::VALUE; | ||
610 | Bar2::VALUE_2222; | ||
611 | } | ||
612 | "#]], | ||
613 | ); | ||
614 | } | ||
615 | |||
616 | #[test] | ||
553 | fn replace_path_in_different_contexts() { | 617 | fn replace_path_in_different_contexts() { |
554 | // Note the <|> inside module a::b which marks the point where the rule is interpreted. We | 618 | // Note the <|> inside module a::b which marks the point where the rule is interpreted. We |
555 | // replace foo with bar, but both need different path qualifiers in different contexts. In f4, | 619 | // replace foo with bar, but both need different path qualifiers in different contexts. In f4, |