aboutsummaryrefslogtreecommitdiff
path: root/crates/assists
diff options
context:
space:
mode:
Diffstat (limited to 'crates/assists')
-rw-r--r--crates/assists/Cargo.toml23
-rw-r--r--crates/assists/src/assist_config.rs30
-rw-r--r--crates/assists/src/assist_context.rs291
-rw-r--r--crates/assists/src/ast_transform.rs200
-rw-r--r--crates/assists/src/handlers/add_custom_impl.rs208
-rw-r--r--crates/assists/src/handlers/add_explicit_type.rs211
-rw-r--r--crates/assists/src/handlers/add_missing_impl_members.rs766
-rw-r--r--crates/assists/src/handlers/add_turbo_fish.rs164
-rw-r--r--crates/assists/src/handlers/apply_demorgan.rs93
-rw-r--r--crates/assists/src/handlers/auto_import.rs1088
-rw-r--r--crates/assists/src/handlers/change_return_type_to_result.rs998
-rw-r--r--crates/assists/src/handlers/change_visibility.rs200
-rw-r--r--crates/assists/src/handlers/early_return.rs515
-rw-r--r--crates/assists/src/handlers/expand_glob_import.rs385
-rw-r--r--crates/assists/src/handlers/extract_struct_from_enum_variant.rs322
-rw-r--r--crates/assists/src/handlers/extract_variable.rs588
-rw-r--r--crates/assists/src/handlers/fill_match_arms.rs747
-rw-r--r--crates/assists/src/handlers/fix_visibility.rs607
-rw-r--r--crates/assists/src/handlers/flip_binexpr.rs142
-rw-r--r--crates/assists/src/handlers/flip_comma.rs84
-rw-r--r--crates/assists/src/handlers/flip_trait_bound.rs121
-rw-r--r--crates/assists/src/handlers/generate_derive.rs132
-rw-r--r--crates/assists/src/handlers/generate_from_impl_for_enum.rs200
-rw-r--r--crates/assists/src/handlers/generate_function.rs1058
-rw-r--r--crates/assists/src/handlers/generate_impl.rs110
-rw-r--r--crates/assists/src/handlers/generate_new.rs421
-rw-r--r--crates/assists/src/handlers/inline_local_variable.rs695
-rw-r--r--crates/assists/src/handlers/introduce_named_lifetime.rs318
-rw-r--r--crates/assists/src/handlers/invert_if.rs109
-rw-r--r--crates/assists/src/handlers/merge_imports.rs321
-rw-r--r--crates/assists/src/handlers/merge_match_arms.rs248
-rw-r--r--crates/assists/src/handlers/move_bounds.rs152
-rw-r--r--crates/assists/src/handlers/move_guard.rs293
-rw-r--r--crates/assists/src/handlers/raw_string.rs504
-rw-r--r--crates/assists/src/handlers/remove_dbg.rs205
-rw-r--r--crates/assists/src/handlers/remove_mut.rs37
-rw-r--r--crates/assists/src/handlers/reorder_fields.rs220
-rw-r--r--crates/assists/src/handlers/replace_if_let_with_match.rs257
-rw-r--r--crates/assists/src/handlers/replace_let_with_if_let.rs100
-rw-r--r--crates/assists/src/handlers/replace_qualified_name_with_use.rs680
-rw-r--r--crates/assists/src/handlers/replace_unwrap_with_match.rs187
-rw-r--r--crates/assists/src/handlers/split_import.rs79
-rw-r--r--crates/assists/src/handlers/unwrap_block.rs517
-rw-r--r--crates/assists/src/lib.rs224
-rw-r--r--crates/assists/src/tests.rs179
-rw-r--r--crates/assists/src/tests/generated.rs891
-rw-r--r--crates/assists/src/utils.rs313
-rw-r--r--crates/assists/src/utils/insert_use.rs546
48 files changed, 16779 insertions, 0 deletions
diff --git a/crates/assists/Cargo.toml b/crates/assists/Cargo.toml
new file mode 100644
index 000000000..a560a35c7
--- /dev/null
+++ b/crates/assists/Cargo.toml
@@ -0,0 +1,23 @@
1[package]
2name = "assists"
3version = "0.0.0"
4license = "MIT OR Apache-2.0"
5authors = ["rust-analyzer developers"]
6edition = "2018"
7
8[lib]
9doctest = false
10
11[dependencies]
12rustc-hash = "1.1.0"
13itertools = "0.9.0"
14either = "1.5.3"
15
16stdx = { path = "../stdx" }
17syntax = { path = "../syntax" }
18text_edit = { path = "../text_edit" }
19profile = { path = "../profile" }
20base_db = { path = "../base_db" }
21ide_db = { path = "../ide_db" }
22hir = { path = "../hir" }
23test_utils = { path = "../test_utils" }
diff --git a/crates/assists/src/assist_config.rs b/crates/assists/src/assist_config.rs
new file mode 100644
index 000000000..cda2abfb9
--- /dev/null
+++ b/crates/assists/src/assist_config.rs
@@ -0,0 +1,30 @@
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 crate::AssistKind;
8
9#[derive(Clone, Debug, PartialEq, Eq)]
10pub struct AssistConfig {
11 pub snippet_cap: Option<SnippetCap>,
12 pub allowed: Option<Vec<AssistKind>>,
13}
14
15impl AssistConfig {
16 pub fn allow_snippets(&mut self, yes: bool) {
17 self.snippet_cap = if yes { Some(SnippetCap { _private: () }) } else { None }
18 }
19}
20
21#[derive(Clone, Copy, Debug, PartialEq, Eq)]
22pub struct SnippetCap {
23 _private: (),
24}
25
26impl Default for AssistConfig {
27 fn default() -> Self {
28 AssistConfig { snippet_cap: Some(SnippetCap { _private: () }), allowed: None }
29 }
30}
diff --git a/crates/assists/src/assist_context.rs b/crates/assists/src/assist_context.rs
new file mode 100644
index 000000000..79574b9ac
--- /dev/null
+++ b/crates/assists/src/assist_context.rs
@@ -0,0 +1,291 @@
1//! See `AssistContext`
2
3use std::mem;
4
5use algo::find_covering_element;
6use base_db::{FileId, FileRange};
7use hir::Semantics;
8use ide_db::{
9 source_change::{SourceChange, SourceFileEdit},
10 RootDatabase,
11};
12use syntax::{
13 algo::{self, find_node_at_offset, SyntaxRewriter},
14 AstNode, SourceFile, SyntaxElement, SyntaxKind, SyntaxToken, TextRange, TextSize,
15 TokenAtOffset,
16};
17use text_edit::{TextEdit, TextEditBuilder};
18
19use crate::{
20 assist_config::{AssistConfig, SnippetCap},
21 Assist, AssistId, AssistKind, GroupLabel, ResolvedAssist,
22};
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 pub(crate) fn source_file(&self) -> &SourceFile {
76 &self.source_file
77 }
78
79 // NB, this ignores active selection.
80 pub(crate) fn offset(&self) -> TextSize {
81 self.frange.range.start()
82 }
83
84 pub(crate) fn token_at_offset(&self) -> TokenAtOffset<SyntaxToken> {
85 self.source_file.syntax().token_at_offset(self.offset())
86 }
87 pub(crate) fn find_token_at_offset(&self, kind: SyntaxKind) -> Option<SyntaxToken> {
88 self.token_at_offset().find(|it| it.kind() == kind)
89 }
90 pub(crate) fn find_node_at_offset<N: AstNode>(&self) -> Option<N> {
91 find_node_at_offset(self.source_file.syntax(), self.offset())
92 }
93 pub(crate) fn find_node_at_offset_with_descend<N: AstNode>(&self) -> Option<N> {
94 self.sema.find_node_at_offset_with_descend(self.source_file.syntax(), self.offset())
95 }
96 pub(crate) fn covering_element(&self) -> SyntaxElement {
97 find_covering_element(self.source_file.syntax(), self.frange.range)
98 }
99 // FIXME: remove
100 pub(crate) fn covering_node_for_range(&self, range: TextRange) -> SyntaxElement {
101 find_covering_element(self.source_file.syntax(), range)
102 }
103}
104
105pub(crate) struct Assists {
106 resolve: bool,
107 file: FileId,
108 buf: Vec<(Assist, Option<SourceChange>)>,
109 allowed: Option<Vec<AssistKind>>,
110}
111
112impl Assists {
113 pub(crate) fn new_resolved(ctx: &AssistContext) -> Assists {
114 Assists {
115 resolve: true,
116 file: ctx.frange.file_id,
117 buf: Vec::new(),
118 allowed: ctx.config.allowed.clone(),
119 }
120 }
121
122 pub(crate) fn new_unresolved(ctx: &AssistContext) -> Assists {
123 Assists {
124 resolve: false,
125 file: ctx.frange.file_id,
126 buf: Vec::new(),
127 allowed: ctx.config.allowed.clone(),
128 }
129 }
130
131 pub(crate) fn finish_unresolved(self) -> Vec<Assist> {
132 assert!(!self.resolve);
133 self.finish()
134 .into_iter()
135 .map(|(label, edit)| {
136 assert!(edit.is_none());
137 label
138 })
139 .collect()
140 }
141
142 pub(crate) fn finish_resolved(self) -> Vec<ResolvedAssist> {
143 assert!(self.resolve);
144 self.finish()
145 .into_iter()
146 .map(|(label, edit)| ResolvedAssist { assist: label, source_change: edit.unwrap() })
147 .collect()
148 }
149
150 pub(crate) fn add(
151 &mut self,
152 id: AssistId,
153 label: impl Into<String>,
154 target: TextRange,
155 f: impl FnOnce(&mut AssistBuilder),
156 ) -> Option<()> {
157 if !self.is_allowed(&id) {
158 return None;
159 }
160 let label = Assist::new(id, label.into(), None, target);
161 self.add_impl(label, f)
162 }
163
164 pub(crate) fn add_group(
165 &mut self,
166 group: &GroupLabel,
167 id: AssistId,
168 label: impl Into<String>,
169 target: TextRange,
170 f: impl FnOnce(&mut AssistBuilder),
171 ) -> Option<()> {
172 if !self.is_allowed(&id) {
173 return None;
174 }
175
176 let label = Assist::new(id, label.into(), Some(group.clone()), target);
177 self.add_impl(label, f)
178 }
179
180 fn add_impl(&mut self, label: Assist, f: impl FnOnce(&mut AssistBuilder)) -> Option<()> {
181 let source_change = if self.resolve {
182 let mut builder = AssistBuilder::new(self.file);
183 f(&mut builder);
184 Some(builder.finish())
185 } else {
186 None
187 };
188
189 self.buf.push((label, source_change));
190 Some(())
191 }
192
193 fn finish(mut self) -> Vec<(Assist, Option<SourceChange>)> {
194 self.buf.sort_by_key(|(label, _edit)| label.target.len());
195 self.buf
196 }
197
198 fn is_allowed(&self, id: &AssistId) -> bool {
199 match &self.allowed {
200 Some(allowed) => allowed.iter().any(|kind| kind.contains(id.1)),
201 None => true,
202 }
203 }
204}
205
206pub(crate) struct AssistBuilder {
207 edit: TextEditBuilder,
208 file_id: FileId,
209 is_snippet: bool,
210 change: SourceChange,
211}
212
213impl AssistBuilder {
214 pub(crate) fn new(file_id: FileId) -> AssistBuilder {
215 AssistBuilder {
216 edit: TextEdit::builder(),
217 file_id,
218 is_snippet: false,
219 change: SourceChange::default(),
220 }
221 }
222
223 pub(crate) fn edit_file(&mut self, file_id: FileId) {
224 self.file_id = file_id;
225 }
226
227 fn commit(&mut self) {
228 let edit = mem::take(&mut self.edit).finish();
229 if !edit.is_empty() {
230 let new_edit = SourceFileEdit { file_id: self.file_id, edit };
231 assert!(!self.change.source_file_edits.iter().any(|it| it.file_id == new_edit.file_id));
232 self.change.source_file_edits.push(new_edit);
233 }
234 }
235
236 /// Remove specified `range` of text.
237 pub(crate) fn delete(&mut self, range: TextRange) {
238 self.edit.delete(range)
239 }
240 /// Append specified `text` at the given `offset`
241 pub(crate) fn insert(&mut self, offset: TextSize, text: impl Into<String>) {
242 self.edit.insert(offset, text.into())
243 }
244 /// Append specified `snippet` at the given `offset`
245 pub(crate) fn insert_snippet(
246 &mut self,
247 _cap: SnippetCap,
248 offset: TextSize,
249 snippet: impl Into<String>,
250 ) {
251 self.is_snippet = true;
252 self.insert(offset, snippet);
253 }
254 /// Replaces specified `range` of text with a given string.
255 pub(crate) fn replace(&mut self, range: TextRange, replace_with: impl Into<String>) {
256 self.edit.replace(range, replace_with.into())
257 }
258 /// Replaces specified `range` of text with a given `snippet`.
259 pub(crate) fn replace_snippet(
260 &mut self,
261 _cap: SnippetCap,
262 range: TextRange,
263 snippet: impl Into<String>,
264 ) {
265 self.is_snippet = true;
266 self.replace(range, snippet);
267 }
268 pub(crate) fn replace_ast<N: AstNode>(&mut self, old: N, new: N) {
269 algo::diff(old.syntax(), new.syntax()).into_text_edit(&mut self.edit)
270 }
271 pub(crate) fn rewrite(&mut self, rewriter: SyntaxRewriter) {
272 let node = rewriter.rewrite_root().unwrap();
273 let new = rewriter.rewrite(&node);
274 algo::diff(&node, &new).into_text_edit(&mut self.edit);
275 }
276
277 // FIXME: kill this API
278 /// Get access to the raw `TextEditBuilder`.
279 pub(crate) fn text_edit_builder(&mut self) -> &mut TextEditBuilder {
280 &mut self.edit
281 }
282
283 fn finish(mut self) -> SourceChange {
284 self.commit();
285 let mut change = mem::take(&mut self.change);
286 if self.is_snippet {
287 change.is_snippet = true;
288 }
289 change
290 }
291}
diff --git a/crates/assists/src/ast_transform.rs b/crates/assists/src/ast_transform.rs
new file mode 100644
index 000000000..5216862ba
--- /dev/null
+++ b/crates/assists/src/ast_transform.rs
@@ -0,0 +1,200 @@
1//! `AstTransformer`s are functions that replace nodes in an AST and can be easily combined.
2use rustc_hash::FxHashMap;
3
4use hir::{HirDisplay, PathResolution, SemanticsScope};
5use syntax::{
6 algo::SyntaxRewriter,
7 ast::{self, AstNode},
8};
9
10pub fn apply<'a, N: AstNode>(transformer: &dyn AstTransform<'a>, node: N) -> N {
11 SyntaxRewriter::from_fn(|element| match element {
12 syntax::SyntaxElement::Node(n) => {
13 let replacement = transformer.get_substitution(&n)?;
14 Some(replacement.into())
15 }
16 _ => None,
17 })
18 .rewrite_ast(&node)
19}
20
21pub trait AstTransform<'a> {
22 fn get_substitution(&self, node: &syntax::SyntaxNode) -> Option<syntax::SyntaxNode>;
23
24 fn chain_before(self, other: Box<dyn AstTransform<'a> + 'a>) -> Box<dyn AstTransform<'a> + 'a>;
25 fn or<T: AstTransform<'a> + 'a>(self, other: T) -> Box<dyn AstTransform<'a> + 'a>
26 where
27 Self: Sized + 'a,
28 {
29 self.chain_before(Box::new(other))
30 }
31}
32
33struct NullTransformer;
34
35impl<'a> AstTransform<'a> for NullTransformer {
36 fn get_substitution(&self, _node: &syntax::SyntaxNode) -> Option<syntax::SyntaxNode> {
37 None
38 }
39 fn chain_before(self, other: Box<dyn AstTransform<'a> + 'a>) -> Box<dyn AstTransform<'a> + 'a> {
40 other
41 }
42}
43
44pub struct SubstituteTypeParams<'a> {
45 source_scope: &'a SemanticsScope<'a>,
46 substs: FxHashMap<hir::TypeParam, ast::Type>,
47 previous: Box<dyn AstTransform<'a> + 'a>,
48}
49
50impl<'a> SubstituteTypeParams<'a> {
51 pub fn for_trait_impl(
52 source_scope: &'a SemanticsScope<'a>,
53 // FIXME: there's implicit invariant that `trait_` and `source_scope` match...
54 trait_: hir::Trait,
55 impl_def: ast::Impl,
56 ) -> SubstituteTypeParams<'a> {
57 let substs = get_syntactic_substs(impl_def).unwrap_or_default();
58 let generic_def: hir::GenericDef = trait_.into();
59 let substs_by_param: FxHashMap<_, _> = generic_def
60 .params(source_scope.db)
61 .into_iter()
62 // this is a trait impl, so we need to skip the first type parameter -- this is a bit hacky
63 .skip(1)
64 // The actual list of trait type parameters may be longer than the one
65 // used in the `impl` block due to trailing default type parameters.
66 // For that case we extend the `substs` with an empty iterator so we
67 // can still hit those trailing values and check if they actually have
68 // a default type. If they do, go for that type from `hir` to `ast` so
69 // the resulting change can be applied correctly.
70 .zip(substs.into_iter().map(Some).chain(std::iter::repeat(None)))
71 .filter_map(|(k, v)| match v {
72 Some(v) => Some((k, v)),
73 None => {
74 let default = k.default(source_scope.db)?;
75 Some((
76 k,
77 ast::make::ty(
78 &default
79 .display_source_code(source_scope.db, source_scope.module()?.into())
80 .ok()?,
81 ),
82 ))
83 }
84 })
85 .collect();
86 return SubstituteTypeParams {
87 source_scope,
88 substs: substs_by_param,
89 previous: Box::new(NullTransformer),
90 };
91
92 // FIXME: It would probably be nicer if we could get this via HIR (i.e. get the
93 // trait ref, and then go from the types in the substs back to the syntax).
94 fn get_syntactic_substs(impl_def: ast::Impl) -> Option<Vec<ast::Type>> {
95 let target_trait = impl_def.trait_()?;
96 let path_type = match target_trait {
97 ast::Type::PathType(path) => path,
98 _ => return None,
99 };
100 let generic_arg_list = path_type.path()?.segment()?.generic_arg_list()?;
101
102 let mut result = Vec::new();
103 for generic_arg in generic_arg_list.generic_args() {
104 match generic_arg {
105 ast::GenericArg::TypeArg(type_arg) => result.push(type_arg.ty()?),
106 ast::GenericArg::AssocTypeArg(_)
107 | ast::GenericArg::LifetimeArg(_)
108 | ast::GenericArg::ConstArg(_) => (),
109 }
110 }
111
112 Some(result)
113 }
114 }
115 fn get_substitution_inner(&self, node: &syntax::SyntaxNode) -> Option<syntax::SyntaxNode> {
116 let type_ref = ast::Type::cast(node.clone())?;
117 let path = match &type_ref {
118 ast::Type::PathType(path_type) => path_type.path()?,
119 _ => return None,
120 };
121 let resolution = self.source_scope.speculative_resolve(&path)?;
122 match resolution {
123 hir::PathResolution::TypeParam(tp) => Some(self.substs.get(&tp)?.syntax().clone()),
124 _ => None,
125 }
126 }
127}
128
129impl<'a> AstTransform<'a> for SubstituteTypeParams<'a> {
130 fn get_substitution(&self, node: &syntax::SyntaxNode) -> Option<syntax::SyntaxNode> {
131 self.get_substitution_inner(node).or_else(|| self.previous.get_substitution(node))
132 }
133 fn chain_before(self, other: Box<dyn AstTransform<'a> + 'a>) -> Box<dyn AstTransform<'a> + 'a> {
134 Box::new(SubstituteTypeParams { previous: other, ..self })
135 }
136}
137
138pub struct QualifyPaths<'a> {
139 target_scope: &'a SemanticsScope<'a>,
140 source_scope: &'a SemanticsScope<'a>,
141 previous: Box<dyn AstTransform<'a> + 'a>,
142}
143
144impl<'a> QualifyPaths<'a> {
145 pub fn new(target_scope: &'a SemanticsScope<'a>, source_scope: &'a SemanticsScope<'a>) -> Self {
146 Self { target_scope, source_scope, previous: Box::new(NullTransformer) }
147 }
148
149 fn get_substitution_inner(&self, node: &syntax::SyntaxNode) -> Option<syntax::SyntaxNode> {
150 // FIXME handle value ns?
151 let from = self.target_scope.module()?;
152 let p = ast::Path::cast(node.clone())?;
153 if p.segment().and_then(|s| s.param_list()).is_some() {
154 // don't try to qualify `Fn(Foo) -> Bar` paths, they are in prelude anyway
155 return None;
156 }
157 let resolution = self.source_scope.speculative_resolve(&p)?;
158 match resolution {
159 PathResolution::Def(def) => {
160 let found_path = from.find_use_path(self.source_scope.db.upcast(), def)?;
161 let mut path = path_to_ast(found_path);
162
163 let type_args = p
164 .segment()
165 .and_then(|s| s.generic_arg_list())
166 .map(|arg_list| apply(self, arg_list));
167 if let Some(type_args) = type_args {
168 let last_segment = path.segment().unwrap();
169 path = path.with_segment(last_segment.with_type_args(type_args))
170 }
171
172 Some(path.syntax().clone())
173 }
174 PathResolution::Local(_)
175 | PathResolution::TypeParam(_)
176 | PathResolution::SelfType(_) => None,
177 PathResolution::Macro(_) => None,
178 PathResolution::AssocItem(_) => None,
179 }
180 }
181}
182
183impl<'a> AstTransform<'a> for QualifyPaths<'a> {
184 fn get_substitution(&self, node: &syntax::SyntaxNode) -> Option<syntax::SyntaxNode> {
185 self.get_substitution_inner(node).or_else(|| self.previous.get_substitution(node))
186 }
187 fn chain_before(self, other: Box<dyn AstTransform<'a> + 'a>) -> Box<dyn AstTransform<'a> + 'a> {
188 Box::new(QualifyPaths { previous: other, ..self })
189 }
190}
191
192pub(crate) fn path_to_ast(path: hir::ModPath) -> ast::Path {
193 let parse = ast::SourceFile::parse(&path.to_string());
194 parse
195 .tree()
196 .syntax()
197 .descendants()
198 .find_map(ast::Path::cast)
199 .unwrap_or_else(|| panic!("failed to parse path {:?}, `{}`", path, path))
200}
diff --git a/crates/assists/src/handlers/add_custom_impl.rs b/crates/assists/src/handlers/add_custom_impl.rs
new file mode 100644
index 000000000..8757fa33f
--- /dev/null
+++ b/crates/assists/src/handlers/add_custom_impl.rs
@@ -0,0 +1,208 @@
1use itertools::Itertools;
2use syntax::{
3 ast::{self, AstNode},
4 Direction, SmolStr,
5 SyntaxKind::{IDENT, WHITESPACE},
6 TextRange, TextSize,
7};
8
9use crate::{
10 assist_context::{AssistContext, Assists},
11 AssistId, AssistKind,
12};
13
14// Assist: add_custom_impl
15//
16// Adds impl block for derived trait.
17//
18// ```
19// #[derive(Deb<|>ug, Display)]
20// struct S;
21// ```
22// ->
23// ```
24// #[derive(Display)]
25// struct S;
26//
27// impl Debug for S {
28// $0
29// }
30// ```
31pub(crate) fn add_custom_impl(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
32 let attr = ctx.find_node_at_offset::<ast::Attr>()?;
33 let input = attr.token_tree()?;
34
35 let attr_name = attr
36 .syntax()
37 .descendants_with_tokens()
38 .filter(|t| t.kind() == IDENT)
39 .find_map(|i| i.into_token())
40 .filter(|t| *t.text() == "derive")?
41 .text()
42 .clone();
43
44 let trait_token =
45 ctx.token_at_offset().find(|t| t.kind() == IDENT && *t.text() != attr_name)?;
46
47 let annotated = attr.syntax().siblings(Direction::Next).find_map(ast::Name::cast)?;
48 let annotated_name = annotated.syntax().text().to_string();
49 let start_offset = annotated.syntax().parent()?.text_range().end();
50
51 let label =
52 format!("Add custom impl `{}` for `{}`", trait_token.text().as_str(), annotated_name);
53
54 let target = attr.syntax().text_range();
55 acc.add(AssistId("add_custom_impl", AssistKind::Refactor), label, target, |builder| {
56 let new_attr_input = input
57 .syntax()
58 .descendants_with_tokens()
59 .filter(|t| t.kind() == IDENT)
60 .filter_map(|t| t.into_token().map(|t| t.text().clone()))
61 .filter(|t| t != trait_token.text())
62 .collect::<Vec<SmolStr>>();
63 let has_more_derives = !new_attr_input.is_empty();
64
65 if has_more_derives {
66 let new_attr_input = format!("({})", new_attr_input.iter().format(", "));
67 builder.replace(input.syntax().text_range(), new_attr_input);
68 } else {
69 let attr_range = attr.syntax().text_range();
70 builder.delete(attr_range);
71
72 let line_break_range = attr
73 .syntax()
74 .next_sibling_or_token()
75 .filter(|t| t.kind() == WHITESPACE)
76 .map(|t| t.text_range())
77 .unwrap_or_else(|| TextRange::new(TextSize::from(0), TextSize::from(0)));
78 builder.delete(line_break_range);
79 }
80
81 match ctx.config.snippet_cap {
82 Some(cap) => {
83 builder.insert_snippet(
84 cap,
85 start_offset,
86 format!("\n\nimpl {} for {} {{\n $0\n}}", trait_token, annotated_name),
87 );
88 }
89 None => {
90 builder.insert(
91 start_offset,
92 format!("\n\nimpl {} for {} {{\n\n}}", trait_token, annotated_name),
93 );
94 }
95 }
96 })
97}
98
99#[cfg(test)]
100mod tests {
101 use crate::tests::{check_assist, check_assist_not_applicable};
102
103 use super::*;
104
105 #[test]
106 fn add_custom_impl_for_unique_input() {
107 check_assist(
108 add_custom_impl,
109 "
110#[derive(Debu<|>g)]
111struct Foo {
112 bar: String,
113}
114 ",
115 "
116struct Foo {
117 bar: String,
118}
119
120impl Debug for Foo {
121 $0
122}
123 ",
124 )
125 }
126
127 #[test]
128 fn add_custom_impl_for_with_visibility_modifier() {
129 check_assist(
130 add_custom_impl,
131 "
132#[derive(Debug<|>)]
133pub struct Foo {
134 bar: String,
135}
136 ",
137 "
138pub struct Foo {
139 bar: String,
140}
141
142impl Debug for Foo {
143 $0
144}
145 ",
146 )
147 }
148
149 #[test]
150 fn add_custom_impl_when_multiple_inputs() {
151 check_assist(
152 add_custom_impl,
153 "
154#[derive(Display, Debug<|>, Serialize)]
155struct Foo {}
156 ",
157 "
158#[derive(Display, Serialize)]
159struct Foo {}
160
161impl Debug for Foo {
162 $0
163}
164 ",
165 )
166 }
167
168 #[test]
169 fn test_ignore_derive_macro_without_input() {
170 check_assist_not_applicable(
171 add_custom_impl,
172 "
173#[derive(<|>)]
174struct Foo {}
175 ",
176 )
177 }
178
179 #[test]
180 fn test_ignore_if_cursor_on_param() {
181 check_assist_not_applicable(
182 add_custom_impl,
183 "
184#[derive<|>(Debug)]
185struct Foo {}
186 ",
187 );
188
189 check_assist_not_applicable(
190 add_custom_impl,
191 "
192#[derive(Debug)<|>]
193struct Foo {}
194 ",
195 )
196 }
197
198 #[test]
199 fn test_ignore_if_not_derive() {
200 check_assist_not_applicable(
201 add_custom_impl,
202 "
203#[allow(non_camel_<|>case_types)]
204struct Foo {}
205 ",
206 )
207 }
208}
diff --git a/crates/assists/src/handlers/add_explicit_type.rs b/crates/assists/src/handlers/add_explicit_type.rs
new file mode 100644
index 000000000..563cbf505
--- /dev/null
+++ b/crates/assists/src/handlers/add_explicit_type.rs
@@ -0,0 +1,211 @@
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<|> = 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<|> = 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<|> = 1; }", "fn f() { let a: i32 = 1; }");
90 }
91
92 #[test]
93 fn add_explicit_type_works_for_underscore() {
94 check_assist(
95 add_explicit_type,
96 "fn f() { let a<|>: _ = 1; }",
97 "fn f() { let a: i32 = 1; }",
98 );
99 }
100
101 #[test]
102 fn add_explicit_type_works_for_nested_underscore() {
103 check_assist(
104 add_explicit_type,
105 r#"
106 enum Option<T> {
107 Some(T),
108 None
109 }
110
111 fn f() {
112 let a<|>: Option<_> = Option::Some(1);
113 }"#,
114 r#"
115 enum Option<T> {
116 Some(T),
117 None
118 }
119
120 fn f() {
121 let a: Option<i32> = Option::Some(1);
122 }"#,
123 );
124 }
125
126 #[test]
127 fn add_explicit_type_works_for_macro_call() {
128 check_assist(
129 add_explicit_type,
130 r"macro_rules! v { () => {0u64} } fn f() { let a<|> = v!(); }",
131 r"macro_rules! v { () => {0u64} } fn f() { let a: u64 = v!(); }",
132 );
133 }
134
135 #[test]
136 fn add_explicit_type_works_for_macro_call_recursive() {
137 check_assist(
138 add_explicit_type,
139 r#"macro_rules! u { () => {0u64} } macro_rules! v { () => {u!()} } fn f() { let a<|> = v!(); }"#,
140 r#"macro_rules! u { () => {0u64} } macro_rules! v { () => {u!()} } fn f() { let a: u64 = v!(); }"#,
141 );
142 }
143
144 #[test]
145 fn add_explicit_type_not_applicable_if_ty_not_inferred() {
146 check_assist_not_applicable(add_explicit_type, "fn f() { let a<|> = None; }");
147 }
148
149 #[test]
150 fn add_explicit_type_not_applicable_if_ty_already_specified() {
151 check_assist_not_applicable(add_explicit_type, "fn f() { let a<|>: i32 = 1; }");
152 }
153
154 #[test]
155 fn add_explicit_type_not_applicable_if_specified_ty_is_tuple() {
156 check_assist_not_applicable(add_explicit_type, "fn f() { let a<|>: (i32, i32) = (3, 4); }");
157 }
158
159 #[test]
160 fn add_explicit_type_not_applicable_if_cursor_after_equals() {
161 check_assist_not_applicable(
162 add_explicit_type,
163 "fn f() {let a =<|> match 1 {2 => 3, 3 => 5};}",
164 )
165 }
166
167 #[test]
168 fn add_explicit_type_not_applicable_if_cursor_before_let() {
169 check_assist_not_applicable(
170 add_explicit_type,
171 "fn f() <|>{let a = match 1 {2 => 3, 3 => 5};}",
172 )
173 }
174
175 #[test]
176 fn closure_parameters_are_not_added() {
177 check_assist_not_applicable(
178 add_explicit_type,
179 r#"
180fn main() {
181 let multiply_by_two<|> = |i| i * 3;
182 let six = multiply_by_two(2);
183}"#,
184 )
185 }
186
187 #[test]
188 fn default_generics_should_not_be_added() {
189 check_assist(
190 add_explicit_type,
191 r#"
192struct Test<K, T = u8> {
193 k: K,
194 t: T,
195}
196
197fn main() {
198 let test<|> = Test { t: 23u8, k: 33 };
199}"#,
200 r#"
201struct Test<K, T = u8> {
202 k: K,
203 t: T,
204}
205
206fn main() {
207 let test: Test<i32> = Test { t: 23u8, k: 33 };
208}"#,
209 );
210 }
211}
diff --git a/crates/assists/src/handlers/add_missing_impl_members.rs b/crates/assists/src/handlers/add_missing_impl_members.rs
new file mode 100644
index 000000000..83a2ada9a
--- /dev/null
+++ b/crates/assists/src/handlers/add_missing_impl_members.rs
@@ -0,0 +1,766 @@
1use hir::HasSource;
2use syntax::{
3 ast::{
4 self,
5 edit::{self, AstNodeEdit, IndentLevel},
6 make, AstNode, NameOwner,
7 },
8 SmolStr,
9};
10
11use crate::{
12 assist_context::{AssistContext, Assists},
13 ast_transform::{self, AstTransform, QualifyPaths, SubstituteTypeParams},
14 utils::{get_missing_assoc_items, render_snippet, resolve_target_trait, Cursor},
15 AssistId, AssistKind,
16};
17
18#[derive(PartialEq)]
19enum AddMissingImplMembersMode {
20 DefaultMethodsOnly,
21 NoDefaultMethods,
22}
23
24// Assist: add_impl_missing_members
25//
26// Adds scaffold for required impl members.
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//
37// }
38// ```
39// ->
40// ```
41// trait Trait<T> {
42// Type X;
43// fn foo(&self) -> T;
44// fn bar(&self) {}
45// }
46//
47// impl Trait<u32> for () {
48// fn foo(&self) -> u32 {
49// ${0:todo!()}
50// }
51// }
52// ```
53pub(crate) fn add_missing_impl_members(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
54 add_missing_impl_members_inner(
55 acc,
56 ctx,
57 AddMissingImplMembersMode::NoDefaultMethods,
58 "add_impl_missing_members",
59 "Implement missing members",
60 )
61}
62
63// Assist: add_impl_default_members
64//
65// Adds scaffold for overriding default impl members.
66//
67// ```
68// trait Trait {
69// Type X;
70// fn foo(&self);
71// fn bar(&self) {}
72// }
73//
74// impl Trait for () {
75// Type X = ();
76// fn foo(&self) {}<|>
77//
78// }
79// ```
80// ->
81// ```
82// trait Trait {
83// Type X;
84// fn foo(&self);
85// fn bar(&self) {}
86// }
87//
88// impl Trait for () {
89// Type X = ();
90// fn foo(&self) {}
91//
92// $0fn bar(&self) {}
93// }
94// ```
95pub(crate) fn add_missing_default_members(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
96 add_missing_impl_members_inner(
97 acc,
98 ctx,
99 AddMissingImplMembersMode::DefaultMethodsOnly,
100 "add_impl_default_members",
101 "Implement default members",
102 )
103}
104
105fn add_missing_impl_members_inner(
106 acc: &mut Assists,
107 ctx: &AssistContext,
108 mode: AddMissingImplMembersMode,
109 assist_id: &'static str,
110 label: &'static str,
111) -> Option<()> {
112 let _p = profile::span("add_missing_impl_members_inner");
113 let impl_def = ctx.find_node_at_offset::<ast::Impl>()?;
114 let impl_item_list = impl_def.assoc_item_list()?;
115
116 let trait_ = resolve_target_trait(&ctx.sema, &impl_def)?;
117
118 let def_name = |item: &ast::AssocItem| -> Option<SmolStr> {
119 match item {
120 ast::AssocItem::Fn(def) => def.name(),
121 ast::AssocItem::TypeAlias(def) => def.name(),
122 ast::AssocItem::Const(def) => def.name(),
123 ast::AssocItem::MacroCall(_) => None,
124 }
125 .map(|it| it.text().clone())
126 };
127
128 let missing_items = get_missing_assoc_items(&ctx.sema, &impl_def)
129 .iter()
130 .map(|i| match i {
131 hir::AssocItem::Function(i) => ast::AssocItem::Fn(i.source(ctx.db()).value),
132 hir::AssocItem::TypeAlias(i) => ast::AssocItem::TypeAlias(i.source(ctx.db()).value),
133 hir::AssocItem::Const(i) => ast::AssocItem::Const(i.source(ctx.db()).value),
134 })
135 .filter(|t| def_name(&t).is_some())
136 .filter(|t| match t {
137 ast::AssocItem::Fn(def) => match mode {
138 AddMissingImplMembersMode::DefaultMethodsOnly => def.body().is_some(),
139 AddMissingImplMembersMode::NoDefaultMethods => def.body().is_none(),
140 },
141 _ => mode == AddMissingImplMembersMode::NoDefaultMethods,
142 })
143 .collect::<Vec<_>>();
144
145 if missing_items.is_empty() {
146 return None;
147 }
148
149 let target = impl_def.syntax().text_range();
150 acc.add(AssistId(assist_id, AssistKind::QuickFix), label, target, |builder| {
151 let n_existing_items = impl_item_list.assoc_items().count();
152 let source_scope = ctx.sema.scope_for_def(trait_);
153 let target_scope = ctx.sema.scope(impl_item_list.syntax());
154 let ast_transform = QualifyPaths::new(&target_scope, &source_scope)
155 .or(SubstituteTypeParams::for_trait_impl(&source_scope, trait_, impl_def));
156 let items = missing_items
157 .into_iter()
158 .map(|it| ast_transform::apply(&*ast_transform, it))
159 .map(|it| match it {
160 ast::AssocItem::Fn(def) => ast::AssocItem::Fn(add_body(def)),
161 ast::AssocItem::TypeAlias(def) => ast::AssocItem::TypeAlias(def.remove_bounds()),
162 _ => it,
163 })
164 .map(|it| edit::remove_attrs_and_docs(&it));
165 let new_impl_item_list = impl_item_list.append_items(items);
166 let first_new_item = new_impl_item_list.assoc_items().nth(n_existing_items).unwrap();
167
168 let original_range = impl_item_list.syntax().text_range();
169 match ctx.config.snippet_cap {
170 None => builder.replace(original_range, new_impl_item_list.to_string()),
171 Some(cap) => {
172 let mut cursor = Cursor::Before(first_new_item.syntax());
173 let placeholder;
174 if let ast::AssocItem::Fn(func) = &first_new_item {
175 if let Some(m) = func.syntax().descendants().find_map(ast::MacroCall::cast) {
176 if m.syntax().text() == "todo!()" {
177 placeholder = m;
178 cursor = Cursor::Replace(placeholder.syntax());
179 }
180 }
181 }
182 builder.replace_snippet(
183 cap,
184 original_range,
185 render_snippet(cap, new_impl_item_list.syntax(), cursor),
186 )
187 }
188 };
189 })
190}
191
192fn add_body(fn_def: ast::Fn) -> ast::Fn {
193 if fn_def.body().is_some() {
194 return fn_def;
195 }
196 let body = make::block_expr(None, Some(make::expr_todo())).indent(IndentLevel(1));
197 fn_def.with_body(body)
198}
199
200#[cfg(test)]
201mod tests {
202 use crate::tests::{check_assist, check_assist_not_applicable};
203
204 use super::*;
205
206 #[test]
207 fn test_add_missing_impl_members() {
208 check_assist(
209 add_missing_impl_members,
210 r#"
211trait Foo {
212 type Output;
213
214 const CONST: usize = 42;
215
216 fn foo(&self);
217 fn bar(&self);
218 fn baz(&self);
219}
220
221struct S;
222
223impl Foo for S {
224 fn bar(&self) {}
225<|>
226}"#,
227 r#"
228trait Foo {
229 type Output;
230
231 const CONST: usize = 42;
232
233 fn foo(&self);
234 fn bar(&self);
235 fn baz(&self);
236}
237
238struct S;
239
240impl Foo for S {
241 fn bar(&self) {}
242
243 $0type Output;
244
245 const CONST: usize = 42;
246
247 fn foo(&self) {
248 todo!()
249 }
250
251 fn baz(&self) {
252 todo!()
253 }
254}"#,
255 );
256 }
257
258 #[test]
259 fn test_copied_overriden_members() {
260 check_assist(
261 add_missing_impl_members,
262 r#"
263trait Foo {
264 fn foo(&self);
265 fn bar(&self) -> bool { true }
266 fn baz(&self) -> u32 { 42 }
267}
268
269struct S;
270
271impl Foo for S {
272 fn bar(&self) {}
273<|>
274}"#,
275 r#"
276trait Foo {
277 fn foo(&self);
278 fn bar(&self) -> bool { true }
279 fn baz(&self) -> u32 { 42 }
280}
281
282struct S;
283
284impl Foo for S {
285 fn bar(&self) {}
286
287 fn foo(&self) {
288 ${0:todo!()}
289 }
290}"#,
291 );
292 }
293
294 #[test]
295 fn test_empty_impl_def() {
296 check_assist(
297 add_missing_impl_members,
298 r#"
299trait Foo { fn foo(&self); }
300struct S;
301impl Foo for S { <|> }"#,
302 r#"
303trait Foo { fn foo(&self); }
304struct S;
305impl Foo for S {
306 fn foo(&self) {
307 ${0:todo!()}
308 }
309}"#,
310 );
311 }
312
313 #[test]
314 fn fill_in_type_params_1() {
315 check_assist(
316 add_missing_impl_members,
317 r#"
318trait Foo<T> { fn foo(&self, t: T) -> &T; }
319struct S;
320impl Foo<u32> for S { <|> }"#,
321 r#"
322trait Foo<T> { fn foo(&self, t: T) -> &T; }
323struct S;
324impl Foo<u32> for S {
325 fn foo(&self, t: u32) -> &u32 {
326 ${0:todo!()}
327 }
328}"#,
329 );
330 }
331
332 #[test]
333 fn fill_in_type_params_2() {
334 check_assist(
335 add_missing_impl_members,
336 r#"
337trait Foo<T> { fn foo(&self, t: T) -> &T; }
338struct S;
339impl<U> Foo<U> for S { <|> }"#,
340 r#"
341trait Foo<T> { fn foo(&self, t: T) -> &T; }
342struct S;
343impl<U> Foo<U> for S {
344 fn foo(&self, t: U) -> &U {
345 ${0:todo!()}
346 }
347}"#,
348 );
349 }
350
351 #[test]
352 fn test_cursor_after_empty_impl_def() {
353 check_assist(
354 add_missing_impl_members,
355 r#"
356trait Foo { fn foo(&self); }
357struct S;
358impl Foo for S {}<|>"#,
359 r#"
360trait Foo { fn foo(&self); }
361struct S;
362impl Foo for S {
363 fn foo(&self) {
364 ${0:todo!()}
365 }
366}"#,
367 )
368 }
369
370 #[test]
371 fn test_qualify_path_1() {
372 check_assist(
373 add_missing_impl_members,
374 r#"
375mod foo {
376 pub struct Bar;
377 trait Foo { fn foo(&self, bar: Bar); }
378}
379struct S;
380impl foo::Foo for S { <|> }"#,
381 r#"
382mod foo {
383 pub struct Bar;
384 trait Foo { fn foo(&self, bar: Bar); }
385}
386struct S;
387impl foo::Foo for S {
388 fn foo(&self, bar: foo::Bar) {
389 ${0:todo!()}
390 }
391}"#,
392 );
393 }
394
395 #[test]
396 fn test_qualify_path_generic() {
397 check_assist(
398 add_missing_impl_members,
399 r#"
400mod foo {
401 pub struct Bar<T>;
402 trait Foo { fn foo(&self, bar: Bar<u32>); }
403}
404struct S;
405impl foo::Foo for S { <|> }"#,
406 r#"
407mod foo {
408 pub struct Bar<T>;
409 trait Foo { fn foo(&self, bar: Bar<u32>); }
410}
411struct S;
412impl foo::Foo for S {
413 fn foo(&self, bar: foo::Bar<u32>) {
414 ${0:todo!()}
415 }
416}"#,
417 );
418 }
419
420 #[test]
421 fn test_qualify_path_and_substitute_param() {
422 check_assist(
423 add_missing_impl_members,
424 r#"
425mod foo {
426 pub struct Bar<T>;
427 trait Foo<T> { fn foo(&self, bar: Bar<T>); }
428}
429struct S;
430impl foo::Foo<u32> for S { <|> }"#,
431 r#"
432mod foo {
433 pub struct Bar<T>;
434 trait Foo<T> { fn foo(&self, bar: Bar<T>); }
435}
436struct S;
437impl foo::Foo<u32> for S {
438 fn foo(&self, bar: foo::Bar<u32>) {
439 ${0:todo!()}
440 }
441}"#,
442 );
443 }
444
445 #[test]
446 fn test_substitute_param_no_qualify() {
447 // when substituting params, the substituted param should not be qualified!
448 check_assist(
449 add_missing_impl_members,
450 r#"
451mod foo {
452 trait Foo<T> { fn foo(&self, bar: T); }
453 pub struct Param;
454}
455struct Param;
456struct S;
457impl foo::Foo<Param> for S { <|> }"#,
458 r#"
459mod foo {
460 trait Foo<T> { fn foo(&self, bar: T); }
461 pub struct Param;
462}
463struct Param;
464struct S;
465impl foo::Foo<Param> for S {
466 fn foo(&self, bar: Param) {
467 ${0:todo!()}
468 }
469}"#,
470 );
471 }
472
473 #[test]
474 fn test_qualify_path_associated_item() {
475 check_assist(
476 add_missing_impl_members,
477 r#"
478mod foo {
479 pub struct Bar<T>;
480 impl Bar<T> { type Assoc = u32; }
481 trait Foo { fn foo(&self, bar: Bar<u32>::Assoc); }
482}
483struct S;
484impl foo::Foo for S { <|> }"#,
485 r#"
486mod foo {
487 pub struct Bar<T>;
488 impl Bar<T> { type Assoc = u32; }
489 trait Foo { fn foo(&self, bar: Bar<u32>::Assoc); }
490}
491struct S;
492impl foo::Foo for S {
493 fn foo(&self, bar: foo::Bar<u32>::Assoc) {
494 ${0:todo!()}
495 }
496}"#,
497 );
498 }
499
500 #[test]
501 fn test_qualify_path_nested() {
502 check_assist(
503 add_missing_impl_members,
504 r#"
505mod foo {
506 pub struct Bar<T>;
507 pub struct Baz;
508 trait Foo { fn foo(&self, bar: Bar<Baz>); }
509}
510struct S;
511impl foo::Foo for S { <|> }"#,
512 r#"
513mod foo {
514 pub struct Bar<T>;
515 pub struct Baz;
516 trait Foo { fn foo(&self, bar: Bar<Baz>); }
517}
518struct S;
519impl foo::Foo for S {
520 fn foo(&self, bar: foo::Bar<foo::Baz>) {
521 ${0:todo!()}
522 }
523}"#,
524 );
525 }
526
527 #[test]
528 fn test_qualify_path_fn_trait_notation() {
529 check_assist(
530 add_missing_impl_members,
531 r#"
532mod foo {
533 pub trait Fn<Args> { type Output; }
534 trait Foo { fn foo(&self, bar: dyn Fn(u32) -> i32); }
535}
536struct S;
537impl foo::Foo for S { <|> }"#,
538 r#"
539mod foo {
540 pub trait Fn<Args> { type Output; }
541 trait Foo { fn foo(&self, bar: dyn Fn(u32) -> i32); }
542}
543struct S;
544impl foo::Foo for S {
545 fn foo(&self, bar: dyn Fn(u32) -> i32) {
546 ${0:todo!()}
547 }
548}"#,
549 );
550 }
551
552 #[test]
553 fn test_empty_trait() {
554 check_assist_not_applicable(
555 add_missing_impl_members,
556 r#"
557trait Foo;
558struct S;
559impl Foo for S { <|> }"#,
560 )
561 }
562
563 #[test]
564 fn test_ignore_unnamed_trait_members_and_default_methods() {
565 check_assist_not_applicable(
566 add_missing_impl_members,
567 r#"
568trait Foo {
569 fn (arg: u32);
570 fn valid(some: u32) -> bool { false }
571}
572struct S;
573impl Foo for S { <|> }"#,
574 )
575 }
576
577 #[test]
578 fn test_with_docstring_and_attrs() {
579 check_assist(
580 add_missing_impl_members,
581 r#"
582#[doc(alias = "test alias")]
583trait Foo {
584 /// doc string
585 type Output;
586
587 #[must_use]
588 fn foo(&self);
589}
590struct S;
591impl Foo for S {}<|>"#,
592 r#"
593#[doc(alias = "test alias")]
594trait Foo {
595 /// doc string
596 type Output;
597
598 #[must_use]
599 fn foo(&self);
600}
601struct S;
602impl Foo for S {
603 $0type Output;
604
605 fn foo(&self) {
606 todo!()
607 }
608}"#,
609 )
610 }
611
612 #[test]
613 fn test_default_methods() {
614 check_assist(
615 add_missing_default_members,
616 r#"
617trait Foo {
618 type Output;
619
620 const CONST: usize = 42;
621
622 fn valid(some: u32) -> bool { false }
623 fn foo(some: u32) -> bool;
624}
625struct S;
626impl Foo for S { <|> }"#,
627 r#"
628trait Foo {
629 type Output;
630
631 const CONST: usize = 42;
632
633 fn valid(some: u32) -> bool { false }
634 fn foo(some: u32) -> bool;
635}
636struct S;
637impl Foo for S {
638 $0fn valid(some: u32) -> bool { false }
639}"#,
640 )
641 }
642
643 #[test]
644 fn test_generic_single_default_parameter() {
645 check_assist(
646 add_missing_impl_members,
647 r#"
648trait Foo<T = Self> {
649 fn bar(&self, other: &T);
650}
651
652struct S;
653impl Foo for S { <|> }"#,
654 r#"
655trait Foo<T = Self> {
656 fn bar(&self, other: &T);
657}
658
659struct S;
660impl Foo for S {
661 fn bar(&self, other: &Self) {
662 ${0:todo!()}
663 }
664}"#,
665 )
666 }
667
668 #[test]
669 fn test_generic_default_parameter_is_second() {
670 check_assist(
671 add_missing_impl_members,
672 r#"
673trait Foo<T1, T2 = Self> {
674 fn bar(&self, this: &T1, that: &T2);
675}
676
677struct S<T>;
678impl Foo<T> for S<T> { <|> }"#,
679 r#"
680trait Foo<T1, T2 = Self> {
681 fn bar(&self, this: &T1, that: &T2);
682}
683
684struct S<T>;
685impl Foo<T> for S<T> {
686 fn bar(&self, this: &T, that: &Self) {
687 ${0:todo!()}
688 }
689}"#,
690 )
691 }
692
693 #[test]
694 fn test_assoc_type_bounds_are_removed() {
695 check_assist(
696 add_missing_impl_members,
697 r#"
698trait Tr {
699 type Ty: Copy + 'static;
700}
701
702impl Tr for ()<|> {
703}"#,
704 r#"
705trait Tr {
706 type Ty: Copy + 'static;
707}
708
709impl Tr for () {
710 $0type Ty;
711}"#,
712 )
713 }
714
715 #[test]
716 fn test_whitespace_fixup_preserves_bad_tokens() {
717 check_assist(
718 add_missing_impl_members,
719 r#"
720trait Tr {
721 fn foo();
722}
723
724impl Tr for ()<|> {
725 +++
726}"#,
727 r#"
728trait Tr {
729 fn foo();
730}
731
732impl Tr for () {
733 fn foo() {
734 ${0:todo!()}
735 }
736 +++
737}"#,
738 )
739 }
740
741 #[test]
742 fn test_whitespace_fixup_preserves_comments() {
743 check_assist(
744 add_missing_impl_members,
745 r#"
746trait Tr {
747 fn foo();
748}
749
750impl Tr for ()<|> {
751 // very important
752}"#,
753 r#"
754trait Tr {
755 fn foo();
756}
757
758impl Tr for () {
759 fn foo() {
760 ${0:todo!()}
761 }
762 // very important
763}"#,
764 )
765 }
766}
diff --git a/crates/assists/src/handlers/add_turbo_fish.rs b/crates/assists/src/handlers/add_turbo_fish.rs
new file mode 100644
index 000000000..f4f997d8e
--- /dev/null
+++ b/crates/assists/src/handlers/add_turbo_fish.rs
@@ -0,0 +1,164 @@
1use ide_db::defs::{classify_name_ref, 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<|>();
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_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 classify_name_ref(&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<|>();
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()<|>;
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<|>();
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<|>::<()>();
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<|>();
160}
161"#,
162 );
163 }
164}
diff --git a/crates/assists/src/handlers/apply_demorgan.rs b/crates/assists/src/handlers/apply_demorgan.rs
new file mode 100644
index 000000000..1a6fdafda
--- /dev/null
+++ b/crates/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 ||<|> !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 &&<|> !x }", "fn f() { !(x || x) }")
72 }
73
74 #[test]
75 fn demorgan_turns_or_into_and() {
76 check_assist(apply_demorgan, "fn f() { !x ||<|> !x }", "fn f() { !(x && x) }")
77 }
78
79 #[test]
80 fn demorgan_removes_inequality() {
81 check_assist(apply_demorgan, "fn f() { x != x ||<|> !x }", "fn f() { !(x == x && x) }")
82 }
83
84 #[test]
85 fn demorgan_general_case() {
86 check_assist(apply_demorgan, "fn f() { x ||<|> 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() { <|> !x || !x }")
92 }
93}
diff --git a/crates/assists/src/handlers/auto_import.rs b/crates/assists/src/handlers/auto_import.rs
new file mode 100644
index 000000000..b9ec3f10b
--- /dev/null
+++ b/crates/assists/src/handlers/auto_import.rs
@@ -0,0 +1,1088 @@
1use std::collections::BTreeSet;
2
3use either::Either;
4use hir::{
5 AsAssocItem, AssocItemContainer, ModPath, Module, ModuleDef, PathResolution, Semantics, Trait,
6 Type,
7};
8use ide_db::{imports_locator, RootDatabase};
9use rustc_hash::FxHashSet;
10use syntax::{
11 ast::{self, AstNode},
12 SyntaxNode,
13};
14
15use crate::{
16 utils::insert_use_statement, AssistContext, AssistId, AssistKind, Assists, GroupLabel,
17};
18
19// Assist: auto_import
20//
21// If the name is unresolved, provides all possible imports for it.
22//
23// ```
24// fn main() {
25// let map = HashMap<|>::new();
26// }
27// # pub mod std { pub mod collections { pub struct HashMap { } } }
28// ```
29// ->
30// ```
31// use std::collections::HashMap;
32//
33// fn main() {
34// let map = HashMap::new();
35// }
36// # pub mod std { pub mod collections { pub struct HashMap { } } }
37// ```
38pub(crate) fn auto_import(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
39 let auto_import_assets = AutoImportAssets::new(ctx)?;
40 let proposed_imports = auto_import_assets.search_for_imports(ctx);
41 if proposed_imports.is_empty() {
42 return None;
43 }
44
45 let range = ctx.sema.original_range(&auto_import_assets.syntax_under_caret).range;
46 let group = auto_import_assets.get_import_group_message();
47 for import in proposed_imports {
48 acc.add_group(
49 &group,
50 AssistId("auto_import", AssistKind::QuickFix),
51 format!("Import `{}`", &import),
52 range,
53 |builder| {
54 insert_use_statement(
55 &auto_import_assets.syntax_under_caret,
56 &import.to_string(),
57 ctx,
58 builder.text_edit_builder(),
59 );
60 },
61 );
62 }
63 Some(())
64}
65
66#[derive(Debug)]
67struct AutoImportAssets {
68 import_candidate: ImportCandidate,
69 module_with_name_to_import: Module,
70 syntax_under_caret: SyntaxNode,
71}
72
73impl AutoImportAssets {
74 fn new(ctx: &AssistContext) -> Option<Self> {
75 if let Some(path_under_caret) = ctx.find_node_at_offset_with_descend::<ast::Path>() {
76 Self::for_regular_path(path_under_caret, &ctx)
77 } else {
78 Self::for_method_call(ctx.find_node_at_offset_with_descend()?, &ctx)
79 }
80 }
81
82 fn for_method_call(method_call: ast::MethodCallExpr, ctx: &AssistContext) -> Option<Self> {
83 let syntax_under_caret = method_call.syntax().to_owned();
84 let module_with_name_to_import = ctx.sema.scope(&syntax_under_caret).module()?;
85 Some(Self {
86 import_candidate: ImportCandidate::for_method_call(&ctx.sema, &method_call)?,
87 module_with_name_to_import,
88 syntax_under_caret,
89 })
90 }
91
92 fn for_regular_path(path_under_caret: ast::Path, ctx: &AssistContext) -> Option<Self> {
93 let syntax_under_caret = path_under_caret.syntax().to_owned();
94 if syntax_under_caret.ancestors().find_map(ast::Use::cast).is_some() {
95 return None;
96 }
97
98 let module_with_name_to_import = ctx.sema.scope(&syntax_under_caret).module()?;
99 Some(Self {
100 import_candidate: ImportCandidate::for_regular_path(&ctx.sema, &path_under_caret)?,
101 module_with_name_to_import,
102 syntax_under_caret,
103 })
104 }
105
106 fn get_search_query(&self) -> &str {
107 match &self.import_candidate {
108 ImportCandidate::UnqualifiedName(name) => name,
109 ImportCandidate::QualifierStart(qualifier_start) => qualifier_start,
110 ImportCandidate::TraitAssocItem(_, trait_assoc_item_name) => trait_assoc_item_name,
111 ImportCandidate::TraitMethod(_, trait_method_name) => trait_method_name,
112 }
113 }
114
115 fn get_import_group_message(&self) -> GroupLabel {
116 let name = match &self.import_candidate {
117 ImportCandidate::UnqualifiedName(name) => format!("Import {}", name),
118 ImportCandidate::QualifierStart(qualifier_start) => {
119 format!("Import {}", qualifier_start)
120 }
121 ImportCandidate::TraitAssocItem(_, trait_assoc_item_name) => {
122 format!("Import a trait for item {}", trait_assoc_item_name)
123 }
124 ImportCandidate::TraitMethod(_, trait_method_name) => {
125 format!("Import a trait for method {}", trait_method_name)
126 }
127 };
128 GroupLabel(name)
129 }
130
131 fn search_for_imports(&self, ctx: &AssistContext) -> BTreeSet<ModPath> {
132 let _p = profile::span("auto_import::search_for_imports");
133 let db = ctx.db();
134 let current_crate = self.module_with_name_to_import.krate();
135 imports_locator::find_imports(&ctx.sema, current_crate, &self.get_search_query())
136 .into_iter()
137 .filter_map(|candidate| match &self.import_candidate {
138 ImportCandidate::TraitAssocItem(assoc_item_type, _) => {
139 let located_assoc_item = match candidate {
140 Either::Left(ModuleDef::Function(located_function)) => located_function
141 .as_assoc_item(db)
142 .map(|assoc| assoc.container(db))
143 .and_then(Self::assoc_to_trait),
144 Either::Left(ModuleDef::Const(located_const)) => located_const
145 .as_assoc_item(db)
146 .map(|assoc| assoc.container(db))
147 .and_then(Self::assoc_to_trait),
148 _ => None,
149 }?;
150
151 let mut trait_candidates = FxHashSet::default();
152 trait_candidates.insert(located_assoc_item.into());
153
154 assoc_item_type
155 .iterate_path_candidates(
156 db,
157 current_crate,
158 &trait_candidates,
159 None,
160 |_, assoc| Self::assoc_to_trait(assoc.container(db)),
161 )
162 .map(ModuleDef::from)
163 .map(Either::Left)
164 }
165 ImportCandidate::TraitMethod(function_callee, _) => {
166 let located_assoc_item =
167 if let Either::Left(ModuleDef::Function(located_function)) = candidate {
168 located_function
169 .as_assoc_item(db)
170 .map(|assoc| assoc.container(db))
171 .and_then(Self::assoc_to_trait)
172 } else {
173 None
174 }?;
175
176 let mut trait_candidates = FxHashSet::default();
177 trait_candidates.insert(located_assoc_item.into());
178
179 function_callee
180 .iterate_method_candidates(
181 db,
182 current_crate,
183 &trait_candidates,
184 None,
185 |_, function| {
186 Self::assoc_to_trait(function.as_assoc_item(db)?.container(db))
187 },
188 )
189 .map(ModuleDef::from)
190 .map(Either::Left)
191 }
192 _ => Some(candidate),
193 })
194 .filter_map(|candidate| match candidate {
195 Either::Left(module_def) => {
196 self.module_with_name_to_import.find_use_path(db, module_def)
197 }
198 Either::Right(macro_def) => {
199 self.module_with_name_to_import.find_use_path(db, macro_def)
200 }
201 })
202 .filter(|use_path| !use_path.segments.is_empty())
203 .take(20)
204 .collect::<BTreeSet<_>>()
205 }
206
207 fn assoc_to_trait(assoc: AssocItemContainer) -> Option<Trait> {
208 if let AssocItemContainer::Trait(extracted_trait) = assoc {
209 Some(extracted_trait)
210 } else {
211 None
212 }
213 }
214}
215
216#[derive(Debug)]
217enum ImportCandidate {
218 /// Simple name like 'HashMap'
219 UnqualifiedName(String),
220 /// First part of the qualified name.
221 /// For 'std::collections::HashMap', that will be 'std'.
222 QualifierStart(String),
223 /// A trait associated function (with no self parameter) or associated constant.
224 /// For 'test_mod::TestEnum::test_function', `Type` is the `test_mod::TestEnum` expression type
225 /// and `String` is the `test_function`
226 TraitAssocItem(Type, String),
227 /// A trait method with self parameter.
228 /// For 'test_enum.test_method()', `Type` is the `test_enum` expression type
229 /// and `String` is the `test_method`
230 TraitMethod(Type, String),
231}
232
233impl ImportCandidate {
234 fn for_method_call(
235 sema: &Semantics<RootDatabase>,
236 method_call: &ast::MethodCallExpr,
237 ) -> Option<Self> {
238 if sema.resolve_method_call(method_call).is_some() {
239 return None;
240 }
241 Some(Self::TraitMethod(
242 sema.type_of_expr(&method_call.expr()?)?,
243 method_call.name_ref()?.syntax().to_string(),
244 ))
245 }
246
247 fn for_regular_path(
248 sema: &Semantics<RootDatabase>,
249 path_under_caret: &ast::Path,
250 ) -> Option<Self> {
251 if sema.resolve_path(path_under_caret).is_some() {
252 return None;
253 }
254
255 let segment = path_under_caret.segment()?;
256 if let Some(qualifier) = path_under_caret.qualifier() {
257 let qualifier_start = qualifier.syntax().descendants().find_map(ast::NameRef::cast)?;
258 let qualifier_start_path =
259 qualifier_start.syntax().ancestors().find_map(ast::Path::cast)?;
260 if let Some(qualifier_start_resolution) = sema.resolve_path(&qualifier_start_path) {
261 let qualifier_resolution = if qualifier_start_path == qualifier {
262 qualifier_start_resolution
263 } else {
264 sema.resolve_path(&qualifier)?
265 };
266 if let PathResolution::Def(ModuleDef::Adt(assoc_item_path)) = qualifier_resolution {
267 Some(ImportCandidate::TraitAssocItem(
268 assoc_item_path.ty(sema.db),
269 segment.syntax().to_string(),
270 ))
271 } else {
272 None
273 }
274 } else {
275 Some(ImportCandidate::QualifierStart(qualifier_start.syntax().to_string()))
276 }
277 } else {
278 Some(ImportCandidate::UnqualifiedName(
279 segment.syntax().descendants().find_map(ast::NameRef::cast)?.syntax().to_string(),
280 ))
281 }
282 }
283}
284
285#[cfg(test)]
286mod tests {
287 use super::*;
288 use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target};
289
290 #[test]
291 fn applicable_when_found_an_import() {
292 check_assist(
293 auto_import,
294 r"
295 <|>PubStruct
296
297 pub mod PubMod {
298 pub struct PubStruct;
299 }
300 ",
301 r"
302 use PubMod::PubStruct;
303
304 PubStruct
305
306 pub mod PubMod {
307 pub struct PubStruct;
308 }
309 ",
310 );
311 }
312
313 #[test]
314 fn applicable_when_found_an_import_in_macros() {
315 check_assist(
316 auto_import,
317 r"
318 macro_rules! foo {
319 ($i:ident) => { fn foo(a: $i) {} }
320 }
321 foo!(Pub<|>Struct);
322
323 pub mod PubMod {
324 pub struct PubStruct;
325 }
326 ",
327 r"
328 use PubMod::PubStruct;
329
330 macro_rules! foo {
331 ($i:ident) => { fn foo(a: $i) {} }
332 }
333 foo!(PubStruct);
334
335 pub mod PubMod {
336 pub struct PubStruct;
337 }
338 ",
339 );
340 }
341
342 #[test]
343 fn auto_imports_are_merged() {
344 check_assist(
345 auto_import,
346 r"
347 use PubMod::PubStruct1;
348
349 struct Test {
350 test: Pub<|>Struct2<u8>,
351 }
352
353 pub mod PubMod {
354 pub struct PubStruct1;
355 pub struct PubStruct2<T> {
356 _t: T,
357 }
358 }
359 ",
360 r"
361 use PubMod::{PubStruct2, PubStruct1};
362
363 struct Test {
364 test: PubStruct2<u8>,
365 }
366
367 pub mod PubMod {
368 pub struct PubStruct1;
369 pub struct PubStruct2<T> {
370 _t: T,
371 }
372 }
373 ",
374 );
375 }
376
377 #[test]
378 fn applicable_when_found_multiple_imports() {
379 check_assist(
380 auto_import,
381 r"
382 PubSt<|>ruct
383
384 pub mod PubMod1 {
385 pub struct PubStruct;
386 }
387 pub mod PubMod2 {
388 pub struct PubStruct;
389 }
390 pub mod PubMod3 {
391 pub struct PubStruct;
392 }
393 ",
394 r"
395 use PubMod3::PubStruct;
396
397 PubStruct
398
399 pub mod PubMod1 {
400 pub struct PubStruct;
401 }
402 pub mod PubMod2 {
403 pub struct PubStruct;
404 }
405 pub mod PubMod3 {
406 pub struct PubStruct;
407 }
408 ",
409 );
410 }
411
412 #[test]
413 fn not_applicable_for_already_imported_types() {
414 check_assist_not_applicable(
415 auto_import,
416 r"
417 use PubMod::PubStruct;
418
419 PubStruct<|>
420
421 pub mod PubMod {
422 pub struct PubStruct;
423 }
424 ",
425 );
426 }
427
428 #[test]
429 fn not_applicable_for_types_with_private_paths() {
430 check_assist_not_applicable(
431 auto_import,
432 r"
433 PrivateStruct<|>
434
435 pub mod PubMod {
436 struct PrivateStruct;
437 }
438 ",
439 );
440 }
441
442 #[test]
443 fn not_applicable_when_no_imports_found() {
444 check_assist_not_applicable(
445 auto_import,
446 "
447 PubStruct<|>",
448 );
449 }
450
451 #[test]
452 fn not_applicable_in_import_statements() {
453 check_assist_not_applicable(
454 auto_import,
455 r"
456 use PubStruct<|>;
457
458 pub mod PubMod {
459 pub struct PubStruct;
460 }",
461 );
462 }
463
464 #[test]
465 fn function_import() {
466 check_assist(
467 auto_import,
468 r"
469 test_function<|>
470
471 pub mod PubMod {
472 pub fn test_function() {};
473 }
474 ",
475 r"
476 use PubMod::test_function;
477
478 test_function
479
480 pub mod PubMod {
481 pub fn test_function() {};
482 }
483 ",
484 );
485 }
486
487 #[test]
488 fn macro_import() {
489 check_assist(
490 auto_import,
491 r"
492//- /lib.rs crate:crate_with_macro
493#[macro_export]
494macro_rules! foo {
495 () => ()
496}
497
498//- /main.rs crate:main deps:crate_with_macro
499fn main() {
500 foo<|>
501}
502",
503 r"use crate_with_macro::foo;
504
505fn main() {
506 foo
507}
508",
509 );
510 }
511
512 #[test]
513 fn auto_import_target() {
514 check_assist_target(
515 auto_import,
516 r"
517 struct AssistInfo {
518 group_label: Option<<|>GroupLabel>,
519 }
520
521 mod m { pub struct GroupLabel; }
522 ",
523 "GroupLabel",
524 )
525 }
526
527 #[test]
528 fn not_applicable_when_path_start_is_imported() {
529 check_assist_not_applicable(
530 auto_import,
531 r"
532 pub mod mod1 {
533 pub mod mod2 {
534 pub mod mod3 {
535 pub struct TestStruct;
536 }
537 }
538 }
539
540 use mod1::mod2;
541 fn main() {
542 mod2::mod3::TestStruct<|>
543 }
544 ",
545 );
546 }
547
548 #[test]
549 fn not_applicable_for_imported_function() {
550 check_assist_not_applicable(
551 auto_import,
552 r"
553 pub mod test_mod {
554 pub fn test_function() {}
555 }
556
557 use test_mod::test_function;
558 fn main() {
559 test_function<|>
560 }
561 ",
562 );
563 }
564
565 #[test]
566 fn associated_struct_function() {
567 check_assist(
568 auto_import,
569 r"
570 mod test_mod {
571 pub struct TestStruct {}
572 impl TestStruct {
573 pub fn test_function() {}
574 }
575 }
576
577 fn main() {
578 TestStruct::test_function<|>
579 }
580 ",
581 r"
582 use test_mod::TestStruct;
583
584 mod test_mod {
585 pub struct TestStruct {}
586 impl TestStruct {
587 pub fn test_function() {}
588 }
589 }
590
591 fn main() {
592 TestStruct::test_function
593 }
594 ",
595 );
596 }
597
598 #[test]
599 fn associated_struct_const() {
600 check_assist(
601 auto_import,
602 r"
603 mod test_mod {
604 pub struct TestStruct {}
605 impl TestStruct {
606 const TEST_CONST: u8 = 42;
607 }
608 }
609
610 fn main() {
611 TestStruct::TEST_CONST<|>
612 }
613 ",
614 r"
615 use test_mod::TestStruct;
616
617 mod test_mod {
618 pub struct TestStruct {}
619 impl TestStruct {
620 const TEST_CONST: u8 = 42;
621 }
622 }
623
624 fn main() {
625 TestStruct::TEST_CONST
626 }
627 ",
628 );
629 }
630
631 #[test]
632 fn associated_trait_function() {
633 check_assist(
634 auto_import,
635 r"
636 mod test_mod {
637 pub trait TestTrait {
638 fn test_function();
639 }
640 pub struct TestStruct {}
641 impl TestTrait for TestStruct {
642 fn test_function() {}
643 }
644 }
645
646 fn main() {
647 test_mod::TestStruct::test_function<|>
648 }
649 ",
650 r"
651 use test_mod::TestTrait;
652
653 mod test_mod {
654 pub trait TestTrait {
655 fn test_function();
656 }
657 pub struct TestStruct {}
658 impl TestTrait for TestStruct {
659 fn test_function() {}
660 }
661 }
662
663 fn main() {
664 test_mod::TestStruct::test_function
665 }
666 ",
667 );
668 }
669
670 #[test]
671 fn not_applicable_for_imported_trait_for_function() {
672 check_assist_not_applicable(
673 auto_import,
674 r"
675 mod test_mod {
676 pub trait TestTrait {
677 fn test_function();
678 }
679 pub trait TestTrait2 {
680 fn test_function();
681 }
682 pub enum TestEnum {
683 One,
684 Two,
685 }
686 impl TestTrait2 for TestEnum {
687 fn test_function() {}
688 }
689 impl TestTrait for TestEnum {
690 fn test_function() {}
691 }
692 }
693
694 use test_mod::TestTrait2;
695 fn main() {
696 test_mod::TestEnum::test_function<|>;
697 }
698 ",
699 )
700 }
701
702 #[test]
703 fn associated_trait_const() {
704 check_assist(
705 auto_import,
706 r"
707 mod test_mod {
708 pub trait TestTrait {
709 const TEST_CONST: u8;
710 }
711 pub struct TestStruct {}
712 impl TestTrait for TestStruct {
713 const TEST_CONST: u8 = 42;
714 }
715 }
716
717 fn main() {
718 test_mod::TestStruct::TEST_CONST<|>
719 }
720 ",
721 r"
722 use test_mod::TestTrait;
723
724 mod test_mod {
725 pub trait TestTrait {
726 const TEST_CONST: u8;
727 }
728 pub struct TestStruct {}
729 impl TestTrait for TestStruct {
730 const TEST_CONST: u8 = 42;
731 }
732 }
733
734 fn main() {
735 test_mod::TestStruct::TEST_CONST
736 }
737 ",
738 );
739 }
740
741 #[test]
742 fn not_applicable_for_imported_trait_for_const() {
743 check_assist_not_applicable(
744 auto_import,
745 r"
746 mod test_mod {
747 pub trait TestTrait {
748 const TEST_CONST: u8;
749 }
750 pub trait TestTrait2 {
751 const TEST_CONST: f64;
752 }
753 pub enum TestEnum {
754 One,
755 Two,
756 }
757 impl TestTrait2 for TestEnum {
758 const TEST_CONST: f64 = 42.0;
759 }
760 impl TestTrait for TestEnum {
761 const TEST_CONST: u8 = 42;
762 }
763 }
764
765 use test_mod::TestTrait2;
766 fn main() {
767 test_mod::TestEnum::TEST_CONST<|>;
768 }
769 ",
770 )
771 }
772
773 #[test]
774 fn trait_method() {
775 check_assist(
776 auto_import,
777 r"
778 mod test_mod {
779 pub trait TestTrait {
780 fn test_method(&self);
781 }
782 pub struct TestStruct {}
783 impl TestTrait for TestStruct {
784 fn test_method(&self) {}
785 }
786 }
787
788 fn main() {
789 let test_struct = test_mod::TestStruct {};
790 test_struct.test_meth<|>od()
791 }
792 ",
793 r"
794 use test_mod::TestTrait;
795
796 mod test_mod {
797 pub trait TestTrait {
798 fn test_method(&self);
799 }
800 pub struct TestStruct {}
801 impl TestTrait for TestStruct {
802 fn test_method(&self) {}
803 }
804 }
805
806 fn main() {
807 let test_struct = test_mod::TestStruct {};
808 test_struct.test_method()
809 }
810 ",
811 );
812 }
813
814 #[test]
815 fn trait_method_cross_crate() {
816 check_assist(
817 auto_import,
818 r"
819 //- /main.rs crate:main deps:dep
820 fn main() {
821 let test_struct = dep::test_mod::TestStruct {};
822 test_struct.test_meth<|>od()
823 }
824 //- /dep.rs crate:dep
825 pub mod test_mod {
826 pub trait TestTrait {
827 fn test_method(&self);
828 }
829 pub struct TestStruct {}
830 impl TestTrait for TestStruct {
831 fn test_method(&self) {}
832 }
833 }
834 ",
835 r"
836 use dep::test_mod::TestTrait;
837
838 fn main() {
839 let test_struct = dep::test_mod::TestStruct {};
840 test_struct.test_method()
841 }
842 ",
843 );
844 }
845
846 #[test]
847 fn assoc_fn_cross_crate() {
848 check_assist(
849 auto_import,
850 r"
851 //- /main.rs crate:main deps:dep
852 fn main() {
853 dep::test_mod::TestStruct::test_func<|>tion
854 }
855 //- /dep.rs crate:dep
856 pub mod test_mod {
857 pub trait TestTrait {
858 fn test_function();
859 }
860 pub struct TestStruct {}
861 impl TestTrait for TestStruct {
862 fn test_function() {}
863 }
864 }
865 ",
866 r"
867 use dep::test_mod::TestTrait;
868
869 fn main() {
870 dep::test_mod::TestStruct::test_function
871 }
872 ",
873 );
874 }
875
876 #[test]
877 fn assoc_const_cross_crate() {
878 check_assist(
879 auto_import,
880 r"
881 //- /main.rs crate:main deps:dep
882 fn main() {
883 dep::test_mod::TestStruct::CONST<|>
884 }
885 //- /dep.rs crate:dep
886 pub mod test_mod {
887 pub trait TestTrait {
888 const CONST: bool;
889 }
890 pub struct TestStruct {}
891 impl TestTrait for TestStruct {
892 const CONST: bool = true;
893 }
894 }
895 ",
896 r"
897 use dep::test_mod::TestTrait;
898
899 fn main() {
900 dep::test_mod::TestStruct::CONST
901 }
902 ",
903 );
904 }
905
906 #[test]
907 fn assoc_fn_as_method_cross_crate() {
908 check_assist_not_applicable(
909 auto_import,
910 r"
911 //- /main.rs crate:main deps:dep
912 fn main() {
913 let test_struct = dep::test_mod::TestStruct {};
914 test_struct.test_func<|>tion()
915 }
916 //- /dep.rs crate:dep
917 pub mod test_mod {
918 pub trait TestTrait {
919 fn test_function();
920 }
921 pub struct TestStruct {}
922 impl TestTrait for TestStruct {
923 fn test_function() {}
924 }
925 }
926 ",
927 );
928 }
929
930 #[test]
931 fn private_trait_cross_crate() {
932 check_assist_not_applicable(
933 auto_import,
934 r"
935 //- /main.rs crate:main deps:dep
936 fn main() {
937 let test_struct = dep::test_mod::TestStruct {};
938 test_struct.test_meth<|>od()
939 }
940 //- /dep.rs crate:dep
941 pub mod test_mod {
942 trait TestTrait {
943 fn test_method(&self);
944 }
945 pub struct TestStruct {}
946 impl TestTrait for TestStruct {
947 fn test_method(&self) {}
948 }
949 }
950 ",
951 );
952 }
953
954 #[test]
955 fn not_applicable_for_imported_trait_for_method() {
956 check_assist_not_applicable(
957 auto_import,
958 r"
959 mod test_mod {
960 pub trait TestTrait {
961 fn test_method(&self);
962 }
963 pub trait TestTrait2 {
964 fn test_method(&self);
965 }
966 pub enum TestEnum {
967 One,
968 Two,
969 }
970 impl TestTrait2 for TestEnum {
971 fn test_method(&self) {}
972 }
973 impl TestTrait for TestEnum {
974 fn test_method(&self) {}
975 }
976 }
977
978 use test_mod::TestTrait2;
979 fn main() {
980 let one = test_mod::TestEnum::One;
981 one.test<|>_method();
982 }
983 ",
984 )
985 }
986
987 #[test]
988 fn dep_import() {
989 check_assist(
990 auto_import,
991 r"
992//- /lib.rs crate:dep
993pub struct Struct;
994
995//- /main.rs crate:main deps:dep
996fn main() {
997 Struct<|>
998}
999",
1000 r"use dep::Struct;
1001
1002fn main() {
1003 Struct
1004}
1005",
1006 );
1007 }
1008
1009 #[test]
1010 fn whole_segment() {
1011 // Tests that only imports whose last segment matches the identifier get suggested.
1012 check_assist(
1013 auto_import,
1014 r"
1015//- /lib.rs crate:dep
1016pub mod fmt {
1017 pub trait Display {}
1018}
1019
1020pub fn panic_fmt() {}
1021
1022//- /main.rs crate:main deps:dep
1023struct S;
1024
1025impl f<|>mt::Display for S {}
1026",
1027 r"use dep::fmt;
1028
1029struct S;
1030
1031impl fmt::Display for S {}
1032",
1033 );
1034 }
1035
1036 #[test]
1037 fn macro_generated() {
1038 // Tests that macro-generated items are suggested from external crates.
1039 check_assist(
1040 auto_import,
1041 r"
1042//- /lib.rs crate:dep
1043macro_rules! mac {
1044 () => {
1045 pub struct Cheese;
1046 };
1047}
1048
1049mac!();
1050
1051//- /main.rs crate:main deps:dep
1052fn main() {
1053 Cheese<|>;
1054}
1055",
1056 r"use dep::Cheese;
1057
1058fn main() {
1059 Cheese;
1060}
1061",
1062 );
1063 }
1064
1065 #[test]
1066 fn casing() {
1067 // Tests that differently cased names don't interfere and we only suggest the matching one.
1068 check_assist(
1069 auto_import,
1070 r"
1071//- /lib.rs crate:dep
1072pub struct FMT;
1073pub struct fmt;
1074
1075//- /main.rs crate:main deps:dep
1076fn main() {
1077 FMT<|>;
1078}
1079",
1080 r"use dep::FMT;
1081
1082fn main() {
1083 FMT;
1084}
1085",
1086 );
1087 }
1088}
diff --git a/crates/assists/src/handlers/change_return_type_to_result.rs b/crates/assists/src/handlers/change_return_type_to_result.rs
new file mode 100644
index 000000000..be480943c
--- /dev/null
+++ b/crates/assists/src/handlers/change_return_type_to_result.rs
@@ -0,0 +1,998 @@
1use std::iter;
2
3use syntax::{
4 ast::{self, make, BlockExpr, Expr, LoopBodyOwner},
5 AstNode, SyntaxNode,
6};
7use test_utils::mark;
8
9use crate::{AssistContext, AssistId, AssistKind, Assists};
10
11// Assist: change_return_type_to_result
12//
13// Change the function's return type to Result.
14//
15// ```
16// fn foo() -> i32<|> { 42i32 }
17// ```
18// ->
19// ```
20// fn foo() -> Result<i32, ${0:_}> { Ok(42i32) }
21// ```
22pub(crate) fn change_return_type_to_result(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
23 let ret_type = ctx.find_node_at_offset::<ast::RetType>()?;
24 // FIXME: extend to lambdas as well
25 let fn_def = ret_type.syntax().parent().and_then(ast::Fn::cast)?;
26
27 let type_ref = &ret_type.ty()?;
28 let ret_type_str = type_ref.syntax().text().to_string();
29 let first_part_ret_type = ret_type_str.splitn(2, '<').next();
30 if let Some(ret_type_first_part) = first_part_ret_type {
31 if ret_type_first_part.ends_with("Result") {
32 mark::hit!(change_return_type_to_result_simple_return_type_already_result);
33 return None;
34 }
35 }
36
37 let block_expr = &fn_def.body()?;
38
39 acc.add(
40 AssistId("change_return_type_to_result", AssistKind::RefactorRewrite),
41 "Wrap return type in Result",
42 type_ref.syntax().text_range(),
43 |builder| {
44 let mut tail_return_expr_collector = TailReturnCollector::new();
45 tail_return_expr_collector.collect_jump_exprs(block_expr, false);
46 tail_return_expr_collector.collect_tail_exprs(block_expr);
47
48 for ret_expr_arg in tail_return_expr_collector.exprs_to_wrap {
49 let ok_wrapped = make::expr_call(
50 make::expr_path(make::path_unqualified(make::path_segment(make::name_ref(
51 "Ok",
52 )))),
53 make::arg_list(iter::once(ret_expr_arg.clone())),
54 );
55 builder.replace_ast(ret_expr_arg, ok_wrapped);
56 }
57
58 match ctx.config.snippet_cap {
59 Some(cap) => {
60 let snippet = format!("Result<{}, ${{0:_}}>", type_ref);
61 builder.replace_snippet(cap, type_ref.syntax().text_range(), snippet)
62 }
63 None => builder
64 .replace(type_ref.syntax().text_range(), format!("Result<{}, _>", type_ref)),
65 }
66 },
67 )
68}
69
70struct TailReturnCollector {
71 exprs_to_wrap: Vec<ast::Expr>,
72}
73
74impl TailReturnCollector {
75 fn new() -> Self {
76 Self { exprs_to_wrap: vec![] }
77 }
78 /// Collect all`return` expression
79 fn collect_jump_exprs(&mut self, block_expr: &BlockExpr, collect_break: bool) {
80 let statements = block_expr.statements();
81 for stmt in statements {
82 let expr = match &stmt {
83 ast::Stmt::ExprStmt(stmt) => stmt.expr(),
84 ast::Stmt::LetStmt(stmt) => stmt.initializer(),
85 ast::Stmt::Item(_) => continue,
86 };
87 if let Some(expr) = &expr {
88 self.handle_exprs(expr, collect_break);
89 }
90 }
91
92 // Browse tail expressions for each block
93 if let Some(expr) = block_expr.expr() {
94 if let Some(last_exprs) = get_tail_expr_from_block(&expr) {
95 for last_expr in last_exprs {
96 let last_expr = match last_expr {
97 NodeType::Node(expr) => expr,
98 NodeType::Leaf(expr) => expr.syntax().clone(),
99 };
100
101 if let Some(last_expr) = Expr::cast(last_expr.clone()) {
102 self.handle_exprs(&last_expr, collect_break);
103 } else if let Some(expr_stmt) = ast::Stmt::cast(last_expr) {
104 let expr_stmt = match &expr_stmt {
105 ast::Stmt::ExprStmt(stmt) => stmt.expr(),
106 ast::Stmt::LetStmt(stmt) => stmt.initializer(),
107 ast::Stmt::Item(_) => None,
108 };
109 if let Some(expr) = &expr_stmt {
110 self.handle_exprs(expr, collect_break);
111 }
112 }
113 }
114 }
115 }
116 }
117
118 fn handle_exprs(&mut self, expr: &Expr, collect_break: bool) {
119 match expr {
120 Expr::BlockExpr(block_expr) => {
121 self.collect_jump_exprs(&block_expr, collect_break);
122 }
123 Expr::ReturnExpr(ret_expr) => {
124 if let Some(ret_expr_arg) = &ret_expr.expr() {
125 self.exprs_to_wrap.push(ret_expr_arg.clone());
126 }
127 }
128 Expr::BreakExpr(break_expr) if collect_break => {
129 if let Some(break_expr_arg) = &break_expr.expr() {
130 self.exprs_to_wrap.push(break_expr_arg.clone());
131 }
132 }
133 Expr::IfExpr(if_expr) => {
134 for block in if_expr.blocks() {
135 self.collect_jump_exprs(&block, collect_break);
136 }
137 }
138 Expr::LoopExpr(loop_expr) => {
139 if let Some(block_expr) = loop_expr.loop_body() {
140 self.collect_jump_exprs(&block_expr, collect_break);
141 }
142 }
143 Expr::ForExpr(for_expr) => {
144 if let Some(block_expr) = for_expr.loop_body() {
145 self.collect_jump_exprs(&block_expr, collect_break);
146 }
147 }
148 Expr::WhileExpr(while_expr) => {
149 if let Some(block_expr) = while_expr.loop_body() {
150 self.collect_jump_exprs(&block_expr, collect_break);
151 }
152 }
153 Expr::MatchExpr(match_expr) => {
154 if let Some(arm_list) = match_expr.match_arm_list() {
155 arm_list.arms().filter_map(|match_arm| match_arm.expr()).for_each(|expr| {
156 self.handle_exprs(&expr, collect_break);
157 });
158 }
159 }
160 _ => {}
161 }
162 }
163
164 fn collect_tail_exprs(&mut self, block: &BlockExpr) {
165 if let Some(expr) = block.expr() {
166 self.handle_exprs(&expr, true);
167 self.fetch_tail_exprs(&expr);
168 }
169 }
170
171 fn fetch_tail_exprs(&mut self, expr: &Expr) {
172 if let Some(exprs) = get_tail_expr_from_block(expr) {
173 for node_type in &exprs {
174 match node_type {
175 NodeType::Leaf(expr) => {
176 self.exprs_to_wrap.push(expr.clone());
177 }
178 NodeType::Node(expr) => {
179 if let Some(last_expr) = Expr::cast(expr.clone()) {
180 self.fetch_tail_exprs(&last_expr);
181 }
182 }
183 }
184 }
185 }
186 }
187}
188
189#[derive(Debug)]
190enum NodeType {
191 Leaf(ast::Expr),
192 Node(SyntaxNode),
193}
194
195/// Get a tail expression inside a block
196fn get_tail_expr_from_block(expr: &Expr) -> Option<Vec<NodeType>> {
197 match expr {
198 Expr::IfExpr(if_expr) => {
199 let mut nodes = vec![];
200 for block in if_expr.blocks() {
201 if let Some(block_expr) = block.expr() {
202 if let Some(tail_exprs) = get_tail_expr_from_block(&block_expr) {
203 nodes.extend(tail_exprs);
204 }
205 } else if let Some(last_expr) = block.syntax().last_child() {
206 nodes.push(NodeType::Node(last_expr));
207 } else {
208 nodes.push(NodeType::Node(block.syntax().clone()));
209 }
210 }
211 Some(nodes)
212 }
213 Expr::LoopExpr(loop_expr) => {
214 loop_expr.syntax().last_child().map(|lc| vec![NodeType::Node(lc)])
215 }
216 Expr::ForExpr(for_expr) => {
217 for_expr.syntax().last_child().map(|lc| vec![NodeType::Node(lc)])
218 }
219 Expr::WhileExpr(while_expr) => {
220 while_expr.syntax().last_child().map(|lc| vec![NodeType::Node(lc)])
221 }
222 Expr::BlockExpr(block_expr) => {
223 block_expr.expr().map(|lc| vec![NodeType::Node(lc.syntax().clone())])
224 }
225 Expr::MatchExpr(match_expr) => {
226 let arm_list = match_expr.match_arm_list()?;
227 let arms: Vec<NodeType> = arm_list
228 .arms()
229 .filter_map(|match_arm| match_arm.expr())
230 .map(|expr| match expr {
231 Expr::ReturnExpr(ret_expr) => NodeType::Node(ret_expr.syntax().clone()),
232 Expr::BreakExpr(break_expr) => NodeType::Node(break_expr.syntax().clone()),
233 _ => match expr.syntax().last_child() {
234 Some(last_expr) => NodeType::Node(last_expr),
235 None => NodeType::Node(expr.syntax().clone()),
236 },
237 })
238 .collect();
239
240 Some(arms)
241 }
242 Expr::BreakExpr(expr) => expr.expr().map(|e| vec![NodeType::Leaf(e)]),
243 Expr::ReturnExpr(ret_expr) => Some(vec![NodeType::Node(ret_expr.syntax().clone())]),
244
245 Expr::CallExpr(_)
246 | Expr::Literal(_)
247 | Expr::TupleExpr(_)
248 | Expr::ArrayExpr(_)
249 | Expr::ParenExpr(_)
250 | Expr::PathExpr(_)
251 | Expr::RecordExpr(_)
252 | Expr::IndexExpr(_)
253 | Expr::MethodCallExpr(_)
254 | Expr::AwaitExpr(_)
255 | Expr::CastExpr(_)
256 | Expr::RefExpr(_)
257 | Expr::PrefixExpr(_)
258 | Expr::RangeExpr(_)
259 | Expr::BinExpr(_)
260 | Expr::MacroCall(_)
261 | Expr::BoxExpr(_) => Some(vec![NodeType::Leaf(expr.clone())]),
262 _ => None,
263 }
264}
265
266#[cfg(test)]
267mod tests {
268 use crate::tests::{check_assist, check_assist_not_applicable};
269
270 use super::*;
271
272 #[test]
273 fn change_return_type_to_result_simple() {
274 check_assist(
275 change_return_type_to_result,
276 r#"fn foo() -> i3<|>2 {
277 let test = "test";
278 return 42i32;
279 }"#,
280 r#"fn foo() -> Result<i32, ${0:_}> {
281 let test = "test";
282 return Ok(42i32);
283 }"#,
284 );
285 }
286
287 #[test]
288 fn change_return_type_to_result_simple_return_type() {
289 check_assist(
290 change_return_type_to_result,
291 r#"fn foo() -> i32<|> {
292 let test = "test";
293 return 42i32;
294 }"#,
295 r#"fn foo() -> Result<i32, ${0:_}> {
296 let test = "test";
297 return Ok(42i32);
298 }"#,
299 );
300 }
301
302 #[test]
303 fn change_return_type_to_result_simple_return_type_bad_cursor() {
304 check_assist_not_applicable(
305 change_return_type_to_result,
306 r#"fn foo() -> i32 {
307 let test = "test";<|>
308 return 42i32;
309 }"#,
310 );
311 }
312
313 #[test]
314 fn change_return_type_to_result_simple_return_type_already_result_std() {
315 check_assist_not_applicable(
316 change_return_type_to_result,
317 r#"fn foo() -> std::result::Result<i32<|>, String> {
318 let test = "test";
319 return 42i32;
320 }"#,
321 );
322 }
323
324 #[test]
325 fn change_return_type_to_result_simple_return_type_already_result() {
326 mark::check!(change_return_type_to_result_simple_return_type_already_result);
327 check_assist_not_applicable(
328 change_return_type_to_result,
329 r#"fn foo() -> Result<i32<|>, String> {
330 let test = "test";
331 return 42i32;
332 }"#,
333 );
334 }
335
336 #[test]
337 fn change_return_type_to_result_simple_with_cursor() {
338 check_assist(
339 change_return_type_to_result,
340 r#"fn foo() -> <|>i32 {
341 let test = "test";
342 return 42i32;
343 }"#,
344 r#"fn foo() -> Result<i32, ${0:_}> {
345 let test = "test";
346 return Ok(42i32);
347 }"#,
348 );
349 }
350
351 #[test]
352 fn change_return_type_to_result_simple_with_tail() {
353 check_assist(
354 change_return_type_to_result,
355 r#"fn foo() -><|> i32 {
356 let test = "test";
357 42i32
358 }"#,
359 r#"fn foo() -> Result<i32, ${0:_}> {
360 let test = "test";
361 Ok(42i32)
362 }"#,
363 );
364 }
365
366 #[test]
367 fn change_return_type_to_result_simple_with_tail_only() {
368 check_assist(
369 change_return_type_to_result,
370 r#"fn foo() -> i32<|> {
371 42i32
372 }"#,
373 r#"fn foo() -> Result<i32, ${0:_}> {
374 Ok(42i32)
375 }"#,
376 );
377 }
378 #[test]
379 fn change_return_type_to_result_simple_with_tail_block_like() {
380 check_assist(
381 change_return_type_to_result,
382 r#"fn foo() -> i32<|> {
383 if true {
384 42i32
385 } else {
386 24i32
387 }
388 }"#,
389 r#"fn foo() -> Result<i32, ${0:_}> {
390 if true {
391 Ok(42i32)
392 } else {
393 Ok(24i32)
394 }
395 }"#,
396 );
397 }
398
399 #[test]
400 fn change_return_type_to_result_simple_with_nested_if() {
401 check_assist(
402 change_return_type_to_result,
403 r#"fn foo() -> i32<|> {
404 if true {
405 if false {
406 1
407 } else {
408 2
409 }
410 } else {
411 24i32
412 }
413 }"#,
414 r#"fn foo() -> Result<i32, ${0:_}> {
415 if true {
416 if false {
417 Ok(1)
418 } else {
419 Ok(2)
420 }
421 } else {
422 Ok(24i32)
423 }
424 }"#,
425 );
426 }
427
428 #[test]
429 fn change_return_type_to_result_simple_with_await() {
430 check_assist(
431 change_return_type_to_result,
432 r#"async fn foo() -> i<|>32 {
433 if true {
434 if false {
435 1.await
436 } else {
437 2.await
438 }
439 } else {
440 24i32.await
441 }
442 }"#,
443 r#"async fn foo() -> Result<i32, ${0:_}> {
444 if true {
445 if false {
446 Ok(1.await)
447 } else {
448 Ok(2.await)
449 }
450 } else {
451 Ok(24i32.await)
452 }
453 }"#,
454 );
455 }
456
457 #[test]
458 fn change_return_type_to_result_simple_with_array() {
459 check_assist(
460 change_return_type_to_result,
461 r#"fn foo() -> [i32;<|> 3] {
462 [1, 2, 3]
463 }"#,
464 r#"fn foo() -> Result<[i32; 3], ${0:_}> {
465 Ok([1, 2, 3])
466 }"#,
467 );
468 }
469
470 #[test]
471 fn change_return_type_to_result_simple_with_cast() {
472 check_assist(
473 change_return_type_to_result,
474 r#"fn foo() -<|>> i32 {
475 if true {
476 if false {
477 1 as i32
478 } else {
479 2 as i32
480 }
481 } else {
482 24 as i32
483 }
484 }"#,
485 r#"fn foo() -> Result<i32, ${0:_}> {
486 if true {
487 if false {
488 Ok(1 as i32)
489 } else {
490 Ok(2 as i32)
491 }
492 } else {
493 Ok(24 as i32)
494 }
495 }"#,
496 );
497 }
498
499 #[test]
500 fn change_return_type_to_result_simple_with_tail_block_like_match() {
501 check_assist(
502 change_return_type_to_result,
503 r#"fn foo() -> i32<|> {
504 let my_var = 5;
505 match my_var {
506 5 => 42i32,
507 _ => 24i32,
508 }
509 }"#,
510 r#"fn foo() -> Result<i32, ${0:_}> {
511 let my_var = 5;
512 match my_var {
513 5 => Ok(42i32),
514 _ => Ok(24i32),
515 }
516 }"#,
517 );
518 }
519
520 #[test]
521 fn change_return_type_to_result_simple_with_loop_with_tail() {
522 check_assist(
523 change_return_type_to_result,
524 r#"fn foo() -> i32<|> {
525 let my_var = 5;
526 loop {
527 println!("test");
528 5
529 }
530
531 my_var
532 }"#,
533 r#"fn foo() -> Result<i32, ${0:_}> {
534 let my_var = 5;
535 loop {
536 println!("test");
537 5
538 }
539
540 Ok(my_var)
541 }"#,
542 );
543 }
544
545 #[test]
546 fn change_return_type_to_result_simple_with_loop_in_let_stmt() {
547 check_assist(
548 change_return_type_to_result,
549 r#"fn foo() -> i32<|> {
550 let my_var = let x = loop {
551 break 1;
552 };
553
554 my_var
555 }"#,
556 r#"fn foo() -> Result<i32, ${0:_}> {
557 let my_var = let x = loop {
558 break 1;
559 };
560
561 Ok(my_var)
562 }"#,
563 );
564 }
565
566 #[test]
567 fn change_return_type_to_result_simple_with_tail_block_like_match_return_expr() {
568 check_assist(
569 change_return_type_to_result,
570 r#"fn foo() -> i32<|> {
571 let my_var = 5;
572 let res = match my_var {
573 5 => 42i32,
574 _ => return 24i32,
575 };
576
577 res
578 }"#,
579 r#"fn foo() -> Result<i32, ${0:_}> {
580 let my_var = 5;
581 let res = match my_var {
582 5 => 42i32,
583 _ => return Ok(24i32),
584 };
585
586 Ok(res)
587 }"#,
588 );
589
590 check_assist(
591 change_return_type_to_result,
592 r#"fn foo() -> i32<|> {
593 let my_var = 5;
594 let res = if my_var == 5 {
595 42i32
596 } else {
597 return 24i32;
598 };
599
600 res
601 }"#,
602 r#"fn foo() -> Result<i32, ${0:_}> {
603 let my_var = 5;
604 let res = if my_var == 5 {
605 42i32
606 } else {
607 return Ok(24i32);
608 };
609
610 Ok(res)
611 }"#,
612 );
613 }
614
615 #[test]
616 fn change_return_type_to_result_simple_with_tail_block_like_match_deeper() {
617 check_assist(
618 change_return_type_to_result,
619 r#"fn foo() -> i32<|> {
620 let my_var = 5;
621 match my_var {
622 5 => {
623 if true {
624 42i32
625 } else {
626 25i32
627 }
628 },
629 _ => {
630 let test = "test";
631 if test == "test" {
632 return bar();
633 }
634 53i32
635 },
636 }
637 }"#,
638 r#"fn foo() -> Result<i32, ${0:_}> {
639 let my_var = 5;
640 match my_var {
641 5 => {
642 if true {
643 Ok(42i32)
644 } else {
645 Ok(25i32)
646 }
647 },
648 _ => {
649 let test = "test";
650 if test == "test" {
651 return Ok(bar());
652 }
653 Ok(53i32)
654 },
655 }
656 }"#,
657 );
658 }
659
660 #[test]
661 fn change_return_type_to_result_simple_with_tail_block_like_early_return() {
662 check_assist(
663 change_return_type_to_result,
664 r#"fn foo() -> i<|>32 {
665 let test = "test";
666 if test == "test" {
667 return 24i32;
668 }
669 53i32
670 }"#,
671 r#"fn foo() -> Result<i32, ${0:_}> {
672 let test = "test";
673 if test == "test" {
674 return Ok(24i32);
675 }
676 Ok(53i32)
677 }"#,
678 );
679 }
680
681 #[test]
682 fn change_return_type_to_result_simple_with_closure() {
683 check_assist(
684 change_return_type_to_result,
685 r#"fn foo(the_field: u32) -><|> u32 {
686 let true_closure = || {
687 return true;
688 };
689 if the_field < 5 {
690 let mut i = 0;
691
692
693 if true_closure() {
694 return 99;
695 } else {
696 return 0;
697 }
698 }
699
700 the_field
701 }"#,
702 r#"fn foo(the_field: u32) -> Result<u32, ${0:_}> {
703 let true_closure = || {
704 return true;
705 };
706 if the_field < 5 {
707 let mut i = 0;
708
709
710 if true_closure() {
711 return Ok(99);
712 } else {
713 return Ok(0);
714 }
715 }
716
717 Ok(the_field)
718 }"#,
719 );
720
721 check_assist(
722 change_return_type_to_result,
723 r#"fn foo(the_field: u32) -> u32<|> {
724 let true_closure = || {
725 return true;
726 };
727 if the_field < 5 {
728 let mut i = 0;
729
730
731 if true_closure() {
732 return 99;
733 } else {
734 return 0;
735 }
736 }
737 let t = None;
738
739 t.unwrap_or_else(|| the_field)
740 }"#,
741 r#"fn foo(the_field: u32) -> Result<u32, ${0:_}> {
742 let true_closure = || {
743 return true;
744 };
745 if the_field < 5 {
746 let mut i = 0;
747
748
749 if true_closure() {
750 return Ok(99);
751 } else {
752 return Ok(0);
753 }
754 }
755 let t = None;
756
757 Ok(t.unwrap_or_else(|| the_field))
758 }"#,
759 );
760 }
761
762 #[test]
763 fn change_return_type_to_result_simple_with_weird_forms() {
764 check_assist(
765 change_return_type_to_result,
766 r#"fn foo() -> i32<|> {
767 let test = "test";
768 if test == "test" {
769 return 24i32;
770 }
771 let mut i = 0;
772 loop {
773 if i == 1 {
774 break 55;
775 }
776 i += 1;
777 }
778 }"#,
779 r#"fn foo() -> Result<i32, ${0:_}> {
780 let test = "test";
781 if test == "test" {
782 return Ok(24i32);
783 }
784 let mut i = 0;
785 loop {
786 if i == 1 {
787 break Ok(55);
788 }
789 i += 1;
790 }
791 }"#,
792 );
793
794 check_assist(
795 change_return_type_to_result,
796 r#"fn foo() -> i32<|> {
797 let test = "test";
798 if test == "test" {
799 return 24i32;
800 }
801 let mut i = 0;
802 loop {
803 loop {
804 if i == 1 {
805 break 55;
806 }
807 i += 1;
808 }
809 }
810 }"#,
811 r#"fn foo() -> Result<i32, ${0:_}> {
812 let test = "test";
813 if test == "test" {
814 return Ok(24i32);
815 }
816 let mut i = 0;
817 loop {
818 loop {
819 if i == 1 {
820 break Ok(55);
821 }
822 i += 1;
823 }
824 }
825 }"#,
826 );
827
828 check_assist(
829 change_return_type_to_result,
830 r#"fn foo() -> i3<|>2 {
831 let test = "test";
832 let other = 5;
833 if test == "test" {
834 let res = match other {
835 5 => 43,
836 _ => return 56,
837 };
838 }
839 let mut i = 0;
840 loop {
841 loop {
842 if i == 1 {
843 break 55;
844 }
845 i += 1;
846 }
847 }
848 }"#,
849 r#"fn foo() -> Result<i32, ${0:_}> {
850 let test = "test";
851 let other = 5;
852 if test == "test" {
853 let res = match other {
854 5 => 43,
855 _ => return Ok(56),
856 };
857 }
858 let mut i = 0;
859 loop {
860 loop {
861 if i == 1 {
862 break Ok(55);
863 }
864 i += 1;
865 }
866 }
867 }"#,
868 );
869
870 check_assist(
871 change_return_type_to_result,
872 r#"fn foo(the_field: u32) -> u32<|> {
873 if the_field < 5 {
874 let mut i = 0;
875 loop {
876 if i > 5 {
877 return 55u32;
878 }
879 i += 3;
880 }
881
882 match i {
883 5 => return 99,
884 _ => return 0,
885 };
886 }
887
888 the_field
889 }"#,
890 r#"fn foo(the_field: u32) -> Result<u32, ${0:_}> {
891 if the_field < 5 {
892 let mut i = 0;
893 loop {
894 if i > 5 {
895 return Ok(55u32);
896 }
897 i += 3;
898 }
899
900 match i {
901 5 => return Ok(99),
902 _ => return Ok(0),
903 };
904 }
905
906 Ok(the_field)
907 }"#,
908 );
909
910 check_assist(
911 change_return_type_to_result,
912 r#"fn foo(the_field: u32) -> u3<|>2 {
913 if the_field < 5 {
914 let mut i = 0;
915
916 match i {
917 5 => return 99,
918 _ => return 0,
919 }
920 }
921
922 the_field
923 }"#,
924 r#"fn foo(the_field: u32) -> Result<u32, ${0:_}> {
925 if the_field < 5 {
926 let mut i = 0;
927
928 match i {
929 5 => return Ok(99),
930 _ => return Ok(0),
931 }
932 }
933
934 Ok(the_field)
935 }"#,
936 );
937
938 check_assist(
939 change_return_type_to_result,
940 r#"fn foo(the_field: u32) -> u32<|> {
941 if the_field < 5 {
942 let mut i = 0;
943
944 if i == 5 {
945 return 99
946 } else {
947 return 0
948 }
949 }
950
951 the_field
952 }"#,
953 r#"fn foo(the_field: u32) -> Result<u32, ${0:_}> {
954 if the_field < 5 {
955 let mut i = 0;
956
957 if i == 5 {
958 return Ok(99)
959 } else {
960 return Ok(0)
961 }
962 }
963
964 Ok(the_field)
965 }"#,
966 );
967
968 check_assist(
969 change_return_type_to_result,
970 r#"fn foo(the_field: u32) -> <|>u32 {
971 if the_field < 5 {
972 let mut i = 0;
973
974 if i == 5 {
975 return 99;
976 } else {
977 return 0;
978 }
979 }
980
981 the_field
982 }"#,
983 r#"fn foo(the_field: u32) -> Result<u32, ${0:_}> {
984 if the_field < 5 {
985 let mut i = 0;
986
987 if i == 5 {
988 return Ok(99);
989 } else {
990 return Ok(0);
991 }
992 }
993
994 Ok(the_field)
995 }"#,
996 );
997 }
998}
diff --git a/crates/assists/src/handlers/change_visibility.rs b/crates/assists/src/handlers/change_visibility.rs
new file mode 100644
index 000000000..32dc05378
--- /dev/null
+++ b/crates/assists/src/handlers/change_visibility.rs
@@ -0,0 +1,200 @@
1use syntax::{
2 ast::{self, NameOwner, VisibilityOwner},
3 AstNode,
4 SyntaxKind::{CONST, ENUM, FN, MODULE, STATIC, STRUCT, TRAIT, 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// <|>fn 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] | T![static] | T![fn] | T![mod] | T![struct] | T![enum] | T![trait]
34 )
35 });
36
37 let (offset, target) = if let Some(keyword) = item_keyword {
38 let parent = keyword.parent();
39 let def_kws = vec![CONST, STATIC, FN, MODULE, STRUCT, ENUM, TRAIT];
40 // Parent is not a definition, can't add visibility
41 if !def_kws.iter().any(|&def_kw| def_kw == parent.kind()) {
42 return None;
43 }
44 // Already have visibility, do nothing
45 if parent.children().any(|child| child.kind() == VISIBILITY) {
46 return None;
47 }
48 (vis_offset(&parent), keyword.text_range())
49 } else if let Some(field_name) = ctx.find_node_at_offset::<ast::Name>() {
50 let field = field_name.syntax().ancestors().find_map(ast::RecordField::cast)?;
51 if field.name()? != field_name {
52 mark::hit!(change_visibility_field_false_positive);
53 return None;
54 }
55 if field.visibility().is_some() {
56 return None;
57 }
58 (vis_offset(field.syntax()), field_name.syntax().text_range())
59 } else if let Some(field) = ctx.find_node_at_offset::<ast::TupleField>() {
60 if field.visibility().is_some() {
61 return None;
62 }
63 (vis_offset(field.syntax()), field.syntax().text_range())
64 } else {
65 return None;
66 };
67
68 acc.add(
69 AssistId("change_visibility", AssistKind::RefactorRewrite),
70 "Change visibility to pub(crate)",
71 target,
72 |edit| {
73 edit.insert(offset, "pub(crate) ");
74 },
75 )
76}
77
78fn change_vis(acc: &mut Assists, vis: ast::Visibility) -> Option<()> {
79 if vis.syntax().text() == "pub" {
80 let target = vis.syntax().text_range();
81 return acc.add(
82 AssistId("change_visibility", AssistKind::RefactorRewrite),
83 "Change Visibility to pub(crate)",
84 target,
85 |edit| {
86 edit.replace(vis.syntax().text_range(), "pub(crate)");
87 },
88 );
89 }
90 if vis.syntax().text() == "pub(crate)" {
91 let target = vis.syntax().text_range();
92 return acc.add(
93 AssistId("change_visibility", AssistKind::RefactorRewrite),
94 "Change visibility to pub",
95 target,
96 |edit| {
97 edit.replace(vis.syntax().text_range(), "pub");
98 },
99 );
100 }
101 None
102}
103
104#[cfg(test)]
105mod tests {
106 use test_utils::mark;
107
108 use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target};
109
110 use super::*;
111
112 #[test]
113 fn change_visibility_adds_pub_crate_to_items() {
114 check_assist(change_visibility, "<|>fn foo() {}", "pub(crate) fn foo() {}");
115 check_assist(change_visibility, "f<|>n foo() {}", "pub(crate) fn foo() {}");
116 check_assist(change_visibility, "<|>struct Foo {}", "pub(crate) struct Foo {}");
117 check_assist(change_visibility, "<|>mod foo {}", "pub(crate) mod foo {}");
118 check_assist(change_visibility, "<|>trait Foo {}", "pub(crate) trait Foo {}");
119 check_assist(change_visibility, "m<|>od {}", "pub(crate) mod {}");
120 check_assist(change_visibility, "unsafe f<|>n foo() {}", "pub(crate) unsafe fn foo() {}");
121 }
122
123 #[test]
124 fn change_visibility_works_with_struct_fields() {
125 check_assist(
126 change_visibility,
127 r"struct S { <|>field: u32 }",
128 r"struct S { pub(crate) field: u32 }",
129 );
130 check_assist(change_visibility, r"struct S ( <|>u32 )", r"struct S ( pub(crate) u32 )");
131 }
132
133 #[test]
134 fn change_visibility_field_false_positive() {
135 mark::check!(change_visibility_field_false_positive);
136 check_assist_not_applicable(
137 change_visibility,
138 r"struct S { field: [(); { let <|>x = ();}] }",
139 )
140 }
141
142 #[test]
143 fn change_visibility_pub_to_pub_crate() {
144 check_assist(change_visibility, "<|>pub fn foo() {}", "pub(crate) fn foo() {}")
145 }
146
147 #[test]
148 fn change_visibility_pub_crate_to_pub() {
149 check_assist(change_visibility, "<|>pub(crate) fn foo() {}", "pub fn foo() {}")
150 }
151
152 #[test]
153 fn change_visibility_const() {
154 check_assist(change_visibility, "<|>const FOO = 3u8;", "pub(crate) const FOO = 3u8;");
155 }
156
157 #[test]
158 fn change_visibility_static() {
159 check_assist(change_visibility, "<|>static FOO = 3u8;", "pub(crate) static FOO = 3u8;");
160 }
161
162 #[test]
163 fn change_visibility_handles_comment_attrs() {
164 check_assist(
165 change_visibility,
166 r"
167 /// docs
168
169 // comments
170
171 #[derive(Debug)]
172 <|>struct Foo;
173 ",
174 r"
175 /// docs
176
177 // comments
178
179 #[derive(Debug)]
180 pub(crate) struct Foo;
181 ",
182 )
183 }
184
185 #[test]
186 fn not_applicable_for_enum_variants() {
187 check_assist_not_applicable(
188 change_visibility,
189 r"mod foo { pub enum Foo {Foo1} }
190 fn main() { foo::Foo::Foo1<|> } ",
191 );
192 }
193
194 #[test]
195 fn change_visibility_target() {
196 check_assist_target(change_visibility, "<|>fn foo() {}", "fn");
197 check_assist_target(change_visibility, "pub(crate)<|> fn foo() {}", "pub(crate)");
198 check_assist_target(change_visibility, "struct S { <|>field: u32 }", "field");
199 }
200}
diff --git a/crates/assists/src/handlers/early_return.rs b/crates/assists/src/handlers/early_return.rs
new file mode 100644
index 000000000..7fd78e9d4
--- /dev/null
+++ b/crates/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// <|>if 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.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(),
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)
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<|> 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<|> 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<|> 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<|> 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<|> 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<|> 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<|> 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<|> 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<|> 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<|> 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<|> 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<|> 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<|> 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<|> true {
508 foo();
509 }
510 }
511 }
512 "#,
513 );
514 }
515}
diff --git a/crates/assists/src/handlers/expand_glob_import.rs b/crates/assists/src/handlers/expand_glob_import.rs
new file mode 100644
index 000000000..81d0af2f3
--- /dev/null
+++ b/crates/assists/src/handlers/expand_glob_import.rs
@@ -0,0 +1,385 @@
1use either::Either;
2use hir::{AssocItem, MacroDef, ModuleDef, Name, PathResolution, ScopeDef, SemanticsScope};
3use ide_db::{
4 defs::{classify_name_ref, Definition, NameRefClass},
5 RootDatabase,
6};
7use syntax::{algo, ast, match_ast, AstNode, SyntaxNode, SyntaxToken, T};
8
9use crate::{
10 assist_context::{AssistBuilder, AssistContext, Assists},
11 AssistId, AssistKind,
12};
13
14// Assist: expand_glob_import
15//
16// Expands glob imports.
17//
18// ```
19// mod foo {
20// pub struct Bar;
21// pub struct Baz;
22// }
23//
24// use foo::*<|>;
25//
26// fn qux(bar: Bar, baz: Baz) {}
27// ```
28// ->
29// ```
30// mod foo {
31// pub struct Bar;
32// pub struct Baz;
33// }
34//
35// use foo::{Baz, Bar};
36//
37// fn qux(bar: Bar, baz: Baz) {}
38// ```
39pub(crate) fn expand_glob_import(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
40 let star = ctx.find_token_at_offset(T![*])?;
41 let mod_path = find_mod_path(&star)?;
42 let module = match ctx.sema.resolve_path(&mod_path)? {
43 PathResolution::Def(ModuleDef::Module(it)) => it,
44 _ => return None,
45 };
46
47 let source_file = ctx.source_file();
48 let scope = ctx.sema.scope_at_offset(source_file.syntax(), ctx.offset());
49
50 let defs_in_mod = find_defs_in_mod(ctx, scope, module)?;
51 let name_refs_in_source_file =
52 source_file.syntax().descendants().filter_map(ast::NameRef::cast).collect();
53 let used_names = find_used_names(ctx, defs_in_mod, name_refs_in_source_file);
54
55 let parent = star.parent().parent()?;
56 acc.add(
57 AssistId("expand_glob_import", AssistKind::RefactorRewrite),
58 "Expand glob import",
59 parent.text_range(),
60 |builder| {
61 replace_ast(builder, &parent, mod_path, used_names);
62 },
63 )
64}
65
66fn find_mod_path(star: &SyntaxToken) -> Option<ast::Path> {
67 star.ancestors().find_map(|n| ast::UseTree::cast(n).and_then(|u| u.path()))
68}
69
70#[derive(PartialEq)]
71enum Def {
72 ModuleDef(ModuleDef),
73 MacroDef(MacroDef),
74}
75
76impl Def {
77 fn name(&self, db: &RootDatabase) -> Option<Name> {
78 match self {
79 Def::ModuleDef(def) => def.name(db),
80 Def::MacroDef(def) => def.name(db),
81 }
82 }
83}
84
85fn find_defs_in_mod(
86 ctx: &AssistContext,
87 from: SemanticsScope<'_>,
88 module: hir::Module,
89) -> Option<Vec<Def>> {
90 let module_scope = module.scope(ctx.db(), from.module());
91
92 let mut defs = vec![];
93 for (_, def) in module_scope {
94 match def {
95 ScopeDef::ModuleDef(def) => defs.push(Def::ModuleDef(def)),
96 ScopeDef::MacroDef(def) => defs.push(Def::MacroDef(def)),
97 _ => continue,
98 }
99 }
100
101 Some(defs)
102}
103
104fn find_used_names(
105 ctx: &AssistContext,
106 defs_in_mod: Vec<Def>,
107 name_refs_in_source_file: Vec<ast::NameRef>,
108) -> Vec<Name> {
109 let defs_in_source_file = name_refs_in_source_file
110 .iter()
111 .filter_map(|r| classify_name_ref(&ctx.sema, r))
112 .filter_map(|rc| match rc {
113 NameRefClass::Definition(Definition::ModuleDef(def)) => Some(Def::ModuleDef(def)),
114 NameRefClass::Definition(Definition::Macro(def)) => Some(Def::MacroDef(def)),
115 _ => None,
116 })
117 .collect::<Vec<Def>>();
118
119 defs_in_mod
120 .iter()
121 .filter(|def| {
122 if let Def::ModuleDef(ModuleDef::Trait(tr)) = def {
123 for item in tr.items(ctx.db()) {
124 if let AssocItem::Function(f) = item {
125 if defs_in_source_file.contains(&Def::ModuleDef(ModuleDef::Function(f))) {
126 return true;
127 }
128 }
129 }
130 }
131
132 defs_in_source_file.contains(def)
133 })
134 .filter_map(|d| d.name(ctx.db()))
135 .collect()
136}
137
138fn replace_ast(
139 builder: &mut AssistBuilder,
140 node: &SyntaxNode,
141 path: ast::Path,
142 used_names: Vec<Name>,
143) {
144 let replacement: Either<ast::UseTree, ast::UseTreeList> = match used_names.as_slice() {
145 [name] => Either::Left(ast::make::use_tree(
146 ast::make::path_from_text(&format!("{}::{}", path, name)),
147 None,
148 None,
149 false,
150 )),
151 names => Either::Right(ast::make::use_tree_list(names.iter().map(|n| {
152 ast::make::use_tree(ast::make::path_from_text(&n.to_string()), None, None, false)
153 }))),
154 };
155
156 let mut replace_node = |replacement: Either<ast::UseTree, ast::UseTreeList>| {
157 algo::diff(node, &replacement.either(|u| u.syntax().clone(), |ut| ut.syntax().clone()))
158 .into_text_edit(builder.text_edit_builder());
159 };
160
161 match_ast! {
162 match node {
163 ast::UseTree(use_tree) => {
164 replace_node(replacement);
165 },
166 ast::UseTreeList(use_tree_list) => {
167 replace_node(replacement);
168 },
169 ast::Use(use_item) => {
170 builder.replace_ast(use_item, ast::make::use_(replacement.left_or_else(|ut| ast::make::use_tree(path, Some(ut), None, false))));
171 },
172 _ => {},
173 }
174 }
175}
176
177#[cfg(test)]
178mod tests {
179 use crate::tests::{check_assist, check_assist_not_applicable};
180
181 use super::*;
182
183 #[test]
184 fn expanding_glob_import() {
185 check_assist(
186 expand_glob_import,
187 r"
188mod foo {
189 pub struct Bar;
190 pub struct Baz;
191 pub struct Qux;
192
193 pub fn f() {}
194}
195
196use foo::*<|>;
197
198fn qux(bar: Bar, baz: Baz) {
199 f();
200}
201",
202 r"
203mod foo {
204 pub struct Bar;
205 pub struct Baz;
206 pub struct Qux;
207
208 pub fn f() {}
209}
210
211use foo::{Baz, Bar, f};
212
213fn qux(bar: Bar, baz: Baz) {
214 f();
215}
216",
217 )
218 }
219
220 #[test]
221 fn expanding_glob_import_with_existing_explicit_names() {
222 check_assist(
223 expand_glob_import,
224 r"
225mod foo {
226 pub struct Bar;
227 pub struct Baz;
228 pub struct Qux;
229
230 pub fn f() {}
231}
232
233use foo::{*<|>, f};
234
235fn qux(bar: Bar, baz: Baz) {
236 f();
237}
238",
239 r"
240mod foo {
241 pub struct Bar;
242 pub struct Baz;
243 pub struct Qux;
244
245 pub fn f() {}
246}
247
248use foo::{Baz, Bar, f};
249
250fn qux(bar: Bar, baz: Baz) {
251 f();
252}
253",
254 )
255 }
256
257 #[test]
258 fn expanding_nested_glob_import() {
259 check_assist(
260 expand_glob_import,
261 r"
262mod foo {
263 mod bar {
264 pub struct Bar;
265 pub struct Baz;
266 pub struct Qux;
267
268 pub fn f() {}
269 }
270
271 mod baz {
272 pub fn g() {}
273 }
274}
275
276use foo::{bar::{*<|>, f}, baz::*};
277
278fn qux(bar: Bar, baz: Baz) {
279 f();
280 g();
281}
282",
283 r"
284mod foo {
285 mod bar {
286 pub struct Bar;
287 pub struct Baz;
288 pub struct Qux;
289
290 pub fn f() {}
291 }
292
293 mod baz {
294 pub fn g() {}
295 }
296}
297
298use foo::{bar::{Baz, Bar, f}, baz::*};
299
300fn qux(bar: Bar, baz: Baz) {
301 f();
302 g();
303}
304",
305 )
306 }
307
308 #[test]
309 fn expanding_glob_import_with_macro_defs() {
310 check_assist(
311 expand_glob_import,
312 r"
313//- /lib.rs crate:foo
314#[macro_export]
315macro_rules! bar {
316 () => ()
317}
318
319pub fn baz() {}
320
321//- /main.rs crate:main deps:foo
322use foo::*<|>;
323
324fn main() {
325 bar!();
326 baz();
327}
328",
329 r"
330use foo::{bar, baz};
331
332fn main() {
333 bar!();
334 baz();
335}
336",
337 )
338 }
339
340 #[test]
341 fn expanding_glob_import_with_trait_method_uses() {
342 check_assist(
343 expand_glob_import,
344 r"
345//- /lib.rs crate:foo
346pub trait Tr {
347 fn method(&self) {}
348}
349impl Tr for () {}
350
351//- /main.rs crate:main deps:foo
352use foo::*<|>;
353
354fn main() {
355 ().method();
356}
357",
358 r"
359use foo::Tr;
360
361fn main() {
362 ().method();
363}
364",
365 )
366 }
367
368 #[test]
369 fn expanding_is_not_applicable_if_cursor_is_not_in_star_token() {
370 check_assist_not_applicable(
371 expand_glob_import,
372 r"
373 mod foo {
374 pub struct Bar;
375 pub struct Baz;
376 pub struct Qux;
377 }
378
379 use foo::Bar<|>;
380
381 fn qux(bar: Bar, baz: Baz) {}
382 ",
383 )
384 }
385}
diff --git a/crates/assists/src/handlers/extract_struct_from_enum_variant.rs b/crates/assists/src/handlers/extract_struct_from_enum_variant.rs
new file mode 100644
index 000000000..d62e06b4a
--- /dev/null
+++ b/crates/assists/src/handlers/extract_struct_from_enum_variant.rs
@@ -0,0 +1,322 @@
1use base_db::FileId;
2use hir::{EnumVariant, Module, ModuleDef, Name};
3use ide_db::{defs::Definition, search::Reference, RootDatabase};
4use rustc_hash::FxHashSet;
5use syntax::{
6 algo::find_node_at_offset,
7 ast::{self, edit::IndentLevel, ArgListOwner, AstNode, NameOwner, VisibilityOwner},
8 SourceFile, TextRange, TextSize,
9};
10
11use crate::{
12 assist_context::AssistBuilder, utils::insert_use_statement, AssistContext, AssistId,
13 AssistKind, Assists,
14};
15
16// Assist: extract_struct_from_enum_variant
17//
18// Extracts a struct from enum variant.
19//
20// ```
21// enum A { <|>One(u32, u32) }
22// ```
23// ->
24// ```
25// struct One(pub u32, pub u32);
26//
27// enum A { One(One) }
28// ```
29pub(crate) fn extract_struct_from_enum_variant(
30 acc: &mut Assists,
31 ctx: &AssistContext,
32) -> Option<()> {
33 let variant = ctx.find_node_at_offset::<ast::Variant>()?;
34 let field_list = match variant.kind() {
35 ast::StructKind::Tuple(field_list) => field_list,
36 _ => return None,
37 };
38 let variant_name = variant.name()?.to_string();
39 let variant_hir = ctx.sema.to_def(&variant)?;
40 if existing_struct_def(ctx.db(), &variant_name, &variant_hir) {
41 return None;
42 }
43 let enum_ast = variant.parent_enum();
44 let visibility = enum_ast.visibility();
45 let enum_hir = ctx.sema.to_def(&enum_ast)?;
46 let variant_hir_name = variant_hir.name(ctx.db());
47 let enum_module_def = ModuleDef::from(enum_hir);
48 let current_module = enum_hir.module(ctx.db());
49 let target = variant.syntax().text_range();
50 acc.add(
51 AssistId("extract_struct_from_enum_variant", AssistKind::RefactorRewrite),
52 "Extract struct from enum variant",
53 target,
54 |builder| {
55 let definition = Definition::ModuleDef(ModuleDef::EnumVariant(variant_hir));
56 let res = definition.find_usages(&ctx.sema, None);
57 let start_offset = variant.parent_enum().syntax().text_range().start();
58 let mut visited_modules_set = FxHashSet::default();
59 visited_modules_set.insert(current_module);
60 for reference in res {
61 let source_file = ctx.sema.parse(reference.file_range.file_id);
62 update_reference(
63 ctx,
64 builder,
65 reference,
66 &source_file,
67 &enum_module_def,
68 &variant_hir_name,
69 &mut visited_modules_set,
70 );
71 }
72 extract_struct_def(
73 builder,
74 &enum_ast,
75 &variant_name,
76 &field_list.to_string(),
77 start_offset,
78 ctx.frange.file_id,
79 &visibility,
80 );
81 let list_range = field_list.syntax().text_range();
82 update_variant(builder, &variant_name, ctx.frange.file_id, list_range);
83 },
84 )
85}
86
87fn existing_struct_def(db: &RootDatabase, variant_name: &str, variant: &EnumVariant) -> bool {
88 variant
89 .parent_enum(db)
90 .module(db)
91 .scope(db, None)
92 .into_iter()
93 .any(|(name, _)| name.to_string() == variant_name.to_string())
94}
95
96fn insert_import(
97 ctx: &AssistContext,
98 builder: &mut AssistBuilder,
99 path: &ast::PathExpr,
100 module: &Module,
101 enum_module_def: &ModuleDef,
102 variant_hir_name: &Name,
103) -> Option<()> {
104 let db = ctx.db();
105 let mod_path = module.find_use_path(db, enum_module_def.clone());
106 if let Some(mut mod_path) = mod_path {
107 mod_path.segments.pop();
108 mod_path.segments.push(variant_hir_name.clone());
109 insert_use_statement(
110 path.syntax(),
111 &mod_path.to_string(),
112 ctx,
113 builder.text_edit_builder(),
114 );
115 }
116 Some(())
117}
118
119// FIXME: this should use strongly-typed `make`, rather than string manipulation.
120fn extract_struct_def(
121 builder: &mut AssistBuilder,
122 enum_: &ast::Enum,
123 variant_name: &str,
124 variant_list: &str,
125 start_offset: TextSize,
126 file_id: FileId,
127 visibility: &Option<ast::Visibility>,
128) -> Option<()> {
129 let visibility_string = if let Some(visibility) = visibility {
130 format!("{} ", visibility.to_string())
131 } else {
132 "".to_string()
133 };
134 let indent = IndentLevel::from_node(enum_.syntax());
135 let struct_def = format!(
136 r#"{}struct {}{};
137
138{}"#,
139 visibility_string,
140 variant_name,
141 list_with_visibility(variant_list),
142 indent
143 );
144 builder.edit_file(file_id);
145 builder.insert(start_offset, struct_def);
146 Some(())
147}
148
149fn update_variant(
150 builder: &mut AssistBuilder,
151 variant_name: &str,
152 file_id: FileId,
153 list_range: TextRange,
154) -> Option<()> {
155 let inside_variant_range = TextRange::new(
156 list_range.start().checked_add(TextSize::from(1))?,
157 list_range.end().checked_sub(TextSize::from(1))?,
158 );
159 builder.edit_file(file_id);
160 builder.replace(inside_variant_range, variant_name);
161 Some(())
162}
163
164fn update_reference(
165 ctx: &AssistContext,
166 builder: &mut AssistBuilder,
167 reference: Reference,
168 source_file: &SourceFile,
169 enum_module_def: &ModuleDef,
170 variant_hir_name: &Name,
171 visited_modules_set: &mut FxHashSet<Module>,
172) -> Option<()> {
173 let path_expr: ast::PathExpr = find_node_at_offset::<ast::PathExpr>(
174 source_file.syntax(),
175 reference.file_range.range.start(),
176 )?;
177 let call = path_expr.syntax().parent().and_then(ast::CallExpr::cast)?;
178 let list = call.arg_list()?;
179 let segment = path_expr.path()?.segment()?;
180 let module = ctx.sema.scope(&path_expr.syntax()).module()?;
181 let list_range = list.syntax().text_range();
182 let inside_list_range = TextRange::new(
183 list_range.start().checked_add(TextSize::from(1))?,
184 list_range.end().checked_sub(TextSize::from(1))?,
185 );
186 builder.edit_file(reference.file_range.file_id);
187 if !visited_modules_set.contains(&module) {
188 if insert_import(ctx, builder, &path_expr, &module, enum_module_def, variant_hir_name)
189 .is_some()
190 {
191 visited_modules_set.insert(module);
192 }
193 }
194 builder.replace(inside_list_range, format!("{}{}", segment, list));
195 Some(())
196}
197
198fn list_with_visibility(list: &str) -> String {
199 list.split(',')
200 .map(|part| {
201 let index = if part.chars().next().unwrap() == '(' { 1usize } else { 0 };
202 let mut mod_part = part.trim().to_string();
203 mod_part.insert_str(index, "pub ");
204 mod_part
205 })
206 .collect::<Vec<String>>()
207 .join(", ")
208}
209
210#[cfg(test)]
211mod tests {
212
213 use crate::{
214 tests::{check_assist, check_assist_not_applicable},
215 utils::FamousDefs,
216 };
217
218 use super::*;
219
220 #[test]
221 fn test_extract_struct_several_fields() {
222 check_assist(
223 extract_struct_from_enum_variant,
224 "enum A { <|>One(u32, u32) }",
225 r#"struct One(pub u32, pub u32);
226
227enum A { One(One) }"#,
228 );
229 }
230
231 #[test]
232 fn test_extract_struct_one_field() {
233 check_assist(
234 extract_struct_from_enum_variant,
235 "enum A { <|>One(u32) }",
236 r#"struct One(pub u32);
237
238enum A { One(One) }"#,
239 );
240 }
241
242 #[test]
243 fn test_extract_struct_pub_visibility() {
244 check_assist(
245 extract_struct_from_enum_variant,
246 "pub enum A { <|>One(u32, u32) }",
247 r#"pub struct One(pub u32, pub u32);
248
249pub enum A { One(One) }"#,
250 );
251 }
252
253 #[test]
254 fn test_extract_struct_with_complex_imports() {
255 check_assist(
256 extract_struct_from_enum_variant,
257 r#"mod my_mod {
258 fn another_fn() {
259 let m = my_other_mod::MyEnum::MyField(1, 1);
260 }
261
262 pub mod my_other_mod {
263 fn another_fn() {
264 let m = MyEnum::MyField(1, 1);
265 }
266
267 pub enum MyEnum {
268 <|>MyField(u8, u8),
269 }
270 }
271}
272
273fn another_fn() {
274 let m = my_mod::my_other_mod::MyEnum::MyField(1, 1);
275}"#,
276 r#"use my_mod::my_other_mod::MyField;
277
278mod my_mod {
279 use my_other_mod::MyField;
280
281 fn another_fn() {
282 let m = my_other_mod::MyEnum::MyField(MyField(1, 1));
283 }
284
285 pub mod my_other_mod {
286 fn another_fn() {
287 let m = MyEnum::MyField(MyField(1, 1));
288 }
289
290 pub struct MyField(pub u8, pub u8);
291
292 pub enum MyEnum {
293 MyField(MyField),
294 }
295 }
296}
297
298fn another_fn() {
299 let m = my_mod::my_other_mod::MyEnum::MyField(MyField(1, 1));
300}"#,
301 );
302 }
303
304 fn check_not_applicable(ra_fixture: &str) {
305 let fixture =
306 format!("//- /main.rs crate:main deps:core\n{}\n{}", ra_fixture, FamousDefs::FIXTURE);
307 check_assist_not_applicable(extract_struct_from_enum_variant, &fixture)
308 }
309
310 #[test]
311 fn test_extract_enum_not_applicable_for_element_with_no_fields() {
312 check_not_applicable("enum A { <|>One }");
313 }
314
315 #[test]
316 fn test_extract_enum_not_applicable_if_struct_exists() {
317 check_not_applicable(
318 r#"struct One;
319 enum A { <|>One(u8) }"#,
320 );
321 }
322}
diff --git a/crates/assists/src/handlers/extract_variable.rs b/crates/assists/src/handlers/extract_variable.rs
new file mode 100644
index 000000000..d2ae137cd
--- /dev/null
+++ b/crates/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// <|>(1 + 2)<|> * 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_str("\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.expr())
143 {
144 if expr.syntax() == &node {
145 mark::hit!(test_extract_var_last_expr);
146 return Some(Anchor::Before(node));
147 }
148 }
149
150 if let Some(parent) = node.parent() {
151 if parent.kind() == MATCH_ARM || parent.kind() == CLOSURE_EXPR {
152 return Some(Anchor::WrapInBlock(node));
153 }
154 }
155
156 if let Some(stmt) = ast::Stmt::cast(node.clone()) {
157 if let ast::Stmt::ExprStmt(stmt) = stmt {
158 if stmt.expr().as_ref() == Some(to_extract) {
159 return Some(Anchor::Replace(stmt));
160 }
161 }
162 return Some(Anchor::Before(node));
163 }
164 None
165 })
166 }
167
168 fn syntax(&self) -> &SyntaxNode {
169 match self {
170 Anchor::Before(it) | Anchor::WrapInBlock(it) => it,
171 Anchor::Replace(stmt) => stmt.syntax(),
172 }
173 }
174}
175
176#[cfg(test)]
177mod tests {
178 use test_utils::mark;
179
180 use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target};
181
182 use super::*;
183
184 #[test]
185 fn test_extract_var_simple() {
186 check_assist(
187 extract_variable,
188 r#"
189fn foo() {
190 foo(<|>1 + 1<|>);
191}"#,
192 r#"
193fn foo() {
194 let $0var_name = 1 + 1;
195 foo(var_name);
196}"#,
197 );
198 }
199
200 #[test]
201 fn extract_var_in_comment_is_not_applicable() {
202 mark::check!(extract_var_in_comment_is_not_applicable);
203 check_assist_not_applicable(extract_variable, "fn main() { 1 + /* <|>comment<|> */ 1; }");
204 }
205
206 #[test]
207 fn test_extract_var_expr_stmt() {
208 mark::check!(test_extract_var_expr_stmt);
209 check_assist(
210 extract_variable,
211 r#"
212fn foo() {
213 <|>1 + 1<|>;
214}"#,
215 r#"
216fn foo() {
217 let $0var_name = 1 + 1;
218}"#,
219 );
220 check_assist(
221 extract_variable,
222 "
223fn foo() {
224 <|>{ let x = 0; x }<|>
225 something_else();
226}",
227 "
228fn foo() {
229 let $0var_name = { let x = 0; x };
230 something_else();
231}",
232 );
233 }
234
235 #[test]
236 fn test_extract_var_part_of_expr_stmt() {
237 check_assist(
238 extract_variable,
239 "
240fn foo() {
241 <|>1<|> + 1;
242}",
243 "
244fn foo() {
245 let $0var_name = 1;
246 var_name + 1;
247}",
248 );
249 }
250
251 #[test]
252 fn test_extract_var_last_expr() {
253 mark::check!(test_extract_var_last_expr);
254 check_assist(
255 extract_variable,
256 r#"
257fn foo() {
258 bar(<|>1 + 1<|>)
259}
260"#,
261 r#"
262fn foo() {
263 let $0var_name = 1 + 1;
264 bar(var_name)
265}
266"#,
267 );
268 check_assist(
269 extract_variable,
270 r#"
271fn foo() {
272 <|>bar(1 + 1)<|>
273}
274"#,
275 r#"
276fn foo() {
277 let $0var_name = bar(1 + 1);
278 var_name
279}
280"#,
281 )
282 }
283
284 #[test]
285 fn test_extract_var_in_match_arm_no_block() {
286 check_assist(
287 extract_variable,
288 "
289fn main() {
290 let x = true;
291 let tuple = match x {
292 true => (<|>2 + 2<|>, true)
293 _ => (0, false)
294 };
295}
296",
297 "
298fn main() {
299 let x = true;
300 let tuple = match x {
301 true => { let $0var_name = 2 + 2; (var_name, true) }
302 _ => (0, false)
303 };
304}
305",
306 );
307 }
308
309 #[test]
310 fn test_extract_var_in_match_arm_with_block() {
311 check_assist(
312 extract_variable,
313 "
314fn main() {
315 let x = true;
316 let tuple = match x {
317 true => {
318 let y = 1;
319 (<|>2 + y<|>, true)
320 }
321 _ => (0, false)
322 };
323}
324",
325 "
326fn main() {
327 let x = true;
328 let tuple = match x {
329 true => {
330 let y = 1;
331 let $0var_name = 2 + y;
332 (var_name, true)
333 }
334 _ => (0, false)
335 };
336}
337",
338 );
339 }
340
341 #[test]
342 fn test_extract_var_in_closure_no_block() {
343 check_assist(
344 extract_variable,
345 "
346fn main() {
347 let lambda = |x: u32| <|>x * 2<|>;
348}
349",
350 "
351fn main() {
352 let lambda = |x: u32| { let $0var_name = x * 2; var_name };
353}
354",
355 );
356 }
357
358 #[test]
359 fn test_extract_var_in_closure_with_block() {
360 check_assist(
361 extract_variable,
362 "
363fn main() {
364 let lambda = |x: u32| { <|>x * 2<|> };
365}
366",
367 "
368fn main() {
369 let lambda = |x: u32| { let $0var_name = x * 2; var_name };
370}
371",
372 );
373 }
374
375 #[test]
376 fn test_extract_var_path_simple() {
377 check_assist(
378 extract_variable,
379 "
380fn main() {
381 let o = <|>Some(true)<|>;
382}
383",
384 "
385fn main() {
386 let $0var_name = Some(true);
387 let o = var_name;
388}
389",
390 );
391 }
392
393 #[test]
394 fn test_extract_var_path_method() {
395 check_assist(
396 extract_variable,
397 "
398fn main() {
399 let v = <|>bar.foo()<|>;
400}
401",
402 "
403fn main() {
404 let $0var_name = bar.foo();
405 let v = var_name;
406}
407",
408 );
409 }
410
411 #[test]
412 fn test_extract_var_return() {
413 check_assist(
414 extract_variable,
415 "
416fn foo() -> u32 {
417 <|>return 2 + 2<|>;
418}
419",
420 "
421fn foo() -> u32 {
422 let $0var_name = 2 + 2;
423 return var_name;
424}
425",
426 );
427 }
428
429 #[test]
430 fn test_extract_var_does_not_add_extra_whitespace() {
431 check_assist(
432 extract_variable,
433 "
434fn foo() -> u32 {
435
436
437 <|>return 2 + 2<|>;
438}
439",
440 "
441fn foo() -> u32 {
442
443
444 let $0var_name = 2 + 2;
445 return var_name;
446}
447",
448 );
449
450 check_assist(
451 extract_variable,
452 "
453fn foo() -> u32 {
454
455 <|>return 2 + 2<|>;
456}
457",
458 "
459fn foo() -> u32 {
460
461 let $0var_name = 2 + 2;
462 return var_name;
463}
464",
465 );
466
467 check_assist(
468 extract_variable,
469 "
470fn foo() -> u32 {
471 let foo = 1;
472
473 // bar
474
475
476 <|>return 2 + 2<|>;
477}
478",
479 "
480fn foo() -> u32 {
481 let foo = 1;
482
483 // bar
484
485
486 let $0var_name = 2 + 2;
487 return var_name;
488}
489",
490 );
491 }
492
493 #[test]
494 fn test_extract_var_break() {
495 check_assist(
496 extract_variable,
497 "
498fn main() {
499 let result = loop {
500 <|>break 2 + 2<|>;
501 };
502}
503",
504 "
505fn main() {
506 let result = loop {
507 let $0var_name = 2 + 2;
508 break var_name;
509 };
510}
511",
512 );
513 }
514
515 #[test]
516 fn test_extract_var_for_cast() {
517 check_assist(
518 extract_variable,
519 "
520fn main() {
521 let v = <|>0f32 as u32<|>;
522}
523",
524 "
525fn main() {
526 let $0var_name = 0f32 as u32;
527 let v = var_name;
528}
529",
530 );
531 }
532
533 #[test]
534 fn extract_var_field_shorthand() {
535 check_assist(
536 extract_variable,
537 r#"
538struct S {
539 foo: i32
540}
541
542fn main() {
543 S { foo: <|>1 + 1<|> }
544}
545"#,
546 r#"
547struct S {
548 foo: i32
549}
550
551fn main() {
552 let $0foo = 1 + 1;
553 S { foo }
554}
555"#,
556 )
557 }
558
559 #[test]
560 fn test_extract_var_for_return_not_applicable() {
561 check_assist_not_applicable(extract_variable, "fn foo() { <|>return<|>; } ");
562 }
563
564 #[test]
565 fn test_extract_var_for_break_not_applicable() {
566 check_assist_not_applicable(extract_variable, "fn main() { loop { <|>break<|>; }; }");
567 }
568
569 // FIXME: This is not quite correct, but good enough(tm) for the sorting heuristic
570 #[test]
571 fn extract_var_target() {
572 check_assist_target(extract_variable, "fn foo() -> u32 { <|>return 2 + 2<|>; }", "2 + 2");
573
574 check_assist_target(
575 extract_variable,
576 "
577fn main() {
578 let x = true;
579 let tuple = match x {
580 true => (<|>2 + 2<|>, true)
581 _ => (0, false)
582 };
583}
584",
585 "2 + 2",
586 );
587 }
588}
diff --git a/crates/assists/src/handlers/fill_match_arms.rs b/crates/assists/src/handlers/fill_match_arms.rs
new file mode 100644
index 000000000..3d9bdb2bf
--- /dev/null
+++ b/crates/assists/src/handlers/fill_match_arms.rs
@@ -0,0 +1,747 @@
1use std::iter;
2
3use hir::{Adt, HasSource, ModuleDef, Semantics};
4use ide_db::RootDatabase;
5use itertools::Itertools;
6use syntax::ast::{self, make, AstNode, MatchArm, NameOwner, Pat};
7use test_utils::mark;
8
9use crate::{
10 utils::{render_snippet, Cursor, FamousDefs},
11 AssistContext, AssistId, AssistKind, Assists,
12};
13
14// Assist: fill_match_arms
15//
16// Adds missing clauses to a `match` expression.
17//
18// ```
19// enum Action { Move { distance: u32 }, Stop }
20//
21// fn handle(action: Action) {
22// match action {
23// <|>
24// }
25// }
26// ```
27// ->
28// ```
29// enum Action { Move { distance: u32 }, Stop }
30//
31// fn handle(action: Action) {
32// match action {
33// $0Action::Move { distance } => {}
34// Action::Stop => {}
35// }
36// }
37// ```
38pub(crate) fn fill_match_arms(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
39 let match_expr = ctx.find_node_at_offset::<ast::MatchExpr>()?;
40 let match_arm_list = match_expr.match_arm_list()?;
41
42 let expr = match_expr.expr()?;
43
44 let mut arms: Vec<MatchArm> = match_arm_list.arms().collect();
45 if arms.len() == 1 {
46 if let Some(Pat::WildcardPat(..)) = arms[0].pat() {
47 arms.clear();
48 }
49 }
50
51 let module = ctx.sema.scope(expr.syntax()).module()?;
52
53 let missing_arms: Vec<MatchArm> = if let Some(enum_def) = resolve_enum_def(&ctx.sema, &expr) {
54 let variants = enum_def.variants(ctx.db());
55
56 let mut variants = variants
57 .into_iter()
58 .filter_map(|variant| build_pat(ctx.db(), module, variant))
59 .filter(|variant_pat| is_variant_missing(&mut arms, variant_pat))
60 .map(|pat| make::match_arm(iter::once(pat), make::expr_empty_block()))
61 .collect::<Vec<_>>();
62 if Some(enum_def) == FamousDefs(&ctx.sema, module.krate()).core_option_Option() {
63 // Match `Some` variant first.
64 mark::hit!(option_order);
65 variants.reverse()
66 }
67 variants
68 } else if let Some(enum_defs) = resolve_tuple_of_enum_def(&ctx.sema, &expr) {
69 // Partial fill not currently supported for tuple of enums.
70 if !arms.is_empty() {
71 return None;
72 }
73
74 // We do not currently support filling match arms for a tuple
75 // containing a single enum.
76 if enum_defs.len() < 2 {
77 return None;
78 }
79
80 // When calculating the match arms for a tuple of enums, we want
81 // to create a match arm for each possible combination of enum
82 // values. The `multi_cartesian_product` method transforms
83 // Vec<Vec<EnumVariant>> into Vec<(EnumVariant, .., EnumVariant)>
84 // where each tuple represents a proposed match arm.
85 enum_defs
86 .into_iter()
87 .map(|enum_def| enum_def.variants(ctx.db()))
88 .multi_cartesian_product()
89 .map(|variants| {
90 let patterns =
91 variants.into_iter().filter_map(|variant| build_pat(ctx.db(), module, variant));
92 ast::Pat::from(make::tuple_pat(patterns))
93 })
94 .filter(|variant_pat| is_variant_missing(&mut arms, variant_pat))
95 .map(|pat| make::match_arm(iter::once(pat), make::expr_empty_block()))
96 .collect()
97 } else {
98 return None;
99 };
100
101 if missing_arms.is_empty() {
102 return None;
103 }
104
105 let target = match_expr.syntax().text_range();
106 acc.add(
107 AssistId("fill_match_arms", AssistKind::QuickFix),
108 "Fill match arms",
109 target,
110 |builder| {
111 let new_arm_list = match_arm_list.remove_placeholder();
112 let n_old_arms = new_arm_list.arms().count();
113 let new_arm_list = new_arm_list.append_arms(missing_arms);
114 let first_new_arm = new_arm_list.arms().nth(n_old_arms);
115 let old_range = match_arm_list.syntax().text_range();
116 match (first_new_arm, ctx.config.snippet_cap) {
117 (Some(first_new_arm), Some(cap)) => {
118 let extend_lifetime;
119 let cursor =
120 match first_new_arm.syntax().descendants().find_map(ast::WildcardPat::cast)
121 {
122 Some(it) => {
123 extend_lifetime = it.syntax().clone();
124 Cursor::Replace(&extend_lifetime)
125 }
126 None => Cursor::Before(first_new_arm.syntax()),
127 };
128 let snippet = render_snippet(cap, new_arm_list.syntax(), cursor);
129 builder.replace_snippet(cap, old_range, snippet);
130 }
131 _ => builder.replace(old_range, new_arm_list.to_string()),
132 }
133 },
134 )
135}
136
137fn is_variant_missing(existing_arms: &mut Vec<MatchArm>, var: &Pat) -> bool {
138 existing_arms.iter().filter_map(|arm| arm.pat()).all(|pat| {
139 // Special casee OrPat as separate top-level pats
140 let top_level_pats: Vec<Pat> = match pat {
141 Pat::OrPat(pats) => pats.pats().collect::<Vec<_>>(),
142 _ => vec![pat],
143 };
144
145 !top_level_pats.iter().any(|pat| does_pat_match_variant(pat, var))
146 })
147}
148
149fn does_pat_match_variant(pat: &Pat, var: &Pat) -> bool {
150 let first_node_text = |pat: &Pat| pat.syntax().first_child().map(|node| node.text());
151
152 let pat_head = match pat {
153 Pat::IdentPat(bind_pat) => {
154 if let Some(p) = bind_pat.pat() {
155 first_node_text(&p)
156 } else {
157 return false;
158 }
159 }
160 pat => first_node_text(pat),
161 };
162
163 let var_head = first_node_text(var);
164
165 pat_head == var_head
166}
167
168fn resolve_enum_def(sema: &Semantics<RootDatabase>, expr: &ast::Expr) -> Option<hir::Enum> {
169 sema.type_of_expr(&expr)?.autoderef(sema.db).find_map(|ty| match ty.as_adt() {
170 Some(Adt::Enum(e)) => Some(e),
171 _ => None,
172 })
173}
174
175fn resolve_tuple_of_enum_def(
176 sema: &Semantics<RootDatabase>,
177 expr: &ast::Expr,
178) -> Option<Vec<hir::Enum>> {
179 sema.type_of_expr(&expr)?
180 .tuple_fields(sema.db)
181 .iter()
182 .map(|ty| {
183 ty.autoderef(sema.db).find_map(|ty| match ty.as_adt() {
184 Some(Adt::Enum(e)) => Some(e),
185 // For now we only handle expansion for a tuple of enums. Here
186 // we map non-enum items to None and rely on `collect` to
187 // convert Vec<Option<hir::Enum>> into Option<Vec<hir::Enum>>.
188 _ => None,
189 })
190 })
191 .collect()
192}
193
194fn build_pat(db: &RootDatabase, module: hir::Module, var: hir::EnumVariant) -> Option<ast::Pat> {
195 let path = crate::ast_transform::path_to_ast(module.find_use_path(db, ModuleDef::from(var))?);
196
197 // FIXME: use HIR for this; it doesn't currently expose struct vs. tuple vs. unit variants though
198 let pat: ast::Pat = match var.source(db).value.kind() {
199 ast::StructKind::Tuple(field_list) => {
200 let pats = iter::repeat(make::wildcard_pat().into()).take(field_list.fields().count());
201 make::tuple_struct_pat(path, pats).into()
202 }
203 ast::StructKind::Record(field_list) => {
204 let pats = field_list.fields().map(|f| make::ident_pat(f.name().unwrap()).into());
205 make::record_pat(path, pats).into()
206 }
207 ast::StructKind::Unit => make::path_pat(path),
208 };
209
210 Some(pat)
211}
212
213#[cfg(test)]
214mod tests {
215 use test_utils::mark;
216
217 use crate::{
218 tests::{check_assist, check_assist_not_applicable, check_assist_target},
219 utils::FamousDefs,
220 };
221
222 use super::fill_match_arms;
223
224 #[test]
225 fn all_match_arms_provided() {
226 check_assist_not_applicable(
227 fill_match_arms,
228 r#"
229 enum A {
230 As,
231 Bs{x:i32, y:Option<i32>},
232 Cs(i32, Option<i32>),
233 }
234 fn main() {
235 match A::As<|> {
236 A::As,
237 A::Bs{x,y:Some(_)} => {}
238 A::Cs(_, Some(_)) => {}
239 }
240 }
241 "#,
242 );
243 }
244
245 #[test]
246 fn tuple_of_non_enum() {
247 // for now this case is not handled, although it potentially could be
248 // in the future
249 check_assist_not_applicable(
250 fill_match_arms,
251 r#"
252 fn main() {
253 match (0, false)<|> {
254 }
255 }
256 "#,
257 );
258 }
259
260 #[test]
261 fn partial_fill_record_tuple() {
262 check_assist(
263 fill_match_arms,
264 r#"
265 enum A {
266 As,
267 Bs { x: i32, y: Option<i32> },
268 Cs(i32, Option<i32>),
269 }
270 fn main() {
271 match A::As<|> {
272 A::Bs { x, y: Some(_) } => {}
273 A::Cs(_, Some(_)) => {}
274 }
275 }
276 "#,
277 r#"
278 enum A {
279 As,
280 Bs { x: i32, y: Option<i32> },
281 Cs(i32, Option<i32>),
282 }
283 fn main() {
284 match A::As {
285 A::Bs { x, y: Some(_) } => {}
286 A::Cs(_, Some(_)) => {}
287 $0A::As => {}
288 }
289 }
290 "#,
291 );
292 }
293
294 #[test]
295 fn partial_fill_or_pat() {
296 check_assist(
297 fill_match_arms,
298 r#"
299enum A { As, Bs, Cs(Option<i32>) }
300fn main() {
301 match A::As<|> {
302 A::Cs(_) | A::Bs => {}
303 }
304}
305"#,
306 r#"
307enum A { As, Bs, Cs(Option<i32>) }
308fn main() {
309 match A::As {
310 A::Cs(_) | A::Bs => {}
311 $0A::As => {}
312 }
313}
314"#,
315 );
316 }
317
318 #[test]
319 fn partial_fill() {
320 check_assist(
321 fill_match_arms,
322 r#"
323enum A { As, Bs, Cs, Ds(String), Es(B) }
324enum B { Xs, Ys }
325fn main() {
326 match A::As<|> {
327 A::Bs if 0 < 1 => {}
328 A::Ds(_value) => { let x = 1; }
329 A::Es(B::Xs) => (),
330 }
331}
332"#,
333 r#"
334enum A { As, Bs, Cs, Ds(String), Es(B) }
335enum B { Xs, Ys }
336fn main() {
337 match A::As {
338 A::Bs if 0 < 1 => {}
339 A::Ds(_value) => { let x = 1; }
340 A::Es(B::Xs) => (),
341 $0A::As => {}
342 A::Cs => {}
343 }
344}
345"#,
346 );
347 }
348
349 #[test]
350 fn partial_fill_bind_pat() {
351 check_assist(
352 fill_match_arms,
353 r#"
354enum A { As, Bs, Cs(Option<i32>) }
355fn main() {
356 match A::As<|> {
357 A::As(_) => {}
358 a @ A::Bs(_) => {}
359 }
360}
361"#,
362 r#"
363enum A { As, Bs, Cs(Option<i32>) }
364fn main() {
365 match A::As {
366 A::As(_) => {}
367 a @ A::Bs(_) => {}
368 A::Cs(${0:_}) => {}
369 }
370}
371"#,
372 );
373 }
374
375 #[test]
376 fn fill_match_arms_empty_body() {
377 check_assist(
378 fill_match_arms,
379 r#"
380enum A { As, Bs, Cs(String), Ds(String, String), Es { x: usize, y: usize } }
381
382fn main() {
383 let a = A::As;
384 match a<|> {}
385}
386"#,
387 r#"
388enum A { As, Bs, Cs(String), Ds(String, String), Es { x: usize, y: usize } }
389
390fn main() {
391 let a = A::As;
392 match a {
393 $0A::As => {}
394 A::Bs => {}
395 A::Cs(_) => {}
396 A::Ds(_, _) => {}
397 A::Es { x, y } => {}
398 }
399}
400"#,
401 );
402 }
403
404 #[test]
405 fn fill_match_arms_tuple_of_enum() {
406 check_assist(
407 fill_match_arms,
408 r#"
409 enum A { One, Two }
410 enum B { One, Two }
411
412 fn main() {
413 let a = A::One;
414 let b = B::One;
415 match (a<|>, b) {}
416 }
417 "#,
418 r#"
419 enum A { One, Two }
420 enum B { One, Two }
421
422 fn main() {
423 let a = A::One;
424 let b = B::One;
425 match (a, b) {
426 $0(A::One, B::One) => {}
427 (A::One, B::Two) => {}
428 (A::Two, B::One) => {}
429 (A::Two, B::Two) => {}
430 }
431 }
432 "#,
433 );
434 }
435
436 #[test]
437 fn fill_match_arms_tuple_of_enum_ref() {
438 check_assist(
439 fill_match_arms,
440 r#"
441 enum A { One, Two }
442 enum B { One, Two }
443
444 fn main() {
445 let a = A::One;
446 let b = B::One;
447 match (&a<|>, &b) {}
448 }
449 "#,
450 r#"
451 enum A { One, Two }
452 enum B { One, Two }
453
454 fn main() {
455 let a = A::One;
456 let b = B::One;
457 match (&a, &b) {
458 $0(A::One, B::One) => {}
459 (A::One, B::Two) => {}
460 (A::Two, B::One) => {}
461 (A::Two, B::Two) => {}
462 }
463 }
464 "#,
465 );
466 }
467
468 #[test]
469 fn fill_match_arms_tuple_of_enum_partial() {
470 check_assist_not_applicable(
471 fill_match_arms,
472 r#"
473 enum A { One, Two }
474 enum B { One, Two }
475
476 fn main() {
477 let a = A::One;
478 let b = B::One;
479 match (a<|>, b) {
480 (A::Two, B::One) => {}
481 }
482 }
483 "#,
484 );
485 }
486
487 #[test]
488 fn fill_match_arms_tuple_of_enum_not_applicable() {
489 check_assist_not_applicable(
490 fill_match_arms,
491 r#"
492 enum A { One, Two }
493 enum B { One, Two }
494
495 fn main() {
496 let a = A::One;
497 let b = B::One;
498 match (a<|>, b) {
499 (A::Two, B::One) => {}
500 (A::One, B::One) => {}
501 (A::One, B::Two) => {}
502 (A::Two, B::Two) => {}
503 }
504 }
505 "#,
506 );
507 }
508
509 #[test]
510 fn fill_match_arms_single_element_tuple_of_enum() {
511 // For now we don't hande the case of a single element tuple, but
512 // we could handle this in the future if `make::tuple_pat` allowed
513 // creating a tuple with a single pattern.
514 check_assist_not_applicable(
515 fill_match_arms,
516 r#"
517 enum A { One, Two }
518
519 fn main() {
520 let a = A::One;
521 match (a<|>, ) {
522 }
523 }
524 "#,
525 );
526 }
527
528 #[test]
529 fn test_fill_match_arm_refs() {
530 check_assist(
531 fill_match_arms,
532 r#"
533 enum A { As }
534
535 fn foo(a: &A) {
536 match a<|> {
537 }
538 }
539 "#,
540 r#"
541 enum A { As }
542
543 fn foo(a: &A) {
544 match a {
545 $0A::As => {}
546 }
547 }
548 "#,
549 );
550
551 check_assist(
552 fill_match_arms,
553 r#"
554 enum A {
555 Es { x: usize, y: usize }
556 }
557
558 fn foo(a: &mut A) {
559 match a<|> {
560 }
561 }
562 "#,
563 r#"
564 enum A {
565 Es { x: usize, y: usize }
566 }
567
568 fn foo(a: &mut A) {
569 match a {
570 $0A::Es { x, y } => {}
571 }
572 }
573 "#,
574 );
575 }
576
577 #[test]
578 fn fill_match_arms_target() {
579 check_assist_target(
580 fill_match_arms,
581 r#"
582 enum E { X, Y }
583
584 fn main() {
585 match E::X<|> {}
586 }
587 "#,
588 "match E::X {}",
589 );
590 }
591
592 #[test]
593 fn fill_match_arms_trivial_arm() {
594 check_assist(
595 fill_match_arms,
596 r#"
597 enum E { X, Y }
598
599 fn main() {
600 match E::X {
601 <|>_ => {}
602 }
603 }
604 "#,
605 r#"
606 enum E { X, Y }
607
608 fn main() {
609 match E::X {
610 $0E::X => {}
611 E::Y => {}
612 }
613 }
614 "#,
615 );
616 }
617
618 #[test]
619 fn fill_match_arms_qualifies_path() {
620 check_assist(
621 fill_match_arms,
622 r#"
623 mod foo { pub enum E { X, Y } }
624 use foo::E::X;
625
626 fn main() {
627 match X {
628 <|>
629 }
630 }
631 "#,
632 r#"
633 mod foo { pub enum E { X, Y } }
634 use foo::E::X;
635
636 fn main() {
637 match X {
638 $0X => {}
639 foo::E::Y => {}
640 }
641 }
642 "#,
643 );
644 }
645
646 #[test]
647 fn fill_match_arms_preserves_comments() {
648 check_assist(
649 fill_match_arms,
650 r#"
651 enum A { One, Two }
652 fn foo(a: A) {
653 match a {
654 // foo bar baz<|>
655 A::One => {}
656 // This is where the rest should be
657 }
658 }
659 "#,
660 r#"
661 enum A { One, Two }
662 fn foo(a: A) {
663 match a {
664 // foo bar baz
665 A::One => {}
666 // This is where the rest should be
667 $0A::Two => {}
668 }
669 }
670 "#,
671 );
672 }
673
674 #[test]
675 fn fill_match_arms_preserves_comments_empty() {
676 check_assist(
677 fill_match_arms,
678 r#"
679 enum A { One, Two }
680 fn foo(a: A) {
681 match a {
682 // foo bar baz<|>
683 }
684 }
685 "#,
686 r#"
687 enum A { One, Two }
688 fn foo(a: A) {
689 match a {
690 // foo bar baz
691 $0A::One => {}
692 A::Two => {}
693 }
694 }
695 "#,
696 );
697 }
698
699 #[test]
700 fn fill_match_arms_placeholder() {
701 check_assist(
702 fill_match_arms,
703 r#"
704 enum A { One, Two, }
705 fn foo(a: A) {
706 match a<|> {
707 _ => (),
708 }
709 }
710 "#,
711 r#"
712 enum A { One, Two, }
713 fn foo(a: A) {
714 match a {
715 $0A::One => {}
716 A::Two => {}
717 }
718 }
719 "#,
720 );
721 }
722
723 #[test]
724 fn option_order() {
725 mark::check!(option_order);
726 let before = r#"
727fn foo(opt: Option<i32>) {
728 match opt<|> {
729 }
730}
731"#;
732 let before = &format!("//- /main.rs crate:main deps:core{}{}", before, FamousDefs::FIXTURE);
733
734 check_assist(
735 fill_match_arms,
736 before,
737 r#"
738fn foo(opt: Option<i32>) {
739 match opt {
740 Some(${0:_}) => {}
741 None => {}
742 }
743}
744"#,
745 );
746 }
747}
diff --git a/crates/assists/src/handlers/fix_visibility.rs b/crates/assists/src/handlers/fix_visibility.rs
new file mode 100644
index 000000000..7cd76ea06
--- /dev/null
+++ b/crates/assists/src/handlers/fix_visibility.rs
@@ -0,0 +1,607 @@
1use base_db::FileId;
2use hir::{db::HirDatabase, HasSource, HasVisibility, PathResolution};
3use syntax::{ast, AstNode, TextRange, TextSize};
4
5use crate::{utils::vis_offset, AssistContext, AssistId, AssistKind, Assists};
6use ast::VisibilityOwner;
7
8// FIXME: this really should be a fix for diagnostic, rather than an assist.
9
10// Assist: fix_visibility
11//
12// Makes inaccessible item public.
13//
14// ```
15// mod m {
16// fn frobnicate() {}
17// }
18// fn main() {
19// m::frobnicate<|>() {}
20// }
21// ```
22// ->
23// ```
24// mod m {
25// $0pub(crate) fn frobnicate() {}
26// }
27// fn main() {
28// m::frobnicate() {}
29// }
30// ```
31pub(crate) fn fix_visibility(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
32 add_vis_to_referenced_module_def(acc, ctx)
33 .or_else(|| add_vis_to_referenced_record_field(acc, ctx))
34}
35
36fn add_vis_to_referenced_module_def(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
37 let path: ast::Path = ctx.find_node_at_offset()?;
38 let path_res = ctx.sema.resolve_path(&path)?;
39 let def = match path_res {
40 PathResolution::Def(def) => def,
41 _ => return None,
42 };
43
44 let current_module = ctx.sema.scope(&path.syntax()).module()?;
45 let target_module = def.module(ctx.db())?;
46
47 let vis = target_module.visibility_of(ctx.db(), &def)?;
48 if vis.is_visible_from(ctx.db(), current_module.into()) {
49 return None;
50 };
51
52 let (offset, current_visibility, target, target_file, target_name) =
53 target_data_for_def(ctx.db(), def)?;
54
55 let missing_visibility =
56 if current_module.krate() == target_module.krate() { "pub(crate)" } else { "pub" };
57
58 let assist_label = match target_name {
59 None => format!("Change visibility to {}", missing_visibility),
60 Some(name) => format!("Change visibility of {} to {}", name, missing_visibility),
61 };
62
63 acc.add(AssistId("fix_visibility", AssistKind::QuickFix), assist_label, target, |builder| {
64 builder.edit_file(target_file);
65 match ctx.config.snippet_cap {
66 Some(cap) => match current_visibility {
67 Some(current_visibility) => builder.replace_snippet(
68 cap,
69 current_visibility.syntax().text_range(),
70 format!("$0{}", missing_visibility),
71 ),
72 None => builder.insert_snippet(cap, offset, format!("$0{} ", missing_visibility)),
73 },
74 None => match current_visibility {
75 Some(current_visibility) => {
76 builder.replace(current_visibility.syntax().text_range(), missing_visibility)
77 }
78 None => builder.insert(offset, format!("{} ", missing_visibility)),
79 },
80 }
81 })
82}
83
84fn add_vis_to_referenced_record_field(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
85 let record_field: ast::RecordExprField = ctx.find_node_at_offset()?;
86 let (record_field_def, _) = ctx.sema.resolve_record_field(&record_field)?;
87
88 let current_module = ctx.sema.scope(record_field.syntax()).module()?;
89 let visibility = record_field_def.visibility(ctx.db());
90 if visibility.is_visible_from(ctx.db(), current_module.into()) {
91 return None;
92 }
93
94 let parent = record_field_def.parent_def(ctx.db());
95 let parent_name = parent.name(ctx.db());
96 let target_module = parent.module(ctx.db());
97
98 let in_file_source = record_field_def.source(ctx.db());
99 let (offset, current_visibility, target) = match in_file_source.value {
100 hir::FieldSource::Named(it) => {
101 let s = it.syntax();
102 (vis_offset(s), it.visibility(), s.text_range())
103 }
104 hir::FieldSource::Pos(it) => {
105 let s = it.syntax();
106 (vis_offset(s), it.visibility(), s.text_range())
107 }
108 };
109
110 let missing_visibility =
111 if current_module.krate() == target_module.krate() { "pub(crate)" } else { "pub" };
112 let target_file = in_file_source.file_id.original_file(ctx.db());
113
114 let target_name = record_field_def.name(ctx.db());
115 let assist_label =
116 format!("Change visibility of {}.{} to {}", parent_name, target_name, missing_visibility);
117
118 acc.add(AssistId("fix_visibility", AssistKind::QuickFix), assist_label, target, |builder| {
119 builder.edit_file(target_file);
120 match ctx.config.snippet_cap {
121 Some(cap) => match current_visibility {
122 Some(current_visibility) => builder.replace_snippet(
123 cap,
124 current_visibility.syntax().text_range(),
125 format!("$0{}", missing_visibility),
126 ),
127 None => builder.insert_snippet(cap, offset, format!("$0{} ", missing_visibility)),
128 },
129 None => match current_visibility {
130 Some(current_visibility) => {
131 builder.replace(current_visibility.syntax().text_range(), missing_visibility)
132 }
133 None => builder.insert(offset, format!("{} ", missing_visibility)),
134 },
135 }
136 })
137}
138
139fn target_data_for_def(
140 db: &dyn HirDatabase,
141 def: hir::ModuleDef,
142) -> Option<(TextSize, Option<ast::Visibility>, TextRange, FileId, Option<hir::Name>)> {
143 fn offset_target_and_file_id<S, Ast>(
144 db: &dyn HirDatabase,
145 x: S,
146 ) -> (TextSize, Option<ast::Visibility>, TextRange, FileId)
147 where
148 S: HasSource<Ast = Ast>,
149 Ast: AstNode + ast::VisibilityOwner,
150 {
151 let source = x.source(db);
152 let in_file_syntax = source.syntax();
153 let file_id = in_file_syntax.file_id;
154 let syntax = in_file_syntax.value;
155 let current_visibility = source.value.visibility();
156 (
157 vis_offset(syntax),
158 current_visibility,
159 syntax.text_range(),
160 file_id.original_file(db.upcast()),
161 )
162 }
163
164 let target_name;
165 let (offset, current_visibility, target, target_file) = match def {
166 hir::ModuleDef::Function(f) => {
167 target_name = Some(f.name(db));
168 offset_target_and_file_id(db, f)
169 }
170 hir::ModuleDef::Adt(adt) => {
171 target_name = Some(adt.name(db));
172 match adt {
173 hir::Adt::Struct(s) => offset_target_and_file_id(db, s),
174 hir::Adt::Union(u) => offset_target_and_file_id(db, u),
175 hir::Adt::Enum(e) => offset_target_and_file_id(db, e),
176 }
177 }
178 hir::ModuleDef::Const(c) => {
179 target_name = c.name(db);
180 offset_target_and_file_id(db, c)
181 }
182 hir::ModuleDef::Static(s) => {
183 target_name = s.name(db);
184 offset_target_and_file_id(db, s)
185 }
186 hir::ModuleDef::Trait(t) => {
187 target_name = Some(t.name(db));
188 offset_target_and_file_id(db, t)
189 }
190 hir::ModuleDef::TypeAlias(t) => {
191 target_name = Some(t.name(db));
192 offset_target_and_file_id(db, t)
193 }
194 hir::ModuleDef::Module(m) => {
195 target_name = m.name(db);
196 let in_file_source = m.declaration_source(db)?;
197 let file_id = in_file_source.file_id.original_file(db.upcast());
198 let syntax = in_file_source.value.syntax();
199 (vis_offset(syntax), in_file_source.value.visibility(), syntax.text_range(), file_id)
200 }
201 // Enum variants can't be private, we can't modify builtin types
202 hir::ModuleDef::EnumVariant(_) | hir::ModuleDef::BuiltinType(_) => return None,
203 };
204
205 Some((offset, current_visibility, target, target_file, target_name))
206}
207
208#[cfg(test)]
209mod tests {
210 use crate::tests::{check_assist, check_assist_not_applicable};
211
212 use super::*;
213
214 #[test]
215 fn fix_visibility_of_fn() {
216 check_assist(
217 fix_visibility,
218 r"mod foo { fn foo() {} }
219 fn main() { foo::foo<|>() } ",
220 r"mod foo { $0pub(crate) fn foo() {} }
221 fn main() { foo::foo() } ",
222 );
223 check_assist_not_applicable(
224 fix_visibility,
225 r"mod foo { pub fn foo() {} }
226 fn main() { foo::foo<|>() } ",
227 )
228 }
229
230 #[test]
231 fn fix_visibility_of_adt_in_submodule() {
232 check_assist(
233 fix_visibility,
234 r"mod foo { struct Foo; }
235 fn main() { foo::Foo<|> } ",
236 r"mod foo { $0pub(crate) struct Foo; }
237 fn main() { foo::Foo } ",
238 );
239 check_assist_not_applicable(
240 fix_visibility,
241 r"mod foo { pub struct Foo; }
242 fn main() { foo::Foo<|> } ",
243 );
244 check_assist(
245 fix_visibility,
246 r"mod foo { enum Foo; }
247 fn main() { foo::Foo<|> } ",
248 r"mod foo { $0pub(crate) enum Foo; }
249 fn main() { foo::Foo } ",
250 );
251 check_assist_not_applicable(
252 fix_visibility,
253 r"mod foo { pub enum Foo; }
254 fn main() { foo::Foo<|> } ",
255 );
256 check_assist(
257 fix_visibility,
258 r"mod foo { union Foo; }
259 fn main() { foo::Foo<|> } ",
260 r"mod foo { $0pub(crate) union Foo; }
261 fn main() { foo::Foo } ",
262 );
263 check_assist_not_applicable(
264 fix_visibility,
265 r"mod foo { pub union Foo; }
266 fn main() { foo::Foo<|> } ",
267 );
268 }
269
270 #[test]
271 fn fix_visibility_of_adt_in_other_file() {
272 check_assist(
273 fix_visibility,
274 r"
275//- /main.rs
276mod foo;
277fn main() { foo::Foo<|> }
278
279//- /foo.rs
280struct Foo;
281",
282 r"$0pub(crate) struct Foo;
283",
284 );
285 }
286
287 #[test]
288 fn fix_visibility_of_struct_field() {
289 check_assist(
290 fix_visibility,
291 r"mod foo { pub struct Foo { bar: (), } }
292 fn main() { foo::Foo { <|>bar: () }; } ",
293 r"mod foo { pub struct Foo { $0pub(crate) bar: (), } }
294 fn main() { foo::Foo { bar: () }; } ",
295 );
296 check_assist(
297 fix_visibility,
298 r"
299//- /lib.rs
300mod foo;
301fn main() { foo::Foo { <|>bar: () }; }
302//- /foo.rs
303pub struct Foo { bar: () }
304",
305 r"pub struct Foo { $0pub(crate) bar: () }
306",
307 );
308 check_assist_not_applicable(
309 fix_visibility,
310 r"mod foo { pub struct Foo { pub bar: (), } }
311 fn main() { foo::Foo { <|>bar: () }; } ",
312 );
313 check_assist_not_applicable(
314 fix_visibility,
315 r"
316//- /lib.rs
317mod foo;
318fn main() { foo::Foo { <|>bar: () }; }
319//- /foo.rs
320pub struct Foo { pub bar: () }
321",
322 );
323 }
324
325 #[test]
326 fn fix_visibility_of_enum_variant_field() {
327 check_assist(
328 fix_visibility,
329 r"mod foo { pub enum Foo { Bar { bar: () } } }
330 fn main() { foo::Foo::Bar { <|>bar: () }; } ",
331 r"mod foo { pub enum Foo { Bar { $0pub(crate) bar: () } } }
332 fn main() { foo::Foo::Bar { bar: () }; } ",
333 );
334 check_assist(
335 fix_visibility,
336 r"
337//- /lib.rs
338mod foo;
339fn main() { foo::Foo::Bar { <|>bar: () }; }
340//- /foo.rs
341pub enum Foo { Bar { bar: () } }
342",
343 r"pub enum Foo { Bar { $0pub(crate) bar: () } }
344",
345 );
346 check_assist_not_applicable(
347 fix_visibility,
348 r"mod foo { pub struct Foo { pub bar: (), } }
349 fn main() { foo::Foo { <|>bar: () }; } ",
350 );
351 check_assist_not_applicable(
352 fix_visibility,
353 r"
354//- /lib.rs
355mod foo;
356fn main() { foo::Foo { <|>bar: () }; }
357//- /foo.rs
358pub struct Foo { pub bar: () }
359",
360 );
361 }
362
363 #[test]
364 #[ignore]
365 // FIXME reenable this test when `Semantics::resolve_record_field` works with union fields
366 fn fix_visibility_of_union_field() {
367 check_assist(
368 fix_visibility,
369 r"mod foo { pub union Foo { bar: (), } }
370 fn main() { foo::Foo { <|>bar: () }; } ",
371 r"mod foo { pub union Foo { $0pub(crate) bar: (), } }
372 fn main() { foo::Foo { bar: () }; } ",
373 );
374 check_assist(
375 fix_visibility,
376 r"
377//- /lib.rs
378mod foo;
379fn main() { foo::Foo { <|>bar: () }; }
380//- /foo.rs
381pub union Foo { bar: () }
382",
383 r"pub union Foo { $0pub(crate) bar: () }
384",
385 );
386 check_assist_not_applicable(
387 fix_visibility,
388 r"mod foo { pub union Foo { pub bar: (), } }
389 fn main() { foo::Foo { <|>bar: () }; } ",
390 );
391 check_assist_not_applicable(
392 fix_visibility,
393 r"
394//- /lib.rs
395mod foo;
396fn main() { foo::Foo { <|>bar: () }; }
397//- /foo.rs
398pub union Foo { pub bar: () }
399",
400 );
401 }
402
403 #[test]
404 fn fix_visibility_of_const() {
405 check_assist(
406 fix_visibility,
407 r"mod foo { const FOO: () = (); }
408 fn main() { foo::FOO<|> } ",
409 r"mod foo { $0pub(crate) const FOO: () = (); }
410 fn main() { foo::FOO } ",
411 );
412 check_assist_not_applicable(
413 fix_visibility,
414 r"mod foo { pub const FOO: () = (); }
415 fn main() { foo::FOO<|> } ",
416 );
417 }
418
419 #[test]
420 fn fix_visibility_of_static() {
421 check_assist(
422 fix_visibility,
423 r"mod foo { static FOO: () = (); }
424 fn main() { foo::FOO<|> } ",
425 r"mod foo { $0pub(crate) static FOO: () = (); }
426 fn main() { foo::FOO } ",
427 );
428 check_assist_not_applicable(
429 fix_visibility,
430 r"mod foo { pub static FOO: () = (); }
431 fn main() { foo::FOO<|> } ",
432 );
433 }
434
435 #[test]
436 fn fix_visibility_of_trait() {
437 check_assist(
438 fix_visibility,
439 r"mod foo { trait Foo { fn foo(&self) {} } }
440 fn main() { let x: &dyn foo::<|>Foo; } ",
441 r"mod foo { $0pub(crate) trait Foo { fn foo(&self) {} } }
442 fn main() { let x: &dyn foo::Foo; } ",
443 );
444 check_assist_not_applicable(
445 fix_visibility,
446 r"mod foo { pub trait Foo { fn foo(&self) {} } }
447 fn main() { let x: &dyn foo::Foo<|>; } ",
448 );
449 }
450
451 #[test]
452 fn fix_visibility_of_type_alias() {
453 check_assist(
454 fix_visibility,
455 r"mod foo { type Foo = (); }
456 fn main() { let x: foo::Foo<|>; } ",
457 r"mod foo { $0pub(crate) type Foo = (); }
458 fn main() { let x: foo::Foo; } ",
459 );
460 check_assist_not_applicable(
461 fix_visibility,
462 r"mod foo { pub type Foo = (); }
463 fn main() { let x: foo::Foo<|>; } ",
464 );
465 }
466
467 #[test]
468 fn fix_visibility_of_module() {
469 check_assist(
470 fix_visibility,
471 r"mod foo { mod bar { fn bar() {} } }
472 fn main() { foo::bar<|>::bar(); } ",
473 r"mod foo { $0pub(crate) mod bar { fn bar() {} } }
474 fn main() { foo::bar::bar(); } ",
475 );
476
477 check_assist(
478 fix_visibility,
479 r"
480//- /main.rs
481mod foo;
482fn main() { foo::bar<|>::baz(); }
483
484//- /foo.rs
485mod bar {
486 pub fn baz() {}
487}
488",
489 r"$0pub(crate) mod bar {
490 pub fn baz() {}
491}
492",
493 );
494
495 check_assist_not_applicable(
496 fix_visibility,
497 r"mod foo { pub mod bar { pub fn bar() {} } }
498 fn main() { foo::bar<|>::bar(); } ",
499 );
500 }
501
502 #[test]
503 fn fix_visibility_of_inline_module_in_other_file() {
504 check_assist(
505 fix_visibility,
506 r"
507//- /main.rs
508mod foo;
509fn main() { foo::bar<|>::baz(); }
510
511//- /foo.rs
512mod bar;
513//- /foo/bar.rs
514pub fn baz() {}
515",
516 r"$0pub(crate) mod bar;
517",
518 );
519 }
520
521 #[test]
522 fn fix_visibility_of_module_declaration_in_other_file() {
523 check_assist(
524 fix_visibility,
525 r"
526//- /main.rs
527mod foo;
528fn main() { foo::bar<|>>::baz(); }
529
530//- /foo.rs
531mod bar {
532 pub fn baz() {}
533}
534",
535 r"$0pub(crate) mod bar {
536 pub fn baz() {}
537}
538",
539 );
540 }
541
542 #[test]
543 fn adds_pub_when_target_is_in_another_crate() {
544 check_assist(
545 fix_visibility,
546 r"
547//- /main.rs crate:a deps:foo
548foo::Bar<|>
549//- /lib.rs crate:foo
550struct Bar;
551",
552 r"$0pub struct Bar;
553",
554 )
555 }
556
557 #[test]
558 fn replaces_pub_crate_with_pub() {
559 check_assist(
560 fix_visibility,
561 r"
562//- /main.rs crate:a deps:foo
563foo::Bar<|>
564//- /lib.rs crate:foo
565pub(crate) struct Bar;
566",
567 r"$0pub struct Bar;
568",
569 );
570 check_assist(
571 fix_visibility,
572 r"
573//- /main.rs crate:a deps:foo
574fn main() {
575 foo::Foo { <|>bar: () };
576}
577//- /lib.rs crate:foo
578pub struct Foo { pub(crate) bar: () }
579",
580 r"pub struct Foo { $0pub bar: () }
581",
582 );
583 }
584
585 #[test]
586 #[ignore]
587 // FIXME handle reexports properly
588 fn fix_visibility_of_reexport() {
589 check_assist(
590 fix_visibility,
591 r"
592 mod foo {
593 use bar::Baz;
594 mod bar { pub(super) struct Baz; }
595 }
596 foo::Baz<|>
597 ",
598 r"
599 mod foo {
600 $0pub(crate) use bar::Baz;
601 mod bar { pub(super) struct Baz; }
602 }
603 foo::Baz
604 ",
605 )
606 }
607}
diff --git a/crates/assists/src/handlers/flip_binexpr.rs b/crates/assists/src/handlers/flip_binexpr.rs
new file mode 100644
index 000000000..404f06133
--- /dev/null
+++ b/crates/assists/src/handlers/flip_binexpr.rs
@@ -0,0 +1,142 @@
1use syntax::ast::{AstNode, BinExpr, BinOp};
2
3use crate::{AssistContext, AssistId, AssistKind, Assists};
4
5// Assist: flip_binexpr
6//
7// Flips operands of a binary expression.
8//
9// ```
10// fn main() {
11// let _ = 90 +<|> 2;
12// }
13// ```
14// ->
15// ```
16// fn main() {
17// let _ = 2 + 90;
18// }
19// ```
20pub(crate) fn flip_binexpr(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
21 let expr = ctx.find_node_at_offset::<BinExpr>()?;
22 let lhs = expr.lhs()?.syntax().clone();
23 let rhs = expr.rhs()?.syntax().clone();
24 let op_range = expr.op_token()?.text_range();
25 // The assist should be applied only if the cursor is on the operator
26 let cursor_in_range = op_range.contains_range(ctx.frange.range);
27 if !cursor_in_range {
28 return None;
29 }
30 let action: FlipAction = expr.op_kind()?.into();
31 // The assist should not be applied for certain operators
32 if let FlipAction::DontFlip = action {
33 return None;
34 }
35
36 acc.add(
37 AssistId("flip_binexpr", AssistKind::RefactorRewrite),
38 "Flip binary expression",
39 op_range,
40 |edit| {
41 if let FlipAction::FlipAndReplaceOp(new_op) = action {
42 edit.replace(op_range, new_op);
43 }
44 edit.replace(lhs.text_range(), rhs.text());
45 edit.replace(rhs.text_range(), lhs.text());
46 },
47 )
48}
49
50enum FlipAction {
51 // Flip the expression
52 Flip,
53 // Flip the expression and replace the operator with this string
54 FlipAndReplaceOp(&'static str),
55 // Do not flip the expression
56 DontFlip,
57}
58
59impl From<BinOp> for FlipAction {
60 fn from(op_kind: BinOp) -> Self {
61 match op_kind {
62 kind if kind.is_assignment() => FlipAction::DontFlip,
63 BinOp::GreaterTest => FlipAction::FlipAndReplaceOp("<"),
64 BinOp::GreaterEqualTest => FlipAction::FlipAndReplaceOp("<="),
65 BinOp::LesserTest => FlipAction::FlipAndReplaceOp(">"),
66 BinOp::LesserEqualTest => FlipAction::FlipAndReplaceOp(">="),
67 _ => FlipAction::Flip,
68 }
69 }
70}
71
72#[cfg(test)]
73mod tests {
74 use super::*;
75
76 use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target};
77
78 #[test]
79 fn flip_binexpr_target_is_the_op() {
80 check_assist_target(flip_binexpr, "fn f() { let res = 1 ==<|> 2; }", "==")
81 }
82
83 #[test]
84 fn flip_binexpr_not_applicable_for_assignment() {
85 check_assist_not_applicable(flip_binexpr, "fn f() { let mut _x = 1; _x +=<|> 2 }")
86 }
87
88 #[test]
89 fn flip_binexpr_works_for_eq() {
90 check_assist(
91 flip_binexpr,
92 "fn f() { let res = 1 ==<|> 2; }",
93 "fn f() { let res = 2 == 1; }",
94 )
95 }
96
97 #[test]
98 fn flip_binexpr_works_for_gt() {
99 check_assist(flip_binexpr, "fn f() { let res = 1 ><|> 2; }", "fn f() { let res = 2 < 1; }")
100 }
101
102 #[test]
103 fn flip_binexpr_works_for_lteq() {
104 check_assist(
105 flip_binexpr,
106 "fn f() { let res = 1 <=<|> 2; }",
107 "fn f() { let res = 2 >= 1; }",
108 )
109 }
110
111 #[test]
112 fn flip_binexpr_works_for_complex_expr() {
113 check_assist(
114 flip_binexpr,
115 "fn f() { let res = (1 + 1) ==<|> (2 + 2); }",
116 "fn f() { let res = (2 + 2) == (1 + 1); }",
117 )
118 }
119
120 #[test]
121 fn flip_binexpr_works_inside_match() {
122 check_assist(
123 flip_binexpr,
124 r#"
125 fn dyn_eq(&self, other: &dyn Diagnostic) -> bool {
126 match other.downcast_ref::<Self>() {
127 None => false,
128 Some(it) => it ==<|> self,
129 }
130 }
131 "#,
132 r#"
133 fn dyn_eq(&self, other: &dyn Diagnostic) -> bool {
134 match other.downcast_ref::<Self>() {
135 None => false,
136 Some(it) => self == it,
137 }
138 }
139 "#,
140 )
141 }
142}
diff --git a/crates/assists/src/handlers/flip_comma.rs b/crates/assists/src/handlers/flip_comma.rs
new file mode 100644
index 000000000..5c69db53e
--- /dev/null
+++ b/crates/assists/src/handlers/flip_comma.rs
@@ -0,0 +1,84 @@
1use syntax::{algo::non_trivia_sibling, Direction, T};
2
3use crate::{AssistContext, AssistId, AssistKind, Assists};
4
5// Assist: flip_comma
6//
7// Flips two comma-separated items.
8//
9// ```
10// fn main() {
11// ((1, 2),<|> (3, 4));
12// }
13// ```
14// ->
15// ```
16// fn main() {
17// ((3, 4), (1, 2));
18// }
19// ```
20pub(crate) fn flip_comma(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
21 let comma = ctx.find_token_at_offset(T![,])?;
22 let prev = non_trivia_sibling(comma.clone().into(), Direction::Prev)?;
23 let next = non_trivia_sibling(comma.clone().into(), Direction::Next)?;
24
25 // Don't apply a "flip" in case of a last comma
26 // that typically comes before punctuation
27 if next.kind().is_punct() {
28 return None;
29 }
30
31 acc.add(
32 AssistId("flip_comma", AssistKind::RefactorRewrite),
33 "Flip comma",
34 comma.text_range(),
35 |edit| {
36 edit.replace(prev.text_range(), next.to_string());
37 edit.replace(next.text_range(), prev.to_string());
38 },
39 )
40}
41
42#[cfg(test)]
43mod tests {
44 use super::*;
45
46 use crate::tests::{check_assist, check_assist_target};
47
48 #[test]
49 fn flip_comma_works_for_function_parameters() {
50 check_assist(
51 flip_comma,
52 "fn foo(x: i32,<|> y: Result<(), ()>) {}",
53 "fn foo(y: Result<(), ()>, x: i32) {}",
54 )
55 }
56
57 #[test]
58 fn flip_comma_target() {
59 check_assist_target(flip_comma, "fn foo(x: i32,<|> y: Result<(), ()>) {}", ",")
60 }
61
62 #[test]
63 #[should_panic]
64 fn flip_comma_before_punct() {
65 // See https://github.com/rust-analyzer/rust-analyzer/issues/1619
66 // "Flip comma" assist shouldn't be applicable to the last comma in enum or struct
67 // declaration body.
68 check_assist_target(
69 flip_comma,
70 "pub enum Test { \
71 A,<|> \
72 }",
73 ",",
74 );
75
76 check_assist_target(
77 flip_comma,
78 "pub struct Test { \
79 foo: usize,<|> \
80 }",
81 ",",
82 );
83 }
84}
diff --git a/crates/assists/src/handlers/flip_trait_bound.rs b/crates/assists/src/handlers/flip_trait_bound.rs
new file mode 100644
index 000000000..347e79b1d
--- /dev/null
+++ b/crates/assists/src/handlers/flip_trait_bound.rs
@@ -0,0 +1,121 @@
1use syntax::{
2 algo::non_trivia_sibling,
3 ast::{self, AstNode},
4 Direction, T,
5};
6
7use crate::{AssistContext, AssistId, AssistKind, Assists};
8
9// Assist: flip_trait_bound
10//
11// Flips two trait bounds.
12//
13// ```
14// fn foo<T: Clone +<|> Copy>() { }
15// ```
16// ->
17// ```
18// fn foo<T: Copy + Clone>() { }
19// ```
20pub(crate) fn flip_trait_bound(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
21 // We want to replicate the behavior of `flip_binexpr` by only suggesting
22 // the assist when the cursor is on a `+`
23 let plus = ctx.find_token_at_offset(T![+])?;
24
25 // Make sure we're in a `TypeBoundList`
26 if ast::TypeBoundList::cast(plus.parent()).is_none() {
27 return None;
28 }
29
30 let (before, after) = (
31 non_trivia_sibling(plus.clone().into(), Direction::Prev)?,
32 non_trivia_sibling(plus.clone().into(), Direction::Next)?,
33 );
34
35 let target = plus.text_range();
36 acc.add(
37 AssistId("flip_trait_bound", AssistKind::RefactorRewrite),
38 "Flip trait bounds",
39 target,
40 |edit| {
41 edit.replace(before.text_range(), after.to_string());
42 edit.replace(after.text_range(), before.to_string());
43 },
44 )
45}
46
47#[cfg(test)]
48mod tests {
49 use super::*;
50
51 use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target};
52
53 #[test]
54 fn flip_trait_bound_assist_available() {
55 check_assist_target(flip_trait_bound, "struct S<T> where T: A <|>+ B + C { }", "+")
56 }
57
58 #[test]
59 fn flip_trait_bound_not_applicable_for_single_trait_bound() {
60 check_assist_not_applicable(flip_trait_bound, "struct S<T> where T: <|>A { }")
61 }
62
63 #[test]
64 fn flip_trait_bound_works_for_struct() {
65 check_assist(
66 flip_trait_bound,
67 "struct S<T> where T: A <|>+ B { }",
68 "struct S<T> where T: B + A { }",
69 )
70 }
71
72 #[test]
73 fn flip_trait_bound_works_for_trait_impl() {
74 check_assist(
75 flip_trait_bound,
76 "impl X for S<T> where T: A +<|> B { }",
77 "impl X for S<T> where T: B + A { }",
78 )
79 }
80
81 #[test]
82 fn flip_trait_bound_works_for_fn() {
83 check_assist(flip_trait_bound, "fn f<T: A <|>+ B>(t: T) { }", "fn f<T: B + A>(t: T) { }")
84 }
85
86 #[test]
87 fn flip_trait_bound_works_for_fn_where_clause() {
88 check_assist(
89 flip_trait_bound,
90 "fn f<T>(t: T) where T: A +<|> B { }",
91 "fn f<T>(t: T) where T: B + A { }",
92 )
93 }
94
95 #[test]
96 fn flip_trait_bound_works_for_lifetime() {
97 check_assist(
98 flip_trait_bound,
99 "fn f<T>(t: T) where T: A <|>+ 'static { }",
100 "fn f<T>(t: T) where T: 'static + A { }",
101 )
102 }
103
104 #[test]
105 fn flip_trait_bound_works_for_complex_bounds() {
106 check_assist(
107 flip_trait_bound,
108 "struct S<T> where T: A<T> <|>+ b_mod::B<T> + C<T> { }",
109 "struct S<T> where T: b_mod::B<T> + A<T> + C<T> { }",
110 )
111 }
112
113 #[test]
114 fn flip_trait_bound_works_for_long_bounds() {
115 check_assist(
116 flip_trait_bound,
117 "struct S<T> where T: A + B + C + D + E + F +<|> G + H + I + J { }",
118 "struct S<T> where T: A + B + C + D + E + G + F + H + I + J { }",
119 )
120 }
121}
diff --git a/crates/assists/src/handlers/generate_derive.rs b/crates/assists/src/handlers/generate_derive.rs
new file mode 100644
index 000000000..314504e15
--- /dev/null
+++ b/crates/assists/src/handlers/generate_derive.rs
@@ -0,0 +1,132 @@
1use syntax::{
2 ast::{self, AstNode, AttrsOwner},
3 SyntaxKind::{COMMENT, WHITESPACE},
4 TextSize,
5};
6
7use crate::{AssistContext, AssistId, AssistKind, Assists};
8
9// Assist: generate_derive
10//
11// Adds a new `#[derive()]` clause to a struct or enum.
12//
13// ```
14// struct Point {
15// x: u32,
16// y: u32,<|>
17// }
18// ```
19// ->
20// ```
21// #[derive($0)]
22// struct Point {
23// x: u32,
24// y: u32,
25// }
26// ```
27pub(crate) fn generate_derive(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
28 let cap = ctx.config.snippet_cap?;
29 let nominal = ctx.find_node_at_offset::<ast::AdtDef>()?;
30 let node_start = derive_insertion_offset(&nominal)?;
31 let target = nominal.syntax().text_range();
32 acc.add(
33 AssistId("generate_derive", AssistKind::Generate),
34 "Add `#[derive]`",
35 target,
36 |builder| {
37 let derive_attr = nominal
38 .attrs()
39 .filter_map(|x| x.as_simple_call())
40 .filter(|(name, _arg)| name == "derive")
41 .map(|(_name, arg)| arg)
42 .next();
43 match derive_attr {
44 None => {
45 builder.insert_snippet(cap, node_start, "#[derive($0)]\n");
46 }
47 Some(tt) => {
48 // Just move the cursor.
49 builder.insert_snippet(
50 cap,
51 tt.syntax().text_range().end() - TextSize::of(')'),
52 "$0",
53 )
54 }
55 };
56 },
57 )
58}
59
60// Insert `derive` after doc comments.
61fn derive_insertion_offset(nominal: &ast::AdtDef) -> Option<TextSize> {
62 let non_ws_child = nominal
63 .syntax()
64 .children_with_tokens()
65 .find(|it| it.kind() != COMMENT && it.kind() != WHITESPACE)?;
66 Some(non_ws_child.text_range().start())
67}
68
69#[cfg(test)]
70mod tests {
71 use crate::tests::{check_assist, check_assist_target};
72
73 use super::*;
74
75 #[test]
76 fn add_derive_new() {
77 check_assist(
78 generate_derive,
79 "struct Foo { a: i32, <|>}",
80 "#[derive($0)]\nstruct Foo { a: i32, }",
81 );
82 check_assist(
83 generate_derive,
84 "struct Foo { <|> a: i32, }",
85 "#[derive($0)]\nstruct Foo { a: i32, }",
86 );
87 }
88
89 #[test]
90 fn add_derive_existing() {
91 check_assist(
92 generate_derive,
93 "#[derive(Clone)]\nstruct Foo { a: i32<|>, }",
94 "#[derive(Clone$0)]\nstruct Foo { a: i32, }",
95 );
96 }
97
98 #[test]
99 fn add_derive_new_with_doc_comment() {
100 check_assist(
101 generate_derive,
102 "
103/// `Foo` is a pretty important struct.
104/// It does stuff.
105struct Foo { a: i32<|>, }
106 ",
107 "
108/// `Foo` is a pretty important struct.
109/// It does stuff.
110#[derive($0)]
111struct Foo { a: i32, }
112 ",
113 );
114 }
115
116 #[test]
117 fn add_derive_target() {
118 check_assist_target(
119 generate_derive,
120 "
121struct SomeThingIrrelevant;
122/// `Foo` is a pretty important struct.
123/// It does stuff.
124struct Foo { a: i32<|>, }
125struct EvenMoreIrrelevant;
126 ",
127 "/// `Foo` is a pretty important struct.
128/// It does stuff.
129struct Foo { a: i32, }",
130 );
131 }
132}
diff --git a/crates/assists/src/handlers/generate_from_impl_for_enum.rs b/crates/assists/src/handlers/generate_from_impl_for_enum.rs
new file mode 100644
index 000000000..7f04b9572
--- /dev/null
+++ b/crates/assists/src/handlers/generate_from_impl_for_enum.rs
@@ -0,0 +1,200 @@
1use ide_db::RootDatabase;
2use syntax::ast::{self, AstNode, NameOwner};
3use test_utils::mark;
4
5use crate::{utils::FamousDefs, AssistContext, AssistId, AssistKind, Assists};
6
7// Assist: generate_from_impl_for_enum
8//
9// Adds a From impl for an enum variant with one tuple field.
10//
11// ```
12// enum A { <|>One(u32) }
13// ```
14// ->
15// ```
16// enum A { One(u32) }
17//
18// impl From<u32> for A {
19// fn from(v: u32) -> Self {
20// A::One(v)
21// }
22// }
23// ```
24pub(crate) fn generate_from_impl_for_enum(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
25 let variant = ctx.find_node_at_offset::<ast::Variant>()?;
26 let variant_name = variant.name()?;
27 let enum_name = variant.parent_enum().name()?;
28 let field_list = match variant.kind() {
29 ast::StructKind::Tuple(field_list) => field_list,
30 _ => return None,
31 };
32 if field_list.fields().count() != 1 {
33 return None;
34 }
35 let field_type = field_list.fields().next()?.ty()?;
36 let path = match field_type {
37 ast::Type::PathType(it) => it,
38 _ => return None,
39 };
40
41 if existing_from_impl(&ctx.sema, &variant).is_some() {
42 mark::hit!(test_add_from_impl_already_exists);
43 return None;
44 }
45
46 let target = variant.syntax().text_range();
47 acc.add(
48 AssistId("generate_from_impl_for_enum", AssistKind::Generate),
49 "Generate `From` impl for this enum variant",
50 target,
51 |edit| {
52 let start_offset = variant.parent_enum().syntax().text_range().end();
53 let buf = format!(
54 r#"
55
56impl From<{0}> for {1} {{
57 fn from(v: {0}) -> Self {{
58 {1}::{2}(v)
59 }}
60}}"#,
61 path.syntax(),
62 enum_name,
63 variant_name
64 );
65 edit.insert(start_offset, buf);
66 },
67 )
68}
69
70fn existing_from_impl(
71 sema: &'_ hir::Semantics<'_, RootDatabase>,
72 variant: &ast::Variant,
73) -> Option<()> {
74 let variant = sema.to_def(variant)?;
75 let enum_ = variant.parent_enum(sema.db);
76 let krate = enum_.module(sema.db).krate();
77
78 let from_trait = FamousDefs(sema, krate).core_convert_From()?;
79
80 let enum_type = enum_.ty(sema.db);
81
82 let wrapped_type = variant.fields(sema.db).get(0)?.signature_ty(sema.db);
83
84 if enum_type.impls_trait(sema.db, from_trait, &[wrapped_type]) {
85 Some(())
86 } else {
87 None
88 }
89}
90
91#[cfg(test)]
92mod tests {
93 use test_utils::mark;
94
95 use crate::tests::{check_assist, check_assist_not_applicable};
96
97 use super::*;
98
99 #[test]
100 fn test_generate_from_impl_for_enum() {
101 check_assist(
102 generate_from_impl_for_enum,
103 "enum A { <|>One(u32) }",
104 r#"enum A { One(u32) }
105
106impl From<u32> for A {
107 fn from(v: u32) -> Self {
108 A::One(v)
109 }
110}"#,
111 );
112 }
113
114 #[test]
115 fn test_generate_from_impl_for_enum_complicated_path() {
116 check_assist(
117 generate_from_impl_for_enum,
118 r#"enum A { <|>One(foo::bar::baz::Boo) }"#,
119 r#"enum A { One(foo::bar::baz::Boo) }
120
121impl From<foo::bar::baz::Boo> for A {
122 fn from(v: foo::bar::baz::Boo) -> Self {
123 A::One(v)
124 }
125}"#,
126 );
127 }
128
129 fn check_not_applicable(ra_fixture: &str) {
130 let fixture =
131 format!("//- /main.rs crate:main deps:core\n{}\n{}", ra_fixture, FamousDefs::FIXTURE);
132 check_assist_not_applicable(generate_from_impl_for_enum, &fixture)
133 }
134
135 #[test]
136 fn test_add_from_impl_no_element() {
137 check_not_applicable("enum A { <|>One }");
138 }
139
140 #[test]
141 fn test_add_from_impl_more_than_one_element_in_tuple() {
142 check_not_applicable("enum A { <|>One(u32, String) }");
143 }
144
145 #[test]
146 fn test_add_from_impl_struct_variant() {
147 check_not_applicable("enum A { <|>One { x: u32 } }");
148 }
149
150 #[test]
151 fn test_add_from_impl_already_exists() {
152 mark::check!(test_add_from_impl_already_exists);
153 check_not_applicable(
154 r#"
155enum A { <|>One(u32), }
156
157impl From<u32> for A {
158 fn from(v: u32) -> Self {
159 A::One(v)
160 }
161}
162"#,
163 );
164 }
165
166 #[test]
167 fn test_add_from_impl_different_variant_impl_exists() {
168 check_assist(
169 generate_from_impl_for_enum,
170 r#"enum A { <|>One(u32), Two(String), }
171
172impl From<String> for A {
173 fn from(v: String) -> Self {
174 A::Two(v)
175 }
176}
177
178pub trait From<T> {
179 fn from(T) -> Self;
180}"#,
181 r#"enum A { One(u32), Two(String), }
182
183impl From<u32> for A {
184 fn from(v: u32) -> Self {
185 A::One(v)
186 }
187}
188
189impl From<String> for A {
190 fn from(v: String) -> Self {
191 A::Two(v)
192 }
193}
194
195pub trait From<T> {
196 fn from(T) -> Self;
197}"#,
198 );
199 }
200}
diff --git a/crates/assists/src/handlers/generate_function.rs b/crates/assists/src/handlers/generate_function.rs
new file mode 100644
index 000000000..b38d64058
--- /dev/null
+++ b/crates/assists/src/handlers/generate_function.rs
@@ -0,0 +1,1058 @@
1use base_db::FileId;
2use hir::HirDisplay;
3use rustc_hash::{FxHashMap, FxHashSet};
4use syntax::{
5 ast::{
6 self,
7 edit::{AstNodeEdit, IndentLevel},
8 make, ArgListOwner, AstNode, ModuleItemOwner,
9 },
10 SyntaxKind, SyntaxNode, TextSize,
11};
12
13use crate::{
14 assist_config::SnippetCap,
15 utils::{render_snippet, Cursor},
16 AssistContext, AssistId, AssistKind, Assists,
17};
18
19// Assist: generate_function
20//
21// Adds a stub function with a signature matching the function under the cursor.
22//
23// ```
24// struct Baz;
25// fn baz() -> Baz { Baz }
26// fn foo() {
27// bar<|>("", baz());
28// }
29//
30// ```
31// ->
32// ```
33// struct Baz;
34// fn baz() -> Baz { Baz }
35// fn foo() {
36// bar("", baz());
37// }
38//
39// fn bar(arg: &str, baz: Baz) {
40// ${0:todo!()}
41// }
42//
43// ```
44pub(crate) fn generate_function(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
45 let path_expr: ast::PathExpr = ctx.find_node_at_offset()?;
46 let call = path_expr.syntax().parent().and_then(ast::CallExpr::cast)?;
47 let path = path_expr.path()?;
48
49 if ctx.sema.resolve_path(&path).is_some() {
50 // The function call already resolves, no need to add a function
51 return None;
52 }
53
54 let target_module = match path.qualifier() {
55 Some(qualifier) => match ctx.sema.resolve_path(&qualifier) {
56 Some(hir::PathResolution::Def(hir::ModuleDef::Module(module))) => Some(module),
57 _ => return None,
58 },
59 None => None,
60 };
61
62 let function_builder = FunctionBuilder::from_call(&ctx, &call, &path, target_module)?;
63
64 let target = call.syntax().text_range();
65 acc.add(
66 AssistId("generate_function", AssistKind::Generate),
67 format!("Generate `{}` function", function_builder.fn_name),
68 target,
69 |builder| {
70 let function_template = function_builder.render();
71 builder.edit_file(function_template.file);
72 let new_fn = function_template.to_string(ctx.config.snippet_cap);
73 match ctx.config.snippet_cap {
74 Some(cap) => builder.insert_snippet(cap, function_template.insert_offset, new_fn),
75 None => builder.insert(function_template.insert_offset, new_fn),
76 }
77 },
78 )
79}
80
81struct FunctionTemplate {
82 insert_offset: TextSize,
83 placeholder_expr: ast::MacroCall,
84 leading_ws: String,
85 fn_def: ast::Fn,
86 trailing_ws: String,
87 file: FileId,
88}
89
90impl FunctionTemplate {
91 fn to_string(&self, cap: Option<SnippetCap>) -> String {
92 let f = match cap {
93 Some(cap) => render_snippet(
94 cap,
95 self.fn_def.syntax(),
96 Cursor::Replace(self.placeholder_expr.syntax()),
97 ),
98 None => self.fn_def.to_string(),
99 };
100 format!("{}{}{}", self.leading_ws, f, self.trailing_ws)
101 }
102}
103
104struct FunctionBuilder {
105 target: GeneratedFunctionTarget,
106 fn_name: ast::Name,
107 type_params: Option<ast::GenericParamList>,
108 params: ast::ParamList,
109 file: FileId,
110 needs_pub: bool,
111}
112
113impl FunctionBuilder {
114 /// Prepares a generated function that matches `call`.
115 /// The function is generated in `target_module` or next to `call`
116 fn from_call(
117 ctx: &AssistContext,
118 call: &ast::CallExpr,
119 path: &ast::Path,
120 target_module: Option<hir::Module>,
121 ) -> Option<Self> {
122 let mut file = ctx.frange.file_id;
123 let target = match &target_module {
124 Some(target_module) => {
125 let module_source = target_module.definition_source(ctx.db());
126 let (in_file, target) = next_space_for_fn_in_module(ctx.sema.db, &module_source)?;
127 file = in_file;
128 target
129 }
130 None => next_space_for_fn_after_call_site(&call)?,
131 };
132 let needs_pub = target_module.is_some();
133 let target_module = target_module.or_else(|| ctx.sema.scope(target.syntax()).module())?;
134 let fn_name = fn_name(&path)?;
135 let (type_params, params) = fn_args(ctx, target_module, &call)?;
136
137 Some(Self { target, fn_name, type_params, params, file, needs_pub })
138 }
139
140 fn render(self) -> FunctionTemplate {
141 let placeholder_expr = make::expr_todo();
142 let fn_body = make::block_expr(vec![], Some(placeholder_expr));
143 let visibility = if self.needs_pub { Some(make::visibility_pub_crate()) } else { None };
144 let mut fn_def =
145 make::fn_(visibility, self.fn_name, self.type_params, self.params, fn_body);
146 let leading_ws;
147 let trailing_ws;
148
149 let insert_offset = match self.target {
150 GeneratedFunctionTarget::BehindItem(it) => {
151 let indent = IndentLevel::from_node(&it);
152 leading_ws = format!("\n\n{}", indent);
153 fn_def = fn_def.indent(indent);
154 trailing_ws = String::new();
155 it.text_range().end()
156 }
157 GeneratedFunctionTarget::InEmptyItemList(it) => {
158 let indent = IndentLevel::from_node(it.syntax());
159 leading_ws = format!("\n{}", indent + 1);
160 fn_def = fn_def.indent(indent + 1);
161 trailing_ws = format!("\n{}", indent);
162 it.syntax().text_range().start() + TextSize::of('{')
163 }
164 };
165
166 let placeholder_expr =
167 fn_def.syntax().descendants().find_map(ast::MacroCall::cast).unwrap();
168 FunctionTemplate {
169 insert_offset,
170 placeholder_expr,
171 leading_ws,
172 fn_def,
173 trailing_ws,
174 file: self.file,
175 }
176 }
177}
178
179enum GeneratedFunctionTarget {
180 BehindItem(SyntaxNode),
181 InEmptyItemList(ast::ItemList),
182}
183
184impl GeneratedFunctionTarget {
185 fn syntax(&self) -> &SyntaxNode {
186 match self {
187 GeneratedFunctionTarget::BehindItem(it) => it,
188 GeneratedFunctionTarget::InEmptyItemList(it) => it.syntax(),
189 }
190 }
191}
192
193fn fn_name(call: &ast::Path) -> Option<ast::Name> {
194 let name = call.segment()?.syntax().to_string();
195 Some(make::name(&name))
196}
197
198/// Computes the type variables and arguments required for the generated function
199fn fn_args(
200 ctx: &AssistContext,
201 target_module: hir::Module,
202 call: &ast::CallExpr,
203) -> Option<(Option<ast::GenericParamList>, ast::ParamList)> {
204 let mut arg_names = Vec::new();
205 let mut arg_types = Vec::new();
206 for arg in call.arg_list()?.args() {
207 arg_names.push(match fn_arg_name(&arg) {
208 Some(name) => name,
209 None => String::from("arg"),
210 });
211 arg_types.push(match fn_arg_type(ctx, target_module, &arg) {
212 Some(ty) => ty,
213 None => String::from("()"),
214 });
215 }
216 deduplicate_arg_names(&mut arg_names);
217 let params = arg_names.into_iter().zip(arg_types).map(|(name, ty)| make::param(name, ty));
218 Some((None, make::param_list(params)))
219}
220
221/// Makes duplicate argument names unique by appending incrementing numbers.
222///
223/// ```
224/// let mut names: Vec<String> =
225/// vec!["foo".into(), "foo".into(), "bar".into(), "baz".into(), "bar".into()];
226/// deduplicate_arg_names(&mut names);
227/// let expected: Vec<String> =
228/// vec!["foo_1".into(), "foo_2".into(), "bar_1".into(), "baz".into(), "bar_2".into()];
229/// assert_eq!(names, expected);
230/// ```
231fn deduplicate_arg_names(arg_names: &mut Vec<String>) {
232 let arg_name_counts = arg_names.iter().fold(FxHashMap::default(), |mut m, name| {
233 *m.entry(name).or_insert(0) += 1;
234 m
235 });
236 let duplicate_arg_names: FxHashSet<String> = arg_name_counts
237 .into_iter()
238 .filter(|(_, count)| *count >= 2)
239 .map(|(name, _)| name.clone())
240 .collect();
241
242 let mut counter_per_name = FxHashMap::default();
243 for arg_name in arg_names.iter_mut() {
244 if duplicate_arg_names.contains(arg_name) {
245 let counter = counter_per_name.entry(arg_name.clone()).or_insert(1);
246 arg_name.push('_');
247 arg_name.push_str(&counter.to_string());
248 *counter += 1;
249 }
250 }
251}
252
253fn fn_arg_name(fn_arg: &ast::Expr) -> Option<String> {
254 match fn_arg {
255 ast::Expr::CastExpr(cast_expr) => fn_arg_name(&cast_expr.expr()?),
256 _ => Some(
257 fn_arg
258 .syntax()
259 .descendants()
260 .filter(|d| ast::NameRef::can_cast(d.kind()))
261 .last()?
262 .to_string(),
263 ),
264 }
265}
266
267fn fn_arg_type(
268 ctx: &AssistContext,
269 target_module: hir::Module,
270 fn_arg: &ast::Expr,
271) -> Option<String> {
272 let ty = ctx.sema.type_of_expr(fn_arg)?;
273 if ty.is_unknown() {
274 return None;
275 }
276
277 if let Ok(rendered) = ty.display_source_code(ctx.db(), target_module.into()) {
278 Some(rendered)
279 } else {
280 None
281 }
282}
283
284/// Returns the position inside the current mod or file
285/// directly after the current block
286/// We want to write the generated function directly after
287/// fns, impls or macro calls, but inside mods
288fn next_space_for_fn_after_call_site(expr: &ast::CallExpr) -> Option<GeneratedFunctionTarget> {
289 let mut ancestors = expr.syntax().ancestors().peekable();
290 let mut last_ancestor: Option<SyntaxNode> = None;
291 while let Some(next_ancestor) = ancestors.next() {
292 match next_ancestor.kind() {
293 SyntaxKind::SOURCE_FILE => {
294 break;
295 }
296 SyntaxKind::ITEM_LIST => {
297 if ancestors.peek().map(|a| a.kind()) == Some(SyntaxKind::MODULE) {
298 break;
299 }
300 }
301 _ => {}
302 }
303 last_ancestor = Some(next_ancestor);
304 }
305 last_ancestor.map(GeneratedFunctionTarget::BehindItem)
306}
307
308fn next_space_for_fn_in_module(
309 db: &dyn hir::db::AstDatabase,
310 module_source: &hir::InFile<hir::ModuleSource>,
311) -> Option<(FileId, GeneratedFunctionTarget)> {
312 let file = module_source.file_id.original_file(db);
313 let assist_item = match &module_source.value {
314 hir::ModuleSource::SourceFile(it) => {
315 if let Some(last_item) = it.items().last() {
316 GeneratedFunctionTarget::BehindItem(last_item.syntax().clone())
317 } else {
318 GeneratedFunctionTarget::BehindItem(it.syntax().clone())
319 }
320 }
321 hir::ModuleSource::Module(it) => {
322 if let Some(last_item) = it.item_list().and_then(|it| it.items().last()) {
323 GeneratedFunctionTarget::BehindItem(last_item.syntax().clone())
324 } else {
325 GeneratedFunctionTarget::InEmptyItemList(it.item_list()?)
326 }
327 }
328 };
329 Some((file, assist_item))
330}
331
332#[cfg(test)]
333mod tests {
334 use crate::tests::{check_assist, check_assist_not_applicable};
335
336 use super::*;
337
338 #[test]
339 fn add_function_with_no_args() {
340 check_assist(
341 generate_function,
342 r"
343fn foo() {
344 bar<|>();
345}
346",
347 r"
348fn foo() {
349 bar();
350}
351
352fn bar() {
353 ${0:todo!()}
354}
355",
356 )
357 }
358
359 #[test]
360 fn add_function_from_method() {
361 // This ensures that the function is correctly generated
362 // in the next outer mod or file
363 check_assist(
364 generate_function,
365 r"
366impl Foo {
367 fn foo() {
368 bar<|>();
369 }
370}
371",
372 r"
373impl Foo {
374 fn foo() {
375 bar();
376 }
377}
378
379fn bar() {
380 ${0:todo!()}
381}
382",
383 )
384 }
385
386 #[test]
387 fn add_function_directly_after_current_block() {
388 // The new fn should not be created at the end of the file or module
389 check_assist(
390 generate_function,
391 r"
392fn foo1() {
393 bar<|>();
394}
395
396fn foo2() {}
397",
398 r"
399fn foo1() {
400 bar();
401}
402
403fn bar() {
404 ${0:todo!()}
405}
406
407fn foo2() {}
408",
409 )
410 }
411
412 #[test]
413 fn add_function_with_no_args_in_same_module() {
414 check_assist(
415 generate_function,
416 r"
417mod baz {
418 fn foo() {
419 bar<|>();
420 }
421}
422",
423 r"
424mod baz {
425 fn foo() {
426 bar();
427 }
428
429 fn bar() {
430 ${0:todo!()}
431 }
432}
433",
434 )
435 }
436
437 #[test]
438 fn add_function_with_function_call_arg() {
439 check_assist(
440 generate_function,
441 r"
442struct Baz;
443fn baz() -> Baz { todo!() }
444fn foo() {
445 bar<|>(baz());
446}
447",
448 r"
449struct Baz;
450fn baz() -> Baz { todo!() }
451fn foo() {
452 bar(baz());
453}
454
455fn bar(baz: Baz) {
456 ${0:todo!()}
457}
458",
459 );
460 }
461
462 #[test]
463 fn add_function_with_method_call_arg() {
464 check_assist(
465 generate_function,
466 r"
467struct Baz;
468impl Baz {
469 fn foo(&self) -> Baz {
470 ba<|>r(self.baz())
471 }
472 fn baz(&self) -> Baz {
473 Baz
474 }
475}
476",
477 r"
478struct Baz;
479impl Baz {
480 fn foo(&self) -> Baz {
481 bar(self.baz())
482 }
483 fn baz(&self) -> Baz {
484 Baz
485 }
486}
487
488fn bar(baz: Baz) {
489 ${0:todo!()}
490}
491",
492 )
493 }
494
495 #[test]
496 fn add_function_with_string_literal_arg() {
497 check_assist(
498 generate_function,
499 r#"
500fn foo() {
501 <|>bar("bar")
502}
503"#,
504 r#"
505fn foo() {
506 bar("bar")
507}
508
509fn bar(arg: &str) {
510 ${0:todo!()}
511}
512"#,
513 )
514 }
515
516 #[test]
517 fn add_function_with_char_literal_arg() {
518 check_assist(
519 generate_function,
520 r#"
521fn foo() {
522 <|>bar('x')
523}
524"#,
525 r#"
526fn foo() {
527 bar('x')
528}
529
530fn bar(arg: char) {
531 ${0:todo!()}
532}
533"#,
534 )
535 }
536
537 #[test]
538 fn add_function_with_int_literal_arg() {
539 check_assist(
540 generate_function,
541 r"
542fn foo() {
543 <|>bar(42)
544}
545",
546 r"
547fn foo() {
548 bar(42)
549}
550
551fn bar(arg: i32) {
552 ${0:todo!()}
553}
554",
555 )
556 }
557
558 #[test]
559 fn add_function_with_cast_int_literal_arg() {
560 check_assist(
561 generate_function,
562 r"
563fn foo() {
564 <|>bar(42 as u8)
565}
566",
567 r"
568fn foo() {
569 bar(42 as u8)
570}
571
572fn bar(arg: u8) {
573 ${0:todo!()}
574}
575",
576 )
577 }
578
579 #[test]
580 fn name_of_cast_variable_is_used() {
581 // Ensures that the name of the cast type isn't used
582 // in the generated function signature.
583 check_assist(
584 generate_function,
585 r"
586fn foo() {
587 let x = 42;
588 bar<|>(x as u8)
589}
590",
591 r"
592fn foo() {
593 let x = 42;
594 bar(x as u8)
595}
596
597fn bar(x: u8) {
598 ${0:todo!()}
599}
600",
601 )
602 }
603
604 #[test]
605 fn add_function_with_variable_arg() {
606 check_assist(
607 generate_function,
608 r"
609fn foo() {
610 let worble = ();
611 <|>bar(worble)
612}
613",
614 r"
615fn foo() {
616 let worble = ();
617 bar(worble)
618}
619
620fn bar(worble: ()) {
621 ${0:todo!()}
622}
623",
624 )
625 }
626
627 #[test]
628 fn add_function_with_impl_trait_arg() {
629 check_assist(
630 generate_function,
631 r"
632trait Foo {}
633fn foo() -> impl Foo {
634 todo!()
635}
636fn baz() {
637 <|>bar(foo())
638}
639",
640 r"
641trait Foo {}
642fn foo() -> impl Foo {
643 todo!()
644}
645fn baz() {
646 bar(foo())
647}
648
649fn bar(foo: impl Foo) {
650 ${0:todo!()}
651}
652",
653 )
654 }
655
656 #[test]
657 fn borrowed_arg() {
658 check_assist(
659 generate_function,
660 r"
661struct Baz;
662fn baz() -> Baz { todo!() }
663
664fn foo() {
665 bar<|>(&baz())
666}
667",
668 r"
669struct Baz;
670fn baz() -> Baz { todo!() }
671
672fn foo() {
673 bar(&baz())
674}
675
676fn bar(baz: &Baz) {
677 ${0:todo!()}
678}
679",
680 )
681 }
682
683 #[test]
684 fn add_function_with_qualified_path_arg() {
685 check_assist(
686 generate_function,
687 r"
688mod Baz {
689 pub struct Bof;
690 pub fn baz() -> Bof { Bof }
691}
692fn foo() {
693 <|>bar(Baz::baz())
694}
695",
696 r"
697mod Baz {
698 pub struct Bof;
699 pub fn baz() -> Bof { Bof }
700}
701fn foo() {
702 bar(Baz::baz())
703}
704
705fn bar(baz: Baz::Bof) {
706 ${0:todo!()}
707}
708",
709 )
710 }
711
712 #[test]
713 #[ignore]
714 // FIXME fix printing the generics of a `Ty` to make this test pass
715 fn add_function_with_generic_arg() {
716 check_assist(
717 generate_function,
718 r"
719fn foo<T>(t: T) {
720 <|>bar(t)
721}
722",
723 r"
724fn foo<T>(t: T) {
725 bar(t)
726}
727
728fn bar<T>(t: T) {
729 ${0:todo!()}
730}
731",
732 )
733 }
734
735 #[test]
736 #[ignore]
737 // FIXME Fix function type printing to make this test pass
738 fn add_function_with_fn_arg() {
739 check_assist(
740 generate_function,
741 r"
742struct Baz;
743impl Baz {
744 fn new() -> Self { Baz }
745}
746fn foo() {
747 <|>bar(Baz::new);
748}
749",
750 r"
751struct Baz;
752impl Baz {
753 fn new() -> Self { Baz }
754}
755fn foo() {
756 bar(Baz::new);
757}
758
759fn bar(arg: fn() -> Baz) {
760 ${0:todo!()}
761}
762",
763 )
764 }
765
766 #[test]
767 #[ignore]
768