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