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.rs424
1 files changed, 424 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..e27def1d8
--- /dev/null
+++ b/crates/ra_assists/src/handlers/generate_new.rs
@@ -0,0 +1,424 @@
1use hir::Adt;
2use ra_syntax::{
3 ast::{
4 self, AstNode, NameOwner, StructKind, TypeAscriptionOwner, TypeParamsOwner, VisibilityOwner,
5 },
6 T,
7};
8use stdx::{format_to, SepBy};
9
10use crate::{AssistContext, AssistId, AssistKind, Assists};
11
12// Assist: generate_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// ```
32pub(crate) fn generate_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("generate_new", AssistKind::Generate), "Generate `new`", 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
93fn 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)
124fn 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
160fn 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)]
177mod 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_generate_new() {
185 // Check output of generation
186 check_assist(
187 generate_new,
188"struct Foo {<|>}",
189"struct Foo {}
190
191impl Foo {
192 fn $0new() -> Self { Self { } }
193}
194",
195 );
196 check_assist(
197 generate_new,
198"struct Foo<T: Clone> {<|>}",
199"struct Foo<T: Clone> {}
200
201impl<T: Clone> Foo<T> {
202 fn $0new() -> Self { Self { } }
203}
204",
205 );
206 check_assist(
207 generate_new,
208"struct Foo<'a, T: Foo<'a>> {<|>}",
209"struct Foo<'a, T: Foo<'a>> {}
210
211impl<'a, T: Foo<'a>> Foo<'a, T> {
212 fn $0new() -> Self { Self { } }
213}
214",
215 );
216 check_assist(
217 generate_new,
218"struct Foo { baz: String <|>}",
219"struct Foo { baz: String }
220
221impl Foo {
222 fn $0new(baz: String) -> Self { Self { baz } }
223}
224",
225 );
226 check_assist(
227 generate_new,
228"struct Foo { baz: String, qux: Vec<i32> <|>}",
229"struct Foo { baz: String, qux: Vec<i32> }
230
231impl 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 generate_new,
240"struct Foo { pub baz: String, pub qux: Vec<i32> <|>}",
241"struct Foo { pub baz: String, pub qux: Vec<i32> }
242
243impl 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 generate_new,
252"struct Foo {<|>}
253
254impl Foo {}
255",
256"struct Foo {}
257
258impl Foo {
259 fn $0new() -> Self { Self { } }
260}
261",
262 );
263 check_assist(
264 generate_new,
265"struct Foo {<|>}
266
267impl Foo {
268 fn qux(&self) {}
269}
270",
271"struct Foo {}
272
273impl Foo {
274 fn $0new() -> Self { Self { } }
275
276 fn qux(&self) {}
277}
278",
279 );
280
281 check_assist(
282 generate_new,
283"struct Foo {<|>}
284
285impl Foo {
286 fn qux(&self) {}
287 fn baz() -> i32 {
288 5
289 }
290}
291",
292"struct Foo {}
293
294impl 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 generate_new,
308"pub struct Foo {<|>}",
309"pub struct Foo {}
310
311impl Foo {
312 pub fn $0new() -> Self { Self { } }
313}
314",
315 );
316 check_assist(
317 generate_new,
318"pub(crate) struct Foo {<|>}",
319"pub(crate) struct Foo {}
320
321impl Foo {
322 pub(crate) fn $0new() -> Self { Self { } }
323}
324",
325 );
326 }
327
328 #[test]
329 fn generate_new_not_applicable_if_fn_exists() {
330 check_assist_not_applicable(
331 generate_new,
332 "
333struct Foo {<|>}
334
335impl Foo {
336 fn new() -> Self {
337 Self
338 }
339}",
340 );
341
342 check_assist_not_applicable(
343 generate_new,
344 "
345struct Foo {<|>}
346
347impl Foo {
348 fn New() -> Self {
349 Self
350 }
351}",
352 );
353 }
354
355 #[test]
356 fn generate_new_target() {
357 check_assist_target(
358 generate_new,
359 "
360struct SomeThingIrrelevant;
361/// Has a lifetime parameter
362struct Foo<'a, T: Foo<'a>> {<|>}
363struct EvenMoreIrrelevant;
364",
365 "/// Has a lifetime parameter
366struct Foo<'a, T: Foo<'a>> {}",
367 );
368 }
369
370 #[test]
371 fn test_unrelated_new() {
372 check_assist(
373 generate_new,
374 r##"
375pub struct AstId<N: AstNode> {
376 file_id: HirFileId,
377 file_ast_id: FileAstId<N>,
378}
379
380impl<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
386pub struct Source<T> {
387 pub file_id: HirFileId,<|>
388 pub ast: T,
389}
390
391impl<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##"
398pub struct AstId<N: AstNode> {
399 file_id: HirFileId,
400 file_ast_id: FileAstId<N>,
401}
402
403impl<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
409pub struct Source<T> {
410 pub file_id: HirFileId,
411 pub ast: T,
412}
413
414impl<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}