]> git.lizzy.rs Git - rust.git/blob - src/tools/rust-analyzer/crates/hir-def/src/adt.rs
Rollup merge of #102914 - GuillaumeGomez:migrate-css-highlight-without-change, r...
[rust.git] / src / tools / rust-analyzer / crates / hir-def / src / adt.rs
1 //! Defines hir-level representation of structs, enums and unions
2
3 use std::{num::NonZeroU32, sync::Arc};
4
5 use base_db::CrateId;
6 use either::Either;
7 use hir_expand::{
8     name::{AsName, Name},
9     HirFileId, InFile,
10 };
11 use la_arena::{Arena, ArenaMap};
12 use syntax::ast::{self, HasName, HasVisibility};
13 use tt::{Delimiter, DelimiterKind, Leaf, Subtree, TokenTree};
14
15 use crate::{
16     body::{CfgExpander, LowerCtx},
17     builtin_type::{BuiltinInt, BuiltinUint},
18     db::DefDatabase,
19     intern::Interned,
20     item_tree::{AttrOwner, Field, FieldAstId, Fields, ItemTree, ModItem, RawVisibilityId},
21     nameres::diagnostics::DefDiagnostic,
22     src::HasChildSource,
23     src::HasSource,
24     trace::Trace,
25     type_ref::TypeRef,
26     visibility::RawVisibility,
27     EnumId, LocalEnumVariantId, LocalFieldId, LocalModuleId, Lookup, ModuleId, StructId, UnionId,
28     VariantId,
29 };
30 use cfg::CfgOptions;
31
32 /// Note that we use `StructData` for unions as well!
33 #[derive(Debug, Clone, PartialEq, Eq)]
34 pub struct StructData {
35     pub name: Name,
36     pub variant_data: Arc<VariantData>,
37     pub repr: Option<ReprData>,
38     pub visibility: RawVisibility,
39 }
40
41 #[derive(Debug, Clone, PartialEq, Eq)]
42 pub struct EnumData {
43     pub name: Name,
44     pub variants: Arena<EnumVariantData>,
45     pub repr: Option<ReprData>,
46     pub visibility: RawVisibility,
47 }
48
49 #[derive(Debug, Clone, PartialEq, Eq)]
50 pub struct EnumVariantData {
51     pub name: Name,
52     pub variant_data: Arc<VariantData>,
53 }
54
55 #[derive(Debug, Clone, PartialEq, Eq)]
56 pub enum VariantData {
57     Record(Arena<FieldData>),
58     Tuple(Arena<FieldData>),
59     Unit,
60 }
61
62 /// A single field of an enum variant or struct
63 #[derive(Debug, Clone, PartialEq, Eq)]
64 pub struct FieldData {
65     pub name: Name,
66     pub type_ref: Interned<TypeRef>,
67     pub visibility: RawVisibility,
68 }
69
70 #[derive(Copy, Debug, Clone, PartialEq, Eq)]
71 pub enum ReprKind {
72     C,
73     BuiltinInt { builtin: Either<BuiltinInt, BuiltinUint>, is_c: bool },
74     Transparent,
75     Default,
76 }
77
78 #[derive(Copy, Debug, Clone, PartialEq, Eq)]
79 pub struct ReprData {
80     pub kind: ReprKind,
81     pub packed: bool,
82     pub align: Option<NonZeroU32>,
83 }
84
85 fn repr_from_value(
86     db: &dyn DefDatabase,
87     krate: CrateId,
88     item_tree: &ItemTree,
89     of: AttrOwner,
90 ) -> Option<ReprData> {
91     item_tree.attrs(db, krate, of).by_key("repr").tt_values().find_map(parse_repr_tt)
92 }
93
94 fn parse_repr_tt(tt: &Subtree) -> Option<ReprData> {
95     match tt.delimiter {
96         Some(Delimiter { kind: DelimiterKind::Parenthesis, .. }) => {}
97         _ => return None,
98     }
99
100     let mut data = ReprData { kind: ReprKind::Default, packed: false, align: None };
101
102     let mut tts = tt.token_trees.iter().peekable();
103     while let Some(tt) = tts.next() {
104         if let TokenTree::Leaf(Leaf::Ident(ident)) = tt {
105             match &*ident.text {
106                 "packed" => {
107                     data.packed = true;
108                     if let Some(TokenTree::Subtree(_)) = tts.peek() {
109                         tts.next();
110                     }
111                 }
112                 "align" => {
113                     if let Some(TokenTree::Subtree(tt)) = tts.peek() {
114                         tts.next();
115                         if let Some(TokenTree::Leaf(Leaf::Literal(lit))) = tt.token_trees.first() {
116                             if let Ok(align) = lit.text.parse() {
117                                 data.align = Some(align);
118                             }
119                         }
120                     }
121                 }
122                 "C" => {
123                     if let ReprKind::BuiltinInt { is_c, .. } = &mut data.kind {
124                         *is_c = true;
125                     } else {
126                         data.kind = ReprKind::C;
127                     }
128                 }
129                 "transparent" => data.kind = ReprKind::Transparent,
130                 repr => {
131                     let is_c = matches!(data.kind, ReprKind::C);
132                     if let Some(builtin) = BuiltinInt::from_suffix(repr)
133                         .map(Either::Left)
134                         .or_else(|| BuiltinUint::from_suffix(repr).map(Either::Right))
135                     {
136                         data.kind = ReprKind::BuiltinInt { builtin, is_c };
137                     }
138                 }
139             }
140         }
141     }
142
143     Some(data)
144 }
145
146 impl StructData {
147     pub(crate) fn struct_data_query(db: &dyn DefDatabase, id: StructId) -> Arc<StructData> {
148         db.struct_data_with_diagnostics(id).0
149     }
150
151     pub(crate) fn struct_data_with_diagnostics_query(
152         db: &dyn DefDatabase,
153         id: StructId,
154     ) -> (Arc<StructData>, Arc<[DefDiagnostic]>) {
155         let loc = id.lookup(db);
156         let krate = loc.container.krate;
157         let item_tree = loc.id.item_tree(db);
158         let repr = repr_from_value(db, krate, &item_tree, ModItem::from(loc.id.value).into());
159         let cfg_options = db.crate_graph()[loc.container.krate].cfg_options.clone();
160
161         let strukt = &item_tree[loc.id.value];
162         let (variant_data, diagnostics) = lower_fields(
163             db,
164             krate,
165             loc.id.file_id(),
166             loc.container.local_id,
167             &item_tree,
168             &cfg_options,
169             &strukt.fields,
170             None,
171         );
172         (
173             Arc::new(StructData {
174                 name: strukt.name.clone(),
175                 variant_data: Arc::new(variant_data),
176                 repr,
177                 visibility: item_tree[strukt.visibility].clone(),
178             }),
179             diagnostics.into(),
180         )
181     }
182
183     pub(crate) fn union_data_query(db: &dyn DefDatabase, id: UnionId) -> Arc<StructData> {
184         db.union_data_with_diagnostics(id).0
185     }
186
187     pub(crate) fn union_data_with_diagnostics_query(
188         db: &dyn DefDatabase,
189         id: UnionId,
190     ) -> (Arc<StructData>, Arc<[DefDiagnostic]>) {
191         let loc = id.lookup(db);
192         let krate = loc.container.krate;
193         let item_tree = loc.id.item_tree(db);
194         let repr = repr_from_value(db, krate, &item_tree, ModItem::from(loc.id.value).into());
195         let cfg_options = db.crate_graph()[loc.container.krate].cfg_options.clone();
196
197         let union = &item_tree[loc.id.value];
198         let (variant_data, diagnostics) = lower_fields(
199             db,
200             krate,
201             loc.id.file_id(),
202             loc.container.local_id,
203             &item_tree,
204             &cfg_options,
205             &union.fields,
206             None,
207         );
208         (
209             Arc::new(StructData {
210                 name: union.name.clone(),
211                 variant_data: Arc::new(variant_data),
212                 repr,
213                 visibility: item_tree[union.visibility].clone(),
214             }),
215             diagnostics.into(),
216         )
217     }
218 }
219
220 impl EnumData {
221     pub(crate) fn enum_data_query(db: &dyn DefDatabase, e: EnumId) -> Arc<EnumData> {
222         db.enum_data_with_diagnostics(e).0
223     }
224
225     pub(crate) fn enum_data_with_diagnostics_query(
226         db: &dyn DefDatabase,
227         e: EnumId,
228     ) -> (Arc<EnumData>, Arc<[DefDiagnostic]>) {
229         let loc = e.lookup(db);
230         let krate = loc.container.krate;
231         let item_tree = loc.id.item_tree(db);
232         let cfg_options = db.crate_graph()[krate].cfg_options.clone();
233         let repr = repr_from_value(db, krate, &item_tree, ModItem::from(loc.id.value).into());
234
235         let enum_ = &item_tree[loc.id.value];
236         let mut variants = Arena::new();
237         let mut diagnostics = Vec::new();
238         for tree_id in enum_.variants.clone() {
239             let attrs = item_tree.attrs(db, krate, tree_id.into());
240             let var = &item_tree[tree_id];
241             if attrs.is_cfg_enabled(&cfg_options) {
242                 let (var_data, field_diagnostics) = lower_fields(
243                     db,
244                     krate,
245                     loc.id.file_id(),
246                     loc.container.local_id,
247                     &item_tree,
248                     &cfg_options,
249                     &var.fields,
250                     Some(enum_.visibility),
251                 );
252                 diagnostics.extend(field_diagnostics);
253
254                 variants.alloc(EnumVariantData {
255                     name: var.name.clone(),
256                     variant_data: Arc::new(var_data),
257                 });
258             } else {
259                 diagnostics.push(DefDiagnostic::unconfigured_code(
260                     loc.container.local_id,
261                     InFile::new(loc.id.file_id(), var.ast_id.upcast()),
262                     attrs.cfg().unwrap(),
263                     cfg_options.clone(),
264                 ))
265             }
266         }
267
268         (
269             Arc::new(EnumData {
270                 name: enum_.name.clone(),
271                 variants,
272                 repr,
273                 visibility: item_tree[enum_.visibility].clone(),
274             }),
275             diagnostics.into(),
276         )
277     }
278
279     pub fn variant(&self, name: &Name) -> Option<LocalEnumVariantId> {
280         let (id, _) = self.variants.iter().find(|(_id, data)| &data.name == name)?;
281         Some(id)
282     }
283
284     pub fn variant_body_type(&self) -> Either<BuiltinInt, BuiltinUint> {
285         match self.repr {
286             Some(ReprData { kind: ReprKind::BuiltinInt { builtin, .. }, .. }) => builtin,
287             _ => Either::Left(BuiltinInt::Isize),
288         }
289     }
290 }
291
292 impl HasChildSource<LocalEnumVariantId> for EnumId {
293     type Value = ast::Variant;
294     fn child_source(
295         &self,
296         db: &dyn DefDatabase,
297     ) -> InFile<ArenaMap<LocalEnumVariantId, Self::Value>> {
298         let src = self.lookup(db).source(db);
299         let mut trace = Trace::new_for_map();
300         lower_enum(db, &mut trace, &src, self.lookup(db).container);
301         src.with_value(trace.into_map())
302     }
303 }
304
305 fn lower_enum(
306     db: &dyn DefDatabase,
307     trace: &mut Trace<EnumVariantData, ast::Variant>,
308     ast: &InFile<ast::Enum>,
309     module_id: ModuleId,
310 ) {
311     let expander = CfgExpander::new(db, ast.file_id, module_id.krate);
312     let variants = ast
313         .value
314         .variant_list()
315         .into_iter()
316         .flat_map(|it| it.variants())
317         .filter(|var| expander.is_cfg_enabled(db, var));
318     for var in variants {
319         trace.alloc(
320             || var.clone(),
321             || EnumVariantData {
322                 name: var.name().map_or_else(Name::missing, |it| it.as_name()),
323                 variant_data: Arc::new(VariantData::new(db, ast.with_value(var.kind()), module_id)),
324             },
325         );
326     }
327 }
328
329 impl VariantData {
330     fn new(db: &dyn DefDatabase, flavor: InFile<ast::StructKind>, module_id: ModuleId) -> Self {
331         let mut expander = CfgExpander::new(db, flavor.file_id, module_id.krate);
332         let mut trace = Trace::new_for_arena();
333         match lower_struct(db, &mut expander, &mut trace, &flavor) {
334             StructKind::Tuple => VariantData::Tuple(trace.into_arena()),
335             StructKind::Record => VariantData::Record(trace.into_arena()),
336             StructKind::Unit => VariantData::Unit,
337         }
338     }
339
340     pub fn fields(&self) -> &Arena<FieldData> {
341         const EMPTY: &Arena<FieldData> = &Arena::new();
342         match &self {
343             VariantData::Record(fields) | VariantData::Tuple(fields) => fields,
344             _ => EMPTY,
345         }
346     }
347
348     pub fn field(&self, name: &Name) -> Option<LocalFieldId> {
349         self.fields().iter().find_map(|(id, data)| if &data.name == name { Some(id) } else { None })
350     }
351
352     pub fn kind(&self) -> StructKind {
353         match self {
354             VariantData::Record(_) => StructKind::Record,
355             VariantData::Tuple(_) => StructKind::Tuple,
356             VariantData::Unit => StructKind::Unit,
357         }
358     }
359 }
360
361 impl HasChildSource<LocalFieldId> for VariantId {
362     type Value = Either<ast::TupleField, ast::RecordField>;
363
364     fn child_source(&self, db: &dyn DefDatabase) -> InFile<ArenaMap<LocalFieldId, Self::Value>> {
365         let (src, module_id) = match self {
366             VariantId::EnumVariantId(it) => {
367                 // I don't really like the fact that we call into parent source
368                 // here, this might add to more queries then necessary.
369                 let src = it.parent.child_source(db);
370                 (src.map(|map| map[it.local_id].kind()), it.parent.lookup(db).container)
371             }
372             VariantId::StructId(it) => {
373                 (it.lookup(db).source(db).map(|it| it.kind()), it.lookup(db).container)
374             }
375             VariantId::UnionId(it) => (
376                 it.lookup(db).source(db).map(|it| {
377                     it.record_field_list()
378                         .map(ast::StructKind::Record)
379                         .unwrap_or(ast::StructKind::Unit)
380                 }),
381                 it.lookup(db).container,
382             ),
383         };
384         let mut expander = CfgExpander::new(db, src.file_id, module_id.krate);
385         let mut trace = Trace::new_for_map();
386         lower_struct(db, &mut expander, &mut trace, &src);
387         src.with_value(trace.into_map())
388     }
389 }
390
391 #[derive(Debug, Copy, Clone, PartialEq, Eq)]
392 pub enum StructKind {
393     Tuple,
394     Record,
395     Unit,
396 }
397
398 fn lower_struct(
399     db: &dyn DefDatabase,
400     expander: &mut CfgExpander,
401     trace: &mut Trace<FieldData, Either<ast::TupleField, ast::RecordField>>,
402     ast: &InFile<ast::StructKind>,
403 ) -> StructKind {
404     let ctx = LowerCtx::new(db, ast.file_id);
405
406     match &ast.value {
407         ast::StructKind::Tuple(fl) => {
408             for (i, fd) in fl.fields().enumerate() {
409                 if !expander.is_cfg_enabled(db, &fd) {
410                     continue;
411                 }
412
413                 trace.alloc(
414                     || Either::Left(fd.clone()),
415                     || FieldData {
416                         name: Name::new_tuple_field(i),
417                         type_ref: Interned::new(TypeRef::from_ast_opt(&ctx, fd.ty())),
418                         visibility: RawVisibility::from_ast(db, ast.with_value(fd.visibility())),
419                     },
420                 );
421             }
422             StructKind::Tuple
423         }
424         ast::StructKind::Record(fl) => {
425             for fd in fl.fields() {
426                 if !expander.is_cfg_enabled(db, &fd) {
427                     continue;
428                 }
429
430                 trace.alloc(
431                     || Either::Right(fd.clone()),
432                     || FieldData {
433                         name: fd.name().map(|n| n.as_name()).unwrap_or_else(Name::missing),
434                         type_ref: Interned::new(TypeRef::from_ast_opt(&ctx, fd.ty())),
435                         visibility: RawVisibility::from_ast(db, ast.with_value(fd.visibility())),
436                     },
437                 );
438             }
439             StructKind::Record
440         }
441         ast::StructKind::Unit => StructKind::Unit,
442     }
443 }
444
445 fn lower_fields(
446     db: &dyn DefDatabase,
447     krate: CrateId,
448     current_file_id: HirFileId,
449     container: LocalModuleId,
450     item_tree: &ItemTree,
451     cfg_options: &CfgOptions,
452     fields: &Fields,
453     override_visibility: Option<RawVisibilityId>,
454 ) -> (VariantData, Vec<DefDiagnostic>) {
455     let mut diagnostics = Vec::new();
456     match fields {
457         Fields::Record(flds) => {
458             let mut arena = Arena::new();
459             for field_id in flds.clone() {
460                 let attrs = item_tree.attrs(db, krate, field_id.into());
461                 let field = &item_tree[field_id];
462                 if attrs.is_cfg_enabled(cfg_options) {
463                     arena.alloc(lower_field(item_tree, field, override_visibility));
464                 } else {
465                     diagnostics.push(DefDiagnostic::unconfigured_code(
466                         container,
467                         InFile::new(
468                             current_file_id,
469                             match field.ast_id {
470                                 FieldAstId::Record(it) => it.upcast(),
471                                 FieldAstId::Tuple(it) => it.upcast(),
472                             },
473                         ),
474                         attrs.cfg().unwrap(),
475                         cfg_options.clone(),
476                     ))
477                 }
478             }
479             (VariantData::Record(arena), diagnostics)
480         }
481         Fields::Tuple(flds) => {
482             let mut arena = Arena::new();
483             for field_id in flds.clone() {
484                 let attrs = item_tree.attrs(db, krate, field_id.into());
485                 let field = &item_tree[field_id];
486                 if attrs.is_cfg_enabled(cfg_options) {
487                     arena.alloc(lower_field(item_tree, field, override_visibility));
488                 } else {
489                     diagnostics.push(DefDiagnostic::unconfigured_code(
490                         container,
491                         InFile::new(
492                             current_file_id,
493                             match field.ast_id {
494                                 FieldAstId::Record(it) => it.upcast(),
495                                 FieldAstId::Tuple(it) => it.upcast(),
496                             },
497                         ),
498                         attrs.cfg().unwrap(),
499                         cfg_options.clone(),
500                     ))
501                 }
502             }
503             (VariantData::Tuple(arena), diagnostics)
504         }
505         Fields::Unit => (VariantData::Unit, diagnostics),
506     }
507 }
508
509 fn lower_field(
510     item_tree: &ItemTree,
511     field: &Field,
512     override_visibility: Option<RawVisibilityId>,
513 ) -> FieldData {
514     FieldData {
515         name: field.name.clone(),
516         type_ref: field.type_ref.clone(),
517         visibility: item_tree[override_visibility.unwrap_or(field.visibility)].clone(),
518     }
519 }