diff options
-rw-r--r-- | crates/ra_assists/src/handlers/add_from_impl_for_enum.rs | 218 | ||||
-rw-r--r-- | crates/ra_assists/src/lib.rs | 2 | ||||
-rw-r--r-- | crates/ra_hir/src/code_model.rs | 34 | ||||
-rw-r--r-- | crates/ra_syntax/src/ast/make.rs | 3 |
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 @@ | |||
1 | use hir::ImplDef; | ||
2 | use ra_syntax::{ | ||
3 | ast::{self, AstNode, NameOwner}, | ||
4 | TextUnit, | ||
5 | }; | ||
6 | use stdx::format_to; | ||
7 | |||
8 | use crate::{Assist, AssistCtx, AssistId}; | ||
9 | use 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 | // ``` | ||
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 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 | |||
59 | impl 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 | |||
74 | fn 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)] | ||
117 | mod 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 | |||
172 | impl From<u32> for A { | ||
173 | fn from(v: u32) -> Self { | ||
174 | A::One(v) | ||
175 | } | ||
176 | } | ||
177 | |||
178 | pub 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 | |||
190 | impl From<String> for A { | ||
191 | fn from(v: String) -> Self { | ||
192 | A::Two(v) | ||
193 | } | ||
194 | } | ||
195 | |||
196 | pub 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 | |||
207 | impl From<String> for A { | ||
208 | fn from(v: String) -> Self { | ||
209 | A::Two(v) | ||
210 | } | ||
211 | } | ||
212 | |||
213 | pub 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 | }; |
24 | use hir_ty::{ | 24 | use 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 | }; |
28 | use ra_db::{CrateId, Edition, FileId}; | 28 | use ra_db::{CrateId, Edition, FileId}; |
29 | use ra_prof::profile; | 29 | use 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 { | |||
22 | pub fn path_qualified(qual: ast::Path, segment: ast::PathSegment) -> ast::Path { | 22 | pub 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 | } |
25 | fn path_from_text(text: &str) -> ast::Path { | 25 | |
26 | pub fn path_from_text(text: &str) -> ast::Path { | ||
26 | ast_from_text(text) | 27 | ast_from_text(text) |
27 | } | 28 | } |
28 | 29 | ||