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