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