ide/inlay_hints/
discriminant.rs

1//! Implementation of "enum variant discriminant" inlay hints:
2//! ```no_run
3//! enum Foo {
4//!    Bar/* = 0*/,
5//! }
6//! ```
7use 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    // data carrying enums without a primitive repr have no stable discriminants
33    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}