Skip to content
This repository was archived by the owner on May 28, 2025. It is now read-only.

Commit 677151e

Browse files
committed
Version 1
1 parent 06b99d4 commit 677151e

File tree

4 files changed

+442
-0
lines changed

4 files changed

+442
-0
lines changed
Lines changed: 343 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,343 @@
1+
use crate::assist_context::{AssistContext, Assists};
2+
use ide_db::{assists::AssistId, SnippetCap};
3+
use syntax::{
4+
ast::{self, HasGenericParams, HasVisibility},
5+
AstNode,
6+
};
7+
8+
// NOTES :
9+
// We generate erroneous code if a function is declared const (E0379)
10+
// This is left to the user to correct as our only option is to remove the
11+
// function completely which we should not be doing.
12+
13+
// Assist: generate_trait_from_impl
14+
//
15+
// Generate trait for an already defined inherent impl and convert impl to a trait impl.
16+
//
17+
// ```
18+
// struct Foo<const N: usize>([i32; N]);
19+
//
20+
// macro_rules! const_maker {
21+
// ($t:ty, $v:tt) => {
22+
// const CONST: $t = $v;
23+
// };
24+
// }
25+
//
26+
// impl<const N: usize> Fo$0o<N> {
27+
// // Used as an associated constant.
28+
// const CONST_ASSOC: usize = N * 4;
29+
//
30+
// fn create() -> Option<()> {
31+
// Some(())
32+
// }
33+
//
34+
// const_maker! {i32, 7}
35+
// }
36+
// ```
37+
// ->
38+
// ```
39+
// struct Foo<const N: usize>([i32; N]);
40+
//
41+
// macro_rules! const_maker {
42+
// ($t:ty, $v:tt) => {
43+
// const CONST: $t = $v;
44+
// };
45+
// }
46+
//
47+
// trait NewTrait<const N: usize> {
48+
// // Used as an associated constant.
49+
// const CONST_ASSOC: usize = N * 4;
50+
//
51+
// fn create() -> Option<()>;
52+
//
53+
// const_maker! {i32, 7}
54+
// }
55+
//
56+
// impl<const N: usize> NewTrait<N> for Foo<N> {
57+
// // Used as an associated constant.
58+
// const CONST_ASSOC: usize = N * 4;
59+
//
60+
// fn create() -> Option<()> {
61+
// Some(())
62+
// }
63+
//
64+
// const_maker! {i32, 7}
65+
// }
66+
// ```
67+
pub(crate) fn generate_trait_from_impl(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
68+
// Get AST Node
69+
let impl_ast = ctx.find_node_at_offset::<ast::Impl>()?;
70+
71+
// If impl is not inherent then we don't really need to go any further.
72+
if impl_ast.for_token().is_some() {
73+
return None;
74+
}
75+
76+
let assoc_items = impl_ast.assoc_item_list();
77+
if assoc_items.is_none() {
78+
// Also do not do anything if no assoc item is there.
79+
return None;
80+
}
81+
82+
let assoc_items = assoc_items.unwrap();
83+
let first_element = assoc_items.assoc_items().next();
84+
if first_element.is_none() {
85+
// No reason for an assist.
86+
return None;
87+
}
88+
89+
acc.add(
90+
AssistId("generate_trait_from_impl", ide_db::assists::AssistKind::Generate),
91+
"Generate trait from impl".to_owned(),
92+
impl_ast.syntax().text_range(),
93+
|builder| {
94+
let trait_items = assoc_items.clone_for_update();
95+
let impl_items = assoc_items.clone_for_update();
96+
97+
trait_items.assoc_items().for_each(|item| {
98+
strip_body(&item);
99+
remove_items_visibility(&item);
100+
});
101+
102+
syntax::ted::replace(assoc_items.clone_for_update().syntax(), impl_items.syntax());
103+
104+
impl_items.assoc_items().for_each(|item| {
105+
remove_items_visibility(&item);
106+
});
107+
108+
let trait_ast = ast::make::trait_(
109+
false,
110+
"NewTrait".to_string(),
111+
HasGenericParams::generic_param_list(&impl_ast),
112+
HasGenericParams::where_clause(&impl_ast),
113+
trait_items,
114+
);
115+
116+
// Change `impl Foo` to `impl NewTrait for Foo`
117+
// First find the PATH_TYPE which is what Foo is.
118+
let impl_name = impl_ast.self_ty().unwrap();
119+
let trait_name = if let Some(genpars) = impl_ast.generic_param_list() {
120+
format!("NewTrait{}", genpars.to_generic_args())
121+
} else {
122+
format!("NewTrait")
123+
};
124+
125+
// // Then replace
126+
builder.replace(
127+
impl_name.clone().syntax().text_range(),
128+
format!("{} for {}", trait_name, impl_name.to_string()),
129+
);
130+
131+
builder.replace(
132+
impl_ast.assoc_item_list().unwrap().syntax().text_range(),
133+
impl_items.to_string(),
134+
);
135+
136+
// Insert trait before TraitImpl
137+
builder.insert_snippet(
138+
SnippetCap::new(true).unwrap(),
139+
impl_ast.syntax().text_range().start(),
140+
format!("{}\n\n", trait_ast.to_string()),
141+
);
142+
},
143+
);
144+
145+
Some(())
146+
}
147+
148+
/// `E0449` Trait items always share the visibility of their trait
149+
fn remove_items_visibility(item: &ast::AssocItem) {
150+
match item {
151+
ast::AssocItem::Const(c) => {
152+
if let Some(vis) = c.visibility() {
153+
syntax::ted::remove(vis.syntax());
154+
}
155+
}
156+
ast::AssocItem::Fn(f) => {
157+
if let Some(vis) = f.visibility() {
158+
syntax::ted::remove(vis.syntax());
159+
}
160+
}
161+
ast::AssocItem::TypeAlias(t) => {
162+
if let Some(vis) = t.visibility() {
163+
syntax::ted::remove(vis.syntax());
164+
}
165+
}
166+
_ => (),
167+
}
168+
}
169+
170+
fn strip_body(item: &ast::AssocItem) {
171+
match item {
172+
ast::AssocItem::Fn(f) => {
173+
if let Some(body) = f.body() {
174+
// In constrast to function bodies, we want to see no ws before a semicolon.
175+
// So let's remove them if we see any.
176+
if let Some(prev) = body.syntax().prev_sibling_or_token() {
177+
if prev.kind() == syntax::SyntaxKind::WHITESPACE {
178+
syntax::ted::remove(prev);
179+
}
180+
}
181+
182+
syntax::ted::replace(body.syntax(), ast::make::tokens::semicolon());
183+
}
184+
}
185+
_ => (),
186+
};
187+
}
188+
189+
#[cfg(test)]
190+
mod tests {
191+
use super::*;
192+
use crate::tests::{check_assist, check_assist_not_applicable};
193+
194+
#[test]
195+
fn test_assoc_item_fn() {
196+
check_assist(
197+
generate_trait_from_impl,
198+
r#"
199+
struct Foo(f64);
200+
201+
impl F$0oo {
202+
fn add(&mut self, x: f64) {
203+
self.0 += x;
204+
}
205+
}"#,
206+
r#"
207+
struct Foo(f64);
208+
209+
trait NewTrait {
210+
fn add(&mut self, x: f64);
211+
}
212+
213+
impl NewTrait for Foo {
214+
fn add(&mut self, x: f64) {
215+
self.0 += x;
216+
}
217+
}"#,
218+
)
219+
}
220+
221+
#[test]
222+
fn test_assoc_item_macro() {
223+
check_assist(
224+
generate_trait_from_impl,
225+
r#"
226+
struct Foo;
227+
228+
macro_rules! const_maker {
229+
($t:ty, $v:tt) => {
230+
const CONST: $t = $v;
231+
};
232+
}
233+
234+
impl F$0oo {
235+
const_maker! {i32, 7}
236+
}"#,
237+
r#"
238+
struct Foo;
239+
240+
macro_rules! const_maker {
241+
($t:ty, $v:tt) => {
242+
const CONST: $t = $v;
243+
};
244+
}
245+
246+
trait NewTrait {
247+
const_maker! {i32, 7}
248+
}
249+
250+
impl NewTrait for Foo {
251+
const_maker! {i32, 7}
252+
}"#,
253+
)
254+
}
255+
256+
#[test]
257+
fn test_assoc_item_const() {
258+
check_assist(
259+
generate_trait_from_impl,
260+
r#"
261+
struct Foo;
262+
263+
impl F$0oo {
264+
const ABC: i32 = 3;
265+
}"#,
266+
r#"
267+
struct Foo;
268+
269+
trait NewTrait {
270+
const ABC: i32 = 3;
271+
}
272+
273+
impl NewTrait for Foo {
274+
const ABC: i32 = 3;
275+
}"#,
276+
)
277+
}
278+
279+
#[test]
280+
fn test_impl_with_generics() {
281+
check_assist(
282+
generate_trait_from_impl,
283+
r#"
284+
struct Foo<const N: usize>([i32; N]);
285+
286+
impl<const N: usize> F$0oo<N> {
287+
// Used as an associated constant.
288+
const CONST: usize = N * 4;
289+
}
290+
"#,
291+
r#"
292+
struct Foo<const N: usize>([i32; N]);
293+
294+
trait NewTrait<const N: usize> {
295+
// Used as an associated constant.
296+
const CONST: usize = N * 4;
297+
}
298+
299+
impl<const N: usize> NewTrait<N> for Foo<N> {
300+
// Used as an associated constant.
301+
const CONST: usize = N * 4;
302+
}
303+
"#,
304+
)
305+
}
306+
307+
#[test]
308+
fn test_e0449_avoided() {
309+
check_assist(
310+
generate_trait_from_impl,
311+
r#"
312+
struct Foo;
313+
314+
impl F$0oo {
315+
pub fn a_func() -> Option<()> {
316+
Some(())
317+
}
318+
}"#,
319+
r#"
320+
struct Foo;
321+
322+
trait NewTrait {
323+
fn a_func() -> Option<()>;
324+
}
325+
326+
impl NewTrait for Foo {
327+
fn a_func() -> Option<()> {
328+
Some(())
329+
}
330+
}"#,
331+
)
332+
}
333+
334+
#[test]
335+
fn test_empty_inherent_impl() {
336+
check_assist_not_applicable(
337+
generate_trait_from_impl,
338+
r#"
339+
impl Emp$0tyImpl{}
340+
"#,
341+
)
342+
}
343+
}

crates/ide-assists/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,7 @@ mod handlers {
160160
mod generate_new;
161161
mod generate_setter;
162162
mod generate_delegate_methods;
163+
mod generate_trait_from_impl;
163164
mod add_return_type;
164165
mod inline_call;
165166
mod inline_const_as_literal;
@@ -266,6 +267,7 @@ mod handlers {
266267
generate_impl::generate_trait_impl,
267268
generate_is_empty_from_len::generate_is_empty_from_len,
268269
generate_new::generate_new,
270+
generate_trait_from_impl::generate_trait_from_impl,
269271
inline_call::inline_call,
270272
inline_call::inline_into_callers,
271273
inline_const_as_literal::inline_const_as_literal,

0 commit comments

Comments
 (0)