ide_assists/handlers/
generate_default_from_enum_variant.rs

1use ide_db::{RootDatabase, famous_defs::FamousDefs};
2use syntax::ast::{self, AstNode, HasName};
3
4use crate::{AssistContext, AssistId, Assists};
5
6// Assist: generate_default_from_enum_variant
7//
8// Adds a Default impl for an enum using a variant.
9//
10// ```
11// enum Version {
12//  Undefined,
13//  Minor$0,
14//  Major,
15// }
16// ```
17// ->
18// ```
19// enum Version {
20//  Undefined,
21//  Minor,
22//  Major,
23// }
24//
25// impl Default for Version {
26//     fn default() -> Self {
27//         Self::Minor
28//     }
29// }
30// ```
31pub(crate) fn generate_default_from_enum_variant(
32    acc: &mut Assists,
33    ctx: &AssistContext<'_>,
34) -> Option<()> {
35    let variant = ctx.find_node_at_offset::<ast::Variant>()?;
36    let variant_name = variant.name()?;
37    let enum_name = variant.parent_enum().name()?;
38    if !matches!(variant.kind(), ast::StructKind::Unit) {
39        cov_mark::hit!(test_gen_default_on_non_unit_variant_not_implemented);
40        return None;
41    }
42    if !variant.syntax().text_range().contains_range(ctx.selection_trimmed()) {
43        return None;
44    }
45
46    if existing_default_impl(&ctx.sema, &variant).is_some() {
47        cov_mark::hit!(test_gen_default_impl_already_exists);
48        return None;
49    }
50
51    let target = variant.syntax().text_range();
52    acc.add(
53        AssistId::generate("generate_default_from_enum_variant"),
54        "Generate `Default` impl from this enum variant",
55        target,
56        |edit| {
57            let start_offset = variant.parent_enum().syntax().text_range().end();
58            let buf = format!(
59                r#"
60
61impl Default for {enum_name} {{
62    fn default() -> Self {{
63        Self::{variant_name}
64    }}
65}}"#,
66            );
67            edit.insert(start_offset, buf);
68        },
69    )
70}
71
72fn existing_default_impl(
73    sema: &'_ hir::Semantics<'_, RootDatabase>,
74    variant: &ast::Variant,
75) -> Option<()> {
76    let variant = sema.to_def(variant)?;
77    let enum_ = variant.parent_enum(sema.db);
78    let krate = enum_.module(sema.db).krate(sema.db);
79
80    let default_trait = FamousDefs(sema, krate).core_default_Default()?;
81    let enum_type = enum_.ty(sema.db);
82
83    if enum_type.impls_trait(sema.db, default_trait, &[]) { Some(()) } else { None }
84}
85
86#[cfg(test)]
87mod tests {
88    use crate::tests::{check_assist, check_assist_not_applicable};
89
90    use super::*;
91
92    #[test]
93    fn test_generate_default_from_variant() {
94        check_assist(
95            generate_default_from_enum_variant,
96            r#"
97//- minicore: default
98enum Variant {
99    Undefined,
100    Minor$0,
101    Major,
102}
103"#,
104            r#"
105enum Variant {
106    Undefined,
107    Minor,
108    Major,
109}
110
111impl Default for Variant {
112    fn default() -> Self {
113        Self::Minor
114    }
115}
116"#,
117        );
118    }
119
120    #[test]
121    fn test_generate_default_selected_variant() {
122        check_assist(
123            generate_default_from_enum_variant,
124            r#"
125//- minicore: default
126enum Variant {
127    Undefined,
128    $0Minor$0,
129    Major,
130}
131"#,
132            r#"
133enum Variant {
134    Undefined,
135    Minor,
136    Major,
137}
138
139impl Default for Variant {
140    fn default() -> Self {
141        Self::Minor
142    }
143}
144"#,
145        );
146    }
147
148    #[test]
149    fn test_generate_default_not_applicable_with_multiple_variant_selection() {
150        check_assist_not_applicable(
151            generate_default_from_enum_variant,
152            r#"
153//- minicore: default
154enum Variant {
155    Undefined,
156    $0Minor,
157    M$0ajor,
158}
159"#,
160        );
161    }
162
163    #[test]
164    fn test_generate_default_already_implemented() {
165        cov_mark::check!(test_gen_default_impl_already_exists);
166        check_assist_not_applicable(
167            generate_default_from_enum_variant,
168            r#"
169//- minicore: default
170enum Variant {
171    Undefined,
172    Minor$0,
173    Major,
174}
175
176impl Default for Variant {
177    fn default() -> Self {
178        Self::Minor
179    }
180}
181"#,
182        );
183    }
184
185    #[test]
186    fn test_add_from_impl_no_element() {
187        cov_mark::check!(test_gen_default_on_non_unit_variant_not_implemented);
188        check_assist_not_applicable(
189            generate_default_from_enum_variant,
190            r#"
191//- minicore: default
192enum Variant {
193    Undefined,
194    Minor(u32)$0,
195    Major,
196}
197"#,
198        );
199    }
200
201    #[test]
202    fn test_generate_default_from_variant_with_one_variant() {
203        check_assist(
204            generate_default_from_enum_variant,
205            r#"
206//- minicore: default
207enum Variant { Undefi$0ned }
208"#,
209            r#"
210enum Variant { Undefined }
211
212impl Default for Variant {
213    fn default() -> Self {
214        Self::Undefined
215    }
216}
217"#,
218        );
219    }
220}