Skip to main content

lisette_semantics/cache/
types.rs

1use rustc_hash::FxHashMap as HashMap;
2
3use ecow::EcoString;
4use serde::{Deserialize, Serialize};
5use syntax::ast::{
6    Annotation, AttributeArg, Generic, Span, StructKind, Visibility as FieldVisibility,
7};
8use syntax::program::{
9    Attributes, Definition, DefinitionBody, Interface, MethodSignatures, Visibility,
10};
11use syntax::types::Type;
12
13/// Span stored as file index + byte offsets.
14/// file_index refers to position in ModuleInterface.files array (sorted by filename).
15/// When loading from cache, file indices are remapped to newly assigned file IDs.
16#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
17pub struct CachedSpan {
18    pub file_index: u32,
19    pub byte_offset: u32,
20    pub byte_length: u32,
21}
22
23impl CachedSpan {
24    pub fn from_span(span: &Span, file_id_to_index: &HashMap<u32, u32>) -> Self {
25        Self {
26            file_index: *file_id_to_index.get(&span.file_id).unwrap_or(&0),
27            byte_offset: span.byte_offset,
28            byte_length: span.byte_length,
29        }
30    }
31
32    pub fn to_span(&self, file_ids: &[u32]) -> Span {
33        Span {
34            file_id: file_ids.get(self.file_index as usize).copied().unwrap_or(0),
35            byte_offset: self.byte_offset,
36            byte_length: self.byte_length,
37        }
38    }
39}
40
41#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
42pub struct CachedGeneric {
43    pub name: String,
44    pub bounds: Vec<Annotation>,
45    pub span: CachedSpan,
46}
47
48impl CachedGeneric {
49    pub fn from_generic(generic: &Generic, file_id_to_index: &HashMap<u32, u32>) -> Self {
50        Self {
51            name: generic.name.to_string(),
52            bounds: generic.bounds.clone(),
53            span: CachedSpan::from_span(&generic.span, file_id_to_index),
54        }
55    }
56
57    pub fn to_generic(&self, file_ids: &[u32]) -> Generic {
58        Generic {
59            name: EcoString::from(self.name.as_str()),
60            bounds: self.bounds.clone(),
61            span: self.span.to_span(file_ids),
62        }
63    }
64}
65
66#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
67pub enum CachedLiteral {
68    Integer { value: u64, text: Option<String> },
69    Float { value: f64, text: Option<String> },
70    Boolean(bool),
71    String(String),
72    Char(String),
73}
74
75impl CachedLiteral {
76    pub fn from_literal(lit: &syntax::ast::Literal) -> Self {
77        use syntax::ast::Literal;
78        match lit {
79            Literal::Integer { value, text } => CachedLiteral::Integer {
80                value: *value,
81                text: text.clone(),
82            },
83            Literal::Float { value, text } => CachedLiteral::Float {
84                value: *value,
85                text: text.clone(),
86            },
87            Literal::Boolean(v) => CachedLiteral::Boolean(*v),
88            Literal::String { value, raw } => {
89                debug_assert!(
90                    !raw,
91                    "cached const literals are canonicalized to non-raw strings"
92                );
93                CachedLiteral::String(value.clone())
94            }
95            Literal::Char(v) => CachedLiteral::Char(v.clone()),
96            // Canonical const literals are never one of these kinds.
97            Literal::Imaginary(_) | Literal::FormatString(_) | Literal::Slice(_) => {
98                CachedLiteral::Integer {
99                    value: 0,
100                    text: None,
101                }
102            }
103        }
104    }
105
106    pub fn to_literal(&self) -> syntax::ast::Literal {
107        use syntax::ast::Literal;
108        match self {
109            CachedLiteral::Integer { value, text } => Literal::Integer {
110                value: *value,
111                text: text.clone(),
112            },
113            CachedLiteral::Float { value, text } => Literal::Float {
114                value: *value,
115                text: text.clone(),
116            },
117            CachedLiteral::Boolean(v) => Literal::Boolean(*v),
118            CachedLiteral::String(v) => Literal::String {
119                value: v.clone(),
120                raw: false,
121            },
122            CachedLiteral::Char(v) => Literal::Char(v.clone()),
123        }
124    }
125}
126
127#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
128pub struct CachedAttribute {
129    pub name: String,
130    pub args: Vec<AttributeArg>,
131}
132
133impl CachedAttribute {
134    pub fn from_attribute(attribute: &syntax::ast::Attribute) -> Self {
135        Self {
136            name: attribute.name.clone(),
137            args: attribute.args.clone(),
138        }
139    }
140
141    pub fn to_attribute(&self) -> syntax::ast::Attribute {
142        syntax::ast::Attribute {
143            name: self.name.clone(),
144            args: self.args.clone(),
145            span: Span::dummy(),
146        }
147    }
148}
149
150#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
151pub struct CachedStructField {
152    pub name: String,
153    pub name_span: CachedSpan,
154    pub ty: Type,
155    pub visibility: FieldVisibility,
156    pub attributes: Vec<CachedAttribute>,
157    pub doc: Option<String>,
158    pub embedded: bool,
159}
160
161impl CachedStructField {
162    pub fn from_field(
163        field: &syntax::ast::StructFieldDefinition,
164        file_id_to_index: &HashMap<u32, u32>,
165    ) -> Self {
166        Self {
167            name: field.name.to_string(),
168            name_span: CachedSpan::from_span(&field.name_span, file_id_to_index),
169            ty: Clone::clone(&field.ty),
170            visibility: field.visibility,
171            attributes: field
172                .attributes
173                .iter()
174                .map(CachedAttribute::from_attribute)
175                .collect(),
176            doc: field.doc.clone(),
177            embedded: field.embedded,
178        }
179    }
180
181    pub fn to_field(&self, file_ids: &[u32]) -> syntax::ast::StructFieldDefinition {
182        syntax::ast::StructFieldDefinition {
183            doc: self.doc.clone(),
184            name: self.name.clone().into(),
185            name_span: self.name_span.to_span(file_ids),
186            ty: self.ty.clone(),
187            visibility: self.visibility,
188            attributes: self.attributes.iter().map(|a| a.to_attribute()).collect(),
189            annotation: Annotation::Unknown,
190            embedded: self.embedded,
191        }
192    }
193}
194
195#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
196pub struct CachedEnumVariant {
197    pub name: String,
198    pub name_span: CachedSpan,
199    pub fields: CachedVariantFields,
200    pub doc: Option<String>,
201}
202
203#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
204pub enum CachedVariantFields {
205    Unit,
206    Tuple(Vec<CachedEnumField>),
207    Struct(Vec<CachedEnumField>),
208}
209
210impl CachedVariantFields {
211    pub fn from_variant_fields(fields: &syntax::ast::VariantFields) -> Self {
212        match fields {
213            syntax::ast::VariantFields::Unit => CachedVariantFields::Unit,
214            syntax::ast::VariantFields::Tuple(fs) => {
215                CachedVariantFields::Tuple(fs.iter().map(CachedEnumField::from_field).collect())
216            }
217            syntax::ast::VariantFields::Struct(fs) => {
218                CachedVariantFields::Struct(fs.iter().map(CachedEnumField::from_field).collect())
219            }
220        }
221    }
222
223    pub fn to_variant_fields(&self) -> syntax::ast::VariantFields {
224        match self {
225            CachedVariantFields::Unit => syntax::ast::VariantFields::Unit,
226            CachedVariantFields::Tuple(fs) => {
227                syntax::ast::VariantFields::Tuple(fs.iter().map(|f| f.to_field()).collect())
228            }
229            CachedVariantFields::Struct(fs) => {
230                syntax::ast::VariantFields::Struct(fs.iter().map(|f| f.to_field()).collect())
231            }
232        }
233    }
234}
235
236impl CachedEnumVariant {
237    pub fn from_variant(
238        variant: &syntax::ast::EnumVariant,
239        file_id_to_index: &HashMap<u32, u32>,
240    ) -> Self {
241        Self {
242            name: variant.name.to_string(),
243            name_span: CachedSpan::from_span(&variant.name_span, file_id_to_index),
244            fields: CachedVariantFields::from_variant_fields(&variant.fields),
245            doc: variant.doc.clone(),
246        }
247    }
248
249    pub fn to_variant(&self, file_ids: &[u32]) -> syntax::ast::EnumVariant {
250        syntax::ast::EnumVariant {
251            doc: self.doc.clone(),
252            name: self.name.clone().into(),
253            name_span: self.name_span.to_span(file_ids),
254            fields: self.fields.to_variant_fields(),
255        }
256    }
257}
258
259#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
260pub struct CachedEnumField {
261    pub name: String,
262    pub ty: Type,
263}
264
265impl CachedEnumField {
266    pub fn from_field(field: &syntax::ast::EnumFieldDefinition) -> Self {
267        Self {
268            name: field.name.to_string(),
269            ty: Clone::clone(&field.ty),
270        }
271    }
272
273    pub fn to_field(&self) -> syntax::ast::EnumFieldDefinition {
274        syntax::ast::EnumFieldDefinition {
275            name: self.name.clone().into(),
276            name_span: Span::dummy(),
277            ty: self.ty.clone(),
278            annotation: Annotation::Unknown,
279        }
280    }
281}
282
283#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
284pub struct CachedInterface {
285    pub name: String,
286    pub generics: Vec<CachedGeneric>,
287    pub parents: Vec<Type>,
288    pub methods: HashMap<String, Type>,
289}
290
291impl CachedInterface {
292    pub fn from_interface(iface: &Interface, file_id_to_index: &HashMap<u32, u32>) -> Self {
293        Self {
294            name: iface.name.to_string(),
295            generics: iface
296                .generics
297                .iter()
298                .map(|g| CachedGeneric::from_generic(g, file_id_to_index))
299                .collect(),
300            parents: iface.parents.iter().map(Clone::clone).collect(),
301            methods: iface
302                .methods
303                .iter()
304                .map(|(k, v)| (k.to_string(), Clone::clone(v)))
305                .collect(),
306        }
307    }
308
309    pub fn to_interface(&self, file_ids: &[u32]) -> Interface {
310        Interface {
311            name: EcoString::from(self.name.as_str()),
312            generics: self
313                .generics
314                .iter()
315                .map(|g| g.to_generic(file_ids))
316                .collect(),
317            parents: self.parents.to_vec(),
318            methods: self
319                .methods
320                .iter()
321                .map(|(k, v)| (EcoString::from(k.as_str()), v.clone()))
322                .collect(),
323        }
324    }
325}
326
327/// Serializable version of Definition. Types are frozen before the cache
328/// writer is reached, so `Var` cannot appear. Mirrors the in-memory
329/// `Definition` shape: common fields up top, variant-specific data in `body`.
330#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
331pub struct CachedDefinition {
332    pub ty: Type,
333    pub name: Option<String>,
334    pub name_span: Option<CachedSpan>,
335    pub doc: Option<String>,
336    pub body: CachedDefinitionBody,
337}
338
339#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
340pub enum CachedDefinitionBody {
341    TypeAlias {
342        generics: Vec<CachedGeneric>,
343        methods: HashMap<String, Type>,
344        is_opaque: bool,
345        attributes: Attributes,
346    },
347    Enum {
348        generics: Vec<CachedGeneric>,
349        variants: Vec<CachedEnumVariant>,
350        methods: HashMap<String, Type>,
351        attributes: Attributes,
352    },
353    Struct {
354        generics: Vec<CachedGeneric>,
355        fields: Vec<CachedStructField>,
356        kind: StructKind,
357        methods: HashMap<String, Type>,
358        constructor: Option<Type>,
359        attributes: Attributes,
360    },
361    Interface {
362        definition: CachedInterface,
363    },
364    Value {
365        allowed_lints: Vec<String>,
366        go_hints: Vec<String>,
367        go_name: Option<String>,
368        const_value: Option<CachedLiteral>,
369    },
370}
371
372impl CachedDefinition {
373    /// Create a CachedDefinition from a Definition.
374    /// Only call this for public definitions that should be cached.
375    pub fn from_definition(definition: &Definition, file_id_to_index: &HashMap<u32, u32>) -> Self {
376        let Definition {
377            ty,
378            name,
379            name_span,
380            doc,
381            body,
382            ..
383        } = definition;
384        let body = match body {
385            DefinitionBody::TypeAlias {
386                generics,
387                annotation,
388                methods,
389                attributes,
390            } => CachedDefinitionBody::TypeAlias {
391                generics: generics
392                    .iter()
393                    .map(|g| CachedGeneric::from_generic(g, file_id_to_index))
394                    .collect(),
395                methods: Self::convert_methods(methods),
396                is_opaque: annotation.is_opaque(),
397                attributes: attributes.clone(),
398            },
399            DefinitionBody::Enum {
400                generics,
401                variants,
402                methods,
403                attributes,
404            } => CachedDefinitionBody::Enum {
405                generics: generics
406                    .iter()
407                    .map(|g| CachedGeneric::from_generic(g, file_id_to_index))
408                    .collect(),
409                variants: variants
410                    .iter()
411                    .map(|v| CachedEnumVariant::from_variant(v, file_id_to_index))
412                    .collect(),
413                methods: Self::convert_methods(methods),
414                attributes: attributes.clone(),
415            },
416            DefinitionBody::Struct {
417                generics,
418                fields,
419                kind,
420                methods,
421                constructor,
422                attributes,
423            } => CachedDefinitionBody::Struct {
424                generics: generics
425                    .iter()
426                    .map(|g| CachedGeneric::from_generic(g, file_id_to_index))
427                    .collect(),
428                fields: fields
429                    .iter()
430                    .map(|f| CachedStructField::from_field(f, file_id_to_index))
431                    .collect(),
432                kind: *kind,
433                methods: Self::convert_methods(methods),
434                constructor: constructor.clone(),
435                attributes: attributes.clone(),
436            },
437            DefinitionBody::Interface { definition } => CachedDefinitionBody::Interface {
438                definition: CachedInterface::from_interface(definition, file_id_to_index),
439            },
440            DefinitionBody::Value {
441                allowed_lints,
442                go_hints,
443                go_name,
444                const_value,
445            } => CachedDefinitionBody::Value {
446                allowed_lints: allowed_lints.clone(),
447                go_hints: go_hints.clone(),
448                go_name: go_name.clone(),
449                const_value: const_value.as_ref().map(CachedLiteral::from_literal),
450            },
451        };
452        CachedDefinition {
453            ty: ty.clone(),
454            name: name.as_ref().map(|n| n.to_string()),
455            name_span: name_span.map(|s| CachedSpan::from_span(&s, file_id_to_index)),
456            doc: doc.clone(),
457            body,
458        }
459    }
460
461    fn convert_methods(methods: &MethodSignatures) -> HashMap<String, Type> {
462        methods
463            .iter()
464            .map(|(k, v)| (k.to_string(), Clone::clone(v)))
465            .collect()
466    }
467
468    fn restore_methods(methods: &HashMap<String, Type>) -> MethodSignatures {
469        methods
470            .iter()
471            .map(|(k, v)| (EcoString::from(k.as_str()), v.clone()))
472            .collect()
473    }
474
475    pub fn to_definition(&self, file_ids: &[u32]) -> Definition {
476        let body = match &self.body {
477            CachedDefinitionBody::TypeAlias {
478                generics,
479                methods,
480                is_opaque,
481                attributes,
482            } => DefinitionBody::TypeAlias {
483                generics: generics.iter().map(|g| g.to_generic(file_ids)).collect(),
484                annotation: if *is_opaque {
485                    Annotation::Opaque {
486                        span: Span::dummy(),
487                    }
488                } else {
489                    Annotation::Unknown
490                },
491                methods: Self::restore_methods(methods),
492                attributes: attributes.clone(),
493            },
494            CachedDefinitionBody::Enum {
495                generics,
496                variants,
497                methods,
498                attributes,
499            } => DefinitionBody::Enum {
500                generics: generics.iter().map(|g| g.to_generic(file_ids)).collect(),
501                variants: variants.iter().map(|v| v.to_variant(file_ids)).collect(),
502                methods: Self::restore_methods(methods),
503                attributes: attributes.clone(),
504            },
505            CachedDefinitionBody::Struct {
506                generics,
507                fields,
508                kind,
509                methods,
510                constructor,
511                attributes,
512            } => DefinitionBody::Struct {
513                generics: generics.iter().map(|g| g.to_generic(file_ids)).collect(),
514                fields: fields.iter().map(|f| f.to_field(file_ids)).collect(),
515                kind: *kind,
516                methods: Self::restore_methods(methods),
517                constructor: constructor.clone(),
518                attributes: attributes.clone(),
519            },
520            CachedDefinitionBody::Interface { definition } => DefinitionBody::Interface {
521                definition: definition.to_interface(file_ids),
522            },
523            CachedDefinitionBody::Value {
524                allowed_lints,
525                go_hints,
526                go_name,
527                const_value,
528            } => DefinitionBody::Value {
529                allowed_lints: allowed_lints.clone(),
530                go_hints: go_hints.clone(),
531                go_name: go_name.clone(),
532                const_value: const_value.as_ref().map(CachedLiteral::to_literal),
533            },
534        };
535        Definition {
536            visibility: Visibility::Public,
537            ty: self.ty.clone(),
538            name: self.name.as_ref().map(|n| EcoString::from(n.as_str())),
539            name_span: self.name_span.as_ref().map(|s| s.to_span(file_ids)),
540            doc: self.doc.clone(),
541            body,
542        }
543    }
544}