sizzle_parser/
schema.rs

1//! Schema definitions.
2
3use std::collections::{HashMap, HashSet};
4
5use thiserror::Error;
6
7use crate::{
8    ast::{AssignExpr, ClassDefEntry, Module, ModuleEntry},
9    builtins,
10    ty_resolver::{CrossModuleTypeMap, IdentTarget, ResolverError, TypeData, TypeResolver},
11    tysys::{ConstValue, Ty, TyExpr},
12    Identifier,
13};
14
15#[derive(Debug, Error)]
16pub enum SchemaError {
17    #[error("unknown import '{0:?}'")]
18    UnknownImport(Identifier),
19
20    #[error("unknown import item '{0:?}' in '{1:?}'")]
21    UnknownImportItem(Identifier, Identifier),
22
23    #[error("unsupported import '{0:?}' in '{1:?}'")]
24    UnsupportedImport(Identifier, Identifier),
25
26    #[error("duplicate field name '{0:?}'")]
27    DuplicateFieldName(Identifier),
28
29    #[error("duplcate item name '{0:?}'")]
30    DuplicateItemName(Identifier),
31
32    #[error("found type cycle including type '{0:?}'")]
33    CyclicTypedefs(Identifier),
34
35    #[error("tyresolv: {0}")]
36    Ty(#[from] ResolverError),
37}
38
39/// High level SSZ schema.
40#[derive(Clone, Debug)]
41pub struct SszSchema {
42    constants: Vec<ConstDef>,
43    classes: Vec<ClassDef>,
44    aliases: Vec<AliasDef>,
45}
46
47impl SszSchema {
48    /// All constants in the schema.
49    pub fn constants(&self) -> &[ConstDef] {
50        &self.constants
51    }
52
53    /// All classes in the schema.
54    pub fn classes(&self) -> &[ClassDef] {
55        &self.classes
56    }
57
58    /// All aliases in the schema.
59    pub fn aliases(&self) -> &[AliasDef] {
60        &self.aliases
61    }
62}
63
64#[derive(Clone, Debug)]
65pub struct ConstDef {
66    name: Identifier,
67    value: ConstValue,
68}
69
70impl ConstDef {
71    pub fn name(&self) -> &Identifier {
72        &self.name
73    }
74
75    pub fn value(&self) -> &ConstValue {
76        &self.value
77    }
78}
79
80/// Class definition.
81#[derive(Clone, Debug)]
82pub struct ClassDef {
83    name: Identifier,
84    parent_ty: Ty,
85    doc: Option<String>,
86    fields: Vec<ClassFieldDef>,
87}
88
89impl ClassDef {
90    /// Name of the class.
91    pub fn name(&self) -> &Identifier {
92        &self.name
93    }
94
95    /// Parent type of the class.
96    pub fn parent_ty(&self) -> &Ty {
97        &self.parent_ty
98    }
99
100    pub fn doc(&self) -> Option<&str> {
101        self.doc.as_ref().map(|s| s.as_ref())
102    }
103
104    /// Fields of the class.
105    pub fn fields(&self) -> &[ClassFieldDef] {
106        &self.fields
107    }
108}
109
110/// Class field definition.
111#[derive(Clone, Debug)]
112pub struct ClassFieldDef {
113    name: Identifier,
114    ty: Ty,
115}
116
117impl ClassFieldDef {
118    /// Name of the field.
119    pub fn name(&self) -> &Identifier {
120        &self.name
121    }
122
123    /// Type of the field.
124    pub fn ty(&self) -> &Ty {
125        &self.ty
126    }
127}
128
129/// Type alias definition.
130#[derive(Clone, Debug)]
131pub struct AliasDef {
132    name: Identifier,
133    ty: Ty,
134}
135
136impl AliasDef {
137    /// Name of the alias.
138    pub fn name(&self) -> &Identifier {
139        &self.name
140    }
141
142    /// Concrete type that we are aliasing.
143    pub fn ty(&self) -> &Ty {
144        &self.ty
145    }
146}
147
148/// Converts a AST module to a full schema.
149pub(crate) fn conv_module_to_schema<'a>(
150    m: &Module,
151    cross_module_types: &'a CrossModuleTypeMap<'a>,
152) -> Result<(SszSchema, HashMap<Identifier, IdentTarget>), SchemaError> {
153    let mut resolver = TypeResolver::new(cross_module_types);
154    builtins::populate_builtin_types(&mut resolver);
155
156    // Do a first pass to prepare the type resolver and abort if there's any obvious duplicates.
157    let mut idents = HashMap::new();
158    let mut constants = Vec::new();
159    let mut class_defs = Vec::new();
160    let mut aliases = Vec::new();
161    for d in m.entries() {
162        let name = d.name();
163        if idents.contains_key(name) {
164            return Err(SchemaError::DuplicateItemName(name.clone()));
165        }
166
167        match d {
168            ModuleEntry::Assignment(def) => match def.value() {
169                AssignExpr::Imported(imported) => {
170                    let path = imported.module_path();
171                    let Some(ident_targets) = cross_module_types.get(path) else {
172                        return Err(SchemaError::UnknownImport(imported.module_name().clone()));
173                    };
174
175                    if ident_targets.is_external() {
176                        // Treat all external types as non-const (we have no way of getting the value)
177                        resolver.decl_user_type(name.clone())?;
178                        idents.insert(name.clone(), IdentTarget::Ty(TypeData {}));
179                        aliases.push(AliasDef {
180                            name: name.clone(),
181                            ty: Ty::Imported(
182                                path.clone(),
183                                imported.base_name().clone(),
184                                imported.full_name(),
185                            ),
186                        });
187                        continue;
188                    }
189
190                    let Some(ident_target) = ident_targets.get(imported.base_name()) else {
191                        return Err(SchemaError::UnknownImportItem(
192                            imported.module_name().clone(),
193                            imported.base_name().clone(),
194                        ));
195                    };
196
197                    match ident_target {
198                        IdentTarget::Ty(_) => {
199                            resolver.decl_user_type(name.clone())?;
200                        }
201                        IdentTarget::Const(const_value) => {
202                            resolver.decl_const(name.clone(), const_value.clone())?;
203                        }
204                        _ => {
205                            return Err(SchemaError::UnsupportedImport(
206                                imported.module_name().clone(),
207                                imported.base_name().clone(),
208                            ));
209                        }
210                    }
211
212                    idents.insert(name.clone(), ident_target.clone());
213                    aliases.push(AliasDef {
214                        name: name.clone(),
215                        ty: Ty::Imported(
216                            path.clone(),
217                            imported.base_name().clone(),
218                            imported.full_name(),
219                        ),
220                    });
221                }
222
223                // This is pretty straightforward, we just look up the identifier in-place.
224                AssignExpr::Name(ident) => match resolver.resolve_ident_with_args(ident, None)? {
225                    TyExpr::Ty(ty) => {
226                        resolver.decl_user_type(name.clone())?;
227
228                        idents.insert(name.clone(), IdentTarget::Ty(TypeData {}));
229                        aliases.push(AliasDef {
230                            name: name.clone(),
231                            ty,
232                        })
233                    }
234                    TyExpr::Int(v) => {
235                        resolver.decl_const(name.clone(), v.clone())?;
236
237                        idents.insert(name.clone(), IdentTarget::Const(v.clone()));
238                        constants.push(ConstDef {
239                            name: name.clone(),
240                            value: v,
241                        })
242                    }
243                    TyExpr::None => panic!("schema: assignment to None"),
244                },
245
246                // Complex types we can also handle.
247                AssignExpr::Complex(complex) => {
248                    let resolved_expr = resolver
249                        .resolve_ident_with_args(complex.base_name(), Some(complex.args()))?;
250                    let ty = match resolved_expr {
251                        TyExpr::Ty(ty) => ty,
252                        TyExpr::Int(_) => {
253                            panic!("schema: resolver generated int for complex tyspec")
254                        }
255                        TyExpr::None => {
256                            panic!("schema: resolver generated None for complex tyspec")
257                        }
258                    };
259
260                    // We expose the type into the resolver as a unit type.
261                    resolver.decl_user_type(name.clone())?;
262
263                    idents.insert(name.clone(), IdentTarget::Ty(TypeData {}));
264                    aliases.push(AliasDef {
265                        name: name.clone(),
266                        ty,
267                    })
268                }
269
270                // Values are trivia.
271                AssignExpr::Value(val) => {
272                    resolver.decl_const(name.clone(), val.clone())?;
273                    idents.insert(name.clone(), IdentTarget::Const(val.clone()));
274                    constants.push(ConstDef {
275                        name: name.clone(),
276                        value: val.clone(),
277                    })
278                }
279            },
280            ModuleEntry::Class(def) => {
281                resolver.decl_user_type(name.clone())?;
282                idents.insert(name.clone(), IdentTarget::Ty(TypeData {}));
283                class_defs.push(def);
284            }
285        }
286    }
287
288    // Now actually construct all the classes.
289    let mut classes = Vec::new();
290    for d in class_defs {
291        classes.push(conv_classdef(d, &resolver)?);
292    }
293
294    // Check for cycles.
295    let class_defs = classes
296        .iter()
297        .map(|d| (d.name(), d))
298        .collect::<HashMap<_, _>>();
299    for id in class_defs.keys() {
300        trace_type_for_cycles(id, id, &class_defs)?;
301    }
302
303    // Create a the final schema.
304    let schema = SszSchema {
305        classes,
306        constants,
307        aliases,
308    };
309
310    Ok((schema, idents))
311}
312
313fn conv_classdef<'a>(
314    def: &ClassDefEntry,
315    resolv: &'a TypeResolver<'a>,
316) -> Result<ClassDef, SchemaError> {
317    let mut field_names = HashSet::new();
318    let mut fields = Vec::new();
319
320    for d in def.fields() {
321        let name = d.name().clone();
322        if field_names.contains(&name) {
323            return Err(SchemaError::DuplicateFieldName(name));
324        }
325
326        field_names.insert(name.clone());
327
328        let ty = resolv.resolve_spec_as_ty(d.ty())?;
329        fields.push(ClassFieldDef { name, ty })
330    }
331
332    Ok(ClassDef {
333        name: def.name().clone(),
334        parent_ty: resolv.resolve_spec_as_ty(def.parent_ty())?,
335        doc: def.doc().map(|s| s.to_owned()),
336        fields,
337    })
338}
339
340fn trace_type_for_cycles<'d>(
341    ident: &Identifier,
342    root: &Identifier,
343    defs: &'d HashMap<&'d Identifier, &'d ClassDef>,
344) -> Result<(), SchemaError> {
345    let Some(def) = defs.get(ident) else {
346        // We know that there's no undefined identifiers by this point, so it
347        // not being here means it's a builtin type or a constant.
348        return Ok(());
349    };
350
351    for f in def.fields() {
352        for reffed_id in f.ty().iter_idents() {
353            if reffed_id == root {
354                return Err(SchemaError::CyclicTypedefs(root.clone()));
355            }
356
357            trace_type_for_cycles(reffed_id, root, defs)?;
358        }
359    }
360
361    Ok(())
362}