aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-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