Skip to content

Commit 2cee853

Browse files
bors[bot]mattyhall
andauthored
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]>
2 parents 642f3f4 + 6a2127b commit 2cee853

File tree

4 files changed

+230
-1
lines changed

4 files changed

+230
-1
lines changed
Original file line numberDiff line numberDiff line change
@@ -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+
}

crates/ra_assists/src/lib.rs

+2
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,7 @@ mod handlers {
122122
mod replace_qualified_name_with_use;
123123
mod replace_unwrap_with_match;
124124
mod split_import;
125+
mod add_from_impl_for_enum;
125126

126127
pub(crate) fn all() -> &'static [AssistHandler] {
127128
&[
@@ -159,6 +160,7 @@ mod handlers {
159160
replace_qualified_name_with_use::replace_qualified_name_with_use,
160161
replace_unwrap_with_match::replace_unwrap_with_match,
161162
split_import::split_import,
163+
add_from_impl_for_enum::add_from_impl_for_enum,
162164
]
163165
}
164166
}

crates/ra_hir/src/code_model.rs

+20
Original file line numberDiff line numberDiff line change
@@ -1084,6 +1084,26 @@ impl Type {
10841084
)
10851085
}
10861086

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+
10871107
// FIXME: this method is broken, as it doesn't take closures into account.
10881108
pub fn as_callable(&self) -> Option<CallableDef> {
10891109
Some(self.ty.value.as_callable()?.0)

crates/ra_syntax/src/ast/make.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@ pub fn path_unqualified(segment: ast::PathSegment) -> ast::Path {
2222
pub fn path_qualified(qual: ast::Path, segment: ast::PathSegment) -> ast::Path {
2323
path_from_text(&format!("{}::{}", qual, segment))
2424
}
25-
fn path_from_text(text: &str) -> ast::Path {
25+
26+
pub fn path_from_text(text: &str) -> ast::Path {
2627
ast_from_text(text)
2728
}
2829

0 commit comments

Comments
 (0)