ide_assists/handlers/
convert_into_to_from.rs1use ide_db::{
2 famous_defs::FamousDefs, helpers::mod_path_to_ast_with_factory, traits::resolve_target_trait,
3};
4use syntax::ast::{self, AstNode, HasGenericArgs, HasName};
5
6use crate::{AssistContext, AssistId, Assists};
7
8pub(crate) fn convert_into_to_from(acc: &mut Assists, ctx: &AssistContext<'_, '_>) -> Option<()> {
37 let impl_ = ctx.find_node_at_offset::<ast::Impl>()?;
38 let src_type = impl_.self_ty()?;
39 let ast_trait = impl_.trait_()?;
40
41 let module = ctx.sema.scope(impl_.syntax())?.module();
42
43 let trait_ = resolve_target_trait(&ctx.sema, &impl_)?;
44 if trait_ != FamousDefs(&ctx.sema, module.krate(ctx.db())).core_convert_Into()? {
45 return None;
46 }
47
48 let cfg = ctx.config.find_path_config(ctx.sema.is_nightly(module.krate(ctx.sema.db)));
49 let current_edition = module.krate(ctx.db()).edition(ctx.db());
50
51 let src_type_mod_path = {
52 let src_type_path = src_type.syntax().descendants().find_map(ast::Path::cast)?;
53 let src_type_def = match ctx.sema.resolve_path(&src_type_path) {
54 Some(hir::PathResolution::Def(module_def)) => module_def,
55 _ => return None,
56 };
57 module.find_path(ctx.db(), src_type_def, cfg)?
58 };
59
60 let dest_type = match &ast_trait {
61 ast::Type::PathType(path) => {
62 path.path()?.segment()?.generic_arg_list()?.generic_args().next()?
63 }
64 _ => return None,
65 };
66
67 let into_fn = impl_.assoc_item_list()?.assoc_items().find_map(|item| {
68 if let ast::AssocItem::Fn(f) = item
69 && f.name()?.text() == "into"
70 {
71 return Some(f);
72 };
73 None
74 })?;
75
76 let into_fn_name = into_fn.name()?;
77 let into_fn_params = into_fn.param_list()?;
78 let into_fn_return = into_fn.ret_type()?;
79
80 let selfs = into_fn
81 .body()?
82 .syntax()
83 .descendants()
84 .filter_map(ast::NameRef::cast)
85 .filter(|name| name.text() == "self" || name.text() == "Self");
86
87 acc.add(
88 AssistId::refactor_rewrite("convert_into_to_from"),
89 "Convert Into to From",
90 impl_.syntax().text_range(),
91 |builder| {
92 let editor = builder.make_editor(impl_.syntax());
93 let make = editor.make();
94 let src_type_path =
95 mod_path_to_ast_with_factory(make, &src_type_mod_path, current_edition);
96
97 editor.replace(src_type.syntax(), make.ty(&dest_type.to_string()).syntax());
98 editor.replace(ast_trait.syntax(), make.ty(&format!("From<{src_type}>")).syntax());
99 editor.replace(into_fn_return.syntax(), make.ret_type(make.ty("Self")).syntax());
100
101 editor.replace(
102 into_fn_params.syntax(),
103 make.param_list(
104 None,
105 [make.param(make.simple_ident_pat(make.name("val")).into(), src_type.clone())],
106 )
107 .syntax(),
108 );
109 editor.replace(into_fn_name.syntax(), make.name("from").syntax());
110
111 for s in selfs {
112 match s.text().as_ref() {
113 "self" => editor.replace(s.syntax(), make.name_ref("val").syntax()),
114 "Self" => {
115 if let Some(path_segment) =
116 s.syntax().parent().and_then(ast::PathSegment::cast)
117 {
118 let self_path = path_segment.parent_path();
119 if self_path.qualifier().is_none()
120 && path_segment.generic_arg_list().is_none()
121 {
122 editor.replace(self_path.syntax(), src_type_path.syntax());
123 }
124 }
125 }
126 _ => {}
127 }
128 }
129
130 builder.add_file_edits(ctx.vfs_file_id(), editor);
131 },
132 )
133}
134
135#[cfg(test)]
136mod tests {
137 use super::*;
138
139 use crate::tests::{check_assist, check_assist_not_applicable};
140
141 #[test]
142 fn convert_into_to_from_converts_a_struct() {
143 check_assist(
144 convert_into_to_from,
145 r#"
146//- minicore: from
147struct Thing {
148 a: String,
149 b: usize
150}
151
152impl $0core::convert::Into<Thing> for usize {
153 fn into(self) -> Thing {
154 Thing {
155 b: self.to_string(),
156 a: self
157 }
158 }
159}
160"#,
161 r#"
162struct Thing {
163 a: String,
164 b: usize
165}
166
167impl From<usize> for Thing {
168 fn from(val: usize) -> Self {
169 Thing {
170 b: val.to_string(),
171 a: val
172 }
173 }
174}
175"#,
176 )
177 }
178
179 #[test]
180 fn convert_into_to_from_converts_enums() {
181 check_assist(
182 convert_into_to_from,
183 r#"
184//- minicore: from
185enum Thing {
186 Foo(String),
187 Bar(String)
188}
189
190impl $0core::convert::Into<String> for Thing {
191 fn into(self) -> String {
192 match self {
193 Self::Foo(s) => s,
194 Self::Bar(s) => s
195 }
196 }
197}
198"#,
199 r#"
200enum Thing {
201 Foo(String),
202 Bar(String)
203}
204
205impl From<Thing> for String {
206 fn from(val: Thing) -> Self {
207 match val {
208 Thing::Foo(s) => s,
209 Thing::Bar(s) => s
210 }
211 }
212}
213"#,
214 )
215 }
216
217 #[test]
218 fn convert_into_to_from_on_enum_with_lifetimes() {
219 check_assist(
220 convert_into_to_from,
221 r#"
222//- minicore: from
223enum Thing<'a> {
224 Foo(&'a str),
225 Bar(&'a str)
226}
227
228impl<'a> $0core::convert::Into<&'a str> for Thing<'a> {
229 fn into(self) -> &'a str {
230 match self {
231 Self::Foo(s) => s,
232 Self::Bar(s) => s
233 }
234 }
235}
236"#,
237 r#"
238enum Thing<'a> {
239 Foo(&'a str),
240 Bar(&'a str)
241}
242
243impl<'a> From<Thing<'a>> for &'a str {
244 fn from(val: Thing<'a>) -> Self {
245 match val {
246 Thing::Foo(s) => s,
247 Thing::Bar(s) => s
248 }
249 }
250}
251"#,
252 )
253 }
254
255 #[test]
256 fn convert_into_to_from_works_on_references() {
257 check_assist(
258 convert_into_to_from,
259 r#"
260//- minicore: from
261struct Thing(String);
262
263impl $0core::convert::Into<String> for &Thing {
264 fn into(self) -> Thing {
265 self.0.clone()
266 }
267}
268"#,
269 r#"
270struct Thing(String);
271
272impl From<&Thing> for String {
273 fn from(val: &Thing) -> Self {
274 val.0.clone()
275 }
276}
277"#,
278 )
279 }
280
281 #[test]
282 fn convert_into_to_from_works_on_qualified_structs() {
283 check_assist(
284 convert_into_to_from,
285 r#"
286//- minicore: from
287mod things {
288 pub struct Thing(String);
289 pub struct BetterThing(String);
290}
291
292impl $0core::convert::Into<things::BetterThing> for &things::Thing {
293 fn into(self) -> Thing {
294 things::BetterThing(self.0.clone())
295 }
296}
297"#,
298 r#"
299mod things {
300 pub struct Thing(String);
301 pub struct BetterThing(String);
302}
303
304impl From<&things::Thing> for things::BetterThing {
305 fn from(val: &things::Thing) -> Self {
306 things::BetterThing(val.0.clone())
307 }
308}
309"#,
310 )
311 }
312
313 #[test]
314 fn convert_into_to_from_works_on_qualified_enums() {
315 check_assist(
316 convert_into_to_from,
317 r#"
318//- minicore: from
319mod things {
320 pub enum Thing {
321 A(String)
322 }
323 pub struct BetterThing {
324 B(String)
325 }
326}
327
328impl $0core::convert::Into<things::BetterThing> for &things::Thing {
329 fn into(self) -> Thing {
330 match self {
331 Self::A(s) => things::BetterThing::B(s)
332 }
333 }
334}
335"#,
336 r#"
337mod things {
338 pub enum Thing {
339 A(String)
340 }
341 pub struct BetterThing {
342 B(String)
343 }
344}
345
346impl From<&things::Thing> for things::BetterThing {
347 fn from(val: &things::Thing) -> Self {
348 match val {
349 things::Thing::A(s) => things::BetterThing::B(s)
350 }
351 }
352}
353"#,
354 )
355 }
356
357 #[test]
358 fn convert_into_to_from_not_applicable_on_any_trait_named_into() {
359 check_assist_not_applicable(
360 convert_into_to_from,
361 r#"
362//- minicore: from
363pub trait Into<T> {
364 pub fn into(self) -> T;
365}
366
367struct Thing {
368 a: String,
369}
370
371impl $0Into<Thing> for String {
372 fn into(self) -> Thing {
373 Thing {
374 a: self
375 }
376 }
377}
378"#,
379 );
380 }
381}