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