4 self, AstNode, NameOwner, StructKind, TypeAscriptionOwner, TypeParamsOwner, VisibilityOwner,
8 use stdx::{format_to, SepBy};
10 use crate::{AssistContext, AssistId, Assists};
14 // Adds a new inherent impl for a type.
17 // struct Ctx<T: Clone> {
23 // struct Ctx<T: Clone> {
27 // impl<T: Clone> Ctx<T> {
28 // fn $0new(data: T) -> Self { Self { data } }
32 pub(crate) fn add_new(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
33 let strukt = ctx.find_node_at_offset::<ast::StructDef>()?;
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,
41 // Return early if we've found an existing new fn
42 let impl_def = find_struct_impl(&ctx, &strukt)?;
44 let target = strukt.syntax().text_range();
45 acc.add(AssistId("add_new"), "Add default constructor", target, |builder| {
46 let mut buf = String::with_capacity(512);
48 if impl_def.is_some() {
52 let vis = strukt.visibility().map_or(String::new(), |v| format!("{} ", v));
54 let params = field_list
57 Some(format!("{}: {}", f.name()?.syntax(), f.ascribed_type()?.syntax()))
60 let fields = field_list.fields().filter_map(|f| f.name()).sep_by(", ");
62 format_to!(buf, " {}fn new({}) -> Self {{ Self {{ {} }} }}", vis, params, fields);
64 let start_offset = impl_def
65 .and_then(|impl_def| {
69 .descendants_with_tokens()
70 .find(|t| t.kind() == T!['{'])?
77 buf = generate_impl_text(&strukt, &buf);
78 strukt.syntax().text_range().end()
81 match ctx.config.snippet_cap {
82 None => builder.insert(start_offset, buf),
84 buf = buf.replace("fn new", "fn $0new");
85 builder.insert_snippet(cap, start_offset, buf);
91 // Generates the surrounding `impl Type { <code> }` including type and lifetime
93 fn 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());
101 buf.push_str(strukt.name().unwrap().text().as_str());
102 if let Some(type_params) = type_params {
103 let lifetime_params = type_params
105 .filter_map(|it| it.lifetime_token())
106 .map(|it| it.text().clone());
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(", "))
112 format_to!(buf, " {{\n{}\n}}\n", code);
117 // Uses a syntax-driven approach to find any impl blocks for the struct that
118 // exist within the module/file
120 // Returns `None` if we've found an existing `new` fn
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)
124 fn find_struct_impl(ctx: &AssistContext, strukt: &ast::StructDef) -> Option<Option<ast::ImplDef>> {
126 let module = strukt.syntax().ancestors().find(|node| {
127 ast::Module::can_cast(node.kind()) || ast::SourceFile::can_cast(node.kind())
130 let struct_def = ctx.sema.to_def(strukt)?;
132 let block = module.descendants().filter_map(ast::ImplDef::cast).find_map(|impl_blk| {
133 let blk = ctx.sema.to_def(&impl_blk)?;
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),
142 let not_trait_impl = blk.target_trait(db).is_none();
144 if !(same_ty && not_trait_impl) {
151 if let Some(ref impl_blk) = block {
152 if has_new_fn(impl_blk) {
160 fn 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") {
178 use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target};
185 // Check output of generation
192 fn $0new() -> Self { Self { } }
198 "struct Foo<T: Clone> {<|>}",
199 "struct Foo<T: Clone> {}
201 impl<T: Clone> Foo<T> {
202 fn $0new() -> Self { Self { } }
208 "struct Foo<'a, T: Foo<'a>> {<|>}",
209 "struct Foo<'a, T: Foo<'a>> {}
211 impl<'a, T: Foo<'a>> Foo<'a, T> {
212 fn $0new() -> Self { Self { } }
218 "struct Foo { baz: String <|>}",
219 "struct Foo { baz: String }
222 fn $0new(baz: String) -> Self { Self { baz } }
228 "struct Foo { baz: String, qux: Vec<i32> <|>}",
229 "struct Foo { baz: String, qux: Vec<i32> }
232 fn $0new(baz: String, qux: Vec<i32>) -> Self { Self { baz, qux } }
237 // Check that visibility modifiers don't get brought in for fields
240 "struct Foo { pub baz: String, pub qux: Vec<i32> <|>}",
241 "struct Foo { pub baz: String, pub qux: Vec<i32> }
244 fn $0new(baz: String, qux: Vec<i32>) -> Self { Self { baz, qux } }
249 // Check that it reuses existing impls
259 fn $0new() -> Self { Self { } }
274 fn $0new() -> Self { Self { } }
295 fn $0new() -> Self { Self { } }
305 // Check visibility of new fn based on struct
308 "pub struct Foo {<|>}",
312 pub fn $0new() -> Self { Self { } }
318 "pub(crate) struct Foo {<|>}",
319 "pub(crate) struct Foo {}
322 pub(crate) fn $0new() -> Self { Self { } }
329 fn add_new_not_applicable_if_fn_exists() {
330 check_assist_not_applicable(
342 check_assist_not_applicable(
356 fn add_new_target() {
360 struct SomeThingIrrelevant;
361 /// Has a lifetime parameter
362 struct Foo<'a, T: Foo<'a>> {<|>}
363 struct EvenMoreIrrelevant;
365 "/// Has a lifetime parameter
366 struct Foo<'a, T: Foo<'a>> {}",
371 fn test_unrelated_new() {
375 pub struct AstId<N: AstNode> {
377 file_ast_id: FileAstId<N>,
380 impl<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 }
386 pub struct Source<T> {
387 pub file_id: HirFileId,<|>
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) }
398 pub struct AstId<N: AstNode> {
400 file_ast_id: FileAstId<N>,
403 impl<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 }
409 pub struct Source<T> {
410 pub file_id: HirFileId,
415 pub fn $0new(file_id: HirFileId, ast: T) -> Self { Self { file_id, ast } }
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) }