ide_assists/handlers/
generate_from_impl_for_enum.rs

1use hir::next_solver::{DbInterner, TypingMode};
2use ide_db::{RootDatabase, famous_defs::FamousDefs};
3use syntax::ast::{self, AstNode, HasName};
4
5use crate::{
6    AssistContext, AssistId, Assists,
7    utils::{generate_trait_impl_text_intransitive, is_selected},
8};
9
10// Assist: generate_from_impl_for_enum
11//
12// Adds a From impl for this enum variant with one tuple field.
13//
14// ```
15// enum A { $0One(u32) }
16// ```
17// ->
18// ```
19// enum A { One(u32) }
20//
21// impl From<u32> for A {
22//     fn from(v: u32) -> Self {
23//         Self::One(v)
24//     }
25// }
26// ```
27pub(crate) fn generate_from_impl_for_enum(
28    acc: &mut Assists,
29    ctx: &AssistContext<'_>,
30) -> Option<()> {
31    let variant = ctx.find_node_at_offset::<ast::Variant>()?;
32    let adt = ast::Adt::Enum(variant.parent_enum());
33    let variants = selected_variants(ctx, &variant)?;
34
35    let target = variant.syntax().text_range();
36    acc.add(
37        AssistId::generate("generate_from_impl_for_enum"),
38        "Generate `From` impl for this enum variant(s)",
39        target,
40        |edit| {
41            let start_offset = variant.parent_enum().syntax().text_range().end();
42            let from_impl = variants
43                .into_iter()
44                .map(|variant_info| {
45                    let from_trait = format!("From<{}>", variant_info.ty);
46                    let impl_code = generate_impl_code(variant_info);
47                    generate_trait_impl_text_intransitive(&adt, &from_trait, &impl_code)
48                })
49                .collect::<String>();
50            edit.insert(start_offset, from_impl);
51        },
52    )
53}
54
55fn generate_impl_code(VariantInfo { name, field_name, ty }: VariantInfo) -> String {
56    if let Some(field) = field_name {
57        format!(
58            r#"    fn from({field}: {ty}) -> Self {{
59        Self::{name} {{ {field} }}
60    }}"#
61        )
62    } else {
63        format!(
64            r#"    fn from(v: {ty}) -> Self {{
65        Self::{name}(v)
66    }}"#
67        )
68    }
69}
70
71struct VariantInfo {
72    name: ast::Name,
73    field_name: Option<ast::Name>,
74    ty: ast::Type,
75}
76
77fn selected_variants(ctx: &AssistContext<'_>, variant: &ast::Variant) -> Option<Vec<VariantInfo>> {
78    variant
79        .parent_enum()
80        .variant_list()?
81        .variants()
82        .filter(|it| is_selected(it, ctx.selection_trimmed(), true))
83        .map(|variant| {
84            let (name, ty) = extract_variant_info(&ctx.sema, &variant)?;
85            Some(VariantInfo { name: variant.name()?, field_name: name, ty })
86        })
87        .collect()
88}
89
90fn extract_variant_info(
91    sema: &'_ hir::Semantics<'_, RootDatabase>,
92    variant: &ast::Variant,
93) -> Option<(Option<ast::Name>, ast::Type)> {
94    let (field_name, field_type) = match variant.kind() {
95        ast::StructKind::Tuple(field_list) => {
96            if field_list.fields().count() != 1 {
97                return None;
98            }
99            (None, field_list.fields().next()?.ty()?)
100        }
101        ast::StructKind::Record(field_list) => {
102            if field_list.fields().count() != 1 {
103                return None;
104            }
105            let field = field_list.fields().next()?;
106            (Some(field.name()?), field.ty()?)
107        }
108        ast::StructKind::Unit => return None,
109    };
110
111    if existing_from_impl(sema, variant).is_some() {
112        cov_mark::hit!(test_add_from_impl_already_exists);
113        return None;
114    }
115    Some((field_name, field_type))
116}
117
118fn existing_from_impl(
119    sema: &'_ hir::Semantics<'_, RootDatabase>,
120    variant: &ast::Variant,
121) -> Option<()> {
122    let db = sema.db;
123    let variant = sema.to_def(variant)?;
124    let krate = variant.module(db).krate(db);
125    let from_trait = FamousDefs(sema, krate).core_convert_From()?;
126    let interner = DbInterner::new_with(db, krate.base());
127    use hir::next_solver::infer::DbInternerInferExt;
128    let infcx = interner.infer_ctxt().build(TypingMode::non_body_analysis());
129
130    let variant = variant.instantiate_infer(&infcx);
131    let enum_ = variant.parent_enum(sema.db);
132    let field_ty = variant.fields(sema.db).first()?.ty(sema.db);
133    let enum_ty = enum_.ty(sema.db);
134    tracing::debug!(?enum_, ?field_ty, ?enum_ty);
135    enum_ty.impls_trait(infcx, from_trait, &[field_ty]).then_some(())
136}
137
138#[cfg(test)]
139mod tests {
140    use crate::tests::{check_assist, check_assist_not_applicable};
141
142    use super::*;
143
144    #[test]
145    fn test_generate_from_impl_for_enum() {
146        check_assist(
147            generate_from_impl_for_enum,
148            r#"
149//- minicore: from
150enum A { $0One(u32) }
151"#,
152            r#"
153enum A { One(u32) }
154
155impl From<u32> for A {
156    fn from(v: u32) -> Self {
157        Self::One(v)
158    }
159}
160"#,
161        );
162    }
163
164    #[test]
165    fn test_generate_from_impl_for_multiple_enum_variants() {
166        check_assist(
167            generate_from_impl_for_enum,
168            r#"
169//- minicore: from
170enum A { $0Foo(u32), Bar$0(i32) }
171"#,
172            r#"
173enum A { Foo(u32), Bar(i32) }
174
175impl From<u32> for A {
176    fn from(v: u32) -> Self {
177        Self::Foo(v)
178    }
179}
180
181impl From<i32> for A {
182    fn from(v: i32) -> Self {
183        Self::Bar(v)
184    }
185}
186"#,
187        );
188    }
189
190    // FIXME(next-solver): it would be nice to not be *required* to resolve the
191    // path in order to properly generate assists
192    #[test]
193    fn test_generate_from_impl_for_enum_complicated_path() {
194        check_assist(
195            generate_from_impl_for_enum,
196            r#"
197//- minicore: from
198mod foo { pub mod bar { pub mod baz { pub struct Boo; } } }
199enum A { $0One(foo::bar::baz::Boo) }
200"#,
201            r#"
202mod foo { pub mod bar { pub mod baz { pub struct Boo; } } }
203enum A { One(foo::bar::baz::Boo) }
204
205impl From<foo::bar::baz::Boo> for A {
206    fn from(v: foo::bar::baz::Boo) -> Self {
207        Self::One(v)
208    }
209}
210"#,
211        );
212    }
213
214    #[test]
215    fn test_add_from_impl_no_element() {
216        check_assist_not_applicable(
217            generate_from_impl_for_enum,
218            r#"
219//- minicore: from
220enum A { $0One }
221"#,
222        );
223    }
224
225    #[test]
226    fn test_add_from_impl_more_than_one_element_in_tuple() {
227        check_assist_not_applicable(
228            generate_from_impl_for_enum,
229            r#"
230//- minicore: from
231enum A { $0One(u32, String) }
232"#,
233        );
234    }
235
236    #[test]
237    fn test_add_from_impl_struct_variant() {
238        check_assist(
239            generate_from_impl_for_enum,
240            r#"
241//- minicore: from
242enum A { $0One { x: u32 } }
243"#,
244            r#"
245enum A { One { x: u32 } }
246
247impl From<u32> for A {
248    fn from(x: u32) -> Self {
249        Self::One { x }
250    }
251}
252"#,
253        );
254    }
255
256    #[test]
257    fn test_add_from_impl_already_exists() {
258        cov_mark::check!(test_add_from_impl_already_exists);
259        check_assist_not_applicable(
260            generate_from_impl_for_enum,
261            r#"
262//- minicore: from
263enum A { $0One(u32), }
264
265impl From<u32> for A {
266    fn from(v: u32) -> Self {
267        Self::One(v)
268    }
269}
270"#,
271        );
272    }
273
274    #[test]
275    fn test_add_from_impl_different_variant_impl_exists() {
276        check_assist(
277            generate_from_impl_for_enum,
278            r#"
279//- minicore: from
280enum A { $0One(u32), Two(String), }
281
282impl From<String> for A {
283    fn from(v: String) -> Self {
284        A::Two(v)
285    }
286}
287
288pub trait From<T> {
289    fn from(T) -> Self;
290}
291"#,
292            r#"
293enum A { One(u32), Two(String), }
294
295impl From<u32> for A {
296    fn from(v: u32) -> Self {
297        Self::One(v)
298    }
299}
300
301impl From<String> for A {
302    fn from(v: String) -> Self {
303        A::Two(v)
304    }
305}
306
307pub trait From<T> {
308    fn from(T) -> Self;
309}
310"#,
311        );
312    }
313
314    #[test]
315    fn test_add_from_impl_static_str() {
316        check_assist(
317            generate_from_impl_for_enum,
318            r#"
319//- minicore: from
320enum A { $0One(&'static str) }
321"#,
322            r#"
323enum A { One(&'static str) }
324
325impl From<&'static str> for A {
326    fn from(v: &'static str) -> Self {
327        Self::One(v)
328    }
329}
330"#,
331        );
332    }
333
334    #[test]
335    fn test_add_from_impl_generic_enum() {
336        check_assist(
337            generate_from_impl_for_enum,
338            r#"
339//- minicore: from
340enum Generic<T, U: Clone> { $0One(T), Two(U) }
341"#,
342            r#"
343enum Generic<T, U: Clone> { One(T), Two(U) }
344
345impl<T, U: Clone> From<T> for Generic<T, U> {
346    fn from(v: T) -> Self {
347        Self::One(v)
348    }
349}
350"#,
351        );
352    }
353
354    #[test]
355    fn test_add_from_impl_with_lifetime() {
356        check_assist(
357            generate_from_impl_for_enum,
358            r#"
359//- minicore: from
360enum Generic<'a> { $0One(&'a i32) }
361"#,
362            r#"
363enum Generic<'a> { One(&'a i32) }
364
365impl<'a> From<&'a i32> for Generic<'a> {
366    fn from(v: &'a i32) -> Self {
367        Self::One(v)
368    }
369}
370"#,
371        );
372    }
373}