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