1use hir::Semantics;
8use ide_db::text_edit::TextEdit;
9use ide_db::{RootDatabase, famous_defs::FamousDefs};
10use syntax::ast::{self, AstNode, HasName};
11
12use crate::{
13 DiscriminantHints, InlayHint, InlayHintLabel, InlayHintPosition, InlayHintsConfig, InlayKind,
14 InlayTooltip,
15};
16
17pub(super) fn enum_hints(
18 acc: &mut Vec<InlayHint>,
19 FamousDefs(sema, _): &FamousDefs<'_, '_>,
20 config: &InlayHintsConfig<'_>,
21 enum_: ast::Enum,
22) -> Option<()> {
23 if let DiscriminantHints::Never = config.discriminant_hints {
24 return None;
25 }
26
27 let def = sema.to_def(&enum_)?;
28 let data_carrying = def.is_data_carrying(sema.db);
29 if matches!(config.discriminant_hints, DiscriminantHints::Fieldless) && data_carrying {
30 return None;
31 }
32 if data_carrying && def.repr(sema.db).is_none_or(|r| r.int.is_none()) {
34 return None;
35 }
36 for variant in enum_.variant_list()?.variants() {
37 variant_hints(acc, config, sema, &enum_, &variant);
38 }
39 Some(())
40}
41
42fn variant_hints(
43 acc: &mut Vec<InlayHint>,
44 config: &InlayHintsConfig<'_>,
45 sema: &Semantics<'_, RootDatabase>,
46 enum_: &ast::Enum,
47 variant: &ast::Variant,
48) -> Option<()> {
49 if variant.expr().is_some() {
50 return None;
51 }
52
53 let eq_token = variant.eq_token();
54 let name = variant.name()?;
55
56 let descended = sema.descend_node_into_attributes(variant.clone()).pop();
57 let desc_pat = descended.as_ref().unwrap_or(variant);
58 let v = sema.to_def(desc_pat)?;
59 let d = v.eval(sema.db);
60
61 let range = match variant.field_list() {
62 Some(field_list) => name.syntax().text_range().cover(field_list.syntax().text_range()),
63 None => name.syntax().text_range(),
64 };
65 let eq_ = if eq_token.is_none() { " =" } else { "" };
66 let label = InlayHintLabel::simple(
67 match d {
68 Ok(val) => {
69 if val >= 10 {
70 format!("{eq_} {val} ({val:#X})")
71 } else {
72 format!("{eq_} {val}")
73 }
74 }
75 Err(_) => format!("{eq_} ?"),
76 },
77 Some(config.lazy_tooltip(|| {
78 InlayTooltip::String(match &d {
79 Ok(_) => "enum variant discriminant".into(),
80 Err(e) => format!("{e:?}"),
81 })
82 })),
83 None,
84 );
85 acc.push(InlayHint {
86 range: match eq_token {
87 Some(t) => range.cover(t.text_range()),
88 _ => range,
89 },
90 kind: InlayKind::Discriminant,
91 label,
92 text_edit: d.ok().map(|val| {
93 config.lazy_text_edit(|| TextEdit::insert(range.end(), format!("{eq_} {val}")))
94 }),
95 position: InlayHintPosition::After,
96 pad_left: false,
97 pad_right: false,
98 resolve_parent: Some(enum_.syntax().text_range()),
99 });
100
101 Some(())
102}
103#[cfg(test)]
104mod tests {
105 use expect_test::expect;
106
107 use crate::inlay_hints::{
108 DiscriminantHints, InlayHintsConfig,
109 tests::{DISABLED_CONFIG, check_edit, check_with_config},
110 };
111
112 #[track_caller]
113 fn check_discriminants(#[rust_analyzer::rust_fixture] ra_fixture: &str) {
114 check_with_config(
115 InlayHintsConfig { discriminant_hints: DiscriminantHints::Always, ..DISABLED_CONFIG },
116 ra_fixture,
117 );
118 }
119
120 #[track_caller]
121 fn check_discriminants_fieldless(#[rust_analyzer::rust_fixture] ra_fixture: &str) {
122 check_with_config(
123 InlayHintsConfig {
124 discriminant_hints: DiscriminantHints::Fieldless,
125 ..DISABLED_CONFIG
126 },
127 ra_fixture,
128 );
129 }
130
131 #[test]
132 fn fieldless() {
133 check_discriminants(
134 r#"
135enum Enum {
136 Variant,
137// ^^^^^^^ = 0$
138 Variant1,
139// ^^^^^^^^ = 1$
140 Variant2,
141// ^^^^^^^^ = 2$
142 Variant5 = 5,
143 Variant6,
144// ^^^^^^^^ = 6$
145}
146"#,
147 );
148 check_discriminants_fieldless(
149 r#"
150enum Enum {
151 Variant,
152// ^^^^^^^ = 0
153 Variant1,
154// ^^^^^^^^ = 1
155 Variant2,
156// ^^^^^^^^ = 2
157 Variant5 = 5,
158 Variant6,
159// ^^^^^^^^ = 6
160}
161"#,
162 );
163 }
164
165 #[test]
166 fn datacarrying_mixed() {
167 check_discriminants(
168 r#"
169#[repr(u8)]
170enum Enum {
171 Variant(),
172// ^^^^^^^^^ = 0
173 Variant1,
174// ^^^^^^^^ = 1
175 Variant2 {},
176// ^^^^^^^^^^^ = 2
177 Variant3,
178// ^^^^^^^^ = 3
179 Variant5 = 5,
180 Variant6,
181// ^^^^^^^^ = 6
182}
183"#,
184 );
185 check_discriminants(
186 r#"
187enum Enum {
188 Variant(),
189 Variant1,
190 Variant2 {},
191 Variant3,
192 Variant5,
193 Variant6,
194}
195"#,
196 );
197 }
198
199 #[test]
200 fn datacarrying_mixed_fieldless_set() {
201 check_discriminants_fieldless(
202 r#"
203#[repr(u8)]
204enum Enum {
205 Variant(),
206 Variant1,
207 Variant2 {},
208 Variant3,
209 Variant5,
210 Variant6,
211}
212"#,
213 );
214 }
215
216 #[test]
217 fn edit() {
218 check_edit(
219 InlayHintsConfig { discriminant_hints: DiscriminantHints::Always, ..DISABLED_CONFIG },
220 r#"
221#[repr(u8)]
222enum Enum {
223 Variant(),
224 Variant1,
225 Variant2 {},
226 Variant3,
227 Variant5,
228 Variant6,
229}
230"#,
231 expect![[r#"
232 #[repr(u8)]
233 enum Enum {
234 Variant() = 0,
235 Variant1 = 1,
236 Variant2 {} = 2,
237 Variant3 = 3,
238 Variant5 = 4,
239 Variant6 = 5,
240 }
241 "#]],
242 );
243 }
244}