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.rs430
1 files changed, 430 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..a08639311
--- /dev/null
+++ b/crates/ra_assists/src/handlers/add_new.rs
@@ -0,0 +1,430 @@
1use format_buf::format;
2use hir::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_ty = {
139 let src = InFile { file_id: ctx.frange.file_id.into(), value: strukt.clone() };
140 sb.to_def(src)?.ty(db)
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 let same_ty = blk.target_ty(db) == struct_ty;
148 let not_trait_impl = blk.target_trait(db).is_none();
149
150 if !(same_ty && not_trait_impl) {
151 None
152 } else {
153 Some(impl_blk)
154 }
155 });
156
157 if let Some(ref impl_blk) = block {
158 if has_new_fn(impl_blk) {
159 return None;
160 }
161 }
162
163 Some(block)
164}
165
166fn has_new_fn(imp: &ast::ImplBlock) -> bool {
167 if let Some(il) = imp.item_list() {
168 for item in il.impl_items() {
169 if let ast::ImplItem::FnDef(f) = item {
170 if let Some(name) = f.name() {
171 if name.text().eq_ignore_ascii_case("new") {
172 return true;
173 }
174 }
175 }
176 }
177 }
178
179 false
180}
181
182#[cfg(test)]
183mod tests {
184 use crate::helpers::{check_assist, check_assist_not_applicable, check_assist_target};
185
186 use super::*;
187
188 #[test]
189 #[rustfmt::skip]
190 fn test_add_new() {
191 // Check output of generation
192 check_assist(
193 add_new,
194"struct Foo {<|>}",
195"struct Foo {}
196
197impl Foo {
198 fn new() -> Self { Self { } }<|>
199}
200",
201 );
202 check_assist(
203 add_new,
204"struct Foo<T: Clone> {<|>}",
205"struct Foo<T: Clone> {}
206
207impl<T: Clone> Foo<T> {
208 fn new() -> Self { Self { } }<|>
209}
210",
211 );
212 check_assist(
213 add_new,
214"struct Foo<'a, T: Foo<'a>> {<|>}",
215"struct Foo<'a, T: Foo<'a>> {}
216
217impl<'a, T: Foo<'a>> Foo<'a, T> {
218 fn new() -> Self { Self { } }<|>
219}
220",
221 );
222 check_assist(
223 add_new,
224"struct Foo { baz: String <|>}",
225"struct Foo { baz: String }
226
227impl Foo {
228 fn new(baz: String) -> Self { Self { baz } }<|>
229}
230",
231 );
232 check_assist(
233 add_new,
234"struct Foo { baz: String, qux: Vec<i32> <|>}",
235"struct Foo { baz: String, qux: Vec<i32> }
236
237impl Foo {
238 fn new(baz: String, qux: Vec<i32>) -> Self { Self { baz, qux } }<|>
239}
240",
241 );
242
243 // Check that visibility modifiers don't get brought in for fields
244 check_assist(
245 add_new,
246"struct Foo { pub baz: String, pub qux: Vec<i32> <|>}",
247"struct Foo { pub baz: String, pub qux: Vec<i32> }
248
249impl Foo {
250 fn new(baz: String, qux: Vec<i32>) -> Self { Self { baz, qux } }<|>
251}
252",
253 );
254
255 // Check that it reuses existing impls
256 check_assist(
257 add_new,
258"struct Foo {<|>}
259
260impl Foo {}
261",
262"struct Foo {}
263
264impl Foo {
265 fn new() -> Self { Self { } }<|>
266}
267",
268 );
269 check_assist(
270 add_new,
271"struct Foo {<|>}
272
273impl Foo {
274 fn qux(&self) {}
275}
276",
277"struct Foo {}
278
279impl Foo {
280 fn new() -> Self { Self { } }<|>
281
282 fn qux(&self) {}
283}
284",
285 );
286
287 check_assist(
288 add_new,
289"struct Foo {<|>}
290
291impl Foo {
292 fn qux(&self) {}
293 fn baz() -> i32 {
294 5
295 }
296}
297",
298"struct Foo {}
299
300impl Foo {
301 fn new() -> Self { Self { } }<|>
302
303 fn qux(&self) {}
304 fn baz() -> i32 {
305 5
306 }
307}
308",
309 );
310
311 // Check visibility of new fn based on struct
312 check_assist(
313 add_new,
314"pub struct Foo {<|>}",
315"pub struct Foo {}
316
317impl Foo {
318 pub fn new() -> Self { Self { } }<|>
319}
320",
321 );
322 check_assist(
323 add_new,
324"pub(crate) struct Foo {<|>}",
325"pub(crate) struct Foo {}
326
327impl Foo {
328 pub(crate) fn new() -> Self { Self { } }<|>
329}
330",
331 );
332 }
333
334 #[test]
335 fn add_new_not_applicable_if_fn_exists() {
336 check_assist_not_applicable(
337 add_new,
338 "
339struct Foo {<|>}
340
341impl Foo {
342 fn new() -> Self {
343 Self
344 }
345}",
346 );
347
348 check_assist_not_applicable(
349 add_new,
350 "
351struct Foo {<|>}
352
353impl Foo {
354 fn New() -> Self {
355 Self
356 }
357}",
358 );
359 }
360
361 #[test]
362 fn add_new_target() {
363 check_assist_target(
364 add_new,
365 "
366struct SomeThingIrrelevant;
367/// Has a lifetime parameter
368struct Foo<'a, T: Foo<'a>> {<|>}
369struct EvenMoreIrrelevant;
370",
371 "/// Has a lifetime parameter
372struct Foo<'a, T: Foo<'a>> {}",
373 );
374 }
375
376 #[test]
377 fn test_unrelated_new() {
378 check_assist(
379 add_new,
380 r##"
381pub struct AstId<N: AstNode> {
382 file_id: HirFileId,
383 file_ast_id: FileAstId<N>,
384}
385
386impl<N: AstNode> AstId<N> {
387 pub fn new(file_id: HirFileId, file_ast_id: FileAstId<N>) -> AstId<N> {
388 AstId { file_id, file_ast_id }
389 }
390}
391
392pub struct Source<T> {
393 pub file_id: HirFileId,<|>
394 pub ast: T,
395}
396
397impl<T> Source<T> {
398 pub fn map<F: FnOnce(T) -> U, U>(self, f: F) -> Source<U> {
399 Source { file_id: self.file_id, ast: f(self.ast) }
400 }
401}
402"##,
403 r##"
404pub struct AstId<N: AstNode> {
405 file_id: HirFileId,
406 file_ast_id: FileAstId<N>,
407}
408
409impl<N: AstNode> AstId<N> {
410 pub fn new(file_id: HirFileId, file_ast_id: FileAstId<N>) -> AstId<N> {
411 AstId { file_id, file_ast_id }
412 }
413}
414
415pub struct Source<T> {
416 pub file_id: HirFileId,
417 pub ast: T,
418}
419
420impl<T> Source<T> {
421 pub fn new(file_id: HirFileId, ast: T) -> Self { Self { file_id, ast } }<|>
422
423 pub fn map<F: FnOnce(T) -> U, U>(self, f: F) -> Source<U> {
424 Source { file_id: self.file_id, ast: f(self.ast) }
425 }
426}
427"##,
428 );
429 }
430}