Skip to content

Commit bc8b278

Browse files
bors[bot]obmarg
andauthored
Merge #8295
8295: Add `convert_into_to_from` assist r=Veykril a=obmarg This adds a "Convert Into to From" assist, useful since clippy has recently started adding lints on every `Into`. It covers converting the signature, and converting any `self`/`Self` references within the body. It does assume that every instance of `Into` can be converted to a `From`, which I _think_ is the case now. Let me know if there's something I'm not thinking of and I can try and make it smarter. Closes #8196 ![CleanShot 2021-04-02 at 13 39 54](https://user-images.githubusercontent.com/556490/113420108-9ce21c00-93c0-11eb-8c49-80b5fb189284.gif) I'm extremely new to this codebase so please let me know if anything needs changed. Co-authored-by: Graeme Coupar <grambo@grambo.me.uk>
2 parents 0829960 + ee03849 commit bc8b278

File tree

5 files changed

+398
-1
lines changed

5 files changed

+398
-1
lines changed
Lines changed: 355 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,355 @@
1+
use ide_db::{
2+
helpers::{mod_path_to_ast, FamousDefs},
3+
traits::resolve_target_trait,
4+
};
5+
use syntax::ast::{self, AstNode, NameOwner};
6+
7+
use crate::{AssistContext, AssistId, AssistKind, Assists};
8+
9+
// Assist: convert_into_to_from
10+
//
11+
// Converts an Into impl to an equivalent From impl.
12+
//
13+
// ```
14+
// # //- /lib.rs crate:core
15+
// # pub mod convert { pub trait Into<T> { pub fn into(self) -> T; } }
16+
// # //- /lib.rs crate:main deps:core
17+
// # use core::convert::Into;
18+
// impl $0Into<Thing> for usize {
19+
// fn into(self) -> Thing {
20+
// Thing {
21+
// b: self.to_string(),
22+
// a: self
23+
// }
24+
// }
25+
// }
26+
// ```
27+
// ->
28+
// ```
29+
// # use core::convert::Into;
30+
// impl From<usize> for Thing {
31+
// fn from(val: usize) -> Self {
32+
// Thing {
33+
// b: val.to_string(),
34+
// a: val
35+
// }
36+
// }
37+
// }
38+
// ```
39+
pub(crate) fn convert_into_to_from(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
40+
let impl_ = ctx.find_node_at_offset::<ast::Impl>()?;
41+
let src_type = impl_.self_ty()?;
42+
let ast_trait = impl_.trait_()?;
43+
44+
let module = ctx.sema.scope(impl_.syntax()).module()?;
45+
46+
let trait_ = resolve_target_trait(&ctx.sema, &impl_)?;
47+
if trait_ != FamousDefs(&ctx.sema, Some(module.krate())).core_convert_Into()? {
48+
return None;
49+
}
50+
51+
let src_type_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+
58+
mod_path_to_ast(&module.find_use_path(ctx.db(), src_type_def)?)
59+
};
60+
61+
let dest_type = match &ast_trait {
62+
ast::Type::PathType(path) => {
63+
path.path()?.segment()?.generic_arg_list()?.generic_args().next()?
64+
}
65+
_ => return None,
66+
};
67+
68+
let into_fn = impl_.assoc_item_list()?.assoc_items().find_map(|item| {
69+
if let ast::AssocItem::Fn(f) = item {
70+
if f.name()?.text() == "into" {
71+
return Some(f);
72+
}
73+
};
74+
None
75+
})?;
76+
77+
let into_fn_name = into_fn.name()?;
78+
let into_fn_params = into_fn.param_list()?;
79+
let into_fn_return = into_fn.ret_type()?;
80+
81+
let selfs = into_fn
82+
.body()?
83+
.syntax()
84+
.descendants()
85+
.filter_map(ast::NameRef::cast)
86+
.filter(|name| name.text() == "self" || name.text() == "Self");
87+
88+
acc.add(
89+
AssistId("convert_into_to_from", AssistKind::RefactorRewrite),
90+
"Convert Into to From",
91+
impl_.syntax().text_range(),
92+
|builder| {
93+
builder.replace(src_type.syntax().text_range(), dest_type.to_string());
94+
builder.replace(ast_trait.syntax().text_range(), format!("From<{}>", src_type));
95+
builder.replace(into_fn_return.syntax().text_range(), "-> Self");
96+
builder.replace(
97+
into_fn_params.syntax().text_range(),
98+
format!("(val: {})", src_type.to_string()),
99+
);
100+
builder.replace(into_fn_name.syntax().text_range(), "from");
101+
102+
for s in selfs {
103+
match s.text().as_ref() {
104+
"self" => builder.replace(s.syntax().text_range(), "val"),
105+
"Self" => builder.replace(s.syntax().text_range(), src_type_path.to_string()),
106+
_ => {}
107+
}
108+
}
109+
},
110+
)
111+
}
112+
113+
#[cfg(test)]
114+
mod tests {
115+
use super::*;
116+
117+
use crate::tests::check_assist;
118+
119+
#[test]
120+
fn convert_into_to_from_converts_a_struct() {
121+
check_convert_into_to_from(
122+
r#"
123+
struct Thing {
124+
a: String,
125+
b: usize
126+
}
127+
128+
impl $0core::convert::Into<Thing> for usize {
129+
fn into(self) -> Thing {
130+
Thing {
131+
b: self.to_string(),
132+
a: self
133+
}
134+
}
135+
}
136+
"#,
137+
r#"
138+
struct Thing {
139+
a: String,
140+
b: usize
141+
}
142+
143+
impl From<usize> for Thing {
144+
fn from(val: usize) -> Self {
145+
Thing {
146+
b: val.to_string(),
147+
a: val
148+
}
149+
}
150+
}
151+
"#,
152+
)
153+
}
154+
155+
#[test]
156+
fn convert_into_to_from_converts_enums() {
157+
check_convert_into_to_from(
158+
r#"
159+
enum Thing {
160+
Foo(String),
161+
Bar(String)
162+
}
163+
164+
impl $0core::convert::Into<String> for Thing {
165+
fn into(self) -> String {
166+
match self {
167+
Self::Foo(s) => s,
168+
Self::Bar(s) => s
169+
}
170+
}
171+
}
172+
"#,
173+
r#"
174+
enum Thing {
175+
Foo(String),
176+
Bar(String)
177+
}
178+
179+
impl From<Thing> for String {
180+
fn from(val: Thing) -> Self {
181+
match val {
182+
Thing::Foo(s) => s,
183+
Thing::Bar(s) => s
184+
}
185+
}
186+
}
187+
"#,
188+
)
189+
}
190+
191+
#[test]
192+
fn convert_into_to_from_on_enum_with_lifetimes() {
193+
check_convert_into_to_from(
194+
r#"
195+
enum Thing<'a> {
196+
Foo(&'a str),
197+
Bar(&'a str)
198+
}
199+
200+
impl<'a> $0core::convert::Into<&'a str> for Thing<'a> {
201+
fn into(self) -> &'a str {
202+
match self {
203+
Self::Foo(s) => s,
204+
Self::Bar(s) => s
205+
}
206+
}
207+
}
208+
"#,
209+
r#"
210+
enum Thing<'a> {
211+
Foo(&'a str),
212+
Bar(&'a str)
213+
}
214+
215+
impl<'a> From<Thing<'a>> for &'a str {
216+
fn from(val: Thing<'a>) -> Self {
217+
match val {
218+
Thing::Foo(s) => s,
219+
Thing::Bar(s) => s
220+
}
221+
}
222+
}
223+
"#,
224+
)
225+
}
226+
227+
#[test]
228+
fn convert_into_to_from_works_on_references() {
229+
check_convert_into_to_from(
230+
r#"
231+
struct Thing(String);
232+
233+
impl $0core::convert::Into<String> for &Thing {
234+
fn into(self) -> Thing {
235+
self.0.clone()
236+
}
237+
}
238+
"#,
239+
r#"
240+
struct Thing(String);
241+
242+
impl From<&Thing> for String {
243+
fn from(val: &Thing) -> Self {
244+
val.0.clone()
245+
}
246+
}
247+
"#,
248+
)
249+
}
250+
251+
#[test]
252+
fn convert_into_to_from_works_on_qualified_structs() {
253+
check_convert_into_to_from(
254+
r#"
255+
mod things {
256+
pub struct Thing(String);
257+
pub struct BetterThing(String);
258+
}
259+
260+
impl $0core::convert::Into<things::BetterThing> for &things::Thing {
261+
fn into(self) -> Thing {
262+
things::BetterThing(self.0.clone())
263+
}
264+
}
265+
"#,
266+
r#"
267+
mod things {
268+
pub struct Thing(String);
269+
pub struct BetterThing(String);
270+
}
271+
272+
impl From<&things::Thing> for things::BetterThing {
273+
fn from(val: &things::Thing) -> Self {
274+
things::BetterThing(val.0.clone())
275+
}
276+
}
277+
"#,
278+
)
279+
}
280+
281+
#[test]
282+
fn convert_into_to_from_works_on_qualified_enums() {
283+
check_convert_into_to_from(
284+
r#"
285+
mod things {
286+
pub enum Thing {
287+
A(String)
288+
}
289+
pub struct BetterThing {
290+
B(String)
291+
}
292+
}
293+
294+
impl $0core::convert::Into<things::BetterThing> for &things::Thing {
295+
fn into(self) -> Thing {
296+
match self {
297+
Self::A(s) => things::BetterThing::B(s)
298+
}
299+
}
300+
}
301+
"#,
302+
r#"
303+
mod things {
304+
pub enum Thing {
305+
A(String)
306+
}
307+
pub struct BetterThing {
308+
B(String)
309+
}
310+
}
311+
312+
impl From<&things::Thing> for things::BetterThing {
313+
fn from(val: &things::Thing) -> Self {
314+
match val {
315+
things::Thing::A(s) => things::BetterThing::B(s)
316+
}
317+
}
318+
}
319+
"#,
320+
)
321+
}
322+
323+
#[test]
324+
fn convert_into_to_from_not_applicable_on_any_trait_named_into() {
325+
check_assist_not_applicable(
326+
r#"
327+
pub trait Into<T> {{
328+
pub fn into(self) -> T;
329+
}}
330+
331+
struct Thing {
332+
a: String,
333+
}
334+
335+
impl $0Into<Thing> for String {
336+
fn into(self) -> Thing {
337+
Thing {
338+
a: self
339+
}
340+
}
341+
}
342+
"#,
343+
);
344+
}
345+
346+
fn check_convert_into_to_from(before: &str, after: &str) {
347+
let before = &format!("//- /main.rs crate:main deps:core{}{}", before, FamousDefs::FIXTURE);
348+
check_assist(convert_into_to_from, before, after);
349+
}
350+
351+
fn check_assist_not_applicable(before: &str) {
352+
let before = &format!("//- /main.rs crate:main deps:core{}{}", before, FamousDefs::FIXTURE);
353+
crate::tests::check_assist_not_applicable(convert_into_to_from, before);
354+
}
355+
}

crates/ide_assists/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,7 @@ mod handlers {
117117
mod convert_integer_literal;
118118
mod convert_comment_block;
119119
mod convert_iter_for_each_to_for;
120+
mod convert_into_to_from;
120121
mod early_return;
121122
mod expand_glob_import;
122123
mod extract_function;
@@ -185,6 +186,7 @@ mod handlers {
185186
convert_integer_literal::convert_integer_literal,
186187
convert_comment_block::convert_comment_block,
187188
convert_iter_for_each_to_for::convert_iter_for_each_to_for,
189+
convert_into_to_from::convert_into_to_from,
188190
early_return::convert_to_guarded_return,
189191
expand_glob_import::expand_glob_import,
190192
extract_struct_from_enum_variant::extract_struct_from_enum_variant,

0 commit comments

Comments
 (0)