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