aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_assists/src
diff options
context:
space:
mode:
authorZac Pullar-Strecker <[email protected]>2020-08-24 10:19:53 +0100
committerZac Pullar-Strecker <[email protected]>2020-08-24 10:20:13 +0100
commit7bbca7a1b3f9293d2f5cc5745199bc5f8396f2f0 (patch)
treebdb47765991cb973b2cd5481a088fac636bd326c /crates/ra_assists/src
parentca464650eeaca6195891199a93f4f76cf3e7e697 (diff)
parente65d48d1fb3d4d91d9dc1148a7a836ff5c9a3c87 (diff)
Merge remote-tracking branch 'upstream/master' into 503-hover-doc-links
Diffstat (limited to 'crates/ra_assists/src')
-rw-r--r--crates/ra_assists/src/assist_config.rs30
-rw-r--r--crates/ra_assists/src/assist_context.rs302
-rw-r--r--crates/ra_assists/src/ast_transform.rs206
-rw-r--r--crates/ra_assists/src/handlers/add_custom_impl.rs208
-rw-r--r--crates/ra_assists/src/handlers/add_explicit_type.rs211
-rw-r--r--crates/ra_assists/src/handlers/add_missing_impl_members.rs711
-rw-r--r--crates/ra_assists/src/handlers/add_turbo_fish.rs164
-rw-r--r--crates/ra_assists/src/handlers/apply_demorgan.rs93
-rw-r--r--crates/ra_assists/src/handlers/auto_import.rs1089
-rw-r--r--crates/ra_assists/src/handlers/change_return_type_to_result.rs990
-rw-r--r--crates/ra_assists/src/handlers/change_visibility.rs200
-rw-r--r--crates/ra_assists/src/handlers/early_return.rs515
-rw-r--r--crates/ra_assists/src/handlers/extract_struct_from_enum_variant.rs321
-rw-r--r--crates/ra_assists/src/handlers/extract_variable.rs588
-rw-r--r--crates/ra_assists/src/handlers/fill_match_arms.rs750
-rw-r--r--crates/ra_assists/src/handlers/fix_visibility.rs607
-rw-r--r--crates/ra_assists/src/handlers/flip_binexpr.rs142
-rw-r--r--crates/ra_assists/src/handlers/flip_comma.rs84
-rw-r--r--crates/ra_assists/src/handlers/flip_trait_bound.rs121
-rw-r--r--crates/ra_assists/src/handlers/generate_derive.rs132
-rw-r--r--crates/ra_assists/src/handlers/generate_from_impl_for_enum.rs200
-rw-r--r--crates/ra_assists/src/handlers/generate_function.rs1058
-rw-r--r--crates/ra_assists/src/handlers/generate_impl.rs109
-rw-r--r--crates/ra_assists/src/handlers/generate_new.rs420
-rw-r--r--crates/ra_assists/src/handlers/inline_local_variable.rs695
-rw-r--r--crates/ra_assists/src/handlers/introduce_named_lifetime.rs318
-rw-r--r--crates/ra_assists/src/handlers/invert_if.rs109
-rw-r--r--crates/ra_assists/src/handlers/merge_imports.rs294
-rw-r--r--crates/ra_assists/src/handlers/merge_match_arms.rs248
-rw-r--r--crates/ra_assists/src/handlers/move_bounds.rs152
-rw-r--r--crates/ra_assists/src/handlers/move_guard.rs303
-rw-r--r--crates/ra_assists/src/handlers/raw_string.rs504
-rw-r--r--crates/ra_assists/src/handlers/remove_dbg.rs205
-rw-r--r--crates/ra_assists/src/handlers/remove_mut.rs37
-rw-r--r--crates/ra_assists/src/handlers/reorder_fields.rs220
-rw-r--r--crates/ra_assists/src/handlers/replace_if_let_with_match.rs255
-rw-r--r--crates/ra_assists/src/handlers/replace_let_with_if_let.rs101
-rw-r--r--crates/ra_assists/src/handlers/replace_qualified_name_with_use.rs646
-rw-r--r--crates/ra_assists/src/handlers/replace_unwrap_with_match.rs187
-rw-r--r--crates/ra_assists/src/handlers/split_import.rs79
-rw-r--r--crates/ra_assists/src/handlers/unwrap_block.rs518
-rw-r--r--crates/ra_assists/src/lib.rs224
-rw-r--r--crates/ra_assists/src/tests.rs179
-rw-r--r--crates/ra_assists/src/tests/generated.rs863
-rw-r--r--crates/ra_assists/src/utils.rs278
-rw-r--r--crates/ra_assists/src/utils/insert_use.rs525
46 files changed, 0 insertions, 16191 deletions
diff --git a/crates/ra_assists/src/assist_config.rs b/crates/ra_assists/src/assist_config.rs
deleted file mode 100644
index cda2abfb9..000000000
--- a/crates/ra_assists/src/assist_config.rs
+++ /dev/null
@@ -1,30 +0,0 @@
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/ra_assists/src/assist_context.rs b/crates/ra_assists/src/assist_context.rs
deleted file mode 100644
index 3407df856..000000000
--- a/crates/ra_assists/src/assist_context.rs
+++ /dev/null
@@ -1,302 +0,0 @@
1//! See `AssistContext`
2
3use std::mem;
4
5use algo::find_covering_element;
6use hir::Semantics;
7use ra_db::{FileId, FileRange};
8use ra_fmt::{leading_indent, reindent};
9use ra_ide_db::{
10 source_change::{SourceChange, SourceFileEdit},
11 RootDatabase,
12};
13use ra_syntax::{
14 algo::{self, find_node_at_offset, SyntaxRewriter},
15 AstNode, SourceFile, SyntaxElement, SyntaxKind, SyntaxNode, SyntaxToken, TextRange, TextSize,
16 TokenAtOffset,
17};
18use ra_text_edit::TextEditBuilder;
19
20use crate::{
21 assist_config::{AssistConfig, SnippetCap},
22 Assist, AssistId, AssistKind, GroupLabel, ResolvedAssist,
23};
24
25/// `AssistContext` allows to apply an assist or check if it could be applied.
26///
27/// Assists use a somewhat over-engineered approach, given the current needs.
28/// The assists workflow consists of two phases. In the first phase, a user asks
29/// for the list of available assists. In the second phase, the user picks a
30/// particular assist and it gets applied.
31///
32/// There are two peculiarities here:
33///
34/// * first, we ideally avoid computing more things then necessary to answer "is
35/// assist applicable" in the first phase.
36/// * second, when we are applying assist, we don't have a guarantee that there
37/// weren't any changes between the point when user asked for assists and when
38/// they applied a particular assist. So, when applying assist, we need to do
39/// all the checks from scratch.
40///
41/// To avoid repeating the same code twice for both "check" and "apply"
42/// functions, we use an approach reminiscent of that of Django's function based
43/// views dealing with forms. Each assist receives a runtime parameter,
44/// `resolve`. It first check if an edit is applicable (potentially computing
45/// info required to compute the actual edit). If it is applicable, and
46/// `resolve` is `true`, it then computes the actual edit.
47///
48/// So, to implement the original assists workflow, we can first apply each edit
49/// with `resolve = false`, and then applying the selected edit again, with
50/// `resolve = true` this time.
51///
52/// Note, however, that we don't actually use such two-phase logic at the
53/// moment, because the LSP API is pretty awkward in this place, and it's much
54/// easier to just compute the edit eagerly :-)
55pub(crate) struct AssistContext<'a> {
56 pub(crate) config: &'a AssistConfig,
57 pub(crate) sema: Semantics<'a, RootDatabase>,
58 pub(crate) frange: FileRange,
59 source_file: SourceFile,
60}
61
62impl<'a> AssistContext<'a> {
63 pub(crate) fn new(
64 sema: Semantics<'a, RootDatabase>,
65 config: &'a AssistConfig,
66 frange: FileRange,
67 ) -> AssistContext<'a> {
68 let source_file = sema.parse(frange.file_id);
69 AssistContext { config, sema, frange, source_file }
70 }
71
72 pub(crate) fn db(&self) -> &RootDatabase {
73 self.sema.db
74 }
75
76 // NB, this ignores active selection.
77 pub(crate) fn offset(&self) -> TextSize {
78 self.frange.range.start()
79 }
80
81 pub(crate) fn token_at_offset(&self) -> TokenAtOffset<SyntaxToken> {
82 self.source_file.syntax().token_at_offset(self.offset())
83 }
84 pub(crate) fn find_token_at_offset(&self, kind: SyntaxKind) -> Option<SyntaxToken> {
85 self.token_at_offset().find(|it| it.kind() == kind)
86 }
87 pub(crate) fn find_node_at_offset<N: AstNode>(&self) -> Option<N> {
88 find_node_at_offset(self.source_file.syntax(), self.offset())
89 }
90 pub(crate) fn find_node_at_offset_with_descend<N: AstNode>(&self) -> Option<N> {
91 self.sema.find_node_at_offset_with_descend(self.source_file.syntax(), self.offset())
92 }
93 pub(crate) fn covering_element(&self) -> SyntaxElement {
94 find_covering_element(self.source_file.syntax(), self.frange.range)
95 }
96 // FIXME: remove
97 pub(crate) fn covering_node_for_range(&self, range: TextRange) -> SyntaxElement {
98 find_covering_element(self.source_file.syntax(), range)
99 }
100}
101
102pub(crate) struct Assists {
103 resolve: bool,
104 file: FileId,
105 buf: Vec<(Assist, Option<SourceChange>)>,
106 allowed: Option<Vec<AssistKind>>,
107}
108
109impl Assists {
110 pub(crate) fn new_resolved(ctx: &AssistContext) -> Assists {
111 Assists {
112 resolve: true,
113 file: ctx.frange.file_id,
114 buf: Vec::new(),
115 allowed: ctx.config.allowed.clone(),
116 }
117 }
118
119 pub(crate) fn new_unresolved(ctx: &AssistContext) -> Assists {
120 Assists {
121 resolve: false,
122 file: ctx.frange.file_id,
123 buf: Vec::new(),
124 allowed: ctx.config.allowed.clone(),
125 }
126 }
127
128 pub(crate) fn finish_unresolved(self) -> Vec<Assist> {
129 assert!(!self.resolve);
130 self.finish()
131 .into_iter()
132 .map(|(label, edit)| {
133 assert!(edit.is_none());
134 label
135 })
136 .collect()
137 }
138
139 pub(crate) fn finish_resolved(self) -> Vec<ResolvedAssist> {
140 assert!(self.resolve);
141 self.finish()
142 .into_iter()
143 .map(|(label, edit)| ResolvedAssist { assist: label, source_change: edit.unwrap() })
144 .collect()
145 }
146
147 pub(crate) fn add(
148 &mut self,
149 id: AssistId,
150 label: impl Into<String>,
151 target: TextRange,
152 f: impl FnOnce(&mut AssistBuilder),
153 ) -> Option<()> {
154 if !self.is_allowed(&id) {
155 return None;
156 }
157 let label = Assist::new(id, label.into(), None, target);
158 self.add_impl(label, f)
159 }
160
161 pub(crate) fn add_group(
162 &mut self,
163 group: &GroupLabel,
164 id: AssistId,
165 label: impl Into<String>,
166 target: TextRange,
167 f: impl FnOnce(&mut AssistBuilder),
168 ) -> Option<()> {
169 if !self.is_allowed(&id) {
170 return None;
171 }
172
173 let label = Assist::new(id, label.into(), Some(group.clone()), target);
174 self.add_impl(label, f)
175 }
176
177 fn add_impl(&mut self, label: Assist, f: impl FnOnce(&mut AssistBuilder)) -> Option<()> {
178 let source_change = if self.resolve {
179 let mut builder = AssistBuilder::new(self.file);
180 f(&mut builder);
181 Some(builder.finish())
182 } else {
183 None
184 };
185
186 self.buf.push((label, source_change));
187 Some(())
188 }
189
190 fn finish(mut self) -> Vec<(Assist, Option<SourceChange>)> {
191 self.buf.sort_by_key(|(label, _edit)| label.target.len());
192 self.buf
193 }
194
195 fn is_allowed(&self, id: &AssistId) -> bool {
196 match &self.allowed {
197 Some(allowed) => allowed.iter().any(|kind| kind.contains(id.1)),
198 None => true,
199 }
200 }
201}
202
203pub(crate) struct AssistBuilder {
204 edit: TextEditBuilder,
205 file_id: FileId,
206 is_snippet: bool,
207 change: SourceChange,
208}
209
210impl AssistBuilder {
211 pub(crate) fn new(file_id: FileId) -> AssistBuilder {
212 AssistBuilder {
213 edit: TextEditBuilder::default(),
214 file_id,
215 is_snippet: false,
216 change: SourceChange::default(),
217 }
218 }
219
220 pub(crate) fn edit_file(&mut self, file_id: FileId) {
221 self.file_id = file_id;
222 }
223
224 fn commit(&mut self) {
225 let edit = mem::take(&mut self.edit).finish();
226 if !edit.is_empty() {
227 let new_edit = SourceFileEdit { file_id: self.file_id, edit };
228 assert!(!self.change.source_file_edits.iter().any(|it| it.file_id == new_edit.file_id));
229 self.change.source_file_edits.push(new_edit);
230 }
231 }
232
233 /// Remove specified `range` of text.
234 pub(crate) fn delete(&mut self, range: TextRange) {
235 self.edit.delete(range)
236 }
237 /// Append specified `text` at the given `offset`
238 pub(crate) fn insert(&mut self, offset: TextSize, text: impl Into<String>) {
239 self.edit.insert(offset, text.into())
240 }
241 /// Append specified `snippet` at the given `offset`
242 pub(crate) fn insert_snippet(
243 &mut self,
244 _cap: SnippetCap,
245 offset: TextSize,
246 snippet: impl Into<String>,
247 ) {
248 self.is_snippet = true;
249 self.insert(offset, snippet);
250 }
251 /// Replaces specified `range` of text with a given string.
252 pub(crate) fn replace(&mut self, range: TextRange, replace_with: impl Into<String>) {
253 self.edit.replace(range, replace_with.into())
254 }
255 /// Replaces specified `range` of text with a given `snippet`.
256 pub(crate) fn replace_snippet(
257 &mut self,
258 _cap: SnippetCap,
259 range: TextRange,
260 snippet: impl Into<String>,
261 ) {
262 self.is_snippet = true;
263 self.replace(range, snippet);
264 }
265 pub(crate) fn replace_ast<N: AstNode>(&mut self, old: N, new: N) {
266 algo::diff(old.syntax(), new.syntax()).into_text_edit(&mut self.edit)
267 }
268 /// Replaces specified `node` of text with a given string, reindenting the
269 /// string to maintain `node`'s existing indent.
270 // FIXME: remove in favor of ra_syntax::edit::IndentLevel::increase_indent
271 pub(crate) fn replace_node_and_indent(
272 &mut self,
273 node: &SyntaxNode,
274 replace_with: impl Into<String>,
275 ) {
276 let mut replace_with = replace_with.into();
277 if let Some(indent) = leading_indent(node) {
278 replace_with = reindent(&replace_with, &indent)
279 }
280 self.replace(node.text_range(), replace_with)
281 }
282 pub(crate) fn rewrite(&mut self, rewriter: SyntaxRewriter) {
283 let node = rewriter.rewrite_root().unwrap();
284 let new = rewriter.rewrite(&node);
285 algo::diff(&node, &new).into_text_edit(&mut self.edit);
286 }
287
288 // FIXME: kill this API
289 /// Get access to the raw `TextEditBuilder`.
290 pub(crate) fn text_edit_builder(&mut self) -> &mut TextEditBuilder {
291 &mut self.edit
292 }
293
294 fn finish(mut self) -> SourceChange {
295 self.commit();
296 let mut change = mem::take(&mut self.change);
297 if self.is_snippet {
298 change.is_snippet = true;
299 }
300 change
301 }
302}
diff --git a/crates/ra_assists/src/ast_transform.rs b/crates/ra_assists/src/ast_transform.rs
deleted file mode 100644
index 5ea4f9f5b..000000000
--- a/crates/ra_assists/src/ast_transform.rs
+++ /dev/null
@@ -1,206 +0,0 @@
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 ra_syntax::{
6 algo::SyntaxRewriter,
7 ast::{self, AstNode},
8};
9
10pub trait AstTransform<'a> {
11 fn get_substitution(&self, node: &ra_syntax::SyntaxNode) -> Option<ra_syntax::SyntaxNode>;
12
13 fn chain_before(self, other: Box<dyn AstTransform<'a> + 'a>) -> Box<dyn AstTransform<'a> + 'a>;
14 fn or<T: AstTransform<'a> + 'a>(self, other: T) -> Box<dyn AstTransform<'a> + 'a>
15 where
16 Self: Sized + 'a,
17 {
18 self.chain_before(Box::new(other))
19 }
20}
21
22struct NullTransformer;
23
24impl<'a> AstTransform<'a> for NullTransformer {
25 fn get_substitution(&self, _node: &ra_syntax::SyntaxNode) -> Option<ra_syntax::SyntaxNode> {
26 None
27 }
28 fn chain_before(self, other: Box<dyn AstTransform<'a> + 'a>) -> Box<dyn AstTransform<'a> + 'a> {
29 other
30 }
31}
32
33pub struct SubstituteTypeParams<'a> {
34 source_scope: &'a SemanticsScope<'a>,
35 substs: FxHashMap<hir::TypeParam, ast::TypeRef>,
36 previous: Box<dyn AstTransform<'a> + 'a>,
37}
38
39impl<'a> SubstituteTypeParams<'a> {
40 pub fn for_trait_impl(
41 source_scope: &'a SemanticsScope<'a>,
42 // FIXME: there's implicit invariant that `trait_` and `source_scope` match...
43 trait_: hir::Trait,
44 impl_def: ast::Impl,
45 ) -> SubstituteTypeParams<'a> {
46 let substs = get_syntactic_substs(impl_def).unwrap_or_default();
47 let generic_def: hir::GenericDef = trait_.into();
48 let substs_by_param: FxHashMap<_, _> = generic_def
49 .params(source_scope.db)
50 .into_iter()
51 // this is a trait impl, so we need to skip the first type parameter -- this is a bit hacky
52 .skip(1)
53 // The actual list of trait type parameters may be longer than the one
54 // used in the `impl` block due to trailing default type parametrs.
55 // For that case we extend the `substs` with an empty iterator so we
56 // can still hit those trailing values and check if they actually have
57 // a default type. If they do, go for that type from `hir` to `ast` so
58 // the resulting change can be applied correctly.
59 .zip(substs.into_iter().map(Some).chain(std::iter::repeat(None)))
60 .filter_map(|(k, v)| match v {
61 Some(v) => Some((k, v)),
62 None => {
63 let default = k.default(source_scope.db)?;
64 Some((
65 k,
66 ast::make::type_ref(
67 &default
68 .display_source_code(source_scope.db, source_scope.module()?.into())
69 .ok()?,
70 ),
71 ))
72 }
73 })
74 .collect();
75 return SubstituteTypeParams {
76 source_scope,
77 substs: substs_by_param,
78 previous: Box::new(NullTransformer),
79 };
80
81 // FIXME: It would probably be nicer if we could get this via HIR (i.e. get the
82 // trait ref, and then go from the types in the substs back to the syntax)
83 fn get_syntactic_substs(impl_def: ast::Impl) -> Option<Vec<ast::TypeRef>> {
84 let target_trait = impl_def.target_trait()?;
85 let path_type = match target_trait {
86 ast::TypeRef::PathType(path) => path,
87 _ => return None,
88 };
89 let type_arg_list = path_type.path()?.segment()?.type_arg_list()?;
90 let mut result = Vec::new();
91 for type_arg in type_arg_list.type_args() {
92 let type_arg: ast::TypeArg = type_arg;
93 result.push(type_arg.type_ref()?);
94 }
95 Some(result)
96 }
97 }
98 fn get_substitution_inner(
99 &self,
100 node: &ra_syntax::SyntaxNode,
101 ) -> Option<ra_syntax::SyntaxNode> {
102 let type_ref = ast::TypeRef::cast(node.clone())?;
103 let path = match &type_ref {
104 ast::TypeRef::PathType(path_type) => path_type.path()?,
105 _ => return None,
106 };
107 // FIXME: use `hir::Path::from_src` instead.
108 #[allow(deprecated)]
109 let path = hir::Path::from_ast(path)?;
110 let resolution = self.source_scope.resolve_hir_path(&path)?;
111 match resolution {
112 hir::PathResolution::TypeParam(tp) => Some(self.substs.get(&tp)?.syntax().clone()),
113 _ => None,
114 }
115 }
116}
117
118impl<'a> AstTransform<'a> for SubstituteTypeParams<'a> {
119 fn get_substitution(&self, node: &ra_syntax::SyntaxNode) -> Option<ra_syntax::SyntaxNode> {
120 self.get_substitution_inner(node).or_else(|| self.previous.get_substitution(node))
121 }
122 fn chain_before(self, other: Box<dyn AstTransform<'a> + 'a>) -> Box<dyn AstTransform<'a> + 'a> {
123 Box::new(SubstituteTypeParams { previous: other, ..self })
124 }
125}
126
127pub struct QualifyPaths<'a> {
128 target_scope: &'a SemanticsScope<'a>,
129 source_scope: &'a SemanticsScope<'a>,
130 previous: Box<dyn AstTransform<'a> + 'a>,
131}
132
133impl<'a> QualifyPaths<'a> {
134 pub fn new(target_scope: &'a SemanticsScope<'a>, source_scope: &'a SemanticsScope<'a>) -> Self {
135 Self { target_scope, source_scope, previous: Box::new(NullTransformer) }
136 }
137
138 fn get_substitution_inner(
139 &self,
140 node: &ra_syntax::SyntaxNode,
141 ) -> Option<ra_syntax::SyntaxNode> {
142 // FIXME handle value ns?
143 let from = self.target_scope.module()?;
144 let p = ast::Path::cast(node.clone())?;
145 if p.segment().and_then(|s| s.param_list()).is_some() {
146 // don't try to qualify `Fn(Foo) -> Bar` paths, they are in prelude anyway
147 return None;
148 }
149 // FIXME: use `hir::Path::from_src` instead.
150 #[allow(deprecated)]
151 let hir_path = hir::Path::from_ast(p.clone());
152 let resolution = self.source_scope.resolve_hir_path(&hir_path?)?;
153 match resolution {
154 PathResolution::Def(def) => {
155 let found_path = from.find_use_path(self.source_scope.db.upcast(), def)?;
156 let mut path = path_to_ast(found_path);
157
158 let type_args = p
159 .segment()
160 .and_then(|s| s.type_arg_list())
161 .map(|arg_list| apply(self, arg_list));
162 if let Some(type_args) = type_args {
163 let last_segment = path.segment().unwrap();
164 path = path.with_segment(last_segment.with_type_args(type_args))
165 }
166
167 Some(path.syntax().clone())
168 }
169 PathResolution::Local(_)
170 | PathResolution::TypeParam(_)
171 | PathResolution::SelfType(_) => None,
172 PathResolution::Macro(_) => None,
173 PathResolution::AssocItem(_) => None,
174 }
175 }
176}
177
178pub fn apply<'a, N: AstNode>(transformer: &dyn AstTransform<'a>, node: N) -> N {
179 SyntaxRewriter::from_fn(|element| match element {
180 ra_syntax::SyntaxElement::Node(n) => {
181 let replacement = transformer.get_substitution(&n)?;
182 Some(replacement.into())
183 }
184 _ => None,
185 })
186 .rewrite_ast(&node)
187}
188
189impl<'a> AstTransform<'a> for QualifyPaths<'a> {
190 fn get_substitution(&self, node: &ra_syntax::SyntaxNode) -> Option<ra_syntax::SyntaxNode> {
191 self.get_substitution_inner(node).or_else(|| self.previous.get_substitution(node))
192 }
193 fn chain_before(self, other: Box<dyn AstTransform<'a> + 'a>) -> Box<dyn AstTransform<'a> + 'a> {
194 Box::new(QualifyPaths { previous: other, ..self })
195 }
196}
197
198pub(crate) fn path_to_ast(path: hir::ModPath) -> ast::Path {
199 let parse = ast::SourceFile::parse(&path.to_string());
200 parse
201 .tree()
202 .syntax()
203 .descendants()
204 .find_map(ast::Path::cast)
205 .unwrap_or_else(|| panic!("failed to parse path {:?}, `{}`", path, path))
206}
diff --git a/crates/ra_assists/src/handlers/add_custom_impl.rs b/crates/ra_assists/src/handlers/add_custom_impl.rs
deleted file mode 100644
index b67438b6b..000000000
--- a/crates/ra_assists/src/handlers/add_custom_impl.rs
+++ /dev/null
@@ -1,208 +0,0 @@
1use ra_syntax::{
2 ast::{self, AstNode},
3 Direction, SmolStr,
4 SyntaxKind::{IDENT, WHITESPACE},
5 TextRange, TextSize,
6};
7use stdx::SepBy;
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 let new_attr_input = new_attr_input.iter().sep_by(", ").surround_with("(", ")").to_string();
65
66 if has_more_derives {
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/ra_assists/src/handlers/add_explicit_type.rs b/crates/ra_assists/src/handlers/add_explicit_type.rs
deleted file mode 100644
index e69f0a89b..000000000
--- a/crates/ra_assists/src/handlers/add_explicit_type.rs
+++ /dev/null
@@ -1,211 +0,0 @@
1use hir::HirDisplay;
2use ra_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::BindPat(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::PlaceholderType::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/ra_assists/src/handlers/add_missing_impl_members.rs b/crates/ra_assists/src/handlers/add_missing_impl_members.rs
deleted file mode 100644
index 95a750aee..000000000
--- a/crates/ra_assists/src/handlers/add_missing_impl_members.rs
+++ /dev/null
@@ -1,711 +0,0 @@
1use hir::HasSource;
2use ra_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// }
53// ```
54pub(crate) fn add_missing_impl_members(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
55 add_missing_impl_members_inner(
56 acc,
57 ctx,
58 AddMissingImplMembersMode::NoDefaultMethods,
59 "add_impl_missing_members",
60 "Implement missing members",
61 )
62}
63
64// Assist: add_impl_default_members
65//
66// Adds scaffold for overriding default impl members.
67//
68// ```
69// trait Trait {
70// Type X;
71// fn foo(&self);
72// fn bar(&self) {}
73// }
74//
75// impl Trait for () {
76// Type X = ();
77// fn foo(&self) {}<|>
78//
79// }
80// ```
81// ->
82// ```
83// trait Trait {
84// Type X;
85// fn foo(&self);
86// fn bar(&self) {}
87// }
88//
89// impl Trait for () {
90// Type X = ();
91// fn foo(&self) {}
92// $0fn bar(&self) {}
93//
94// }
95// ```
96pub(crate) fn add_missing_default_members(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
97 add_missing_impl_members_inner(
98 acc,
99 ctx,
100 AddMissingImplMembersMode::DefaultMethodsOnly,
101 "add_impl_default_members",
102 "Implement default members",
103 )
104}
105
106fn add_missing_impl_members_inner(
107 acc: &mut Assists,
108 ctx: &AssistContext,
109 mode: AddMissingImplMembersMode,
110 assist_id: &'static str,
111 label: &'static str,
112) -> Option<()> {
113 let _p = ra_prof::profile("add_missing_impl_members_inner");
114 let impl_def = ctx.find_node_at_offset::<ast::Impl>()?;
115 let impl_item_list = impl_def.assoc_item_list()?;
116
117 let trait_ = resolve_target_trait(&ctx.sema, &impl_def)?;
118
119 let def_name = |item: &ast::AssocItem| -> Option<SmolStr> {
120 match item {
121 ast::AssocItem::Fn(def) => def.name(),
122 ast::AssocItem::TypeAlias(def) => def.name(),
123 ast::AssocItem::Const(def) => def.name(),
124 ast::AssocItem::MacroCall(_) => None,
125 }
126 .map(|it| it.text().clone())
127 };
128
129 let missing_items = get_missing_assoc_items(&ctx.sema, &impl_def)
130 .iter()
131 .map(|i| match i {
132 hir::AssocItem::Function(i) => ast::AssocItem::Fn(i.source(ctx.db()).value),
133 hir::AssocItem::TypeAlias(i) => ast::AssocItem::TypeAlias(i.source(ctx.db()).value),
134 hir::AssocItem::Const(i) => ast::AssocItem::Const(i.source(ctx.db()).value),
135 })
136 .filter(|t| def_name(&t).is_some())
137 .filter(|t| match t {
138 ast::AssocItem::Fn(def) => match mode {
139 AddMissingImplMembersMode::DefaultMethodsOnly => def.body().is_some(),
140 AddMissingImplMembersMode::NoDefaultMethods => def.body().is_none(),
141 },
142 _ => mode == AddMissingImplMembersMode::NoDefaultMethods,
143 })
144 .collect::<Vec<_>>();
145
146 if missing_items.is_empty() {
147 return None;
148 }
149
150 let target = impl_def.syntax().text_range();
151 acc.add(AssistId(assist_id, AssistKind::QuickFix), label, target, |builder| {
152 let n_existing_items = impl_item_list.assoc_items().count();
153 let source_scope = ctx.sema.scope_for_def(trait_);
154 let target_scope = ctx.sema.scope(impl_item_list.syntax());
155 let ast_transform = QualifyPaths::new(&target_scope, &source_scope)
156 .or(SubstituteTypeParams::for_trait_impl(&source_scope, trait_, impl_def));
157 let items = missing_items
158 .into_iter()
159 .map(|it| ast_transform::apply(&*ast_transform, it))
160 .map(|it| match it {
161 ast::AssocItem::Fn(def) => ast::AssocItem::Fn(add_body(def)),
162 ast::AssocItem::TypeAlias(def) => ast::AssocItem::TypeAlias(def.remove_bounds()),
163 _ => it,
164 })
165 .map(|it| edit::remove_attrs_and_docs(&it));
166 let new_impl_item_list = impl_item_list.append_items(items);
167 let first_new_item = new_impl_item_list.assoc_items().nth(n_existing_items).unwrap();
168
169 let original_range = impl_item_list.syntax().text_range();
170 match ctx.config.snippet_cap {
171 None => builder.replace(original_range, new_impl_item_list.to_string()),
172 Some(cap) => {
173 let mut cursor = Cursor::Before(first_new_item.syntax());
174 let placeholder;
175 if let ast::AssocItem::Fn(func) = &first_new_item {
176 if let Some(m) = func.syntax().descendants().find_map(ast::MacroCall::cast) {
177 if m.syntax().text() == "todo!()" {
178 placeholder = m;
179 cursor = Cursor::Replace(placeholder.syntax());
180 }
181 }
182 }
183 builder.replace_snippet(
184 cap,
185 original_range,
186 render_snippet(cap, new_impl_item_list.syntax(), cursor),
187 )
188 }
189 };
190 })
191}
192
193fn add_body(fn_def: ast::Fn) -> ast::Fn {
194 if fn_def.body().is_some() {
195 return fn_def;
196 }
197 let body = make::block_expr(None, Some(make::expr_todo())).indent(IndentLevel(1));
198 fn_def.with_body(body)
199}
200
201#[cfg(test)]
202mod tests {
203 use crate::tests::{check_assist, check_assist_not_applicable};
204
205 use super::*;
206
207 #[test]
208 fn test_add_missing_impl_members() {
209 check_assist(
210 add_missing_impl_members,
211 r#"
212trait Foo {
213 type Output;
214
215 const CONST: usize = 42;
216
217 fn foo(&self);
218 fn bar(&self);
219 fn baz(&self);
220}
221
222struct S;
223
224impl Foo for S {
225 fn bar(&self) {}
226<|>
227}"#,
228 r#"
229trait Foo {
230 type Output;
231
232 const CONST: usize = 42;
233
234 fn foo(&self);
235 fn bar(&self);
236 fn baz(&self);
237}
238
239struct S;
240
241impl Foo for S {
242 fn bar(&self) {}
243 $0type Output;
244 const CONST: usize = 42;
245 fn foo(&self) {
246 todo!()
247 }
248 fn baz(&self) {
249 todo!()
250 }
251
252}"#,
253 );
254 }
255
256 #[test]
257 fn test_copied_overriden_members() {
258 check_assist(
259 add_missing_impl_members,
260 r#"
261trait Foo {
262 fn foo(&self);
263 fn bar(&self) -> bool { true }
264 fn baz(&self) -> u32 { 42 }
265}
266
267struct S;
268
269impl Foo for S {
270 fn bar(&self) {}
271<|>
272}"#,
273 r#"
274trait Foo {
275 fn foo(&self);
276 fn bar(&self) -> bool { true }
277 fn baz(&self) -> u32 { 42 }
278}
279
280struct S;
281
282impl Foo for S {
283 fn bar(&self) {}
284 fn foo(&self) {
285 ${0:todo!()}
286 }
287
288}"#,
289 );
290 }
291
292 #[test]
293 fn test_empty_impl_def() {
294 check_assist(
295 add_missing_impl_members,
296 r#"
297trait Foo { fn foo(&self); }
298struct S;
299impl Foo for S { <|> }"#,
300 r#"
301trait Foo { fn foo(&self); }
302struct S;
303impl Foo for S {
304 fn foo(&self) {
305 ${0:todo!()}
306 }
307}"#,
308 );
309 }
310
311 #[test]
312 fn fill_in_type_params_1() {
313 check_assist(
314 add_missing_impl_members,
315 r#"
316trait Foo<T> { fn foo(&self, t: T) -> &T; }
317struct S;
318impl Foo<u32> for S { <|> }"#,
319 r#"
320trait Foo<T> { fn foo(&self, t: T) -> &T; }
321struct S;
322impl Foo<u32> for S {
323 fn foo(&self, t: u32) -> &u32 {
324 ${0:todo!()}
325 }
326}"#,
327 );
328 }
329
330 #[test]
331 fn fill_in_type_params_2() {
332 check_assist(
333 add_missing_impl_members,
334 r#"
335trait Foo<T> { fn foo(&self, t: T) -> &T; }
336struct S;
337impl<U> Foo<U> for S { <|> }"#,
338 r#"
339trait Foo<T> { fn foo(&self, t: T) -> &T; }
340struct S;
341impl<U> Foo<U> for S {
342 fn foo(&self, t: U) -> &U {
343 ${0:todo!()}
344 }
345}"#,
346 );
347 }
348
349 #[test]
350 fn test_cursor_after_empty_impl_def() {
351 check_assist(
352 add_missing_impl_members,
353 r#"
354trait Foo { fn foo(&self); }
355struct S;
356impl Foo for S {}<|>"#,
357 r#"
358trait Foo { fn foo(&self); }
359struct S;
360impl Foo for S {
361 fn foo(&self) {
362 ${0:todo!()}
363 }
364}"#,
365 )
366 }
367
368 #[test]
369 fn test_qualify_path_1() {
370 check_assist(
371 add_missing_impl_members,
372 r#"
373mod foo {
374 pub struct Bar;
375 trait Foo { fn foo(&self, bar: Bar); }
376}
377struct S;
378impl foo::Foo for S { <|> }"#,
379 r#"
380mod foo {
381 pub struct Bar;
382 trait Foo { fn foo(&self, bar: Bar); }
383}
384struct S;
385impl foo::Foo for S {
386 fn foo(&self, bar: foo::Bar) {
387 ${0:todo!()}
388 }
389}"#,
390 );
391 }
392
393 #[test]
394 fn test_qualify_path_generic() {
395 check_assist(
396 add_missing_impl_members,
397 r#"
398mod foo {
399 pub struct Bar<T>;
400 trait Foo { fn foo(&self, bar: Bar<u32>); }
401}
402struct S;
403impl foo::Foo for S { <|> }"#,
404 r#"
405mod foo {
406 pub struct Bar<T>;
407 trait Foo { fn foo(&self, bar: Bar<u32>); }
408}
409struct S;
410impl foo::Foo for S {
411 fn foo(&self, bar: foo::Bar<u32>) {
412 ${0:todo!()}
413 }
414}"#,
415 );
416 }
417
418 #[test]
419 fn test_qualify_path_and_substitute_param() {
420 check_assist(
421 add_missing_impl_members,
422 r#"
423mod foo {
424 pub struct Bar<T>;
425 trait Foo<T> { fn foo(&self, bar: Bar<T>); }
426}
427struct S;
428impl foo::Foo<u32> for S { <|> }"#,
429 r#"
430mod foo {
431 pub struct Bar<T>;
432 trait Foo<T> { fn foo(&self, bar: Bar<T>); }
433}
434struct S;
435impl foo::Foo<u32> for S {
436 fn foo(&self, bar: foo::Bar<u32>) {
437 ${0:todo!()}
438 }
439}"#,
440 );
441 }
442
443 #[test]
444 fn test_substitute_param_no_qualify() {
445 // when substituting params, the substituted param should not be qualified!
446 check_assist(
447 add_missing_impl_members,
448 r#"
449mod foo {
450 trait Foo<T> { fn foo(&self, bar: T); }
451 pub struct Param;
452}
453struct Param;
454struct S;
455impl foo::Foo<Param> for S { <|> }"#,
456 r#"
457mod foo {
458 trait Foo<T> { fn foo(&self, bar: T); }
459 pub struct Param;
460}
461struct Param;
462struct S;
463impl foo::Foo<Param> for S {
464 fn foo(&self, bar: Param) {
465 ${0:todo!()}
466 }
467}"#,
468 );
469 }
470
471 #[test]
472 fn test_qualify_path_associated_item() {
473 check_assist(
474 add_missing_impl_members,
475 r#"
476mod foo {
477 pub struct Bar<T>;
478 impl Bar<T> { type Assoc = u32; }
479 trait Foo { fn foo(&self, bar: Bar<u32>::Assoc); }
480}
481struct S;
482impl foo::Foo for S { <|> }"#,
483 r#"
484mod foo {
485 pub struct Bar<T>;
486 impl Bar<T> { type Assoc = u32; }
487 trait Foo { fn foo(&self, bar: Bar<u32>::Assoc); }
488}
489struct S;
490impl foo::Foo for S {
491 fn foo(&self, bar: foo::Bar<u32>::Assoc) {
492 ${0:todo!()}
493 }
494}"#,
495 );
496 }
497
498 #[test]
499 fn test_qualify_path_nested() {
500 check_assist(
501 add_missing_impl_members,
502 r#"
503mod foo {
504 pub struct Bar<T>;
505 pub struct Baz;
506 trait Foo { fn foo(&self, bar: Bar<Baz>); }
507}
508struct S;
509impl foo::Foo for S { <|> }"#,
510 r#"
511mod foo {
512 pub struct Bar<T>;
513 pub struct Baz;
514 trait Foo { fn foo(&self, bar: Bar<Baz>); }
515}
516struct S;
517impl foo::Foo for S {
518 fn foo(&self, bar: foo::Bar<foo::Baz>) {
519 ${0:todo!()}
520 }
521}"#,
522 );
523 }
524
525 #[test]
526 fn test_qualify_path_fn_trait_notation() {
527 check_assist(
528 add_missing_impl_members,
529 r#"
530mod foo {
531 pub trait Fn<Args> { type Output; }
532 trait Foo { fn foo(&self, bar: dyn Fn(u32) -> i32); }
533}
534struct S;
535impl foo::Foo for S { <|> }"#,
536 r#"
537mod foo {
538 pub trait Fn<Args> { type Output; }
539 trait Foo { fn foo(&self, bar: dyn Fn(u32) -> i32); }
540}
541struct S;
542impl foo::Foo for S {
543 fn foo(&self, bar: dyn Fn(u32) -> i32) {
544 ${0:todo!()}
545 }
546}"#,
547 );
548 }
549
550 #[test]
551 fn test_empty_trait() {
552 check_assist_not_applicable(
553 add_missing_impl_members,
554 r#"
555trait Foo;
556struct S;
557impl Foo for S { <|> }"#,
558 )
559 }
560
561 #[test]
562 fn test_ignore_unnamed_trait_members_and_default_methods() {
563 check_assist_not_applicable(
564 add_missing_impl_members,
565 r#"
566trait Foo {
567 fn (arg: u32);
568 fn valid(some: u32) -> bool { false }
569}
570struct S;
571impl Foo for S { <|> }"#,
572 )
573 }
574
575 #[test]
576 fn test_with_docstring_and_attrs() {
577 check_assist(
578 add_missing_impl_members,
579 r#"
580#[doc(alias = "test alias")]
581trait Foo {
582 /// doc string
583 type Output;
584
585 #[must_use]
586 fn foo(&self);
587}
588struct S;
589impl Foo for S {}<|>"#,
590 r#"
591#[doc(alias = "test alias")]
592trait Foo {
593 /// doc string
594 type Output;
595
596 #[must_use]
597 fn foo(&self);
598}
599struct S;
600impl Foo for S {
601 $0type Output;
602 fn foo(&self) {
603 todo!()
604 }
605}"#,
606 )
607 }
608
609 #[test]
610 fn test_default_methods() {
611 check_assist(
612 add_missing_default_members,
613 r#"
614trait Foo {
615 type Output;
616
617 const CONST: usize = 42;
618
619 fn valid(some: u32) -> bool { false }
620 fn foo(some: u32) -> bool;
621}
622struct S;
623impl Foo for S { <|> }"#,
624 r#"
625trait Foo {
626 type Output;
627
628 const CONST: usize = 42;
629
630 fn valid(some: u32) -> bool { false }
631 fn foo(some: u32) -> bool;
632}
633struct S;
634impl Foo for S {
635 $0fn valid(some: u32) -> bool { false }
636}"#,
637 )
638 }
639
640 #[test]
641 fn test_generic_single_default_parameter() {
642 check_assist(
643 add_missing_impl_members,
644 r#"
645trait Foo<T = Self> {
646 fn bar(&self, other: &T);
647}
648
649struct S;
650impl Foo for S { <|> }"#,
651 r#"
652trait Foo<T = Self> {
653 fn bar(&self, other: &T);
654}
655
656struct S;
657impl Foo for S {
658 fn bar(&self, other: &Self) {
659 ${0:todo!()}
660 }
661}"#,
662 )
663 }
664
665 #[test]
666 fn test_generic_default_parameter_is_second() {
667 check_assist(
668 add_missing_impl_members,
669 r#"
670trait Foo<T1, T2 = Self> {
671 fn bar(&self, this: &T1, that: &T2);
672}
673
674struct S<T>;
675impl Foo<T> for S<T> { <|> }"#,
676 r#"
677trait Foo<T1, T2 = Self> {
678 fn bar(&self, this: &T1, that: &T2);
679}
680
681struct S<T>;
682impl Foo<T> for S<T> {
683 fn bar(&self, this: &T, that: &Self) {
684 ${0:todo!()}
685 }
686}"#,
687 )
688 }
689
690 #[test]
691 fn test_assoc_type_bounds_are_removed() {
692 check_assist(
693 add_missing_impl_members,
694 r#"
695trait Tr {
696 type Ty: Copy + 'static;
697}
698
699impl Tr for ()<|> {
700}"#,
701 r#"
702trait Tr {
703 type Ty: Copy + 'static;
704}
705
706impl Tr for () {
707 $0type Ty;
708}"#,
709 )
710 }
711}
diff --git a/crates/ra_assists/src/handlers/add_turbo_fish.rs b/crates/ra_assists/src/handlers/add_turbo_fish.rs
deleted file mode 100644
index 0c565e89a..000000000
--- a/crates/ra_assists/src/handlers/add_turbo_fish.rs
+++ /dev/null
@@ -1,164 +0,0 @@
1use ra_ide_db::defs::{classify_name_ref, Definition, NameRefClass};
2use ra_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::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/ra_assists/src/handlers/apply_demorgan.rs b/crates/ra_assists/src/handlers/apply_demorgan.rs
deleted file mode 100644
index de701f8b8..000000000
--- a/crates/ra_assists/src/handlers/apply_demorgan.rs
+++ /dev/null
@@ -1,93 +0,0 @@
1use ra_syntax::ast::{self, AstNode};
2
3use crate::{utils::invert_boolean_expression, AssistContext, AssistId, AssistKind, Assists};
4
5// Assist: apply_demorgan
6//
7// Apply [De Morgan's law](https://en.wikipedia.org/wiki/De_Morgan%27s_laws).
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/ra_assists/src/handlers/auto_import.rs b/crates/ra_assists/src/handlers/auto_import.rs
deleted file mode 100644
index 01e7b7a44..000000000
--- a/crates/ra_assists/src/handlers/auto_import.rs
+++ /dev/null
@@ -1,1089 +0,0 @@
1use std::collections::BTreeSet;
2
3use either::Either;
4use hir::{
5 AsAssocItem, AssocItemContainer, ModPath, Module, ModuleDef, PathResolution, Semantics, Trait,
6 Type,
7};
8use ra_ide_db::{imports_locator, RootDatabase};
9use ra_prof::profile;
10use ra_syntax::{
11 ast::{self, AstNode},
12 SyntaxNode,
13};
14use rustc_hash::FxHashSet;
15
16use crate::{
17 utils::insert_use_statement, AssistContext, AssistId, AssistKind, Assists, GroupLabel,
18};
19
20// Assist: auto_import
21//
22// If the name is unresolved, provides all possible imports for it.
23//
24// ```
25// fn main() {
26// let map = HashMap<|>::new();
27// }
28// # pub mod std { pub mod collections { pub struct HashMap { } } }
29// ```
30// ->
31// ```
32// use std::collections::HashMap;
33//
34// fn main() {
35// let map = HashMap::new();
36// }
37// # pub mod std { pub mod collections { pub struct HashMap { } } }
38// ```
39pub(crate) fn auto_import(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
40 let auto_import_assets = AutoImportAssets::new(ctx)?;
41 let proposed_imports = auto_import_assets.search_for_imports(ctx);
42 if proposed_imports.is_empty() {
43 return None;
44 }
45
46 let range = ctx.sema.original_range(&auto_import_assets.syntax_under_caret).range;
47 let group = auto_import_assets.get_import_group_message();
48 for import in proposed_imports {
49 acc.add_group(
50 &group,
51 AssistId("auto_import", AssistKind::QuickFix),
52 format!("Import `{}`", &import),
53 range,
54 |builder| {
55 insert_use_statement(
56 &auto_import_assets.syntax_under_caret,
57 &import,
58 ctx,
59 builder.text_edit_builder(),
60 );
61 },
62 );
63 }
64 Some(())
65}
66
67#[derive(Debug)]
68struct AutoImportAssets {
69 import_candidate: ImportCandidate,
70 module_with_name_to_import: Module,
71 syntax_under_caret: SyntaxNode,
72}
73
74impl AutoImportAssets {
75 fn new(ctx: &AssistContext) -> Option<Self> {
76 if let Some(path_under_caret) = ctx.find_node_at_offset_with_descend::<ast::Path>() {
77 Self::for_regular_path(path_under_caret, &ctx)
78 } else {
79 Self::for_method_call(ctx.find_node_at_offset_with_descend()?, &ctx)
80 }
81 }
82
83 fn for_method_call(method_call: ast::MethodCallExpr, ctx: &AssistContext) -> Option<Self> {
84 let syntax_under_caret = method_call.syntax().to_owned();
85 let module_with_name_to_import = ctx.sema.scope(&syntax_under_caret).module()?;
86 Some(Self {
87 import_candidate: ImportCandidate::for_method_call(&ctx.sema, &method_call)?,
88 module_with_name_to_import,
89 syntax_under_caret,
90 })
91 }
92
93 fn for_regular_path(path_under_caret: ast::Path, ctx: &AssistContext) -> Option<Self> {
94 let syntax_under_caret = path_under_caret.syntax().to_owned();
95 if syntax_under_caret.ancestors().find_map(ast::Use::cast).is_some() {
96 return None;
97 }
98
99 let module_with_name_to_import = ctx.sema.scope(&syntax_under_caret).module()?;
100 Some(Self {
101 import_candidate: ImportCandidate::for_regular_path(&ctx.sema, &path_under_caret)?,
102 module_with_name_to_import,
103 syntax_under_caret,
104 })
105 }
106
107 fn get_search_query(&self) -> &str {
108 match &self.import_candidate {
109 ImportCandidate::UnqualifiedName(name) => name,
110 ImportCandidate::QualifierStart(qualifier_start) => qualifier_start,
111 ImportCandidate::TraitAssocItem(_, trait_assoc_item_name) => trait_assoc_item_name,
112 ImportCandidate::TraitMethod(_, trait_method_name) => trait_method_name,
113 }
114 }
115
116 fn get_import_group_message(&self) -> GroupLabel {
117 let name = match &self.import_candidate {
118 ImportCandidate::UnqualifiedName(name) => format!("Import {}", name),
119 ImportCandidate::QualifierStart(qualifier_start) => {
120 format!("Import {}", qualifier_start)
121 }
122 ImportCandidate::TraitAssocItem(_, trait_assoc_item_name) => {
123 format!("Import a trait for item {}", trait_assoc_item_name)
124 }
125 ImportCandidate::TraitMethod(_, trait_method_name) => {
126 format!("Import a trait for method {}", trait_method_name)
127 }
128 };
129 GroupLabel(name)
130 }
131
132 fn search_for_imports(&self, ctx: &AssistContext) -> BTreeSet<ModPath> {
133 let _p = profile("auto_import::search_for_imports");
134 let db = ctx.db();
135 let current_crate = self.module_with_name_to_import.krate();
136 imports_locator::find_imports(&ctx.sema, current_crate, &self.get_search_query())
137 .into_iter()
138 .filter_map(|candidate| match &self.import_candidate {
139 ImportCandidate::TraitAssocItem(assoc_item_type, _) => {
140 let located_assoc_item = match candidate {
141 Either::Left(ModuleDef::Function(located_function)) => located_function
142 .as_assoc_item(db)
143 .map(|assoc| assoc.container(db))
144 .and_then(Self::assoc_to_trait),
145 Either::Left(ModuleDef::Const(located_const)) => located_const
146 .as_assoc_item(db)
147 .map(|assoc| assoc.container(db))
148 .and_then(Self::assoc_to_trait),
149 _ => None,
150 }?;
151
152 let mut trait_candidates = FxHashSet::default();
153 trait_candidates.insert(located_assoc_item.into());
154
155 assoc_item_type
156 .iterate_path_candidates(
157 db,
158 current_crate,
159 &trait_candidates,
160 None,
161 |_, assoc| Self::assoc_to_trait(assoc.container(db)),
162 )
163 .map(ModuleDef::from)
164 .map(Either::Left)
165 }
166 ImportCandidate::TraitMethod(function_callee, _) => {
167 let located_assoc_item =
168 if let Either::Left(ModuleDef::Function(located_function)) = candidate {
169 located_function
170 .as_assoc_item(db)
171 .map(|assoc| assoc.container(db))
172 .and_then(Self::assoc_to_trait)
173 } else {
174 None
175 }?;
176
177 let mut trait_candidates = FxHashSet::default();
178 trait_candidates.insert(located_assoc_item.into());
179
180 function_callee
181 .iterate_method_candidates(
182 db,
183 current_crate,
184 &trait_candidates,
185 None,
186 |_, function| {
187 Self::assoc_to_trait(function.as_assoc_item(db)?.container(db))
188 },
189 )
190 .map(ModuleDef::from)
191 .map(Either::Left)
192 }
193 _ => Some(candidate),
194 })
195 .filter_map(|candidate| match candidate {
196 Either::Left(module_def) => {
197 self.module_with_name_to_import.find_use_path(db, module_def)
198 }
199 Either::Right(macro_def) => {
200 self.module_with_name_to_import.find_use_path(db, macro_def)
201 }
202 })
203 .filter(|use_path| !use_path.segments.is_empty())
204 .take(20)
205 .collect::<BTreeSet<_>>()
206 }
207
208 fn assoc_to_trait(assoc: AssocItemContainer) -> Option<Trait> {
209 if let AssocItemContainer::Trait(extracted_trait) = assoc {
210 Some(extracted_trait)
211 } else {
212 None
213 }
214 }
215}
216
217#[derive(Debug)]
218enum ImportCandidate {
219 /// Simple name like 'HashMap'
220 UnqualifiedName(String),
221 /// First part of the qualified name.
222 /// For 'std::collections::HashMap', that will be 'std'.
223 QualifierStart(String),
224 /// A trait associated function (with no self parameter) or associated constant.
225 /// For 'test_mod::TestEnum::test_function', `Type` is the `test_mod::TestEnum` expression type
226 /// and `String` is the `test_function`
227 TraitAssocItem(Type, String),
228 /// A trait method with self parameter.
229 /// For 'test_enum.test_method()', `Type` is the `test_enum` expression type
230 /// and `String` is the `test_method`
231 TraitMethod(Type, String),
232}
233
234impl ImportCandidate {
235 fn for_method_call(
236 sema: &Semantics<RootDatabase>,
237 method_call: &ast::MethodCallExpr,
238 ) -> Option<Self> {
239 if sema.resolve_method_call(method_call).is_some() {
240 return None;
241 }
242 Some(Self::TraitMethod(
243 sema.type_of_expr(&method_call.expr()?)?,
244 method_call.name_ref()?.syntax().to_string(),
245 ))
246 }
247
248 fn for_regular_path(
249 sema: &Semantics<RootDatabase>,
250 path_under_caret: &ast::Path,
251 ) -> Option<Self> {
252 if sema.resolve_path(path_under_caret).is_some() {
253 return None;
254 }
255
256 let segment = path_under_caret.segment()?;
257 if let Some(qualifier) = path_under_caret.qualifier() {
258 let qualifier_start = qualifier.syntax().descendants().find_map(ast::NameRef::cast)?;
259 let qualifier_start_path =
260 qualifier_start.syntax().ancestors().find_map(ast::Path::cast)?;
261 if let Some(qualifier_start_resolution) = sema.resolve_path(&qualifier_start_path) {
262 let qualifier_resolution = if qualifier_start_path == qualifier {
263 qualifier_start_resolution
264 } else {
265 sema.resolve_path(&qualifier)?
266 };
267 if let PathResolution::Def(ModuleDef::Adt(assoc_item_path)) = qualifier_resolution {
268 Some(ImportCandidate::TraitAssocItem(
269 assoc_item_path.ty(sema.db),
270 segment.syntax().to_string(),
271 ))
272 } else {
273 None
274 }
275 } else {
276 Some(ImportCandidate::QualifierStart(qualifier_start.syntax().to_string()))
277 }
278 } else {
279 Some(ImportCandidate::UnqualifiedName(
280 segment.syntax().descendants().find_map(ast::NameRef::cast)?.syntax().to_string(),
281 ))
282 }
283 }
284}
285
286#[cfg(test)]
287mod tests {
288 use super::*;
289 use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target};
290
291 #[test]
292 fn applicable_when_found_an_import() {
293 check_assist(
294 auto_import,
295 r"
296 <|>PubStruct
297
298 pub mod PubMod {
299 pub struct PubStruct;
300 }
301 ",
302 r"
303 use PubMod::PubStruct;
304
305 PubStruct
306
307 pub mod PubMod {
308 pub struct PubStruct;
309 }
310 ",
311 );
312 }
313
314 #[test]
315 fn applicable_when_found_an_import_in_macros() {
316 check_assist(
317 auto_import,
318 r"
319 macro_rules! foo {
320 ($i:ident) => { fn foo(a: $i) {} }
321 }
322 foo!(Pub<|>Struct);
323
324 pub mod PubMod {
325 pub struct PubStruct;
326 }
327 ",
328 r"
329 use PubMod::PubStruct;
330
331 macro_rules! foo {
332 ($i:ident) => { fn foo(a: $i) {} }
333 }
334 foo!(PubStruct);
335
336 pub mod PubMod {
337 pub struct PubStruct;
338 }
339 ",
340 );
341 }
342
343 #[test]
344 fn auto_imports_are_merged() {
345 check_assist(
346 auto_import,
347 r"
348 use PubMod::PubStruct1;
349
350 struct Test {
351 test: Pub<|>Struct2<u8>,
352 }
353
354 pub mod PubMod {
355 pub struct PubStruct1;
356 pub struct PubStruct2<T> {
357 _t: T,
358 }
359 }
360 ",
361 r"
362 use PubMod::{PubStruct2, PubStruct1};
363
364 struct Test {
365 test: PubStruct2<u8>,
366 }
367
368 pub mod PubMod {
369 pub struct PubStruct1;
370 pub struct PubStruct2<T> {
371 _t: T,
372 }
373 }
374 ",
375 );
376 }
377
378 #[test]
379 fn applicable_when_found_multiple_imports() {
380 check_assist(
381 auto_import,
382 r"
383 PubSt<|>ruct
384
385 pub mod PubMod1 {
386 pub struct PubStruct;
387 }
388 pub mod PubMod2 {
389 pub struct PubStruct;
390 }
391 pub mod PubMod3 {
392 pub struct PubStruct;
393 }
394 ",
395 r"
396 use PubMod3::PubStruct;
397
398 PubStruct
399
400 pub mod PubMod1 {
401 pub struct PubStruct;
402 }
403 pub mod PubMod2 {
404 pub struct PubStruct;
405 }
406 pub mod PubMod3 {
407 pub struct PubStruct;
408 }
409 ",
410 );
411 }
412
413 #[test]
414 fn not_applicable_for_already_imported_types() {
415 check_assist_not_applicable(
416 auto_import,
417 r"
418 use PubMod::PubStruct;
419
420 PubStruct<|>
421
422 pub mod PubMod {
423 pub struct PubStruct;
424 }
425 ",
426 );
427 }
428
429 #[test]
430 fn not_applicable_for_types_with_private_paths() {
431 check_assist_not_applicable(
432 auto_import,
433 r"
434 PrivateStruct<|>
435
436 pub mod PubMod {
437 struct PrivateStruct;
438 }
439 ",
440 );
441 }
442
443 #[test]
444 fn not_applicable_when_no_imports_found() {
445 check_assist_not_applicable(
446 auto_import,
447 "
448 PubStruct<|>",
449 );
450 }
451
452 #[test]
453 fn not_applicable_in_import_statements() {
454 check_assist_not_applicable(
455 auto_import,
456 r"
457 use PubStruct<|>;
458
459 pub mod PubMod {
460 pub struct PubStruct;
461 }",
462 );
463 }
464
465 #[test]
466 fn function_import() {
467 check_assist(
468 auto_import,
469 r"
470 test_function<|>
471
472 pub mod PubMod {
473 pub fn test_function() {};
474 }
475 ",
476 r"
477 use PubMod::test_function;
478
479 test_function
480
481 pub mod PubMod {
482 pub fn test_function() {};
483 }
484 ",
485 );
486 }
487
488 #[test]
489 fn macro_import() {
490 check_assist(
491 auto_import,
492 r"
493//- /lib.rs crate:crate_with_macro
494#[macro_export]
495macro_rules! foo {
496 () => ()
497}
498
499//- /main.rs crate:main deps:crate_with_macro
500fn main() {
501 foo<|>
502}
503",
504 r"use crate_with_macro::foo;
505
506fn main() {
507 foo
508}
509",
510 );
511 }
512
513 #[test]
514 fn auto_import_target() {
515 check_assist_target(
516 auto_import,
517 r"
518 struct AssistInfo {
519 group_label: Option<<|>GroupLabel>,
520 }
521
522 mod m { pub struct GroupLabel; }
523 ",
524 "GroupLabel",
525 )
526 }
527
528 #[test]
529 fn not_applicable_when_path_start_is_imported() {
530 check_assist_not_applicable(
531 auto_import,
532 r"
533 pub mod mod1 {
534 pub mod mod2 {
535 pub mod mod3 {
536 pub struct TestStruct;
537 }
538 }
539 }
540
541 use mod1::mod2;
542 fn main() {
543 mod2::mod3::TestStruct<|>
544 }
545 ",
546 );
547 }
548
549 #[test]
550 fn not_applicable_for_imported_function() {
551 check_assist_not_applicable(
552 auto_import,
553 r"
554 pub mod test_mod {
555 pub fn test_function() {}
556 }
557
558 use test_mod::test_function;
559 fn main() {
560 test_function<|>
561 }
562 ",
563 );
564 }
565
566 #[test]
567 fn associated_struct_function() {
568 check_assist(
569 auto_import,
570 r"
571 mod test_mod {
572 pub struct TestStruct {}
573 impl TestStruct {
574 pub fn test_function() {}
575 }
576 }
577
578 fn main() {
579 TestStruct::test_function<|>
580 }
581 ",
582 r"
583 use test_mod::TestStruct;
584
585 mod test_mod {
586 pub struct TestStruct {}
587 impl TestStruct {
588 pub fn test_function() {}
589 }
590 }
591
592 fn main() {
593 TestStruct::test_function
594 }
595 ",
596 );
597 }
598
599 #[test]
600 fn associated_struct_const() {
601 check_assist(
602 auto_import,
603 r"
604 mod test_mod {
605 pub struct TestStruct {}
606 impl TestStruct {
607 const TEST_CONST: u8 = 42;
608 }
609 }
610
611 fn main() {
612 TestStruct::TEST_CONST<|>
613 }
614 ",
615 r"
616 use test_mod::TestStruct;
617
618 mod test_mod {
619 pub struct TestStruct {}
620 impl TestStruct {
621 const TEST_CONST: u8 = 42;
622 }
623 }
624
625 fn main() {
626 TestStruct::TEST_CONST
627 }
628 ",
629 );
630 }
631
632 #[test]
633 fn associated_trait_function() {
634 check_assist(
635 auto_import,
636 r"
637 mod test_mod {
638 pub trait TestTrait {
639 fn test_function();
640 }
641 pub struct TestStruct {}
642 impl TestTrait for TestStruct {
643 fn test_function() {}
644 }
645 }
646
647 fn main() {
648 test_mod::TestStruct::test_function<|>
649 }
650 ",
651 r"
652 use test_mod::TestTrait;
653
654 mod test_mod {
655 pub trait TestTrait {
656 fn test_function();
657 }
658 pub struct TestStruct {}
659 impl TestTrait for TestStruct {
660 fn test_function() {}
661 }
662 }
663
664 fn main() {
665 test_mod::TestStruct::test_function
666 }
667 ",
668 );
669 }
670
671 #[test]
672 fn not_applicable_for_imported_trait_for_function() {
673 check_assist_not_applicable(
674 auto_import,
675 r"
676 mod test_mod {
677 pub trait TestTrait {
678 fn test_function();
679 }
680 pub trait TestTrait2 {
681 fn test_function();
682 }
683 pub enum TestEnum {
684 One,
685 Two,
686 }
687 impl TestTrait2 for TestEnum {
688 fn test_function() {}
689 }
690 impl TestTrait for TestEnum {
691 fn test_function() {}
692 }
693 }
694
695 use test_mod::TestTrait2;
696 fn main() {
697 test_mod::TestEnum::test_function<|>;
698 }
699 ",
700 )
701 }
702
703 #[test]
704 fn associated_trait_const() {
705 check_assist(
706 auto_import,
707 r"
708 mod test_mod {
709 pub trait TestTrait {
710 const TEST_CONST: u8;
711 }
712 pub struct TestStruct {}
713 impl TestTrait for TestStruct {
714 const TEST_CONST: u8 = 42;
715 }
716 }
717
718 fn main() {
719 test_mod::TestStruct::TEST_CONST<|>
720 }
721 ",
722 r"
723 use test_mod::TestTrait;
724
725 mod test_mod {
726 pub trait TestTrait {
727 const TEST_CONST: u8;
728 }
729 pub struct TestStruct {}
730 impl TestTrait for TestStruct {
731 const TEST_CONST: u8 = 42;
732 }
733 }
734
735 fn main() {
736 test_mod::TestStruct::TEST_CONST
737 }
738 ",
739 );
740 }
741
742 #[test]
743 fn not_applicable_for_imported_trait_for_const() {
744 check_assist_not_applicable(
745 auto_import,
746 r"
747 mod test_mod {
748 pub trait TestTrait {
749 const TEST_CONST: u8;
750 }
751 pub trait TestTrait2 {
752 const TEST_CONST: f64;
753 }
754 pub enum TestEnum {
755 One,
756 Two,
757 }
758 impl TestTrait2 for TestEnum {
759 const TEST_CONST: f64 = 42.0;
760 }
761 impl TestTrait for TestEnum {
762 const TEST_CONST: u8 = 42;
763 }
764 }
765
766 use test_mod::TestTrait2;
767 fn main() {
768 test_mod::TestEnum::TEST_CONST<|>;
769 }
770 ",
771 )
772 }
773
774 #[test]
775 fn trait_method() {
776 check_assist(
777 auto_import,
778 r"
779 mod test_mod {
780 pub trait TestTrait {
781 fn test_method(&self);
782 }
783 pub struct TestStruct {}
784 impl TestTrait for TestStruct {
785 fn test_method(&self) {}
786 }
787 }
788
789 fn main() {
790 let test_struct = test_mod::TestStruct {};
791 test_struct.test_meth<|>od()
792 }
793 ",
794 r"
795 use test_mod::TestTrait;
796
797 mod test_mod {
798 pub trait TestTrait {
799 fn test_method(&self);
800 }
801 pub struct TestStruct {}
802 impl TestTrait for TestStruct {
803 fn test_method(&self) {}
804 }
805 }
806
807 fn main() {
808 let test_struct = test_mod::TestStruct {};
809 test_struct.test_method()
810 }
811 ",
812 );
813 }
814
815 #[test]
816 fn trait_method_cross_crate() {
817 check_assist(
818 auto_import,
819 r"
820 //- /main.rs crate:main deps:dep
821 fn main() {
822 let test_struct = dep::test_mod::TestStruct {};
823 test_struct.test_meth<|>od()
824 }
825 //- /dep.rs crate:dep
826 pub mod test_mod {
827 pub trait TestTrait {
828 fn test_method(&self);
829 }
830 pub struct TestStruct {}
831 impl TestTrait for TestStruct {
832 fn test_method(&self) {}
833 }
834 }
835 ",
836 r"
837 use dep::test_mod::TestTrait;
838
839 fn main() {
840 let test_struct = dep::test_mod::TestStruct {};
841 test_struct.test_method()
842 }
843 ",
844 );
845 }
846
847 #[test]
848 fn assoc_fn_cross_crate() {
849 check_assist(
850 auto_import,
851 r"
852 //- /main.rs crate:main deps:dep
853 fn main() {
854 dep::test_mod::TestStruct::test_func<|>tion
855 }
856 //- /dep.rs crate:dep
857 pub mod test_mod {
858 pub trait TestTrait {
859 fn test_function();
860 }
861 pub struct TestStruct {}
862 impl TestTrait for TestStruct {
863 fn test_function() {}
864 }
865 }
866 ",
867 r"
868 use dep::test_mod::TestTrait;
869
870 fn main() {
871 dep::test_mod::TestStruct::test_function
872 }
873 ",
874 );
875 }
876
877 #[test]
878 fn assoc_const_cross_crate() {
879 check_assist(
880 auto_import,
881 r"
882 //- /main.rs crate:main deps:dep
883 fn main() {
884 dep::test_mod::TestStruct::CONST<|>
885 }
886 //- /dep.rs crate:dep
887 pub mod test_mod {
888 pub trait TestTrait {
889 const CONST: bool;
890 }
891 pub struct TestStruct {}
892 impl TestTrait for TestStruct {
893 const CONST: bool = true;
894 }
895 }
896 ",
897 r"
898 use dep::test_mod::TestTrait;
899
900 fn main() {
901 dep::test_mod::TestStruct::CONST
902 }
903 ",
904 );
905 }
906
907 #[test]
908 fn assoc_fn_as_method_cross_crate() {
909 check_assist_not_applicable(
910 auto_import,
911 r"
912 //- /main.rs crate:main deps:dep
913 fn main() {
914 let test_struct = dep::test_mod::TestStruct {};
915 test_struct.test_func<|>tion()
916 }
917 //- /dep.rs crate:dep
918 pub mod test_mod {
919 pub trait TestTrait {
920 fn test_function();
921 }
922 pub struct TestStruct {}
923 impl TestTrait for TestStruct {
924 fn test_function() {}
925 }
926 }
927 ",
928 );
929 }
930
931 #[test]
932 fn private_trait_cross_crate() {
933 check_assist_not_applicable(
934 auto_import,
935 r"
936 //- /main.rs crate:main deps:dep
937 fn main() {
938 let test_struct = dep::test_mod::TestStruct {};
939 test_struct.test_meth<|>od()
940 }
941 //- /dep.rs crate:dep
942 pub mod test_mod {
943 trait TestTrait {
944 fn test_method(&self);
945 }
946 pub struct TestStruct {}
947 impl TestTrait for TestStruct {
948 fn test_method(&self) {}
949 }
950 }
951 ",
952 );
953 }
954
955 #[test]
956 fn not_applicable_for_imported_trait_for_method() {
957 check_assist_not_applicable(
958 auto_import,
959 r"
960 mod test_mod {
961 pub trait TestTrait {
962 fn test_method(&self);
963 }
964 pub trait TestTrait2 {
965 fn test_method(&self);
966 }
967 pub enum TestEnum {
968 One,
969 Two,
970 }
971 impl TestTrait2 for TestEnum {
972 fn test_method(&self) {}
973 }
974 impl TestTrait for TestEnum {
975 fn test_method(&self) {}
976 }
977 }
978
979 use test_mod::TestTrait2;
980 fn main() {
981 let one = test_mod::TestEnum::One;
982 one.test<|>_method();
983 }
984 ",
985 )
986 }
987
988 #[test]
989 fn dep_import() {
990 check_assist(
991 auto_import,
992 r"
993//- /lib.rs crate:dep
994pub struct Struct;
995
996//- /main.rs crate:main deps:dep
997fn main() {
998 Struct<|>
999}
1000",
1001 r"use dep::Struct;
1002
1003fn main() {
1004 Struct
1005}
1006",
1007 );
1008 }
1009
1010 #[test]
1011 fn whole_segment() {
1012 // Tests that only imports whose last segment matches the identifier get suggested.
1013 check_assist(
1014 auto_import,
1015 r"
1016//- /lib.rs crate:dep
1017pub mod fmt {
1018 pub trait Display {}
1019}
1020
1021pub fn panic_fmt() {}
1022
1023//- /main.rs crate:main deps:dep
1024struct S;
1025
1026impl f<|>mt::Display for S {}
1027",
1028 r"use dep::fmt;
1029
1030struct S;
1031
1032impl fmt::Display for S {}
1033",
1034 );
1035 }
1036
1037 #[test]
1038 fn macro_generated() {
1039 // Tests that macro-generated items are suggested from external crates.
1040 check_assist(
1041 auto_import,
1042 r"
1043//- /lib.rs crate:dep
1044macro_rules! mac {
1045 () => {
1046 pub struct Cheese;
1047 };
1048}
1049
1050mac!();
1051
1052//- /main.rs crate:main deps:dep
1053fn main() {
1054 Cheese<|>;
1055}
1056",
1057 r"use dep::Cheese;
1058
1059fn main() {
1060 Cheese;
1061}
1062",
1063 );
1064 }
1065
1066 #[test]
1067 fn casing() {
1068 // Tests that differently cased names don't interfere and we only suggest the matching one.
1069 check_assist(
1070 auto_import,
1071 r"
1072//- /lib.rs crate:dep
1073pub struct FMT;
1074pub struct fmt;
1075
1076//- /main.rs crate:main deps:dep
1077fn main() {
1078 FMT<|>;
1079}
1080",
1081 r"use dep::FMT;
1082
1083fn main() {
1084 FMT;
1085}
1086",
1087 );
1088 }
1089}
diff --git a/crates/ra_assists/src/handlers/change_return_type_to_result.rs b/crates/ra_assists/src/handlers/change_return_type_to_result.rs
deleted file mode 100644
index 167e162d8..000000000
--- a/crates/ra_assists/src/handlers/change_return_type_to_result.rs
+++ /dev/null
@@ -1,990 +0,0 @@
1use ra_syntax::{
2 ast::{self, BlockExpr, Expr, LoopBodyOwner},
3 AstNode, SyntaxNode,
4};
5
6use crate::{AssistContext, AssistId, AssistKind, Assists};
7use test_utils::mark;
8
9// Assist: change_return_type_to_result
10//
11// Change the function's return type to Result.
12//
13// ```
14// fn foo() -> i32<|> { 42i32 }
15// ```
16// ->
17// ```
18// fn foo() -> Result<i32, ${0:_}> { Ok(42i32) }
19// ```
20pub(crate) fn change_return_type_to_result(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
21 let ret_type = ctx.find_node_at_offset::<ast::RetType>()?;
22 // FIXME: extend to lambdas as well
23 let fn_def = ret_type.syntax().parent().and_then(ast::Fn::cast)?;
24
25 let type_ref = &ret_type.ty()?;
26 let ret_type_str = type_ref.syntax().text().to_string();
27 let first_part_ret_type = ret_type_str.splitn(2, '<').next();
28 if let Some(ret_type_first_part) = first_part_ret_type {
29 if ret_type_first_part.ends_with("Result") {
30 mark::hit!(change_return_type_to_result_simple_return_type_already_result);
31 return None;
32 }
33 }
34
35 let block_expr = &fn_def.body()?;
36
37 acc.add(
38 AssistId("change_return_type_to_result", AssistKind::RefactorRewrite),
39 "Wrap return type in Result",
40 type_ref.syntax().text_range(),
41 |builder| {
42 let mut tail_return_expr_collector = TailReturnCollector::new();
43 tail_return_expr_collector.collect_jump_exprs(block_expr, false);
44 tail_return_expr_collector.collect_tail_exprs(block_expr);
45
46 for ret_expr_arg in tail_return_expr_collector.exprs_to_wrap {
47 builder.replace_node_and_indent(&ret_expr_arg, format!("Ok({})", ret_expr_arg));
48 }
49
50 match ctx.config.snippet_cap {
51 Some(cap) => {
52 let snippet = format!("Result<{}, ${{0:_}}>", type_ref);
53 builder.replace_snippet(cap, type_ref.syntax().text_range(), snippet)
54 }
55 None => builder
56 .replace(type_ref.syntax().text_range(), format!("Result<{}, _>", type_ref)),
57 }
58 },
59 )
60}
61
62struct TailReturnCollector {
63 exprs_to_wrap: Vec<SyntaxNode>,
64}
65
66impl TailReturnCollector {
67 fn new() -> Self {
68 Self { exprs_to_wrap: vec![] }
69 }
70 /// Collect all`return` expression
71 fn collect_jump_exprs(&mut self, block_expr: &BlockExpr, collect_break: bool) {
72 let statements = block_expr.statements();
73 for stmt in statements {
74 let expr = match &stmt {
75 ast::Stmt::ExprStmt(stmt) => stmt.expr(),
76 ast::Stmt::LetStmt(stmt) => stmt.initializer(),
77 };
78 if let Some(expr) = &expr {
79 self.handle_exprs(expr, collect_break);
80 }
81 }
82
83 // Browse tail expressions for each block
84 if let Some(expr) = block_expr.expr() {
85 if let Some(last_exprs) = get_tail_expr_from_block(&expr) {
86 for last_expr in last_exprs {
87 let last_expr = match last_expr {
88 NodeType::Node(expr) | NodeType::Leaf(expr) => expr,
89 };
90
91 if let Some(last_expr) = Expr::cast(last_expr.clone()) {
92 self.handle_exprs(&last_expr, collect_break);
93 } else if let Some(expr_stmt) = ast::Stmt::cast(last_expr) {
94 let expr_stmt = match &expr_stmt {
95 ast::Stmt::ExprStmt(stmt) => stmt.expr(),
96 ast::Stmt::LetStmt(stmt) => stmt.initializer(),
97 };
98 if let Some(expr) = &expr_stmt {
99 self.handle_exprs(expr, collect_break);
100 }
101 }
102 }
103 }
104 }
105 }
106
107 fn handle_exprs(&mut self, expr: &Expr, collect_break: bool) {
108 match expr {
109 Expr::BlockExpr(block_expr) => {
110 self.collect_jump_exprs(&block_expr, collect_break);
111 }
112 Expr::ReturnExpr(ret_expr) => {
113 if let Some(ret_expr_arg) = &ret_expr.expr() {
114 self.exprs_to_wrap.push(ret_expr_arg.syntax().clone());
115 }
116 }
117 Expr::BreakExpr(break_expr) if collect_break => {
118 if let Some(break_expr_arg) = &break_expr.expr() {
119 self.exprs_to_wrap.push(break_expr_arg.syntax().clone());
120 }
121 }
122 Expr::IfExpr(if_expr) => {
123 for block in if_expr.blocks() {
124 self.collect_jump_exprs(&block, collect_break);
125 }
126 }
127 Expr::LoopExpr(loop_expr) => {
128 if let Some(block_expr) = loop_expr.loop_body() {
129 self.collect_jump_exprs(&block_expr, collect_break);
130 }
131 }
132 Expr::ForExpr(for_expr) => {
133 if let Some(block_expr) = for_expr.loop_body() {
134 self.collect_jump_exprs(&block_expr, collect_break);
135 }
136 }
137 Expr::WhileExpr(while_expr) => {
138 if let Some(block_expr) = while_expr.loop_body() {
139 self.collect_jump_exprs(&block_expr, collect_break);
140 }
141 }
142 Expr::MatchExpr(match_expr) => {
143 if let Some(arm_list) = match_expr.match_arm_list() {
144 arm_list.arms().filter_map(|match_arm| match_arm.expr()).for_each(|expr| {
145 self.handle_exprs(&expr, collect_break);
146 });
147 }
148 }
149 _ => {}
150 }
151 }
152
153 fn collect_tail_exprs(&mut self, block: &BlockExpr) {
154 if let Some(expr) = block.expr() {
155 self.handle_exprs(&expr, true);
156 self.fetch_tail_exprs(&expr);
157 }
158 }
159
160 fn fetch_tail_exprs(&mut self, expr: &Expr) {
161 if let Some(exprs) = get_tail_expr_from_block(expr) {
162 for node_type in &exprs {
163 match node_type {
164 NodeType::Leaf(expr) => {
165 self.exprs_to_wrap.push(expr.clone());
166 }
167 NodeType::Node(expr) => match &Expr::cast(expr.clone()) {
168 Some(last_expr) => {
169 self.fetch_tail_exprs(last_expr);
170 }
171 None => {
172 self.exprs_to_wrap.push(expr.clone());
173 }
174 },
175 }
176 }
177 }
178 }
179}
180
181#[derive(Debug)]
182enum NodeType {
183 Leaf(SyntaxNode),
184 Node(SyntaxNode),
185}
186
187/// Get a tail expression inside a block
188fn get_tail_expr_from_block(expr: &Expr) -> Option<Vec<NodeType>> {
189 match expr {
190 Expr::IfExpr(if_expr) => {
191 let mut nodes = vec![];
192 for block in if_expr.blocks() {
193 if let Some(block_expr) = block.expr() {
194 if let Some(tail_exprs) = get_tail_expr_from_block(&block_expr) {
195 nodes.extend(tail_exprs);
196 }
197 } else if let Some(last_expr) = block.syntax().last_child() {
198 nodes.push(NodeType::Node(last_expr));
199 } else {
200 nodes.push(NodeType::Node(block.syntax().clone()));
201 }
202 }
203 Some(nodes)
204 }
205 Expr::LoopExpr(loop_expr) => {
206 loop_expr.syntax().last_child().map(|lc| vec![NodeType::Node(lc)])
207 }
208 Expr::ForExpr(for_expr) => {
209 for_expr.syntax().last_child().map(|lc| vec![NodeType::Node(lc)])
210 }
211 Expr::WhileExpr(while_expr) => {
212 while_expr.syntax().last_child().map(|lc| vec![NodeType::Node(lc)])
213 }
214 Expr::BlockExpr(block_expr) => {
215 block_expr.expr().map(|lc| vec![NodeType::Node(lc.syntax().clone())])
216 }
217 Expr::MatchExpr(match_expr) => {
218 let arm_list = match_expr.match_arm_list()?;
219 let arms: Vec<NodeType> = arm_list
220 .arms()
221 .filter_map(|match_arm| match_arm.expr())
222 .map(|expr| match expr {
223 Expr::ReturnExpr(ret_expr) => NodeType::Node(ret_expr.syntax().clone()),
224 Expr::BreakExpr(break_expr) => NodeType::Node(break_expr.syntax().clone()),
225 _ => match expr.syntax().last_child() {
226 Some(last_expr) => NodeType::Node(last_expr),
227 None => NodeType::Node(expr.syntax().clone()),
228 },
229 })
230 .collect();
231
232 Some(arms)
233 }
234 Expr::BreakExpr(expr) => expr.expr().map(|e| vec![NodeType::Leaf(e.syntax().clone())]),
235 Expr::ReturnExpr(ret_expr) => Some(vec![NodeType::Node(ret_expr.syntax().clone())]),
236 Expr::CallExpr(call_expr) => Some(vec![NodeType::Leaf(call_expr.syntax().clone())]),
237 Expr::Literal(lit_expr) => Some(vec![NodeType::Leaf(lit_expr.syntax().clone())]),
238 Expr::TupleExpr(expr) => Some(vec![NodeType::Leaf(expr.syntax().clone())]),
239 Expr::ArrayExpr(expr) => Some(vec![NodeType::Leaf(expr.syntax().clone())]),
240 Expr::ParenExpr(expr) => Some(vec![NodeType::Leaf(expr.syntax().clone())]),
241 Expr::PathExpr(expr) => Some(vec![NodeType::Leaf(expr.syntax().clone())]),
242 Expr::Label(expr) => Some(vec![NodeType::Leaf(expr.syntax().clone())]),
243 Expr::RecordExpr(expr) => Some(vec![NodeType::Leaf(expr.syntax().clone())]),
244 Expr::IndexExpr(expr) => Some(vec![NodeType::Leaf(expr.syntax().clone())]),
245 Expr::MethodCallExpr(expr) => Some(vec![NodeType::Leaf(expr.syntax().clone())]),
246 Expr::AwaitExpr(expr) => Some(vec![NodeType::Leaf(expr.syntax().clone())]),
247 Expr::CastExpr(expr) => Some(vec![NodeType::Leaf(expr.syntax().clone())]),
248 Expr::RefExpr(expr) => Some(vec![NodeType::Leaf(expr.syntax().clone())]),
249 Expr::PrefixExpr(expr) => Some(vec![NodeType::Leaf(expr.syntax().clone())]),
250 Expr::RangeExpr(expr) => Some(vec![NodeType::Leaf(expr.syntax().clone())]),
251 Expr::BinExpr(expr) => Some(vec![NodeType::Leaf(expr.syntax().clone())]),
252 Expr::MacroCall(expr) => Some(vec![NodeType::Leaf(expr.syntax().clone())]),
253 Expr::BoxExpr(expr) => Some(vec![NodeType::Leaf(expr.syntax().clone())]),
254 _ => None,
255 }
256}
257
258#[cfg(test)]
259mod tests {
260 use crate::tests::{check_assist, check_assist_not_applicable};
261
262 use super::*;
263
264 #[test]
265 fn change_return_type_to_result_simple() {
266 check_assist(
267 change_return_type_to_result,
268 r#"fn foo() -> i3<|>2 {
269 let test = "test";
270 return 42i32;
271 }"#,
272 r#"fn foo() -> Result<i32, ${0:_}> {
273 let test = "test";
274 return Ok(42i32);
275 }"#,
276 );
277 }
278
279 #[test]
280 fn change_return_type_to_result_simple_return_type() {
281 check_assist(
282 change_return_type_to_result,
283 r#"fn foo() -> i32<|> {
284 let test = "test";
285 return 42i32;
286 }"#,
287 r#"fn foo() -> Result<i32, ${0:_}> {
288 let test = "test";
289 return Ok(42i32);
290 }"#,
291 );
292 }
293
294 #[test]
295 fn change_return_type_to_result_simple_return_type_bad_cursor() {
296 check_assist_not_applicable(
297 change_return_type_to_result,
298 r#"fn foo() -> i32 {
299 let test = "test";<|>
300 return 42i32;
301 }"#,
302 );
303 }
304
305 #[test]
306 fn change_return_type_to_result_simple_return_type_already_result_std() {
307 check_assist_not_applicable(
308 change_return_type_to_result,
309 r#"fn foo() -> std::result::Result<i32<|>, String> {
310 let test = "test";
311 return 42i32;
312 }"#,
313 );
314 }
315
316 #[test]
317 fn change_return_type_to_result_simple_return_type_already_result() {
318 mark::check!(change_return_type_to_result_simple_return_type_already_result);
319 check_assist_not_applicable(
320 change_return_type_to_result,
321 r#"fn foo() -> Result<i32<|>, String> {
322 let test = "test";
323 return 42i32;
324 }"#,
325 );
326 }
327
328 #[test]
329 fn change_return_type_to_result_simple_with_cursor() {
330 check_assist(
331 change_return_type_to_result,
332 r#"fn foo() -> <|>i32 {
333 let test = "test";
334 return 42i32;
335 }"#,
336 r#"fn foo() -> Result<i32, ${0:_}> {
337 let test = "test";
338 return Ok(42i32);
339 }"#,
340 );
341 }
342
343 #[test]
344 fn change_return_type_to_result_simple_with_tail() {
345 check_assist(
346 change_return_type_to_result,
347 r#"fn foo() -><|> i32 {
348 let test = "test";
349 42i32
350 }"#,
351 r#"fn foo() -> Result<i32, ${0:_}> {
352 let test = "test";
353 Ok(42i32)
354 }"#,
355 );
356 }
357
358 #[test]
359 fn change_return_type_to_result_simple_with_tail_only() {
360 check_assist(
361 change_return_type_to_result,
362 r#"fn foo() -> i32<|> {
363 42i32
364 }"#,
365 r#"fn foo() -> Result<i32, ${0:_}> {
366 Ok(42i32)
367 }"#,
368 );
369 }
370 #[test]
371 fn change_return_type_to_result_simple_with_tail_block_like() {
372 check_assist(
373 change_return_type_to_result,
374 r#"fn foo() -> i32<|> {
375 if true {
376 42i32
377 } else {
378 24i32
379 }
380 }"#,
381 r#"fn foo() -> Result<i32, ${0:_}> {
382 if true {
383 Ok(42i32)
384 } else {
385 Ok(24i32)
386 }
387 }"#,
388 );
389 }
390
391 #[test]
392 fn change_return_type_to_result_simple_with_nested_if() {
393 check_assist(
394 change_return_type_to_result,
395 r#"fn foo() -> i32<|> {
396 if true {
397 if false {
398 1
399 } else {
400 2
401 }
402 } else {
403 24i32
404 }
405 }"#,
406 r#"fn foo() -> Result<i32, ${0:_}> {
407 if true {
408 if false {
409 Ok(1)
410 } else {
411 Ok(2)
412 }
413 } else {
414 Ok(24i32)
415 }
416 }"#,
417 );
418 }
419
420 #[test]
421 fn change_return_type_to_result_simple_with_await() {
422 check_assist(
423 change_return_type_to_result,
424 r#"async fn foo() -> i<|>32 {
425 if true {
426 if false {
427 1.await
428 } else {
429 2.await
430 }
431 } else {
432 24i32.await
433 }
434 }"#,
435 r#"async fn foo() -> Result<i32, ${0:_}> {
436 if true {
437 if false {
438 Ok(1.await)
439 } else {
440 Ok(2.await)
441 }
442 } else {
443 Ok(24i32.await)
444 }
445 }"#,
446 );
447 }
448
449 #[test]
450 fn change_return_type_to_result_simple_with_array() {
451 check_assist(
452 change_return_type_to_result,
453 r#"fn foo() -> [i32;<|> 3] {
454 [1, 2, 3]
455 }"#,
456 r#"fn foo() -> Result<[i32; 3], ${0:_}> {
457 Ok([1, 2, 3])
458 }"#,
459 );
460 }
461
462 #[test]
463 fn change_return_type_to_result_simple_with_cast() {
464 check_assist(
465 change_return_type_to_result,
466 r#"fn foo() -<|>> i32 {
467 if true {
468 if false {
469 1 as i32
470 } else {
471 2 as i32
472 }
473 } else {
474 24 as i32
475 }
476 }"#,
477 r#"fn foo() -> Result<i32, ${0:_}> {
478 if true {
479 if false {
480 Ok(1 as i32)
481 } else {
482 Ok(2 as i32)
483 }
484 } else {
485 Ok(24 as i32)
486 }
487 }"#,
488 );
489 }
490
491 #[test]
492 fn change_return_type_to_result_simple_with_tail_block_like_match() {
493 check_assist(
494 change_return_type_to_result,
495 r#"fn foo() -> i32<|> {
496 let my_var = 5;
497 match my_var {
498 5 => 42i32,
499 _ => 24i32,
500 }
501 }"#,
502 r#"fn foo() -> Result<i32, ${0:_}> {
503 let my_var = 5;
504 match my_var {
505 5 => Ok(42i32),
506 _ => Ok(24i32),
507 }
508 }"#,
509 );
510 }
511
512 #[test]
513 fn change_return_type_to_result_simple_with_loop_with_tail() {
514 check_assist(
515 change_return_type_to_result,
516 r#"fn foo() -> i32<|> {
517 let my_var = 5;
518 loop {
519 println!("test");
520 5
521 }
522
523 my_var
524 }"#,
525 r#"fn foo() -> Result<i32, ${0:_}> {
526 let my_var = 5;
527 loop {
528 println!("test");
529 5
530 }
531
532 Ok(my_var)
533 }"#,
534 );
535 }
536
537 #[test]
538 fn change_return_type_to_result_simple_with_loop_in_let_stmt() {
539 check_assist(
540 change_return_type_to_result,
541 r#"fn foo() -> i32<|> {
542 let my_var = let x = loop {
543 break 1;
544 };
545
546 my_var
547 }"#,
548 r#"fn foo() -> Result<i32, ${0:_}> {
549 let my_var = let x = loop {
550 break 1;
551 };
552
553 Ok(my_var)
554 }"#,
555 );
556 }
557
558 #[test]
559 fn change_return_type_to_result_simple_with_tail_block_like_match_return_expr() {
560 check_assist(
561 change_return_type_to_result,
562 r#"fn foo() -> i32<|> {
563 let my_var = 5;
564 let res = match my_var {
565 5 => 42i32,
566 _ => return 24i32,
567 };
568
569 res
570 }"#,
571 r#"fn foo() -> Result<i32, ${0:_}> {
572 let my_var = 5;
573 let res = match my_var {
574 5 => 42i32,
575 _ => return Ok(24i32),
576 };
577
578 Ok(res)
579 }"#,
580 );
581
582 check_assist(
583 change_return_type_to_result,
584 r#"fn foo() -> i32<|> {
585 let my_var = 5;
586 let res = if my_var == 5 {
587 42i32
588 } else {
589 return 24i32;
590 };
591
592 res
593 }"#,
594 r#"fn foo() -> Result<i32, ${0:_}> {
595 let my_var = 5;
596 let res = if my_var == 5 {
597 42i32
598 } else {
599 return Ok(24i32);
600 };
601
602 Ok(res)
603 }"#,
604 );
605 }
606
607 #[test]
608 fn change_return_type_to_result_simple_with_tail_block_like_match_deeper() {
609 check_assist(
610 change_return_type_to_result,
611 r#"fn foo() -> i32<|> {
612 let my_var = 5;
613 match my_var {
614 5 => {
615 if true {
616 42i32
617 } else {
618 25i32
619 }
620 },
621 _ => {
622 let test = "test";
623 if test == "test" {
624 return bar();
625 }
626 53i32
627 },
628 }
629 }"#,
630 r#"fn foo() -> Result<i32, ${0:_}> {
631 let my_var = 5;
632 match my_var {
633 5 => {
634 if true {
635 Ok(42i32)
636 } else {
637 Ok(25i32)
638 }
639 },
640 _ => {
641 let test = "test";
642 if test == "test" {
643 return Ok(bar());
644 }
645 Ok(53i32)
646 },
647 }
648 }"#,
649 );
650 }
651
652 #[test]
653 fn change_return_type_to_result_simple_with_tail_block_like_early_return() {
654 check_assist(
655 change_return_type_to_result,
656 r#"fn foo() -> i<|>32 {
657 let test = "test";
658 if test == "test" {
659 return 24i32;
660 }
661 53i32
662 }"#,
663 r#"fn foo() -> Result<i32, ${0:_}> {
664 let test = "test";
665 if test == "test" {
666 return Ok(24i32);
667 }
668 Ok(53i32)
669 }"#,
670 );
671 }
672
673 #[test]
674 fn change_return_type_to_result_simple_with_closure() {
675 check_assist(
676 change_return_type_to_result,
677 r#"fn foo(the_field: u32) -><|> u32 {
678 let true_closure = || {
679 return true;
680 };
681 if the_field < 5 {
682 let mut i = 0;
683
684
685 if true_closure() {
686 return 99;
687 } else {
688 return 0;
689 }
690 }
691
692 the_field
693 }"#,
694 r#"fn foo(the_field: u32) -> Result<u32, ${0:_}> {
695 let true_closure = || {
696 return true;
697 };
698 if the_field < 5 {
699 let mut i = 0;
700
701
702 if true_closure() {
703 return Ok(99);
704 } else {
705 return Ok(0);
706 }
707 }
708
709 Ok(the_field)
710 }"#,
711 );
712
713 check_assist(
714 change_return_type_to_result,
715 r#"fn foo(the_field: u32) -> u32<|> {
716 let true_closure = || {
717 return true;
718 };
719 if the_field < 5 {
720 let mut i = 0;
721
722
723 if true_closure() {
724 return 99;
725 } else {
726 return 0;
727 }
728 }
729 let t = None;
730
731 t.unwrap_or_else(|| the_field)
732 }"#,
733 r#"fn foo(the_field: u32) -> Result<u32, ${0:_}> {
734 let true_closure = || {
735 return true;
736 };
737 if the_field < 5 {
738 let mut i = 0;
739
740
741 if true_closure() {
742 return Ok(99);
743 } else {
744 return Ok(0);
745 }
746 }
747 let t = None;
748
749 Ok(t.unwrap_or_else(|| the_field))
750 }"#,
751 );
752 }
753
754 #[test]
755 fn change_return_type_to_result_simple_with_weird_forms() {
756 check_assist(
757 change_return_type_to_result,
758 r#"fn foo() -> i32<|> {
759 let test = "test";
760 if test == "test" {
761 return 24i32;
762 }
763 let mut i = 0;
764 loop {
765 if i == 1 {
766 break 55;
767 }
768 i += 1;
769 }
770 }"#,
771 r#"fn foo() -> Result<i32, ${0:_}> {
772 let test = "test";
773 if test == "test" {
774 return Ok(24i32);
775 }
776 let mut i = 0;
777 loop {
778 if i == 1 {
779 break Ok(55);
780 }
781 i += 1;
782 }
783 }"#,
784 );
785
786 check_assist(
787 change_return_type_to_result,
788 r#"fn foo() -> i32<|> {
789 let test = "test";
790 if test == "test" {
791 return 24i32;
792 }
793 let mut i = 0;
794 loop {
795 loop {
796 if i == 1 {
797 break 55;
798 }
799 i += 1;
800 }
801 }
802 }"#,
803 r#"fn foo() -> Result<i32, ${0:_}> {
804 let test = "test";
805 if test == "test" {
806 return Ok(24i32);
807 }
808 let mut i = 0;
809 loop {
810 loop {
811 if i == 1 {
812 break Ok(55);
813 }
814 i += 1;
815 }
816 }
817 }"#,
818 );
819
820 check_assist(
821 change_return_type_to_result,
822 r#"fn foo() -> i3<|>2 {
823 let test = "test";
824 let other = 5;
825 if test == "test" {
826 let res = match other {
827 5 => 43,
828 _ => return 56,
829 };
830 }
831 let mut i = 0;
832 loop {
833 loop {
834 if i == 1 {
835 break 55;
836 }
837 i += 1;
838 }
839 }
840 }"#,
841 r#"fn foo() -> Result<i32, ${0:_}> {
842 let test = "test";
843 let other = 5;
844 if test == "test" {
845 let res = match other {
846 5 => 43,
847 _ => return Ok(56),
848 };
849 }
850 let mut i = 0;
851 loop {
852 loop {
853 if i == 1 {
854 break Ok(55);
855 }
856 i += 1;
857 }
858 }
859 }"#,
860 );
861
862 check_assist(
863 change_return_type_to_result,
864 r#"fn foo(the_field: u32) -> u32<|> {
865 if the_field < 5 {
866 let mut i = 0;
867 loop {
868 if i > 5 {
869 return 55u32;
870 }
871 i += 3;
872 }
873
874 match i {
875 5 => return 99,
876 _ => return 0,
877 };
878 }
879
880 the_field
881 }"#,
882 r#"fn foo(the_field: u32) -> Result<u32, ${0:_}> {
883 if the_field < 5 {
884 let mut i = 0;
885 loop {
886 if i > 5 {
887 return Ok(55u32);
888 }
889 i += 3;
890 }
891
892 match i {
893 5 => return Ok(99),
894 _ => return Ok(0),
895 };
896 }
897
898 Ok(the_field)
899 }"#,
900 );
901
902 check_assist(
903 change_return_type_to_result,
904 r#"fn foo(the_field: u32) -> u3<|>2 {
905 if the_field < 5 {
906 let mut i = 0;
907
908 match i {
909 5 => return 99,
910 _ => return 0,
911 }
912 }
913
914 the_field
915 }"#,
916 r#"fn foo(the_field: u32) -> Result<u32, ${0:_}> {
917 if the_field < 5 {
918 let mut i = 0;
919
920 match i {
921 5 => return Ok(99),
922 _ => return Ok(0),
923 }
924 }
925
926 Ok(the_field)
927 }"#,
928 );
929
930 check_assist(
931 change_return_type_to_result,
932 r#"fn foo(the_field: u32) -> u32<|> {
933 if the_field < 5 {
934 let mut i = 0;
935
936 if i == 5 {
937 return 99
938 } else {
939 return 0
940 }
941 }
942
943 the_field
944 }"#,
945 r#"fn foo(the_field: u32) -> Result<u32, ${0:_}> {
946 if the_field < 5 {
947 let mut i = 0;
948
949 if i == 5 {
950 return Ok(99)
951 } else {
952 return Ok(0)
953 }
954 }
955
956 Ok(the_field)
957 }"#,
958 );
959
960 check_assist(
961 change_return_type_to_result,
962 r#"fn foo(the_field: u32) -> <|>u32 {
963 if the_field < 5 {
964 let mut i = 0;
965
966 if i == 5 {
967 return 99;
968 } else {
969 return 0;
970 }
971 }
972
973 the_field
974 }"#,
975 r#"fn foo(the_field: u32) -> Result<u32, ${0:_}> {
976 if the_field < 5 {
977 let mut i = 0;
978
979 if i == 5 {
980 return Ok(99);
981 } else {
982 return Ok(0);
983 }
984 }
985
986 Ok(the_field)
987 }"#,
988 );
989 }
990}
diff --git a/crates/ra_assists/src/handlers/change_visibility.rs b/crates/ra_assists/src/handlers/change_visibility.rs
deleted file mode 100644
index 724daa93f..000000000
--- a/crates/ra_assists/src/handlers/change_visibility.rs
+++ /dev/null
@@ -1,200 +0,0 @@
1use ra_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/ra_assists/src/handlers/early_return.rs b/crates/ra_assists/src/handlers/early_return.rs
deleted file mode 100644
index 3650289fd..000000000
--- a/crates/ra_assists/src/handlers/early_return.rs
+++ /dev/null
@@ -1,515 +0,0 @@
1use std::{iter::once, ops::RangeInclusive};
2
3use ra_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.args().count() == 1 => {
55 let path = pat.path()?;
56 match path.qualifier() {
57 None => {
58 let bound_ident = pat.args().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::bind_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::placeholder_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::bind_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