diff options
Diffstat (limited to 'crates/ra_assists/src/handlers/generate_new.rs')
-rw-r--r-- | crates/ra_assists/src/handlers/generate_new.rs | 424 |
1 files changed, 424 insertions, 0 deletions
diff --git a/crates/ra_assists/src/handlers/generate_new.rs b/crates/ra_assists/src/handlers/generate_new.rs new file mode 100644 index 000000000..b37bba761 --- /dev/null +++ b/crates/ra_assists/src/handlers/generate_new.rs | |||
@@ -0,0 +1,424 @@ | |||
1 | use hir::Adt; | ||
2 | use ra_syntax::{ | ||
3 | ast::{ | ||
4 | self, AstNode, NameOwner, StructKind, TypeAscriptionOwner, TypeParamsOwner, VisibilityOwner, | ||
5 | }, | ||
6 | T, | ||
7 | }; | ||
8 | use stdx::{format_to, SepBy}; | ||
9 | |||
10 | use crate::{AssistContext, AssistId, AssistKind, Assists}; | ||
11 | |||
12 | // Assist: generate_new | ||
13 | // | ||
14 | // Adds a new inherent impl for a type. | ||
15 | // | ||
16 | // ``` | ||
17 | // struct Ctx<T: Clone> { | ||
18 | // data: T,<|> | ||
19 | // } | ||
20 | // ``` | ||
21 | // -> | ||
22 | // ``` | ||
23 | // struct Ctx<T: Clone> { | ||
24 | // data: T, | ||
25 | // } | ||
26 | // | ||
27 | // impl<T: Clone> Ctx<T> { | ||
28 | // fn $0new(data: T) -> Self { Self { data } } | ||
29 | // } | ||
30 | // | ||
31 | // ``` | ||
32 | pub(crate) fn generate_new(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { | ||
33 | let strukt = ctx.find_node_at_offset::<ast::StructDef>()?; | ||
34 | |||
35 | // We want to only apply this to non-union structs with named fields | ||
36 | let field_list = match strukt.kind() { | ||
37 | StructKind::Record(named) => named, | ||
38 | _ => return None, | ||
39 | }; | ||
40 | |||
41 | // Return early if we've found an existing new fn | ||
42 | let impl_def = find_struct_impl(&ctx, &strukt)?; | ||
43 | |||
44 | let target = strukt.syntax().text_range(); | ||
45 | acc.add(AssistId("generate_new", AssistKind::None), "Generate `new`", target, |builder| { | ||
46 | let mut buf = String::with_capacity(512); | ||
47 | |||
48 | if impl_def.is_some() { | ||
49 | buf.push('\n'); | ||
50 | } | ||
51 | |||
52 | let vis = strukt.visibility().map_or(String::new(), |v| format!("{} ", v)); | ||
53 | |||
54 | let params = field_list | ||
55 | .fields() | ||
56 | .filter_map(|f| { | ||
57 | Some(format!("{}: {}", f.name()?.syntax(), f.ascribed_type()?.syntax())) | ||
58 | }) | ||
59 | .sep_by(", "); | ||
60 | let fields = field_list.fields().filter_map(|f| f.name()).sep_by(", "); | ||
61 | |||
62 | format_to!(buf, " {}fn new({}) -> Self {{ Self {{ {} }} }}", vis, params, fields); | ||
63 | |||
64 | let start_offset = impl_def | ||
65 | .and_then(|impl_def| { | ||
66 | buf.push('\n'); | ||
67 | let start = impl_def | ||
68 | .syntax() | ||
69 | .descendants_with_tokens() | ||
70 | .find(|t| t.kind() == T!['{'])? | ||
71 | .text_range() | ||
72 | .end(); | ||
73 | |||
74 | Some(start) | ||
75 | }) | ||
76 | .unwrap_or_else(|| { | ||
77 | buf = generate_impl_text(&strukt, &buf); | ||
78 | strukt.syntax().text_range().end() | ||
79 | }); | ||
80 | |||
81 | match ctx.config.snippet_cap { | ||
82 | None => builder.insert(start_offset, buf), | ||
83 | Some(cap) => { | ||
84 | buf = buf.replace("fn new", "fn $0new"); | ||
85 | builder.insert_snippet(cap, start_offset, buf); | ||
86 | } | ||
87 | } | ||
88 | }) | ||
89 | } | ||
90 | |||
91 | // Generates the surrounding `impl Type { <code> }` including type and lifetime | ||
92 | // parameters | ||
93 | fn generate_impl_text(strukt: &ast::StructDef, code: &str) -> String { | ||
94 | let type_params = strukt.type_param_list(); | ||
95 | let mut buf = String::with_capacity(code.len()); | ||
96 | buf.push_str("\n\nimpl"); | ||
97 | if let Some(type_params) = &type_params { | ||
98 | format_to!(buf, "{}", type_params.syntax()); | ||
99 | } | ||
100 | buf.push_str(" "); | ||
101 | buf.push_str(strukt.name().unwrap().text().as_str()); | ||
102 | if let Some(type_params) = type_params { | ||
103 | let lifetime_params = type_params | ||
104 | .lifetime_params() | ||
105 | .filter_map(|it| it.lifetime_token()) | ||
106 | .map(|it| it.text().clone()); | ||
107 | let type_params = | ||
108 | type_params.type_params().filter_map(|it| it.name()).map(|it| it.text().clone()); | ||
109 | format_to!(buf, "<{}>", lifetime_params.chain(type_params).sep_by(", ")) | ||
110 | } | ||
111 | |||
112 | format_to!(buf, " {{\n{}\n}}\n", code); | ||
113 | |||
114 | buf | ||
115 | } | ||
116 | |||
117 | // Uses a syntax-driven approach to find any impl blocks for the struct that | ||
118 | // exist within the module/file | ||
119 | // | ||
120 | // Returns `None` if we've found an existing `new` fn | ||
121 | // | ||
122 | // FIXME: change the new fn checking to a more semantic approach when that's more | ||
123 | // viable (e.g. we process proc macros, etc) | ||
124 | fn find_struct_impl(ctx: &AssistContext, strukt: &ast::StructDef) -> Option<Option<ast::ImplDef>> { | ||
125 | let db = ctx.db(); | ||
126 | let module = strukt.syntax().ancestors().find(|node| { | ||
127 | ast::Module::can_cast(node.kind()) || ast::SourceFile::can_cast(node.kind()) | ||
128 | })?; | ||
129 | |||
130 | let struct_def = ctx.sema.to_def(strukt)?; | ||
131 | |||
132 | let block = module.descendants().filter_map(ast::ImplDef::cast).find_map(|impl_blk| { | ||
133 | let blk = ctx.sema.to_def(&impl_blk)?; | ||
134 | |||
135 | // FIXME: handle e.g. `struct S<T>; impl<U> S<U> {}` | ||
136 | // (we currently use the wrong type parameter) | ||
137 | // also we wouldn't want to use e.g. `impl S<u32>` | ||
138 | let same_ty = match blk.target_ty(db).as_adt() { | ||
139 | Some(def) => def == Adt::Struct(struct_def), | ||
140 | None => false, | ||
141 | }; | ||
142 | let not_trait_impl = blk.target_trait(db).is_none(); | ||
143 | |||
144 | if !(same_ty && not_trait_impl) { | ||
145 | None | ||
146 | } else { | ||
147 | Some(impl_blk) | ||
148 | } | ||
149 | }); | ||
150 | |||
151 | if let Some(ref impl_blk) = block { | ||
152 | if has_new_fn(impl_blk) { | ||
153 | return None; | ||
154 | } | ||
155 | } | ||
156 | |||
157 | Some(block) | ||
158 | } | ||
159 | |||
160 | fn has_new_fn(imp: &ast::ImplDef) -> bool { | ||
161 | if let Some(il) = imp.item_list() { | ||
162 | for item in il.assoc_items() { | ||
163 | if let ast::AssocItem::FnDef(f) = item { | ||
164 | if let Some(name) = f.name() { | ||
165 | if name.text().eq_ignore_ascii_case("new") { | ||
166 | return true; | ||
167 | } | ||
168 | } | ||
169 | } | ||
170 | } | ||
171 | } | ||
172 | |||
173 | false | ||
174 | } | ||
175 | |||
176 | #[cfg(test)] | ||
177 | mod tests { | ||
178 | use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target}; | ||
179 | |||
180 | use super::*; | ||
181 | |||
182 | #[test] | ||
183 | #[rustfmt::skip] | ||
184 | fn test_generate_new() { | ||
185 | // Check output of generation | ||
186 | check_assist( | ||
187 | generate_new, | ||
188 | "struct Foo {<|>}", | ||
189 | "struct Foo {} | ||
190 | |||
191 | impl Foo { | ||
192 | fn $0new() -> Self { Self { } } | ||
193 | } | ||
194 | ", | ||
195 | ); | ||
196 | check_assist( | ||
197 | generate_new, | ||
198 | "struct Foo<T: Clone> {<|>}", | ||
199 | "struct Foo<T: Clone> {} | ||
200 | |||
201 | impl<T: Clone> Foo<T> { | ||
202 | fn $0new() -> Self { Self { } } | ||
203 | } | ||
204 | ", | ||
205 | ); | ||
206 | check_assist( | ||
207 | generate_new, | ||
208 | "struct Foo<'a, T: Foo<'a>> {<|>}", | ||
209 | "struct Foo<'a, T: Foo<'a>> {} | ||
210 | |||
211 | impl<'a, T: Foo<'a>> Foo<'a, T> { | ||
212 | fn $0new() -> Self { Self { } } | ||
213 | } | ||
214 | ", | ||
215 | ); | ||
216 | check_assist( | ||
217 | generate_new, | ||
218 | "struct Foo { baz: String <|>}", | ||
219 | "struct Foo { baz: String } | ||
220 | |||
221 | impl Foo { | ||
222 | fn $0new(baz: String) -> Self { Self { baz } } | ||
223 | } | ||
224 | ", | ||
225 | ); | ||
226 | check_assist( | ||
227 | generate_new, | ||
228 | "struct Foo { baz: String, qux: Vec<i32> <|>}", | ||
229 | "struct Foo { baz: String, qux: Vec<i32> } | ||
230 | |||
231 | impl Foo { | ||
232 | fn $0new(baz: String, qux: Vec<i32>) -> Self { Self { baz, qux } } | ||
233 | } | ||
234 | ", | ||
235 | ); | ||
236 | |||
237 | // Check that visibility modifiers don't get brought in for fields | ||
238 | check_assist( | ||
239 | generate_new, | ||
240 | "struct Foo { pub baz: String, pub qux: Vec<i32> <|>}", | ||
241 | "struct Foo { pub baz: String, pub qux: Vec<i32> } | ||
242 | |||
243 | impl Foo { | ||
244 | fn $0new(baz: String, qux: Vec<i32>) -> Self { Self { baz, qux } } | ||
245 | } | ||
246 | ", | ||
247 | ); | ||
248 | |||
249 | // Check that it reuses existing impls | ||
250 | check_assist( | ||
251 | generate_new, | ||
252 | "struct Foo {<|>} | ||
253 | |||
254 | impl Foo {} | ||
255 | ", | ||
256 | "struct Foo {} | ||
257 | |||
258 | impl Foo { | ||
259 | fn $0new() -> Self { Self { } } | ||
260 | } | ||
261 | ", | ||
262 | ); | ||
263 | check_assist( | ||
264 | generate_new, | ||
265 | "struct Foo {<|>} | ||
266 | |||
267 | impl Foo { | ||
268 | fn qux(&self) {} | ||
269 | } | ||
270 | ", | ||
271 | "struct Foo {} | ||
272 | |||
273 | impl Foo { | ||
274 | fn $0new() -> Self { Self { } } | ||
275 | |||
276 | fn qux(&self) {} | ||
277 | } | ||
278 | ", | ||
279 | ); | ||
280 | |||
281 | check_assist( | ||
282 | generate_new, | ||
283 | "struct Foo {<|>} | ||
284 | |||
285 | impl Foo { | ||
286 | fn qux(&self) {} | ||
287 | fn baz() -> i32 { | ||
288 | 5 | ||
289 | } | ||
290 | } | ||
291 | ", | ||
292 | "struct Foo {} | ||
293 | |||
294 | impl Foo { | ||
295 | fn $0new() -> Self { Self { } } | ||
296 | |||
297 | fn qux(&self) {} | ||
298 | fn baz() -> i32 { | ||
299 | 5 | ||
300 | } | ||
301 | } | ||
302 | ", | ||
303 | ); | ||
304 | |||
305 | // Check visibility of new fn based on struct | ||
306 | check_assist( | ||
307 | generate_new, | ||
308 | "pub struct Foo {<|>}", | ||
309 | "pub struct Foo {} | ||
310 | |||
311 | impl Foo { | ||
312 | pub fn $0new() -> Self { Self { } } | ||
313 | } | ||
314 | ", | ||
315 | ); | ||
316 | check_assist( | ||
317 | generate_new, | ||
318 | "pub(crate) struct Foo {<|>}", | ||
319 | "pub(crate) struct Foo {} | ||
320 | |||
321 | impl Foo { | ||
322 | pub(crate) fn $0new() -> Self { Self { } } | ||
323 | } | ||
324 | ", | ||
325 | ); | ||
326 | } | ||
327 | |||
328 | #[test] | ||
329 | fn generate_new_not_applicable_if_fn_exists() { | ||
330 | check_assist_not_applicable( | ||
331 | generate_new, | ||
332 | " | ||
333 | struct Foo {<|>} | ||
334 | |||
335 | impl Foo { | ||
336 | fn new() -> Self { | ||
337 | Self | ||
338 | } | ||
339 | }", | ||
340 | ); | ||
341 | |||
342 | check_assist_not_applicable( | ||
343 | generate_new, | ||
344 | " | ||
345 | struct Foo {<|>} | ||
346 | |||
347 | impl Foo { | ||
348 | fn New() -> Self { | ||
349 | Self | ||
350 | } | ||
351 | }", | ||
352 | ); | ||
353 | } | ||
354 | |||
355 | #[test] | ||
356 | fn generate_new_target() { | ||
357 | check_assist_target( | ||
358 | generate_new, | ||
359 | " | ||
360 | struct SomeThingIrrelevant; | ||
361 | /// Has a lifetime parameter | ||
362 | struct Foo<'a, T: Foo<'a>> {<|>} | ||
363 | struct EvenMoreIrrelevant; | ||
364 | ", | ||
365 | "/// Has a lifetime parameter | ||
366 | struct Foo<'a, T: Foo<'a>> {}", | ||
367 | ); | ||
368 | } | ||
369 | |||
370 | #[test] | ||
371 | fn test_unrelated_new() { | ||
372 | check_assist( | ||
373 | generate_new, | ||
374 | r##" | ||
375 | pub struct AstId<N: AstNode> { | ||
376 | file_id: HirFileId, | ||
377 | file_ast_id: FileAstId<N>, | ||
378 | } | ||
379 | |||
380 | impl<N: AstNode> AstId<N> { | ||
381 | pub fn new(file_id: HirFileId, file_ast_id: FileAstId<N>) -> AstId<N> { | ||
382 | AstId { file_id, file_ast_id } | ||
383 | } | ||
384 | } | ||
385 | |||
386 | pub struct Source<T> { | ||
387 | pub file_id: HirFileId,<|> | ||
388 | pub ast: T, | ||
389 | } | ||
390 | |||
391 | impl<T> Source<T> { | ||
392 | pub fn map<F: FnOnce(T) -> U, U>(self, f: F) -> Source<U> { | ||
393 | Source { file_id: self.file_id, ast: f(self.ast) } | ||
394 | } | ||
395 | } | ||
396 | "##, | ||
397 | r##" | ||
398 | pub struct AstId<N: AstNode> { | ||
399 | file_id: HirFileId, | ||
400 | file_ast_id: FileAstId<N>, | ||
401 | } | ||
402 | |||
403 | impl<N: AstNode> AstId<N> { | ||
404 | pub fn new(file_id: HirFileId, file_ast_id: FileAstId<N>) -> AstId<N> { | ||
405 | AstId { file_id, file_ast_id } | ||
406 | } | ||
407 | } | ||
408 | |||
409 | pub struct Source<T> { | ||
410 | pub file_id: HirFileId, | ||
411 | pub ast: T, | ||
412 | } | ||
413 | |||
414 | impl<T> Source<T> { | ||
415 | pub fn $0new(file_id: HirFileId, ast: T) -> Self { Self { file_id, ast } } | ||
416 | |||
417 | pub fn map<F: FnOnce(T) -> U, U>(self, f: F) -> Source<U> { | ||
418 | Source { file_id: self.file_id, ast: f(self.ast) } | ||
419 | } | ||
420 | } | ||
421 | "##, | ||
422 | ); | ||
423 | } | ||
424 | } | ||