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