diff options
author | bors[bot] <26634292+bors[bot]@users.noreply.github.com> | 2020-04-03 08:46:46 +0100 |
---|---|---|
committer | GitHub <[email protected]> | 2020-04-03 08:46:46 +0100 |
commit | 2cee8531c5236ae7d66717bea604f1224c23ea56 (patch) | |
tree | b2e2de013f8c028b5cd70ac421c8e8f7d4c6bfb5 | |
parent | 642f3f4bd62019d8c0fdd6304ff07d87a5aca627 (diff) | |
parent | 6a2127be28a837215801f4ac3cd7d46ef7c4485b (diff) |
Merge #3814
3814: Add impl From for enum variant assist r=flodiebold a=mattyhall
Basically adds a From impl for tuple enum variants with one field. It was recommended to me on the zulip to maybe try using the trait solver, but I had trouble with that as, although it could resolve the trait impl, it couldn't resolve the variable unambiguously in real use. I'm also unsure of how it would work if there were already multiple From impls to resolve - I can't see a way we could get more than one solution to my query.
Fixes #3766
Co-authored-by: Matthew Hall <[email protected]>
-rw-r--r-- | crates/ra_assists/src/handlers/add_from_impl_for_enum.rs | 206 | ||||
-rw-r--r-- | crates/ra_assists/src/lib.rs | 2 | ||||
-rw-r--r-- | crates/ra_hir/src/code_model.rs | 20 | ||||
-rw-r--r-- | crates/ra_syntax/src/ast/make.rs | 3 |
4 files changed, 230 insertions, 1 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..864373aa5 --- /dev/null +++ b/crates/ra_assists/src/handlers/add_from_impl_for_enum.rs | |||
@@ -0,0 +1,206 @@ | |||
1 | use ra_syntax::{ | ||
2 | ast::{self, AstNode, NameOwner}, | ||
3 | TextUnit, | ||
4 | }; | ||
5 | use stdx::format_to; | ||
6 | |||
7 | use crate::{Assist, AssistCtx, AssistId}; | ||
8 | use ra_ide_db::RootDatabase; | ||
9 | |||
10 | // Assist add_from_impl_for_enum | ||
11 | // | ||
12 | // Adds a From impl for an enum variant with one tuple field | ||
13 | // | ||
14 | // ``` | ||
15 | // enum A { <|>One(u32) } | ||
16 | // ``` | ||
17 | // -> | ||
18 | // ``` | ||
19 | // enum A { One(u32) } | ||
20 | // | ||
21 | // impl From<u32> for A { | ||
22 | // fn from(v: u32) -> Self { | ||
23 | // A::One(v) | ||
24 | // } | ||
25 | // } | ||
26 | // ``` | ||
27 | pub(crate) fn add_from_impl_for_enum(ctx: AssistCtx) -> Option<Assist> { | ||
28 | let variant = ctx.find_node_at_offset::<ast::EnumVariant>()?; | ||
29 | let variant_name = variant.name()?; | ||
30 | let enum_name = variant.parent_enum().name()?; | ||
31 | let field_list = match variant.kind() { | ||
32 | ast::StructKind::Tuple(field_list) => field_list, | ||
33 | _ => return None, | ||
34 | }; | ||
35 | if field_list.fields().count() != 1 { | ||
36 | return None; | ||
37 | } | ||
38 | let field_type = field_list.fields().next()?.type_ref()?; | ||
39 | let path = match field_type { | ||
40 | ast::TypeRef::PathType(p) => p, | ||
41 | _ => return None, | ||
42 | }; | ||
43 | |||
44 | if already_has_from_impl(ctx.sema, &variant) { | ||
45 | return None; | ||
46 | } | ||
47 | |||
48 | ctx.add_assist( | ||
49 | AssistId("add_from_impl_for_enum"), | ||
50 | "Add From impl for this enum variant", | ||
51 | |edit| { | ||
52 | let start_offset = variant.parent_enum().syntax().text_range().end(); | ||
53 | let mut buf = String::new(); | ||
54 | format_to!( | ||
55 | buf, | ||
56 | r#" | ||
57 | |||
58 | impl From<{0}> for {1} {{ | ||
59 | fn from(v: {0}) -> Self {{ | ||
60 | {1}::{2}(v) | ||
61 | }} | ||
62 | }}"#, | ||
63 | path.syntax(), | ||
64 | enum_name, | ||
65 | variant_name | ||
66 | ); | ||
67 | edit.insert(start_offset, buf); | ||
68 | edit.set_cursor(start_offset + TextUnit::of_str("\n\n")); | ||
69 | }, | ||
70 | ) | ||
71 | } | ||
72 | |||
73 | fn already_has_from_impl( | ||
74 | sema: &'_ hir::Semantics<'_, RootDatabase>, | ||
75 | variant: &ast::EnumVariant, | ||
76 | ) -> bool { | ||
77 | let scope = sema.scope(&variant.syntax()); | ||
78 | |||
79 | let from_path = ast::make::path_from_text("From"); | ||
80 | let from_hir_path = match hir::Path::from_ast(from_path) { | ||
81 | Some(p) => p, | ||
82 | None => return false, | ||
83 | }; | ||
84 | let from_trait = match scope.resolve_hir_path(&from_hir_path) { | ||
85 | Some(hir::PathResolution::Def(hir::ModuleDef::Trait(t))) => t, | ||
86 | _ => return false, | ||
87 | }; | ||
88 | |||
89 | let e: hir::Enum = match sema.to_def(&variant.parent_enum()) { | ||
90 | Some(e) => e, | ||
91 | None => return false, | ||
92 | }; | ||
93 | let e_ty = e.ty(sema.db); | ||
94 | |||
95 | let hir_enum_var: hir::EnumVariant = match sema.to_def(variant) { | ||
96 | Some(ev) => ev, | ||
97 | None => return false, | ||
98 | }; | ||
99 | let var_ty = hir_enum_var.fields(sema.db)[0].signature_ty(sema.db); | ||
100 | |||
101 | e_ty.impls_trait(sema.db, from_trait, &[var_ty.clone()]) | ||
102 | } | ||
103 | |||
104 | #[cfg(test)] | ||
105 | mod tests { | ||
106 | use super::*; | ||
107 | use crate::helpers::{check_assist, check_assist_not_applicable}; | ||
108 | |||
109 | #[test] | ||
110 | fn test_add_from_impl_for_enum() { | ||
111 | check_assist( | ||
112 | add_from_impl_for_enum, | ||
113 | "enum A { <|>One(u32) }", | ||
114 | r#"enum A { One(u32) } | ||
115 | |||
116 | <|>impl From<u32> for A { | ||
117 | fn from(v: u32) -> Self { | ||
118 | A::One(v) | ||
119 | } | ||
120 | }"#, | ||
121 | ); | ||
122 | } | ||
123 | |||
124 | #[test] | ||
125 | fn test_add_from_impl_for_enum_complicated_path() { | ||
126 | check_assist( | ||
127 | add_from_impl_for_enum, | ||
128 | "enum A { <|>One(foo::bar::baz::Boo) }", | ||
129 | r#"enum A { One(foo::bar::baz::Boo) } | ||
130 | |||
131 | <|>impl From<foo::bar::baz::Boo> for A { | ||
132 | fn from(v: foo::bar::baz::Boo) -> Self { | ||
133 | A::One(v) | ||
134 | } | ||
135 | }"#, | ||
136 | ); | ||
137 | } | ||
138 | |||
139 | #[test] | ||
140 | fn test_add_from_impl_no_element() { | ||
141 | check_assist_not_applicable(add_from_impl_for_enum, "enum A { <|>One }"); | ||
142 | } | ||
143 | |||
144 | #[test] | ||
145 | fn test_add_from_impl_more_than_one_element_in_tuple() { | ||
146 | check_assist_not_applicable(add_from_impl_for_enum, "enum A { <|>One(u32, String) }"); | ||
147 | } | ||
148 | |||
149 | #[test] | ||
150 | fn test_add_from_impl_struct_variant() { | ||
151 | check_assist_not_applicable(add_from_impl_for_enum, "enum A { <|>One { x: u32 } }"); | ||
152 | } | ||
153 | |||
154 | #[test] | ||
155 | fn test_add_from_impl_already_exists() { | ||
156 | check_assist_not_applicable( | ||
157 | add_from_impl_for_enum, | ||
158 | r#"enum A { <|>One(u32), } | ||
159 | |||
160 | impl From<u32> for A { | ||
161 | fn from(v: u32) -> Self { | ||
162 | A::One(v) | ||
163 | } | ||
164 | } | ||
165 | |||
166 | pub trait From<T> { | ||
167 | fn from(T) -> Self; | ||
168 | }"#, | ||
169 | ); | ||
170 | } | ||
171 | |||
172 | #[test] | ||
173 | fn test_add_from_impl_different_variant_impl_exists() { | ||
174 | check_assist( | ||
175 | add_from_impl_for_enum, | ||
176 | r#"enum A { <|>One(u32), Two(String), } | ||
177 | |||
178 | impl From<String> for A { | ||
179 | fn from(v: String) -> Self { | ||
180 | A::Two(v) | ||
181 | } | ||
182 | } | ||
183 | |||
184 | pub trait From<T> { | ||
185 | fn from(T) -> Self; | ||
186 | }"#, | ||
187 | r#"enum A { One(u32), Two(String), } | ||
188 | |||
189 | <|>impl From<u32> for A { | ||
190 | fn from(v: u32) -> Self { | ||
191 | A::One(v) | ||
192 | } | ||
193 | } | ||
194 | |||
195 | impl From<String> for A { | ||
196 | fn from(v: String) -> Self { | ||
197 | A::Two(v) | ||
198 | } | ||
199 | } | ||
200 | |||
201 | pub trait From<T> { | ||
202 | fn from(T) -> Self; | ||
203 | }"#, | ||
204 | ); | ||
205 | } | ||
206 | } | ||
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..c6f3bdb8e 100644 --- a/crates/ra_hir/src/code_model.rs +++ b/crates/ra_hir/src/code_model.rs | |||
@@ -1084,6 +1084,26 @@ impl Type { | |||
1084 | ) | 1084 | ) |
1085 | } | 1085 | } |
1086 | 1086 | ||
1087 | pub fn impls_trait(&self, db: &dyn HirDatabase, trait_: Trait, args: &[Type]) -> bool { | ||
1088 | let trait_ref = hir_ty::TraitRef { | ||
1089 | trait_: trait_.id, | ||
1090 | substs: Substs::build_for_def(db, trait_.id) | ||
1091 | .push(self.ty.value.clone()) | ||
1092 | .fill(args.iter().map(|t| t.ty.value.clone())) | ||
1093 | .build(), | ||
1094 | }; | ||
1095 | |||
1096 | let goal = Canonical { | ||
1097 | value: hir_ty::InEnvironment::new( | ||
1098 | self.ty.environment.clone(), | ||
1099 | hir_ty::Obligation::Trait(trait_ref), | ||
1100 | ), | ||
1101 | num_vars: 0, | ||
1102 | }; | ||
1103 | |||
1104 | db.trait_solve(self.krate, goal).is_some() | ||
1105 | } | ||
1106 | |||
1087 | // FIXME: this method is broken, as it doesn't take closures into account. | 1107 | // FIXME: this method is broken, as it doesn't take closures into account. |
1088 | pub fn as_callable(&self) -> Option<CallableDef> { | 1108 | pub fn as_callable(&self) -> Option<CallableDef> { |
1089 | Some(self.ty.value.as_callable()?.0) | 1109 | Some(self.ty.value.as_callable()?.0) |
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 | ||