]> git.lizzy.rs Git - rust.git/blob - crates/ra_assists/src/handlers/add_from_impl_for_enum.rs
81deb3dfa3b89828e56a22ac914c83cfe957dbe2
[rust.git] / crates / ra_assists / src / handlers / add_from_impl_for_enum.rs
1 use ra_ide_db::RootDatabase;
2 use ra_syntax::{
3     ast::{self, AstNode, NameOwner},
4     TextSize,
5 };
6 use stdx::format_to;
7
8 use crate::{utils::FamousDefs, Assist, AssistCtx, AssistId};
9 use test_utils::tested_by;
10
11 // Assist add_from_impl_for_enum
12 //
13 // Adds a From impl for an enum variant with one tuple field
14 //
15 // ```
16 // enum A { <|>One(u32) }
17 // ```
18 // ->
19 // ```
20 // enum A { One(u32) }
21 //
22 // impl From<u32> for A {
23 //     fn from(v: u32) -> Self {
24 //         A::One(v)
25 //     }
26 // }
27 // ```
28 pub(crate) fn add_from_impl_for_enum(ctx: AssistCtx) -> Option<Assist> {
29     let variant = ctx.find_node_at_offset::<ast::EnumVariant>()?;
30     let variant_name = variant.name()?;
31     let enum_name = variant.parent_enum().name()?;
32     let field_list = match variant.kind() {
33         ast::StructKind::Tuple(field_list) => field_list,
34         _ => return None,
35     };
36     if field_list.fields().count() != 1 {
37         return None;
38     }
39     let field_type = field_list.fields().next()?.type_ref()?;
40     let path = match field_type {
41         ast::TypeRef::PathType(p) => p,
42         _ => return None,
43     };
44
45     if existing_from_impl(ctx.sema, &variant).is_some() {
46         tested_by!(test_add_from_impl_already_exists);
47         return None;
48     }
49
50     let target = variant.syntax().text_range();
51     ctx.add_assist(
52         AssistId("add_from_impl_for_enum"),
53         "Add From impl for this enum variant",
54         target,
55         |edit| {
56             let start_offset = variant.parent_enum().syntax().text_range().end();
57             let mut buf = String::new();
58             format_to!(
59                 buf,
60                 r#"
61
62 impl From<{0}> for {1} {{
63     fn from(v: {0}) -> Self {{
64         {1}::{2}(v)
65     }}
66 }}"#,
67                 path.syntax(),
68                 enum_name,
69                 variant_name
70             );
71             edit.insert(start_offset, buf);
72             edit.set_cursor(start_offset + TextSize::of("\n\n"));
73         },
74     )
75 }
76
77 fn existing_from_impl(
78     sema: &'_ hir::Semantics<'_, RootDatabase>,
79     variant: &ast::EnumVariant,
80 ) -> Option<()> {
81     let variant = sema.to_def(variant)?;
82     let enum_ = variant.parent_enum(sema.db);
83     let krate = enum_.module(sema.db).krate();
84
85     let from_trait = FamousDefs(sema, krate).core_convert_From()?;
86
87     let enum_type = enum_.ty(sema.db);
88
89     let wrapped_type = variant.fields(sema.db).get(0)?.signature_ty(sema.db);
90
91     if enum_type.impls_trait(sema.db, from_trait, &[wrapped_type]) {
92         Some(())
93     } else {
94         None
95     }
96 }
97
98 #[cfg(test)]
99 mod tests {
100     use super::*;
101
102     use crate::tests::{check_assist, check_assist_not_applicable};
103     use test_utils::covers;
104
105     #[test]
106     fn test_add_from_impl_for_enum() {
107         check_assist(
108             add_from_impl_for_enum,
109             "enum A { <|>One(u32) }",
110             r#"enum A { One(u32) }
111
112 <|>impl From<u32> for A {
113     fn from(v: u32) -> Self {
114         A::One(v)
115     }
116 }"#,
117         );
118     }
119
120     #[test]
121     fn test_add_from_impl_for_enum_complicated_path() {
122         check_assist(
123             add_from_impl_for_enum,
124             "enum A { <|>One(foo::bar::baz::Boo) }",
125             r#"enum A { One(foo::bar::baz::Boo) }
126
127 <|>impl From<foo::bar::baz::Boo> for A {
128     fn from(v: foo::bar::baz::Boo) -> Self {
129         A::One(v)
130     }
131 }"#,
132         );
133     }
134
135     fn check_not_applicable(ra_fixture: &str) {
136         let fixture =
137             format!("//- main.rs crate:main deps:core\n{}\n{}", ra_fixture, FamousDefs::FIXTURE);
138         check_assist_not_applicable(add_from_impl_for_enum, &fixture)
139     }
140
141     #[test]
142     fn test_add_from_impl_no_element() {
143         check_not_applicable("enum A { <|>One }");
144     }
145
146     #[test]
147     fn test_add_from_impl_more_than_one_element_in_tuple() {
148         check_not_applicable("enum A { <|>One(u32, String) }");
149     }
150
151     #[test]
152     fn test_add_from_impl_struct_variant() {
153         check_not_applicable("enum A { <|>One { x: u32 } }");
154     }
155
156     #[test]
157     fn test_add_from_impl_already_exists() {
158         covers!(test_add_from_impl_already_exists);
159         check_not_applicable(
160             r#"
161 enum A { <|>One(u32), }
162
163 impl From<u32> for A {
164     fn from(v: u32) -> Self {
165         A::One(v)
166     }
167 }
168 "#,
169         );
170     }
171
172     #[test]
173     fn test_add_from_impl_different_variant_impl_exists() {
174         check_assist(
175             add_from_impl_for_enum,
176             r#"enum A { <|>One(u32), Two(String), }
177
178 impl From<String> for A {
179     fn from(v: String) -> Self {
180         A::Two(v)
181     }
182 }
183
184 pub trait From<T> {
185     fn from(T) -> Self;
186 }"#,
187             r#"enum A { One(u32), Two(String), }
188
189 <|>impl From<u32> for A {
190     fn from(v: u32) -> Self {
191         A::One(v)
192     }
193 }
194
195 impl From<String> for A {
196     fn from(v: String) -> Self {
197         A::Two(v)
198     }
199 }
200
201 pub trait From<T> {
202     fn from(T) -> Self;
203 }"#,
204         );
205     }
206 }