aboutsummaryrefslogtreecommitdiff
path: root/crates
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
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')
-rw-r--r--crates/ra_assists/src/handlers/add_from_impl_for_enum.rs218
-rw-r--r--crates/ra_assists/src/lib.rs2
-rw-r--r--crates/ra_hir/src/code_model.rs34
-rw-r--r--crates/ra_syntax/src/ast/make.rs3
4 files changed, 255 insertions, 2 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}
diff --git a/crates/ra_assists/src/lib.rs b/crates/ra_assists/src/lib.rs
index fa1f3dd26..6b4c56dcd 100644
--- a/crates/ra_assists/src/lib.rs
+++ b/crates/ra_assists/src/lib.rs
@@ -122,6 +122,7 @@ mod handlers {
122 mod replace_qualified_name_with_use; 122 mod replace_qualified_name_with_use;
123 mod replace_unwrap_with_match; 123 mod replace_unwrap_with_match;
124 mod split_import; 124 mod split_import;
125 mod add_from_impl_for_enum;
125 126
126 pub(crate) fn all() -> &'static [AssistHandler] { 127 pub(crate) fn all() -> &'static [AssistHandler] {
127 &[ 128 &[
@@ -159,6 +160,7 @@ mod handlers {
159 replace_qualified_name_with_use::replace_qualified_name_with_use, 160 replace_qualified_name_with_use::replace_qualified_name_with_use,
160 replace_unwrap_with_match::replace_unwrap_with_match, 161 replace_unwrap_with_match::replace_unwrap_with_match,
161 split_import::split_import, 162 split_import::split_import,
163 add_from_impl_for_enum::add_from_impl_for_enum,
162 ] 164 ]
163 } 165 }
164} 166}
diff --git a/crates/ra_hir/src/code_model.rs b/crates/ra_hir/src/code_model.rs
index cd2a8fc62..3889a7e5a 100644
--- a/crates/ra_hir/src/code_model.rs
+++ b/crates/ra_hir/src/code_model.rs
@@ -23,7 +23,7 @@ use hir_expand::{
23}; 23};
24use hir_ty::{ 24use hir_ty::{
25 autoderef, display::HirFormatter, expr::ExprValidator, method_resolution, ApplicationTy, 25 autoderef, display::HirFormatter, expr::ExprValidator, method_resolution, ApplicationTy,
26 Canonical, InEnvironment, Substs, TraitEnvironment, Ty, TyDefId, TypeCtor, 26 Canonical, InEnvironment, Substs, TraitEnvironment, Ty, TyDefId, TypeCtor, TypeWalk,
27}; 27};
28use ra_db::{CrateId, Edition, FileId}; 28use ra_db::{CrateId, Edition, FileId};
29use ra_prof::profile; 29use ra_prof::profile;
@@ -960,6 +960,38 @@ impl ImplDef {
960 db.impl_data(self.id).target_trait.clone() 960 db.impl_data(self.id).target_trait.clone()
961 } 961 }
962 962
963 pub fn target_trait_substs_matches(&self, db: &dyn HirDatabase, typs: &[Type]) -> bool {
964 let type_ref = match self.target_trait(db) {
965 Some(typ_ref) => typ_ref,
966 None => return false,
967 };
968 let resolver = self.id.resolver(db.upcast());
969 let ctx = hir_ty::TyLoweringContext::new(db, &resolver);
970 let ty = Ty::from_hir(&ctx, &type_ref);
971 let d = match ty.dyn_trait_ref() {
972 Some(d) => d,
973 None => return false,
974 };
975 let mut matches = true;
976 let mut i = 0;
977 d.substs.walk(&mut |t| {
978 if matches {
979 if i >= typs.len() {
980 matches = false;
981 return;
982 }
983 match t {
984 Ty::Bound(_) => matches = i == 0,
985 _ => {
986 matches = *t == typs[i].ty.value;
987 i += 1;
988 }
989 }
990 }
991 });
992 matches
993 }
994
963 pub fn target_type(&self, db: &dyn HirDatabase) -> TypeRef { 995 pub fn target_type(&self, db: &dyn HirDatabase) -> TypeRef {
964 db.impl_data(self.id).target_type.clone() 996 db.impl_data(self.id).target_type.clone()
965 } 997 }
diff --git a/crates/ra_syntax/src/ast/make.rs b/crates/ra_syntax/src/ast/make.rs
index 0c908573d..c49cf9a3b 100644
--- a/crates/ra_syntax/src/ast/make.rs
+++ b/crates/ra_syntax/src/ast/make.rs
@@ -22,7 +22,8 @@ pub fn path_unqualified(segment: ast::PathSegment) -> ast::Path {
22pub fn path_qualified(qual: ast::Path, segment: ast::PathSegment) -> ast::Path { 22pub fn path_qualified(qual: ast::Path, segment: ast::PathSegment) -> ast::Path {
23 path_from_text(&format!("{}::{}", qual, segment)) 23 path_from_text(&format!("{}::{}", qual, segment))
24} 24}
25fn path_from_text(text: &str) -> ast::Path { 25
26pub fn path_from_text(text: &str) -> ast::Path {
26 ast_from_text(text) 27 ast_from_text(text)
27} 28}
28 29