4 self, AstNode, NameOwner, StructKind, TypeAscriptionOwner, TypeParamsOwner, VisibilityOwner,
8 use stdx::{format_to, SepBy};
10 use crate::{AssistContext, AssistId, AssistKind, 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();
46 AssistId("add_new", AssistKind::Refactor),
47 "Add default constructor",
50 let mut buf = String::with_capacity(512);
52 if impl_def.is_some() {
56 let vis = strukt.visibility().map_or(String::new(), |v| format!("{} ", v));
58 let params = field_list
61 Some(format!("{}: {}", f.name()?.syntax(), f.ascribed_type()?.syntax()))
64 let fields = field_list.fields().filter_map(|f| f.name()).sep_by(", ");
66 format_to!(buf, " {}fn new({}) -> Self {{ Self {{ {} }} }}", vis, params, fields);
68 let start_offset = impl_def
69 .and_then(|impl_def| {
73 .descendants_with_tokens()
74 .find(|t| t.kind() == T!['{'])?
81 buf = generate_impl_text(&strukt, &buf);
82 strukt.syntax().text_range().end()
85 match ctx.config.snippet_cap {
86 None => builder.insert(start_offset, buf),
88 buf = buf.replace("fn new", "fn $0new");
89 builder.insert_snippet(cap, start_offset, buf);
96 // Generates the surrounding `impl Type { <code> }` including type and lifetime
98 fn generate_impl_text(strukt: &ast::StructDef, code: &str) -> String {
99 let type_params = strukt.type_param_list();
100 let mut buf = String::with_capacity(code.len());
101 buf.push_str("\n\nimpl");
102 if let Some(type_params) = &type_params {
103 format_to!(buf, "{}", type_params.syntax());
106 buf.push_str(strukt.name().unwrap().text().as_str());
107 if let Some(type_params) = type_params {
108 let lifetime_params = type_params
110 .filter_map(|it| it.lifetime_token())
111 .map(|it| it.text().clone());
113 type_params.type_params().filter_map(|it| it.name()).map(|it| it.text().clone());
114 format_to!(buf, "<{}>", lifetime_params.chain(type_params).sep_by(", "))
117 format_to!(buf, " {{\n{}\n}}\n", code);
122 // Uses a syntax-driven approach to find any impl blocks for the struct that
123 // exist within the module/file
125 // Returns `None` if we've found an existing `new` fn
127 // FIXME: change the new fn checking to a more semantic approach when that's more
128 // viable (e.g. we process proc macros, etc)
129 fn find_struct_impl(ctx: &AssistContext, strukt: &ast::StructDef) -> Option<Option<ast::ImplDef>> {
131 let module = strukt.syntax().ancestors().find(|node| {
132 ast::Module::can_cast(node.kind()) || ast::SourceFile::can_cast(node.kind())
135 let struct_def = ctx.sema.to_def(strukt)?;
137 let block = module.descendants().filter_map(ast::ImplDef::cast).find_map(|impl_blk| {
138 let blk = ctx.sema.to_def(&impl_blk)?;
140 // FIXME: handle e.g. `struct S<T>; impl<U> S<U> {}`
141 // (we currently use the wrong type parameter)
142 // also we wouldn't want to use e.g. `impl S<u32>`
143 let same_ty = match blk.target_ty(db).as_adt() {
144 Some(def) => def == Adt::Struct(struct_def),
147 let not_trait_impl = blk.target_trait(db).is_none();
149 if !(same_ty && not_trait_impl) {
156 if let Some(ref impl_blk) = block {
157 if has_new_fn(impl_blk) {
165 fn has_new_fn(imp: &ast::ImplDef) -> bool {
166 if let Some(il) = imp.item_list() {
167 for item in il.assoc_items() {
168 if let ast::AssocItem::FnDef(f) = item {
169 if let Some(name) = f.name() {
170 if name.text().eq_ignore_ascii_case("new") {
183 use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target};
190 // Check output of generation
197 fn $0new() -> Self { Self { } }
203 "struct Foo<T: Clone> {<|>}",
204 "struct Foo<T: Clone> {}
206 impl<T: Clone> Foo<T> {
207 fn $0new() -> Self { Self { } }
213 "struct Foo<'a, T: Foo<'a>> {<|>}",
214 "struct Foo<'a, T: Foo<'a>> {}
216 impl<'a, T: Foo<'a>> Foo<'a, T> {
217 fn $0new() -> Self { Self { } }
223 "struct Foo { baz: String <|>}",
224 "struct Foo { baz: String }
227 fn $0new(baz: String) -> Self { Self { baz } }
233 "struct Foo { baz: String, qux: Vec<i32> <|>}",
234 "struct Foo { baz: String, qux: Vec<i32> }
237 fn $0new(baz: String, qux: Vec<i32>) -> Self { Self { baz, qux } }
242 // Check that visibility modifiers don't get brought in for fields
245 "struct Foo { pub baz: String, pub qux: Vec<i32> <|>}",
246 "struct Foo { pub baz: String, pub qux: Vec<i32> }
249 fn $0new(baz: String, qux: Vec<i32>) -> Self { Self { baz, qux } }
254 // Check that it reuses existing impls
264 fn $0new() -> Self { Self { } }
279 fn $0new() -> Self { Self { } }
300 fn $0new() -> Self { Self { } }
310 // Check visibility of new fn based on struct
313 "pub struct Foo {<|>}",
317 pub fn $0new() -> Self { Self { } }
323 "pub(crate) struct Foo {<|>}",
324 "pub(crate) struct Foo {}
327 pub(crate) fn $0new() -> Self { Self { } }
334 fn add_new_not_applicable_if_fn_exists() {
335 check_assist_not_applicable(
347 check_assist_not_applicable(
361 fn add_new_target() {
365 struct SomeThingIrrelevant;
366 /// Has a lifetime parameter
367 struct Foo<'a, T: Foo<'a>> {<|>}
368 struct EvenMoreIrrelevant;
370 "/// Has a lifetime parameter
371 struct Foo<'a, T: Foo<'a>> {}",
376 fn test_unrelated_new() {
380 pub struct AstId<N: AstNode> {
382 file_ast_id: FileAstId<N>,
385 impl<N: AstNode> AstId<N> {
386 pub fn new(file_id: HirFileId, file_ast_id: FileAstId<N>) -> AstId<N> {
387 AstId { file_id, file_ast_id }
391 pub struct Source<T> {
392 pub file_id: HirFileId,<|>
397 pub fn map<F: FnOnce(T) -> U, U>(self, f: F) -> Source<U> {
398 Source { file_id: self.file_id, ast: f(self.ast) }
403 pub struct AstId<N: AstNode> {
405 file_ast_id: FileAstId<N>,
408 impl<N: AstNode> AstId<N> {
409 pub fn new(file_id: HirFileId, file_ast_id: FileAstId<N>) -> AstId<N> {
410 AstId { file_id, file_ast_id }
414 pub struct Source<T> {
415 pub file_id: HirFileId,
420 pub fn $0new(file_id: HirFileId, ast: T) -> Self { Self { file_id, ast } }
422 pub fn map<F: FnOnce(T) -> U, U>(self, f: F) -> Source<U> {
423 Source { file_id: self.file_id, ast: f(self.ast) }