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.const_arg().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.const_arg()
76 && let Some(expr) = expr.expr()
77 {
78 *radix = expr_radix(&expr).unwrap_or(*radix);
79 return;
80 }
81
82 let Some(variant_def) = sema.to_def(variant_node) else {
83 return;
84 };
85 let Ok(discriminant) = variant_def.eval(sema.db) else {
86 return;
87 };
88
89 let variant_range = variant_node.syntax().text_range();
90
91 let (group_size, prefix, text) = match radix {
92 Radix::Binary => (4, "0b", format!("{discriminant:b}")),
93 Radix::Octal => (3, "0o", format!("{discriminant:o}")),
94 Radix::Decimal => (6, "", discriminant.to_string()),
95 Radix::Hexadecimal => (4, "0x", format!("{discriminant:x}")),
96 };
97 let pretty_num = add_group_separators(&text, group_size);
98 builder.insert(variant_range.end(), format!(" = {prefix}{pretty_num}"));
99}
100
101fn expr_radix(expr: &ast::Expr) -> Option<Radix> {
102 if let ast::Expr::Literal(lit) = expr
103 && let ast::LiteralKind::IntNumber(num) = lit.kind()
104 {
105 Some(num.radix())
106 } else {
107 None
108 }
109}
110
111#[cfg(test)]
112mod tests {
113 use crate::tests::{check_assist, check_assist_not_applicable};
114
115 use super::add_explicit_enum_discriminant;
116
117 #[test]
118 fn non_primitive_repr_non_data_bearing_add_discriminant() {
119 check_assist(
120 add_explicit_enum_discriminant,
121 r#"
122enum TheEnum$0 {
123 Foo,
124 Bar,
125 Baz = 42,
126 Quux,
127 FooBar = -5,
128 FooBaz,
129}
130"#,
131 r#"
132enum TheEnum {
133 Foo = 0,
134 Bar = 1,
135 Baz = 42,
136 Quux = 43,
137 FooBar = -5,
138 FooBaz = -4,
139}
140"#,
141 );
142 }
143
144 #[test]
145 fn primitive_repr_data_bearing_add_discriminant() {
146 check_assist(
147 add_explicit_enum_discriminant,
148 r#"
149#[repr(u8)]
150$0enum TheEnum {
151 Foo { x: u32 },
152 Bar,
153 Baz(String),
154 Quux,
155}
156"#,
157 r#"
158#[repr(u8)]
159enum TheEnum {
160 Foo { x: u32 } = 0,
161 Bar = 1,
162 Baz(String) = 2,
163 Quux = 3,
164}
165"#,
166 );
167 }
168
169 #[test]
170 fn non_primitive_repr_data_bearing_not_applicable() {
171 check_assist_not_applicable(
172 add_explicit_enum_discriminant,
173 r#"
174enum TheEnum$0 {
175 Foo,
176 Bar(u16),
177 Baz,
178}
179"#,
180 );
181 }
182
183 #[test]
184 fn primitive_repr_non_data_bearing_add_discriminant() {
185 check_assist(
186 add_explicit_enum_discriminant,
187 r#"
188#[repr(i64)]
189enum TheEnum {
190 Foo = 1 << 63,
191 Bar,
192 Baz$0 = 0x7fff_ffff_ffff_fffe,
193 Quux,
194}
195"#,
196 r#"
197#[repr(i64)]
198enum TheEnum {
199 Foo = 1 << 63,
200 Bar = -9_223372_036854_775807,
201 Baz = 0x7fff_ffff_ffff_fffe,
202 Quux = 0x7fff_ffff_ffff_ffff,
203}
204"#,
205 );
206 }
207
208 #[test]
209 fn discriminants_already_explicit_not_applicable() {
210 check_assist_not_applicable(
211 add_explicit_enum_discriminant,
212 r#"
213enum TheEnum$0 {
214 Foo = 0,
215 Bar = 4,
216}
217"#,
218 );
219 }
220
221 #[test]
222 fn empty_enum_not_applicable() {
223 check_assist_not_applicable(
224 add_explicit_enum_discriminant,
225 r#"
226enum TheEnum$0 {}
227"#,
228 );
229 }
230}