aboutsummaryrefslogtreecommitdiff
path: root/crates/assists/src/handlers/generate_new.rs
diff options
context:
space:
mode:
authorAleksey Kladov <[email protected]>2020-08-13 16:33:38 +0100
committerAleksey Kladov <[email protected]>2020-08-13 16:33:38 +0100
commitfc34403018079ea053f26d0a31b7517053c7dd8c (patch)
tree500d7c2ec2179309be12a063634cb6a77c9af845 /crates/assists/src/handlers/generate_new.rs
parentae3abd6e575940eb1221acf26c09e96352f052fa (diff)
Rename ra_assists -> assists
Diffstat (limited to 'crates/assists/src/handlers/generate_new.rs')
-rw-r--r--crates/assists/src/handlers/generate_new.rs421
1 files changed, 421 insertions, 0 deletions
diff --git a/crates/assists/src/handlers/generate_new.rs b/crates/assists/src/handlers/generate_new.rs
new file mode 100644
index 000000000..7db10f276
--- /dev/null
+++ b/crates/assists/src/handlers/generate_new.rs
@@ -0,0 +1,421 @@
1use hir::Adt;
2use itertools::Itertools;
3use stdx::format_to;
4use syntax::{
5 ast::{self, AstNode, GenericParamsOwner, NameOwner, StructKind, VisibilityOwner},
6 T,
7};
8
9use crate::{AssistContext, AssistId, AssistKind, Assists};
10
11// Assist: generate_new
12//
13// Adds a new inherent impl for a type.
14//
15// ```
16// struct Ctx<T: Clone> {
17// data: T,<|>
18// }
19// ```
20// ->
21// ```
22// struct Ctx<T: Clone> {
23// data: T,
24// }
25//
26// impl<T: Clone> Ctx<T> {
27// fn $0new(data: T) -> Self { Self { data } }
28// }
29//
30// ```
31pub(crate) fn generate_new(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
32 let strukt = ctx.find_node_at_offset::<ast::Struct>()?;
33
34 // We want to only apply this to non-union structs with named fields
35 let field_list = match strukt.kind() {
36 StructKind::Record(named) => named,
37 _ => return None,
38 };
39
40 // Return early if we've found an existing new fn
41 let impl_def = find_struct_impl(&ctx, &strukt)?;
42
43 let target = strukt.syntax().text_range();
44 acc.add(AssistId("generate_new", AssistKind::Generate), "Generate `new`", target, |builder| {
45 let mut buf = String::with_capacity(512);
46
47 if impl_def.is_some() {
48 buf.push('\n');
49 }
50
51 let vis = strukt.visibility().map_or(String::new(), |v| format!("{} ", v));
52
53 let params = field_list
54 .fields()
55 .filter_map(|f| Some(format!("{}: {}", f.name()?.syntax(), f.ty()?.syntax())))
56 .format(", ");
57 let fields = field_list.fields().filter_map(|f| f.name()).format(", ");
58
59 format_to!(buf, " {}fn new({}) -> Self {{ Self {{ {} }} }}", vis, params, fields);
60
61 let start_offset = impl_def
62 .and_then(|impl_def| {
63 buf.push('\n');
64 let start = impl_def
65 .syntax()
66 .descendants_with_tokens()
67 .find(|t| t.kind() == T!['{'])?
68 .text_range()
69 .end();
70
71 Some(start)
72 })
73 .unwrap_or_else(|| {
74 buf = generate_impl_text(&strukt, &buf);
75 strukt.syntax().text_range().end()
76 });
77
78 match ctx.config.snippet_cap {
79 None => builder.insert(start_offset, buf),
80 Some(cap) => {
81 buf = buf.replace("fn new", "fn $0new");
82 builder.insert_snippet(cap, start_offset, buf);
83 }
84 }
85 })
86}
87
88// Generates the surrounding `impl Type { <code> }` including type and lifetime
89// parameters
90fn generate_impl_text(strukt: &ast::Struct, code: &str) -> String {
91 let type_params = strukt.generic_param_list();
92 let mut buf = String::with_capacity(code.len());
93 buf.push_str("\n\nimpl");
94 if let Some(type_params) = &type_params {
95 format_to!(buf, "{}", type_params.syntax());
96 }
97 buf.push_str(" ");
98 buf.push_str(strukt.name().unwrap().text().as_str());
99 if let Some(type_params) = type_params {
100 let lifetime_params = type_params
101 .lifetime_params()
102 .filter_map(|it| it.lifetime_token())
103 .map(|it| it.text().clone());
104 let type_params =
105 type_params.type_params().filter_map(|it| it.name()).map(|it| it.text().clone());
106 format_to!(buf, "<{}>", lifetime_params.chain(type_params).format(", "))
107 }
108
109 format_to!(buf, " {{\n{}\n}}\n", code);
110
111 buf
112}
113
114// Uses a syntax-driven approach to find any impl blocks for the struct that
115// exist within the module/file
116//
117// Returns `None` if we've found an existing `new` fn
118//
119// FIXME: change the new fn checking to a more semantic approach when that's more
120// viable (e.g. we process proc macros, etc)
121fn find_struct_impl(ctx: &AssistContext, strukt: &ast::Struct) -> Option<Option<ast::Impl>> {
122 let db = ctx.db();
123 let module = strukt.syntax().ancestors().find(|node| {
124 ast::Module::can_cast(node.kind()) || ast::SourceFile::can_cast(node.kind())
125 })?;
126
127 let struct_def = ctx.sema.to_def(strukt)?;
128
129 let block = module.descendants().filter_map(ast::Impl::cast).find_map(|impl_blk| {
130 let blk = ctx.sema.to_def(&impl_blk)?;
131
132 // FIXME: handle e.g. `struct S<T>; impl<U> S<U> {}`
133 // (we currently use the wrong type parameter)
134 // also we wouldn't want to use e.g. `impl S<u32>`
135 let same_ty = match blk.target_ty(db).as_adt() {
136 Some(def) => def == Adt::Struct(struct_def),
137 None => false,
138 };
139 let not_trait_impl = blk.target_trait(db).is_none();
140
141 if !(same_ty && not_trait_impl) {
142 None
143 } else {
144 Some(impl_blk)
145 }
146 });
147
148 if let Some(ref impl_blk) = block {
149 if has_new_fn(impl_blk) {
150 return None;
151 }
152 }
153
154 Some(block)
155}
156
157fn has_new_fn(imp: &ast::Impl) -> bool {
158 if let Some(il) = imp.assoc_item_list() {
159 for item in il.assoc_items() {
160 if let ast::AssocItem::Fn(f) = item {
161 if let Some(name) = f.name() {
162 if name.text().eq_ignore_ascii_case("new") {
163 return true;
164 }
165 }
166 }
167 }
168 }
169
170 false
171}
172
173#[cfg(test)]
174mod tests {
175 use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target};
176
177 use super::*;
178
179 #[test]
180 #[rustfmt::skip]
181 fn test_generate_new() {
182 // Check output of generation
183 check_assist(
184 generate_new,
185"struct Foo {<|>}",
186"struct Foo {}
187
188impl Foo {
189 fn $0new() -> Self { Self { } }
190}
191",
192 );
193 check_assist(
194 generate_new,
195"struct Foo<T: Clone> {<|>}",
196"struct Foo<T: Clone> {}
197
198impl<T: Clone> Foo<T> {
199 fn $0new() -> Self { Self { } }
200}
201",
202 );
203 check_assist(
204 generate_new,
205"struct Foo<'a, T: Foo<'a>> {<|>}",
206"struct Foo<'a, T: Foo<'a>> {}
207
208impl<'a, T: Foo<'a>> Foo<'a, T> {
209 fn $0new() -> Self { Self { } }
210}
211",
212 );
213 check_assist(
214 generate_new,
215"struct Foo { baz: String <|>}",
216"struct Foo { baz: String }
217
218impl Foo {
219 fn $0new(baz: String) -> Self { Self { baz } }
220}
221",
222 );
223 check_assist(
224 generate_new,
225"struct Foo { baz: String, qux: Vec<i32> <|>}",
226"struct Foo { baz: String, qux: Vec<i32> }
227
228impl Foo {
229 fn $0new(baz: String, qux: Vec<i32>) -> Self { Self { baz, qux } }
230}
231",
232 );
233
234 // Check that visibility modifiers don't get brought in for fields
235 check_assist(
236 generate_new,
237"struct Foo { pub baz: String, pub qux: Vec<i32> <|>}",
238"struct Foo { pub baz: String, pub qux: Vec<i32> }
239
240impl Foo {
241 fn $0new(baz: String, qux: Vec<i32>) -> Self { Self { baz, qux } }
242}
243",
244 );
245
246 // Check that it reuses existing impls
247 check_assist(
248 generate_new,
249"struct Foo {<|>}
250
251impl Foo {}
252",
253"struct Foo {}
254
255impl Foo {
256 fn $0new() -> Self { Self { } }
257}
258",
259 );
260 check_assist(
261 generate_new,
262"struct Foo {<|>}
263
264impl Foo {
265 fn qux(&self) {}
266}
267",
268"struct Foo {}
269
270impl Foo {
271 fn $0new() -> Self { Self { } }
272
273 fn qux(&self) {}
274}
275",
276 );
277
278 check_assist(
279 generate_new,
280"struct Foo {<|>}
281
282impl Foo {
283 fn qux(&self) {}
284 fn baz() -> i32 {
285 5
286 }
287}
288",
289"struct Foo {}
290
291impl Foo {
292 fn $0new() -> Self { Self { } }
293
294 fn qux(&self) {}
295 fn baz() -> i32 {
296 5
297 }
298}
299",
300 );
301
302 // Check visibility of new fn based on struct
303 check_assist(
304 generate_new,
305"pub struct Foo {<|>}",
306"pub struct Foo {}
307
308impl Foo {
309 pub fn $0new() -> Self { Self { } }
310}
311",
312 );
313 check_assist(
314 generate_new,
315"pub(crate) struct Foo {<|>}",
316"pub(crate) struct Foo {}
317
318impl Foo {
319 pub(crate) fn $0new() -> Self { Self { } }
320}
321",
322 );
323 }
324
325 #[test]
326 fn generate_new_not_applicable_if_fn_exists() {
327 check_assist_not_applicable(
328 generate_new,
329 "
330struct Foo {<|>}
331
332impl Foo {
333 fn new() -> Self {
334 Self
335 }
336}",
337 );
338
339 check_assist_not_applicable(
340 generate_new,
341 "
342struct Foo {<|>}
343
344impl Foo {
345 fn New() -> Self {
346 Self
347 }
348}",
349 );
350 }
351
352 #[test]
353 fn generate_new_target() {
354 check_assist_target(
355 generate_new,
356 "
357struct SomeThingIrrelevant;
358/// Has a lifetime parameter
359struct Foo<'a, T: Foo<'a>> {<|>}
360struct EvenMoreIrrelevant;
361",
362 "/// Has a lifetime parameter
363struct Foo<'a, T: Foo<'a>> {}",
364 );
365 }
366
367 #[test]
368 fn test_unrelated_new() {
369 check_assist(
370 generate_new,
371 r##"
372pub struct AstId<N: AstNode> {
373 file_id: HirFileId,
374 file_ast_id: FileAstId<N>,
375}
376
377impl<N: AstNode> AstId<N> {
378 pub fn new(file_id: HirFileId, file_ast_id: FileAstId<N>) -> AstId<N> {
379 AstId { file_id, file_ast_id }
380 }
381}
382
383pub struct Source<T> {
384 pub file_id: HirFileId,<|>
385 pub ast: T,
386}
387
388impl<T> Source<T> {
389 pub fn map<F: FnOnce(T) -> U, U>(self, f: F) -> Source<U> {
390 Source { file_id: self.file_id, ast: f(self.ast) }
391 }
392}
393"##,
394 r##"
395pub struct AstId<N: AstNode> {
396 file_id: HirFileId,
397 file_ast_id: FileAstId<N>,
398}
399
400impl<N: AstNode> AstId<N> {
401 pub fn new(file_id: HirFileId, file_ast_id: FileAstId<N>) -> AstId<N> {
402 AstId { file_id, file_ast_id }
403 }
404}
405
406pub struct Source<T> {
407 pub file_id: HirFileId,
408 pub ast: T,
409}
410
411impl<T> Source<T> {
412 pub fn $0new(file_id: HirFileId, ast: T) -> Self { Self { file_id, ast } }
413
414 pub fn map<F: FnOnce(T) -> U, U>(self, f: F) -> Source<U> {
415 Source { file_id: self.file_id, ast: f(self.ast) }
416 }
417}
418"##,
419 );
420 }
421}