ide_assists/handlers/
move_const_to_impl.rs1use hir::{AsAssocItem, AssocItemContainer, FileRange, HasSource};
2use ide_db::{assists::AssistId, defs::Definition, search::SearchScope};
3use syntax::{
4 SyntaxKind,
5 ast::{self, AstNode, edit::IndentLevel, edit_in_place::Indent},
6};
7
8use crate::assist_context::{AssistContext, Assists};
9
10pub(crate) fn move_const_to_impl(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
43 let db = ctx.db();
44 let const_: ast::Const = ctx.find_node_at_offset()?;
45 if let Some(body) = const_.body()
47 && body.syntax().text_range().contains(ctx.offset())
48 {
49 return None;
50 }
51
52 let parent_fn = const_.syntax().ancestors().find_map(ast::Fn::cast)?;
53
54 let AssocItemContainer::Impl(impl_) =
58 ctx.sema.to_def(&parent_fn)?.as_assoc_item(db)?.container(db)
59 else {
60 return None;
61 };
62 if impl_.trait_(db).is_some() {
63 return None;
64 }
65
66 let def = ctx.sema.to_def(&const_)?;
67 let name = def.name(db)?;
68 let items = impl_.source(db)?.value.assoc_item_list()?;
69
70 let ty = impl_.self_ty(db);
71 if ty
73 .iterate_assoc_items(db, |assoc| {
74 assoc.name(db).filter(|it| it == &name)
79 })
80 .is_some()
81 {
82 return None;
83 }
84
85 acc.add(
86 AssistId::refactor_rewrite("move_const_to_impl"),
87 "Move const to impl block",
88 const_.syntax().text_range(),
89 |builder| {
90 let usages = Definition::Const(def)
91 .usages(&ctx.sema)
92 .in_scope(&SearchScope::file_range(FileRange {
93 file_id: ctx.file_id(),
94 range: parent_fn.syntax().text_range(),
95 }))
96 .all();
97
98 let range_to_delete = match const_.syntax().next_sibling_or_token() {
99 Some(s) if matches!(s.kind(), SyntaxKind::WHITESPACE) => {
100 const_.syntax().text_range().cover(s.text_range())
102 }
103 _ => const_.syntax().text_range(),
104 };
105 builder.delete(range_to_delete);
106
107 let usages = usages.iter().flat_map(|(file_id, usages)| {
108 let edition = file_id.edition(ctx.db());
109 usages.iter().map(move |usage| (edition, usage.range))
110 });
111 for (edition, range) in usages {
112 let const_ref = format!("Self::{}", name.display(ctx.db(), edition));
113 builder.replace(range, const_ref);
114 }
115
116 let last_const =
120 items.assoc_items().take_while(|it| matches!(it, ast::AssocItem::Const(_))).last();
121 let insert_offset = match &last_const {
122 Some(it) => it.syntax().text_range().end(),
123 None => match items.l_curly_token() {
124 Some(l_curly) => l_curly.text_range().end(),
125 None => items.syntax().text_range().start(),
128 },
129 };
130
131 let fixup = if last_const.is_none() { "\n" } else { "" };
136 let indent = IndentLevel::from_node(parent_fn.syntax());
137
138 let const_ = const_.clone_for_update();
139 const_.reindent_to(indent);
140 builder.insert(insert_offset, format!("\n{indent}{const_}{fixup}"));
141 },
142 )
143}
144
145#[cfg(test)]
146mod tests {
147 use crate::tests::{check_assist, check_assist_not_applicable};
148
149 use super::*;
150
151 #[test]
152 fn not_applicable_to_top_level_const() {
153 check_assist_not_applicable(
154 move_const_to_impl,
155 r#"
156const C$0: () = ();
157"#,
158 );
159 }
160
161 #[test]
162 fn not_applicable_to_free_fn() {
163 check_assist_not_applicable(
164 move_const_to_impl,
165 r#"
166fn f() {
167 const C$0: () = ();
168}
169"#,
170 );
171 }
172
173 #[test]
174 fn not_applicable_when_at_const_body() {
175 check_assist_not_applicable(
176 move_const_to_impl,
177 r#"
178struct S;
179impl S {
180 fn f() {
181 const C: () = ($0);
182 }
183}
184 "#,
185 );
186 }
187
188 #[test]
189 fn not_applicable_when_inside_const_body_block() {
190 check_assist_not_applicable(
191 move_const_to_impl,
192 r#"
193struct S;
194impl S {
195 fn f() {
196 const C: () = {
197 ($0)
198 };
199 }
200}
201 "#,
202 );
203 }
204
205 #[test]
206 fn not_applicable_to_trait_impl_fn() {
207 check_assist_not_applicable(
208 move_const_to_impl,
209 r#"
210trait Trait {
211 fn f();
212}
213impl Trait for () {
214 fn f() {
215 const C$0: () = ();
216 }
217}
218"#,
219 );
220 }
221
222 #[test]
223 fn not_applicable_to_non_assoc_fn_inside_impl() {
224 check_assist_not_applicable(
225 move_const_to_impl,
226 r#"
227struct S;
228impl S {
229 fn f() {
230 fn g() {
231 const C$0: () = ();
232 }
233 }
234}
235"#,
236 );
237 }
238
239 #[test]
240 fn not_applicable_when_const_with_same_name_exists() {
241 check_assist_not_applicable(
242 move_const_to_impl,
243 r#"
244struct S;
245impl S {
246 const C: usize = 42;
247 fn f() {
248 const C$0: () = ();
249 }
250"#,
251 );
252
253 check_assist_not_applicable(
254 move_const_to_impl,
255 r#"
256struct S;
257impl S {
258 const C: usize = 42;
259}
260impl S {
261 fn f() {
262 const C$0: () = ();
263 }
264"#,
265 );
266 }
267
268 #[test]
269 fn move_const_simple_body() {
270 check_assist(
271 move_const_to_impl,
272 r#"
273struct S;
274impl S {
275 fn f() -> usize {
276 /// doc comment
277 const C$0: usize = 42;
278
279 C * C
280 }
281}
282"#,
283 r#"
284struct S;
285impl S {
286 /// doc comment
287 const C: usize = 42;
288
289 fn f() -> usize {
290 Self::C * Self::C
291 }
292}
293"#,
294 );
295 }
296
297 #[test]
298 fn move_const_simple_body_existing_const() {
299 check_assist(
300 move_const_to_impl,
301 r#"
302struct S;
303impl S {
304 const X: () = ();
305 const Y: () = ();
306
307 fn f() -> usize {
308 /// doc comment
309 const C$0: usize = 42;
310
311 C * C
312 }
313}
314"#,
315 r#"
316struct S;
317impl S {
318 const X: () = ();
319 const Y: () = ();
320 /// doc comment
321 const C: usize = 42;
322
323 fn f() -> usize {
324 Self::C * Self::C
325 }
326}
327"#,
328 );
329 }
330
331 #[test]
332 fn move_const_block_body() {
333 check_assist(
334 move_const_to_impl,
335 r#"
336struct S;
337impl S {
338 fn f() -> usize {
339 /// doc comment
340 const C$0: usize = {
341 let a = 3;
342 let b = 4;
343 a * b
344 };
345
346 C * C
347 }
348}
349"#,
350 r#"
351struct S;
352impl S {
353 /// doc comment
354 const C: usize = {
355 let a = 3;
356 let b = 4;
357 a * b
358 };
359
360 fn f() -> usize {
361 Self::C * Self::C
362 }
363}
364"#,
365 );
366 }
367
368 #[test]
369 fn correct_indent_when_nested() {
370 check_assist(
371 move_const_to_impl,
372 r#"
373fn main() {
374 struct S;
375 impl S {
376 fn f() -> usize {
377 /// doc comment
378 const C$0: usize = 42;
379
380 C * C
381 }
382 }
383}
384"#,
385 r#"
386fn main() {
387 struct S;
388 impl S {
389 /// doc comment
390 const C: usize = 42;
391
392 fn f() -> usize {
393 Self::C * Self::C
394 }
395 }
396}
397"#,
398 )
399 }
400
401 #[test]
402 fn move_const_in_nested_scope_with_same_name_in_other_scope() {
403 check_assist(
404 move_const_to_impl,
405 r#"
406struct S;
407impl S {
408 fn f() -> usize {
409 const C: &str = "outer";
410
411 let n = {
412 /// doc comment
413 const C$0: usize = 42;
414
415 let m = {
416 const C: &str = "inner";
417 C.len()
418 };
419
420 C * m
421 };
422
423 n + C.len()
424 }
425}
426"#,
427 r#"
428struct S;
429impl S {
430 /// doc comment
431 const C: usize = 42;
432
433 fn f() -> usize {
434 const C: &str = "outer";
435
436 let n = {
437 let m = {
438 const C: &str = "inner";
439 C.len()
440 };
441
442 Self::C * m
443 };
444
445 n + C.len()
446 }
447}
448"#,
449 );
450 }
451}