aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_assists/src/handlers/add_new.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_assists/src/handlers/add_new.rs')
-rw-r--r--crates/ra_assists/src/handlers/add_new.rs436
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 @@
1use format_buf::format;
2use hir::{Adt, InFile};
3use join_to_string::join;
4use ra_syntax::{
5 ast::{
6 self, AstNode, NameOwner, StructKind, TypeAscriptionOwner, TypeParamsOwner, VisibilityOwner,
7 },
8 TextUnit, T,
9};
10use std::fmt::Write;
11
12use 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// ```
34pub(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
100fn 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)
131fn 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
172fn 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)]
189mod 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
203impl 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
213impl<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
223impl<'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
233impl 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
243impl 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
255impl 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
266impl Foo {}
267",
268"struct Foo {}
269
270impl Foo {
271 fn new() -> Self { Self { } }<|>
272}
273",
274 );
275 check_assist(
276 add_new,
277"struct Foo {<|>}
278
279impl Foo {
280 fn qux(&self) {}
281}
282",
283"struct Foo {}
284
285impl 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
297impl Foo {
298 fn qux(&self) {}
299 fn baz() -> i32 {
300 5
301 }
302}
303",
304"struct Foo {}
305
306impl 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
323impl 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
333impl 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 "
345struct Foo {<|>}
346
347impl Foo {
348 fn new() -> Self {
349 Self
350 }
351}",
352 );
353
354 check_assist_not_applicable(
355 add_new,
356 "
357struct Foo {<|>}
358
359impl 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 "
372struct SomeThingIrrelevant;
373/// Has a lifetime parameter
374struct Foo<'a, T: Foo<'a>> {<|>}
375struct EvenMoreIrrelevant;
376",
377 "/// Has a lifetime parameter
378struct Foo<'a, T: Foo<'a>> {}",
379 );
380 }
381
382 #[test]
383 fn test_unrelated_new() {
384 check_assist(
385 add_new,
386 r##"
387pub struct AstId<N: AstNode> {
388 file_id: HirFileId,
389 file_ast_id: FileAstId<N>,
390}
391
392impl<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
398pub struct Source<T> {
399 pub file_id: HirFileId,<|>
400 pub ast: T,
401}
402
403impl<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##"
410pub struct AstId<N: AstNode> {
411 file_id: HirFileId,
412 file_ast_id: FileAstId<N>,
413}
414
415impl<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
421pub struct Source<T> {
422 pub file_id: HirFileId,
423 pub ast: T,
424}
425
426impl<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}