Skip to content

Commit 0b926e4

Browse files
committed
Auto merge of rust-lang#17985 - riverbl:explicit-enum-discriminant, r=Veykril
Add explicit enum discriminant assist Add assist for adding explicit discriminants to all variants of an enum. Closes rust-lang#17798.
2 parents 28bc643 + d6573f9 commit 0b926e4

File tree

4 files changed

+258
-3
lines changed

4 files changed

+258
-3
lines changed

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

Lines changed: 27 additions & 3 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::{
@@ -184,6 +184,22 @@ pub fn try_const_usize(db: &dyn HirDatabase, c: &Const) -> Option<u128> {
184184
}
185185
}
186186

187+
pub fn try_const_isize(db: &dyn HirDatabase, c: &Const) -> Option<i128> {
188+
match &c.data(Interner).value {
189+
chalk_ir::ConstValue::BoundVar(_) => None,
190+
chalk_ir::ConstValue::InferenceVar(_) => None,
191+
chalk_ir::ConstValue::Placeholder(_) => None,
192+
chalk_ir::ConstValue::Concrete(c) => match &c.interned {
193+
ConstScalar::Bytes(it, _) => Some(i128::from_le_bytes(pad16(it, true))),
194+
ConstScalar::UnevaluatedConst(c, subst) => {
195+
let ec = db.const_eval(*c, subst.clone(), None).ok()?;
196+
try_const_isize(db, &ec)
197+
}
198+
_ => None,
199+
},
200+
}
201+
}
202+
187203
pub(crate) fn const_eval_recover(
188204
_: &dyn HirDatabase,
189205
_: &Cycle,
@@ -256,8 +272,8 @@ pub(crate) fn const_eval_discriminant_variant(
256272
) -> Result<i128, ConstEvalError> {
257273
let def = variant_id.into();
258274
let body = db.body(def);
275+
let loc = variant_id.lookup(db.upcast());
259276
if body.exprs[body.body_expr] == Expr::Missing {
260-
let loc = variant_id.lookup(db.upcast());
261277
let prev_idx = loc.index.checked_sub(1);
262278
let value = match prev_idx {
263279
Some(prev_idx) => {
@@ -269,13 +285,21 @@ pub(crate) fn const_eval_discriminant_variant(
269285
};
270286
return Ok(value);
271287
}
288+
289+
let repr = db.enum_data(loc.parent).repr;
290+
let is_signed = repr.and_then(|repr| repr.int).is_none_or(|int| int.is_signed());
291+
272292
let mir_body = db.monomorphized_mir_body(
273293
def,
274294
Substitution::empty(Interner),
275295
db.trait_environment_for_body(def),
276296
)?;
277297
let c = interpret_mir(db, mir_body, false, None).0?;
278-
let c = try_const_usize(db, &c).unwrap() as i128;
298+
let c = if is_signed {
299+
try_const_isize(db, &c).unwrap()
300+
} else {
301+
try_const_usize(db, &c).unwrap() as i128
302+
};
279303
Ok(c)
280304
}
281305

Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
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+
FooBar = -5,
104+
FooBaz,
105+
}
106+
"#,
107+
r#"
108+
enum TheEnum {
109+
Foo = 0,
110+
Bar = 1,
111+
Baz = 42,
112+
Quux = 43,
113+
FooBar = -5,
114+
FooBaz = -4,
115+
}
116+
"#,
117+
);
118+
}
119+
120+
#[test]
121+
fn primitive_repr_data_bearing_add_discriminant() {
122+
check_assist(
123+
explicit_enum_discriminant,
124+
r#"
125+
#[repr(u8)]
126+
$0enum TheEnum {
127+
Foo { x: u32 },
128+
Bar,
129+
Baz(String),
130+
Quux,
131+
}
132+
"#,
133+
r#"
134+
#[repr(u8)]
135+
enum TheEnum {
136+
Foo { x: u32 } = 0,
137+
Bar = 1,
138+
Baz(String) = 2,
139+
Quux = 3,
140+
}
141+
"#,
142+
);
143+
}
144+
145+
#[test]
146+
fn non_primitive_repr_data_bearing_not_applicable() {
147+
check_assist_not_applicable(
148+
explicit_enum_discriminant,
149+
r#"
150+
enum TheEnum$0 {
151+
Foo,
152+
Bar(u16),
153+
Baz,
154+
}
155+
"#,
156+
);
157+
}
158+
159+
#[test]
160+
fn primitive_repr_non_data_bearing_add_discriminant() {
161+
check_assist(
162+
explicit_enum_discriminant,
163+
r#"
164+
#[repr(i64)]
165+
enum TheEnum {
166+
Foo = 1 << 63,
167+
Bar,
168+
Baz$0 = 0x7fff_ffff_ffff_fffe,
169+
Quux,
170+
}
171+
"#,
172+
r#"
173+
#[repr(i64)]
174+
enum TheEnum {
175+
Foo = 1 << 63,
176+
Bar = -9223372036854775807,
177+
Baz = 0x7fff_ffff_ffff_fffe,
178+
Quux = 9223372036854775807,
179+
}
180+
"#,
181+
);
182+
}
183+
184+
#[test]
185+
fn discriminants_already_explicit_not_applicable() {
186+
check_assist_not_applicable(
187+
explicit_enum_discriminant,
188+
r#"
189+
enum TheEnum$0 {
190+
Foo = 0,
191+
Bar = 4,
192+
}
193+
"#,
194+
);
195+
}
196+
197+
#[test]
198+
fn empty_enum_not_applicable() {
199+
check_assist_not_applicable(
200+
explicit_enum_discriminant,
201+
r#"
202+
enum TheEnum$0 {}
203+
"#,
204+
);
205+
}
206+
}

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,

src/tools/rust-analyzer/crates/ide-assists/src/tests/generated.rs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -909,6 +909,29 @@ fn qux(bar: Bar, baz: Baz) {}
909909
)
910910
}
911911

912+
#[test]
913+
fn doctest_explicit_enum_discriminant() {
914+
check_doc_test(
915+
"explicit_enum_discriminant",
916+
r#####"
917+
enum TheEnum$0 {
918+
Foo,
919+
Bar,
920+
Baz = 42,
921+
Quux,
922+
}
923+
"#####,
924+
r#####"
925+
enum TheEnum {
926+
Foo = 0,
927+
Bar = 1,
928+
Baz = 42,
929+
Quux = 43,
930+
}
931+
"#####,
932+
)
933+
}
934+
912935
#[test]
913936
fn doctest_extract_expressions_from_format_string() {
914937
check_doc_test(

0 commit comments

Comments
 (0)