• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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