]> git.lizzy.rs Git - rust.git/blob - crates/ra_assists/src/handlers/add_new.rs
Centralize fixture parsing for assists
[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, 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(AssistId("add_new"), "Add default constructor", 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_add_new() {
185         // Check output of generation
186         check_assist(
187             add_new,
188 "struct Foo {<|>}",
189 "struct Foo {}
190
191 impl Foo {
192     fn $0new() -> Self { Self {  } }
193 }
194 ",
195         );
196         check_assist(
197             add_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             add_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             add_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             add_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             add_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             add_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             add_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             add_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             add_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             add_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 add_new_not_applicable_if_fn_exists() {
330         check_assist_not_applicable(
331             add_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             add_new,
344             "
345 struct Foo {<|>}
346
347 impl Foo {
348     fn New() -> Self {
349         Self
350     }
351 }",
352         );
353     }
354
355     #[test]
356     fn add_new_target() {
357         check_assist_target(
358             add_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             add_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 }