1use std::collections::HashMap;
2
3use crate::error::{PanicKind, SoilError};
4use crate::value::Closure;
5
6#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
11pub struct TypeId(pub(crate) u32);
12
13impl TypeId {
14 pub fn index(self) -> u32 {
15 self.0
16 }
17
18 pub fn from_index(index: u32) -> Self {
21 TypeId(index)
22 }
23}
24
25#[derive(Debug, Clone, PartialEq, Eq)]
29pub enum TypeShape {
30 I64,
31 U64,
32 I32,
33 U32,
34 I16,
35 U16,
36 I8,
37 U8,
38 BigInt,
39 F64,
40 Utf8,
41 Bytes,
42 Unit,
43 List(Box<TypeShape>),
44 Map(Box<TypeShape>, Box<TypeShape>),
45 Closure,
49 Named(TypeId),
50}
51
52#[derive(Debug, Clone, Copy, PartialEq, Eq)]
55pub enum Strategy {
56 Structural,
57 Opaque,
58}
59
60#[derive(Debug)]
69pub enum IgnoredDefault {
70 Const(crate::value::Value),
72 CopyField {
74 name: String,
75 index: usize,
79 },
80 Native(Closure),
81}
82
83impl IgnoredDefault {
84 pub fn call(&self, args: &[crate::value::Value]) -> Result<crate::value::Value, SoilError> {
87 match self {
88 IgnoredDefault::Const(value) => Ok(value.clone()),
89 IgnoredDefault::CopyField { index, .. } => Ok(args[*index].clone()),
90 IgnoredDefault::Native(closure) => closure.call(args),
91 }
92 }
93}
94
95#[derive(Debug)]
96pub struct FieldDesc {
97 pub name: String,
98 pub shape: TypeShape,
99 pub ignored: Option<IgnoredDefault>,
103}
104
105#[derive(Debug)]
106pub struct VariantDesc {
107 pub name: String,
108 pub payload: Option<TypeShape>,
111}
112
113#[derive(Debug)]
114pub enum TypeBody {
115 Record(Vec<FieldDesc>),
116 Sum(Vec<VariantDesc>),
117 Opaque,
120}
121
122#[derive(Debug)]
123pub struct TypeDesc {
124 pub name: String,
126 pub strategy: Strategy,
127 pub body: TypeBody,
128}
129
130enum Slot {
131 Declared {
134 name: String,
135 },
136 Defined {
137 desc: TypeDesc,
138 },
139}
140
141pub struct Registry {
151 slots: Vec<Slot>,
152 by_name: HashMap<String, TypeId>,
153 bool_id: TypeId,
154}
155
156impl Default for Registry {
157 fn default() -> Self {
158 Self::new()
159 }
160}
161
162impl Registry {
163 pub fn new() -> Self {
164 let mut registry = Registry {
165 slots: Vec::new(),
166 by_name: HashMap::new(),
167 bool_id: TypeId(0),
168 };
169 registry.bool_id = registry
173 .register(TypeDesc {
174 name: "Bool".to_string(),
175 strategy: Strategy::Structural,
176 body: TypeBody::Sum(vec![
177 VariantDesc {
178 name: "True".to_string(),
179 payload: None,
180 },
181 VariantDesc {
182 name: "False".to_string(),
183 payload: None,
184 },
185 ]),
186 })
187 .expect("Bool registration cannot fail on an empty registry");
188 registry
189 }
190
191 pub fn bool_id(&self) -> TypeId {
192 self.bool_id
193 }
194
195 pub fn declare(&mut self, name: &str) -> Result<TypeId, SoilError> {
196 if self.by_name.contains_key(name) {
197 return Err(SoilError::new(
198 PanicKind::CapiMisuse,
199 format!("type name already registered: {name}"),
200 ));
201 }
202 let id = TypeId(
203 u32::try_from(self.slots.len())
204 .map_err(|_| SoilError::new(PanicKind::CapiMisuse, "type registry full"))?,
205 );
206 self.slots.push(Slot::Declared {
207 name: name.to_string(),
208 });
209 self.by_name.insert(name.to_string(), id);
210 Ok(id)
211 }
212
213 pub fn define(&mut self, id: TypeId, desc: TypeDesc) -> Result<(), SoilError> {
214 let slot = self.slots.get(id.0 as usize).ok_or_else(|| {
215 SoilError::new(PanicKind::CapiMisuse, format!("undefined type id {}", id.0))
216 })?;
217 match slot {
218 Slot::Defined { .. } => {
219 return Err(SoilError::new(
220 PanicKind::CapiMisuse,
221 format!("type id {} is already defined", id.0),
222 ));
223 }
224 Slot::Declared { name } => {
225 if *name != desc.name {
226 return Err(SoilError::new(
227 PanicKind::CapiMisuse,
228 format!(
229 "type id {} was declared as {name} but defined as {}",
230 id.0, desc.name
231 ),
232 ));
233 }
234 }
235 }
236 self.validate(&desc)?;
237 self.slots[id.0 as usize] = Slot::Defined { desc };
238 Ok(())
239 }
240
241 pub fn register(&mut self, desc: TypeDesc) -> Result<TypeId, SoilError> {
246 let name = desc.name.clone();
247 let id = self.declare(&name)?;
248 match self.define(id, desc) {
249 Ok(()) => Ok(id),
250 Err(err) => {
251 self.slots.pop();
252 self.by_name.remove(&name);
253 Err(err)
254 }
255 }
256 }
257
258 pub fn get(&self, id: TypeId) -> Result<&TypeDesc, SoilError> {
259 match self.slots.get(id.0 as usize) {
260 Some(Slot::Defined { desc }) => Ok(desc),
261 Some(Slot::Declared { name }) => Err(SoilError::new(
262 PanicKind::CapiMisuse,
263 format!("type {name} is declared but not defined"),
264 )),
265 None => Err(SoilError::new(
266 PanicKind::CapiMisuse,
267 format!("undefined type id {}", id.0),
268 )),
269 }
270 }
271
272 pub fn lookup(&self, name: &str) -> Option<TypeId> {
273 self.by_name.get(name).copied()
274 }
275
276 pub fn derivable(&self, id: TypeId) -> Result<bool, SoilError> {
286 let mut visited = vec![false; self.slots.len()];
287 self.derivable_walk(id, &mut visited)
288 }
289
290 fn derivable_walk(&self, id: TypeId, visited: &mut [bool]) -> Result<bool, SoilError> {
291 if visited[id.0 as usize] {
292 return Ok(true);
295 }
296 visited[id.0 as usize] = true;
297 let desc = self.get(id)?;
298 match &desc.body {
299 TypeBody::Opaque => Ok(true),
301 TypeBody::Record(fields) => {
302 for field in fields {
303 if !self.shape_derivable(&field.shape, visited)? {
304 return Ok(false);
305 }
306 }
307 Ok(true)
308 }
309 TypeBody::Sum(variants) => {
310 for variant in variants {
311 if let Some(shape) = &variant.payload {
312 if !self.shape_derivable(shape, visited)? {
313 return Ok(false);
314 }
315 }
316 }
317 Ok(true)
318 }
319 }
320 }
321
322 fn shape_derivable(&self, shape: &TypeShape, visited: &mut [bool]) -> Result<bool, SoilError> {
323 match shape {
324 TypeShape::Closure => Ok(false),
325 TypeShape::List(elem) => self.shape_derivable(elem, visited),
326 TypeShape::Map(key, value) => {
327 Ok(self.shape_derivable(key, visited)? && self.shape_derivable(value, visited)?)
328 }
329 TypeShape::Named(id) => self.derivable_walk(*id, visited),
330 _ => Ok(true),
331 }
332 }
333
334 fn validate(&self, desc: &TypeDesc) -> Result<(), SoilError> {
335 if matches!(desc.body, TypeBody::Opaque) && desc.strategy != Strategy::Opaque {
336 return Err(SoilError::new(
337 PanicKind::CapiMisuse,
338 format!(
339 "opaque-bodied type {} must use the opaque strategy",
340 desc.name
341 ),
342 ));
343 }
344 match &desc.body {
345 TypeBody::Record(fields) => {
346 let mut seen = std::collections::HashSet::new();
347 for field in fields {
348 if !seen.insert(field.name.as_str()) {
349 return Err(SoilError::new(
350 PanicKind::CapiMisuse,
351 format!("duplicate field {} in type {}", field.name, desc.name),
352 ));
353 }
354 self.validate_shape(&field.shape, &desc.name)?;
355 if let Some(IgnoredDefault::CopyField { name, index }) = &field.ignored {
356 let target = fields.iter().find(|f| f.name == *name).ok_or_else(|| {
357 SoilError::new(
358 PanicKind::CapiMisuse,
359 format!(
360 "ignored field {} of {} copies unknown field {name}",
361 field.name, desc.name
362 ),
363 )
364 })?;
365 if target.ignored.is_some() {
366 return Err(SoilError::new(
367 PanicKind::CapiMisuse,
368 format!(
369 "ignored field {} of {} may not copy another ignored field",
370 field.name, desc.name
371 ),
372 ));
373 }
374 if target.shape != field.shape {
375 return Err(SoilError::new(
376 PanicKind::CapiMisuse,
377 format!(
378 "ignored field {} of {} copies a field of a different shape",
379 field.name, desc.name
380 ),
381 ));
382 }
383 let expected = fields
384 .iter()
385 .filter(|f| f.ignored.is_none())
386 .position(|f| f.name == *name)
387 .expect("target is non-ignored");
388 if *index != expected {
389 return Err(SoilError::new(
390 PanicKind::CapiMisuse,
391 format!(
392 "ignored field {} of {}: CopyField index {index} does not \
393 match target position {expected}",
394 field.name, desc.name
395 ),
396 ));
397 }
398 }
399 }
400 }
401 TypeBody::Sum(variants) => {
402 if variants.is_empty() {
403 return Err(SoilError::new(
404 PanicKind::CapiMisuse,
405 format!("sum type {} has no variants", desc.name),
406 ));
407 }
408 let mut seen = std::collections::HashSet::new();
409 for variant in variants {
410 if !seen.insert(variant.name.as_str()) {
411 return Err(SoilError::new(
412 PanicKind::CapiMisuse,
413 format!("duplicate variant {} in type {}", variant.name, desc.name),
414 ));
415 }
416 if let Some(shape) = &variant.payload {
417 self.validate_shape(shape, &desc.name)?;
418 }
419 }
420 }
421 TypeBody::Opaque => {}
422 }
423 Ok(())
424 }
425
426 fn validate_shape(&self, shape: &TypeShape, context: &str) -> Result<(), SoilError> {
427 match shape {
428 TypeShape::List(elem) => self.validate_shape(elem, context),
429 TypeShape::Map(key, value) => {
430 self.validate_shape(key, context)?;
431 self.validate_shape(value, context)
432 }
433 TypeShape::Named(id) => {
434 if self.slots.get(id.0 as usize).is_none() {
435 return Err(SoilError::new(
436 PanicKind::CapiMisuse,
437 format!("type {context} references undeclared type id {}", id.0),
438 ));
439 }
440 Ok(())
441 }
442 _ => Ok(()),
443 }
444 }
445}
446
447#[cfg(test)]
448mod tests {
449 use super::*;
450
451 fn record(name: &str, fields: Vec<FieldDesc>) -> TypeDesc {
452 TypeDesc {
453 name: name.to_string(),
454 strategy: Strategy::Structural,
455 body: TypeBody::Record(fields),
456 }
457 }
458
459 fn field(name: &str, shape: TypeShape) -> FieldDesc {
460 FieldDesc {
461 name: name.to_string(),
462 shape,
463 ignored: None,
464 }
465 }
466
467 #[test]
468 fn bool_is_preregistered() {
469 let registry = Registry::new();
470 let id = registry.lookup("Bool").unwrap();
471 assert_eq!(id, registry.bool_id());
472 let desc = registry.get(id).unwrap();
473 match &desc.body {
474 TypeBody::Sum(variants) => {
475 let names: Vec<&str> = variants.iter().map(|v| v.name.as_str()).collect();
476 assert_eq!(names, vec!["True", "False"]);
477 }
478 other => panic!("Bool should be a sum, got {other:?}"),
479 }
480 }
481
482 #[test]
483 fn register_and_lookup_round_trip() {
484 let mut registry = Registry::new();
485 let id = registry
486 .register(record(
487 "SummaryRow",
488 vec![
489 field("count", TypeShape::U64),
490 field("mean", TypeShape::F64),
491 ],
492 ))
493 .unwrap();
494 assert_eq!(registry.lookup("SummaryRow"), Some(id));
495 assert_eq!(registry.get(id).unwrap().name, "SummaryRow");
496 assert!(registry.derivable(id).unwrap());
497 }
498
499 #[test]
500 fn duplicate_names_rejected() {
501 let mut registry = Registry::new();
502 registry.register(record("A", vec![])).unwrap();
503 assert!(registry.register(record("A", vec![])).is_err());
504 assert!(registry.declare("Bool").is_err());
505 }
506
507 #[test]
508 fn recursive_type_via_declare_define() {
509 let mut registry = Registry::new();
510 let tree = registry.declare("Tree").unwrap();
511 registry
512 .define(
513 tree,
514 TypeDesc {
515 name: "Tree".to_string(),
516 strategy: Strategy::Structural,
517 body: TypeBody::Sum(vec![
518 VariantDesc {
519 name: "Leaf".to_string(),
520 payload: Some(TypeShape::I64),
521 },
522 VariantDesc {
523 name: "Node".to_string(),
524 payload: Some(TypeShape::List(Box::new(TypeShape::Named(tree)))),
525 },
526 ]),
527 },
528 )
529 .unwrap();
530 assert!(registry.derivable(tree).unwrap());
531 let pending = registry.declare("Pending").unwrap();
533 assert!(registry.get(pending).is_err());
534 }
535
536 #[test]
537 fn define_checks_name_and_double_definition() {
538 let mut registry = Registry::new();
539 let id = registry.declare("A").unwrap();
540 assert!(registry.define(id, record("B", vec![])).is_err());
541 registry.define(id, record("A", vec![])).unwrap();
542 assert!(registry.define(id, record("A", vec![])).is_err());
543 }
544
545 #[test]
546 fn closure_poisons_derivability_transitively() {
547 let mut registry = Registry::new();
548 let inner = registry
549 .register(record("Inner", vec![field("callback", TypeShape::Closure)]))
550 .unwrap();
551 let outer = registry
552 .register(record(
553 "Outer",
554 vec![field(
555 "items",
556 TypeShape::List(Box::new(TypeShape::Named(inner))),
557 )],
558 ))
559 .unwrap();
560 assert!(!registry.derivable(inner).unwrap());
561 assert!(!registry.derivable(outer).unwrap());
562 }
563
564 #[test]
565 fn opaque_body_requires_opaque_strategy_and_derives() {
566 let mut registry = Registry::new();
567 assert!(registry
568 .register(TypeDesc {
569 name: "Handle".to_string(),
570 strategy: Strategy::Structural,
571 body: TypeBody::Opaque,
572 })
573 .is_err());
574 let id = registry
575 .register(TypeDesc {
576 name: "Handle".to_string(),
577 strategy: Strategy::Opaque,
578 body: TypeBody::Opaque,
579 })
580 .unwrap();
581 assert!(registry.derivable(id).unwrap());
582 }
583
584 #[test]
585 fn sum_must_have_variants_and_unique_names() {
586 let mut registry = Registry::new();
587 assert!(registry
588 .register(TypeDesc {
589 name: "Empty".to_string(),
590 strategy: Strategy::Structural,
591 body: TypeBody::Sum(vec![]),
592 })
593 .is_err());
594 assert!(registry
595 .register(TypeDesc {
596 name: "Dup".to_string(),
597 strategy: Strategy::Structural,
598 body: TypeBody::Sum(vec![
599 VariantDesc {
600 name: "X".to_string(),
601 payload: None
602 },
603 VariantDesc {
604 name: "X".to_string(),
605 payload: None
606 },
607 ]),
608 })
609 .is_err());
610 }
611}