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