1 //! Implementation of "enum variant discriminant" inlay hints:
2 //! ```no_run
3 //! enum Foo {
4 //! Bar/* = 0*/,
5 //! }
6 //! ```
7 use hir::Semantics;
8 use ide_db::{base_db::FileId, famous_defs::FamousDefs, RootDatabase};
9 use syntax::ast::{self, AstNode, HasName};
10
11 use crate::{
12 DiscriminantHints, InlayHint, InlayHintLabel, InlayHintPosition, InlayHintsConfig, InlayKind,
13 InlayTooltip,
14 };
15
enum_hints( acc: &mut Vec<InlayHint>, FamousDefs(sema, _): &FamousDefs<'_, '_>, config: &InlayHintsConfig, _: FileId, enum_: ast::Enum, ) -> Option<()>16 pub(super) fn enum_hints(
17 acc: &mut Vec<InlayHint>,
18 FamousDefs(sema, _): &FamousDefs<'_, '_>,
19 config: &InlayHintsConfig,
20 _: FileId,
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 // data carrying enums without a primitive repr have no stable discriminants
33 if data_carrying && def.repr(sema.db).map_or(true, |r| r.int.is_none()) {
34 return None;
35 }
36 for variant in enum_.variant_list()?.variants() {
37 variant_hints(acc, sema, &variant);
38 }
39 Some(())
40 }
41
variant_hints( acc: &mut Vec<InlayHint>, sema: &Semantics<'_, RootDatabase>, variant: &ast::Variant, ) -> Option<()>42 fn variant_hints(
43 acc: &mut Vec<InlayHint>,
44 sema: &Semantics<'_, RootDatabase>,
45 variant: &ast::Variant,
46 ) -> Option<()> {
47 if variant.expr().is_some() {
48 return None;
49 }
50
51 let eq_token = variant.eq_token();
52 let name = variant.name()?;
53
54 let descended = sema.descend_node_into_attributes(variant.clone()).pop();
55 let desc_pat = descended.as_ref().unwrap_or(variant);
56 let v = sema.to_def(desc_pat)?;
57 let d = v.eval(sema.db);
58
59 let range = match variant.field_list() {
60 Some(field_list) => name.syntax().text_range().cover(field_list.syntax().text_range()),
61 None => name.syntax().text_range(),
62 };
63 let eq_ = if eq_token.is_none() { " =" } else { "" };
64 let label = InlayHintLabel::simple(
65 match d {
66 Ok(x) => {
67 if x >= 10 {
68 format!("{eq_} {x} ({x:#X})")
69 } else {
70 format!("{eq_} {x}")
71 }
72 }
73 Err(_) => format!("{eq_} ?"),
74 },
75 Some(InlayTooltip::String(match &d {
76 Ok(_) => "enum variant discriminant".into(),
77 Err(e) => format!("{e:?}").into(),
78 })),
79 None,
80 );
81 acc.push(InlayHint {
82 range: match eq_token {
83 Some(t) => range.cover(t.text_range()),
84 _ => range,
85 },
86 kind: InlayKind::Discriminant,
87 label,
88 text_edit: None,
89 position: InlayHintPosition::After,
90 pad_left: false,
91 pad_right: false,
92 });
93
94 Some(())
95 }
96 #[cfg(test)]
97 mod tests {
98 use crate::inlay_hints::{
99 tests::{check_with_config, DISABLED_CONFIG},
100 DiscriminantHints, InlayHintsConfig,
101 };
102
103 #[track_caller]
check_discriminants(ra_fixture: &str)104 fn check_discriminants(ra_fixture: &str) {
105 check_with_config(
106 InlayHintsConfig { discriminant_hints: DiscriminantHints::Always, ..DISABLED_CONFIG },
107 ra_fixture,
108 );
109 }
110
111 #[track_caller]
check_discriminants_fieldless(ra_fixture: &str)112 fn check_discriminants_fieldless(ra_fixture: &str) {
113 check_with_config(
114 InlayHintsConfig {
115 discriminant_hints: DiscriminantHints::Fieldless,
116 ..DISABLED_CONFIG
117 },
118 ra_fixture,
119 );
120 }
121
122 #[test]
fieldless()123 fn fieldless() {
124 check_discriminants(
125 r#"
126 enum Enum {
127 Variant,
128 // ^^^^^^^ = 0$
129 Variant1,
130 // ^^^^^^^^ = 1$
131 Variant2,
132 // ^^^^^^^^ = 2$
133 Variant5 = 5,
134 Variant6,
135 // ^^^^^^^^ = 6$
136 }
137 "#,
138 );
139 check_discriminants_fieldless(
140 r#"
141 enum Enum {
142 Variant,
143 // ^^^^^^^ = 0
144 Variant1,
145 // ^^^^^^^^ = 1
146 Variant2,
147 // ^^^^^^^^ = 2
148 Variant5 = 5,
149 Variant6,
150 // ^^^^^^^^ = 6
151 }
152 "#,
153 );
154 }
155
156 #[test]
datacarrying_mixed()157 fn datacarrying_mixed() {
158 check_discriminants(
159 r#"
160 #[repr(u8)]
161 enum Enum {
162 Variant(),
163 // ^^^^^^^^^ = 0
164 Variant1,
165 // ^^^^^^^^ = 1
166 Variant2 {},
167 // ^^^^^^^^^^^ = 2
168 Variant3,
169 // ^^^^^^^^ = 3
170 Variant5 = 5,
171 Variant6,
172 // ^^^^^^^^ = 6
173 }
174 "#,
175 );
176 check_discriminants(
177 r#"
178 enum Enum {
179 Variant(),
180 Variant1,
181 Variant2 {},
182 Variant3,
183 Variant5,
184 Variant6,
185 }
186 "#,
187 );
188 }
189
190 #[test]
datacarrying_mixed_fieldless_set()191 fn datacarrying_mixed_fieldless_set() {
192 check_discriminants_fieldless(
193 r#"
194 #[repr(u8)]
195 enum Enum {
196 Variant(),
197 Variant1,
198 Variant2 {},
199 Variant3,
200 Variant5,
201 Variant6,
202 }
203 "#,
204 );
205 }
206 }
207