1use 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#[derive(Clone, Debug)]
41pub struct SszSchema {
42 constants: Vec<ConstDef>,
43 classes: Vec<ClassDef>,
44 aliases: Vec<AliasDef>,
45}
46
47impl SszSchema {
48 pub fn constants(&self) -> &[ConstDef] {
50 &self.constants
51 }
52
53 pub fn classes(&self) -> &[ClassDef] {
55 &self.classes
56 }
57
58 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#[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 pub fn name(&self) -> &Identifier {
92 &self.name
93 }
94
95 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 pub fn fields(&self) -> &[ClassFieldDef] {
106 &self.fields
107 }
108}
109
110#[derive(Clone, Debug)]
112pub struct ClassFieldDef {
113 name: Identifier,
114 ty: Ty,
115}
116
117impl ClassFieldDef {
118 pub fn name(&self) -> &Identifier {
120 &self.name
121 }
122
123 pub fn ty(&self) -> &Ty {
125 &self.ty
126 }
127}
128
129#[derive(Clone, Debug)]
131pub struct AliasDef {
132 name: Identifier,
133 ty: Ty,
134}
135
136impl AliasDef {
137 pub fn name(&self) -> &Identifier {
139 &self.name
140 }
141
142 pub fn ty(&self) -> &Ty {
144 &self.ty
145 }
146}
147
148pub(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 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 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 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 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 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 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 let mut classes = Vec::new();
290 for d in class_defs {
291 classes.push(conv_classdef(d, &resolver)?);
292 }
293
294 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 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 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}