diff options
author | Lukas Wirth <[email protected]> | 2020-11-05 22:34:50 +0000 |
---|---|---|
committer | Lukas Wirth <[email protected]> | 2020-11-05 22:41:46 +0000 |
commit | 19443c1fa30e5a360c84f82d0b7aac733ea2e240 (patch) | |
tree | 0e61f9346db4e3bfb7d207b8e6de1fd32ea9c305 | |
parent | 7709b6a2d4b74b5838fbe5b30b6188d6a549b580 (diff) |
Add missing AssocItems in add_custom_impl assist
-rw-r--r-- | crates/assists/src/handlers/add_custom_impl.rs | 161 | ||||
-rw-r--r-- | crates/assists/src/handlers/add_missing_impl_members.rs | 92 | ||||
-rw-r--r-- | crates/assists/src/utils.rs | 92 | ||||
-rw-r--r-- | crates/syntax/src/ast/make.rs | 4 | ||||
-rw-r--r-- | xtask/tests/tidy.rs | 1 |
5 files changed, 240 insertions, 110 deletions
diff --git a/crates/assists/src/handlers/add_custom_impl.rs b/crates/assists/src/handlers/add_custom_impl.rs index 669dd9b21..c13493fd8 100644 --- a/crates/assists/src/handlers/add_custom_impl.rs +++ b/crates/assists/src/handlers/add_custom_impl.rs | |||
@@ -4,13 +4,15 @@ use syntax::{ | |||
4 | ast::{self, make, AstNode}, | 4 | ast::{self, make, AstNode}, |
5 | Direction, SmolStr, | 5 | Direction, SmolStr, |
6 | SyntaxKind::{IDENT, WHITESPACE}, | 6 | SyntaxKind::{IDENT, WHITESPACE}, |
7 | TextRange, TextSize, | 7 | TextSize, |
8 | }; | 8 | }; |
9 | 9 | ||
10 | use crate::{ | 10 | use crate::{ |
11 | assist_config::SnippetCap, | ||
12 | assist_context::{AssistBuilder, AssistContext, Assists}, | 11 | assist_context::{AssistBuilder, AssistContext, Assists}, |
13 | utils::mod_path_to_ast, | 12 | utils::{ |
13 | add_trait_assoc_items_to_impl, filter_assoc_items, mod_path_to_ast, render_snippet, Cursor, | ||
14 | DefaultMethods, | ||
15 | }, | ||
14 | AssistId, AssistKind, | 16 | AssistId, AssistKind, |
15 | }; | 17 | }; |
16 | 18 | ||
@@ -47,11 +49,10 @@ pub(crate) fn add_custom_impl(acc: &mut Assists, ctx: &AssistContext) -> Option< | |||
47 | ctx.token_at_offset().find(|t| t.kind() == IDENT && *t.text() != attr_name)?; | 49 | ctx.token_at_offset().find(|t| t.kind() == IDENT && *t.text() != attr_name)?; |
48 | let trait_path = make::path_unqualified(make::path_segment(make::name_ref(trait_token.text()))); | 50 | let trait_path = make::path_unqualified(make::path_segment(make::name_ref(trait_token.text()))); |
49 | 51 | ||
50 | let annotated = attr.syntax().siblings(Direction::Next).find_map(ast::Name::cast)?; | 52 | let annotated_name = attr.syntax().siblings(Direction::Next).find_map(ast::Name::cast)?; |
51 | let annotated_name = annotated.syntax().text().to_string(); | 53 | let insert_pos = annotated_name.syntax().parent()?.text_range().end(); |
52 | let insert_pos = annotated.syntax().parent()?.text_range().end(); | ||
53 | 54 | ||
54 | let current_module = ctx.sema.scope(annotated.syntax()).module()?; | 55 | let current_module = ctx.sema.scope(annotated_name.syntax()).module()?; |
55 | let current_crate = current_module.krate(); | 56 | let current_crate = current_module.krate(); |
56 | 57 | ||
57 | let found_traits = imports_locator::find_imports(&ctx.sema, current_crate, trait_token.text()) | 58 | let found_traits = imports_locator::find_imports(&ctx.sema, current_crate, trait_token.text()) |
@@ -69,21 +70,22 @@ pub(crate) fn add_custom_impl(acc: &mut Assists, ctx: &AssistContext) -> Option< | |||
69 | }); | 70 | }); |
70 | 71 | ||
71 | let mut no_traits_found = true; | 72 | let mut no_traits_found = true; |
72 | for (trait_path, _trait) in found_traits.inspect(|_| no_traits_found = false) { | 73 | for (trait_path, trait_) in found_traits.inspect(|_| no_traits_found = false) { |
73 | add_assist(acc, ctx.config.snippet_cap, &attr, &trait_path, &annotated_name, insert_pos)?; | 74 | add_assist(acc, ctx, &attr, &trait_path, Some(trait_), &annotated_name, insert_pos)?; |
74 | } | 75 | } |
75 | if no_traits_found { | 76 | if no_traits_found { |
76 | add_assist(acc, ctx.config.snippet_cap, &attr, &trait_path, &annotated_name, insert_pos)?; | 77 | add_assist(acc, ctx, &attr, &trait_path, None, &annotated_name, insert_pos)?; |
77 | } | 78 | } |
78 | Some(()) | 79 | Some(()) |
79 | } | 80 | } |
80 | 81 | ||
81 | fn add_assist( | 82 | fn add_assist( |
82 | acc: &mut Assists, | 83 | acc: &mut Assists, |
83 | snippet_cap: Option<SnippetCap>, | 84 | ctx: &AssistContext, |
84 | attr: &ast::Attr, | 85 | attr: &ast::Attr, |
85 | trait_path: &ast::Path, | 86 | trait_path: &ast::Path, |
86 | annotated_name: &str, | 87 | trait_: Option<hir::Trait>, |
88 | annotated_name: &ast::Name, | ||
87 | insert_pos: TextSize, | 89 | insert_pos: TextSize, |
88 | ) -> Option<()> { | 90 | ) -> Option<()> { |
89 | let target = attr.syntax().text_range(); | 91 | let target = attr.syntax().text_range(); |
@@ -92,25 +94,62 @@ fn add_assist( | |||
92 | let trait_name = trait_path.segment().and_then(|seg| seg.name_ref())?; | 94 | let trait_name = trait_path.segment().and_then(|seg| seg.name_ref())?; |
93 | 95 | ||
94 | acc.add(AssistId("add_custom_impl", AssistKind::Refactor), label, target, |builder| { | 96 | acc.add(AssistId("add_custom_impl", AssistKind::Refactor), label, target, |builder| { |
97 | let impl_def_with_items = | ||
98 | impl_def_from_trait(&ctx.sema, annotated_name, trait_, trait_path); | ||
95 | update_attribute(builder, &input, &trait_name, &attr); | 99 | update_attribute(builder, &input, &trait_name, &attr); |
96 | match snippet_cap { | 100 | match (ctx.config.snippet_cap, impl_def_with_items) { |
97 | Some(cap) => { | 101 | (None, _) => builder.insert( |
102 | insert_pos, | ||
103 | format!("\n\nimpl {} for {} {{\n\n}}", trait_path, annotated_name), | ||
104 | ), | ||
105 | (Some(cap), None) => builder.insert_snippet( | ||
106 | cap, | ||
107 | insert_pos, | ||
108 | format!("\n\nimpl {} for {} {{\n $0\n}}", trait_path, annotated_name), | ||
109 | ), | ||
110 | (Some(cap), Some((impl_def, first_assoc_item))) => { | ||
111 | let mut cursor = Cursor::Before(first_assoc_item.syntax()); | ||
112 | let placeholder; | ||
113 | if let ast::AssocItem::Fn(ref func) = first_assoc_item { | ||
114 | if let Some(m) = func.syntax().descendants().find_map(ast::MacroCall::cast) { | ||
115 | if m.syntax().text() == "todo!()" { | ||
116 | placeholder = m; | ||
117 | cursor = Cursor::Replace(placeholder.syntax()); | ||
118 | } | ||
119 | } | ||
120 | } | ||
121 | |||
98 | builder.insert_snippet( | 122 | builder.insert_snippet( |
99 | cap, | 123 | cap, |
100 | insert_pos, | 124 | insert_pos, |
101 | format!("\n\nimpl {} for {} {{\n $0\n}}", trait_path, annotated_name), | 125 | format!("\n\n{}", render_snippet(cap, impl_def.syntax(), cursor)), |
102 | ); | 126 | ) |
103 | } | ||
104 | None => { | ||
105 | builder.insert( | ||
106 | insert_pos, | ||
107 | format!("\n\nimpl {} for {} {{\n\n}}", trait_path, annotated_name), | ||
108 | ); | ||
109 | } | 127 | } |
110 | } | 128 | }; |
111 | }) | 129 | }) |
112 | } | 130 | } |
113 | 131 | ||
132 | fn impl_def_from_trait( | ||
133 | sema: &hir::Semantics<ide_db::RootDatabase>, | ||
134 | annotated_name: &ast::Name, | ||
135 | trait_: Option<hir::Trait>, | ||
136 | trait_path: &ast::Path, | ||
137 | ) -> Option<(ast::Impl, ast::AssocItem)> { | ||
138 | let trait_ = trait_?; | ||
139 | let target_scope = sema.scope(annotated_name.syntax()); | ||
140 | let trait_items = filter_assoc_items(sema.db, &trait_.items(sema.db), DefaultMethods::No); | ||
141 | if trait_items.is_empty() { | ||
142 | return None; | ||
143 | } | ||
144 | let impl_def = make::impl_trait( | ||
145 | trait_path.clone(), | ||
146 | make::path_unqualified(make::path_segment(make::name_ref(annotated_name.text()))), | ||
147 | ); | ||
148 | let (impl_def, first_assoc_item) = | ||
149 | add_trait_assoc_items_to_impl(sema, trait_items, trait_, impl_def, target_scope); | ||
150 | Some((impl_def, first_assoc_item)) | ||
151 | } | ||
152 | |||
114 | fn update_attribute( | 153 | fn update_attribute( |
115 | builder: &mut AssistBuilder, | 154 | builder: &mut AssistBuilder, |
116 | input: &ast::TokenTree, | 155 | input: &ast::TokenTree, |
@@ -133,13 +172,14 @@ fn update_attribute( | |||
133 | let attr_range = attr.syntax().text_range(); | 172 | let attr_range = attr.syntax().text_range(); |
134 | builder.delete(attr_range); | 173 | builder.delete(attr_range); |
135 | 174 | ||
136 | let line_break_range = attr | 175 | if let Some(line_break_range) = attr |
137 | .syntax() | 176 | .syntax() |
138 | .next_sibling_or_token() | 177 | .next_sibling_or_token() |
139 | .filter(|t| t.kind() == WHITESPACE) | 178 | .filter(|t| t.kind() == WHITESPACE) |
140 | .map(|t| t.text_range()) | 179 | .map(|t| t.text_range()) |
141 | .unwrap_or_else(|| TextRange::new(TextSize::from(0), TextSize::from(0))); | 180 | { |
142 | builder.delete(line_break_range); | 181 | builder.delete(line_break_range); |
182 | } | ||
143 | } | 183 | } |
144 | } | 184 | } |
145 | 185 | ||
@@ -150,12 +190,17 @@ mod tests { | |||
150 | use super::*; | 190 | use super::*; |
151 | 191 | ||
152 | #[test] | 192 | #[test] |
153 | fn add_custom_impl_qualified() { | 193 | fn add_custom_impl_debug() { |
154 | check_assist( | 194 | check_assist( |
155 | add_custom_impl, | 195 | add_custom_impl, |
156 | " | 196 | " |
157 | mod fmt { | 197 | mod fmt { |
158 | pub trait Debug {} | 198 | pub struct Error; |
199 | pub type Result = Result<(), Error>; | ||
200 | pub struct Formatter<'a>; | ||
201 | pub trait Debug { | ||
202 | fn fmt(&self, f: &mut Formatter<'_>) -> Result; | ||
203 | } | ||
159 | } | 204 | } |
160 | 205 | ||
161 | #[derive(Debu<|>g)] | 206 | #[derive(Debu<|>g)] |
@@ -165,7 +210,12 @@ struct Foo { | |||
165 | ", | 210 | ", |
166 | " | 211 | " |
167 | mod fmt { | 212 | mod fmt { |
168 | pub trait Debug {} | 213 | pub struct Error; |
214 | pub type Result = Result<(), Error>; | ||
215 | pub struct Formatter<'a>; | ||
216 | pub trait Debug { | ||
217 | fn fmt(&self, f: &mut Formatter<'_>) -> Result; | ||
218 | } | ||
169 | } | 219 | } |
170 | 220 | ||
171 | struct Foo { | 221 | struct Foo { |
@@ -173,7 +223,58 @@ struct Foo { | |||
173 | } | 223 | } |
174 | 224 | ||
175 | impl fmt::Debug for Foo { | 225 | impl fmt::Debug for Foo { |
176 | $0 | 226 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
227 | ${0:todo!()} | ||
228 | } | ||
229 | } | ||
230 | ", | ||
231 | ) | ||
232 | } | ||
233 | #[test] | ||
234 | fn add_custom_impl_all() { | ||
235 | check_assist( | ||
236 | add_custom_impl, | ||
237 | " | ||
238 | mod foo { | ||
239 | pub trait Bar { | ||
240 | type Qux; | ||
241 | const Baz: usize = 42; | ||
242 | const Fez: usize; | ||
243 | fn foo(); | ||
244 | fn bar() {} | ||
245 | } | ||
246 | } | ||
247 | |||
248 | #[derive(<|>Bar)] | ||
249 | struct Foo { | ||
250 | bar: String, | ||
251 | } | ||
252 | ", | ||
253 | " | ||
254 | mod foo { | ||
255 | pub trait Bar { | ||
256 | type Qux; | ||
257 | const Baz: usize = 42; | ||
258 | const Fez: usize; | ||
259 | fn foo(); | ||
260 | fn bar() {} | ||
261 | } | ||
262 | } | ||
263 | |||
264 | struct Foo { | ||
265 | bar: String, | ||
266 | } | ||
267 | |||
268 | impl foo::Bar for Foo { | ||
269 | $0type Qux; | ||
270 | |||
271 | const Baz: usize = 42; | ||
272 | |||
273 | const Fez: usize; | ||
274 | |||
275 | fn foo() { | ||
276 | todo!() | ||
277 | } | ||
177 | } | 278 | } |
178 | ", | 279 | ", |
179 | ) | 280 | ) |
diff --git a/crates/assists/src/handlers/add_missing_impl_members.rs b/crates/assists/src/handlers/add_missing_impl_members.rs index b82fb30ad..bbb71e261 100644 --- a/crates/assists/src/handlers/add_missing_impl_members.rs +++ b/crates/assists/src/handlers/add_missing_impl_members.rs | |||
@@ -1,27 +1,14 @@ | |||
1 | use hir::HasSource; | 1 | use ide_db::traits::resolve_target_trait; |
2 | use ide_db::traits::{get_missing_assoc_items, resolve_target_trait}; | 2 | use syntax::ast::{self, AstNode}; |
3 | use syntax::{ | ||
4 | ast::{ | ||
5 | self, | ||
6 | edit::{self, AstNodeEdit, IndentLevel}, | ||
7 | make, AstNode, NameOwner, | ||
8 | }, | ||
9 | SmolStr, | ||
10 | }; | ||
11 | 3 | ||
12 | use crate::{ | 4 | use crate::{ |
13 | assist_context::{AssistContext, Assists}, | 5 | assist_context::{AssistContext, Assists}, |
14 | ast_transform::{self, AstTransform, QualifyPaths, SubstituteTypeParams}, | 6 | utils::add_trait_assoc_items_to_impl, |
15 | utils::{render_snippet, Cursor}, | 7 | utils::DefaultMethods, |
8 | utils::{filter_assoc_items, render_snippet, Cursor}, | ||
16 | AssistId, AssistKind, | 9 | AssistId, AssistKind, |
17 | }; | 10 | }; |
18 | 11 | ||
19 | #[derive(PartialEq)] | ||
20 | enum AddMissingImplMembersMode { | ||
21 | DefaultMethodsOnly, | ||
22 | NoDefaultMethods, | ||
23 | } | ||
24 | |||
25 | // Assist: add_impl_missing_members | 12 | // Assist: add_impl_missing_members |
26 | // | 13 | // |
27 | // Adds scaffold for required impl members. | 14 | // Adds scaffold for required impl members. |
@@ -55,7 +42,7 @@ pub(crate) fn add_missing_impl_members(acc: &mut Assists, ctx: &AssistContext) - | |||
55 | add_missing_impl_members_inner( | 42 | add_missing_impl_members_inner( |
56 | acc, | 43 | acc, |
57 | ctx, | 44 | ctx, |
58 | AddMissingImplMembersMode::NoDefaultMethods, | 45 | DefaultMethods::No, |
59 | "add_impl_missing_members", | 46 | "add_impl_missing_members", |
60 | "Implement missing members", | 47 | "Implement missing members", |
61 | ) | 48 | ) |
@@ -97,7 +84,7 @@ pub(crate) fn add_missing_default_members(acc: &mut Assists, ctx: &AssistContext | |||
97 | add_missing_impl_members_inner( | 84 | add_missing_impl_members_inner( |
98 | acc, | 85 | acc, |
99 | ctx, | 86 | ctx, |
100 | AddMissingImplMembersMode::DefaultMethodsOnly, | 87 | DefaultMethods::Only, |
101 | "add_impl_default_members", | 88 | "add_impl_default_members", |
102 | "Implement default members", | 89 | "Implement default members", |
103 | ) | 90 | ) |
@@ -106,7 +93,7 @@ pub(crate) fn add_missing_default_members(acc: &mut Assists, ctx: &AssistContext | |||
106 | fn add_missing_impl_members_inner( | 93 | fn add_missing_impl_members_inner( |
107 | acc: &mut Assists, | 94 | acc: &mut Assists, |
108 | ctx: &AssistContext, | 95 | ctx: &AssistContext, |
109 | mode: AddMissingImplMembersMode, | 96 | mode: DefaultMethods, |
110 | assist_id: &'static str, | 97 | assist_id: &'static str, |
111 | label: &'static str, | 98 | label: &'static str, |
112 | ) -> Option<()> { | 99 | ) -> Option<()> { |
@@ -114,32 +101,11 @@ fn add_missing_impl_members_inner( | |||
114 | let impl_def = ctx.find_node_at_offset::<ast::Impl>()?; | 101 | let impl_def = ctx.find_node_at_offset::<ast::Impl>()?; |
115 | let trait_ = resolve_target_trait(&ctx.sema, &impl_def)?; | 102 | let trait_ = resolve_target_trait(&ctx.sema, &impl_def)?; |
116 | 103 | ||
117 | let def_name = |item: &ast::AssocItem| -> Option<SmolStr> { | 104 | let missing_items = filter_assoc_items( |
118 | match item { | 105 | ctx.db(), |
119 | ast::AssocItem::Fn(def) => def.name(), | 106 | &ide_db::traits::get_missing_assoc_items(&ctx.sema, &impl_def), |
120 | ast::AssocItem::TypeAlias(def) => def.name(), | 107 | mode, |
121 | ast::AssocItem::Const(def) => def.name(), | 108 | ); |
122 | ast::AssocItem::MacroCall(_) => None, | ||
123 | } | ||
124 | .map(|it| it.text().clone()) | ||
125 | }; | ||
126 | |||
127 | let missing_items = get_missing_assoc_items(&ctx.sema, &impl_def) | ||
128 | .iter() | ||
129 | .map(|i| match i { | ||
130 | hir::AssocItem::Function(i) => ast::AssocItem::Fn(i.source(ctx.db()).value), | ||
131 | hir::AssocItem::TypeAlias(i) => ast::AssocItem::TypeAlias(i.source(ctx.db()).value), | ||
132 | hir::AssocItem::Const(i) => ast::AssocItem::Const(i.source(ctx.db()).value), | ||
133 | }) | ||
134 | .filter(|t| def_name(&t).is_some()) | ||
135 | .filter(|t| match t { | ||
136 | ast::AssocItem::Fn(def) => match mode { | ||
137 | AddMissingImplMembersMode::DefaultMethodsOnly => def.body().is_some(), | ||
138 | AddMissingImplMembersMode::NoDefaultMethods => def.body().is_none(), | ||
139 | }, | ||
140 | _ => mode == AddMissingImplMembersMode::NoDefaultMethods, | ||
141 | }) | ||
142 | .collect::<Vec<_>>(); | ||
143 | 109 | ||
144 | if missing_items.is_empty() { | 110 | if missing_items.is_empty() { |
145 | return None; | 111 | return None; |
@@ -147,29 +113,9 @@ fn add_missing_impl_members_inner( | |||
147 | 113 | ||
148 | let target = impl_def.syntax().text_range(); | 114 | let target = impl_def.syntax().text_range(); |
149 | acc.add(AssistId(assist_id, AssistKind::QuickFix), label, target, |builder| { | 115 | acc.add(AssistId(assist_id, AssistKind::QuickFix), label, target, |builder| { |
150 | let impl_item_list = impl_def.assoc_item_list().unwrap_or_else(make::assoc_item_list); | ||
151 | |||
152 | let n_existing_items = impl_item_list.assoc_items().count(); | ||
153 | let source_scope = ctx.sema.scope_for_def(trait_); | ||
154 | let target_scope = ctx.sema.scope(impl_def.syntax()); | 116 | let target_scope = ctx.sema.scope(impl_def.syntax()); |
155 | let ast_transform = QualifyPaths::new(&target_scope, &source_scope) | 117 | let (new_impl_def, first_new_item) = |
156 | .or(SubstituteTypeParams::for_trait_impl(&source_scope, trait_, impl_def.clone())); | 118 | add_trait_assoc_items_to_impl(&ctx.sema, missing_items, trait_, impl_def, target_scope); |
157 | |||
158 | let items = missing_items | ||
159 | .into_iter() | ||
160 | .map(|it| ast_transform::apply(&*ast_transform, it)) | ||
161 | .map(|it| match it { | ||
162 | ast::AssocItem::Fn(def) => ast::AssocItem::Fn(add_body(def)), | ||
163 | ast::AssocItem::TypeAlias(def) => ast::AssocItem::TypeAlias(def.remove_bounds()), | ||
164 | _ => it, | ||
165 | }) | ||
166 | .map(|it| edit::remove_attrs_and_docs(&it)); | ||
167 | |||
168 | let new_impl_item_list = impl_item_list.append_items(items); | ||
169 | let new_impl_def = impl_def.with_assoc_item_list(new_impl_item_list); | ||
170 | let first_new_item = | ||
171 | new_impl_def.assoc_item_list().unwrap().assoc_items().nth(n_existing_items).unwrap(); | ||
172 | |||
173 | match ctx.config.snippet_cap { | 119 | match ctx.config.snippet_cap { |
174 | None => builder.replace(target, new_impl_def.to_string()), | 120 | None => builder.replace(target, new_impl_def.to_string()), |
175 | Some(cap) => { | 121 | Some(cap) => { |
@@ -193,14 +139,6 @@ fn add_missing_impl_members_inner( | |||
193 | }) | 139 | }) |
194 | } | 140 | } |
195 | 141 | ||
196 | fn add_body(fn_def: ast::Fn) -> ast::Fn { | ||
197 | if fn_def.body().is_some() { | ||
198 | return fn_def; | ||
199 | } | ||
200 | let body = make::block_expr(None, Some(make::expr_todo())).indent(IndentLevel(1)); | ||
201 | fn_def.with_body(body) | ||
202 | } | ||
203 | |||
204 | #[cfg(test)] | 142 | #[cfg(test)] |
205 | mod tests { | 143 | mod tests { |
206 | use crate::tests::{check_assist, check_assist_not_applicable}; | 144 | use crate::tests::{check_assist, check_assist_not_applicable}; |
diff --git a/crates/assists/src/utils.rs b/crates/assists/src/utils.rs index 56f925ee6..7071fe96b 100644 --- a/crates/assists/src/utils.rs +++ b/crates/assists/src/utils.rs | |||
@@ -4,17 +4,22 @@ pub(crate) mod import_assets; | |||
4 | 4 | ||
5 | use std::ops; | 5 | use std::ops; |
6 | 6 | ||
7 | use hir::{Crate, Enum, Module, ScopeDef, Semantics, Trait}; | 7 | use hir::{Crate, Enum, HasSource, Module, ScopeDef, Semantics, Trait}; |
8 | use ide_db::RootDatabase; | 8 | use ide_db::RootDatabase; |
9 | use itertools::Itertools; | 9 | use itertools::Itertools; |
10 | use syntax::{ | 10 | use syntax::{ |
11 | ast::{self, make, ArgListOwner}, | 11 | ast::edit::AstNodeEdit, |
12 | ast::NameOwner, | ||
13 | ast::{self, edit, make, ArgListOwner}, | ||
12 | AstNode, Direction, | 14 | AstNode, Direction, |
13 | SyntaxKind::*, | 15 | SyntaxKind::*, |
14 | SyntaxNode, TextSize, T, | 16 | SyntaxNode, TextSize, T, |
15 | }; | 17 | }; |
16 | 18 | ||
17 | use crate::assist_config::SnippetCap; | 19 | use crate::{ |
20 | assist_config::SnippetCap, | ||
21 | ast_transform::{self, AstTransform, QualifyPaths, SubstituteTypeParams}, | ||
22 | }; | ||
18 | 23 | ||
19 | pub use insert_use::MergeBehaviour; | 24 | pub use insert_use::MergeBehaviour; |
20 | pub(crate) use insert_use::{insert_use, ImportScope}; | 25 | pub(crate) use insert_use::{insert_use, ImportScope}; |
@@ -77,6 +82,87 @@ pub fn extract_trivial_expression(block: &ast::BlockExpr) -> Option<ast::Expr> { | |||
77 | None | 82 | None |
78 | } | 83 | } |
79 | 84 | ||
85 | #[derive(Copy, Clone, PartialEq)] | ||
86 | pub enum DefaultMethods { | ||
87 | Only, | ||
88 | No, | ||
89 | } | ||
90 | |||
91 | pub fn filter_assoc_items( | ||
92 | db: &RootDatabase, | ||
93 | items: &[hir::AssocItem], | ||
94 | default_methods: DefaultMethods, | ||
95 | ) -> Vec<ast::AssocItem> { | ||
96 | fn has_def_name(item: &ast::AssocItem) -> bool { | ||
97 | match item { | ||
98 | ast::AssocItem::Fn(def) => def.name(), | ||
99 | ast::AssocItem::TypeAlias(def) => def.name(), | ||
100 | ast::AssocItem::Const(def) => def.name(), | ||
101 | ast::AssocItem::MacroCall(_) => None, | ||
102 | } | ||
103 | .is_some() | ||
104 | }; | ||
105 | |||
106 | items | ||
107 | .iter() | ||
108 | .map(|i| match i { | ||
109 | hir::AssocItem::Function(i) => ast::AssocItem::Fn(i.source(db).value), | ||
110 | hir::AssocItem::TypeAlias(i) => ast::AssocItem::TypeAlias(i.source(db).value), | ||
111 | hir::AssocItem::Const(i) => ast::AssocItem::Const(i.source(db).value), | ||
112 | }) | ||
113 | .filter(has_def_name) | ||
114 | .filter(|it| match it { | ||
115 | ast::AssocItem::Fn(def) => matches!( | ||
116 | (default_methods, def.body()), | ||
117 | (DefaultMethods::Only, Some(_)) | (DefaultMethods::No, None) | ||
118 | ), | ||
119 | _ => default_methods == DefaultMethods::No, | ||
120 | }) | ||
121 | .collect::<Vec<_>>() | ||
122 | } | ||
123 | |||
124 | pub fn add_trait_assoc_items_to_impl( | ||
125 | sema: &hir::Semantics<ide_db::RootDatabase>, | ||
126 | items: Vec<ast::AssocItem>, | ||
127 | trait_: hir::Trait, | ||
128 | impl_def: ast::Impl, | ||
129 | target_scope: hir::SemanticsScope, | ||
130 | ) -> (ast::Impl, ast::AssocItem) { | ||
131 | let impl_item_list = impl_def.assoc_item_list().unwrap_or_else(make::assoc_item_list); | ||
132 | |||
133 | let n_existing_items = impl_item_list.assoc_items().count(); | ||
134 | let source_scope = sema.scope_for_def(trait_); | ||
135 | let ast_transform = QualifyPaths::new(&target_scope, &source_scope) | ||
136 | .or(SubstituteTypeParams::for_trait_impl(&source_scope, trait_, impl_def.clone())); | ||
137 | |||
138 | let items = items | ||
139 | .into_iter() | ||
140 | .map(|it| ast_transform::apply(&*ast_transform, it)) | ||
141 | .map(|it| match it { | ||
142 | ast::AssocItem::Fn(def) => ast::AssocItem::Fn(add_body(def)), | ||
143 | ast::AssocItem::TypeAlias(def) => ast::AssocItem::TypeAlias(def.remove_bounds()), | ||
144 | _ => it, | ||
145 | }) | ||
146 | .map(|it| edit::remove_attrs_and_docs(&it)); | ||
147 | |||
148 | let new_impl_item_list = impl_item_list.append_items(items); | ||
149 | let new_impl_def = impl_def.with_assoc_item_list(new_impl_item_list); | ||
150 | let first_new_item = | ||
151 | new_impl_def.assoc_item_list().unwrap().assoc_items().nth(n_existing_items).unwrap(); | ||
152 | return (new_impl_def, first_new_item); | ||
153 | |||
154 | fn add_body(fn_def: ast::Fn) -> ast::Fn { | ||
155 | match fn_def.body() { | ||
156 | Some(_) => fn_def, | ||
157 | None => { | ||
158 | let body = | ||
159 | make::block_expr(None, Some(make::expr_todo())).indent(edit::IndentLevel(1)); | ||
160 | fn_def.with_body(body) | ||
161 | } | ||
162 | } | ||
163 | } | ||
164 | } | ||
165 | |||
80 | #[derive(Clone, Copy, Debug)] | 166 | #[derive(Clone, Copy, Debug)] |
81 | pub(crate) enum Cursor<'a> { | 167 | pub(crate) enum Cursor<'a> { |
82 | Replace(&'a SyntaxNode), | 168 | Replace(&'a SyntaxNode), |
diff --git a/crates/syntax/src/ast/make.rs b/crates/syntax/src/ast/make.rs index b1578820f..876659a2b 100644 --- a/crates/syntax/src/ast/make.rs +++ b/crates/syntax/src/ast/make.rs | |||
@@ -25,6 +25,10 @@ pub fn assoc_item_list() -> ast::AssocItemList { | |||
25 | ast_from_text("impl C for D {};") | 25 | ast_from_text("impl C for D {};") |
26 | } | 26 | } |
27 | 27 | ||
28 | pub fn impl_trait(trait_: ast::Path, ty: ast::Path) -> ast::Impl { | ||
29 | ast_from_text(&format!("impl {} for {} {{}}", trait_, ty)) | ||
30 | } | ||
31 | |||
28 | pub fn path_segment(name_ref: ast::NameRef) -> ast::PathSegment { | 32 | pub fn path_segment(name_ref: ast::NameRef) -> ast::PathSegment { |
29 | ast_from_text(&format!("use {};", name_ref)) | 33 | ast_from_text(&format!("use {};", name_ref)) |
30 | } | 34 | } |
diff --git a/xtask/tests/tidy.rs b/xtask/tests/tidy.rs index 9de60c76c..4c7db8405 100644 --- a/xtask/tests/tidy.rs +++ b/xtask/tests/tidy.rs | |||
@@ -215,6 +215,7 @@ fn check_todo(path: &Path, text: &str) { | |||
215 | "tests/cli.rs", | 215 | "tests/cli.rs", |
216 | // Some of our assists generate `todo!()`. | 216 | // Some of our assists generate `todo!()`. |
217 | "tests/generated.rs", | 217 | "tests/generated.rs", |
218 | "handlers/add_custom_impl.rs", | ||
218 | "handlers/add_missing_impl_members.rs", | 219 | "handlers/add_missing_impl_members.rs", |
219 | "handlers/add_turbo_fish.rs", | 220 | "handlers/add_turbo_fish.rs", |
220 | "handlers/generate_function.rs", | 221 | "handlers/generate_function.rs", |