diff options
-rw-r--r-- | crates/assists/src/handlers/generate_enum_match_method.rs | 100 | ||||
-rw-r--r-- | crates/assists/src/handlers/generate_new.rs | 64 | ||||
-rw-r--r-- | crates/assists/src/utils.rs | 75 |
3 files changed, 85 insertions, 154 deletions
diff --git a/crates/assists/src/handlers/generate_enum_match_method.rs b/crates/assists/src/handlers/generate_enum_match_method.rs index 079ed27bd..270b438b7 100644 --- a/crates/assists/src/handlers/generate_enum_match_method.rs +++ b/crates/assists/src/handlers/generate_enum_match_method.rs | |||
@@ -1,10 +1,9 @@ | |||
1 | use hir::Adt; | 1 | use stdx::{format_to, to_lower_snake_case}; |
2 | use stdx::format_to; | ||
3 | use syntax::ast::{self, AstNode, NameOwner}; | 2 | use syntax::ast::{self, AstNode, NameOwner}; |
4 | use syntax::{ast::VisibilityOwner, T}; | 3 | use syntax::{ast::VisibilityOwner, T}; |
5 | use test_utils::mark; | 4 | use test_utils::mark; |
6 | 5 | ||
7 | use crate::{AssistContext, AssistId, AssistKind, Assists}; | 6 | use crate::{utils::find_struct_impl, AssistContext, AssistId, AssistKind, Assists}; |
8 | 7 | ||
9 | // Assist: generate_enum_match_method | 8 | // Assist: generate_enum_match_method |
10 | // | 9 | // |
@@ -40,10 +39,14 @@ pub(crate) fn generate_enum_match_method(acc: &mut Assists, ctx: &AssistContext) | |||
40 | return None; | 39 | return None; |
41 | } | 40 | } |
42 | 41 | ||
43 | let fn_name = to_lower_snake_case(&format!("{}", variant_name)); | 42 | let fn_name = to_lower_snake_case(&variant_name.to_string()); |
44 | 43 | ||
45 | // Return early if we've found an existing new fn | 44 | // Return early if we've found an existing new fn |
46 | let impl_def = find_struct_impl(&ctx, &parent_enum, format!("is_{}", fn_name).as_str())?; | 45 | let impl_def = find_struct_impl( |
46 | &ctx, | ||
47 | &ast::AdtDef::Enum(parent_enum.clone()), | ||
48 | format!("is_{}", fn_name).as_str(), | ||
49 | )?; | ||
47 | 50 | ||
48 | let target = variant.syntax().text_range(); | 51 | let target = variant.syntax().text_range(); |
49 | acc.add( | 52 | acc.add( |
@@ -95,94 +98,14 @@ pub(crate) fn generate_enum_match_method(acc: &mut Assists, ctx: &AssistContext) | |||
95 | // parameters | 98 | // parameters |
96 | fn generate_impl_text(strukt: &ast::Enum, code: &str) -> String { | 99 | fn generate_impl_text(strukt: &ast::Enum, code: &str) -> String { |
97 | let mut buf = String::with_capacity(code.len()); | 100 | let mut buf = String::with_capacity(code.len()); |
98 | buf.push_str("\n\nimpl"); | 101 | buf.push_str("\n\nimpl "); |
99 | buf.push_str(" "); | ||
100 | buf.push_str(strukt.name().unwrap().text()); | 102 | buf.push_str(strukt.name().unwrap().text()); |
101 | format_to!(buf, " {{\n{}\n}}", code); | 103 | format_to!(buf, " {{\n{}\n}}", code); |
102 | buf | 104 | buf |
103 | } | 105 | } |
104 | 106 | ||
105 | fn to_lower_snake_case(s: &str) -> String { | ||
106 | let mut buf = String::with_capacity(s.len()); | ||
107 | let mut prev = false; | ||
108 | for c in s.chars() { | ||
109 | if c.is_ascii_uppercase() && prev { | ||
110 | buf.push('_') | ||
111 | } | ||
112 | prev = true; | ||
113 | |||
114 | buf.push(c.to_ascii_lowercase()); | ||
115 | } | ||
116 | buf | ||
117 | } | ||
118 | |||
119 | // Uses a syntax-driven approach to find any impl blocks for the struct that | ||
120 | // exist within the module/file | ||
121 | // | ||
122 | // Returns `None` if we've found an existing `new` fn | ||
123 | // | ||
124 | // FIXME: change the new fn checking to a more semantic approach when that's more | ||
125 | // viable (e.g. we process proc macros, etc) | ||
126 | fn find_struct_impl( | ||
127 | ctx: &AssistContext, | ||
128 | strukt: &ast::Enum, | ||
129 | name: &str, | ||
130 | ) -> Option<Option<ast::Impl>> { | ||
131 | let db = ctx.db(); | ||
132 | let module = strukt.syntax().ancestors().find(|node| { | ||
133 | ast::Module::can_cast(node.kind()) || ast::SourceFile::can_cast(node.kind()) | ||
134 | })?; | ||
135 | |||
136 | let struct_def = ctx.sema.to_def(strukt)?; | ||
137 | |||
138 | let block = module.descendants().filter_map(ast::Impl::cast).find_map(|impl_blk| { | ||
139 | let blk = ctx.sema.to_def(&impl_blk)?; | ||
140 | |||
141 | // FIXME: handle e.g. `struct S<T>; impl<U> S<U> {}` | ||
142 | // (we currently use the wrong type parameter) | ||
143 | // also we wouldn't want to use e.g. `impl S<u32>` | ||
144 | let same_ty = match blk.target_ty(db).as_adt() { | ||
145 | Some(def) => def == Adt::Enum(struct_def), | ||
146 | None => false, | ||
147 | }; | ||
148 | let not_trait_impl = blk.target_trait(db).is_none(); | ||
149 | |||
150 | if !(same_ty && not_trait_impl) { | ||
151 | None | ||
152 | } else { | ||
153 | Some(impl_blk) | ||
154 | } | ||
155 | }); | ||
156 | |||
157 | if let Some(ref impl_blk) = block { | ||
158 | if has_fn(impl_blk, name) { | ||
159 | mark::hit!(test_gen_enum_match_impl_already_exists); | ||
160 | return None; | ||
161 | } | ||
162 | } | ||
163 | |||
164 | Some(block) | ||
165 | } | ||
166 | |||
167 | fn has_fn(imp: &ast::Impl, rhs_name: &str) -> bool { | ||
168 | if let Some(il) = imp.assoc_item_list() { | ||
169 | for item in il.assoc_items() { | ||
170 | if let ast::AssocItem::Fn(f) = item { | ||
171 | if let Some(name) = f.name() { | ||
172 | if name.text().eq_ignore_ascii_case(rhs_name) { | ||
173 | return true; | ||
174 | } | ||
175 | } | ||
176 | } | ||
177 | } | ||
178 | } | ||
179 | |||
180 | false | ||
181 | } | ||
182 | |||
183 | #[cfg(test)] | 107 | #[cfg(test)] |
184 | mod tests { | 108 | mod tests { |
185 | use ide_db::helpers::FamousDefs; | ||
186 | use test_utils::mark; | 109 | use test_utils::mark; |
187 | 110 | ||
188 | use crate::tests::{check_assist, check_assist_not_applicable}; | 111 | use crate::tests::{check_assist, check_assist_not_applicable}; |
@@ -190,9 +113,7 @@ mod tests { | |||
190 | use super::*; | 113 | use super::*; |
191 | 114 | ||
192 | fn check_not_applicable(ra_fixture: &str) { | 115 | fn check_not_applicable(ra_fixture: &str) { |
193 | let fixture = | 116 | check_assist_not_applicable(generate_enum_match_method, ra_fixture) |
194 | format!("//- /main.rs crate:main deps:core\n{}\n{}", ra_fixture, FamousDefs::FIXTURE); | ||
195 | check_assist_not_applicable(generate_enum_match_method, &fixture) | ||
196 | } | 117 | } |
197 | 118 | ||
198 | #[test] | 119 | #[test] |
@@ -221,7 +142,6 @@ impl Variant { | |||
221 | 142 | ||
222 | #[test] | 143 | #[test] |
223 | fn test_generate_enum_match_already_implemented() { | 144 | fn test_generate_enum_match_already_implemented() { |
224 | mark::check!(test_gen_enum_match_impl_already_exists); | ||
225 | check_not_applicable( | 145 | check_not_applicable( |
226 | r#" | 146 | r#" |
227 | enum Variant { | 147 | enum Variant { |
diff --git a/crates/assists/src/handlers/generate_new.rs b/crates/assists/src/handlers/generate_new.rs index b7390855a..84832273f 100644 --- a/crates/assists/src/handlers/generate_new.rs +++ b/crates/assists/src/handlers/generate_new.rs | |||
@@ -1,4 +1,3 @@ | |||
1 | use hir::Adt; | ||
2 | use itertools::Itertools; | 1 | use itertools::Itertools; |
3 | use stdx::format_to; | 2 | use stdx::format_to; |
4 | use syntax::{ | 3 | use syntax::{ |
@@ -6,7 +5,7 @@ use syntax::{ | |||
6 | SmolStr, T, | 5 | SmolStr, T, |
7 | }; | 6 | }; |
8 | 7 | ||
9 | use crate::{AssistContext, AssistId, AssistKind, Assists}; | 8 | use crate::{utils::find_struct_impl, AssistContext, AssistId, AssistKind, Assists}; |
10 | 9 | ||
11 | // Assist: generate_new | 10 | // Assist: generate_new |
12 | // | 11 | // |
@@ -38,7 +37,7 @@ pub(crate) fn generate_new(acc: &mut Assists, ctx: &AssistContext) -> Option<()> | |||
38 | }; | 37 | }; |
39 | 38 | ||
40 | // Return early if we've found an existing new fn | 39 | // Return early if we've found an existing new fn |
41 | let impl_def = find_struct_impl(&ctx, &strukt)?; | 40 | let impl_def = find_struct_impl(&ctx, &ast::AdtDef::Struct(strukt.clone()), "new")?; |
42 | 41 | ||
43 | let target = strukt.syntax().text_range(); | 42 | let target = strukt.syntax().text_range(); |
44 | acc.add(AssistId("generate_new", AssistKind::Generate), "Generate `new`", target, |builder| { | 43 | acc.add(AssistId("generate_new", AssistKind::Generate), "Generate `new`", target, |builder| { |
@@ -111,65 +110,6 @@ fn generate_impl_text(strukt: &ast::Struct, code: &str) -> String { | |||
111 | buf | 110 | buf |
112 | } | 111 | } |
113 | 112 | ||
114 | // Uses a syntax-driven approach to find any impl blocks for the struct that | ||
115 | // exist within the module/file | ||
116 | // | ||
117 | // Returns `None` if we've found an existing `new` fn | ||
118 | // | ||
119 | // FIXME: change the new fn checking to a more semantic approach when that's more | ||
120 | // viable (e.g. we process proc macros, etc) | ||
121 | fn find_struct_impl(ctx: &AssistContext, strukt: &ast::Struct) -> Option<Option<ast::Impl>> { | ||
122 | let db = ctx.db(); | ||
123 | let module = strukt.syntax().ancestors().find(|node| { | ||
124 | ast::Module::can_cast(node.kind()) || ast::SourceFile::can_cast(node.kind()) | ||
125 | })?; | ||
126 | |||
127 | let struct_def = ctx.sema.to_def(strukt)?; | ||
128 | |||
129 | let block = module.descendants().filter_map(ast::Impl::cast).find_map(|impl_blk| { | ||
130 | let blk = ctx.sema.to_def(&impl_blk)?; | ||
131 | |||
132 | // FIXME: handle e.g. `struct S<T>; impl<U> S<U> {}` | ||
133 | // (we currently use the wrong type parameter) | ||
134 | // also we wouldn't want to use e.g. `impl S<u32>` | ||
135 | let same_ty = match blk.target_ty(db).as_adt() { | ||
136 | Some(def) => def == Adt::Struct(struct_def), | ||
137 | None => false, | ||
138 | }; | ||
139 | let not_trait_impl = blk.target_trait(db).is_none(); | ||
140 | |||
141 | if !(same_ty && not_trait_impl) { | ||
142 | None | ||
143 | } else { | ||
144 | Some(impl_blk) | ||
145 | } | ||
146 | }); | ||
147 | |||
148 | if let Some(ref impl_blk) = block { | ||
149 | if has_new_fn(impl_blk) { | ||
150 | return None; | ||
151 | } | ||
152 | } | ||
153 | |||
154 | Some(block) | ||
155 | } | ||
156 | |||
157 | fn has_new_fn(imp: &ast::Impl) -> bool { | ||
158 | if let Some(il) = imp.assoc_item_list() { | ||
159 | for item in il.assoc_items() { | ||
160 | if let ast::AssocItem::Fn(f) = item { | ||
161 | if let Some(name) = f.name() { | ||
162 | if name.text().eq_ignore_ascii_case("new") { | ||
163 | return true; | ||
164 | } | ||
165 | } | ||
166 | } | ||
167 | } | ||
168 | } | ||
169 | |||
170 | false | ||
171 | } | ||
172 | |||
173 | #[cfg(test)] | 113 | #[cfg(test)] |
174 | mod tests { | 114 | mod tests { |
175 | use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target}; | 115 | use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target}; |
diff --git a/crates/assists/src/utils.rs b/crates/assists/src/utils.rs index 4e762e18b..3842558d8 100644 --- a/crates/assists/src/utils.rs +++ b/crates/assists/src/utils.rs | |||
@@ -2,7 +2,7 @@ | |||
2 | 2 | ||
3 | use std::ops; | 3 | use std::ops; |
4 | 4 | ||
5 | use hir::HasSource; | 5 | use hir::{Adt, HasSource}; |
6 | use ide_db::{helpers::SnippetCap, RootDatabase}; | 6 | use ide_db::{helpers::SnippetCap, RootDatabase}; |
7 | use itertools::Itertools; | 7 | use itertools::Itertools; |
8 | use syntax::{ | 8 | use syntax::{ |
@@ -15,7 +15,10 @@ use syntax::{ | |||
15 | SyntaxNode, TextSize, T, | 15 | SyntaxNode, TextSize, T, |
16 | }; | 16 | }; |
17 | 17 | ||
18 | use crate::ast_transform::{self, AstTransform, QualifyPaths, SubstituteTypeParams}; | 18 | use crate::{ |
19 | assist_context::AssistContext, | ||
20 | ast_transform::{self, AstTransform, QualifyPaths, SubstituteTypeParams}, | ||
21 | }; | ||
19 | 22 | ||
20 | pub(crate) fn unwrap_trivial_block(block: ast::BlockExpr) -> ast::Expr { | 23 | pub(crate) fn unwrap_trivial_block(block: ast::BlockExpr) -> ast::Expr { |
21 | extract_trivial_expression(&block) | 24 | extract_trivial_expression(&block) |
@@ -267,3 +270,71 @@ pub(crate) fn does_pat_match_variant(pat: &ast::Pat, var: &ast::Pat) -> bool { | |||
267 | 270 | ||
268 | pat_head == var_head | 271 | pat_head == var_head |
269 | } | 272 | } |
273 | |||
274 | // Uses a syntax-driven approach to find any impl blocks for the struct that | ||
275 | // exist within the module/file | ||
276 | // | ||
277 | // Returns `None` if we've found an existing `new` fn | ||
278 | // | ||
279 | // FIXME: change the new fn checking to a more semantic approach when that's more | ||
280 | // viable (e.g. we process proc macros, etc) | ||
281 | pub(crate) fn find_struct_impl( | ||
282 | ctx: &AssistContext, | ||
283 | strukt: &ast::AdtDef, | ||
284 | name: &str, | ||
285 | ) -> Option<Option<ast::Impl>> { | ||
286 | let db = ctx.db(); | ||
287 | let module = strukt.syntax().ancestors().find(|node| { | ||
288 | ast::Module::can_cast(node.kind()) || ast::SourceFile::can_cast(node.kind()) | ||
289 | })?; | ||
290 | |||
291 | let struct_def = match strukt { | ||
292 | ast::AdtDef::Enum(e) => Adt::Enum(ctx.sema.to_def(e)?), | ||
293 | ast::AdtDef::Struct(s) => Adt::Struct(ctx.sema.to_def(s)?), | ||
294 | ast::AdtDef::Union(u) => Adt::Union(ctx.sema.to_def(u)?), | ||
295 | }; | ||
296 | |||
297 | let block = module.descendants().filter_map(ast::Impl::cast).find_map(|impl_blk| { | ||
298 | let blk = ctx.sema.to_def(&impl_blk)?; | ||
299 | |||
300 | // FIXME: handle e.g. `struct S<T>; impl<U> S<U> {}` | ||
301 | // (we currently use the wrong type parameter) | ||
302 | // also we wouldn't want to use e.g. `impl S<u32>` | ||
303 | |||
304 | let same_ty = match blk.target_ty(db).as_adt() { | ||
305 | Some(def) => def == struct_def, | ||
306 | None => false, | ||
307 | }; | ||
308 | let not_trait_impl = blk.target_trait(db).is_none(); | ||
309 | |||
310 | if !(same_ty && not_trait_impl) { | ||
311 | None | ||
312 | } else { | ||
313 | Some(impl_blk) | ||
314 | } | ||
315 | }); | ||
316 | |||
317 | if let Some(ref impl_blk) = block { | ||
318 | if has_fn(impl_blk, name) { | ||
319 | return None; | ||
320 | } | ||
321 | } | ||
322 | |||
323 | Some(block) | ||
324 | } | ||
325 | |||
326 | fn has_fn(imp: &ast::Impl, rhs_name: &str) -> bool { | ||
327 | if let Some(il) = imp.assoc_item_list() { | ||
328 | for item in il.assoc_items() { | ||
329 | if let ast::AssocItem::Fn(f) = item { | ||
330 | if let Some(name) = f.name() { | ||
331 | if name.text().eq_ignore_ascii_case(rhs_name) { | ||
332 | return true; | ||
333 | } | ||
334 | } | ||
335 | } | ||
336 | } | ||
337 | } | ||
338 | |||
339 | false | ||
340 | } | ||