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