1use hir::Semantics;
2use ide_db::{RootDatabase, assists::AssistId, source_change::SourceChangeBuilder};
3use syntax::{
4 AstNode,
5 ast::{self, Radix},
6};
7
8use crate::{AssistContext, Assists, utils::add_group_separators};
9
10pub(crate) fn add_explicit_enum_discriminant(
32 acc: &mut Assists,
33 ctx: &AssistContext<'_>,
34) -> Option<()> {
35 let enum_node = ctx.find_node_at_offset::<ast::Enum>()?;
36 let enum_def = ctx.sema.to_def(&enum_node)?;
37
38 let is_data_carrying = enum_def.is_data_carrying(ctx.db());
39 let has_primitive_repr = enum_def.repr(ctx.db()).and_then(|repr| repr.int).is_some();
40
41 if is_data_carrying && !has_primitive_repr {
43 return None;
44 }
45
46 let variant_list = enum_node.variant_list()?;
47
48 if variant_list.variants().all(|variant_node| variant_node.expr().is_some()) {
51 return None;
52 }
53
54 acc.add(
55 AssistId::refactor_rewrite("add_explicit_enum_discriminant"),
56 "Add explicit enum discriminants",
57 enum_node.syntax().text_range(),
58 |builder| {
59 let mut radix = Radix::Decimal;
60 for variant_node in variant_list.variants() {
61 add_variant_discriminant(&ctx.sema, builder, &variant_node, &mut radix);
62 }
63 },
64 );
65
66 Some(())
67}
68
69fn add_variant_discriminant(
70 sema: &Semantics<'_, RootDatabase>,
71 builder: &mut SourceChangeBuilder,
72 variant_node: &ast::Variant,
73 radix: &mut Radix,
74) {
75 if let Some(expr) = variant_node.expr() {
76 *radix = expr_radix(&expr).unwrap_or(*radix);
77 return;
78 }
79
80 let Some(variant_def) = sema.to_def(variant_node) else {
81 return;
82 };
83 let Ok(discriminant) = variant_def.eval(sema.db) else {
84 return;
85 };
86
87 let variant_range = variant_node.syntax().text_range();
88
89 let (group_size, prefix, text) = match radix {
90 Radix::Binary => (4, "0b", format!("{discriminant:b}")),
91 Radix::Octal => (3, "0o", format!("{discriminant:o}")),
92 Radix::Decimal => (6, "", discriminant.to_string()),
93 Radix::Hexadecimal => (4, "0x", format!("{discriminant:x}")),
94 };
95 let pretty_num = add_group_separators(&text, group_size);
96 builder.insert(variant_range.end(), format!(" = {prefix}{pretty_num}"));
97}
98
99fn expr_radix(expr: &ast::Expr) -> Option<Radix> {
100 if let ast::Expr::Literal(lit) = expr
101 && let ast::LiteralKind::IntNumber(num) = lit.kind()
102 {
103 Some(num.radix())
104 } else {
105 None
106 }
107}
108
109#[cfg(test)]
110mod tests {
111 use crate::tests::{check_assist, check_assist_not_applicable};
112
113 use super::add_explicit_enum_discriminant;
114
115 #[test]
116 fn non_primitive_repr_non_data_bearing_add_discriminant() {
117 check_assist(
118 add_explicit_enum_discriminant,
119 r#"
120enum TheEnum$0 {
121 Foo,
122 Bar,
123 Baz = 42,
124 Quux,
125 FooBar = -5,
126 FooBaz,
127}
128"#,
129 r#"
130enum TheEnum {
131 Foo = 0,
132 Bar = 1,
133 Baz = 42,
134 Quux = 43,
135 FooBar = -5,
136 FooBaz = -4,
137}
138"#,
139 );
140 }
141
142 #[test]
143 fn primitive_repr_data_bearing_add_discriminant() {
144 check_assist(
145 add_explicit_enum_discriminant,
146 r#"
147#[repr(u8)]
148$0enum TheEnum {
149 Foo { x: u32 },
150 Bar,
151 Baz(String),
152 Quux,
153}
154"#,
155 r#"
156#[repr(u8)]
157enum TheEnum {
158 Foo { x: u32 } = 0,
159 Bar = 1,
160 Baz(String) = 2,
161 Quux = 3,
162}
163"#,
164 );
165 }
166
167 #[test]
168 fn non_primitive_repr_data_bearing_not_applicable() {
169 check_assist_not_applicable(
170 add_explicit_enum_discriminant,
171 r#"
172enum TheEnum$0 {
173 Foo,
174 Bar(u16),
175 Baz,
176}
177"#,
178 );
179 }
180
181 #[test]
182 fn primitive_repr_non_data_bearing_add_discriminant() {
183 check_assist(
184 add_explicit_enum_discriminant,
185 r#"
186#[repr(i64)]
187enum TheEnum {
188 Foo = 1 << 63,
189 Bar,
190 Baz$0 = 0x7fff_ffff_ffff_fffe,
191 Quux,
192}
193"#,
194 r#"
195#[repr(i64)]
196enum TheEnum {
197 Foo = 1 << 63,
198 Bar = -9_223372_036854_775807,
199 Baz = 0x7fff_ffff_ffff_fffe,
200 Quux = 0x7fff_ffff_ffff_ffff,
201}
202"#,
203 );
204 }
205
206 #[test]
207 fn discriminants_already_explicit_not_applicable() {
208 check_assist_not_applicable(
209 add_explicit_enum_discriminant,
210 r#"
211enum TheEnum$0 {
212 Foo = 0,
213 Bar = 4,
214}
215"#,
216 );
217 }
218
219 #[test]
220 fn empty_enum_not_applicable() {
221 check_assist_not_applicable(
222 add_explicit_enum_discriminant,
223 r#"
224enum TheEnum$0 {}
225"#,
226 );
227 }
228}