aboutsummaryrefslogtreecommitdiff
path: root/crates/ide_assists/src
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ide_assists/src')
-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
65 files changed, 27067 insertions, 0 deletions
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 {
672 pub fn h() {}
673 pub fn m() {}
674
675 pub mod q {
676 pub fn j() {}
677 }
678 }
679 }
680}
681
682use foo::{
683 bar::{*, f},
684 baz::{g, qux::{q::j, *$0}}
685};
686
687fn qux(bar: Bar, baz: Baz) {
688 f();
689 g();
690 h();
691 j();
692}
693",
694 r"
695mod foo {
696 pub mod bar {
697 pub struct Bar;
698 pub struct Baz;
699 pub struct Qux;
700
701 pub fn f() {}
702 }
703
704 pub mod baz {
705 pub fn g() {}
706
707 pub mod qux {
708 pub fn h() {}
709 pub fn m() {}
710
711 pub mod q {
712 pub fn j() {}
713 }
714 }
715 }
716}
717
718use foo::{
719 bar::{*, f},
720 baz::{g, qux::{q::j, h}}
721};
722
723fn qux(bar: Bar, baz: Baz) {
724 f();
725 g();
726 h();
727 j();
728}
729",
730 );
731 }
732
733 #[test]
734 fn expanding_glob_import_with_macro_defs() {
735 // FIXME: this is currently fails because `Definition::find_usages` ignores macros
736 // https://github.com/rust-analyzer/rust-analyzer/issues/3484
737 //
738 // check_assist(
739 // expand_glob_import,
740 // r"
741 // //- /lib.rs crate:foo
742 // #[macro_export]
743 // macro_rules! bar {
744 // () => ()
745 // }
746
747 // pub fn baz() {}
748
749 // //- /main.rs crate:main deps:foo
750 // use foo::*$0;
751
752 // fn main() {
753 // bar!();
754 // baz();
755 // }
756 // ",
757 // r"
758 // use foo::{bar, baz};
759
760 // fn main() {
761 // bar!();
762 // baz();
763 // }
764 // ",
765 // )
766 }
767
768 #[test]
769 fn expanding_glob_import_with_trait_method_uses() {
770 check_assist(
771 expand_glob_import,
772 r"
773//- /lib.rs crate:foo
774pub trait Tr {
775 fn method(&self) {}
776}
777impl Tr for () {}
778
779//- /main.rs crate:main deps:foo
780use foo::*$0;
781
782fn main() {
783 ().method();
784}
785",
786 r"
787use foo::Tr;
788
789fn main() {
790 ().method();
791}
792",
793 );
794
795 check_assist(
796 expand_glob_import,
797 r"
798//- /lib.rs crate:foo
799pub trait Tr {
800 fn method(&self) {}
801}
802impl Tr for () {}
803
804pub trait Tr2 {
805 fn method2(&self) {}
806}
807impl Tr2 for () {}
808
809//- /main.rs crate:main deps:foo
810use foo::*$0;
811
812fn main() {
813 ().method();
814}
815",
816 r"
817use foo::Tr;
818
819fn main() {
820 ().method();
821}
822",
823 );
824 }
825
826 #[test]
827 fn expanding_is_not_applicable_if_target_module_is_not_accessible_from_current_scope() {
828 check_assist_not_applicable(
829 expand_glob_import,
830 r"
831mod foo {
832 mod bar {
833 pub struct Bar;
834 }
835}
836
837use foo::bar::*$0;
838
839fn baz(bar: Bar) {}
840",
841 );
842
843 check_assist_not_applicable(
844 expand_glob_import,
845 r"
846mod foo {
847 mod bar {
848 pub mod baz {
849 pub struct Baz;
850 }
851 }
852}
853
854use foo::bar::baz::*$0;
855
856fn qux(baz: Baz) {}
857",
858 );
859 }
860
861 #[test]
862 fn expanding_is_not_applicable_if_cursor_is_not_in_star_token() {
863 check_assist_not_applicable(
864 expand_glob_import,
865 r"
866 mod foo {
867 pub struct Bar;
868 pub struct Baz;
869 pub struct Qux;
870 }
871
872 use foo::Bar$0;
873
874 fn qux(bar: Bar, baz: Baz) {}
875 ",
876 )
877 }
878
879 #[test]
880 fn expanding_glob_import_single_nested_glob_only() {
881 check_assist(
882 expand_glob_import,
883 r"
884mod foo {
885 pub struct Bar;
886}
887
888use foo::{*$0};
889
890struct Baz {
891 bar: Bar
892}
893",
894 r"
895mod foo {
896 pub struct Bar;
897}
898
899use foo::Bar;
900
901struct Baz {
902 bar: Bar
903}
904",
905 );
906 }
907}
diff --git a/crates/ide_assists/src/handlers/extract_function.rs b/crates/ide_assists/src/handlers/extract_function.rs
new file mode 100644
index 000000000..9f34cc725
--- /dev/null
+++ b/crates/ide_assists/src/handlers/extract_function.rs
@@ -0,0 +1,3378 @@
1use std::iter;
2
3use ast::make;
4use either::Either;
5use hir::{HirDisplay, Local};
6use ide_db::{
7 defs::{Definition, NameRefClass},
8 search::{FileReference, ReferenceAccess, SearchScope},
9};
10use itertools::Itertools;
11use stdx::format_to;
12use syntax::{
13 algo::SyntaxRewriter,
14 ast::{
15 self,
16 edit::{AstNodeEdit, IndentLevel},
17 AstNode,
18 },
19 SyntaxElement,
20 SyntaxKind::{self, BLOCK_EXPR, BREAK_EXPR, COMMENT, PATH_EXPR, RETURN_EXPR},
21 SyntaxNode, SyntaxToken, TextRange, TextSize, TokenAtOffset, WalkEvent, T,
22};
23use test_utils::mark;
24
25use crate::{
26 assist_context::{AssistContext, Assists},
27 AssistId,
28};
29
30// Assist: extract_function
31//
32// Extracts selected statements into new function.
33//
34// ```
35// fn main() {
36// let n = 1;
37// $0let m = n + 2;
38// let k = m + n;$0
39// let g = 3;
40// }
41// ```
42// ->
43// ```
44// fn main() {
45// let n = 1;
46// fun_name(n);
47// let g = 3;
48// }
49//
50// fn $0fun_name(n: i32) {
51// let m = n + 2;
52// let k = m + n;
53// }
54// ```
55pub(crate) fn extract_function(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
56 if ctx.frange.range.is_empty() {
57 return None;
58 }
59
60 let node = ctx.covering_element();
61 if node.kind() == COMMENT {
62 mark::hit!(extract_function_in_comment_is_not_applicable);
63 return None;
64 }
65
66 let node = element_to_node(node);
67
68 let body = extraction_target(&node, ctx.frange.range)?;
69
70 let vars_used_in_body = vars_used_in_body(ctx, &body);
71 let self_param = self_param_from_usages(ctx, &body, &vars_used_in_body);
72
73 let anchor = if self_param.is_some() { Anchor::Method } else { Anchor::Freestanding };
74 let insert_after = scope_for_fn_insertion(&body, anchor)?;
75 let module = ctx.sema.scope(&insert_after).module()?;
76
77 let vars_defined_in_body_and_outlive = vars_defined_in_body_and_outlive(ctx, &body);
78 let ret_ty = body_return_ty(ctx, &body)?;
79
80 // FIXME: we compute variables that outlive here just to check `never!` condition
81 // this requires traversing whole `body` (cheap) and finding all references (expensive)
82 // maybe we can move this check to `edit` closure somehow?
83 if stdx::never!(!vars_defined_in_body_and_outlive.is_empty() && !ret_ty.is_unit()) {
84 // We should not have variables that outlive body if we have expression block
85 return None;
86 }
87 let control_flow = external_control_flow(ctx, &body)?;
88
89 let target_range = body.text_range();
90
91 acc.add(
92 AssistId("extract_function", crate::AssistKind::RefactorExtract),
93 "Extract into function",
94 target_range,
95 move |builder| {
96 let params = extracted_function_params(ctx, &body, &vars_used_in_body);
97
98 let fun = Function {
99 name: "fun_name".to_string(),
100 self_param: self_param.map(|(_, pat)| pat),
101 params,
102 control_flow,
103 ret_ty,
104 body,
105 vars_defined_in_body_and_outlive,
106 };
107
108 let new_indent = IndentLevel::from_node(&insert_after);
109 let old_indent = fun.body.indent_level();
110
111 builder.replace(target_range, format_replacement(ctx, &fun, old_indent));
112
113 let fn_def = format_function(ctx, module, &fun, old_indent, new_indent);
114 let insert_offset = insert_after.text_range().end();
115 builder.insert(insert_offset, fn_def);
116 },
117 )
118}
119
120fn external_control_flow(ctx: &AssistContext, body: &FunctionBody) -> Option<ControlFlow> {
121 let mut ret_expr = None;
122 let mut try_expr = None;
123 let mut break_expr = None;
124 let mut continue_expr = None;
125 let (syntax, text_range) = match body {
126 FunctionBody::Expr(expr) => (expr.syntax(), expr.syntax().text_range()),
127 FunctionBody::Span { parent, text_range } => (parent.syntax(), *text_range),
128 };
129
130 let mut nested_loop = None;
131 let mut nested_scope = None;
132
133 for e in syntax.preorder() {
134 let e = match e {
135 WalkEvent::Enter(e) => e,
136 WalkEvent::Leave(e) => {
137 if nested_loop.as_ref() == Some(&e) {
138 nested_loop = None;
139 }
140 if nested_scope.as_ref() == Some(&e) {
141 nested_scope = None;
142 }
143 continue;
144 }
145 };
146 if nested_scope.is_some() {
147 continue;
148 }
149 if !text_range.contains_range(e.text_range()) {
150 continue;
151 }
152 match e.kind() {
153 SyntaxKind::LOOP_EXPR | SyntaxKind::WHILE_EXPR | SyntaxKind::FOR_EXPR => {
154 if nested_loop.is_none() {
155 nested_loop = Some(e);
156 }
157 }
158 SyntaxKind::FN
159 | SyntaxKind::CONST
160 | SyntaxKind::STATIC
161 | SyntaxKind::IMPL
162 | SyntaxKind::MODULE => {
163 if nested_scope.is_none() {
164 nested_scope = Some(e);
165 }
166 }
167 SyntaxKind::RETURN_EXPR => {
168 ret_expr = Some(ast::ReturnExpr::cast(e).unwrap());
169 }
170 SyntaxKind::TRY_EXPR => {
171 try_expr = Some(ast::TryExpr::cast(e).unwrap());
172 }
173 SyntaxKind::BREAK_EXPR if nested_loop.is_none() => {
174 break_expr = Some(ast::BreakExpr::cast(e).unwrap());
175 }
176 SyntaxKind::CONTINUE_EXPR if nested_loop.is_none() => {
177 continue_expr = Some(ast::ContinueExpr::cast(e).unwrap());
178 }
179 _ => {}
180 }
181 }
182
183 let kind = match (try_expr, ret_expr, break_expr, continue_expr) {
184 (Some(e), None, None, None) => {
185 let func = e.syntax().ancestors().find_map(ast::Fn::cast)?;
186 let def = ctx.sema.to_def(&func)?;
187 let ret_ty = def.ret_type(ctx.db());
188 let kind = try_kind_of_ty(ret_ty, ctx)?;
189
190 Some(FlowKind::Try { kind })
191 }
192 (Some(_), Some(r), None, None) => match r.expr() {
193 Some(expr) => {
194 if let Some(kind) = expr_err_kind(&expr, ctx) {
195 Some(FlowKind::TryReturn { expr, kind })
196 } else {
197 mark::hit!(external_control_flow_try_and_return_non_err);
198 return None;
199 }
200 }
201 None => return None,
202 },
203 (Some(_), _, _, _) => {
204 mark::hit!(external_control_flow_try_and_bc);
205 return None;
206 }
207 (None, Some(r), None, None) => match r.expr() {
208 Some(expr) => Some(FlowKind::ReturnValue(expr)),
209 None => Some(FlowKind::Return),
210 },
211 (None, Some(_), _, _) => {
212 mark::hit!(external_control_flow_return_and_bc);
213 return None;
214 }
215 (None, None, Some(_), Some(_)) => {
216 mark::hit!(external_control_flow_break_and_continue);
217 return None;
218 }
219 (None, None, Some(b), None) => match b.expr() {
220 Some(expr) => Some(FlowKind::BreakValue(expr)),
221 None => Some(FlowKind::Break),
222 },
223 (None, None, None, Some(_)) => Some(FlowKind::Continue),
224 (None, None, None, None) => None,
225 };
226
227 Some(ControlFlow { kind })
228}
229
230/// Checks is expr is `Err(_)` or `None`
231fn expr_err_kind(expr: &ast::Expr, ctx: &AssistContext) -> Option<TryKind> {
232 let func_name = match expr {
233 ast::Expr::CallExpr(call_expr) => call_expr.expr()?,
234 ast::Expr::PathExpr(_) => expr.clone(),
235 _ => return None,
236 };
237 let text = func_name.syntax().text();
238
239 if text == "Err" {
240 Some(TryKind::Result { ty: ctx.sema.type_of_expr(expr)? })
241 } else if text == "None" {
242 Some(TryKind::Option)
243 } else {
244 None
245 }
246}
247
248#[derive(Debug)]
249struct Function {
250 name: String,
251 self_param: Option<ast::SelfParam>,
252 params: Vec<Param>,
253 control_flow: ControlFlow,
254 ret_ty: RetType,
255 body: FunctionBody,
256 vars_defined_in_body_and_outlive: Vec<Local>,
257}
258
259#[derive(Debug)]
260struct Param {
261 var: Local,
262 ty: hir::Type,
263 has_usages_afterwards: bool,
264 has_mut_inside_body: bool,
265 is_copy: bool,
266}
267
268#[derive(Debug)]
269struct ControlFlow {
270 kind: Option<FlowKind>,
271}
272
273#[derive(Debug, Clone, Copy, PartialEq, Eq)]
274enum ParamKind {
275 Value,
276 MutValue,
277 SharedRef,
278 MutRef,
279}
280
281#[derive(Debug, Eq, PartialEq)]
282enum FunType {
283 Unit,
284 Single(hir::Type),
285 Tuple(Vec<hir::Type>),
286}
287
288impl Function {
289 fn return_type(&self, ctx: &AssistContext) -> FunType {
290 match &self.ret_ty {
291 RetType::Expr(ty) if ty.is_unit() => FunType::Unit,
292 RetType::Expr(ty) => FunType::Single(ty.clone()),
293 RetType::Stmt => match self.vars_defined_in_body_and_outlive.as_slice() {
294 [] => FunType::Unit,
295 [var] => FunType::Single(var.ty(ctx.db())),
296 vars => {
297 let types = vars.iter().map(|v| v.ty(ctx.db())).collect();
298 FunType::Tuple(types)
299 }
300 },
301 }
302 }
303}
304
305impl ParamKind {
306 fn is_ref(&self) -> bool {
307 matches!(self, ParamKind::SharedRef | ParamKind::MutRef)
308 }
309}
310
311impl Param {
312 fn kind(&self) -> ParamKind {
313 match (self.has_usages_afterwards, self.has_mut_inside_body, self.is_copy) {
314 (true, true, _) => ParamKind::MutRef,
315 (true, false, false) => ParamKind::SharedRef,
316 (false, true, _) => ParamKind::MutValue,
317 (true, false, true) | (false, false, _) => ParamKind::Value,
318 }
319 }
320
321 fn to_arg(&self, ctx: &AssistContext) -> ast::Expr {
322 let var = path_expr_from_local(ctx, self.var);
323 match self.kind() {
324 ParamKind::Value | ParamKind::MutValue => var,
325 ParamKind::SharedRef => make::expr_ref(var, false),
326 ParamKind::MutRef => make::expr_ref(var, true),
327 }
328 }
329
330 fn to_param(&self, ctx: &AssistContext, module: hir::Module) -> ast::Param {
331 let var = self.var.name(ctx.db()).unwrap().to_string();
332 let var_name = make::name(&var);
333 let pat = match self.kind() {
334 ParamKind::MutValue => make::ident_mut_pat(var_name),
335 ParamKind::Value | ParamKind::SharedRef | ParamKind::MutRef => {
336 make::ident_pat(var_name)
337 }
338 };
339
340 let ty = make_ty(&self.ty, ctx, module);
341 let ty = match self.kind() {
342 ParamKind::Value | ParamKind::MutValue => ty,
343 ParamKind::SharedRef => make::ty_ref(ty, false),
344 ParamKind::MutRef => make::ty_ref(ty, true),
345 };
346
347 make::param(pat.into(), ty)
348 }
349}
350
351/// Control flow that is exported from extracted function
352///
353/// E.g.:
354/// ```rust,no_run
355/// loop {
356/// $0
357/// if 42 == 42 {
358/// break;
359/// }
360/// $0
361/// }
362/// ```
363#[derive(Debug, Clone)]
364enum FlowKind {
365 /// Return without value (`return;`)
366 Return,
367 /// Return with value (`return $expr;`)
368 ReturnValue(ast::Expr),
369 Try {
370 kind: TryKind,
371 },
372 TryReturn {
373 expr: ast::Expr,
374 kind: TryKind,
375 },
376 /// Break without value (`return;`)
377 Break,
378 /// Break with value (`break $expr;`)
379 BreakValue(ast::Expr),
380 /// Continue
381 Continue,
382}
383
384#[derive(Debug, Clone)]
385enum TryKind {
386 Option,
387 Result { ty: hir::Type },
388}
389
390impl FlowKind {
391 fn make_result_handler(&self, expr: Option<ast::Expr>) -> ast::Expr {
392 match self {
393 FlowKind::Return | FlowKind::ReturnValue(_) => make::expr_return(expr),
394 FlowKind::Break | FlowKind::BreakValue(_) => make::expr_break(expr),
395 FlowKind::Try { .. } | FlowKind::TryReturn { .. } => {
396 stdx::never!("cannot have result handler with try");
397 expr.unwrap_or_else(|| make::expr_return(None))
398 }
399 FlowKind::Continue => {
400 stdx::always!(expr.is_none(), "continue with value is not possible");
401 make::expr_continue()
402 }
403 }
404 }
405
406 fn expr_ty(&self, ctx: &AssistContext) -> Option<hir::Type> {
407 match self {
408 FlowKind::ReturnValue(expr)
409 | FlowKind::BreakValue(expr)
410 | FlowKind::TryReturn { expr, .. } => ctx.sema.type_of_expr(expr),
411 FlowKind::Try { .. } => {
412 stdx::never!("try does not have defined expr_ty");
413 None
414 }
415 FlowKind::Return | FlowKind::Break | FlowKind::Continue => None,
416 }
417 }
418}
419
420fn try_kind_of_ty(ty: hir::Type, ctx: &AssistContext) -> Option<TryKind> {
421 if ty.is_unknown() {
422 // We favour Result for `expr?`
423 return Some(TryKind::Result { ty });
424 }
425 let adt = ty.as_adt()?;
426 let name = adt.name(ctx.db());
427 // FIXME: use lang items to determine if it is std type or user defined
428 // E.g. if user happens to define type named `Option`, we would have false positive
429 match name.to_string().as_str() {
430 "Option" => Some(TryKind::Option),
431 "Result" => Some(TryKind::Result { ty }),
432 _ => None,
433 }
434}
435
436#[derive(Debug)]
437enum RetType {
438 Expr(hir::Type),
439 Stmt,
440}
441
442impl RetType {
443 fn is_unit(&self) -> bool {
444 match self {
445 RetType::Expr(ty) => ty.is_unit(),
446 RetType::Stmt => true,
447 }
448 }
449}
450
451/// Semantically same as `ast::Expr`, but preserves identity when using only part of the Block
452#[derive(Debug)]
453enum FunctionBody {
454 Expr(ast::Expr),
455 Span { parent: ast::BlockExpr, text_range: TextRange },
456}
457
458impl FunctionBody {
459 fn from_whole_node(node: SyntaxNode) -> Option<Self> {
460 match node.kind() {
461 PATH_EXPR => None,
462 BREAK_EXPR => ast::BreakExpr::cast(node).and_then(|e| e.expr()).map(Self::Expr),
463 RETURN_EXPR => ast::ReturnExpr::cast(node).and_then(|e| e.expr()).map(Self::Expr),
464 BLOCK_EXPR => ast::BlockExpr::cast(node)
465 .filter(|it| it.is_standalone())
466 .map(Into::into)
467 .map(Self::Expr),
468 _ => ast::Expr::cast(node).map(Self::Expr),
469 }
470 }
471
472 fn from_range(node: SyntaxNode, text_range: TextRange) -> Option<FunctionBody> {
473 let block = ast::BlockExpr::cast(node)?;
474 Some(Self::Span { parent: block, text_range })
475 }
476
477 fn indent_level(&self) -> IndentLevel {
478 match &self {
479 FunctionBody::Expr(expr) => IndentLevel::from_node(expr.syntax()),
480 FunctionBody::Span { parent, .. } => IndentLevel::from_node(parent.syntax()) + 1,
481 }
482 }
483
484 fn tail_expr(&self) -> Option<ast::Expr> {
485 match &self {
486 FunctionBody::Expr(expr) => Some(expr.clone()),
487 FunctionBody::Span { parent, text_range } => {
488 let tail_expr = parent.tail_expr()?;
489 if text_range.contains_range(tail_expr.syntax().text_range()) {
490 Some(tail_expr)
491 } else {
492 None
493 }
494 }
495 }
496 }
497
498 fn descendants(&self) -> impl Iterator<Item = SyntaxNode> + '_ {
499 match self {
500 FunctionBody::Expr(expr) => Either::Right(expr.syntax().descendants()),
501 FunctionBody::Span { parent, text_range } => Either::Left(
502 parent
503 .syntax()
504 .descendants()
505 .filter(move |it| text_range.contains_range(it.text_range())),
506 ),
507 }
508 }
509
510 fn text_range(&self) -> TextRange {
511 match self {
512 FunctionBody::Expr(expr) => expr.syntax().text_range(),
513 FunctionBody::Span { parent: _, text_range } => *text_range,
514 }
515 }
516
517 fn contains_range(&self, range: TextRange) -> bool {
518 self.text_range().contains_range(range)
519 }
520
521 fn preceedes_range(&self, range: TextRange) -> bool {
522 self.text_range().end() <= range.start()
523 }
524
525 fn contains_node(&self, node: &SyntaxNode) -> bool {
526 self.contains_range(node.text_range())
527 }
528}
529
530impl HasTokenAtOffset for FunctionBody {
531 fn token_at_offset(&self, offset: TextSize) -> TokenAtOffset<SyntaxToken> {
532 match self {
533 FunctionBody::Expr(expr) => expr.syntax().token_at_offset(offset),
534 FunctionBody::Span { parent, text_range } => {
535 match parent.syntax().token_at_offset(offset) {
536 TokenAtOffset::None => TokenAtOffset::None,
537 TokenAtOffset::Single(t) => {
538 if text_range.contains_range(t.text_range()) {
539 TokenAtOffset::Single(t)
540 } else {
541 TokenAtOffset::None
542 }
543 }
544 TokenAtOffset::Between(a, b) => {
545 match (
546 text_range.contains_range(a.text_range()),
547 text_range.contains_range(b.text_range()),
548 ) {
549 (true, true) => TokenAtOffset::Between(a, b),
550 (true, false) => TokenAtOffset::Single(a),
551 (false, true) => TokenAtOffset::Single(b),
552 (false, false) => TokenAtOffset::None,
553 }
554 }
555 }
556 }
557 }
558 }
559}
560
561/// node or token's parent
562fn element_to_node(node: SyntaxElement) -> SyntaxNode {
563 match node {
564 syntax::NodeOrToken::Node(n) => n,
565 syntax::NodeOrToken::Token(t) => t.parent(),
566 }
567}
568
569/// Try to guess what user wants to extract
570///
571/// We have basically have two cases:
572/// * We want whole node, like `loop {}`, `2 + 2`, `{ let n = 1; }` exprs.
573/// Then we can use `ast::Expr`
574/// * We want a few statements for a block. E.g.
575/// ```rust,no_run
576/// fn foo() -> i32 {
577/// let m = 1;
578/// $0
579/// let n = 2;
580/// let k = 3;
581/// k + n
582/// $0
583/// }
584/// ```
585///
586fn extraction_target(node: &SyntaxNode, selection_range: TextRange) -> Option<FunctionBody> {
587 // we have selected exactly the expr node
588 // wrap it before anything else
589 if node.text_range() == selection_range {
590 let body = FunctionBody::from_whole_node(node.clone());
591 if body.is_some() {
592 return body;
593 }
594 }
595
596 // we have selected a few statements in a block
597 // so covering_element returns the whole block
598 if node.kind() == BLOCK_EXPR {
599 let body = FunctionBody::from_range(node.clone(), selection_range);
600 if body.is_some() {
601 return body;
602 }
603 }
604
605 // we have selected single statement
606 // `from_whole_node` failed because (let) statement is not and expression
607 // so we try to expand covering_element to parent and repeat the previous
608 if let Some(parent) = node.parent() {
609 if parent.kind() == BLOCK_EXPR {
610 let body = FunctionBody::from_range(parent, selection_range);
611 if body.is_some() {
612 return body;
613 }
614 }
615 }
616
617 // select the closest containing expr (both ifs are used)
618 std::iter::once(node.clone()).chain(node.ancestors()).find_map(FunctionBody::from_whole_node)
619}
620
621/// list local variables that are referenced in `body`
622fn vars_used_in_body(ctx: &AssistContext, body: &FunctionBody) -> Vec<Local> {
623 // FIXME: currently usages inside macros are not found
624 body.descendants()
625 .filter_map(ast::NameRef::cast)
626 .filter_map(|name_ref| NameRefClass::classify(&ctx.sema, &name_ref))
627 .map(|name_kind| name_kind.referenced(ctx.db()))
628 .filter_map(|definition| match definition {
629 Definition::Local(local) => Some(local),
630 _ => None,
631 })
632 .unique()
633 .collect()
634}
635
636/// find `self` param, that was not defined inside `body`
637///
638/// It should skip `self` params from impls inside `body`
639fn self_param_from_usages(
640 ctx: &AssistContext,
641 body: &FunctionBody,
642 vars_used_in_body: &[Local],
643) -> Option<(Local, ast::SelfParam)> {
644 let mut iter = vars_used_in_body
645 .iter()
646 .filter(|var| var.is_self(ctx.db()))
647 .map(|var| (var, var.source(ctx.db())))
648 .filter(|(_, src)| is_defined_before(ctx, body, src))
649 .filter_map(|(&node, src)| match src.value {
650 Either::Right(it) => Some((node, it)),
651 Either::Left(_) => {
652 stdx::never!(false, "Local::is_self returned true, but source is IdentPat");
653 None
654 }
655 });
656
657 let self_param = iter.next();
658 stdx::always!(
659 iter.next().is_none(),
660 "body references two different self params, both defined outside"
661 );
662
663 self_param
664}
665
666/// find variables that should be extracted as params
667///
668/// Computes additional info that affects param type and mutability
669fn extracted_function_params(
670 ctx: &AssistContext,
671 body: &FunctionBody,
672 vars_used_in_body: &[Local],
673) -> Vec<Param> {
674 vars_used_in_body
675 .iter()
676 .filter(|var| !var.is_self(ctx.db()))
677 .map(|node| (node, node.source(ctx.db())))
678 .filter(|(_, src)| is_defined_before(ctx, body, src))
679 .filter_map(|(&node, src)| {
680 if src.value.is_left() {
681 Some(node)
682 } else {
683 stdx::never!(false, "Local::is_self returned false, but source is SelfParam");
684 None
685 }
686 })
687 .map(|var| {
688 let usages = LocalUsages::find(ctx, var);
689 let ty = var.ty(ctx.db());
690 let is_copy = ty.is_copy(ctx.db());
691 Param {
692 var,
693 ty,
694 has_usages_afterwards: has_usages_after_body(&usages, body),
695 has_mut_inside_body: has_exclusive_usages(ctx, &usages, body),
696 is_copy,
697 }
698 })
699 .collect()
700}
701
702fn has_usages_after_body(usages: &LocalUsages, body: &FunctionBody) -> bool {
703 usages.iter().any(|reference| body.preceedes_range(reference.range))
704}
705
706/// checks if relevant var is used with `&mut` access inside body
707fn has_exclusive_usages(ctx: &AssistContext, usages: &LocalUsages, body: &FunctionBody) -> bool {
708 usages
709 .iter()
710 .filter(|reference| body.contains_range(reference.range))
711 .any(|reference| reference_is_exclusive(reference, body, ctx))
712}
713
714/// checks if this reference requires `&mut` access inside body
715fn reference_is_exclusive(
716 reference: &FileReference,
717 body: &FunctionBody,
718 ctx: &AssistContext,
719) -> bool {
720 // we directly modify variable with set: `n = 0`, `n += 1`
721 if reference.access == Some(ReferenceAccess::Write) {
722 return true;
723 }
724
725 // we take `&mut` reference to variable: `&mut v`
726 let path = match path_element_of_reference(body, reference) {
727 Some(path) => path,
728 None => return false,
729 };
730
731 expr_require_exclusive_access(ctx, &path).unwrap_or(false)
732}
733
734/// checks if this expr requires `&mut` access, recurses on field access
735fn expr_require_exclusive_access(ctx: &AssistContext, expr: &ast::Expr) -> Option<bool> {
736 let parent = expr.syntax().parent()?;
737
738 if let Some(bin_expr) = ast::BinExpr::cast(parent.clone()) {
739 if bin_expr.op_kind()?.is_assignment() {
740 return Some(bin_expr.lhs()?.syntax() == expr.syntax());
741 }
742 return Some(false);
743 }
744
745 if let Some(ref_expr) = ast::RefExpr::cast(parent.clone()) {
746 return Some(ref_expr.mut_token().is_some());
747 }
748
749 if let Some(method_call) = ast::MethodCallExpr::cast(parent.clone()) {
750 let func = ctx.sema.resolve_method_call(&method_call)?;
751 let self_param = func.self_param(ctx.db())?;
752 let access = self_param.access(ctx.db());
753
754 return Some(matches!(access, hir::Access::Exclusive));
755 }
756
757 if let Some(field) = ast::FieldExpr::cast(parent) {
758 return expr_require_exclusive_access(ctx, &field.into());
759 }
760
761 Some(false)
762}
763
764/// Container of local varaible usages
765///
766/// Semanticall same as `UsageSearchResult`, but provides more convenient interface
767struct LocalUsages(ide_db::search::UsageSearchResult);
768
769impl LocalUsages {
770 fn find(ctx: &AssistContext, var: Local) -> Self {
771 Self(
772 Definition::Local(var)
773 .usages(&ctx.sema)
774 .in_scope(SearchScope::single_file(ctx.frange.file_id))
775 .all(),
776 )
777 }
778
779 fn iter(&self) -> impl Iterator<Item = &FileReference> + '_ {
780 self.0.iter().flat_map(|(_, rs)| rs.iter())
781 }
782}
783
784trait HasTokenAtOffset {
785 fn token_at_offset(&self, offset: TextSize) -> TokenAtOffset<SyntaxToken>;
786}
787
788impl HasTokenAtOffset for SyntaxNode {
789 fn token_at_offset(&self, offset: TextSize) -> TokenAtOffset<SyntaxToken> {
790 SyntaxNode::token_at_offset(&self, offset)
791 }
792}
793
794/// find relevant `ast::PathExpr` for reference
795///
796/// # Preconditions
797///
798/// `node` must cover `reference`, that is `node.text_range().contains_range(reference.range)`
799fn path_element_of_reference(
800 node: &dyn HasTokenAtOffset,
801 reference: &FileReference,
802) -> Option<ast::Expr> {
803 let token = node.token_at_offset(reference.range.start()).right_biased().or_else(|| {
804 stdx::never!(false, "cannot find token at variable usage: {:?}", reference);
805 None
806 })?;
807 let path = token.ancestors().find_map(ast::Expr::cast).or_else(|| {
808 stdx::never!(false, "cannot find path parent of variable usage: {:?}", token);
809 None
810 })?;
811 stdx::always!(matches!(path, ast::Expr::PathExpr(_)));
812 Some(path)
813}
814
815/// list local variables defined inside `body`
816fn vars_defined_in_body(body: &FunctionBody, ctx: &AssistContext) -> Vec<Local> {
817 // FIXME: this doesn't work well with macros
818 // see https://github.com/rust-analyzer/rust-analyzer/pull/7535#discussion_r570048550
819 body.descendants()
820 .filter_map(ast::IdentPat::cast)
821 .filter_map(|let_stmt| ctx.sema.to_def(&let_stmt))
822 .unique()
823 .collect()
824}
825
826/// list local variables defined inside `body` that should be returned from extracted function
827fn vars_defined_in_body_and_outlive(ctx: &AssistContext, body: &FunctionBody) -> Vec<Local> {
828 let mut vars_defined_in_body = vars_defined_in_body(&body, ctx);
829 vars_defined_in_body.retain(|var| var_outlives_body(ctx, body, var));
830 vars_defined_in_body
831}
832
833/// checks if the relevant local was defined before(outside of) body
834fn is_defined_before(
835 ctx: &AssistContext,
836 body: &FunctionBody,
837 src: &hir::InFile<Either<ast::IdentPat, ast::SelfParam>>,
838) -> bool {
839 src.file_id.original_file(ctx.db()) == ctx.frange.file_id
840 && !body.contains_node(&either_syntax(&src.value))
841}
842
843fn either_syntax(value: &Either<ast::IdentPat, ast::SelfParam>) -> &SyntaxNode {
844 match value {
845 Either::Left(pat) => pat.syntax(),
846 Either::Right(it) => it.syntax(),
847 }
848}
849
850/// checks if local variable is used after(outside of) body
851fn var_outlives_body(ctx: &AssistContext, body: &FunctionBody, var: &Local) -> bool {
852 let usages = LocalUsages::find(ctx, *var);
853 let has_usages = usages.iter().any(|reference| body.preceedes_range(reference.range));
854 has_usages
855}
856
857fn body_return_ty(ctx: &AssistContext, body: &FunctionBody) -> Option<RetType> {
858 match body.tail_expr() {
859 Some(expr) => {
860 let ty = ctx.sema.type_of_expr(&expr)?;
861 Some(RetType::Expr(ty))
862 }
863 None => Some(RetType::Stmt),
864 }
865}
866/// Where to put extracted function definition
867#[derive(Debug)]
868enum Anchor {
869 /// Extract free function and put right after current top-level function
870 Freestanding,
871 /// Extract method and put right after current function in the impl-block
872 Method,
873}
874
875/// find where to put extracted function definition
876///
877/// Function should be put right after returned node
878fn scope_for_fn_insertion(body: &FunctionBody, anchor: Anchor) -> Option<SyntaxNode> {
879 match body {
880 FunctionBody::Expr(e) => scope_for_fn_insertion_node(e.syntax(), anchor),
881 FunctionBody::Span { parent, .. } => scope_for_fn_insertion_node(parent.syntax(), anchor),
882 }
883}
884
885fn scope_for_fn_insertion_node(node: &SyntaxNode, anchor: Anchor) -> Option<SyntaxNode> {
886 let mut ancestors = node.ancestors().peekable();
887 let mut last_ancestor = None;
888 while let Some(next_ancestor) = ancestors.next() {
889 match next_ancestor.kind() {
890 SyntaxKind::SOURCE_FILE => break,
891 SyntaxKind::ITEM_LIST => {
892 if !matches!(anchor, Anchor::Freestanding) {
893 continue;
894 }
895 if ancestors.peek().map(SyntaxNode::kind) == Some(SyntaxKind::MODULE) {
896 break;
897 }
898 }
899 SyntaxKind::ASSOC_ITEM_LIST => {
900 if !matches!(anchor, Anchor::Method) {
901 continue;
902 }
903 if ancestors.peek().map(SyntaxNode::kind) == Some(SyntaxKind::IMPL) {
904 break;
905 }
906 }
907 _ => {}
908 }
909 last_ancestor = Some(next_ancestor);
910 }
911 last_ancestor
912}
913
914fn format_replacement(ctx: &AssistContext, fun: &Function, indent: IndentLevel) -> String {
915 let ret_ty = fun.return_type(ctx);
916
917 let args = fun.params.iter().map(|param| param.to_arg(ctx));
918 let args = make::arg_list(args);
919 let call_expr = if fun.self_param.is_some() {
920 let self_arg = make::expr_path(make_path_from_text("self"));
921 make::expr_method_call(self_arg, &fun.name, args)
922 } else {
923 let func = make::expr_path(make_path_from_text(&fun.name));
924 make::expr_call(func, args)
925 };
926
927 let handler = FlowHandler::from_ret_ty(fun, &ret_ty);
928
929 let expr = handler.make_call_expr(call_expr).indent(indent);
930
931 let mut buf = String::new();
932 match fun.vars_defined_in_body_and_outlive.as_slice() {
933 [] => {}
934 [var] => format_to!(buf, "let {} = ", var.name(ctx.db()).unwrap()),
935 [v0, vs @ ..] => {
936 buf.push_str("let (");
937 format_to!(buf, "{}", v0.name(ctx.db()).unwrap());
938 for var in vs {
939 format_to!(buf, ", {}", var.name(ctx.db()).unwrap());
940 }
941 buf.push_str(") = ");
942 }
943 }
944 format_to!(buf, "{}", expr);
945 if fun.ret_ty.is_unit()
946 && (!fun.vars_defined_in_body_and_outlive.is_empty() || !expr.is_block_like())
947 {
948 buf.push(';');
949 }
950 buf
951}
952
953enum FlowHandler {
954 None,
955 Try { kind: TryKind },
956 If { action: FlowKind },
957 IfOption { action: FlowKind },
958 MatchOption { none: FlowKind },
959 MatchResult { err: FlowKind },
960}
961
962impl FlowHandler {
963 fn from_ret_ty(fun: &Function, ret_ty: &FunType) -> FlowHandler {
964 match &fun.control_flow.kind {
965 None => FlowHandler::None,
966 Some(flow_kind) => {
967 let action = flow_kind.clone();
968 if *ret_ty == FunType::Unit {
969 match flow_kind {
970 FlowKind::Return | FlowKind::Break | FlowKind::Continue => {
971 FlowHandler::If { action }
972 }
973 FlowKind::ReturnValue(_) | FlowKind::BreakValue(_) => {
974 FlowHandler::IfOption { action }
975 }
976 FlowKind::Try { kind } | FlowKind::TryReturn { kind, .. } => {
977 FlowHandler::Try { kind: kind.clone() }
978 }
979 }
980 } else {
981 match flow_kind {
982 FlowKind::Return | FlowKind::Break | FlowKind::Continue => {
983 FlowHandler::MatchOption { none: action }
984 }
985 FlowKind::ReturnValue(_) | FlowKind::BreakValue(_) => {
986 FlowHandler::MatchResult { err: action }
987 }
988 FlowKind::Try { kind } | FlowKind::TryReturn { kind, .. } => {
989 FlowHandler::Try { kind: kind.clone() }
990 }
991 }
992 }
993 }
994 }
995 }
996
997 fn make_call_expr(&self, call_expr: ast::Expr) -> ast::Expr {
998 match self {
999 FlowHandler::None => call_expr,
1000 FlowHandler::Try { kind: _ } => make::expr_try(call_expr),
1001 FlowHandler::If { action } => {
1002 let action = action.make_result_handler(None);
1003 let stmt = make::expr_stmt(action);
1004 let block = make::block_expr(iter::once(stmt.into()), None);
1005 let condition = make::condition(call_expr, None);
1006 make::expr_if(condition, block, None)
1007 }
1008 FlowHandler::IfOption { action } => {
1009 let path = make_path_from_text("Some");
1010 let value_pat = make::ident_pat(make::name("value"));
1011 let pattern = make::tuple_struct_pat(path, iter::once(value_pat.into()));
1012 let cond = make::condition(call_expr, Some(pattern.into()));
1013 let value = make::expr_path(make_path_from_text("value"));
1014 let action_expr = action.make_result_handler(Some(value));
1015 let action_stmt = make::expr_stmt(action_expr);
1016 let then = make::block_expr(iter::once(action_stmt.into()), None);
1017 make::expr_if(cond, then, None)
1018 }
1019 FlowHandler::MatchOption { none } => {
1020 let some_name = "value";
1021
1022 let some_arm = {
1023 let path = make_path_from_text("Some");
1024 let value_pat = make::ident_pat(make::name(some_name));
1025 let pat = make::tuple_struct_pat(path, iter::once(value_pat.into()));
1026 let value = make::expr_path(make_path_from_text(some_name));
1027 make::match_arm(iter::once(pat.into()), value)
1028 };
1029 let none_arm = {
1030 let path = make_path_from_text("None");
1031 let pat = make::path_pat(path);
1032 make::match_arm(iter::once(pat), none.make_result_handler(None))
1033 };
1034 let arms = make::match_arm_list(vec![some_arm, none_arm]);
1035 make::expr_match(call_expr, arms)
1036 }
1037 FlowHandler::MatchResult { err } => {
1038 let ok_name = "value";
1039 let err_name = "value";
1040
1041 let ok_arm = {
1042 let path = make_path_from_text("Ok");
1043 let value_pat = make::ident_pat(make::name(ok_name));
1044 let pat = make::tuple_struct_pat(path, iter::once(value_pat.into()));
1045 let value = make::expr_path(make_path_from_text(ok_name));
1046 make::match_arm(iter::once(pat.into()), value)
1047 };
1048 let err_arm = {
1049 let path = make_path_from_text("Err");
1050 let value_pat = make::ident_pat(make::name(err_name));
1051 let pat = make::tuple_struct_pat(path, iter::once(value_pat.into()));
1052 let value = make::expr_path(make_path_from_text(err_name));
1053 make::match_arm(iter::once(pat.into()), err.make_result_handler(Some(value)))
1054 };
1055 let arms = make::match_arm_list(vec![ok_arm, err_arm]);
1056 make::expr_match(call_expr, arms)
1057 }
1058 }
1059 }
1060}
1061
1062fn make_path_from_text(text: &str) -> ast::Path {
1063 make::path_unqualified(make::path_segment(make::name_ref(text)))
1064}
1065
1066fn path_expr_from_local(ctx: &AssistContext, var: Local) -> ast::Expr {
1067 let name = var.name(ctx.db()).unwrap().to_string();
1068 make::expr_path(make_path_from_text(&name))
1069}
1070
1071fn format_function(
1072 ctx: &AssistContext,
1073 module: hir::Module,
1074 fun: &Function,
1075 old_indent: IndentLevel,
1076 new_indent: IndentLevel,
1077) -> String {
1078 let mut fn_def = String::new();
1079 let params = make_param_list(ctx, module, fun);
1080 let ret_ty = make_ret_ty(ctx, module, fun);
1081 let body = make_body(ctx, old_indent, new_indent, fun);
1082 format_to!(fn_def, "\n\n{}fn $0{}{}", new_indent, fun.name, params);
1083 if let Some(ret_ty) = ret_ty {
1084 format_to!(fn_def, " {}", ret_ty);
1085 }
1086 format_to!(fn_def, " {}", body);
1087
1088 fn_def
1089}
1090
1091fn make_param_list(ctx: &AssistContext, module: hir::Module, fun: &Function) -> ast::ParamList {
1092 let self_param = fun.self_param.clone();
1093 let params = fun.params.iter().map(|param| param.to_param(ctx, module));
1094 make::param_list(self_param, params)
1095}
1096
1097impl FunType {
1098 fn make_ty(&self, ctx: &AssistContext, module: hir::Module) -> ast::Type {
1099 match self {
1100 FunType::Unit => make::ty_unit(),
1101 FunType::Single(ty) => make_ty(ty, ctx, module),
1102 FunType::Tuple(types) => match types.as_slice() {
1103 [] => {
1104 stdx::never!("tuple type with 0 elements");
1105 make::ty_unit()
1106 }
1107 [ty] => {
1108 stdx::never!("tuple type with 1 element");
1109 make_ty(ty, ctx, module)
1110 }
1111 types => {
1112 let types = types.iter().map(|ty| make_ty(ty, ctx, module));
1113 make::ty_tuple(types)
1114 }
1115 },
1116 }
1117 }
1118}
1119
1120fn make_ret_ty(ctx: &AssistContext, module: hir::Module, fun: &Function) -> Option<ast::RetType> {
1121 let fun_ty = fun.return_type(ctx);
1122 let handler = FlowHandler::from_ret_ty(fun, &fun_ty);
1123 let ret_ty = match &handler {
1124 FlowHandler::None => {
1125 if matches!(fun_ty, FunType::Unit) {
1126 return None;
1127 }
1128 fun_ty.make_ty(ctx, module)
1129 }
1130 FlowHandler::Try { kind: TryKind::Option } => {
1131 make::ty_generic(make::name_ref("Option"), iter::once(fun_ty.make_ty(ctx, module)))
1132 }
1133 FlowHandler::Try { kind: TryKind::Result { ty: parent_ret_ty } } => {
1134 let handler_ty = parent_ret_ty
1135 .type_parameters()
1136 .nth(1)
1137 .map(|ty| make_ty(&ty, ctx, module))
1138 .unwrap_or_else(make::ty_unit);
1139 make::ty_generic(
1140 make::name_ref("Result"),
1141 vec![fun_ty.make_ty(ctx, module), handler_ty],
1142 )
1143 }
1144 FlowHandler::If { .. } => make::ty("bool"),
1145 FlowHandler::IfOption { action } => {
1146 let handler_ty = action
1147 .expr_ty(ctx)
1148 .map(|ty| make_ty(&ty, ctx, module))
1149 .unwrap_or_else(make::ty_unit);
1150 make::ty_generic(make::name_ref("Option"), iter::once(handler_ty))
1151 }
1152 FlowHandler::MatchOption { .. } => {
1153 make::ty_generic(make::name_ref("Option"), iter::once(fun_ty.make_ty(ctx, module)))
1154 }
1155 FlowHandler::MatchResult { err } => {
1156 let handler_ty =
1157 err.expr_ty(ctx).map(|ty| make_ty(&ty, ctx, module)).unwrap_or_else(make::ty_unit);
1158 make::ty_generic(
1159 make::name_ref("Result"),
1160 vec![fun_ty.make_ty(ctx, module), handler_ty],
1161 )
1162 }
1163 };
1164 Some(make::ret_type(ret_ty))
1165}
1166
1167fn make_body(
1168 ctx: &AssistContext,
1169 old_indent: IndentLevel,
1170 new_indent: IndentLevel,
1171 fun: &Function,
1172) -> ast::BlockExpr {
1173 let ret_ty = fun.return_type(ctx);
1174 let handler = FlowHandler::from_ret_ty(fun, &ret_ty);
1175 let block = match &fun.body {
1176 FunctionBody::Expr(expr) => {
1177 let expr = rewrite_body_segment(ctx, &fun.params, &handler, expr.syntax());
1178 let expr = ast::Expr::cast(expr).unwrap();
1179 let expr = expr.dedent(old_indent).indent(IndentLevel(1));
1180
1181 make::block_expr(Vec::new(), Some(expr))
1182 }
1183 FunctionBody::Span { parent, text_range } => {
1184 let mut elements: Vec<_> = parent
1185 .syntax()
1186 .children()
1187 .filter(|it| text_range.contains_range(it.text_range()))
1188 .map(|it| rewrite_body_segment(ctx, &fun.params, &handler, &it))
1189 .collect();
1190
1191 let mut tail_expr = match elements.pop() {
1192 Some(node) => ast::Expr::cast(node.clone()).or_else(|| {
1193 elements.push(node);
1194 None
1195 }),
1196 None => None,
1197 };
1198
1199 if tail_expr.is_none() {
1200 match fun.vars_defined_in_body_and_outlive.as_slice() {
1201 [] => {}
1202 [var] => {
1203 tail_expr = Some(path_expr_from_local(ctx, *var));
1204 }
1205 vars => {
1206 let exprs = vars.iter().map(|var| path_expr_from_local(ctx, *var));
1207 let expr = make::expr_tuple(exprs);
1208 tail_expr = Some(expr);
1209 }
1210 }
1211 }
1212
1213 let elements = elements.into_iter().filter_map(|node| match ast::Stmt::cast(node) {
1214 Some(stmt) => Some(stmt),
1215 None => {
1216 stdx::never!("block contains non-statement");
1217 None
1218 }
1219 });
1220
1221 let body_indent = IndentLevel(1);
1222 let elements = elements.map(|stmt| stmt.dedent(old_indent).indent(body_indent));
1223 let tail_expr = tail_expr.map(|expr| expr.dedent(old_indent).indent(body_indent));
1224
1225 make::block_expr(elements, tail_expr)
1226 }
1227 };
1228
1229 let block = match &handler {
1230 FlowHandler::None => block,
1231 FlowHandler::Try { kind } => {
1232 let block = with_default_tail_expr(block, make::expr_unit());
1233 map_tail_expr(block, |tail_expr| {
1234 let constructor = match kind {
1235 TryKind::Option => "Some",
1236 TryKind::Result { .. } => "Ok",
1237 };
1238 let func = make::expr_path(make_path_from_text(constructor));
1239 let args = make::arg_list(iter::once(tail_expr));
1240 make::expr_call(func, args)
1241 })
1242 }
1243 FlowHandler::If { .. } => {
1244 let lit_false = ast::Literal::cast(make::tokens::literal("false").parent()).unwrap();
1245 with_tail_expr(block, lit_false.into())
1246 }
1247 FlowHandler::IfOption { .. } => {
1248 let none = make::expr_path(make_path_from_text("None"));
1249 with_tail_expr(block, none)
1250 }
1251 FlowHandler::MatchOption { .. } => map_tail_expr(block, |tail_expr| {
1252 let some = make::expr_path(make_path_from_text("Some"));
1253 let args = make::arg_list(iter::once(tail_expr));
1254 make::expr_call(some, args)
1255 }),
1256 FlowHandler::MatchResult { .. } => map_tail_expr(block, |tail_expr| {
1257 let ok = make::expr_path(make_path_from_text("Ok"));
1258 let args = make::arg_list(iter::once(tail_expr));
1259 make::expr_call(ok, args)
1260 }),
1261 };
1262
1263 block.indent(new_indent)
1264}
1265
1266fn map_tail_expr(block: ast::BlockExpr, f: impl FnOnce(ast::Expr) -> ast::Expr) -> ast::BlockExpr {
1267 let tail_expr = match block.tail_expr() {
1268 Some(tail_expr) => tail_expr,
1269 None => return block,
1270 };
1271 make::block_expr(block.statements(), Some(f(tail_expr)))
1272}
1273
1274fn with_default_tail_expr(block: ast::BlockExpr, tail_expr: ast::Expr) -> ast::BlockExpr {
1275 match block.tail_expr() {
1276 Some(_) => block,
1277 None => make::block_expr(block.statements(), Some(tail_expr)),
1278 }
1279}
1280
1281fn with_tail_expr(block: ast::BlockExpr, tail_expr: ast::Expr) -> ast::BlockExpr {
1282 let stmt_tail = block.tail_expr().map(|expr| make::expr_stmt(expr).into());
1283 let stmts = block.statements().chain(stmt_tail);
1284 make::block_expr(stmts, Some(tail_expr))
1285}
1286
1287fn format_type(ty: &hir::Type, ctx: &AssistContext, module: hir::Module) -> String {
1288 ty.display_source_code(ctx.db(), module.into()).ok().unwrap_or_else(|| "()".to_string())
1289}
1290
1291fn make_ty(ty: &hir::Type, ctx: &AssistContext, module: hir::Module) -> ast::Type {
1292 let ty_str = format_type(ty, ctx, module);
1293 make::ty(&ty_str)
1294}
1295
1296fn rewrite_body_segment(
1297 ctx: &AssistContext,
1298 params: &[Param],
1299 handler: &FlowHandler,
1300 syntax: &SyntaxNode,
1301) -> SyntaxNode {
1302 let syntax = fix_param_usages(ctx, params, syntax);
1303 update_external_control_flow(handler, &syntax)
1304}
1305
1306/// change all usages to account for added `&`/`&mut` for some params
1307fn fix_param_usages(ctx: &AssistContext, params: &[Param], syntax: &SyntaxNode) -> SyntaxNode {
1308 let mut rewriter = SyntaxRewriter::default();
1309 for param in params {
1310 if !param.kind().is_ref() {
1311 continue;
1312 }
1313
1314 let usages = LocalUsages::find(ctx, param.var);
1315 let usages = usages
1316 .iter()
1317 .filter(|reference| syntax.text_range().contains_range(reference.range))
1318 .filter_map(|reference| path_element_of_reference(syntax, reference));
1319 for path in usages {
1320 match path.syntax().ancestors().skip(1).find_map(ast::Expr::cast) {
1321 Some(ast::Expr::MethodCallExpr(_)) | Some(ast::Expr::FieldExpr(_)) => {
1322 // do nothing
1323 }
1324 Some(ast::Expr::RefExpr(node))
1325 if param.kind() == ParamKind::MutRef && node.mut_token().is_some() =>
1326 {
1327 rewriter.replace_ast(&node.clone().into(), &node.expr().unwrap());
1328 }
1329 Some(ast::Expr::RefExpr(node))
1330 if param.kind() == ParamKind::SharedRef && node.mut_token().is_none() =>
1331 {
1332 rewriter.replace_ast(&node.clone().into(), &node.expr().unwrap());
1333 }
1334 Some(_) | None => {
1335 rewriter.replace_ast(&path, &make::expr_prefix(T![*], path.clone()));
1336 }
1337 };
1338 }
1339 }
1340
1341 rewriter.rewrite(syntax)
1342}
1343
1344fn update_external_control_flow(handler: &FlowHandler, syntax: &SyntaxNode) -> SyntaxNode {
1345 let mut rewriter = SyntaxRewriter::default();
1346
1347 let mut nested_loop = None;
1348 let mut nested_scope = None;
1349 for event in syntax.preorder() {
1350 let node = match event {
1351 WalkEvent::Enter(e) => {
1352 match e.kind() {
1353 SyntaxKind::LOOP_EXPR | SyntaxKind::WHILE_EXPR | SyntaxKind::FOR_EXPR => {
1354 if nested_loop.is_none() {
1355 nested_loop = Some(e.clone());
1356 }
1357 }
1358 SyntaxKind::FN
1359 | SyntaxKind::CONST
1360 | SyntaxKind::STATIC
1361 | SyntaxKind::IMPL
1362 | SyntaxKind::MODULE => {
1363 if nested_scope.is_none() {
1364 nested_scope = Some(e.clone());
1365 }
1366 }
1367 _ => {}
1368 }
1369 e
1370 }
1371 WalkEvent::Leave(e) => {
1372 if nested_loop.as_ref() == Some(&e) {
1373 nested_loop = None;
1374 }
1375 if nested_scope.as_ref() == Some(&e) {
1376 nested_scope = None;
1377 }
1378 continue;
1379 }
1380 };
1381 if nested_scope.is_some() {
1382 continue;
1383 }
1384 let expr = match ast::Expr::cast(node) {
1385 Some(e) => e,
1386 None => continue,
1387 };
1388 match expr {
1389 ast::Expr::ReturnExpr(return_expr) if nested_scope.is_none() => {
1390 let expr = return_expr.expr();
1391 if let Some(replacement) = make_rewritten_flow(handler, expr) {
1392 rewriter.replace_ast(&return_expr.into(), &replacement);
1393 }
1394 }
1395 ast::Expr::BreakExpr(break_expr) if nested_loop.is_none() => {
1396 let expr = break_expr.expr();
1397 if let Some(replacement) = make_rewritten_flow(handler, expr) {
1398 rewriter.replace_ast(&break_expr.into(), &replacement);
1399 }
1400 }
1401 ast::Expr::ContinueExpr(continue_expr) if nested_loop.is_none() => {
1402 if let Some(replacement) = make_rewritten_flow(handler, None) {
1403 rewriter.replace_ast(&continue_expr.into(), &replacement);
1404 }
1405 }
1406 _ => {
1407 // do nothing
1408 }
1409 }
1410 }
1411
1412 rewriter.rewrite(syntax)
1413}
1414
1415fn make_rewritten_flow(handler: &FlowHandler, arg_expr: Option<ast::Expr>) -> Option<ast::Expr> {
1416 let value = match handler {
1417 FlowHandler::None | FlowHandler::Try { .. } => return None,
1418 FlowHandler::If { .. } => {
1419 ast::Literal::cast(make::tokens::literal("true").parent()).unwrap().into()
1420 }
1421 FlowHandler::IfOption { .. } => {
1422 let expr = arg_expr.unwrap_or_else(|| make::expr_tuple(Vec::new()));
1423 let args = make::arg_list(iter::once(expr));
1424 make::expr_call(make::expr_path(make_path_from_text("Some")), args)
1425 }
1426 FlowHandler::MatchOption { .. } => make::expr_path(make_path_from_text("None")),
1427 FlowHandler::MatchResult { .. } => {
1428 let expr = arg_expr.unwrap_or_else(|| make::expr_tuple(Vec::new()));
1429 let args = make::arg_list(iter::once(expr));
1430 make::expr_call(make::expr_path(make_path_from_text("Err")), args)
1431 }
1432 };
1433 Some(make::expr_return(Some(value)))
1434}
1435
1436#[cfg(test)]
1437mod tests {
1438 use crate::tests::{check_assist, check_assist_not_applicable};
1439
1440 use super::*;
1441
1442 #[test]
1443 fn no_args_from_binary_expr() {
1444 check_assist(
1445 extract_function,
1446 r#"
1447fn foo() {
1448 foo($01 + 1$0);
1449}"#,
1450 r#"
1451fn foo() {
1452 foo(fun_name());
1453}
1454
1455fn $0fun_name() -> i32 {
1456 1 + 1
1457}"#,
1458 );
1459 }
1460
1461 #[test]
1462 fn no_args_from_binary_expr_in_module() {
1463 check_assist(
1464 extract_function,
1465 r#"
1466mod bar {
1467 fn foo() {
1468 foo($01 + 1$0);
1469 }
1470}"#,
1471 r#"
1472mod bar {
1473 fn foo() {
1474 foo(fun_name());
1475 }
1476
1477 fn $0fun_name() -> i32 {
1478 1 + 1
1479 }
1480}"#,
1481 );
1482 }
1483
1484 #[test]
1485 fn no_args_from_binary_expr_indented() {
1486 check_assist(
1487 extract_function,
1488 r#"
1489fn foo() {
1490 $0{ 1 + 1 }$0;
1491}"#,
1492 r#"
1493fn foo() {
1494 fun_name();
1495}
1496
1497fn $0fun_name() -> i32 {
1498 { 1 + 1 }
1499}"#,
1500 );
1501 }
1502
1503 #[test]
1504 fn no_args_from_stmt_with_last_expr() {
1505 check_assist(
1506 extract_function,
1507 r#"
1508fn foo() -> i32 {
1509 let k = 1;
1510 $0let m = 1;
1511 m + 1$0
1512}"#,
1513 r#"
1514fn foo() -> i32 {
1515 let k = 1;
1516 fun_name()
1517}
1518
1519fn $0fun_name() -> i32 {
1520 let m = 1;
1521 m + 1
1522}"#,
1523 );
1524 }
1525
1526 #[test]
1527 fn no_args_from_stmt_unit() {
1528 check_assist(
1529 extract_function,
1530 r#"
1531fn foo() {
1532 let k = 3;
1533 $0let m = 1;
1534 let n = m + 1;$0
1535 let g = 5;
1536}"#,
1537 r#"
1538fn foo() {
1539 let k = 3;
1540 fun_name();
1541 let g = 5;
1542}
1543
1544fn $0fun_name() {
1545 let m = 1;
1546 let n = m + 1;
1547}"#,
1548 );
1549 }
1550
1551 #[test]
1552 fn no_args_if() {
1553 check_assist(
1554 extract_function,
1555 r#"
1556fn foo() {
1557 $0if true { }$0
1558}"#,
1559 r#"
1560fn foo() {
1561 fun_name();
1562}
1563
1564fn $0fun_name() {
1565 if true { }
1566}"#,
1567 );
1568 }
1569
1570 #[test]
1571 fn no_args_if_else() {
1572 check_assist(
1573 extract_function,
1574 r#"
1575fn foo() -> i32 {
1576 $0if true { 1 } else { 2 }$0
1577}"#,
1578 r#"
1579fn foo() -> i32 {
1580 fun_name()
1581}
1582
1583fn $0fun_name() -> i32 {
1584 if true { 1 } else { 2 }
1585}"#,
1586 );
1587 }
1588
1589 #[test]
1590 fn no_args_if_let_else() {
1591 check_assist(
1592 extract_function,
1593 r#"
1594fn foo() -> i32 {
1595 $0if let true = false { 1 } else { 2 }$0
1596}"#,
1597 r#"
1598fn foo() -> i32 {
1599 fun_name()
1600}
1601
1602fn $0fun_name() -> i32 {
1603 if let true = false { 1 } else { 2 }
1604}"#,
1605 );
1606 }
1607
1608 #[test]
1609 fn no_args_match() {
1610 check_assist(
1611 extract_function,
1612 r#"
1613fn foo() -> i32 {
1614 $0match true {
1615 true => 1,
1616 false => 2,
1617 }$0
1618}"#,
1619 r#"
1620fn foo() -> i32 {
1621 fun_name()
1622}
1623
1624fn $0fun_name() -> i32 {
1625 match true {
1626 true => 1,
1627 false => 2,
1628 }
1629}"#,
1630 );
1631 }
1632
1633 #[test]
1634 fn no_args_while() {
1635 check_assist(
1636 extract_function,
1637 r#"
1638fn foo() {
1639 $0while true { }$0
1640}"#,
1641 r#"
1642fn foo() {
1643 fun_name();
1644}
1645
1646fn $0fun_name() {
1647 while true { }
1648}"#,
1649 );
1650 }
1651
1652 #[test]
1653 fn no_args_for() {
1654 check_assist(
1655 extract_function,
1656 r#"
1657fn foo() {
1658 $0for v in &[0, 1] { }$0
1659}"#,
1660 r#"
1661fn foo() {
1662 fun_name();
1663}
1664
1665fn $0fun_name() {
1666 for v in &[0, 1] { }
1667}"#,
1668 );
1669 }
1670
1671 #[test]
1672 fn no_args_from_loop_unit() {
1673 check_assist(
1674 extract_function,
1675 r#"
1676fn foo() {
1677 $0loop {
1678 let m = 1;
1679 }$0
1680}"#,
1681 r#"
1682fn foo() {
1683 fun_name()
1684}
1685
1686fn $0fun_name() -> ! {
1687 loop {
1688 let m = 1;
1689 }
1690}"#,
1691 );
1692 }
1693
1694 #[test]
1695 fn no_args_from_loop_with_return() {
1696 check_assist(
1697 extract_function,
1698 r#"
1699fn foo() {
1700 let v = $0loop {
1701 let m = 1;
1702 break m;
1703 }$0;
1704}"#,
1705 r#"
1706fn foo() {
1707 let v = fun_name();
1708}
1709
1710fn $0fun_name() -> i32 {
1711 loop {
1712 let m = 1;
1713 break m;
1714 }
1715}"#,
1716 );
1717 }
1718
1719 #[test]
1720 fn no_args_from_match() {
1721 check_assist(
1722 extract_function,
1723 r#"
1724fn foo() {
1725 let v: i32 = $0match Some(1) {
1726 Some(x) => x,
1727 None => 0,
1728 }$0;
1729}"#,
1730 r#"
1731fn foo() {
1732 let v: i32 = fun_name();
1733}
1734
1735fn $0fun_name() -> i32 {
1736 match Some(1) {
1737 Some(x) => x,
1738 None => 0,
1739 }
1740}"#,
1741 );
1742 }
1743
1744 #[test]
1745 fn argument_form_expr() {
1746 check_assist(
1747 extract_function,
1748 r"
1749fn foo() -> u32 {
1750 let n = 2;
1751 $0n+2$0
1752}",
1753 r"
1754fn foo() -> u32 {
1755 let n = 2;
1756 fun_name(n)
1757}
1758
1759fn $0fun_name(n: u32) -> u32 {
1760 n+2
1761}",
1762 )
1763 }
1764
1765 #[test]
1766 fn argument_used_twice_form_expr() {
1767 check_assist(
1768 extract_function,
1769 r"
1770fn foo() -> u32 {
1771 let n = 2;
1772 $0n+n$0
1773}",
1774 r"
1775fn foo() -> u32 {
1776 let n = 2;
1777 fun_name(n)
1778}
1779
1780fn $0fun_name(n: u32) -> u32 {
1781 n+n
1782}",
1783 )
1784 }
1785
1786 #[test]
1787 fn two_arguments_form_expr() {
1788 check_assist(
1789 extract_function,
1790 r"
1791fn foo() -> u32 {
1792 let n = 2;
1793 let m = 3;
1794 $0n+n*m$0
1795}",
1796 r"
1797fn foo() -> u32 {
1798 let n = 2;
1799 let m = 3;
1800 fun_name(n, m)
1801}
1802
1803fn $0fun_name(n: u32, m: u32) -> u32 {
1804 n+n*m
1805}",
1806 )
1807 }
1808
1809 #[test]
1810 fn argument_and_locals() {
1811 check_assist(
1812 extract_function,
1813 r"
1814fn foo() -> u32 {
1815 let n = 2;
1816 $0let m = 1;
1817 n + m$0
1818}",
1819 r"
1820fn foo() -> u32 {
1821 let n = 2;
1822 fun_name(n)
1823}
1824
1825fn $0fun_name(n: u32) -> u32 {
1826 let m = 1;
1827 n + m
1828}",
1829 )
1830 }
1831
1832 #[test]
1833 fn in_comment_is_not_applicable() {
1834 mark::check!(extract_function_in_comment_is_not_applicable);
1835 check_assist_not_applicable(extract_function, r"fn main() { 1 + /* $0comment$0 */ 1; }");
1836 }
1837
1838 #[test]
1839 fn part_of_expr_stmt() {
1840 check_assist(
1841 extract_function,
1842 "
1843fn foo() {
1844 $01$0 + 1;
1845}",
1846 "
1847fn foo() {
1848 fun_name() + 1;
1849}
1850
1851fn $0fun_name() -> i32 {
1852 1
1853}",
1854 );
1855 }
1856
1857 #[test]
1858 fn function_expr() {
1859 check_assist(
1860 extract_function,
1861 r#"
1862fn foo() {
1863 $0bar(1 + 1)$0
1864}"#,
1865 r#"
1866fn foo() {
1867 fun_name();
1868}
1869
1870fn $0fun_name() {
1871 bar(1 + 1)
1872}"#,
1873 )
1874 }
1875
1876 #[test]
1877 fn extract_from_nested() {
1878 check_assist(
1879 extract_function,
1880 r"
1881fn main() {
1882 let x = true;
1883 let tuple = match x {
1884 true => ($02 + 2$0, true)
1885 _ => (0, false)
1886 };
1887}",
1888 r"
1889fn main() {
1890 let x = true;
1891 let tuple = match x {
1892 true => (fun_name(), true)
1893 _ => (0, false)
1894 };
1895}
1896
1897fn $0fun_name() -> i32 {
1898 2 + 2
1899}",
1900 );
1901 }
1902
1903 #[test]
1904 fn param_from_closure() {
1905 check_assist(
1906 extract_function,
1907 r"
1908fn main() {
1909 let lambda = |x: u32| $0x * 2$0;
1910}",
1911 r"
1912fn main() {
1913 let lambda = |x: u32| fun_name(x);
1914}
1915
1916fn $0fun_name(x: u32) -> u32 {
1917 x * 2
1918}",
1919 );
1920 }
1921
1922 #[test]
1923 fn extract_return_stmt() {
1924 check_assist(
1925 extract_function,
1926 r"
1927fn foo() -> u32 {
1928 $0return 2 + 2$0;
1929}",
1930 r"
1931fn foo() -> u32 {
1932 return fun_name();
1933}
1934
1935fn $0fun_name() -> u32 {
1936 2 + 2
1937}",
1938 );
1939 }
1940
1941 #[test]
1942 fn does_not_add_extra_whitespace() {
1943 check_assist(
1944 extract_function,
1945 r"
1946fn foo() -> u32 {
1947
1948
1949 $0return 2 + 2$0;
1950}",
1951 r"
1952fn foo() -> u32 {
1953
1954
1955 return fun_name();
1956}
1957
1958fn $0fun_name() -> u32 {
1959 2 + 2
1960}",
1961 );
1962 }
1963
1964 #[test]
1965 fn break_stmt() {
1966 check_assist(
1967 extract_function,
1968 r"
1969fn main() {
1970 let result = loop {
1971 $0break 2 + 2$0;
1972 };
1973}",
1974 r"
1975fn main() {
1976 let result = loop {
1977 break fun_name();
1978 };
1979}
1980
1981fn $0fun_name() -> i32 {
1982 2 + 2
1983}",
1984 );
1985 }
1986
1987 #[test]
1988 fn extract_cast() {
1989 check_assist(
1990 extract_function,
1991 r"
1992fn main() {
1993 let v = $00f32 as u32$0;
1994}",
1995 r"
1996fn main() {
1997 let v = fun_name();
1998}
1999
2000fn $0fun_name() -> u32 {
2001 0f32 as u32
2002}",
2003 );
2004 }
2005
2006 #[test]
2007 fn return_not_applicable() {
2008 check_assist_not_applicable(extract_function, r"fn foo() { $0return$0; } ");
2009 }
2010
2011 #[test]
2012 fn method_to_freestanding() {
2013 check_assist(
2014 extract_function,
2015 r"
2016struct S;
2017
2018impl S {
2019 fn foo(&self) -> i32 {
2020 $01+1$0
2021 }
2022}",
2023 r"
2024struct S;
2025
2026impl S {
2027 fn foo(&self) -> i32 {
2028 fun_name()
2029 }
2030}
2031
2032fn $0fun_name() -> i32 {
2033 1+1
2034}",
2035 );
2036 }
2037
2038 #[test]
2039 fn method_with_reference() {
2040 check_assist(
2041 extract_function,
2042 r"
2043struct S { f: i32 };
2044
2045impl S {
2046 fn foo(&self) -> i32 {
2047 $01+self.f$0
2048 }
2049}",
2050 r"
2051struct S { f: i32 };
2052
2053impl S {
2054 fn foo(&self) -> i32 {
2055 self.fun_name()
2056 }
2057
2058 fn $0fun_name(&self) -> i32 {
2059 1+self.f
2060 }
2061}",
2062 );
2063 }
2064
2065 #[test]
2066 fn method_with_mut() {
2067 check_assist(
2068 extract_function,
2069 r"
2070struct S { f: i32 };
2071
2072impl S {
2073 fn foo(&mut self) {
2074 $0self.f += 1;$0
2075 }
2076}",
2077 r"
2078struct S { f: i32 };
2079
2080impl S {
2081 fn foo(&mut self) {
2082 self.fun_name();
2083 }
2084
2085 fn $0fun_name(&mut self) {
2086 self.f += 1;
2087 }
2088}",
2089 );
2090 }
2091
2092 #[test]
2093 fn variable_defined_inside_and_used_after_no_ret() {
2094 check_assist(
2095 extract_function,
2096 r"
2097fn foo() {
2098 let n = 1;
2099 $0let k = n * n;$0
2100 let m = k + 1;
2101}",
2102 r"
2103fn foo() {
2104 let n = 1;
2105 let k = fun_name(n);
2106 let m = k + 1;
2107}
2108
2109fn $0fun_name(n: i32) -> i32 {
2110 let k = n * n;
2111 k
2112}",
2113 );
2114 }
2115
2116 #[test]
2117 fn two_variables_defined_inside_and_used_after_no_ret() {
2118 check_assist(
2119 extract_function,
2120 r"
2121fn foo() {
2122 let n = 1;
2123 $0let k = n * n;
2124 let m = k + 2;$0
2125 let h = k + m;
2126}",
2127 r"
2128fn foo() {
2129 let n = 1;
2130 let (k, m) = fun_name(n);
2131 let h = k + m;
2132}
2133
2134fn $0fun_name(n: i32) -> (i32, i32) {
2135 let k = n * n;
2136 let m = k + 2;
2137 (k, m)
2138}",
2139 );
2140 }
2141
2142 #[test]
2143 fn nontrivial_patterns_define_variables() {
2144 check_assist(
2145 extract_function,
2146 r"
2147struct Counter(i32);
2148fn foo() {
2149 $0let Counter(n) = Counter(0);$0
2150 let m = n;
2151}",
2152 r"
2153struct Counter(i32);
2154fn foo() {
2155 let n = fun_name();
2156 let m = n;
2157}
2158
2159fn $0fun_name() -> i32 {
2160 let Counter(n) = Counter(0);
2161 n
2162}",
2163 );
2164 }
2165
2166 #[test]
2167 fn struct_with_two_fields_pattern_define_variables() {
2168 check_assist(
2169 extract_function,
2170 r"
2171struct Counter { n: i32, m: i32 };
2172fn foo() {
2173 $0let Counter { n, m: k } = Counter { n: 1, m: 2 };$0
2174 let h = n + k;
2175}",
2176 r"
2177struct Counter { n: i32, m: i32 };
2178fn foo() {
2179 let (n, k) = fun_name();
2180 let h = n + k;
2181}
2182
2183fn $0fun_name() -> (i32, i32) {
2184 let Counter { n, m: k } = Counter { n: 1, m: 2 };
2185 (n, k)
2186}",
2187 );
2188 }
2189
2190 #[test]
2191 fn mut_var_from_outer_scope() {
2192 check_assist(
2193 extract_function,
2194 r"
2195fn foo() {
2196 let mut n = 1;
2197 $0n += 1;$0
2198 let m = n + 1;
2199}",
2200 r"
2201fn foo() {
2202 let mut n = 1;
2203 fun_name(&mut n);
2204 let m = n + 1;
2205}
2206
2207fn $0fun_name(n: &mut i32) {
2208 *n += 1;
2209}",
2210 );
2211 }
2212
2213 #[test]
2214 fn mut_field_from_outer_scope() {
2215 check_assist(
2216 extract_function,
2217 r"
2218struct C { n: i32 }
2219fn foo() {
2220 let mut c = C { n: 0 };
2221 $0c.n += 1;$0
2222 let m = c.n + 1;
2223}",
2224 r"
2225struct C { n: i32 }
2226fn foo() {
2227 let mut c = C { n: 0 };
2228 fun_name(&mut c);
2229 let m = c.n + 1;
2230}
2231
2232fn $0fun_name(c: &mut C) {
2233 c.n += 1;
2234}",
2235 );
2236 }
2237
2238 #[test]
2239 fn mut_nested_field_from_outer_scope() {
2240 check_assist(
2241 extract_function,
2242 r"
2243struct P { n: i32}
2244struct C { p: P }
2245fn foo() {
2246 let mut c = C { p: P { n: 0 } };
2247 let mut v = C { p: P { n: 0 } };
2248 let u = C { p: P { n: 0 } };
2249 $0c.p.n += u.p.n;
2250 let r = &mut v.p.n;$0
2251 let m = c.p.n + v.p.n + u.p.n;
2252}",
2253 r"
2254struct P { n: i32}
2255struct C { p: P }
2256fn foo() {
2257 let mut c = C { p: P { n: 0 } };
2258 let mut v = C { p: P { n: 0 } };
2259 let u = C { p: P { n: 0 } };
2260 fun_name(&mut c, &u, &mut v);
2261 let m = c.p.n + v.p.n + u.p.n;
2262}
2263
2264fn $0fun_name(c: &mut C, u: &C, v: &mut C) {
2265 c.p.n += u.p.n;
2266 let r = &mut v.p.n;
2267}",
2268 );
2269 }
2270
2271 #[test]
2272 fn mut_param_many_usages_stmt() {
2273 check_assist(
2274 extract_function,
2275 r"
2276fn bar(k: i32) {}
2277trait I: Copy {
2278 fn succ(&self) -> Self;
2279 fn inc(&mut self) -> Self { let v = self.succ(); *self = v; v }
2280}
2281impl I for i32 {
2282 fn succ(&self) -> Self { *self + 1 }
2283}
2284fn foo() {
2285 let mut n = 1;
2286 $0n += n;
2287 bar(n);
2288 bar(n+1);
2289 bar(n*n);
2290 bar(&n);
2291 n.inc();
2292 let v = &mut n;
2293 *v = v.succ();
2294 n.succ();$0
2295 let m = n + 1;
2296}",
2297 r"
2298fn bar(k: i32) {}
2299trait I: Copy {
2300 fn succ(&self) -> Self;
2301 fn inc(&mut self) -> Self { let v = self.succ(); *self = v; v }
2302}
2303impl I for i32 {
2304 fn succ(&self) -> Self { *self + 1 }
2305}
2306fn foo() {
2307 let mut n = 1;
2308 fun_name(&mut n);
2309 let m = n + 1;
2310}
2311
2312fn $0fun_name(n: &mut i32) {
2313 *n += *n;
2314 bar(*n);
2315 bar(*n+1);
2316 bar(*n**n);
2317 bar(&*n);
2318 n.inc();
2319 let v = n;
2320 *v = v.succ();
2321 n.succ();
2322}",
2323 );
2324 }
2325
2326 #[test]
2327 fn mut_param_many_usages_expr() {
2328 check_assist(
2329 extract_function,
2330 r"
2331fn bar(k: i32) {}
2332trait I: Copy {
2333 fn succ(&self) -> Self;
2334 fn inc(&mut self) -> Self { let v = self.succ(); *self = v; v }
2335}
2336impl I for i32 {
2337 fn succ(&self) -> Self { *self + 1 }
2338}
2339fn foo() {
2340 let mut n = 1;
2341 $0{
2342 n += n;
2343 bar(n);
2344 bar(n+1);
2345 bar(n*n);
2346 bar(&n);
2347 n.inc();
2348 let v = &mut n;
2349 *v = v.succ();
2350 n.succ();
2351 }$0
2352 let m = n + 1;
2353}",
2354 r"
2355fn bar(k: i32) {}
2356trait I: Copy {
2357 fn succ(&self) -> Self;
2358 fn inc(&mut self) -> Self { let v = self.succ(); *self = v; v }
2359}
2360impl I for i32 {
2361 fn succ(&self) -> Self { *self + 1 }
2362}
2363fn foo() {
2364 let mut n = 1;
2365 fun_name(&mut n);
2366 let m = n + 1;
2367}
2368
2369fn $0fun_name(n: &mut i32) {
2370 {
2371 *n += *n;
2372 bar(*n);
2373 bar(*n+1);
2374 bar(*n**n);
2375 bar(&*n);
2376 n.inc();
2377 let v = n;
2378 *v = v.succ();
2379 n.succ();
2380 }
2381}",
2382 );
2383 }
2384
2385 #[test]
2386 fn mut_param_by_value() {
2387 check_assist(
2388 extract_function,
2389 r"
2390fn foo() {
2391 let mut n = 1;
2392 $0n += 1;$0
2393}",
2394 r"
2395fn foo() {
2396 let mut n = 1;
2397 fun_name(n);
2398}
2399
2400fn $0fun_name(mut n: i32) {
2401 n += 1;
2402}",
2403 );
2404 }
2405
2406 #[test]
2407 fn mut_param_because_of_mut_ref() {
2408 check_assist(
2409 extract_function,
2410 r"
2411fn foo() {
2412 let mut n = 1;
2413 $0let v = &mut n;
2414 *v += 1;$0
2415 let k = n;
2416}",
2417 r"
2418fn foo() {
2419 let mut n = 1;
2420 fun_name(&mut n);
2421 let k = n;
2422}
2423
2424fn $0fun_name(n: &mut i32) {
2425 let v = n;
2426 *v += 1;
2427}",
2428 );
2429 }
2430
2431 #[test]
2432 fn mut_param_by_value_because_of_mut_ref() {
2433 check_assist(
2434 extract_function,
2435 r"
2436fn foo() {
2437 let mut n = 1;
2438 $0let v = &mut n;
2439 *v += 1;$0
2440}",
2441 r"
2442fn foo() {
2443 let mut n = 1;
2444 fun_name(n);
2445}
2446
2447fn $0fun_name(mut n: i32) {
2448 let v = &mut n;
2449 *v += 1;
2450}",
2451 );
2452 }
2453
2454 #[test]
2455 fn mut_method_call() {
2456 check_assist(
2457 extract_function,
2458 r"
2459trait I {
2460 fn inc(&mut self);
2461}
2462impl I for i32 {
2463 fn inc(&mut self) { *self += 1 }
2464}
2465fn foo() {
2466 let mut n = 1;
2467 $0n.inc();$0
2468}",
2469 r"
2470trait I {
2471 fn inc(&mut self);
2472}
2473impl I for i32 {
2474 fn inc(&mut self) { *self += 1 }
2475}
2476fn foo() {
2477 let mut n = 1;
2478 fun_name(n);
2479}
2480
2481fn $0fun_name(mut n: i32) {
2482 n.inc();
2483}",
2484 );
2485 }
2486
2487 #[test]
2488 fn shared_method_call() {
2489 check_assist(
2490 extract_function,
2491 r"
2492trait I {
2493 fn succ(&self);
2494}
2495impl I for i32 {
2496 fn succ(&self) { *self + 1 }
2497}
2498fn foo() {
2499 let mut n = 1;
2500 $0n.succ();$0
2501}",
2502 r"
2503trait I {
2504 fn succ(&self);
2505}
2506impl I for i32 {
2507 fn succ(&self) { *self + 1 }
2508}
2509fn foo() {
2510 let mut n = 1;
2511 fun_name(n);
2512}
2513
2514fn $0fun_name(n: i32) {
2515 n.succ();
2516}",
2517 );
2518 }
2519
2520 #[test]
2521 fn mut_method_call_with_other_receiver() {
2522 check_assist(
2523 extract_function,
2524 r"
2525trait I {
2526 fn inc(&mut self, n: i32);
2527}
2528impl I for i32 {
2529 fn inc(&mut self, n: i32) { *self += n }
2530}
2531fn foo() {
2532 let mut n = 1;
2533 $0let mut m = 2;
2534 m.inc(n);$0
2535}",
2536 r"
2537trait I {
2538 fn inc(&mut self, n: i32);
2539}
2540impl I for i32 {
2541 fn inc(&mut self, n: i32) { *self += n }
2542}
2543fn foo() {
2544 let mut n = 1;
2545 fun_name(n);
2546}
2547
2548fn $0fun_name(n: i32) {
2549 let mut m = 2;
2550 m.inc(n);
2551}",
2552 );
2553 }
2554
2555 #[test]
2556 fn non_copy_without_usages_after() {
2557 check_assist(
2558 extract_function,
2559 r"
2560struct Counter(i32);
2561fn foo() {
2562 let c = Counter(0);
2563 $0let n = c.0;$0
2564}",
2565 r"
2566struct Counter(i32);
2567fn foo() {
2568 let c = Counter(0);
2569 fun_name(c);
2570}
2571
2572fn $0fun_name(c: Counter) {
2573 let n = c.0;
2574}",
2575 );
2576 }
2577
2578 #[test]
2579 fn non_copy_used_after() {
2580 check_assist(
2581 extract_function,
2582 r"
2583struct Counter(i32);
2584fn foo() {
2585 let c = Counter(0);
2586 $0let n = c.0;$0
2587 let m = c.0;
2588}",
2589 r"
2590struct Counter(i32);
2591fn foo() {
2592 let c = Counter(0);
2593 fun_name(&c);
2594 let m = c.0;
2595}
2596
2597fn $0fun_name(c: &Counter) {
2598 let n = c.0;
2599}",
2600 );
2601 }
2602
2603 #[test]
2604 fn copy_used_after() {
2605 check_assist(
2606 extract_function,
2607 r##"
2608#[lang = "copy"]
2609pub trait Copy {}
2610impl Copy for i32 {}
2611fn foo() {
2612 let n = 0;
2613 $0let m = n;$0
2614 let k = n;
2615}"##,
2616 r##"
2617#[lang = "copy"]
2618pub trait Copy {}
2619impl Copy for i32 {}
2620fn foo() {
2621 let n = 0;
2622 fun_name(n);
2623 let k = n;
2624}
2625
2626fn $0fun_name(n: i32) {
2627 let m = n;
2628}"##,
2629 )
2630 }
2631
2632 #[test]
2633 fn copy_custom_used_after() {
2634 check_assist(
2635 extract_function,
2636 r##"
2637#[lang = "copy"]
2638pub trait Copy {}
2639struct Counter(i32);
2640impl Copy for Counter {}
2641fn foo() {
2642 let c = Counter(0);
2643 $0let n = c.0;$0
2644 let m = c.0;
2645}"##,
2646 r##"
2647#[lang = "copy"]
2648pub trait Copy {}
2649struct Counter(i32);
2650impl Copy for Counter {}
2651fn foo() {
2652 let c = Counter(0);
2653 fun_name(c);
2654 let m = c.0;
2655}
2656
2657fn $0fun_name(c: Counter) {
2658 let n = c.0;
2659}"##,
2660 );
2661 }
2662
2663 #[test]
2664 fn indented_stmts() {
2665 check_assist(
2666 extract_function,
2667 r"
2668fn foo() {
2669 if true {
2670 loop {
2671 $0let n = 1;
2672 let m = 2;$0
2673 }
2674 }
2675}",
2676 r"
2677fn foo() {
2678 if true {
2679 loop {
2680 fun_name();
2681 }
2682 }
2683}
2684
2685fn $0fun_name() {
2686 let n = 1;
2687 let m = 2;
2688}",
2689 );
2690 }
2691
2692 #[test]
2693 fn indented_stmts_inside_mod() {
2694 check_assist(
2695 extract_function,
2696 r"
2697mod bar {
2698 fn foo() {
2699 if true {
2700 loop {
2701 $0let n = 1;
2702 let m = 2;$0
2703 }
2704 }
2705 }
2706}",
2707 r"
2708mod bar {
2709 fn foo() {
2710 if true {
2711 loop {
2712 fun_name();
2713 }
2714 }
2715 }
2716
2717 fn $0fun_name() {
2718 let n = 1;
2719 let m = 2;
2720 }
2721}",
2722 );
2723 }
2724
2725 #[test]
2726 fn break_loop() {
2727 check_assist(
2728 extract_function,
2729 r##"
2730enum Option<T> {
2731 #[lang = "None"] None,
2732 #[lang = "Some"] Some(T),
2733}
2734use Option::*;
2735fn foo() {
2736 loop {
2737 let n = 1;
2738 $0let m = n + 1;
2739 break;
2740 let k = 2;$0
2741 let h = 1 + k;
2742 }
2743}"##,
2744 r##"
2745enum Option<T> {
2746 #[lang = "None"] None,
2747 #[lang = "Some"] Some(T),
2748}
2749use Option::*;
2750fn foo() {
2751 loop {
2752 let n = 1;
2753 let k = match fun_name(n) {
2754 Some(value) => value,
2755 None => break,
2756 };
2757 let h = 1 + k;
2758 }
2759}
2760
2761fn $0fun_name(n: i32) -> Option<i32> {
2762 let m = n + 1;
2763 return None;
2764 let k = 2;
2765 Some(k)
2766}"##,
2767 );
2768 }
2769
2770 #[test]
2771 fn return_to_parent() {
2772 check_assist(
2773 extract_function,
2774 r##"
2775#[lang = "copy"]
2776pub trait Copy {}
2777impl Copy for i32 {}
2778enum Result<T, E> {
2779 #[lang = "Ok"] Ok(T),
2780 #[lang = "Err"] Err(E),
2781}
2782use Result::*;
2783fn foo() -> i64 {
2784 let n = 1;
2785 $0let m = n + 1;
2786 return 1;
2787 let k = 2;$0
2788 (n + k) as i64
2789}"##,
2790 r##"
2791#[lang = "copy"]
2792pub trait Copy {}
2793impl Copy for i32 {}
2794enum Result<T, E> {
2795 #[lang = "Ok"] Ok(T),
2796 #[lang = "Err"] Err(E),
2797}
2798use Result::*;
2799fn foo() -> i64 {
2800 let n = 1;
2801 let k = match fun_name(n) {
2802 Ok(value) => value,
2803 Err(value) => return value,
2804 };
2805 (n + k) as i64
2806}
2807
2808fn $0fun_name(n: i32) -> Result<i32, i64> {
2809 let m = n + 1;
2810 return Err(1);
2811 let k = 2;
2812 Ok(k)
2813}"##,
2814 );
2815 }
2816
2817 #[test]
2818 fn break_and_continue() {
2819 mark::check!(external_control_flow_break_and_continue);
2820 check_assist_not_applicable(
2821 extract_function,
2822 r##"
2823fn foo() {
2824 loop {
2825 let n = 1;
2826 $0let m = n + 1;
2827 break;
2828 let k = 2;
2829 continue;
2830 let k = k + 1;$0
2831 let r = n + k;
2832 }
2833}"##,
2834 );
2835 }
2836
2837 #[test]
2838 fn return_and_break() {
2839 mark::check!(external_control_flow_return_and_bc);
2840 check_assist_not_applicable(
2841 extract_function,
2842 r##"
2843fn foo() {
2844 loop {
2845 let n = 1;
2846 $0let m = n + 1;
2847 break;
2848 let k = 2;
2849 return;
2850 let k = k + 1;$0
2851 let r = n + k;
2852 }
2853}"##,
2854 );
2855 }
2856
2857 #[test]
2858 fn break_loop_with_if() {
2859 check_assist(
2860 extract_function,
2861 r##"
2862fn foo() {
2863 loop {
2864 let mut n = 1;
2865 $0let m = n + 1;
2866 break;
2867 n += m;$0
2868 let h = 1 + n;
2869 }
2870}"##,
2871 r##"
2872fn foo() {
2873 loop {
2874 let mut n = 1;
2875 if fun_name(&mut n) {
2876 break;
2877 }
2878 let h = 1 + n;
2879 }
2880}
2881
2882fn $0fun_name(n: &mut i32) -> bool {
2883 let m = *n + 1;
2884 return true;
2885 *n += m;
2886 false
2887}"##,
2888 );
2889 }
2890
2891 #[test]
2892 fn break_loop_nested() {
2893 check_assist(
2894 extract_function,
2895 r##"
2896fn foo() {
2897 loop {
2898 let mut n = 1;
2899 $0let m = n + 1;
2900 if m == 42 {
2901 break;
2902 }$0
2903 let h = 1;
2904 }
2905}"##,
2906 r##"
2907fn foo() {
2908 loop {
2909 let mut n = 1;
2910 if fun_name(n) {
2911 break;
2912 }
2913 let h = 1;
2914 }
2915}
2916
2917fn $0fun_name(n: i32) -> bool {
2918 let m = n + 1;
2919 if m == 42 {
2920 return true;
2921 }
2922 false
2923}"##,
2924 );
2925 }
2926
2927 #[test]
2928 fn return_from_nested_loop() {
2929 check_assist(
2930 extract_function,
2931 r##"
2932fn foo() {
2933 loop {
2934 let n = 1;
2935 $0
2936 let k = 1;
2937 loop {
2938 return;
2939 }
2940 let m = k + 1;$0
2941 let h = 1 + m;
2942 }
2943}"##,
2944 r##"
2945fn foo() {
2946 loop {
2947 let n = 1;
2948 let m = match fun_name() {
2949 Some(value) => value,
2950 None => return,
2951 };
2952 let h = 1 + m;
2953 }
2954}
2955
2956fn $0fun_name() -> Option<i32> {
2957 let k = 1;
2958 loop {
2959 return None;
2960 }
2961 let m = k + 1;
2962 Some(m)
2963}"##,
2964 );
2965 }
2966
2967 #[test]
2968 fn break_from_nested_loop() {
2969 check_assist(
2970 extract_function,
2971 r##"
2972fn foo() {
2973 loop {
2974 let n = 1;
2975 $0let k = 1;
2976 loop {
2977 break;
2978 }
2979 let m = k + 1;$0
2980 let h = 1 + m;
2981 }
2982}"##,
2983 r##"
2984fn foo() {
2985 loop {
2986 let n = 1;
2987 let m = fun_name();
2988 let h = 1 + m;
2989 }
2990}
2991
2992fn $0fun_name() -> i32 {
2993 let k = 1;
2994 loop {
2995 break;
2996 }
2997 let m = k + 1;
2998 m
2999}"##,
3000 );
3001 }
3002
3003 #[test]
3004 fn break_from_nested_and_outer_loops() {
3005 check_assist(
3006 extract_function,
3007 r##"
3008fn foo() {
3009 loop {
3010 let n = 1;
3011 $0let k = 1;
3012 loop {
3013 break;
3014 }
3015 if k == 42 {
3016 break;
3017 }
3018 let m = k + 1;$0
3019 let h = 1 + m;
3020 }
3021}"##,
3022 r##"
3023fn foo() {
3024 loop {
3025 let n = 1;
3026 let m = match fun_name() {
3027 Some(value) => value,
3028 None => break,
3029 };
3030 let h = 1 + m;
3031 }
3032}
3033
3034fn $0fun_name() -> Option<i32> {
3035 let k = 1;
3036 loop {
3037 break;
3038 }
3039 if k == 42 {
3040 return None;
3041 }
3042 let m = k + 1;
3043 Some(m)
3044}"##,
3045 );
3046 }
3047
3048 #[test]
3049 fn return_from_nested_fn() {
3050 check_assist(
3051 extract_function,
3052 r##"
3053fn foo() {
3054 loop {
3055 let n = 1;
3056 $0let k = 1;
3057 fn test() {
3058 return;
3059 }
3060 let m = k + 1;$0
3061 let h = 1 + m;
3062 }
3063}"##,
3064 r##"
3065fn foo() {
3066 loop {
3067 let n = 1;
3068 let m = fun_name();
3069 let h = 1 + m;
3070 }
3071}
3072
3073fn $0fun_name() -> i32 {
3074 let k = 1;
3075 fn test() {
3076 return;
3077 }
3078 let m = k + 1;
3079 m
3080}"##,
3081 );
3082 }
3083
3084 #[test]
3085 fn break_with_value() {
3086 check_assist(
3087 extract_function,
3088 r##"
3089fn foo() -> i32 {
3090 loop {
3091 let n = 1;
3092 $0let k = 1;
3093 if k == 42 {
3094 break 3;
3095 }
3096 let m = k + 1;$0
3097 let h = 1;
3098 }
3099}"##,
3100 r##"
3101fn foo() -> i32 {
3102 loop {
3103 let n = 1;
3104 if let Some(value) = fun_name() {
3105 break value;
3106 }
3107 let h = 1;
3108 }
3109}
3110
3111fn $0fun_name() -> Option<i32> {
3112 let k = 1;
3113 if k == 42 {
3114 return Some(3);
3115 }
3116 let m = k + 1;
3117 None
3118}"##,
3119 );
3120 }
3121
3122 #[test]
3123 fn break_with_value_and_return() {
3124 check_assist(
3125 extract_function,
3126 r##"
3127fn foo() -> i64 {
3128 loop {
3129 let n = 1;
3130 $0
3131 let k = 1;
3132 if k == 42 {
3133 break 3;
3134 }
3135 let m = k + 1;$0
3136 let h = 1 + m;
3137 }
3138}"##,
3139 r##"
3140fn foo() -> i64 {
3141 loop {
3142 let n = 1;
3143 let m = match fun_name() {
3144 Ok(value) => value,
3145 Err(value) => break value,
3146 };
3147 let h = 1 + m;
3148 }
3149}
3150
3151fn $0fun_name() -> Result<i32, i64> {
3152 let k = 1;
3153 if k == 42 {
3154 return Err(3);
3155 }
3156 let m = k + 1;
3157 Ok(m)
3158}"##,
3159 );
3160 }
3161
3162 #[test]
3163 fn try_option() {
3164 check_assist(
3165 extract_function,
3166 r##"
3167enum Option<T> { None, Some(T), }
3168use Option::*;
3169fn bar() -> Option<i32> { None }
3170fn foo() -> Option<()> {
3171 let n = bar()?;
3172 $0let k = foo()?;
3173 let m = k + 1;$0
3174 let h = 1 + m;
3175 Some(())
3176}"##,
3177 r##"
3178enum Option<T> { None, Some(T), }
3179use Option::*;
3180fn bar() -> Option<i32> { None }
3181fn foo() -> Option<()> {
3182 let n = bar()?;
3183 let m = fun_name()?;
3184 let h = 1 + m;
3185 Some(())
3186}
3187
3188fn $0fun_name() -> Option<i32> {
3189 let k = foo()?;
3190 let m = k + 1;
3191 Some(m)
3192}"##,
3193 );
3194 }
3195
3196 #[test]
3197 fn try_option_unit() {
3198 check_assist(
3199 extract_function,
3200 r##"
3201enum Option<T> { None, Some(T), }
3202use Option::*;
3203fn foo() -> Option<()> {
3204 let n = 1;
3205 $0let k = foo()?;
3206 let m = k + 1;$0
3207 let h = 1 + n;
3208 Some(())
3209}"##,
3210 r##"
3211enum Option<T> { None, Some(T), }
3212use Option::*;
3213fn foo() -> Option<()> {
3214 let n = 1;
3215 fun_name()?;
3216 let h = 1 + n;
3217 Some(())
3218}
3219
3220fn $0fun_name() -> Option<()> {
3221 let k = foo()?;
3222 let m = k + 1;
3223 Some(())
3224}"##,
3225 );
3226 }
3227
3228 #[test]
3229 fn try_result() {
3230 check_assist(
3231 extract_function,
3232 r##"
3233enum Result<T, E> { Ok(T), Err(E), }
3234use Result::*;
3235fn foo() -> Result<(), i64> {
3236 let n = 1;
3237 $0let k = foo()?;
3238 let m = k + 1;$0
3239 let h = 1 + m;
3240 Ok(())
3241}"##,
3242 r##"
3243enum Result<T, E> { Ok(T), Err(E), }
3244use Result::*;
3245fn foo() -> Result<(), i64> {
3246 let n = 1;
3247 let m = fun_name()?;
3248 let h = 1 + m;
3249 Ok(())
3250}
3251
3252fn $0fun_name() -> Result<i32, i64> {
3253 let k = foo()?;
3254 let m = k + 1;
3255 Ok(m)
3256}"##,
3257 );
3258 }
3259
3260 #[test]
3261 fn try_option_with_return() {
3262 check_assist(
3263 extract_function,
3264 r##"
3265enum Option<T> { None, Some(T) }
3266use Option::*;
3267fn foo() -> Option<()> {
3268 let n = 1;
3269 $0let k = foo()?;
3270 if k == 42 {
3271 return None;
3272 }
3273 let m = k + 1;$0
3274 let h = 1 + m;
3275 Some(())
3276}"##,
3277 r##"
3278enum Option<T> { None, Some(T) }
3279use Option::*;
3280fn foo() -> Option<()> {
3281 let n = 1;
3282 let m = fun_name()?;
3283 let h = 1 + m;
3284 Some(())
3285}
3286
3287fn $0fun_name() -> Option<i32> {
3288 let k = foo()?;
3289 if k == 42 {
3290 return None;
3291 }
3292 let m = k + 1;
3293 Some(m)
3294}"##,
3295 );
3296 }
3297
3298 #[test]
3299 fn try_result_with_return() {
3300 check_assist(
3301 extract_function,
3302 r##"
3303enum Result<T, E> { Ok(T), Err(E), }
3304use Result::*;
3305fn foo() -> Result<(), i64> {
3306 let n = 1;
3307 $0let k = foo()?;
3308 if k == 42 {
3309 return Err(1);
3310 }
3311 let m = k + 1;$0
3312 let h = 1 + m;
3313 Ok(())
3314}"##,
3315 r##"
3316enum Result<T, E> { Ok(T), Err(E), }
3317use Result::*;
3318fn foo() -> Result<(), i64> {
3319 let n = 1;
3320 let m = fun_name()?;
3321 let h = 1 + m;
3322 Ok(())
3323}
3324
3325fn $0fun_name() -> Result<i32, i64> {
3326 let k = foo()?;
3327 if k == 42 {
3328 return Err(1);
3329 }
3330 let m = k + 1;
3331 Ok(m)
3332}"##,
3333 );
3334 }
3335
3336 #[test]
3337 fn try_and_break() {
3338 mark::check!(external_control_flow_try_and_bc);
3339 check_assist_not_applicable(
3340 extract_function,
3341 r##"
3342enum Option<T> { None, Some(T) }
3343use Option::*;
3344fn foo() -> Option<()> {
3345 loop {
3346 let n = Some(1);
3347 $0let m = n? + 1;
3348 break;
3349 let k = 2;
3350 let k = k + 1;$0
3351 let r = n + k;
3352 }
3353 Some(())
3354}"##,
3355 );
3356 }
3357
3358 #[test]
3359 fn try_and_return_ok() {
3360 mark::check!(external_control_flow_try_and_return_non_err);
3361 check_assist_not_applicable(
3362 extract_function,
3363 r##"
3364enum Result<T, E> { Ok(T), Err(E), }
3365use Result::*;
3366fn foo() -> Result<(), i64> {
3367 let n = 1;
3368 $0let k = foo()?;
3369 if k == 42 {
3370 return Ok(1);
3371 }
3372 let m = k + 1;$0
3373 let h = 1 + m;
3374 Ok(())
3375}"##,
3376 );
3377 }
3378}
diff --git a/crates/ide_assists/src/handlers/extract_struct_from_enum_variant.rs b/crates/ide_assists/src/handlers/extract_struct_from_enum_variant.rs
new file mode 100644
index 000000000..5c7678b53
--- /dev/null
+++ b/crates/ide_assists/src/handlers/extract_struct_from_enum_variant.rs
@@ -0,0 +1,520 @@
1use std::iter;
2
3use either::Either;
4use hir::{AsName, Module, ModuleDef, Name, Variant};
5use ide_db::{
6 defs::Definition,
7 helpers::{
8 insert_use::{insert_use, ImportScope},
9 mod_path_to_ast,
10 },
11 search::FileReference,
12 RootDatabase,
13};
14use rustc_hash::FxHashSet;
15use syntax::{
16 algo::{find_node_at_offset, SyntaxRewriter},
17 ast::{self, edit::IndentLevel, make, AstNode, NameOwner, VisibilityOwner},
18 SourceFile, SyntaxElement, SyntaxNode, T,
19};
20
21use crate::{AssistContext, AssistId, AssistKind, Assists};
22
23// Assist: extract_struct_from_enum_variant
24//
25// Extracts a struct from enum variant.
26//
27// ```
28// enum A { $0One(u32, u32) }
29// ```
30// ->
31// ```
32// struct One(pub u32, pub u32);
33//
34// enum A { One(One) }
35// ```
36pub(crate) fn extract_struct_from_enum_variant(
37 acc: &mut Assists,
38 ctx: &AssistContext,
39) -> Option<()> {
40 let variant = ctx.find_node_at_offset::<ast::Variant>()?;
41 let field_list = extract_field_list_if_applicable(&variant)?;
42
43 let variant_name = variant.name()?;
44 let variant_hir = ctx.sema.to_def(&variant)?;
45 if existing_definition(ctx.db(), &variant_name, &variant_hir) {
46 return None;
47 }
48
49 let enum_ast = variant.parent_enum();
50 let enum_hir = ctx.sema.to_def(&enum_ast)?;
51 let target = variant.syntax().text_range();
52 acc.add(
53 AssistId("extract_struct_from_enum_variant", AssistKind::RefactorRewrite),
54 "Extract struct from enum variant",
55 target,
56 |builder| {
57 let variant_hir_name = variant_hir.name(ctx.db());
58 let enum_module_def = ModuleDef::from(enum_hir);
59 let usages =
60 Definition::ModuleDef(ModuleDef::Variant(variant_hir)).usages(&ctx.sema).all();
61
62 let mut visited_modules_set = FxHashSet::default();
63 let current_module = enum_hir.module(ctx.db());
64 visited_modules_set.insert(current_module);
65 let mut def_rewriter = None;
66 for (file_id, references) in usages {
67 let mut rewriter = SyntaxRewriter::default();
68 let source_file = ctx.sema.parse(file_id);
69 for reference in references {
70 update_reference(
71 ctx,
72 &mut rewriter,
73 reference,
74 &source_file,
75 &enum_module_def,
76 &variant_hir_name,
77 &mut visited_modules_set,
78 );
79 }
80 if file_id == ctx.frange.file_id {
81 def_rewriter = Some(rewriter);
82 continue;
83 }
84 builder.edit_file(file_id);
85 builder.rewrite(rewriter);
86 }
87 let mut rewriter = def_rewriter.unwrap_or_default();
88 update_variant(&mut rewriter, &variant);
89 extract_struct_def(
90 &mut rewriter,
91 &enum_ast,
92 variant_name.clone(),
93 &field_list,
94 &variant.parent_enum().syntax().clone().into(),
95 enum_ast.visibility(),
96 );
97 builder.edit_file(ctx.frange.file_id);
98 builder.rewrite(rewriter);
99 },
100 )
101}
102
103fn extract_field_list_if_applicable(
104 variant: &ast::Variant,
105) -> Option<Either<ast::RecordFieldList, ast::TupleFieldList>> {
106 match variant.kind() {
107 ast::StructKind::Record(field_list) if field_list.fields().next().is_some() => {
108 Some(Either::Left(field_list))
109 }
110 ast::StructKind::Tuple(field_list) if field_list.fields().count() > 1 => {
111 Some(Either::Right(field_list))
112 }
113 _ => None,
114 }
115}
116
117fn existing_definition(db: &RootDatabase, variant_name: &ast::Name, variant: &Variant) -> bool {
118 variant
119 .parent_enum(db)
120 .module(db)
121 .scope(db, None)
122 .into_iter()
123 .filter(|(_, def)| match def {
124 // only check type-namespace
125 hir::ScopeDef::ModuleDef(def) => matches!(
126 def,
127 ModuleDef::Module(_)
128 | ModuleDef::Adt(_)
129 | ModuleDef::Variant(_)
130 | ModuleDef::Trait(_)
131 | ModuleDef::TypeAlias(_)
132 | ModuleDef::BuiltinType(_)
133 ),
134 _ => false,
135 })
136 .any(|(name, _)| name == variant_name.as_name())
137}
138
139fn insert_import(
140 ctx: &AssistContext,
141 rewriter: &mut SyntaxRewriter,
142 scope_node: &SyntaxNode,
143 module: &Module,
144 enum_module_def: &ModuleDef,
145 variant_hir_name: &Name,
146) -> Option<()> {
147 let db = ctx.db();
148 let mod_path = module.find_use_path_prefixed(
149 db,
150 enum_module_def.clone(),
151 ctx.config.insert_use.prefix_kind,
152 );
153 if let Some(mut mod_path) = mod_path {
154 mod_path.pop_segment();
155 mod_path.push_segment(variant_hir_name.clone());
156 let scope = ImportScope::find_insert_use_container(scope_node, &ctx.sema)?;
157 *rewriter += insert_use(&scope, mod_path_to_ast(&mod_path), ctx.config.insert_use.merge);
158 }
159 Some(())
160}
161
162fn extract_struct_def(
163 rewriter: &mut SyntaxRewriter,
164 enum_: &ast::Enum,
165 variant_name: ast::Name,
166 field_list: &Either<ast::RecordFieldList, ast::TupleFieldList>,
167 start_offset: &SyntaxElement,
168 visibility: Option<ast::Visibility>,
169) -> Option<()> {
170 let pub_vis = Some(make::visibility_pub());
171 let field_list = match field_list {
172 Either::Left(field_list) => {
173 make::record_field_list(field_list.fields().flat_map(|field| {
174 Some(make::record_field(pub_vis.clone(), field.name()?, field.ty()?))
175 }))
176 .into()
177 }
178 Either::Right(field_list) => make::tuple_field_list(
179 field_list
180 .fields()
181 .flat_map(|field| Some(make::tuple_field(pub_vis.clone(), field.ty()?))),
182 )
183 .into(),
184 };
185
186 rewriter.insert_before(
187 start_offset,
188 make::struct_(visibility, variant_name, None, field_list).syntax(),
189 );
190 rewriter.insert_before(start_offset, &make::tokens::blank_line());
191
192 if let indent_level @ 1..=usize::MAX = IndentLevel::from_node(enum_.syntax()).0 as usize {
193 rewriter
194 .insert_before(start_offset, &make::tokens::whitespace(&" ".repeat(4 * indent_level)));
195 }
196 Some(())
197}
198
199fn update_variant(rewriter: &mut SyntaxRewriter, variant: &ast::Variant) -> Option<()> {
200 let name = variant.name()?;
201 let tuple_field = make::tuple_field(None, make::ty(name.text()));
202 let replacement = make::variant(
203 name,
204 Some(ast::FieldList::TupleFieldList(make::tuple_field_list(iter::once(tuple_field)))),
205 );
206 rewriter.replace(variant.syntax(), replacement.syntax());
207 Some(())
208}
209
210fn update_reference(
211 ctx: &AssistContext,
212 rewriter: &mut SyntaxRewriter,
213 reference: FileReference,
214 source_file: &SourceFile,
215 enum_module_def: &ModuleDef,
216 variant_hir_name: &Name,
217 visited_modules_set: &mut FxHashSet<Module>,
218) -> Option<()> {
219 let offset = reference.range.start();
220 let (segment, expr) = if let Some(path_expr) =
221 find_node_at_offset::<ast::PathExpr>(source_file.syntax(), offset)
222 {
223 // tuple variant
224 (path_expr.path()?.segment()?, path_expr.syntax().parent()?)
225 } else if let Some(record_expr) =
226 find_node_at_offset::<ast::RecordExpr>(source_file.syntax(), offset)
227 {
228 // record variant
229 (record_expr.path()?.segment()?, record_expr.syntax().clone())
230 } else {
231 return None;
232 };
233
234 let module = ctx.sema.scope(&expr).module()?;
235 if !visited_modules_set.contains(&module) {
236 if insert_import(ctx, rewriter, &expr, &module, enum_module_def, variant_hir_name).is_some()
237 {
238 visited_modules_set.insert(module);
239 }
240 }
241 rewriter.insert_after(segment.syntax(), &make::token(T!['(']));
242 rewriter.insert_after(segment.syntax(), segment.syntax());
243 rewriter.insert_after(&expr, &make::token(T![')']));
244 Some(())
245}
246
247#[cfg(test)]
248mod tests {
249 use ide_db::helpers::FamousDefs;
250
251 use crate::tests::{check_assist, check_assist_not_applicable};
252
253 use super::*;
254
255 #[test]
256 fn test_extract_struct_several_fields_tuple() {
257 check_assist(
258 extract_struct_from_enum_variant,
259 "enum A { $0One(u32, u32) }",
260 r#"struct One(pub u32, pub u32);
261
262enum A { One(One) }"#,
263 );
264 }
265
266 #[test]
267 fn test_extract_struct_several_fields_named() {
268 check_assist(
269 extract_struct_from_enum_variant,
270 "enum A { $0One { foo: u32, bar: u32 } }",
271 r#"struct One{ pub foo: u32, pub bar: u32 }
272
273enum A { One(One) }"#,
274 );
275 }
276
277 #[test]
278 fn test_extract_struct_one_field_named() {
279 check_assist(
280 extract_struct_from_enum_variant,
281 "enum A { $0One { foo: u32 } }",
282 r#"struct One{ pub foo: u32 }
283
284enum A { One(One) }"#,
285 );
286 }
287
288 #[test]
289 fn test_extract_enum_variant_name_value_namespace() {
290 check_assist(
291 extract_struct_from_enum_variant,
292 r#"const One: () = ();
293enum A { $0One(u32, u32) }"#,
294 r#"const One: () = ();
295struct One(pub u32, pub u32);
296
297enum A { One(One) }"#,
298 );
299 }
300
301 #[test]
302 fn test_extract_struct_pub_visibility() {
303 check_assist(
304 extract_struct_from_enum_variant,
305 "pub enum A { $0One(u32, u32) }",
306 r#"pub struct One(pub u32, pub u32);
307
308pub enum A { One(One) }"#,
309 );
310 }
311
312 #[test]
313 fn test_extract_struct_with_complex_imports() {
314 check_assist(
315 extract_struct_from_enum_variant,
316 r#"mod my_mod {
317 fn another_fn() {
318 let m = my_other_mod::MyEnum::MyField(1, 1);
319 }
320
321 pub mod my_other_mod {
322 fn another_fn() {
323 let m = MyEnum::MyField(1, 1);
324 }
325
326 pub enum MyEnum {
327 $0MyField(u8, u8),
328 }
329 }
330}
331
332fn another_fn() {
333 let m = my_mod::my_other_mod::MyEnum::MyField(1, 1);
334}"#,
335 r#"use my_mod::my_other_mod::MyField;
336
337mod my_mod {
338 use self::my_other_mod::MyField;
339
340 fn another_fn() {
341 let m = my_other_mod::MyEnum::MyField(MyField(1, 1));
342 }
343
344 pub mod my_other_mod {
345 fn another_fn() {
346 let m = MyEnum::MyField(MyField(1, 1));
347 }
348
349 pub struct MyField(pub u8, pub u8);
350
351 pub enum MyEnum {
352 MyField(MyField),
353 }
354 }
355}
356
357fn another_fn() {
358 let m = my_mod::my_other_mod::MyEnum::MyField(MyField(1, 1));
359}"#,
360 );
361 }
362
363 #[test]
364 fn extract_record_fix_references() {
365 check_assist(
366 extract_struct_from_enum_variant,
367 r#"
368enum E {
369 $0V { i: i32, j: i32 }
370}
371
372fn f() {
373 let e = E::V { i: 9, j: 2 };
374}
375"#,
376 r#"
377struct V{ pub i: i32, pub j: i32 }
378
379enum E {
380 V(V)
381}
382
383fn f() {
384 let e = E::V(V { i: 9, j: 2 });
385}
386"#,
387 )
388 }
389
390 #[test]
391 fn test_several_files() {
392 check_assist(
393 extract_struct_from_enum_variant,
394 r#"
395//- /main.rs
396enum E {
397 $0V(i32, i32)
398}
399mod foo;
400
401//- /foo.rs
402use crate::E;
403fn f() {
404 let e = E::V(9, 2);
405}
406"#,
407 r#"
408//- /main.rs
409struct V(pub i32, pub i32);
410
411enum E {
412 V(V)
413}
414mod foo;
415
416//- /foo.rs
417use crate::{E, V};
418fn f() {
419 let e = E::V(V(9, 2));
420}
421"#,
422 )
423 }
424
425 #[test]
426 fn test_several_files_record() {
427 check_assist(
428 extract_struct_from_enum_variant,
429 r#"
430//- /main.rs
431enum E {
432 $0V { i: i32, j: i32 }
433}
434mod foo;
435
436//- /foo.rs
437use crate::E;
438fn f() {
439 let e = E::V { i: 9, j: 2 };
440}
441"#,
442 r#"
443//- /main.rs
444struct V{ pub i: i32, pub j: i32 }
445
446enum E {
447 V(V)
448}
449mod foo;
450
451//- /foo.rs
452use crate::{E, V};
453fn f() {
454 let e = E::V(V { i: 9, j: 2 });
455}
456"#,
457 )
458 }
459
460 #[test]
461 fn test_extract_struct_record_nested_call_exp() {
462 check_assist(
463 extract_struct_from_enum_variant,
464 r#"
465enum A { $0One { a: u32, b: u32 } }
466
467struct B(A);
468
469fn foo() {
470 let _ = B(A::One { a: 1, b: 2 });
471}
472"#,
473 r#"
474struct One{ pub a: u32, pub b: u32 }
475
476enum A { One(One) }
477
478struct B(A);
479
480fn foo() {
481 let _ = B(A::One(One { a: 1, b: 2 }));
482}
483"#,
484 );
485 }
486
487 fn check_not_applicable(ra_fixture: &str) {
488 let fixture =
489 format!("//- /main.rs crate:main deps:core\n{}\n{}", ra_fixture, FamousDefs::FIXTURE);
490 check_assist_not_applicable(extract_struct_from_enum_variant, &fixture)
491 }
492
493 #[test]
494 fn test_extract_enum_not_applicable_for_element_with_no_fields() {
495 check_not_applicable("enum A { $0One }");
496 }
497
498 #[test]
499 fn test_extract_enum_not_applicable_if_struct_exists() {
500 check_not_applicable(
501 r#"struct One;
502 enum A { $0One(u8, u32) }"#,
503 );
504 }
505
506 #[test]
507 fn test_extract_not_applicable_one_field() {
508 check_not_applicable(r"enum A { $0One(u32) }");
509 }
510
511 #[test]
512 fn test_extract_not_applicable_no_field_tuple() {
513 check_not_applicable(r"enum A { $0None() }");
514 }
515
516 #[test]
517 fn test_extract_not_applicable_no_field_named() {
518 check_not_applicable(r"enum A { $0None {} }");
519 }
520}
diff --git a/crates/ide_assists/src/handlers/extract_variable.rs b/crates/ide_assists/src/handlers/extract_variable.rs
new file mode 100644
index 000000000..98f3dc6ca
--- /dev/null
+++ b/crates/ide_assists/src/handlers/extract_variable.rs
@@ -0,0 +1,588 @@
1use stdx::format_to;
2use syntax::{
3 ast::{self, AstNode},
4 SyntaxKind::{
5 BLOCK_EXPR, BREAK_EXPR, CLOSURE_EXPR, COMMENT, LOOP_EXPR, MATCH_ARM, PATH_EXPR, RETURN_EXPR,
6 },
7 SyntaxNode,
8};
9use test_utils::mark;
10
11use crate::{AssistContext, AssistId, AssistKind, Assists};
12
13// Assist: extract_variable
14//
15// Extracts subexpression into a variable.
16//
17// ```
18// fn main() {
19// $0(1 + 2)$0 * 4;
20// }
21// ```
22// ->
23// ```
24// fn main() {
25// let $0var_name = (1 + 2);
26// var_name * 4;
27// }
28// ```
29pub(crate) fn extract_variable(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
30 if ctx.frange.range.is_empty() {
31 return None;
32 }
33 let node = ctx.covering_element();
34 if node.kind() == COMMENT {
35 mark::hit!(extract_var_in_comment_is_not_applicable);
36 return None;
37 }
38 let to_extract = node.ancestors().find_map(valid_target_expr)?;
39 let anchor = Anchor::from(&to_extract)?;
40 let indent = anchor.syntax().prev_sibling_or_token()?.as_token()?.clone();
41 let target = to_extract.syntax().text_range();
42 acc.add(
43 AssistId("extract_variable", AssistKind::RefactorExtract),
44 "Extract into variable",
45 target,
46 move |edit| {
47 let field_shorthand =
48 match to_extract.syntax().parent().and_then(ast::RecordExprField::cast) {
49 Some(field) => field.name_ref(),
50 None => None,
51 };
52
53 let mut buf = String::new();
54
55 let var_name = match &field_shorthand {
56 Some(it) => it.to_string(),
57 None => "var_name".to_string(),
58 };
59 let expr_range = match &field_shorthand {
60 Some(it) => it.syntax().text_range().cover(to_extract.syntax().text_range()),
61 None => to_extract.syntax().text_range(),
62 };
63
64 if let Anchor::WrapInBlock(_) = anchor {
65 format_to!(buf, "{{ let {} = ", var_name);
66 } else {
67 format_to!(buf, "let {} = ", var_name);
68 };
69 format_to!(buf, "{}", to_extract.syntax());
70
71 if let Anchor::Replace(stmt) = anchor {
72 mark::hit!(test_extract_var_expr_stmt);
73 if stmt.semicolon_token().is_none() {
74 buf.push_str(";");
75 }
76 match ctx.config.snippet_cap {
77 Some(cap) => {
78 let snip = buf
79 .replace(&format!("let {}", var_name), &format!("let $0{}", var_name));
80 edit.replace_snippet(cap, expr_range, snip)
81 }
82 None => edit.replace(expr_range, buf),
83 }
84 return;
85 }
86
87 buf.push_str(";");
88
89 // We want to maintain the indent level,
90 // but we do not want to duplicate possible
91 // extra newlines in the indent block
92 let text = indent.text();
93 if text.starts_with('\n') {
94 buf.push('\n');
95 buf.push_str(text.trim_start_matches('\n'));
96 } else {
97 buf.push_str(text);
98 }
99
100 edit.replace(expr_range, var_name.clone());
101 let offset = anchor.syntax().text_range().start();
102 match ctx.config.snippet_cap {
103 Some(cap) => {
104 let snip =
105 buf.replace(&format!("let {}", var_name), &format!("let $0{}", var_name));
106 edit.insert_snippet(cap, offset, snip)
107 }
108 None => edit.insert(offset, buf),
109 }
110
111 if let Anchor::WrapInBlock(_) = anchor {
112 edit.insert(anchor.syntax().text_range().end(), " }");
113 }
114 },
115 )
116}
117
118/// Check whether the node is a valid expression which can be extracted to a variable.
119/// In general that's true for any expression, but in some cases that would produce invalid code.
120fn valid_target_expr(node: SyntaxNode) -> Option<ast::Expr> {
121 match node.kind() {
122 PATH_EXPR | LOOP_EXPR => None,
123 BREAK_EXPR => ast::BreakExpr::cast(node).and_then(|e| e.expr()),
124 RETURN_EXPR => ast::ReturnExpr::cast(node).and_then(|e| e.expr()),
125 BLOCK_EXPR => {
126 ast::BlockExpr::cast(node).filter(|it| it.is_standalone()).map(ast::Expr::from)
127 }
128 _ => ast::Expr::cast(node),
129 }
130}
131
132enum Anchor {
133 Before(SyntaxNode),
134 Replace(ast::ExprStmt),
135 WrapInBlock(SyntaxNode),
136}
137
138impl Anchor {
139 fn from(to_extract: &ast::Expr) -> Option<Anchor> {
140 to_extract.syntax().ancestors().find_map(|node| {
141 if let Some(expr) =
142 node.parent().and_then(ast::BlockExpr::cast).and_then(|it| it.tail_expr())
143 {
144 if expr.syntax() == &node {
145 mark::hit!(test_extract_var_last_expr);
146 return Some(Anchor::Before(node));
147 }
148 }
149
150 if let Some(parent) = node.parent() {
151 if parent.kind() == MATCH_ARM || parent.kind() == CLOSURE_EXPR {
152 return Some(Anchor::WrapInBlock(node));
153 }
154 }
155
156 if let Some(stmt) = ast::Stmt::cast(node.clone()) {
157 if let ast::Stmt::ExprStmt(stmt) = stmt {
158 if stmt.expr().as_ref() == Some(to_extract) {
159 return Some(Anchor::Replace(stmt));
160 }
161 }
162 return Some(Anchor::Before(node));
163 }
164 None
165 })
166 }
167
168 fn syntax(&self) -> &SyntaxNode {
169 match self {
170 Anchor::Before(it) | Anchor::WrapInBlock(it) => it,
171 Anchor::Replace(stmt) => stmt.syntax(),
172 }
173 }
174}
175
176#[cfg(test)]
177mod tests {
178 use test_utils::mark;
179
180 use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target};
181
182 use super::*;
183
184 #[test]
185 fn test_extract_var_simple() {
186 check_assist(
187 extract_variable,
188 r#"
189fn foo() {
190 foo($01 + 1$0);
191}"#,
192 r#"
193fn foo() {
194 let $0var_name = 1 + 1;
195 foo(var_name);
196}"#,
197 );
198 }
199
200 #[test]
201 fn extract_var_in_comment_is_not_applicable() {
202 mark::check!(extract_var_in_comment_is_not_applicable);
203 check_assist_not_applicable(extract_variable, "fn main() { 1 + /* $0comment$0 */ 1; }");
204 }
205
206 #[test]
207 fn test_extract_var_expr_stmt() {
208 mark::check!(test_extract_var_expr_stmt);
209 check_assist(
210 extract_variable,
211 r#"
212fn foo() {
213 $01 + 1$0;
214}"#,
215 r#"
216fn foo() {
217 let $0var_name = 1 + 1;
218}"#,
219 );
220 check_assist(
221 extract_variable,
222 "
223fn foo() {
224 $0{ let x = 0; x }$0
225 something_else();
226}",
227 "
228fn foo() {
229 let $0var_name = { let x = 0; x };
230 something_else();
231}",
232 );
233 }
234
235 #[test]
236 fn test_extract_var_part_of_expr_stmt() {
237 check_assist(
238 extract_variable,
239 "
240fn foo() {
241 $01$0 + 1;
242}",
243 "
244fn foo() {
245 let $0var_name = 1;
246 var_name + 1;
247}",
248 );
249 }
250
251 #[test]
252 fn test_extract_var_last_expr() {
253 mark::check!(test_extract_var_last_expr);
254 check_assist(
255 extract_variable,
256 r#"
257fn foo() {
258 bar($01 + 1$0)
259}
260"#,
261 r#"
262fn foo() {
263 let $0var_name = 1 + 1;
264 bar(var_name)
265}
266"#,
267 );
268 check_assist(
269 extract_variable,
270 r#"
271fn foo() {
272 $0bar(1 + 1)$0
273}
274"#,
275 r#"
276fn foo() {
277 let $0var_name = bar(1 + 1);
278 var_name
279}
280"#,
281 )
282 }
283
284 #[test]
285 fn test_extract_var_in_match_arm_no_block() {
286 check_assist(
287 extract_variable,
288 "
289fn main() {
290 let x = true;
291 let tuple = match x {
292 true => ($02 + 2$0, true)
293 _ => (0, false)
294 };
295}
296",
297 "
298fn main() {
299 let x = true;
300 let tuple = match x {
301 true => { let $0var_name = 2 + 2; (var_name, true) }
302 _ => (0, false)
303 };
304}
305",
306 );
307 }
308
309 #[test]
310 fn test_extract_var_in_match_arm_with_block() {
311 check_assist(
312 extract_variable,
313 "
314fn main() {
315 let x = true;
316 let tuple = match x {
317 true => {
318 let y = 1;
319 ($02 + y$0, true)
320 }
321 _ => (0, false)
322 };
323}
324",
325 "
326fn main() {
327 let x = true;
328 let tuple = match x {
329 true => {
330 let y = 1;
331 let $0var_name = 2 + y;
332 (var_name, true)
333 }
334 _ => (0, false)
335 };
336}
337",
338 );
339 }
340
341 #[test]
342 fn test_extract_var_in_closure_no_block() {
343 check_assist(
344 extract_variable,
345 "
346fn main() {
347 let lambda = |x: u32| $0x * 2$0;
348}
349",
350 "
351fn main() {
352 let lambda = |x: u32| { let $0var_name = x * 2; var_name };
353}
354",
355 );
356 }
357
358 #[test]
359 fn test_extract_var_in_closure_with_block() {
360 check_assist(
361 extract_variable,
362 "
363fn main() {
364 let lambda = |x: u32| { $0x * 2$0 };
365}
366",
367 "
368fn main() {
369 let lambda = |x: u32| { let $0var_name = x * 2; var_name };
370}
371",
372 );
373 }
374
375 #[test]
376 fn test_extract_var_path_simple() {
377 check_assist(
378 extract_variable,
379 "
380fn main() {
381 let o = $0Some(true)$0;
382}
383",
384 "
385fn main() {
386 let $0var_name = Some(true);
387 let o = var_name;
388}
389",
390 );
391 }
392
393 #[test]
394 fn test_extract_var_path_method() {
395 check_assist(
396 extract_variable,
397 "
398fn main() {
399 let v = $0bar.foo()$0;
400}
401",
402 "
403fn main() {
404 let $0var_name = bar.foo();
405 let v = var_name;
406}
407",
408 );
409 }
410
411 #[test]
412 fn test_extract_var_return() {
413 check_assist(
414 extract_variable,
415 "
416fn foo() -> u32 {
417 $0return 2 + 2$0;
418}
419",
420 "
421fn foo() -> u32 {
422 let $0var_name = 2 + 2;
423 return var_name;
424}
425",
426 );
427 }
428
429 #[test]
430 fn test_extract_var_does_not_add_extra_whitespace() {
431 check_assist(
432 extract_variable,
433 "
434fn foo() -> u32 {
435
436
437 $0return 2 + 2$0;
438}
439",
440 "
441fn foo() -> u32 {
442
443
444 let $0var_name = 2 + 2;
445 return var_name;
446}
447",
448 );
449
450 check_assist(
451 extract_variable,
452 "
453fn foo() -> u32 {
454
455 $0return 2 + 2$0;
456}
457",
458 "
459fn foo() -> u32 {
460
461 let $0var_name = 2 + 2;
462 return var_name;
463}
464",
465 );
466
467 check_assist(
468 extract_variable,
469 "
470fn foo() -> u32 {
471 let foo = 1;
472
473 // bar
474
475
476 $0return 2 + 2$0;
477}
478",
479 "
480fn foo() -> u32 {
481 let foo = 1;
482
483 // bar
484
485
486 let $0var_name = 2 + 2;
487 return var_name;
488}
489",
490 );
491 }
492
493 #[test]
494 fn test_extract_var_break() {
495 check_assist(
496 extract_variable,
497 "
498fn main() {
499 let result = loop {
500 $0break 2 + 2$0;
501 };
502}
503",
504 "
505fn main() {
506 let result = loop {
507 let $0var_name = 2 + 2;
508 break var_name;
509 };
510}
511",
512 );
513 }
514
515 #[test]
516 fn test_extract_var_for_cast() {
517 check_assist(
518 extract_variable,
519 "
520fn main() {
521 let v = $00f32 as u32$0;
522}
523",
524 "
525fn main() {
526 let $0var_name = 0f32 as u32;
527 let v = var_name;
528}
529",
530 );
531 }
532
533 #[test]
534 fn extract_var_field_shorthand() {
535 check_assist(
536 extract_variable,
537 r#"
538struct S {
539 foo: i32
540}
541
542fn main() {
543 S { foo: $01 + 1$0 }
544}
545"#,
546 r#"
547struct S {
548 foo: i32
549}
550
551fn main() {
552 let $0foo = 1 + 1;
553 S { foo }
554}
555"#,
556 )
557 }
558
559 #[test]
560 fn test_extract_var_for_return_not_applicable() {
561 check_assist_not_applicable(extract_variable, "fn foo() { $0return$0; } ");
562 }
563
564 #[test]
565 fn test_extract_var_for_break_not_applicable() {
566 check_assist_not_applicable(extract_variable, "fn main() { loop { $0break$0; }; }");
567 }
568
569 // FIXME: This is not quite correct, but good enough(tm) for the sorting heuristic
570 #[test]
571 fn extract_var_target() {
572 check_assist_target(extract_variable, "fn foo() -> u32 { $0return 2 + 2$0; }", "2 + 2");
573
574 check_assist_target(
575 extract_variable,
576 "
577fn main() {
578 let x = true;
579 let tuple = match x {
580 true => ($02 + 2$0, true)
581 _ => (0, false)
582 };
583}
584",
585 "2 + 2",
586 );
587 }
588}
diff --git a/crates/ide_assists/src/handlers/fill_match_arms.rs b/crates/ide_assists/src/handlers/fill_match_arms.rs
new file mode 100644
index 000000000..7086e47d2
--- /dev/null
+++ b/crates/ide_assists/src/handlers/fill_match_arms.rs
@@ -0,0 +1,787 @@
1use std::iter;
2
3use hir::{Adt, HasSource, ModuleDef, Semantics};
4use ide_db::helpers::{mod_path_to_ast, FamousDefs};
5use ide_db::RootDatabase;
6use itertools::Itertools;
7use syntax::ast::{self, make, AstNode, MatchArm, NameOwner, Pat};
8use test_utils::mark;
9
10use crate::{
11 utils::{does_pat_match_variant, render_snippet, Cursor},
12 AssistContext, AssistId, AssistKind, Assists,
13};
14
15// Assist: fill_match_arms
16//
17// Adds missing clauses to a `match` expression.
18//
19// ```
20// enum Action { Move { distance: u32 }, Stop }
21//
22// fn handle(action: Action) {
23// match action {
24// $0
25// }
26// }
27// ```
28// ->
29// ```
30// enum Action { Move { distance: u32 }, Stop }
31//
32// fn handle(action: Action) {
33// match action {
34// $0Action::Move { distance } => {}
35// Action::Stop => {}
36// }
37// }
38// ```
39pub(crate) fn fill_match_arms(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
40 let match_expr = ctx.find_node_at_offset_with_descend::<ast::MatchExpr>()?;
41 let match_arm_list = match_expr.match_arm_list()?;
42
43 let expr = match_expr.expr()?;
44
45 let mut arms: Vec<MatchArm> = match_arm_list.arms().collect();
46 if arms.len() == 1 {
47 if let Some(Pat::WildcardPat(..)) = arms[0].pat() {
48 arms.clear();
49 }
50 }
51
52 let module = ctx.sema.scope(expr.syntax()).module()?;
53
54 let missing_arms: Vec<MatchArm> = if let Some(enum_def) = resolve_enum_def(&ctx.sema, &expr) {
55 let variants = enum_def.variants(ctx.db());
56
57 let mut variants = variants
58 .into_iter()
59 .filter_map(|variant| build_pat(ctx.db(), module, variant))
60 .filter(|variant_pat| is_variant_missing(&mut arms, variant_pat))
61 .map(|pat| make::match_arm(iter::once(pat), make::expr_empty_block()))
62 .collect::<Vec<_>>();
63 if Some(enum_def) == FamousDefs(&ctx.sema, Some(module.krate())).core_option_Option() {
64 // Match `Some` variant first.
65 mark::hit!(option_order);
66 variants.reverse()
67 }
68 variants
69 } else if let Some(enum_defs) = resolve_tuple_of_enum_def(&ctx.sema, &expr) {
70 // Partial fill not currently supported for tuple of enums.
71 if !arms.is_empty() {
72 return None;
73 }
74
75 // We do not currently support filling match arms for a tuple
76 // containing a single enum.
77 if enum_defs.len() < 2 {
78 return None;
79 }
80
81 // When calculating the match arms for a tuple of enums, we want
82 // to create a match arm for each possible combination of enum
83 // values. The `multi_cartesian_product` method transforms
84 // Vec<Vec<EnumVariant>> into Vec<(EnumVariant, .., EnumVariant)>
85 // where each tuple represents a proposed match arm.
86 enum_defs
87 .into_iter()
88 .map(|enum_def| enum_def.variants(ctx.db()))
89 .multi_cartesian_product()
90 .map(|variants| {
91 let patterns =
92 variants.into_iter().filter_map(|variant| build_pat(ctx.db(), module, variant));
93 ast::Pat::from(make::tuple_pat(patterns))
94 })
95 .filter(|variant_pat| is_variant_missing(&mut arms, variant_pat))
96 .map(|pat| make::match_arm(iter::once(pat), make::expr_empty_block()))
97 .collect()
98 } else {
99 return None;
100 };
101
102 if missing_arms.is_empty() {
103 return None;
104 }
105
106 let target = ctx.sema.original_range(match_expr.syntax()).range;
107 acc.add(
108 AssistId("fill_match_arms", AssistKind::QuickFix),
109 "Fill match arms",
110 target,
111 |builder| {
112 let new_arm_list = match_arm_list.remove_placeholder();
113 let n_old_arms = new_arm_list.arms().count();
114 let new_arm_list = new_arm_list.append_arms(missing_arms);
115 let first_new_arm = new_arm_list.arms().nth(n_old_arms);
116 let old_range = ctx.sema.original_range(match_arm_list.syntax()).range;
117 match (first_new_arm, ctx.config.snippet_cap) {
118 (Some(first_new_arm), Some(cap)) => {
119 let extend_lifetime;
120 let cursor =
121 match first_new_arm.syntax().descendants().find_map(ast::WildcardPat::cast)
122 {
123 Some(it) => {
124 extend_lifetime = it.syntax().clone();
125 Cursor::Replace(&extend_lifetime)
126 }
127 None => Cursor::Before(first_new_arm.syntax()),
128 };
129 let snippet = render_snippet(cap, new_arm_list.syntax(), cursor);
130 builder.replace_snippet(cap, old_range, snippet);
131 }
132 _ => builder.replace(old_range, new_arm_list.to_string()),
133 }
134 },
135 )
136}
137
138fn is_variant_missing(existing_arms: &mut Vec<MatchArm>, var: &Pat) -> bool {
139 existing_arms.iter().filter_map(|arm| arm.pat()).all(|pat| {
140 // Special casee OrPat as separate top-level pats
141 let top_level_pats: Vec<Pat> = match pat {
142 Pat::OrPat(pats) => pats.pats().collect::<Vec<_>>(),
143 _ => vec![pat],
144 };
145
146 !top_level_pats.iter().any(|pat| does_pat_match_variant(pat, var))
147 })
148}
149
150fn resolve_enum_def(sema: &Semantics<RootDatabase>, expr: &ast::Expr) -> Option<hir::Enum> {
151 sema.type_of_expr(&expr)?.autoderef(sema.db).find_map(|ty| match ty.as_adt() {
152 Some(Adt::Enum(e)) => Some(e),
153 _ => None,
154 })
155}
156
157fn resolve_tuple_of_enum_def(
158 sema: &Semantics<RootDatabase>,
159 expr: &ast::Expr,
160) -> Option<Vec<hir::Enum>> {
161 sema.type_of_expr(&expr)?
162 .tuple_fields(sema.db)
163 .iter()
164 .map(|ty| {
165 ty.autoderef(sema.db).find_map(|ty| match ty.as_adt() {
166 Some(Adt::Enum(e)) => Some(e),
167 // For now we only handle expansion for a tuple of enums. Here
168 // we map non-enum items to None and rely on `collect` to
169 // convert Vec<Option<hir::Enum>> into Option<Vec<hir::Enum>>.
170 _ => None,
171 })
172 })
173 .collect()
174}
175
176fn build_pat(db: &RootDatabase, module: hir::Module, var: hir::Variant) -> Option<ast::Pat> {
177 let path = mod_path_to_ast(&module.find_use_path(db, ModuleDef::from(var))?);
178
179 // FIXME: use HIR for this; it doesn't currently expose struct vs. tuple vs. unit variants though
180 let pat: ast::Pat = match var.source(db)?.value.kind() {
181 ast::StructKind::Tuple(field_list) => {
182 let pats = iter::repeat(make::wildcard_pat().into()).take(field_list.fields().count());
183 make::tuple_struct_pat(path, pats).into()
184 }
185 ast::StructKind::Record(field_list) => {
186 let pats = field_list.fields().map(|f| make::ident_pat(f.name().unwrap()).into());
187 make::record_pat(path, pats).into()
188 }
189 ast::StructKind::Unit => make::path_pat(path),
190 };
191
192 Some(pat)
193}
194
195#[cfg(test)]
196mod tests {
197 use ide_db::helpers::FamousDefs;
198 use test_utils::mark;
199
200 use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target};
201
202 use super::fill_match_arms;
203
204 #[test]
205 fn all_match_arms_provided() {
206 check_assist_not_applicable(
207 fill_match_arms,
208 r#"
209 enum A {
210 As,
211 Bs{x:i32, y:Option<i32>},
212 Cs(i32, Option<i32>),
213 }
214 fn main() {
215 match A::As$0 {
216 A::As,
217 A::Bs{x,y:Some(_)} => {}
218 A::Cs(_, Some(_)) => {}
219 }
220 }
221 "#,
222 );
223 }
224
225 #[test]
226 fn tuple_of_non_enum() {
227 // for now this case is not handled, although it potentially could be
228 // in the future
229 check_assist_not_applicable(
230 fill_match_arms,
231 r#"
232 fn main() {
233 match (0, false)$0 {
234 }
235 }
236 "#,
237 );
238 }
239
240 #[test]
241 fn partial_fill_record_tuple() {
242 check_assist(
243 fill_match_arms,
244 r#"
245 enum A {
246 As,
247 Bs { x: i32, y: Option<i32> },
248 Cs(i32, Option<i32>),
249 }
250 fn main() {
251 match A::As$0 {
252 A::Bs { x, y: Some(_) } => {}
253 A::Cs(_, Some(_)) => {}
254 }
255 }
256 "#,
257 r#"
258 enum A {
259 As,
260 Bs { x: i32, y: Option<i32> },
261 Cs(i32, Option<i32>),
262 }
263 fn main() {
264 match A::As {
265 A::Bs { x, y: Some(_) } => {}
266 A::Cs(_, Some(_)) => {}
267 $0A::As => {}
268 }
269 }
270 "#,
271 );
272 }
273
274 #[test]
275 fn partial_fill_option() {
276 check_assist(
277 fill_match_arms,
278 r#"
279enum Option<T> { Some(T), None }
280use Option::*;
281
282fn main() {
283 match None$0 {
284 None => {}
285 }
286}
287 "#,
288 r#"
289enum Option<T> { Some(T), None }
290use Option::*;
291
292fn main() {
293 match None {
294 None => {}
295 Some(${0:_}) => {}
296 }
297}
298 "#,
299 );
300 }
301
302 #[test]
303 fn partial_fill_or_pat() {
304 check_assist(
305 fill_match_arms,
306 r#"
307enum A { As, Bs, Cs(Option<i32>) }
308fn main() {
309 match A::As$0 {
310 A::Cs(_) | A::Bs => {}
311 }
312}
313"#,
314 r#"
315enum A { As, Bs, Cs(Option<i32>) }
316fn main() {
317 match A::As {
318 A::Cs(_) | A::Bs => {}
319 $0A::As => {}
320 }
321}
322"#,
323 );
324 }
325
326 #[test]
327 fn partial_fill() {
328 check_assist(
329 fill_match_arms,
330 r#"
331enum A { As, Bs, Cs, Ds(String), Es(B) }
332enum B { Xs, Ys }
333fn main() {
334 match A::As$0 {
335 A::Bs if 0 < 1 => {}
336 A::Ds(_value) => { let x = 1; }
337 A::Es(B::Xs) => (),
338 }
339}
340"#,
341 r#"
342enum A { As, Bs, Cs, Ds(String), Es(B) }
343enum B { Xs, Ys }
344fn main() {
345 match A::As {
346 A::Bs if 0 < 1 => {}
347 A::Ds(_value) => { let x = 1; }
348 A::Es(B::Xs) => (),
349 $0A::As => {}
350 A::Cs => {}
351 }
352}
353"#,
354 );
355 }
356
357 #[test]
358 fn partial_fill_bind_pat() {
359 check_assist(
360 fill_match_arms,
361 r#"
362enum A { As, Bs, Cs(Option<i32>) }
363fn main() {
364 match A::As$0 {
365 A::As(_) => {}
366 a @ A::Bs(_) => {}
367 }
368}
369"#,
370 r#"
371enum A { As, Bs, Cs(Option<i32>) }
372fn main() {
373 match A::As {
374 A::As(_) => {}
375 a @ A::Bs(_) => {}
376 A::Cs(${0:_}) => {}
377 }
378}
379"#,
380 );
381 }
382
383 #[test]
384 fn fill_match_arms_empty_body() {
385 check_assist(
386 fill_match_arms,
387 r#"
388enum A { As, Bs, Cs(String), Ds(String, String), Es { x: usize, y: usize } }
389
390fn main() {
391 let a = A::As;
392 match a$0 {}
393}
394"#,
395 r#"
396enum A { As, Bs, Cs(String), Ds(String, String), Es { x: usize, y: usize } }
397
398fn main() {
399 let a = A::As;
400 match a {
401 $0A::As => {}
402 A::Bs => {}
403 A::Cs(_) => {}
404 A::Ds(_, _) => {}
405 A::Es { x, y } => {}
406 }
407}
408"#,
409 );
410 }
411
412 #[test]
413 fn fill_match_arms_tuple_of_enum() {
414 check_assist(
415 fill_match_arms,
416 r#"
417 enum A { One, Two }
418 enum B { One, Two }
419
420 fn main() {
421 let a = A::One;
422 let b = B::One;
423 match (a$0, b) {}
424 }
425 "#,
426 r#"
427 enum A { One, Two }
428 enum B { One, Two }
429
430 fn main() {
431 let a = A::One;
432 let b = B::One;
433 match (a, b) {
434 $0(A::One, B::One) => {}
435 (A::One, B::Two) => {}
436 (A::Two, B::One) => {}
437 (A::Two, B::Two) => {}
438 }
439 }
440 "#,
441 );
442 }
443
444 #[test]
445 fn fill_match_arms_tuple_of_enum_ref() {
446 check_assist(
447 fill_match_arms,
448 r#"
449 enum A { One, Two }
450 enum B { One, Two }
451
452 fn main() {
453 let a = A::One;
454 let b = B::One;
455 match (&a$0, &b) {}
456 }
457 "#,
458 r#"
459 enum A { One, Two }
460 enum B { One, Two }
461
462 fn main() {
463 let a = A::One;
464 let b = B::One;
465 match (&a, &b) {
466 $0(A::One, B::One) => {}
467 (A::One, B::Two) => {}
468 (A::Two, B::One) => {}
469 (A::Two, B::Two) => {}
470 }
471 }
472 "#,
473 );
474 }
475
476 #[test]
477 fn fill_match_arms_tuple_of_enum_partial() {
478 check_assist_not_applicable(
479 fill_match_arms,
480 r#"
481 enum A { One, Two }
482 enum B { One, Two }
483
484 fn main() {
485 let a = A::One;
486 let b = B::One;
487 match (a$0, b) {
488 (A::Two, B::One) => {}
489 }
490 }
491 "#,
492 );
493 }
494
495 #[test]
496 fn fill_match_arms_tuple_of_enum_not_applicable() {
497 check_assist_not_applicable(
498 fill_match_arms,
499 r#"
500 enum A { One, Two }
501 enum B { One, Two }
502
503 fn main() {
504 let a = A::One;
505 let b = B::One;
506 match (a$0, b) {
507 (A::Two, B::One) => {}
508 (A::One, B::One) => {}
509 (A::One, B::Two) => {}
510 (A::Two, B::Two) => {}
511 }
512 }
513 "#,
514 );
515 }
516
517 #[test]
518 fn fill_match_arms_single_element_tuple_of_enum() {
519 // For now we don't hande the case of a single element tuple, but
520 // we could handle this in the future if `make::tuple_pat` allowed
521 // creating a tuple with a single pattern.
522 check_assist_not_applicable(
523 fill_match_arms,
524 r#"
525 enum A { One, Two }
526
527 fn main() {
528 let a = A::One;
529 match (a$0, ) {
530 }
531 }
532 "#,
533 );
534 }
535
536 #[test]
537 fn test_fill_match_arm_refs() {
538 check_assist(
539 fill_match_arms,
540 r#"
541 enum A { As }
542
543 fn foo(a: &A) {
544 match a$0 {
545 }
546 }
547 "#,
548 r#"
549 enum A { As }
550
551 fn foo(a: &A) {
552 match a {
553 $0A::As => {}
554 }
555 }
556 "#,
557 );
558
559 check_assist(
560 fill_match_arms,
561 r#"
562 enum A {
563 Es { x: usize, y: usize }
564 }
565
566 fn foo(a: &mut A) {
567 match a$0 {
568 }
569 }
570 "#,
571 r#"
572 enum A {
573 Es { x: usize, y: usize }
574 }
575
576 fn foo(a: &mut A) {
577 match a {
578 $0A::Es { x, y } => {}
579 }
580 }
581 "#,
582 );
583 }
584
585 #[test]
586 fn fill_match_arms_target() {
587 check_assist_target(
588 fill_match_arms,
589 r#"
590 enum E { X, Y }
591
592 fn main() {
593 match E::X$0 {}
594 }
595 "#,
596 "match E::X {}",
597 );
598 }
599
600 #[test]
601 fn fill_match_arms_trivial_arm() {
602 check_assist(
603 fill_match_arms,
604 r#"
605 enum E { X, Y }
606
607 fn main() {
608 match E::X {
609 $0_ => {}
610 }
611 }
612 "#,
613 r#"
614 enum E { X, Y }
615
616 fn main() {
617 match E::X {
618 $0E::X => {}
619 E::Y => {}
620 }
621 }
622 "#,
623 );
624 }
625
626 #[test]
627 fn fill_match_arms_qualifies_path() {
628 check_assist(
629 fill_match_arms,
630 r#"
631 mod foo { pub enum E { X, Y } }
632 use foo::E::X;
633
634 fn main() {
635 match X {
636 $0
637 }
638 }
639 "#,
640 r#"
641 mod foo { pub enum E { X, Y } }
642 use foo::E::X;
643
644 fn main() {
645 match X {
646 $0X => {}
647 foo::E::Y => {}
648 }
649 }
650 "#,
651 );
652 }
653
654 #[test]
655 fn fill_match_arms_preserves_comments() {
656 check_assist(
657 fill_match_arms,
658 r#"
659 enum A { One, Two }
660 fn foo(a: A) {
661 match a {
662 // foo bar baz$0
663 A::One => {}
664 // This is where the rest should be
665 }
666 }
667 "#,
668 r#"
669 enum A { One, Two }
670 fn foo(a: A) {
671 match a {
672 // foo bar baz
673 A::One => {}
674 // This is where the rest should be
675 $0A::Two => {}
676 }
677 }
678 "#,
679 );
680 }
681
682 #[test]
683 fn fill_match_arms_preserves_comments_empty() {
684 check_assist(
685 fill_match_arms,
686 r#"
687 enum A { One, Two }
688 fn foo(a: A) {
689 match a {
690 // foo bar baz$0
691 }
692 }
693 "#,
694 r#"
695 enum A { One, Two }
696 fn foo(a: A) {
697 match a {
698 // foo bar baz
699 $0A::One => {}
700 A::Two => {}
701 }
702 }
703 "#,
704 );
705 }
706
707 #[test]
708 fn fill_match_arms_placeholder() {
709 check_assist(
710 fill_match_arms,
711 r#"
712 enum A { One, Two, }
713 fn foo(a: A) {
714 match a$0 {
715 _ => (),
716 }
717 }
718 "#,
719 r#"
720 enum A { One, Two, }
721 fn foo(a: A) {
722 match a {
723 $0A::One => {}
724 A::Two => {}
725 }
726 }
727 "#,
728 );
729 }
730
731 #[test]
732 fn option_order() {
733 mark::check!(option_order);
734 let before = r#"
735fn foo(opt: Option<i32>) {
736 match opt$0 {
737 }
738}
739"#;
740 let before = &format!("//- /main.rs crate:main deps:core{}{}", before, FamousDefs::FIXTURE);
741
742 check_assist(
743 fill_match_arms,
744 before,
745 r#"
746fn foo(opt: Option<i32>) {
747 match opt {
748 Some(${0:_}) => {}
749 None => {}
750 }
751}
752"#,
753 );
754 }
755
756 #[test]
757 fn works_inside_macro_call() {
758 check_assist(
759 fill_match_arms,
760 r#"
761macro_rules! m { ($expr:expr) => {$expr}}
762enum Test {
763 A,
764 B,
765 C,
766}
767
768fn foo(t: Test) {
769 m!(match t$0 {});
770}"#,
771 r#"macro_rules! m { ($expr:expr) => {$expr}}
772enum Test {
773 A,
774 B,
775 C,
776}
777
778fn foo(t: Test) {
779 m!(match t {
780 $0Test::A => {}
781 Test::B => {}
782 Test::C => {}
783});
784}"#,
785 );
786 }
787}
diff --git a/crates/ide_assists/src/handlers/fix_visibility.rs b/crates/ide_assists/src/handlers/fix_visibility.rs
new file mode 100644
index 000000000..6c7824e55
--- /dev/null
+++ b/crates/ide_assists/src/handlers/fix_visibility.rs
@@ -0,0 +1,607 @@
1use hir::{db::HirDatabase, HasSource, HasVisibility, PathResolution};
2use ide_db::base_db::FileId;
3use syntax::{
4 ast::{self, VisibilityOwner},
5 AstNode, TextRange, TextSize,
6};
7
8use crate::{utils::vis_offset, AssistContext, AssistId, AssistKind, Assists};
9
10// FIXME: this really should be a fix for diagnostic, rather than an assist.
11
12// Assist: fix_visibility
13//
14// Makes inaccessible item public.
15//
16// ```
17// mod m {
18// fn frobnicate() {}
19// }
20// fn main() {
21// m::frobnicate$0() {}
22// }
23// ```
24// ->
25// ```
26// mod m {
27// $0pub(crate) fn frobnicate() {}
28// }
29// fn main() {
30// m::frobnicate() {}
31// }
32// ```
33pub(crate) fn fix_visibility(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
34 add_vis_to_referenced_module_def(acc, ctx)
35 .or_else(|| add_vis_to_referenced_record_field(acc, ctx))
36}
37
38fn add_vis_to_referenced_module_def(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
39 let path: ast::Path = ctx.find_node_at_offset()?;
40 let path_res = ctx.sema.resolve_path(&path)?;
41 let def = match path_res {
42 PathResolution::Def(def) => def,
43 _ => return None,
44 };
45
46 let current_module = ctx.sema.scope(&path.syntax()).module()?;
47 let target_module = def.module(ctx.db())?;
48
49 let vis = target_module.visibility_of(ctx.db(), &def)?;
50 if vis.is_visible_from(ctx.db(), current_module.into()) {
51 return None;
52 };
53
54 let (offset, current_visibility, target, target_file, target_name) =
55 target_data_for_def(ctx.db(), def)?;
56
57 let missing_visibility =
58 if current_module.krate() == target_module.krate() { "pub(crate)" } else { "pub" };
59
60 let assist_label = match target_name {
61 None => format!("Change visibility to {}", missing_visibility),
62 Some(name) => format!("Change visibility of {} to {}", name, missing_visibility),
63 };
64
65 acc.add(AssistId("fix_visibility", AssistKind::QuickFix), assist_label, target, |builder| {
66 builder.edit_file(target_file);
67 match ctx.config.snippet_cap {
68 Some(cap) => match current_visibility {
69 Some(current_visibility) => builder.replace_snippet(
70 cap,
71 current_visibility.syntax().text_range(),
72 format!("$0{}", missing_visibility),
73 ),
74 None => builder.insert_snippet(cap, offset, format!("$0{} ", missing_visibility)),
75 },
76 None => match current_visibility {
77 Some(current_visibility) => {
78 builder.replace(current_visibility.syntax().text_range(), missing_visibility)
79 }
80 None => builder.insert(offset, format!("{} ", missing_visibility)),
81 },
82 }
83 })
84}
85
86fn add_vis_to_referenced_record_field(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
87 let record_field: ast::RecordExprField = ctx.find_node_at_offset()?;
88 let (record_field_def, _) = ctx.sema.resolve_record_field(&record_field)?;
89
90 let current_module = ctx.sema.scope(record_field.syntax()).module()?;
91 let visibility = record_field_def.visibility(ctx.db());
92 if visibility.is_visible_from(ctx.db(), current_module.into()) {
93 return None;
94 }
95
96 let parent = record_field_def.parent_def(ctx.db());
97 let parent_name = parent.name(ctx.db());
98 let target_module = parent.module(ctx.db());
99
100 let in_file_source = record_field_def.source(ctx.db())?;
101 let (offset, current_visibility, target) = match in_file_source.value {
102 hir::FieldSource::Named(it) => {
103 let s = it.syntax();
104 (vis_offset(s), it.visibility(), s.text_range())
105 }
106 hir::FieldSource::Pos(it) => {
107 let s = it.syntax();
108 (vis_offset(s), it.visibility(), s.text_range())
109 }
110 };
111
112 let missing_visibility =
113 if current_module.krate() == target_module.krate() { "pub(crate)" } else { "pub" };
114 let target_file = in_file_source.file_id.original_file(ctx.db());
115
116 let target_name = record_field_def.name(ctx.db());
117 let assist_label =
118 format!("Change visibility of {}.{} to {}", parent_name, target_name, missing_visibility);
119
120 acc.add(AssistId("fix_visibility", AssistKind::QuickFix), assist_label, target, |builder| {
121 builder.edit_file(target_file);
122 match ctx.config.snippet_cap {
123 Some(cap) => match current_visibility {
124 Some(current_visibility) => builder.replace_snippet(
125 cap,
126 current_visibility.syntax().text_range(),
127 format!("$0{}", missing_visibility),
128 ),
129 None => builder.insert_snippet(cap, offset, format!("$0{} ", missing_visibility)),
130 },
131 None => match current_visibility {
132 Some(current_visibility) => {
133 builder.replace(current_visibility.syntax().text_range(), missing_visibility)
134 }
135 None => builder.insert(offset, format!("{} ", missing_visibility)),
136 },
137 }
138 })
139}
140
141fn target_data_for_def(
142 db: &dyn HirDatabase,
143 def: hir::ModuleDef,
144) -> Option<(TextSize, Option<ast::Visibility>, TextRange, FileId, Option<hir::Name>)> {
145 fn offset_target_and_file_id<S, Ast>(
146 db: &dyn HirDatabase,
147 x: S,
148 ) -> Option<(TextSize, Option<ast::Visibility>, TextRange, FileId)>
149 where
150 S: HasSource<Ast = Ast>,
151 Ast: AstNode + ast::VisibilityOwner,
152 {
153 let source = x.source(db)?;
154 let in_file_syntax = source.syntax();
155 let file_id = in_file_syntax.file_id;
156 let syntax = in_file_syntax.value;
157 let current_visibility = source.value.visibility();
158 Some((
159 vis_offset(syntax),
160 current_visibility,
161 syntax.text_range(),
162 file_id.original_file(db.upcast()),
163 ))
164 }
165
166 let target_name;
167 let (offset, current_visibility, target, target_file) = match def {
168 hir::ModuleDef::Function(f) => {
169 target_name = Some(f.name(db));
170 offset_target_and_file_id(db, f)?
171 }
172 hir::ModuleDef::Adt(adt) => {
173 target_name = Some(adt.name(db));
174 match adt {
175 hir::Adt::Struct(s) => offset_target_and_file_id(db, s)?,
176 hir::Adt::Union(u) => offset_target_and_file_id(db, u)?,
177 hir::Adt::Enum(e) => offset_target_and_file_id(db, e)?,
178 }
179 }
180 hir::ModuleDef::Const(c) => {
181 target_name = c.name(db);
182 offset_target_and_file_id(db, c)?
183 }
184 hir::ModuleDef::Static(s) => {
185 target_name = s.name(db);
186 offset_target_and_file_id(db, s)?
187 }
188 hir::ModuleDef::Trait(t) => {
189 target_name = Some(t.name(db));
190 offset_target_and_file_id(db, t)?
191 }
192 hir::ModuleDef::TypeAlias(t) => {
193 target_name = Some(t.name(db));
194 offset_target_and_file_id(db, t)?
195 }
196 hir::ModuleDef::Module(m) => {
197 target_name = m.name(db);
198 let in_file_source = m.declaration_source(db)?;
199 let file_id = in_file_source.file_id.original_file(db.upcast());
200 let syntax = in_file_source.value.syntax();
201 (vis_offset(syntax), in_file_source.value.visibility(), syntax.text_range(), file_id)
202 }
203 // Enum variants can't be private, we can't modify builtin types
204 hir::ModuleDef::Variant(_) | hir::ModuleDef::BuiltinType(_) => return None,
205 };
206
207 Some((offset, current_visibility, target, target_file, target_name))
208}
209
210#[cfg(test)]
211mod tests {
212 use crate::tests::{check_assist, check_assist_not_applicable};
213
214 use super::*;
215
216 #[test]
217 fn fix_visibility_of_fn() {
218 check_assist(
219 fix_visibility,
220 r"mod foo { fn foo() {} }
221 fn main() { foo::foo$0() } ",
222 r"mod foo { $0pub(crate) fn foo() {} }
223 fn main() { foo::foo() } ",
224 );
225 check_assist_not_applicable(
226 fix_visibility,
227 r"mod foo { pub fn foo() {} }
228 fn main() { foo::foo$0() } ",
229 )
230 }
231
232 #[test]
233 fn fix_visibility_of_adt_in_submodule() {
234 check_assist(
235 fix_visibility,
236 r"mod foo { struct Foo; }
237 fn main() { foo::Foo$0 } ",
238 r"mod foo { $0pub(crate) struct Foo; }
239 fn main() { foo::Foo } ",
240 );
241 check_assist_not_applicable(
242 fix_visibility,
243 r"mod foo { pub struct Foo; }
244 fn main() { foo::Foo$0 } ",
245 );
246 check_assist(
247 fix_visibility,
248 r"mod foo { enum Foo; }
249 fn main() { foo::Foo$0 } ",
250 r"mod foo { $0pub(crate) enum Foo; }
251 fn main() { foo::Foo } ",
252 );
253 check_assist_not_applicable(
254 fix_visibility,
255 r"mod foo { pub enum Foo; }
256 fn main() { foo::Foo$0 } ",
257 );
258 check_assist(
259 fix_visibility,
260 r"mod foo { union Foo; }
261 fn main() { foo::Foo$0 } ",
262 r"mod foo { $0pub(crate) union Foo; }
263 fn main() { foo::Foo } ",
264 );
265 check_assist_not_applicable(
266 fix_visibility,
267 r"mod foo { pub union Foo; }
268 fn main() { foo::Foo$0 } ",
269 );
270 }
271
272 #[test]
273 fn fix_visibility_of_adt_in_other_file() {
274 check_assist(
275 fix_visibility,
276 r"
277//- /main.rs
278mod foo;
279fn main() { foo::Foo$0 }
280
281//- /foo.rs
282struct Foo;
283",
284 r"$0pub(crate) struct Foo;
285",
286 );
287 }
288
289 #[test]
290 fn fix_visibility_of_struct_field() {
291 check_assist(
292 fix_visibility,
293 r"mod foo { pub struct Foo { bar: (), } }
294 fn main() { foo::Foo { $0bar: () }; } ",
295 r"mod foo { pub struct Foo { $0pub(crate) bar: (), } }
296 fn main() { foo::Foo { bar: () }; } ",
297 );
298 check_assist(
299 fix_visibility,
300 r"
301//- /lib.rs
302mod foo;
303fn main() { foo::Foo { $0bar: () }; }
304//- /foo.rs
305pub struct Foo { bar: () }
306",
307 r"pub struct Foo { $0pub(crate) bar: () }
308",
309 );
310 check_assist_not_applicable(
311 fix_visibility,
312 r"mod foo { pub struct Foo { pub bar: (), } }
313 fn main() { foo::Foo { $0bar: () }; } ",
314 );
315 check_assist_not_applicable(
316 fix_visibility,
317 r"
318//- /lib.rs
319mod foo;
320fn main() { foo::Foo { $0bar: () }; }
321//- /foo.rs
322pub struct Foo { pub bar: () }
323",
324 );
325 }
326
327 #[test]
328 fn fix_visibility_of_enum_variant_field() {
329 // Enum variants, as well as their fields, always get the enum's visibility. In fact, rustc
330 // rejects any visibility specifiers on them, so this assist should never fire on them.
331 check_assist_not_applicable(
332 fix_visibility,
333 r"mod foo { pub enum Foo { Bar { bar: () } } }
334 fn main() { foo::Foo::Bar { $0bar: () }; } ",
335 );
336 check_assist_not_applicable(
337 fix_visibility,
338 r"
339//- /lib.rs
340mod foo;
341fn main() { foo::Foo::Bar { $0bar: () }; }
342//- /foo.rs
343pub enum Foo { Bar { bar: () } }
344",
345 );
346 check_assist_not_applicable(
347 fix_visibility,
348 r"mod foo { pub struct Foo { pub bar: (), } }
349 fn main() { foo::Foo { $0bar: () }; } ",
350 );
351 check_assist_not_applicable(
352 fix_visibility,
353 r"
354//- /lib.rs
355mod foo;
356fn main() { foo::Foo { $0bar: () }; }
357//- /foo.rs
358pub struct Foo { pub bar: () }
359",
360 );
361 }
362
363 #[test]
364 #[ignore]
365 // FIXME reenable this test when `Semantics::resolve_record_field` works with union fields
366 fn fix_visibility_of_union_field() {
367 check_assist(
368 fix_visibility,
369 r"mod foo { pub union Foo { bar: (), } }
370 fn main() { foo::Foo { $0bar: () }; } ",
371 r"mod foo { pub union Foo { $0pub(crate) bar: (), } }
372 fn main() { foo::Foo { bar: () }; } ",
373 );
374 check_assist(
375 fix_visibility,
376 r"
377//- /lib.rs
378mod foo;
379fn main() { foo::Foo { $0bar: () }; }
380//- /foo.rs
381pub union Foo { bar: () }
382",
383 r"pub union Foo { $0pub(crate) bar: () }
384",
385 );
386 check_assist_not_applicable(
387 fix_visibility,
388 r"mod foo { pub union Foo { pub bar: (), } }
389 fn main() { foo::Foo { $0bar: () }; } ",
390 );
391 check_assist_not_applicable(
392 fix_visibility,
393 r"
394//- /lib.rs
395mod foo;
396fn main() { foo::Foo { $0bar: () }; }
397//- /foo.rs
398pub union Foo { pub bar: () }
399",
400 );
401 }
402
403 #[test]
404 fn fix_visibility_of_const() {
405 check_assist(
406 fix_visibility,
407 r"mod foo { const FOO: () = (); }
408 fn main() { foo::FOO$0 } ",
409 r"mod foo { $0pub(crate) const FOO: () = (); }
410 fn main() { foo::FOO } ",
411 );
412 check_assist_not_applicable(
413 fix_visibility,
414 r"mod foo { pub const FOO: () = (); }
415 fn main() { foo::FOO$0 } ",
416 );
417 }
418
419 #[test]
420 fn fix_visibility_of_static() {
421 check_assist(
422 fix_visibility,
423 r"mod foo { static FOO: () = (); }
424 fn main() { foo::FOO$0 } ",
425 r"mod foo { $0pub(crate) static FOO: () = (); }
426 fn main() { foo::FOO } ",
427 );
428 check_assist_not_applicable(
429 fix_visibility,
430 r"mod foo { pub static FOO: () = (); }
431 fn main() { foo::FOO$0 } ",
432 );
433 }
434
435 #[test]
436 fn fix_visibility_of_trait() {
437 check_assist(
438 fix_visibility,
439 r"mod foo { trait Foo { fn foo(&self) {} } }
440 fn main() { let x: &dyn foo::$0Foo; } ",
441 r"mod foo { $0pub(crate) trait Foo { fn foo(&self) {} } }
442 fn main() { let x: &dyn foo::Foo; } ",
443 );
444 check_assist_not_applicable(
445 fix_visibility,
446 r"mod foo { pub trait Foo { fn foo(&self) {} } }
447 fn main() { let x: &dyn foo::Foo$0; } ",
448 );
449 }
450
451 #[test]
452 fn fix_visibility_of_type_alias() {
453 check_assist(
454 fix_visibility,
455 r"mod foo { type Foo = (); }
456 fn main() { let x: foo::Foo$0; } ",
457 r"mod foo { $0pub(crate) type Foo = (); }
458 fn main() { let x: foo::Foo; } ",
459 );
460 check_assist_not_applicable(
461 fix_visibility,
462 r"mod foo { pub type Foo = (); }
463 fn main() { let x: foo::Foo$0; } ",
464 );
465 }
466
467 #[test]
468 fn fix_visibility_of_module() {
469 check_assist(
470 fix_visibility,
471 r"mod foo { mod bar { fn bar() {} } }
472 fn main() { foo::bar$0::bar(); } ",
473 r"mod foo { $0pub(crate) mod bar { fn bar() {} } }
474 fn main() { foo::bar::bar(); } ",
475 );
476
477 check_assist(
478 fix_visibility,
479 r"
480//- /main.rs
481mod foo;
482fn main() { foo::bar$0::baz(); }
483
484//- /foo.rs
485mod bar {
486 pub fn baz() {}
487}
488",
489 r"$0pub(crate) mod bar {
490 pub fn baz() {}
491}
492",
493 );
494
495 check_assist_not_applicable(
496 fix_visibility,
497 r"mod foo { pub mod bar { pub fn bar() {} } }
498 fn main() { foo::bar$0::bar(); } ",
499 );
500 }
501
502 #[test]
503 fn fix_visibility_of_inline_module_in_other_file() {
504 check_assist(
505 fix_visibility,
506 r"
507//- /main.rs
508mod foo;
509fn main() { foo::bar$0::baz(); }
510
511//- /foo.rs
512mod bar;
513//- /foo/bar.rs
514pub fn baz() {}
515",
516 r"$0pub(crate) mod bar;
517",
518 );
519 }
520
521 #[test]
522 fn fix_visibility_of_module_declaration_in_other_file() {
523 check_assist(
524 fix_visibility,
525 r"
526//- /main.rs
527mod foo;
528fn main() { foo::bar$0>::baz(); }
529
530//- /foo.rs
531mod bar {
532 pub fn baz() {}
533}
534",
535 r"$0pub(crate) mod bar {
536 pub fn baz() {}
537}
538",
539 );
540 }
541
542 #[test]
543 fn adds_pub_when_target_is_in_another_crate() {
544 check_assist(
545 fix_visibility,
546 r"
547//- /main.rs crate:a deps:foo
548foo::Bar$0
549//- /lib.rs crate:foo
550struct Bar;
551",
552 r"$0pub struct Bar;
553",
554 )
555 }
556
557 #[test]
558 fn replaces_pub_crate_with_pub() {
559 check_assist(
560 fix_visibility,
561 r"
562//- /main.rs crate:a deps:foo
563foo::Bar$0
564//- /lib.rs crate:foo
565pub(crate) struct Bar;
566",
567 r"$0pub struct Bar;
568",
569 );
570 check_assist(
571 fix_visibility,
572 r"
573//- /main.rs crate:a deps:foo
574fn main() {
575 foo::Foo { $0bar: () };
576}
577//- /lib.rs crate:foo
578pub struct Foo { pub(crate) bar: () }
579",
580 r"pub struct Foo { $0pub bar: () }
581",
582 );
583 }
584
585 #[test]
586 #[ignore]
587 // FIXME handle reexports properly
588 fn fix_visibility_of_reexport() {
589 check_assist(
590 fix_visibility,
591 r"
592 mod foo {
593 use bar::Baz;
594 mod bar { pub(super) struct Baz; }
595 }
596 foo::Baz$0
597 ",
598 r"
599 mod foo {
600 $0pub(crate) use bar::Baz;
601 mod bar { pub(super) struct Baz; }
602 }
603 foo::Baz
604 ",
605 )
606 }
607}
diff --git a/crates/ide_assists/src/handlers/flip_binexpr.rs b/crates/ide_assists/src/handlers/flip_binexpr.rs
new file mode 100644
index 000000000..209e5d43c
--- /dev/null
+++ b/crates/ide_assists/src/handlers/flip_binexpr.rs
@@ -0,0 +1,134 @@
1use syntax::ast::{AstNode, BinExpr, BinOp};
2
3use crate::{AssistContext, AssistId, AssistKind, Assists};
4
5// Assist: flip_binexpr
6//
7// Flips operands of a binary expression.
8//
9// ```
10// fn main() {
11// let _ = 90 +$0 2;
12// }
13// ```
14// ->
15// ```
16// fn main() {
17// let _ = 2 + 90;
18// }
19// ```
20pub(crate) fn flip_binexpr(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
21 let expr = ctx.find_node_at_offset::<BinExpr>()?;
22 let lhs = expr.lhs()?.syntax().clone();
23 let rhs = expr.rhs()?.syntax().clone();
24 let op_range = expr.op_token()?.text_range();
25 // The assist should be applied only if the cursor is on the operator
26 let cursor_in_range = op_range.contains_range(ctx.frange.range);
27 if !cursor_in_range {
28 return None;
29 }
30 let action: FlipAction = expr.op_kind()?.into();
31 // The assist should not be applied for certain operators
32 if let FlipAction::DontFlip = action {
33 return None;
34 }
35
36 acc.add(
37 AssistId("flip_binexpr", AssistKind::RefactorRewrite),
38 "Flip binary expression",
39 op_range,
40 |edit| {
41 if let FlipAction::FlipAndReplaceOp(new_op) = action {
42 edit.replace(op_range, new_op);
43 }
44 edit.replace(lhs.text_range(), rhs.text());
45 edit.replace(rhs.text_range(), lhs.text());
46 },
47 )
48}
49
50enum FlipAction {
51 // Flip the expression
52 Flip,
53 // Flip the expression and replace the operator with this string
54 FlipAndReplaceOp(&'static str),
55 // Do not flip the expression
56 DontFlip,
57}
58
59impl From<BinOp> for FlipAction {
60 fn from(op_kind: BinOp) -> Self {
61 match op_kind {
62 kind if kind.is_assignment() => FlipAction::DontFlip,
63 BinOp::GreaterTest => FlipAction::FlipAndReplaceOp("<"),
64 BinOp::GreaterEqualTest => FlipAction::FlipAndReplaceOp("<="),
65 BinOp::LesserTest => FlipAction::FlipAndReplaceOp(">"),
66 BinOp::LesserEqualTest => FlipAction::FlipAndReplaceOp(">="),
67 _ => FlipAction::Flip,
68 }
69 }
70}
71
72#[cfg(test)]
73mod tests {
74 use super::*;
75
76 use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target};
77
78 #[test]
79 fn flip_binexpr_target_is_the_op() {
80 check_assist_target(flip_binexpr, "fn f() { let res = 1 ==$0 2; }", "==")
81 }
82
83 #[test]
84 fn flip_binexpr_not_applicable_for_assignment() {
85 check_assist_not_applicable(flip_binexpr, "fn f() { let mut _x = 1; _x +=$0 2 }")
86 }
87
88 #[test]
89 fn flip_binexpr_works_for_eq() {
90 check_assist(flip_binexpr, "fn f() { let res = 1 ==$0 2; }", "fn f() { let res = 2 == 1; }")
91 }
92
93 #[test]
94 fn flip_binexpr_works_for_gt() {
95 check_assist(flip_binexpr, "fn f() { let res = 1 >$0 2; }", "fn f() { let res = 2 < 1; }")
96 }
97
98 #[test]
99 fn flip_binexpr_works_for_lteq() {
100 check_assist(flip_binexpr, "fn f() { let res = 1 <=$0 2; }", "fn f() { let res = 2 >= 1; }")
101 }
102
103 #[test]
104 fn flip_binexpr_works_for_complex_expr() {
105 check_assist(
106 flip_binexpr,
107 "fn f() { let res = (1 + 1) ==$0 (2 + 2); }",
108 "fn f() { let res = (2 + 2) == (1 + 1); }",
109 )
110 }
111
112 #[test]
113 fn flip_binexpr_works_inside_match() {
114 check_assist(
115 flip_binexpr,
116 r#"
117 fn dyn_eq(&self, other: &dyn Diagnostic) -> bool {
118 match other.downcast_ref::<Self>() {
119 None => false,
120 Some(it) => it ==$0 self,
121 }
122 }
123 "#,
124 r#"
125 fn dyn_eq(&self, other: &dyn Diagnostic) -> bool {
126 match other.downcast_ref::<Self>() {
127 None => false,
128 Some(it) => self == it,
129 }
130 }
131 "#,
132 )
133 }
134}
diff --git a/crates/ide_assists/src/handlers/flip_comma.rs b/crates/ide_assists/src/handlers/flip_comma.rs
new file mode 100644
index 000000000..18cf64a34
--- /dev/null
+++ b/crates/ide_assists/src/handlers/flip_comma.rs
@@ -0,0 +1,84 @@
1use syntax::{algo::non_trivia_sibling, Direction, T};
2
3use crate::{AssistContext, AssistId, AssistKind, Assists};
4
5// Assist: flip_comma
6//
7// Flips two comma-separated items.
8//
9// ```
10// fn main() {
11// ((1, 2),$0 (3, 4));
12// }
13// ```
14// ->
15// ```
16// fn main() {
17// ((3, 4), (1, 2));
18// }
19// ```
20pub(crate) fn flip_comma(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
21 let comma = ctx.find_token_syntax_at_offset(T![,])?;
22 let prev = non_trivia_sibling(comma.clone().into(), Direction::Prev)?;
23 let next = non_trivia_sibling(comma.clone().into(), Direction::Next)?;
24
25 // Don't apply a "flip" in case of a last comma
26 // that typically comes before punctuation
27 if next.kind().is_punct() {
28 return None;
29 }
30
31 acc.add(
32 AssistId("flip_comma", AssistKind::RefactorRewrite),
33 "Flip comma",
34 comma.text_range(),
35 |edit| {
36 edit.replace(prev.text_range(), next.to_string());
37 edit.replace(next.text_range(), prev.to_string());
38 },
39 )
40}
41
42#[cfg(test)]
43mod tests {
44 use super::*;
45
46 use crate::tests::{check_assist, check_assist_target};
47
48 #[test]
49 fn flip_comma_works_for_function_parameters() {
50 check_assist(
51 flip_comma,
52 r#"fn foo(x: i32,$0 y: Result<(), ()>) {}"#,
53 r#"fn foo(y: Result<(), ()>, x: i32) {}"#,
54 )
55 }
56
57 #[test]
58 fn flip_comma_target() {
59 check_assist_target(flip_comma, r#"fn foo(x: i32,$0 y: Result<(), ()>) {}"#, ",")
60 }
61
62 #[test]
63 #[should_panic]
64 fn flip_comma_before_punct() {
65 // See https://github.com/rust-analyzer/rust-analyzer/issues/1619
66 // "Flip comma" assist shouldn't be applicable to the last comma in enum or struct
67 // declaration body.
68 check_assist_target(
69 flip_comma,
70 "pub enum Test { \
71 A,$0 \
72 }",
73 ",",
74 );
75
76 check_assist_target(
77 flip_comma,
78 "pub struct Test { \
79 foo: usize,$0 \
80 }",
81 ",",
82 );
83 }
84}
diff --git a/crates/ide_assists/src/handlers/flip_trait_bound.rs b/crates/ide_assists/src/handlers/flip_trait_bound.rs
new file mode 100644
index 000000000..d419d263e
--- /dev/null
+++ b/crates/ide_assists/src/handlers/flip_trait_bound.rs
@@ -0,0 +1,121 @@
1use syntax::{
2 algo::non_trivia_sibling,
3 ast::{self, AstNode},
4 Direction, T,
5};
6
7use crate::{AssistContext, AssistId, AssistKind, Assists};
8
9// Assist: flip_trait_bound
10//
11// Flips two trait bounds.
12//
13// ```
14// fn foo<T: Clone +$0 Copy>() { }
15// ```
16// ->
17// ```
18// fn foo<T: Copy + Clone>() { }
19// ```
20pub(crate) fn flip_trait_bound(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
21 // We want to replicate the behavior of `flip_binexpr` by only suggesting
22 // the assist when the cursor is on a `+`
23 let plus = ctx.find_token_syntax_at_offset(T![+])?;
24
25 // Make sure we're in a `TypeBoundList`
26 if ast::TypeBoundList::cast(plus.parent()).is_none() {
27 return None;
28 }
29
30 let (before, after) = (
31 non_trivia_sibling(plus.clone().into(), Direction::Prev)?,
32 non_trivia_sibling(plus.clone().into(), Direction::Next)?,
33 );
34
35 let target = plus.text_range();
36 acc.add(
37 AssistId("flip_trait_bound", AssistKind::RefactorRewrite),
38 "Flip trait bounds",
39 target,
40 |edit| {
41 edit.replace(before.text_range(), after.to_string());
42 edit.replace(after.text_range(), before.to_string());
43 },
44 )
45}
46
47#[cfg(test)]
48mod tests {
49 use super::*;
50
51 use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target};
52
53 #[test]
54 fn flip_trait_bound_assist_available() {
55 check_assist_target(flip_trait_bound, "struct S<T> where T: A $0+ B + C { }", "+")
56 }
57
58 #[test]
59 fn flip_trait_bound_not_applicable_for_single_trait_bound() {
60 check_assist_not_applicable(flip_trait_bound, "struct S<T> where T: $0A { }")
61 }
62
63 #[test]
64 fn flip_trait_bound_works_for_struct() {
65 check_assist(
66 flip_trait_bound,
67 "struct S<T> where T: A $0+ B { }",
68 "struct S<T> where T: B + A { }",
69 )
70 }
71
72 #[test]
73 fn flip_trait_bound_works_for_trait_impl() {
74 check_assist(
75 flip_trait_bound,
76 "impl X for S<T> where T: A +$0 B { }",
77 "impl X for S<T> where T: B + A { }",
78 )
79 }
80
81 #[test]
82 fn flip_trait_bound_works_for_fn() {
83 check_assist(flip_trait_bound, "fn f<T: A $0+ B>(t: T) { }", "fn f<T: B + A>(t: T) { }")
84 }
85
86 #[test]
87 fn flip_trait_bound_works_for_fn_where_clause() {
88 check_assist(
89 flip_trait_bound,
90 "fn f<T>(t: T) where T: A +$0 B { }",
91 "fn f<T>(t: T) where T: B + A { }",
92 )
93 }
94
95 #[test]
96 fn flip_trait_bound_works_for_lifetime() {
97 check_assist(
98 flip_trait_bound,
99 "fn f<T>(t: T) where T: A $0+ 'static { }",
100 "fn f<T>(t: T) where T: 'static + A { }",
101 )
102 }
103
104 #[test]
105 fn flip_trait_bound_works_for_complex_bounds() {
106 check_assist(
107 flip_trait_bound,
108 "struct S<T> where T: A<T> $0+ b_mod::B<T> + C<T> { }",
109 "struct S<T> where T: b_mod::B<T> + A<T> + C<T> { }",
110 )
111 }
112
113 #[test]
114 fn flip_trait_bound_works_for_long_bounds() {
115 check_assist(
116 flip_trait_bound,
117 "struct S<T> where T: A + B + C + D + E + F +$0 G + H + I + J { }",
118 "struct S<T> where T: A + B + C + D + E + G + F + H + I + J { }",
119 )
120 }
121}
diff --git a/crates/ide_assists/src/handlers/generate_default_from_enum_variant.rs b/crates/ide_assists/src/handlers/generate_default_from_enum_variant.rs
new file mode 100644
index 000000000..6a2ab9596
--- /dev/null
+++ b/crates/ide_assists/src/handlers/generate_default_from_enum_variant.rs
@@ -0,0 +1,175 @@
1use ide_db::helpers::FamousDefs;
2use ide_db::RootDatabase;
3use syntax::ast::{self, AstNode, NameOwner};
4use test_utils::mark;
5
6use crate::{AssistContext, AssistId, AssistKind, Assists};
7
8// Assist: generate_default_from_enum_variant
9//
10// Adds a Default impl for an enum using a variant.
11//
12// ```
13// enum Version {
14// Undefined,
15// Minor$0,
16// Major,
17// }
18// ```
19// ->
20// ```
21// enum Version {
22// Undefined,
23// Minor,
24// Major,
25// }
26//
27// impl Default for Version {
28// fn default() -> Self {
29// Self::Minor
30// }
31// }
32// ```
33pub(crate) fn generate_default_from_enum_variant(
34 acc: &mut Assists,
35 ctx: &AssistContext,
36) -> Option<()> {
37 let variant = ctx.find_node_at_offset::<ast::Variant>()?;
38 let variant_name = variant.name()?;
39 let enum_name = variant.parent_enum().name()?;
40 if !matches!(variant.kind(), ast::StructKind::Unit) {
41 mark::hit!(test_gen_default_on_non_unit_variant_not_implemented);
42 return None;
43 }
44
45 if existing_default_impl(&ctx.sema, &variant).is_some() {
46 mark::hit!(test_gen_default_impl_already_exists);
47 return None;
48 }
49
50 let target = variant.syntax().text_range();
51 acc.add(
52 AssistId("generate_default_from_enum_variant", AssistKind::Generate),
53 "Generate `Default` impl from this enum variant",
54 target,
55 |edit| {
56 let start_offset = variant.parent_enum().syntax().text_range().end();
57 let buf = format!(
58 r#"
59
60impl Default for {0} {{
61 fn default() -> Self {{
62 Self::{1}
63 }}
64}}"#,
65 enum_name, variant_name
66 );
67 edit.insert(start_offset, buf);
68 },
69 )
70}
71
72fn existing_default_impl(
73 sema: &'_ hir::Semantics<'_, RootDatabase>,
74 variant: &ast::Variant,
75) -> Option<()> {
76 let variant = sema.to_def(variant)?;
77 let enum_ = variant.parent_enum(sema.db);
78 let krate = enum_.module(sema.db).krate();
79
80 let default_trait = FamousDefs(sema, Some(krate)).core_default_Default()?;
81 let enum_type = enum_.ty(sema.db);
82
83 if enum_type.impls_trait(sema.db, default_trait, &[]) {
84 Some(())
85 } else {
86 None
87 }
88}
89
90#[cfg(test)]
91mod tests {
92 use test_utils::mark;
93
94 use crate::tests::{check_assist, check_assist_not_applicable};
95
96 use super::*;
97
98 fn check_not_applicable(ra_fixture: &str) {
99 let fixture =
100 format!("//- /main.rs crate:main deps:core\n{}\n{}", ra_fixture, FamousDefs::FIXTURE);
101 check_assist_not_applicable(generate_default_from_enum_variant, &fixture)
102 }
103
104 #[test]
105 fn test_generate_default_from_variant() {
106 check_assist(
107 generate_default_from_enum_variant,
108 r#"
109enum Variant {
110 Undefined,
111 Minor$0,
112 Major,
113}"#,
114 r#"enum Variant {
115 Undefined,
116 Minor,
117 Major,
118}
119
120impl Default for Variant {
121 fn default() -> Self {
122 Self::Minor
123 }
124}"#,
125 );
126 }
127
128 #[test]
129 fn test_generate_default_already_implemented() {
130 mark::check!(test_gen_default_impl_already_exists);
131 check_not_applicable(
132 r#"
133enum Variant {
134 Undefined,
135 Minor$0,
136 Major,
137}
138
139impl Default for Variant {
140 fn default() -> Self {
141 Self::Minor
142 }
143}"#,
144 );
145 }
146
147 #[test]
148 fn test_add_from_impl_no_element() {
149 mark::check!(test_gen_default_on_non_unit_variant_not_implemented);
150 check_not_applicable(
151 r#"
152enum Variant {
153 Undefined,
154 Minor(u32)$0,
155 Major,
156}"#,
157 );
158 }
159
160 #[test]
161 fn test_generate_default_from_variant_with_one_variant() {
162 check_assist(
163 generate_default_from_enum_variant,
164 r#"enum Variant { Undefi$0ned }"#,
165 r#"
166enum Variant { Undefined }
167
168impl Default for Variant {
169 fn default() -> Self {
170 Self::Undefined
171 }
172}"#,
173 );
174 }
175}
diff --git a/crates/ide_assists/src/handlers/generate_derive.rs b/crates/ide_assists/src/handlers/generate_derive.rs
new file mode 100644
index 000000000..adae8ab7e
--- /dev/null
+++ b/crates/ide_assists/src/handlers/generate_derive.rs
@@ -0,0 +1,132 @@
1use syntax::{
2 ast::{self, AstNode, AttrsOwner},
3 SyntaxKind::{COMMENT, WHITESPACE},
4 TextSize,
5};
6
7use crate::{AssistContext, AssistId, AssistKind, Assists};
8
9// Assist: generate_derive
10//
11// Adds a new `#[derive()]` clause to a struct or enum.
12//
13// ```
14// struct Point {
15// x: u32,
16// y: u32,$0
17// }
18// ```
19// ->
20// ```
21// #[derive($0)]
22// struct Point {
23// x: u32,
24// y: u32,
25// }
26// ```
27pub(crate) fn generate_derive(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
28 let cap = ctx.config.snippet_cap?;
29 let nominal = ctx.find_node_at_offset::<ast::Adt>()?;
30 let node_start = derive_insertion_offset(&nominal)?;
31 let target = nominal.syntax().text_range();
32 acc.add(
33 AssistId("generate_derive", AssistKind::Generate),
34 "Add `#[derive]`",
35 target,
36 |builder| {
37 let derive_attr = nominal
38 .attrs()
39 .filter_map(|x| x.as_simple_call())
40 .filter(|(name, _arg)| name == "derive")
41 .map(|(_name, arg)| arg)
42 .next();
43 match derive_attr {
44 None => {
45 builder.insert_snippet(cap, node_start, "#[derive($0)]\n");
46 }
47 Some(tt) => {
48 // Just move the cursor.
49 builder.insert_snippet(
50 cap,
51 tt.syntax().text_range().end() - TextSize::of(')'),
52 "$0",
53 )
54 }
55 };
56 },
57 )
58}
59
60// Insert `derive` after doc comments.
61fn derive_insertion_offset(nominal: &ast::Adt) -> Option<TextSize> {
62 let non_ws_child = nominal
63 .syntax()
64 .children_with_tokens()
65 .find(|it| it.kind() != COMMENT && it.kind() != WHITESPACE)?;
66 Some(non_ws_child.text_range().start())
67}
68
69#[cfg(test)]
70mod tests {
71 use crate::tests::{check_assist, check_assist_target};
72
73 use super::*;
74
75 #[test]
76 fn add_derive_new() {
77 check_assist(
78 generate_derive,
79 "struct Foo { a: i32, $0}",
80 "#[derive($0)]\nstruct Foo { a: i32, }",
81 );
82 check_assist(
83 generate_derive,
84 "struct Foo { $0 a: i32, }",
85 "#[derive($0)]\nstruct Foo { a: i32, }",
86 );
87 }
88
89 #[test]
90 fn add_derive_existing() {
91 check_assist(
92 generate_derive,
93 "#[derive(Clone)]\nstruct Foo { a: i32$0, }",
94 "#[derive(Clone$0)]\nstruct Foo { a: i32, }",
95 );
96 }
97
98 #[test]
99 fn add_derive_new_with_doc_comment() {
100 check_assist(
101 generate_derive,
102 "
103/// `Foo` is a pretty important struct.
104/// It does stuff.
105struct Foo { a: i32$0, }
106 ",
107 "
108/// `Foo` is a pretty important struct.
109/// It does stuff.
110#[derive($0)]
111struct Foo { a: i32, }
112 ",
113 );
114 }
115
116 #[test]
117 fn add_derive_target() {
118 check_assist_target(
119 generate_derive,
120 "
121struct SomeThingIrrelevant;
122/// `Foo` is a pretty important struct.
123/// It does stuff.
124struct Foo { a: i32$0, }
125struct EvenMoreIrrelevant;
126 ",
127 "/// `Foo` is a pretty important struct.
128/// It does stuff.
129struct Foo { a: i32, }",
130 );
131 }
132}
diff --git a/crates/ide_assists/src/handlers/generate_enum_match_method.rs b/crates/ide_assists/src/handlers/generate_enum_match_method.rs
new file mode 100644
index 000000000..aeb887e71
--- /dev/null
+++ b/crates/ide_assists/src/handlers/generate_enum_match_method.rs
@@ -0,0 +1,240 @@
1use stdx::{format_to, to_lower_snake_case};
2use syntax::ast::VisibilityOwner;
3use syntax::ast::{self, AstNode, NameOwner};
4use test_utils::mark;
5
6use crate::{
7 utils::{find_impl_block_end, find_struct_impl, generate_impl_text},
8 AssistContext, AssistId, AssistKind, Assists,
9};
10
11// Assist: generate_enum_match_method
12//
13// Generate an `is_` method for an enum variant.
14//
15// ```
16// enum Version {
17// Undefined,
18// Minor$0,
19// Major,
20// }
21// ```
22// ->
23// ```
24// enum Version {
25// Undefined,
26// Minor,
27// Major,
28// }
29//
30// impl Version {
31// /// Returns `true` if the version is [`Minor`].
32// fn is_minor(&self) -> bool {
33// matches!(self, Self::Minor)
34// }
35// }
36// ```
37pub(crate) fn generate_enum_match_method(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
38 let variant = ctx.find_node_at_offset::<ast::Variant>()?;
39 let variant_name = variant.name()?;
40 let parent_enum = variant.parent_enum();
41 if !matches!(variant.kind(), ast::StructKind::Unit) {
42 mark::hit!(test_gen_enum_match_on_non_unit_variant_not_implemented);
43 return None;
44 }
45
46 let enum_lowercase_name = to_lower_snake_case(&parent_enum.name()?.to_string());
47 let fn_name = to_lower_snake_case(&variant_name.to_string());
48
49 // Return early if we've found an existing new fn
50 let impl_def = find_struct_impl(
51 &ctx,
52 &ast::Adt::Enum(parent_enum.clone()),
53 format!("is_{}", fn_name).as_str(),
54 )?;
55
56 let target = variant.syntax().text_range();
57 acc.add(
58 AssistId("generate_enum_match_method", AssistKind::Generate),
59 "Generate an `is_` method for an enum variant",
60 target,
61 |builder| {
62 let mut buf = String::with_capacity(512);
63
64 if impl_def.is_some() {
65 buf.push('\n');
66 }
67
68 let vis = parent_enum.visibility().map_or(String::new(), |v| format!("{} ", v));
69 format_to!(
70 buf,
71 " /// Returns `true` if the {} is [`{}`].
72 {}fn is_{}(&self) -> bool {{
73 matches!(self, Self::{})
74 }}",
75 enum_lowercase_name,
76 variant_name,
77 vis,
78 fn_name,
79 variant_name
80 );
81
82 let start_offset = impl_def
83 .and_then(|impl_def| find_impl_block_end(impl_def, &mut buf))
84 .unwrap_or_else(|| {
85 buf = generate_impl_text(&ast::Adt::Enum(parent_enum.clone()), &buf);
86 parent_enum.syntax().text_range().end()
87 });
88
89 builder.insert(start_offset, buf);
90 },
91 )
92}
93
94#[cfg(test)]
95mod tests {
96 use test_utils::mark;
97
98 use crate::tests::{check_assist, check_assist_not_applicable};
99
100 use super::*;
101
102 fn check_not_applicable(ra_fixture: &str) {
103 check_assist_not_applicable(generate_enum_match_method, ra_fixture)
104 }
105
106 #[test]
107 fn test_generate_enum_match_from_variant() {
108 check_assist(
109 generate_enum_match_method,
110 r#"
111enum Variant {
112 Undefined,
113 Minor$0,
114 Major,
115}"#,
116 r#"enum Variant {
117 Undefined,
118 Minor,
119 Major,
120}
121
122impl Variant {
123 /// Returns `true` if the variant is [`Minor`].
124 fn is_minor(&self) -> bool {
125 matches!(self, Self::Minor)
126 }
127}"#,
128 );
129 }
130
131 #[test]
132 fn test_generate_enum_match_already_implemented() {
133 check_not_applicable(
134 r#"
135enum Variant {
136 Undefined,
137 Minor$0,
138 Major,
139}
140
141impl Variant {
142 fn is_minor(&self) -> bool {
143 matches!(self, Self::Minor)
144 }
145}"#,
146 );
147 }
148
149 #[test]
150 fn test_add_from_impl_no_element() {
151 mark::check!(test_gen_enum_match_on_non_unit_variant_not_implemented);
152 check_not_applicable(
153 r#"
154enum Variant {
155 Undefined,
156 Minor(u32)$0,
157 Major,
158}"#,
159 );
160 }
161
162 #[test]
163 fn test_generate_enum_match_from_variant_with_one_variant() {
164 check_assist(
165 generate_enum_match_method,
166 r#"enum Variant { Undefi$0ned }"#,
167 r#"
168enum Variant { Undefined }
169
170impl Variant {
171 /// Returns `true` if the variant is [`Undefined`].
172 fn is_undefined(&self) -> bool {
173 matches!(self, Self::Undefined)
174 }
175}"#,
176 );
177 }
178
179 #[test]
180 fn test_generate_enum_match_from_variant_with_visibility_marker() {
181 check_assist(
182 generate_enum_match_method,
183 r#"
184pub(crate) enum Variant {
185 Undefined,
186 Minor$0,
187 Major,
188}"#,
189 r#"pub(crate) enum Variant {
190 Undefined,
191 Minor,
192 Major,
193}
194
195impl Variant {
196 /// Returns `true` if the variant is [`Minor`].
197 pub(crate) fn is_minor(&self) -> bool {
198 matches!(self, Self::Minor)
199 }
200}"#,
201 );
202 }
203
204 #[test]
205 fn test_multiple_generate_enum_match_from_variant() {
206 check_assist(
207 generate_enum_match_method,
208 r#"
209enum Variant {
210 Undefined,
211 Minor,
212 Major$0,
213}
214
215impl Variant {
216 /// Returns `true` if the variant is [`Minor`].
217 fn is_minor(&self) -> bool {
218 matches!(self, Self::Minor)
219 }
220}"#,
221 r#"enum Variant {
222 Undefined,
223 Minor,
224 Major,
225}
226
227impl Variant {
228 /// Returns `true` if the variant is [`Minor`].
229 fn is_minor(&self) -> bool {
230 matches!(self, Self::Minor)
231 }
232
233 /// Returns `true` if the variant is [`Major`].
234 fn is_major(&self) -> bool {
235 matches!(self, Self::Major)
236 }
237}"#,
238 );
239 }
240}
diff --git a/crates/ide_assists/src/handlers/generate_from_impl_for_enum.rs b/crates/ide_assists/src/handlers/generate_from_impl_for_enum.rs
new file mode 100644
index 000000000..d9388a737
--- /dev/null
+++ b/crates/ide_assists/src/handlers/generate_from_impl_for_enum.rs
@@ -0,0 +1,268 @@
1use ide_db::helpers::FamousDefs;
2use ide_db::RootDatabase;
3use syntax::ast::{self, AstNode, NameOwner};
4use test_utils::mark;
5
6use crate::{utils::generate_trait_impl_text, AssistContext, AssistId, AssistKind, Assists};
7
8// Assist: generate_from_impl_for_enum
9//
10// Adds a From impl for an enum variant with one tuple field.
11//
12// ```
13// enum A { $0One(u32) }
14// ```
15// ->
16// ```
17// enum A { One(u32) }
18//
19// impl From<u32> for A {
20// fn from(v: u32) -> Self {
21// Self::One(v)
22// }
23// }
24// ```
25pub(crate) fn generate_from_impl_for_enum(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
26 let variant = ctx.find_node_at_offset::<ast::Variant>()?;
27 let variant_name = variant.name()?;
28 let enum_ = ast::Adt::Enum(variant.parent_enum());
29 let (field_name, field_type) = match variant.kind() {
30 ast::StructKind::Tuple(field_list) => {
31 if field_list.fields().count() != 1 {
32 return None;
33 }
34 (None, field_list.fields().next()?.ty()?)
35 }
36 ast::StructKind::Record(field_list) => {
37 if field_list.fields().count() != 1 {
38 return None;
39 }
40 let field = field_list.fields().next()?;
41 (Some(field.name()?), field.ty()?)
42 }
43 ast::StructKind::Unit => return None,
44 };
45
46 if existing_from_impl(&ctx.sema, &variant).is_some() {
47 mark::hit!(test_add_from_impl_already_exists);
48 return None;
49 }
50
51 let target = variant.syntax().text_range();
52 acc.add(
53 AssistId("generate_from_impl_for_enum", AssistKind::Generate),
54 "Generate `From` impl for this enum variant",
55 target,
56 |edit| {
57 let start_offset = variant.parent_enum().syntax().text_range().end();
58 let from_trait = format!("From<{}>", field_type.syntax());
59 let impl_code = if let Some(name) = field_name {
60 format!(
61 r#" fn from({0}: {1}) -> Self {{
62 Self::{2} {{ {0} }}
63 }}"#,
64 name.text(),
65 field_type.syntax(),
66 variant_name,
67 )
68 } else {
69 format!(
70 r#" fn from(v: {}) -> Self {{
71 Self::{}(v)
72 }}"#,
73 field_type.syntax(),
74 variant_name,
75 )
76 };
77 let from_impl = generate_trait_impl_text(&enum_, &from_trait, &impl_code);
78 edit.insert(start_offset, from_impl);
79 },
80 )
81}
82
83fn existing_from_impl(
84 sema: &'_ hir::Semantics<'_, RootDatabase>,
85 variant: &ast::Variant,
86) -> Option<()> {
87 let variant = sema.to_def(variant)?;
88 let enum_ = variant.parent_enum(sema.db);
89 let krate = enum_.module(sema.db).krate();
90
91 let from_trait = FamousDefs(sema, Some(krate)).core_convert_From()?;
92
93 let enum_type = enum_.ty(sema.db);
94
95 let wrapped_type = variant.fields(sema.db).get(0)?.signature_ty(sema.db);
96
97 if enum_type.impls_trait(sema.db, from_trait, &[wrapped_type]) {
98 Some(())
99 } else {
100 None
101 }
102}
103
104#[cfg(test)]
105mod tests {
106 use test_utils::mark;
107
108 use crate::tests::{check_assist, check_assist_not_applicable};
109
110 use super::*;
111
112 #[test]
113 fn test_generate_from_impl_for_enum() {
114 check_assist(
115 generate_from_impl_for_enum,
116 "enum A { $0One(u32) }",
117 r#"enum A { One(u32) }
118
119impl From<u32> for A {
120 fn from(v: u32) -> Self {
121 Self::One(v)
122 }
123}"#,
124 );
125 }
126
127 #[test]
128 fn test_generate_from_impl_for_enum_complicated_path() {
129 check_assist(
130 generate_from_impl_for_enum,
131 r#"enum A { $0One(foo::bar::baz::Boo) }"#,
132 r#"enum A { One(foo::bar::baz::Boo) }
133
134impl From<foo::bar::baz::Boo> for A {
135 fn from(v: foo::bar::baz::Boo) -> Self {
136 Self::One(v)
137 }
138}"#,
139 );
140 }
141
142 fn check_not_applicable(ra_fixture: &str) {
143 let fixture =
144 format!("//- /main.rs crate:main deps:core\n{}\n{}", ra_fixture, FamousDefs::FIXTURE);
145 check_assist_not_applicable(generate_from_impl_for_enum, &fixture)
146 }
147
148 #[test]
149 fn test_add_from_impl_no_element() {
150 check_not_applicable("enum A { $0One }");
151 }
152
153 #[test]
154 fn test_add_from_impl_more_than_one_element_in_tuple() {
155 check_not_applicable("enum A { $0One(u32, String) }");
156 }
157
158 #[test]
159 fn test_add_from_impl_struct_variant() {
160 check_assist(
161 generate_from_impl_for_enum,
162 "enum A { $0One { x: u32 } }",
163 r#"enum A { One { x: u32 } }
164
165impl From<u32> for A {
166 fn from(x: u32) -> Self {
167 Self::One { x }
168 }
169}"#,
170 );
171 }
172
173 #[test]
174 fn test_add_from_impl_already_exists() {
175 mark::check!(test_add_from_impl_already_exists);
176 check_not_applicable(
177 r#"
178enum A { $0One(u32), }
179
180impl From<u32> for A {
181 fn from(v: u32) -> Self {
182 Self::One(v)
183 }
184}
185"#,
186 );
187 }
188
189 #[test]
190 fn test_add_from_impl_different_variant_impl_exists() {
191 check_assist(
192 generate_from_impl_for_enum,
193 r#"enum A { $0One(u32), Two(String), }
194
195impl From<String> for A {
196 fn from(v: String) -> Self {
197 A::Two(v)
198 }
199}
200
201pub trait From<T> {
202 fn from(T) -> Self;
203}"#,
204 r#"enum A { One(u32), Two(String), }
205
206impl From<u32> for A {
207 fn from(v: u32) -> Self {
208 Self::One(v)
209 }
210}
211
212impl From<String> for A {
213 fn from(v: String) -> Self {
214 A::Two(v)
215 }
216}
217
218pub trait From<T> {
219 fn from(T) -> Self;
220}"#,
221 );
222 }
223
224 #[test]
225 fn test_add_from_impl_static_str() {
226 check_assist(
227 generate_from_impl_for_enum,
228 "enum A { $0One(&'static str) }",
229 r#"enum A { One(&'static str) }
230
231impl From<&'static str> for A {
232 fn from(v: &'static str) -> Self {
233 Self::One(v)
234 }
235}"#,
236 );
237 }
238
239 #[test]
240 fn test_add_from_impl_generic_enum() {
241 check_assist(
242 generate_from_impl_for_enum,
243 "enum Generic<T, U: Clone> { $0One(T), Two(U) }",
244 r#"enum Generic<T, U: Clone> { One(T), Two(U) }
245
246impl<T, U: Clone> From<T> for Generic<T, U> {
247 fn from(v: T) -> Self {
248 Self::One(v)
249 }
250}"#,
251 );
252 }
253
254 #[test]
255 fn test_add_from_impl_with_lifetime() {
256 check_assist(
257 generate_from_impl_for_enum,
258 "enum Generic<'a> { $0One(&'a i32) }",
259 r#"enum Generic<'a> { One(&'a i32) }
260
261impl<'a> From<&'a i32> for Generic<'a> {
262 fn from(v: &'a i32) -> Self {
263 Self::One(v)
264 }
265}"#,
266 );
267 }
268}
diff --git a/crates/ide_assists/src/handlers/generate_function.rs b/crates/ide_assists/src/handlers/generate_function.rs
new file mode 100644
index 000000000..959824981
--- /dev/null
+++ b/crates/ide_assists/src/handlers/generate_function.rs
@@ -0,0 +1,1071 @@
1use hir::HirDisplay;
2use ide_db::{base_db::FileId, helpers::SnippetCap};
3use rustc_hash::{FxHashMap, FxHashSet};
4use syntax::{
5 ast::{
6 self,
7 edit::{AstNodeEdit, IndentLevel},
8 make, ArgListOwner, AstNode, ModuleItemOwner,
9 },
10 SyntaxKind, SyntaxNode, TextSize,
11};
12
13use crate::{
14 utils::{render_snippet, Cursor},
15 AssistContext, AssistId, AssistKind, Assists,
16};
17
18// Assist: generate_function
19//
20// Adds a stub function with a signature matching the function under the cursor.
21//
22// ```
23// struct Baz;
24// fn baz() -> Baz { Baz }
25// fn foo() {
26// bar$0("", baz());
27// }
28//
29// ```
30// ->
31// ```
32// struct Baz;
33// fn baz() -> Baz { Baz }
34// fn foo() {
35// bar("", baz());
36// }
37//
38// fn bar(arg: &str, baz: Baz) ${0:-> ()} {
39// todo!()
40// }
41//
42// ```
43pub(crate) fn generate_function(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
44 let path_expr: ast::PathExpr = ctx.find_node_at_offset()?;
45 let call = path_expr.syntax().parent().and_then(ast::CallExpr::cast)?;
46 let path = path_expr.path()?;
47
48 if ctx.sema.resolve_path(&path).is_some() {
49 // The function call already resolves, no need to add a function
50 return None;
51 }
52
53 let target_module = match path.qualifier() {
54 Some(qualifier) => match ctx.sema.resolve_path(&qualifier) {
55 Some(hir::PathResolution::Def(hir::ModuleDef::Module(module))) => Some(module),
56 _ => return None,
57 },
58 None => None,
59 };
60
61 let function_builder = FunctionBuilder::from_call(&ctx, &call, &path, target_module)?;
62
63 let target = call.syntax().text_range();
64 acc.add(
65 AssistId("generate_function", AssistKind::Generate),
66 format!("Generate `{}` function", function_builder.fn_name),
67 target,
68 |builder| {
69 let function_template = function_builder.render();
70 builder.edit_file(function_template.file);
71 let new_fn = function_template.to_string(ctx.config.snippet_cap);
72 match ctx.config.snippet_cap {
73 Some(cap) => builder.insert_snippet(cap, function_template.insert_offset, new_fn),
74 None => builder.insert(function_template.insert_offset, new_fn),
75 }
76 },
77 )
78}
79
80struct FunctionTemplate {
81 insert_offset: TextSize,
82 leading_ws: String,
83 fn_def: ast::Fn,
84 ret_type: ast::RetType,
85 trailing_ws: String,
86 file: FileId,
87}
88
89impl FunctionTemplate {
90 fn to_string(&self, cap: Option<SnippetCap>) -> String {
91 let f = match cap {
92 Some(cap) => {
93 render_snippet(cap, self.fn_def.syntax(), Cursor::Replace(self.ret_type.syntax()))
94 }
95 None => self.fn_def.to_string(),
96 };
97 format!("{}{}{}", self.leading_ws, f, self.trailing_ws)
98 }
99}
100
101struct FunctionBuilder {
102 target: GeneratedFunctionTarget,
103 fn_name: ast::Name,
104 type_params: Option<ast::GenericParamList>,
105 params: ast::ParamList,
106 file: FileId,
107 needs_pub: bool,
108}
109
110impl FunctionBuilder {
111 /// Prepares a generated function that matches `call`.
112 /// The function is generated in `target_module` or next to `call`
113 fn from_call(
114 ctx: &AssistContext,
115 call: &ast::CallExpr,
116 path: &ast::Path,
117 target_module: Option<hir::Module>,
118 ) -> Option<Self> {
119 let mut file = ctx.frange.file_id;
120 let target = match &target_module {
121 Some(target_module) => {
122 let module_source = target_module.definition_source(ctx.db());
123 let (in_file, target) = next_space_for_fn_in_module(ctx.sema.db, &module_source)?;
124 file = in_file;
125 target
126 }
127 None => next_space_for_fn_after_call_site(&call)?,
128 };
129 let needs_pub = target_module.is_some();
130 let target_module = target_module.or_else(|| ctx.sema.scope(target.syntax()).module())?;
131 let fn_name = fn_name(&path)?;
132 let (type_params, params) = fn_args(ctx, target_module, &call)?;
133
134 Some(Self { target, fn_name, type_params, params, file, needs_pub })
135 }
136
137 fn render(self) -> FunctionTemplate {
138 let placeholder_expr = make::expr_todo();
139 let fn_body = make::block_expr(vec![], Some(placeholder_expr));
140 let visibility = if self.needs_pub { Some(make::visibility_pub_crate()) } else { None };
141 let mut fn_def = make::fn_(
142 visibility,
143 self.fn_name,
144 self.type_params,
145 self.params,
146 fn_body,
147 Some(make::ret_type(make::ty_unit())),
148 );
149 let leading_ws;
150 let trailing_ws;
151
152 let insert_offset = match self.target {
153 GeneratedFunctionTarget::BehindItem(it) => {
154 let indent = IndentLevel::from_node(&it);
155 leading_ws = format!("\n\n{}", indent);
156 fn_def = fn_def.indent(indent);
157 trailing_ws = String::new();
158 it.text_range().end()
159 }
160 GeneratedFunctionTarget::InEmptyItemList(it) => {
161 let indent = IndentLevel::from_node(&it);
162 leading_ws = format!("\n{}", indent + 1);
163 fn_def = fn_def.indent(indent + 1);
164 trailing_ws = format!("\n{}", indent);
165 it.text_range().start() + TextSize::of('{')
166 }
167 };
168
169 FunctionTemplate {
170 insert_offset,
171 leading_ws,
172 ret_type: fn_def.ret_type().unwrap(),
173 fn_def,
174 trailing_ws,
175 file: self.file,
176 }
177 }
178}
179
180enum GeneratedFunctionTarget {
181 BehindItem(SyntaxNode),
182 InEmptyItemList(SyntaxNode),
183}
184
185impl GeneratedFunctionTarget {
186 fn syntax(&self) -> &SyntaxNode {
187 match self {
188 GeneratedFunctionTarget::BehindItem(it) => it,
189 GeneratedFunctionTarget::InEmptyItemList(it) => it,
190 }
191 }
192}
193
194fn fn_name(call: &ast::Path) -> Option<ast::Name> {
195 let name = call.segment()?.syntax().to_string();
196 Some(make::name(&name))
197}
198
199/// Computes the type variables and arguments required for the generated function
200fn fn_args(
201 ctx: &AssistContext,
202 target_module: hir::Module,
203 call: &ast::CallExpr,
204) -> Option<(Option<ast::GenericParamList>, ast::ParamList)> {
205 let mut arg_names = Vec::new();
206 let mut arg_types = Vec::new();
207 for arg in call.arg_list()?.args() {
208 arg_names.push(match fn_arg_name(&arg) {
209 Some(name) => name,
210 None => String::from("arg"),
211 });
212 arg_types.push(match fn_arg_type(ctx, target_module, &arg) {
213 Some(ty) => ty,
214 None => String::from("()"),
215 });
216 }
217 deduplicate_arg_names(&mut arg_names);
218 let params = arg_names
219 .into_iter()
220 .zip(arg_types)
221 .map(|(name, ty)| make::param(make::ident_pat(make::name(&name)).into(), make::ty(&ty)));
222 Some((None, make::param_list(None, params)))
223}
224
225/// Makes duplicate argument names unique by appending incrementing numbers.
226///
227/// ```
228/// let mut names: Vec<String> =
229/// vec!["foo".into(), "foo".into(), "bar".into(), "baz".into(), "bar".into()];
230/// deduplicate_arg_names(&mut names);
231/// let expected: Vec<String> =
232/// vec!["foo_1".into(), "foo_2".into(), "bar_1".into(), "baz".into(), "bar_2".into()];
233/// assert_eq!(names, expected);
234/// ```
235fn deduplicate_arg_names(arg_names: &mut Vec<String>) {
236 let arg_name_counts = arg_names.iter().fold(FxHashMap::default(), |mut m, name| {
237 *m.entry(name).or_insert(0) += 1;
238 m
239 });
240 let duplicate_arg_names: FxHashSet<String> = arg_name_counts
241 .into_iter()
242 .filter(|(_, count)| *count >= 2)
243 .map(|(name, _)| name.clone())
244 .collect();
245
246 let mut counter_per_name = FxHashMap::default();
247 for arg_name in arg_names.iter_mut() {
248 if duplicate_arg_names.contains(arg_name) {
249 let counter = counter_per_name.entry(arg_name.clone()).or_insert(1);
250 arg_name.push('_');
251 arg_name.push_str(&counter.to_string());
252 *counter += 1;
253 }
254 }
255}
256
257fn fn_arg_name(fn_arg: &ast::Expr) -> Option<String> {
258 match fn_arg {
259 ast::Expr::CastExpr(cast_expr) => fn_arg_name(&cast_expr.expr()?),
260 _ => Some(
261 fn_arg
262 .syntax()
263 .descendants()
264 .filter(|d| ast::NameRef::can_cast(d.kind()))
265 .last()?
266 .to_string(),
267 ),
268 }
269}
270
271fn fn_arg_type(
272 ctx: &AssistContext,
273 target_module: hir::Module,
274 fn_arg: &ast::Expr,
275) -> Option<String> {
276 let ty = ctx.sema.type_of_expr(fn_arg)?;
277 if ty.is_unknown() {
278 return None;
279 }
280
281 if let Ok(rendered) = ty.display_source_code(ctx.db(), target_module.into()) {
282 Some(rendered)
283 } else {
284 None
285 }
286}
287
288/// Returns the position inside the current mod or file
289/// directly after the current block
290/// We want to write the generated function directly after
291/// fns, impls or macro calls, but inside mods
292fn next_space_for_fn_after_call_site(expr: &ast::CallExpr) -> Option<GeneratedFunctionTarget> {
293 let mut ancestors = expr.syntax().ancestors().peekable();
294 let mut last_ancestor: Option<SyntaxNode> = None;
295 while let Some(next_ancestor) = ancestors.next() {
296 match next_ancestor.kind() {
297 SyntaxKind::SOURCE_FILE => {
298 break;
299 }
300 SyntaxKind::ITEM_LIST => {
301 if ancestors.peek().map(|a| a.kind()) == Some(SyntaxKind::MODULE) {
302 break;
303 }
304 }
305 _ => {}
306 }
307 last_ancestor = Some(next_ancestor);
308 }
309 last_ancestor.map(GeneratedFunctionTarget::BehindItem)
310}
311
312fn next_space_for_fn_in_module(
313 db: &dyn hir::db::AstDatabase,
314 module_source: &hir::InFile<hir::ModuleSource>,
315) -> Option<(FileId, GeneratedFunctionTarget)> {
316 let file = module_source.file_id.original_file(db);
317 let assist_item = match &module_source.value {
318 hir::ModuleSource::SourceFile(it) => {
319 if let Some(last_item) = it.items().last() {
320 GeneratedFunctionTarget::BehindItem(last_item.syntax().clone())
321 } else {
322 GeneratedFunctionTarget::BehindItem(it.syntax().clone())
323 }
324 }
325 hir::ModuleSource::Module(it) => {
326 if let Some(last_item) = it.item_list().and_then(|it| it.items().last()) {
327 GeneratedFunctionTarget::BehindItem(last_item.syntax().clone())
328 } else {
329 GeneratedFunctionTarget::InEmptyItemList(it.item_list()?.syntax().clone())
330 }
331 }
332 hir::ModuleSource::BlockExpr(it) => {
333 if let Some(last_item) =
334 it.statements().take_while(|stmt| matches!(stmt, ast::Stmt::Item(_))).last()
335 {
336 GeneratedFunctionTarget::BehindItem(last_item.syntax().clone())
337 } else {
338 GeneratedFunctionTarget::InEmptyItemList(it.syntax().clone())
339 }
340 }
341 };
342 Some((file, assist_item))
343}
344
345#[cfg(test)]
346mod tests {
347 use crate::tests::{check_assist, check_assist_not_applicable};
348
349 use super::*;
350
351 #[test]
352 fn add_function_with_no_args() {
353 check_assist(
354 generate_function,
355 r"
356fn foo() {
357 bar$0();
358}
359",
360 r"
361fn foo() {
362 bar();
363}
364
365fn bar() ${0:-> ()} {
366 todo!()
367}
368",
369 )
370 }
371
372 #[test]
373 fn add_function_from_method() {
374 // This ensures that the function is correctly generated
375 // in the next outer mod or file
376 check_assist(
377 generate_function,
378 r"
379impl Foo {
380 fn foo() {
381 bar$0();
382 }
383}
384",
385 r"
386impl Foo {
387 fn foo() {
388 bar();
389 }
390}
391
392fn bar() ${0:-> ()} {
393 todo!()
394}
395",
396 )
397 }
398
399 #[test]
400 fn add_function_directly_after_current_block() {
401 // The new fn should not be created at the end of the file or module
402 check_assist(
403 generate_function,
404 r"
405fn foo1() {
406 bar$0();
407}
408
409fn foo2() {}
410",
411 r"
412fn foo1() {
413 bar();
414}
415
416fn bar() ${0:-> ()} {
417 todo!()
418}
419
420fn foo2() {}
421",
422 )
423 }
424
425 #[test]
426 fn add_function_with_no_args_in_same_module() {
427 check_assist(
428 generate_function,
429 r"
430mod baz {
431 fn foo() {
432 bar$0();
433 }
434}
435",
436 r"
437mod baz {
438 fn foo() {
439 bar();
440 }
441
442 fn bar() ${0:-> ()} {
443 todo!()
444 }
445}
446",
447 )
448 }
449
450 #[test]
451 fn add_function_with_function_call_arg() {
452 check_assist(
453 generate_function,
454 r"
455struct Baz;
456fn baz() -> Baz { todo!() }
457fn foo() {
458 bar$0(baz());
459}
460",
461 r"
462struct Baz;
463fn baz() -> Baz { todo!() }
464fn foo() {
465 bar(baz());
466}
467
468fn bar(baz: Baz) ${0:-> ()} {
469 todo!()
470}
471",
472 );
473 }
474
475 #[test]
476 fn add_function_with_method_call_arg() {
477 check_assist(
478 generate_function,
479 r"
480struct Baz;
481impl Baz {
482 fn foo(&self) -> Baz {
483 ba$0r(self.baz())
484 }
485 fn baz(&self) -> Baz {
486 Baz
487 }
488}
489",
490 r"
491struct Baz;
492impl Baz {
493 fn foo(&self) -> Baz {
494 bar(self.baz())
495 }
496 fn baz(&self) -> Baz {
497 Baz
498 }
499}
500
501fn bar(baz: Baz) ${0:-> ()} {
502 todo!()
503}
504",
505 )
506 }
507
508 #[test]
509 fn add_function_with_string_literal_arg() {
510 check_assist(
511 generate_function,
512 r#"
513fn foo() {
514 $0bar("bar")
515}
516"#,
517 r#"
518fn foo() {
519 bar("bar")
520}
521
522fn bar(arg: &str) ${0:-> ()} {
523 todo!()
524}
525"#,
526 )
527 }
528
529 #[test]
530 fn add_function_with_char_literal_arg() {
531 check_assist(
532 generate_function,
533 r#"
534fn foo() {
535 $0bar('x')
536}
537"#,
538 r#"
539fn foo() {
540 bar('x')
541}
542
543fn bar(arg: char) ${0:-> ()} {
544 todo!()
545}
546"#,
547 )
548 }
549
550 #[test]
551 fn add_function_with_int_literal_arg() {
552 check_assist(
553 generate_function,
554 r"
555fn foo() {
556 $0bar(42)
557}
558",
559 r"
560fn foo() {
561 bar(42)
562}
563
564fn bar(arg: i32) ${0:-> ()} {
565 todo!()
566}
567",
568 )
569 }
570
571 #[test]
572 fn add_function_with_cast_int_literal_arg() {
573 check_assist(
574 generate_function,
575 r"
576fn foo() {
577 $0bar(42 as u8)
578}
579",
580 r"
581fn foo() {
582 bar(42 as u8)
583}
584
585fn bar(arg: u8) ${0:-> ()} {
586 todo!()
587}
588",
589 )
590 }
591
592 #[test]
593 fn name_of_cast_variable_is_used() {
594 // Ensures that the name of the cast type isn't used
595 // in the generated function signature.
596 check_assist(
597 generate_function,
598 r"
599fn foo() {
600 let x = 42;
601 bar$0(x as u8)
602}
603",
604 r"
605fn foo() {
606 let x = 42;
607 bar(x as u8)
608}
609
610fn bar(x: u8) ${0:-> ()} {
611 todo!()
612}
613",
614 )
615 }
616
617 #[test]
618 fn add_function_with_variable_arg() {
619 check_assist(
620 generate_function,
621 r"
622fn foo() {
623 let worble = ();
624 $0bar(worble)
625}
626",
627 r"
628fn foo() {
629 let worble = ();
630 bar(worble)
631}
632
633fn bar(worble: ()) ${0:-> ()} {
634 todo!()
635}
636",
637 )
638 }
639
640 #[test]
641 fn add_function_with_impl_trait_arg() {
642 check_assist(
643 generate_function,
644 r"
645trait Foo {}
646fn foo() -> impl Foo {
647 todo!()
648}
649fn baz() {
650 $0bar(foo())
651}
652",
653 r"
654trait Foo {}
655fn foo() -> impl Foo {
656 todo!()
657}
658fn baz() {
659 bar(foo())
660}
661
662fn bar(foo: impl Foo) ${0:-> ()} {
663 todo!()
664}
665",
666 )
667 }
668
669 #[test]
670 fn borrowed_arg() {
671 check_assist(
672 generate_function,
673 r"
674struct Baz;
675fn baz() -> Baz { todo!() }
676
677fn foo() {
678 bar$0(&baz())
679}
680",
681 r"
682struct Baz;
683fn baz() -> Baz { todo!() }
684
685fn foo() {
686 bar(&baz())
687}
688
689fn bar(baz: &Baz) ${0:-> ()} {
690 todo!()
691}
692",
693 )
694 }
695
696 #[test]
697 fn add_function_with_qualified_path_arg() {
698 check_assist(
699 generate_function,
700 r"
701mod Baz {
702 pub struct Bof;
703 pub fn baz() -> Bof { Bof }
704}
705fn foo() {
706 $0bar(Baz::baz())
707}
708",
709 r"
710mod Baz {
711 pub struct Bof;
712 pub fn baz() -> Bof { Bof }
713}
714fn foo() {
715 bar(Baz::baz())
716}
717
718fn bar(baz: Baz::Bof) ${0:-> ()} {
719 todo!()
720}
721",
722 )
723 }
724
725 #[test]
726 #[ignore]
727 // FIXME fix printing the generics of a `Ty` to make this test pass
728 fn add_function_with_generic_arg() {
729 check_assist(
730 generate_function,
731 r"
732fn foo<T>(t: T) {
733 $0bar(t)
734}
735",
736 r"
737fn foo<T>(t: T) {
738 bar(t)
739}
740
741fn bar<T>(t: T) ${0:-> ()} {
742 todo!()
743}
744",
745 )
746 }
747
748 #[test]
749 #[ignore]
750 // FIXME Fix function type printing to make this test pass
751 fn add_function_with_fn_arg() {
752 check_assist(
753 generate_function,
754 r"
755struct Baz;
756impl Baz {
757 fn new() -> Self { Baz }
758}
759fn foo() {
760 $0bar(Baz::new);
761}
762",
763 r"
764struct Baz;
765impl Baz {
766 fn new() -> Self { Baz }
767}
768fn foo() {
769 bar(Baz::new);
770}
771
772fn bar(arg: fn() -> Baz) ${0:-> ()} {
773 todo!()
774}
775",
776 )
777 }
778
779 #[test]
780 #[ignore]
781 // FIXME Fix closure type printing to make this test pass
782 fn add_function_with_closure_arg() {
783 check_assist(
784 generate_function,
785 r"
786fn foo() {
787 let closure = |x: i64| x - 1;
788 $0bar(closure)
789}
790",
791 r"
792fn foo() {
793 let closure = |x: i64| x - 1;
794 bar(closure)
795}
796
797fn bar(closure: impl Fn(i64) -> i64) ${0:-> ()} {
798 todo!()
799}
800",
801 )
802 }
803
804 #[test]
805 fn unresolveable_types_default_to_unit() {
806 check_assist(
807 generate_function,
808 r"
809fn foo() {
810 $0bar(baz)
811}
812",
813 r"
814fn foo() {
815 bar(baz)
816}
817
818fn bar(baz: ()) ${0:-> ()} {
819 todo!()
820}
821",
822 )
823 }
824
825 #[test]
826 fn arg_names_dont_overlap() {
827 check_assist(
828 generate_function,
829 r"
830struct Baz;
831fn baz() -> Baz { Baz }
832fn foo() {
833 $0bar(baz(), baz())
834}
835",
836 r"
837struct Baz;
838fn baz() -> Baz { Baz }
839fn foo() {
840 bar(baz(), baz())
841}
842
843fn bar(baz_1: Baz, baz_2: Baz) ${0:-> ()} {
844 todo!()
845}
846",
847 )
848 }
849
850 #[test]
851 fn arg_name_counters_start_at_1_per_name() {
852 check_assist(
853 generate_function,
854 r#"
855struct Baz;
856fn baz() -> Baz { Baz }
857fn foo() {
858 $0bar(baz(), baz(), "foo", "bar")
859}
860"#,
861 r#"
862struct Baz;
863fn baz() -> Baz { Baz }
864fn foo() {
865 bar(baz(), baz(), "foo", "bar")
866}
867
868fn bar(baz_1: Baz, baz_2: Baz, arg_1: &str, arg_2: &str) ${0:-> ()} {
869 todo!()
870}
871"#,
872 )
873 }
874
875 #[test]
876 fn add_function_in_module() {
877 check_assist(
878 generate_function,
879 r"
880mod bar {}
881
882fn foo() {
883 bar::my_fn$0()
884}
885",
886 r"
887mod bar {
888 pub(crate) fn my_fn() ${0:-> ()} {
889 todo!()
890 }
891}
892
893fn foo() {
894 bar::my_fn()
895}
896",
897 )
898 }
899
900 #[test]
901 #[ignore]
902 // Ignored until local imports are supported.
903 // See https://github.com/rust-analyzer/rust-analyzer/issues/1165
904 fn qualified_path_uses_correct_scope() {
905 check_assist(
906 generate_function,
907 "
908mod foo {
909 pub struct Foo;
910}
911fn bar() {
912 use foo::Foo;
913 let foo = Foo;
914 baz$0(foo)
915}
916",
917 "
918mod foo {
919 pub struct Foo;
920}
921fn bar() {
922 use foo::Foo;
923 let foo = Foo;
924 baz(foo)
925}
926
927fn baz(foo: foo::Foo) ${0:-> ()} {
928 todo!()
929}
930",
931 )
932 }
933
934 #[test]
935 fn add_function_in_module_containing_other_items() {
936 check_assist(
937 generate_function,
938 r"
939mod bar {
940 fn something_else() {}
941}
942
943fn foo() {
944 bar::my_fn$0()
945}
946",
947 r"
948mod bar {
949 fn something_else() {}
950
951 pub(crate) fn my_fn() ${0:-> ()} {
952 todo!()
953 }
954}
955
956fn foo() {
957 bar::my_fn()
958}
959",
960 )
961 }
962
963 #[test]
964 fn add_function_in_nested_module() {
965 check_assist(
966 generate_function,
967 r"
968mod bar {
969 mod baz {}
970}
971
972fn foo() {
973 bar::baz::my_fn$0()
974}
975",
976 r"
977mod bar {
978 mod baz {
979 pub(crate) fn my_fn() ${0:-> ()} {
980 todo!()
981 }
982 }
983}
984
985fn foo() {
986 bar::baz::my_fn()
987}
988",
989 )
990 }
991
992 #[test]
993 fn add_function_in_another_file() {
994 check_assist(
995 generate_function,
996 r"
997//- /main.rs
998mod foo;
999
1000fn main() {
1001 foo::bar$0()
1002}
1003//- /foo.rs
1004",
1005 r"
1006
1007
1008pub(crate) fn bar() ${0:-> ()} {
1009 todo!()
1010}",
1011 )
1012 }
1013
1014 #[test]
1015 fn add_function_not_applicable_if_function_already_exists() {
1016 check_assist_not_applicable(
1017 generate_function,
1018 r"
1019fn foo() {
1020 bar$0();
1021}
1022
1023fn bar() {}
1024",
1025 )
1026 }
1027
1028 #[test]
1029 fn add_function_not_applicable_if_unresolved_variable_in_call_is_selected() {
1030 check_assist_not_applicable(
1031 // bar is resolved, but baz isn't.
1032 // The assist is only active if the cursor is on an unresolved path,
1033 // but the assist should only be offered if the path is a function call.
1034 generate_function,
1035 r"
1036fn foo() {
1037 bar(b$0az);
1038}
1039
1040fn bar(baz: ()) {}
1041",
1042 )
1043 }
1044
1045 #[test]
1046 #[ignore]
1047 fn create_method_with_no_args() {
1048 check_assist(
1049 generate_function,
1050 r"
1051struct Foo;
1052impl Foo {
1053 fn foo(&self) {
1054 self.bar()$0;
1055 }
1056}
1057 ",
1058 r"
1059struct Foo;
1060impl Foo {
1061 fn foo(&self) {
1062 self.bar();
1063 }
1064 fn bar(&self) {
1065 todo!();
1066 }
1067}
1068 ",
1069 )
1070 }
1071}
diff --git a/crates/ide_assists/src/handlers/generate_getter.rs b/crates/ide_assists/src/handlers/generate_getter.rs
new file mode 100644
index 000000000..df7d1bb95
--- /dev/null
+++ b/crates/ide_assists/src/handlers/generate_getter.rs
@@ -0,0 +1,192 @@
1use stdx::{format_to, to_lower_snake_case};
2use syntax::ast::{self, AstNode, NameOwner, VisibilityOwner};
3
4use crate::{
5 utils::{find_impl_block_end, find_struct_impl, generate_impl_text},
6 AssistContext, AssistId, AssistKind, Assists, GroupLabel,
7};
8
9// Assist: generate_getter
10//
11// Generate a getter method.
12//
13// ```
14// struct Person {
15// nam$0e: String,
16// }
17// ```
18// ->
19// ```
20// struct Person {
21// name: String,
22// }
23//
24// impl Person {
25// /// Get a reference to the person's name.
26// fn name(&self) -> &String {
27// &self.name
28// }
29// }
30// ```
31pub(crate) fn generate_getter(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
32 let strukt = ctx.find_node_at_offset::<ast::Struct>()?;
33 let field = ctx.find_node_at_offset::<ast::RecordField>()?;
34
35 let strukt_name = strukt.name()?;
36 let field_name = field.name()?;
37 let field_ty = field.ty()?;
38
39 // Return early if we've found an existing fn
40 let fn_name = to_lower_snake_case(&field_name.to_string());
41 let impl_def = find_struct_impl(&ctx, &ast::Adt::Struct(strukt.clone()), fn_name.as_str())?;
42
43 let target = field.syntax().text_range();
44 acc.add_group(
45 &GroupLabel("Generate getter/setter".to_owned()),
46 AssistId("generate_getter", AssistKind::Generate),
47 "Generate a getter method",
48 target,
49 |builder| {
50 let mut buf = String::with_capacity(512);
51
52 let fn_name_spaced = fn_name.replace('_', " ");
53 let strukt_name_spaced =
54 to_lower_snake_case(&strukt_name.to_string()).replace('_', " ");
55
56 if impl_def.is_some() {
57 buf.push('\n');
58 }
59
60 let vis = strukt.visibility().map_or(String::new(), |v| format!("{} ", v));
61 format_to!(
62 buf,
63 " /// Get a reference to the {}'s {}.
64 {}fn {}(&self) -> &{} {{
65 &self.{}
66 }}",
67 strukt_name_spaced,
68 fn_name_spaced,
69 vis,
70 fn_name,
71 field_ty,
72 fn_name,
73 );
74
75 let start_offset = impl_def
76 .and_then(|impl_def| find_impl_block_end(impl_def, &mut buf))
77 .unwrap_or_else(|| {
78 buf = generate_impl_text(&ast::Adt::Struct(strukt.clone()), &buf);
79 strukt.syntax().text_range().end()
80 });
81
82 builder.insert(start_offset, buf);
83 },
84 )
85}
86
87#[cfg(test)]
88mod tests {
89 use crate::tests::{check_assist, check_assist_not_applicable};
90
91 use super::*;
92
93 fn check_not_applicable(ra_fixture: &str) {
94 check_assist_not_applicable(generate_getter, ra_fixture)
95 }
96
97 #[test]
98 fn test_generate_getter_from_field() {
99 check_assist(
100 generate_getter,
101 r#"
102struct Context<T: Clone> {
103 dat$0a: T,
104}"#,
105 r#"
106struct Context<T: Clone> {
107 data: T,
108}
109
110impl<T: Clone> Context<T> {
111 /// Get a reference to the context's data.
112 fn data(&self) -> &T {
113 &self.data
114 }
115}"#,
116 );
117 }
118
119 #[test]
120 fn test_generate_getter_already_implemented() {
121 check_not_applicable(
122 r#"
123struct Context<T: Clone> {
124 dat$0a: T,
125}
126
127impl<T: Clone> Context<T> {
128 fn data(&self) -> &T {
129 &self.data
130 }
131}"#,
132 );
133 }
134
135 #[test]
136 fn test_generate_getter_from_field_with_visibility_marker() {
137 check_assist(
138 generate_getter,
139 r#"
140pub(crate) struct Context<T: Clone> {
141 dat$0a: T,
142}"#,
143 r#"
144pub(crate) struct Context<T: Clone> {
145 data: T,
146}
147
148impl<T: Clone> Context<T> {
149 /// Get a reference to the context's data.
150 pub(crate) fn data(&self) -> &T {
151 &self.data
152 }
153}"#,
154 );
155 }
156
157 #[test]
158 fn test_multiple_generate_getter() {
159 check_assist(
160 generate_getter,
161 r#"
162struct Context<T: Clone> {
163 data: T,
164 cou$0nt: usize,
165}
166
167impl<T: Clone> Context<T> {
168 /// Get a reference to the context's data.
169 fn data(&self) -> &T {
170 &self.data
171 }
172}"#,
173 r#"
174struct Context<T: Clone> {
175 data: T,
176 count: usize,
177}
178
179impl<T: Clone> Context<T> {
180 /// Get a reference to the context's data.
181 fn data(&self) -> &T {
182 &self.data
183 }
184
185 /// Get a reference to the context's count.
186 fn count(&self) -> &usize {
187 &self.count
188 }
189}"#,
190 );
191 }
192}
diff --git a/crates/ide_assists/src/handlers/generate_getter_mut.rs b/crates/ide_assists/src/handlers/generate_getter_mut.rs
new file mode 100644
index 000000000..821c2eed5
--- /dev/null
+++ b/crates/ide_assists/src/handlers/generate_getter_mut.rs
@@ -0,0 +1,195 @@
1use stdx::{format_to, to_lower_snake_case};
2use syntax::ast::{self, AstNode, NameOwner, VisibilityOwner};
3
4use crate::{
5 utils::{find_impl_block_end, find_struct_impl, generate_impl_text},
6 AssistContext, AssistId, AssistKind, Assists, GroupLabel,
7};
8
9// Assist: generate_getter_mut
10//
11// Generate a mut getter method.
12//
13// ```
14// struct Person {
15// nam$0e: String,
16// }
17// ```
18// ->
19// ```
20// struct Person {
21// name: String,
22// }
23//
24// impl Person {
25// /// Get a mutable reference to the person's name.
26// fn name_mut(&mut self) -> &mut String {
27// &mut self.name
28// }
29// }
30// ```
31pub(crate) fn generate_getter_mut(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
32 let strukt = ctx.find_node_at_offset::<ast::Struct>()?;
33 let field = ctx.find_node_at_offset::<ast::RecordField>()?;
34
35 let strukt_name = strukt.name()?;
36 let field_name = field.name()?;
37 let field_ty = field.ty()?;
38
39 // Return early if we've found an existing fn
40 let fn_name = to_lower_snake_case(&field_name.to_string());
41 let impl_def = find_struct_impl(
42 &ctx,
43 &ast::Adt::Struct(strukt.clone()),
44 format!("{}_mut", fn_name).as_str(),
45 )?;
46
47 let target = field.syntax().text_range();
48 acc.add_group(
49 &GroupLabel("Generate getter/setter".to_owned()),
50 AssistId("generate_getter_mut", AssistKind::Generate),
51 "Generate a mut getter method",
52 target,
53 |builder| {
54 let mut buf = String::with_capacity(512);
55 let fn_name_spaced = fn_name.replace('_', " ");
56 let strukt_name_spaced =
57 to_lower_snake_case(&strukt_name.to_string()).replace('_', " ");
58
59 if impl_def.is_some() {
60 buf.push('\n');
61 }
62
63 let vis = strukt.visibility().map_or(String::new(), |v| format!("{} ", v));
64 format_to!(
65 buf,
66 " /// Get a mutable reference to the {}'s {}.
67 {}fn {}_mut(&mut self) -> &mut {} {{
68 &mut self.{}
69 }}",
70 strukt_name_spaced,
71 fn_name_spaced,
72 vis,
73 fn_name,
74 field_ty,
75 fn_name,
76 );
77
78 let start_offset = impl_def
79 .and_then(|impl_def| find_impl_block_end(impl_def, &mut buf))
80 .unwrap_or_else(|| {
81 buf = generate_impl_text(&ast::Adt::Struct(strukt.clone()), &buf);
82 strukt.syntax().text_range().end()
83 });
84
85 builder.insert(start_offset, buf);
86 },
87 )
88}
89
90#[cfg(test)]
91mod tests {
92 use crate::tests::{check_assist, check_assist_not_applicable};
93
94 use super::*;
95
96 fn check_not_applicable(ra_fixture: &str) {
97 check_assist_not_applicable(generate_getter_mut, ra_fixture)
98 }
99
100 #[test]
101 fn test_generate_getter_mut_from_field() {
102 check_assist(
103 generate_getter_mut,
104 r#"
105struct Context<T: Clone> {
106 dat$0a: T,
107}"#,
108 r#"
109struct Context<T: Clone> {
110 data: T,
111}
112
113impl<T: Clone> Context<T> {
114 /// Get a mutable reference to the context's data.
115 fn data_mut(&mut self) -> &mut T {
116 &mut self.data
117 }
118}"#,
119 );
120 }
121
122 #[test]
123 fn test_generate_getter_mut_already_implemented() {
124 check_not_applicable(
125 r#"
126struct Context<T: Clone> {
127 dat$0a: T,
128}
129
130impl<T: Clone> Context<T> {
131 fn data_mut(&mut self) -> &mut T {
132 &mut self.data
133 }
134}"#,
135 );
136 }
137
138 #[test]
139 fn test_generate_getter_mut_from_field_with_visibility_marker() {
140 check_assist(
141 generate_getter_mut,
142 r#"
143pub(crate) struct Context<T: Clone> {
144 dat$0a: T,
145}"#,
146 r#"
147pub(crate) struct Context<T: Clone> {
148 data: T,
149}
150
151impl<T: Clone> Context<T> {
152 /// Get a mutable reference to the context's data.
153 pub(crate) fn data_mut(&mut self) -> &mut T {
154 &mut self.data
155 }
156}"#,
157 );
158 }
159
160 #[test]
161 fn test_multiple_generate_getter_mut() {
162 check_assist(
163 generate_getter_mut,
164 r#"
165struct Context<T: Clone> {
166 data: T,
167 cou$0nt: usize,
168}
169
170impl<T: Clone> Context<T> {
171 /// Get a mutable reference to the context's data.
172 fn data_mut(&mut self) -> &mut T {
173 &mut self.data
174 }
175}"#,
176 r#"
177struct Context<T: Clone> {
178 data: T,
179 count: usize,
180}
181
182impl<T: Clone> Context<T> {
183 /// Get a mutable reference to the context's data.
184 fn data_mut(&mut self) -> &mut T {
185 &mut self.data
186 }
187
188 /// Get a mutable reference to the context's count.
189 fn count_mut(&mut self) -> &mut usize {
190 &mut self.count
191 }
192}"#,
193 );
194 }
195}
diff --git a/crates/ide_assists/src/handlers/generate_impl.rs b/crates/ide_assists/src/handlers/generate_impl.rs
new file mode 100644
index 000000000..a8e3c4fc2
--- /dev/null
+++ b/crates/ide_assists/src/handlers/generate_impl.rs
@@ -0,0 +1,166 @@
1use syntax::ast::{self, AstNode, NameOwner};
2
3use crate::{utils::generate_impl_text, AssistContext, AssistId, AssistKind, Assists};
4
5// Assist: generate_impl
6//
7// Adds a new inherent impl for a type.
8//
9// ```
10// struct Ctx<T: Clone> {
11// data: T,$0
12// }
13// ```
14// ->
15// ```
16// struct Ctx<T: Clone> {
17// data: T,
18// }
19//
20// impl<T: Clone> Ctx<T> {
21// $0
22// }
23// ```
24pub(crate) fn generate_impl(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
25 let nominal = ctx.find_node_at_offset::<ast::Adt>()?;
26 let name = nominal.name()?;
27 let target = nominal.syntax().text_range();
28
29 acc.add(
30 AssistId("generate_impl", AssistKind::Generate),
31 format!("Generate impl for `{}`", name),
32 target,
33 |edit| {
34 let start_offset = nominal.syntax().text_range().end();
35 match ctx.config.snippet_cap {
36 Some(cap) => {
37 let snippet = generate_impl_text(&nominal, " $0");
38 edit.insert_snippet(cap, start_offset, snippet);
39 }
40 None => {
41 let snippet = generate_impl_text(&nominal, "");
42 edit.insert(start_offset, snippet);
43 }
44 }
45 },
46 )
47}
48
49#[cfg(test)]
50mod tests {
51 use crate::tests::{check_assist, check_assist_target};
52
53 use super::*;
54
55 #[test]
56 fn test_add_impl() {
57 check_assist(
58 generate_impl,
59 "struct Foo {$0}\n",
60 "struct Foo {}\n\nimpl Foo {\n $0\n}\n",
61 );
62 check_assist(
63 generate_impl,
64 "struct Foo<T: Clone> {$0}",
65 "struct Foo<T: Clone> {}\n\nimpl<T: Clone> Foo<T> {\n $0\n}",
66 );
67 check_assist(
68 generate_impl,
69 "struct Foo<'a, T: Foo<'a>> {$0}",
70 "struct Foo<'a, T: Foo<'a>> {}\n\nimpl<'a, T: Foo<'a>> Foo<'a, T> {\n $0\n}",
71 );
72 check_assist(
73 generate_impl,
74 r#"
75 #[cfg(feature = "foo")]
76 struct Foo<'a, T: Foo<'a>> {$0}"#,
77 r#"
78 #[cfg(feature = "foo")]
79 struct Foo<'a, T: Foo<'a>> {}
80
81 #[cfg(feature = "foo")]
82 impl<'a, T: Foo<'a>> Foo<'a, T> {
83 $0
84 }"#,
85 );
86
87 check_assist(
88 generate_impl,
89 r#"
90 #[cfg(not(feature = "foo"))]
91 struct Foo<'a, T: Foo<'a>> {$0}"#,
92 r#"
93 #[cfg(not(feature = "foo"))]
94 struct Foo<'a, T: Foo<'a>> {}
95
96 #[cfg(not(feature = "foo"))]
97 impl<'a, T: Foo<'a>> Foo<'a, T> {
98 $0
99 }"#,
100 );
101
102 check_assist(
103 generate_impl,
104 r#"
105 struct Defaulted<T = i32> {}$0"#,
106 r#"
107 struct Defaulted<T = i32> {}
108
109 impl<T> Defaulted<T> {
110 $0
111 }"#,
112 );
113
114 check_assist(
115 generate_impl,
116 r#"
117 struct Defaulted<'a, 'b: 'a, T: Debug + Clone + 'a + 'b = String> {}$0"#,
118 r#"
119 struct Defaulted<'a, 'b: 'a, T: Debug + Clone + 'a + 'b = String> {}
120
121 impl<'a, 'b: 'a, T: Debug + Clone + 'a + 'b> Defaulted<'a, 'b, T> {
122 $0
123 }"#,
124 );
125
126 check_assist(
127 generate_impl,
128 r#"pub trait Trait {}
129struct Struct<T>$0
130where
131 T: Trait,
132{
133 inner: T,
134}"#,
135 r#"pub trait Trait {}
136struct Struct<T>
137where
138 T: Trait,
139{
140 inner: T,
141}
142
143impl<T> Struct<T>
144where
145 T: Trait,
146{
147 $0
148}"#,
149 );
150 }
151
152 #[test]
153 fn add_impl_target() {
154 check_assist_target(
155 generate_impl,
156 "
157struct SomeThingIrrelevant;
158/// Has a lifetime parameter
159struct Foo<'a, T: Foo<'a>> {$0}
160struct EvenMoreIrrelevant;
161",
162 "/// Has a lifetime parameter
163struct Foo<'a, T: Foo<'a>> {}",
164 );
165 }
166}
diff --git a/crates/ide_assists/src/handlers/generate_new.rs b/crates/ide_assists/src/handlers/generate_new.rs
new file mode 100644
index 000000000..8ce5930b7
--- /dev/null
+++ b/crates/ide_assists/src/handlers/generate_new.rs
@@ -0,0 +1,315 @@
1use ast::Adt;
2use itertools::Itertools;
3use stdx::format_to;
4use syntax::ast::{self, AstNode, NameOwner, StructKind, VisibilityOwner};
5
6use crate::{
7 utils::{find_impl_block_start, find_struct_impl, generate_impl_text},
8 AssistContext, AssistId, AssistKind, Assists,
9};
10
11// Assist: generate_new
12//
13// Adds a new inherent impl for a type.
14//
15// ```
16// struct Ctx<T: Clone> {
17// data: T,$0
18// }
19// ```
20// ->
21// ```
22// struct Ctx<T: Clone> {
23// data: T,
24// }
25//
26// impl<T: Clone> Ctx<T> {
27// fn $0new(data: T) -> Self { Self { data } }
28// }
29// ```
30pub(crate) fn generate_new(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
31 let strukt = ctx.find_node_at_offset::<ast::Struct>()?;
32
33 // We want to only apply this to non-union structs with named fields
34 let field_list = match strukt.kind() {
35 StructKind::Record(named) => named,
36 _ => return None,
37 };
38
39 // Return early if we've found an existing new fn
40 let impl_def = find_struct_impl(&ctx, &Adt::Struct(strukt.clone()), "new")?;
41
42 let target = strukt.syntax().text_range();
43 acc.add(AssistId("generate_new", AssistKind::Generate), "Generate `new`", target, |builder| {
44 let mut buf = String::with_capacity(512);
45
46 if impl_def.is_some() {
47 buf.push('\n');
48 }
49
50 let vis = strukt.visibility().map_or(String::new(), |v| format!("{} ", v));
51
52 let params = field_list
53 .fields()
54 .filter_map(|f| Some(format!("{}: {}", f.name()?.syntax(), f.ty()?.syntax())))
55 .format(", ");
56 let fields = field_list.fields().filter_map(|f| f.name()).format(", ");
57
58 format_to!(buf, " {}fn new({}) -> Self {{ Self {{ {} }} }}", vis, params, fields);
59
60 let start_offset = impl_def
61 .and_then(|impl_def| find_impl_block_start(impl_def, &mut buf))
62 .unwrap_or_else(|| {
63 buf = generate_impl_text(&Adt::Struct(strukt.clone()), &buf);
64 strukt.syntax().text_range().end()
65 });
66
67 match ctx.config.snippet_cap {
68 None => builder.insert(start_offset, buf),
69 Some(cap) => {
70 buf = buf.replace("fn new", "fn $0new");
71 builder.insert_snippet(cap, start_offset, buf);
72 }
73 }
74 })
75}
76
77#[cfg(test)]
78mod tests {
79 use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target};
80
81 use super::*;
82
83 #[test]
84 #[rustfmt::skip]
85 fn test_generate_new() {
86 // Check output of generation
87 check_assist(
88 generate_new,
89"struct Foo {$0}",
90"struct Foo {}
91
92impl Foo {
93 fn $0new() -> Self { Self { } }
94}",
95 );
96 check_assist(
97 generate_new,
98"struct Foo<T: Clone> {$0}",
99"struct Foo<T: Clone> {}
100
101impl<T: Clone> Foo<T> {
102 fn $0new() -> Self { Self { } }
103}",
104 );
105 check_assist(
106 generate_new,
107"struct Foo<'a, T: Foo<'a>> {$0}",
108"struct Foo<'a, T: Foo<'a>> {}
109
110impl<'a, T: Foo<'a>> Foo<'a, T> {
111 fn $0new() -> Self { Self { } }
112}",
113 );
114 check_assist(
115 generate_new,
116"struct Foo { baz: String $0}",
117"struct Foo { baz: String }
118
119impl Foo {
120 fn $0new(baz: String) -> Self { Self { baz } }
121}",
122 );
123 check_assist(
124 generate_new,
125"struct Foo { baz: String, qux: Vec<i32> $0}",
126"struct Foo { baz: String, qux: Vec<i32> }
127
128impl Foo {
129 fn $0new(baz: String, qux: Vec<i32>) -> Self { Self { baz, qux } }
130}",
131 );
132
133 // Check that visibility modifiers don't get brought in for fields
134 check_assist(
135 generate_new,
136"struct Foo { pub baz: String, pub qux: Vec<i32> $0}",
137"struct Foo { pub baz: String, pub qux: Vec<i32> }
138
139impl Foo {
140 fn $0new(baz: String, qux: Vec<i32>) -> Self { Self { baz, qux } }
141}",
142 );
143
144 // Check that it reuses existing impls
145 check_assist(
146 generate_new,
147"struct Foo {$0}
148
149impl Foo {}
150",
151"struct Foo {}
152
153impl Foo {
154 fn $0new() -> Self { Self { } }
155}
156",
157 );
158 check_assist(
159 generate_new,
160"struct Foo {$0}
161
162impl Foo {
163 fn qux(&self) {}
164}
165",
166"struct Foo {}
167
168impl Foo {
169 fn $0new() -> Self { Self { } }
170
171 fn qux(&self) {}
172}
173",
174 );
175
176 check_assist(
177 generate_new,
178"struct Foo {$0}
179
180impl Foo {
181 fn qux(&self) {}
182 fn baz() -> i32 {
183 5
184 }
185}
186",
187"struct Foo {}
188
189impl Foo {
190 fn $0new() -> Self { Self { } }
191
192 fn qux(&self) {}
193 fn baz() -> i32 {
194 5
195 }
196}
197",
198 );
199
200 // Check visibility of new fn based on struct
201 check_assist(
202 generate_new,
203"pub struct Foo {$0}",
204"pub struct Foo {}
205
206impl Foo {
207 pub fn $0new() -> Self { Self { } }
208}",
209 );
210 check_assist(
211 generate_new,
212"pub(crate) struct Foo {$0}",
213"pub(crate) struct Foo {}
214
215impl Foo {
216 pub(crate) fn $0new() -> Self { Self { } }
217}",
218 );
219 }
220
221 #[test]
222 fn generate_new_not_applicable_if_fn_exists() {
223 check_assist_not_applicable(
224 generate_new,
225 "
226struct Foo {$0}
227
228impl Foo {
229 fn new() -> Self {
230 Self
231 }
232}",
233 );
234
235 check_assist_not_applicable(
236 generate_new,
237 "
238struct Foo {$0}
239
240impl Foo {
241 fn New() -> Self {
242 Self
243 }
244}",
245 );
246 }
247
248 #[test]
249 fn generate_new_target() {
250 check_assist_target(
251 generate_new,
252 "
253struct SomeThingIrrelevant;
254/// Has a lifetime parameter
255struct Foo<'a, T: Foo<'a>> {$0}
256struct EvenMoreIrrelevant;
257",
258 "/// Has a lifetime parameter
259struct Foo<'a, T: Foo<'a>> {}",
260 );
261 }
262
263 #[test]
264 fn test_unrelated_new() {
265 check_assist(
266 generate_new,
267 r##"
268pub struct AstId<N: AstNode> {
269 file_id: HirFileId,
270 file_ast_id: FileAstId<N>,
271}
272
273impl<N: AstNode> AstId<N> {
274 pub fn new(file_id: HirFileId, file_ast_id: FileAstId<N>) -> AstId<N> {
275 AstId { file_id, file_ast_id }
276 }
277}
278
279pub struct Source<T> {
280 pub file_id: HirFileId,$0
281 pub ast: T,
282}
283
284impl<T> Source<T> {
285 pub fn map<F: FnOnce(T) -> U, U>(self, f: F) -> Source<U> {
286 Source { file_id: self.file_id, ast: f(self.ast) }
287 }
288}"##,
289 r##"
290pub struct AstId<N: AstNode> {
291 file_id: HirFileId,
292 file_ast_id: FileAstId<N>,
293}
294
295impl<N: AstNode> AstId<N> {
296 pub fn new(file_id: HirFileId, file_ast_id: FileAstId<N>) -> AstId<N> {
297 AstId { file_id, file_ast_id }
298 }
299}
300
301pub struct Source<T> {
302 pub file_id: HirFileId,
303 pub ast: T,
304}
305
306impl<T> Source<T> {
307 pub fn $0new(file_id: HirFileId, ast: T) -> Self { Self { file_id, ast } }
308
309 pub fn map<F: FnOnce(T) -> U, U>(self, f: F) -> Source<U> {
310 Source { file_id: self.file_id, ast: f(self.ast) }
311 }
312}"##,
313 );
314 }
315}
diff --git a/crates/ide_assists/src/handlers/generate_setter.rs b/crates/ide_assists/src/handlers/generate_setter.rs
new file mode 100644
index 000000000..288cf745d
--- /dev/null
+++ b/crates/ide_assists/src/handlers/generate_setter.rs
@@ -0,0 +1,198 @@
1use stdx::{format_to, to_lower_snake_case};
2use syntax::ast::{self, AstNode, NameOwner, VisibilityOwner};
3
4use crate::{
5 utils::{find_impl_block_end, find_struct_impl, generate_impl_text},
6 AssistContext, AssistId, AssistKind, Assists, GroupLabel,
7};
8
9// Assist: generate_setter
10//
11// Generate a setter method.
12//
13// ```
14// struct Person {
15// nam$0e: String,
16// }
17// ```
18// ->
19// ```
20// struct Person {
21// name: String,
22// }
23//
24// impl Person {
25// /// Set the person's name.
26// fn set_name(&mut self, name: String) {
27// self.name = name;
28// }
29// }
30// ```
31pub(crate) fn generate_setter(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
32 let strukt = ctx.find_node_at_offset::<ast::Struct>()?;
33 let field = ctx.find_node_at_offset::<ast::RecordField>()?;
34
35 let strukt_name = strukt.name()?;
36 let field_name = field.name()?;
37 let field_ty = field.ty()?;
38
39 // Return early if we've found an existing fn
40 let fn_name = to_lower_snake_case(&field_name.to_string());
41 let impl_def = find_struct_impl(
42 &ctx,
43 &ast::Adt::Struct(strukt.clone()),
44 format!("set_{}", fn_name).as_str(),
45 )?;
46
47 let target = field.syntax().text_range();
48 acc.add_group(
49 &GroupLabel("Generate getter/setter".to_owned()),
50 AssistId("generate_setter", AssistKind::Generate),
51 "Generate a setter method",
52 target,
53 |builder| {
54 let mut buf = String::with_capacity(512);
55
56 let fn_name_spaced = fn_name.replace('_', " ");
57 let strukt_name_spaced =
58 to_lower_snake_case(&strukt_name.to_string()).replace('_', " ");
59
60 if impl_def.is_some() {
61 buf.push('\n');
62 }
63
64 let vis = strukt.visibility().map_or(String::new(), |v| format!("{} ", v));
65 format_to!(
66 buf,
67 " /// Set the {}'s {}.
68 {}fn set_{}(&mut self, {}: {}) {{
69 self.{} = {};
70 }}",
71 strukt_name_spaced,
72 fn_name_spaced,
73 vis,
74 fn_name,
75 fn_name,
76 field_ty,
77 fn_name,
78 fn_name,
79 );
80
81 let start_offset = impl_def
82 .and_then(|impl_def| find_impl_block_end(impl_def, &mut buf))
83 .unwrap_or_else(|| {
84 buf = generate_impl_text(&ast::Adt::Struct(strukt.clone()), &buf);
85 strukt.syntax().text_range().end()
86 });
87
88 builder.insert(start_offset, buf);
89 },
90 )
91}
92
93#[cfg(test)]
94mod tests {
95 use crate::tests::{check_assist, check_assist_not_applicable};
96
97 use super::*;
98
99 fn check_not_applicable(ra_fixture: &str) {
100 check_assist_not_applicable(generate_setter, ra_fixture)
101 }
102
103 #[test]
104 fn test_generate_setter_from_field() {
105 check_assist(
106 generate_setter,
107 r#"
108struct Person<T: Clone> {
109 dat$0a: T,
110}"#,
111 r#"
112struct Person<T: Clone> {
113 data: T,
114}
115
116impl<T: Clone> Person<T> {
117 /// Set the person's data.
118 fn set_data(&mut self, data: T) {
119 self.data = data;
120 }
121}"#,
122 );
123 }
124
125 #[test]
126 fn test_generate_setter_already_implemented() {
127 check_not_applicable(
128 r#"
129struct Person<T: Clone> {
130 dat$0a: T,
131}
132
133impl<T: Clone> Person<T> {
134 fn set_data(&mut self, data: T) {
135 self.data = data;
136 }
137}"#,
138 );
139 }
140
141 #[test]
142 fn test_generate_setter_from_field_with_visibility_marker() {
143 check_assist(
144 generate_setter,
145 r#"
146pub(crate) struct Person<T: Clone> {
147 dat$0a: T,
148}"#,
149 r#"
150pub(crate) struct Person<T: Clone> {
151 data: T,
152}
153
154impl<T: Clone> Person<T> {
155 /// Set the person's data.
156 pub(crate) fn set_data(&mut self, data: T) {
157 self.data = data;
158 }
159}"#,
160 );
161 }
162
163 #[test]
164 fn test_multiple_generate_setter() {
165 check_assist(
166 generate_setter,
167 r#"
168struct Context<T: Clone> {
169 data: T,
170 cou$0nt: usize,
171}
172
173impl<T: Clone> Context<T> {
174 /// Set the context's data.
175 fn set_data(&mut self, data: T) {
176 self.data = data;
177 }
178}"#,
179 r#"
180struct Context<T: Clone> {
181 data: T,
182 count: usize,
183}
184
185impl<T: Clone> Context<T> {
186 /// Set the context's data.
187 fn set_data(&mut self, data: T) {
188 self.data = data;
189 }
190
191 /// Set the context's count.
192 fn set_count(&mut self, count: usize) {
193 self.count = count;
194 }
195}"#,
196 );
197 }
198}
diff --git a/crates/ide_assists/src/handlers/infer_function_return_type.rs b/crates/ide_assists/src/handlers/infer_function_return_type.rs
new file mode 100644
index 000000000..5279af1f3
--- /dev/null
+++ b/crates/ide_assists/src/handlers/infer_function_return_type.rs
@@ -0,0 +1,345 @@
1use hir::HirDisplay;
2use syntax::{ast, AstNode, TextRange, TextSize};
3use test_utils::mark;
4
5use crate::{AssistContext, AssistId, AssistKind, Assists};
6
7// Assist: infer_function_return_type
8//
9// Adds the return type to a function or closure inferred from its tail expression if it doesn't have a return
10// type specified. This assists is useable in a functions or closures tail expression or return type position.
11//
12// ```
13// fn foo() { 4$02i32 }
14// ```
15// ->
16// ```
17// fn foo() -> i32 { 42i32 }
18// ```
19pub(crate) fn infer_function_return_type(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
20 let (fn_type, tail_expr, builder_edit_pos) = extract_tail(ctx)?;
21 let module = ctx.sema.scope(tail_expr.syntax()).module()?;
22 let ty = ctx.sema.type_of_expr(&tail_expr)?;
23 if ty.is_unit() {
24 return None;
25 }
26 let ty = ty.display_source_code(ctx.db(), module.into()).ok()?;
27
28 acc.add(
29 AssistId("infer_function_return_type", AssistKind::RefactorRewrite),
30 match fn_type {
31 FnType::Function => "Add this function's return type",
32 FnType::Closure { .. } => "Add this closure's return type",
33 },
34 tail_expr.syntax().text_range(),
35 |builder| {
36 match builder_edit_pos {
37 InsertOrReplace::Insert(insert_pos) => {
38 builder.insert(insert_pos, &format!("-> {} ", ty))
39 }
40 InsertOrReplace::Replace(text_range) => {
41 builder.replace(text_range, &format!("-> {}", ty))
42 }
43 }
44 if let FnType::Closure { wrap_expr: true } = fn_type {
45 mark::hit!(wrap_closure_non_block_expr);
46 // `|x| x` becomes `|x| -> T x` which is invalid, so wrap it in a block
47 builder.replace(tail_expr.syntax().text_range(), &format!("{{{}}}", tail_expr));
48 }
49 },
50 )
51}
52
53enum InsertOrReplace {
54 Insert(TextSize),
55 Replace(TextRange),
56}
57
58/// Check the potentially already specified return type and reject it or turn it into a builder command
59/// if allowed.
60fn ret_ty_to_action(ret_ty: Option<ast::RetType>, insert_pos: TextSize) -> Option<InsertOrReplace> {
61 match ret_ty {
62 Some(ret_ty) => match ret_ty.ty() {
63 Some(ast::Type::InferType(_)) | None => {
64 mark::hit!(existing_infer_ret_type);
65 mark::hit!(existing_infer_ret_type_closure);
66 Some(InsertOrReplace::Replace(ret_ty.syntax().text_range()))
67 }
68 _ => {
69 mark::hit!(existing_ret_type);
70 mark::hit!(existing_ret_type_closure);
71 None
72 }
73 },
74 None => Some(InsertOrReplace::Insert(insert_pos + TextSize::from(1))),
75 }
76}
77
78enum FnType {
79 Function,
80 Closure { wrap_expr: bool },
81}
82
83fn extract_tail(ctx: &AssistContext) -> Option<(FnType, ast::Expr, InsertOrReplace)> {
84 let (fn_type, tail_expr, return_type_range, action) =
85 if let Some(closure) = ctx.find_node_at_offset::<ast::ClosureExpr>() {
86 let rpipe_pos = closure.param_list()?.syntax().last_token()?.text_range().end();
87 let action = ret_ty_to_action(closure.ret_type(), rpipe_pos)?;
88
89 let body = closure.body()?;
90 let body_start = body.syntax().first_token()?.text_range().start();
91 let (tail_expr, wrap_expr) = match body {
92 ast::Expr::BlockExpr(block) => (block.tail_expr()?, false),
93 body => (body, true),
94 };
95
96 let ret_range = TextRange::new(rpipe_pos, body_start);
97 (FnType::Closure { wrap_expr }, tail_expr, ret_range, action)
98 } else {
99 let func = ctx.find_node_at_offset::<ast::Fn>()?;
100 let rparen_pos = func.param_list()?.r_paren_token()?.text_range().end();
101 let action = ret_ty_to_action(func.ret_type(), rparen_pos)?;
102
103 let body = func.body()?;
104 let tail_expr = body.tail_expr()?;
105
106 let ret_range_end = body.l_curly_token()?.text_range().start();
107 let ret_range = TextRange::new(rparen_pos, ret_range_end);
108 (FnType::Function, tail_expr, ret_range, action)
109 };
110 let frange = ctx.frange.range;
111 if return_type_range.contains_range(frange) {
112 mark::hit!(cursor_in_ret_position);
113 mark::hit!(cursor_in_ret_position_closure);
114 } else if tail_expr.syntax().text_range().contains_range(frange) {
115 mark::hit!(cursor_on_tail);
116 mark::hit!(cursor_on_tail_closure);
117 } else {
118 return None;
119 }
120 Some((fn_type, tail_expr, action))
121}
122
123#[cfg(test)]
124mod tests {
125 use crate::tests::{check_assist, check_assist_not_applicable};
126
127 use super::*;
128
129 #[test]
130 fn infer_return_type_specified_inferred() {
131 mark::check!(existing_infer_ret_type);
132 check_assist(
133 infer_function_return_type,
134 r#"fn foo() -> $0_ {
135 45
136}"#,
137 r#"fn foo() -> i32 {
138 45
139}"#,
140 );
141 }
142
143 #[test]
144 fn infer_return_type_specified_inferred_closure() {
145 mark::check!(existing_infer_ret_type_closure);
146 check_assist(
147 infer_function_return_type,
148 r#"fn foo() {
149 || -> _ {$045};
150}"#,
151 r#"fn foo() {
152 || -> i32 {45};
153}"#,
154 );
155 }
156
157 #[test]
158 fn infer_return_type_cursor_at_return_type_pos() {
159 mark::check!(cursor_in_ret_position);
160 check_assist(
161 infer_function_return_type,
162 r#"fn foo() $0{
163 45
164}"#,
165 r#"fn foo() -> i32 {
166 45
167}"#,
168 );
169 }
170
171 #[test]
172 fn infer_return_type_cursor_at_return_type_pos_closure() {
173 mark::check!(cursor_in_ret_position_closure);
174 check_assist(
175 infer_function_return_type,
176 r#"fn foo() {
177 || $045
178}"#,
179 r#"fn foo() {
180 || -> i32 {45}
181}"#,
182 );
183 }
184
185 #[test]
186 fn infer_return_type() {
187 mark::check!(cursor_on_tail);
188 check_assist(
189 infer_function_return_type,
190 r#"fn foo() {
191 45$0
192}"#,
193 r#"fn foo() -> i32 {
194 45
195}"#,
196 );
197 }
198
199 #[test]
200 fn infer_return_type_nested() {
201 check_assist(
202 infer_function_return_type,
203 r#"fn foo() {
204 if true {
205 3$0
206 } else {
207 5
208 }
209}"#,
210 r#"fn foo() -> i32 {
211 if true {
212 3
213 } else {
214 5
215 }
216}"#,
217 );
218 }
219
220 #[test]
221 fn not_applicable_ret_type_specified() {
222 mark::check!(existing_ret_type);
223 check_assist_not_applicable(
224 infer_function_return_type,
225 r#"fn foo() -> i32 {
226 ( 45$0 + 32 ) * 123
227}"#,
228 );
229 }
230
231 #[test]
232 fn not_applicable_non_tail_expr() {
233 check_assist_not_applicable(
234 infer_function_return_type,
235 r#"fn foo() {
236 let x = $03;
237 ( 45 + 32 ) * 123
238}"#,
239 );
240 }
241
242 #[test]
243 fn not_applicable_unit_return_type() {
244 check_assist_not_applicable(
245 infer_function_return_type,
246 r#"fn foo() {
247 ($0)
248}"#,
249 );
250 }
251
252 #[test]
253 fn infer_return_type_closure_block() {
254 mark::check!(cursor_on_tail_closure);
255 check_assist(
256 infer_function_return_type,
257 r#"fn foo() {
258 |x: i32| {
259 x$0
260 };
261}"#,
262 r#"fn foo() {
263 |x: i32| -> i32 {
264 x
265 };
266}"#,
267 );
268 }
269
270 #[test]
271 fn infer_return_type_closure() {
272 check_assist(
273 infer_function_return_type,
274 r#"fn foo() {
275 |x: i32| { x$0 };
276}"#,
277 r#"fn foo() {
278 |x: i32| -> i32 { x };
279}"#,
280 );
281 }
282
283 #[test]
284 fn infer_return_type_closure_wrap() {
285 mark::check!(wrap_closure_non_block_expr);
286 check_assist(
287 infer_function_return_type,
288 r#"fn foo() {
289 |x: i32| x$0;
290}"#,
291 r#"fn foo() {
292 |x: i32| -> i32 {x};
293}"#,
294 );
295 }
296
297 #[test]
298 fn infer_return_type_nested_closure() {
299 check_assist(
300 infer_function_return_type,
301 r#"fn foo() {
302 || {
303 if true {
304 3$0
305 } else {
306 5
307 }
308 }
309}"#,
310 r#"fn foo() {
311 || -> i32 {
312 if true {
313 3
314 } else {
315 5
316 }
317 }
318}"#,
319 );
320 }
321
322 #[test]
323 fn not_applicable_ret_type_specified_closure() {
324 mark::check!(existing_ret_type_closure);
325 check_assist_not_applicable(
326 infer_function_return_type,
327 r#"fn foo() {
328 || -> i32 { 3$0 }
329}"#,
330 );
331 }
332
333 #[test]
334 fn not_applicable_non_tail_expr_closure() {
335 check_assist_not_applicable(
336 infer_function_return_type,
337 r#"fn foo() {
338 || -> i32 {
339 let x = 3$0;
340 6
341 }
342}"#,
343 );
344 }
345}
diff --git a/crates/ide_assists/src/handlers/inline_function.rs b/crates/ide_assists/src/handlers/inline_function.rs
new file mode 100644
index 000000000..6ec99b09b
--- /dev/null
+++ b/crates/ide_assists/src/handlers/inline_function.rs
@@ -0,0 +1,202 @@
1use ast::make;
2use hir::{HasSource, PathResolution};
3use syntax::{
4 ast::{self, edit::AstNodeEdit, ArgListOwner},
5 AstNode,
6};
7use test_utils::mark;
8
9use crate::{
10 assist_context::{AssistContext, Assists},
11 AssistId, AssistKind,
12};
13
14// Assist: inline_function
15//
16// Inlines a function body.
17//
18// ```
19// fn add(a: u32, b: u32) -> u32 { a + b }
20// fn main() {
21// let x = add$0(1, 2);
22// }
23// ```
24// ->
25// ```
26// fn add(a: u32, b: u32) -> u32 { a + b }
27// fn main() {
28// let x = {
29// let a = 1;
30// let b = 2;
31// a + b
32// };
33// }
34// ```
35pub(crate) fn inline_function(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
36 let path_expr: ast::PathExpr = ctx.find_node_at_offset()?;
37 let call = path_expr.syntax().parent().and_then(ast::CallExpr::cast)?;
38 let path = path_expr.path()?;
39
40 let function = match ctx.sema.resolve_path(&path)? {
41 PathResolution::Def(hir::ModuleDef::Function(f)) => f,
42 _ => return None,
43 };
44
45 let function_source = function.source(ctx.db())?;
46 let arguments: Vec<_> = call.arg_list()?.args().collect();
47 let parameters = function_parameter_patterns(&function_source.value)?;
48
49 if arguments.len() != parameters.len() {
50 // Can't inline the function because they've passed the wrong number of
51 // arguments to this function
52 mark::hit!(inline_function_incorrect_number_of_arguments);
53 return None;
54 }
55
56 let new_bindings = parameters.into_iter().zip(arguments);
57
58 let body = function_source.value.body()?;
59
60 acc.add(
61 AssistId("inline_function", AssistKind::RefactorInline),
62 format!("Inline `{}`", path),
63 call.syntax().text_range(),
64 |builder| {
65 let mut statements: Vec<ast::Stmt> = Vec::new();
66
67 for (pattern, value) in new_bindings {
68 statements.push(make::let_stmt(pattern, Some(value)).into());
69 }
70
71 statements.extend(body.statements());
72
73 let original_indentation = call.indent_level();
74 let replacement = make::block_expr(statements, body.tail_expr())
75 .reset_indent()
76 .indent(original_indentation);
77
78 builder.replace_ast(ast::Expr::CallExpr(call), ast::Expr::BlockExpr(replacement));
79 },
80 )
81}
82
83fn function_parameter_patterns(value: &ast::Fn) -> Option<Vec<ast::Pat>> {
84 let mut patterns = Vec::new();
85
86 for param in value.param_list()?.params() {
87 let pattern = param.pat()?;
88 patterns.push(pattern);
89 }
90
91 Some(patterns)
92}
93
94#[cfg(test)]
95mod tests {
96 use crate::tests::{check_assist, check_assist_not_applicable};
97
98 use super::*;
99
100 #[test]
101 fn no_args_or_return_value_gets_inlined_without_block() {
102 check_assist(
103 inline_function,
104 r#"
105fn foo() { println!("Hello, World!"); }
106fn main() {
107 fo$0o();
108}
109"#,
110 r#"
111fn foo() { println!("Hello, World!"); }
112fn main() {
113 {
114 println!("Hello, World!");
115 };
116}
117"#,
118 );
119 }
120
121 #[test]
122 fn args_with_side_effects() {
123 check_assist(
124 inline_function,
125 r#"
126fn foo(name: String) { println!("Hello, {}!", name); }
127fn main() {
128 foo$0(String::from("Michael"));
129}
130"#,
131 r#"
132fn foo(name: String) { println!("Hello, {}!", name); }
133fn main() {
134 {
135 let name = String::from("Michael");
136 println!("Hello, {}!", name);
137 };
138}
139"#,
140 );
141 }
142
143 #[test]
144 fn method_inlining_isnt_supported() {
145 check_assist_not_applicable(
146 inline_function,
147 r"
148struct Foo;
149impl Foo { fn bar(&self) {} }
150
151fn main() { Foo.bar$0(); }
152",
153 );
154 }
155
156 #[test]
157 fn not_applicable_when_incorrect_number_of_parameters_are_provided() {
158 mark::check!(inline_function_incorrect_number_of_arguments);
159 check_assist_not_applicable(
160 inline_function,
161 r#"
162fn add(a: u32, b: u32) -> u32 { a + b }
163fn main() { let x = add$0(42); }
164"#,
165 );
166 }
167
168 #[test]
169 fn function_with_multiple_statements() {
170 check_assist(
171 inline_function,
172 r#"
173fn foo(a: u32, b: u32) -> u32 {
174 let x = a + b;
175 let y = x - b;
176 x * y
177}
178
179fn main() {
180 let x = foo$0(1, 2);
181}
182"#,
183 r#"
184fn foo(a: u32, b: u32) -> u32 {
185 let x = a + b;
186 let y = x - b;
187 x * y
188}
189
190fn main() {
191 let x = {
192 let a = 1;
193 let b = 2;
194 let x = a + b;
195 let y = x - b;
196 x * y
197 };
198}
199"#,
200 );
201 }
202}
diff --git a/crates/ide_assists/src/handlers/inline_local_variable.rs b/crates/ide_assists/src/handlers/inline_local_variable.rs
new file mode 100644
index 000000000..da5522670
--- /dev/null
+++ b/crates/ide_assists/src/handlers/inline_local_variable.rs
@@ -0,0 +1,732 @@
1use ide_db::{defs::Definition, search::FileReference};
2use rustc_hash::FxHashMap;
3use syntax::{
4 ast::{self, AstNode, AstToken},
5 TextRange,
6};
7use test_utils::mark;
8
9use crate::{
10 assist_context::{AssistContext, Assists},
11 AssistId, AssistKind,
12};
13
14// Assist: inline_local_variable
15//
16// Inlines local variable.
17//
18// ```
19// fn main() {
20// let x$0 = 1 + 2;
21// x * 4;
22// }
23// ```
24// ->
25// ```
26// fn main() {
27// (1 + 2) * 4;
28// }
29// ```
30pub(crate) fn inline_local_variable(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
31 let let_stmt = ctx.find_node_at_offset::<ast::LetStmt>()?;
32 let bind_pat = match let_stmt.pat()? {
33 ast::Pat::IdentPat(pat) => pat,
34 _ => return None,
35 };
36 if bind_pat.mut_token().is_some() {
37 mark::hit!(test_not_inline_mut_variable);
38 return None;
39 }
40 if !bind_pat.syntax().text_range().contains_inclusive(ctx.offset()) {
41 mark::hit!(not_applicable_outside_of_bind_pat);
42 return None;
43 }
44 let initializer_expr = let_stmt.initializer()?;
45
46 let def = ctx.sema.to_def(&bind_pat)?;
47 let def = Definition::Local(def);
48 let usages = def.usages(&ctx.sema).all();
49 if usages.is_empty() {
50 mark::hit!(test_not_applicable_if_variable_unused);
51 return None;
52 };
53
54 let delete_range = if let Some(whitespace) = let_stmt
55 .syntax()
56 .next_sibling_or_token()
57 .and_then(|it| ast::Whitespace::cast(it.as_token()?.clone()))
58 {
59 TextRange::new(
60 let_stmt.syntax().text_range().start(),
61 whitespace.syntax().text_range().end(),
62 )
63 } else {
64 let_stmt.syntax().text_range()
65 };
66
67 let wrap_in_parens = usages
68 .references
69 .iter()
70 .map(|(&file_id, refs)| {
71 refs.iter()
72 .map(|&FileReference { range, .. }| {
73 let usage_node = ctx
74 .covering_node_for_range(range)
75 .ancestors()
76 .find_map(ast::PathExpr::cast)?;
77 let usage_parent_option =
78 usage_node.syntax().parent().and_then(ast::Expr::cast);
79 let usage_parent = match usage_parent_option {
80 Some(u) => u,
81 None => return Some(false),
82 };
83
84 Some(!matches!(
85 (&initializer_expr, usage_parent),
86 (ast::Expr::CallExpr(_), _)
87 | (ast::Expr::IndexExpr(_), _)
88 | (ast::Expr::MethodCallExpr(_), _)
89 | (ast::Expr::FieldExpr(_), _)
90 | (ast::Expr::TryExpr(_), _)
91 | (ast::Expr::RefExpr(_), _)
92 | (ast::Expr::Literal(_), _)
93 | (ast::Expr::TupleExpr(_), _)
94 | (ast::Expr::ArrayExpr(_), _)
95 | (ast::Expr::ParenExpr(_), _)
96 | (ast::Expr::PathExpr(_), _)
97 | (ast::Expr::BlockExpr(_), _)
98 | (ast::Expr::EffectExpr(_), _)
99 | (_, ast::Expr::CallExpr(_))
100 | (_, ast::Expr::TupleExpr(_))
101 | (_, ast::Expr::ArrayExpr(_))
102 | (_, ast::Expr::ParenExpr(_))
103 | (_, ast::Expr::ForExpr(_))
104 | (_, ast::Expr::WhileExpr(_))
105 | (_, ast::Expr::BreakExpr(_))
106 | (_, ast::Expr::ReturnExpr(_))
107 | (_, ast::Expr::MatchExpr(_))
108 ))
109 })
110 .collect::<Option<_>>()
111 .map(|b| (file_id, b))
112 })
113 .collect::<Option<FxHashMap<_, Vec<_>>>>()?;
114
115 let init_str = initializer_expr.syntax().text().to_string();
116 let init_in_paren = format!("({})", &init_str);
117
118 let target = bind_pat.syntax().text_range();
119 acc.add(
120 AssistId("inline_local_variable", AssistKind::RefactorInline),
121 "Inline variable",
122 target,
123 move |builder| {
124 builder.delete(delete_range);
125 for (file_id, references) in usages.references {
126 for (&should_wrap, reference) in wrap_in_parens[&file_id].iter().zip(references) {
127 let replacement =
128 if should_wrap { init_in_paren.clone() } else { init_str.clone() };
129 match reference.name.as_name_ref() {
130 Some(name_ref)
131 if ast::RecordExprField::for_field_name(name_ref).is_some() =>
132 {
133 mark::hit!(inline_field_shorthand);
134 builder.insert(reference.range.end(), format!(": {}", replacement));
135 }
136 _ => builder.replace(reference.range, replacement),
137 }
138 }
139 }
140 },
141 )
142}
143
144#[cfg(test)]
145mod tests {
146 use test_utils::mark;
147
148 use crate::tests::{check_assist, check_assist_not_applicable};
149
150 use super::*;
151
152 #[test]
153 fn test_inline_let_bind_literal_expr() {
154 check_assist(
155 inline_local_variable,
156 r"
157fn bar(a: usize) {}
158fn foo() {
159 let a$0 = 1;
160 a + 1;
161 if a > 10 {
162 }
163
164 while a > 10 {
165
166 }
167 let b = a * 10;
168 bar(a);
169}",
170 r"
171fn bar(a: usize) {}
172fn foo() {
173 1 + 1;
174 if 1 > 10 {
175 }
176
177 while 1 > 10 {
178
179 }
180 let b = 1 * 10;
181 bar(1);
182}",
183 );
184 }
185
186 #[test]
187 fn test_inline_let_bind_bin_expr() {
188 check_assist(
189 inline_local_variable,
190 r"
191fn bar(a: usize) {}
192fn foo() {
193 let a$0 = 1 + 1;
194 a + 1;
195 if a > 10 {
196 }
197
198 while a > 10 {
199
200 }
201 let b = a * 10;
202 bar(a);
203}",
204 r"
205fn bar(a: usize) {}
206fn foo() {
207 (1 + 1) + 1;
208 if (1 + 1) > 10 {
209 }
210
211 while (1 + 1) > 10 {
212
213 }
214 let b = (1 + 1) * 10;
215 bar(1 + 1);
216}",
217 );
218 }
219
220 #[test]
221 fn test_inline_let_bind_function_call_expr() {
222 check_assist(
223 inline_local_variable,
224 r"
225fn bar(a: usize) {}
226fn foo() {
227 let a$0 = bar(1);
228 a + 1;
229 if a > 10 {
230 }
231
232 while a > 10 {
233
234 }
235 let b = a * 10;
236 bar(a);
237}",
238 r"
239fn bar(a: usize) {}
240fn foo() {
241 bar(1) + 1;
242 if bar(1) > 10 {
243 }
244
245 while bar(1) > 10 {
246
247 }
248 let b = bar(1) * 10;
249 bar(bar(1));
250}",
251 );
252 }
253
254 #[test]
255 fn test_inline_let_bind_cast_expr() {
256 check_assist(
257 inline_local_variable,
258 r"
259fn bar(a: usize): usize { a }
260fn foo() {
261 let a$0 = bar(1) as u64;
262 a + 1;
263 if a > 10 {
264 }
265
266 while a > 10 {
267
268 }
269 let b = a * 10;
270 bar(a);
271}",
272 r"
273fn bar(a: usize): usize { a }
274fn foo() {
275 (bar(1) as u64) + 1;
276 if (bar(1) as u64) > 10 {
277 }
278
279 while (bar(1) as u64) > 10 {
280
281 }
282 let b = (bar(1) as u64) * 10;
283 bar(bar(1) as u64);
284}",
285 );
286 }
287
288 #[test]
289 fn test_inline_let_bind_block_expr() {
290 check_assist(
291 inline_local_variable,
292 r"
293fn foo() {
294 let a$0 = { 10 + 1 };
295 a + 1;
296 if a > 10 {
297 }
298
299 while a > 10 {
300
301 }
302 let b = a * 10;
303 bar(a);
304}",
305 r"
306fn foo() {
307 { 10 + 1 } + 1;
308 if { 10 + 1 } > 10 {
309 }
310
311 while { 10 + 1 } > 10 {
312
313 }
314 let b = { 10 + 1 } * 10;
315 bar({ 10 + 1 });
316}",
317 );
318 }
319
320 #[test]
321 fn test_inline_let_bind_paren_expr() {
322 check_assist(
323 inline_local_variable,
324 r"
325fn foo() {
326 let a$0 = ( 10 + 1 );
327 a + 1;
328 if a > 10 {
329 }
330
331 while a > 10 {
332
333 }
334 let b = a * 10;
335 bar(a);
336}",
337 r"
338fn foo() {
339 ( 10 + 1 ) + 1;
340 if ( 10 + 1 ) > 10 {
341 }
342
343 while ( 10 + 1 ) > 10 {
344
345 }
346 let b = ( 10 + 1 ) * 10;
347 bar(( 10 + 1 ));
348}",
349 );
350 }
351
352 #[test]
353 fn test_not_inline_mut_variable() {
354 mark::check!(test_not_inline_mut_variable);
355 check_assist_not_applicable(
356 inline_local_variable,
357 r"
358fn foo() {
359 let mut a$0 = 1 + 1;
360 a + 1;
361}",
362 );
363 }
364
365 #[test]
366 fn test_call_expr() {
367 check_assist(
368 inline_local_variable,
369 r"
370fn foo() {
371 let a$0 = bar(10 + 1);
372 let b = a * 10;
373 let c = a as usize;
374}",
375 r"
376fn foo() {
377 let b = bar(10 + 1) * 10;
378 let c = bar(10 + 1) as usize;
379}",
380 );
381 }
382
383 #[test]
384 fn test_index_expr() {
385 check_assist(
386 inline_local_variable,
387 r"
388fn foo() {
389 let x = vec![1, 2, 3];
390 let a$0 = x[0];
391 let b = a * 10;
392 let c = a as usize;
393}",
394 r"
395fn foo() {
396 let x = vec![1, 2, 3];
397 let b = x[0] * 10;
398 let c = x[0] as usize;
399}",
400 );
401 }
402
403 #[test]
404 fn test_method_call_expr() {
405 check_assist(
406 inline_local_variable,
407 r"
408fn foo() {
409 let bar = vec![1];
410 let a$0 = bar.len();
411 let b = a * 10;
412 let c = a as usize;
413}",
414 r"
415fn foo() {
416 let bar = vec![1];
417 let b = bar.len() * 10;
418 let c = bar.len() as usize;
419}",
420 );
421 }
422
423 #[test]
424 fn test_field_expr() {
425 check_assist(
426 inline_local_variable,
427 r"
428struct Bar {
429 foo: usize
430}
431
432fn foo() {
433 let bar = Bar { foo: 1 };
434 let a$0 = bar.foo;
435 let b = a * 10;
436 let c = a as usize;
437}",
438 r"
439struct Bar {
440 foo: usize
441}
442
443fn foo() {
444 let bar = Bar { foo: 1 };
445 let b = bar.foo * 10;
446 let c = bar.foo as usize;
447}",
448 );
449 }
450
451 #[test]
452 fn test_try_expr() {
453 check_assist(
454 inline_local_variable,
455 r"
456fn foo() -> Option<usize> {
457 let bar = Some(1);
458 let a$0 = bar?;
459 let b = a * 10;
460 let c = a as usize;
461 None
462}",
463 r"
464fn foo() -> Option<usize> {
465 let bar = Some(1);
466 let b = bar? * 10;
467 let c = bar? as usize;
468 None
469}",
470 );
471 }
472
473 #[test]
474 fn test_ref_expr() {
475 check_assist(
476 inline_local_variable,
477 r"
478fn foo() {
479 let bar = 10;
480 let a$0 = &bar;
481 let b = a * 10;
482}",
483 r"
484fn foo() {
485 let bar = 10;
486 let b = &bar * 10;
487}",
488 );
489 }
490
491 #[test]
492 fn test_tuple_expr() {
493 check_assist(
494 inline_local_variable,
495 r"
496fn foo() {
497 let a$0 = (10, 20);
498 let b = a[0];
499}",
500 r"
501fn foo() {
502 let b = (10, 20)[0];
503}",
504 );
505 }
506
507 #[test]
508 fn test_array_expr() {
509 check_assist(
510 inline_local_variable,
511 r"
512fn foo() {
513 let a$0 = [1, 2, 3];
514 let b = a.len();
515}",
516 r"
517fn foo() {
518 let b = [1, 2, 3].len();
519}",
520 );
521 }
522
523 #[test]
524 fn test_paren() {
525 check_assist(
526 inline_local_variable,
527 r"
528fn foo() {
529 let a$0 = (10 + 20);
530 let b = a * 10;
531 let c = a as usize;
532}",
533 r"
534fn foo() {
535 let b = (10 + 20) * 10;
536 let c = (10 + 20) as usize;
537}",
538 );
539 }
540
541 #[test]
542 fn test_path_expr() {
543 check_assist(
544 inline_local_variable,
545 r"
546fn foo() {
547 let d = 10;
548 let a$0 = d;
549 let b = a * 10;
550 let c = a as usize;
551}",
552 r"
553fn foo() {
554 let d = 10;
555 let b = d * 10;
556 let c = d as usize;
557}",
558 );
559 }
560
561 #[test]
562 fn test_block_expr() {
563 check_assist(
564 inline_local_variable,
565 r"
566fn foo() {
567 let a$0 = { 10 };
568 let b = a * 10;
569 let c = a as usize;
570}",
571 r"
572fn foo() {
573 let b = { 10 } * 10;
574 let c = { 10 } as usize;
575}",
576 );
577 }
578
579 #[test]
580 fn test_used_in_different_expr1() {
581 check_assist(
582 inline_local_variable,
583 r"
584fn foo() {
585 let a$0 = 10 + 20;
586 let b = a * 10;
587 let c = (a, 20);
588 let d = [a, 10];
589 let e = (a);
590}",
591 r"
592fn foo() {
593 let b = (10 + 20) * 10;
594 let c = (10 + 20, 20);
595 let d = [10 + 20, 10];
596 let e = (10 + 20);
597}",
598 );
599 }
600
601 #[test]
602 fn test_used_in_for_expr() {
603 check_assist(
604 inline_local_variable,
605 r"
606fn foo() {
607 let a$0 = vec![10, 20];
608 for i in a {}
609}",
610 r"
611fn foo() {
612 for i in vec![10, 20] {}
613}",
614 );
615 }
616
617 #[test]
618 fn test_used_in_while_expr() {
619 check_assist(
620 inline_local_variable,
621 r"
622fn foo() {
623 let a$0 = 1 > 0;
624 while a {}
625}",
626 r"
627fn foo() {
628 while 1 > 0 {}
629}",
630 );
631 }
632
633 #[test]
634 fn test_used_in_break_expr() {
635 check_assist(
636 inline_local_variable,
637 r"
638fn foo() {
639 let a$0 = 1 + 1;
640 loop {
641 break a;
642 }
643}",
644 r"
645fn foo() {
646 loop {
647 break 1 + 1;
648 }
649}",
650 );
651 }
652
653 #[test]
654 fn test_used_in_return_expr() {
655 check_assist(
656 inline_local_variable,
657 r"
658fn foo() {
659 let a$0 = 1 > 0;
660 return a;
661}",
662 r"
663fn foo() {
664 return 1 > 0;
665}",
666 );
667 }
668
669 #[test]
670 fn test_used_in_match_expr() {
671 check_assist(
672 inline_local_variable,
673 r"
674fn foo() {
675 let a$0 = 1 > 0;
676 match a {}
677}",
678 r"
679fn foo() {
680 match 1 > 0 {}
681}",
682 );
683 }
684
685 #[test]
686 fn inline_field_shorthand() {
687 mark::check!(inline_field_shorthand);
688 check_assist(
689 inline_local_variable,
690 r"
691struct S { foo: i32}
692fn main() {
693 let $0foo = 92;
694 S { foo }
695}
696",
697 r"
698struct S { foo: i32}
699fn main() {
700 S { foo: 92 }
701}
702",
703 );
704 }
705
706 #[test]
707 fn test_not_applicable_if_variable_unused() {
708 mark::check!(test_not_applicable_if_variable_unused);
709 check_assist_not_applicable(
710 inline_local_variable,
711 r"
712fn foo() {
713 let $0a = 0;
714}
715 ",
716 )
717 }
718
719 #[test]
720 fn not_applicable_outside_of_bind_pat() {
721 mark::check!(not_applicable_outside_of_bind_pat);
722 check_assist_not_applicable(
723 inline_local_variable,
724 r"
725fn main() {
726 let x = $01 + 2;
727 x * 4;
728}
729",
730 )
731 }
732}
diff --git a/crates/ide_assists/src/handlers/introduce_named_lifetime.rs b/crates/ide_assists/src/handlers/introduce_named_lifetime.rs
new file mode 100644
index 000000000..02782eb6d
--- /dev/null
+++ b/crates/ide_assists/src/handlers/introduce_named_lifetime.rs
@@ -0,0 +1,315 @@
1use rustc_hash::FxHashSet;
2use syntax::{
3 ast::{self, GenericParamsOwner, NameOwner},
4 AstNode, TextRange, TextSize,
5};
6
7use crate::{assist_context::AssistBuilder, AssistContext, AssistId, AssistKind, Assists};
8
9static ASSIST_NAME: &str = "introduce_named_lifetime";
10static ASSIST_LABEL: &str = "Introduce named lifetime";
11
12// Assist: introduce_named_lifetime
13//
14// Change an anonymous lifetime to a named lifetime.
15//
16// ```
17// impl Cursor<'_$0> {
18// fn node(self) -> &SyntaxNode {
19// match self {
20// Cursor::Replace(node) | Cursor::Before(node) => node,
21// }
22// }
23// }
24// ```
25// ->
26// ```
27// impl<'a> Cursor<'a> {
28// fn node(self) -> &SyntaxNode {
29// match self {
30// Cursor::Replace(node) | Cursor::Before(node) => node,
31// }
32// }
33// }
34// ```
35// FIXME: How can we handle renaming any one of multiple anonymous lifetimes?
36// FIXME: should also add support for the case fun(f: &Foo) -> &$0Foo
37pub(crate) fn introduce_named_lifetime(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
38 let lifetime =
39 ctx.find_node_at_offset::<ast::Lifetime>().filter(|lifetime| lifetime.text() == "'_")?;
40 if let Some(fn_def) = lifetime.syntax().ancestors().find_map(ast::Fn::cast) {
41 generate_fn_def_assist(acc, &fn_def, lifetime.lifetime_ident_token()?.text_range())
42 } else if let Some(impl_def) = lifetime.syntax().ancestors().find_map(ast::Impl::cast) {
43 generate_impl_def_assist(acc, &impl_def, lifetime.lifetime_ident_token()?.text_range())
44 } else {
45 None
46 }
47}
48
49/// Generate the assist for the fn def case
50fn generate_fn_def_assist(
51 acc: &mut Assists,
52 fn_def: &ast::Fn,
53 lifetime_loc: TextRange,
54) -> Option<()> {
55 let param_list: ast::ParamList = fn_def.param_list()?;
56 let new_lifetime_param = generate_unique_lifetime_param_name(&fn_def.generic_param_list())?;
57 let end_of_fn_ident = fn_def.name()?.ident_token()?.text_range().end();
58 let self_param =
59 // use the self if it's a reference and has no explicit lifetime
60 param_list.self_param().filter(|p| p.lifetime().is_none() && p.amp_token().is_some());
61 // compute the location which implicitly has the same lifetime as the anonymous lifetime
62 let loc_needing_lifetime = if let Some(self_param) = self_param {
63 // if we have a self reference, use that
64 Some(self_param.name()?.syntax().text_range().start())
65 } else {
66 // otherwise, if there's a single reference parameter without a named liftime, use that
67 let fn_params_without_lifetime: Vec<_> = param_list
68 .params()
69 .filter_map(|param| match param.ty() {
70 Some(ast::Type::RefType(ascribed_type)) if ascribed_type.lifetime().is_none() => {
71 Some(ascribed_type.amp_token()?.text_range().end())
72 }
73 _ => None,
74 })
75 .collect();
76 match fn_params_without_lifetime.len() {
77 1 => Some(fn_params_without_lifetime.into_iter().nth(0)?),
78 0 => None,
79 // multiple unnnamed is invalid. assist is not applicable
80 _ => return None,
81 }
82 };
83 acc.add(AssistId(ASSIST_NAME, AssistKind::Refactor), ASSIST_LABEL, lifetime_loc, |builder| {
84 add_lifetime_param(fn_def, builder, end_of_fn_ident, new_lifetime_param);
85 builder.replace(lifetime_loc, format!("'{}", new_lifetime_param));
86 loc_needing_lifetime.map(|loc| builder.insert(loc, format!("'{} ", new_lifetime_param)));
87 })
88}
89
90/// Generate the assist for the impl def case
91fn generate_impl_def_assist(
92 acc: &mut Assists,
93 impl_def: &ast::Impl,
94 lifetime_loc: TextRange,
95) -> Option<()> {
96 let new_lifetime_param = generate_unique_lifetime_param_name(&impl_def.generic_param_list())?;
97 let end_of_impl_kw = impl_def.impl_token()?.text_range().end();
98 acc.add(AssistId(ASSIST_NAME, AssistKind::Refactor), ASSIST_LABEL, lifetime_loc, |builder| {
99 add_lifetime_param(impl_def, builder, end_of_impl_kw, new_lifetime_param);
100 builder.replace(lifetime_loc, format!("'{}", new_lifetime_param));
101 })
102}
103
104/// Given a type parameter list, generate a unique lifetime parameter name
105/// which is not in the list
106fn generate_unique_lifetime_param_name(
107 existing_type_param_list: &Option<ast::GenericParamList>,
108) -> Option<char> {
109 match existing_type_param_list {
110 Some(type_params) => {
111 let used_lifetime_params: FxHashSet<_> = type_params
112 .lifetime_params()
113 .map(|p| p.syntax().text().to_string()[1..].to_owned())
114 .collect();
115 (b'a'..=b'z').map(char::from).find(|c| !used_lifetime_params.contains(&c.to_string()))
116 }
117 None => Some('a'),
118 }
119}
120
121/// Add the lifetime param to `builder`. If there are type parameters in `type_params_owner`, add it to the end. Otherwise
122/// add new type params brackets with the lifetime parameter at `new_type_params_loc`.
123fn add_lifetime_param<TypeParamsOwner: ast::GenericParamsOwner>(
124 type_params_owner: &TypeParamsOwner,
125 builder: &mut AssistBuilder,
126 new_type_params_loc: TextSize,
127 new_lifetime_param: char,
128) {
129 match type_params_owner.generic_param_list() {
130 // add the new lifetime parameter to an existing type param list
131 Some(type_params) => {
132 builder.insert(
133 (u32::from(type_params.syntax().text_range().end()) - 1).into(),
134 format!(", '{}", new_lifetime_param),
135 );
136 }
137 // create a new type param list containing only the new lifetime parameter
138 None => {
139 builder.insert(new_type_params_loc, format!("<'{}>", new_lifetime_param));
140 }
141 }
142}
143
144#[cfg(test)]
145mod tests {
146 use super::*;
147 use crate::tests::{check_assist, check_assist_not_applicable};
148
149 #[test]
150 fn test_example_case() {
151 check_assist(
152 introduce_named_lifetime,
153 r#"impl Cursor<'_$0> {
154 fn node(self) -> &SyntaxNode {
155 match self {
156 Cursor::Replace(node) | Cursor::Before(node) => node,
157 }
158 }
159 }"#,
160 r#"impl<'a> Cursor<'a> {
161 fn node(self) -> &SyntaxNode {
162 match self {
163 Cursor::Replace(node) | Cursor::Before(node) => node,
164 }
165 }
166 }"#,
167 );
168 }
169
170 #[test]
171 fn test_example_case_simplified() {
172 check_assist(
173 introduce_named_lifetime,
174 r#"impl Cursor<'_$0> {"#,
175 r#"impl<'a> Cursor<'a> {"#,
176 );
177 }
178
179 #[test]
180 fn test_example_case_cursor_after_tick() {
181 check_assist(
182 introduce_named_lifetime,
183 r#"impl Cursor<'$0_> {"#,
184 r#"impl<'a> Cursor<'a> {"#,
185 );
186 }
187
188 #[test]
189 fn test_impl_with_other_type_param() {
190 check_assist(
191 introduce_named_lifetime,
192 "impl<I> fmt::Display for SepByBuilder<'_$0, I>
193 where
194 I: Iterator,
195 I::Item: fmt::Display,
196 {",
197 "impl<I, 'a> fmt::Display for SepByBuilder<'a, I>
198 where
199 I: Iterator,
200 I::Item: fmt::Display,
201 {",
202 )
203 }
204
205 #[test]
206 fn test_example_case_cursor_before_tick() {
207 check_assist(
208 introduce_named_lifetime,
209 r#"impl Cursor<$0'_> {"#,
210 r#"impl<'a> Cursor<'a> {"#,
211 );
212 }
213
214 #[test]
215 fn test_not_applicable_cursor_position() {
216 check_assist_not_applicable(introduce_named_lifetime, r#"impl Cursor<'_>$0 {"#);
217 check_assist_not_applicable(introduce_named_lifetime, r#"impl Cursor$0<'_> {"#);
218 }
219
220 #[test]
221 fn test_not_applicable_lifetime_already_name() {
222 check_assist_not_applicable(introduce_named_lifetime, r#"impl Cursor<'a$0> {"#);
223 check_assist_not_applicable(introduce_named_lifetime, r#"fn my_fun<'a>() -> X<'a$0>"#);
224 }
225
226 #[test]
227 fn test_with_type_parameter() {
228 check_assist(
229 introduce_named_lifetime,
230 r#"impl<T> Cursor<T, '_$0>"#,
231 r#"impl<T, 'a> Cursor<T, 'a>"#,
232 );
233 }
234
235 #[test]
236 fn test_with_existing_lifetime_name_conflict() {
237 check_assist(
238 introduce_named_lifetime,
239 r#"impl<'a, 'b> Cursor<'a, 'b, '_$0>"#,
240 r#"impl<'a, 'b, 'c> Cursor<'a, 'b, 'c>"#,
241 );
242 }
243
244 #[test]
245 fn test_function_return_value_anon_lifetime_param() {
246 check_assist(
247 introduce_named_lifetime,
248 r#"fn my_fun() -> X<'_$0>"#,
249 r#"fn my_fun<'a>() -> X<'a>"#,
250 );
251 }
252
253 #[test]
254 fn test_function_return_value_anon_reference_lifetime() {
255 check_assist(
256 introduce_named_lifetime,
257 r#"fn my_fun() -> &'_$0 X"#,
258 r#"fn my_fun<'a>() -> &'a X"#,
259 );
260 }
261
262 #[test]
263 fn test_function_param_anon_lifetime() {
264 check_assist(
265 introduce_named_lifetime,
266 r#"fn my_fun(x: X<'_$0>)"#,
267 r#"fn my_fun<'a>(x: X<'a>)"#,
268 );
269 }
270
271 #[test]
272 fn test_function_add_lifetime_to_params() {
273 check_assist(
274 introduce_named_lifetime,
275 r#"fn my_fun(f: &Foo) -> X<'_$0>"#,
276 r#"fn my_fun<'a>(f: &'a Foo) -> X<'a>"#,
277 );
278 }
279
280 #[test]
281 fn test_function_add_lifetime_to_params_in_presence_of_other_lifetime() {
282 check_assist(
283 introduce_named_lifetime,
284 r#"fn my_fun<'other>(f: &Foo, b: &'other Bar) -> X<'_$0>"#,
285 r#"fn my_fun<'other, 'a>(f: &'a Foo, b: &'other Bar) -> X<'a>"#,
286 );
287 }
288
289 #[test]
290 fn test_function_not_applicable_without_self_and_multiple_unnamed_param_lifetimes() {
291 // this is not permitted under lifetime elision rules
292 check_assist_not_applicable(
293 introduce_named_lifetime,
294 r#"fn my_fun(f: &Foo, b: &Bar) -> X<'_$0>"#,
295 );
296 }
297
298 #[test]
299 fn test_function_add_lifetime_to_self_ref_param() {
300 check_assist(
301 introduce_named_lifetime,
302 r#"fn my_fun<'other>(&self, f: &Foo, b: &'other Bar) -> X<'_$0>"#,
303 r#"fn my_fun<'other, 'a>(&'a self, f: &Foo, b: &'other Bar) -> X<'a>"#,
304 );
305 }
306
307 #[test]
308 fn test_function_add_lifetime_to_param_with_non_ref_self() {
309 check_assist(
310 introduce_named_lifetime,
311 r#"fn my_fun<'other>(self, f: &Foo, b: &'other Bar) -> X<'_$0>"#,
312 r#"fn my_fun<'other, 'a>(self, f: &'a Foo, b: &'other Bar) -> X<'a>"#,
313 );
314 }
315}
diff --git a/crates/ide_assists/src/handlers/invert_if.rs b/crates/ide_assists/src/handlers/invert_if.rs
new file mode 100644
index 000000000..5b69dafd4
--- /dev/null
+++ b/crates/ide_assists/src/handlers/invert_if.rs
@@ -0,0 +1,146 @@
1use syntax::{
2 ast::{self, AstNode},
3 T,
4};
5
6use crate::{
7 assist_context::{AssistContext, Assists},
8 utils::invert_boolean_expression,
9 AssistId, AssistKind,
10};
11
12// Assist: invert_if
13//
14// Apply invert_if
15// This transforms if expressions of the form `if !x {A} else {B}` into `if x {B} else {A}`
16// This also works with `!=`. This assist can only be applied with the cursor
17// on `if`.
18//
19// ```
20// fn main() {
21// if$0 !y { A } else { B }
22// }
23// ```
24// ->
25// ```
26// fn main() {
27// if y { B } else { A }
28// }
29// ```
30
31pub(crate) fn invert_if(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
32 let if_keyword = ctx.find_token_syntax_at_offset(T![if])?;
33 let expr = ast::IfExpr::cast(if_keyword.parent())?;
34 let if_range = if_keyword.text_range();
35 let cursor_in_range = if_range.contains_range(ctx.frange.range);
36 if !cursor_in_range {
37 return None;
38 }
39
40 // This assist should not apply for if-let.
41 if expr.condition()?.pat().is_some() {
42 return None;
43 }
44
45 let cond = expr.condition()?.expr()?;
46 let then_node = expr.then_branch()?.syntax().clone();
47 let else_block = match expr.else_branch()? {
48 ast::ElseBranch::Block(it) => it,
49 ast::ElseBranch::IfExpr(_) => return None,
50 };
51
52 acc.add(AssistId("invert_if", AssistKind::RefactorRewrite), "Invert if", if_range, |edit| {
53 let flip_cond = invert_boolean_expression(cond.clone());
54 edit.replace_ast(cond, flip_cond);
55
56 let else_node = else_block.syntax();
57 let else_range = else_node.text_range();
58 let then_range = then_node.text_range();
59
60 edit.replace(else_range, then_node.text());
61 edit.replace(then_range, else_node.text());
62 })
63}
64
65#[cfg(test)]
66mod tests {
67 use super::*;
68
69 use crate::tests::{check_assist, check_assist_not_applicable};
70
71 #[test]
72 fn invert_if_composite_condition() {
73 check_assist(
74 invert_if,
75 "fn f() { i$0f x == 3 || x == 4 || x == 5 { 1 } else { 3 * 2 } }",
76 "fn f() { if !(x == 3 || x == 4 || x == 5) { 3 * 2 } else { 1 } }",
77 )
78 }
79
80 #[test]
81 fn invert_if_remove_not_parentheses() {
82 check_assist(
83 invert_if,
84 "fn f() { i$0f !(x == 3 || x == 4 || x == 5) { 3 * 2 } else { 1 } }",
85 "fn f() { if x == 3 || x == 4 || x == 5 { 1 } else { 3 * 2 } }",
86 )
87 }
88
89 #[test]
90 fn invert_if_remove_inequality() {
91 check_assist(
92 invert_if,
93 "fn f() { i$0f x != 3 { 1 } else { 3 + 2 } }",
94 "fn f() { if x == 3 { 3 + 2 } else { 1 } }",
95 )
96 }
97
98 #[test]
99 fn invert_if_remove_not() {
100 check_assist(
101 invert_if,
102 "fn f() { $0if !cond { 3 * 2 } else { 1 } }",
103 "fn f() { if cond { 1 } else { 3 * 2 } }",
104 )
105 }
106
107 #[test]
108 fn invert_if_general_case() {
109 check_assist(
110 invert_if,
111 "fn f() { i$0f cond { 3 * 2 } else { 1 } }",
112 "fn f() { if !cond { 1 } else { 3 * 2 } }",
113 )
114 }
115
116 #[test]
117 fn invert_if_doesnt_apply_with_cursor_not_on_if() {
118 check_assist_not_applicable(invert_if, "fn f() { if !$0cond { 3 * 2 } else { 1 } }")
119 }
120
121 #[test]
122 fn invert_if_doesnt_apply_with_if_let() {
123 check_assist_not_applicable(
124 invert_if,
125 "fn f() { i$0f let Some(_) = Some(1) { 1 } else { 0 } }",
126 )
127 }
128
129 #[test]
130 fn invert_if_option_case() {
131 check_assist(
132 invert_if,
133 "fn f() { if$0 doc_style.is_some() { Class::DocComment } else { Class::Comment } }",
134 "fn f() { if doc_style.is_none() { Class::Comment } else { Class::DocComment } }",
135 )
136 }
137
138 #[test]
139 fn invert_if_result_case() {
140 check_assist(
141 invert_if,
142 "fn f() { i$0f doc_style.is_err() { Class::Err } else { Class::Ok } }",
143 "fn f() { if doc_style.is_ok() { Class::Ok } else { Class::Err } }",
144 )
145 }
146}
diff --git a/crates/ide_assists/src/handlers/merge_imports.rs b/crates/ide_assists/src/handlers/merge_imports.rs
new file mode 100644
index 000000000..7bd7e1e36
--- /dev/null
+++ b/crates/ide_assists/src/handlers/merge_imports.rs
@@ -0,0 +1,343 @@
1use ide_db::helpers::insert_use::{try_merge_imports, try_merge_trees, MergeBehavior};
2use syntax::{
3 algo::{neighbor, SyntaxRewriter},
4 ast, AstNode,
5};
6
7use crate::{
8 assist_context::{AssistContext, Assists},
9 utils::next_prev,
10 AssistId, AssistKind,
11};
12
13// Assist: merge_imports
14//
15// Merges two imports with a common prefix.
16//
17// ```
18// use std::$0fmt::Formatter;
19// use std::io;
20// ```
21// ->
22// ```
23// use std::{fmt::Formatter, io};
24// ```
25pub(crate) fn merge_imports(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
26 let tree: ast::UseTree = ctx.find_node_at_offset()?;
27 let mut rewriter = SyntaxRewriter::default();
28 let mut offset = ctx.offset();
29
30 if let Some(use_item) = tree.syntax().parent().and_then(ast::Use::cast) {
31 let (merged, to_delete) =
32 next_prev().filter_map(|dir| neighbor(&use_item, dir)).find_map(|use_item2| {
33 try_merge_imports(&use_item, &use_item2, MergeBehavior::Full).zip(Some(use_item2))
34 })?;
35
36 rewriter.replace_ast(&use_item, &merged);
37 rewriter += to_delete.remove();
38
39 if to_delete.syntax().text_range().end() < offset {
40 offset -= to_delete.syntax().text_range().len();
41 }
42 } else {
43 let (merged, to_delete) =
44 next_prev().filter_map(|dir| neighbor(&tree, dir)).find_map(|use_tree| {
45 try_merge_trees(&tree, &use_tree, MergeBehavior::Full).zip(Some(use_tree))
46 })?;
47
48 rewriter.replace_ast(&tree, &merged);
49 rewriter += to_delete.remove();
50
51 if to_delete.syntax().text_range().end() < offset {
52 offset -= to_delete.syntax().text_range().len();
53 }
54 };
55
56 let target = tree.syntax().text_range();
57 acc.add(
58 AssistId("merge_imports", AssistKind::RefactorRewrite),
59 "Merge imports",
60 target,
61 |builder| {
62 builder.rewrite(rewriter);
63 },
64 )
65}
66
67#[cfg(test)]
68mod tests {
69 use crate::tests::{check_assist, check_assist_not_applicable};
70
71 use super::*;
72
73 #[test]
74 fn test_merge_equal() {
75 check_assist(
76 merge_imports,
77 r"
78use std::fmt$0::{Display, Debug};
79use std::fmt::{Display, Debug};
80",
81 r"
82use std::fmt::{Debug, Display};
83",
84 )
85 }
86
87 #[test]
88 fn test_merge_first() {
89 check_assist(
90 merge_imports,
91 r"
92use std::fmt$0::Debug;
93use std::fmt::Display;
94",
95 r"
96use std::fmt::{Debug, Display};
97",
98 )
99 }
100
101 #[test]
102 fn test_merge_second() {
103 check_assist(
104 merge_imports,
105 r"
106use std::fmt::Debug;
107use std::fmt$0::Display;
108",
109 r"
110use std::fmt::{Debug, Display};
111",
112 );
113 }
114
115 #[test]
116 fn merge_self1() {
117 check_assist(
118 merge_imports,
119 r"
120use std::fmt$0;
121use std::fmt::Display;
122",
123 r"
124use std::fmt::{self, Display};
125",
126 );
127 }
128
129 #[test]
130 fn merge_self2() {
131 check_assist(
132 merge_imports,
133 r"
134use std::{fmt, $0fmt::Display};
135",
136 r"
137use std::{fmt::{self, Display}};
138",
139 );
140 }
141
142 #[test]
143 fn skip_pub1() {
144 check_assist_not_applicable(
145 merge_imports,
146 r"
147pub use std::fmt$0::Debug;
148use std::fmt::Display;
149",
150 );
151 }
152
153 #[test]
154 fn skip_pub_last() {
155 check_assist_not_applicable(
156 merge_imports,
157 r"
158use std::fmt$0::Debug;
159pub use std::fmt::Display;
160",
161 );
162 }
163
164 #[test]
165 fn skip_pub_crate_pub() {
166 check_assist_not_applicable(
167 merge_imports,
168 r"
169pub(crate) use std::fmt$0::Debug;
170pub use std::fmt::Display;
171",
172 );
173 }
174
175 #[test]
176 fn skip_pub_pub_crate() {
177 check_assist_not_applicable(
178 merge_imports,
179 r"
180pub use std::fmt$0::Debug;
181pub(crate) use std::fmt::Display;
182",
183 );
184 }
185
186 #[test]
187 fn merge_pub() {
188 check_assist(
189 merge_imports,
190 r"
191pub use std::fmt$0::Debug;
192pub use std::fmt::Display;
193",
194 r"
195pub use std::fmt::{Debug, Display};
196",
197 )
198 }
199
200 #[test]
201 fn merge_pub_crate() {
202 check_assist(
203 merge_imports,
204 r"
205pub(crate) use std::fmt$0::Debug;
206pub(crate) use std::fmt::Display;
207",
208 r"
209pub(crate) use std::fmt::{Debug, Display};
210",
211 )
212 }
213
214 #[test]
215 fn test_merge_nested() {
216 check_assist(
217 merge_imports,
218 r"
219use std::{fmt$0::Debug, fmt::Display};
220",
221 r"
222use std::{fmt::{Debug, Display}};
223",
224 );
225 }
226
227 #[test]
228 fn test_merge_nested2() {
229 check_assist(
230 merge_imports,
231 r"
232use std::{fmt::Debug, fmt$0::Display};
233",
234 r"
235use std::{fmt::{Debug, Display}};
236",
237 );
238 }
239
240 #[test]
241 fn test_merge_single_wildcard_diff_prefixes() {
242 check_assist(
243 merge_imports,
244 r"
245use std$0::cell::*;
246use std::str;
247",
248 r"
249use std::{cell::*, str};
250",
251 )
252 }
253
254 #[test]
255 fn test_merge_both_wildcard_diff_prefixes() {
256 check_assist(
257 merge_imports,
258 r"
259use std$0::cell::*;
260use std::str::*;
261",
262 r"
263use std::{cell::*, str::*};
264",
265 )
266 }
267
268 #[test]
269 fn removes_just_enough_whitespace() {
270 check_assist(
271 merge_imports,
272 r"
273use foo$0::bar;
274use foo::baz;
275
276/// Doc comment
277",
278 r"
279use foo::{bar, baz};
280
281/// Doc comment
282",
283 );
284 }
285
286 #[test]
287 fn works_with_trailing_comma() {
288 check_assist(
289 merge_imports,
290 r"
291use {
292 foo$0::bar,
293 foo::baz,
294};
295",
296 r"
297use {
298 foo::{bar, baz},
299};
300",
301 );
302 check_assist(
303 merge_imports,
304 r"
305use {
306 foo::baz,
307 foo$0::bar,
308};
309",
310 r"
311use {
312 foo::{bar, baz},
313};
314",
315 );
316 }
317
318 #[test]
319 fn test_double_comma() {
320 check_assist(
321 merge_imports,
322 r"
323use foo::bar::baz;
324use foo::$0{
325 FooBar,
326};
327",
328 r"
329use foo::{FooBar, bar::baz};
330",
331 )
332 }
333
334 #[test]
335 fn test_empty_use() {
336 check_assist_not_applicable(
337 merge_imports,
338 r"
339use std::$0
340fn main() {}",
341 );
342 }
343}
diff --git a/crates/ide_assists/src/handlers/merge_match_arms.rs b/crates/ide_assists/src/handlers/merge_match_arms.rs
new file mode 100644
index 000000000..9bf076cb9
--- /dev/null
+++ b/crates/ide_assists/src/handlers/merge_match_arms.rs
@@ -0,0 +1,248 @@
1use std::iter::successors;
2
3use syntax::{
4 algo::neighbor,
5 ast::{self, AstNode},
6 Direction,
7};
8
9use crate::{AssistContext, AssistId, AssistKind, Assists, TextRange};
10
11// Assist: merge_match_arms
12//
13// Merges identical match arms.
14//
15// ```
16// enum Action { Move { distance: u32 }, Stop }
17//
18// fn handle(action: Action) {
19// match action {
20// $0Action::Move(..) => foo(),
21// Action::Stop => foo(),
22// }
23// }
24// ```
25// ->
26// ```
27// enum Action { Move { distance: u32 }, Stop }
28//
29// fn handle(action: Action) {
30// match action {
31// Action::Move(..) | Action::Stop => foo(),
32// }
33// }
34// ```
35pub(crate) fn merge_match_arms(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
36 let current_arm = ctx.find_node_at_offset::<ast::MatchArm>()?;
37 // Don't try to handle arms with guards for now - can add support for this later
38 if current_arm.guard().is_some() {
39 return None;
40 }
41 let current_expr = current_arm.expr()?;
42 let current_text_range = current_arm.syntax().text_range();
43
44 // We check if the following match arms match this one. We could, but don't,
45 // compare to the previous match arm as well.
46 let arms_to_merge = successors(Some(current_arm), |it| neighbor(it, Direction::Next))
47 .take_while(|arm| {
48 if arm.guard().is_some() {
49 return false;
50 }
51 match arm.expr() {
52 Some(expr) => expr.syntax().text() == current_expr.syntax().text(),
53 None => false,
54 }
55 })
56 .collect::<Vec<_>>();
57
58 if arms_to_merge.len() <= 1 {
59 return None;
60 }
61
62 acc.add(
63 AssistId("merge_match_arms", AssistKind::RefactorRewrite),
64 "Merge match arms",
65 current_text_range,
66 |edit| {
67 let pats = if arms_to_merge.iter().any(contains_placeholder) {
68 "_".into()
69 } else {
70 arms_to_merge
71 .iter()
72 .filter_map(ast::MatchArm::pat)
73 .map(|x| x.syntax().to_string())
74 .collect::<Vec<String>>()
75 .join(" | ")
76 };
77
78 let arm = format!("{} => {}", pats, current_expr.syntax().text());
79
80 let start = arms_to_merge.first().unwrap().syntax().text_range().start();
81 let end = arms_to_merge.last().unwrap().syntax().text_range().end();
82
83 edit.replace(TextRange::new(start, end), arm);
84 },
85 )
86}
87
88fn contains_placeholder(a: &ast::MatchArm) -> bool {
89 matches!(a.pat(), Some(ast::Pat::WildcardPat(..)))
90}
91
92#[cfg(test)]
93mod tests {
94 use crate::tests::{check_assist, check_assist_not_applicable};
95
96 use super::*;
97
98 #[test]
99 fn merge_match_arms_single_patterns() {
100 check_assist(
101 merge_match_arms,
102 r#"
103 #[derive(Debug)]
104 enum X { A, B, C }
105
106 fn main() {
107 let x = X::A;
108 let y = match x {
109 X::A => { 1i32$0 }
110 X::B => { 1i32 }
111 X::C => { 2i32 }
112 }
113 }
114 "#,
115 r#"
116 #[derive(Debug)]
117 enum X { A, B, C }
118
119 fn main() {
120 let x = X::A;
121 let y = match x {
122 X::A | X::B => { 1i32 }
123 X::C => { 2i32 }
124 }
125 }
126 "#,
127 );
128 }
129
130 #[test]
131 fn merge_match_arms_multiple_patterns() {
132 check_assist(
133 merge_match_arms,
134 r#"
135 #[derive(Debug)]
136 enum X { A, B, C, D, E }
137
138 fn main() {
139 let x = X::A;
140 let y = match x {
141 X::A | X::B => {$0 1i32 },
142 X::C | X::D => { 1i32 },
143 X::E => { 2i32 },
144 }
145 }
146 "#,
147 r#"
148 #[derive(Debug)]
149 enum X { A, B, C, D, E }
150
151 fn main() {
152 let x = X::A;
153 let y = match x {
154 X::A | X::B | X::C | X::D => { 1i32 },
155 X::E => { 2i32 },
156 }
157 }
158 "#,
159 );
160 }
161
162 #[test]
163 fn merge_match_arms_placeholder_pattern() {
164 check_assist(
165 merge_match_arms,
166 r#"
167 #[derive(Debug)]
168 enum X { A, B, C, D, E }
169
170 fn main() {
171 let x = X::A;
172 let y = match x {
173 X::A => { 1i32 },
174 X::B => { 2i$032 },
175 _ => { 2i32 }
176 }
177 }
178 "#,
179 r#"
180 #[derive(Debug)]
181 enum X { A, B, C, D, E }
182
183 fn main() {
184 let x = X::A;
185 let y = match x {
186 X::A => { 1i32 },
187 _ => { 2i32 }
188 }
189 }
190 "#,
191 );
192 }
193
194 #[test]
195 fn merges_all_subsequent_arms() {
196 check_assist(
197 merge_match_arms,
198 r#"
199 enum X { A, B, C, D, E }
200
201 fn main() {
202 match X::A {
203 X::A$0 => 92,
204 X::B => 92,
205 X::C => 92,
206 X::D => 62,
207 _ => panic!(),
208 }
209 }
210 "#,
211 r#"
212 enum X { A, B, C, D, E }
213
214 fn main() {
215 match X::A {
216 X::A | X::B | X::C => 92,
217 X::D => 62,
218 _ => panic!(),
219 }
220 }
221 "#,
222 )
223 }
224
225 #[test]
226 fn merge_match_arms_rejects_guards() {
227 check_assist_not_applicable(
228 merge_match_arms,
229 r#"
230 #[derive(Debug)]
231 enum X {
232 A(i32),
233 B,
234 C
235 }
236
237 fn main() {
238 let x = X::A;
239 let y = match x {
240 X::A(a) if a > 5 => { $01i32 },
241 X::B => { 1i32 },
242 X::C => { 2i32 }
243 }
244 }
245 "#,
246 );
247 }
248}
diff --git a/crates/ide_assists/src/handlers/move_bounds.rs b/crates/ide_assists/src/handlers/move_bounds.rs
new file mode 100644
index 000000000..cf260c6f8
--- /dev/null
+++ b/crates/ide_assists/src/handlers/move_bounds.rs
@@ -0,0 +1,152 @@
1use syntax::{
2 ast::{self, edit::AstNodeEdit, make, AstNode, NameOwner, TypeBoundsOwner},
3 match_ast,
4 SyntaxKind::*,
5 T,
6};
7
8use crate::{AssistContext, AssistId, AssistKind, Assists};
9
10// Assist: move_bounds_to_where_clause
11//
12// Moves inline type bounds to a where clause.
13//
14// ```
15// fn apply<T, U, $0F: FnOnce(T) -> U>(f: F, x: T) -> U {
16// f(x)
17// }
18// ```
19// ->
20// ```
21// fn apply<T, U, F>(f: F, x: T) -> U where F: FnOnce(T) -> U {
22// f(x)
23// }
24// ```
25pub(crate) fn move_bounds_to_where_clause(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
26 let type_param_list = ctx.find_node_at_offset::<ast::GenericParamList>()?;
27
28 let mut type_params = type_param_list.type_params();
29 if type_params.all(|p| p.type_bound_list().is_none()) {
30 return None;
31 }
32
33 let parent = type_param_list.syntax().parent()?;
34 if parent.children_with_tokens().any(|it| it.kind() == WHERE_CLAUSE) {
35 return None;
36 }
37
38 let anchor = match_ast! {
39 match parent {
40 ast::Fn(it) => it.body()?.syntax().clone().into(),
41 ast::Trait(it) => it.assoc_item_list()?.syntax().clone().into(),
42 ast::Impl(it) => it.assoc_item_list()?.syntax().clone().into(),
43 ast::Enum(it) => it.variant_list()?.syntax().clone().into(),
44 ast::Struct(it) => {
45 it.syntax().children_with_tokens()
46 .find(|it| it.kind() == RECORD_FIELD_LIST || it.kind() == T![;])?
47 },
48 _ => return None
49 }
50 };
51
52 let target = type_param_list.syntax().text_range();
53 acc.add(
54 AssistId("move_bounds_to_where_clause", AssistKind::RefactorRewrite),
55 "Move to where clause",
56 target,
57 |edit| {
58 let new_params = type_param_list
59 .type_params()
60 .filter(|it| it.type_bound_list().is_some())
61 .map(|type_param| {
62 let without_bounds = type_param.remove_bounds();
63 (type_param, without_bounds)
64 });
65
66 let new_type_param_list = type_param_list.replace_descendants(new_params);
67 edit.replace_ast(type_param_list.clone(), new_type_param_list);
68
69 let where_clause = {
70 let predicates = type_param_list.type_params().filter_map(build_predicate);
71 make::where_clause(predicates)
72 };
73
74 let to_insert = match anchor.prev_sibling_or_token() {
75 Some(ref elem) if elem.kind() == WHITESPACE => {
76 format!("{} ", where_clause.syntax())
77 }
78 _ => format!(" {}", where_clause.syntax()),
79 };
80 edit.insert(anchor.text_range().start(), to_insert);
81 },
82 )
83}
84
85fn build_predicate(param: ast::TypeParam) -> Option<ast::WherePred> {
86 let path = {
87 let name_ref = make::name_ref(&param.name()?.syntax().to_string());
88 let segment = make::path_segment(name_ref);
89 make::path_unqualified(segment)
90 };
91 let predicate = make::where_pred(path, param.type_bound_list()?.bounds());
92 Some(predicate)
93}
94
95#[cfg(test)]
96mod tests {
97 use super::*;
98
99 use crate::tests::check_assist;
100
101 #[test]
102 fn move_bounds_to_where_clause_fn() {
103 check_assist(
104 move_bounds_to_where_clause,
105 r#"
106 fn foo<T: u32, $0F: FnOnce(T) -> T>() {}
107 "#,
108 r#"
109 fn foo<T, F>() where T: u32, F: FnOnce(T) -> T {}
110 "#,
111 );
112 }
113
114 #[test]
115 fn move_bounds_to_where_clause_impl() {
116 check_assist(
117 move_bounds_to_where_clause,
118 r#"
119 impl<U: u32, $0T> A<U, T> {}
120 "#,
121 r#"
122 impl<U, T> A<U, T> where U: u32 {}
123 "#,
124 );
125 }
126
127 #[test]
128 fn move_bounds_to_where_clause_struct() {
129 check_assist(
130 move_bounds_to_where_clause,
131 r#"
132 struct A<$0T: Iterator<Item = u32>> {}
133 "#,
134 r#"
135 struct A<T> where T: Iterator<Item = u32> {}
136 "#,
137 );
138 }
139
140 #[test]
141 fn move_bounds_to_where_clause_tuple_struct() {
142 check_assist(
143 move_bounds_to_where_clause,
144 r#"
145 struct Pair<$0T: u32>(T, T);
146 "#,
147 r#"
148 struct Pair<T>(T, T) where T: u32;
149 "#,
150 );
151 }
152}
diff --git a/crates/ide_assists/src/handlers/move_guard.rs b/crates/ide_assists/src/handlers/move_guard.rs
new file mode 100644
index 000000000..3f22302a9
--- /dev/null
+++ b/crates/ide_assists/src/handlers/move_guard.rs
@@ -0,0 +1,367 @@
1use syntax::{
2 ast::{edit::AstNodeEdit, make, AstNode, BlockExpr, Expr, IfExpr, MatchArm},
3 SyntaxKind::WHITESPACE,
4};
5
6use crate::{AssistContext, AssistId, AssistKind, Assists};
7
8// Assist: move_guard_to_arm_body
9//
10// Moves match guard into match arm body.
11//
12// ```
13// enum Action { Move { distance: u32 }, Stop }
14//
15// fn handle(action: Action) {
16// match action {
17// Action::Move { distance } $0if distance > 10 => foo(),
18// _ => (),
19// }
20// }
21// ```
22// ->
23// ```
24// enum Action { Move { distance: u32 }, Stop }
25//
26// fn handle(action: Action) {
27// match action {
28// Action::Move { distance } => if distance > 10 {
29// foo()
30// },
31// _ => (),
32// }
33// }
34// ```
35pub(crate) fn move_guard_to_arm_body(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
36 let match_arm = ctx.find_node_at_offset::<MatchArm>()?;
37 let guard = match_arm.guard()?;
38 let space_before_guard = guard.syntax().prev_sibling_or_token();
39
40 let guard_condition = guard.expr()?;
41 let arm_expr = match_arm.expr()?;
42 let if_expr = make::expr_if(
43 make::condition(guard_condition, None),
44 make::block_expr(None, Some(arm_expr.clone())),
45 None,
46 )
47 .indent(arm_expr.indent_level());
48
49 let target = guard.syntax().text_range();
50 acc.add(
51 AssistId("move_guard_to_arm_body", AssistKind::RefactorRewrite),
52 "Move guard to arm body",
53 target,
54 |edit| {
55 match space_before_guard {
56 Some(element) if element.kind() == WHITESPACE => {
57 edit.delete(element.text_range());
58 }
59 _ => (),
60 };
61
62 edit.delete(guard.syntax().text_range());
63 edit.replace_ast(arm_expr, if_expr);
64 },
65 )
66}
67
68// Assist: move_arm_cond_to_match_guard
69//
70// Moves if expression from match arm body into a guard.
71//
72// ```
73// enum Action { Move { distance: u32 }, Stop }
74//
75// fn handle(action: Action) {
76// match action {
77// Action::Move { distance } => $0if distance > 10 { foo() },
78// _ => (),
79// }
80// }
81// ```
82// ->
83// ```
84// enum Action { Move { distance: u32 }, Stop }
85//
86// fn handle(action: Action) {
87// match action {
88// Action::Move { distance } if distance > 10 => foo(),
89// _ => (),
90// }
91// }
92// ```
93pub(crate) fn move_arm_cond_to_match_guard(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
94 let match_arm: MatchArm = ctx.find_node_at_offset::<MatchArm>()?;
95 let match_pat = match_arm.pat()?;
96 let arm_body = match_arm.expr()?;
97
98 let mut replace_node = None;
99 let if_expr: IfExpr = IfExpr::cast(arm_body.syntax().clone()).or_else(|| {
100 let block_expr = BlockExpr::cast(arm_body.syntax().clone())?;
101 if let Expr::IfExpr(e) = block_expr.tail_expr()? {
102 replace_node = Some(block_expr.syntax().clone());
103 Some(e)
104 } else {
105 None
106 }
107 })?;
108 let replace_node = replace_node.unwrap_or_else(|| if_expr.syntax().clone());
109
110 let cond = if_expr.condition()?;
111 let then_block = if_expr.then_branch()?;
112
113 // Not support if with else branch
114 if if_expr.else_branch().is_some() {
115 return None;
116 }
117 // Not support moving if let to arm guard
118 if cond.pat().is_some() {
119 return None;
120 }
121
122 let buf = format!(" if {}", cond.syntax().text());
123
124 acc.add(
125 AssistId("move_arm_cond_to_match_guard", AssistKind::RefactorRewrite),
126 "Move condition to match guard",
127 replace_node.text_range(),
128 |edit| {
129 let then_only_expr = then_block.statements().next().is_none();
130
131 match &then_block.tail_expr() {
132 Some(then_expr) if then_only_expr => {
133 edit.replace(replace_node.text_range(), then_expr.syntax().text())
134 }
135 _ if replace_node != *if_expr.syntax() => {
136 // Dedent because if_expr is in a BlockExpr
137 let replace_with = then_block.dedent(1.into()).syntax().text();
138 edit.replace(replace_node.text_range(), replace_with)
139 }
140 _ => edit.replace(replace_node.text_range(), then_block.syntax().text()),
141 }
142
143 edit.insert(match_pat.syntax().text_range().end(), buf);
144 },
145 )
146}
147
148#[cfg(test)]
149mod tests {
150 use super::*;
151
152 use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target};
153
154 #[test]
155 fn move_guard_to_arm_body_target() {
156 check_assist_target(
157 move_guard_to_arm_body,
158 r#"
159fn main() {
160 match 92 {
161 x $0if x > 10 => false,
162 _ => true
163 }
164}
165"#,
166 r#"if x > 10"#,
167 );
168 }
169
170 #[test]
171 fn move_guard_to_arm_body_works() {
172 check_assist(
173 move_guard_to_arm_body,
174 r#"
175fn main() {
176 match 92 {
177 x $0if x > 10 => false,
178 _ => true
179 }
180}
181"#,
182 r#"
183fn main() {
184 match 92 {
185 x => if x > 10 {
186 false
187 },
188 _ => true
189 }
190}
191"#,
192 );
193 }
194
195 #[test]
196 fn move_guard_to_arm_body_works_complex_match() {
197 check_assist(
198 move_guard_to_arm_body,
199 r#"
200fn main() {
201 match 92 {
202 $0x @ 4 | x @ 5 if x > 5 => true,
203 _ => false
204 }
205}
206"#,
207 r#"
208fn main() {
209 match 92 {
210 x @ 4 | x @ 5 => if x > 5 {
211 true
212 },
213 _ => false
214 }
215}
216"#,
217 );
218 }
219
220 #[test]
221 fn move_arm_cond_to_match_guard_works() {
222 check_assist(
223 move_arm_cond_to_match_guard,
224 r#"
225fn main() {
226 match 92 {
227 x => if x > 10 { $0false },
228 _ => true
229 }
230}
231"#,
232 r#"
233fn main() {
234 match 92 {
235 x if x > 10 => false,
236 _ => true
237 }
238}
239"#,
240 );
241 }
242
243 #[test]
244 fn move_arm_cond_in_block_to_match_guard_works() {
245 check_assist(
246 move_arm_cond_to_match_guard,
247 r#"
248fn main() {
249 match 92 {
250 x => {
251 $0if x > 10 {
252 false
253 }
254 },
255 _ => true
256 }
257}
258"#,
259 r#"
260fn main() {
261 match 92 {
262 x if x > 10 => false,
263 _ => true
264 }
265}
266"#,
267 );
268 }
269
270 #[test]
271 fn move_arm_cond_to_match_guard_if_let_not_works() {
272 check_assist_not_applicable(
273 move_arm_cond_to_match_guard,
274 r#"
275fn main() {
276 match 92 {
277 x => if let 62 = x { $0false },
278 _ => true
279 }
280}
281"#,
282 );
283 }
284
285 #[test]
286 fn move_arm_cond_to_match_guard_if_empty_body_works() {
287 check_assist(
288 move_arm_cond_to_match_guard,
289 r#"
290fn main() {
291 match 92 {
292 x => if x > 10 { $0 },
293 _ => true
294 }
295}
296"#,
297 r#"
298fn main() {
299 match 92 {
300 x if x > 10 => { },
301 _ => true
302 }
303}
304"#,
305 );
306 }
307
308 #[test]
309 fn move_arm_cond_to_match_guard_if_multiline_body_works() {
310 check_assist(
311 move_arm_cond_to_match_guard,
312 r#"
313fn main() {
314 match 92 {
315 x => if x > 10 {
316 92;$0
317 false
318 },
319 _ => true
320 }
321}
322"#,
323 r#"
324fn main() {
325 match 92 {
326 x if x > 10 => {
327 92;
328 false
329 },
330 _ => true
331 }
332}
333"#,
334 );
335 }
336
337 #[test]
338 fn move_arm_cond_in_block_to_match_guard_if_multiline_body_works() {
339 check_assist(
340 move_arm_cond_to_match_guard,
341 r#"
342fn main() {
343 match 92 {
344 x => {
345 if x > 10 {
346 92;$0
347 false
348 }
349 }
350 _ => true
351 }
352}
353"#,
354 r#"
355fn main() {
356 match 92 {
357 x if x > 10 => {
358 92;
359 false
360 }
361 _ => true
362 }
363}
364"#,
365 )
366 }
367}
diff --git a/crates/ide_assists/src/handlers/move_module_to_file.rs b/crates/ide_assists/src/handlers/move_module_to_file.rs
new file mode 100644
index 000000000..91c395c1b
--- /dev/null
+++ b/crates/ide_assists/src/handlers/move_module_to_file.rs
@@ -0,0 +1,188 @@
1use ast::{edit::IndentLevel, VisibilityOwner};
2use ide_db::base_db::AnchoredPathBuf;
3use stdx::format_to;
4use syntax::{
5 ast::{self, edit::AstNodeEdit, NameOwner},
6 AstNode, TextRange,
7};
8use test_utils::mark;
9
10use crate::{AssistContext, AssistId, AssistKind, Assists};
11
12// Assist: move_module_to_file
13//
14// Moves inline module's contents to a separate file.
15//
16// ```
17// mod $0foo {
18// fn t() {}
19// }
20// ```
21// ->
22// ```
23// mod foo;
24// ```
25pub(crate) fn move_module_to_file(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
26 let module_ast = ctx.find_node_at_offset::<ast::Module>()?;
27 let module_items = module_ast.item_list()?;
28
29 let l_curly_offset = module_items.syntax().text_range().start();
30 if l_curly_offset <= ctx.offset() {
31 mark::hit!(available_before_curly);
32 return None;
33 }
34 let target = TextRange::new(module_ast.syntax().text_range().start(), l_curly_offset);
35
36 let module_name = module_ast.name()?;
37
38 let module_def = ctx.sema.to_def(&module_ast)?;
39 let parent_module = module_def.parent(ctx.db())?;
40
41 acc.add(
42 AssistId("move_module_to_file", AssistKind::RefactorExtract),
43 "Extract module to file",
44 target,
45 |builder| {
46 let path = {
47 let dir = match parent_module.name(ctx.db()) {
48 Some(name) if !parent_module.is_mod_rs(ctx.db()) => format!("{}/", name),
49 _ => String::new(),
50 };
51 format!("./{}{}.rs", dir, module_name)
52 };
53 let contents = {
54 let items = module_items.dedent(IndentLevel(1)).to_string();
55 let mut items =
56 items.trim_start_matches('{').trim_end_matches('}').trim().to_string();
57 if !items.is_empty() {
58 items.push('\n');
59 }
60 items
61 };
62
63 let mut buf = String::new();
64 if let Some(v) = module_ast.visibility() {
65 format_to!(buf, "{} ", v);
66 }
67 format_to!(buf, "mod {};", module_name);
68
69 builder.replace(module_ast.syntax().text_range(), buf);
70
71 let dst = AnchoredPathBuf { anchor: ctx.frange.file_id, path };
72 builder.create_file(dst, contents);
73 },
74 )
75}
76
77#[cfg(test)]
78mod tests {
79 use crate::tests::{check_assist, check_assist_not_applicable};
80
81 use super::*;
82
83 #[test]
84 fn extract_from_root() {
85 check_assist(
86 move_module_to_file,
87 r#"
88mod $0tests {
89 #[test] fn t() {}
90}
91"#,
92 r#"
93//- /main.rs
94mod tests;
95//- /tests.rs
96#[test] fn t() {}
97"#,
98 );
99 }
100
101 #[test]
102 fn extract_from_submodule() {
103 check_assist(
104 move_module_to_file,
105 r#"
106//- /main.rs
107mod submod;
108//- /submod.rs
109$0mod inner {
110 fn f() {}
111}
112fn g() {}
113"#,
114 r#"
115//- /submod.rs
116mod inner;
117fn g() {}
118//- /submod/inner.rs
119fn f() {}
120"#,
121 );
122 }
123
124 #[test]
125 fn extract_from_mod_rs() {
126 check_assist(
127 move_module_to_file,
128 r#"
129//- /main.rs
130mod submodule;
131//- /submodule/mod.rs
132mod inner$0 {
133 fn f() {}
134}
135fn g() {}
136"#,
137 r#"
138//- /submodule/mod.rs
139mod inner;
140fn g() {}
141//- /submodule/inner.rs
142fn f() {}
143"#,
144 );
145 }
146
147 #[test]
148 fn extract_public() {
149 check_assist(
150 move_module_to_file,
151 r#"
152pub mod $0tests {
153 #[test] fn t() {}
154}
155"#,
156 r#"
157//- /main.rs
158pub mod tests;
159//- /tests.rs
160#[test] fn t() {}
161"#,
162 );
163 }
164
165 #[test]
166 fn extract_public_crate() {
167 check_assist(
168 move_module_to_file,
169 r#"
170pub(crate) mod $0tests {
171 #[test] fn t() {}
172}
173"#,
174 r#"
175//- /main.rs
176pub(crate) mod tests;
177//- /tests.rs
178#[test] fn t() {}
179"#,
180 );
181 }
182
183 #[test]
184 fn available_before_curly() {
185 mark::check!(available_before_curly);
186 check_assist_not_applicable(move_module_to_file, r#"mod m { $0 }"#);
187 }
188}
diff --git a/crates/ide_assists/src/handlers/pull_assignment_up.rs b/crates/ide_assists/src/handlers/pull_assignment_up.rs
new file mode 100644
index 000000000..13e1cb754
--- /dev/null
+++ b/crates/ide_assists/src/handlers/pull_assignment_up.rs
@@ -0,0 +1,400 @@
1use syntax::{
2 ast::{self, edit::AstNodeEdit, make},
3 AstNode,
4};
5use test_utils::mark;
6
7use crate::{
8 assist_context::{AssistContext, Assists},
9 AssistId, AssistKind,
10};
11
12// Assist: pull_assignment_up
13//
14// Extracts variable assignment to outside an if or match statement.
15//
16// ```
17// fn main() {
18// let mut foo = 6;
19//
20// if true {
21// $0foo = 5;
22// } else {
23// foo = 4;
24// }
25// }
26// ```
27// ->
28// ```
29// fn main() {
30// let mut foo = 6;
31//
32// foo = if true {
33// 5
34// } else {
35// 4
36// };
37// }
38// ```
39pub(crate) fn pull_assignment_up(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
40 let assign_expr = ctx.find_node_at_offset::<ast::BinExpr>()?;
41 let name_expr = if assign_expr.op_kind()? == ast::BinOp::Assignment {
42 assign_expr.lhs()?
43 } else {
44 return None;
45 };
46
47 let (old_stmt, new_stmt) = if let Some(if_expr) = ctx.find_node_at_offset::<ast::IfExpr>() {
48 (
49 ast::Expr::cast(if_expr.syntax().to_owned())?,
50 exprify_if(&if_expr, &ctx.sema, &name_expr)?.indent(if_expr.indent_level()),
51 )
52 } else if let Some(match_expr) = ctx.find_node_at_offset::<ast::MatchExpr>() {
53 (
54 ast::Expr::cast(match_expr.syntax().to_owned())?,
55 exprify_match(&match_expr, &ctx.sema, &name_expr)?,
56 )
57 } else {
58 return None;
59 };
60
61 let expr_stmt = make::expr_stmt(new_stmt);
62
63 acc.add(
64 AssistId("pull_assignment_up", AssistKind::RefactorExtract),
65 "Pull assignment up",
66 old_stmt.syntax().text_range(),
67 move |edit| {
68 edit.replace(old_stmt.syntax().text_range(), format!("{} = {};", name_expr, expr_stmt));
69 },
70 )
71}
72
73fn exprify_match(
74 match_expr: &ast::MatchExpr,
75 sema: &hir::Semantics<ide_db::RootDatabase>,
76 name: &ast::Expr,
77) -> Option<ast::Expr> {
78 let new_arm_list = match_expr
79 .match_arm_list()?
80 .arms()
81 .map(|arm| {
82 if let ast::Expr::BlockExpr(block) = arm.expr()? {
83 let new_block = exprify_block(&block, sema, name)?.indent(block.indent_level());
84 Some(arm.replace_descendant(block, new_block))
85 } else {
86 None
87 }
88 })
89 .collect::<Option<Vec<_>>>()?;
90 let new_arm_list = match_expr
91 .match_arm_list()?
92 .replace_descendants(match_expr.match_arm_list()?.arms().zip(new_arm_list));
93 Some(make::expr_match(match_expr.expr()?, new_arm_list))
94}
95
96fn exprify_if(
97 statement: &ast::IfExpr,
98 sema: &hir::Semantics<ide_db::RootDatabase>,
99 name: &ast::Expr,
100) -> Option<ast::Expr> {
101 let then_branch = exprify_block(&statement.then_branch()?, sema, name)?;
102 let else_branch = match statement.else_branch()? {
103 ast::ElseBranch::Block(ref block) => {
104 ast::ElseBranch::Block(exprify_block(block, sema, name)?)
105 }
106 ast::ElseBranch::IfExpr(expr) => {
107 mark::hit!(test_pull_assignment_up_chained_if);
108 ast::ElseBranch::IfExpr(ast::IfExpr::cast(
109 exprify_if(&expr, sema, name)?.syntax().to_owned(),
110 )?)
111 }
112 };
113 Some(make::expr_if(statement.condition()?, then_branch, Some(else_branch)))
114}
115
116fn exprify_block(
117 block: &ast::BlockExpr,
118 sema: &hir::Semantics<ide_db::RootDatabase>,
119 name: &ast::Expr,
120) -> Option<ast::BlockExpr> {
121 if block.tail_expr().is_some() {
122 return None;
123 }
124
125 let mut stmts: Vec<_> = block.statements().collect();
126 let stmt = stmts.pop()?;
127
128 if let ast::Stmt::ExprStmt(stmt) = stmt {
129 if let ast::Expr::BinExpr(expr) = stmt.expr()? {
130 if expr.op_kind()? == ast::BinOp::Assignment && is_equivalent(sema, &expr.lhs()?, name)
131 {
132 // The last statement in the block is an assignment to the name we want
133 return Some(make::block_expr(stmts, Some(expr.rhs()?)));
134 }
135 }
136 }
137 None
138}
139
140fn is_equivalent(
141 sema: &hir::Semantics<ide_db::RootDatabase>,
142 expr0: &ast::Expr,
143 expr1: &ast::Expr,
144) -> bool {
145 match (expr0, expr1) {
146 (ast::Expr::FieldExpr(field_expr0), ast::Expr::FieldExpr(field_expr1)) => {
147 mark::hit!(test_pull_assignment_up_field_assignment);
148 sema.resolve_field(field_expr0) == sema.resolve_field(field_expr1)
149 }
150 (ast::Expr::PathExpr(path0), ast::Expr::PathExpr(path1)) => {
151 let path0 = path0.path();
152 let path1 = path1.path();
153 if let (Some(path0), Some(path1)) = (path0, path1) {
154 sema.resolve_path(&path0) == sema.resolve_path(&path1)
155 } else {
156 false
157 }
158 }
159 _ => false,
160 }
161}
162
163#[cfg(test)]
164mod tests {
165 use super::*;
166
167 use crate::tests::{check_assist, check_assist_not_applicable};
168
169 #[test]
170 fn test_pull_assignment_up_if() {
171 check_assist(
172 pull_assignment_up,
173 r#"
174fn foo() {
175 let mut a = 1;
176
177 if true {
178 $0a = 2;
179 } else {
180 a = 3;
181 }
182}"#,
183 r#"
184fn foo() {
185 let mut a = 1;
186
187 a = if true {
188 2
189 } else {
190 3
191 };
192}"#,
193 );
194 }
195
196 #[test]
197 fn test_pull_assignment_up_match() {
198 check_assist(
199 pull_assignment_up,
200 r#"
201fn foo() {
202 let mut a = 1;
203
204 match 1 {
205 1 => {
206 $0a = 2;
207 },
208 2 => {
209 a = 3;
210 },
211 3 => {
212 a = 4;
213 }
214 }
215}"#,
216 r#"
217fn foo() {
218 let mut a = 1;
219
220 a = match 1 {
221 1 => {
222 2
223 },
224 2 => {
225 3
226 },
227 3 => {
228 4
229 }
230 };
231}"#,
232 );
233 }
234
235 #[test]
236 fn test_pull_assignment_up_not_last_not_applicable() {
237 check_assist_not_applicable(
238 pull_assignment_up,
239 r#"
240fn foo() {
241 let mut a = 1;
242
243 if true {
244 $0a = 2;
245 b = a;
246 } else {
247 a = 3;
248 }
249}"#,
250 )
251 }
252
253 #[test]
254 fn test_pull_assignment_up_chained_if() {
255 mark::check!(test_pull_assignment_up_chained_if);
256 check_assist(
257 pull_assignment_up,
258 r#"
259fn foo() {
260 let mut a = 1;
261
262 if true {
263 $0a = 2;
264 } else if false {
265 a = 3;
266 } else {
267 a = 4;
268 }
269}"#,
270 r#"
271fn foo() {
272 let mut a = 1;
273
274 a = if true {
275 2
276 } else if false {
277 3
278 } else {
279 4
280 };
281}"#,
282 );
283 }
284
285 #[test]
286 fn test_pull_assignment_up_retains_stmts() {
287 check_assist(
288 pull_assignment_up,
289 r#"
290fn foo() {
291 let mut a = 1;
292
293 if true {
294 let b = 2;
295 $0a = 2;
296 } else {
297 let b = 3;
298 a = 3;
299 }
300}"#,
301 r#"
302fn foo() {
303 let mut a = 1;
304
305 a = if true {
306 let b = 2;
307 2
308 } else {
309 let b = 3;
310 3
311 };
312}"#,
313 )
314 }
315
316 #[test]
317 fn pull_assignment_up_let_stmt_not_applicable() {
318 check_assist_not_applicable(
319 pull_assignment_up,
320 r#"
321fn foo() {
322 let mut a = 1;
323
324 let b = if true {
325 $0a = 2
326 } else {
327 a = 3
328 };
329}"#,
330 )
331 }
332
333 #[test]
334 fn pull_assignment_up_if_missing_assigment_not_applicable() {
335 check_assist_not_applicable(
336 pull_assignment_up,
337 r#"
338fn foo() {
339 let mut a = 1;
340
341 if true {
342 $0a = 2;
343 } else {}
344}"#,
345 )
346 }
347
348 #[test]
349 fn pull_assignment_up_match_missing_assigment_not_applicable() {
350 check_assist_not_applicable(
351 pull_assignment_up,
352 r#"
353fn foo() {
354 let mut a = 1;
355
356 match 1 {
357 1 => {
358 $0a = 2;
359 },
360 2 => {
361 a = 3;
362 },
363 3 => {},
364 }
365}"#,
366 )
367 }
368
369 #[test]
370 fn test_pull_assignment_up_field_assignment() {
371 mark::check!(test_pull_assignment_up_field_assignment);
372 check_assist(
373 pull_assignment_up,
374 r#"
375struct A(usize);
376
377fn foo() {
378 let mut a = A(1);
379
380 if true {
381 $0a.0 = 2;
382 } else {
383 a.0 = 3;
384 }
385}"#,
386 r#"
387struct A(usize);
388
389fn foo() {
390 let mut a = A(1);
391
392 a.0 = if true {
393 2
394 } else {
395 3
396 };
397}"#,
398 )
399 }
400}
diff --git a/crates/ide_assists/src/handlers/qualify_path.rs b/crates/ide_assists/src/handlers/qualify_path.rs
new file mode 100644
index 000000000..b0b0d31b4
--- /dev/null
+++ b/crates/ide_assists/src/handlers/qualify_path.rs
@@ -0,0 +1,1205 @@
1use std::iter;
2
3use hir::{AsAssocItem, AsName};
4use ide_db::helpers::{import_assets::ImportCandidate, mod_path_to_ast};
5use ide_db::RootDatabase;
6use syntax::{
7 ast,
8 ast::{make, ArgListOwner},
9 AstNode,
10};
11use test_utils::mark;
12
13use crate::{
14 assist_context::{AssistContext, Assists},
15 AssistId, AssistKind, GroupLabel,
16};
17
18use super::auto_import::find_importable_node;
19
20// Assist: qualify_path
21//
22// If the name is unresolved, provides all possible qualified paths for it.
23//
24// ```
25// fn main() {
26// let map = HashMap$0::new();
27// }
28// # pub mod std { pub mod collections { pub struct HashMap { } } }
29// ```
30// ->
31// ```
32// fn main() {
33// let map = std::collections::HashMap::new();
34// }
35// # pub mod std { pub mod collections { pub struct HashMap { } } }
36// ```
37pub(crate) fn qualify_path(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
38 let (import_assets, syntax_under_caret) = find_importable_node(ctx)?;
39 let proposed_imports = import_assets.search_for_relative_paths(&ctx.sema);
40 if proposed_imports.is_empty() {
41 return None;
42 }
43
44 let candidate = import_assets.import_candidate();
45 let range = ctx.sema.original_range(&syntax_under_caret).range;
46
47 let qualify_candidate = match candidate {
48 ImportCandidate::Path(candidate) => {
49 if candidate.qualifier.is_some() {
50 mark::hit!(qualify_path_qualifier_start);
51 let path = ast::Path::cast(syntax_under_caret)?;
52 let (prev_segment, segment) = (path.qualifier()?.segment()?, path.segment()?);
53 QualifyCandidate::QualifierStart(segment, prev_segment.generic_arg_list())
54 } else {
55 mark::hit!(qualify_path_unqualified_name);
56 let path = ast::Path::cast(syntax_under_caret)?;
57 let generics = path.segment()?.generic_arg_list();
58 QualifyCandidate::UnqualifiedName(generics)
59 }
60 }
61 ImportCandidate::TraitAssocItem(_) => {
62 mark::hit!(qualify_path_trait_assoc_item);
63 let path = ast::Path::cast(syntax_under_caret)?;
64 let (qualifier, segment) = (path.qualifier()?, path.segment()?);
65 QualifyCandidate::TraitAssocItem(qualifier, segment)
66 }
67 ImportCandidate::TraitMethod(_) => {
68 mark::hit!(qualify_path_trait_method);
69 let mcall_expr = ast::MethodCallExpr::cast(syntax_under_caret)?;
70 QualifyCandidate::TraitMethod(ctx.sema.db, mcall_expr)
71 }
72 };
73
74 let group_label = group_label(candidate);
75 for (import, item) in proposed_imports {
76 acc.add_group(
77 &group_label,
78 AssistId("qualify_path", AssistKind::QuickFix),
79 label(candidate, &import),
80 range,
81 |builder| {
82 qualify_candidate.qualify(
83 |replace_with: String| builder.replace(range, replace_with),
84 import,
85 item,
86 )
87 },
88 );
89 }
90 Some(())
91}
92
93enum QualifyCandidate<'db> {
94 QualifierStart(ast::PathSegment, Option<ast::GenericArgList>),
95 UnqualifiedName(Option<ast::GenericArgList>),
96 TraitAssocItem(ast::Path, ast::PathSegment),
97 TraitMethod(&'db RootDatabase, ast::MethodCallExpr),
98}
99
100impl QualifyCandidate<'_> {
101 fn qualify(&self, mut replacer: impl FnMut(String), import: hir::ModPath, item: hir::ItemInNs) {
102 let import = mod_path_to_ast(&import);
103 match self {
104 QualifyCandidate::QualifierStart(segment, generics) => {
105 let generics = generics.as_ref().map_or_else(String::new, ToString::to_string);
106 replacer(format!("{}{}::{}", import, generics, segment));
107 }
108 QualifyCandidate::UnqualifiedName(generics) => {
109 let generics = generics.as_ref().map_or_else(String::new, ToString::to_string);
110 replacer(format!("{}{}", import.to_string(), generics));
111 }
112 QualifyCandidate::TraitAssocItem(qualifier, segment) => {
113 replacer(format!("<{} as {}>::{}", qualifier, import, segment));
114 }
115 &QualifyCandidate::TraitMethod(db, ref mcall_expr) => {
116 Self::qualify_trait_method(db, mcall_expr, replacer, import, item);
117 }
118 }
119 }
120
121 fn qualify_trait_method(
122 db: &RootDatabase,
123 mcall_expr: &ast::MethodCallExpr,
124 mut replacer: impl FnMut(String),
125 import: ast::Path,
126 item: hir::ItemInNs,
127 ) -> Option<()> {
128 let receiver = mcall_expr.receiver()?;
129 let trait_method_name = mcall_expr.name_ref()?;
130 let generics =
131 mcall_expr.generic_arg_list().as_ref().map_or_else(String::new, ToString::to_string);
132 let arg_list = mcall_expr.arg_list().map(|arg_list| arg_list.args());
133 let trait_ = item_as_trait(db, item)?;
134 let method = find_trait_method(db, trait_, &trait_method_name)?;
135 if let Some(self_access) = method.self_param(db).map(|sp| sp.access(db)) {
136 let receiver = match self_access {
137 hir::Access::Shared => make::expr_ref(receiver, false),
138 hir::Access::Exclusive => make::expr_ref(receiver, true),
139 hir::Access::Owned => receiver,
140 };
141 replacer(format!(
142 "{}::{}{}{}",
143 import,
144 trait_method_name,
145 generics,
146 match arg_list {
147 Some(args) => make::arg_list(iter::once(receiver).chain(args)),
148 None => make::arg_list(iter::once(receiver)),
149 }
150 ));
151 }
152 Some(())
153 }
154}
155
156fn find_trait_method(
157 db: &RootDatabase,
158 trait_: hir::Trait,
159 trait_method_name: &ast::NameRef,
160) -> Option<hir::Function> {
161 if let Some(hir::AssocItem::Function(method)) =
162 trait_.items(db).into_iter().find(|item: &hir::AssocItem| {
163 item.name(db).map(|name| name == trait_method_name.as_name()).unwrap_or(false)
164 })
165 {
166 Some(method)
167 } else {
168 None
169 }
170}
171
172fn item_as_trait(db: &RootDatabase, item: hir::ItemInNs) -> Option<hir::Trait> {
173 let item_module_def = hir::ModuleDef::from(item.as_module_def_id()?);
174
175 if let hir::ModuleDef::Trait(trait_) = item_module_def {
176 Some(trait_)
177 } else {
178 item_module_def.as_assoc_item(db)?.containing_trait(db)
179 }
180}
181
182fn group_label(candidate: &ImportCandidate) -> GroupLabel {
183 let name = match candidate {
184 ImportCandidate::Path(it) => &it.name,
185 ImportCandidate::TraitAssocItem(it) | ImportCandidate::TraitMethod(it) => &it.name,
186 }
187 .text();
188 GroupLabel(format!("Qualify {}", name))
189}
190
191fn label(candidate: &ImportCandidate, import: &hir::ModPath) -> String {
192 match candidate {
193 ImportCandidate::Path(candidate) => {
194 if candidate.qualifier.is_some() {
195 format!("Qualify with `{}`", &import)
196 } else {
197 format!("Qualify as `{}`", &import)
198 }
199 }
200 ImportCandidate::TraitAssocItem(_) => format!("Qualify `{}`", &import),
201 ImportCandidate::TraitMethod(_) => format!("Qualify with cast as `{}`", &import),
202 }
203}
204
205#[cfg(test)]
206mod tests {
207 use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target};
208
209 use super::*;
210
211 #[test]
212 fn applicable_when_found_an_import_partial() {
213 mark::check!(qualify_path_unqualified_name);
214 check_assist(
215 qualify_path,
216 r"
217 mod std {
218 pub mod fmt {
219 pub struct Formatter;
220 }
221 }
222
223 use std::fmt;
224
225 $0Formatter
226 ",
227 r"
228 mod std {
229 pub mod fmt {
230 pub struct Formatter;
231 }
232 }
233
234 use std::fmt;
235
236 fmt::Formatter
237 ",
238 );
239 }
240
241 #[test]
242 fn applicable_when_found_an_import() {
243 check_assist(
244 qualify_path,
245 r"
246 $0PubStruct
247
248 pub mod PubMod {
249 pub struct PubStruct;
250 }
251 ",
252 r"
253 PubMod::PubStruct
254
255 pub mod PubMod {
256 pub struct PubStruct;
257 }
258 ",
259 );
260 }
261
262 #[test]
263 fn applicable_in_macros() {
264 check_assist(
265 qualify_path,
266 r"
267 macro_rules! foo {
268 ($i:ident) => { fn foo(a: $i) {} }
269 }
270 foo!(Pub$0Struct);
271
272 pub mod PubMod {
273 pub struct PubStruct;
274 }
275 ",
276 r"
277 macro_rules! foo {
278 ($i:ident) => { fn foo(a: $i) {} }
279 }
280 foo!(PubMod::PubStruct);
281
282 pub mod PubMod {
283 pub struct PubStruct;
284 }
285 ",
286 );
287 }
288
289 #[test]
290 fn applicable_when_found_multiple_imports() {
291 check_assist(
292 qualify_path,
293 r"
294 PubSt$0ruct
295
296 pub mod PubMod1 {
297 pub struct PubStruct;
298 }
299 pub mod PubMod2 {
300 pub struct PubStruct;
301 }
302 pub mod PubMod3 {
303 pub struct PubStruct;
304 }
305 ",
306 r"
307 PubMod3::PubStruct
308
309 pub mod PubMod1 {
310 pub struct PubStruct;
311 }
312 pub mod PubMod2 {
313 pub struct PubStruct;
314 }
315 pub mod PubMod3 {
316 pub struct PubStruct;
317 }
318 ",
319 );
320 }
321
322 #[test]
323 fn not_applicable_for_already_imported_types() {
324 check_assist_not_applicable(
325 qualify_path,
326 r"
327 use PubMod::PubStruct;
328
329 PubStruct$0
330
331 pub mod PubMod {
332 pub struct PubStruct;
333 }
334 ",
335 );
336 }
337
338 #[test]
339 fn not_applicable_for_types_with_private_paths() {
340 check_assist_not_applicable(
341 qualify_path,
342 r"
343 PrivateStruct$0
344
345 pub mod PubMod {
346 struct PrivateStruct;
347 }
348 ",
349 );
350 }
351
352 #[test]
353 fn not_applicable_when_no_imports_found() {
354 check_assist_not_applicable(
355 qualify_path,
356 "
357 PubStruct$0",
358 );
359 }
360
361 #[test]
362 fn not_applicable_in_import_statements() {
363 check_assist_not_applicable(
364 qualify_path,
365 r"
366 use PubStruct$0;
367
368 pub mod PubMod {
369 pub struct PubStruct;
370 }",
371 );
372 }
373
374 #[test]
375 fn qualify_function() {
376 check_assist(
377 qualify_path,
378 r"
379 test_function$0
380
381 pub mod PubMod {
382 pub fn test_function() {};
383 }
384 ",
385 r"
386 PubMod::test_function
387
388 pub mod PubMod {
389 pub fn test_function() {};
390 }
391 ",
392 );
393 }
394
395 #[test]
396 fn qualify_macro() {
397 check_assist(
398 qualify_path,
399 r"
400//- /lib.rs crate:crate_with_macro
401#[macro_export]
402macro_rules! foo {
403 () => ()
404}
405
406//- /main.rs crate:main deps:crate_with_macro
407fn main() {
408 foo$0
409}
410",
411 r"
412fn main() {
413 crate_with_macro::foo
414}
415",
416 );
417 }
418
419 #[test]
420 fn qualify_path_target() {
421 check_assist_target(
422 qualify_path,
423 r"
424 struct AssistInfo {
425 group_label: Option<$0GroupLabel>,
426 }
427
428 mod m { pub struct GroupLabel; }
429 ",
430 "GroupLabel",
431 )
432 }
433
434 #[test]
435 fn not_applicable_when_path_start_is_imported() {
436 check_assist_not_applicable(
437 qualify_path,
438 r"
439 pub mod mod1 {
440 pub mod mod2 {
441 pub mod mod3 {
442 pub struct TestStruct;
443 }
444 }
445 }
446
447 use mod1::mod2;
448 fn main() {
449 mod2::mod3::TestStruct$0
450 }
451 ",
452 );
453 }
454
455 #[test]
456 fn not_applicable_for_imported_function() {
457 check_assist_not_applicable(
458 qualify_path,
459 r"
460 pub mod test_mod {
461 pub fn test_function() {}
462 }
463
464 use test_mod::test_function;
465 fn main() {
466 test_function$0
467 }
468 ",
469 );
470 }
471
472 #[test]
473 fn associated_struct_function() {
474 check_assist(
475 qualify_path,
476 r"
477 mod test_mod {
478 pub struct TestStruct {}
479 impl TestStruct {
480 pub fn test_function() {}
481 }
482 }
483
484 fn main() {
485 TestStruct::test_function$0
486 }
487 ",
488 r"
489 mod test_mod {
490 pub struct TestStruct {}
491 impl TestStruct {
492 pub fn test_function() {}
493 }
494 }
495
496 fn main() {
497 test_mod::TestStruct::test_function
498 }
499 ",
500 );
501 }
502
503 #[test]
504 fn associated_struct_const() {
505 mark::check!(qualify_path_qualifier_start);
506 check_assist(
507 qualify_path,
508 r"
509 mod test_mod {
510 pub struct TestStruct {}
511 impl TestStruct {
512 const TEST_CONST: u8 = 42;
513 }
514 }
515
516 fn main() {
517 TestStruct::TEST_CONST$0
518 }
519 ",
520 r"
521 mod test_mod {
522 pub struct TestStruct {}
523 impl TestStruct {
524 const TEST_CONST: u8 = 42;
525 }
526 }
527
528 fn main() {
529 test_mod::TestStruct::TEST_CONST
530 }
531 ",
532 );
533 }
534
535 #[test]
536 fn associated_trait_function() {
537 check_assist(
538 qualify_path,
539 r"
540 mod test_mod {
541 pub trait TestTrait {
542 fn test_function();
543 }
544 pub struct TestStruct {}
545 impl TestTrait for TestStruct {
546 fn test_function() {}
547 }
548 }
549
550 fn main() {
551 test_mod::TestStruct::test_function$0
552 }
553 ",
554 r"
555 mod test_mod {
556 pub trait TestTrait {
557 fn test_function();
558 }
559 pub struct TestStruct {}
560 impl TestTrait for TestStruct {
561 fn test_function() {}
562 }
563 }
564
565 fn main() {
566 <test_mod::TestStruct as test_mod::TestTrait>::test_function
567 }
568 ",
569 );
570 }
571
572 #[test]
573 fn not_applicable_for_imported_trait_for_function() {
574 check_assist_not_applicable(
575 qualify_path,
576 r"
577 mod test_mod {
578 pub trait TestTrait {
579 fn test_function();
580 }
581 pub trait TestTrait2 {
582 fn test_function();
583 }
584 pub enum TestEnum {
585 One,
586 Two,
587 }
588 impl TestTrait2 for TestEnum {
589 fn test_function() {}
590 }
591 impl TestTrait for TestEnum {
592 fn test_function() {}
593 }
594 }
595
596 use test_mod::TestTrait2;
597 fn main() {
598 test_mod::TestEnum::test_function$0;
599 }
600 ",
601 )
602 }
603
604 #[test]
605 fn associated_trait_const() {
606 mark::check!(qualify_path_trait_assoc_item);
607 check_assist(
608 qualify_path,
609 r"
610 mod test_mod {
611 pub trait TestTrait {
612 const TEST_CONST: u8;
613 }
614 pub struct TestStruct {}
615 impl TestTrait for TestStruct {
616 const TEST_CONST: u8 = 42;
617 }
618 }
619
620 fn main() {
621 test_mod::TestStruct::TEST_CONST$0
622 }
623 ",
624 r"
625 mod test_mod {
626 pub trait TestTrait {
627 const TEST_CONST: u8;
628 }
629 pub struct TestStruct {}
630 impl TestTrait for TestStruct {
631 const TEST_CONST: u8 = 42;
632 }
633 }
634
635 fn main() {
636 <test_mod::TestStruct as test_mod::TestTrait>::TEST_CONST
637 }
638 ",
639 );
640 }
641
642 #[test]
643 fn not_applicable_for_imported_trait_for_const() {
644 check_assist_not_applicable(
645 qualify_path,
646 r"
647 mod test_mod {
648 pub trait TestTrait {
649 const TEST_CONST: u8;
650 }
651 pub trait TestTrait2 {
652 const TEST_CONST: f64;
653 }
654 pub enum TestEnum {
655 One,
656 Two,
657 }
658 impl TestTrait2 for TestEnum {
659 const TEST_CONST: f64 = 42.0;
660 }
661 impl TestTrait for TestEnum {
662 const TEST_CONST: u8 = 42;
663 }
664 }
665
666 use test_mod::TestTrait2;
667 fn main() {
668 test_mod::TestEnum::TEST_CONST$0;
669 }
670 ",
671 )
672 }
673
674 #[test]
675 fn trait_method() {
676 mark::check!(qualify_path_trait_method);
677 check_assist(
678 qualify_path,
679 r"
680 mod test_mod {
681 pub trait TestTrait {
682 fn test_method(&self);
683 }
684 pub struct TestStruct {}
685 impl TestTrait for TestStruct {
686 fn test_method(&self) {}
687 }
688 }
689
690 fn main() {
691 let test_struct = test_mod::TestStruct {};
692 test_struct.test_meth$0od()
693 }
694 ",
695 r"
696 mod test_mod {
697 pub trait TestTrait {
698 fn test_method(&self);
699 }
700 pub struct TestStruct {}
701 impl TestTrait for TestStruct {
702 fn test_method(&self) {}
703 }
704 }
705
706 fn main() {
707 let test_struct = test_mod::TestStruct {};
708 test_mod::TestTrait::test_method(&test_struct)
709 }
710 ",
711 );
712 }
713
714 #[test]
715 fn trait_method_multi_params() {
716 check_assist(
717 qualify_path,
718 r"
719 mod test_mod {
720 pub trait TestTrait {
721 fn test_method(&self, test: i32);
722 }
723 pub struct TestStruct {}
724 impl TestTrait for TestStruct {
725 fn test_method(&self, test: i32) {}
726 }
727 }
728
729 fn main() {
730 let test_struct = test_mod::TestStruct {};
731 test_struct.test_meth$0od(42)
732 }
733 ",
734 r"
735 mod test_mod {
736 pub trait TestTrait {
737 fn test_method(&self, test: i32);
738 }
739 pub struct TestStruct {}
740 impl TestTrait for TestStruct {
741 fn test_method(&self, test: i32) {}
742 }
743 }
744
745 fn main() {
746 let test_struct = test_mod::TestStruct {};
747 test_mod::TestTrait::test_method(&test_struct, 42)
748 }
749 ",
750 );
751 }
752
753 #[test]
754 fn trait_method_consume() {
755 check_assist(
756 qualify_path,
757 r"
758 mod test_mod {
759 pub trait TestTrait {
760 fn test_method(self);
761 }
762 pub struct TestStruct {}
763 impl TestTrait for TestStruct {
764 fn test_method(self) {}
765 }
766 }
767
768 fn main() {
769 let test_struct = test_mod::TestStruct {};
770 test_struct.test_meth$0od()
771 }
772 ",
773 r"
774 mod test_mod {
775 pub trait TestTrait {
776 fn test_method(self);
777 }
778 pub struct TestStruct {}
779 impl TestTrait for TestStruct {
780 fn test_method(self) {}
781 }
782 }
783
784 fn main() {
785 let test_struct = test_mod::TestStruct {};
786 test_mod::TestTrait::test_method(test_struct)
787 }
788 ",
789 );
790 }
791
792 #[test]
793 fn trait_method_cross_crate() {
794 check_assist(
795 qualify_path,
796 r"
797 //- /main.rs crate:main deps:dep
798 fn main() {
799 let test_struct = dep::test_mod::TestStruct {};
800 test_struct.test_meth$0od()
801 }
802 //- /dep.rs crate:dep
803 pub mod test_mod {
804 pub trait TestTrait {
805 fn test_method(&self);
806 }
807 pub struct TestStruct {}
808 impl TestTrait for TestStruct {
809 fn test_method(&self) {}
810 }
811 }
812 ",
813 r"
814 fn main() {
815 let test_struct = dep::test_mod::TestStruct {};
816 dep::test_mod::TestTrait::test_method(&test_struct)
817 }
818 ",
819 );
820 }
821
822 #[test]
823 fn assoc_fn_cross_crate() {
824 check_assist(
825 qualify_path,
826 r"
827 //- /main.rs crate:main deps:dep
828 fn main() {
829 dep::test_mod::TestStruct::test_func$0tion
830 }
831 //- /dep.rs crate:dep
832 pub mod test_mod {
833 pub trait TestTrait {
834 fn test_function();
835 }
836 pub struct TestStruct {}
837 impl TestTrait for TestStruct {
838 fn test_function() {}
839 }
840 }
841 ",
842 r"
843 fn main() {
844 <dep::test_mod::TestStruct as dep::test_mod::TestTrait>::test_function
845 }
846 ",
847 );
848 }
849
850 #[test]
851 fn assoc_const_cross_crate() {
852 check_assist(
853 qualify_path,
854 r"
855 //- /main.rs crate:main deps:dep
856 fn main() {
857 dep::test_mod::TestStruct::CONST$0
858 }
859 //- /dep.rs crate:dep
860 pub mod test_mod {
861 pub trait TestTrait {
862 const CONST: bool;
863 }
864 pub struct TestStruct {}
865 impl TestTrait for TestStruct {
866 const CONST: bool = true;
867 }
868 }
869 ",
870 r"
871 fn main() {
872 <dep::test_mod::TestStruct as dep::test_mod::TestTrait>::CONST
873 }
874 ",
875 );
876 }
877
878 #[test]
879 fn assoc_fn_as_method_cross_crate() {
880 check_assist_not_applicable(
881 qualify_path,
882 r"
883 //- /main.rs crate:main deps:dep
884 fn main() {
885 let test_struct = dep::test_mod::TestStruct {};
886 test_struct.test_func$0tion()
887 }
888 //- /dep.rs crate:dep
889 pub mod test_mod {
890 pub trait TestTrait {
891 fn test_function();
892 }
893 pub struct TestStruct {}
894 impl TestTrait for TestStruct {
895 fn test_function() {}
896 }
897 }
898 ",
899 );
900 }
901
902 #[test]
903 fn private_trait_cross_crate() {
904 check_assist_not_applicable(
905 qualify_path,
906 r"
907 //- /main.rs crate:main deps:dep
908 fn main() {
909 let test_struct = dep::test_mod::TestStruct {};
910 test_struct.test_meth$0od()
911 }
912 //- /dep.rs crate:dep
913 pub mod test_mod {
914 trait TestTrait {
915 fn test_method(&self);
916 }
917 pub struct TestStruct {}
918 impl TestTrait for TestStruct {
919 fn test_method(&self) {}
920 }
921 }
922 ",
923 );
924 }
925
926 #[test]
927 fn not_applicable_for_imported_trait_for_method() {
928 check_assist_not_applicable(
929 qualify_path,
930 r"
931 mod test_mod {
932 pub trait TestTrait {
933 fn test_method(&self);
934 }
935 pub trait TestTrait2 {
936 fn test_method(&self);
937 }
938 pub enum TestEnum {
939 One,
940 Two,
941 }
942 impl TestTrait2 for TestEnum {
943 fn test_method(&self) {}
944 }
945 impl TestTrait for TestEnum {
946 fn test_method(&self) {}
947 }
948 }
949
950 use test_mod::TestTrait2;
951 fn main() {
952 let one = test_mod::TestEnum::One;
953 one.test$0_method();
954 }
955 ",
956 )
957 }
958
959 #[test]
960 fn dep_import() {
961 check_assist(
962 qualify_path,
963 r"
964//- /lib.rs crate:dep
965pub struct Struct;
966
967//- /main.rs crate:main deps:dep
968fn main() {
969 Struct$0
970}
971",
972 r"
973fn main() {
974 dep::Struct
975}
976",
977 );
978 }
979
980 #[test]
981 fn whole_segment() {
982 // Tests that only imports whose last segment matches the identifier get suggested.
983 check_assist(
984 qualify_path,
985 r"
986//- /lib.rs crate:dep
987pub mod fmt {
988 pub trait Display {}
989}
990
991pub fn panic_fmt() {}
992
993//- /main.rs crate:main deps:dep
994struct S;
995
996impl f$0mt::Display for S {}
997",
998 r"
999struct S;
1000
1001impl dep::fmt::Display for S {}
1002",
1003 );
1004 }
1005
1006 #[test]
1007 fn macro_generated() {
1008 // Tests that macro-generated items are suggested from external crates.
1009 check_assist(
1010 qualify_path,
1011 r"
1012//- /lib.rs crate:dep
1013macro_rules! mac {
1014 () => {
1015 pub struct Cheese;
1016 };
1017}
1018
1019mac!();
1020
1021//- /main.rs crate:main deps:dep
1022fn main() {
1023 Cheese$0;
1024}
1025",
1026 r"
1027fn main() {
1028 dep::Cheese;
1029}
1030",
1031 );
1032 }
1033
1034 #[test]
1035 fn casing() {
1036 // Tests that differently cased names don't interfere and we only suggest the matching one.
1037 check_assist(
1038 qualify_path,
1039 r"
1040//- /lib.rs crate:dep
1041pub struct FMT;
1042pub struct fmt;
1043
1044//- /main.rs crate:main deps:dep
1045fn main() {
1046 FMT$0;
1047}
1048",
1049 r"
1050fn main() {
1051 dep::FMT;
1052}
1053",
1054 );
1055 }
1056
1057 #[test]
1058 fn keep_generic_annotations() {
1059 check_assist(
1060 qualify_path,
1061 r"
1062//- /lib.rs crate:dep
1063pub mod generic { pub struct Thing<'a, T>(&'a T); }
1064
1065//- /main.rs crate:main deps:dep
1066fn foo() -> Thin$0g<'static, ()> {}
1067
1068fn main() {}
1069",
1070 r"
1071fn foo() -> dep::generic::Thing<'static, ()> {}
1072
1073fn main() {}
1074",
1075 );
1076 }
1077
1078 #[test]
1079 fn keep_generic_annotations_leading_colon() {
1080 check_assist(
1081 qualify_path,
1082 r"
1083//- /lib.rs crate:dep
1084pub mod generic { pub struct Thing<'a, T>(&'a T); }
1085
1086//- /main.rs crate:main deps:dep
1087fn foo() -> Thin$0g::<'static, ()> {}
1088
1089fn main() {}
1090",
1091 r"
1092fn foo() -> dep::generic::Thing::<'static, ()> {}
1093
1094fn main() {}
1095",
1096 );
1097 }
1098
1099 #[test]
1100 fn associated_struct_const_generic() {
1101 check_assist(
1102 qualify_path,
1103 r"
1104 mod test_mod {
1105 pub struct TestStruct<T> {}
1106 impl<T> TestStruct<T> {
1107 const TEST_CONST: u8 = 42;
1108 }
1109 }
1110
1111 fn main() {
1112 TestStruct::<()>::TEST_CONST$0
1113 }
1114 ",
1115 r"
1116 mod test_mod {
1117 pub struct TestStruct<T> {}
1118 impl<T> TestStruct<T> {
1119 const TEST_CONST: u8 = 42;
1120 }
1121 }
1122
1123 fn main() {
1124 test_mod::TestStruct::<()>::TEST_CONST
1125 }
1126 ",
1127 );
1128 }
1129
1130 #[test]
1131 fn associated_trait_const_generic() {
1132 check_assist(
1133 qualify_path,
1134 r"
1135 mod test_mod {
1136 pub trait TestTrait {
1137 const TEST_CONST: u8;
1138 }
1139 pub struct TestStruct<T> {}
1140 impl<T> TestTrait for TestStruct<T> {
1141 const TEST_CONST: u8 = 42;
1142 }
1143 }
1144
1145 fn main() {
1146 test_mod::TestStruct::<()>::TEST_CONST$0
1147 }
1148 ",
1149 r"
1150 mod test_mod {
1151 pub trait TestTrait {
1152 const TEST_CONST: u8;
1153 }
1154 pub struct TestStruct<T> {}
1155 impl<T> TestTrait for TestStruct<T> {
1156 const TEST_CONST: u8 = 42;
1157 }
1158 }
1159
1160 fn main() {
1161 <test_mod::TestStruct::<()> as test_mod::TestTrait>::TEST_CONST
1162 }
1163 ",
1164 );
1165 }
1166
1167 #[test]
1168 fn trait_method_generic() {
1169 check_assist(
1170 qualify_path,
1171 r"
1172 mod test_mod {
1173 pub trait TestTrait {
1174 fn test_method<T>(&self);
1175 }
1176 pub struct TestStruct {}
1177 impl TestTrait for TestStruct {
1178 fn test_method<T>(&self) {}
1179 }
1180 }
1181
1182 fn main() {
1183 let test_struct = test_mod::TestStruct {};
1184 test_struct.test_meth$0od::<()>()
1185 }
1186 ",
1187 r"
1188 mod test_mod {
1189 pub trait TestTrait {
1190 fn test_method<T>(&self);
1191 }
1192 pub struct TestStruct {}
1193 impl TestTrait for TestStruct {
1194 fn test_method<T>(&self) {}
1195 }
1196 }
1197
1198 fn main() {
1199 let test_struct = test_mod::TestStruct {};
1200 test_mod::TestTrait::test_method::<()>(&test_struct)
1201 }
1202 ",
1203 );
1204 }
1205}
diff --git a/crates/ide_assists/src/handlers/raw_string.rs b/crates/ide_assists/src/handlers/raw_string.rs
new file mode 100644
index 000000000..d95267607
--- /dev/null
+++ b/crates/ide_assists/src/handlers/raw_string.rs
@@ -0,0 +1,512 @@
1use std::borrow::Cow;
2
3use syntax::{ast, AstToken, TextRange, TextSize};
4use test_utils::mark;
5
6use crate::{AssistContext, AssistId, AssistKind, Assists};
7
8// Assist: make_raw_string
9//
10// Adds `r#` to a plain string literal.
11//
12// ```
13// fn main() {
14// "Hello,$0 World!";
15// }
16// ```
17// ->
18// ```
19// fn main() {
20// r#"Hello, World!"#;
21// }
22// ```
23pub(crate) fn make_raw_string(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
24 let token = ctx.find_token_at_offset::<ast::String>()?;
25 if token.is_raw() {
26 return None;
27 }
28 let value = token.value()?;
29 let target = token.syntax().text_range();
30 acc.add(
31 AssistId("make_raw_string", AssistKind::RefactorRewrite),
32 "Rewrite as raw string",
33 target,
34 |edit| {
35 let hashes = "#".repeat(required_hashes(&value).max(1));
36 if matches!(value, Cow::Borrowed(_)) {
37 // Avoid replacing the whole string to better position the cursor.
38 edit.insert(token.syntax().text_range().start(), format!("r{}", hashes));
39 edit.insert(token.syntax().text_range().end(), format!("{}", hashes));
40 } else {
41 edit.replace(
42 token.syntax().text_range(),
43 format!("r{}\"{}\"{}", hashes, value, hashes),
44 );
45 }
46 },
47 )
48}
49
50// Assist: make_usual_string
51//
52// Turns a raw string into a plain string.
53//
54// ```
55// fn main() {
56// r#"Hello,$0 "World!""#;
57// }
58// ```
59// ->
60// ```
61// fn main() {
62// "Hello, \"World!\"";
63// }
64// ```
65pub(crate) fn make_usual_string(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
66 let token = ctx.find_token_at_offset::<ast::String>()?;
67 if !token.is_raw() {
68 return None;
69 }
70 let value = token.value()?;
71 let target = token.syntax().text_range();
72 acc.add(
73 AssistId("make_usual_string", AssistKind::RefactorRewrite),
74 "Rewrite as regular string",
75 target,
76 |edit| {
77 // parse inside string to escape `"`
78 let escaped = value.escape_default().to_string();
79 if let Some(offsets) = token.quote_offsets() {
80 if token.text()[offsets.contents - token.syntax().text_range().start()] == escaped {
81 edit.replace(offsets.quotes.0, "\"");
82 edit.replace(offsets.quotes.1, "\"");
83 return;
84 }
85 }
86
87 edit.replace(token.syntax().text_range(), format!("\"{}\"", escaped));
88 },
89 )
90}
91
92// Assist: add_hash
93//
94// Adds a hash to a raw string literal.
95//
96// ```
97// fn main() {
98// r#"Hello,$0 World!"#;
99// }
100// ```
101// ->
102// ```
103// fn main() {
104// r##"Hello, World!"##;
105// }
106// ```
107pub(crate) fn add_hash(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
108 let token = ctx.find_token_at_offset::<ast::String>()?;
109 if !token.is_raw() {
110 return None;
111 }
112 let text_range = token.syntax().text_range();
113 let target = text_range;
114 acc.add(AssistId("add_hash", AssistKind::Refactor), "Add #", target, |edit| {
115 edit.insert(text_range.start() + TextSize::of('r'), "#");
116 edit.insert(text_range.end(), "#");
117 })
118}
119
120// Assist: remove_hash
121//
122// Removes a hash from a raw string literal.
123//
124// ```
125// fn main() {
126// r#"Hello,$0 World!"#;
127// }
128// ```
129// ->
130// ```
131// fn main() {
132// r"Hello, World!";
133// }
134// ```
135pub(crate) fn remove_hash(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
136 let token = ctx.find_token_at_offset::<ast::String>()?;
137 if !token.is_raw() {
138 return None;
139 }
140
141 let text = token.text();
142 if !text.starts_with("r#") && text.ends_with('#') {
143 return None;
144 }
145
146 let existing_hashes = text.chars().skip(1).take_while(|&it| it == '#').count();
147
148 let text_range = token.syntax().text_range();
149 let internal_text = &text[token.text_range_between_quotes()? - text_range.start()];
150
151 if existing_hashes == required_hashes(internal_text) {
152 mark::hit!(cant_remove_required_hash);
153 return None;
154 }
155
156 acc.add(AssistId("remove_hash", AssistKind::RefactorRewrite), "Remove #", text_range, |edit| {
157 edit.delete(TextRange::at(text_range.start() + TextSize::of('r'), TextSize::of('#')));
158 edit.delete(TextRange::new(text_range.end() - TextSize::of('#'), text_range.end()));
159 })
160}
161
162fn required_hashes(s: &str) -> usize {
163 let mut res = 0usize;
164 for idx in s.match_indices('"').map(|(i, _)| i) {
165 let (_, sub) = s.split_at(idx + 1);
166 let n_hashes = sub.chars().take_while(|c| *c == '#').count();
167 res = res.max(n_hashes + 1)
168 }
169 res
170}
171
172#[test]
173fn test_required_hashes() {
174 assert_eq!(0, required_hashes("abc"));
175 assert_eq!(0, required_hashes("###"));
176 assert_eq!(1, required_hashes("\""));
177 assert_eq!(2, required_hashes("\"#abc"));
178 assert_eq!(0, required_hashes("#abc"));
179 assert_eq!(3, required_hashes("#ab\"##c"));
180 assert_eq!(5, required_hashes("#ab\"##\"####c"));
181}
182
183#[cfg(test)]
184mod tests {
185 use test_utils::mark;
186
187 use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target};
188
189 use super::*;
190
191 #[test]
192 fn make_raw_string_target() {
193 check_assist_target(
194 make_raw_string,
195 r#"
196 fn f() {
197 let s = $0"random\nstring";
198 }
199 "#,
200 r#""random\nstring""#,
201 );
202 }
203
204 #[test]
205 fn make_raw_string_works() {
206 check_assist(
207 make_raw_string,
208 r#"
209fn f() {
210 let s = $0"random\nstring";
211}
212"#,
213 r##"
214fn f() {
215 let s = r#"random
216string"#;
217}
218"##,
219 )
220 }
221
222 #[test]
223 fn make_raw_string_works_inside_macros() {
224 check_assist(
225 make_raw_string,
226 r#"
227 fn f() {
228 format!($0"x = {}", 92)
229 }
230 "#,
231 r##"
232 fn f() {
233 format!(r#"x = {}"#, 92)
234 }
235 "##,
236 )
237 }
238
239 #[test]
240 fn make_raw_string_hashes_inside_works() {
241 check_assist(
242 make_raw_string,
243 r###"
244fn f() {
245 let s = $0"#random##\nstring";
246}
247"###,
248 r####"
249fn f() {
250 let s = r#"#random##
251string"#;
252}
253"####,
254 )
255 }
256
257 #[test]
258 fn make_raw_string_closing_hashes_inside_works() {
259 check_assist(
260 make_raw_string,
261 r###"
262fn f() {
263 let s = $0"#random\"##\nstring";
264}
265"###,
266 r####"
267fn f() {
268 let s = r###"#random"##
269string"###;
270}
271"####,
272 )
273 }
274
275 #[test]
276 fn make_raw_string_nothing_to_unescape_works() {
277 check_assist(
278 make_raw_string,
279 r#"
280 fn f() {
281 let s = $0"random string";
282 }
283 "#,
284 r##"
285 fn f() {
286 let s = r#"random string"#;
287 }
288 "##,
289 )
290 }
291
292 #[test]
293 fn make_raw_string_not_works_on_partial_string() {
294 check_assist_not_applicable(
295 make_raw_string,
296 r#"
297 fn f() {
298 let s = "foo$0
299 }
300 "#,
301 )
302 }
303
304 #[test]
305 fn make_usual_string_not_works_on_partial_string() {
306 check_assist_not_applicable(
307 make_usual_string,
308 r#"
309 fn main() {
310 let s = r#"bar$0
311 }
312 "#,
313 )
314 }
315
316 #[test]
317 fn add_hash_target() {
318 check_assist_target(
319 add_hash,
320 r#"
321 fn f() {
322 let s = $0r"random string";
323 }
324 "#,
325 r#"r"random string""#,
326 );
327 }
328
329 #[test]
330 fn add_hash_works() {
331 check_assist(
332 add_hash,
333 r#"
334 fn f() {
335 let s = $0r"random string";
336 }
337 "#,
338 r##"
339 fn f() {
340 let s = r#"random string"#;
341 }
342 "##,
343 )
344 }
345
346 #[test]
347 fn add_more_hash_works() {
348 check_assist(
349 add_hash,
350 r##"
351 fn f() {
352 let s = $0r#"random"string"#;
353 }
354 "##,
355 r###"
356 fn f() {
357 let s = r##"random"string"##;
358 }
359 "###,
360 )
361 }
362
363 #[test]
364 fn add_hash_not_works() {
365 check_assist_not_applicable(
366 add_hash,
367 r#"
368 fn f() {
369 let s = $0"random string";
370 }
371 "#,
372 );
373 }
374
375 #[test]
376 fn remove_hash_target() {
377 check_assist_target(
378 remove_hash,
379 r##"
380 fn f() {
381 let s = $0r#"random string"#;
382 }
383 "##,
384 r##"r#"random string"#"##,
385 );
386 }
387
388 #[test]
389 fn remove_hash_works() {
390 check_assist(
391 remove_hash,
392 r##"fn f() { let s = $0r#"random string"#; }"##,
393 r#"fn f() { let s = r"random string"; }"#,
394 )
395 }
396
397 #[test]
398 fn cant_remove_required_hash() {
399 mark::check!(cant_remove_required_hash);
400 check_assist_not_applicable(
401 remove_hash,
402 r##"
403 fn f() {
404 let s = $0r#"random"str"ing"#;
405 }
406 "##,
407 )
408 }
409
410 #[test]
411 fn remove_more_hash_works() {
412 check_assist(
413 remove_hash,
414 r###"
415 fn f() {
416 let s = $0r##"random string"##;
417 }
418 "###,
419 r##"
420 fn f() {
421 let s = r#"random string"#;
422 }
423 "##,
424 )
425 }
426
427 #[test]
428 fn remove_hash_doesnt_work() {
429 check_assist_not_applicable(remove_hash, r#"fn f() { let s = $0"random string"; }"#);
430 }
431
432 #[test]
433 fn remove_hash_no_hash_doesnt_work() {
434 check_assist_not_applicable(remove_hash, r#"fn f() { let s = $0r"random string"; }"#);
435 }
436
437 #[test]
438 fn make_usual_string_target() {
439 check_assist_target(
440 make_usual_string,
441 r##"
442 fn f() {
443 let s = $0r#"random string"#;
444 }
445 "##,
446 r##"r#"random string"#"##,
447 );
448 }
449
450 #[test]
451 fn make_usual_string_works() {
452 check_assist(
453 make_usual_string,
454 r##"
455 fn f() {
456 let s = $0r#"random string"#;
457 }
458 "##,
459 r#"
460 fn f() {
461 let s = "random string";
462 }
463 "#,
464 )
465 }
466
467 #[test]
468 fn make_usual_string_with_quote_works() {
469 check_assist(
470 make_usual_string,
471 r##"
472 fn f() {
473 let s = $0r#"random"str"ing"#;
474 }
475 "##,
476 r#"
477 fn f() {
478 let s = "random\"str\"ing";
479 }
480 "#,
481 )
482 }
483
484 #[test]
485 fn make_usual_string_more_hash_works() {
486 check_assist(
487 make_usual_string,
488 r###"
489 fn f() {
490 let s = $0r##"random string"##;
491 }
492 "###,
493 r##"
494 fn f() {
495 let s = "random string";
496 }
497 "##,
498 )
499 }
500
501 #[test]
502 fn make_usual_string_not_works() {
503 check_assist_not_applicable(
504 make_usual_string,
505 r#"
506 fn f() {
507 let s = $0"random string";
508 }
509 "#,
510 );
511 }
512}
diff --git a/crates/ide_assists/src/handlers/remove_dbg.rs b/crates/ide_assists/src/handlers/remove_dbg.rs
new file mode 100644
index 000000000..6114091f2
--- /dev/null
+++ b/crates/ide_assists/src/handlers/remove_dbg.rs
@@ -0,0 +1,421 @@
1use syntax::{
2 ast::{self, AstNode},
3 match_ast, SyntaxElement, TextRange, TextSize, T,
4};
5
6use crate::{AssistContext, AssistId, AssistKind, Assists};
7
8// Assist: remove_dbg
9//
10// Removes `dbg!()` macro call.
11//
12// ```
13// fn main() {
14// $0dbg!(92);
15// }
16// ```
17// ->
18// ```
19// fn main() {
20// 92;
21// }
22// ```
23pub(crate) fn remove_dbg(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
24 let macro_call = ctx.find_node_at_offset::<ast::MacroCall>()?;
25 let new_contents = adjusted_macro_contents(&macro_call)?;
26
27 let macro_text_range = macro_call.syntax().text_range();
28 let macro_end = if macro_call.semicolon_token().is_some() {
29 macro_text_range.end() - TextSize::of(';')
30 } else {
31 macro_text_range.end()
32 };
33
34 acc.add(
35 AssistId("remove_dbg", AssistKind::Refactor),
36 "Remove dbg!()",
37 macro_text_range,
38 |builder| {
39 builder.replace(TextRange::new(macro_text_range.start(), macro_end), new_contents);
40 },
41 )
42}
43
44fn adjusted_macro_contents(macro_call: &ast::MacroCall) -> Option<String> {
45 let contents = get_valid_macrocall_contents(&macro_call, "dbg")?;
46 let macro_text_with_brackets = macro_call.token_tree()?.syntax().text();
47 let macro_text_in_brackets = macro_text_with_brackets.slice(TextRange::new(
48 TextSize::of('('),
49 macro_text_with_brackets.len() - TextSize::of(')'),
50 ));
51
52 Some(
53 if !is_leaf_or_control_flow_expr(macro_call)
54 && needs_parentheses_around_macro_contents(contents)
55 {
56 format!("({})", macro_text_in_brackets)
57 } else {
58 macro_text_in_brackets.to_string()
59 },
60 )
61}
62
63fn is_leaf_or_control_flow_expr(macro_call: &ast::MacroCall) -> bool {
64 macro_call.syntax().next_sibling().is_none()
65 || match macro_call.syntax().parent() {
66 Some(parent) => match_ast! {
67 match parent {
68 ast::Condition(_it) => true,
69 ast::MatchExpr(_it) => true,
70 _ => false,
71 }
72 },
73 None => false,
74 }
75}
76
77/// Verifies that the given macro_call actually matches the given name
78/// and contains proper ending tokens, then returns the contents between the ending tokens
79fn get_valid_macrocall_contents(
80 macro_call: &ast::MacroCall,
81 macro_name: &str,
82) -> Option<Vec<SyntaxElement>> {
83 let path = macro_call.path()?;
84 let name_ref = path.segment()?.name_ref()?;
85
86 // Make sure it is actually a dbg-macro call, dbg followed by !
87 let excl = path.syntax().next_sibling_or_token()?;
88 if name_ref.text() != macro_name || excl.kind() != T![!] {
89 return None;
90 }
91
92 let mut children_with_tokens = macro_call.token_tree()?.syntax().children_with_tokens();
93 let first_child = children_with_tokens.next()?;
94 let mut contents_between_brackets = children_with_tokens.collect::<Vec<_>>();
95 let last_child = contents_between_brackets.pop()?;
96
97 if contents_between_brackets.is_empty() {
98 None
99 } else {
100 match (first_child.kind(), last_child.kind()) {
101 (T!['('], T![')']) | (T!['['], T![']']) | (T!['{'], T!['}']) => {
102 Some(contents_between_brackets)
103 }
104 _ => None,
105 }
106 }
107}
108
109fn needs_parentheses_around_macro_contents(macro_contents: Vec<SyntaxElement>) -> bool {
110 if macro_contents.len() < 2 {
111 return false;
112 }
113 let mut macro_contents = macro_contents.into_iter().peekable();
114 let mut unpaired_brackets_in_contents = Vec::new();
115 while let Some(element) = macro_contents.next() {
116 match element.kind() {
117 T!['('] | T!['['] | T!['{'] => unpaired_brackets_in_contents.push(element),
118 T![')'] => {
119 if !matches!(unpaired_brackets_in_contents.pop(), Some(correct_bracket) if correct_bracket.kind() == T!['('])
120 {
121 return true;
122 }
123 }
124 T![']'] => {
125 if !matches!(unpaired_brackets_in_contents.pop(), Some(correct_bracket) if correct_bracket.kind() == T!['['])
126 {
127 return true;
128 }
129 }
130 T!['}'] => {
131 if !matches!(unpaired_brackets_in_contents.pop(), Some(correct_bracket) if correct_bracket.kind() == T!['{'])
132 {
133 return true;
134 }
135 }
136 symbol_kind => {
137 let symbol_not_in_bracket = unpaired_brackets_in_contents.is_empty();
138 if symbol_not_in_bracket
139 && symbol_kind != T![:] // paths
140 && (symbol_kind != T![.] // field/method access
141 || macro_contents // range expressions consist of two SyntaxKind::Dot in macro invocations
142 .peek()
143 .map(|element| element.kind() == T![.])
144 .unwrap_or(false))
145 && symbol_kind != T![?] // try operator
146 && (symbol_kind.is_punct() || symbol_kind == T![as])
147 {
148 return true;
149 }
150 }
151 }
152 }
153 !unpaired_brackets_in_contents.is_empty()
154}
155
156#[cfg(test)]
157mod tests {
158 use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target};
159
160 use super::*;
161
162 #[test]
163 fn test_remove_dbg() {
164 check_assist(remove_dbg, "$0dbg!(1 + 1)", "1 + 1");
165
166 check_assist(remove_dbg, "dbg!$0((1 + 1))", "(1 + 1)");
167
168 check_assist(remove_dbg, "dbg!(1 $0+ 1)", "1 + 1");
169
170 check_assist(remove_dbg, "let _ = $0dbg!(1 + 1)", "let _ = 1 + 1");
171
172 check_assist(
173 remove_dbg,
174 "
175fn foo(n: usize) {
176 if let Some(_) = dbg!(n.$0checked_sub(4)) {
177 // ...
178 }
179}
180",
181 "
182fn foo(n: usize) {
183 if let Some(_) = n.checked_sub(4) {
184 // ...
185 }
186}
187",
188 );
189
190 check_assist(remove_dbg, "$0dbg!(Foo::foo_test()).bar()", "Foo::foo_test().bar()");
191 }
192
193 #[test]
194 fn test_remove_dbg_with_brackets_and_braces() {
195 check_assist(remove_dbg, "dbg![$01 + 1]", "1 + 1");
196 check_assist(remove_dbg, "dbg!{$01 + 1}", "1 + 1");
197 }
198
199 #[test]
200 fn test_remove_dbg_not_applicable() {
201 check_assist_not_applicable(remove_dbg, "$0vec![1, 2, 3]");
202 check_assist_not_applicable(remove_dbg, "$0dbg(5, 6, 7)");
203 check_assist_not_applicable(remove_dbg, "$0dbg!(5, 6, 7");
204 }
205
206 #[test]
207 fn test_remove_dbg_target() {
208 check_assist_target(
209 remove_dbg,
210 "
211fn foo(n: usize) {
212 if let Some(_) = dbg!(n.$0checked_sub(4)) {
213 // ...
214 }
215}
216",
217 "dbg!(n.checked_sub(4))",
218 );
219 }
220
221 #[test]
222 fn test_remove_dbg_keep_semicolon() {
223 // https://github.com/rust-analyzer/rust-analyzer/issues/5129#issuecomment-651399779
224 // not quite though
225 // adding a comment at the end of the line makes
226 // the ast::MacroCall to include the semicolon at the end
227 check_assist(
228 remove_dbg,
229 r#"let res = $0dbg!(1 * 20); // needless comment"#,
230 r#"let res = 1 * 20; // needless comment"#,
231 );
232 }
233
234 #[test]
235 fn remove_dbg_from_non_leaf_simple_expression() {
236 check_assist(
237 remove_dbg,
238 "
239fn main() {
240 let mut a = 1;
241 while dbg!$0(a) < 10000 {
242 a += 1;
243 }
244}
245",
246 "
247fn main() {
248 let mut a = 1;
249 while a < 10000 {
250 a += 1;
251 }
252}
253",
254 );
255 }
256
257 #[test]
258 fn test_remove_dbg_keep_expression() {
259 check_assist(
260 remove_dbg,
261 r#"let res = $0dbg!(a + b).foo();"#,
262 r#"let res = (a + b).foo();"#,
263 );
264
265 check_assist(remove_dbg, r#"let res = $0dbg!(2 + 2) * 5"#, r#"let res = (2 + 2) * 5"#);
266 check_assist(remove_dbg, r#"let res = $0dbg![2 + 2] * 5"#, r#"let res = (2 + 2) * 5"#);
267 }
268
269 #[test]
270 fn test_remove_dbg_method_chaining() {
271 check_assist(
272 remove_dbg,
273 r#"let res = $0dbg!(foo().bar()).baz();"#,
274 r#"let res = foo().bar().baz();"#,
275 );
276 check_assist(
277 remove_dbg,
278 r#"let res = $0dbg!(foo.bar()).baz();"#,
279 r#"let res = foo.bar().baz();"#,
280 );
281 }
282
283 #[test]
284 fn test_remove_dbg_field_chaining() {
285 check_assist(remove_dbg, r#"let res = $0dbg!(foo.bar).baz;"#, r#"let res = foo.bar.baz;"#);
286 }
287
288 #[test]
289 fn test_remove_dbg_from_inside_fn() {
290 check_assist_target(
291 remove_dbg,
292 r#"
293fn square(x: u32) -> u32 {
294 x * x
295}
296
297fn main() {
298 let x = square(dbg$0!(5 + 10));
299 println!("{}", x);
300}"#,
301 "dbg!(5 + 10)",
302 );
303
304 check_assist(
305 remove_dbg,
306 r#"
307fn square(x: u32) -> u32 {
308 x * x
309}
310
311fn main() {
312 let x = square(dbg$0!(5 + 10));
313 println!("{}", x);
314}"#,
315 r#"
316fn square(x: u32) -> u32 {
317 x * x
318}
319
320fn main() {
321 let x = square(5 + 10);
322 println!("{}", x);
323}"#,
324 );
325 }
326
327 #[test]
328 fn test_remove_dbg_try_expr() {
329 check_assist(
330 remove_dbg,
331 r#"let res = $0dbg!(result?).foo();"#,
332 r#"let res = result?.foo();"#,
333 );
334 }
335
336 #[test]
337 fn test_remove_dbg_await_expr() {
338 check_assist(
339 remove_dbg,
340 r#"let res = $0dbg!(fut.await).foo();"#,
341 r#"let res = fut.await.foo();"#,
342 );
343 }
344
345 #[test]
346 fn test_remove_dbg_as_cast() {
347 check_assist(
348 remove_dbg,
349 r#"let res = $0dbg!(3 as usize).foo();"#,
350 r#"let res = (3 as usize).foo();"#,
351 );
352 }
353
354 #[test]
355 fn test_remove_dbg_index_expr() {
356 check_assist(
357 remove_dbg,
358 r#"let res = $0dbg!(array[3]).foo();"#,
359 r#"let res = array[3].foo();"#,
360 );
361 check_assist(
362 remove_dbg,
363 r#"let res = $0dbg!(tuple.3).foo();"#,
364 r#"let res = tuple.3.foo();"#,
365 );
366 }
367
368 #[test]
369 fn test_remove_dbg_range_expr() {
370 check_assist(
371 remove_dbg,
372 r#"let res = $0dbg!(foo..bar).foo();"#,
373 r#"let res = (foo..bar).foo();"#,
374 );
375 check_assist(
376 remove_dbg,
377 r#"let res = $0dbg!(foo..=bar).foo();"#,
378 r#"let res = (foo..=bar).foo();"#,
379 );
380 }
381
382 #[test]
383 fn test_remove_dbg_followed_by_block() {
384 check_assist(
385 remove_dbg,
386 r#"fn foo() {
387 if $0dbg!(x || y) {}
388}"#,
389 r#"fn foo() {
390 if x || y {}
391}"#,
392 );
393 check_assist(
394 remove_dbg,
395 r#"fn foo() {
396 while let foo = $0dbg!(&x) {}
397}"#,
398 r#"fn foo() {
399 while let foo = &x {}
400}"#,
401 );
402 check_assist(
403 remove_dbg,
404 r#"fn foo() {
405 if let foo = $0dbg!(&x) {}
406}"#,
407 r#"fn foo() {
408 if let foo = &x {}
409}"#,
410 );
411 check_assist(
412 remove_dbg,
413 r#"fn foo() {
414 match $0dbg!(&x) {}
415}"#,
416 r#"fn foo() {
417 match &x {}
418}"#,
419 );
420 }
421}
diff --git a/crates/ide_assists/src/handlers/remove_mut.rs b/crates/ide_assists/src/handlers/remove_mut.rs
new file mode 100644
index 000000000..30d36dacd
--- /dev/null
+++ b/crates/ide_assists/src/handlers/remove_mut.rs
@@ -0,0 +1,37 @@
1use syntax::{SyntaxKind, TextRange, T};
2
3use crate::{AssistContext, AssistId, AssistKind, Assists};
4
5// Assist: remove_mut
6//
7// Removes the `mut` keyword.
8//
9// ```
10// impl Walrus {
11// fn feed(&mut$0 self, amount: u32) {}
12// }
13// ```
14// ->
15// ```
16// impl Walrus {
17// fn feed(&self, amount: u32) {}
18// }
19// ```
20pub(crate) fn remove_mut(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
21 let mut_token = ctx.find_token_syntax_at_offset(T![mut])?;
22 let delete_from = mut_token.text_range().start();
23 let delete_to = match mut_token.next_token() {
24 Some(it) if it.kind() == SyntaxKind::WHITESPACE => it.text_range().end(),
25 _ => mut_token.text_range().end(),
26 };
27
28 let target = mut_token.text_range();
29 acc.add(
30 AssistId("remove_mut", AssistKind::Refactor),
31 "Remove `mut` keyword",
32 target,
33 |builder| {
34 builder.delete(TextRange::new(delete_from, delete_to));
35 },
36 )
37}
diff --git a/crates/ide_assists/src/handlers/remove_unused_param.rs b/crates/ide_assists/src/handlers/remove_unused_param.rs
new file mode 100644
index 000000000..c961680e2
--- /dev/null
+++ b/crates/ide_assists/src/handlers/remove_unused_param.rs
@@ -0,0 +1,288 @@
1use ide_db::{base_db::FileId, defs::Definition, search::FileReference};
2use syntax::{
3 algo::find_node_at_range,
4 ast::{self, ArgListOwner},
5 AstNode, SourceFile, SyntaxKind, SyntaxNode, TextRange, T,
6};
7use test_utils::mark;
8use SyntaxKind::WHITESPACE;
9
10use crate::{
11 assist_context::AssistBuilder, utils::next_prev, AssistContext, AssistId, AssistKind, Assists,
12};
13
14// Assist: remove_unused_param
15//
16// Removes unused function parameter.
17//
18// ```
19// fn frobnicate(x: i32$0) {}
20//
21// fn main() {
22// frobnicate(92);
23// }
24// ```
25// ->
26// ```
27// fn frobnicate() {}
28//
29// fn main() {
30// frobnicate();
31// }
32// ```
33pub(crate) fn remove_unused_param(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
34 let param: ast::Param = ctx.find_node_at_offset()?;
35 let ident_pat = match param.pat()? {
36 ast::Pat::IdentPat(it) => it,
37 _ => return None,
38 };
39 let func = param.syntax().ancestors().find_map(ast::Fn::cast)?;
40 let param_position = func.param_list()?.params().position(|it| it == param)?;
41
42 let fn_def = {
43 let func = ctx.sema.to_def(&func)?;
44 Definition::ModuleDef(func.into())
45 };
46
47 let param_def = {
48 let local = ctx.sema.to_def(&ident_pat)?;
49 Definition::Local(local)
50 };
51 if param_def.usages(&ctx.sema).at_least_one() {
52 mark::hit!(keep_used);
53 return None;
54 }
55 acc.add(
56 AssistId("remove_unused_param", AssistKind::Refactor),
57 "Remove unused parameter",
58 param.syntax().text_range(),
59 |builder| {
60 builder.delete(range_to_remove(param.syntax()));
61 for (file_id, references) in fn_def.usages(&ctx.sema).all() {
62 process_usages(ctx, builder, file_id, references, param_position);
63 }
64 },
65 )
66}
67
68fn process_usages(
69 ctx: &AssistContext,
70 builder: &mut AssistBuilder,
71 file_id: FileId,
72 references: Vec<FileReference>,
73 arg_to_remove: usize,
74) {
75 let source_file = ctx.sema.parse(file_id);
76 builder.edit_file(file_id);
77 for usage in references {
78 if let Some(text_range) = process_usage(&source_file, usage, arg_to_remove) {
79 builder.delete(text_range);
80 }
81 }
82}
83
84fn process_usage(
85 source_file: &SourceFile,
86 FileReference { range, .. }: FileReference,
87 arg_to_remove: usize,
88) -> Option<TextRange> {
89 let call_expr: ast::CallExpr = find_node_at_range(source_file.syntax(), range)?;
90 let call_expr_range = call_expr.expr()?.syntax().text_range();
91 if !call_expr_range.contains_range(range) {
92 return None;
93 }
94 let arg = call_expr.arg_list()?.args().nth(arg_to_remove)?;
95 Some(range_to_remove(arg.syntax()))
96}
97
98fn range_to_remove(node: &SyntaxNode) -> TextRange {
99 let up_to_comma = next_prev().find_map(|dir| {
100 node.siblings_with_tokens(dir)
101 .filter_map(|it| it.into_token())
102 .find(|it| it.kind() == T![,])
103 .map(|it| (dir, it))
104 });
105 if let Some((dir, token)) = up_to_comma {
106 if node.next_sibling().is_some() {
107 let up_to_space = token
108 .siblings_with_tokens(dir)
109 .skip(1)
110 .take_while(|it| it.kind() == WHITESPACE)
111 .last()
112 .and_then(|it| it.into_token());
113 return node
114 .text_range()
115 .cover(up_to_space.map_or(token.text_range(), |it| it.text_range()));
116 }
117 node.text_range().cover(token.text_range())
118 } else {
119 node.text_range()
120 }
121}
122
123#[cfg(test)]
124mod tests {
125 use crate::tests::{check_assist, check_assist_not_applicable};
126
127 use super::*;
128
129 #[test]
130 fn remove_unused() {
131 check_assist(
132 remove_unused_param,
133 r#"
134fn a() { foo(9, 2) }
135fn foo(x: i32, $0y: i32) { x; }
136fn b() { foo(9, 2,) }
137"#,
138 r#"
139fn a() { foo(9) }
140fn foo(x: i32) { x; }
141fn b() { foo(9, ) }
142"#,
143 );
144 }
145
146 #[test]
147 fn remove_unused_first_param() {
148 check_assist(
149 remove_unused_param,
150 r#"
151fn foo($0x: i32, y: i32) { y; }
152fn a() { foo(1, 2) }
153fn b() { foo(1, 2,) }
154"#,
155 r#"
156fn foo(y: i32) { y; }
157fn a() { foo(2) }
158fn b() { foo(2,) }
159"#,
160 );
161 }
162
163 #[test]
164 fn remove_unused_single_param() {
165 check_assist(
166 remove_unused_param,
167 r#"
168fn foo($0x: i32) { 0; }
169fn a() { foo(1) }
170fn b() { foo(1, ) }
171"#,
172 r#"
173fn foo() { 0; }
174fn a() { foo() }
175fn b() { foo( ) }
176"#,
177 );
178 }
179
180 #[test]
181 fn remove_unused_surrounded_by_parms() {
182 check_assist(
183 remove_unused_param,
184 r#"
185fn foo(x: i32, $0y: i32, z: i32) { x; }
186fn a() { foo(1, 2, 3) }
187fn b() { foo(1, 2, 3,) }
188"#,
189 r#"
190fn foo(x: i32, z: i32) { x; }
191fn a() { foo(1, 3) }
192fn b() { foo(1, 3,) }
193"#,
194 );
195 }
196
197 #[test]
198 fn remove_unused_qualified_call() {
199 check_assist(
200 remove_unused_param,
201 r#"
202mod bar { pub fn foo(x: i32, $0y: i32) { x; } }
203fn b() { bar::foo(9, 2) }
204"#,
205 r#"
206mod bar { pub fn foo(x: i32) { x; } }
207fn b() { bar::foo(9) }
208"#,
209 );
210 }
211
212 #[test]
213 fn remove_unused_turbofished_func() {
214 check_assist(
215 remove_unused_param,
216 r#"
217pub fn foo<T>(x: T, $0y: i32) { x; }
218fn b() { foo::<i32>(9, 2) }
219"#,
220 r#"
221pub fn foo<T>(x: T) { x; }
222fn b() { foo::<i32>(9) }
223"#,
224 );
225 }
226
227 #[test]
228 fn remove_unused_generic_unused_param_func() {
229 check_assist(
230 remove_unused_param,
231 r#"
232pub fn foo<T>(x: i32, $0y: T) { x; }
233fn b() { foo::<i32>(9, 2) }
234fn b2() { foo(9, 2) }
235"#,
236 r#"
237pub fn foo<T>(x: i32) { x; }
238fn b() { foo::<i32>(9) }
239fn b2() { foo(9) }
240"#,
241 );
242 }
243
244 #[test]
245 fn keep_used() {
246 mark::check!(keep_used);
247 check_assist_not_applicable(
248 remove_unused_param,
249 r#"
250fn foo(x: i32, $0y: i32) { y; }
251fn main() { foo(9, 2) }
252"#,
253 );
254 }
255
256 #[test]
257 fn remove_across_files() {
258 check_assist(
259 remove_unused_param,
260 r#"
261//- /main.rs
262fn foo(x: i32, $0y: i32) { x; }
263
264mod foo;
265
266//- /foo.rs
267use super::foo;
268
269fn bar() {
270 let _ = foo(1, 2);
271}
272"#,
273 r#"
274//- /main.rs
275fn foo(x: i32) { x; }
276
277mod foo;
278
279//- /foo.rs
280use super::foo;
281
282fn bar() {
283 let _ = foo(1);
284}
285"#,
286 )
287 }
288}
diff --git a/crates/ide_assists/src/handlers/reorder_fields.rs b/crates/ide_assists/src/handlers/reorder_fields.rs
new file mode 100644
index 000000000..fba7d6ddb
--- /dev/null
+++ b/crates/ide_assists/src/handlers/reorder_fields.rs
@@ -0,0 +1,227 @@
1use itertools::Itertools;
2use rustc_hash::FxHashMap;
3
4use hir::{Adt, ModuleDef, PathResolution, Semantics, Struct};
5use ide_db::RootDatabase;
6use syntax::{algo, ast, match_ast, AstNode, SyntaxKind, SyntaxKind::*, SyntaxNode};
7use test_utils::mark;
8
9use crate::{AssistContext, AssistId, AssistKind, Assists};
10
11// Assist: reorder_fields
12//
13// Reorder the fields of record literals and record patterns in the same order as in
14// the definition.
15//
16// ```
17// struct Foo {foo: i32, bar: i32};
18// const test: Foo = $0Foo {bar: 0, foo: 1}
19// ```
20// ->
21// ```
22// struct Foo {foo: i32, bar: i32};
23// const test: Foo = Foo {foo: 1, bar: 0}
24// ```
25//
26pub(crate) fn reorder_fields(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
27 reorder::<ast::RecordExpr>(acc, ctx).or_else(|| reorder::<ast::RecordPat>(acc, ctx))
28}
29
30fn reorder<R: AstNode>(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
31 let record = ctx.find_node_at_offset::<R>()?;
32 let path = record.syntax().children().find_map(ast::Path::cast)?;
33
34 let ranks = compute_fields_ranks(&path, &ctx)?;
35
36 let fields = get_fields(&record.syntax());
37 let sorted_fields = sorted_by_rank(&fields, |node| {
38 *ranks.get(&get_field_name(node)).unwrap_or(&usize::max_value())
39 });
40
41 if sorted_fields == fields {
42 mark::hit!(reorder_sorted_fields);
43 return None;
44 }
45
46 let target = record.syntax().text_range();
47 acc.add(
48 AssistId("reorder_fields", AssistKind::RefactorRewrite),
49 "Reorder record fields",
50 target,
51 |edit| {
52 let mut rewriter = algo::SyntaxRewriter::default();
53 for (old, new) in fields.iter().zip(&sorted_fields) {
54 rewriter.replace(old, new);
55 }
56 edit.rewrite(rewriter);
57 },
58 )
59}
60
61fn get_fields_kind(node: &SyntaxNode) -> Vec<SyntaxKind> {
62 match node.kind() {
63 RECORD_EXPR => vec![RECORD_EXPR_FIELD],
64 RECORD_PAT => vec![RECORD_PAT_FIELD, IDENT_PAT],
65 _ => vec![],
66 }
67}
68
69fn get_field_name(node: &SyntaxNode) -> String {
70 let res = match_ast! {
71 match node {
72 ast::RecordExprField(field) => field.field_name().map(|it| it.to_string()),
73 ast::RecordPatField(field) => field.field_name().map(|it| it.to_string()),
74 _ => None,
75 }
76 };
77 res.unwrap_or_default()
78}
79
80fn get_fields(record: &SyntaxNode) -> Vec<SyntaxNode> {
81 let kinds = get_fields_kind(record);
82 record.children().flat_map(|n| n.children()).filter(|n| kinds.contains(&n.kind())).collect()
83}
84
85fn sorted_by_rank(
86 fields: &[SyntaxNode],
87 get_rank: impl Fn(&SyntaxNode) -> usize,
88) -> Vec<SyntaxNode> {
89 fields.iter().cloned().sorted_by_key(get_rank).collect()
90}
91
92fn struct_definition(path: &ast::Path, sema: &Semantics<RootDatabase>) -> Option<Struct> {
93 match sema.resolve_path(path) {
94 Some(PathResolution::Def(ModuleDef::Adt(Adt::Struct(s)))) => Some(s),
95 _ => None,
96 }
97}
98
99fn compute_fields_ranks(path: &ast::Path, ctx: &AssistContext) -> Option<FxHashMap<String, usize>> {
100 Some(
101 struct_definition(path, &ctx.sema)?
102 .fields(ctx.db())
103 .iter()
104 .enumerate()
105 .map(|(idx, field)| (field.name(ctx.db()).to_string(), idx))
106 .collect(),
107 )
108}
109
110#[cfg(test)]
111mod tests {
112 use test_utils::mark;
113
114 use crate::tests::{check_assist, check_assist_not_applicable};
115
116 use super::*;
117
118 #[test]
119 fn reorder_sorted_fields() {
120 mark::check!(reorder_sorted_fields);
121 check_assist_not_applicable(
122 reorder_fields,
123 r#"
124struct Foo {
125 foo: i32,
126 bar: i32,
127}
128
129const test: Foo = $0Foo { foo: 0, bar: 0 };
130"#,
131 )
132 }
133
134 #[test]
135 fn trivial_empty_fields() {
136 check_assist_not_applicable(
137 reorder_fields,
138 r#"
139struct Foo {};
140const test: Foo = $0Foo {}
141"#,
142 )
143 }
144
145 #[test]
146 fn reorder_struct_fields() {
147 check_assist(
148 reorder_fields,
149 r#"
150struct Foo {foo: i32, bar: i32};
151const test: Foo = $0Foo {bar: 0, foo: 1}
152"#,
153 r#"
154struct Foo {foo: i32, bar: i32};
155const test: Foo = Foo {foo: 1, bar: 0}
156"#,
157 )
158 }
159
160 #[test]
161 fn reorder_struct_pattern() {
162 check_assist(
163 reorder_fields,
164 r#"
165struct Foo { foo: i64, bar: i64, baz: i64 }
166
167fn f(f: Foo) -> {
168 match f {
169 $0Foo { baz: 0, ref mut bar, .. } => (),
170 _ => ()
171 }
172}
173"#,
174 r#"
175struct Foo { foo: i64, bar: i64, baz: i64 }
176
177fn f(f: Foo) -> {
178 match f {
179 Foo { ref mut bar, baz: 0, .. } => (),
180 _ => ()
181 }
182}
183"#,
184 )
185 }
186
187 #[test]
188 fn reorder_with_extra_field() {
189 check_assist(
190 reorder_fields,
191 r#"
192struct Foo {
193 foo: String,
194 bar: String,
195}
196
197impl Foo {
198 fn new() -> Foo {
199 let foo = String::new();
200 $0Foo {
201 bar: foo.clone(),
202 extra: "Extra field",
203 foo,
204 }
205 }
206}
207"#,
208 r#"
209struct Foo {
210 foo: String,
211 bar: String,
212}
213
214impl Foo {
215 fn new() -> Foo {
216 let foo = String::new();
217 Foo {
218 foo,
219 bar: foo.clone(),
220 extra: "Extra field",
221 }
222 }
223}
224"#,
225 )
226 }
227}
diff --git a/crates/ide_assists/src/handlers/reorder_impl.rs b/crates/ide_assists/src/handlers/reorder_impl.rs
new file mode 100644
index 000000000..309f291c8
--- /dev/null
+++ b/crates/ide_assists/src/handlers/reorder_impl.rs
@@ -0,0 +1,201 @@
1use itertools::Itertools;
2use rustc_hash::FxHashMap;
3
4use hir::{PathResolution, Semantics};
5use ide_db::RootDatabase;
6use syntax::{
7 algo,
8 ast::{self, NameOwner},
9 AstNode,
10};
11use test_utils::mark;
12
13use crate::{AssistContext, AssistId, AssistKind, Assists};
14
15// Assist: reorder_impl
16//
17// Reorder the methods of an `impl Trait`. The methods will be ordered
18// in the same order as in the trait definition.
19//
20// ```
21// trait Foo {
22// fn a() {}
23// fn b() {}
24// fn c() {}
25// }
26//
27// struct Bar;
28// $0impl Foo for Bar {
29// fn b() {}
30// fn c() {}
31// fn a() {}
32// }
33// ```
34// ->
35// ```
36// trait Foo {
37// fn a() {}
38// fn b() {}
39// fn c() {}
40// }
41//
42// struct Bar;
43// impl Foo for Bar {
44// fn a() {}
45// fn b() {}
46// fn c() {}
47// }
48// ```
49//
50pub(crate) fn reorder_impl(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
51 let impl_ast = ctx.find_node_at_offset::<ast::Impl>()?;
52 let items = impl_ast.assoc_item_list()?;
53 let methods = get_methods(&items);
54
55 let path = impl_ast
56 .trait_()
57 .and_then(|t| match t {
58 ast::Type::PathType(path) => Some(path),
59 _ => None,
60 })?
61 .path()?;
62
63 let ranks = compute_method_ranks(&path, ctx)?;
64 let sorted: Vec<_> = methods
65 .iter()
66 .cloned()
67 .sorted_by_key(|f| {
68 f.name().and_then(|n| ranks.get(&n.to_string()).copied()).unwrap_or(usize::max_value())
69 })
70 .collect();
71
72 // Don't edit already sorted methods:
73 if methods == sorted {
74 mark::hit!(not_applicable_if_sorted);
75 return None;
76 }
77
78 let target = items.syntax().text_range();
79 acc.add(AssistId("reorder_impl", AssistKind::RefactorRewrite), "Sort methods", target, |edit| {
80 let mut rewriter = algo::SyntaxRewriter::default();
81 for (old, new) in methods.iter().zip(&sorted) {
82 rewriter.replace(old.syntax(), new.syntax());
83 }
84 edit.rewrite(rewriter);
85 })
86}
87
88fn compute_method_ranks(path: &ast::Path, ctx: &AssistContext) -> Option<FxHashMap<String, usize>> {
89 let td = trait_definition(path, &ctx.sema)?;
90
91 Some(
92 td.items(ctx.db())
93 .iter()
94 .flat_map(|i| match i {
95 hir::AssocItem::Function(f) => Some(f),
96 _ => None,
97 })
98 .enumerate()
99 .map(|(idx, func)| ((func.name(ctx.db()).to_string(), idx)))
100 .collect(),
101 )
102}
103
104fn trait_definition(path: &ast::Path, sema: &Semantics<RootDatabase>) -> Option<hir::Trait> {
105 match sema.resolve_path(path)? {
106 PathResolution::Def(hir::ModuleDef::Trait(trait_)) => Some(trait_),
107 _ => None,
108 }
109}
110
111fn get_methods(items: &ast::AssocItemList) -> Vec<ast::Fn> {
112 items
113 .assoc_items()
114 .flat_map(|i| match i {
115 ast::AssocItem::Fn(f) => Some(f),
116 _ => None,
117 })
118 .filter(|f| f.name().is_some())
119 .collect()
120}
121
122#[cfg(test)]
123mod tests {
124 use test_utils::mark;
125
126 use crate::tests::{check_assist, check_assist_not_applicable};
127
128 use super::*;
129
130 #[test]
131 fn not_applicable_if_sorted() {
132 mark::check!(not_applicable_if_sorted);
133 check_assist_not_applicable(
134 reorder_impl,
135 r#"
136trait Bar {
137 fn a() {}
138 fn z() {}
139 fn b() {}
140}
141struct Foo;
142$0impl Bar for Foo {
143 fn a() {}
144 fn z() {}
145 fn b() {}
146}
147 "#,
148 )
149 }
150
151 #[test]
152 fn not_applicable_if_empty() {
153 check_assist_not_applicable(
154 reorder_impl,
155 r#"
156trait Bar {};
157struct Foo;
158$0impl Bar for Foo {}
159 "#,
160 )
161 }
162
163 #[test]
164 fn reorder_impl_trait_methods() {
165 check_assist(
166 reorder_impl,
167 r#"
168trait Bar {
169 fn a() {}
170 fn c() {}
171 fn b() {}
172 fn d() {}
173}
174
175struct Foo;
176$0impl Bar for Foo {
177 fn d() {}
178 fn b() {}
179 fn c() {}
180 fn a() {}
181}
182 "#,
183 r#"
184trait Bar {
185 fn a() {}
186 fn c() {}
187 fn b() {}
188 fn d() {}
189}
190
191struct Foo;
192impl Bar for Foo {
193 fn a() {}
194 fn c() {}
195 fn b() {}
196 fn d() {}
197}
198 "#,
199 )
200 }
201}
diff --git a/crates/ide_assists/src/handlers/replace_derive_with_manual_impl.rs b/crates/ide_assists/src/handlers/replace_derive_with_manual_impl.rs
new file mode 100644
index 000000000..c69bc5cac
--- /dev/null
+++ b/crates/ide_assists/src/handlers/replace_derive_with_manual_impl.rs
@@ -0,0 +1,404 @@
1use ide_db::helpers::mod_path_to_ast;
2use ide_db::imports_locator;
3use itertools::Itertools;
4use syntax::{
5 ast::{self, make, AstNode, NameOwner},
6 SyntaxKind::{IDENT, WHITESPACE},
7 TextSize,
8};
9
10use crate::{
11 assist_context::{AssistBuilder, AssistContext, Assists},
12 utils::{
13 add_trait_assoc_items_to_impl, filter_assoc_items, generate_trait_impl_text,
14 render_snippet, Cursor, DefaultMethods,
15 },
16 AssistId, AssistKind,
17};
18
19// Assist: replace_derive_with_manual_impl
20//
21// Converts a `derive` impl into a manual one.
22//
23// ```
24// # trait Debug { fn fmt(&self, f: &mut Formatter) -> Result<()>; }
25// #[derive(Deb$0ug, Display)]
26// struct S;
27// ```
28// ->
29// ```
30// # trait Debug { fn fmt(&self, f: &mut Formatter) -> Result<()>; }
31// #[derive(Display)]
32// struct S;
33//
34// impl Debug for S {
35// fn fmt(&self, f: &mut Formatter) -> Result<()> {
36// ${0:todo!()}
37// }
38// }
39// ```
40pub(crate) fn replace_derive_with_manual_impl(
41 acc: &mut Assists,
42 ctx: &AssistContext,
43) -> Option<()> {
44 let attr = ctx.find_node_at_offset::<ast::Attr>()?;
45
46 let has_derive = attr
47 .syntax()
48 .descendants_with_tokens()
49 .filter(|t| t.kind() == IDENT)
50 .find_map(syntax::NodeOrToken::into_token)
51 .filter(|t| t.text() == "derive")
52 .is_some();
53 if !has_derive {
54 return None;
55 }
56
57 let trait_token = ctx.token_at_offset().find(|t| t.kind() == IDENT && t.text() != "derive")?;
58 let trait_path = make::path_unqualified(make::path_segment(make::name_ref(trait_token.text())));
59
60 let adt = attr.syntax().parent().and_then(ast::Adt::cast)?;
61 let annotated_name = adt.name()?;
62 let insert_pos = adt.syntax().text_range().end();
63
64 let current_module = ctx.sema.scope(annotated_name.syntax()).module()?;
65 let current_crate = current_module.krate();
66
67 let found_traits = imports_locator::find_exact_imports(
68 &ctx.sema,
69 current_crate,
70 trait_token.text().to_string(),
71 )
72 .filter_map(|candidate: either::Either<hir::ModuleDef, hir::MacroDef>| match candidate {
73 either::Either::Left(hir::ModuleDef::Trait(trait_)) => Some(trait_),
74 _ => None,
75 })
76 .flat_map(|trait_| {
77 current_module
78 .find_use_path(ctx.sema.db, hir::ModuleDef::Trait(trait_))
79 .as_ref()
80 .map(mod_path_to_ast)
81 .zip(Some(trait_))
82 });
83
84 let mut no_traits_found = true;
85 for (trait_path, trait_) in found_traits.inspect(|_| no_traits_found = false) {
86 add_assist(acc, ctx, &attr, &trait_path, Some(trait_), &adt, &annotated_name, insert_pos)?;
87 }
88 if no_traits_found {
89 add_assist(acc, ctx, &attr, &trait_path, None, &adt, &annotated_name, insert_pos)?;
90 }
91 Some(())
92}
93
94fn add_assist(
95 acc: &mut Assists,
96 ctx: &AssistContext,
97 attr: &ast::Attr,
98 trait_path: &ast::Path,
99 trait_: Option<hir::Trait>,
100 adt: &ast::Adt,
101 annotated_name: &ast::Name,
102 insert_pos: TextSize,
103) -> Option<()> {
104 let target = attr.syntax().text_range();
105 let input = attr.token_tree()?;
106 let label = format!("Convert to manual `impl {} for {}`", trait_path, annotated_name);
107 let trait_name = trait_path.segment().and_then(|seg| seg.name_ref())?;
108
109 acc.add(
110 AssistId("replace_derive_with_manual_impl", AssistKind::Refactor),
111 label,
112 target,
113 |builder| {
114 let impl_def_with_items =
115 impl_def_from_trait(&ctx.sema, annotated_name, trait_, trait_path);
116 update_attribute(builder, &input, &trait_name, &attr);
117 let trait_path = format!("{}", trait_path);
118 match (ctx.config.snippet_cap, impl_def_with_items) {
119 (None, _) => {
120 builder.insert(insert_pos, generate_trait_impl_text(adt, &trait_path, ""))
121 }
122 (Some(cap), None) => builder.insert_snippet(
123 cap,
124 insert_pos,
125 generate_trait_impl_text(adt, &trait_path, " $0"),
126 ),
127 (Some(cap), Some((impl_def, first_assoc_item))) => {
128 let mut cursor = Cursor::Before(first_assoc_item.syntax());
129 let placeholder;
130 if let ast::AssocItem::Fn(ref func) = first_assoc_item {
131 if let Some(m) = func.syntax().descendants().find_map(ast::MacroCall::cast)
132 {
133 if m.syntax().text() == "todo!()" {
134 placeholder = m;
135 cursor = Cursor::Replace(placeholder.syntax());
136 }
137 }
138 }
139
140 builder.insert_snippet(
141 cap,
142 insert_pos,
143 format!("\n\n{}", render_snippet(cap, impl_def.syntax(), cursor)),
144 )
145 }
146 };
147 },
148 )
149}
150
151fn impl_def_from_trait(
152 sema: &hir::Semantics<ide_db::RootDatabase>,
153 annotated_name: &ast::Name,
154 trait_: Option<hir::Trait>,
155 trait_path: &ast::Path,
156) -> Option<(ast::Impl, ast::AssocItem)> {
157 let trait_ = trait_?;
158 let target_scope = sema.scope(annotated_name.syntax());
159 let trait_items = filter_assoc_items(sema.db, &trait_.items(sema.db), DefaultMethods::No);
160 if trait_items.is_empty() {
161 return None;
162 }
163 let impl_def = make::impl_trait(
164 trait_path.clone(),
165 make::path_unqualified(make::path_segment(make::name_ref(annotated_name.text()))),
166 );
167 let (impl_def, first_assoc_item) =
168 add_trait_assoc_items_to_impl(sema, trait_items, trait_, impl_def, target_scope);
169 Some((impl_def, first_assoc_item))
170}
171
172fn update_attribute(
173 builder: &mut AssistBuilder,
174 input: &ast::TokenTree,
175 trait_name: &ast::NameRef,
176 attr: &ast::Attr,
177) {
178 let new_attr_input = input
179 .syntax()
180 .descendants_with_tokens()
181 .filter(|t| t.kind() == IDENT)
182 .filter_map(|t| t.into_token().map(|t| t.text().to_string()))
183 .filter(|t| t != trait_name.text())
184 .collect::<Vec<_>>();
185 let has_more_derives = !new_attr_input.is_empty();
186
187 if has_more_derives {
188 let new_attr_input = format!("({})", new_attr_input.iter().format(", "));
189 builder.replace(input.syntax().text_range(), new_attr_input);
190 } else {
191 let attr_range = attr.syntax().text_range();
192 builder.delete(attr_range);
193
194 if let Some(line_break_range) = attr
195 .syntax()
196 .next_sibling_or_token()
197 .filter(|t| t.kind() == WHITESPACE)
198 .map(|t| t.text_range())
199 {
200 builder.delete(line_break_range);
201 }
202 }
203}
204
205#[cfg(test)]
206mod tests {
207 use crate::tests::{check_assist, check_assist_not_applicable};
208
209 use super::*;
210
211 #[test]
212 fn add_custom_impl_debug() {
213 check_assist(
214 replace_derive_with_manual_impl,
215 "
216mod fmt {
217 pub struct Error;
218 pub type Result = Result<(), Error>;
219 pub struct Formatter<'a>;
220 pub trait Debug {
221 fn fmt(&self, f: &mut Formatter<'_>) -> Result;
222 }
223}
224
225#[derive(Debu$0g)]
226struct Foo {
227 bar: String,
228}
229",
230 "
231mod fmt {
232 pub struct Error;
233 pub type Result = Result<(), Error>;
234 pub struct Formatter<'a>;
235 pub trait Debug {
236 fn fmt(&self, f: &mut Formatter<'_>) -> Result;
237 }
238}
239
240struct Foo {
241 bar: String,
242}
243
244impl fmt::Debug for Foo {
245 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
246 ${0:todo!()}
247 }
248}
249",
250 )
251 }
252 #[test]
253 fn add_custom_impl_all() {
254 check_assist(
255 replace_derive_with_manual_impl,
256 "
257mod foo {
258 pub trait Bar {
259 type Qux;
260 const Baz: usize = 42;
261 const Fez: usize;
262 fn foo();
263 fn bar() {}
264 }
265}
266
267#[derive($0Bar)]
268struct Foo {
269 bar: String,
270}
271",
272 "
273mod foo {
274 pub trait Bar {
275 type Qux;
276 const Baz: usize = 42;
277 const Fez: usize;
278 fn foo();
279 fn bar() {}
280 }
281}
282
283struct Foo {
284 bar: String,
285}
286
287impl foo::Bar for Foo {
288 $0type Qux;
289
290 const Baz: usize = 42;
291
292 const Fez: usize;
293
294 fn foo() {
295 todo!()
296 }
297}
298",
299 )
300 }
301 #[test]
302 fn add_custom_impl_for_unique_input() {
303 check_assist(
304 replace_derive_with_manual_impl,
305 "
306#[derive(Debu$0g)]
307struct Foo {
308 bar: String,
309}
310 ",
311 "
312struct Foo {
313 bar: String,
314}
315
316impl Debug for Foo {
317 $0
318}
319 ",
320 )
321 }
322
323 #[test]
324 fn add_custom_impl_for_with_visibility_modifier() {
325 check_assist(
326 replace_derive_with_manual_impl,
327 "
328#[derive(Debug$0)]
329pub struct Foo {
330 bar: String,
331}
332 ",
333 "
334pub struct Foo {
335 bar: String,
336}
337
338impl Debug for Foo {
339 $0
340}
341 ",
342 )
343 }
344
345 #[test]
346 fn add_custom_impl_when_multiple_inputs() {
347 check_assist(
348 replace_derive_with_manual_impl,
349 "
350#[derive(Display, Debug$0, Serialize)]
351struct Foo {}
352 ",
353 "
354#[derive(Display, Serialize)]
355struct Foo {}
356
357impl Debug for Foo {
358 $0
359}
360 ",
361 )
362 }
363
364 #[test]
365 fn test_ignore_derive_macro_without_input() {
366 check_assist_not_applicable(
367 replace_derive_with_manual_impl,
368 "
369#[derive($0)]
370struct Foo {}
371 ",
372 )
373 }
374
375 #[test]
376 fn test_ignore_if_cursor_on_param() {
377 check_assist_not_applicable(
378 replace_derive_with_manual_impl,
379 "
380#[derive$0(Debug)]
381struct Foo {}
382 ",
383 );
384
385 check_assist_not_applicable(
386 replace_derive_with_manual_impl,
387 "
388#[derive(Debug)$0]
389struct Foo {}
390 ",
391 )
392 }
393
394 #[test]
395 fn test_ignore_if_not_derive() {
396 check_assist_not_applicable(
397 replace_derive_with_manual_impl,
398 "
399#[allow(non_camel_$0case_types)]
400struct Foo {}
401 ",
402 )
403 }
404}
diff --git a/crates/ide_assists/src/handlers/replace_if_let_with_match.rs b/crates/ide_assists/src/handlers/replace_if_let_with_match.rs
new file mode 100644
index 000000000..aee880625
--- /dev/null
+++ b/crates/ide_assists/src/handlers/replace_if_let_with_match.rs
@@ -0,0 +1,599 @@
1use std::iter;
2
3use ide_db::{ty_filter::TryEnum, RootDatabase};
4use syntax::{
5 ast::{
6 self,
7 edit::{AstNodeEdit, IndentLevel},
8 make,
9 },
10 AstNode,
11};
12
13use crate::{
14 utils::{does_pat_match_variant, unwrap_trivial_block},
15 AssistContext, AssistId, AssistKind, Assists,
16};
17
18// Assist: replace_if_let_with_match
19//
20// Replaces `if let` with an else branch with a `match` expression.
21//
22// ```
23// enum Action { Move { distance: u32 }, Stop }
24//
25// fn handle(action: Action) {
26// $0if let Action::Move { distance } = action {
27// foo(distance)
28// } else {
29// bar()
30// }
31// }
32// ```
33// ->
34// ```
35// enum Action { Move { distance: u32 }, Stop }
36//
37// fn handle(action: Action) {
38// match action {
39// Action::Move { distance } => foo(distance),
40// _ => bar(),
41// }
42// }
43// ```
44pub(crate) fn replace_if_let_with_match(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
45 let if_expr: ast::IfExpr = ctx.find_node_at_offset()?;
46 let cond = if_expr.condition()?;
47 let pat = cond.pat()?;
48 let expr = cond.expr()?;
49 let then_block = if_expr.then_branch()?;
50 let else_block = match if_expr.else_branch()? {
51 ast::ElseBranch::Block(it) => it,
52 ast::ElseBranch::IfExpr(_) => return None,
53 };
54
55 let target = if_expr.syntax().text_range();
56 acc.add(
57 AssistId("replace_if_let_with_match", AssistKind::RefactorRewrite),
58 "Replace with match",
59 target,
60 move |edit| {
61 let match_expr = {
62 let then_arm = {
63 let then_block = then_block.reset_indent().indent(IndentLevel(1));
64 let then_expr = unwrap_trivial_block(then_block);
65 make::match_arm(vec![pat.clone()], then_expr)
66 };
67 let else_arm = {
68 let pattern = ctx
69 .sema
70 .type_of_pat(&pat)
71 .and_then(|ty| TryEnum::from_ty(&ctx.sema, &ty))
72 .map(|it| {
73 if does_pat_match_variant(&pat, &it.sad_pattern()) {
74 it.happy_pattern()
75 } else {
76 it.sad_pattern()
77 }
78 })
79 .unwrap_or_else(|| make::wildcard_pat().into());
80 let else_expr = unwrap_trivial_block(else_block);
81 make::match_arm(vec![pattern], else_expr)
82 };
83 let match_expr =
84 make::expr_match(expr, make::match_arm_list(vec![then_arm, else_arm]));
85 match_expr.indent(IndentLevel::from_node(if_expr.syntax()))
86 };
87
88 edit.replace_ast::<ast::Expr>(if_expr.into(), match_expr);
89 },
90 )
91}
92
93// Assist: replace_match_with_if_let
94//
95// Replaces a binary `match` with a wildcard pattern and no guards with an `if let` expression.
96//
97// ```
98// enum Action { Move { distance: u32 }, Stop }
99//
100// fn handle(action: Action) {
101// $0match action {
102// Action::Move { distance } => foo(distance),
103// _ => bar(),
104// }
105// }
106// ```
107// ->
108// ```
109// enum Action { Move { distance: u32 }, Stop }
110//
111// fn handle(action: Action) {
112// if let Action::Move { distance } = action {
113// foo(distance)
114// } else {
115// bar()
116// }
117// }
118// ```
119pub(crate) fn replace_match_with_if_let(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
120 let match_expr: ast::MatchExpr = ctx.find_node_at_offset()?;
121 let mut arms = match_expr.match_arm_list()?.arms();
122 let first_arm = arms.next()?;
123 let second_arm = arms.next()?;
124 if arms.next().is_some() || first_arm.guard().is_some() || second_arm.guard().is_some() {
125 return None;
126 }
127 let condition_expr = match_expr.expr()?;
128 let (if_let_pat, then_expr, else_expr) = if is_pat_wildcard_or_sad(&ctx.sema, &first_arm.pat()?)
129 {
130 (second_arm.pat()?, second_arm.expr()?, first_arm.expr()?)
131 } else if is_pat_wildcard_or_sad(&ctx.sema, &second_arm.pat()?) {
132 (first_arm.pat()?, first_arm.expr()?, second_arm.expr()?)
133 } else {
134 return None;
135 };
136
137 let target = match_expr.syntax().text_range();
138 acc.add(
139 AssistId("replace_match_with_if_let", AssistKind::RefactorRewrite),
140 "Replace with if let",
141 target,
142 move |edit| {
143 let condition = make::condition(condition_expr, Some(if_let_pat));
144 let then_block = match then_expr.reset_indent() {
145 ast::Expr::BlockExpr(block) => block,
146 expr => make::block_expr(iter::empty(), Some(expr)),
147 };
148 let else_expr = match else_expr {
149 ast::Expr::BlockExpr(block)
150 if block.statements().count() == 0 && block.tail_expr().is_none() =>
151 {
152 None
153 }
154 ast::Expr::TupleExpr(tuple) if tuple.fields().count() == 0 => None,
155 expr => Some(expr),
156 };
157 let if_let_expr = make::expr_if(
158 condition,
159 then_block,
160 else_expr.map(|else_expr| {
161 ast::ElseBranch::Block(make::block_expr(iter::empty(), Some(else_expr)))
162 }),
163 )
164 .indent(IndentLevel::from_node(match_expr.syntax()));
165
166 edit.replace_ast::<ast::Expr>(match_expr.into(), if_let_expr);
167 },
168 )
169}
170
171fn is_pat_wildcard_or_sad(sema: &hir::Semantics<RootDatabase>, pat: &ast::Pat) -> bool {
172 sema.type_of_pat(&pat)
173 .and_then(|ty| TryEnum::from_ty(sema, &ty))
174 .map(|it| it.sad_pattern().syntax().text() == pat.syntax().text())
175 .unwrap_or_else(|| matches!(pat, ast::Pat::WildcardPat(_)))
176}
177
178#[cfg(test)]
179mod tests {
180 use super::*;
181
182 use crate::tests::{check_assist, check_assist_target};
183
184 #[test]
185 fn test_replace_if_let_with_match_unwraps_simple_expressions() {
186 check_assist(
187 replace_if_let_with_match,
188 r#"
189impl VariantData {
190 pub fn is_struct(&self) -> bool {
191 if $0let VariantData::Struct(..) = *self {
192 true
193 } else {
194 false
195 }
196 }
197} "#,
198 r#"
199impl VariantData {
200 pub fn is_struct(&self) -> bool {
201 match *self {
202 VariantData::Struct(..) => true,
203 _ => false,
204 }
205 }
206} "#,
207 )
208 }
209
210 #[test]
211 fn test_replace_if_let_with_match_doesnt_unwrap_multiline_expressions() {
212 check_assist(
213 replace_if_let_with_match,
214 r#"
215fn foo() {
216 if $0let VariantData::Struct(..) = a {
217 bar(
218 123
219 )
220 } else {
221 false
222 }
223} "#,
224 r#"
225fn foo() {
226 match a {
227 VariantData::Struct(..) => {
228 bar(
229 123
230 )
231 }
232 _ => false,
233 }
234} "#,
235 )
236 }
237
238 #[test]
239 fn replace_if_let_with_match_target() {
240 check_assist_target(
241 replace_if_let_with_match,
242 r#"
243impl VariantData {
244 pub fn is_struct(&self) -> bool {
245 if $0let VariantData::Struct(..) = *self {
246 true
247 } else {
248 false
249 }
250 }
251} "#,
252 "if let VariantData::Struct(..) = *self {
253 true
254 } else {
255 false
256 }",
257 );
258 }
259
260 #[test]
261 fn special_case_option() {
262 check_assist(
263 replace_if_let_with_match,
264 r#"
265enum Option<T> { Some(T), None }
266use Option::*;
267
268fn foo(x: Option<i32>) {
269 $0if let Some(x) = x {
270 println!("{}", x)
271 } else {
272 println!("none")
273 }
274}
275 "#,
276 r#"
277enum Option<T> { Some(T), None }
278use Option::*;
279
280fn foo(x: Option<i32>) {
281 match x {
282 Some(x) => println!("{}", x),
283 None => println!("none"),
284 }
285}
286 "#,
287 );
288 }
289
290 #[test]
291 fn special_case_inverted_option() {
292 check_assist(
293 replace_if_let_with_match,
294 r#"
295enum Option<T> { Some(T), None }
296use Option::*;
297
298fn foo(x: Option<i32>) {
299 $0if let None = x {
300 println!("none")
301 } else {
302 println!("some")
303 }
304}
305 "#,
306 r#"
307enum Option<T> { Some(T), None }
308use Option::*;
309
310fn foo(x: Option<i32>) {
311 match x {
312 None => println!("none"),
313 Some(_) => println!("some"),
314 }
315}
316 "#,
317 );
318 }
319
320 #[test]
321 fn special_case_result() {
322 check_assist(
323 replace_if_let_with_match,
324 r#"
325enum Result<T, E> { Ok(T), Err(E) }
326use Result::*;
327
328fn foo(x: Result<i32, ()>) {
329 $0if let Ok(x) = x {
330 println!("{}", x)
331 } else {
332 println!("none")
333 }
334}
335 "#,
336 r#"
337enum Result<T, E> { Ok(T), Err(E) }
338use Result::*;
339
340fn foo(x: Result<i32, ()>) {
341 match x {
342 Ok(x) => println!("{}", x),
343 Err(_) => println!("none"),
344 }
345}
346 "#,
347 );
348 }
349
350 #[test]
351 fn special_case_inverted_result() {
352 check_assist(
353 replace_if_let_with_match,
354 r#"
355enum Result<T, E> { Ok(T), Err(E) }
356use Result::*;
357
358fn foo(x: Result<i32, ()>) {
359 $0if let Err(x) = x {
360 println!("{}", x)
361 } else {
362 println!("ok")
363 }
364}
365 "#,
366 r#"
367enum Result<T, E> { Ok(T), Err(E) }
368use Result::*;
369
370fn foo(x: Result<i32, ()>) {
371 match x {
372 Err(x) => println!("{}", x),
373 Ok(_) => println!("ok"),
374 }
375}
376 "#,
377 );
378 }
379
380 #[test]
381 fn nested_indent() {
382 check_assist(
383 replace_if_let_with_match,
384 r#"
385fn main() {
386 if true {
387 $0if let Ok(rel_path) = path.strip_prefix(root_path) {
388 let rel_path = RelativePathBuf::from_path(rel_path).ok()?;
389 Some((*id, rel_path))
390 } else {
391 None
392 }
393 }
394}
395"#,
396 r#"
397fn main() {
398 if true {
399 match path.strip_prefix(root_path) {
400 Ok(rel_path) => {
401 let rel_path = RelativePathBuf::from_path(rel_path).ok()?;
402 Some((*id, rel_path))
403 }
404 _ => None,
405 }
406 }
407}
408"#,
409 )
410 }
411
412 #[test]
413 fn test_replace_match_with_if_let_unwraps_simple_expressions() {
414 check_assist(
415 replace_match_with_if_let,
416 r#"
417impl VariantData {
418 pub fn is_struct(&self) -> bool {
419 $0match *self {
420 VariantData::Struct(..) => true,
421 _ => false,
422 }
423 }
424} "#,
425 r#"
426impl VariantData {
427 pub fn is_struct(&self) -> bool {
428 if let VariantData::Struct(..) = *self {
429 true
430 } else {
431 false
432 }
433 }
434} "#,
435 )
436 }
437
438 #[test]
439 fn test_replace_match_with_if_let_doesnt_unwrap_multiline_expressions() {
440 check_assist(
441 replace_match_with_if_let,
442 r#"
443fn foo() {
444 $0match a {
445 VariantData::Struct(..) => {
446 bar(
447 123
448 )
449 }
450 _ => false,
451 }
452} "#,
453 r#"
454fn foo() {
455 if let VariantData::Struct(..) = a {
456 bar(
457 123
458 )
459 } else {
460 false
461 }
462} "#,
463 )
464 }
465
466 #[test]
467 fn replace_match_with_if_let_target() {
468 check_assist_target(
469 replace_match_with_if_let,
470 r#"
471impl VariantData {
472 pub fn is_struct(&self) -> bool {
473 $0match *self {
474 VariantData::Struct(..) => true,
475 _ => false,
476 }
477 }
478} "#,
479 r#"match *self {
480 VariantData::Struct(..) => true,
481 _ => false,
482 }"#,
483 );
484 }
485
486 #[test]
487 fn special_case_option_match_to_if_let() {
488 check_assist(
489 replace_match_with_if_let,
490 r#"
491enum Option<T> { Some(T), None }
492use Option::*;
493
494fn foo(x: Option<i32>) {
495 $0match x {
496 Some(x) => println!("{}", x),
497 None => println!("none"),
498 }
499}
500 "#,
501 r#"
502enum Option<T> { Some(T), None }
503use Option::*;
504
505fn foo(x: Option<i32>) {
506 if let Some(x) = x {
507 println!("{}", x)
508 } else {
509 println!("none")
510 }
511}
512 "#,
513 );
514 }
515
516 #[test]
517 fn special_case_result_match_to_if_let() {
518 check_assist(
519 replace_match_with_if_let,
520 r#"
521enum Result<T, E> { Ok(T), Err(E) }
522use Result::*;
523
524fn foo(x: Result<i32, ()>) {
525 $0match x {
526 Ok(x) => println!("{}", x),
527 Err(_) => println!("none"),
528 }
529}
530 "#,
531 r#"
532enum Result<T, E> { Ok(T), Err(E) }
533use Result::*;
534
535fn foo(x: Result<i32, ()>) {
536 if let Ok(x) = x {
537 println!("{}", x)
538 } else {
539 println!("none")
540 }
541}
542 "#,
543 );
544 }
545
546 #[test]
547 fn nested_indent_match_to_if_let() {
548 check_assist(
549 replace_match_with_if_let,
550 r#"
551fn main() {
552 if true {
553 $0match path.strip_prefix(root_path) {
554 Ok(rel_path) => {
555 let rel_path = RelativePathBuf::from_path(rel_path).ok()?;
556 Some((*id, rel_path))
557 }
558 _ => None,
559 }
560 }
561}
562"#,
563 r#"
564fn main() {
565 if true {
566 if let Ok(rel_path) = path.strip_prefix(root_path) {
567 let rel_path = RelativePathBuf::from_path(rel_path).ok()?;
568 Some((*id, rel_path))
569 } else {
570 None
571 }
572 }
573}
574"#,
575 )
576 }
577
578 #[test]
579 fn replace_match_with_if_let_empty_wildcard_expr() {
580 check_assist(
581 replace_match_with_if_let,
582 r#"
583fn main() {
584 $0match path.strip_prefix(root_path) {
585 Ok(rel_path) => println!("{}", rel_path),
586 _ => (),
587 }
588}
589"#,
590 r#"
591fn main() {
592 if let Ok(rel_path) = path.strip_prefix(root_path) {
593 println!("{}", rel_path)
594 }
595}
596"#,
597 )
598 }
599}
diff --git a/crates/ide_assists/src/handlers/replace_impl_trait_with_generic.rs b/crates/ide_assists/src/handlers/replace_impl_trait_with_generic.rs
new file mode 100644
index 000000000..ff25b61ea
--- /dev/null
+++ b/crates/ide_assists/src/handlers/replace_impl_trait_with_generic.rs
@@ -0,0 +1,168 @@
1use syntax::ast::{self, edit::AstNodeEdit, make, AstNode, GenericParamsOwner};
2
3use crate::{AssistContext, AssistId, AssistKind, Assists};
4
5// Assist: replace_impl_trait_with_generic
6//
7// Replaces `impl Trait` function argument with the named generic.
8//
9// ```
10// fn foo(bar: $0impl Bar) {}
11// ```
12// ->
13// ```
14// fn foo<B: Bar>(bar: B) {}
15// ```
16pub(crate) fn replace_impl_trait_with_generic(
17 acc: &mut Assists,
18 ctx: &AssistContext,
19) -> Option<()> {
20 let type_impl_trait = ctx.find_node_at_offset::<ast::ImplTraitType>()?;
21 let type_param = type_impl_trait.syntax().parent().and_then(ast::Param::cast)?;
22 let type_fn = type_param.syntax().ancestors().find_map(ast::Fn::cast)?;
23
24 let impl_trait_ty = type_impl_trait.type_bound_list()?;
25
26 let target = type_fn.syntax().text_range();
27 acc.add(
28 AssistId("replace_impl_trait_with_generic", AssistKind::RefactorRewrite),
29 "Replace impl trait with generic",
30 target,
31 |edit| {
32 let generic_letter = impl_trait_ty.to_string().chars().next().unwrap().to_string();
33
34 let generic_param_list = type_fn
35 .generic_param_list()
36 .unwrap_or_else(|| make::generic_param_list(None))
37 .append_param(make::generic_param(generic_letter.clone(), Some(impl_trait_ty)));
38
39 let new_type_fn = type_fn
40 .replace_descendant::<ast::Type>(type_impl_trait.into(), make::ty(&generic_letter))
41 .with_generic_param_list(generic_param_list);
42
43 edit.replace_ast(type_fn.clone(), new_type_fn);
44 },
45 )
46}
47
48#[cfg(test)]
49mod tests {
50 use super::*;
51
52 use crate::tests::check_assist;
53
54 #[test]
55 fn replace_impl_trait_with_generic_params() {
56 check_assist(
57 replace_impl_trait_with_generic,
58 r#"
59 fn foo<G>(bar: $0impl Bar) {}
60 "#,
61 r#"
62 fn foo<G, B: Bar>(bar: B) {}
63 "#,
64 );
65 }
66
67 #[test]
68 fn replace_impl_trait_without_generic_params() {
69 check_assist(
70 replace_impl_trait_with_generic,
71 r#"
72 fn foo(bar: $0impl Bar) {}
73 "#,
74 r#"
75 fn foo<B: Bar>(bar: B) {}
76 "#,
77 );
78 }
79
80 #[test]
81 fn replace_two_impl_trait_with_generic_params() {
82 check_assist(
83 replace_impl_trait_with_generic,
84 r#"
85 fn foo<G>(foo: impl Foo, bar: $0impl Bar) {}
86 "#,
87 r#"
88 fn foo<G, B: Bar>(foo: impl Foo, bar: B) {}
89 "#,
90 );
91 }
92
93 #[test]
94 fn replace_impl_trait_with_empty_generic_params() {
95 check_assist(
96 replace_impl_trait_with_generic,
97 r#"
98 fn foo<>(bar: $0impl Bar) {}
99 "#,
100 r#"
101 fn foo<B: Bar>(bar: B) {}
102 "#,
103 );
104 }
105
106 #[test]
107 fn replace_impl_trait_with_empty_multiline_generic_params() {
108 check_assist(
109 replace_impl_trait_with_generic,
110 r#"
111 fn foo<
112 >(bar: $0impl Bar) {}
113 "#,
114 r#"
115 fn foo<B: Bar
116 >(bar: B) {}
117 "#,
118 );
119 }
120
121 #[test]
122 #[ignore = "This case is very rare but there is no simple solutions to fix it."]
123 fn replace_impl_trait_with_exist_generic_letter() {
124 check_assist(
125 replace_impl_trait_with_generic,
126 r#"
127 fn foo<B>(bar: $0impl Bar) {}
128 "#,
129 r#"
130 fn foo<B, C: Bar>(bar: C) {}
131 "#,
132 );
133 }
134
135 #[test]
136 fn replace_impl_trait_with_multiline_generic_params() {
137 check_assist(
138 replace_impl_trait_with_generic,
139 r#"
140 fn foo<
141 G: Foo,
142 F,
143 H,
144 >(bar: $0impl Bar) {}
145 "#,
146 r#"
147 fn foo<
148 G: Foo,
149 F,
150 H, B: Bar
151 >(bar: B) {}
152 "#,
153 );
154 }
155
156 #[test]
157 fn replace_impl_trait_multiple() {
158 check_assist(
159 replace_impl_trait_with_generic,
160 r#"
161 fn foo(bar: $0impl Foo + Bar) {}
162 "#,
163 r#"
164 fn foo<F: Foo + Bar>(bar: F) {}
165 "#,
166 );
167 }
168}
diff --git a/crates/ide_assists/src/handlers/replace_let_with_if_let.rs b/crates/ide_assists/src/handlers/replace_let_with_if_let.rs
new file mode 100644
index 000000000..5a27ada6b
--- /dev/null
+++ b/crates/ide_assists/src/handlers/replace_let_with_if_let.rs
@@ -0,0 +1,101 @@
1use std::iter::once;
2
3use syntax::{
4 ast::{
5 self,
6 edit::{AstNodeEdit, IndentLevel},
7 make,
8 },
9 AstNode, T,
10};
11
12use crate::{AssistContext, AssistId, AssistKind, Assists};
13use ide_db::ty_filter::TryEnum;
14
15// Assist: replace_let_with_if_let
16//
17// Replaces `let` with an `if-let`.
18//
19// ```
20// # enum Option<T> { Some(T), None }
21//
22// fn main(action: Action) {
23// $0let x = compute();
24// }
25//
26// fn compute() -> Option<i32> { None }
27// ```
28// ->
29// ```
30// # enum Option<T> { Some(T), None }
31//
32// fn main(action: Action) {
33// if let Some(x) = compute() {
34// }
35// }
36//
37// fn compute() -> Option<i32> { None }
38// ```
39pub(crate) fn replace_let_with_if_let(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
40 let let_kw = ctx.find_token_syntax_at_offset(T![let])?;
41 let let_stmt = let_kw.ancestors().find_map(ast::LetStmt::cast)?;
42 let init = let_stmt.initializer()?;
43 let original_pat = let_stmt.pat()?;
44 let ty = ctx.sema.type_of_expr(&init)?;
45 let happy_variant = TryEnum::from_ty(&ctx.sema, &ty).map(|it| it.happy_case());
46
47 let target = let_kw.text_range();
48 acc.add(
49 AssistId("replace_let_with_if_let", AssistKind::RefactorRewrite),
50 "Replace with if-let",
51 target,
52 |edit| {
53 let with_placeholder: ast::Pat = match happy_variant {
54 None => make::wildcard_pat().into(),
55 Some(var_name) => make::tuple_struct_pat(
56 make::path_unqualified(make::path_segment(make::name_ref(var_name))),
57 once(make::wildcard_pat().into()),
58 )
59 .into(),
60 };
61 let block =
62 make::block_expr(None, None).indent(IndentLevel::from_node(let_stmt.syntax()));
63 let if_ = make::expr_if(make::condition(init, Some(with_placeholder)), block, None);
64 let stmt = make::expr_stmt(if_);
65
66 let placeholder = stmt.syntax().descendants().find_map(ast::WildcardPat::cast).unwrap();
67 let stmt = stmt.replace_descendant(placeholder.into(), original_pat);
68
69 edit.replace_ast(ast::Stmt::from(let_stmt), ast::Stmt::from(stmt));
70 },
71 )
72}
73
74#[cfg(test)]
75mod tests {
76 use crate::tests::check_assist;
77
78 use super::*;
79
80 #[test]
81 fn replace_let_unknown_enum() {
82 check_assist(
83 replace_let_with_if_let,
84 r"
85enum E<T> { X(T), Y(T) }
86
87fn main() {
88 $0let x = E::X(92);
89}
90 ",
91 r"
92enum E<T> { X(T), Y(T) }
93
94fn main() {
95 if let x = E::X(92) {
96 }
97}
98 ",
99 )
100 }
101}
diff --git a/crates/ide_assists/src/handlers/replace_qualified_name_with_use.rs b/crates/ide_assists/src/handlers/replace_qualified_name_with_use.rs
new file mode 100644
index 000000000..f3bc6cf39
--- /dev/null
+++ b/crates/ide_assists/src/handlers/replace_qualified_name_with_use.rs
@@ -0,0 +1,678 @@
1use ide_db::helpers::insert_use::{insert_use, ImportScope};
2use syntax::{algo::SyntaxRewriter, ast, match_ast, AstNode, SyntaxNode};
3use test_utils::mark;
4
5use crate::{AssistContext, AssistId, AssistKind, Assists};
6
7// Assist: replace_qualified_name_with_use
8//
9// Adds a use statement for a given fully-qualified name.
10//
11// ```
12// fn process(map: std::collections::$0HashMap<String, String>) {}
13// ```
14// ->
15// ```
16// use std::collections::HashMap;
17//
18// fn process(map: HashMap<String, String>) {}
19// ```
20pub(crate) fn replace_qualified_name_with_use(
21 acc: &mut Assists,
22 ctx: &AssistContext,
23) -> Option<()> {
24 let path: ast::Path = ctx.find_node_at_offset()?;
25 // We don't want to mess with use statements
26 if path.syntax().ancestors().find_map(ast::Use::cast).is_some() {
27 return None;
28 }
29 if path.qualifier().is_none() {
30 mark::hit!(dont_import_trivial_paths);
31 return None;
32 }
33
34 let target = path.syntax().text_range();
35 let scope = ImportScope::find_insert_use_container(path.syntax(), &ctx.sema)?;
36 let syntax = scope.as_syntax_node();
37 acc.add(
38 AssistId("replace_qualified_name_with_use", AssistKind::RefactorRewrite),
39 "Replace qualified path with use",
40 target,
41 |builder| {
42 // Now that we've brought the name into scope, re-qualify all paths that could be
43 // affected (that is, all paths inside the node we added the `use` to).
44 let mut rewriter = SyntaxRewriter::default();
45 shorten_paths(&mut rewriter, syntax.clone(), &path);
46 if let Some(ref import_scope) = ImportScope::from(syntax.clone()) {
47 rewriter += insert_use(import_scope, path, ctx.config.insert_use.merge);
48 builder.rewrite(rewriter);
49 }
50 },
51 )
52}
53
54/// Adds replacements to `re` that shorten `path` in all descendants of `node`.
55fn shorten_paths(rewriter: &mut SyntaxRewriter<'static>, node: SyntaxNode, path: &ast::Path) {
56 for child in node.children() {
57 match_ast! {
58 match child {
59 // Don't modify `use` items, as this can break the `use` item when injecting a new
60 // import into the use tree.
61 ast::Use(_it) => continue,
62 // Don't descend into submodules, they don't have the same `use` items in scope.
63 ast::Module(_it) => continue,
64
65 ast::Path(p) => {
66 match maybe_replace_path(rewriter, p.clone(), path.clone()) {
67 Some(()) => {},
68 None => shorten_paths(rewriter, p.syntax().clone(), path),
69 }
70 },
71 _ => shorten_paths(rewriter, child, path),
72 }
73 }
74 }
75}
76
77fn maybe_replace_path(
78 rewriter: &mut SyntaxRewriter<'static>,
79 path: ast::Path,
80 target: ast::Path,
81) -> Option<()> {
82 if !path_eq(path.clone(), target) {
83 return None;
84 }
85
86 // Shorten `path`, leaving only its last segment.
87 if let Some(parent) = path.qualifier() {
88 rewriter.delete(parent.syntax());
89 }
90 if let Some(double_colon) = path.coloncolon_token() {
91 rewriter.delete(&double_colon);
92 }
93
94 Some(())
95}
96
97fn path_eq(lhs: ast::Path, rhs: ast::Path) -> bool {
98 let mut lhs_curr = lhs;
99 let mut rhs_curr = rhs;
100 loop {
101 match (lhs_curr.segment(), rhs_curr.segment()) {
102 (Some(lhs), Some(rhs)) if lhs.syntax().text() == rhs.syntax().text() => (),
103 _ => return false,
104 }
105
106 match (lhs_curr.qualifier(), rhs_curr.qualifier()) {
107 (Some(lhs), Some(rhs)) => {
108 lhs_curr = lhs;
109 rhs_curr = rhs;
110 }
111 (None, None) => return true,
112 _ => return false,
113 }
114 }
115}
116
117#[cfg(test)]
118mod tests {
119 use crate::tests::{check_assist, check_assist_not_applicable};
120
121 use super::*;
122
123 #[test]
124 fn test_replace_already_imported() {
125 check_assist(
126 replace_qualified_name_with_use,
127 r"use std::fs;
128
129fn main() {
130 std::f$0s::Path
131}",
132 r"use std::fs;
133
134fn main() {
135 fs::Path
136}",
137 )
138 }
139
140 #[test]
141 fn test_replace_add_use_no_anchor() {
142 check_assist(
143 replace_qualified_name_with_use,
144 r"
145std::fmt::Debug$0
146 ",
147 r"
148use std::fmt::Debug;
149
150Debug
151 ",
152 );
153 }
154 #[test]
155 fn test_replace_add_use_no_anchor_with_item_below() {
156 check_assist(
157 replace_qualified_name_with_use,
158 r"
159std::fmt::Debug$0
160
161fn main() {
162}
163 ",
164 r"
165use std::fmt::Debug;
166
167Debug
168
169fn main() {
170}
171 ",
172 );
173 }
174
175 #[test]
176 fn test_replace_add_use_no_anchor_with_item_above() {
177 check_assist(
178 replace_qualified_name_with_use,
179 r"
180fn main() {
181}
182
183std::fmt::Debug$0
184 ",
185 r"
186use std::fmt::Debug;
187
188fn main() {
189}
190
191Debug
192 ",
193 );
194 }
195
196 #[test]
197 fn test_replace_add_use_no_anchor_2seg() {
198 check_assist(
199 replace_qualified_name_with_use,
200 r"
201std::fmt$0::Debug
202 ",
203 r"
204use std::fmt;
205
206fmt::Debug
207 ",
208 );
209 }
210
211 #[test]
212 fn test_replace_add_use() {
213 check_assist(
214 replace_qualified_name_with_use,
215 r"
216use stdx;
217
218impl std::fmt::Debug$0 for Foo {
219}
220 ",
221 r"
222use std::fmt::Debug;
223
224use stdx;
225
226impl Debug for Foo {
227}
228 ",
229 );
230 }
231
232 #[test]
233 fn test_replace_file_use_other_anchor() {
234 check_assist(
235 replace_qualified_name_with_use,
236 r"
237impl std::fmt::Debug$0 for Foo {
238}
239 ",
240 r"
241use std::fmt::Debug;
242
243impl Debug for Foo {
244}
245 ",
246 );
247 }
248
249 #[test]
250 fn test_replace_add_use_other_anchor_indent() {
251 check_assist(
252 replace_qualified_name_with_use,
253 r"
254 impl std::fmt::Debug$0 for Foo {
255 }
256 ",
257 r"
258 use std::fmt::Debug;
259
260 impl Debug for Foo {
261 }
262 ",
263 );
264 }
265
266 #[test]
267 fn test_replace_split_different() {
268 check_assist(
269 replace_qualified_name_with_use,
270 r"
271use std::fmt;
272
273impl std::io$0 for Foo {
274}
275 ",
276 r"
277use std::{fmt, io};
278
279impl io for Foo {
280}
281 ",
282 );
283 }
284
285 #[test]
286 fn test_replace_split_self_for_use() {
287 check_assist(
288 replace_qualified_name_with_use,
289 r"
290use std::fmt;
291
292impl std::fmt::Debug$0 for Foo {
293}
294 ",
295 r"
296use std::fmt::{self, Debug};
297
298impl Debug for Foo {
299}
300 ",
301 );
302 }
303
304 #[test]
305 fn test_replace_split_self_for_target() {
306 check_assist(
307 replace_qualified_name_with_use,
308 r"
309use std::fmt::Debug;
310
311impl std::fmt$0 for Foo {
312}
313 ",
314 r"
315use std::fmt::{self, Debug};
316
317impl fmt for Foo {
318}
319 ",
320 );
321 }
322
323 #[test]
324 fn test_replace_add_to_nested_self_nested() {
325 check_assist(
326 replace_qualified_name_with_use,
327 r"
328use std::fmt::{Debug, nested::{Display}};
329
330impl std::fmt::nested$0 for Foo {
331}
332",
333 r"
334use std::fmt::{Debug, nested::{self, Display}};
335
336impl nested for Foo {
337}
338",
339 );
340 }
341
342 #[test]
343 fn test_replace_add_to_nested_self_already_included() {
344 check_assist(
345 replace_qualified_name_with_use,
346 r"
347use std::fmt::{Debug, nested::{self, Display}};
348
349impl std::fmt::nested$0 for Foo {
350}
351",
352 r"
353use std::fmt::{Debug, nested::{self, Display}};
354
355impl nested for Foo {
356}
357",
358 );
359 }
360
361 #[test]
362 fn test_replace_add_to_nested_nested() {
363 check_assist(
364 replace_qualified_name_with_use,
365 r"
366use std::fmt::{Debug, nested::{Display}};
367
368impl std::fmt::nested::Debug$0 for Foo {
369}
370",
371 r"
372use std::fmt::{Debug, nested::{Debug, Display}};
373
374impl Debug for Foo {
375}
376",
377 );
378 }
379
380 #[test]
381 fn test_replace_split_common_target_longer() {
382 check_assist(
383 replace_qualified_name_with_use,
384 r"
385use std::fmt::Debug;
386
387impl std::fmt::nested::Display$0 for Foo {
388}
389",
390 r"
391use std::fmt::{Debug, nested::Display};
392
393impl Display for Foo {
394}
395",
396 );
397 }
398
399 #[test]
400 fn test_replace_split_common_use_longer() {
401 check_assist(
402 replace_qualified_name_with_use,
403 r"
404use std::fmt::nested::Debug;
405
406impl std::fmt::Display$0 for Foo {
407}
408",
409 r"
410use std::fmt::{Display, nested::Debug};
411
412impl Display for Foo {
413}
414",
415 );
416 }
417
418 #[test]
419 fn test_replace_use_nested_import() {
420 check_assist(
421 replace_qualified_name_with_use,
422 r"
423use crate::{
424 ty::{Substs, Ty},
425 AssocItem,
426};
427
428fn foo() { crate::ty::lower$0::trait_env() }
429",
430 r"
431use crate::{AssocItem, ty::{Substs, Ty, lower}};
432
433fn foo() { lower::trait_env() }
434",
435 );
436 }
437
438 #[test]
439 fn test_replace_alias() {
440 check_assist(
441 replace_qualified_name_with_use,
442 r"
443use std::fmt as foo;
444
445impl foo::Debug$0 for Foo {
446}
447",
448 r"
449use std::fmt as foo;
450
451use foo::Debug;
452
453impl Debug for Foo {
454}
455",
456 );
457 }
458
459 #[test]
460 fn dont_import_trivial_paths() {
461 mark::check!(dont_import_trivial_paths);
462 check_assist_not_applicable(
463 replace_qualified_name_with_use,
464 r"
465impl foo$0 for Foo {
466}
467",
468 );
469 }
470
471 #[test]
472 fn test_replace_not_applicable_in_use() {
473 check_assist_not_applicable(
474 replace_qualified_name_with_use,
475 r"
476use std::fmt$0;
477",
478 );
479 }
480
481 #[test]
482 fn test_replace_add_use_no_anchor_in_mod_mod() {
483 check_assist(
484 replace_qualified_name_with_use,
485 r"
486mod foo {
487 mod bar {
488 std::fmt::Debug$0
489 }
490}
491 ",
492 r"
493mod foo {
494 mod bar {
495 use std::fmt::Debug;
496
497 Debug
498 }
499}
500 ",
501 );
502 }
503
504 #[test]
505 fn inserts_imports_after_inner_attributes() {
506 check_assist(
507 replace_qualified_name_with_use,
508 r"
509#![allow(dead_code)]
510
511fn main() {
512 std::fmt::Debug$0
513}
514 ",
515 r"
516#![allow(dead_code)]
517
518use std::fmt::Debug;
519
520fn main() {
521 Debug
522}
523 ",
524 );
525 }
526
527 #[test]
528 fn replaces_all_affected_paths() {
529 check_assist(
530 replace_qualified_name_with_use,
531 r"
532fn main() {
533 std::fmt::Debug$0;
534 let x: std::fmt::Debug = std::fmt::Debug;
535}
536 ",
537 r"
538use std::fmt::Debug;
539
540fn main() {
541 Debug;
542 let x: Debug = Debug;
543}
544 ",
545 );
546 }
547
548 #[test]
549 fn replaces_all_affected_paths_mod() {
550 check_assist(
551 replace_qualified_name_with_use,
552 r"
553mod m {
554 fn f() {
555 std::fmt::Debug$0;
556 let x: std::fmt::Debug = std::fmt::Debug;
557 }
558 fn g() {
559 std::fmt::Debug;
560 }
561}
562
563fn f() {
564 std::fmt::Debug;
565}
566 ",
567 r"
568mod m {
569 use std::fmt::Debug;
570
571 fn f() {
572 Debug;
573 let x: Debug = Debug;
574 }
575 fn g() {
576 Debug;
577 }
578}
579
580fn f() {
581 std::fmt::Debug;
582}
583 ",
584 );
585 }
586
587 #[test]
588 fn does_not_replace_in_submodules() {
589 check_assist(
590 replace_qualified_name_with_use,
591 r"
592fn main() {
593 std::fmt::Debug$0;
594}
595
596mod sub {
597 fn f() {
598 std::fmt::Debug;
599 }
600}
601 ",
602 r"
603use std::fmt::Debug;
604
605fn main() {
606 Debug;
607}
608
609mod sub {
610 fn f() {
611 std::fmt::Debug;
612 }
613}
614 ",
615 );
616 }
617
618 #[test]
619 fn does_not_replace_in_use() {
620 check_assist(
621 replace_qualified_name_with_use,
622 r"
623use std::fmt::Display;
624
625fn main() {
626 std::fmt$0;
627}
628 ",
629 r"
630use std::fmt::{self, Display};
631
632fn main() {
633 fmt;
634}
635 ",
636 );
637 }
638
639 #[test]
640 fn does_not_replace_pub_use() {
641 check_assist(
642 replace_qualified_name_with_use,
643 r"
644pub use std::fmt;
645
646impl std::io$0 for Foo {
647}
648 ",
649 r"
650pub use std::fmt;
651use std::io;
652
653impl io for Foo {
654}
655 ",
656 );
657 }
658
659 #[test]
660 fn does_not_replace_pub_crate_use() {
661 check_assist(
662 replace_qualified_name_with_use,
663 r"
664pub(crate) use std::fmt;
665
666impl std::io$0 for Foo {
667}
668 ",
669 r"
670pub(crate) use std::fmt;
671use std::io;
672
673impl io for Foo {
674}
675 ",
676 );
677 }
678}
diff --git a/crates/ide_assists/src/handlers/replace_string_with_char.rs b/crates/ide_assists/src/handlers/replace_string_with_char.rs
new file mode 100644
index 000000000..317318c24
--- /dev/null
+++ b/crates/ide_assists/src/handlers/replace_string_with_char.rs
@@ -0,0 +1,137 @@
1use syntax::{ast, AstToken, SyntaxKind::STRING};
2
3use crate::{AssistContext, AssistId, AssistKind, Assists};
4
5// Assist: replace_string_with_char
6//
7// Replace string with char.
8//
9// ```
10// fn main() {
11// find("{$0");
12// }
13// ```
14// ->
15// ```
16// fn main() {
17// find('{');
18// }
19// ```
20pub(crate) fn replace_string_with_char(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
21 let token = ctx.find_token_syntax_at_offset(STRING).and_then(ast::String::cast)?;
22 let value = token.value()?;
23 let target = token.syntax().text_range();
24
25 if value.chars().take(2).count() != 1 {
26 return None;
27 }
28
29 acc.add(
30 AssistId("replace_string_with_char", AssistKind::RefactorRewrite),
31 "Replace string with char",
32 target,
33 |edit| {
34 edit.replace(token.syntax().text_range(), format!("'{}'", value));
35 },
36 )
37}
38
39#[cfg(test)]
40mod tests {
41 use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target};
42
43 use super::*;
44
45 #[test]
46 fn replace_string_with_char_target() {
47 check_assist_target(
48 replace_string_with_char,
49 r#"
50 fn f() {
51 let s = "$0c";
52 }
53 "#,
54 r#""c""#,
55 );
56 }
57
58 #[test]
59 fn replace_string_with_char_assist() {
60 check_assist(
61 replace_string_with_char,
62 r#"
63 fn f() {
64 let s = "$0c";
65 }
66 "#,
67 r##"
68 fn f() {
69 let s = 'c';
70 }
71 "##,
72 )
73 }
74
75 #[test]
76 fn replace_string_with_char_assist_with_emoji() {
77 check_assist(
78 replace_string_with_char,
79 r#"
80 fn f() {
81 let s = "$0😀";
82 }
83 "#,
84 r##"
85 fn f() {
86 let s = '😀';
87 }
88 "##,
89 )
90 }
91
92 #[test]
93 fn replace_string_with_char_assist_not_applicable() {
94 check_assist_not_applicable(
95 replace_string_with_char,
96 r#"
97 fn f() {
98 let s = "$0test";
99 }
100 "#,
101 )
102 }
103
104 #[test]
105 fn replace_string_with_char_works_inside_macros() {
106 check_assist(
107 replace_string_with_char,
108 r#"
109 fn f() {
110 format!($0"x", 92)
111 }
112 "#,
113 r##"
114 fn f() {
115 format!('x', 92)
116 }
117 "##,
118 )
119 }
120
121 #[test]
122 fn replace_string_with_char_works_func_args() {
123 check_assist(
124 replace_string_with_char,
125 r#"
126 fn f() {
127 find($0"x");
128 }
129 "#,
130 r##"
131 fn f() {
132 find('x');
133 }
134 "##,
135 )
136 }
137}
diff --git a/crates/ide_assists/src/handlers/replace_unwrap_with_match.rs b/crates/ide_assists/src/handlers/replace_unwrap_with_match.rs
new file mode 100644
index 000000000..a986a6ae8
--- /dev/null
+++ b/crates/ide_assists/src/handlers/replace_unwrap_with_match.rs
@@ -0,0 +1,188 @@
1use std::iter;
2
3use syntax::{
4 ast::{
5 self,
6 edit::{AstNodeEdit, IndentLevel},
7 make,
8 },
9 AstNode,
10};
11
12use crate::{
13 utils::{render_snippet, Cursor},
14 AssistContext, AssistId, AssistKind, Assists,
15};
16use ide_db::ty_filter::TryEnum;
17
18// Assist: replace_unwrap_with_match
19//
20// Replaces `unwrap` a `match` expression. Works for Result and Option.
21//
22// ```
23// enum Result<T, E> { Ok(T), Err(E) }
24// fn main() {
25// let x: Result<i32, i32> = Result::Ok(92);
26// let y = x.$0unwrap();
27// }
28// ```
29// ->
30// ```
31// enum Result<T, E> { Ok(T), Err(E) }
32// fn main() {
33// let x: Result<i32, i32> = Result::Ok(92);
34// let y = match x {
35// Ok(a) => a,
36// $0_ => unreachable!(),
37// };
38// }
39// ```
40pub(crate) fn replace_unwrap_with_match(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
41 let method_call: ast::MethodCallExpr = ctx.find_node_at_offset()?;
42 let name = method_call.name_ref()?;
43 if name.text() != "unwrap" {
44 return None;
45 }
46 let caller = method_call.receiver()?;
47 let ty = ctx.sema.type_of_expr(&caller)?;
48 let happy_variant = TryEnum::from_ty(&ctx.sema, &ty)?.happy_case();
49 let target = method_call.syntax().text_range();
50 acc.add(
51 AssistId("replace_unwrap_with_match", AssistKind::RefactorRewrite),
52 "Replace unwrap with match",
53 target,
54 |builder| {
55 let ok_path = make::path_unqualified(make::path_segment(make::name_ref(happy_variant)));
56 let it = make::ident_pat(make::name("a")).into();
57 let ok_tuple = make::tuple_struct_pat(ok_path, iter::once(it)).into();
58
59 let bind_path = make::path_unqualified(make::path_segment(make::name_ref("a")));
60 let ok_arm = make::match_arm(iter::once(ok_tuple), make::expr_path(bind_path));
61
62 let unreachable_call = make::expr_unreachable();
63 let err_arm =
64 make::match_arm(iter::once(make::wildcard_pat().into()), unreachable_call);
65
66 let match_arm_list = make::match_arm_list(vec![ok_arm, err_arm]);
67 let match_expr = make::expr_match(caller.clone(), match_arm_list)
68 .indent(IndentLevel::from_node(method_call.syntax()));
69
70 let range = method_call.syntax().text_range();
71 match ctx.config.snippet_cap {
72 Some(cap) => {
73 let err_arm = match_expr
74 .syntax()
75 .descendants()
76 .filter_map(ast::MatchArm::cast)
77 .last()
78 .unwrap();
79 let snippet =
80 render_snippet(cap, match_expr.syntax(), Cursor::Before(err_arm.syntax()));
81 builder.replace_snippet(cap, range, snippet)
82 }
83 None => builder.replace(range, match_expr.to_string()),
84 }
85 },
86 )
87}
88
89#[cfg(test)]
90mod tests {
91 use crate::tests::{check_assist, check_assist_target};
92
93 use super::*;
94
95 #[test]
96 fn test_replace_result_unwrap_with_match() {
97 check_assist(
98 replace_unwrap_with_match,
99 r"
100enum Result<T, E> { Ok(T), Err(E) }
101fn i<T>(a: T) -> T { a }
102fn main() {
103 let x: Result<i32, i32> = Result::Ok(92);
104 let y = i(x).$0unwrap();
105}
106 ",
107 r"
108enum Result<T, E> { Ok(T), Err(E) }
109fn i<T>(a: T) -> T { a }
110fn main() {
111 let x: Result<i32, i32> = Result::Ok(92);
112 let y = match i(x) {
113 Ok(a) => a,
114 $0_ => unreachable!(),
115 };
116}
117 ",
118 )
119 }
120
121 #[test]
122 fn test_replace_option_unwrap_with_match() {
123 check_assist(
124 replace_unwrap_with_match,
125 r"
126enum Option<T> { Some(T), None }
127fn i<T>(a: T) -> T { a }
128fn main() {
129 let x = Option::Some(92);
130 let y = i(x).$0unwrap();
131}
132 ",
133 r"
134enum Option<T> { Some(T), None }
135fn i<T>(a: T) -> T { a }
136fn main() {
137 let x = Option::Some(92);
138 let y = match i(x) {
139 Some(a) => a,
140 $0_ => unreachable!(),
141 };
142}
143 ",
144 );
145 }
146
147 #[test]
148 fn test_replace_result_unwrap_with_match_chaining() {
149 check_assist(
150 replace_unwrap_with_match,
151 r"
152enum Result<T, E> { Ok(T), Err(E) }
153fn i<T>(a: T) -> T { a }
154fn main() {
155 let x: Result<i32, i32> = Result::Ok(92);
156 let y = i(x).$0unwrap().count_zeroes();
157}
158 ",
159 r"
160enum Result<T, E> { Ok(T), Err(E) }
161fn i<T>(a: T) -> T { a }
162fn main() {
163 let x: Result<i32, i32> = Result::Ok(92);
164 let y = match i(x) {
165 Ok(a) => a,
166 $0_ => unreachable!(),
167 }.count_zeroes();
168}
169 ",
170 )
171 }
172
173 #[test]
174 fn replace_unwrap_with_match_target() {
175 check_assist_target(
176 replace_unwrap_with_match,
177 r"
178enum Option<T> { Some(T), None }
179fn i<T>(a: T) -> T { a }
180fn main() {
181 let x = Option::Some(92);
182 let y = i(x).$0unwrap();
183}
184 ",
185 r"i(x).unwrap()",
186 );
187 }
188}
diff --git a/crates/ide_assists/src/handlers/split_import.rs b/crates/ide_assists/src/handlers/split_import.rs
new file mode 100644
index 000000000..9319a4267
--- /dev/null
+++ b/crates/ide_assists/src/handlers/split_import.rs
@@ -0,0 +1,79 @@
1use std::iter::successors;
2
3use syntax::{ast, AstNode, T};
4
5use crate::{AssistContext, AssistId, AssistKind, Assists};
6
7// Assist: split_import
8//
9// Wraps the tail of import into braces.
10//
11// ```
12// use std::$0collections::HashMap;
13// ```
14// ->
15// ```
16// use std::{collections::HashMap};
17// ```
18pub(crate) fn split_import(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
19 let colon_colon = ctx.find_token_syntax_at_offset(T![::])?;
20 let path = ast::Path::cast(colon_colon.parent())?.qualifier()?;
21 let top_path = successors(Some(path.clone()), |it| it.parent_path()).last()?;
22
23 let use_tree = top_path.syntax().ancestors().find_map(ast::UseTree::cast)?;
24
25 let new_tree = use_tree.split_prefix(&path);
26 if new_tree == use_tree {
27 return None;
28 }
29
30 let target = colon_colon.text_range();
31 acc.add(AssistId("split_import", AssistKind::RefactorRewrite), "Split import", target, |edit| {
32 edit.replace_ast(use_tree, new_tree);
33 })
34}
35
36#[cfg(test)]
37mod tests {
38 use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target};
39
40 use super::*;
41
42 #[test]
43 fn test_split_import() {
44 check_assist(
45 split_import,
46 "use crate::$0db::RootDatabase;",
47 "use crate::{db::RootDatabase};",
48 )
49 }
50
51 #[test]
52 fn split_import_works_with_trees() {
53 check_assist(
54 split_import,
55 "use crate:$0:db::{RootDatabase, FileSymbol}",
56 "use crate::{db::{RootDatabase, FileSymbol}}",
57 )
58 }
59
60 #[test]
61 fn split_import_target() {
62 check_assist_target(split_import, "use crate::$0db::{RootDatabase, FileSymbol}", "::");
63 }
64
65 #[test]
66 fn issue4044() {
67 check_assist_not_applicable(split_import, "use crate::$0:::self;")
68 }
69
70 #[test]
71 fn test_empty_use() {
72 check_assist_not_applicable(
73 split_import,
74 r"
75use std::$0
76fn main() {}",
77 );
78 }
79}
diff --git a/crates/ide_assists/src/handlers/toggle_ignore.rs b/crates/ide_assists/src/handlers/toggle_ignore.rs
new file mode 100644
index 000000000..33e12a7d0
--- /dev/null
+++ b/crates/ide_assists/src/handlers/toggle_ignore.rs
@@ -0,0 +1,98 @@
1use syntax::{
2 ast::{self, AttrsOwner},
3 AstNode, AstToken,
4};
5
6use crate::{utils::test_related_attribute, AssistContext, AssistId, AssistKind, Assists};
7
8// Assist: toggle_ignore
9//
10// Adds `#[ignore]` attribute to the test.
11//
12// ```
13// $0#[test]
14// fn arithmetics {
15// assert_eq!(2 + 2, 5);
16// }
17// ```
18// ->
19// ```
20// #[test]
21// #[ignore]
22// fn arithmetics {
23// assert_eq!(2 + 2, 5);
24// }
25// ```
26pub(crate) fn toggle_ignore(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
27 let attr: ast::Attr = ctx.find_node_at_offset()?;
28 let func = attr.syntax().parent().and_then(ast::Fn::cast)?;
29 let attr = test_related_attribute(&func)?;
30
31 match has_ignore_attribute(&func) {
32 None => acc.add(
33 AssistId("toggle_ignore", AssistKind::None),
34 "Ignore this test",
35 attr.syntax().text_range(),
36 |builder| builder.insert(attr.syntax().text_range().end(), &format!("\n#[ignore]")),
37 ),
38 Some(ignore_attr) => acc.add(
39 AssistId("toggle_ignore", AssistKind::None),
40 "Re-enable this test",
41 ignore_attr.syntax().text_range(),
42 |builder| {
43 builder.delete(ignore_attr.syntax().text_range());
44 let whitespace = ignore_attr
45 .syntax()
46 .next_sibling_or_token()
47 .and_then(|x| x.into_token())
48 .and_then(ast::Whitespace::cast);
49 if let Some(whitespace) = whitespace {
50 builder.delete(whitespace.syntax().text_range());
51 }
52 },
53 ),
54 }
55}
56
57fn has_ignore_attribute(fn_def: &ast::Fn) -> Option<ast::Attr> {
58 fn_def.attrs().find(|attr| attr.path().map(|it| it.syntax().text() == "ignore") == Some(true))
59}
60
61#[cfg(test)]
62mod tests {
63 use crate::tests::check_assist;
64
65 use super::*;
66
67 #[test]
68 fn test_base_case() {
69 check_assist(
70 toggle_ignore,
71 r#"
72 #[test$0]
73 fn test() {}
74 "#,
75 r#"
76 #[test]
77 #[ignore]
78 fn test() {}
79 "#,
80 )
81 }
82
83 #[test]
84 fn test_unignore() {
85 check_assist(
86 toggle_ignore,
87 r#"
88 #[test$0]
89 #[ignore]
90 fn test() {}
91 "#,
92 r#"
93 #[test]
94 fn test() {}
95 "#,
96 )
97 }
98}
diff --git a/crates/ide_assists/src/handlers/unmerge_use.rs b/crates/ide_assists/src/handlers/unmerge_use.rs
new file mode 100644
index 000000000..3dbef8e51
--- /dev/null
+++ b/crates/ide_assists/src/handlers/unmerge_use.rs
@@ -0,0 +1,231 @@
1use syntax::{
2 algo::SyntaxRewriter,
3 ast::{self, edit::AstNodeEdit, VisibilityOwner},
4 AstNode, SyntaxKind,
5};
6use test_utils::mark;
7
8use crate::{
9 assist_context::{AssistContext, Assists},
10 AssistId, AssistKind,
11};
12
13// Assist: unmerge_use
14//
15// Extracts single use item from use list.
16//
17// ```
18// use std::fmt::{Debug, Display$0};
19// ```
20// ->
21// ```
22// use std::fmt::{Debug};
23// use std::fmt::Display;
24// ```
25pub(crate) fn unmerge_use(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
26 let tree: ast::UseTree = ctx.find_node_at_offset()?;
27
28 let tree_list = tree.syntax().parent().and_then(ast::UseTreeList::cast)?;
29 if tree_list.use_trees().count() < 2 {
30 mark::hit!(skip_single_use_item);
31 return None;
32 }
33
34 let use_: ast::Use = tree_list.syntax().ancestors().find_map(ast::Use::cast)?;
35 let path = resolve_full_path(&tree)?;
36
37 let target = tree.syntax().text_range();
38 acc.add(
39 AssistId("unmerge_use", AssistKind::RefactorRewrite),
40 "Unmerge use",
41 target,
42 |builder| {
43 let new_use = ast::make::use_(
44 use_.visibility(),
45 ast::make::use_tree(
46 path,
47 tree.use_tree_list(),
48 tree.rename(),
49 tree.star_token().is_some(),
50 ),
51 );
52
53 let mut rewriter = SyntaxRewriter::default();
54 rewriter += tree.remove();
55 rewriter.insert_after(use_.syntax(), &ast::make::tokens::single_newline());
56 if let ident_level @ 1..=usize::MAX = use_.indent_level().0 as usize {
57 rewriter.insert_after(
58 use_.syntax(),
59 &ast::make::tokens::whitespace(&" ".repeat(4 * ident_level)),
60 );
61 }
62 rewriter.insert_after(use_.syntax(), new_use.syntax());
63
64 builder.rewrite(rewriter);
65 },
66 )
67}
68
69fn resolve_full_path(tree: &ast::UseTree) -> Option<ast::Path> {
70 let mut paths = tree
71 .syntax()
72 .ancestors()
73 .take_while(|n| n.kind() != SyntaxKind::USE_KW)
74 .filter_map(ast::UseTree::cast)
75 .filter_map(|t| t.path());
76
77 let mut final_path = paths.next()?;
78 for path in paths {
79 final_path = ast::make::path_concat(path, final_path)
80 }
81 Some(final_path)
82}
83
84#[cfg(test)]
85mod tests {
86 use crate::tests::{check_assist, check_assist_not_applicable};
87
88 use super::*;
89
90 #[test]
91 fn skip_single_use_item() {
92 mark::check!(skip_single_use_item);
93 check_assist_not_applicable(
94 unmerge_use,
95 r"
96use std::fmt::Debug$0;
97",
98 );
99 check_assist_not_applicable(
100 unmerge_use,
101 r"
102use std::fmt::{Debug$0};
103",
104 );
105 check_assist_not_applicable(
106 unmerge_use,
107 r"
108use std::fmt::Debug as Dbg$0;
109",
110 );
111 }
112
113 #[test]
114 fn skip_single_glob_import() {
115 check_assist_not_applicable(
116 unmerge_use,
117 r"
118use std::fmt::*$0;
119",
120 );
121 }
122
123 #[test]
124 fn unmerge_use_item() {
125 check_assist(
126 unmerge_use,
127 r"
128use std::fmt::{Debug, Display$0};
129",
130 r"
131use std::fmt::{Debug};
132use std::fmt::Display;
133",
134 );
135
136 check_assist(
137 unmerge_use,
138 r"
139use std::fmt::{Debug, format$0, Display};
140",
141 r"
142use std::fmt::{Debug, Display};
143use std::fmt::format;
144",
145 );
146 }
147
148 #[test]
149 fn unmerge_glob_import() {
150 check_assist(
151 unmerge_use,
152 r"
153use std::fmt::{*$0, Display};
154",
155 r"
156use std::fmt::{Display};
157use std::fmt::*;
158",
159 );
160 }
161
162 #[test]
163 fn unmerge_renamed_use_item() {
164 check_assist(
165 unmerge_use,
166 r"
167use std::fmt::{Debug, Display as Disp$0};
168",
169 r"
170use std::fmt::{Debug};
171use std::fmt::Display as Disp;
172",
173 );
174 }
175
176 #[test]
177 fn unmerge_indented_use_item() {
178 check_assist(
179 unmerge_use,
180 r"
181mod format {
182 use std::fmt::{Debug, Display$0 as Disp, format};
183}
184",
185 r"
186mod format {
187 use std::fmt::{Debug, format};
188 use std::fmt::Display as Disp;
189}
190",
191 );
192 }
193
194 #[test]
195 fn unmerge_nested_use_item() {
196 check_assist(
197 unmerge_use,
198 r"
199use foo::bar::{baz::{qux$0, foobar}, barbaz};
200",
201 r"
202use foo::bar::{baz::{foobar}, barbaz};
203use foo::bar::baz::qux;
204",
205 );
206 check_assist(
207 unmerge_use,
208 r"
209use foo::bar::{baz$0::{qux, foobar}, barbaz};
210",
211 r"
212use foo::bar::{barbaz};
213use foo::bar::baz::{qux, foobar};
214",
215 );
216 }
217
218 #[test]
219 fn unmerge_use_item_with_visibility() {
220 check_assist(
221 unmerge_use,
222 r"
223pub use std::fmt::{Debug, Display$0};
224",
225 r"
226pub use std::fmt::{Debug};
227pub use std::fmt::Display;
228",
229 );
230 }
231}
diff --git a/crates/ide_assists/src/handlers/unwrap_block.rs b/crates/ide_assists/src/handlers/unwrap_block.rs
new file mode 100644
index 000000000..ed6f6177d
--- /dev/null
+++ b/crates/ide_assists/src/handlers/unwrap_block.rs
@@ -0,0 +1,582 @@
1use syntax::{
2 ast::{
3 self,
4 edit::{AstNodeEdit, IndentLevel},
5 },
6 AstNode, SyntaxKind, TextRange, T,
7};
8
9use crate::{utils::unwrap_trivial_block, AssistContext, AssistId, AssistKind, Assists};
10
11// Assist: unwrap_block
12//
13// This assist removes if...else, for, while and loop control statements to just keep the body.
14//
15// ```
16// fn foo() {
17// if true {$0
18// println!("foo");
19// }
20// }
21// ```
22// ->
23// ```
24// fn foo() {
25// println!("foo");
26// }
27// ```
28pub(crate) fn unwrap_block(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
29 let assist_id = AssistId("unwrap_block", AssistKind::RefactorRewrite);
30 let assist_label = "Unwrap block";
31
32 let l_curly_token = ctx.find_token_syntax_at_offset(T!['{'])?;
33 let mut block = ast::BlockExpr::cast(l_curly_token.parent())?;
34 let target = block.syntax().text_range();
35 let mut parent = block.syntax().parent()?;
36 if ast::MatchArm::can_cast(parent.kind()) {
37 parent = parent.ancestors().find(|it| ast::MatchExpr::can_cast(it.kind()))?
38 }
39
40 if matches!(parent.kind(), SyntaxKind::BLOCK_EXPR | SyntaxKind::EXPR_STMT) {
41 return acc.add(assist_id, assist_label, target, |builder| {
42 builder.replace(
43 block.syntax().text_range(),
44 update_expr_string(block.to_string(), &[' ', '{', '\n']),
45 );
46 });
47 }
48
49 let parent = ast::Expr::cast(parent)?;
50
51 match parent.clone() {
52 ast::Expr::ForExpr(_) | ast::Expr::WhileExpr(_) | ast::Expr::LoopExpr(_) => (),
53 ast::Expr::MatchExpr(_) => block = block.dedent(IndentLevel(1)),
54 ast::Expr::IfExpr(if_expr) => {
55 let then_branch = if_expr.then_branch()?;
56 if then_branch == block {
57 if let Some(ancestor) = if_expr.syntax().parent().and_then(ast::IfExpr::cast) {
58 // For `else if` blocks
59 let ancestor_then_branch = ancestor.then_branch()?;
60
61 return acc.add(assist_id, assist_label, target, |edit| {
62 let range_to_del_else_if = TextRange::new(
63 ancestor_then_branch.syntax().text_range().end(),
64 l_curly_token.text_range().start(),
65 );
66 let range_to_del_rest = TextRange::new(
67 then_branch.syntax().text_range().end(),
68 if_expr.syntax().text_range().end(),
69 );
70
71 edit.delete(range_to_del_rest);
72 edit.delete(range_to_del_else_if);
73 edit.replace(
74 target,
75 update_expr_string(then_branch.to_string(), &[' ', '{']),
76 );
77 });
78 }
79 } else {
80 return acc.add(assist_id, assist_label, target, |edit| {
81 let range_to_del = TextRange::new(
82 then_branch.syntax().text_range().end(),
83 l_curly_token.text_range().start(),
84 );
85
86 edit.delete(range_to_del);
87 edit.replace(target, update_expr_string(block.to_string(), &[' ', '{']));
88 });
89 }
90 }
91 _ => return None,
92 };
93
94 let unwrapped = unwrap_trivial_block(block);
95 acc.add(assist_id, assist_label, target, |builder| {
96 builder.replace(
97 parent.syntax().text_range(),
98 update_expr_string(unwrapped.to_string(), &[' ', '{', '\n']),
99 );
100 })
101}
102
103fn update_expr_string(expr_str: String, trim_start_pat: &[char]) -> String {
104 let expr_string = expr_str.trim_start_matches(trim_start_pat);
105 let mut expr_string_lines: Vec<&str> = expr_string.lines().collect();
106 expr_string_lines.pop(); // Delete last line
107
108 expr_string_lines
109 .into_iter()
110 .map(|line| line.replacen(" ", "", 1)) // Delete indentation
111 .collect::<Vec<String>>()
112 .join("\n")
113}
114
115#[cfg(test)]
116mod tests {
117 use crate::tests::{check_assist, check_assist_not_applicable};
118
119 use super::*;
120
121 #[test]
122 fn unwrap_tail_expr_block() {
123 check_assist(
124 unwrap_block,
125 r#"
126fn main() {
127 $0{
128 92
129 }
130}
131"#,
132 r#"
133fn main() {
134 92
135}
136"#,
137 )
138 }
139
140 #[test]
141 fn unwrap_stmt_expr_block() {
142 check_assist(
143 unwrap_block,
144 r#"
145fn main() {
146 $0{
147 92;
148 }
149 ()
150}
151"#,
152 r#"
153fn main() {
154 92;
155 ()
156}
157"#,
158 );
159 // Pedantically, we should add an `;` here...
160 check_assist(
161 unwrap_block,
162 r#"
163fn main() {
164 $0{
165 92
166 }
167 ()
168}
169"#,
170 r#"
171fn main() {
172 92
173 ()
174}
175"#,
176 );
177 }
178
179 #[test]
180 fn simple_if() {
181 check_assist(
182 unwrap_block,
183 r#"
184fn main() {
185 bar();
186 if true {$0
187 foo();
188
189 //comment
190 bar();
191 } else {
192 println!("bar");
193 }
194}
195"#,
196 r#"
197fn main() {
198 bar();
199 foo();
200
201 //comment
202 bar();
203}
204"#,
205 );
206 }
207
208 #[test]
209 fn simple_if_else() {
210 check_assist(
211 unwrap_block,
212 r#"
213fn main() {
214 bar();
215 if true {
216 foo();
217
218 //comment
219 bar();
220 } else {$0
221 println!("bar");
222 }
223}
224"#,
225 r#"
226fn main() {
227 bar();
228 if true {
229 foo();
230
231 //comment
232 bar();
233 }
234 println!("bar");
235}
236"#,
237 );
238 }
239
240 #[test]
241 fn simple_if_else_if() {
242 check_assist(
243 unwrap_block,
244 r#"
245fn main() {
246 //bar();
247 if true {
248 println!("true");
249
250 //comment
251 //bar();
252 } else if false {$0
253 println!("bar");
254 } else {
255 println!("foo");
256 }
257}
258"#,
259 r#"
260fn main() {
261 //bar();
262 if true {
263 println!("true");
264
265 //comment
266 //bar();
267 }
268 println!("bar");
269}
270"#,
271 );
272 }
273
274 #[test]
275 fn simple_if_else_if_nested() {
276 check_assist(
277 unwrap_block,
278 r#"
279fn main() {
280 //bar();
281 if true {
282 println!("true");
283
284 //comment
285 //bar();
286 } else if false {
287 println!("bar");
288 } else if true {$0
289 println!("foo");
290 }
291}
292"#,
293 r#"
294fn main() {
295 //bar();
296 if true {
297 println!("true");
298
299 //comment
300 //bar();
301 } else if false {
302 println!("bar");
303 }
304 println!("foo");
305}
306"#,
307 );
308 }
309
310 #[test]
311 fn simple_if_else_if_nested_else() {
312 check_assist(
313 unwrap_block,
314 r#"
315fn main() {
316 //bar();
317 if true {
318 println!("true");
319
320 //comment
321 //bar();
322 } else if false {
323 println!("bar");
324 } else if true {
325 println!("foo");
326 } else {$0
327 println!("else");
328 }
329}
330"#,
331 r#"
332fn main() {
333 //bar();
334 if true {
335 println!("true");
336
337 //comment
338 //bar();
339 } else if false {
340 println!("bar");
341 } else if true {
342 println!("foo");
343 }
344 println!("else");
345}
346"#,
347 );
348 }
349
350 #[test]
351 fn simple_if_else_if_nested_middle() {
352 check_assist(
353 unwrap_block,
354 r#"
355fn main() {
356 //bar();
357 if true {
358 println!("true");
359
360 //comment
361 //bar();
362 } else if false {
363 println!("bar");
364 } else if true {$0
365 println!("foo");
366 } else {
367 println!("else");
368 }
369}
370"#,
371 r#"
372fn main() {
373 //bar();
374 if true {
375 println!("true");
376
377 //comment
378 //bar();
379 } else if false {
380 println!("bar");
381 }
382 println!("foo");
383}
384"#,
385 );
386 }
387
388 #[test]
389 fn simple_if_bad_cursor_position() {
390 check_assist_not_applicable(
391 unwrap_block,
392 r#"
393fn main() {
394 bar();$0
395 if true {
396 foo();
397
398 //comment
399 bar();
400 } else {
401 println!("bar");
402 }
403}
404"#,
405 );
406 }
407
408 #[test]
409 fn simple_for() {
410 check_assist(
411 unwrap_block,
412 r#"
413fn main() {
414 for i in 0..5 {$0
415 if true {
416 foo();
417
418 //comment
419 bar();
420 } else {
421 println!("bar");
422 }
423 }
424}
425"#,
426 r#"
427fn main() {
428 if true {
429 foo();
430
431 //comment
432 bar();
433 } else {
434 println!("bar");
435 }
436}
437"#,
438 );
439 }
440
441 #[test]
442 fn simple_if_in_for() {
443 check_assist(
444 unwrap_block,
445 r#"
446fn main() {
447 for i in 0..5 {
448 if true {$0
449 foo();
450
451 //comment
452 bar();
453 } else {
454 println!("bar");
455 }
456 }
457}
458"#,
459 r#"
460fn main() {
461 for i in 0..5 {
462 foo();
463
464 //comment
465 bar();
466 }
467}
468"#,
469 );
470 }
471
472 #[test]
473 fn simple_loop() {
474 check_assist(
475 unwrap_block,
476 r#"
477fn main() {
478 loop {$0
479 if true {
480 foo();
481
482 //comment
483 bar();
484 } else {
485 println!("bar");
486 }
487 }
488}
489"#,
490 r#"
491fn main() {
492 if true {
493 foo();
494
495 //comment
496 bar();
497 } else {
498 println!("bar");
499 }
500}
501"#,
502 );
503 }
504
505 #[test]
506 fn simple_while() {
507 check_assist(
508 unwrap_block,
509 r#"
510fn main() {
511 while true {$0
512 if true {
513 foo();
514
515 //comment
516 bar();
517 } else {
518 println!("bar");
519 }
520 }
521}
522"#,
523 r#"
524fn main() {
525 if true {
526 foo();
527
528 //comment
529 bar();
530 } else {
531 println!("bar");
532 }
533}
534"#,
535 );
536 }
537
538 #[test]
539 fn unwrap_match_arm() {
540 check_assist(
541 unwrap_block,
542 r#"
543fn main() {
544 match rel_path {
545 Ok(rel_path) => {$0
546 let rel_path = RelativePathBuf::from_path(rel_path).ok()?;
547 Some((*id, rel_path))
548 }
549 Err(_) => None,
550 }
551}
552"#,
553 r#"
554fn main() {
555 let rel_path = RelativePathBuf::from_path(rel_path).ok()?;
556 Some((*id, rel_path))
557}
558"#,
559 );
560 }
561
562 #[test]
563 fn simple_if_in_while_bad_cursor_position() {
564 check_assist_not_applicable(
565 unwrap_block,
566 r#"
567fn main() {
568 while true {
569 if true {
570 foo();$0
571
572 //comment
573 bar();
574 } else {
575 println!("bar");
576 }
577 }
578}
579"#,
580 );
581 }
582}
diff --git a/crates/ide_assists/src/handlers/wrap_return_type_in_result.rs b/crates/ide_assists/src/handlers/wrap_return_type_in_result.rs
new file mode 100644
index 000000000..fec16fc49
--- /dev/null
+++ b/crates/ide_assists/src/handlers/wrap_return_type_in_result.rs
@@ -0,0 +1,1158 @@
1use std::iter;
2
3use syntax::{
4 ast::{self, make, BlockExpr, Expr, LoopBodyOwner},
5 match_ast, AstNode, SyntaxNode,
6};
7use test_utils::mark;
8
9use crate::{AssistContext, AssistId, AssistKind, Assists};
10
11// Assist: wrap_return_type_in_result
12//
13// Wrap the function's return type into Result.
14//
15// ```
16// fn foo() -> i32$0 { 42i32 }
17// ```
18// ->
19// ```
20// fn foo() -> Result<i32, ${0:_}> { Ok(42i32) }
21// ```
22pub(crate) fn wrap_return_type_in_result(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
23 let ret_type = ctx.find_node_at_offset::<ast::RetType>()?;
24 let parent = ret_type.syntax().parent()?;
25 let block_expr = match_ast! {
26 match parent {
27 ast::Fn(func) => func.body()?,
28 ast::ClosureExpr(closure) => match closure.body()? {
29 Expr::BlockExpr(block) => block,
30 // closures require a block when a return type is specified
31 _ => return None,
32 },
33 _ => return None,
34 }
35 };
36
37 let type_ref = &ret_type.ty()?;
38 let ret_type_str = type_ref.syntax().text().to_string();
39 let first_part_ret_type = ret_type_str.splitn(2, '<').next();
40 if let Some(ret_type_first_part) = first_part_ret_type {
41 if ret_type_first_part.ends_with("Result") {
42 mark::hit!(wrap_return_type_in_result_simple_return_type_already_result);
43 return None;
44 }
45 }
46
47 acc.add(
48 AssistId("wrap_return_type_in_result", AssistKind::RefactorRewrite),
49 "Wrap return type in Result",
50 type_ref.syntax().text_range(),
51 |builder| {
52 let mut tail_return_expr_collector = TailReturnCollector::new();
53 tail_return_expr_collector.collect_jump_exprs(&block_expr, false);
54 tail_return_expr_collector.collect_tail_exprs(&block_expr);
55
56 for ret_expr_arg in tail_return_expr_collector.exprs_to_wrap {
57 let ok_wrapped = make::expr_call(
58 make::expr_path(make::path_unqualified(make::path_segment(make::name_ref(
59 "Ok",
60 )))),
61 make::arg_list(iter::once(ret_expr_arg.clone())),
62 );
63 builder.replace_ast(ret_expr_arg, ok_wrapped);
64 }
65
66 match ctx.config.snippet_cap {
67 Some(cap) => {
68 let snippet = format!("Result<{}, ${{0:_}}>", type_ref);
69 builder.replace_snippet(cap, type_ref.syntax().text_range(), snippet)
70 }
71 None => builder
72 .replace(type_ref.syntax().text_range(), format!("Result<{}, _>", type_ref)),
73 }
74 },
75 )
76}
77
78struct TailReturnCollector {
79 exprs_to_wrap: Vec<ast::Expr>,
80}
81
82impl TailReturnCollector {
83 fn new() -> Self {
84 Self { exprs_to_wrap: vec![] }
85 }
86 /// Collect all`return` expression
87 fn collect_jump_exprs(&mut self, block_expr: &BlockExpr, collect_break: bool) {
88 let statements = block_expr.statements();
89 for stmt in statements {
90 let expr = match &stmt {
91 ast::Stmt::ExprStmt(stmt) => stmt.expr(),
92 ast::Stmt::LetStmt(stmt) => stmt.initializer(),
93 ast::Stmt::Item(_) => continue,
94 };
95 if let Some(expr) = &expr {
96 self.handle_exprs(expr, collect_break);
97 }
98 }
99
100 // Browse tail expressions for each block
101 if let Some(expr) = block_expr.tail_expr() {
102 if let Some(last_exprs) = get_tail_expr_from_block(&expr) {
103 for last_expr in last_exprs {
104 let last_expr = match last_expr {
105 NodeType::Node(expr) => expr,
106 NodeType::Leaf(expr) => expr.syntax().clone(),
107 };
108
109 if let Some(last_expr) = Expr::cast(last_expr.clone()) {
110 self.handle_exprs(&last_expr, collect_break);
111 } else if let Some(expr_stmt) = ast::Stmt::cast(last_expr) {
112 let expr_stmt = match &expr_stmt {
113 ast::Stmt::ExprStmt(stmt) => stmt.expr(),
114 ast::Stmt::LetStmt(stmt) => stmt.initializer(),
115 ast::Stmt::Item(_) => None,
116 };
117 if let Some(expr) = &expr_stmt {
118 self.handle_exprs(expr, collect_break);
119 }
120 }
121 }
122 }
123 }
124 }
125
126 fn handle_exprs(&mut self, expr: &Expr, collect_break: bool) {
127 match expr {
128 Expr::BlockExpr(block_expr) => {
129 self.collect_jump_exprs(&block_expr, collect_break);
130 }
131 Expr::ReturnExpr(ret_expr) => {
132 if let Some(ret_expr_arg) = &ret_expr.expr() {
133 self.exprs_to_wrap.push(ret_expr_arg.clone());
134 }
135 }
136 Expr::BreakExpr(break_expr) if collect_break => {
137 if let Some(break_expr_arg) = &break_expr.expr() {
138 self.exprs_to_wrap.push(break_expr_arg.clone());
139 }
140 }
141 Expr::IfExpr(if_expr) => {
142 for block in if_expr.blocks() {
143 self.collect_jump_exprs(&block, collect_break);
144 }
145 }
146 Expr::LoopExpr(loop_expr) => {
147 if let Some(block_expr) = loop_expr.loop_body() {
148 self.collect_jump_exprs(&block_expr, collect_break);
149 }
150 }
151 Expr::ForExpr(for_expr) => {
152 if let Some(block_expr) = for_expr.loop_body() {
153 self.collect_jump_exprs(&block_expr, collect_break);
154 }
155 }
156 Expr::WhileExpr(while_expr) => {
157 if let Some(block_expr) = while_expr.loop_body() {
158 self.collect_jump_exprs(&block_expr, collect_break);
159 }
160 }
161 Expr::MatchExpr(match_expr) => {
162 if let Some(arm_list) = match_expr.match_arm_list() {
163 arm_list.arms().filter_map(|match_arm| match_arm.expr()).for_each(|expr| {
164 self.handle_exprs(&expr, collect_break);
165 });
166 }
167 }
168 _ => {}
169 }
170 }
171
172 fn collect_tail_exprs(&mut self, block: &BlockExpr) {
173 if let Some(expr) = block.tail_expr() {
174 self.handle_exprs(&expr, true);
175 self.fetch_tail_exprs(&expr);
176 }
177 }
178
179 fn fetch_tail_exprs(&mut self, expr: &Expr) {
180 if let Some(exprs) = get_tail_expr_from_block(expr) {
181 for node_type in &exprs {
182 match node_type {
183 NodeType::Leaf(expr) => {
184 self.exprs_to_wrap.push(expr.clone());
185 }
186 NodeType::Node(expr) => {
187 if let Some(last_expr) = Expr::cast(expr.clone()) {
188 self.fetch_tail_exprs(&last_expr);
189 }
190 }
191 }
192 }
193 }
194 }
195}
196
197#[derive(Debug)]
198enum NodeType {
199 Leaf(ast::Expr),
200 Node(SyntaxNode),
201}
202
203/// Get a tail expression inside a block
204fn get_tail_expr_from_block(expr: &Expr) -> Option<Vec<NodeType>> {
205 match expr {
206 Expr::IfExpr(if_expr) => {
207 let mut nodes = vec![];
208 for block in if_expr.blocks() {
209 if let Some(block_expr) = block.tail_expr() {
210 if let Some(tail_exprs) = get_tail_expr_from_block(&block_expr) {
211 nodes.extend(tail_exprs);
212 }
213 } else if let Some(last_expr) = block.syntax().last_child() {
214 nodes.push(NodeType::Node(last_expr));
215 } else {
216 nodes.push(NodeType::Node(block.syntax().clone()));
217 }
218 }
219 Some(nodes)
220 }
221 Expr::LoopExpr(loop_expr) => {
222 loop_expr.syntax().last_child().map(|lc| vec![NodeType::Node(lc)])
223 }
224 Expr::ForExpr(for_expr) => {
225 for_expr.syntax().last_child().map(|lc| vec![NodeType::Node(lc)])
226 }
227 Expr::WhileExpr(while_expr) => {
228 while_expr.syntax().last_child().map(|lc| vec![NodeType::Node(lc)])
229 }
230 Expr::BlockExpr(block_expr) => {
231 block_expr.tail_expr().map(|lc| vec![NodeType::Node(lc.syntax().clone())])
232 }
233 Expr::MatchExpr(match_expr) => {
234 let arm_list = match_expr.match_arm_list()?;
235 let arms: Vec<NodeType> = arm_list
236 .arms()
237 .filter_map(|match_arm| match_arm.expr())
238 .map(|expr| match expr {
239 Expr::ReturnExpr(ret_expr) => NodeType::Node(ret_expr.syntax().clone()),
240 Expr::BreakExpr(break_expr) => NodeType::Node(break_expr.syntax().clone()),
241 _ => match expr.syntax().last_child() {
242 Some(last_expr) => NodeType::Node(last_expr),
243 None => NodeType::Node(expr.syntax().clone()),
244 },
245 })
246 .collect();
247
248 Some(arms)
249 }
250 Expr::BreakExpr(expr) => expr.expr().map(|e| vec![NodeType::Leaf(e)]),
251 Expr::ReturnExpr(ret_expr) => Some(vec![NodeType::Node(ret_expr.syntax().clone())]),
252
253 Expr::CallExpr(_)
254 | Expr::Literal(_)
255 | Expr::TupleExpr(_)
256 | Expr::ArrayExpr(_)
257 | Expr::ParenExpr(_)
258 | Expr::PathExpr(_)
259 | Expr::RecordExpr(_)
260 | Expr::IndexExpr(_)
261 | Expr::MethodCallExpr(_)
262 | Expr::AwaitExpr(_)
263 | Expr::CastExpr(_)
264 | Expr::RefExpr(_)
265 | Expr::PrefixExpr(_)
266 | Expr::RangeExpr(_)
267 | Expr::BinExpr(_)
268 | Expr::MacroCall(_)
269 | Expr::BoxExpr(_) => Some(vec![NodeType::Leaf(expr.clone())]),
270 _ => None,
271 }
272}
273
274#[cfg(test)]
275mod tests {
276 use crate::tests::{check_assist, check_assist_not_applicable};
277
278 use super::*;
279
280 #[test]
281 fn wrap_return_type_in_result_simple() {
282 check_assist(
283 wrap_return_type_in_result,
284 r#"
285fn foo() -> i3$02 {
286 let test = "test";
287 return 42i32;
288}
289"#,
290 r#"
291fn foo() -> Result<i32, ${0:_}> {
292 let test = "test";
293 return Ok(42i32);
294}
295"#,
296 );
297 }
298
299 #[test]
300 fn wrap_return_type_in_result_simple_closure() {
301 check_assist(
302 wrap_return_type_in_result,
303 r#"
304fn foo() {
305 || -> i32$0 {
306 let test = "test";
307 return 42i32;
308 };
309}
310"#,
311 r#"
312fn foo() {
313 || -> Result<i32, ${0:_}> {
314 let test = "test";
315 return Ok(42i32);
316 };
317}
318"#,
319 );
320 }
321
322 #[test]
323 fn wrap_return_type_in_result_simple_return_type_bad_cursor() {
324 check_assist_not_applicable(
325 wrap_return_type_in_result,
326 r#"
327fn foo() -> i32 {
328 let test = "test";$0
329 return 42i32;
330}
331"#,
332 );
333 }
334
335 #[test]
336 fn wrap_return_type_in_result_simple_return_type_bad_cursor_closure() {
337 check_assist_not_applicable(
338 wrap_return_type_in_result,
339 r#"
340fn foo() {
341 || -> i32 {
342 let test = "test";$0
343 return 42i32;
344 };
345}
346"#,
347 );
348 }
349
350 #[test]
351 fn wrap_return_type_in_result_closure_non_block() {
352 check_assist_not_applicable(wrap_return_type_in_result, r#"fn foo() { || -> i$032 3; }"#);
353 }
354
355 #[test]
356 fn wrap_return_type_in_result_simple_return_type_already_result_std() {
357 check_assist_not_applicable(
358 wrap_return_type_in_result,
359 r#"
360fn foo() -> std::result::Result<i32$0, String> {
361 let test = "test";
362 return 42i32;
363}
364"#,
365 );
366 }
367
368 #[test]
369 fn wrap_return_type_in_result_simple_return_type_already_result() {
370 mark::check!(wrap_return_type_in_result_simple_return_type_already_result);
371 check_assist_not_applicable(
372 wrap_return_type_in_result,
373 r#"
374fn foo() -> Result<i32$0, String> {
375 let test = "test";
376 return 42i32;
377}
378"#,
379 );
380 }
381
382 #[test]
383 fn wrap_return_type_in_result_simple_return_type_already_result_closure() {
384 check_assist_not_applicable(
385 wrap_return_type_in_result,
386 r#"
387fn foo() {
388 || -> Result<i32$0, String> {
389 let test = "test";
390 return 42i32;
391 };
392}
393"#,
394 );
395 }
396
397 #[test]
398 fn wrap_return_type_in_result_simple_with_cursor() {
399 check_assist(
400 wrap_return_type_in_result,
401 r#"
402fn foo() -> $0i32 {
403 let test = "test";
404 return 42i32;
405}
406"#,
407 r#"
408fn foo() -> Result<i32, ${0:_}> {
409 let test = "test";
410 return Ok(42i32);
411}
412"#,
413 );
414 }
415
416 #[test]
417 fn wrap_return_type_in_result_simple_with_tail() {
418 check_assist(
419 wrap_return_type_in_result,
420 r#"
421fn foo() ->$0 i32 {
422 let test = "test";
423 42i32
424}
425"#,
426 r#"
427fn foo() -> Result<i32, ${0:_}> {
428 let test = "test";
429 Ok(42i32)
430}
431"#,
432 );
433 }
434
435 #[test]
436 fn wrap_return_type_in_result_simple_with_tail_closure() {
437 check_assist(
438 wrap_return_type_in_result,
439 r#"
440fn foo() {
441 || ->$0 i32 {
442 let test = "test";
443 42i32
444 };
445}
446"#,
447 r#"
448fn foo() {
449 || -> Result<i32, ${0:_}> {
450 let test = "test";
451 Ok(42i32)
452 };
453}
454"#,
455 );
456 }
457
458 #[test]
459 fn wrap_return_type_in_result_simple_with_tail_only() {
460 check_assist(
461 wrap_return_type_in_result,
462 r#"fn foo() -> i32$0 { 42i32 }"#,
463 r#"fn foo() -> Result<i32, ${0:_}> { Ok(42i32) }"#,
464 );
465 }
466
467 #[test]
468 fn wrap_return_type_in_result_simple_with_tail_block_like() {
469 check_assist(
470 wrap_return_type_in_result,
471 r#"
472fn foo() -> i32$0 {
473 if true {
474 42i32
475 } else {
476 24i32
477 }
478}
479"#,
480 r#"
481fn foo() -> Result<i32, ${0:_}> {
482 if true {
483 Ok(42i32)
484 } else {
485 Ok(24i32)
486 }
487}
488"#,
489 );
490 }
491
492 #[test]
493 fn wrap_return_type_in_result_simple_without_block_closure() {
494 check_assist(
495 wrap_return_type_in_result,
496 r#"
497fn foo() {
498 || -> i32$0 {
499 if true {
500 42i32
501 } else {
502 24i32
503 }
504 };
505}
506"#,
507 r#"
508fn foo() {
509 || -> Result<i32, ${0:_}> {
510 if true {
511 Ok(42i32)
512 } else {
513 Ok(24i32)
514 }
515 };
516}
517"#,
518 );
519 }
520
521 #[test]
522 fn wrap_return_type_in_result_simple_with_nested_if() {
523 check_assist(
524 wrap_return_type_in_result,
525 r#"
526fn foo() -> i32$0 {
527 if true {
528 if false {
529 1
530 } else {
531 2
532 }
533 } else {
534 24i32
535 }
536}
537"#,
538 r#"
539fn foo() -> Result<i32, ${0:_}> {
540 if true {
541 if false {
542 Ok(1)
543 } else {
544 Ok(2)
545 }
546 } else {
547 Ok(24i32)
548 }
549}
550"#,
551 );
552 }
553
554 #[test]
555 fn wrap_return_type_in_result_simple_with_await() {
556 check_assist(
557 wrap_return_type_in_result,
558 r#"
559async fn foo() -> i$032 {
560 if true {
561 if false {
562 1.await
563 } else {
564 2.await
565 }
566 } else {
567 24i32.await
568 }
569}
570"#,
571 r#"
572async fn foo() -> Result<i32, ${0:_}> {
573 if true {
574 if false {
575 Ok(1.await)
576 } else {
577 Ok(2.await)
578 }
579 } else {
580 Ok(24i32.await)
581 }
582}
583"#,
584 );
585 }
586
587 #[test]
588 fn wrap_return_type_in_result_simple_with_array() {
589 check_assist(
590 wrap_return_type_in_result,
591 r#"fn foo() -> [i32;$0 3] { [1, 2, 3] }"#,
592 r#"fn foo() -> Result<[i32; 3], ${0:_}> { Ok([1, 2, 3]) }"#,
593 );
594 }
595
596 #[test]
597 fn wrap_return_type_in_result_simple_with_cast() {
598 check_assist(
599 wrap_return_type_in_result,
600 r#"
601fn foo() -$0> i32 {
602 if true {
603 if false {
604 1 as i32
605 } else {
606 2 as i32
607 }
608 } else {
609 24 as i32
610 }
611}
612"#,
613 r#"
614fn foo() -> Result<i32, ${0:_}> {
615 if true {
616 if false {
617 Ok(1 as i32)
618 } else {
619 Ok(2 as i32)
620 }
621 } else {
622 Ok(24 as i32)
623 }
624}
625"#,
626 );
627 }
628
629 #[test]
630 fn wrap_return_type_in_result_simple_with_tail_block_like_match() {
631 check_assist(
632 wrap_return_type_in_result,
633 r#"
634fn foo() -> i32$0 {
635 let my_var = 5;
636 match my_var {
637 5 => 42i32,
638 _ => 24i32,
639 }
640}
641"#,
642 r#"
643fn foo() -> Result<i32, ${0:_}> {
644 let my_var = 5;
645 match my_var {
646 5 => Ok(42i32),
647 _ => Ok(24i32),
648 }
649}
650"#,
651 );
652 }
653
654 #[test]
655 fn wrap_return_type_in_result_simple_with_loop_with_tail() {
656 check_assist(
657 wrap_return_type_in_result,
658 r#"
659fn foo() -> i32$0 {
660 let my_var = 5;
661 loop {
662 println!("test");
663 5
664 }
665 my_var
666}
667"#,
668 r#"
669fn foo() -> Result<i32, ${0:_}> {
670 let my_var = 5;
671 loop {
672 println!("test");
673 5
674 }
675 Ok(my_var)
676}
677"#,
678 );
679 }
680
681 #[test]
682 fn wrap_return_type_in_result_simple_with_loop_in_let_stmt() {
683 check_assist(
684 wrap_return_type_in_result,
685 r#"
686fn foo() -> i32$0 {
687 let my_var = let x = loop {
688 break 1;
689 };
690 my_var
691}
692"#,
693 r#"
694fn foo() -> Result<i32, ${0:_}> {
695 let my_var = let x = loop {
696 break 1;
697 };
698 Ok(my_var)
699}
700"#,
701 );
702 }
703
704 #[test]
705 fn wrap_return_type_in_result_simple_with_tail_block_like_match_return_expr() {
706 check_assist(
707 wrap_return_type_in_result,
708 r#"
709fn foo() -> i32$0 {
710 let my_var = 5;
711 let res = match my_var {
712 5 => 42i32,
713 _ => return 24i32,
714 };
715 res
716}
717"#,
718 r#"
719fn foo() -> Result<i32, ${0:_}> {
720 let my_var = 5;
721 let res = match my_var {
722 5 => 42i32,
723 _ => return Ok(24i32),
724 };
725 Ok(res)
726}
727"#,
728 );
729
730 check_assist(
731 wrap_return_type_in_result,
732 r#"
733fn foo() -> i32$0 {
734 let my_var = 5;
735 let res = if my_var == 5 {
736 42i32
737 } else {
738 return 24i32;
739 };
740 res
741}
742"#,
743 r#"
744fn foo() -> Result<i32, ${0:_}> {
745 let my_var = 5;
746 let res = if my_var == 5 {
747 42i32
748 } else {
749 return Ok(24i32);
750 };
751 Ok(res)
752}
753"#,
754 );
755 }
756
757 #[test]
758 fn wrap_return_type_in_result_simple_with_tail_block_like_match_deeper() {
759 check_assist(
760 wrap_return_type_in_result,
761 r#"
762fn foo() -> i32$0 {
763 let my_var = 5;
764 match my_var {
765 5 => {
766 if true {
767 42i32
768 } else {
769 25i32
770 }
771 },
772 _ => {
773 let test = "test";
774 if test == "test" {
775 return bar();
776 }
777 53i32
778 },
779 }
780}
781"#,
782 r#"
783fn foo() -> Result<i32, ${0:_}> {
784 let my_var = 5;
785 match my_var {
786 5 => {
787 if true {
788 Ok(42i32)
789 } else {
790 Ok(25i32)
791 }
792 },
793 _ => {
794 let test = "test";
795 if test == "test" {
796 return Ok(bar());
797 }
798 Ok(53i32)
799 },
800 }
801}
802"#,
803 );
804 }
805
806 #[test]
807 fn wrap_return_type_in_result_simple_with_tail_block_like_early_return() {
808 check_assist(
809 wrap_return_type_in_result,
810 r#"
811fn foo() -> i$032 {
812 let test = "test";
813 if test == "test" {
814 return 24i32;
815 }
816 53i32
817}
818"#,
819 r#"
820fn foo() -> Result<i32, ${0:_}> {
821 let test = "test";
822 if test == "test" {
823 return Ok(24i32);
824 }
825 Ok(53i32)
826}
827"#,
828 );
829 }
830
831 #[test]
832 fn wrap_return_type_in_result_simple_with_closure() {
833 check_assist(
834 wrap_return_type_in_result,
835 r#"
836fn foo(the_field: u32) ->$0 u32 {
837 let true_closure = || { return true; };
838 if the_field < 5 {
839 let mut i = 0;
840 if true_closure() {
841 return 99;
842 } else {
843 return 0;
844 }
845 }
846 the_field
847}
848"#,
849 r#"
850fn foo(the_field: u32) -> Result<u32, ${0:_}> {
851 let true_closure = || { return true; };
852 if the_field < 5 {
853 let mut i = 0;
854 if true_closure() {
855 return Ok(99);
856 } else {
857 return Ok(0);
858 }
859 }
860 Ok(the_field)
861}
862"#,
863 );
864
865 check_assist(
866 wrap_return_type_in_result,
867 r#"
868 fn foo(the_field: u32) -> u32$0 {
869 let true_closure = || {
870 return true;
871 };
872 if the_field < 5 {
873 let mut i = 0;
874
875
876 if true_closure() {
877 return 99;
878 } else {
879 return 0;
880 }
881 }
882 let t = None;
883
884 t.unwrap_or_else(|| the_field)
885 }
886 "#,
887 r#"
888 fn foo(the_field: u32) -> Result<u32, ${0:_}> {
889 let true_closure = || {
890 return true;
891 };
892 if the_field < 5 {
893 let mut i = 0;
894
895
896 if true_closure() {
897 return Ok(99);
898 } else {
899 return Ok(0);
900 }
901 }
902 let t = None;
903
904 Ok(t.unwrap_or_else(|| the_field))
905 }
906 "#,
907 );
908 }
909
910 #[test]
911 fn wrap_return_type_in_result_simple_with_weird_forms() {
912 check_assist(
913 wrap_return_type_in_result,
914 r#"
915fn foo() -> i32$0 {
916 let test = "test";
917 if test == "test" {
918 return 24i32;
919 }
920 let mut i = 0;
921 loop {
922 if i == 1 {
923 break 55;
924 }
925 i += 1;
926 }
927}
928"#,
929 r#"
930fn foo() -> Result<i32, ${0:_}> {
931 let test = "test";
932 if test == "test" {
933 return Ok(24i32);
934 }
935 let mut i = 0;
936 loop {
937 if i == 1 {
938 break Ok(55);
939 }
940 i += 1;
941 }
942}
943"#,
944 );
945
946 check_assist(
947 wrap_return_type_in_result,
948 r#"
949fn foo() -> i32$0 {
950 let test = "test";
951 if test == "test" {
952 return 24i32;
953 }
954 let mut i = 0;
955 loop {
956 loop {
957 if i == 1 {
958 break 55;
959 }
960 i += 1;
961 }
962 }
963}
964"#,
965 r#"
966fn foo() -> Result<i32, ${0:_}> {
967 let test = "test";
968 if test == "test" {
969 return Ok(24i32);
970 }
971 let mut i = 0;
972 loop {
973 loop {
974 if i == 1 {
975 break Ok(55);
976 }
977 i += 1;
978 }
979 }
980}
981"#,
982 );
983
984 check_assist(
985 wrap_return_type_in_result,
986 r#"
987fn foo() -> i3$02 {
988 let test = "test";
989 let other = 5;
990 if test == "test" {
991 let res = match other {
992 5 => 43,
993 _ => return 56,
994 };
995 }
996 let mut i = 0;
997 loop {
998 loop {
999 if i == 1 {
1000 break 55;
1001 }
1002 i += 1;
1003 }
1004 }
1005}
1006"#,
1007 r#"
1008fn foo() -> Result<i32, ${0:_}> {
1009 let test = "test";
1010 let other = 5;
1011 if test == "test" {
1012 let res = match other {
1013 5 => 43,
1014 _ => return Ok(56),
1015 };
1016 }
1017 let mut i = 0;
1018 loop {
1019 loop {
1020 if i == 1 {
1021 break Ok(55);
1022 }
1023 i += 1;
1024 }
1025 }
1026}
1027"#,
1028 );
1029
1030 check_assist(
1031 wrap_return_type_in_result,
1032 r#"
1033fn foo(the_field: u32) -> u32$0 {
1034 if the_field < 5 {
1035 let mut i = 0;
1036 loop {
1037 if i > 5 {
1038 return 55u32;
1039 }
1040 i += 3;
1041 }
1042 match i {
1043 5 => return 99,
1044 _ => return 0,
1045 };
1046 }
1047 the_field
1048}
1049"#,
1050 r#"
1051fn foo(the_field: u32) -> Result<u32, ${0:_}> {
1052 if the_field < 5 {
1053 let mut i = 0;
1054 loop {
1055 if i > 5 {
1056 return Ok(55u32);
1057 }
1058 i += 3;
1059 }
1060 match i {
1061 5 => return Ok(99),
1062 _ => return Ok(0),
1063 };
1064 }
1065 Ok(the_field)
1066}
1067"#,
1068 );
1069
1070 check_assist(
1071 wrap_return_type_in_result,
1072 r#"
1073fn foo(the_field: u32) -> u3$02 {
1074 if the_field < 5 {
1075 let mut i = 0;
1076 match i {
1077 5 => return 99,
1078 _ => return 0,
1079 }
1080 }
1081 the_field
1082}
1083"#,
1084 r#"
1085fn foo(the_field: u32) -> Result<u32, ${0:_}> {
1086 if the_field < 5 {
1087 let mut i = 0;
1088 match i {
1089 5 => return Ok(99),
1090 _ => return Ok(0),
1091 }
1092 }
1093 Ok(the_field)
1094}
1095"#,
1096 );
1097
1098 check_assist(
1099 wrap_return_type_in_result,
1100 r#"
1101fn foo(the_field: u32) -> u32$0 {
1102 if the_field < 5 {
1103 let mut i = 0;
1104 if i == 5 {
1105 return 99
1106 } else {
1107 return 0
1108 }
1109 }
1110 the_field
1111}
1112"#,
1113 r#"
1114fn foo(the_field: u32) -> Result<u32, ${0:_}> {
1115 if the_field < 5 {
1116 let mut i = 0;
1117 if i == 5 {
1118 return Ok(99)
1119 } else {
1120 return Ok(0)
1121 }
1122 }
1123 Ok(the_field)
1124}
1125"#,
1126 );
1127
1128 check_assist(
1129 wrap_return_type_in_result,
1130 r#"
1131fn foo(the_field: u32) -> $0u32 {
1132 if the_field < 5 {
1133 let mut i = 0;
1134 if i == 5 {
1135 return 99;
1136 } else {
1137 return 0;
1138 }
1139 }
1140 the_field
1141}
1142"#,
1143 r#"
1144fn foo(the_field: u32) -> Result<u32, ${0:_}> {
1145 if the_field < 5 {
1146 let mut i = 0;
1147 if i == 5 {
1148 return Ok(99);
1149 } else {
1150 return Ok(0);
1151 }
1152 }
1153 Ok(the_field)
1154}
1155"#,
1156 );
1157 }
1158}
diff --git a/crates/ide_assists/src/lib.rs b/crates/ide_assists/src/lib.rs
new file mode 100644
index 000000000..7067cf8b6
--- /dev/null
+++ b/crates/ide_assists/src/lib.rs
@@ -0,0 +1,246 @@
1//! `assists` crate provides a bunch of code assists, also known as code
2//! actions (in LSP) or intentions (in IntelliJ).
3//!
4//! An assist is a micro-refactoring, which is automatically activated in
5//! certain context. For example, if the cursor is over `,`, a "swap `,`" assist
6//! becomes available.
7
8#[allow(unused)]
9macro_rules! eprintln {
10 ($($tt:tt)*) => { stdx::eprintln!($($tt)*) };
11}
12
13mod assist_config;
14mod assist_context;
15#[cfg(test)]
16mod tests;
17pub mod utils;
18pub mod ast_transform;
19
20use hir::Semantics;
21use ide_db::base_db::FileRange;
22use ide_db::{label::Label, source_change::SourceChange, RootDatabase};
23use syntax::TextRange;
24
25pub(crate) use crate::assist_context::{AssistContext, Assists};
26
27pub use assist_config::AssistConfig;
28
29#[derive(Debug, Clone, Copy, PartialEq, Eq)]
30pub enum AssistKind {
31 None,
32 QuickFix,
33 Generate,
34 Refactor,
35 RefactorExtract,
36 RefactorInline,
37 RefactorRewrite,
38}
39
40impl AssistKind {
41 pub fn contains(self, other: AssistKind) -> bool {
42 if self == other {
43 return true;
44 }
45
46 match self {
47 AssistKind::None | AssistKind::Generate => return true,
48 AssistKind::Refactor => match other {
49 AssistKind::RefactorExtract
50 | AssistKind::RefactorInline
51 | AssistKind::RefactorRewrite => return true,
52 _ => return false,
53 },
54 _ => return false,
55 }
56 }
57}
58
59/// Unique identifier of the assist, should not be shown to the user
60/// directly.
61#[derive(Debug, Clone, Copy, PartialEq, Eq)]
62pub struct AssistId(pub &'static str, pub AssistKind);
63
64#[derive(Clone, Debug)]
65pub struct GroupLabel(pub String);
66
67#[derive(Debug, Clone)]
68pub struct Assist {
69 pub id: AssistId,
70 /// Short description of the assist, as shown in the UI.
71 pub label: Label,
72 pub group: Option<GroupLabel>,
73 /// Target ranges are used to sort assists: the smaller the target range,
74 /// the more specific assist is, and so it should be sorted first.
75 pub target: TextRange,
76 /// Computing source change sometimes is much more costly then computing the
77 /// other fields. Additionally, the actual change is not required to show
78 /// the lightbulb UI, it only is needed when the user tries to apply an
79 /// assist. So, we compute it lazily: the API allow requesting assists with
80 /// or without source change. We could (and in fact, used to) distinguish
81 /// between resolved and unresolved assists at the type level, but this is
82 /// cumbersome, especially if you want to embed an assist into another data
83 /// structure, such as a diagnostic.
84 pub source_change: Option<SourceChange>,
85}
86
87impl Assist {
88 /// Return all the assists applicable at the given position.
89 pub fn get(
90 db: &RootDatabase,
91 config: &AssistConfig,
92 resolve: bool,
93 range: FileRange,
94 ) -> Vec<Assist> {
95 let sema = Semantics::new(db);
96 let ctx = AssistContext::new(sema, config, range);
97 let mut acc = Assists::new(&ctx, resolve);
98 handlers::all().iter().for_each(|handler| {
99 handler(&mut acc, &ctx);
100 });
101 acc.finish()
102 }
103}
104
105mod handlers {
106 use crate::{AssistContext, Assists};
107
108 pub(crate) type Handler = fn(&mut Assists, &AssistContext) -> Option<()>;
109
110 mod add_explicit_type;
111 mod add_lifetime_to_type;
112 mod add_missing_impl_members;
113 mod add_turbo_fish;
114 mod apply_demorgan;
115 mod auto_import;
116 mod change_visibility;
117 mod convert_integer_literal;
118 mod early_return;
119 mod expand_glob_import;
120 mod extract_function;
121 mod extract_struct_from_enum_variant;
122 mod extract_variable;
123 mod fill_match_arms;
124 mod fix_visibility;
125 mod flip_binexpr;
126 mod flip_comma;
127 mod flip_trait_bound;
128 mod generate_default_from_enum_variant;
129 mod generate_derive;
130 mod generate_enum_match_method;
131 mod generate_from_impl_for_enum;
132 mod generate_function;
133 mod generate_getter;
134 mod generate_getter_mut;
135 mod generate_impl;
136 mod generate_new;
137 mod generate_setter;
138 mod infer_function_return_type;
139 mod inline_function;
140 mod inline_local_variable;
141 mod introduce_named_lifetime;
142 mod invert_if;
143 mod merge_imports;
144 mod merge_match_arms;
145 mod move_bounds;
146 mod move_guard;
147 mod move_module_to_file;
148 mod pull_assignment_up;
149 mod qualify_path;
150 mod raw_string;
151 mod remove_dbg;
152 mod remove_mut;
153 mod remove_unused_param;
154 mod reorder_fields;
155 mod reorder_impl;
156 mod replace_derive_with_manual_impl;
157 mod replace_if_let_with_match;
158 mod replace_impl_trait_with_generic;
159 mod replace_let_with_if_let;
160 mod replace_qualified_name_with_use;
161 mod replace_string_with_char;
162 mod replace_unwrap_with_match;
163 mod split_import;
164 mod toggle_ignore;
165 mod unmerge_use;
166 mod unwrap_block;
167 mod wrap_return_type_in_result;
168
169 pub(crate) fn all() -> &'static [Handler] {
170 &[
171 // These are alphabetic for the foolish consistency
172 add_explicit_type::add_explicit_type,
173 add_lifetime_to_type::add_lifetime_to_type,
174 add_turbo_fish::add_turbo_fish,
175 apply_demorgan::apply_demorgan,
176 auto_import::auto_import,
177 change_visibility::change_visibility,
178 convert_integer_literal::convert_integer_literal,
179 early_return::convert_to_guarded_return,
180 expand_glob_import::expand_glob_import,
181 move_module_to_file::move_module_to_file,
182 extract_struct_from_enum_variant::extract_struct_from_enum_variant,
183 fill_match_arms::fill_match_arms,
184 fix_visibility::fix_visibility,
185 flip_binexpr::flip_binexpr,
186 flip_comma::flip_comma,
187 flip_trait_bound::flip_trait_bound,
188 generate_default_from_enum_variant::generate_default_from_enum_variant,
189 generate_derive::generate_derive,
190 generate_enum_match_method::generate_enum_match_method,
191 generate_from_impl_for_enum::generate_from_impl_for_enum,
192 generate_function::generate_function,
193 generate_getter::generate_getter,
194 generate_getter_mut::generate_getter_mut,
195 generate_impl::generate_impl,
196 generate_new::generate_new,
197 generate_setter::generate_setter,
198 infer_function_return_type::infer_function_return_type,
199 inline_function::inline_function,
200 inline_local_variable::inline_local_variable,
201 introduce_named_lifetime::introduce_named_lifetime,
202 invert_if::invert_if,
203 merge_imports::merge_imports,
204 merge_match_arms::merge_match_arms,
205 move_bounds::move_bounds_to_where_clause,
206 move_guard::move_arm_cond_to_match_guard,
207 move_guard::move_guard_to_arm_body,
208 pull_assignment_up::pull_assignment_up,
209 qualify_path::qualify_path,
210 raw_string::add_hash,
211 raw_string::make_usual_string,
212 raw_string::remove_hash,
213 remove_dbg::remove_dbg,
214 remove_mut::remove_mut,
215 remove_unused_param::remove_unused_param,
216 reorder_fields::reorder_fields,
217 reorder_impl::reorder_impl,
218 replace_derive_with_manual_impl::replace_derive_with_manual_impl,
219 replace_if_let_with_match::replace_if_let_with_match,
220 replace_if_let_with_match::replace_match_with_if_let,
221 replace_impl_trait_with_generic::replace_impl_trait_with_generic,
222 replace_let_with_if_let::replace_let_with_if_let,
223 replace_qualified_name_with_use::replace_qualified_name_with_use,
224 replace_unwrap_with_match::replace_unwrap_with_match,
225 split_import::split_import,
226 toggle_ignore::toggle_ignore,
227 unmerge_use::unmerge_use,
228 unwrap_block::unwrap_block,
229 wrap_return_type_in_result::wrap_return_type_in_result,
230 // These are manually sorted for better priorities. By default,
231 // priority is determined by the size of the target range (smaller
232 // target wins). If the ranges are equal, position in this list is
233 // used as a tie-breaker.
234 add_missing_impl_members::add_missing_impl_members,
235 add_missing_impl_members::add_missing_default_members,
236 //
237 replace_string_with_char::replace_string_with_char,
238 raw_string::make_raw_string,
239 //
240 extract_variable::extract_variable,
241 extract_function::extract_function,
242 // Are you sure you want to add new assist here, and not to the
243 // sorted list above?
244 ]
245 }
246}
diff --git a/crates/ide_assists/src/tests.rs b/crates/ide_assists/src/tests.rs
new file mode 100644
index 000000000..384eb7eee
--- /dev/null
+++ b/crates/ide_assists/src/tests.rs
@@ -0,0 +1,275 @@
1mod generated;
2
3use expect_test::expect;
4use hir::Semantics;
5use ide_db::{
6 base_db::{fixture::WithFixture, FileId, FileRange, SourceDatabaseExt},
7 helpers::{
8 insert_use::{InsertUseConfig, MergeBehavior},
9 SnippetCap,
10 },
11 source_change::FileSystemEdit,
12 RootDatabase,
13};
14use stdx::{format_to, trim_indent};
15use syntax::TextRange;
16use test_utils::{assert_eq_text, extract_offset};
17
18use crate::{handlers::Handler, Assist, AssistConfig, AssistContext, AssistKind, Assists};
19
20pub(crate) const TEST_CONFIG: AssistConfig = AssistConfig {
21 snippet_cap: SnippetCap::new(true),
22 allowed: None,
23 insert_use: InsertUseConfig {
24 merge: Some(MergeBehavior::Full),
25 prefix_kind: hir::PrefixKind::Plain,
26 },
27};
28
29pub(crate) fn with_single_file(text: &str) -> (RootDatabase, FileId) {
30 RootDatabase::with_single_file(text)
31}
32
33pub(crate) fn check_assist(assist: Handler, ra_fixture_before: &str, ra_fixture_after: &str) {
34 let ra_fixture_after = trim_indent(ra_fixture_after);
35 check(assist, ra_fixture_before, ExpectedResult::After(&ra_fixture_after), None);
36}
37
38// There is no way to choose what assist within a group you want to test against,
39// so this is here to allow you choose.
40pub(crate) fn check_assist_by_label(
41 assist: Handler,
42 ra_fixture_before: &str,
43 ra_fixture_after: &str,
44 label: &str,
45) {
46 let ra_fixture_after = trim_indent(ra_fixture_after);
47 check(assist, ra_fixture_before, ExpectedResult::After(&ra_fixture_after), Some(label));
48}
49
50// FIXME: instead of having a separate function here, maybe use
51// `extract_ranges` and mark the target as `<target> </target>` in the
52// fixture?
53#[track_caller]
54pub(crate) fn check_assist_target(assist: Handler, ra_fixture: &str, target: &str) {
55 check(assist, ra_fixture, ExpectedResult::Target(target), None);
56}
57
58#[track_caller]
59pub(crate) fn check_assist_not_applicable(assist: Handler, ra_fixture: &str) {
60 check(assist, ra_fixture, ExpectedResult::NotApplicable, None);
61}
62
63#[track_caller]
64fn check_doc_test(assist_id: &str, before: &str, after: &str) {
65 let after = trim_indent(after);
66 let (db, file_id, selection) = RootDatabase::with_range_or_offset(&before);
67 let before = db.file_text(file_id).to_string();
68 let frange = FileRange { file_id, range: selection.into() };
69
70 let assist = Assist::get(&db, &TEST_CONFIG, true, frange)
71 .into_iter()
72 .find(|assist| assist.id.0 == assist_id)
73 .unwrap_or_else(|| {
74 panic!(
75 "\n\nAssist is not applicable: {}\nAvailable assists: {}",
76 assist_id,
77 Assist::get(&db, &TEST_CONFIG, false, frange)
78 .into_iter()
79 .map(|assist| assist.id.0)
80 .collect::<Vec<_>>()
81 .join(", ")
82 )
83 });
84
85 let actual = {
86 let source_change = assist.source_change.unwrap();
87 let mut actual = before;
88 if let Some(source_file_edit) = source_change.get_source_edit(file_id) {
89 source_file_edit.apply(&mut actual);
90 }
91 actual
92 };
93 assert_eq_text!(&after, &actual);
94}
95
96enum ExpectedResult<'a> {
97 NotApplicable,
98 After(&'a str),
99 Target(&'a str),
100}
101
102#[track_caller]
103fn check(handler: Handler, before: &str, expected: ExpectedResult, assist_label: Option<&str>) {
104 let (db, file_with_caret_id, range_or_offset) = RootDatabase::with_range_or_offset(before);
105 let text_without_caret = db.file_text(file_with_caret_id).to_string();
106
107 let frange = FileRange { file_id: file_with_caret_id, range: range_or_offset.into() };
108
109 let sema = Semantics::new(&db);
110 let config = TEST_CONFIG;
111 let ctx = AssistContext::new(sema, &config, frange);
112 let mut acc = Assists::new(&ctx, true);
113 handler(&mut acc, &ctx);
114 let mut res = acc.finish();
115
116 let assist = match assist_label {
117 Some(label) => res.into_iter().find(|resolved| resolved.label == label),
118 None => res.pop(),
119 };
120
121 match (assist, expected) {
122 (Some(assist), ExpectedResult::After(after)) => {
123 let source_change = assist.source_change.unwrap();
124 assert!(!source_change.source_file_edits.is_empty());
125 let skip_header = source_change.source_file_edits.len() == 1
126 && source_change.file_system_edits.len() == 0;
127
128 let mut buf = String::new();
129 for (file_id, edit) in source_change.source_file_edits {
130 let mut text = db.file_text(file_id).as_ref().to_owned();
131 edit.apply(&mut text);
132 if !skip_header {
133 let sr = db.file_source_root(file_id);
134 let sr = db.source_root(sr);
135 let path = sr.path_for_file(&file_id).unwrap();
136 format_to!(buf, "//- {}\n", path)
137 }
138 buf.push_str(&text);
139 }
140
141 for file_system_edit in source_change.file_system_edits {
142 if let FileSystemEdit::CreateFile { dst, initial_contents } = file_system_edit {
143 let sr = db.file_source_root(dst.anchor);
144 let sr = db.source_root(sr);
145 let mut base = sr.path_for_file(&dst.anchor).unwrap().clone();
146 base.pop();
147 let created_file_path = format!("{}{}", base.to_string(), &dst.path[1..]);
148 format_to!(buf, "//- {}\n", created_file_path);
149 buf.push_str(&initial_contents);
150 }
151 }
152
153 assert_eq_text!(after, &buf);
154 }
155 (Some(assist), ExpectedResult::Target(target)) => {
156 let range = assist.target;
157 assert_eq_text!(&text_without_caret[range], target);
158 }
159 (Some(_), ExpectedResult::NotApplicable) => panic!("assist should not be applicable!"),
160 (None, ExpectedResult::After(_)) | (None, ExpectedResult::Target(_)) => {
161 panic!("code action is not applicable")
162 }
163 (None, ExpectedResult::NotApplicable) => (),
164 };
165}
166
167fn labels(assists: &[Assist]) -> String {
168 let mut labels = assists
169 .iter()
170 .map(|assist| {
171 let mut label = match &assist.group {
172 Some(g) => g.0.clone(),
173 None => assist.label.to_string(),
174 };
175 label.push('\n');
176 label
177 })
178 .collect::<Vec<_>>();
179 labels.dedup();
180 labels.into_iter().collect::<String>()
181}
182
183#[test]
184fn assist_order_field_struct() {
185 let before = "struct Foo { $0bar: u32 }";
186 let (before_cursor_pos, before) = extract_offset(before);
187 let (db, file_id) = with_single_file(&before);
188 let frange = FileRange { file_id, range: TextRange::empty(before_cursor_pos) };
189 let assists = Assist::get(&db, &TEST_CONFIG, false, frange);
190 let mut assists = assists.iter();
191
192 assert_eq!(assists.next().expect("expected assist").label, "Change visibility to pub(crate)");
193 assert_eq!(assists.next().expect("expected assist").label, "Generate a getter method");
194 assert_eq!(assists.next().expect("expected assist").label, "Generate a mut getter method");
195 assert_eq!(assists.next().expect("expected assist").label, "Generate a setter method");
196 assert_eq!(assists.next().expect("expected assist").label, "Add `#[derive]`");
197}
198
199#[test]
200fn assist_order_if_expr() {
201 let (db, frange) = RootDatabase::with_range(
202 r#"
203pub fn test_some_range(a: int) -> bool {
204 if let 2..6 = $05$0 {
205 true
206 } else {
207 false
208 }
209}
210"#,
211 );
212
213 let assists = Assist::get(&db, &TEST_CONFIG, false, frange);
214 let expected = labels(&assists);
215
216 expect![[r#"
217 Convert integer base
218 Extract into variable
219 Extract into function
220 Replace with match
221 "#]]
222 .assert_eq(&expected);
223}
224
225#[test]
226fn assist_filter_works() {
227 let (db, frange) = RootDatabase::with_range(
228 r#"
229pub fn test_some_range(a: int) -> bool {
230 if let 2..6 = $05$0 {
231 true
232 } else {
233 false
234 }
235}
236"#,
237 );
238 {
239 let mut cfg = TEST_CONFIG;
240 cfg.allowed = Some(vec![AssistKind::Refactor]);
241
242 let assists = Assist::get(&db, &cfg, false, frange);
243 let expected = labels(&assists);
244
245 expect![[r#"
246 Convert integer base
247 Extract into variable
248 Extract into function
249 Replace with match
250 "#]]
251 .assert_eq(&expected);
252 }
253
254 {
255 let mut cfg = TEST_CONFIG;
256 cfg.allowed = Some(vec![AssistKind::RefactorExtract]);
257 let assists = Assist::get(&db, &cfg, false, frange);
258 let expected = labels(&assists);
259
260 expect![[r#"
261 Extract into variable
262 Extract into function
263 "#]]
264 .assert_eq(&expected);
265 }
266
267 {
268 let mut cfg = TEST_CONFIG;
269 cfg.allowed = Some(vec![AssistKind::QuickFix]);
270 let assists = Assist::get(&db, &cfg, false, frange);
271 let expected = labels(&assists);
272
273 expect![[r#""#]].assert_eq(&expected);
274 }
275}
diff --git a/crates/ide_assists/src/tests/generated.rs b/crates/ide_assists/src/tests/generated.rs
new file mode 100644
index 000000000..0516deaff
--- /dev/null
+++ b/crates/ide_assists/src/tests/generated.rs
@@ -0,0 +1,1329 @@
1//! Generated file, do not edit by hand, see `xtask/src/codegen`
2
3use super::check_doc_test;
4
5#[test]
6fn doctest_add_explicit_type() {
7 check_doc_test(
8 "add_explicit_type",
9 r#####"
10fn main() {
11 let x$0 = 92;
12}
13"#####,
14 r#####"
15fn main() {
16 let x: i32 = 92;
17}
18"#####,
19 )
20}
21
22#[test]
23fn doctest_add_hash() {
24 check_doc_test(
25 "add_hash",
26 r#####"
27fn main() {
28 r#"Hello,$0 World!"#;
29}
30"#####,
31 r#####"
32fn main() {
33 r##"Hello, World!"##;
34}
35"#####,
36 )
37}
38
39#[test]
40fn doctest_add_impl_default_members() {
41 check_doc_test(
42 "add_impl_default_members",
43 r#####"
44trait Trait {
45 type X;
46 fn foo(&self);
47 fn bar(&self) {}
48}
49
50impl Trait for () {
51 type X = ();
52 fn foo(&self) {}$0
53
54}
55"#####,
56 r#####"
57trait Trait {
58 type X;
59 fn foo(&self);
60 fn bar(&self) {}
61}
62
63impl Trait for () {
64 type X = ();
65 fn foo(&self) {}
66
67 $0fn bar(&self) {}
68}
69"#####,
70 )
71}
72
73#[test]
74fn doctest_add_impl_missing_members() {
75 check_doc_test(
76 "add_impl_missing_members",
77 r#####"
78trait Trait<T> {
79 type X;
80 fn foo(&self) -> T;
81 fn bar(&self) {}
82}
83
84impl Trait<u32> for () {$0
85
86}
87"#####,
88 r#####"
89trait Trait<T> {
90 type X;
91 fn foo(&self) -> T;
92 fn bar(&self) {}
93}
94
95impl Trait<u32> for () {
96 $0type X;
97
98 fn foo(&self) -> u32 {
99 todo!()
100 }
101}
102"#####,
103 )
104}
105
106#[test]
107fn doctest_add_lifetime_to_type() {
108 check_doc_test(
109 "add_lifetime_to_type",
110 r#####"
111struct Point {
112 x: &$0u32,
113 y: u32,
114}
115"#####,
116 r#####"
117struct Point<'a> {
118 x: &'a u32,
119 y: u32,
120}
121"#####,
122 )
123}
124
125#[test]
126fn doctest_add_turbo_fish() {
127 check_doc_test(
128 "add_turbo_fish",
129 r#####"
130fn make<T>() -> T { todo!() }
131fn main() {
132 let x = make$0();
133}
134"#####,
135 r#####"
136fn make<T>() -> T { todo!() }
137fn main() {
138 let x = make::<${0:_}>();
139}
140"#####,
141 )
142}
143
144#[test]
145fn doctest_apply_demorgan() {
146 check_doc_test(
147 "apply_demorgan",
148 r#####"
149fn main() {
150 if x != 4 ||$0 !y {}
151}
152"#####,
153 r#####"
154fn main() {
155 if !(x == 4 && y) {}
156}
157"#####,
158 )
159}
160
161#[test]
162fn doctest_auto_import() {
163 check_doc_test(
164 "auto_import",
165 r#####"
166fn main() {
167 let map = HashMap$0::new();
168}
169pub mod std { pub mod collections { pub struct HashMap { } } }
170"#####,
171 r#####"
172use std::collections::HashMap;
173
174fn main() {
175 let map = HashMap::new();
176}
177pub mod std { pub mod collections { pub struct HashMap { } } }
178"#####,
179 )
180}
181
182#[test]
183fn doctest_change_visibility() {
184 check_doc_test(
185 "change_visibility",
186 r#####"
187$0fn frobnicate() {}
188"#####,
189 r#####"
190pub(crate) fn frobnicate() {}
191"#####,
192 )
193}
194
195#[test]
196fn doctest_convert_integer_literal() {
197 check_doc_test(
198 "convert_integer_literal",
199 r#####"
200const _: i32 = 10$0;
201"#####,
202 r#####"
203const _: i32 = 0b1010;
204"#####,
205 )
206}
207
208#[test]
209fn doctest_convert_to_guarded_return() {
210 check_doc_test(
211 "convert_to_guarded_return",
212 r#####"
213fn main() {
214 $0if cond {
215 foo();
216 bar();
217 }
218}
219"#####,
220 r#####"
221fn main() {
222 if !cond {
223 return;
224 }
225 foo();
226 bar();
227}
228"#####,
229 )
230}
231
232#[test]
233fn doctest_expand_glob_import() {
234 check_doc_test(
235 "expand_glob_import",
236 r#####"
237mod foo {
238 pub struct Bar;
239 pub struct Baz;
240}
241
242use foo::*$0;
243
244fn qux(bar: Bar, baz: Baz) {}
245"#####,
246 r#####"
247mod foo {
248 pub struct Bar;
249 pub struct Baz;
250}
251
252use foo::{Baz, Bar};
253
254fn qux(bar: Bar, baz: Baz) {}
255"#####,
256 )
257}
258
259#[test]
260fn doctest_extract_function() {
261 check_doc_test(
262 "extract_function",
263 r#####"
264fn main() {
265 let n = 1;
266 $0let m = n + 2;
267 let k = m + n;$0
268 let g = 3;
269}
270"#####,
271 r#####"
272fn main() {
273 let n = 1;
274 fun_name(n);
275 let g = 3;
276}
277
278fn $0fun_name(n: i32) {
279 let m = n + 2;
280 let k = m + n;
281}
282"#####,
283 )
284}
285
286#[test]
287fn doctest_extract_struct_from_enum_variant() {
288 check_doc_test(
289 "extract_struct_from_enum_variant",
290 r#####"
291enum A { $0One(u32, u32) }
292"#####,
293 r#####"
294struct One(pub u32, pub u32);
295
296enum A { One(One) }
297"#####,
298 )
299}
300
301#[test]
302fn doctest_extract_variable() {
303 check_doc_test(
304 "extract_variable",
305 r#####"
306fn main() {
307 $0(1 + 2)$0 * 4;
308}
309"#####,
310 r#####"
311fn main() {
312 let $0var_name = (1 + 2);
313 var_name * 4;
314}
315"#####,
316 )
317}
318
319#[test]
320fn doctest_fill_match_arms() {
321 check_doc_test(
322 "fill_match_arms",
323 r#####"
324enum Action { Move { distance: u32 }, Stop }
325
326fn handle(action: Action) {
327 match action {
328 $0
329 }
330}
331"#####,
332 r#####"
333enum Action { Move { distance: u32 }, Stop }
334
335fn handle(action: Action) {
336 match action {
337 $0Action::Move { distance } => {}
338 Action::Stop => {}
339 }
340}
341"#####,
342 )
343}
344
345#[test]
346fn doctest_fix_visibility() {
347 check_doc_test(
348 "fix_visibility",
349 r#####"
350mod m {
351 fn frobnicate() {}
352}
353fn main() {
354 m::frobnicate$0() {}
355}
356"#####,
357 r#####"
358mod m {
359 $0pub(crate) fn frobnicate() {}
360}
361fn main() {
362 m::frobnicate() {}
363}
364"#####,
365 )
366}
367
368#[test]
369fn doctest_flip_binexpr() {
370 check_doc_test(
371 "flip_binexpr",
372 r#####"
373fn main() {
374 let _ = 90 +$0 2;
375}
376"#####,
377 r#####"
378fn main() {
379 let _ = 2 + 90;
380}
381"#####,
382 )
383}
384
385#[test]
386fn doctest_flip_comma() {
387 check_doc_test(
388 "flip_comma",
389 r#####"
390fn main() {
391 ((1, 2),$0 (3, 4));
392}
393"#####,
394 r#####"
395fn main() {
396 ((3, 4), (1, 2));
397}
398"#####,
399 )
400}
401
402#[test]
403fn doctest_flip_trait_bound() {
404 check_doc_test(
405 "flip_trait_bound",
406 r#####"
407fn foo<T: Clone +$0 Copy>() { }
408"#####,
409 r#####"
410fn foo<T: Copy + Clone>() { }
411"#####,
412 )
413}
414
415#[test]
416fn doctest_generate_default_from_enum_variant() {
417 check_doc_test(
418 "generate_default_from_enum_variant",
419 r#####"
420enum Version {
421 Undefined,
422 Minor$0,
423 Major,
424}
425"#####,
426 r#####"
427enum Version {
428 Undefined,
429 Minor,
430 Major,
431}
432
433impl Default for Version {
434 fn default() -> Self {
435 Self::Minor
436 }
437}
438"#####,
439 )
440}
441
442#[test]
443fn doctest_generate_derive() {
444 check_doc_test(
445 "generate_derive",
446 r#####"
447struct Point {
448 x: u32,
449 y: u32,$0
450}
451"#####,
452 r#####"
453#[derive($0)]
454struct Point {
455 x: u32,
456 y: u32,
457}
458"#####,
459 )
460}
461
462#[test]
463fn doctest_generate_enum_match_method() {
464 check_doc_test(
465 "generate_enum_match_method",
466 r#####"
467enum Version {
468 Undefined,
469 Minor$0,
470 Major,
471}
472"#####,
473 r#####"
474enum Version {
475 Undefined,
476 Minor,
477 Major,
478}
479
480impl Version {
481 /// Returns `true` if the version is [`Minor`].
482 fn is_minor(&self) -> bool {
483 matches!(self, Self::Minor)
484 }
485}
486"#####,
487 )
488}
489
490#[test]
491fn doctest_generate_from_impl_for_enum() {
492 check_doc_test(
493 "generate_from_impl_for_enum",
494 r#####"
495enum A { $0One(u32) }
496"#####,
497 r#####"
498enum A { One(u32) }
499
500impl From<u32> for A {
501 fn from(v: u32) -> Self {
502 Self::One(v)
503 }
504}
505"#####,
506 )
507}
508
509#[test]
510fn doctest_generate_function() {
511 check_doc_test(
512 "generate_function",
513 r#####"
514struct Baz;
515fn baz() -> Baz { Baz }
516fn foo() {
517 bar$0("", baz());
518}
519
520"#####,
521 r#####"
522struct Baz;
523fn baz() -> Baz { Baz }
524fn foo() {
525 bar("", baz());
526}
527
528fn bar(arg: &str, baz: Baz) ${0:-> ()} {
529 todo!()
530}
531
532"#####,
533 )
534}
535
536#[test]
537fn doctest_generate_getter() {
538 check_doc_test(
539 "generate_getter",
540 r#####"
541struct Person {
542 nam$0e: String,
543}
544"#####,
545 r#####"
546struct Person {
547 name: String,
548}
549
550impl Person {
551 /// Get a reference to the person's name.
552 fn name(&self) -> &String {
553 &self.name
554 }
555}
556"#####,
557 )
558}
559
560#[test]
561fn doctest_generate_getter_mut() {
562 check_doc_test(
563 "generate_getter_mut",
564 r#####"
565struct Person {
566 nam$0e: String,
567}
568"#####,
569 r#####"
570struct Person {
571 name: String,
572}
573
574impl Person {
575 /// Get a mutable reference to the person's name.
576 fn name_mut(&mut self) -> &mut String {
577 &mut self.name
578 }
579}
580"#####,
581 )
582}
583
584#[test]
585fn doctest_generate_impl() {
586 check_doc_test(
587 "generate_impl",
588 r#####"
589struct Ctx<T: Clone> {
590 data: T,$0
591}
592"#####,
593 r#####"
594struct Ctx<T: Clone> {
595 data: T,
596}
597
598impl<T: Clone> Ctx<T> {
599 $0
600}
601"#####,
602 )
603}
604
605#[test]
606fn doctest_generate_new() {
607 check_doc_test(
608 "generate_new",
609 r#####"
610struct Ctx<T: Clone> {
611 data: T,$0
612}
613"#####,
614 r#####"
615struct Ctx<T: Clone> {
616 data: T,
617}
618
619impl<T: Clone> Ctx<T> {
620 fn $0new(data: T) -> Self { Self { data } }
621}
622"#####,
623 )
624}
625
626#[test]
627fn doctest_generate_setter() {
628 check_doc_test(
629 "generate_setter",
630 r#####"
631struct Person {
632 nam$0e: String,
633}
634"#####,
635 r#####"
636struct Person {
637 name: String,
638}
639
640impl Person {
641 /// Set the person's name.
642 fn set_name(&mut self, name: String) {
643 self.name = name;
644 }
645}
646"#####,
647 )
648}
649
650#[test]
651fn doctest_infer_function_return_type() {
652 check_doc_test(
653 "infer_function_return_type",
654 r#####"
655fn foo() { 4$02i32 }
656"#####,
657 r#####"
658fn foo() -> i32 { 42i32 }
659"#####,
660 )
661}
662
663#[test]
664fn doctest_inline_function() {
665 check_doc_test(
666 "inline_function",
667 r#####"
668fn add(a: u32, b: u32) -> u32 { a + b }
669fn main() {
670 let x = add$0(1, 2);
671}
672"#####,
673 r#####"
674fn add(a: u32, b: u32) -> u32 { a + b }
675fn main() {
676 let x = {
677 let a = 1;
678 let b = 2;
679 a + b
680 };
681}
682"#####,
683 )
684}
685
686#[test]
687fn doctest_inline_local_variable() {
688 check_doc_test(
689 "inline_local_variable",
690 r#####"
691fn main() {
692 let x$0 = 1 + 2;
693 x * 4;
694}
695"#####,
696 r#####"
697fn main() {
698 (1 + 2) * 4;
699}
700"#####,
701 )
702}
703
704#[test]
705fn doctest_introduce_named_lifetime() {
706 check_doc_test(
707 "introduce_named_lifetime",
708 r#####"
709impl Cursor<'_$0> {
710 fn node(self) -> &SyntaxNode {
711 match self {
712 Cursor::Replace(node) | Cursor::Before(node) => node,
713 }
714 }
715}
716"#####,
717 r#####"
718impl<'a> Cursor<'a> {
719 fn node(self) -> &SyntaxNode {
720 match self {
721 Cursor::Replace(node) | Cursor::Before(node) => node,
722 }
723 }
724}
725"#####,
726 )
727}
728
729#[test]
730fn doctest_invert_if() {
731 check_doc_test(
732 "invert_if",
733 r#####"
734fn main() {
735 if$0 !y { A } else { B }
736}
737"#####,
738 r#####"
739fn main() {
740 if y { B } else { A }
741}
742"#####,
743 )
744}
745
746#[test]
747fn doctest_make_raw_string() {
748 check_doc_test(
749 "make_raw_string",
750 r#####"
751fn main() {
752 "Hello,$0 World!";
753}
754"#####,
755 r#####"
756fn main() {
757 r#"Hello, World!"#;
758}
759"#####,
760 )
761}
762
763#[test]
764fn doctest_make_usual_string() {
765 check_doc_test(
766 "make_usual_string",
767 r#####"
768fn main() {
769 r#"Hello,$0 "World!""#;
770}
771"#####,
772 r#####"
773fn main() {
774 "Hello, \"World!\"";
775}
776"#####,
777 )
778}
779
780#[test]
781fn doctest_merge_imports() {
782 check_doc_test(
783 "merge_imports",
784 r#####"
785use std::$0fmt::Formatter;
786use std::io;
787"#####,
788 r#####"
789use std::{fmt::Formatter, io};
790"#####,
791 )
792}
793
794#[test]
795fn doctest_merge_match_arms() {
796 check_doc_test(
797 "merge_match_arms",
798 r#####"
799enum Action { Move { distance: u32 }, Stop }
800
801fn handle(action: Action) {
802 match action {
803 $0Action::Move(..) => foo(),
804 Action::Stop => foo(),
805 }
806}
807"#####,
808 r#####"
809enum Action { Move { distance: u32 }, Stop }
810
811fn handle(action: Action) {
812 match action {
813 Action::Move(..) | Action::Stop => foo(),
814 }
815}
816"#####,
817 )
818}
819
820#[test]
821fn doctest_move_arm_cond_to_match_guard() {
822 check_doc_test(
823 "move_arm_cond_to_match_guard",
824 r#####"
825enum Action { Move { distance: u32 }, Stop }
826
827fn handle(action: Action) {
828 match action {
829 Action::Move { distance } => $0if distance > 10 { foo() },
830 _ => (),
831 }
832}
833"#####,
834 r#####"
835enum Action { Move { distance: u32 }, Stop }
836
837fn handle(action: Action) {
838 match action {
839 Action::Move { distance } if distance > 10 => foo(),
840 _ => (),
841 }
842}
843"#####,
844 )
845}
846
847#[test]
848fn doctest_move_bounds_to_where_clause() {
849 check_doc_test(
850 "move_bounds_to_where_clause",
851 r#####"
852fn apply<T, U, $0F: FnOnce(T) -> U>(f: F, x: T) -> U {
853 f(x)
854}
855"#####,
856 r#####"
857fn apply<T, U, F>(f: F, x: T) -> U where F: FnOnce(T) -> U {
858 f(x)
859}
860"#####,
861 )
862}
863
864#[test]
865fn doctest_move_guard_to_arm_body() {
866 check_doc_test(
867 "move_guard_to_arm_body",
868 r#####"
869enum Action { Move { distance: u32 }, Stop }
870
871fn handle(action: Action) {
872 match action {
873 Action::Move { distance } $0if distance > 10 => foo(),
874 _ => (),
875 }
876}
877"#####,
878 r#####"
879enum Action { Move { distance: u32 }, Stop }
880
881fn handle(action: Action) {
882 match action {
883 Action::Move { distance } => if distance > 10 {
884 foo()
885 },
886 _ => (),
887 }
888}
889"#####,
890 )
891}
892
893#[test]
894fn doctest_move_module_to_file() {
895 check_doc_test(
896 "move_module_to_file",
897 r#####"
898mod $0foo {
899 fn t() {}
900}
901"#####,
902 r#####"
903mod foo;
904"#####,
905 )
906}
907
908#[test]
909fn doctest_pull_assignment_up() {
910 check_doc_test(
911 "pull_assignment_up",
912 r#####"
913fn main() {
914 let mut foo = 6;
915
916 if true {
917 $0foo = 5;
918 } else {
919 foo = 4;
920 }
921}
922"#####,
923 r#####"
924fn main() {
925 let mut foo = 6;
926
927 foo = if true {
928 5
929 } else {
930 4
931 };
932}
933"#####,
934 )
935}
936
937#[test]
938fn doctest_qualify_path() {
939 check_doc_test(
940 "qualify_path",
941 r#####"
942fn main() {
943 let map = HashMap$0::new();
944}
945pub mod std { pub mod collections { pub struct HashMap { } } }
946"#####,
947 r#####"
948fn main() {
949 let map = std::collections::HashMap::new();
950}
951pub mod std { pub mod collections { pub struct HashMap { } } }
952"#####,
953 )
954}
955
956#[test]
957fn doctest_remove_dbg() {
958 check_doc_test(
959 "remove_dbg",
960 r#####"
961fn main() {
962 $0dbg!(92);
963}
964"#####,
965 r#####"
966fn main() {
967 92;
968}
969"#####,
970 )
971}
972
973#[test]
974fn doctest_remove_hash() {
975 check_doc_test(
976 "remove_hash",
977 r#####"
978fn main() {
979 r#"Hello,$0 World!"#;
980}
981"#####,
982 r#####"
983fn main() {
984 r"Hello, World!";
985}
986"#####,
987 )
988}
989
990#[test]
991fn doctest_remove_mut() {
992 check_doc_test(
993 "remove_mut",
994 r#####"
995impl Walrus {
996 fn feed(&mut$0 self, amount: u32) {}
997}
998"#####,
999 r#####"
1000impl Walrus {
1001 fn feed(&self, amount: u32) {}
1002}
1003"#####,
1004 )
1005}
1006
1007#[test]
1008fn doctest_remove_unused_param() {
1009 check_doc_test(
1010 "remove_unused_param",
1011 r#####"
1012fn frobnicate(x: i32$0) {}
1013
1014fn main() {
1015 frobnicate(92);
1016}
1017"#####,
1018 r#####"
1019fn frobnicate() {}
1020
1021fn main() {
1022 frobnicate();
1023}
1024"#####,
1025 )
1026}
1027
1028#[test]
1029fn doctest_reorder_fields() {
1030 check_doc_test(
1031 "reorder_fields",
1032 r#####"
1033struct Foo {foo: i32, bar: i32};
1034const test: Foo = $0Foo {bar: 0, foo: 1}
1035"#####,
1036 r#####"
1037struct Foo {foo: i32, bar: i32};
1038const test: Foo = Foo {foo: 1, bar: 0}
1039"#####,
1040 )
1041}
1042
1043#[test]
1044fn doctest_reorder_impl() {
1045 check_doc_test(
1046 "reorder_impl",
1047 r#####"
1048trait Foo {
1049 fn a() {}
1050 fn b() {}
1051 fn c() {}
1052}
1053
1054struct Bar;
1055$0impl Foo for Bar {
1056 fn b() {}
1057 fn c() {}
1058 fn a() {}
1059}
1060"#####,
1061 r#####"
1062trait Foo {
1063 fn a() {}
1064 fn b() {}
1065 fn c() {}
1066}
1067
1068struct Bar;
1069impl Foo for Bar {
1070 fn a() {}
1071 fn b() {}
1072 fn c() {}
1073}
1074"#####,
1075 )
1076}
1077
1078#[test]
1079fn doctest_replace_derive_with_manual_impl() {
1080 check_doc_test(
1081 "replace_derive_with_manual_impl",
1082 r#####"
1083trait Debug { fn fmt(&self, f: &mut Formatter) -> Result<()>; }
1084#[derive(Deb$0ug, Display)]
1085struct S;
1086"#####,
1087 r#####"
1088trait Debug { fn fmt(&self, f: &mut Formatter) -> Result<()>; }
1089#[derive(Display)]
1090struct S;
1091
1092impl Debug for S {
1093 fn fmt(&self, f: &mut Formatter) -> Result<()> {
1094 ${0:todo!()}
1095 }
1096}
1097"#####,
1098 )
1099}
1100
1101#[test]
1102fn doctest_replace_if_let_with_match() {
1103 check_doc_test(
1104 "replace_if_let_with_match",
1105 r#####"
1106enum Action { Move { distance: u32 }, Stop }
1107
1108fn handle(action: Action) {
1109 $0if let Action::Move { distance } = action {
1110 foo(distance)
1111 } else {
1112 bar()
1113 }
1114}
1115"#####,
1116 r#####"
1117enum Action { Move { distance: u32 }, Stop }
1118
1119fn handle(action: Action) {
1120 match action {
1121 Action::Move { distance } => foo(distance),
1122 _ => bar(),
1123 }
1124}
1125"#####,
1126 )
1127}
1128
1129#[test]
1130fn doctest_replace_impl_trait_with_generic() {
1131 check_doc_test(
1132 "replace_impl_trait_with_generic",
1133 r#####"
1134fn foo(bar: $0impl Bar) {}
1135"#####,
1136 r#####"
1137fn foo<B: Bar>(bar: B) {}
1138"#####,
1139 )
1140}
1141
1142#[test]
1143fn doctest_replace_let_with_if_let() {
1144 check_doc_test(
1145 "replace_let_with_if_let",
1146 r#####"
1147enum Option<T> { Some(T), None }
1148
1149fn main(action: Action) {
1150 $0let x = compute();
1151}
1152
1153fn compute() -> Option<i32> { None }
1154"#####,
1155 r#####"
1156enum Option<T> { Some(T), None }
1157
1158fn main(action: Action) {
1159 if let Some(x) = compute() {
1160 }
1161}
1162
1163fn compute() -> Option<i32> { None }
1164"#####,
1165 )
1166}
1167
1168#[test]
1169fn doctest_replace_match_with_if_let() {
1170 check_doc_test(
1171 "replace_match_with_if_let",
1172 r#####"
1173enum Action { Move { distance: u32 }, Stop }
1174
1175fn handle(action: Action) {
1176 $0match action {
1177 Action::Move { distance } => foo(distance),
1178 _ => bar(),
1179 }
1180}
1181"#####,
1182 r#####"
1183enum Action { Move { distance: u32 }, Stop }
1184
1185fn handle(action: Action) {
1186 if let Action::Move { distance } = action {
1187 foo(distance)
1188 } else {
1189 bar()
1190 }
1191}
1192"#####,
1193 )
1194}
1195
1196#[test]
1197fn doctest_replace_qualified_name_with_use() {
1198 check_doc_test(
1199 "replace_qualified_name_with_use",
1200 r#####"
1201fn process(map: std::collections::$0HashMap<String, String>) {}
1202"#####,
1203 r#####"
1204use std::collections::HashMap;
1205
1206fn process(map: HashMap<String, String>) {}
1207"#####,
1208 )
1209}
1210
1211#[test]
1212fn doctest_replace_string_with_char() {
1213 check_doc_test(
1214 "replace_string_with_char",
1215 r#####"
1216fn main() {
1217 find("{$0");
1218}
1219"#####,
1220 r#####"
1221fn main() {
1222 find('{');
1223}
1224"#####,
1225 )
1226}
1227
1228#[test]
1229fn doctest_replace_unwrap_with_match() {
1230 check_doc_test(
1231 "replace_unwrap_with_match",
1232 r#####"
1233enum Result<T, E> { Ok(T), Err(E) }
1234fn main() {
1235 let x: Result<i32, i32> = Result::Ok(92);
1236 let y = x.$0unwrap();
1237}
1238"#####,
1239 r#####"
1240enum Result<T, E> { Ok(T), Err(E) }
1241fn main() {
1242 let x: Result<i32, i32> = Result::Ok(92);
1243 let y = match x {
1244 Ok(a) => a,
1245 $0_ => unreachable!(),
1246 };
1247}
1248"#####,
1249 )
1250}
1251
1252#[test]
1253fn doctest_split_import() {
1254 check_doc_test(
1255 "split_import",
1256 r#####"
1257use std::$0collections::HashMap;
1258"#####,
1259 r#####"
1260use std::{collections::HashMap};
1261"#####,
1262 )
1263}
1264
1265#[test]
1266fn doctest_toggle_ignore() {
1267 check_doc_test(
1268 "toggle_ignore",
1269 r#####"
1270$0#[test]
1271fn arithmetics {
1272 assert_eq!(2 + 2, 5);
1273}
1274"#####,
1275 r#####"
1276#[test]
1277#[ignore]
1278fn arithmetics {
1279 assert_eq!(2 + 2, 5);
1280}
1281"#####,
1282 )
1283}
1284
1285#[test]
1286fn doctest_unmerge_use() {
1287 check_doc_test(
1288 "unmerge_use",
1289 r#####"
1290use std::fmt::{Debug, Display$0};
1291"#####,
1292 r#####"
1293use std::fmt::{Debug};
1294use std::fmt::Display;
1295"#####,
1296 )
1297}
1298
1299#[test]
1300fn doctest_unwrap_block() {
1301 check_doc_test(
1302 "unwrap_block",
1303 r#####"
1304fn foo() {
1305 if true {$0
1306 println!("foo");
1307 }
1308}
1309"#####,
1310 r#####"
1311fn foo() {
1312 println!("foo");
1313}
1314"#####,
1315 )
1316}
1317
1318#[test]
1319fn doctest_wrap_return_type_in_result() {
1320 check_doc_test(
1321 "wrap_return_type_in_result",
1322 r#####"
1323fn foo() -> i32$0 { 42i32 }
1324"#####,
1325 r#####"
1326fn foo() -> Result<i32, ${0:_}> { Ok(42i32) }
1327"#####,
1328 )
1329}
diff --git a/crates/ide_assists/src/utils.rs b/crates/ide_assists/src/utils.rs
new file mode 100644
index 000000000..0074da741
--- /dev/null
+++ b/crates/ide_assists/src/utils.rs
@@ -0,0 +1,434 @@
1//! Assorted functions shared by several assists.
2
3use std::ops;
4
5use ast::TypeBoundsOwner;
6use hir::{Adt, HasSource};
7use ide_db::{helpers::SnippetCap, RootDatabase};
8use itertools::Itertools;
9use stdx::format_to;
10use syntax::{
11 ast::edit::AstNodeEdit,
12 ast::AttrsOwner,
13 ast::NameOwner,
14 ast::{self, edit, make, ArgListOwner, GenericParamsOwner},
15 AstNode, Direction, SmolStr,
16 SyntaxKind::*,
17 SyntaxNode, TextSize, T,
18};
19
20use crate::{
21 assist_context::AssistContext,
22 ast_transform::{self, AstTransform, QualifyPaths, SubstituteTypeParams},
23};
24
25pub(crate) fn unwrap_trivial_block(block: ast::BlockExpr) -> ast::Expr {
26 extract_trivial_expression(&block)
27 .filter(|expr| !expr.syntax().text().contains_char('\n'))
28 .unwrap_or_else(|| block.into())
29}
30
31pub fn extract_trivial_expression(block: &ast::BlockExpr) -> Option<ast::Expr> {
32 let has_anything_else = |thing: &SyntaxNode| -> bool {
33 let mut non_trivial_children =
34 block.syntax().children_with_tokens().filter(|it| match it.kind() {
35 WHITESPACE | T!['{'] | T!['}'] => false,
36 _ => it.as_node() != Some(thing),
37 });
38 non_trivial_children.next().is_some()
39 };
40
41 if let Some(expr) = block.tail_expr() {
42 if has_anything_else(expr.syntax()) {
43 return None;
44 }
45 return Some(expr);
46 }
47 // Unwrap `{ continue; }`
48 let (stmt,) = block.statements().next_tuple()?;
49 if let ast::Stmt::ExprStmt(expr_stmt) = stmt {
50 if has_anything_else(expr_stmt.syntax()) {
51 return None;
52 }
53 let expr = expr_stmt.expr()?;
54 match expr.syntax().kind() {
55 CONTINUE_EXPR | BREAK_EXPR | RETURN_EXPR => return Some(expr),
56 _ => (),
57 }
58 }
59 None
60}
61
62/// This is a method with a heuristics to support test methods annotated with custom test annotations, such as
63/// `#[test_case(...)]`, `#[tokio::test]` and similar.
64/// Also a regular `#[test]` annotation is supported.
65///
66/// It may produce false positives, for example, `#[wasm_bindgen_test]` requires a different command to run the test,
67/// but it's better than not to have the runnables for the tests at all.
68pub fn test_related_attribute(fn_def: &ast::Fn) -> Option<ast::Attr> {
69 fn_def.attrs().find_map(|attr| {
70 let path = attr.path()?;
71 if path.syntax().text().to_string().contains("test") {
72 Some(attr)
73 } else {
74 None
75 }
76 })
77}
78
79#[derive(Copy, Clone, PartialEq)]
80pub enum DefaultMethods {
81 Only,
82 No,
83}
84
85pub fn filter_assoc_items(
86 db: &RootDatabase,
87 items: &[hir::AssocItem],
88 default_methods: DefaultMethods,
89) -> Vec<ast::AssocItem> {
90 fn has_def_name(item: &ast::AssocItem) -> bool {
91 match item {
92 ast::AssocItem::Fn(def) => def.name(),
93 ast::AssocItem::TypeAlias(def) => def.name(),
94 ast::AssocItem::Const(def) => def.name(),
95 ast::AssocItem::MacroCall(_) => None,
96 }
97 .is_some()
98 }
99
100 items
101 .iter()
102 // Note: This throws away items with no source.
103 .filter_map(|i| {
104 let item = match i {
105 hir::AssocItem::Function(i) => ast::AssocItem::Fn(i.source(db)?.value),
106 hir::AssocItem::TypeAlias(i) => ast::AssocItem::TypeAlias(i.source(db)?.value),
107 hir::AssocItem::Const(i) => ast::AssocItem::Const(i.source(db)?.value),
108 };
109 Some(item)
110 })
111 .filter(has_def_name)
112 .filter(|it| match it {
113 ast::AssocItem::Fn(def) => matches!(
114 (default_methods, def.body()),
115 (DefaultMethods::Only, Some(_)) | (DefaultMethods::No, None)
116 ),
117 _ => default_methods == DefaultMethods::No,
118 })
119 .collect::<Vec<_>>()
120}
121
122pub fn add_trait_assoc_items_to_impl(
123 sema: &hir::Semantics<ide_db::RootDatabase>,
124 items: Vec<ast::AssocItem>,
125 trait_: hir::Trait,
126 impl_def: ast::Impl,
127 target_scope: hir::SemanticsScope,
128) -> (ast::Impl, ast::AssocItem) {
129 let impl_item_list = impl_def.assoc_item_list().unwrap_or_else(make::assoc_item_list);
130
131 let n_existing_items = impl_item_list.assoc_items().count();
132 let source_scope = sema.scope_for_def(trait_);
133 let ast_transform = QualifyPaths::new(&target_scope, &source_scope)
134 .or(SubstituteTypeParams::for_trait_impl(&source_scope, trait_, impl_def.clone()));
135
136 let items = items
137 .into_iter()
138 .map(|it| ast_transform::apply(&*ast_transform, it))
139 .map(|it| match it {
140 ast::AssocItem::Fn(def) => ast::AssocItem::Fn(add_body(def)),
141 ast::AssocItem::TypeAlias(def) => ast::AssocItem::TypeAlias(def.remove_bounds()),
142 _ => it,
143 })
144 .map(|it| edit::remove_attrs_and_docs(&it));
145
146 let new_impl_item_list = impl_item_list.append_items(items);
147 let new_impl_def = impl_def.with_assoc_item_list(new_impl_item_list);
148 let first_new_item =
149 new_impl_def.assoc_item_list().unwrap().assoc_items().nth(n_existing_items).unwrap();
150 return (new_impl_def, first_new_item);
151
152 fn add_body(fn_def: ast::Fn) -> ast::Fn {
153 match fn_def.body() {
154 Some(_) => fn_def,
155 None => {
156 let body =
157 make::block_expr(None, Some(make::expr_todo())).indent(edit::IndentLevel(1));
158 fn_def.with_body(body)
159 }
160 }
161 }
162}
163
164#[derive(Clone, Copy, Debug)]
165pub(crate) enum Cursor<'a> {
166 Replace(&'a SyntaxNode),
167 Before(&'a SyntaxNode),
168}
169
170impl<'a> Cursor<'a> {
171 fn node(self) -> &'a SyntaxNode {
172 match self {
173 Cursor::Replace(node) | Cursor::Before(node) => node,
174 }
175 }
176}
177
178pub(crate) fn render_snippet(_cap: SnippetCap, node: &SyntaxNode, cursor: Cursor) -> String {
179 assert!(cursor.node().ancestors().any(|it| it == *node));
180 let range = cursor.node().text_range() - node.text_range().start();
181 let range: ops::Range<usize> = range.into();
182
183 let mut placeholder = cursor.node().to_string();
184 escape(&mut placeholder);
185 let tab_stop = match cursor {
186 Cursor::Replace(placeholder) => format!("${{0:{}}}", placeholder),
187 Cursor::Before(placeholder) => format!("$0{}", placeholder),
188 };
189
190 let mut buf = node.to_string();
191 buf.replace_range(range, &tab_stop);
192 return buf;
193
194 fn escape(buf: &mut String) {
195 stdx::replace(buf, '{', r"\{");
196 stdx::replace(buf, '}', r"\}");
197 stdx::replace(buf, '$', r"\$");
198 }
199}
200
201pub(crate) fn vis_offset(node: &SyntaxNode) -> TextSize {
202 node.children_with_tokens()
203 .find(|it| !matches!(it.kind(), WHITESPACE | COMMENT | ATTR))
204 .map(|it| it.text_range().start())
205 .unwrap_or_else(|| node.text_range().start())
206}
207
208pub(crate) fn invert_boolean_expression(expr: ast::Expr) -> ast::Expr {
209 if let Some(expr) = invert_special_case(&expr) {
210 return expr;
211 }
212 make::expr_prefix(T![!], expr)
213}
214
215fn invert_special_case(expr: &ast::Expr) -> Option<ast::Expr> {
216 match expr {
217 ast::Expr::BinExpr(bin) => match bin.op_kind()? {
218 ast::BinOp::NegatedEqualityTest => bin.replace_op(T![==]).map(|it| it.into()),
219 ast::BinOp::EqualityTest => bin.replace_op(T![!=]).map(|it| it.into()),
220 // Parenthesize composite boolean expressions before prefixing `!`
221 ast::BinOp::BooleanAnd | ast::BinOp::BooleanOr => {
222 Some(make::expr_prefix(T![!], make::expr_paren(expr.clone())))
223 }
224 _ => None,
225 },
226 ast::Expr::MethodCallExpr(mce) => {
227 let receiver = mce.receiver()?;
228 let method = mce.name_ref()?;
229 let arg_list = mce.arg_list()?;
230
231 let method = match method.text() {
232 "is_some" => "is_none",
233 "is_none" => "is_some",
234 "is_ok" => "is_err",
235 "is_err" => "is_ok",
236 _ => return None,
237 };
238 Some(make::expr_method_call(receiver, method, arg_list))
239 }
240 ast::Expr::PrefixExpr(pe) if pe.op_kind()? == ast::PrefixOp::Not => {
241 if let ast::Expr::ParenExpr(parexpr) = pe.expr()? {
242 parexpr.expr()
243 } else {
244 pe.expr()
245 }
246 }
247 // FIXME:
248 // ast::Expr::Literal(true | false )
249 _ => None,
250 }
251}
252
253pub(crate) fn next_prev() -> impl Iterator<Item = Direction> {
254 [Direction::Next, Direction::Prev].iter().copied()
255}
256
257pub(crate) fn does_pat_match_variant(pat: &ast::Pat, var: &ast::Pat) -> bool {
258 let first_node_text = |pat: &ast::Pat| pat.syntax().first_child().map(|node| node.text());
259
260 let pat_head = match pat {
261 ast::Pat::IdentPat(bind_pat) => {
262 if let Some(p) = bind_pat.pat() {
263 first_node_text(&p)
264 } else {
265 return pat.syntax().text() == var.syntax().text();
266 }
267 }
268 pat => first_node_text(pat),
269 };
270
271 let var_head = first_node_text(var);
272
273 pat_head == var_head
274}
275
276// Uses a syntax-driven approach to find any impl blocks for the struct that
277// exist within the module/file
278//
279// Returns `None` if we've found an existing fn
280//
281// FIXME: change the new fn checking to a more semantic approach when that's more
282// viable (e.g. we process proc macros, etc)
283// FIXME: this partially overlaps with `find_impl_block_*`
284pub(crate) fn find_struct_impl(
285 ctx: &AssistContext,
286 strukt: &ast::Adt,
287 name: &str,
288) -> Option<Option<ast::Impl>> {
289 let db = ctx.db();
290 let module = strukt.syntax().ancestors().find(|node| {
291 ast::Module::can_cast(node.kind()) || ast::SourceFile::can_cast(node.kind())
292 })?;
293
294 let struct_def = match strukt {
295 ast::Adt::Enum(e) => Adt::Enum(ctx.sema.to_def(e)?),
296 ast::Adt::Struct(s) => Adt::Struct(ctx.sema.to_def(s)?),
297 ast::Adt::Union(u) => Adt::Union(ctx.sema.to_def(u)?),
298 };
299
300 let block = module.descendants().filter_map(ast::Impl::cast).find_map(|impl_blk| {
301 let blk = ctx.sema.to_def(&impl_blk)?;
302
303 // FIXME: handle e.g. `struct S<T>; impl<U> S<U> {}`
304 // (we currently use the wrong type parameter)
305 // also we wouldn't want to use e.g. `impl S<u32>`
306
307 let same_ty = match blk.target_ty(db).as_adt() {
308 Some(def) => def == struct_def,
309 None => false,
310 };
311 let not_trait_impl = blk.target_trait(db).is_none();
312
313 if !(same_ty && not_trait_impl) {
314 None
315 } else {
316 Some(impl_blk)
317 }
318 });
319
320 if let Some(ref impl_blk) = block {
321 if has_fn(impl_blk, name) {
322 return None;
323 }
324 }
325
326 Some(block)
327}
328
329fn has_fn(imp: &ast::Impl, rhs_name: &str) -> bool {
330 if let Some(il) = imp.assoc_item_list() {
331 for item in il.assoc_items() {
332 if let ast::AssocItem::Fn(f) = item {
333 if let Some(name) = f.name() {
334 if name.text().eq_ignore_ascii_case(rhs_name) {
335 return true;
336 }
337 }
338 }
339 }
340 }
341
342 false
343}
344
345/// Find the start of the `impl` block for the given `ast::Impl`.
346//
347// FIXME: this partially overlaps with `find_struct_impl`
348pub(crate) fn find_impl_block_start(impl_def: ast::Impl, buf: &mut String) -> Option<TextSize> {
349 buf.push('\n');
350 let start = impl_def.assoc_item_list().and_then(|it| it.l_curly_token())?.text_range().end();
351 Some(start)
352}
353
354/// Find the end of the `impl` block for the given `ast::Impl`.
355//
356// FIXME: this partially overlaps with `find_struct_impl`
357pub(crate) fn find_impl_block_end(impl_def: ast::Impl, buf: &mut String) -> Option<TextSize> {
358 buf.push('\n');
359 let end = impl_def
360 .assoc_item_list()
361 .and_then(|it| it.r_curly_token())?
362 .prev_sibling_or_token()?
363 .text_range()
364 .end();
365 Some(end)
366}
367
368// Generates the surrounding `impl Type { <code> }` including type and lifetime
369// parameters
370pub(crate) fn generate_impl_text(adt: &ast::Adt, code: &str) -> String {
371 generate_impl_text_inner(adt, None, code)
372}
373
374// Generates the surrounding `impl <trait> for Type { <code> }` including type
375// and lifetime parameters
376pub(crate) fn generate_trait_impl_text(adt: &ast::Adt, trait_text: &str, code: &str) -> String {
377 generate_impl_text_inner(adt, Some(trait_text), code)
378}
379
380fn generate_impl_text_inner(adt: &ast::Adt, trait_text: Option<&str>, code: &str) -> String {
381 let generic_params = adt.generic_param_list();
382 let mut buf = String::with_capacity(code.len());
383 buf.push_str("\n\n");
384 adt.attrs()
385 .filter(|attr| attr.as_simple_call().map(|(name, _arg)| name == "cfg").unwrap_or(false))
386 .for_each(|attr| buf.push_str(format!("{}\n", attr.to_string()).as_str()));
387 buf.push_str("impl");
388 if let Some(generic_params) = &generic_params {
389 let lifetimes = generic_params.lifetime_params().map(|lt| format!("{}", lt.syntax()));
390 let type_params = generic_params.type_params().map(|type_param| {
391 let mut buf = String::new();
392 if let Some(it) = type_param.name() {
393 format_to!(buf, "{}", it.syntax());
394 }
395 if let Some(it) = type_param.colon_token() {
396 format_to!(buf, "{} ", it);
397 }
398 if let Some(it) = type_param.type_bound_list() {
399 format_to!(buf, "{}", it.syntax());
400 }
401 buf
402 });
403 let generics = lifetimes.chain(type_params).format(", ");
404 format_to!(buf, "<{}>", generics);
405 }
406 buf.push(' ');
407 if let Some(trait_text) = trait_text {
408 buf.push_str(trait_text);
409 buf.push_str(" for ");
410 }
411 buf.push_str(adt.name().unwrap().text());
412 if let Some(generic_params) = generic_params {
413 let lifetime_params = generic_params
414 .lifetime_params()
415 .filter_map(|it| it.lifetime())
416 .map(|it| SmolStr::from(it.text()));
417 let type_params = generic_params
418 .type_params()
419 .filter_map(|it| it.name())
420 .map(|it| SmolStr::from(it.text()));
421 format_to!(buf, "<{}>", lifetime_params.chain(type_params).format(", "))
422 }
423
424 match adt.where_clause() {
425 Some(where_clause) => {
426 format_to!(buf, "\n{}\n{{\n{}\n}}", where_clause, code);
427 }
428 None => {
429 format_to!(buf, " {{\n{}\n}}", code);
430 }
431 }
432
433 buf
434}