aboutsummaryrefslogtreecommitdiff
path: root/crates/ide_assists
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ide_assists')
-rw-r--r--crates/ide_assists/Cargo.toml26
-rw-r--r--crates/ide_assists/src/assist_config.rs16
-rw-r--r--crates/ide_assists/src/assist_context.rs253
-rw-r--r--crates/ide_assists/src/ast_transform.rs213
-rw-r--r--crates/ide_assists/src/handlers/add_explicit_type.rs207
-rw-r--r--crates/ide_assists/src/handlers/add_lifetime_to_type.rs228
-rw-r--r--crates/ide_assists/src/handlers/add_missing_impl_members.rs814
-rw-r--r--crates/ide_assists/src/handlers/add_turbo_fish.rs164
-rw-r--r--crates/ide_assists/src/handlers/apply_demorgan.rs93
-rw-r--r--crates/ide_assists/src/handlers/auto_import.rs970
-rw-r--r--crates/ide_assists/src/handlers/change_visibility.rs212
-rw-r--r--crates/ide_assists/src/handlers/convert_integer_literal.rs268
-rw-r--r--crates/ide_assists/src/handlers/early_return.rs515
-rw-r--r--crates/ide_assists/src/handlers/expand_glob_import.rs907
-rw-r--r--crates/ide_assists/src/handlers/extract_function.rs3378
-rw-r--r--crates/ide_assists/src/handlers/extract_struct_from_enum_variant.rs520
-rw-r--r--crates/ide_assists/src/handlers/extract_variable.rs588
-rw-r--r--crates/ide_assists/src/handlers/fill_match_arms.rs787
-rw-r--r--crates/ide_assists/src/handlers/fix_visibility.rs607
-rw-r--r--crates/ide_assists/src/handlers/flip_binexpr.rs134
-rw-r--r--crates/ide_assists/src/handlers/flip_comma.rs84
-rw-r--r--crates/ide_assists/src/handlers/flip_trait_bound.rs121
-rw-r--r--crates/ide_assists/src/handlers/generate_default_from_enum_variant.rs175
-rw-r--r--crates/ide_assists/src/handlers/generate_derive.rs132
-rw-r--r--crates/ide_assists/src/handlers/generate_enum_match_method.rs240
-rw-r--r--crates/ide_assists/src/handlers/generate_from_impl_for_enum.rs268
-rw-r--r--crates/ide_assists/src/handlers/generate_function.rs1071
-rw-r--r--crates/ide_assists/src/handlers/generate_getter.rs192
-rw-r--r--crates/ide_assists/src/handlers/generate_getter_mut.rs195
-rw-r--r--crates/ide_assists/src/handlers/generate_impl.rs166
-rw-r--r--crates/ide_assists/src/handlers/generate_new.rs315
-rw-r--r--crates/ide_assists/src/handlers/generate_setter.rs198
-rw-r--r--crates/ide_assists/src/handlers/infer_function_return_type.rs345
-rw-r--r--crates/ide_assists/src/handlers/inline_function.rs202
-rw-r--r--crates/ide_assists/src/handlers/inline_local_variable.rs732
-rw-r--r--crates/ide_assists/src/handlers/introduce_named_lifetime.rs315
-rw-r--r--crates/ide_assists/src/handlers/invert_if.rs146
-rw-r--r--crates/ide_assists/src/handlers/merge_imports.rs343
-rw-r--r--crates/ide_assists/src/handlers/merge_match_arms.rs248
-rw-r--r--crates/ide_assists/src/handlers/move_bounds.rs152
-rw-r--r--crates/ide_assists/src/handlers/move_guard.rs367
-rw-r--r--crates/ide_assists/src/handlers/move_module_to_file.rs188
-rw-r--r--crates/ide_assists/src/handlers/pull_assignment_up.rs400
-rw-r--r--crates/ide_assists/src/handlers/qualify_path.rs1205
-rw-r--r--crates/ide_assists/src/handlers/raw_string.rs512
-rw-r--r--crates/ide_assists/src/handlers/remove_dbg.rs421
-rw-r--r--crates/ide_assists/src/handlers/remove_mut.rs37
-rw-r--r--crates/ide_assists/src/handlers/remove_unused_param.rs288
-rw-r--r--crates/ide_assists/src/handlers/reorder_fields.rs227
-rw-r--r--crates/ide_assists/src/handlers/reorder_impl.rs201
-rw-r--r--crates/ide_assists/src/handlers/replace_derive_with_manual_impl.rs404
-rw-r--r--crates/ide_assists/src/handlers/replace_if_let_with_match.rs599
-rw-r--r--crates/ide_assists/src/handlers/replace_impl_trait_with_generic.rs168
-rw-r--r--crates/ide_assists/src/handlers/replace_let_with_if_let.rs101
-rw-r--r--crates/ide_assists/src/handlers/replace_qualified_name_with_use.rs678
-rw-r--r--crates/ide_assists/src/handlers/replace_string_with_char.rs137
-rw-r--r--crates/ide_assists/src/handlers/replace_unwrap_with_match.rs188
-rw-r--r--crates/ide_assists/src/handlers/split_import.rs79
-rw-r--r--crates/ide_assists/src/handlers/toggle_ignore.rs98
-rw-r--r--crates/ide_assists/src/handlers/unmerge_use.rs231
-rw-r--r--crates/ide_assists/src/handlers/unwrap_block.rs582
-rw-r--r--crates/ide_assists/src/handlers/wrap_return_type_in_result.rs1158
-rw-r--r--crates/ide_assists/src/lib.rs246
-rw-r--r--crates/ide_assists/src/tests.rs275
-rw-r--r--crates/ide_assists/src/tests/generated.rs1329
-rw-r--r--crates/ide_assists/src/utils.rs434
66 files changed, 27093 insertions, 0 deletions
diff --git a/crates/ide_assists/Cargo.toml b/crates/ide_assists/Cargo.toml
new file mode 100644
index 000000000..a34bdd6c3
--- /dev/null
+++ b/crates/ide_assists/Cargo.toml
@@ -0,0 +1,26 @@
1[package]
2name = "ide_assists"
3version = "0.0.0"
4description = "TBD"
5license = "MIT OR Apache-2.0"
6authors = ["rust-analyzer developers"]
7edition = "2018"
8
9[lib]
10doctest = false
11
12[dependencies]
13rustc-hash = "1.1.0"
14itertools = "0.10.0"
15either = "1.6.1"
16
17stdx = { path = "../stdx", version = "0.0.0" }
18syntax = { path = "../syntax", version = "0.0.0" }
19text_edit = { path = "../text_edit", version = "0.0.0" }
20profile = { path = "../profile", version = "0.0.0" }
21ide_db = { path = "../ide_db", version = "0.0.0" }
22hir = { path = "../hir", version = "0.0.0" }
23test_utils = { path = "../test_utils", version = "0.0.0" }
24
25[dev-dependencies]
26expect-test = "1.1"
diff --git a/crates/ide_assists/src/assist_config.rs b/crates/ide_assists/src/assist_config.rs
new file mode 100644
index 000000000..9cabf037c
--- /dev/null
+++ b/crates/ide_assists/src/assist_config.rs
@@ -0,0 +1,16 @@
1//! Settings for tweaking assists.
2//!
3//! The fun thing here is `SnippetCap` -- this type can only be created in this
4//! module, and we use to statically check that we only produce snippet
5//! assists if we are allowed to.
6
7use ide_db::helpers::{insert_use::InsertUseConfig, SnippetCap};
8
9use crate::AssistKind;
10
11#[derive(Clone, Debug, PartialEq, Eq)]
12pub struct AssistConfig {
13 pub snippet_cap: Option<SnippetCap>,
14 pub allowed: Option<Vec<AssistKind>>,
15 pub insert_use: InsertUseConfig,
16}
diff --git a/crates/ide_assists/src/assist_context.rs b/crates/ide_assists/src/assist_context.rs
new file mode 100644
index 000000000..bba6c08e0
--- /dev/null
+++ b/crates/ide_assists/src/assist_context.rs
@@ -0,0 +1,253 @@
1//! See `AssistContext`
2
3use std::mem;
4
5use hir::Semantics;
6use ide_db::{
7 base_db::{AnchoredPathBuf, FileId, FileRange},
8 helpers::SnippetCap,
9};
10use ide_db::{
11 label::Label,
12 source_change::{FileSystemEdit, SourceChange},
13 RootDatabase,
14};
15use syntax::{
16 algo::{self, find_node_at_offset, SyntaxRewriter},
17 AstNode, AstToken, SourceFile, SyntaxElement, SyntaxKind, SyntaxToken, TextRange, TextSize,
18 TokenAtOffset,
19};
20use text_edit::{TextEdit, TextEditBuilder};
21
22use crate::{assist_config::AssistConfig, Assist, AssistId, AssistKind, GroupLabel};
23
24/// `AssistContext` allows to apply an assist or check if it could be applied.
25///
26/// Assists use a somewhat over-engineered approach, given the current needs.
27/// The assists workflow consists of two phases. In the first phase, a user asks
28/// for the list of available assists. In the second phase, the user picks a
29/// particular assist and it gets applied.
30///
31/// There are two peculiarities here:
32///
33/// * first, we ideally avoid computing more things then necessary to answer "is
34/// assist applicable" in the first phase.
35/// * second, when we are applying assist, we don't have a guarantee that there
36/// weren't any changes between the point when user asked for assists and when
37/// they applied a particular assist. So, when applying assist, we need to do
38/// all the checks from scratch.
39///
40/// To avoid repeating the same code twice for both "check" and "apply"
41/// functions, we use an approach reminiscent of that of Django's function based
42/// views dealing with forms. Each assist receives a runtime parameter,
43/// `resolve`. It first check if an edit is applicable (potentially computing
44/// info required to compute the actual edit). If it is applicable, and
45/// `resolve` is `true`, it then computes the actual edit.
46///
47/// So, to implement the original assists workflow, we can first apply each edit
48/// with `resolve = false`, and then applying the selected edit again, with
49/// `resolve = true` this time.
50///
51/// Note, however, that we don't actually use such two-phase logic at the
52/// moment, because the LSP API is pretty awkward in this place, and it's much
53/// easier to just compute the edit eagerly :-)
54pub(crate) struct AssistContext<'a> {
55 pub(crate) config: &'a AssistConfig,
56 pub(crate) sema: Semantics<'a, RootDatabase>,
57 pub(crate) frange: FileRange,
58 source_file: SourceFile,
59}
60
61impl<'a> AssistContext<'a> {
62 pub(crate) fn new(
63 sema: Semantics<'a, RootDatabase>,
64 config: &'a AssistConfig,
65 frange: FileRange,
66 ) -> AssistContext<'a> {
67 let source_file = sema.parse(frange.file_id);
68 AssistContext { config, sema, frange, source_file }
69 }
70
71 pub(crate) fn db(&self) -> &RootDatabase {
72 self.sema.db
73 }
74
75 // NB, this ignores active selection.
76 pub(crate) fn offset(&self) -> TextSize {
77 self.frange.range.start()
78 }
79
80 pub(crate) fn token_at_offset(&self) -> TokenAtOffset<SyntaxToken> {
81 self.source_file.syntax().token_at_offset(self.offset())
82 }
83 pub(crate) fn find_token_syntax_at_offset(&self, kind: SyntaxKind) -> Option<SyntaxToken> {
84 self.token_at_offset().find(|it| it.kind() == kind)
85 }
86 pub(crate) fn find_token_at_offset<T: AstToken>(&self) -> Option<T> {
87 self.token_at_offset().find_map(T::cast)
88 }
89 pub(crate) fn find_node_at_offset<N: AstNode>(&self) -> Option<N> {
90 find_node_at_offset(self.source_file.syntax(), self.offset())
91 }
92 pub(crate) fn find_node_at_offset_with_descend<N: AstNode>(&self) -> Option<N> {
93 self.sema.find_node_at_offset_with_descend(self.source_file.syntax(), self.offset())
94 }
95 pub(crate) fn covering_element(&self) -> SyntaxElement {
96 self.source_file.syntax().covering_element(self.frange.range)
97 }
98 // FIXME: remove
99 pub(crate) fn covering_node_for_range(&self, range: TextRange) -> SyntaxElement {
100 self.source_file.syntax().covering_element(range)
101 }
102}
103
104pub(crate) struct Assists {
105 resolve: bool,
106 file: FileId,
107 buf: Vec<Assist>,
108 allowed: Option<Vec<AssistKind>>,
109}
110
111impl Assists {
112 pub(crate) fn new(ctx: &AssistContext, resolve: bool) -> Assists {
113 Assists {
114 resolve,
115 file: ctx.frange.file_id,
116 buf: Vec::new(),
117 allowed: ctx.config.allowed.clone(),
118 }
119 }
120
121 pub(crate) fn finish(mut self) -> Vec<Assist> {
122 self.buf.sort_by_key(|assist| assist.target.len());
123 self.buf
124 }
125
126 pub(crate) fn add(
127 &mut self,
128 id: AssistId,
129 label: impl Into<String>,
130 target: TextRange,
131 f: impl FnOnce(&mut AssistBuilder),
132 ) -> Option<()> {
133 if !self.is_allowed(&id) {
134 return None;
135 }
136 let label = Label::new(label.into());
137 let assist = Assist { id, label, group: None, target, source_change: None };
138 self.add_impl(assist, f)
139 }
140
141 pub(crate) fn add_group(
142 &mut self,
143 group: &GroupLabel,
144 id: AssistId,
145 label: impl Into<String>,
146 target: TextRange,
147 f: impl FnOnce(&mut AssistBuilder),
148 ) -> Option<()> {
149 if !self.is_allowed(&id) {
150 return None;
151 }
152 let label = Label::new(label.into());
153 let assist = Assist { id, label, group: Some(group.clone()), target, source_change: None };
154 self.add_impl(assist, f)
155 }
156
157 fn add_impl(&mut self, mut assist: Assist, f: impl FnOnce(&mut AssistBuilder)) -> Option<()> {
158 let source_change = if self.resolve {
159 let mut builder = AssistBuilder::new(self.file);
160 f(&mut builder);
161 Some(builder.finish())
162 } else {
163 None
164 };
165 assist.source_change = source_change;
166
167 self.buf.push(assist);
168 Some(())
169 }
170
171 fn is_allowed(&self, id: &AssistId) -> bool {
172 match &self.allowed {
173 Some(allowed) => allowed.iter().any(|kind| kind.contains(id.1)),
174 None => true,
175 }
176 }
177}
178
179pub(crate) struct AssistBuilder {
180 edit: TextEditBuilder,
181 file_id: FileId,
182 source_change: SourceChange,
183}
184
185impl AssistBuilder {
186 pub(crate) fn new(file_id: FileId) -> AssistBuilder {
187 AssistBuilder { edit: TextEdit::builder(), file_id, source_change: SourceChange::default() }
188 }
189
190 pub(crate) fn edit_file(&mut self, file_id: FileId) {
191 self.commit();
192 self.file_id = file_id;
193 }
194
195 fn commit(&mut self) {
196 let edit = mem::take(&mut self.edit).finish();
197 if !edit.is_empty() {
198 self.source_change.insert_source_edit(self.file_id, edit);
199 }
200 }
201
202 /// Remove specified `range` of text.
203 pub(crate) fn delete(&mut self, range: TextRange) {
204 self.edit.delete(range)
205 }
206 /// Append specified `text` at the given `offset`
207 pub(crate) fn insert(&mut self, offset: TextSize, text: impl Into<String>) {
208 self.edit.insert(offset, text.into())
209 }
210 /// Append specified `snippet` at the given `offset`
211 pub(crate) fn insert_snippet(
212 &mut self,
213 _cap: SnippetCap,
214 offset: TextSize,
215 snippet: impl Into<String>,
216 ) {
217 self.source_change.is_snippet = true;
218 self.insert(offset, snippet);
219 }
220 /// Replaces specified `range` of text with a given string.
221 pub(crate) fn replace(&mut self, range: TextRange, replace_with: impl Into<String>) {
222 self.edit.replace(range, replace_with.into())
223 }
224 /// Replaces specified `range` of text with a given `snippet`.
225 pub(crate) fn replace_snippet(
226 &mut self,
227 _cap: SnippetCap,
228 range: TextRange,
229 snippet: impl Into<String>,
230 ) {
231 self.source_change.is_snippet = true;
232 self.replace(range, snippet);
233 }
234 pub(crate) fn replace_ast<N: AstNode>(&mut self, old: N, new: N) {
235 algo::diff(old.syntax(), new.syntax()).into_text_edit(&mut self.edit)
236 }
237 pub(crate) fn rewrite(&mut self, rewriter: SyntaxRewriter) {
238 if let Some(node) = rewriter.rewrite_root() {
239 let new = rewriter.rewrite(&node);
240 algo::diff(&node, &new).into_text_edit(&mut self.edit);
241 }
242 }
243 pub(crate) fn create_file(&mut self, dst: AnchoredPathBuf, content: impl Into<String>) {
244 let file_system_edit =
245 FileSystemEdit::CreateFile { dst: dst, initial_contents: content.into() };
246 self.source_change.push_file_system_edit(file_system_edit);
247 }
248
249 fn finish(mut self) -> SourceChange {
250 self.commit();
251 mem::take(&mut self.source_change)
252 }
253}
diff --git a/crates/ide_assists/src/ast_transform.rs b/crates/ide_assists/src/ast_transform.rs
new file mode 100644
index 000000000..4a3ed7783
--- /dev/null
+++ b/crates/ide_assists/src/ast_transform.rs
@@ -0,0 +1,213 @@
1//! `AstTransformer`s are functions that replace nodes in an AST and can be easily combined.
2use hir::{HirDisplay, PathResolution, SemanticsScope};
3use ide_db::helpers::mod_path_to_ast;
4use rustc_hash::FxHashMap;
5use syntax::{
6 algo::SyntaxRewriter,
7 ast::{self, AstNode},
8 SyntaxNode,
9};
10
11pub fn apply<'a, N: AstNode>(transformer: &dyn AstTransform<'a>, node: N) -> N {
12 SyntaxRewriter::from_fn(|element| match element {
13 syntax::SyntaxElement::Node(n) => {
14 let replacement = transformer.get_substitution(&n, transformer)?;
15 Some(replacement.into())
16 }
17 _ => None,
18 })
19 .rewrite_ast(&node)
20}
21
22/// `AstTransform` helps with applying bulk transformations to syntax nodes.
23///
24/// This is mostly useful for IDE code generation. If you paste some existing
25/// code into a new context (for example, to add method overrides to an `impl`
26/// block), you generally want to appropriately qualify the names, and sometimes
27/// you might want to substitute generic parameters as well:
28///
29/// ```
30/// mod x {
31/// pub struct A;
32/// pub trait T<U> { fn foo(&self, _: U) -> A; }
33/// }
34///
35/// mod y {
36/// use x::T;
37///
38/// impl T<()> for () {
39/// // If we invoke **Add Missing Members** here, we want to copy-paste `foo`.
40/// // But we want a slightly-modified version of it:
41/// fn foo(&self, _: ()) -> x::A {}
42/// }
43/// }
44/// ```
45///
46/// So, a single `AstTransform` describes such function from `SyntaxNode` to
47/// `SyntaxNode`. Note that the API here is a bit too high-order and high-brow.
48/// We'd want to somehow express this concept simpler, but so far nobody got to
49/// simplifying this!
50pub trait AstTransform<'a> {
51 fn get_substitution(
52 &self,
53 node: &SyntaxNode,
54 recur: &dyn AstTransform<'a>,
55 ) -> Option<SyntaxNode>;
56
57 fn or<T: AstTransform<'a> + 'a>(self, other: T) -> Box<dyn AstTransform<'a> + 'a>
58 where
59 Self: Sized + 'a,
60 {
61 Box::new(Or(Box::new(self), Box::new(other)))
62 }
63}
64
65struct Or<'a>(Box<dyn AstTransform<'a> + 'a>, Box<dyn AstTransform<'a> + 'a>);
66
67impl<'a> AstTransform<'a> for Or<'a> {
68 fn get_substitution(
69 &self,
70 node: &SyntaxNode,
71 recur: &dyn AstTransform<'a>,
72 ) -> Option<SyntaxNode> {
73 self.0.get_substitution(node, recur).or_else(|| self.1.get_substitution(node, recur))
74 }
75}
76
77pub struct SubstituteTypeParams<'a> {
78 source_scope: &'a SemanticsScope<'a>,
79 substs: FxHashMap<hir::TypeParam, ast::Type>,
80}
81
82impl<'a> SubstituteTypeParams<'a> {
83 pub fn for_trait_impl(
84 source_scope: &'a SemanticsScope<'a>,
85 // FIXME: there's implicit invariant that `trait_` and `source_scope` match...
86 trait_: hir::Trait,
87 impl_def: ast::Impl,
88 ) -> SubstituteTypeParams<'a> {
89 let substs = get_syntactic_substs(impl_def).unwrap_or_default();
90 let generic_def: hir::GenericDef = trait_.into();
91 let substs_by_param: FxHashMap<_, _> = generic_def
92 .type_params(source_scope.db)
93 .into_iter()
94 // this is a trait impl, so we need to skip the first type parameter -- this is a bit hacky
95 .skip(1)
96 // The actual list of trait type parameters may be longer than the one
97 // used in the `impl` block due to trailing default type parameters.
98 // For that case we extend the `substs` with an empty iterator so we
99 // can still hit those trailing values and check if they actually have
100 // a default type. If they do, go for that type from `hir` to `ast` so
101 // the resulting change can be applied correctly.
102 .zip(substs.into_iter().map(Some).chain(std::iter::repeat(None)))
103 .filter_map(|(k, v)| match v {
104 Some(v) => Some((k, v)),
105 None => {
106 let default = k.default(source_scope.db)?;
107 Some((
108 k,
109 ast::make::ty(
110 &default
111 .display_source_code(source_scope.db, source_scope.module()?.into())
112 .ok()?,
113 ),
114 ))
115 }
116 })
117 .collect();
118 return SubstituteTypeParams { source_scope, substs: substs_by_param };
119
120 // FIXME: It would probably be nicer if we could get this via HIR (i.e. get the
121 // trait ref, and then go from the types in the substs back to the syntax).
122 fn get_syntactic_substs(impl_def: ast::Impl) -> Option<Vec<ast::Type>> {
123 let target_trait = impl_def.trait_()?;
124 let path_type = match target_trait {
125 ast::Type::PathType(path) => path,
126 _ => return None,
127 };
128 let generic_arg_list = path_type.path()?.segment()?.generic_arg_list()?;
129
130 let mut result = Vec::new();
131 for generic_arg in generic_arg_list.generic_args() {
132 match generic_arg {
133 ast::GenericArg::TypeArg(type_arg) => result.push(type_arg.ty()?),
134 ast::GenericArg::AssocTypeArg(_)
135 | ast::GenericArg::LifetimeArg(_)
136 | ast::GenericArg::ConstArg(_) => (),
137 }
138 }
139
140 Some(result)
141 }
142 }
143}
144
145impl<'a> AstTransform<'a> for SubstituteTypeParams<'a> {
146 fn get_substitution(
147 &self,
148 node: &SyntaxNode,
149 _recur: &dyn AstTransform<'a>,
150 ) -> Option<SyntaxNode> {
151 let type_ref = ast::Type::cast(node.clone())?;
152 let path = match &type_ref {
153 ast::Type::PathType(path_type) => path_type.path()?,
154 _ => return None,
155 };
156 let resolution = self.source_scope.speculative_resolve(&path)?;
157 match resolution {
158 hir::PathResolution::TypeParam(tp) => Some(self.substs.get(&tp)?.syntax().clone()),
159 _ => None,
160 }
161 }
162}
163
164pub struct QualifyPaths<'a> {
165 target_scope: &'a SemanticsScope<'a>,
166 source_scope: &'a SemanticsScope<'a>,
167}
168
169impl<'a> QualifyPaths<'a> {
170 pub fn new(target_scope: &'a SemanticsScope<'a>, source_scope: &'a SemanticsScope<'a>) -> Self {
171 Self { target_scope, source_scope }
172 }
173}
174
175impl<'a> AstTransform<'a> for QualifyPaths<'a> {
176 fn get_substitution(
177 &self,
178 node: &SyntaxNode,
179 recur: &dyn AstTransform<'a>,
180 ) -> Option<SyntaxNode> {
181 // FIXME handle value ns?
182 let from = self.target_scope.module()?;
183 let p = ast::Path::cast(node.clone())?;
184 if p.segment().and_then(|s| s.param_list()).is_some() {
185 // don't try to qualify `Fn(Foo) -> Bar` paths, they are in prelude anyway
186 return None;
187 }
188 let resolution = self.source_scope.speculative_resolve(&p)?;
189 match resolution {
190 PathResolution::Def(def) => {
191 let found_path = from.find_use_path(self.source_scope.db.upcast(), def)?;
192 let mut path = mod_path_to_ast(&found_path);
193
194 let type_args = p
195 .segment()
196 .and_then(|s| s.generic_arg_list())
197 .map(|arg_list| apply(recur, arg_list));
198 if let Some(type_args) = type_args {
199 let last_segment = path.segment().unwrap();
200 path = path.with_segment(last_segment.with_generic_args(type_args))
201 }
202
203 Some(path.syntax().clone())
204 }
205 PathResolution::Local(_)
206 | PathResolution::TypeParam(_)
207 | PathResolution::SelfType(_)
208 | PathResolution::ConstParam(_) => None,
209 PathResolution::Macro(_) => None,
210 PathResolution::AssocItem(_) => None,
211 }
212 }
213}
diff --git a/crates/ide_assists/src/handlers/add_explicit_type.rs b/crates/ide_assists/src/handlers/add_explicit_type.rs
new file mode 100644
index 000000000..cb1548cef
--- /dev/null
+++ b/crates/ide_assists/src/handlers/add_explicit_type.rs
@@ -0,0 +1,207 @@
1use hir::HirDisplay;
2use syntax::{
3 ast::{self, AstNode, LetStmt, NameOwner},
4 TextRange,
5};
6
7use crate::{AssistContext, AssistId, AssistKind, Assists};
8
9// Assist: add_explicit_type
10//
11// Specify type for a let binding.
12//
13// ```
14// fn main() {
15// let x$0 = 92;
16// }
17// ```
18// ->
19// ```
20// fn main() {
21// let x: i32 = 92;
22// }
23// ```
24pub(crate) fn add_explicit_type(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
25 let let_stmt = ctx.find_node_at_offset::<LetStmt>()?;
26 let module = ctx.sema.scope(let_stmt.syntax()).module()?;
27 let expr = let_stmt.initializer()?;
28 // Must be a binding
29 let pat = match let_stmt.pat()? {
30 ast::Pat::IdentPat(bind_pat) => bind_pat,
31 _ => return None,
32 };
33 let pat_range = pat.syntax().text_range();
34 // The binding must have a name
35 let name = pat.name()?;
36 let name_range = name.syntax().text_range();
37 let stmt_range = let_stmt.syntax().text_range();
38 let eq_range = let_stmt.eq_token()?.text_range();
39 // Assist should only be applicable if cursor is between 'let' and '='
40 let let_range = TextRange::new(stmt_range.start(), eq_range.start());
41 let cursor_in_range = let_range.contains_range(ctx.frange.range);
42 if !cursor_in_range {
43 return None;
44 }
45 // Assist not applicable if the type has already been specified
46 // and it has no placeholders
47 let ascribed_ty = let_stmt.ty();
48 if let Some(ty) = &ascribed_ty {
49 if ty.syntax().descendants().find_map(ast::InferType::cast).is_none() {
50 return None;
51 }
52 }
53 // Infer type
54 let ty = ctx.sema.type_of_expr(&expr)?;
55
56 if ty.contains_unknown() || ty.is_closure() {
57 return None;
58 }
59
60 let inferred_type = ty.display_source_code(ctx.db(), module.into()).ok()?;
61 acc.add(
62 AssistId("add_explicit_type", AssistKind::RefactorRewrite),
63 format!("Insert explicit type `{}`", inferred_type),
64 pat_range,
65 |builder| match ascribed_ty {
66 Some(ascribed_ty) => {
67 builder.replace(ascribed_ty.syntax().text_range(), inferred_type);
68 }
69 None => {
70 builder.insert(name_range.end(), format!(": {}", inferred_type));
71 }
72 },
73 )
74}
75
76#[cfg(test)]
77mod tests {
78 use super::*;
79
80 use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target};
81
82 #[test]
83 fn add_explicit_type_target() {
84 check_assist_target(add_explicit_type, "fn f() { let a$0 = 1; }", "a");
85 }
86
87 #[test]
88 fn add_explicit_type_works_for_simple_expr() {
89 check_assist(add_explicit_type, "fn f() { let a$0 = 1; }", "fn f() { let a: i32 = 1; }");
90 }
91
92 #[test]
93 fn add_explicit_type_works_for_underscore() {
94 check_assist(add_explicit_type, "fn f() { let a$0: _ = 1; }", "fn f() { let a: i32 = 1; }");
95 }
96
97 #[test]
98 fn add_explicit_type_works_for_nested_underscore() {
99 check_assist(
100 add_explicit_type,
101 r#"
102 enum Option<T> {
103 Some(T),
104 None
105 }
106
107 fn f() {
108 let a$0: Option<_> = Option::Some(1);
109 }"#,
110 r#"
111 enum Option<T> {
112 Some(T),
113 None
114 }
115
116 fn f() {
117 let a: Option<i32> = Option::Some(1);
118 }"#,
119 );
120 }
121
122 #[test]
123 fn add_explicit_type_works_for_macro_call() {
124 check_assist(
125 add_explicit_type,
126 r"macro_rules! v { () => {0u64} } fn f() { let a$0 = v!(); }",
127 r"macro_rules! v { () => {0u64} } fn f() { let a: u64 = v!(); }",
128 );
129 }
130
131 #[test]
132 fn add_explicit_type_works_for_macro_call_recursive() {
133 check_assist(
134 add_explicit_type,
135 r#"macro_rules! u { () => {0u64} } macro_rules! v { () => {u!()} } fn f() { let a$0 = v!(); }"#,
136 r#"macro_rules! u { () => {0u64} } macro_rules! v { () => {u!()} } fn f() { let a: u64 = v!(); }"#,
137 );
138 }
139
140 #[test]
141 fn add_explicit_type_not_applicable_if_ty_not_inferred() {
142 check_assist_not_applicable(add_explicit_type, "fn f() { let a$0 = None; }");
143 }
144
145 #[test]
146 fn add_explicit_type_not_applicable_if_ty_already_specified() {
147 check_assist_not_applicable(add_explicit_type, "fn f() { let a$0: i32 = 1; }");
148 }
149
150 #[test]
151 fn add_explicit_type_not_applicable_if_specified_ty_is_tuple() {
152 check_assist_not_applicable(add_explicit_type, "fn f() { let a$0: (i32, i32) = (3, 4); }");
153 }
154
155 #[test]
156 fn add_explicit_type_not_applicable_if_cursor_after_equals() {
157 check_assist_not_applicable(
158 add_explicit_type,
159 "fn f() {let a =$0 match 1 {2 => 3, 3 => 5};}",
160 )
161 }
162
163 #[test]
164 fn add_explicit_type_not_applicable_if_cursor_before_let() {
165 check_assist_not_applicable(
166 add_explicit_type,
167 "fn f() $0{let a = match 1 {2 => 3, 3 => 5};}",
168 )
169 }
170
171 #[test]
172 fn closure_parameters_are_not_added() {
173 check_assist_not_applicable(
174 add_explicit_type,
175 r#"
176fn main() {
177 let multiply_by_two$0 = |i| i * 3;
178 let six = multiply_by_two(2);
179}"#,
180 )
181 }
182
183 #[test]
184 fn default_generics_should_not_be_added() {
185 check_assist(
186 add_explicit_type,
187 r#"
188struct Test<K, T = u8> {
189 k: K,
190 t: T,
191}
192
193fn main() {
194 let test$0 = Test { t: 23u8, k: 33 };
195}"#,
196 r#"
197struct Test<K, T = u8> {
198 k: K,
199 t: T,
200}
201
202fn main() {
203 let test: Test<i32> = Test { t: 23u8, k: 33 };
204}"#,
205 );
206 }
207}
diff --git a/crates/ide_assists/src/handlers/add_lifetime_to_type.rs b/crates/ide_assists/src/handlers/add_lifetime_to_type.rs
new file mode 100644
index 000000000..2edf7b204
--- /dev/null
+++ b/crates/ide_assists/src/handlers/add_lifetime_to_type.rs
@@ -0,0 +1,228 @@
1use ast::FieldList;
2use syntax::ast::{self, AstNode, GenericParamsOwner, NameOwner, RefType, Type};
3
4use crate::{AssistContext, AssistId, AssistKind, Assists};
5
6// Assist: add_lifetime_to_type
7//
8// Adds a new lifetime to a struct, enum or union.
9//
10// ```
11// struct Point {
12// x: &$0u32,
13// y: u32,
14// }
15// ```
16// ->
17// ```
18// struct Point<'a> {
19// x: &'a u32,
20// y: u32,
21// }
22// ```
23pub(crate) fn add_lifetime_to_type(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
24 let ref_type_focused = ctx.find_node_at_offset::<ast::RefType>()?;
25 if ref_type_focused.lifetime().is_some() {
26 return None;
27 }
28
29 let node = ctx.find_node_at_offset::<ast::Adt>()?;
30 let has_lifetime = node
31 .generic_param_list()
32 .map(|gen_list| gen_list.lifetime_params().count() > 0)
33 .unwrap_or_default();
34
35 if has_lifetime {
36 return None;
37 }
38
39 let ref_types = fetch_borrowed_types(&node)?;
40 let target = node.syntax().text_range();
41
42 acc.add(
43 AssistId("add_lifetime_to_type", AssistKind::Generate),
44 "Add lifetime`",
45 target,
46 |builder| {
47 match node.generic_param_list() {
48 Some(gen_param) => {
49 if let Some(left_angle) = gen_param.l_angle_token() {
50 builder.insert(left_angle.text_range().end(), "'a, ");
51 }
52 }
53 None => {
54 if let Some(name) = node.name() {
55 builder.insert(name.syntax().text_range().end(), "<'a>");
56 }
57 }
58 }
59
60 for ref_type in ref_types {
61 if let Some(amp_token) = ref_type.amp_token() {
62 builder.insert(amp_token.text_range().end(), "'a ");
63 }
64 }
65 },
66 )
67}
68
69fn fetch_borrowed_types(node: &ast::Adt) -> Option<Vec<RefType>> {
70 let ref_types: Vec<RefType> = match node {
71 ast::Adt::Enum(enum_) => {
72 let variant_list = enum_.variant_list()?;
73 variant_list
74 .variants()
75 .filter_map(|variant| {
76 let field_list = variant.field_list()?;
77
78 find_ref_types_from_field_list(&field_list)
79 })
80 .flatten()
81 .collect()
82 }
83 ast::Adt::Struct(strukt) => {
84 let field_list = strukt.field_list()?;
85 find_ref_types_from_field_list(&field_list)?
86 }
87 ast::Adt::Union(un) => {
88 let record_field_list = un.record_field_list()?;
89 record_field_list
90 .fields()
91 .filter_map(|r_field| {
92 if let Type::RefType(ref_type) = r_field.ty()? {
93 if ref_type.lifetime().is_none() {
94 return Some(ref_type);
95 }
96 }
97
98 None
99 })
100 .collect()
101 }
102 };
103
104 if ref_types.is_empty() {
105 None
106 } else {
107 Some(ref_types)
108 }
109}
110
111fn find_ref_types_from_field_list(field_list: &FieldList) -> Option<Vec<RefType>> {
112 let ref_types: Vec<RefType> = match field_list {
113 ast::FieldList::RecordFieldList(record_list) => record_list
114 .fields()
115 .filter_map(|f| {
116 if let Type::RefType(ref_type) = f.ty()? {
117 if ref_type.lifetime().is_none() {
118 return Some(ref_type);
119 }
120 }
121
122 None
123 })
124 .collect(),
125 ast::FieldList::TupleFieldList(tuple_field_list) => tuple_field_list
126 .fields()
127 .filter_map(|f| {
128 if let Type::RefType(ref_type) = f.ty()? {
129 if ref_type.lifetime().is_none() {
130 return Some(ref_type);
131 }
132 }
133
134 None
135 })
136 .collect(),
137 };
138
139 if ref_types.is_empty() {
140 None
141 } else {
142 Some(ref_types)
143 }
144}
145
146#[cfg(test)]
147mod tests {
148 use crate::tests::{check_assist, check_assist_not_applicable};
149
150 use super::*;
151
152 #[test]
153 fn add_lifetime_to_struct() {
154 check_assist(
155 add_lifetime_to_type,
156 "struct Foo { a: &$0i32 }",
157 "struct Foo<'a> { a: &'a i32 }",
158 );
159
160 check_assist(
161 add_lifetime_to_type,
162 "struct Foo { a: &$0i32, b: &usize }",
163 "struct Foo<'a> { a: &'a i32, b: &'a usize }",
164 );
165
166 check_assist(
167 add_lifetime_to_type,
168 "struct Foo { a: &$0i32, b: usize }",
169 "struct Foo<'a> { a: &'a i32, b: usize }",
170 );
171
172 check_assist(
173 add_lifetime_to_type,
174 "struct Foo<T> { a: &$0T, b: usize }",
175 "struct Foo<'a, T> { a: &'a T, b: usize }",
176 );
177
178 check_assist_not_applicable(add_lifetime_to_type, "struct Foo<'a> { a: &$0'a i32 }");
179 check_assist_not_applicable(add_lifetime_to_type, "struct Foo { a: &'a$0 i32 }");
180 }
181
182 #[test]
183 fn add_lifetime_to_enum() {
184 check_assist(
185 add_lifetime_to_type,
186 "enum Foo { Bar { a: i32 }, Other, Tuple(u32, &$0u32)}",
187 "enum Foo<'a> { Bar { a: i32 }, Other, Tuple(u32, &'a u32)}",
188 );
189
190 check_assist(
191 add_lifetime_to_type,
192 "enum Foo { Bar { a: &$0i32 }}",
193 "enum Foo<'a> { Bar { a: &'a i32 }}",
194 );
195
196 check_assist(
197 add_lifetime_to_type,
198 "enum Foo<T> { Bar { a: &$0i32, b: &T }}",
199 "enum Foo<'a, T> { Bar { a: &'a i32, b: &'a T }}",
200 );
201
202 check_assist_not_applicable(add_lifetime_to_type, "enum Foo<'a> { Bar { a: &$0'a i32 }}");
203 check_assist_not_applicable(add_lifetime_to_type, "enum Foo { Bar, $0Misc }");
204 }
205
206 #[test]
207 fn add_lifetime_to_union() {
208 check_assist(
209 add_lifetime_to_type,
210 "union Foo { a: &$0i32 }",
211 "union Foo<'a> { a: &'a i32 }",
212 );
213
214 check_assist(
215 add_lifetime_to_type,
216 "union Foo { a: &$0i32, b: &usize }",
217 "union Foo<'a> { a: &'a i32, b: &'a usize }",
218 );
219
220 check_assist(
221 add_lifetime_to_type,
222 "union Foo<T> { a: &$0T, b: usize }",
223 "union Foo<'a, T> { a: &'a T, b: usize }",
224 );
225
226 check_assist_not_applicable(add_lifetime_to_type, "struct Foo<'a> { a: &'a $0i32 }");
227 }
228}
diff --git a/crates/ide_assists/src/handlers/add_missing_impl_members.rs b/crates/ide_assists/src/handlers/add_missing_impl_members.rs
new file mode 100644
index 000000000..63cea754d
--- /dev/null
+++ b/crates/ide_assists/src/handlers/add_missing_impl_members.rs
@@ -0,0 +1,814 @@
1use ide_db::traits::resolve_target_trait;
2use syntax::ast::{self, AstNode};
3
4use crate::{
5 assist_context::{AssistContext, Assists},
6 utils::add_trait_assoc_items_to_impl,
7 utils::DefaultMethods,
8 utils::{filter_assoc_items, render_snippet, Cursor},
9 AssistId, AssistKind,
10};
11
12// Assist: add_impl_missing_members
13//
14// Adds scaffold for required impl members.
15//
16// ```
17// trait Trait<T> {
18// type X;
19// fn foo(&self) -> T;
20// fn bar(&self) {}
21// }
22//
23// impl Trait<u32> for () {$0
24//
25// }
26// ```
27// ->
28// ```
29// trait Trait<T> {
30// type X;
31// fn foo(&self) -> T;
32// fn bar(&self) {}
33// }
34//
35// impl Trait<u32> for () {
36// $0type X;
37//
38// fn foo(&self) -> u32 {
39// todo!()
40// }
41// }
42// ```
43pub(crate) fn add_missing_impl_members(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
44 add_missing_impl_members_inner(
45 acc,
46 ctx,
47 DefaultMethods::No,
48 "add_impl_missing_members",
49 "Implement missing members",
50 )
51}
52
53// Assist: add_impl_default_members
54//
55// Adds scaffold for overriding default impl members.
56//
57// ```
58// trait Trait {
59// type X;
60// fn foo(&self);
61// fn bar(&self) {}
62// }
63//
64// impl Trait for () {
65// type X = ();
66// fn foo(&self) {}$0
67//
68// }
69// ```
70// ->
71// ```
72// trait Trait {
73// type X;
74// fn foo(&self);
75// fn bar(&self) {}
76// }
77//
78// impl Trait for () {
79// type X = ();
80// fn foo(&self) {}
81//
82// $0fn bar(&self) {}
83// }
84// ```
85pub(crate) fn add_missing_default_members(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
86 add_missing_impl_members_inner(
87 acc,
88 ctx,
89 DefaultMethods::Only,
90 "add_impl_default_members",
91 "Implement default members",
92 )
93}
94
95fn add_missing_impl_members_inner(
96 acc: &mut Assists,
97 ctx: &AssistContext,
98 mode: DefaultMethods,
99 assist_id: &'static str,
100 label: &'static str,
101) -> Option<()> {
102 let _p = profile::span("add_missing_impl_members_inner");
103 let impl_def = ctx.find_node_at_offset::<ast::Impl>()?;
104 let trait_ = resolve_target_trait(&ctx.sema, &impl_def)?;
105
106 let missing_items = filter_assoc_items(
107 ctx.db(),
108 &ide_db::traits::get_missing_assoc_items(&ctx.sema, &impl_def),
109 mode,
110 );
111
112 if missing_items.is_empty() {
113 return None;
114 }
115
116 let target = impl_def.syntax().text_range();
117 acc.add(AssistId(assist_id, AssistKind::QuickFix), label, target, |builder| {
118 let target_scope = ctx.sema.scope(impl_def.syntax());
119 let (new_impl_def, first_new_item) =
120 add_trait_assoc_items_to_impl(&ctx.sema, missing_items, trait_, impl_def, target_scope);
121 match ctx.config.snippet_cap {
122 None => builder.replace(target, new_impl_def.to_string()),
123 Some(cap) => {
124 let mut cursor = Cursor::Before(first_new_item.syntax());
125 let placeholder;
126 if let ast::AssocItem::Fn(func) = &first_new_item {
127 if let Some(m) = func.syntax().descendants().find_map(ast::MacroCall::cast) {
128 if m.syntax().text() == "todo!()" {
129 placeholder = m;
130 cursor = Cursor::Replace(placeholder.syntax());
131 }
132 }
133 }
134 builder.replace_snippet(
135 cap,
136 target,
137 render_snippet(cap, new_impl_def.syntax(), cursor),
138 )
139 }
140 };
141 })
142}
143
144#[cfg(test)]
145mod tests {
146 use crate::tests::{check_assist, check_assist_not_applicable};
147
148 use super::*;
149
150 #[test]
151 fn test_add_missing_impl_members() {
152 check_assist(
153 add_missing_impl_members,
154 r#"
155trait Foo {
156 type Output;
157
158 const CONST: usize = 42;
159
160 fn foo(&self);
161 fn bar(&self);
162 fn baz(&self);
163}
164
165struct S;
166
167impl Foo for S {
168 fn bar(&self) {}
169$0
170}"#,
171 r#"
172trait Foo {
173 type Output;
174
175 const CONST: usize = 42;
176
177 fn foo(&self);
178 fn bar(&self);
179 fn baz(&self);
180}
181
182struct S;
183
184impl Foo for S {
185 fn bar(&self) {}
186
187 $0type Output;
188
189 const CONST: usize = 42;
190
191 fn foo(&self) {
192 todo!()
193 }
194
195 fn baz(&self) {
196 todo!()
197 }
198}"#,
199 );
200 }
201
202 #[test]
203 fn test_copied_overriden_members() {
204 check_assist(
205 add_missing_impl_members,
206 r#"
207trait Foo {
208 fn foo(&self);
209 fn bar(&self) -> bool { true }
210 fn baz(&self) -> u32 { 42 }
211}
212
213struct S;
214
215impl Foo for S {
216 fn bar(&self) {}
217$0
218}"#,
219 r#"
220trait Foo {
221 fn foo(&self);
222 fn bar(&self) -> bool { true }
223 fn baz(&self) -> u32 { 42 }
224}
225
226struct S;
227
228impl Foo for S {
229 fn bar(&self) {}
230
231 fn foo(&self) {
232 ${0:todo!()}
233 }
234}"#,
235 );
236 }
237
238 #[test]
239 fn test_empty_impl_def() {
240 check_assist(
241 add_missing_impl_members,
242 r#"
243trait Foo { fn foo(&self); }
244struct S;
245impl Foo for S { $0 }"#,
246 r#"
247trait Foo { fn foo(&self); }
248struct S;
249impl Foo for S {
250 fn foo(&self) {
251 ${0:todo!()}
252 }
253}"#,
254 );
255 }
256
257 #[test]
258 fn test_impl_def_without_braces() {
259 check_assist(
260 add_missing_impl_members,
261 r#"
262trait Foo { fn foo(&self); }
263struct S;
264impl Foo for S$0"#,
265 r#"
266trait Foo { fn foo(&self); }
267struct S;
268impl Foo for S {
269 fn foo(&self) {
270 ${0:todo!()}
271 }
272}"#,
273 );
274 }
275
276 #[test]
277 fn fill_in_type_params_1() {
278 check_assist(
279 add_missing_impl_members,
280 r#"
281trait Foo<T> { fn foo(&self, t: T) -> &T; }
282struct S;
283impl Foo<u32> for S { $0 }"#,
284 r#"
285trait Foo<T> { fn foo(&self, t: T) -> &T; }
286struct S;
287impl Foo<u32> for S {
288 fn foo(&self, t: u32) -> &u32 {
289 ${0:todo!()}
290 }
291}"#,
292 );
293 }
294
295 #[test]
296 fn fill_in_type_params_2() {
297 check_assist(
298 add_missing_impl_members,
299 r#"
300trait Foo<T> { fn foo(&self, t: T) -> &T; }
301struct S;
302impl<U> Foo<U> for S { $0 }"#,
303 r#"
304trait Foo<T> { fn foo(&self, t: T) -> &T; }
305struct S;
306impl<U> Foo<U> for S {
307 fn foo(&self, t: U) -> &U {
308 ${0:todo!()}
309 }
310}"#,
311 );
312 }
313
314 #[test]
315 fn test_cursor_after_empty_impl_def() {
316 check_assist(
317 add_missing_impl_members,
318 r#"
319trait Foo { fn foo(&self); }
320struct S;
321impl Foo for S {}$0"#,
322 r#"
323trait Foo { fn foo(&self); }
324struct S;
325impl Foo for S {
326 fn foo(&self) {
327 ${0:todo!()}
328 }
329}"#,
330 )
331 }
332
333 #[test]
334 fn test_qualify_path_1() {
335 check_assist(
336 add_missing_impl_members,
337 r#"
338mod foo {
339 pub struct Bar;
340 trait Foo { fn foo(&self, bar: Bar); }
341}
342struct S;
343impl foo::Foo for S { $0 }"#,
344 r#"
345mod foo {
346 pub struct Bar;
347 trait Foo { fn foo(&self, bar: Bar); }
348}
349struct S;
350impl foo::Foo for S {
351 fn foo(&self, bar: foo::Bar) {
352 ${0:todo!()}
353 }
354}"#,
355 );
356 }
357
358 #[test]
359 fn test_qualify_path_2() {
360 check_assist(
361 add_missing_impl_members,
362 r#"
363mod foo {
364 pub mod bar {
365 pub struct Bar;
366 pub trait Foo { fn foo(&self, bar: Bar); }
367 }
368}
369
370use foo::bar;
371
372struct S;
373impl bar::Foo for S { $0 }"#,
374 r#"
375mod foo {
376 pub mod bar {
377 pub struct Bar;
378 pub trait Foo { fn foo(&self, bar: Bar); }
379 }
380}
381
382use foo::bar;
383
384struct S;
385impl bar::Foo for S {
386 fn foo(&self, bar: bar::Bar) {
387 ${0:todo!()}
388 }
389}"#,
390 );
391 }
392
393 #[test]
394 fn test_qualify_path_generic() {
395 check_assist(
396 add_missing_impl_members,
397 r#"
398mod foo {
399 pub struct Bar<T>;
400 trait Foo { fn foo(&self, bar: Bar<u32>); }
401}
402struct S;
403impl foo::Foo for S { $0 }"#,
404 r#"
405mod foo {
406 pub struct Bar<T>;
407 trait Foo { fn foo(&self, bar: Bar<u32>); }
408}
409struct S;
410impl foo::Foo for S {
411 fn foo(&self, bar: foo::Bar<u32>) {
412 ${0:todo!()}
413 }
414}"#,
415 );
416 }
417
418 #[test]
419 fn test_qualify_path_and_substitute_param() {
420 check_assist(
421 add_missing_impl_members,
422 r#"
423mod foo {
424 pub struct Bar<T>;
425 trait Foo<T> { fn foo(&self, bar: Bar<T>); }
426}
427struct S;
428impl foo::Foo<u32> for S { $0 }"#,
429 r#"
430mod foo {
431 pub struct Bar<T>;
432 trait Foo<T> { fn foo(&self, bar: Bar<T>); }
433}
434struct S;
435impl foo::Foo<u32> for S {
436 fn foo(&self, bar: foo::Bar<u32>) {
437 ${0:todo!()}
438 }
439}"#,
440 );
441 }
442
443 #[test]
444 fn test_substitute_param_no_qualify() {
445 // when substituting params, the substituted param should not be qualified!
446 check_assist(
447 add_missing_impl_members,
448 r#"
449mod foo {
450 trait Foo<T> { fn foo(&self, bar: T); }
451 pub struct Param;
452}
453struct Param;
454struct S;
455impl foo::Foo<Param> for S { $0 }"#,
456 r#"
457mod foo {
458 trait Foo<T> { fn foo(&self, bar: T); }
459 pub struct Param;
460}
461struct Param;
462struct S;
463impl foo::Foo<Param> for S {
464 fn foo(&self, bar: Param) {
465 ${0:todo!()}
466 }
467}"#,
468 );
469 }
470
471 #[test]
472 fn test_qualify_path_associated_item() {
473 check_assist(
474 add_missing_impl_members,
475 r#"
476mod foo {
477 pub struct Bar<T>;
478 impl Bar<T> { type Assoc = u32; }
479 trait Foo { fn foo(&self, bar: Bar<u32>::Assoc); }
480}
481struct S;
482impl foo::Foo for S { $0 }"#,
483 r#"
484mod foo {
485 pub struct Bar<T>;
486 impl Bar<T> { type Assoc = u32; }
487 trait Foo { fn foo(&self, bar: Bar<u32>::Assoc); }
488}
489struct S;
490impl foo::Foo for S {
491 fn foo(&self, bar: foo::Bar<u32>::Assoc) {
492 ${0:todo!()}
493 }
494}"#,
495 );
496 }
497
498 #[test]
499 fn test_qualify_path_nested() {
500 check_assist(
501 add_missing_impl_members,
502 r#"
503mod foo {
504 pub struct Bar<T>;
505 pub struct Baz;
506 trait Foo { fn foo(&self, bar: Bar<Baz>); }
507}
508struct S;
509impl foo::Foo for S { $0 }"#,
510 r#"
511mod foo {
512 pub struct Bar<T>;
513 pub struct Baz;
514 trait Foo { fn foo(&self, bar: Bar<Baz>); }
515}
516struct S;
517impl foo::Foo for S {
518 fn foo(&self, bar: foo::Bar<foo::Baz>) {
519 ${0:todo!()}
520 }
521}"#,
522 );
523 }
524
525 #[test]
526 fn test_qualify_path_fn_trait_notation() {
527 check_assist(
528 add_missing_impl_members,
529 r#"
530mod foo {
531 pub trait Fn<Args> { type Output; }
532 trait Foo { fn foo(&self, bar: dyn Fn(u32) -> i32); }
533}
534struct S;
535impl foo::Foo for S { $0 }"#,
536 r#"
537mod foo {
538 pub trait Fn<Args> { type Output; }
539 trait Foo { fn foo(&self, bar: dyn Fn(u32) -> i32); }
540}
541struct S;
542impl foo::Foo for S {
543 fn foo(&self, bar: dyn Fn(u32) -> i32) {
544 ${0:todo!()}
545 }
546}"#,
547 );
548 }
549
550 #[test]
551 fn test_empty_trait() {
552 check_assist_not_applicable(
553 add_missing_impl_members,
554 r#"
555trait Foo;
556struct S;
557impl Foo for S { $0 }"#,
558 )
559 }
560
561 #[test]
562 fn test_ignore_unnamed_trait_members_and_default_methods() {
563 check_assist_not_applicable(
564 add_missing_impl_members,
565 r#"
566trait Foo {
567 fn (arg: u32);
568 fn valid(some: u32) -> bool { false }
569}
570struct S;
571impl Foo for S { $0 }"#,
572 )
573 }
574
575 #[test]
576 fn test_with_docstring_and_attrs() {
577 check_assist(
578 add_missing_impl_members,
579 r#"
580#[doc(alias = "test alias")]
581trait Foo {
582 /// doc string
583 type Output;
584
585 #[must_use]
586 fn foo(&self);
587}
588struct S;
589impl Foo for S {}$0"#,
590 r#"
591#[doc(alias = "test alias")]
592trait Foo {
593 /// doc string
594 type Output;
595
596 #[must_use]
597 fn foo(&self);
598}
599struct S;
600impl Foo for S {
601 $0type Output;
602
603 fn foo(&self) {
604 todo!()
605 }
606}"#,
607 )
608 }
609
610 #[test]
611 fn test_default_methods() {
612 check_assist(
613 add_missing_default_members,
614 r#"
615trait Foo {
616 type Output;
617
618 const CONST: usize = 42;
619
620 fn valid(some: u32) -> bool { false }
621 fn foo(some: u32) -> bool;
622}
623struct S;
624impl Foo for S { $0 }"#,
625 r#"
626trait Foo {
627 type Output;
628
629 const CONST: usize = 42;
630
631 fn valid(some: u32) -> bool { false }
632 fn foo(some: u32) -> bool;
633}
634struct S;
635impl Foo for S {
636 $0fn valid(some: u32) -> bool { false }
637}"#,
638 )
639 }
640
641 #[test]
642 fn test_generic_single_default_parameter() {
643 check_assist(
644 add_missing_impl_members,
645 r#"
646trait Foo<T = Self> {
647 fn bar(&self, other: &T);
648}
649
650struct S;
651impl Foo for S { $0 }"#,
652 r#"
653trait Foo<T = Self> {
654 fn bar(&self, other: &T);
655}
656
657struct S;
658impl Foo for S {
659 fn bar(&self, other: &Self) {
660 ${0:todo!()}
661 }
662}"#,
663 )
664 }
665
666 #[test]
667 fn test_generic_default_parameter_is_second() {
668 check_assist(
669 add_missing_impl_members,
670 r#"
671trait Foo<T1, T2 = Self> {
672 fn bar(&self, this: &T1, that: &T2);
673}
674
675struct S<T>;
676impl Foo<T> for S<T> { $0 }"#,
677 r#"
678trait Foo<T1, T2 = Self> {
679 fn bar(&self, this: &T1, that: &T2);
680}
681
682struct S<T>;
683impl Foo<T> for S<T> {
684 fn bar(&self, this: &T, that: &Self) {
685 ${0:todo!()}
686 }
687}"#,
688 )
689 }
690
691 #[test]
692 fn test_assoc_type_bounds_are_removed() {
693 check_assist(
694 add_missing_impl_members,
695 r#"
696trait Tr {
697 type Ty: Copy + 'static;
698}
699
700impl Tr for ()$0 {
701}"#,
702 r#"
703trait Tr {
704 type Ty: Copy + 'static;
705}
706
707impl Tr for () {
708 $0type Ty;
709}"#,
710 )
711 }
712
713 #[test]
714 fn test_whitespace_fixup_preserves_bad_tokens() {
715 check_assist(
716 add_missing_impl_members,
717 r#"
718trait Tr {
719 fn foo();
720}
721
722impl Tr for ()$0 {
723 +++
724}"#,
725 r#"
726trait Tr {
727 fn foo();
728}
729
730impl Tr for () {
731 fn foo() {
732 ${0:todo!()}
733 }
734 +++
735}"#,
736 )
737 }
738
739 #[test]
740 fn test_whitespace_fixup_preserves_comments() {
741 check_assist(
742 add_missing_impl_members,
743 r#"
744trait Tr {
745 fn foo();
746}
747
748impl Tr for ()$0 {
749 // very important
750}"#,
751 r#"
752trait Tr {
753 fn foo();
754}
755
756impl Tr for () {
757 fn foo() {
758 ${0:todo!()}
759 }
760 // very important
761}"#,
762 )
763 }
764
765 #[test]
766 fn weird_path() {
767 check_assist(
768 add_missing_impl_members,
769 r#"
770trait Test {
771 fn foo(&self, x: crate)
772}
773impl Test for () {
774 $0
775}
776"#,
777 r#"
778trait Test {
779 fn foo(&self, x: crate)
780}
781impl Test for () {
782 fn foo(&self, x: crate) {
783 ${0:todo!()}
784 }
785}
786"#,
787 )
788 }
789
790 #[test]
791 fn missing_generic_type() {
792 check_assist(
793 add_missing_impl_members,
794 r#"
795trait Foo<BAR> {
796 fn foo(&self, bar: BAR);
797}
798impl Foo for () {
799 $0
800}
801"#,
802 r#"
803trait Foo<BAR> {
804 fn foo(&self, bar: BAR);
805}
806impl Foo for () {
807 fn foo(&self, bar: BAR) {
808 ${0:todo!()}
809 }
810}
811"#,
812 )
813 }
814}
diff --git a/crates/ide_assists/src/handlers/add_turbo_fish.rs b/crates/ide_assists/src/handlers/add_turbo_fish.rs
new file mode 100644
index 000000000..8e9ea4fad
--- /dev/null
+++ b/crates/ide_assists/src/handlers/add_turbo_fish.rs
@@ -0,0 +1,164 @@
1use ide_db::defs::{Definition, NameRefClass};
2use syntax::{ast, AstNode, SyntaxKind, T};
3use test_utils::mark;
4
5use crate::{
6 assist_context::{AssistContext, Assists},
7 AssistId, AssistKind,
8};
9
10// Assist: add_turbo_fish
11//
12// Adds `::<_>` to a call of a generic method or function.
13//
14// ```
15// fn make<T>() -> T { todo!() }
16// fn main() {
17// let x = make$0();
18// }
19// ```
20// ->
21// ```
22// fn make<T>() -> T { todo!() }
23// fn main() {
24// let x = make::<${0:_}>();
25// }
26// ```
27pub(crate) fn add_turbo_fish(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
28 let ident = ctx.find_token_syntax_at_offset(SyntaxKind::IDENT).or_else(|| {
29 let arg_list = ctx.find_node_at_offset::<ast::ArgList>()?;
30 if arg_list.args().count() > 0 {
31 return None;
32 }
33 mark::hit!(add_turbo_fish_after_call);
34 arg_list.l_paren_token()?.prev_token().filter(|it| it.kind() == SyntaxKind::IDENT)
35 })?;
36 let next_token = ident.next_token()?;
37 if next_token.kind() == T![::] {
38 mark::hit!(add_turbo_fish_one_fish_is_enough);
39 return None;
40 }
41 let name_ref = ast::NameRef::cast(ident.parent())?;
42 let def = match NameRefClass::classify(&ctx.sema, &name_ref)? {
43 NameRefClass::Definition(def) => def,
44 NameRefClass::ExternCrate(_) | NameRefClass::FieldShorthand { .. } => return None,
45 };
46 let fun = match def {
47 Definition::ModuleDef(hir::ModuleDef::Function(it)) => it,
48 _ => return None,
49 };
50 let generics = hir::GenericDef::Function(fun).params(ctx.sema.db);
51 if generics.is_empty() {
52 mark::hit!(add_turbo_fish_non_generic);
53 return None;
54 }
55 acc.add(
56 AssistId("add_turbo_fish", AssistKind::RefactorRewrite),
57 "Add `::<>`",
58 ident.text_range(),
59 |builder| match ctx.config.snippet_cap {
60 Some(cap) => builder.insert_snippet(cap, ident.text_range().end(), "::<${0:_}>"),
61 None => builder.insert(ident.text_range().end(), "::<_>"),
62 },
63 )
64}
65
66#[cfg(test)]
67mod tests {
68 use crate::tests::{check_assist, check_assist_not_applicable};
69
70 use super::*;
71 use test_utils::mark;
72
73 #[test]
74 fn add_turbo_fish_function() {
75 check_assist(
76 add_turbo_fish,
77 r#"
78fn make<T>() -> T {}
79fn main() {
80 make$0();
81}
82"#,
83 r#"
84fn make<T>() -> T {}
85fn main() {
86 make::<${0:_}>();
87}
88"#,
89 );
90 }
91
92 #[test]
93 fn add_turbo_fish_after_call() {
94 mark::check!(add_turbo_fish_after_call);
95 check_assist(
96 add_turbo_fish,
97 r#"
98fn make<T>() -> T {}
99fn main() {
100 make()$0;
101}
102"#,
103 r#"
104fn make<T>() -> T {}
105fn main() {
106 make::<${0:_}>();
107}
108"#,
109 );
110 }
111
112 #[test]
113 fn add_turbo_fish_method() {
114 check_assist(
115 add_turbo_fish,
116 r#"
117struct S;
118impl S {
119 fn make<T>(&self) -> T {}
120}
121fn main() {
122 S.make$0();
123}
124"#,
125 r#"
126struct S;
127impl S {
128 fn make<T>(&self) -> T {}
129}
130fn main() {
131 S.make::<${0:_}>();
132}
133"#,
134 );
135 }
136
137 #[test]
138 fn add_turbo_fish_one_fish_is_enough() {
139 mark::check!(add_turbo_fish_one_fish_is_enough);
140 check_assist_not_applicable(
141 add_turbo_fish,
142 r#"
143fn make<T>() -> T {}
144fn main() {
145 make$0::<()>();
146}
147"#,
148 );
149 }
150
151 #[test]
152 fn add_turbo_fish_non_generic() {
153 mark::check!(add_turbo_fish_non_generic);
154 check_assist_not_applicable(
155 add_turbo_fish,
156 r#"
157fn make() -> () {}
158fn main() {
159 make$0();
160}
161"#,
162 );
163 }
164}
diff --git a/crates/ide_assists/src/handlers/apply_demorgan.rs b/crates/ide_assists/src/handlers/apply_demorgan.rs
new file mode 100644
index 000000000..ed4d11455
--- /dev/null
+++ b/crates/ide_assists/src/handlers/apply_demorgan.rs
@@ -0,0 +1,93 @@
1use syntax::ast::{self, AstNode};
2
3use crate::{utils::invert_boolean_expression, AssistContext, AssistId, AssistKind, Assists};
4
5// Assist: apply_demorgan
6//
7// Apply https://en.wikipedia.org/wiki/De_Morgan%27s_laws[De Morgan's law].
8// This transforms expressions of the form `!l || !r` into `!(l && r)`.
9// This also works with `&&`. This assist can only be applied with the cursor
10// on either `||` or `&&`, with both operands being a negation of some kind.
11// This means something of the form `!x` or `x != y`.
12//
13// ```
14// fn main() {
15// if x != 4 ||$0 !y {}
16// }
17// ```
18// ->
19// ```
20// fn main() {
21// if !(x == 4 && y) {}
22// }
23// ```
24pub(crate) fn apply_demorgan(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
25 let expr = ctx.find_node_at_offset::<ast::BinExpr>()?;
26 let op = expr.op_kind()?;
27 let op_range = expr.op_token()?.text_range();
28 let opposite_op = opposite_logic_op(op)?;
29 let cursor_in_range = op_range.contains_range(ctx.frange.range);
30 if !cursor_in_range {
31 return None;
32 }
33
34 let lhs = expr.lhs()?;
35 let lhs_range = lhs.syntax().text_range();
36 let not_lhs = invert_boolean_expression(lhs);
37
38 let rhs = expr.rhs()?;
39 let rhs_range = rhs.syntax().text_range();
40 let not_rhs = invert_boolean_expression(rhs);
41
42 acc.add(
43 AssistId("apply_demorgan", AssistKind::RefactorRewrite),
44 "Apply De Morgan's law",
45 op_range,
46 |edit| {
47 edit.replace(op_range, opposite_op);
48 edit.replace(lhs_range, format!("!({}", not_lhs.syntax().text()));
49 edit.replace(rhs_range, format!("{})", not_rhs.syntax().text()));
50 },
51 )
52}
53
54// Return the opposite text for a given logical operator, if it makes sense
55fn opposite_logic_op(kind: ast::BinOp) -> Option<&'static str> {
56 match kind {
57 ast::BinOp::BooleanOr => Some("&&"),
58 ast::BinOp::BooleanAnd => Some("||"),
59 _ => None,
60 }
61}
62
63#[cfg(test)]
64mod tests {
65 use super::*;
66
67 use crate::tests::{check_assist, check_assist_not_applicable};
68
69 #[test]
70 fn demorgan_turns_and_into_or() {
71 check_assist(apply_demorgan, "fn f() { !x &&$0 !x }", "fn f() { !(x || x) }")
72 }
73
74 #[test]
75 fn demorgan_turns_or_into_and() {
76 check_assist(apply_demorgan, "fn f() { !x ||$0 !x }", "fn f() { !(x && x) }")
77 }
78
79 #[test]
80 fn demorgan_removes_inequality() {
81 check_assist(apply_demorgan, "fn f() { x != x ||$0 !x }", "fn f() { !(x == x && x) }")
82 }
83
84 #[test]
85 fn demorgan_general_case() {
86 check_assist(apply_demorgan, "fn f() { x ||$0 x }", "fn f() { !(!x && !x) }")
87 }
88
89 #[test]
90 fn demorgan_doesnt_apply_with_cursor_not_on_op() {
91 check_assist_not_applicable(apply_demorgan, "fn f() { $0 !x || !x }")
92 }
93}
diff --git a/crates/ide_assists/src/handlers/auto_import.rs b/crates/ide_assists/src/handlers/auto_import.rs
new file mode 100644
index 000000000..e93901cb3
--- /dev/null
+++ b/crates/ide_assists/src/handlers/auto_import.rs
@@ -0,0 +1,970 @@
1use ide_db::helpers::{
2 import_assets::{ImportAssets, ImportCandidate},
3 insert_use::{insert_use, ImportScope},
4 mod_path_to_ast,
5};
6use syntax::{ast, AstNode, SyntaxNode};
7
8use crate::{AssistContext, AssistId, AssistKind, Assists, GroupLabel};
9
10// Feature: Auto Import
11//
12// Using the `auto-import` assist it is possible to insert missing imports for unresolved items.
13// When inserting an import it will do so in a structured manner by keeping imports grouped,
14// separated by a newline in the following order:
15//
16// - `std` and `core`
17// - External Crates
18// - Current Crate, paths prefixed by `crate`
19// - Current Module, paths prefixed by `self`
20// - Super Module, paths prefixed by `super`
21//
22// Example:
23// ```rust
24// use std::fs::File;
25//
26// use itertools::Itertools;
27// use syntax::ast;
28//
29// use crate::utils::insert_use;
30//
31// use self::auto_import;
32//
33// use super::AssistContext;
34// ```
35//
36// .Merge Behaviour
37//
38// It is possible to configure how use-trees are merged with the `importMergeBehaviour` setting.
39// It has the following configurations:
40//
41// - `full`: This setting will cause auto-import to always completely merge use-trees that share the
42// same path prefix while also merging inner trees that share the same path-prefix. This kind of
43// nesting is only supported in Rust versions later than 1.24.
44// - `last`: This setting will cause auto-import to merge use-trees as long as the resulting tree
45// will only contain a nesting of single segment paths at the very end.
46// - `none`: This setting will cause auto-import to never merge use-trees keeping them as simple
47// paths.
48//
49// In `VS Code` the configuration for this is `rust-analyzer.assist.importMergeBehaviour`.
50//
51// .Import Prefix
52//
53// The style of imports in the same crate is configurable through the `importPrefix` setting.
54// It has the following configurations:
55//
56// - `by_crate`: This setting will force paths to be always absolute, starting with the `crate`
57// prefix, unless the item is defined outside of the current crate.
58// - `by_self`: This setting will force paths that are relative to the current module to always
59// start with `self`. This will result in paths that always start with either `crate`, `self`,
60// `super` or an extern crate identifier.
61// - `plain`: This setting does not impose any restrictions in imports.
62//
63// In `VS Code` the configuration for this is `rust-analyzer.assist.importPrefix`.
64
65// Assist: auto_import
66//
67// If the name is unresolved, provides all possible imports for it.
68//
69// ```
70// fn main() {
71// let map = HashMap$0::new();
72// }
73// # pub mod std { pub mod collections { pub struct HashMap { } } }
74// ```
75// ->
76// ```
77// use std::collections::HashMap;
78//
79// fn main() {
80// let map = HashMap::new();
81// }
82// # pub mod std { pub mod collections { pub struct HashMap { } } }
83// ```
84pub(crate) fn auto_import(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
85 let (import_assets, syntax_under_caret) = find_importable_node(ctx)?;
86 let proposed_imports =
87 import_assets.search_for_imports(&ctx.sema, ctx.config.insert_use.prefix_kind);
88 if proposed_imports.is_empty() {
89 return None;
90 }
91
92 let range = ctx.sema.original_range(&syntax_under_caret).range;
93 let group = import_group_message(import_assets.import_candidate());
94 let scope = ImportScope::find_insert_use_container(&syntax_under_caret, &ctx.sema)?;
95 for (import, _) in proposed_imports {
96 acc.add_group(
97 &group,
98 AssistId("auto_import", AssistKind::QuickFix),
99 format!("Import `{}`", &import),
100 range,
101 |builder| {
102 let rewriter =
103 insert_use(&scope, mod_path_to_ast(&import), ctx.config.insert_use.merge);
104 builder.rewrite(rewriter);
105 },
106 );
107 }
108 Some(())
109}
110
111pub(super) fn find_importable_node(ctx: &AssistContext) -> Option<(ImportAssets, SyntaxNode)> {
112 if let Some(path_under_caret) = ctx.find_node_at_offset_with_descend::<ast::Path>() {
113 ImportAssets::for_exact_path(&path_under_caret, &ctx.sema)
114 .zip(Some(path_under_caret.syntax().clone()))
115 } else if let Some(method_under_caret) =
116 ctx.find_node_at_offset_with_descend::<ast::MethodCallExpr>()
117 {
118 ImportAssets::for_method_call(&method_under_caret, &ctx.sema)
119 .zip(Some(method_under_caret.syntax().clone()))
120 } else {
121 None
122 }
123}
124
125fn import_group_message(import_candidate: &ImportCandidate) -> GroupLabel {
126 let name = match import_candidate {
127 ImportCandidate::Path(candidate) => format!("Import {}", candidate.name.text()),
128 ImportCandidate::TraitAssocItem(candidate) => {
129 format!("Import a trait for item {}", candidate.name.text())
130 }
131 ImportCandidate::TraitMethod(candidate) => {
132 format!("Import a trait for method {}", candidate.name.text())
133 }
134 };
135 GroupLabel(name)
136}
137
138#[cfg(test)]
139mod tests {
140 use super::*;
141 use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target};
142
143 #[test]
144 fn applicable_when_found_an_import_partial() {
145 check_assist(
146 auto_import,
147 r"
148 mod std {
149 pub mod fmt {
150 pub struct Formatter;
151 }
152 }
153
154 use std::fmt;
155
156 $0Formatter
157 ",
158 r"
159 mod std {
160 pub mod fmt {
161 pub struct Formatter;
162 }
163 }
164
165 use std::fmt::{self, Formatter};
166
167 Formatter
168 ",
169 );
170 }
171
172 #[test]
173 fn applicable_when_found_an_import() {
174 check_assist(
175 auto_import,
176 r"
177 $0PubStruct
178
179 pub mod PubMod {
180 pub struct PubStruct;
181 }
182 ",
183 r"
184 use PubMod::PubStruct;
185
186 PubStruct
187
188 pub mod PubMod {
189 pub struct PubStruct;
190 }
191 ",
192 );
193 }
194
195 #[test]
196 fn applicable_when_found_an_import_in_macros() {
197 check_assist(
198 auto_import,
199 r"
200 macro_rules! foo {
201 ($i:ident) => { fn foo(a: $i) {} }
202 }
203 foo!(Pub$0Struct);
204
205 pub mod PubMod {
206 pub struct PubStruct;
207 }
208 ",
209 r"
210 use PubMod::PubStruct;
211
212 macro_rules! foo {
213 ($i:ident) => { fn foo(a: $i) {} }
214 }
215 foo!(PubStruct);
216
217 pub mod PubMod {
218 pub struct PubStruct;
219 }
220 ",
221 );
222 }
223
224 #[test]
225 fn auto_imports_are_merged() {
226 check_assist(
227 auto_import,
228 r"
229 use PubMod::PubStruct1;
230
231 struct Test {
232 test: Pub$0Struct2<u8>,
233 }
234
235 pub mod PubMod {
236 pub struct PubStruct1;
237 pub struct PubStruct2<T> {
238 _t: T,
239 }
240 }
241 ",
242 r"
243 use PubMod::{PubStruct1, PubStruct2};
244
245 struct Test {
246 test: PubStruct2<u8>,
247 }
248
249 pub mod PubMod {
250 pub struct PubStruct1;
251 pub struct PubStruct2<T> {
252 _t: T,
253 }
254 }
255 ",
256 );
257 }
258
259 #[test]
260 fn applicable_when_found_multiple_imports() {
261 check_assist(
262 auto_import,
263 r"
264 PubSt$0ruct
265
266 pub mod PubMod1 {
267 pub struct PubStruct;
268 }
269 pub mod PubMod2 {
270 pub struct PubStruct;
271 }
272 pub mod PubMod3 {
273 pub struct PubStruct;
274 }
275 ",
276 r"
277 use PubMod3::PubStruct;
278
279 PubStruct
280
281 pub mod PubMod1 {
282 pub struct PubStruct;
283 }
284 pub mod PubMod2 {
285 pub struct PubStruct;
286 }
287 pub mod PubMod3 {
288 pub struct PubStruct;
289 }
290 ",
291 );
292 }
293
294 #[test]
295 fn not_applicable_for_already_imported_types() {
296 check_assist_not_applicable(
297 auto_import,
298 r"
299 use PubMod::PubStruct;
300
301 PubStruct$0
302
303 pub mod PubMod {
304 pub struct PubStruct;
305 }
306 ",
307 );
308 }
309
310 #[test]
311 fn not_applicable_for_types_with_private_paths() {
312 check_assist_not_applicable(
313 auto_import,
314 r"
315 PrivateStruct$0
316
317 pub mod PubMod {
318 struct PrivateStruct;
319 }
320 ",
321 );
322 }
323
324 #[test]
325 fn not_applicable_when_no_imports_found() {
326 check_assist_not_applicable(
327 auto_import,
328 "
329 PubStruct$0",
330 );
331 }
332
333 #[test]
334 fn not_applicable_in_import_statements() {
335 check_assist_not_applicable(
336 auto_import,
337 r"
338 use PubStruct$0;
339
340 pub mod PubMod {
341 pub struct PubStruct;
342 }",
343 );
344 }
345
346 #[test]
347 fn function_import() {
348 check_assist(
349 auto_import,
350 r"
351 test_function$0
352
353 pub mod PubMod {
354 pub fn test_function() {};
355 }
356 ",
357 r"
358 use PubMod::test_function;
359
360 test_function
361
362 pub mod PubMod {
363 pub fn test_function() {};
364 }
365 ",
366 );
367 }
368
369 #[test]
370 fn macro_import() {
371 check_assist(
372 auto_import,
373 r"
374//- /lib.rs crate:crate_with_macro
375#[macro_export]
376macro_rules! foo {
377 () => ()
378}
379
380//- /main.rs crate:main deps:crate_with_macro
381fn main() {
382 foo$0
383}
384",
385 r"use crate_with_macro::foo;
386
387fn main() {
388 foo
389}
390",
391 );
392 }
393
394 #[test]
395 fn auto_import_target() {
396 check_assist_target(
397 auto_import,
398 r"
399 struct AssistInfo {
400 group_label: Option<$0GroupLabel>,
401 }
402
403 mod m { pub struct GroupLabel; }
404 ",
405 "GroupLabel",
406 )
407 }
408
409 #[test]
410 fn not_applicable_when_path_start_is_imported() {
411 check_assist_not_applicable(
412 auto_import,
413 r"
414 pub mod mod1 {
415 pub mod mod2 {
416 pub mod mod3 {
417 pub struct TestStruct;
418 }
419 }
420 }
421
422 use mod1::mod2;
423 fn main() {
424 mod2::mod3::TestStruct$0
425 }
426 ",
427 );
428 }
429
430 #[test]
431 fn not_applicable_for_imported_function() {
432 check_assist_not_applicable(
433 auto_import,
434 r"
435 pub mod test_mod {
436 pub fn test_function() {}
437 }
438
439 use test_mod::test_function;
440 fn main() {
441 test_function$0
442 }
443 ",
444 );
445 }
446
447 #[test]
448 fn associated_struct_function() {
449 check_assist(
450 auto_import,
451 r"
452 mod test_mod {
453 pub struct TestStruct {}
454 impl TestStruct {
455 pub fn test_function() {}
456 }
457 }
458
459 fn main() {
460 TestStruct::test_function$0
461 }
462 ",
463 r"
464 use test_mod::TestStruct;
465
466 mod test_mod {
467 pub struct TestStruct {}
468 impl TestStruct {
469 pub fn test_function() {}
470 }
471 }
472
473 fn main() {
474 TestStruct::test_function
475 }
476 ",
477 );
478 }
479
480 #[test]
481 fn associated_struct_const() {
482 check_assist(
483 auto_import,
484 r"
485 mod test_mod {
486 pub struct TestStruct {}
487 impl TestStruct {
488 const TEST_CONST: u8 = 42;
489 }
490 }
491
492 fn main() {
493 TestStruct::TEST_CONST$0
494 }
495 ",
496 r"
497 use test_mod::TestStruct;
498
499 mod test_mod {
500 pub struct TestStruct {}
501 impl TestStruct {
502 const TEST_CONST: u8 = 42;
503 }
504 }
505
506 fn main() {
507 TestStruct::TEST_CONST
508 }
509 ",
510 );
511 }
512
513 #[test]
514 fn associated_trait_function() {
515 check_assist(
516 auto_import,
517 r"
518 mod test_mod {
519 pub trait TestTrait {
520 fn test_function();
521 }
522 pub struct TestStruct {}
523 impl TestTrait for TestStruct {
524 fn test_function() {}
525 }
526 }
527
528 fn main() {
529 test_mod::TestStruct::test_function$0
530 }
531 ",
532 r"
533 use test_mod::TestTrait;
534
535 mod test_mod {
536 pub trait TestTrait {
537 fn test_function();
538 }
539 pub struct TestStruct {}
540 impl TestTrait for TestStruct {
541 fn test_function() {}
542 }
543 }
544
545 fn main() {
546 test_mod::TestStruct::test_function
547 }
548 ",
549 );
550 }
551
552 #[test]
553 fn not_applicable_for_imported_trait_for_function() {
554 check_assist_not_applicable(
555 auto_import,
556 r"
557 mod test_mod {
558 pub trait TestTrait {
559 fn test_function();
560 }
561 pub trait TestTrait2 {
562 fn test_function();
563 }
564 pub enum TestEnum {
565 One,
566 Two,
567 }
568 impl TestTrait2 for TestEnum {
569 fn test_function() {}
570 }
571 impl TestTrait for TestEnum {
572 fn test_function() {}
573 }
574 }
575
576 use test_mod::TestTrait2;
577 fn main() {
578 test_mod::TestEnum::test_function$0;
579 }
580 ",
581 )
582 }
583
584 #[test]
585 fn associated_trait_const() {
586 check_assist(
587 auto_import,
588 r"
589 mod test_mod {
590 pub trait TestTrait {
591 const TEST_CONST: u8;
592 }
593 pub struct TestStruct {}
594 impl TestTrait for TestStruct {
595 const TEST_CONST: u8 = 42;
596 }
597 }
598
599 fn main() {
600 test_mod::TestStruct::TEST_CONST$0
601 }
602 ",
603 r"
604 use test_mod::TestTrait;
605
606 mod test_mod {
607 pub trait TestTrait {
608 const TEST_CONST: u8;
609 }
610 pub struct TestStruct {}
611 impl TestTrait for TestStruct {
612 const TEST_CONST: u8 = 42;
613 }
614 }
615
616 fn main() {
617 test_mod::TestStruct::TEST_CONST
618 }
619 ",
620 );
621 }
622
623 #[test]
624 fn not_applicable_for_imported_trait_for_const() {
625 check_assist_not_applicable(
626 auto_import,
627 r"
628 mod test_mod {
629 pub trait TestTrait {
630 const TEST_CONST: u8;
631 }
632 pub trait TestTrait2 {
633 const TEST_CONST: f64;
634 }
635 pub enum TestEnum {
636 One,
637 Two,
638 }
639 impl TestTrait2 for TestEnum {
640 const TEST_CONST: f64 = 42.0;
641 }
642 impl TestTrait for TestEnum {
643 const TEST_CONST: u8 = 42;
644 }
645 }
646
647 use test_mod::TestTrait2;
648 fn main() {
649 test_mod::TestEnum::TEST_CONST$0;
650 }
651 ",
652 )
653 }
654
655 #[test]
656 fn trait_method() {
657 check_assist(
658 auto_import,
659 r"
660 mod test_mod {
661 pub trait TestTrait {
662 fn test_method(&self);
663 }
664 pub struct TestStruct {}
665 impl TestTrait for TestStruct {
666 fn test_method(&self) {}
667 }
668 }
669
670 fn main() {
671 let test_struct = test_mod::TestStruct {};
672 test_struct.test_meth$0od()
673 }
674 ",
675 r"
676 use test_mod::TestTrait;
677
678 mod test_mod {
679 pub trait TestTrait {
680 fn test_method(&self);
681 }
682 pub struct TestStruct {}
683 impl TestTrait for TestStruct {
684 fn test_method(&self) {}
685 }
686 }
687
688 fn main() {
689 let test_struct = test_mod::TestStruct {};
690 test_struct.test_method()
691 }
692 ",
693 );
694 }
695
696 #[test]
697 fn trait_method_cross_crate() {
698 check_assist(
699 auto_import,
700 r"
701 //- /main.rs crate:main deps:dep
702 fn main() {
703 let test_struct = dep::test_mod::TestStruct {};
704 test_struct.test_meth$0od()
705 }
706 //- /dep.rs crate:dep
707 pub mod test_mod {
708 pub trait TestTrait {
709 fn test_method(&self);
710 }
711 pub struct TestStruct {}
712 impl TestTrait for TestStruct {
713 fn test_method(&self) {}
714 }
715 }
716 ",
717 r"
718 use dep::test_mod::TestTrait;
719
720 fn main() {
721 let test_struct = dep::test_mod::TestStruct {};
722 test_struct.test_method()
723 }
724 ",
725 );
726 }
727
728 #[test]
729 fn assoc_fn_cross_crate() {
730 check_assist(
731 auto_import,
732 r"
733 //- /main.rs crate:main deps:dep
734 fn main() {
735 dep::test_mod::TestStruct::test_func$0tion
736 }
737 //- /dep.rs crate:dep
738 pub mod test_mod {
739 pub trait TestTrait {
740 fn test_function();
741 }
742 pub struct TestStruct {}
743 impl TestTrait for TestStruct {
744 fn test_function() {}
745 }
746 }
747 ",
748 r"
749 use dep::test_mod::TestTrait;
750
751 fn main() {
752 dep::test_mod::TestStruct::test_function
753 }
754 ",
755 );
756 }
757
758 #[test]
759 fn assoc_const_cross_crate() {
760 check_assist(
761 auto_import,
762 r"
763 //- /main.rs crate:main deps:dep
764 fn main() {
765 dep::test_mod::TestStruct::CONST$0
766 }
767 //- /dep.rs crate:dep
768 pub mod test_mod {
769 pub trait TestTrait {
770 const CONST: bool;
771 }
772 pub struct TestStruct {}
773 impl TestTrait for TestStruct {
774 const CONST: bool = true;
775 }
776 }
777 ",
778 r"
779 use dep::test_mod::TestTrait;
780
781 fn main() {
782 dep::test_mod::TestStruct::CONST
783 }
784 ",
785 );
786 }
787
788 #[test]
789 fn assoc_fn_as_method_cross_crate() {
790 check_assist_not_applicable(
791 auto_import,
792 r"
793 //- /main.rs crate:main deps:dep
794 fn main() {
795 let test_struct = dep::test_mod::TestStruct {};
796 test_struct.test_func$0tion()
797 }
798 //- /dep.rs crate:dep
799 pub mod test_mod {
800 pub trait TestTrait {
801 fn test_function();
802 }
803 pub struct TestStruct {}
804 impl TestTrait for TestStruct {
805 fn test_function() {}
806 }
807 }
808 ",
809 );
810 }
811
812 #[test]
813 fn private_trait_cross_crate() {
814 check_assist_not_applicable(
815 auto_import,
816 r"
817 //- /main.rs crate:main deps:dep
818 fn main() {
819 let test_struct = dep::test_mod::TestStruct {};
820 test_struct.test_meth$0od()
821 }
822 //- /dep.rs crate:dep
823 pub mod test_mod {
824 trait TestTrait {
825 fn test_method(&self);
826 }
827 pub struct TestStruct {}
828 impl TestTrait for TestStruct {
829 fn test_method(&self) {}
830 }
831 }
832 ",
833 );
834 }
835
836 #[test]
837 fn not_applicable_for_imported_trait_for_method() {
838 check_assist_not_applicable(
839 auto_import,
840 r"
841 mod test_mod {
842 pub trait TestTrait {
843 fn test_method(&self);
844 }
845 pub trait TestTrait2 {
846 fn test_method(&self);
847 }
848 pub enum TestEnum {
849 One,
850 Two,
851 }
852 impl TestTrait2 for TestEnum {
853 fn test_method(&self) {}
854 }
855 impl TestTrait for TestEnum {
856 fn test_method(&self) {}
857 }
858 }
859
860 use test_mod::TestTrait2;
861 fn main() {
862 let one = test_mod::TestEnum::One;
863 one.test$0_method();
864 }
865 ",
866 )
867 }
868
869 #[test]
870 fn dep_import() {
871 check_assist(
872 auto_import,
873 r"
874//- /lib.rs crate:dep
875pub struct Struct;
876
877//- /main.rs crate:main deps:dep
878fn main() {
879 Struct$0
880}
881",
882 r"use dep::Struct;
883
884fn main() {
885 Struct
886}
887",
888 );
889 }
890
891 #[test]
892 fn whole_segment() {
893 // Tests that only imports whose last segment matches the identifier get suggested.
894 check_assist(
895 auto_import,
896 r"
897//- /lib.rs crate:dep
898pub mod fmt {
899 pub trait Display {}
900}
901
902pub fn panic_fmt() {}
903
904//- /main.rs crate:main deps:dep
905struct S;
906
907impl f$0mt::Display for S {}
908",
909 r"use dep::fmt;
910
911struct S;
912
913impl fmt::Display for S {}
914",
915 );
916 }
917
918 #[test]
919 fn macro_generated() {
920 // Tests that macro-generated items are suggested from external crates.
921 check_assist(
922 auto_import,
923 r"
924//- /lib.rs crate:dep
925macro_rules! mac {
926 () => {
927 pub struct Cheese;
928 };
929}
930
931mac!();
932
933//- /main.rs crate:main deps:dep
934fn main() {
935 Cheese$0;
936}
937",
938 r"use dep::Cheese;
939
940fn main() {
941 Cheese;
942}
943",
944 );
945 }
946
947 #[test]
948 fn casing() {
949 // Tests that differently cased names don't interfere and we only suggest the matching one.
950 check_assist(
951 auto_import,
952 r"
953//- /lib.rs crate:dep
954pub struct FMT;
955pub struct fmt;
956
957//- /main.rs crate:main deps:dep
958fn main() {
959 FMT$0;
960}
961",
962 r"use dep::FMT;
963
964fn main() {
965 FMT;
966}
967",
968 );
969 }
970}
diff --git a/crates/ide_assists/src/handlers/change_visibility.rs b/crates/ide_assists/src/handlers/change_visibility.rs
new file mode 100644
index 000000000..ac8c44124
--- /dev/null
+++ b/crates/ide_assists/src/handlers/change_visibility.rs
@@ -0,0 +1,212 @@
1use syntax::{
2 ast::{self, NameOwner, VisibilityOwner},
3 AstNode,
4 SyntaxKind::{CONST, ENUM, FN, MODULE, STATIC, STRUCT, TRAIT, TYPE_ALIAS, VISIBILITY},
5 T,
6};
7use test_utils::mark;
8
9use crate::{utils::vis_offset, AssistContext, AssistId, AssistKind, Assists};
10
11// Assist: change_visibility
12//
13// Adds or changes existing visibility specifier.
14//
15// ```
16// $0fn frobnicate() {}
17// ```
18// ->
19// ```
20// pub(crate) fn frobnicate() {}
21// ```
22pub(crate) fn change_visibility(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
23 if let Some(vis) = ctx.find_node_at_offset::<ast::Visibility>() {
24 return change_vis(acc, vis);
25 }
26 add_vis(acc, ctx)
27}
28
29fn add_vis(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
30 let item_keyword = ctx.token_at_offset().find(|leaf| {
31 matches!(
32 leaf.kind(),
33 T![const]
34 | T![static]
35 | T![fn]
36 | T![mod]
37 | T![struct]
38 | T![enum]
39 | T![trait]
40 | T![type]
41 )
42 });
43
44 let (offset, target) = if let Some(keyword) = item_keyword {
45 let parent = keyword.parent();
46 let def_kws = vec![CONST, STATIC, TYPE_ALIAS, FN, MODULE, STRUCT, ENUM, TRAIT];
47 // Parent is not a definition, can't add visibility
48 if !def_kws.iter().any(|&def_kw| def_kw == parent.kind()) {
49 return None;
50 }
51 // Already have visibility, do nothing
52 if parent.children().any(|child| child.kind() == VISIBILITY) {
53 return None;
54 }
55 (vis_offset(&parent), keyword.text_range())
56 } else if let Some(field_name) = ctx.find_node_at_offset::<ast::Name>() {
57 let field = field_name.syntax().ancestors().find_map(ast::RecordField::cast)?;
58 if field.name()? != field_name {
59 mark::hit!(change_visibility_field_false_positive);
60 return None;
61 }
62 if field.visibility().is_some() {
63 return None;
64 }
65 (vis_offset(field.syntax()), field_name.syntax().text_range())
66 } else if let Some(field) = ctx.find_node_at_offset::<ast::TupleField>() {
67 if field.visibility().is_some() {
68 return None;
69 }
70 (vis_offset(field.syntax()), field.syntax().text_range())
71 } else {
72 return None;
73 };
74
75 acc.add(
76 AssistId("change_visibility", AssistKind::RefactorRewrite),
77 "Change visibility to pub(crate)",
78 target,
79 |edit| {
80 edit.insert(offset, "pub(crate) ");
81 },
82 )
83}
84
85fn change_vis(acc: &mut Assists, vis: ast::Visibility) -> Option<()> {
86 if vis.syntax().text() == "pub" {
87 let target = vis.syntax().text_range();
88 return acc.add(
89 AssistId("change_visibility", AssistKind::RefactorRewrite),
90 "Change Visibility to pub(crate)",
91 target,
92 |edit| {
93 edit.replace(vis.syntax().text_range(), "pub(crate)");
94 },
95 );
96 }
97 if vis.syntax().text() == "pub(crate)" {
98 let target = vis.syntax().text_range();
99 return acc.add(
100 AssistId("change_visibility", AssistKind::RefactorRewrite),
101 "Change visibility to pub",
102 target,
103 |edit| {
104 edit.replace(vis.syntax().text_range(), "pub");
105 },
106 );
107 }
108 None
109}
110
111#[cfg(test)]
112mod tests {
113 use test_utils::mark;
114
115 use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target};
116
117 use super::*;
118
119 #[test]
120 fn change_visibility_adds_pub_crate_to_items() {
121 check_assist(change_visibility, "$0fn foo() {}", "pub(crate) fn foo() {}");
122 check_assist(change_visibility, "f$0n foo() {}", "pub(crate) fn foo() {}");
123 check_assist(change_visibility, "$0struct Foo {}", "pub(crate) struct Foo {}");
124 check_assist(change_visibility, "$0mod foo {}", "pub(crate) mod foo {}");
125 check_assist(change_visibility, "$0trait Foo {}", "pub(crate) trait Foo {}");
126 check_assist(change_visibility, "m$0od {}", "pub(crate) mod {}");
127 check_assist(change_visibility, "unsafe f$0n foo() {}", "pub(crate) unsafe fn foo() {}");
128 }
129
130 #[test]
131 fn change_visibility_works_with_struct_fields() {
132 check_assist(
133 change_visibility,
134 r"struct S { $0field: u32 }",
135 r"struct S { pub(crate) field: u32 }",
136 );
137 check_assist(change_visibility, r"struct S ( $0u32 )", r"struct S ( pub(crate) u32 )");
138 }
139
140 #[test]
141 fn change_visibility_field_false_positive() {
142 mark::check!(change_visibility_field_false_positive);
143 check_assist_not_applicable(
144 change_visibility,
145 r"struct S { field: [(); { let $0x = ();}] }",
146 )
147 }
148
149 #[test]
150 fn change_visibility_pub_to_pub_crate() {
151 check_assist(change_visibility, "$0pub fn foo() {}", "pub(crate) fn foo() {}")
152 }
153
154 #[test]
155 fn change_visibility_pub_crate_to_pub() {
156 check_assist(change_visibility, "$0pub(crate) fn foo() {}", "pub fn foo() {}")
157 }
158
159 #[test]
160 fn change_visibility_const() {
161 check_assist(change_visibility, "$0const FOO = 3u8;", "pub(crate) const FOO = 3u8;");
162 }
163
164 #[test]
165 fn change_visibility_static() {
166 check_assist(change_visibility, "$0static FOO = 3u8;", "pub(crate) static FOO = 3u8;");
167 }
168
169 #[test]
170 fn change_visibility_type_alias() {
171 check_assist(change_visibility, "$0type T = ();", "pub(crate) type T = ();");
172 }
173
174 #[test]
175 fn change_visibility_handles_comment_attrs() {
176 check_assist(
177 change_visibility,
178 r"
179 /// docs
180
181 // comments
182
183 #[derive(Debug)]
184 $0struct Foo;
185 ",
186 r"
187 /// docs
188
189 // comments
190
191 #[derive(Debug)]
192 pub(crate) struct Foo;
193 ",
194 )
195 }
196
197 #[test]
198 fn not_applicable_for_enum_variants() {
199 check_assist_not_applicable(
200 change_visibility,
201 r"mod foo { pub enum Foo {Foo1} }
202 fn main() { foo::Foo::Foo1$0 } ",
203 );
204 }
205
206 #[test]
207 fn change_visibility_target() {
208 check_assist_target(change_visibility, "$0fn foo() {}", "fn");
209 check_assist_target(change_visibility, "pub(crate)$0 fn foo() {}", "pub(crate)");
210 check_assist_target(change_visibility, "struct S { $0field: u32 }", "field");
211 }
212}
diff --git a/crates/ide_assists/src/handlers/convert_integer_literal.rs b/crates/ide_assists/src/handlers/convert_integer_literal.rs
new file mode 100644
index 000000000..a8a819cfc
--- /dev/null
+++ b/crates/ide_assists/src/handlers/convert_integer_literal.rs
@@ -0,0 +1,268 @@
1use syntax::{ast, ast::Radix, AstToken};
2
3use crate::{AssistContext, AssistId, AssistKind, Assists, GroupLabel};
4
5// Assist: convert_integer_literal
6//
7// Converts the base of integer literals to other bases.
8//
9// ```
10// const _: i32 = 10$0;
11// ```
12// ->
13// ```
14// const _: i32 = 0b1010;
15// ```
16pub(crate) fn convert_integer_literal(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
17 let literal = ctx.find_node_at_offset::<ast::Literal>()?;
18 let literal = match literal.kind() {
19 ast::LiteralKind::IntNumber(it) => it,
20 _ => return None,
21 };
22 let radix = literal.radix();
23 let value = literal.value()?;
24 let suffix = literal.suffix();
25
26 let range = literal.syntax().text_range();
27 let group_id = GroupLabel("Convert integer base".into());
28
29 for &target_radix in Radix::ALL {
30 if target_radix == radix {
31 continue;
32 }
33
34 let mut converted = match target_radix {
35 Radix::Binary => format!("0b{:b}", value),
36 Radix::Octal => format!("0o{:o}", value),
37 Radix::Decimal => value.to_string(),
38 Radix::Hexadecimal => format!("0x{:X}", value),
39 };
40
41 let label = format!("Convert {} to {}{}", literal, converted, suffix.unwrap_or_default());
42
43 // Appends the type suffix back into the new literal if it exists.
44 if let Some(suffix) = suffix {
45 converted.push_str(suffix);
46 }
47
48 acc.add_group(
49 &group_id,
50 AssistId("convert_integer_literal", AssistKind::RefactorInline),
51 label,
52 range,
53 |builder| builder.replace(range, converted),
54 );
55 }
56
57 Some(())
58}
59
60#[cfg(test)]
61mod tests {
62 use crate::tests::{check_assist_by_label, check_assist_not_applicable, check_assist_target};
63
64 use super::*;
65
66 #[test]
67 fn binary_target() {
68 check_assist_target(convert_integer_literal, "const _: i32 = 0b1010$0;", "0b1010");
69 }
70
71 #[test]
72 fn octal_target() {
73 check_assist_target(convert_integer_literal, "const _: i32 = 0o12$0;", "0o12");
74 }
75
76 #[test]
77 fn decimal_target() {
78 check_assist_target(convert_integer_literal, "const _: i32 = 10$0;", "10");
79 }
80
81 #[test]
82 fn hexadecimal_target() {
83 check_assist_target(convert_integer_literal, "const _: i32 = 0xA$0;", "0xA");
84 }
85
86 #[test]
87 fn binary_target_with_underscores() {
88 check_assist_target(convert_integer_literal, "const _: i32 = 0b10_10$0;", "0b10_10");
89 }
90
91 #[test]
92 fn octal_target_with_underscores() {
93 check_assist_target(convert_integer_literal, "const _: i32 = 0o1_2$0;", "0o1_2");
94 }
95
96 #[test]
97 fn decimal_target_with_underscores() {
98 check_assist_target(convert_integer_literal, "const _: i32 = 1_0$0;", "1_0");
99 }
100
101 #[test]
102 fn hexadecimal_target_with_underscores() {
103 check_assist_target(convert_integer_literal, "const _: i32 = 0x_A$0;", "0x_A");
104 }
105
106 #[test]
107 fn convert_decimal_integer() {
108 let before = "const _: i32 = 1000$0;";
109
110 check_assist_by_label(
111 convert_integer_literal,
112 before,
113 "const _: i32 = 0b1111101000;",
114 "Convert 1000 to 0b1111101000",
115 );
116
117 check_assist_by_label(
118 convert_integer_literal,
119 before,
120 "const _: i32 = 0o1750;",
121 "Convert 1000 to 0o1750",
122 );
123
124 check_assist_by_label(
125 convert_integer_literal,
126 before,
127 "const _: i32 = 0x3E8;",
128 "Convert 1000 to 0x3E8",
129 );
130 }
131
132 #[test]
133 fn convert_hexadecimal_integer() {
134 let before = "const _: i32 = 0xFF$0;";
135
136 check_assist_by_label(
137 convert_integer_literal,
138 before,
139 "const _: i32 = 0b11111111;",
140 "Convert 0xFF to 0b11111111",
141 );
142
143 check_assist_by_label(
144 convert_integer_literal,
145 before,
146 "const _: i32 = 0o377;",
147 "Convert 0xFF to 0o377",
148 );
149
150 check_assist_by_label(
151 convert_integer_literal,
152 before,
153 "const _: i32 = 255;",
154 "Convert 0xFF to 255",
155 );
156 }
157
158 #[test]
159 fn convert_binary_integer() {
160 let before = "const _: i32 = 0b11111111$0;";
161
162 check_assist_by_label(
163 convert_integer_literal,
164 before,
165 "const _: i32 = 0o377;",
166 "Convert 0b11111111 to 0o377",
167 );
168
169 check_assist_by_label(
170 convert_integer_literal,
171 before,
172 "const _: i32 = 255;",
173 "Convert 0b11111111 to 255",
174 );
175
176 check_assist_by_label(
177 convert_integer_literal,
178 before,
179 "const _: i32 = 0xFF;",
180 "Convert 0b11111111 to 0xFF",
181 );
182 }
183
184 #[test]
185 fn convert_octal_integer() {
186 let before = "const _: i32 = 0o377$0;";
187
188 check_assist_by_label(
189 convert_integer_literal,
190 before,
191 "const _: i32 = 0b11111111;",
192 "Convert 0o377 to 0b11111111",
193 );
194
195 check_assist_by_label(
196 convert_integer_literal,
197 before,
198 "const _: i32 = 255;",
199 "Convert 0o377 to 255",
200 );
201
202 check_assist_by_label(
203 convert_integer_literal,
204 before,
205 "const _: i32 = 0xFF;",
206 "Convert 0o377 to 0xFF",
207 );
208 }
209
210 #[test]
211 fn convert_integer_with_underscores() {
212 let before = "const _: i32 = 1_00_0$0;";
213
214 check_assist_by_label(
215 convert_integer_literal,
216 before,
217 "const _: i32 = 0b1111101000;",
218 "Convert 1_00_0 to 0b1111101000",
219 );
220
221 check_assist_by_label(
222 convert_integer_literal,
223 before,
224 "const _: i32 = 0o1750;",
225 "Convert 1_00_0 to 0o1750",
226 );
227
228 check_assist_by_label(
229 convert_integer_literal,
230 before,
231 "const _: i32 = 0x3E8;",
232 "Convert 1_00_0 to 0x3E8",
233 );
234 }
235
236 #[test]
237 fn convert_integer_with_suffix() {
238 let before = "const _: i32 = 1000i32$0;";
239
240 check_assist_by_label(
241 convert_integer_literal,
242 before,
243 "const _: i32 = 0b1111101000i32;",
244 "Convert 1000i32 to 0b1111101000i32",
245 );
246
247 check_assist_by_label(
248 convert_integer_literal,
249 before,
250 "const _: i32 = 0o1750i32;",
251 "Convert 1000i32 to 0o1750i32",
252 );
253
254 check_assist_by_label(
255 convert_integer_literal,
256 before,
257 "const _: i32 = 0x3E8i32;",
258 "Convert 1000i32 to 0x3E8i32",
259 );
260 }
261
262 #[test]
263 fn convert_overflowing_literal() {
264 let before = "const _: i32 =
265 111111111111111111111111111111111111111111111111111111111111111111111111$0;";
266 check_assist_not_applicable(convert_integer_literal, before);
267 }
268}
diff --git a/crates/ide_assists/src/handlers/early_return.rs b/crates/ide_assists/src/handlers/early_return.rs
new file mode 100644
index 000000000..6b87c3c05
--- /dev/null
+++ b/crates/ide_assists/src/handlers/early_return.rs
@@ -0,0 +1,515 @@
1use std::{iter::once, ops::RangeInclusive};
2
3use syntax::{
4 algo::replace_children,
5 ast::{
6 self,
7 edit::{AstNodeEdit, IndentLevel},
8 make,
9 },
10 AstNode,
11 SyntaxKind::{FN, LOOP_EXPR, L_CURLY, R_CURLY, WHILE_EXPR, WHITESPACE},
12 SyntaxNode,
13};
14
15use crate::{
16 assist_context::{AssistContext, Assists},
17 utils::invert_boolean_expression,
18 AssistId, AssistKind,
19};
20
21// Assist: convert_to_guarded_return
22//
23// Replace a large conditional with a guarded return.
24//
25// ```
26// fn main() {
27// $0if cond {
28// foo();
29// bar();
30// }
31// }
32// ```
33// ->
34// ```
35// fn main() {
36// if !cond {
37// return;
38// }
39// foo();
40// bar();
41// }
42// ```
43pub(crate) fn convert_to_guarded_return(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
44 let if_expr: ast::IfExpr = ctx.find_node_at_offset()?;
45 if if_expr.else_branch().is_some() {
46 return None;
47 }
48
49 let cond = if_expr.condition()?;
50
51 // Check if there is an IfLet that we can handle.
52 let if_let_pat = match cond.pat() {
53 None => None, // No IfLet, supported.
54 Some(ast::Pat::TupleStructPat(pat)) if pat.fields().count() == 1 => {
55 let path = pat.path()?;
56 match path.qualifier() {
57 None => {
58 let bound_ident = pat.fields().next().unwrap();
59 Some((path, bound_ident))
60 }
61 Some(_) => return None,
62 }
63 }
64 Some(_) => return None, // Unsupported IfLet.
65 };
66
67 let cond_expr = cond.expr()?;
68 let then_block = if_expr.then_branch()?;
69
70 let parent_block = if_expr.syntax().parent()?.ancestors().find_map(ast::BlockExpr::cast)?;
71
72 if parent_block.tail_expr()? != if_expr.clone().into() {
73 return None;
74 }
75
76 // check for early return and continue
77 let first_in_then_block = then_block.syntax().first_child()?;
78 if ast::ReturnExpr::can_cast(first_in_then_block.kind())
79 || ast::ContinueExpr::can_cast(first_in_then_block.kind())
80 || first_in_then_block
81 .children()
82 .any(|x| ast::ReturnExpr::can_cast(x.kind()) || ast::ContinueExpr::can_cast(x.kind()))
83 {
84 return None;
85 }
86
87 let parent_container = parent_block.syntax().parent()?;
88
89 let early_expression: ast::Expr = match parent_container.kind() {
90 WHILE_EXPR | LOOP_EXPR => make::expr_continue(),
91 FN => make::expr_return(None),
92 _ => return None,
93 };
94
95 if then_block.syntax().first_child_or_token().map(|t| t.kind() == L_CURLY).is_none() {
96 return None;
97 }
98
99 then_block.syntax().last_child_or_token().filter(|t| t.kind() == R_CURLY)?;
100
101 let target = if_expr.syntax().text_range();
102 acc.add(
103 AssistId("convert_to_guarded_return", AssistKind::RefactorRewrite),
104 "Convert to guarded return",
105 target,
106 |edit| {
107 let if_indent_level = IndentLevel::from_node(&if_expr.syntax());
108 let new_block = match if_let_pat {
109 None => {
110 // If.
111 let new_expr = {
112 let then_branch =
113 make::block_expr(once(make::expr_stmt(early_expression).into()), None);
114 let cond = invert_boolean_expression(cond_expr);
115 make::expr_if(make::condition(cond, None), then_branch, None)
116 .indent(if_indent_level)
117 };
118 replace(new_expr.syntax(), &then_block, &parent_block, &if_expr)
119 }
120 Some((path, bound_ident)) => {
121 // If-let.
122 let match_expr = {
123 let happy_arm = {
124 let pat = make::tuple_struct_pat(
125 path,
126 once(make::ident_pat(make::name("it")).into()),
127 );
128 let expr = {
129 let name_ref = make::name_ref("it");
130 let segment = make::path_segment(name_ref);
131 let path = make::path_unqualified(segment);
132 make::expr_path(path)
133 };
134 make::match_arm(once(pat.into()), expr)
135 };
136
137 let sad_arm = make::match_arm(
138 // FIXME: would be cool to use `None` or `Err(_)` if appropriate
139 once(make::wildcard_pat().into()),
140 early_expression,
141 );
142
143 make::expr_match(cond_expr, make::match_arm_list(vec![happy_arm, sad_arm]))
144 };
145
146 let let_stmt = make::let_stmt(
147 make::ident_pat(make::name(&bound_ident.syntax().to_string())).into(),
148 Some(match_expr),
149 );
150 let let_stmt = let_stmt.indent(if_indent_level);
151 replace(let_stmt.syntax(), &then_block, &parent_block, &if_expr)
152 }
153 };
154 edit.replace_ast(parent_block, ast::BlockExpr::cast(new_block).unwrap());
155
156 fn replace(
157 new_expr: &SyntaxNode,
158 then_block: &ast::BlockExpr,
159 parent_block: &ast::BlockExpr,
160 if_expr: &ast::IfExpr,
161 ) -> SyntaxNode {
162 let then_block_items = then_block.dedent(IndentLevel(1));
163 let end_of_then = then_block_items.syntax().last_child_or_token().unwrap();
164 let end_of_then =
165 if end_of_then.prev_sibling_or_token().map(|n| n.kind()) == Some(WHITESPACE) {
166 end_of_then.prev_sibling_or_token().unwrap()
167 } else {
168 end_of_then
169 };
170 let mut then_statements = new_expr.children_with_tokens().chain(
171 then_block_items
172 .syntax()
173 .children_with_tokens()
174 .skip(1)
175 .take_while(|i| *i != end_of_then),
176 );
177 replace_children(
178 &parent_block.syntax(),
179 RangeInclusive::new(
180 if_expr.clone().syntax().clone().into(),
181 if_expr.syntax().clone().into(),
182 ),
183 &mut then_statements,
184 )
185 }
186 },
187 )
188}
189
190#[cfg(test)]
191mod tests {
192 use crate::tests::{check_assist, check_assist_not_applicable};
193
194 use super::*;
195
196 #[test]
197 fn convert_inside_fn() {
198 check_assist(
199 convert_to_guarded_return,
200 r#"
201 fn main() {
202 bar();
203 if$0 true {
204 foo();
205
206 //comment
207 bar();
208 }
209 }
210 "#,
211 r#"
212 fn main() {
213 bar();
214 if !true {
215 return;
216 }
217 foo();
218
219 //comment
220 bar();
221 }
222 "#,
223 );
224 }
225
226 #[test]
227 fn convert_let_inside_fn() {
228 check_assist(
229 convert_to_guarded_return,
230 r#"
231 fn main(n: Option<String>) {
232 bar();
233 if$0 let Some(n) = n {
234 foo(n);
235
236 //comment
237 bar();
238 }
239 }
240 "#,
241 r#"
242 fn main(n: Option<String>) {
243 bar();
244 let n = match n {
245 Some(it) => it,
246 _ => return,
247 };
248 foo(n);
249
250 //comment
251 bar();
252 }
253 "#,
254 );
255 }
256
257 #[test]
258 fn convert_if_let_result() {
259 check_assist(
260 convert_to_guarded_return,
261 r#"
262 fn main() {
263 if$0 let Ok(x) = Err(92) {
264 foo(x);
265 }
266 }
267 "#,
268 r#"
269 fn main() {
270 let x = match Err(92) {
271 Ok(it) => it,
272 _ => return,
273 };
274 foo(x);
275 }
276 "#,
277 );
278 }
279
280 #[test]
281 fn convert_let_ok_inside_fn() {
282 check_assist(
283 convert_to_guarded_return,
284 r#"
285 fn main(n: Option<String>) {
286 bar();
287 if$0 let Ok(n) = n {
288 foo(n);
289
290 //comment
291 bar();
292 }
293 }
294 "#,
295 r#"
296 fn main(n: Option<String>) {
297 bar();
298 let n = match n {
299 Ok(it) => it,
300 _ => return,
301 };
302 foo(n);
303
304 //comment
305 bar();
306 }
307 "#,
308 );
309 }
310
311 #[test]
312 fn convert_inside_while() {
313 check_assist(
314 convert_to_guarded_return,
315 r#"
316 fn main() {
317 while true {
318 if$0 true {
319 foo();
320 bar();
321 }
322 }
323 }
324 "#,
325 r#"
326 fn main() {
327 while true {
328 if !true {
329 continue;
330 }
331 foo();
332 bar();
333 }
334 }
335 "#,
336 );
337 }
338
339 #[test]
340 fn convert_let_inside_while() {
341 check_assist(
342 convert_to_guarded_return,
343 r#"
344 fn main() {
345 while true {
346 if$0 let Some(n) = n {
347 foo(n);
348 bar();
349 }
350 }
351 }
352 "#,
353 r#"
354 fn main() {
355 while true {
356 let n = match n {
357 Some(it) => it,
358 _ => continue,
359 };
360 foo(n);
361 bar();
362 }
363 }
364 "#,
365 );
366 }
367
368 #[test]
369 fn convert_inside_loop() {
370 check_assist(
371 convert_to_guarded_return,
372 r#"
373 fn main() {
374 loop {
375 if$0 true {
376 foo();
377 bar();
378 }
379 }
380 }
381 "#,
382 r#"
383 fn main() {
384 loop {
385 if !true {
386 continue;
387 }
388 foo();
389 bar();
390 }
391 }
392 "#,
393 );
394 }
395
396 #[test]
397 fn convert_let_inside_loop() {
398 check_assist(
399 convert_to_guarded_return,
400 r#"
401 fn main() {
402 loop {
403 if$0 let Some(n) = n {
404 foo(n);
405 bar();
406 }
407 }
408 }
409 "#,
410 r#"
411 fn main() {
412 loop {
413 let n = match n {
414 Some(it) => it,
415 _ => continue,
416 };
417 foo(n);
418 bar();
419 }
420 }
421 "#,
422 );
423 }
424
425 #[test]
426 fn ignore_already_converted_if() {
427 check_assist_not_applicable(
428 convert_to_guarded_return,
429 r#"
430 fn main() {
431 if$0 true {
432 return;
433 }
434 }
435 "#,
436 );
437 }
438
439 #[test]
440 fn ignore_already_converted_loop() {
441 check_assist_not_applicable(
442 convert_to_guarded_return,
443 r#"
444 fn main() {
445 loop {
446 if$0 true {
447 continue;
448 }
449 }
450 }
451 "#,
452 );
453 }
454
455 #[test]
456 fn ignore_return() {
457 check_assist_not_applicable(
458 convert_to_guarded_return,
459 r#"
460 fn main() {
461 if$0 true {
462 return
463 }
464 }
465 "#,
466 );
467 }
468
469 #[test]
470 fn ignore_else_branch() {
471 check_assist_not_applicable(
472 convert_to_guarded_return,
473 r#"
474 fn main() {
475 if$0 true {
476 foo();
477 } else {
478 bar()
479 }
480 }
481 "#,
482 );
483 }
484
485 #[test]
486 fn ignore_statements_aftert_if() {
487 check_assist_not_applicable(
488 convert_to_guarded_return,
489 r#"
490 fn main() {
491 if$0 true {
492 foo();
493 }
494 bar();
495 }
496 "#,
497 );
498 }
499
500 #[test]
501 fn ignore_statements_inside_if() {
502 check_assist_not_applicable(
503 convert_to_guarded_return,
504 r#"
505 fn main() {
506 if false {
507 if$0 true {
508 foo();
509 }
510 }
511 }
512 "#,
513 );
514 }
515}
diff --git a/crates/ide_assists/src/handlers/expand_glob_import.rs b/crates/ide_assists/src/handlers/expand_glob_import.rs
new file mode 100644
index 000000000..5fe617ba4
--- /dev/null
+++ b/crates/ide_assists/src/handlers/expand_glob_import.rs
@@ -0,0 +1,907 @@
1use either::Either;
2use hir::{AssocItem, MacroDef, Module, ModuleDef, Name, PathResolution, ScopeDef};
3use ide_db::{
4 defs::{Definition, NameRefClass},
5 search::SearchScope,
6};
7use syntax::{
8 algo::SyntaxRewriter,
9 ast::{self, make},
10 AstNode, Direction, SyntaxNode, SyntaxToken, T,
11};
12
13use crate::{
14 assist_context::{AssistContext, Assists},
15 AssistId, AssistKind,
16};
17
18// Assist: expand_glob_import
19//
20// Expands glob imports.
21//
22// ```
23// mod foo {
24// pub struct Bar;
25// pub struct Baz;
26// }
27//
28// use foo::*$0;
29//
30// fn qux(bar: Bar, baz: Baz) {}
31// ```
32// ->
33// ```
34// mod foo {
35// pub struct Bar;
36// pub struct Baz;
37// }
38//
39// use foo::{Baz, Bar};
40//
41// fn qux(bar: Bar, baz: Baz) {}
42// ```
43pub(crate) fn expand_glob_import(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
44 let star = ctx.find_token_syntax_at_offset(T![*])?;
45 let (parent, mod_path) = find_parent_and_path(&star)?;
46 let target_module = match ctx.sema.resolve_path(&mod_path)? {
47 PathResolution::Def(ModuleDef::Module(it)) => it,
48 _ => return None,
49 };
50
51 let current_scope = ctx.sema.scope(&star.parent());
52 let current_module = current_scope.module()?;
53
54 let refs_in_target = find_refs_in_mod(ctx, target_module, Some(current_module))?;
55 let imported_defs = find_imported_defs(ctx, star)?;
56 let names_to_import = find_names_to_import(ctx, refs_in_target, imported_defs);
57
58 let target = parent.clone().either(|n| n.syntax().clone(), |n| n.syntax().clone());
59 acc.add(
60 AssistId("expand_glob_import", AssistKind::RefactorRewrite),
61 "Expand glob import",
62 target.text_range(),
63 |builder| {
64 let mut rewriter = SyntaxRewriter::default();
65 replace_ast(&mut rewriter, parent, mod_path, names_to_import);
66 builder.rewrite(rewriter);
67 },
68 )
69}
70
71fn find_parent_and_path(
72 star: &SyntaxToken,
73) -> Option<(Either<ast::UseTree, ast::UseTreeList>, ast::Path)> {
74 return star.ancestors().find_map(|n| {
75 find_use_tree_list(n.clone())
76 .and_then(|(u, p)| Some((Either::Right(u), p)))
77 .or_else(|| find_use_tree(n).and_then(|(u, p)| Some((Either::Left(u), p))))
78 });
79
80 fn find_use_tree_list(n: SyntaxNode) -> Option<(ast::UseTreeList, ast::Path)> {
81 let use_tree_list = ast::UseTreeList::cast(n)?;
82 let path = use_tree_list.parent_use_tree().path()?;
83 Some((use_tree_list, path))
84 }
85
86 fn find_use_tree(n: SyntaxNode) -> Option<(ast::UseTree, ast::Path)> {
87 let use_tree = ast::UseTree::cast(n)?;
88 let path = use_tree.path()?;
89 Some((use_tree, path))
90 }
91}
92
93#[derive(Debug, PartialEq, Clone)]
94enum Def {
95 ModuleDef(ModuleDef),
96 MacroDef(MacroDef),
97}
98
99impl Def {
100 fn is_referenced_in(&self, ctx: &AssistContext) -> bool {
101 let def = match self {
102 Def::ModuleDef(def) => Definition::ModuleDef(*def),
103 Def::MacroDef(def) => Definition::Macro(*def),
104 };
105
106 let search_scope = SearchScope::single_file(ctx.frange.file_id);
107 def.usages(&ctx.sema).in_scope(search_scope).at_least_one()
108 }
109}
110
111#[derive(Debug, Clone)]
112struct Ref {
113 // could be alias
114 visible_name: Name,
115 def: Def,
116}
117
118impl Ref {
119 fn from_scope_def(name: Name, scope_def: ScopeDef) -> Option<Self> {
120 match scope_def {
121 ScopeDef::ModuleDef(def) => Some(Ref { visible_name: name, def: Def::ModuleDef(def) }),
122 ScopeDef::MacroDef(def) => Some(Ref { visible_name: name, def: Def::MacroDef(def) }),
123 _ => None,
124 }
125 }
126}
127
128#[derive(Debug, Clone)]
129struct Refs(Vec<Ref>);
130
131impl Refs {
132 fn used_refs(&self, ctx: &AssistContext) -> Refs {
133 Refs(
134 self.0
135 .clone()
136 .into_iter()
137 .filter(|r| {
138 if let Def::ModuleDef(ModuleDef::Trait(tr)) = r.def {
139 if tr
140 .items(ctx.db())
141 .into_iter()
142 .find(|ai| {
143 if let AssocItem::Function(f) = *ai {
144 Def::ModuleDef(ModuleDef::Function(f)).is_referenced_in(ctx)
145 } else {
146 false
147 }
148 })
149 .is_some()
150 {
151 return true;
152 }
153 }
154
155 r.def.is_referenced_in(ctx)
156 })
157 .collect(),
158 )
159 }
160
161 fn filter_out_by_defs(&self, defs: Vec<Def>) -> Refs {
162 Refs(self.0.clone().into_iter().filter(|r| !defs.contains(&r.def)).collect())
163 }
164}
165
166fn find_refs_in_mod(
167 ctx: &AssistContext,
168 module: Module,
169 visible_from: Option<Module>,
170) -> Option<Refs> {
171 if let Some(from) = visible_from {
172 if !is_mod_visible_from(ctx, module, from) {
173 return None;
174 }
175 }
176
177 let module_scope = module.scope(ctx.db(), visible_from);
178 let refs = module_scope.into_iter().filter_map(|(n, d)| Ref::from_scope_def(n, d)).collect();
179 Some(Refs(refs))
180}
181
182fn is_mod_visible_from(ctx: &AssistContext, module: Module, from: Module) -> bool {
183 match module.parent(ctx.db()) {
184 Some(parent) => {
185 parent.visibility_of(ctx.db(), &ModuleDef::Module(module)).map_or(true, |vis| {
186 vis.is_visible_from(ctx.db(), from.into()) && is_mod_visible_from(ctx, parent, from)
187 })
188 }
189 None => true,
190 }
191}
192
193// looks for name refs in parent use block's siblings
194//
195// mod bar {
196// mod qux {
197// struct Qux;
198// }
199//
200// pub use qux::Qux;
201// }
202//
203// ↓ ---------------
204// use foo::*$0;
205// use baz::Baz;
206// ↑ ---------------
207fn find_imported_defs(ctx: &AssistContext, star: SyntaxToken) -> Option<Vec<Def>> {
208 let parent_use_item_syntax =
209 star.ancestors().find_map(|n| if ast::Use::can_cast(n.kind()) { Some(n) } else { None })?;
210
211 Some(
212 [Direction::Prev, Direction::Next]
213 .iter()
214 .map(|dir| {
215 parent_use_item_syntax
216 .siblings(dir.to_owned())
217 .filter(|n| ast::Use::can_cast(n.kind()))
218 })
219 .flatten()
220 .filter_map(|n| Some(n.descendants().filter_map(ast::NameRef::cast)))
221 .flatten()
222 .filter_map(|r| match NameRefClass::classify(&ctx.sema, &r)? {
223 NameRefClass::Definition(Definition::ModuleDef(def)) => Some(Def::ModuleDef(def)),
224 NameRefClass::Definition(Definition::Macro(def)) => Some(Def::MacroDef(def)),
225 _ => None,
226 })
227 .collect(),
228 )
229}
230
231fn find_names_to_import(
232 ctx: &AssistContext,
233 refs_in_target: Refs,
234 imported_defs: Vec<Def>,
235) -> Vec<Name> {
236 let used_refs = refs_in_target.used_refs(ctx).filter_out_by_defs(imported_defs);
237 used_refs.0.iter().map(|r| r.visible_name.clone()).collect()
238}
239
240fn replace_ast(
241 rewriter: &mut SyntaxRewriter,
242 parent: Either<ast::UseTree, ast::UseTreeList>,
243 path: ast::Path,
244 names_to_import: Vec<Name>,
245) {
246 let existing_use_trees = match parent.clone() {
247 Either::Left(_) => vec![],
248 Either::Right(u) => u
249 .use_trees()
250 .filter(|n|
251 // filter out star
252 n.star_token().is_none())
253 .collect(),
254 };
255
256 let new_use_trees: Vec<ast::UseTree> = names_to_import
257 .iter()
258 .map(|n| {
259 let path = make::path_unqualified(make::path_segment(make::name_ref(&n.to_string())));
260 make::use_tree(path, None, None, false)
261 })
262 .collect();
263
264 let use_trees = [&existing_use_trees[..], &new_use_trees[..]].concat();
265
266 match use_trees.as_slice() {
267 [name] => {
268 if let Some(end_path) = name.path() {
269 rewriter.replace_ast(
270 &parent.left_or_else(|tl| tl.parent_use_tree()),
271 &make::use_tree(make::path_concat(path, end_path), None, None, false),
272 );
273 }
274 }
275 names => match &parent {
276 Either::Left(parent) => rewriter.replace_ast(
277 parent,
278 &make::use_tree(path, Some(make::use_tree_list(names.to_owned())), None, false),
279 ),
280 Either::Right(parent) => {
281 rewriter.replace_ast(parent, &make::use_tree_list(names.to_owned()))
282 }
283 },
284 };
285}
286
287#[cfg(test)]
288mod tests {
289 use crate::tests::{check_assist, check_assist_not_applicable};
290
291 use super::*;
292
293 #[test]
294 fn expanding_glob_import() {
295 check_assist(
296 expand_glob_import,
297 r"
298mod foo {
299 pub struct Bar;
300 pub struct Baz;
301 pub struct Qux;
302
303 pub fn f() {}
304}
305
306use foo::*$0;
307
308fn qux(bar: Bar, baz: Baz) {
309 f();
310}
311",
312 r"
313mod foo {
314 pub struct Bar;
315 pub struct Baz;
316 pub struct Qux;
317
318 pub fn f() {}
319}
320
321use foo::{Baz, Bar, f};
322
323fn qux(bar: Bar, baz: Baz) {
324 f();
325}
326",
327 )
328 }
329
330 #[test]
331 fn expanding_glob_import_with_existing_explicit_names() {
332 check_assist(
333 expand_glob_import,
334 r"
335mod foo {
336 pub struct Bar;
337 pub struct Baz;
338 pub struct Qux;
339
340 pub fn f() {}
341}
342
343use foo::{*$0, f};
344
345fn qux(bar: Bar, baz: Baz) {
346 f();
347}
348",
349 r"
350mod foo {
351 pub struct Bar;
352 pub struct Baz;
353 pub struct Qux;
354
355 pub fn f() {}
356}
357
358use foo::{f, Baz, Bar};
359
360fn qux(bar: Bar, baz: Baz) {
361 f();
362}
363",
364 )
365 }
366
367 #[test]
368 fn expanding_glob_import_with_existing_uses_in_same_module() {
369 check_assist(
370 expand_glob_import,
371 r"
372mod foo {
373 pub struct Bar;
374 pub struct Baz;
375 pub struct Qux;
376
377 pub fn f() {}
378}
379
380use foo::Bar;
381use foo::{*$0, f};
382
383fn qux(bar: Bar, baz: Baz) {
384 f();
385}
386",
387 r"
388mod foo {
389 pub struct Bar;
390 pub struct Baz;
391 pub struct Qux;
392
393 pub fn f() {}
394}
395
396use foo::Bar;
397use foo::{f, Baz};
398
399fn qux(bar: Bar, baz: Baz) {
400 f();
401}
402",
403 )
404 }
405
406 #[test]
407 fn expanding_nested_glob_import() {
408 check_assist(
409 expand_glob_import,
410 r"
411mod foo {
412 pub mod bar {
413 pub struct Bar;
414 pub struct Baz;
415 pub struct Qux;
416
417 pub fn f() {}
418 }
419
420 pub mod baz {
421 pub fn g() {}
422 }
423}
424
425use foo::{bar::{*$0, f}, baz::*};
426
427fn qux(bar: Bar, baz: Baz) {
428 f();
429 g();
430}
431",
432 r"
433mod foo {
434 pub mod bar {
435 pub struct Bar;
436 pub struct Baz;
437 pub struct Qux;
438
439 pub fn f() {}
440 }
441
442 pub mod baz {
443 pub fn g() {}
444 }
445}
446
447use foo::{bar::{f, Baz, Bar}, baz::*};
448
449fn qux(bar: Bar, baz: Baz) {
450 f();
451 g();
452}
453",
454 );
455
456 check_assist(
457 expand_glob_import,
458 r"
459mod foo {
460 pub mod bar {
461 pub struct Bar;
462 pub struct Baz;
463 pub struct Qux;
464
465 pub fn f() {}
466 }
467
468 pub mod baz {
469 pub fn g() {}
470 }
471}
472
473use foo::{bar::{Bar, Baz, f}, baz::*$0};
474
475fn qux(bar: Bar, baz: Baz) {
476 f();
477 g();
478}
479",
480 r"
481mod foo {
482 pub mod bar {
483 pub struct Bar;
484 pub struct Baz;
485 pub struct Qux;
486
487 pub fn f() {}
488 }
489
490 pub mod baz {
491 pub fn g() {}
492 }
493}
494
495use foo::{bar::{Bar, Baz, f}, baz::g};
496
497fn qux(bar: Bar, baz: Baz) {
498 f();
499 g();
500}
501",
502 );
503
504 check_assist(
505 expand_glob_import,
506 r"
507mod foo {
508 pub mod bar {
509 pub struct Bar;
510 pub struct Baz;
511 pub struct Qux;
512
513 pub fn f() {}
514 }
515
516 pub mod baz {
517 pub fn g() {}
518
519 pub mod qux {
520 pub fn h() {}
521 pub fn m() {}
522
523 pub mod q {
524 pub fn j() {}
525 }
526 }
527 }
528}
529
530use foo::{
531 bar::{*, f},
532 baz::{g, qux::*$0}
533};
534
535fn qux(bar: Bar, baz: Baz) {
536 f();
537 g();
538 h();
539 q::j();
540}
541",
542 r"
543mod foo {
544 pub mod bar {
545 pub struct Bar;
546 pub struct Baz;
547 pub struct Qux;
548
549 pub fn f() {}
550 }
551
552 pub mod baz {
553 pub fn g() {}
554
555 pub mod qux {
556 pub fn h() {}
557 pub fn m() {}
558
559 pub mod q {
560 pub fn j() {}
561 }
562 }
563 }
564}
565
566use foo::{
567 bar::{*, f},
568 baz::{g, qux::{q, h}}
569};
570
571fn qux(bar: Bar, baz: Baz) {
572 f();
573 g();
574 h();
575 q::j();
576}
577",
578 );
579
580 check_assist(
581 expand_glob_import,
582 r"
583mod foo {
584 pub mod bar {
585 pub struct Bar;
586 pub struct Baz;
587 pub struct Qux;
588
589 pub fn f() {}
590 }
591
592 pub mod baz {
593 pub fn g() {}
594
595 pub mod qux {
596 pub fn h() {}
597 pub fn m() {}
598
599 pub mod q {
600 pub fn j() {}
601 }
602 }
603 }
604}
605
606use foo::{
607 bar::{*, f},
608 baz::{g, qux::{h, q::*$0}}
609};
610
611fn qux(bar: Bar, baz: Baz) {
612 f();
613 g();
614 h();
615 j();
616}
617",
618 r"
619mod foo {
620 pub mod bar {
621 pub struct Bar;
622 pub struct Baz;
623 pub struct Qux;
624
625 pub fn f() {}
626 }
627
628 pub mod baz {
629 pub fn g() {}
630
631 pub mod qux {
632 pub fn h() {}
633 pub fn m() {}
634
635 pub mod q {
636 pub fn j() {}
637 }
638 }
639 }
640}
641
642use foo::{
643 bar::{*, f},
644 baz::{g, qux::{h, q::j}}
645};
646
647fn qux(bar: Bar, baz: Baz) {
648 f();
649 g();
650 h();
651 j();
652}
653",
654 );
655
656 check_assist(
657 expand_glob_import,
658 r"
659mod foo {
660 pub mod bar {
661 pub struct Bar;
662 pub struct Baz;
663 pub struct Qux;
664
665 pub fn f() {}
666 }
667
668 pub mod baz {
669 pub fn g() {}
670
671 pub mod qux {