aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_assists/src/handlers
diff options
context:
space:
mode:
authorMatthew Hall <[email protected]>2020-04-01 22:26:41 +0100
committerMatthew Hall <[email protected]>2020-04-01 22:26:41 +0100
commit1fee60181fea56ebe6b5e4aeb11cf9df25a1d087 (patch)
tree0a157b2f478a2b4334167da483f401d0ba5e4938 /crates/ra_assists/src/handlers
parent1c2d4135db867efe335a0654d86429bea7bb9caf (diff)
Add impl From for enum variant assist
Basically adds a From impl for tuple enum variants with one field. Added to cover the fairly common case of implementing your own Error that can be created from another one, although other use cases exist.
Diffstat (limited to 'crates/ra_assists/src/handlers')
-rw-r--r--crates/ra_assists/src/handlers/add_from_impl_for_enum.rs218
1 files changed, 218 insertions, 0 deletions
diff --git a/crates/ra_assists/src/handlers/add_from_impl_for_enum.rs b/crates/ra_assists/src/handlers/add_from_impl_for_enum.rs
new file mode 100644
index 000000000..cf94a214a
--- /dev/null
+++ b/crates/ra_assists/src/handlers/add_from_impl_for_enum.rs
@@ -0,0 +1,218 @@
1use hir::ImplDef;
2use ra_syntax::{
3 ast::{self, AstNode, NameOwner},
4 TextUnit,
5};
6use stdx::format_to;
7
8use crate::{Assist, AssistCtx, AssistId};
9use ra_ide_db::RootDatabase;
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// ```
28pub(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 already_has_from_impl(ctx.sema, &variant) {
46 return None;
47 }
48
49 ctx.add_assist(
50 AssistId("add_from_impl_for_enum"),
51 "Add From impl for this enum variant",
52 |edit| {
53 let start_offset = variant.parent_enum().syntax().text_range().end();
54 let mut buf = String::new();
55 format_to!(
56 buf,
57 r#"
58
59impl From<{0}> for {1} {{
60 fn from(v: {0}) -> Self {{
61 {1}::{2}(v)
62 }}
63}}"#,
64 path.syntax(),
65 enum_name,
66 variant_name
67 );
68 edit.insert(start_offset, buf);
69 edit.set_cursor(start_offset + TextUnit::of_str("\n\n"));
70 },
71 )
72}
73
74fn already_has_from_impl(
75 sema: &'_ hir::Semantics<'_, RootDatabase>,
76 variant: &ast::EnumVariant,
77) -> bool {
78 let scope = sema.scope(&variant.syntax());
79
80 let from_path = ast::make::path_from_text("From");
81 let from_hir_path = match hir::Path::from_ast(from_path) {
82 Some(p) => p,
83 None => return false,
84 };
85 let from_trait = match scope.resolve_hir_path(&from_hir_path) {
86 Some(hir::PathResolution::Def(hir::ModuleDef::Trait(t))) => t,
87 _ => return false,
88 };
89
90 let e: hir::Enum = match sema.to_def(&variant.parent_enum()) {
91 Some(e) => e,
92 None => return false,
93 };
94 let e_ty = e.ty(sema.db);
95
96 let hir_enum_var: hir::EnumVariant = match sema.to_def(variant) {
97 Some(ev) => ev,
98 None => return false,
99 };
100 let var_ty = hir_enum_var.fields(sema.db)[0].signature_ty(sema.db);
101
102 let krate = match scope.module() {
103 Some(s) => s.krate(),
104 _ => return false,
105 };
106 let impls = ImplDef::for_trait(sema.db, krate, from_trait);
107 let imp = impls.iter().find(|imp| {
108 let targets_enum = imp.target_ty(sema.db) == e_ty;
109 let param_matches = imp.target_trait_substs_matches(sema.db, &[var_ty.clone()]);
110 targets_enum && param_matches
111 });
112
113 imp.is_some()
114}
115
116#[cfg(test)]
117mod tests {
118 use super::*;
119 use crate::helpers::{check_assist, check_assist_not_applicable};
120
121 #[test]
122 fn test_add_from_impl_for_enum() {
123 check_assist(
124 add_from_impl_for_enum,
125 "enum A { <|>One(u32) }",
126 r#"enum A { One(u32) }
127
128<|>impl From<u32> for A {
129 fn from(v: u32) -> Self {
130 A::One(v)
131 }
132}"#,
133 );
134 }
135
136 #[test]
137 fn test_add_from_impl_for_enum_complicated_path() {
138 check_assist(
139 add_from_impl_for_enum,
140 "enum A { <|>One(foo::bar::baz::Boo) }",
141 r#"enum A { One(foo::bar::baz::Boo) }
142
143<|>impl From<foo::bar::baz::Boo> for A {
144 fn from(v: foo::bar::baz::Boo) -> Self {
145 A::One(v)
146 }
147}"#,
148 );
149 }
150
151 #[test]
152 fn test_add_from_impl_no_element() {
153 check_assist_not_applicable(add_from_impl_for_enum, "enum A { <|>One }");
154 }
155
156 #[test]
157 fn test_add_from_impl_more_than_one_element_in_tuple() {
158 check_assist_not_applicable(add_from_impl_for_enum, "enum A { <|>One(u32, String) }");
159 }
160
161 #[test]
162 fn test_add_from_impl_struct_variant() {
163 check_assist_not_applicable(add_from_impl_for_enum, "enum A { <|>One { x: u32 } }");
164 }
165
166 #[test]
167 fn test_add_from_impl_already_exists() {
168 check_assist_not_applicable(
169 add_from_impl_for_enum,
170 r#"enum A { <|>One(u32), }
171
172impl From<u32> for A {
173 fn from(v: u32) -> Self {
174 A::One(v)
175 }
176}
177
178pub trait From<T> {
179 fn from(T) -> Self;
180}"#,
181 );
182 }
183
184 #[test]
185 fn test_add_from_impl_different_variant_impl_exists() {
186 check_assist(
187 add_from_impl_for_enum,
188 r#"enum A { <|>One(u32), Two(String), }
189
190impl From<String> for A {
191 fn from(v: String) -> Self {
192 A::Two(v)
193 }
194}
195
196pub trait From<T> {
197 fn from(T) -> Self;
198}"#,
199 r#"enum A { One(u32), Two(String), }
200
201<|>impl From<u32> for A {
202 fn from(v: u32) -> Self {
203 A::One(v)
204 }
205}
206
207impl From<String> for A {
208 fn from(v: String) -> Self {
209 A::Two(v)
210 }
211}
212
213pub trait From<T> {
214 fn from(T) -> Self;
215}"#,
216 );
217 }
218}