Skip to content

Commit ce3ca99

Browse files
committed
Add explicit enum discriminant assist
Add assist for adding explicit discriminants to all variants of an enum.
1 parent 1850ce3 commit ce3ca99

File tree

3 files changed

+221
-5
lines changed

3 files changed

+221
-5
lines changed

src/tools/rust-analyzer/crates/hir-ty/src/consteval.rs

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ use hir_def::{
1111
ConstBlockLoc, EnumVariantId, GeneralConstId, StaticId,
1212
};
1313
use hir_expand::Lookup;
14-
use stdx::never;
14+
use stdx::{never, IsNoneOr};
1515
use triomphe::Arc;
1616

1717
use crate::{
@@ -169,15 +169,23 @@ pub fn usize_const(db: &dyn HirDatabase, value: Option<u128>, krate: CrateId) ->
169169
}
170170

171171
pub fn try_const_usize(db: &dyn HirDatabase, c: &Const) -> Option<u128> {
172+
try_const_usize_sign_extend(db, c, false)
173+
}
174+
175+
pub fn try_const_usize_sign_extend(
176+
db: &dyn HirDatabase,
177+
c: &Const,
178+
is_signed: bool,
179+
) -> Option<u128> {
172180
match &c.data(Interner).value {
173181
chalk_ir::ConstValue::BoundVar(_) => None,
174182
chalk_ir::ConstValue::InferenceVar(_) => None,
175183
chalk_ir::ConstValue::Placeholder(_) => None,
176184
chalk_ir::ConstValue::Concrete(c) => match &c.interned {
177-
ConstScalar::Bytes(it, _) => Some(u128::from_le_bytes(pad16(it, false))),
185+
ConstScalar::Bytes(it, _) => Some(u128::from_le_bytes(pad16(it, is_signed))),
178186
ConstScalar::UnevaluatedConst(c, subst) => {
179187
let ec = db.const_eval(*c, subst.clone(), None).ok()?;
180-
try_const_usize(db, &ec)
188+
try_const_usize_sign_extend(db, &ec, is_signed)
181189
}
182190
_ => None,
183191
},
@@ -256,8 +264,8 @@ pub(crate) fn const_eval_discriminant_variant(
256264
) -> Result<i128, ConstEvalError> {
257265
let def = variant_id.into();
258266
let body = db.body(def);
267+
let loc = variant_id.lookup(db.upcast());
259268
if body.exprs[body.body_expr] == Expr::Missing {
260-
let loc = variant_id.lookup(db.upcast());
261269
let prev_idx = loc.index.checked_sub(1);
262270
let value = match prev_idx {
263271
Some(prev_idx) => {
@@ -269,13 +277,17 @@ pub(crate) fn const_eval_discriminant_variant(
269277
};
270278
return Ok(value);
271279
}
280+
281+
let repr = db.enum_data(loc.parent).repr;
282+
let is_signed = repr.and_then(|repr| repr.int).is_none_or(|int| int.is_signed());
283+
272284
let mir_body = db.monomorphized_mir_body(
273285
def,
274286
Substitution::empty(Interner),
275287
db.trait_environment_for_body(def),
276288
)?;
277289
let c = interpret_mir(db, mir_body, false, None).0?;
278-
let c = try_const_usize(db, &c).unwrap() as i128;
290+
let c = try_const_usize_sign_extend(db, &c, is_signed).unwrap() as i128;
279291
Ok(c)
280292
}
281293

Lines changed: 202 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,202 @@
1+
use hir::Semantics;
2+
use ide_db::{
3+
assists::{AssistId, AssistKind},
4+
source_change::SourceChangeBuilder,
5+
RootDatabase,
6+
};
7+
use syntax::{ast, AstNode};
8+
9+
use crate::{AssistContext, Assists};
10+
11+
// Assist: explicit_enum_discriminant
12+
//
13+
// Adds explicit discriminant to all enum variants.
14+
//
15+
// ```
16+
// enum TheEnum$0 {
17+
// Foo,
18+
// Bar,
19+
// Baz = 42,
20+
// Quux,
21+
// }
22+
// ```
23+
// ->
24+
// ```
25+
// enum TheEnum {
26+
// Foo = 0,
27+
// Bar = 1,
28+
// Baz = 42,
29+
// Quux = 43,
30+
// }
31+
// ```
32+
pub(crate) fn explicit_enum_discriminant(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
33+
let enum_node = ctx.find_node_at_offset::<ast::Enum>()?;
34+
let enum_def = ctx.sema.to_def(&enum_node)?;
35+
36+
let is_data_carrying = enum_def.is_data_carrying(ctx.db());
37+
let has_primitive_repr = enum_def.repr(ctx.db()).and_then(|repr| repr.int).is_some();
38+
39+
// Data carrying enums without a primitive repr have no stable discriminants.
40+
if is_data_carrying && !has_primitive_repr {
41+
return None;
42+
}
43+
44+
let variant_list = enum_node.variant_list()?;
45+
46+
// Don't offer the assist if the enum has no variants or if all variants already have an
47+
// explicit discriminant.
48+
if variant_list.variants().all(|variant_node| variant_node.expr().is_some()) {
49+
return None;
50+
}
51+
52+
acc.add(
53+
AssistId("explicit_enum_discriminant", AssistKind::RefactorRewrite),
54+
"Add explicit enum discriminants",
55+
enum_node.syntax().text_range(),
56+
|builder| {
57+
for variant_node in variant_list.variants() {
58+
add_variant_discriminant(&ctx.sema, builder, &variant_node);
59+
}
60+
},
61+
);
62+
63+
Some(())
64+
}
65+
66+
fn add_variant_discriminant(
67+
sema: &Semantics<'_, RootDatabase>,
68+
builder: &mut SourceChangeBuilder,
69+
variant_node: &ast::Variant,
70+
) {
71+
if variant_node.expr().is_some() {
72+
return;
73+
}
74+
75+
let Some(variant_def) = sema.to_def(variant_node) else {
76+
return;
77+
};
78+
let Ok(discriminant) = variant_def.eval(sema.db) else {
79+
return;
80+
};
81+
82+
let variant_range = variant_node.syntax().text_range();
83+
84+
builder.insert(variant_range.end(), format!(" = {discriminant}"));
85+
}
86+
87+
#[cfg(test)]
88+
mod tests {
89+
use crate::tests::{check_assist, check_assist_not_applicable};
90+
91+
use super::explicit_enum_discriminant;
92+
93+
#[test]
94+
fn non_primitive_repr_non_data_bearing_add_discriminant() {
95+
check_assist(
96+
explicit_enum_discriminant,
97+
r#"
98+
enum TheEnum$0 {
99+
Foo,
100+
Bar,
101+
Baz = 42,
102+
Quux,
103+
}
104+
"#,
105+
r#"
106+
enum TheEnum {
107+
Foo = 0,
108+
Bar = 1,
109+
Baz = 42,
110+
Quux = 43,
111+
}
112+
"#,
113+
);
114+
}
115+
116+
#[test]
117+
fn primitive_repr_data_bearing_add_discriminant() {
118+
check_assist(
119+
explicit_enum_discriminant,
120+
r#"
121+
#[repr(u8)]
122+
$0enum TheEnum {
123+
Foo { x: u32 },
124+
Bar,
125+
Baz(String),
126+
Quux,
127+
}
128+
"#,
129+
r#"
130+
#[repr(u8)]
131+
enum TheEnum {
132+
Foo { x: u32 } = 0,
133+
Bar = 1,
134+
Baz(String) = 2,
135+
Quux = 3,
136+
}
137+
"#,
138+
);
139+
}
140+
141+
#[test]
142+
fn non_primitive_repr_data_bearing_not_applicable() {
143+
check_assist_not_applicable(
144+
explicit_enum_discriminant,
145+
r#"
146+
enum TheEnum$0 {
147+
Foo,
148+
Bar(u16),
149+
Baz,
150+
}
151+
"#,
152+
);
153+
}
154+
155+
#[test]
156+
fn primitive_repr_non_data_bearing_add_discriminant() {
157+
check_assist(
158+
explicit_enum_discriminant,
159+
r#"
160+
#[repr(i64)]
161+
enum TheEnum {
162+
Foo = 1 << 63,
163+
Bar,
164+
Baz$0 = 0x7fff_ffff_ffff_fffe,
165+
Quux,
166+
}
167+
"#,
168+
r#"
169+
#[repr(i64)]
170+
enum TheEnum {
171+
Foo = 1 << 63,
172+
Bar = -9223372036854775807,
173+
Baz = 0x7fff_ffff_ffff_fffe,
174+
Quux = 9223372036854775807,
175+
}
176+
"#,
177+
);
178+
}
179+
180+
#[test]
181+
fn discriminants_already_explicit_not_applicable() {
182+
check_assist_not_applicable(
183+
explicit_enum_discriminant,
184+
r#"
185+
enum TheEnum$0 {
186+
Foo = 0,
187+
Bar = 4,
188+
}
189+
"#,
190+
);
191+
}
192+
193+
#[test]
194+
fn empty_enum_not_applicable() {
195+
check_assist_not_applicable(
196+
explicit_enum_discriminant,
197+
r#"
198+
enum TheEnum$0 {}
199+
"#,
200+
);
201+
}
202+
}

src/tools/rust-analyzer/crates/ide-assists/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,7 @@ mod handlers {
136136
mod destructure_tuple_binding;
137137
mod desugar_doc_comment;
138138
mod expand_glob_import;
139+
mod explicit_enum_discriminant;
139140
mod extract_expressions_from_format_string;
140141
mod extract_function;
141142
mod extract_module;
@@ -266,6 +267,7 @@ mod handlers {
266267
destructure_tuple_binding::destructure_tuple_binding,
267268
destructure_struct_binding::destructure_struct_binding,
268269
expand_glob_import::expand_glob_import,
270+
explicit_enum_discriminant::explicit_enum_discriminant,
269271
extract_expressions_from_format_string::extract_expressions_from_format_string,
270272
extract_struct_from_enum_variant::extract_struct_from_enum_variant,
271273
extract_type_alias::extract_type_alias,

0 commit comments

Comments
 (0)