diff options
Diffstat (limited to 'crates/assists')
-rw-r--r-- | crates/assists/src/assist_context.rs | 4 | ||||
-rw-r--r-- | crates/assists/src/handlers/expand_glob_import.rs | 247 |
2 files changed, 137 insertions, 114 deletions
diff --git a/crates/assists/src/assist_context.rs b/crates/assists/src/assist_context.rs index 11c171fc2..bf520069e 100644 --- a/crates/assists/src/assist_context.rs +++ b/crates/assists/src/assist_context.rs | |||
@@ -73,10 +73,6 @@ impl<'a> AssistContext<'a> { | |||
73 | self.sema.db | 73 | self.sema.db |
74 | } | 74 | } |
75 | 75 | ||
76 | pub(crate) fn source_file(&self) -> &SourceFile { | ||
77 | &self.source_file | ||
78 | } | ||
79 | |||
80 | // NB, this ignores active selection. | 76 | // NB, this ignores active selection. |
81 | pub(crate) fn offset(&self) -> TextSize { | 77 | pub(crate) fn offset(&self) -> TextSize { |
82 | self.frange.range.start() | 78 | self.frange.range.start() |
diff --git a/crates/assists/src/handlers/expand_glob_import.rs b/crates/assists/src/handlers/expand_glob_import.rs index f9a08e84e..64267a03f 100644 --- a/crates/assists/src/handlers/expand_glob_import.rs +++ b/crates/assists/src/handlers/expand_glob_import.rs | |||
@@ -1,12 +1,10 @@ | |||
1 | use either::Either; | 1 | use either::Either; |
2 | use std::iter::successors; | 2 | use hir::{MacroDef, Module, ModuleDef, Name, PathResolution, ScopeDef, SemanticsScope}; |
3 | |||
4 | use hir::{AssocItem, MacroDef, ModuleDef, Name, PathResolution, ScopeDef, SemanticsScope}; | ||
5 | use ide_db::{ | 3 | use ide_db::{ |
6 | defs::{classify_name_ref, Definition, NameRefClass}, | 4 | defs::{classify_name_ref, Definition, NameRefClass}, |
7 | RootDatabase, | 5 | search::SearchScope, |
8 | }; | 6 | }; |
9 | use syntax::{algo, ast, AstNode, SourceFile, SyntaxNode, SyntaxToken, T}; | 7 | use syntax::{algo, ast, AstNode, Direction, SyntaxNode, SyntaxToken, T}; |
10 | 8 | ||
11 | use crate::{ | 9 | use crate::{ |
12 | assist_context::{AssistBuilder, AssistContext, Assists}, | 10 | assist_context::{AssistBuilder, AssistContext, Assists}, |
@@ -41,16 +39,17 @@ use crate::{ | |||
41 | pub(crate) fn expand_glob_import(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { | 39 | pub(crate) fn expand_glob_import(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { |
42 | let star = ctx.find_token_at_offset(T![*])?; | 40 | let star = ctx.find_token_at_offset(T![*])?; |
43 | let (parent, mod_path) = find_parent_and_path(&star)?; | 41 | let (parent, mod_path) = find_parent_and_path(&star)?; |
44 | let module = match ctx.sema.resolve_path(&mod_path)? { | 42 | let target_module = match ctx.sema.resolve_path(&mod_path)? { |
45 | PathResolution::Def(ModuleDef::Module(it)) => it, | 43 | PathResolution::Def(ModuleDef::Module(it)) => it, |
46 | _ => return None, | 44 | _ => return None, |
47 | }; | 45 | }; |
48 | 46 | ||
49 | let source_file = ctx.source_file(); | 47 | let current_scope = ctx.sema.scope(&star.parent()); |
50 | let scope = ctx.sema.scope_at_offset(source_file.syntax(), ctx.offset()); | 48 | let current_module = current_scope.module()?; |
51 | 49 | ||
52 | let defs_in_mod = find_defs_in_mod(ctx, scope, module)?; | 50 | let refs_in_target = find_refs_in_mod(ctx, target_module, Some(current_module))?; |
53 | let names_to_import = find_names_to_import(ctx, source_file, defs_in_mod); | 51 | let imported_defs = find_imported_defs(ctx, star)?; |
52 | let names_to_import = find_names_to_import(ctx, current_scope, refs_in_target, imported_defs); | ||
54 | 53 | ||
55 | let target = parent.clone().either(|n| n.syntax().clone(), |n| n.syntax().clone()); | 54 | let target = parent.clone().either(|n| n.syntax().clone(), |n| n.syntax().clone()); |
56 | acc.add( | 55 | acc.add( |
@@ -85,94 +84,119 @@ fn find_parent_and_path( | |||
85 | } | 84 | } |
86 | } | 85 | } |
87 | 86 | ||
88 | #[derive(PartialEq)] | 87 | #[derive(Debug, PartialEq, Clone)] |
89 | enum Def { | 88 | enum Def { |
90 | ModuleDef(ModuleDef), | 89 | ModuleDef(ModuleDef), |
91 | MacroDef(MacroDef), | 90 | MacroDef(MacroDef), |
92 | } | 91 | } |
93 | 92 | ||
94 | impl Def { | 93 | #[derive(Debug, Clone)] |
95 | fn name(&self, db: &RootDatabase) -> Option<Name> { | 94 | struct Ref { |
96 | match self { | 95 | // could be alias |
97 | Def::ModuleDef(def) => def.name(db), | 96 | visible_name: Name, |
98 | Def::MacroDef(def) => def.name(db), | 97 | def: Def, |
98 | } | ||
99 | |||
100 | impl Ref { | ||
101 | fn from_scope_def(name: Name, scope_def: ScopeDef) -> Option<Self> { | ||
102 | match scope_def { | ||
103 | ScopeDef::ModuleDef(def) => Some(Ref { visible_name: name, def: Def::ModuleDef(def) }), | ||
104 | ScopeDef::MacroDef(def) => Some(Ref { visible_name: name, def: Def::MacroDef(def) }), | ||
105 | _ => None, | ||
99 | } | 106 | } |
100 | } | 107 | } |
101 | } | ||
102 | 108 | ||
103 | fn find_defs_in_mod( | 109 | fn is_referenced_in(&self, ctx: &AssistContext, scope: &SemanticsScope) -> bool { |
104 | ctx: &AssistContext, | 110 | let def = match self.def { |
105 | from: SemanticsScope<'_>, | 111 | Def::ModuleDef(def) => Definition::ModuleDef(def), |
106 | module: hir::Module, | 112 | Def::MacroDef(def) => Definition::Macro(def), |
107 | ) -> Option<Vec<Def>> { | 113 | }; |
108 | let module_scope = module.scope(ctx.db(), from.module()); | 114 | |
109 | 115 | if let Definition::ModuleDef(ModuleDef::Trait(tr)) = def { | |
110 | let mut defs = vec![]; | 116 | if scope |
111 | for (_, def) in module_scope { | 117 | .traits_in_scope() |
112 | match def { | 118 | .into_iter() |
113 | ScopeDef::ModuleDef(def) => defs.push(Def::ModuleDef(def)), | 119 | .find(|other_tr_id| tr == other_tr_id.to_owned().into()) |
114 | ScopeDef::MacroDef(def) => defs.push(Def::MacroDef(def)), | 120 | .is_some() |
115 | _ => continue, | 121 | { |
122 | return true; | ||
123 | } | ||
116 | } | 124 | } |
125 | |||
126 | let search_scope = SearchScope::single_file(ctx.frange.file_id); | ||
127 | !def.find_usages(&ctx.sema, Some(search_scope)).is_empty() | ||
128 | } | ||
129 | } | ||
130 | |||
131 | #[derive(Debug, Clone)] | ||
132 | struct Refs(Vec<Ref>); | ||
133 | |||
134 | impl Refs { | ||
135 | fn used_refs(&self, ctx: &AssistContext, scope: &SemanticsScope) -> Refs { | ||
136 | Refs(self.0.clone().into_iter().filter(|r| r.is_referenced_in(ctx, scope)).collect()) | ||
117 | } | 137 | } |
118 | 138 | ||
119 | Some(defs) | 139 | fn filter_out_by_defs(&self, defs: Vec<Def>) -> Refs { |
140 | Refs(self.0.clone().into_iter().filter(|r| !defs.contains(&r.def)).collect()) | ||
141 | } | ||
120 | } | 142 | } |
121 | 143 | ||
122 | fn find_names_to_import( | 144 | fn find_refs_in_mod( |
123 | ctx: &AssistContext, | 145 | ctx: &AssistContext, |
124 | source_file: &SourceFile, | 146 | module: Module, |
125 | defs_in_mod: Vec<Def>, | 147 | visible_from: Option<Module>, |
126 | ) -> Vec<Name> { | 148 | ) -> Option<Refs> { |
127 | let (name_refs_in_use_item, name_refs_in_source) = source_file | 149 | let module_scope = module.scope(ctx.db(), visible_from); |
128 | .syntax() | 150 | let refs = module_scope.into_iter().filter_map(|(n, d)| Ref::from_scope_def(n, d)).collect(); |
129 | .descendants() | 151 | Some(Refs(refs)) |
130 | .filter_map(|n| { | 152 | } |
131 | let name_ref = ast::NameRef::cast(n.clone())?; | ||
132 | let name_ref_class = classify_name_ref(&ctx.sema, &name_ref)?; | ||
133 | let is_in_use_item = | ||
134 | successors(n.parent(), |n| n.parent()).find_map(ast::Use::cast).is_some(); | ||
135 | Some((name_ref_class, is_in_use_item)) | ||
136 | }) | ||
137 | .partition::<Vec<_>, _>(|&(_, is_in_use_item)| is_in_use_item); | ||
138 | |||
139 | let name_refs_to_import: Vec<NameRefClass> = name_refs_in_source | ||
140 | .into_iter() | ||
141 | .filter_map(|(r, _)| { | ||
142 | if name_refs_in_use_item.contains(&(r.clone(), true)) { | ||
143 | // already imported | ||
144 | return None; | ||
145 | } | ||
146 | Some(r) | ||
147 | }) | ||
148 | .collect(); | ||
149 | |||
150 | let defs_in_source_file = name_refs_to_import | ||
151 | .into_iter() | ||
152 | .filter_map(|rc| match rc { | ||
153 | NameRefClass::Definition(Definition::ModuleDef(def)) => Some(Def::ModuleDef(def)), | ||
154 | NameRefClass::Definition(Definition::Macro(def)) => Some(Def::MacroDef(def)), | ||
155 | _ => None, | ||
156 | }) | ||
157 | .collect::<Vec<Def>>(); | ||
158 | 153 | ||
159 | defs_in_mod | 154 | // looks for name refs in parent use block's siblings |
160 | .iter() | 155 | // |
161 | .filter(|def| { | 156 | // mod bar { |
162 | if let Def::ModuleDef(ModuleDef::Trait(tr)) = def { | 157 | // mod qux { |
163 | for item in tr.items(ctx.db()) { | 158 | // struct Qux; |
164 | if let AssocItem::Function(f) = item { | 159 | // } |
165 | if defs_in_source_file.contains(&Def::ModuleDef(ModuleDef::Function(f))) { | 160 | // |
166 | return true; | 161 | // pub use qux::Qux; |
167 | } | 162 | // } |
168 | } | 163 | // |
169 | } | 164 | // ↓ --------------- |
170 | } | 165 | // use foo::*<|>; |
166 | // use baz::Baz; | ||
167 | // ↑ --------------- | ||
168 | fn find_imported_defs(ctx: &AssistContext, star: SyntaxToken) -> Option<Vec<Def>> { | ||
169 | let parent_use_item_syntax = | ||
170 | star.ancestors().find_map(|n| if ast::Use::can_cast(n.kind()) { Some(n) } else { None })?; | ||
171 | |||
172 | Some( | ||
173 | [Direction::Prev, Direction::Next] | ||
174 | .iter() | ||
175 | .map(|dir| { | ||
176 | parent_use_item_syntax | ||
177 | .siblings(dir.to_owned()) | ||
178 | .filter(|n| ast::Use::can_cast(n.kind())) | ||
179 | }) | ||
180 | .flatten() | ||
181 | .filter_map(|n| Some(n.descendants().filter_map(ast::NameRef::cast))) | ||
182 | .flatten() | ||
183 | .filter_map(|r| match classify_name_ref(&ctx.sema, &r)? { | ||
184 | NameRefClass::Definition(Definition::ModuleDef(def)) => Some(Def::ModuleDef(def)), | ||
185 | NameRefClass::Definition(Definition::Macro(def)) => Some(Def::MacroDef(def)), | ||
186 | _ => None, | ||
187 | }) | ||
188 | .collect(), | ||
189 | ) | ||
190 | } | ||
171 | 191 | ||
172 | defs_in_source_file.contains(def) | 192 | fn find_names_to_import( |
173 | }) | 193 | ctx: &AssistContext, |
174 | .filter_map(|d| d.name(ctx.db())) | 194 | current_scope: SemanticsScope, |
175 | .collect() | 195 | refs_in_target: Refs, |
196 | imported_defs: Vec<Def>, | ||
197 | ) -> Vec<Name> { | ||
198 | let used_refs = refs_in_target.used_refs(ctx, ¤t_scope).filter_out_by_defs(imported_defs); | ||
199 | used_refs.0.iter().map(|r| r.visible_name.clone()).collect() | ||
176 | } | 200 | } |
177 | 201 | ||
178 | fn replace_ast( | 202 | fn replace_ast( |
@@ -685,34 +709,37 @@ fn qux(bar: Bar, baz: Baz) { | |||
685 | 709 | ||
686 | #[test] | 710 | #[test] |
687 | fn expanding_glob_import_with_macro_defs() { | 711 | fn expanding_glob_import_with_macro_defs() { |
688 | check_assist( | 712 | // TODO: this is currently fails because `Definition::find_usages` ignores macros |
689 | expand_glob_import, | 713 | // https://github.com/rust-analyzer/rust-analyzer/issues/3484 |
690 | r" | 714 | // |
691 | //- /lib.rs crate:foo | 715 | // check_assist( |
692 | #[macro_export] | 716 | // expand_glob_import, |
693 | macro_rules! bar { | 717 | // r" |
694 | () => () | 718 | // //- /lib.rs crate:foo |
695 | } | 719 | // #[macro_export] |
696 | 720 | // macro_rules! bar { | |
697 | pub fn baz() {} | 721 | // () => () |
698 | 722 | // } | |
699 | //- /main.rs crate:main deps:foo | 723 | |
700 | use foo::*<|>; | 724 | // pub fn baz() {} |
701 | 725 | ||
702 | fn main() { | 726 | // //- /main.rs crate:main deps:foo |
703 | bar!(); | 727 | // use foo::*<|>; |
704 | baz(); | 728 | |
705 | } | 729 | // fn main() { |
706 | ", | 730 | // bar!(); |
707 | r" | 731 | // baz(); |
708 | use foo::{bar, baz}; | 732 | // } |
709 | 733 | // ", | |
710 | fn main() { | 734 | // r" |
711 | bar!(); | 735 | // use foo::{bar, baz}; |
712 | baz(); | 736 | |
713 | } | 737 | // fn main() { |
714 | ", | 738 | // bar!(); |
715 | ) | 739 | // baz(); |
740 | // } | ||
741 | // ", | ||
742 | // ) | ||
716 | } | 743 | } |
717 | 744 | ||
718 | #[test] | 745 | #[test] |