aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_assists/src/handlers/generate_new.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_assists/src/handlers/generate_new.rs')
-rw-r--r--crates/ra_assists/src/handlers/generate_new.rs420
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 @@
1use hir::Adt;
2use ra_syntax::{
3 ast::{self, AstNode, GenericParamsOwner, NameOwner, StructKind, VisibilityOwner},
4 T,
5};
6use stdx::{format_to, SepBy};
7
8use 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// ```
30pub(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
89fn 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)
120fn 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
156fn 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)]
173mod 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
187impl 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
197impl<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
207impl<'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
217impl 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
227impl 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
239impl 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
250impl Foo {}
251",
252"struct Foo {}
253
254impl Foo {
255 fn $0new() -> Self { Self { } }
256}
257",
258 );
259 check_assist(
260 generate_new,
261"struct Foo {<|>}
262
263impl Foo {
264 fn qux(&self) {}
265}
266",
267"struct Foo {}
268
269impl 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
281impl Foo {
282 fn qux(&self) {}
283 fn baz() -> i32 {
284 5
285 }
286}
287",
288"struct Foo {}
289
290impl 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
307impl 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
317impl 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 "
329struct Foo {<|>}
330
331impl Foo {
332 fn new() -> Self {
333 Self
334 }
335}",
336 );
337
338 check_assist_not_applicable(
339 generate_new,
340 "
341struct Foo {<|>}
342
343impl 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 "
356struct SomeThingIrrelevant;
357/// Has a lifetime parameter
358struct Foo<'a, T: Foo<'a>> {<|>}
359struct EvenMoreIrrelevant;
360",
361 "/// Has a lifetime parameter
362struct Foo<'a, T: Foo<'a>> {}",
363 );
364 }
365
366 #[test]
367 fn test_unrelated_new() {
368 check_assist(
369 generate_new,
370 r##"
371pub struct AstId<N: AstNode> {
372 file_id: HirFileId,
373 file_ast_id: FileAstId<N>,
374}
375
376impl<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
382pub struct Source<T> {
383 pub file_id: HirFileId,<|>
384 pub ast: T,
385}
386
387impl<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##"
394pub struct AstId<N: AstNode> {
395 file_id: HirFileId,
396 file_ast_id: FileAstId<N>,
397}
398
399impl<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
405pub struct Source<T> {
406 pub file_id: HirFileId,
407 pub ast: T,
408}
409
410impl<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}