Skip to main content

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