1use crate::node::{MatchArm, Node, NodeHash, Produces};
32use crate::store::{Result, Store};
33use crate::ty::{Confidence, Effect, Type};
34use serde::Serialize;
35use std::cell::Cell;
36use std::collections::{BTreeSet, HashMap};
37
38#[derive(Clone, Copy, PartialEq, Eq, Debug, Serialize)]
40pub enum Status {
41 Complete,
43 Incomplete,
45}
46
47#[derive(Clone, PartialEq, Eq, Debug, Serialize)]
49pub enum Failures {
50 Exhaustive,
51 Uncovered(Vec<String>),
52}
53
54#[derive(Clone, PartialEq, Eq, Debug, Serialize)]
57pub struct Violation {
58 pub principle: u8,
59 pub node: NodeHash,
60 pub detail: String,
61}
62
63#[derive(Clone, PartialEq, Eq, Debug, Serialize)]
65pub struct Report {
66 pub node: NodeHash,
67 pub status: Status,
68 pub holes: Vec<NodeHash>,
69 pub effects: BTreeSet<Effect>,
71 pub confidence: Option<Confidence>,
73 pub failures: Failures,
74 pub violations: Vec<Violation>,
75}
76
77impl Report {
78 pub fn ok(&self) -> bool {
81 self.violations.is_empty()
82 }
83}
84
85#[derive(Clone)]
87struct Signature {
88 type_params: Vec<String>,
89 params: Vec<crate::node::Param>,
90 produces: Produces,
91 requires: BTreeSet<Effect>,
92 on_failure: Vec<String>,
93}
94
95#[derive(Clone, Default)]
98struct SubtreeFacts {
99 holes: Vec<NodeHash>,
100 missing: Vec<NodeHash>,
101}
102
103struct FnResult {
105 result_confidence: Option<Confidence>,
106 effects: BTreeSet<Effect>,
107 uncovered_failures: Vec<String>,
108}
109
110type Scope = Vec<(String, Option<(Type, Confidence)>)>;
114
115type RecordTable = HashMap<String, Vec<(String, Type)>>;
117type VariantTable = HashMap<String, Vec<(String, Vec<(String, Type)>)>>;
119
120pub struct Checker<'a> {
123 store: &'a Store,
124 facts: HashMap<NodeHash, SubtreeFacts>,
125 records: RecordTable,
128 variants: VariantTable,
129 computed: Cell<u64>,
130}
131
132impl<'a> Checker<'a> {
133 pub fn new(store: &'a Store) -> Self {
134 Self {
135 store,
136 facts: HashMap::new(),
137 records: HashMap::new(),
138 variants: HashMap::new(),
139 computed: Cell::new(0),
140 }
141 }
142
143 pub fn computed_count(&self) -> u64 {
146 self.computed.get()
147 }
148
149 pub fn check(&mut self, root: &NodeHash) -> Result<Report> {
151 let Some(root_node) = self.store.get(root)? else {
152 return Ok(Report {
153 node: root.clone(),
154 status: Status::Complete,
155 holes: Vec::new(),
156 effects: BTreeSet::new(),
157 confidence: None,
158 failures: Failures::Exhaustive,
159 violations: vec![Violation {
160 principle: 4,
161 node: root.clone(),
162 detail: "root node is not present in the store".into(),
163 }],
164 });
165 };
166
167 let facts = self.subtree_facts(root)?;
168 let mut violations: Vec<Violation> = facts
169 .missing
170 .iter()
171 .map(|h| Violation {
172 principle: 4,
173 node: h.clone(),
174 detail: "referenced child node is not present in the store".into(),
175 })
176 .collect();
177
178 let sigs = self.signatures(root, &root_node)?;
179 self.records = self.record_defs(&root_node)?;
180 self.variants = self.variant_defs(&root_node)?;
181 let mut effects = BTreeSet::new();
182 let mut confidence = None;
183 let mut uncovered: Vec<String> = Vec::new();
184
185 match &root_node {
186 Node::Function { .. } => {
187 let fr = self.check_function(root, &root_node, &sigs, &mut violations)?;
188 effects = fr.effects;
189 confidence = fr.result_confidence;
190 uncovered = fr.uncovered_failures;
191 }
192 Node::Module { functions, .. } => {
193 for fh in functions {
194 if let Some(fnode) = self.store.get(fh)? {
195 let fr = self.check_function(fh, &fnode, &sigs, &mut violations)?;
196 effects.extend(fr.effects);
197 for u in fr.uncovered_failures {
198 if !uncovered.contains(&u) {
199 uncovered.push(u);
200 }
201 }
202 }
203 }
204 }
205 _ => {
206 let mut fl = BTreeSet::new();
208 confidence = self
209 .walk_expr(root, &[], &sigs, &mut violations, &mut effects, &mut fl)?
210 .map(|(_, c)| c);
211 }
212 }
213
214 Ok(Report {
215 node: root.clone(),
216 status: if facts.holes.is_empty() {
217 Status::Complete
218 } else {
219 Status::Incomplete
220 },
221 holes: facts.holes,
222 effects,
223 confidence,
224 failures: if uncovered.is_empty() {
225 Failures::Exhaustive
226 } else {
227 Failures::Uncovered(uncovered)
228 },
229 violations,
230 })
231 }
232
233 fn subtree_facts(&mut self, hash: &NodeHash) -> Result<SubtreeFacts> {
236 if let Some(cached) = self.facts.get(hash) {
237 return Ok(cached.clone());
238 }
239 self.computed.set(self.computed.get() + 1);
240
241 let node = self
242 .store
243 .get(hash)?
244 .expect("subtree_facts caller guarantees presence");
245
246 let mut facts = SubtreeFacts::default();
247 if let Node::Hole { .. } = node {
248 facts.holes.push(hash.clone());
249 }
250 for child in child_hashes(&node) {
251 match self.store.get(child)? {
252 None => facts.missing.push(child.clone()),
253 Some(_) => {
254 let sub = self.subtree_facts(child)?;
255 facts.holes.extend(sub.holes);
256 facts.missing.extend(sub.missing);
257 }
258 }
259 }
260
261 self.facts.insert(hash.clone(), facts.clone());
262 Ok(facts)
263 }
264
265 fn signatures(&self, _root: &NodeHash, root_node: &Node) -> Result<HashMap<String, Signature>> {
268 let mut sigs = HashMap::new();
269 let mut add = |node: &Node| {
270 if let Node::Function {
271 name,
272 type_params,
273 params,
274 produces,
275 requires,
276 on_failure,
277 ..
278 } = node
279 {
280 sigs.insert(
281 name.clone(),
282 Signature {
283 type_params: type_params.clone(),
284 params: params.clone(),
285 produces: produces.clone(),
286 requires: requires.clone(),
287 on_failure: on_failure.clone(),
288 },
289 );
290 }
291 };
292 match root_node {
293 Node::Function { .. } => add(root_node),
294 Node::Module { functions, .. } => {
295 for fh in functions {
296 if let Some(fnode) = self.store.get(fh)? {
297 add(&fnode);
298 }
299 }
300 }
301 _ => {}
302 }
303 Ok(sigs)
304 }
305
306 fn record_defs(&self, root_node: &Node) -> Result<RecordTable> {
309 let mut recs = HashMap::new();
310 let mut add = |node: &Node| {
311 if let Node::RecordDef { name, fields } = node {
312 recs.insert(name.clone(), fields.clone());
313 }
314 };
315 match root_node {
316 Node::RecordDef { .. } => add(root_node),
317 Node::Module { types, .. } => {
318 for th in types {
319 if let Some(tnode) = self.store.get(th)? {
320 add(&tnode);
321 }
322 }
323 }
324 _ => {}
325 }
326 Ok(recs)
327 }
328
329 fn variant_defs(&self, root_node: &Node) -> Result<VariantTable> {
332 let mut vars = HashMap::new();
333 let mut add = |node: &Node| {
334 if let Node::VariantDef { name, cases } = node {
335 vars.insert(name.clone(), cases.clone());
336 }
337 };
338 match root_node {
339 Node::VariantDef { .. } => add(root_node),
340 Node::Module { types, .. } => {
341 for th in types {
342 if let Some(tnode) = self.store.get(th)? {
343 add(&tnode);
344 }
345 }
346 }
347 _ => {}
348 }
349 Ok(vars)
350 }
351
352 fn check_function(
354 &self,
355 fn_hash: &NodeHash,
356 fn_node: &Node,
357 sigs: &HashMap<String, Signature>,
358 out: &mut Vec<Violation>,
359 ) -> Result<FnResult> {
360 let Node::Function {
361 params,
362 produces,
363 requires,
364 on_failure,
365 body,
366 result,
367 ..
368 } = fn_node
369 else {
370 return Ok(FnResult {
371 result_confidence: None,
372 effects: BTreeSet::new(),
373 uncovered_failures: Vec::new(),
374 });
375 };
376
377 let mut scope: Scope = params
378 .iter()
379 .map(|p| (p.name.clone(), Some((p.ty.clone(), p.min_confidence))))
380 .collect();
381 let mut effects = BTreeSet::new();
382 let mut failures = BTreeSet::new();
383
384 for step_hash in body {
385 let Some(step) = self.store.get(step_hash)? else {
386 continue;
387 };
388 if let Node::Step { binding, value } = &step {
389 let v = self
390 .walk_expr(value, &scope, sigs, out, &mut effects, &mut failures)?;
391 scope.push((binding.clone(), v));
392 }
393 }
394
395 let result_confidence = match self
396 .walk_expr(result, &scope, sigs, out, &mut effects, &mut failures)?
397 {
398 Some((rt, rc)) => {
399 if !compatible(&rt, &produces.ty) {
400 out.push(Violation {
401 principle: 2,
402 node: result.clone(),
403 detail: format!(
404 "result is {:?} but `produces` declares {:?}",
405 rt, produces.ty
406 ),
407 });
408 }
409 if rc < produces.confidence {
410 out.push(Violation {
411 principle: 7,
412 node: result.clone(),
413 detail: format!(
414 "result confidence {:?} is weaker than declared {:?}",
415 rc, produces.confidence
416 ),
417 });
418 }
419 Some(rc)
420 }
421 None => None,
422 };
423
424 for e in &effects {
426 if !requires.contains(e) {
427 out.push(Violation {
428 principle: 5,
429 node: fn_hash.clone(),
430 detail: format!("performs effect {e:?} not declared in `requires`"),
431 });
432 }
433 }
434
435 let mut uncovered_failures = Vec::new();
438 for f in &failures {
439 if !on_failure.contains(f) {
440 uncovered_failures.push(f.clone());
441 out.push(Violation {
442 principle: 6,
443 node: fn_hash.clone(),
444 detail: format!("failure `{f}` is not covered by `on_failure`"),
445 });
446 }
447 }
448
449 Ok(FnResult {
450 result_confidence,
451 effects,
452 uncovered_failures,
453 })
454 }
455
456 #[allow(clippy::only_used_in_recursion)]
461 fn walk_expr(
462 &self,
463 hash: &NodeHash,
464 scope: &[(String, Option<(Type, Confidence)>)],
465 sigs: &HashMap<String, Signature>,
466 out: &mut Vec<Violation>,
467 effects: &mut BTreeSet<Effect>,
468 failures: &mut BTreeSet<String>,
469 ) -> Result<Option<(Type, Confidence)>> {
470 let Some(node) = self.store.get(hash)? else {
471 return Ok(None);
473 };
474 Ok(match node {
475 Node::Lit(_) => Some((Type::Number, Confidence::Structural)),
476 Node::FloatLit(_) => Some((Type::Float, Confidence::Structural)),
477 Node::FloatOp { op, lhs, rhs } => {
478 let l = self.walk_expr(&lhs, scope, sigs, out, effects, failures)?;
479 let r = self.walk_expr(&rhs, scope, sigs, out, effects, failures)?;
480 for (h, t) in [(&lhs, &l), (&rhs, &r)] {
481 if let Some((ty, _)) = t {
482 if *ty != Type::Float && *ty != Type::Never {
483 out.push(Violation {
484 principle: 2,
485 node: h.clone(),
486 detail: format!(
487 "float operand is {ty:?}, expected Float"
488 ),
489 });
490 }
491 }
492 }
493 let conf = match (&l, &r) {
494 (Some((_, a)), Some((_, b))) => (*a).min(*b),
495 _ => Confidence::Structural,
496 };
497 use crate::node::BinOp as B;
498 if op.is_logical() || op == B::Mod || op == B::Neq {
499 out.push(Violation {
500 principle: 2,
501 node: hash.clone(),
502 detail: format!(
503 "operator `{}` is not defined on Float",
504 op.symbol()
505 ),
506 });
507 None
508 } else if op.is_comparison() {
509 Some((Type::Bool, conf))
510 } else {
511 Some((Type::Float, conf))
512 }
513 }
514 Node::IntToFloat(a) => {
515 let t = self.walk_expr(&a, scope, sigs, out, effects, failures)?;
516 if let Some((ty, c)) = t {
517 if ty != Type::Number && ty != Type::Never {
518 out.push(Violation {
519 principle: 2,
520 node: a.clone(),
521 detail: format!("to_float expects Number, got {ty:?}"),
522 });
523 }
524 Some((Type::Float, c))
525 } else {
526 None
527 }
528 }
529 Node::FloatToInt(a) => {
530 let t = self.walk_expr(&a, scope, sigs, out, effects, failures)?;
531 if let Some((ty, c)) = t {
532 if ty != Type::Float && ty != Type::Never {
533 out.push(Violation {
534 principle: 2,
535 node: a.clone(),
536 detail: format!("to_int expects Float, got {ty:?}"),
537 });
538 }
539 Some((Type::Number, c))
540 } else {
541 None
542 }
543 }
544 Node::DecimalLit(_) => {
545 Some((Type::Decimal, Confidence::Structural))
546 }
547 Node::DecimalOp { op, lhs, rhs } => {
548 let l = self.walk_expr(&lhs, scope, sigs, out, effects, failures)?;
549 let r = self.walk_expr(&rhs, scope, sigs, out, effects, failures)?;
550 for (h, t) in [(&lhs, &l), (&rhs, &r)] {
551 if let Some((ty, _)) = t {
552 if *ty != Type::Decimal && *ty != Type::Never {
553 out.push(Violation {
554 principle: 2,
555 node: h.clone(),
556 detail: format!(
557 "decimal operand is {ty:?}, expected Decimal"
558 ),
559 });
560 }
561 }
562 }
563 let conf = match (&l, &r) {
564 (Some((_, a)), Some((_, b))) => (*a).min(*b),
565 _ => Confidence::Structural,
566 };
567 use crate::node::BinOp as B;
568 if op.is_logical() || op == B::Mod {
569 out.push(Violation {
570 principle: 2,
571 node: hash.clone(),
572 detail: format!(
573 "operator `{}` is not defined on Decimal",
574 op.symbol()
575 ),
576 });
577 None
578 } else if op.is_comparison() {
579 Some((Type::Bool, conf))
580 } else {
581 Some((Type::Decimal, conf))
582 }
583 }
584 Node::IntToDecimal(a) => {
585 let t = self.walk_expr(&a, scope, sigs, out, effects, failures)?;
586 if let Some((ty, c)) = t {
587 if ty != Type::Number && ty != Type::Never {
588 out.push(Violation {
589 principle: 2,
590 node: a.clone(),
591 detail: format!(
592 "to_decimal expects Number, got {ty:?}"
593 ),
594 });
595 }
596 Some((Type::Decimal, c))
597 } else {
598 None
599 }
600 }
601 Node::DecimalToInt(a) | Node::DecimalRaw(a) => {
602 let t = self.walk_expr(&a, scope, sigs, out, effects, failures)?;
603 if let Some((ty, c)) = t {
604 if ty != Type::Decimal && ty != Type::Never {
605 out.push(Violation {
606 principle: 2,
607 node: a.clone(),
608 detail: format!(
609 "expects Decimal, got {ty:?}"
610 ),
611 });
612 }
613 Some((Type::Number, c))
614 } else {
615 None
616 }
617 }
618 Node::Bool(_) => Some((Type::Bool, Confidence::Structural)),
619 Node::Not(arg) => {
620 let a = self.walk_expr(&arg, scope, sigs, out, effects, failures)?;
621 if let Some((at, ac)) = a {
622 if at != Type::Bool && at != Type::Never {
623 out.push(Violation {
624 principle: 2,
625 node: arg.clone(),
626 detail: format!("`!` operand is {at:?}, expected Bool"),
627 });
628 }
629 Some((Type::Bool, ac))
630 } else {
631 None
632 }
633 }
634 Node::Str(_) => Some((Type::String, Confidence::Structural)),
635 Node::Now => {
636 effects.insert(Effect::Time);
640 Some((Type::Number, Confidence::External))
641 }
642 Node::List(elems) => {
643 if elems.is_empty() {
644 out.push(Violation {
645 principle: 2,
646 node: hash.clone(),
647 detail: "empty list literal needs a type annotation (v0.3)"
648 .into(),
649 });
650 return Ok(None);
651 }
652 let mut elem_ty: Option<Type> = None;
653 let mut conf = Confidence::Persisted;
654 for e in &elems {
655 if let Some((t, c)) =
656 self.walk_expr(e, scope, sigs, out, effects, failures)?
657 {
658 match &elem_ty {
659 None => elem_ty = Some(t),
660 Some(et) => {
661 if !compatible(et, &t) {
662 out.push(Violation {
663 principle: 2,
664 node: e.clone(),
665 detail: format!(
666 "list element is {t:?}, expected {et:?}"
667 ),
668 });
669 }
670 }
671 }
672 conf = conf.min(c);
673 }
674 }
675 elem_ty.map(|t| (Type::List(Box::new(t)), conf))
676 }
677 Node::ListEmpty { elem } => {
678 Some((Type::List(Box::new(elem)), Confidence::Structural))
679 }
680 Node::ListCons { head, tail } => {
681 let ht = self.walk_expr(&head, scope, sigs, out, effects, failures)?;
682 let tt = self.walk_expr(&tail, scope, sigs, out, effects, failures)?;
683 match tt {
684 Some((Type::List(et), tc)) => {
685 if let Some((h, _)) = &ht {
686 if !compatible(&et, h) {
687 out.push(Violation {
688 principle: 2,
689 node: head.clone(),
690 detail: format!(
691 "cons head is {h:?} but the list is List<{et:?}>"
692 ),
693 });
694 }
695 }
696 let hc =
697 ht.map(|(_, c)| c).unwrap_or(Confidence::Persisted);
698 Some((Type::List(et), hc.min(tc)))
699 }
700 Some((other, _)) => {
701 out.push(Violation {
702 principle: 2,
703 node: tail.clone(),
704 detail: format!(
705 "cons tail must be a List, got {other:?}"
706 ),
707 });
708 None
709 }
710 None => None,
711 }
712 }
713 Node::OptionSome(v) => self
714 .walk_expr(&v, scope, sigs, out, effects, failures)?
715 .map(|(t, c)| (Type::Option(Box::new(t)), c)),
716 Node::OptionNone { elem } => {
717 Some((Type::Option(Box::new(elem)), Confidence::Structural))
718 }
719 Node::OptionElse { opt, default } => {
720 let ot = self.walk_expr(&opt, scope, sigs, out, effects, failures)?;
721 let dt =
722 self.walk_expr(&default, scope, sigs, out, effects, failures)?;
723 match ot {
724 Some((Type::Option(inner), oc)) => {
725 if let Some((d, _)) = &dt {
726 if !compatible(&inner, d) {
727 out.push(Violation {
728 principle: 2,
729 node: default.clone(),
730 detail: format!(
731 "option default is {d:?} but the Option holds {inner:?}"
732 ),
733 });
734 }
735 }
736 let dc =
737 dt.map(|(_, c)| c).unwrap_or(Confidence::Persisted);
738 Some((*inner, oc.min(dc)))
739 }
740 Some((other, _)) => {
741 out.push(Violation {
742 principle: 2,
743 node: opt.clone(),
744 detail: format!(
745 "option_else expects an Option, got {other:?}"
746 ),
747 });
748 None
749 }
750 None => None,
751 }
752 }
753 Node::OptionMatch {
754 opt,
755 some_bind,
756 some_body,
757 none_body,
758 } => {
759 let ot = self.walk_expr(&opt, scope, sigs, out, effects, failures)?;
760 let (inner, oc) = match ot {
761 Some((Type::Option(inner), oc)) => (*inner, oc),
762 Some((Type::Never, oc)) => (Type::Never, oc),
763 Some((other, _)) => {
764 out.push(Violation {
765 principle: 2,
766 node: opt.clone(),
767 detail: format!(
768 "OptionMatch scrutinee is {other:?}, expected Option"
769 ),
770 });
771 return Ok(None);
772 }
773 None => return Ok(None),
774 };
775 let mut s2 = scope.to_vec();
776 s2.push((some_bind.clone(), Some((inner, oc))));
777 let st = self.walk_expr(
778 &some_body, &s2, sigs, out, effects, failures,
779 )?;
780 let nt = self.walk_expr(
781 &none_body, scope, sigs, out, effects, failures,
782 )?;
783 match (st, nt) {
784 (Some((s, sc)), Some((n, nc))) => {
785 if !compatible(&s, &n) {
786 out.push(Violation {
787 principle: 2,
788 node: hash.clone(),
789 detail: format!(
790 "OptionMatch arms differ: {s:?} vs {n:?}"
791 ),
792 });
793 }
794 let ty = if s == Type::Never { n } else { s };
795 Some((ty, oc.min(sc).min(nc)))
796 }
797 (Some(v), None) | (None, Some(v)) => Some(v),
798 (None, None) => None,
799 }
800 }
801 Node::ListTryGet { list, index } => {
802 let lt = self.walk_expr(&list, scope, sigs, out, effects, failures)?;
803 let it = self.walk_expr(&index, scope, sigs, out, effects, failures)?;
804 if let Some((t, _)) = &it {
805 if *t != Type::Number && *t != Type::Never {
806 out.push(Violation {
807 principle: 2,
808 node: index.clone(),
809 detail: format!("list index is {t:?}, expected Number"),
810 });
811 }
812 }
813 match lt {
814 Some((Type::List(elem), lc)) => {
815 let ic =
816 it.map(|(_, c)| c).unwrap_or(Confidence::Persisted);
817 Some((Type::Option(elem), lc.min(ic)))
818 }
819 Some((other, _)) => {
820 out.push(Violation {
821 principle: 2,
822 node: list.clone(),
823 detail: format!(
824 "list_try_get on a non-List type {other:?}"
825 ),
826 });
827 None
828 }
829 None => None,
830 }
831 }
832 Node::ListLen(arg) => {
833 match self.walk_expr(&arg, scope, sigs, out, effects, failures)? {
834 Some((Type::List(_), c)) => Some((Type::Number, c)),
835 Some((other, _)) => {
836 out.push(Violation {
837 principle: 2,
838 node: arg.clone(),
839 detail: format!("list_len expects a List, got {other:?}"),
840 });
841 None
842 }
843 None => None,
844 }
845 }
846 Node::ListGet { list, index } => {
847 let lt = self.walk_expr(&list, scope, sigs, out, effects, failures)?;
848 let it = self.walk_expr(&index, scope, sigs, out, effects, failures)?;
849 if let Some((t, _)) = &it {
850 if *t != Type::Number && *t != Type::Never {
851 out.push(Violation {
852 principle: 2,
853 node: index.clone(),
854 detail: format!("list index is {t:?}, expected Number"),
855 });
856 }
857 }
858 match lt {
859 Some((Type::List(elem), lc)) => {
860 let ic = it.map(|(_, c)| c).unwrap_or(Confidence::Persisted);
861 Some((*elem, lc.min(ic)))
862 }
863 Some((other, _)) => {
864 out.push(Violation {
865 principle: 2,
866 node: list.clone(),
867 detail: format!("list_get on a non-List type {other:?}"),
868 });
869 None
870 }
871 None => None,
872 }
873 }
874 Node::Map(pairs) => {
875 if pairs.is_empty() {
876 out.push(Violation {
877 principle: 2,
878 node: hash.clone(),
879 detail: "empty map literal needs a type annotation (v0.3)"
880 .into(),
881 });
882 return Ok(None);
883 }
884 let mut kt: Option<Type> = None;
885 let mut vt: Option<Type> = None;
886 let mut conf = Confidence::Persisted;
887 for (k, v) in &pairs {
888 if let Some((t, c)) =
889 self.walk_expr(k, scope, sigs, out, effects, failures)?
890 {
891 match &kt {
892 None => kt = Some(t),
893 Some(e) => {
894 if !compatible(e, &t) {
895 out.push(Violation {
896 principle: 2,
897 node: k.clone(),
898 detail: format!(
899 "map key is {t:?}, expected {e:?}"
900 ),
901 });
902 }
903 }
904 }
905 conf = conf.min(c);
906 }
907 if let Some((t, c)) =
908 self.walk_expr(v, scope, sigs, out, effects, failures)?
909 {
910 match &vt {
911 None => vt = Some(t),
912 Some(e) => {
913 if !compatible(e, &t) {
914 out.push(Violation {
915 principle: 2,
916 node: v.clone(),
917 detail: format!(
918 "map value is {t:?}, expected {e:?}"
919 ),
920 });
921 }
922 }
923 }
924 conf = conf.min(c);
925 }
926 }
927 match (kt, vt) {
928 (Some(k), Some(v)) => {
929 Some((Type::Map(Box::new(k), Box::new(v)), conf))
930 }
931 _ => None,
932 }
933 }
934 Node::MapGet { map, key } => {
935 let mt = self.walk_expr(&map, scope, sigs, out, effects, failures)?;
936 let kt = self.walk_expr(&key, scope, sigs, out, effects, failures)?;
937 match mt {
938 Some((Type::Map(k, v), mc)) => {
939 if let Some((t, _)) = &kt {
940 if !compatible(&k, t) {
941 out.push(Violation {
942 principle: 2,
943 node: key.clone(),
944 detail: format!(
945 "map key is {t:?}, expected {k:?}"
946 ),
947 });
948 }
949 }
950 let kc = kt.map(|(_, c)| c).unwrap_or(Confidence::Persisted);
951 Some((*v, mc.min(kc)))
952 }
953 Some((other, _)) => {
954 out.push(Violation {
955 principle: 2,
956 node: map.clone(),
957 detail: format!("map_get on a non-Map type {other:?}"),
958 });
959 None
960 }
961 None => None,
962 }
963 }
964 Node::MapTryGet { map, key } => {
965 let mt = self.walk_expr(&map, scope, sigs, out, effects, failures)?;
966 let kt = self.walk_expr(&key, scope, sigs, out, effects, failures)?;
967 match mt {
968 Some((Type::Map(k, v), mc)) => {
969 if let Some((t, _)) = &kt {
970 if !compatible(&k, t) {
971 out.push(Violation {
972 principle: 2,
973 node: key.clone(),
974 detail: format!(
975 "map key is {t:?}, expected {k:?}"
976 ),
977 });
978 }
979 }
980 let kc = kt.map(|(_, c)| c).unwrap_or(Confidence::Persisted);
981 Some((Type::Option(v), mc.min(kc)))
982 }
983 Some((Type::Never, c)) => Some((Type::Never, c)),
984 Some((other, _)) => {
985 out.push(Violation {
986 principle: 2,
987 node: map.clone(),
988 detail: format!("map_try_get on a non-Map type {other:?}"),
989 });
990 None
991 }
992 None => None,
993 }
994 }
995 Node::MapLen(arg) => {
996 match self.walk_expr(&arg, scope, sigs, out, effects, failures)? {
997 Some((Type::Map(_, _), c)) => Some((Type::Number, c)),
998 Some((other, _)) => {
999 out.push(Violation {
1000 principle: 2,
1001 node: arg.clone(),
1002 detail: format!("map_len expects a Map, got {other:?}"),
1003 });
1004 None
1005 }
1006 None => None,
1007 }
1008 }
1009 Node::Log(arg) => {
1010 effects.insert(Effect::Log);
1013 self.walk_expr(&arg, scope, sigs, out, effects, failures)?
1014 }
1015 Node::Publish(arg) => {
1016 effects.insert(Effect::Live);
1020 let t =
1021 self.walk_expr(&arg, scope, sigs, out, effects, failures)?;
1022 if let Some((ty, _)) = t {
1023 if ty != Type::String && ty != Type::Never {
1024 out.push(Violation {
1025 principle: 2,
1026 node: arg.clone(),
1027 detail: format!(
1028 "publish topic is {ty:?}, expected String"
1029 ),
1030 });
1031 }
1032 }
1033 Some((Type::Number, Confidence::Structural))
1034 }
1035 Node::SetHeader { name, value } => {
1036 effects.insert(Effect::Resp);
1041 for (label, arg) in
1042 [("name", &name), ("value", &value)]
1043 {
1044 let t = self.walk_expr(
1045 arg, scope, sigs, out, effects, failures,
1046 )?;
1047 if let Some((ty, _)) = t {
1048 if ty != Type::String && ty != Type::Never {
1049 out.push(Violation {
1050 principle: 2,
1051 node: (*arg).clone(),
1052 detail: format!(
1053 "set_header {label} is {ty:?}, \
1054 expected String"
1055 ),
1056 });
1057 }
1058 }
1059 }
1060 Some((Type::Number, Confidence::Structural))
1061 }
1062 Node::Rand => {
1063 effects.insert(Effect::Rand);
1064 Some((Type::Number, Confidence::External))
1065 }
1066 Node::MutNew(v) => {
1067 effects.insert(Effect::Mut);
1068 self.walk_expr(&v, scope, sigs, out, effects, failures)?
1069 .map(|(t, c)| (Type::Cell(Box::new(t)), c))
1070 }
1071 Node::MutGet(cell) => {
1072 match self.walk_expr(&cell, scope, sigs, out, effects, failures)? {
1073 Some((Type::Cell(t), c)) => Some((*t, c)),
1074 Some((other, _)) => {
1075 out.push(Violation {
1076 principle: 2,
1077 node: cell.clone(),
1078 detail: format!("cell_get on a non-Cell type {other:?}"),
1079 });
1080 None
1081 }
1082 None => None,
1083 }
1084 }
1085 Node::MutSet { cell, value } => {
1086 effects.insert(Effect::Mut);
1087 let ct = self.walk_expr(&cell, scope, sigs, out, effects, failures)?;
1088 let vt =
1089 self.walk_expr(&value, scope, sigs, out, effects, failures)?;
1090 if let (Some((Type::Cell(et), _)), Some((vty, _))) = (&ct, &vt) {
1091 if !compatible(et, vty) {
1092 out.push(Violation {
1093 principle: 2,
1094 node: value.clone(),
1095 detail: format!(
1096 "cell holds {et:?} but assigned {vty:?}"
1097 ),
1098 });
1099 }
1100 } else if let Some((other, _)) = &ct {
1101 if !matches!(other, Type::Cell(_)) {
1102 out.push(Violation {
1103 principle: 2,
1104 node: cell.clone(),
1105 detail: format!("cell_set on a non-Cell type {other:?}"),
1106 });
1107 }
1108 }
1109 vt }
1111 Node::DiskWrite { path, content } => {
1112 effects.insert(Effect::Disk);
1113 let p = self.walk_expr(&path, scope, sigs, out, effects, failures)?;
1114 let cn =
1115 self.walk_expr(&content, scope, sigs, out, effects, failures)?;
1116 for (n, t) in [(&path, &p), (&content, &cn)] {
1117 if let Some((ty, _)) = t {
1118 if *ty != Type::String && *ty != Type::Never {
1119 out.push(Violation {
1120 principle: 2,
1121 node: n.clone(),
1122 detail: format!("disk_write expects String, got {ty:?}"),
1123 });
1124 }
1125 }
1126 }
1127 Some((Type::Number, Confidence::External))
1128 }
1129 Node::DiskRead(path) => {
1130 effects.insert(Effect::Disk);
1131 if let Some((t, _)) =
1132 self.walk_expr(&path, scope, sigs, out, effects, failures)?
1133 {
1134 if t != Type::String && t != Type::Never {
1135 out.push(Violation {
1136 principle: 2,
1137 node: path.clone(),
1138 detail: format!("disk_read expects String, got {t:?}"),
1139 });
1140 }
1141 }
1142 Some((Type::String, Confidence::External))
1145 }
1146 Node::NetGet(url) => {
1147 effects.insert(Effect::Net);
1148 if let Some((t, _)) =
1149 self.walk_expr(&url, scope, sigs, out, effects, failures)?
1150 {
1151 if t != Type::String && t != Type::Never {
1152 out.push(Violation {
1153 principle: 2,
1154 node: url.clone(),
1155 detail: format!("net_get expects String, got {t:?}"),
1156 });
1157 }
1158 }
1159 Some((Type::Number, Confidence::External))
1160 }
1161 Node::DbQuery { sql, params } => {
1162 effects.insert(Effect::Db);
1163 if let Some((t, _)) =
1164 self.walk_expr(&sql, scope, sigs, out, effects, failures)?
1165 {
1166 if t != Type::String && t != Type::Never {
1167 out.push(Violation {
1168 principle: 2,
1169 node: sql.clone(),
1170 detail: format!("db_query expects String, got {t:?}"),
1171 });
1172 }
1173 }
1174 if let Some((t, _)) =
1175 self.walk_expr(¶ms, scope, sigs, out, effects, failures)?
1176 {
1177 let ok = matches!(&t, Type::List(e) if **e == Type::String)
1178 || t == Type::Never;
1179 if !ok {
1180 out.push(Violation {
1181 principle: 2,
1182 node: params.clone(),
1183 detail: format!(
1184 "db_query params must be List<String>, got {t:?}"
1185 ),
1186 });
1187 }
1188 }
1189 Some((Type::String, Confidence::Persisted))
1192 }
1193 Node::StrConcat(a, b) => {
1194 let at = self.walk_expr(&a, scope, sigs, out, effects, failures)?;
1195 let bt = self.walk_expr(&b, scope, sigs, out, effects, failures)?;
1196 for (n, t) in [(&a, &at), (&b, &bt)] {
1197 if let Some((ty, _)) = t {
1198 if *ty != Type::String && *ty != Type::Never {
1199 out.push(Violation {
1200 principle: 2,
1201 node: n.clone(),
1202 detail: format!("str_concat expects String, got {ty:?}"),
1203 });
1204 }
1205 }
1206 }
1207 let c = at
1208 .map(|(_, c)| c)
1209 .unwrap_or(Confidence::Persisted)
1210 .min(bt.map(|(_, c)| c).unwrap_or(Confidence::Persisted));
1211 Some((Type::String, c))
1212 }
1213 Node::StrSlice { s, start, len } => {
1214 let st = self.walk_expr(&s, scope, sigs, out, effects, failures)?;
1215 let stt =
1216 self.walk_expr(&start, scope, sigs, out, effects, failures)?;
1217 let lnt = self.walk_expr(&len, scope, sigs, out, effects, failures)?;
1218 if let Some((t, _)) = &st {
1219 if *t != Type::String && *t != Type::Never {
1220 out.push(Violation {
1221 principle: 2,
1222 node: s.clone(),
1223 detail: format!("str_slice expects String, got {t:?}"),
1224 });
1225 }
1226 }
1227 for (n, t) in [(&start, &stt), (&len, &lnt)] {
1228 if let Some((ty, _)) = t {
1229 if *ty != Type::Number && *ty != Type::Never {
1230 out.push(Violation {
1231 principle: 2,
1232 node: n.clone(),
1233 detail: format!("str_slice index is {ty:?}, expected Number"),
1234 });
1235 }
1236 }
1237 }
1238 let c = [st, stt, lnt]
1239 .into_iter()
1240 .flatten()
1241 .map(|(_, c)| c)
1242 .min()
1243 .unwrap_or(Confidence::Structural);
1244 Some((Type::String, c))
1245 }
1246 Node::StrEq(a, b)
1247 | Node::StrContains { haystack: a, needle: b }
1248 | Node::StrStartsWith { s: a, prefix: b } => {
1249 let at = self.walk_expr(&a, scope, sigs, out, effects, failures)?;
1250 let bt = self.walk_expr(&b, scope, sigs, out, effects, failures)?;
1251 for (n, t) in [(&a, &at), (&b, &bt)] {
1252 if let Some((ty, _)) = t {
1253 if *ty != Type::String && *ty != Type::Never {
1254 out.push(Violation {
1255 principle: 2,
1256 node: n.clone(),
1257 detail: format!("string op expects String, got {ty:?}"),
1258 });
1259 }
1260 }
1261 }
1262 let c = at
1263 .map(|(_, c)| c)
1264 .unwrap_or(Confidence::Persisted)
1265 .min(bt.map(|(_, c)| c).unwrap_or(Confidence::Persisted));
1266 Some((Type::Bool, c))
1267 }
1268 Node::StrIndexOf { haystack, needle } => {
1269 let ht =
1270 self.walk_expr(&haystack, scope, sigs, out, effects, failures)?;
1271 let nt =
1272 self.walk_expr(&needle, scope, sigs, out, effects, failures)?;
1273 for (node, t) in [(&haystack, &ht), (&needle, &nt)] {
1274 if let Some((ty, _)) = t {
1275 if *ty != Type::String && *ty != Type::Never {
1276 out.push(Violation {
1277 principle: 2,
1278 node: node.clone(),
1279 detail: format!(
1280 "string op expects String, got {ty:?}"
1281 ),
1282 });
1283 }
1284 }
1285 }
1286 let c = ht
1287 .map(|(_, c)| c)
1288 .unwrap_or(Confidence::Persisted)
1289 .min(nt.map(|(_, c)| c).unwrap_or(Confidence::Persisted));
1290 Some((Type::Number, c))
1291 }
1292 Node::StrLen(arg) => {
1293 match self.walk_expr(&arg, scope, sigs, out, effects, failures)? {
1294 Some((t, c)) => {
1295 if t != Type::String && t != Type::Never {
1296 out.push(Violation {
1297 principle: 2,
1298 node: arg.clone(),
1299 detail: format!("str_len expects String, got {t:?}"),
1300 });
1301 }
1302 Some((Type::Number, c))
1303 }
1304 None => None,
1305 }
1306 }
1307 Node::StrLower(arg) => {
1308 match self.walk_expr(&arg, scope, sigs, out, effects, failures)? {
1309 Some((t, c)) => {
1310 if t != Type::String && t != Type::Never {
1311 out.push(Violation {
1312 principle: 2,
1313 node: arg.clone(),
1314 detail: format!(
1315 "str_lower expects String, got {t:?}"
1316 ),
1317 });
1318 }
1319 Some((Type::String, c))
1320 }
1321 None => None,
1322 }
1323 }
1324 Node::StrFromCode(arg) => {
1325 match self.walk_expr(&arg, scope, sigs, out, effects, failures)? {
1326 Some((t, c)) => {
1327 if t != Type::Number && t != Type::Never {
1328 out.push(Violation {
1329 principle: 2,
1330 node: arg.clone(),
1331 detail: format!(
1332 "str_from_code expects Number, got {t:?}"
1333 ),
1334 });
1335 }
1336 Some((Type::String, c))
1337 }
1338 None => None,
1339 }
1340 }
1341 Node::NumberToStr(arg) => {
1342 match self.walk_expr(&arg, scope, sigs, out, effects, failures)? {
1343 Some((t, c)) => {
1344 if t != Type::Number && t != Type::Never {
1345 out.push(Violation {
1346 principle: 2,
1347 node: arg.clone(),
1348 detail: format!(
1349 "number_to_str expects Number, got {t:?}"
1350 ),
1351 });
1352 }
1353 Some((Type::String, c))
1354 }
1355 None => None,
1356 }
1357 }
1358 Node::StrToNumber(arg) => {
1359 match self.walk_expr(&arg, scope, sigs, out, effects, failures)? {
1360 Some((t, c)) => {
1361 if t != Type::String && t != Type::Never {
1362 out.push(Violation {
1363 principle: 2,
1364 node: arg.clone(),
1365 detail: format!(
1366 "str_to_number expects String, got {t:?}"
1367 ),
1368 });
1369 }
1370 Some((Type::Number, c))
1371 }
1372 None => None,
1373 }
1374 }
1375 Node::StrToNumberOpt(arg) => {
1376 match self.walk_expr(&arg, scope, sigs, out, effects, failures)? {
1377 Some((t, c)) => {
1378 if t != Type::String && t != Type::Never {
1379 out.push(Violation {
1380 principle: 2,
1381 node: arg.clone(),
1382 detail: format!(
1383 "str_to_number_opt expects String, got {t:?}"
1384 ),
1385 });
1386 }
1387 Some((Type::Option(Box::new(Type::Number)), c))
1388 }
1389 None => None,
1390 }
1391 }
1392 Node::Hole { .. } => None,
1393 Node::Ref(name) => {
1394 match scope.iter().rev().find(|(n, _)| n == &name) {
1395 Some((_, v)) => v.clone(),
1396 None => {
1397 out.push(Violation {
1398 principle: 1,
1399 node: hash.clone(),
1400 detail: format!("unresolved reference: `{name}`"),
1401 });
1402 None
1403 }
1404 }
1405 }
1406 Node::Step { value, .. } => {
1407 self.walk_expr(&value, scope, sigs, out, effects, failures)?
1408 }
1409 Node::Fail(f) => {
1410 failures.insert(f);
1415 Some((Type::Never, Confidence::Persisted))
1416 }
1417 Node::Handle { body, handlers } => {
1418 let mut body_f = BTreeSet::new();
1421 let b = self.walk_expr(&body, scope, sigs, out, effects, &mut body_f)?;
1422 for f in &body_f {
1423 if !handlers.iter().any(|(v, _)| v == f) {
1424 failures.insert(f.clone());
1425 }
1426 }
1427 match b {
1428 None => None,
1429 Some((bt, bc)) => {
1430 let mut conf = bc;
1431 for (_, recover) in &handlers {
1432 if let Some((rt, rc)) = self
1433 .walk_expr(recover, scope, sigs, out, effects, failures)?
1434 {
1435 if !compatible(&rt, &bt) {
1436 out.push(Violation {
1437 principle: 2,
1438 node: recover.clone(),
1439 detail: format!(
1440 "handler recovers as {rt:?} but the value is {bt:?}"
1441 ),
1442 });
1443 }
1444 conf = conf.min(rc);
1445 }
1446 }
1447 Some((bt, conf))
1448 }
1449 }
1450 }
1451 Node::If {
1452 cond,
1453 then_branch,
1454 else_branch,
1455 } => {
1456 let c = self.walk_expr(&cond, scope, sigs, out, effects, failures)?;
1457 let t = self.walk_expr(&then_branch, scope, sigs, out, effects, failures)?;
1458 let e = self.walk_expr(&else_branch, scope, sigs, out, effects, failures)?;
1459 if let Some((ct, _)) = &c {
1460 if *ct != Type::Bool && *ct != Type::Never {
1461 out.push(Violation {
1462 principle: 2,
1463 node: cond.clone(),
1464 detail: format!("condition is {ct:?}, expected Bool"),
1465 });
1466 }
1467 }
1468 match (t, e) {
1469 (Some((tt, tc)), Some((et, ec))) => {
1470 if !compatible(&tt, &et) {
1471 out.push(Violation {
1472 principle: 2,
1473 node: hash.clone(),
1474 detail: format!(
1475 "branch types differ: {tt:?} vs {et:?}"
1476 ),
1477 });
1478 }
1479 let rty = if tt == Type::Never { et } else { tt };
1482 let mut conf = tc.min(ec);
1483 if let Some((_, cc)) = c {
1484 conf = conf.min(cc);
1485 }
1486 Some((rty, conf))
1487 }
1488 _ => None,
1489 }
1490 }
1491 Node::BinOp { op, lhs, rhs } => {
1492 let l = self.walk_expr(&lhs, scope, sigs, out, effects, failures)?;
1493 let r = self.walk_expr(&rhs, scope, sigs, out, effects, failures)?;
1494 match (l, r) {
1495 (Some((lt, lc)), Some((rt, rc))) => {
1496 let conf = lc.min(rc);
1500 if op.is_logical() {
1501 for (operand, ty) in [(&lhs, <), (&rhs, &rt)] {
1502 if *ty != Type::Bool && *ty != Type::Never {
1503 out.push(Violation {
1504 principle: 2,
1505 node: operand.clone(),
1506 detail: format!(
1507 "logical operand is {ty:?}, expected Bool"
1508 ),
1509 });
1510 }
1511 }
1512 Some((Type::Bool, conf))
1513 } else if op.is_comparison() {
1514 if !compatible(<, &rt) {
1515 out.push(Violation {
1516 principle: 2,
1517 node: hash.clone(),
1518 detail: format!(
1519 "comparison operands differ: {lt:?} vs {rt:?}"
1520 ),
1521 });
1522 }
1523 Some((Type::Bool, conf))
1524 } else {
1525 if lt != Type::Number && lt != Type::Never {
1526 out.push(Violation {
1527 principle: 2,
1528 node: lhs.clone(),
1529 detail: format!(
1530 "arithmetic operand is {lt:?}, expected Number"
1531 ),
1532 });
1533 }
1534 if rt != Type::Number && rt != Type::Never {
1535 out.push(Violation {
1536 principle: 2,
1537 node: rhs.clone(),
1538 detail: format!(
1539 "arithmetic operand is {rt:?}, expected Number"
1540 ),
1541 });
1542 }
1543 Some((Type::Number, conf))
1544 }
1545 }
1546 _ => None,
1547 }
1548 }
1549 Node::Call { func, args } => {
1550 let Some(sig) = sigs.get(&func) else {
1551 out.push(Violation {
1552 principle: 1,
1553 node: hash.clone(),
1554 detail: format!("unresolved function: `{func}`"),
1555 });
1556 for arg in &args {
1557 self.walk_expr(arg, scope, sigs, out, effects, failures)?;
1558 }
1559 return Ok(None);
1560 };
1561 if args.len() != sig.params.len() {
1562 out.push(Violation {
1563 principle: 2,
1564 node: hash.clone(),
1565 detail: format!(
1566 "`{func}` takes {} argument(s), {} given",
1567 sig.params.len(),
1568 args.len()
1569 ),
1570 });
1571 }
1572 let mut subst: HashMap<String, Type> = HashMap::new();
1577 for (i, arg) in args.iter().enumerate() {
1578 let inferred =
1579 self.walk_expr(arg, scope, sigs, out, effects, failures)?;
1580 if let (Some((at, ac)), Some(p)) = (inferred, sig.params.get(i)) {
1581 if !unify(&p.ty, &at, &mut subst) {
1582 out.push(Violation {
1583 principle: 2,
1584 node: arg.clone(),
1585 detail: format!(
1586 "argument `{}` is {:?} but `{func}` expects {:?}",
1587 p.name, at, p.ty
1588 ),
1589 });
1590 }
1591 if ac < p.min_confidence {
1592 out.push(Violation {
1593 principle: 7,
1594 node: arg.clone(),
1595 detail: format!(
1596 "argument `{}` is {:?} but `{func}` requires at least {:?}",
1597 p.name, ac, p.min_confidence
1598 ),
1599 });
1600 }
1601 }
1602 }
1603 effects.extend(sig.requires.iter().copied());
1604 failures.extend(sig.on_failure.iter().cloned());
1605 let result_ty = substitute(&sig.produces.ty, &subst);
1606 for tp in &sig.type_params {
1613 if ty_mentions(&sig.produces.ty, tp)
1614 && !sig.params.iter().any(|p| ty_mentions(&p.ty, tp))
1615 {
1616 out.push(Violation {
1617 principle: 2,
1618 node: hash.clone(),
1619 detail: format!(
1620 "type parameter `{tp}` of `{func}` appears only \
1621 in the result and cannot be inferred"
1622 ),
1623 });
1624 }
1625 }
1626 Some((result_ty, sig.produces.confidence))
1627 }
1628 Node::FuncRef(name) => {
1629 let Some(sig) = sigs.get(&name) else {
1630 out.push(Violation {
1631 principle: 1,
1632 node: hash.clone(),
1633 detail: format!("unresolved function: `{name}`"),
1634 });
1635 return Ok(None);
1636 };
1637 if !sig.type_params.is_empty() {
1643 out.push(Violation {
1644 principle: 2,
1645 node: hash.clone(),
1646 detail: format!(
1647 "cannot take a function value of generic `{name}` (v0.4)"
1648 ),
1649 });
1650 }
1651 if !sig.on_failure.is_empty() {
1652 out.push(Violation {
1653 principle: 2,
1654 node: hash.clone(),
1655 detail: format!(
1656 "cannot take a function value of fallible `{name}` (v0.4)"
1657 ),
1658 });
1659 }
1660 let fn_ty = Type::Fn {
1661 params: sig.params.iter().map(|p| p.ty.clone()).collect(),
1662 ret: Box::new(sig.produces.ty.clone()),
1663 effects: sig.requires.clone(),
1664 };
1665 Some((fn_ty, Confidence::Structural))
1666 }
1667 Node::CallValue { callee, args } => {
1668 let c =
1669 self.walk_expr(&callee, scope, sigs, out, effects, failures)?;
1670 let (cty, cconf) = match c {
1671 Some(v) => v,
1672 None => {
1673 for arg in &args {
1674 self.walk_expr(
1675 arg, scope, sigs, out, effects, failures,
1676 )?;
1677 }
1678 return Ok(None);
1679 }
1680 };
1681 let Type::Fn {
1682 params,
1683 ret,
1684 effects: fx,
1685 } = cty
1686 else {
1687 if cty != Type::Never {
1688 out.push(Violation {
1689 principle: 2,
1690 node: callee.clone(),
1691 detail: format!(
1692 "callee is {cty:?}, not a function value"
1693 ),
1694 });
1695 }
1696 for arg in &args {
1697 self.walk_expr(
1698 arg, scope, sigs, out, effects, failures,
1699 )?;
1700 }
1701 return Ok(None);
1702 };
1703 if args.len() != params.len() {
1704 out.push(Violation {
1705 principle: 2,
1706 node: hash.clone(),
1707 detail: format!(
1708 "function value takes {} argument(s), {} given",
1709 params.len(),
1710 args.len()
1711 ),
1712 });
1713 }
1714 let mut conf = cconf;
1716 for (i, arg) in args.iter().enumerate() {
1717 let inferred =
1718 self.walk_expr(arg, scope, sigs, out, effects, failures)?;
1719 if let (Some((at, ac)), Some(pt)) = (inferred, params.get(i)) {
1720 if !compatible(pt, &at) {
1721 out.push(Violation {
1722 principle: 2,
1723 node: arg.clone(),
1724 detail: format!(
1725 "argument {i} is {at:?} but the function \
1726 value expects {pt:?}"
1727 ),
1728 });
1729 }
1730 conf = conf.min(ac);
1731 }
1732 }
1733 effects.extend(fx.iter().copied());
1734 Some((*ret, conf))
1735 }
1736 Node::Lambda { params, body } => {
1737 let mut s2 = scope.to_vec();
1742 for p in ¶ms {
1743 s2.push((
1744 p.name.clone(),
1745 Some((p.ty.clone(), p.min_confidence)),
1746 ));
1747 }
1748 let mut lam_fx = BTreeSet::new();
1749 let mut lam_fail = BTreeSet::new();
1750 let bt = self.walk_expr(
1751 &body, &s2, sigs, out, &mut lam_fx, &mut lam_fail,
1752 )?;
1753 if !lam_fail.is_empty() {
1754 out.push(Violation {
1755 principle: 6,
1756 node: hash.clone(),
1757 detail: format!(
1758 "a lambda may not raise an uncaught failure: {} (v0.4)",
1759 lam_fail.iter().cloned().collect::<Vec<_>>().join(", ")
1760 ),
1761 });
1762 }
1763 let ret = bt.map(|(t, _)| t).unwrap_or(Type::Never);
1764 let fn_ty = Type::Fn {
1765 params: params.iter().map(|p| p.ty.clone()).collect(),
1766 ret: Box::new(ret),
1767 effects: lam_fx,
1768 };
1769 Some((fn_ty, Confidence::Structural))
1770 }
1771 Node::Record { type_name, fields } => {
1772 match self.records.get(&type_name).cloned() {
1773 None => {
1774 out.push(Violation {
1775 principle: 1,
1776 node: hash.clone(),
1777 detail: format!("unknown record type: `{type_name}`"),
1778 });
1779 for (_, fh) in &fields {
1780 self.walk_expr(fh, scope, sigs, out, effects, failures)?;
1781 }
1782 None
1783 }
1784 Some(def_fields) => {
1785 let mut conf = Confidence::Persisted;
1786 for (fname, fty) in &def_fields {
1787 match fields.iter().find(|(n, _)| n == fname) {
1788 None => out.push(Violation {
1789 principle: 2,
1790 node: hash.clone(),
1791 detail: format!(
1792 "missing field `{fname}` for `{type_name}`"
1793 ),
1794 }),
1795 Some((_, fh)) => {
1796 if let Some((vt, vc)) = self.walk_expr(
1797 fh, scope, sigs, out, effects, failures,
1798 )? {
1799 if !compatible(&vt, fty) {
1800 out.push(Violation {
1801 principle: 2,
1802 node: fh.clone(),
1803 detail: format!(
1804 "field `{fname}` is {vt:?}, expected {fty:?}"
1805 ),
1806 });
1807 }
1808 conf = conf.min(vc);
1809 }
1810 }
1811 }
1812 }
1813 for (n, fh) in &fields {
1814 if !def_fields.iter().any(|(dn, _)| dn == n) {
1815 out.push(Violation {
1816 principle: 2,
1817 node: fh.clone(),
1818 detail: format!("`{type_name}` has no field `{n}`"),
1819 });
1820 self.walk_expr(fh, scope, sigs, out, effects, failures)?;
1821 }
1822 }
1823 if def_fields.is_empty() {
1824 conf = Confidence::Structural;
1825 }
1826 Some((Type::Named(type_name.clone()), conf))
1827 }
1828 }
1829 }
1830 Node::Field {
1831 base,
1832 type_name,
1833 field,
1834 } => {
1835 match self.walk_expr(&base, scope, sigs, out, effects, failures)? {
1836 Some((Type::Named(rec), bc)) => {
1837 if rec != type_name {
1838 out.push(Violation {
1839 principle: 2,
1840 node: hash.clone(),
1841 detail: format!(
1842 "field access typed as `{type_name}` but base is `{rec}`"
1843 ),
1844 });
1845 }
1846 match self.records.get(&rec) {
1847 Some(fs) => match fs.iter().find(|(n, _)| n == &field) {
1848 Some((_, fty)) => Some((fty.clone(), bc)),
1849 None => {
1850 out.push(Violation {
1851 principle: 2,
1852 node: hash.clone(),
1853 detail: format!("`{rec}` has no field `{field}`"),
1854 });
1855 None
1856 }
1857 },
1858 None => {
1859 out.push(Violation {
1860 principle: 1,
1861 node: hash.clone(),
1862 detail: format!("unknown record type: `{rec}`"),
1863 });
1864 None
1865 }
1866 }
1867 }
1868 Some((other, _)) => {
1869 out.push(Violation {
1870 principle: 2,
1871 node: base.clone(),
1872 detail: format!(
1873 "field access on non-record type {other:?}"
1874 ),
1875 });
1876 None
1877 }
1878 None => None,
1879 }
1880 }
1881 Node::Variant {
1882 type_name,
1883 case,
1884 fields,
1885 } => match self.variants.get(&type_name).cloned() {
1886 None => {
1887 out.push(Violation {
1888 principle: 1,
1889 node: hash.clone(),
1890 detail: format!("unknown variant type: `{type_name}`"),
1891 });
1892 for (_, fh) in &fields {
1893 self.walk_expr(fh, scope, sigs, out, effects, failures)?;
1894 }
1895 None
1896 }
1897 Some(cases) => match cases.iter().find(|(c, _)| c == &case) {
1898 None => {
1899 out.push(Violation {
1900 principle: 2,
1901 node: hash.clone(),
1902 detail: format!("`{type_name}` has no case `{case}`"),
1903 });
1904 for (_, fh) in &fields {
1905 self.walk_expr(fh, scope, sigs, out, effects, failures)?;
1906 }
1907 None
1908 }
1909 Some((_, payload)) => {
1910 let payload = payload.clone();
1911 let mut conf = Confidence::Persisted;
1912 for (fname, fty) in &payload {
1913 match fields.iter().find(|(n, _)| n == fname) {
1914 None => out.push(Violation {
1915 principle: 2,
1916 node: hash.clone(),
1917 detail: format!(
1918 "missing field `{fname}` for `{type_name}.{case}`"
1919 ),
1920 }),
1921 Some((_, fh)) => {
1922 if let Some((vt, vc)) = self.walk_expr(
1923 fh, scope, sigs, out, effects, failures,
1924 )? {
1925 if !compatible(&vt, fty) {
1926 out.push(Violation {
1927 principle: 2,
1928 node: fh.clone(),
1929 detail: format!(
1930 "field `{fname}` is {vt:?}, expected {fty:?}"
1931 ),
1932 });
1933 }
1934 conf = conf.min(vc);
1935 }
1936 }
1937 }
1938 }
1939 for (n, fh) in &fields {
1940 if !payload.iter().any(|(dn, _)| dn == n) {
1941 out.push(Violation {
1942 principle: 2,
1943 node: fh.clone(),
1944 detail: format!(
1945 "`{type_name}.{case}` has no field `{n}`"
1946 ),
1947 });
1948 self.walk_expr(fh, scope, sigs, out, effects, failures)?;
1949 }
1950 }
1951 if payload.is_empty() {
1952 conf = Confidence::Structural;
1953 }
1954 Some((Type::Named(type_name.clone()), conf))
1955 }
1956 },
1957 },
1958 Node::Match {
1959 scrutinee,
1960 type_name,
1961 arms,
1962 } => {
1963 match self.walk_expr(&scrutinee, scope, sigs, out, effects, failures)? {
1964 Some((Type::Named(vname), sconf)) => {
1965 if vname != type_name {
1966 out.push(Violation {
1967 principle: 2,
1968 node: hash.clone(),
1969 detail: format!(
1970 "match typed as `{type_name}` but scrutinee is `{vname}`"
1971 ),
1972 });
1973 }
1974 match self.variants.get(&vname).cloned() {
1975 None => {
1976 out.push(Violation {
1977 principle: 1,
1978 node: hash.clone(),
1979 detail: format!("unknown variant type: `{vname}`"),
1980 });
1981 None
1982 }
1983 Some(cases) => {
1984 for (cname, _) in &cases {
1985 if !arms.iter().any(|a| &a.case == cname) {
1986 out.push(Violation {
1987 principle: 2,
1988 node: hash.clone(),
1989 detail: format!(
1990 "non-exhaustive match: case `{cname}` not covered"
1991 ),
1992 });
1993 }
1994 }
1995 let mut result_ty: Option<Type> = None;
1996 let mut conf = sconf;
1997 for arm in &arms {
1998 let Some((_, payload)) =
1999 cases.iter().find(|(c, _)| c == &arm.case)
2000 else {
2001 out.push(Violation {
2002 principle: 2,
2003 node: hash.clone(),
2004 detail: format!(
2005 "`{vname}` has no case `{}`",
2006 arm.case
2007 ),
2008 });
2009 continue;
2010 };
2011 if arm.bindings.len() != payload.len() {
2012 out.push(Violation {
2013 principle: 2,
2014 node: hash.clone(),
2015 detail: format!(
2016 "case `{}` has {} field(s), {} bound",
2017 arm.case,
2018 payload.len(),
2019 arm.bindings.len()
2020 ),
2021 });
2022 }
2023 let mut s2 = scope.to_vec();
2024 for (i, b) in arm.bindings.iter().enumerate() {
2025 let entry = payload
2026 .get(i)
2027 .map(|(_, ft)| (ft.clone(), sconf));
2028 s2.push((b.clone(), entry));
2029 }
2030 if let Some((bt, bc)) = self.walk_expr(
2031 &arm.body, &s2, sigs, out, effects, failures,
2032 )? {
2033 match &result_ty {
2034 None => result_ty = Some(bt),
2035 Some(rt) => {
2036 if !compatible(rt, &bt) {
2037 out.push(Violation {
2038 principle: 2,
2039 node: hash.clone(),
2040 detail: format!(
2041 "match arms differ: {rt:?} vs {bt:?}"
2042 ),
2043 });
2044 }
2045 }
2046 }
2047 conf = conf.min(bc);
2048 }
2049 }
2050 result_ty.map(|t| (t, conf))
2051 }
2052 }
2053 }
2054 Some((other, _)) => {
2055 out.push(Violation {
2056 principle: 2,
2057 node: scrutinee.clone(),
2058 detail: format!("match on non-variant type {other:?}"),
2059 });
2060 None
2061 }
2062 None => None,
2063 }
2064 }
2065 Node::Function { .. }
2066 | Node::Module { .. }
2067 | Node::RecordDef { .. }
2068 | Node::VariantDef { .. } => None,
2069 })
2070 }
2071}
2072
2073fn compatible(a: &Type, b: &Type) -> bool {
2076 a == b || *a == Type::Never || *b == Type::Never
2077}
2078
2079fn unify(pat: &Type, actual: &Type, subst: &mut HashMap<String, Type>) -> bool {
2083 if *actual == Type::Never {
2084 return true;
2085 }
2086 match pat {
2087 Type::Var(v) => match subst.get(v) {
2088 Some(bound) => compatible(&bound.clone(), actual),
2089 None => {
2090 subst.insert(v.clone(), actual.clone());
2091 true
2092 }
2093 },
2094 Type::List(a) => {
2095 if let Type::List(b) = actual {
2096 unify(a, b, subst)
2097 } else {
2098 false
2099 }
2100 }
2101 Type::Option(a) => {
2102 if let Type::Option(b) = actual {
2103 unify(a, b, subst)
2104 } else {
2105 false
2106 }
2107 }
2108 Type::Cell(a) => {
2109 if let Type::Cell(b) = actual {
2110 unify(a, b, subst)
2111 } else {
2112 false
2113 }
2114 }
2115 Type::Map(ka, va) => {
2116 if let Type::Map(kb, vb) = actual {
2117 unify(ka, kb, subst) && unify(va, vb, subst)
2118 } else {
2119 false
2120 }
2121 }
2122 Type::Result(oa, ea) => {
2123 if let Type::Result(ob, eb) = actual {
2124 unify(oa, ob, subst) && unify(ea, eb, subst)
2125 } else {
2126 false
2127 }
2128 }
2129 Type::Fn {
2130 params: pa,
2131 ret: ra,
2132 ..
2133 } => {
2134 if let Type::Fn {
2135 params: pb,
2136 ret: rb,
2137 ..
2138 } = actual
2139 {
2140 pa.len() == pb.len()
2141 && pa.iter().zip(pb).all(|(x, y)| unify(x, y, subst))
2142 && unify(ra, rb, subst)
2143 } else {
2144 false
2145 }
2146 }
2147 _ => pat == actual,
2148 }
2149}
2150
2151fn substitute(t: &Type, subst: &HashMap<String, Type>) -> Type {
2153 match t {
2154 Type::Var(v) => subst.get(v).cloned().unwrap_or_else(|| t.clone()),
2155 Type::List(a) => Type::List(Box::new(substitute(a, subst))),
2156 Type::Option(a) => Type::Option(Box::new(substitute(a, subst))),
2157 Type::Cell(a) => Type::Cell(Box::new(substitute(a, subst))),
2158 Type::Map(k, v) => Type::Map(
2159 Box::new(substitute(k, subst)),
2160 Box::new(substitute(v, subst)),
2161 ),
2162 Type::Result(o, e) => Type::Result(
2163 Box::new(substitute(o, subst)),
2164 Box::new(substitute(e, subst)),
2165 ),
2166 Type::Fn {
2167 params,
2168 ret,
2169 effects,
2170 } => Type::Fn {
2171 params: params.iter().map(|p| substitute(p, subst)).collect(),
2172 ret: Box::new(substitute(ret, subst)),
2173 effects: effects.clone(),
2174 },
2175 _ => t.clone(),
2176 }
2177}
2178
2179fn ty_mentions(t: &Type, name: &str) -> bool {
2181 match t {
2182 Type::Var(v) => v == name,
2183 Type::List(a) | Type::Option(a) | Type::Cell(a) => ty_mentions(a, name),
2184 Type::Map(k, v) | Type::Result(k, v) => {
2185 ty_mentions(k, name) || ty_mentions(v, name)
2186 }
2187 Type::Fn { params, ret, .. } => {
2188 params.iter().any(|p| ty_mentions(p, name)) || ty_mentions(ret, name)
2189 }
2190 _ => false,
2191 }
2192}
2193
2194pub(crate) fn child_hashes(node: &Node) -> Vec<&NodeHash> {
2196 match node {
2197 Node::Lit(_)
2198 | Node::FloatLit(_)
2199 | Node::DecimalLit(_)
2200 | Node::Bool(_)
2201 | Node::Str(_)
2202 | Node::Now
2203 | Node::Rand
2204 | Node::Ref(_)
2205 | Node::FuncRef(_)
2206 | Node::Hole { .. }
2207 | Node::Fail(_)
2208 | Node::RecordDef { .. }
2209 | Node::VariantDef { .. } => Vec::new(),
2210 Node::StrLen(arg)
2211 | Node::StrLower(arg)
2212 | Node::StrFromCode(arg)
2213 | Node::IntToFloat(arg)
2214 | Node::FloatToInt(arg)
2215 | Node::IntToDecimal(arg)
2216 | Node::DecimalToInt(arg)
2217 | Node::DecimalRaw(arg)
2218 | Node::Not(arg)
2219 | Node::NumberToStr(arg)
2220 | Node::StrToNumber(arg)
2221 | Node::StrToNumberOpt(arg) => vec![arg],
2222 Node::StrConcat(a, b) | Node::StrEq(a, b) => vec![a, b],
2223 Node::StrSlice { s, start, len } => vec![s, start, len],
2224 Node::StrContains { haystack, needle } => vec![haystack, needle],
2225 Node::StrStartsWith { s, prefix } => vec![s, prefix],
2226 Node::StrIndexOf { haystack, needle } => vec![haystack, needle],
2227 Node::List(elems) => elems.iter().collect(),
2228 Node::ListEmpty { .. } => Vec::new(),
2229 Node::ListCons { head, tail } => vec![head, tail],
2230 Node::OptionSome(v) => vec![v],
2231 Node::OptionNone { .. } => Vec::new(),
2232 Node::OptionElse { opt, default } => vec![opt, default],
2233 Node::OptionMatch {
2234 opt,
2235 some_body,
2236 none_body,
2237 ..
2238 } => vec![opt, some_body, none_body],
2239 Node::ListTryGet { list, index } => vec![list, index],
2240 Node::ListLen(arg) => vec![arg],
2241 Node::ListGet { list, index } => vec![list, index],
2242 Node::Map(pairs) => {
2243 let mut v = Vec::with_capacity(pairs.len() * 2);
2244 for (k, val) in pairs {
2245 v.push(k);
2246 v.push(val);
2247 }
2248 v
2249 }
2250 Node::MapGet { map, key } => vec![map, key],
2251 Node::MapTryGet { map, key } => vec![map, key],
2252 Node::MapLen(arg) => vec![arg],
2253 Node::Log(arg) | Node::Publish(arg) => vec![arg],
2254 Node::SetHeader { name, value } => vec![name, value],
2255 Node::MutNew(v) => vec![v],
2256 Node::MutGet(cell) => vec![cell],
2257 Node::MutSet { cell, value } => vec![cell, value],
2258 Node::DiskWrite { path, content } => vec![path, content],
2259 Node::DiskRead(path) => vec![path],
2260 Node::NetGet(url) => vec![url],
2261 Node::DbQuery { sql, params } => vec![sql, params],
2262 Node::Record { fields, .. } => fields.iter().map(|(_, h)| h).collect(),
2263 Node::Variant { fields, .. } => fields.iter().map(|(_, h)| h).collect(),
2264 Node::Field { base, .. } => vec![base],
2265 Node::Match {
2266 scrutinee, arms, ..
2267 } => {
2268 let mut v = vec![scrutinee];
2269 v.extend(arms.iter().map(|a| &a.body));
2270 v
2271 }
2272 Node::Handle { body, handlers } => {
2273 let mut v = vec![body];
2274 v.extend(handlers.iter().map(|(_, h)| h));
2275 v
2276 }
2277 Node::Call { args, .. } => args.iter().collect(),
2278 Node::CallValue { callee, args } => {
2279 let mut v = vec![callee];
2280 v.extend(args.iter());
2281 v
2282 }
2283 Node::Lambda { body, .. } => vec![body],
2284 Node::Step { value, .. } => vec![value],
2285 Node::BinOp { lhs, rhs, .. }
2286 | Node::FloatOp { lhs, rhs, .. }
2287 | Node::DecimalOp { lhs, rhs, .. } => vec![lhs, rhs],
2288 Node::If {
2289 cond,
2290 then_branch,
2291 else_branch,
2292 } => vec![cond, then_branch, else_branch],
2293 Node::Function { body, result, .. } => {
2294 let mut v: Vec<&NodeHash> = body.iter().collect();
2295 v.push(result);
2296 v
2297 }
2298 Node::Module {
2299 types, functions, ..
2300 } => {
2301 let mut v: Vec<&NodeHash> = types.iter().collect();
2302 v.extend(functions.iter());
2303 v
2304 }
2305 }
2306}
2307
2308pub(crate) fn with_child_hashes(node: &Node, kids: &[NodeHash]) -> Node {
2317 let k = |i: usize| kids[i].clone();
2318 match node {
2319 Node::Lit(_)
2321 | Node::FloatLit(_)
2322 | Node::DecimalLit(_)
2323 | Node::Bool(_)
2324 | Node::Str(_)
2325 | Node::Now
2326 | Node::Rand
2327 | Node::Ref(_)
2328 | Node::FuncRef(_)
2329 | Node::Hole { .. }
2330 | Node::Fail(_)
2331 | Node::RecordDef { .. }
2332 | Node::VariantDef { .. }
2333 | Node::ListEmpty { .. }
2334 | Node::OptionNone { .. } => node.clone(),
2335
2336 Node::StrLen(_) => Node::StrLen(k(0)),
2337 Node::StrLower(_) => Node::StrLower(k(0)),
2338 Node::StrFromCode(_) => Node::StrFromCode(k(0)),
2339 Node::IntToFloat(_) => Node::IntToFloat(k(0)),
2340 Node::FloatToInt(_) => Node::FloatToInt(k(0)),
2341 Node::IntToDecimal(_) => Node::IntToDecimal(k(0)),
2342 Node::DecimalToInt(_) => Node::DecimalToInt(k(0)),
2343 Node::DecimalRaw(_) => Node::DecimalRaw(k(0)),
2344 Node::Not(_) => Node::Not(k(0)),
2345 Node::NumberToStr(_) => Node::NumberToStr(k(0)),
2346 Node::StrToNumber(_) => Node::StrToNumber(k(0)),
2347 Node::StrToNumberOpt(_) => Node::StrToNumberOpt(k(0)),
2348 Node::ListLen(_) => Node::ListLen(k(0)),
2349 Node::MapLen(_) => Node::MapLen(k(0)),
2350 Node::OptionSome(_) => Node::OptionSome(k(0)),
2351 Node::Log(_) => Node::Log(k(0)),
2352 Node::Publish(_) => Node::Publish(k(0)),
2353 Node::SetHeader { .. } => Node::SetHeader {
2354 name: k(0),
2355 value: k(1),
2356 },
2357 Node::MutNew(_) => Node::MutNew(k(0)),
2358 Node::MutGet(_) => Node::MutGet(k(0)),
2359 Node::DiskRead(_) => Node::DiskRead(k(0)),
2360 Node::NetGet(_) => Node::NetGet(k(0)),
2361
2362 Node::StrConcat(_, _) => Node::StrConcat(k(0), k(1)),
2363 Node::StrEq(_, _) => Node::StrEq(k(0), k(1)),
2364 Node::StrContains { .. } => Node::StrContains {
2365 haystack: k(0),
2366 needle: k(1),
2367 },
2368 Node::StrStartsWith { .. } => Node::StrStartsWith {
2369 s: k(0),
2370 prefix: k(1),
2371 },
2372 Node::StrIndexOf { .. } => Node::StrIndexOf {
2373 haystack: k(0),
2374 needle: k(1),
2375 },
2376 Node::StrSlice { .. } => Node::StrSlice {
2377 s: k(0),
2378 start: k(1),
2379 len: k(2),
2380 },
2381 Node::ListCons { .. } => Node::ListCons {
2382 head: k(0),
2383 tail: k(1),
2384 },
2385 Node::OptionElse { .. } => Node::OptionElse {
2386 opt: k(0),
2387 default: k(1),
2388 },
2389 Node::OptionMatch {
2390 some_bind,
2391 ..
2392 } => Node::OptionMatch {
2393 opt: k(0),
2394 some_bind: some_bind.clone(),
2395 some_body: k(1),
2396 none_body: k(2),
2397 },
2398 Node::ListTryGet { .. } => Node::ListTryGet {
2399 list: k(0),
2400 index: k(1),
2401 },
2402 Node::ListGet { .. } => Node::ListGet {
2403 list: k(0),
2404 index: k(1),
2405 },
2406 Node::MapGet { .. } => Node::MapGet {
2407 map: k(0),
2408 key: k(1),
2409 },
2410 Node::MapTryGet { .. } => Node::MapTryGet {
2411 map: k(0),
2412 key: k(1),
2413 },
2414 Node::MutSet { .. } => Node::MutSet {
2415 cell: k(0),
2416 value: k(1),
2417 },
2418 Node::DiskWrite { .. } => Node::DiskWrite {
2419 path: k(0),
2420 content: k(1),
2421 },
2422 Node::DbQuery { .. } => Node::DbQuery {
2423 sql: k(0),
2424 params: k(1),
2425 },
2426 Node::BinOp { op, .. } => Node::BinOp {
2427 op: *op,
2428 lhs: k(0),
2429 rhs: k(1),
2430 },
2431 Node::FloatOp { op, .. } => Node::FloatOp {
2432 op: *op,
2433 lhs: k(0),
2434 rhs: k(1),
2435 },
2436 Node::DecimalOp { op, .. } => Node::DecimalOp {
2437 op: *op,
2438 lhs: k(0),
2439 rhs: k(1),
2440 },
2441 Node::If { .. } => Node::If {
2442 cond: k(0),
2443 then_branch: k(1),
2444 else_branch: k(2),
2445 },
2446 Node::List(elems) => {
2447 Node::List((0..elems.len()).map(k).collect())
2448 }
2449 Node::Map(pairs) => Node::Map(
2450 (0..pairs.len()).map(|i| (k(2 * i), k(2 * i + 1))).collect(),
2451 ),
2452 Node::Record {
2453 type_name, fields, ..
2454 } => Node::Record {
2455 type_name: type_name.clone(),
2456 fields: fields
2457 .iter()
2458 .enumerate()
2459 .map(|(i, (n, _))| (n.clone(), k(i)))
2460 .collect(),
2461 },
2462 Node::Variant {
2463 type_name,
2464 case,
2465 fields,
2466 } => Node::Variant {
2467 type_name: type_name.clone(),
2468 case: case.clone(),
2469 fields: fields
2470 .iter()
2471 .enumerate()
2472 .map(|(i, (n, _))| (n.clone(), k(i)))
2473 .collect(),
2474 },
2475 Node::Field {
2476 type_name, field, ..
2477 } => Node::Field {
2478 base: k(0),
2479 type_name: type_name.clone(),
2480 field: field.clone(),
2481 },
2482 Node::Match {
2483 type_name, arms, ..
2484 } => Node::Match {
2485 scrutinee: k(0),
2486 type_name: type_name.clone(),
2487 arms: arms
2488 .iter()
2489 .enumerate()
2490 .map(|(i, a)| MatchArm {
2491 body: k(i + 1),
2492 ..a.clone()
2493 })
2494 .collect(),
2495 },
2496 Node::Handle { handlers, .. } => Node::Handle {
2497 body: k(0),
2498 handlers: handlers
2499 .iter()
2500 .enumerate()
2501 .map(|(i, (n, _))| (n.clone(), k(i + 1)))
2502 .collect(),
2503 },
2504 Node::Call { func, args } => Node::Call {
2505 func: func.clone(),
2506 args: (0..args.len()).map(k).collect(),
2507 },
2508 Node::CallValue { args, .. } => Node::CallValue {
2509 callee: k(0),
2510 args: (0..args.len()).map(|i| k(i + 1)).collect(),
2511 },
2512 Node::Lambda { params, .. } => Node::Lambda {
2513 params: params.clone(),
2514 body: k(0),
2515 },
2516 Node::Step { binding, .. } => Node::Step {
2517 binding: binding.clone(),
2518 value: k(0),
2519 },
2520 Node::Function {
2521 name,
2522 type_params,
2523 params,
2524 produces,
2525 requires,
2526 on_failure,
2527 body,
2528 ..
2529 } => Node::Function {
2530 name: name.clone(),
2531 type_params: type_params.clone(),
2532 params: params.clone(),
2533 produces: produces.clone(),
2534 requires: requires.clone(),
2535 on_failure: on_failure.clone(),
2536 body: (0..body.len()).map(k).collect(),
2537 result: k(body.len()),
2538 },
2539 Node::Module {
2540 name,
2541 types,
2542 functions,
2543 } => Node::Module {
2544 name: name.clone(),
2545 types: (0..types.len()).map(k).collect(),
2546 functions: (0..functions.len())
2547 .map(|i| k(types.len() + i))
2548 .collect(),
2549 },
2550 }
2551}
2552
2553#[cfg(test)]
2554mod tests {
2555 use super::*;
2556 use crate::node::{BinOp, MatchArm, Param};
2557
2558 fn p(name: &str, ty: Type, c: Confidence) -> Param {
2559 Param {
2560 name: name.into(),
2561 ty,
2562 min_confidence: c,
2563 }
2564 }
2565
2566 fn produces(ty: Type, confidence: Confidence) -> Produces {
2567 Produces { ty, confidence }
2568 }
2569
2570 #[allow(clippy::too_many_arguments)]
2571 fn function(
2572 store: &Store,
2573 name: &str,
2574 params: Vec<Param>,
2575 prod: Produces,
2576 requires: BTreeSet<Effect>,
2577 on_failure: Vec<&str>,
2578 body: Vec<NodeHash>,
2579 result: NodeHash,
2580 ) -> NodeHash {
2581 store
2582 .put(&Node::Function {
2583 name: name.into(),
2584 type_params: vec![],
2585 params,
2586 produces: prod,
2587 requires,
2588 on_failure: on_failure.into_iter().map(String::from).collect(),
2589 body,
2590 result,
2591 })
2592 .unwrap()
2593 }
2594
2595 fn add_fn(s: &Store) -> NodeHash {
2599 let sum = s.put(&Node::Lit(0)).unwrap();
2600 function(
2601 s,
2602 "add",
2603 vec![
2604 p("a", Type::Number, Confidence::External),
2605 p("b", Type::Number, Confidence::External),
2606 ],
2607 produces(Type::Number, Confidence::Structural),
2608 BTreeSet::new(),
2609 vec![],
2610 vec![],
2611 sum,
2612 )
2613 }
2614
2615 #[test]
2616 fn a_well_typed_module_is_clean_and_complete() {
2617 let s = Store::open_in_memory().unwrap();
2618 let add = add_fn(&s);
2619 let n = s.put(&Node::Ref("n".into())).unwrap();
2620 let call = s
2621 .put(&Node::Call {
2622 func: "add".into(),
2623 args: vec![n.clone(), n],
2624 })
2625 .unwrap();
2626 let step = s
2627 .put(&Node::Step {
2628 binding: "d".into(),
2629 value: call,
2630 })
2631 .unwrap();
2632 let res = s.put(&Node::Ref("d".into())).unwrap();
2633 let double = function(
2634 &s,
2635 "double",
2636 vec![p("n", Type::Number, Confidence::External)],
2637 produces(Type::Number, Confidence::Structural),
2638 BTreeSet::new(),
2639 vec![],
2640 vec![step],
2641 res,
2642 );
2643 let m = s
2644 .put(&Node::Module {
2645 name: "m".into(),
2646 types: vec![],
2647 functions: vec![add, double],
2648 })
2649 .unwrap();
2650
2651 let r = Checker::new(&s).check(&m).unwrap();
2652 assert!(r.ok(), "unexpected: {:?}", r.violations);
2653 assert_eq!(r.status, Status::Complete);
2654 assert_eq!(r.failures, Failures::Exhaustive);
2655 }
2656
2657 #[test]
2658 fn the_boolean_layer_type_checks() {
2659 let s = Store::open_in_memory().unwrap();
2660 let t = s.put(&Node::Bool(true)).unwrap();
2663 let nott = s.put(&Node::Not(t.clone())).unwrap();
2664 let one = s.put(&Node::Lit(1)).unwrap();
2665 let two = s.put(&Node::Lit(2)).unwrap();
2666 let lt = s
2667 .put(&Node::BinOp {
2668 op: BinOp::Lt,
2669 lhs: one.clone(),
2670 rhs: two,
2671 })
2672 .unwrap();
2673 let or = s
2674 .put(&Node::BinOp {
2675 op: BinOp::Or,
2676 lhs: nott,
2677 rhs: lt,
2678 })
2679 .unwrap();
2680 let ok = function(
2681 &s,
2682 "ok",
2683 vec![],
2684 produces(Type::Bool, Confidence::Structural),
2685 BTreeSet::new(),
2686 vec![],
2687 vec![],
2688 or,
2689 );
2690 let r = Checker::new(&s).check(&ok).unwrap();
2691 assert!(r.ok(), "unexpected: {:?}", r.violations);
2692
2693 let five = s.put(&Node::Lit(5)).unwrap();
2695 let badnot = s.put(&Node::Not(five)).unwrap();
2696 let bf = function(
2697 &s,
2698 "bf",
2699 vec![],
2700 produces(Type::Bool, Confidence::Structural),
2701 BTreeSet::new(),
2702 vec![],
2703 vec![],
2704 badnot,
2705 );
2706 let r2 = Checker::new(&s).check(&bf).unwrap();
2707 assert!(!r2.ok());
2708 assert!(r2
2709 .violations
2710 .iter()
2711 .any(|v| v.principle == 2 && v.detail.contains("expected Bool")));
2712
2713 let andbad = s
2715 .put(&Node::BinOp {
2716 op: BinOp::And,
2717 lhs: one,
2718 rhs: t,
2719 })
2720 .unwrap();
2721 let ab = function(
2722 &s,
2723 "ab",
2724 vec![],
2725 produces(Type::Bool, Confidence::Structural),
2726 BTreeSet::new(),
2727 vec![],
2728 vec![],
2729 andbad,
2730 );
2731 let r3 = Checker::new(&s).check(&ab).unwrap();
2732 assert!(!r3.ok());
2733 assert!(r3
2734 .violations
2735 .iter()
2736 .any(|v| v.principle == 2 && v.detail.contains("logical operand")));
2737 }
2738
2739 #[test]
2740 fn function_values_type_check() {
2741 let s = Store::open_in_memory().unwrap();
2742 let fn_num_num = Type::Fn {
2743 params: vec![Type::Number],
2744 ret: Box::new(Type::Number),
2745 effects: BTreeSet::new(),
2746 };
2747 let dbl = function(
2750 &s,
2751 "double",
2752 vec![p("n", Type::Number, Confidence::Structural)],
2753 produces(Type::Number, Confidence::Structural),
2754 BTreeSet::new(),
2755 vec![],
2756 vec![],
2757 s.put(&Node::Ref("n".into())).unwrap(),
2758 );
2759 let call_f = s
2760 .put(&Node::CallValue {
2761 callee: s.put(&Node::Ref("f".into())).unwrap(),
2762 args: vec![s.put(&Node::Ref("x".into())).unwrap()],
2763 })
2764 .unwrap();
2765 let apply = function(
2766 &s,
2767 "apply",
2768 vec![
2769 p("f", fn_num_num.clone(), Confidence::Structural),
2770 p("x", Type::Number, Confidence::Structural),
2771 ],
2772 produces(Type::Number, Confidence::Structural),
2773 BTreeSet::new(),
2774 vec![],
2775 vec![],
2776 call_f,
2777 );
2778 let main = function(
2779 &s,
2780 "main",
2781 vec![],
2782 produces(Type::Number, Confidence::Structural),
2783 BTreeSet::new(),
2784 vec![],
2785 vec![],
2786 s.put(&Node::Call {
2787 func: "apply".into(),
2788 args: vec![
2789 s.put(&Node::FuncRef("double".into())).unwrap(),
2790 s.put(&Node::Lit(21)).unwrap(),
2791 ],
2792 })
2793 .unwrap(),
2794 );
2795 let m = s
2796 .put(&Node::Module {
2797 name: "m".into(),
2798 types: vec![],
2799 functions: vec![dbl, apply, main],
2800 })
2801 .unwrap();
2802 let r = Checker::new(&s).check(&m).unwrap();
2803 assert!(r.ok(), "unexpected: {:?}", r.violations);
2804
2805 let id = identity_fn(&s);
2807 let badgen = function(
2808 &s,
2809 "bg",
2810 vec![],
2811 produces(fn_num_num.clone(), Confidence::Structural),
2812 BTreeSet::new(),
2813 vec![],
2814 vec![],
2815 s.put(&Node::FuncRef("identity".into())).unwrap(),
2816 );
2817 let mg = s
2818 .put(&Node::Module {
2819 name: "mg".into(),
2820 types: vec![],
2821 functions: vec![id, badgen],
2822 })
2823 .unwrap();
2824 let rg = Checker::new(&s).check(&mg).unwrap();
2825 assert!(rg
2826 .violations
2827 .iter()
2828 .any(|v| v.principle == 2 && v.detail.contains("generic")));
2829
2830 let boom = function(
2832 &s,
2833 "boom",
2834 vec![],
2835 produces(Type::Number, Confidence::Structural),
2836 BTreeSet::new(),
2837 vec!["Boom"],
2838 vec![],
2839 s.put(&Node::Fail("Boom".into())).unwrap(),
2840 );
2841 let badfal = function(
2842 &s,
2843 "bf2",
2844 vec![],
2845 produces(fn_num_num, Confidence::Structural),
2846 BTreeSet::new(),
2847 vec![],
2848 vec![],
2849 s.put(&Node::FuncRef("boom".into())).unwrap(),
2850 );
2851 let mf = s
2852 .put(&Node::Module {
2853 name: "mf".into(),
2854 types: vec![],
2855 functions: vec![boom, badfal],
2856 })
2857 .unwrap();
2858 let rf = Checker::new(&s).check(&mf).unwrap();
2859 assert!(rf
2860 .violations
2861 .iter()
2862 .any(|v| v.principle == 2 && v.detail.contains("fallible")));
2863
2864 let badcall = function(
2866 &s,
2867 "bc",
2868 vec![],
2869 produces(Type::Number, Confidence::Structural),
2870 BTreeSet::new(),
2871 vec![],
2872 vec![],
2873 s.put(&Node::CallValue {
2874 callee: s.put(&Node::Lit(5)).unwrap(),
2875 args: vec![],
2876 })
2877 .unwrap(),
2878 );
2879 let rc = Checker::new(&s).check(&badcall).unwrap();
2880 assert!(rc
2881 .violations
2882 .iter()
2883 .any(|v| v.principle == 2 && v.detail.contains("not a function value")));
2884 }
2885
2886 #[test]
2887 fn closures_type_check_and_isolate_effects() {
2888 let s = Store::open_in_memory().unwrap();
2889 let lam_param = |name: &str| Param {
2890 name: name.into(),
2891 ty: Type::Number,
2892 min_confidence: Confidence::External,
2893 };
2894 let body = s
2896 .put(&Node::BinOp {
2897 op: BinOp::Add,
2898 lhs: s.put(&Node::Ref("x".into())).unwrap(),
2899 rhs: s.put(&Node::Ref("k".into())).unwrap(),
2900 })
2901 .unwrap();
2902 let lam = s
2903 .put(&Node::Lambda {
2904 params: vec![lam_param("x")],
2905 body,
2906 })
2907 .unwrap();
2908 let fn_num_num = Type::Fn {
2909 params: vec![Type::Number],
2910 ret: Box::new(Type::Number),
2911 effects: BTreeSet::new(),
2912 };
2913 let mk = function(
2914 &s,
2915 "mk",
2916 vec![p("k", Type::Number, Confidence::Structural)],
2917 produces(fn_num_num.clone(), Confidence::Structural),
2918 BTreeSet::new(),
2919 vec![],
2920 vec![],
2921 lam,
2922 );
2923 let r = Checker::new(&s)
2924 .check(
2925 &s.put(&Node::Module {
2926 name: "m".into(),
2927 types: vec![],
2928 functions: vec![mk],
2929 })
2930 .unwrap(),
2931 )
2932 .unwrap();
2933 assert!(r.ok(), "unexpected: {:?}", r.violations);
2934
2935 let fn_log = Type::Fn {
2940 params: vec![Type::Number],
2941 ret: Box::new(Type::Number),
2942 effects: [Effect::Log].into_iter().collect(),
2943 };
2944 let log_body = s
2945 .put(&Node::Log(s.put(&Node::Ref("x".into())).unwrap()))
2946 .unwrap();
2947 let log_lam = s
2948 .put(&Node::Lambda {
2949 params: vec![lam_param("x")],
2950 body: log_body,
2951 })
2952 .unwrap();
2953 let mklog = function(
2954 &s,
2955 "mklog",
2956 vec![],
2957 produces(fn_log.clone(), Confidence::Structural),
2958 BTreeSet::new(), vec![],
2960 vec![],
2961 log_lam,
2962 );
2963 let r2 = Checker::new(&s)
2964 .check(
2965 &s.put(&Node::Module {
2966 name: "m2".into(),
2967 types: vec![],
2968 functions: vec![mklog],
2969 })
2970 .unwrap(),
2971 )
2972 .unwrap();
2973 assert!(
2974 r2.ok(),
2975 "creating an effectful closure must be pure: {:?}",
2976 r2.violations
2977 );
2978
2979 let call_f = s
2982 .put(&Node::CallValue {
2983 callee: s.put(&Node::Ref("f".into())).unwrap(),
2984 args: vec![s.put(&Node::Lit(0)).unwrap()],
2985 })
2986 .unwrap();
2987 let runner = function(
2988 &s,
2989 "runner",
2990 vec![p("f", fn_log, Confidence::Structural)],
2991 produces(Type::Number, Confidence::Structural),
2992 BTreeSet::new(), vec![],
2994 vec![],
2995 call_f,
2996 );
2997 let r3 = Checker::new(&s)
2998 .check(
2999 &s.put(&Node::Module {
3000 name: "m3".into(),
3001 types: vec![],
3002 functions: vec![runner],
3003 })
3004 .unwrap(),
3005 )
3006 .unwrap();
3007 assert!(r3
3008 .violations
3009 .iter()
3010 .any(|v| v.principle == 5 && v.detail.contains("Log")));
3011
3012 let fail_lam = s
3014 .put(&Node::Lambda {
3015 params: vec![lam_param("x")],
3016 body: s.put(&Node::Fail("Boom".into())).unwrap(),
3017 })
3018 .unwrap();
3019 let mkfail = function(
3020 &s,
3021 "mkfail",
3022 vec![],
3023 produces(fn_num_num, Confidence::Structural),
3024 BTreeSet::new(),
3025 vec![],
3026 vec![],
3027 fail_lam,
3028 );
3029 let r4 = Checker::new(&s)
3030 .check(
3031 &s.put(&Node::Module {
3032 name: "m4".into(),
3033 types: vec![],
3034 functions: vec![mkfail],
3035 })
3036 .unwrap(),
3037 )
3038 .unwrap();
3039 assert!(r4
3040 .violations
3041 .iter()
3042 .any(|v| v.principle == 6 && v.detail.contains("uncaught failure")));
3043 }
3044
3045 #[test]
3046 fn option_match_type_checks() {
3047 let s = Store::open_in_memory().unwrap();
3048 let good = s
3050 .put(&Node::OptionMatch {
3051 opt: s.put(&Node::Ref("o".into())).unwrap(),
3052 some_bind: "v".into(),
3053 some_body: s
3054 .put(&Node::BinOp {
3055 op: BinOp::Add,
3056 lhs: s.put(&Node::Ref("v".into())).unwrap(),
3057 rhs: s.put(&Node::Lit(1)).unwrap(),
3058 })
3059 .unwrap(),
3060 none_body: s.put(&Node::Lit(0)).unwrap(),
3061 })
3062 .unwrap();
3063 let f = function(
3064 &s,
3065 "f",
3066 vec![p(
3067 "o",
3068 Type::Option(Box::new(Type::Number)),
3069 Confidence::Structural,
3070 )],
3071 produces(Type::Number, Confidence::Structural),
3072 BTreeSet::new(),
3073 vec![],
3074 vec![],
3075 good,
3076 );
3077 let r = Checker::new(&s).check(&f).unwrap();
3078 assert!(r.ok(), "unexpected: {:?}", r.violations);
3079
3080 let bad_scrut = s
3082 .put(&Node::OptionMatch {
3083 opt: s.put(&Node::Lit(5)).unwrap(),
3084 some_bind: "v".into(),
3085 some_body: s.put(&Node::Ref("v".into())).unwrap(),
3086 none_body: s.put(&Node::Lit(0)).unwrap(),
3087 })
3088 .unwrap();
3089 let bf = function(
3090 &s,
3091 "bf",
3092 vec![],
3093 produces(Type::Number, Confidence::Structural),
3094 BTreeSet::new(),
3095 vec![],
3096 vec![],
3097 bad_scrut,
3098 );
3099 let r2 = Checker::new(&s).check(&bf).unwrap();
3100 assert!(r2
3101 .violations
3102 .iter()
3103 .any(|v| v.principle == 2 && v.detail.contains("expected Option")));
3104
3105 let bad_arms = s
3107 .put(&Node::OptionMatch {
3108 opt: s.put(&Node::Ref("o".into())).unwrap(),
3109 some_bind: "v".into(),
3110 some_body: s.put(&Node::Str("x".into())).unwrap(),
3111 none_body: s.put(&Node::Lit(0)).unwrap(),
3112 })
3113 .unwrap();
3114 let ba = function(
3115 &s,
3116 "ba",
3117 vec![p(
3118 "o",
3119 Type::Option(Box::new(Type::Number)),
3120 Confidence::Structural,
3121 )],
3122 produces(Type::String, Confidence::Structural),
3123 BTreeSet::new(),
3124 vec![],
3125 vec![],
3126 bad_arms,
3127 );
3128 let r3 = Checker::new(&s).check(&ba).unwrap();
3129 assert!(r3
3130 .violations
3131 .iter()
3132 .any(|v| v.principle == 2 && v.detail.contains("arms differ")));
3133 }
3134
3135 #[test]
3136 fn float_type_checks() {
3137 let s = Store::open_in_memory().unwrap();
3138 let f = |v: f64| s.put(&Node::FloatLit(v.to_bits())).unwrap();
3139
3140 let good = function(
3142 &s,
3143 "g",
3144 vec![],
3145 produces(Type::Float, Confidence::Structural),
3146 BTreeSet::new(),
3147 vec![],
3148 vec![],
3149 s.put(&Node::FloatOp {
3150 op: BinOp::Mul,
3151 lhs: f(1.5),
3152 rhs: f(2.0),
3153 })
3154 .unwrap(),
3155 );
3156 let r = Checker::new(&s).check(&good).unwrap();
3157 assert!(r.ok(), "unexpected: {:?}", r.violations);
3158
3159 let mixed = function(
3161 &s,
3162 "mx",
3163 vec![],
3164 produces(Type::Float, Confidence::Structural),
3165 BTreeSet::new(),
3166 vec![],
3167 vec![],
3168 s.put(&Node::FloatOp {
3169 op: BinOp::Add,
3170 lhs: f(1.0),
3171 rhs: s.put(&Node::Lit(2)).unwrap(),
3172 })
3173 .unwrap(),
3174 );
3175 let r2 = Checker::new(&s).check(&mixed).unwrap();
3176 assert!(r2
3177 .violations
3178 .iter()
3179 .any(|v| v.principle == 2 && v.detail.contains("expected Float")));
3180
3181 let modf = function(
3183 &s,
3184 "mf",
3185 vec![],
3186 produces(Type::Float, Confidence::Structural),
3187 BTreeSet::new(),
3188 vec![],
3189 vec![],
3190 s.put(&Node::FloatOp {
3191 op: BinOp::Mod,
3192 lhs: f(5.0),
3193 rhs: f(2.0),
3194 })
3195 .unwrap(),
3196 );
3197 let r3 = Checker::new(&s).check(&modf).unwrap();
3198 assert!(r3
3199 .violations
3200 .iter()
3201 .any(|v| v.principle == 2 && v.detail.contains("not defined on Float")));
3202 }
3203
3204 #[test]
3205 fn decimal_type_checks() {
3206 let s = Store::open_in_memory().unwrap();
3207 let d = |v: i64| s.put(&Node::DecimalLit(v)).unwrap();
3208
3209 let good = function(
3210 &s,
3211 "g",
3212 vec![],
3213 produces(Type::Decimal, Confidence::Structural),
3214 BTreeSet::new(),
3215 vec![],
3216 vec![],
3217 s.put(&Node::DecimalOp {
3218 op: BinOp::Mul,
3219 lhs: d(12500),
3220 rhs: d(40000),
3221 })
3222 .unwrap(),
3223 );
3224 assert!(Checker::new(&s).check(&good).unwrap().ok());
3225
3226 let mixed = function(
3228 &s,
3229 "mx",
3230 vec![],
3231 produces(Type::Decimal, Confidence::Structural),
3232 BTreeSet::new(),
3233 vec![],
3234 vec![],
3235 s.put(&Node::DecimalOp {
3236 op: BinOp::Add,
3237 lhs: d(10000),
3238 rhs: s.put(&Node::Lit(2)).unwrap(),
3239 })
3240 .unwrap(),
3241 );
3242 let r2 = Checker::new(&s).check(&mixed).unwrap();
3243 assert!(r2
3244 .violations
3245 .iter()
3246 .any(|v| v.principle == 2 && v.detail.contains("expected Decimal")));
3247
3248 let modd = function(
3250 &s,
3251 "md",
3252 vec![],
3253 produces(Type::Decimal, Confidence::Structural),
3254 BTreeSet::new(),
3255 vec![],
3256 vec![],
3257 s.put(&Node::DecimalOp {
3258 op: BinOp::Mod,
3259 lhs: d(50000),
3260 rhs: d(20000),
3261 })
3262 .unwrap(),
3263 );
3264 let r3 = Checker::new(&s).check(&modd).unwrap();
3265 assert!(r3
3266 .violations
3267 .iter()
3268 .any(|v| v.principle == 2 && v.detail.contains("not defined on Decimal")));
3269 }
3270
3271 #[test]
3272 fn publish_requires_the_live_effect() {
3273 let s = Store::open_in_memory().unwrap();
3274 let body = || s.put(&Node::Publish(
3275 s.put(&Node::Str("items".into())).unwrap(),
3276 )).unwrap();
3277 let ok = function(
3279 &s,
3280 "notify",
3281 vec![],
3282 produces(Type::Number, Confidence::Structural),
3283 [Effect::Live].into_iter().collect(),
3284 vec![],
3285 vec![],
3286 body(),
3287 );
3288 assert!(Checker::new(&s).check(&ok).unwrap().ok());
3289 let bad = function(
3291 &s,
3292 "notify2",
3293 vec![],
3294 produces(Type::Number, Confidence::Structural),
3295 BTreeSet::new(),
3296 vec![],
3297 vec![],
3298 body(),
3299 );
3300 let r = Checker::new(&s).check(&bad).unwrap();
3301 assert!(
3302 r.violations
3303 .iter()
3304 .any(|v| v.principle == 5 && v.detail.contains("Live")),
3305 "publish without `requires Live` must be P5: {:?}",
3306 r.violations
3307 );
3308 }
3309
3310 #[test]
3311 fn numeric_and_closure_soundness() {
3312 let s = Store::open_in_memory().unwrap();
3318 let flt = |v: f64| s.put(&Node::FloatLit(v.to_bits())).unwrap();
3319 let dec = |v: i64| s.put(&Node::DecimalLit(v)).unwrap();
3320 let lit = |v: i64| s.put(&Node::Lit(v)).unwrap();
3321 let rf = |n: &str| s.put(&Node::Ref(n.into())).unwrap();
3322 let rejects = |f: &NodeHash, why: &str| {
3323 let r = Checker::new(&s).check(f).unwrap();
3324 assert!(
3325 !r.ok() && r.violations.iter().any(|v| v.principle == 2),
3326 "UNSOUND: checker accepted `{why}` — {:?}",
3327 r.violations
3328 );
3329 };
3330 let module = |fns: Vec<NodeHash>| {
3331 s.put(&Node::Module {
3332 name: "m".into(),
3333 types: vec![],
3334 functions: fns,
3335 })
3336 .unwrap()
3337 };
3338 let pnum = |n: &str| p(n, Type::Number, Confidence::Structural);
3339
3340 rejects(
3342 &function(
3343 &s,
3344 "r1",
3345 vec![],
3346 produces(Type::Number, Confidence::Structural),
3347 BTreeSet::new(),
3348 vec![],
3349 vec![],
3350 flt(1.0),
3351 ),
3352 "Float as Number result",
3353 );
3354 rejects(
3356 &function(
3357 &s,
3358 "r2",
3359 vec![],
3360 produces(Type::Number, Confidence::Structural),
3361 BTreeSet::new(),
3362 vec![],
3363 vec![],
3364 s.put(&Node::BinOp {
3365 op: BinOp::Add,
3366 lhs: lit(1),
3367 rhs: flt(2.0),
3368 })
3369 .unwrap(),
3370 ),
3371 "Number + Float",
3372 );
3373 rejects(
3375 &function(
3376 &s,
3377 "r3",
3378 vec![],
3379 produces(Type::Float, Confidence::Structural),
3380 BTreeSet::new(),
3381 vec![],
3382 vec![],
3383 s.put(&Node::FloatOp {
3384 op: BinOp::Add,
3385 lhs: flt(1.0),
3386 rhs: dec(20000),
3387 })
3388 .unwrap(),
3389 ),
3390 "Float + Decimal",
3391 );
3392 let idn = function(
3394 &s,
3395 "idn",
3396 vec![pnum("n")],
3397 produces(Type::Number, Confidence::Structural),
3398 BTreeSet::new(),
3399 vec![],
3400 vec![],
3401 rf("n"),
3402 );
3403 let c4 = function(
3404 &s,
3405 "c4",
3406 vec![],
3407 produces(Type::Number, Confidence::Structural),
3408 BTreeSet::new(),
3409 vec![],
3410 vec![],
3411 s.put(&Node::Call {
3412 func: "idn".into(),
3413 args: vec![flt(3.0)],
3414 })
3415 .unwrap(),
3416 );
3417 rejects(&module(vec![idn, c4]), "Float arg to Number param");
3418 rejects(
3420 &function(
3421 &s,
3422 "r5",
3423 vec![],
3424 produces(Type::Number, Confidence::Structural),
3425 BTreeSet::new(),
3426 vec![],
3427 vec![],
3428 s.put(&Node::CallValue {
3429 callee: s
3430 .put(&Node::Lambda {
3431 params: vec![pnum("x")],
3432 body: rf("x"),
3433 })
3434 .unwrap(),
3435 args: vec![lit(1), lit(2)],
3436 })
3437 .unwrap(),
3438 ),
3439 "closure arity mismatch",
3440 );
3441 rejects(
3443 &function(
3444 &s,
3445 "r6",
3446 vec![],
3447 produces(Type::Number, Confidence::Structural),
3448 BTreeSet::new(),
3449 vec![],
3450 vec![],
3451 s.put(&Node::CallValue {
3452 callee: s
3453 .put(&Node::Lambda {
3454 params: vec![pnum("x")],
3455 body: rf("x"),
3456 })
3457 .unwrap(),
3458 args: vec![flt(1.0)],
3459 })
3460 .unwrap(),
3461 ),
3462 "Float into Fn(Number)",
3463 );
3464 let apply = function(
3466 &s,
3467 "apply",
3468 vec![p(
3469 "f",
3470 Type::Fn {
3471 params: vec![Type::Number],
3472 ret: Box::new(Type::Number),
3473 effects: BTreeSet::new(),
3474 },
3475 Confidence::Structural,
3476 )],
3477 produces(Type::Number, Confidence::Structural),
3478 BTreeSet::new(),
3479 vec![],
3480 vec![],
3481 s.put(&Node::CallValue {
3482 callee: rf("f"),
3483 args: vec![lit(1)],
3484 })
3485 .unwrap(),
3486 );
3487 let c7 = function(
3488 &s,
3489 "c7",
3490 vec![],
3491 produces(Type::Number, Confidence::Structural),
3492 BTreeSet::new(),
3493 vec![],
3494 vec![],
3495 s.put(&Node::Call {
3496 func: "apply".into(),
3497 args: vec![s
3498 .put(&Node::Lambda {
3499 params: vec![pnum("x")],
3500 body: s.put(&Node::Str("nope".into())).unwrap(),
3501 })
3502 .unwrap()],
3503 })
3504 .unwrap(),
3505 );
3506 rejects(
3507 &module(vec![apply, c7]),
3508 "Fn(Number)->String where Fn(Number)->Number expected",
3509 );
3510 let phantom = s
3512 .put(&Node::Function {
3513 name: "phantom".into(),
3514 type_params: vec!["T".into()],
3515 params: vec![pnum("x")],
3516 produces: produces(
3517 Type::Option(Box::new(Type::Var("T".into()))),
3518 Confidence::Structural,
3519 ),
3520 requires: BTreeSet::new(),
3521 on_failure: vec![],
3522 body: vec![],
3523 result: s
3524 .put(&Node::OptionNone {
3525 elem: Type::Var("T".into()),
3526 })
3527 .unwrap(),
3528 })
3529 .unwrap();
3530 let usep = function(
3531 &s,
3532 "usep",
3533 vec![],
3534 produces(
3535 Type::Option(Box::new(Type::Number)),
3536 Confidence::Structural,
3537 ),
3538 BTreeSet::new(),
3539 vec![],
3540 vec![],
3541 s.put(&Node::Call {
3542 func: "phantom".into(),
3543 args: vec![lit(0)],
3544 })
3545 .unwrap(),
3546 );
3547 rejects(
3548 &module(vec![phantom, usep]),
3549 "type param only in result (uninferable)",
3550 );
3551 rejects(
3553 &function(
3554 &s,
3555 "r9",
3556 vec![p(
3557 "o",
3558 Type::Option(Box::new(Type::Number)),
3559 Confidence::Structural,
3560 )],
3561 produces(Type::Float, Confidence::Structural),
3562 BTreeSet::new(),
3563 vec![],
3564 vec![],
3565 s.put(&Node::OptionMatch {
3566 opt: rf("o"),
3567 some_bind: "v".into(),
3568 some_body: flt(1.0),
3569 none_body: lit(0),
3570 })
3571 .unwrap(),
3572 ),
3573 "OptionMatch Float/Number arms",
3574 );
3575 rejects(
3577 &function(
3578 &s,
3579 "r10",
3580 vec![],
3581 produces(Type::Float, Confidence::Structural),
3582 BTreeSet::new(),
3583 vec![],
3584 vec![],
3585 s.put(&Node::IntToFloat(
3586 s.put(&Node::Str("x".into())).unwrap(),
3587 ))
3588 .unwrap(),
3589 ),
3590 "to_float(String)",
3591 );
3592 }
3593
3594 #[test]
3595 fn effect_failure_confidence_soundness() {
3596 let s = Store::open_in_memory().unwrap();
3601 let lit = |v: i64| s.put(&Node::Lit(v)).unwrap();
3602 let rf = |n: &str| s.put(&Node::Ref(n.into())).unwrap();
3603 let module = |fns: Vec<NodeHash>| {
3604 s.put(&Node::Module {
3605 name: "m".into(),
3606 types: vec![],
3607 functions: fns,
3608 })
3609 .unwrap()
3610 };
3611 let rej = |f: &NodeHash, principle: u8, why: &str| {
3612 let r = Checker::new(&s).check(f).unwrap();
3613 assert!(
3614 !r.ok() && r.violations.iter().any(|v| v.principle == principle),
3615 "UNSOUND: accepted `{why}` (expected P{principle}) — {:?}",
3616 r.violations
3617 );
3618 };
3619 let truth = || {
3620 s.put(&Node::BinOp {
3621 op: BinOp::Eq,
3622 lhs: lit(0),
3623 rhs: lit(0),
3624 })
3625 .unwrap()
3626 };
3627
3628 rej(
3630 &function(
3631 &s,
3632 "e1",
3633 vec![],
3634 produces(Type::Number, Confidence::Structural),
3635 BTreeSet::new(),
3636 vec![],
3637 vec![],
3638 s.put(&Node::Log(lit(1))).unwrap(),
3639 ),
3640 5,
3641 "Log with requires {}",
3642 );
3643 rej(
3645 &function(
3646 &s,
3647 "e2",
3648 vec![],
3649 produces(Type::Number, Confidence::Structural),
3650 BTreeSet::new(),
3651 vec![],
3652 vec![],
3653 s.put(&Node::If {
3654 cond: truth(),
3655 then_branch: s.put(&Node::Log(lit(1))).unwrap(),
3656 else_branch: lit(0),
3657 })
3658 .unwrap(),
3659 ),
3660 5,
3661 "Log inside an If branch with requires {}",
3662 );
3663 let timed = function(
3665 &s,
3666 "timed",
3667 vec![],
3668 produces(Type::Number, Confidence::Structural),
3669 BTreeSet::from([Effect::Time]),
3670 vec![],
3671 vec![],
3672 s.put(&Node::Now).unwrap(),
3673 );
3674 let caller5 = function(
3675 &s,
3676 "caller5",
3677 vec![],
3678 produces(Type::Number, Confidence::Structural),
3679 BTreeSet::new(),
3680 vec![],
3681 vec![],
3682 s.put(&Node::Call {
3683 func: "timed".into(),
3684 args: vec![],
3685 })
3686 .unwrap(),
3687 );
3688 rej(
3689 &module(vec![timed, caller5]),
3690 5,
3691 "calling a Time fn without declaring Time",
3692 );
3693 rej(
3695 &function(
3696 &s,
3697 "f1",
3698 vec![],
3699 produces(Type::Number, Confidence::Structural),
3700 BTreeSet::new(),
3701 vec![],
3702 vec![],
3703 s.put(&Node::If {
3704 cond: truth(),
3705 then_branch: s.put(&Node::Fail("Boom".into())).unwrap(),
3706 else_branch: lit(0),
3707 })
3708 .unwrap(),
3709 ),
3710 6,
3711 "Fail in a branch, on_failure []",
3712 );
3713 rej(
3715 &function(
3716 &s,
3717 "f2",
3718 vec![],
3719 produces(Type::Number, Confidence::Structural),
3720 BTreeSet::new(),
3721 vec![],
3722 vec![],
3723 s.put(&Node::Handle {
3724 body: s.put(&Node::Fail("Y".into())).unwrap(),
3725 handlers: vec![("X".into(), lit(0))],
3726 })
3727 .unwrap(),
3728 ),
3729 6,
3730 "Handle X but body fails Y",
3731 );
3732 let raiser = function(
3734 &s,
3735 "raiser",
3736 vec![],
3737 produces(Type::Number, Confidence::Structural),
3738 BTreeSet::new(),
3739 vec!["Boom"],
3740 vec![],
3741 s.put(&Node::Fail("Boom".into())).unwrap(),
3742 );
3743 let caller6 = function(
3744 &s,
3745 "caller6",
3746 vec![],
3747 produces(Type::Number, Confidence::Structural),
3748 BTreeSet::new(),
3749 vec![],
3750 vec![],
3751 s.put(&Node::Call {
3752 func: "raiser".into(),
3753 args: vec![],
3754 })
3755 .unwrap(),
3756 );
3757 rej(
3758 &module(vec![raiser, caller6]),
3759 6,
3760 "calling a fallible fn without covering its failure",
3761 );
3762 let sink = function(
3764 &s,
3765 "sink",
3766 vec![p("x", Type::Number, Confidence::Validated)],
3767 produces(Type::Number, Confidence::Structural),
3768 BTreeSet::new(),
3769 vec![],
3770 vec![],
3771 rf("x"),
3772 );
3773 let weak = function(
3774 &s,
3775 "weak",
3776 vec![p("e", Type::Number, Confidence::External)],
3777 produces(Type::Number, Confidence::Structural),
3778 BTreeSet::new(),
3779 vec![],
3780 vec![],
3781 s.put(&Node::Call {
3782 func: "sink".into(),
3783 args: vec![rf("e")],
3784 })
3785 .unwrap(),
3786 );
3787 rej(
3788 &module(vec![sink, weak]),
3789 7,
3790 "External arg into a Validated param",
3791 );
3792 let sink2 = function(
3794 &s,
3795 "sink2",
3796 vec![p("x", Type::Number, Confidence::Validated)],
3797 produces(Type::Number, Confidence::Structural),
3798 BTreeSet::new(),
3799 vec![],
3800 vec![],
3801 rf("x"),
3802 );
3803 let derived = function(
3804 &s,
3805 "derived",
3806 vec![p("e", Type::Number, Confidence::External)],
3807 produces(Type::Number, Confidence::Structural),
3808 BTreeSet::new(),
3809 vec![],
3810 vec![],
3811 s.put(&Node::Call {
3812 func: "sink2".into(),
3813 args: vec![s
3814 .put(&Node::BinOp {
3815 op: BinOp::Add,
3816 lhs: rf("e"),
3817 rhs: lit(1),
3818 })
3819 .unwrap()],
3820 })
3821 .unwrap(),
3822 );
3823 rej(
3824 &module(vec![sink2, derived]),
3825 7,
3826 "weakest-input: (External + 1) into Validated param",
3827 );
3828 }
3829
3830 #[test]
3831 fn a_type_mismatch_is_a_principle_2_violation() {
3832 let s = Store::open_in_memory().unwrap();
3833 let lit = s.put(&Node::Lit(1)).unwrap();
3835 let f = function(
3836 &s,
3837 "f",
3838 vec![],
3839 produces(Type::String, Confidence::Structural),
3840 BTreeSet::new(),
3841 vec![],
3842 vec![],
3843 lit.clone(),
3844 );
3845 let r = Checker::new(&s).check(&f).unwrap();
3846 assert!(!r.ok());
3847 let v = &r.violations[0];
3848 assert_eq!(v.principle, 2);
3849 assert_eq!(v.node, lit);
3850 assert!(v.detail.contains("String"));
3851 }
3852
3853 #[test]
3854 fn passing_weaker_confidence_than_required_is_a_principle_7_violation() {
3855 let s = Store::open_in_memory().unwrap();
3856 let xref = s.put(&Node::Ref("x".into())).unwrap();
3858 let need = function(
3859 &s,
3860 "need",
3861 vec![p("x", Type::Number, Confidence::Validated)],
3862 produces(Type::Number, Confidence::Structural),
3863 BTreeSet::new(),
3864 vec![],
3865 vec![],
3866 xref,
3867 );
3868 let raw = s.put(&Node::Ref("raw".into())).unwrap();
3869 let call = s
3870 .put(&Node::Call {
3871 func: "need".into(),
3872 args: vec![raw.clone()],
3873 })
3874 .unwrap();
3875 let caller = function(
3876 &s,
3877 "caller",
3878 vec![p("raw", Type::Number, Confidence::External)],
3879 produces(Type::Number, Confidence::Structural),
3880 BTreeSet::new(),
3881 vec![],
3882 vec![],
3883 call,
3884 );
3885 let m = s
3886 .put(&Node::Module {
3887 name: "m".into(),
3888 types: vec![],
3889 functions: vec![need, caller],
3890 })
3891 .unwrap();
3892 let r = Checker::new(&s).check(&m).unwrap();
3893 assert!(r
3894 .violations
3895 .iter()
3896 .any(|v| v.principle == 7 && v.node == raw));
3897 }
3898
3899 #[test]
3900 fn an_undeclared_effect_is_a_principle_5_violation() {
3901 let s = Store::open_in_memory().unwrap();
3902 let unit = s.put(&Node::Lit(0)).unwrap();
3904 let mut db = BTreeSet::new();
3905 db.insert(Effect::Db);
3906 let writer = function(
3907 &s,
3908 "writer",
3909 vec![],
3910 produces(Type::Number, Confidence::Structural),
3911 db,
3912 vec![],
3913 vec![],
3914 unit,
3915 );
3916 let call = s
3917 .put(&Node::Call {
3918 func: "writer".into(),
3919 args: vec![],
3920 })
3921 .unwrap();
3922 let caller = function(
3923 &s,
3924 "caller",
3925 vec![],
3926 produces(Type::Number, Confidence::Structural),
3927 BTreeSet::new(), vec![],
3929 vec![],
3930 call,
3931 );
3932 let m = s
3933 .put(&Node::Module {
3934 name: "m".into(),
3935 types: vec![],
3936 functions: vec![writer, caller],
3937 })
3938 .unwrap();
3939 let r = Checker::new(&s).check(&m).unwrap();
3940 assert!(r.violations.iter().any(|v| v.principle == 5));
3941 assert!(r.effects.contains(&Effect::Db));
3942 }
3943
3944 #[test]
3945 fn an_uncovered_failure_is_a_principle_6_violation() {
3946 let s = Store::open_in_memory().unwrap();
3947 let unit = s.put(&Node::Lit(0)).unwrap();
3948 let risky = function(
3949 &s,
3950 "risky",
3951 vec![],
3952 produces(Type::Number, Confidence::Structural),
3953 BTreeSet::new(),
3954 vec!["Boom"],
3955 vec![],
3956 unit.clone(),
3957 );
3958 let call = s
3959 .put(&Node::Call {
3960 func: "risky".into(),
3961 args: vec![],
3962 })
3963 .unwrap();
3964 let caller = function(
3965 &s,
3966 "caller",
3967 vec![],
3968 produces(Type::Number, Confidence::Structural),
3969 BTreeSet::new(),
3970 vec![], vec![],
3972 call,
3973 );
3974 let m = s
3975 .put(&Node::Module {
3976 name: "m".into(),
3977 types: vec![],
3978 functions: vec![risky, caller],
3979 })
3980 .unwrap();
3981 let r = Checker::new(&s).check(&m).unwrap();
3982 assert!(r.violations.iter().any(|v| v.principle == 6));
3983 assert_eq!(r.failures, Failures::Uncovered(vec!["Boom".into()]));
3984 }
3985
3986 #[test]
3987 fn an_unresolved_reference_is_a_principle_1_violation() {
3988 let s = Store::open_in_memory().unwrap();
3989 let ghost = s.put(&Node::Ref("ghost".into())).unwrap();
3990 let f = function(
3991 &s,
3992 "f",
3993 vec![],
3994 produces(Type::Number, Confidence::Structural),
3995 BTreeSet::new(),
3996 vec![],
3997 vec![],
3998 ghost.clone(),
3999 );
4000 let r = Checker::new(&s).check(&f).unwrap();
4001 assert!(r
4002 .violations
4003 .iter()
4004 .any(|v| v.principle == 1 && v.node == ghost));
4005 }
4006
4007 #[test]
4008 fn a_hole_makes_it_incomplete_but_not_invalid() {
4009 let s = Store::open_in_memory().unwrap();
4010 let hole = s
4011 .put(&Node::Hole {
4012 expects: "Number".into(),
4013 })
4014 .unwrap();
4015 let f = function(
4016 &s,
4017 "f",
4018 vec![],
4019 produces(Type::Number, Confidence::Structural),
4020 BTreeSet::new(),
4021 vec![],
4022 vec![hole.clone()],
4023 s.put(&Node::Lit(0)).unwrap(),
4024 );
4025 let r = Checker::new(&s).check(&f).unwrap();
4026 assert_eq!(r.status, Status::Incomplete);
4027 assert_eq!(r.holes, vec![hole]);
4028 assert!(r.ok());
4029 }
4030
4031 #[test]
4032 fn a_missing_child_is_a_principle_4_violation() {
4033 let s = Store::open_in_memory().unwrap();
4034 let dangling = Node::Ref("never".into()).hash();
4035 let step = s
4036 .put(&Node::Step {
4037 binding: "x".into(),
4038 value: dangling.clone(),
4039 })
4040 .unwrap();
4041 let f = function(
4042 &s,
4043 "f",
4044 vec![],
4045 produces(Type::Number, Confidence::Structural),
4046 BTreeSet::new(),
4047 vec![],
4048 vec![step],
4049 s.put(&Node::Lit(0)).unwrap(),
4050 );
4051 let r = Checker::new(&s).check(&f).unwrap();
4052 assert!(r
4053 .violations
4054 .iter()
4055 .any(|v| v.principle == 4 && v.node == dangling));
4056 }
4057
4058 #[test]
4059 fn a_step_binding_is_not_in_scope_for_its_own_value() {
4060 let s = Store::open_in_memory().unwrap();
4061 let aref = s.put(&Node::Ref("a".into())).unwrap();
4062 let step = s
4063 .put(&Node::Step {
4064 binding: "a".into(),
4065 value: aref.clone(),
4066 })
4067 .unwrap();
4068 let f = function(
4069 &s,
4070 "f",
4071 vec![],
4072 produces(Type::Number, Confidence::Structural),
4073 BTreeSet::new(),
4074 vec![],
4075 vec![step],
4076 s.put(&Node::Lit(0)).unwrap(),
4077 );
4078 let r = Checker::new(&s).check(&f).unwrap();
4079 assert!(r
4080 .violations
4081 .iter()
4082 .any(|v| v.principle == 1 && v.node == aref));
4083 }
4084
4085 #[test]
4086 fn unchanged_subtrees_are_not_recomputed() {
4087 let s = Store::open_in_memory().unwrap();
4088 let f = function(
4089 &s,
4090 "f",
4091 vec![],
4092 produces(Type::Number, Confidence::Structural),
4093 BTreeSet::new(),
4094 vec![],
4095 vec![],
4096 s.put(&Node::Lit(1)).unwrap(),
4097 );
4098 let mut c = Checker::new(&s);
4099 c.check(&f).unwrap();
4100 let after_first = c.computed_count();
4101 assert!(after_first > 0);
4102 c.check(&f).unwrap();
4103 assert_eq!(c.computed_count(), after_first);
4104 }
4105
4106 #[test]
4110 fn arithmetic_confidence_is_the_weakest_input() {
4111 let s = Store::open_in_memory().unwrap();
4112 let a = s.put(&Node::Ref("a".into())).unwrap();
4113 let b = s.put(&Node::Ref("b".into())).unwrap();
4114 let sum = s
4115 .put(&Node::BinOp {
4116 op: BinOp::Add,
4117 lhs: a,
4118 rhs: b,
4119 })
4120 .unwrap();
4121 let f = function(
4122 &s,
4123 "combine",
4124 vec![
4125 p("a", Type::Number, Confidence::Validated),
4126 p("b", Type::Number, Confidence::External),
4127 ],
4128 produces(Type::Number, Confidence::Validated),
4129 BTreeSet::new(),
4130 vec![],
4131 vec![],
4132 sum.clone(),
4133 );
4134 let r = Checker::new(&s).check(&f).unwrap();
4135 assert!(
4136 r.violations
4137 .iter()
4138 .any(|v| v.principle == 7 && v.node == sum),
4139 "expected weakest-input P7, got {:?}",
4140 r.violations
4141 );
4142 assert_eq!(r.confidence, Some(Confidence::External));
4143 }
4144
4145 #[test]
4146 fn a_comparison_yields_bool() {
4147 let s = Store::open_in_memory().unwrap();
4148 let a = s.put(&Node::Ref("a".into())).unwrap();
4149 let b = s.put(&Node::Ref("b".into())).unwrap();
4150 let cmp = s
4151 .put(&Node::BinOp {
4152 op: BinOp::Lt,
4153 lhs: a,
4154 rhs: b,
4155 })
4156 .unwrap();
4157 let f = function(
4158 &s,
4159 "less",
4160 vec![
4161 p("a", Type::Number, Confidence::Structural),
4162 p("b", Type::Number, Confidence::Structural),
4163 ],
4164 produces(Type::Bool, Confidence::Structural),
4165 BTreeSet::new(),
4166 vec![],
4167 vec![],
4168 cmp,
4169 );
4170 let r = Checker::new(&s).check(&f).unwrap();
4171 assert!(r.ok(), "unexpected: {:?}", r.violations);
4172 }
4173
4174 #[test]
4175 fn arithmetic_on_a_bool_is_a_principle_2_violation() {
4176 let s = Store::open_in_memory().unwrap();
4177 let a = s.put(&Node::Ref("a".into())).unwrap();
4178 let b = s.put(&Node::Ref("b".into())).unwrap();
4179 let cmp = s
4180 .put(&Node::BinOp {
4181 op: BinOp::Lt,
4182 lhs: a.clone(),
4183 rhs: b,
4184 })
4185 .unwrap();
4186 let bad = s
4188 .put(&Node::BinOp {
4189 op: BinOp::Add,
4190 lhs: cmp,
4191 rhs: a,
4192 })
4193 .unwrap();
4194 let f = function(
4195 &s,
4196 "bad",
4197 vec![
4198 p("a", Type::Number, Confidence::Structural),
4199 p("b", Type::Number, Confidence::Structural),
4200 ],
4201 produces(Type::Number, Confidence::Structural),
4202 BTreeSet::new(),
4203 vec![],
4204 vec![],
4205 bad,
4206 );
4207 let r = Checker::new(&s).check(&f).unwrap();
4208 assert!(r.violations.iter().any(|v| v.principle == 2));
4209 }
4210
4211 #[test]
4212 fn a_non_bool_condition_is_a_principle_2_violation() {
4213 let s = Store::open_in_memory().unwrap();
4214 let one = s.put(&Node::Lit(1)).unwrap();
4215 let zero = s.put(&Node::Lit(0)).unwrap();
4216 let iff = s
4217 .put(&Node::If {
4218 cond: one.clone(), then_branch: one.clone(),
4220 else_branch: zero,
4221 })
4222 .unwrap();
4223 let f = function(
4224 &s,
4225 "f",
4226 vec![],
4227 produces(Type::Number, Confidence::Structural),
4228 BTreeSet::new(),
4229 vec![],
4230 vec![],
4231 iff,
4232 );
4233 let r = Checker::new(&s).check(&f).unwrap();
4234 assert!(r
4235 .violations
4236 .iter()
4237 .any(|v| v.principle == 2 && v.node == one));
4238 }
4239
4240 #[test]
4243 fn a_well_typed_conditional_is_clean() {
4244 let s = Store::open_in_memory().unwrap();
4245 let n = s.put(&Node::Ref("n".into())).unwrap();
4246 let zero = s.put(&Node::Lit(0)).unwrap();
4247 let cond = s
4248 .put(&Node::BinOp {
4249 op: BinOp::Lt,
4250 lhs: n.clone(),
4251 rhs: zero.clone(),
4252 })
4253 .unwrap();
4254 let neg = s
4255 .put(&Node::BinOp {
4256 op: BinOp::Sub,
4257 lhs: zero,
4258 rhs: n.clone(),
4259 })
4260 .unwrap();
4261 let iff = s
4262 .put(&Node::If {
4263 cond,
4264 then_branch: neg,
4265 else_branch: n,
4266 })
4267 .unwrap();
4268 let f = function(
4269 &s,
4270 "abs",
4271 vec![p("n", Type::Number, Confidence::External)],
4272 produces(Type::Number, Confidence::External),
4273 BTreeSet::new(),
4274 vec![],
4275 vec![],
4276 iff,
4277 );
4278 let r = Checker::new(&s).check(&f).unwrap();
4279 assert!(r.ok(), "unexpected: {:?}", r.violations);
4280 assert_eq!(r.confidence, Some(Confidence::External));
4281 }
4282
4283 #[test]
4284 fn an_uncovered_fail_is_a_principle_6_violation() {
4285 let s = Store::open_in_memory().unwrap();
4286 let boom = s.put(&Node::Fail("Boom".into())).unwrap();
4287 let f = function(
4288 &s,
4289 "f",
4290 vec![],
4291 produces(Type::Number, Confidence::Structural),
4292 BTreeSet::new(),
4293 vec![], vec![],
4295 boom,
4296 );
4297 let r = Checker::new(&s).check(&f).unwrap();
4298 assert!(r.violations.iter().any(|v| v.principle == 6));
4299 assert_eq!(r.failures, Failures::Uncovered(vec!["Boom".into()]));
4300 }
4301
4302 #[test]
4308 fn a_fallible_function_with_a_covered_failure_is_clean() {
4309 let s = Store::open_in_memory().unwrap();
4310 let a = s.put(&Node::Ref("a".into())).unwrap();
4311 let b = s.put(&Node::Ref("b".into())).unwrap();
4312 let zero = s.put(&Node::Lit(0)).unwrap();
4313 let is_zero = s
4314 .put(&Node::BinOp {
4315 op: BinOp::Eq,
4316 lhs: b.clone(),
4317 rhs: zero,
4318 })
4319 .unwrap();
4320 let boom = s.put(&Node::Fail("DivByZero".into())).unwrap();
4321 let div = s
4322 .put(&Node::BinOp {
4323 op: BinOp::Div,
4324 lhs: a,
4325 rhs: b,
4326 })
4327 .unwrap();
4328 let iff = s
4329 .put(&Node::If {
4330 cond: is_zero,
4331 then_branch: boom,
4332 else_branch: div,
4333 })
4334 .unwrap();
4335 let f = function(
4336 &s,
4337 "safe_div",
4338 vec![
4339 p("a", Type::Number, Confidence::Structural),
4340 p("b", Type::Number, Confidence::Structural),
4341 ],
4342 produces(Type::Number, Confidence::Structural),
4343 BTreeSet::new(),
4344 vec!["DivByZero"],
4345 vec![],
4346 iff,
4347 );
4348 let r = Checker::new(&s).check(&f).unwrap();
4349 assert!(r.ok(), "unexpected: {:?}", r.violations);
4350 assert_eq!(r.failures, Failures::Exhaustive);
4351 }
4352
4353 #[test]
4354 fn handle_catches_a_failure_so_it_need_not_propagate() {
4355 let s = Store::open_in_memory().unwrap();
4356 let unit = s.put(&Node::Lit(0)).unwrap();
4358 let risky = function(
4359 &s,
4360 "risky",
4361 vec![],
4362 produces(Type::Number, Confidence::Structural),
4363 BTreeSet::new(),
4364 vec!["Boom"],
4365 vec![],
4366 unit,
4367 );
4368 let call = s
4370 .put(&Node::Call {
4371 func: "risky".into(),
4372 args: vec![],
4373 })
4374 .unwrap();
4375 let zero = s.put(&Node::Lit(0)).unwrap();
4376 let handle = s
4377 .put(&Node::Handle {
4378 body: call,
4379 handlers: vec![("Boom".into(), zero)],
4380 })
4381 .unwrap();
4382 let step = s
4383 .put(&Node::Step {
4384 binding: "x".into(),
4385 value: handle,
4386 })
4387 .unwrap();
4388 let xref = s.put(&Node::Ref("x".into())).unwrap();
4389 let caller = function(
4390 &s,
4391 "caller",
4392 vec![],
4393 produces(Type::Number, Confidence::Structural),
4394 BTreeSet::new(),
4395 vec![], vec![step],
4397 xref,
4398 );
4399 let m = s
4400 .put(&Node::Module {
4401 name: "m".into(),
4402 types: vec![],
4403 functions: vec![risky, caller],
4404 })
4405 .unwrap();
4406 let r = Checker::new(&s).check(&m).unwrap();
4407 assert!(r.ok(), "unexpected: {:?}", r.violations);
4408 assert_eq!(r.failures, Failures::Exhaustive);
4409 }
4410
4411 fn point_def(s: &Store) -> NodeHash {
4412 s.put(&Node::RecordDef {
4413 name: "Point".into(),
4414 fields: vec![
4415 ("x".into(), Type::Number),
4416 ("y".into(), Type::Number),
4417 ],
4418 })
4419 .unwrap()
4420 }
4421
4422 #[test]
4423 fn a_record_module_typechecks_clean() {
4424 let s = Store::open_in_memory().unwrap();
4425 let pd = point_def(&s);
4426
4427 let a = s.put(&Node::Ref("a".into())).unwrap();
4429 let zero = s.put(&Node::Lit(0)).unwrap();
4430 let rec = s
4431 .put(&Node::Record {
4432 type_name: "Point".into(),
4433 fields: vec![("x".into(), a), ("y".into(), zero)],
4434 })
4435 .unwrap();
4436 let mk = function(
4437 &s,
4438 "mk",
4439 vec![p("a", Type::Number, Confidence::Structural)],
4440 produces(Type::Named("Point".into()), Confidence::Structural),
4441 BTreeSet::new(),
4442 vec![],
4443 vec![],
4444 rec,
4445 );
4446
4447 let pref = s.put(&Node::Ref("pt".into())).unwrap();
4449 let fx = s
4450 .put(&Node::Field {
4451 base: pref,
4452 type_name: "Point".into(),
4453 field: "x".into(),
4454 })
4455 .unwrap();
4456 let getx = function(
4457 &s,
4458 "getx",
4459 vec![p(
4460 "pt",
4461 Type::Named("Point".into()),
4462 Confidence::Structural,
4463 )],
4464 produces(Type::Number, Confidence::Structural),
4465 BTreeSet::new(),
4466 vec![],
4467 vec![],
4468 fx,
4469 );
4470
4471 let m = s
4472 .put(&Node::Module {
4473 name: "m".into(),
4474 types: vec![pd],
4475 functions: vec![mk, getx],
4476 })
4477 .unwrap();
4478 let r = Checker::new(&s).check(&m).unwrap();
4479 assert!(r.ok(), "unexpected: {:?}", r.violations);
4480 }
4481
4482 #[test]
4483 fn a_wrong_field_type_is_a_principle_2_violation() {
4484 let s = Store::open_in_memory().unwrap();
4485 let pd = point_def(&s);
4486 let a = s.put(&Node::Ref("a".into())).unwrap();
4488 let b = s.put(&Node::Ref("b".into())).unwrap();
4489 let cmp = s
4490 .put(&Node::BinOp {
4491 op: BinOp::Lt,
4492 lhs: a,
4493 rhs: b,
4494 })
4495 .unwrap();
4496 let zero = s.put(&Node::Lit(0)).unwrap();
4497 let rec = s
4498 .put(&Node::Record {
4499 type_name: "Point".into(),
4500 fields: vec![("x".into(), cmp), ("y".into(), zero)],
4501 })
4502 .unwrap();
4503 let mk = function(
4504 &s,
4505 "mk",
4506 vec![
4507 p("a", Type::Number, Confidence::Structural),
4508 p("b", Type::Number, Confidence::Structural),
4509 ],
4510 produces(Type::Named("Point".into()), Confidence::Structural),
4511 BTreeSet::new(),
4512 vec![],
4513 vec![],
4514 rec,
4515 );
4516 let m = s
4517 .put(&Node::Module {
4518 name: "m".into(),
4519 types: vec![pd],
4520 functions: vec![mk],
4521 })
4522 .unwrap();
4523 let r = Checker::new(&s).check(&m).unwrap();
4524 assert!(r.violations.iter().any(|v| v.principle == 2));
4525 }
4526
4527 #[test]
4528 fn an_unknown_record_type_is_a_principle_1_violation() {
4529 let s = Store::open_in_memory().unwrap();
4530 let z = s.put(&Node::Lit(0)).unwrap();
4531 let rec = s
4532 .put(&Node::Record {
4533 type_name: "Nope".into(),
4534 fields: vec![("a".into(), z)],
4535 })
4536 .unwrap();
4537 let f = function(
4538 &s,
4539 "f",
4540 vec![],
4541 produces(Type::Named("Nope".into()), Confidence::Structural),
4542 BTreeSet::new(),
4543 vec![],
4544 vec![],
4545 rec,
4546 );
4547 let r = Checker::new(&s).check(&f).unwrap();
4548 assert!(r.violations.iter().any(|v| v.principle == 1));
4549 }
4550
4551 fn status_def(s: &Store) -> NodeHash {
4554 s.put(&Node::VariantDef {
4555 name: "Status".into(),
4556 cases: vec![
4557 ("Active".into(), vec![]),
4558 ("Closed".into(), vec![("reason".into(), Type::Number)]),
4559 ],
4560 })
4561 .unwrap()
4562 }
4563
4564 #[test]
4565 fn a_variant_module_with_exhaustive_match_is_clean() {
4566 let s = Store::open_in_memory().unwrap();
4567 let sd = status_def(&s);
4568
4569 let st = s.put(&Node::Ref("st".into())).unwrap();
4571 let zero = s.put(&Node::Lit(0)).unwrap();
4572 let rref = s.put(&Node::Ref("reason".into())).unwrap();
4573 let m = s
4574 .put(&Node::Match {
4575 scrutinee: st,
4576 type_name: "Status".into(),
4577 arms: vec![
4578 MatchArm {
4579 case: "Active".into(),
4580 bindings: vec![],
4581 body: zero,
4582 },
4583 MatchArm {
4584 case: "Closed".into(),
4585 bindings: vec!["reason".into()],
4586 body: rref,
4587 },
4588 ],
4589 })
4590 .unwrap();
4591 let describe = function(
4592 &s,
4593 "describe",
4594 vec![p(
4595 "st",
4596 Type::Named("Status".into()),
4597 Confidence::Structural,
4598 )],
4599 produces(Type::Number, Confidence::Structural),
4600 BTreeSet::new(),
4601 vec![],
4602 vec![],
4603 m,
4604 );
4605 let module = s
4606 .put(&Node::Module {
4607 name: "m".into(),
4608 types: vec![sd],
4609 functions: vec![describe],
4610 })
4611 .unwrap();
4612 let r = Checker::new(&s).check(&module).unwrap();
4613 assert!(r.ok(), "unexpected: {:?}", r.violations);
4614 }
4615
4616 #[test]
4617 fn a_non_exhaustive_match_is_a_principle_2_violation() {
4618 let s = Store::open_in_memory().unwrap();
4619 let sd = status_def(&s);
4620 let st = s.put(&Node::Ref("st".into())).unwrap();
4621 let zero = s.put(&Node::Lit(0)).unwrap();
4622 let m = s
4624 .put(&Node::Match {
4625 scrutinee: st,
4626 type_name: "Status".into(),
4627 arms: vec![MatchArm {
4628 case: "Active".into(),
4629 bindings: vec![],
4630 body: zero,
4631 }],
4632 })
4633 .unwrap();
4634 let f = function(
4635 &s,
4636 "f",
4637 vec![p(
4638 "st",
4639 Type::Named("Status".into()),
4640 Confidence::Structural,
4641 )],
4642 produces(Type::Number, Confidence::Structural),
4643 BTreeSet::new(),
4644 vec![],
4645 vec![],
4646 m,
4647 );
4648 let module = s
4649 .put(&Node::Module {
4650 name: "m".into(),
4651 types: vec![sd],
4652 functions: vec![f],
4653 })
4654 .unwrap();
4655 let r = Checker::new(&s).check(&module).unwrap();
4656 assert!(r
4657 .violations
4658 .iter()
4659 .any(|v| v.principle == 2 && v.detail.contains("Closed")));
4660 }
4661
4662 #[test]
4663 fn constructing_an_unknown_case_is_a_principle_2_violation() {
4664 let s = Store::open_in_memory().unwrap();
4665 let sd = status_def(&s);
4666 let bad = s
4667 .put(&Node::Variant {
4668 type_name: "Status".into(),
4669 case: "Nope".into(),
4670 fields: vec![],
4671 })
4672 .unwrap();
4673 let f = function(
4674 &s,
4675 "mk",
4676 vec![],
4677 produces(Type::Named("Status".into()), Confidence::Structural),
4678 BTreeSet::new(),
4679 vec![],
4680 vec![],
4681 bad,
4682 );
4683 let module = s
4684 .put(&Node::Module {
4685 name: "m".into(),
4686 types: vec![sd],
4687 functions: vec![f],
4688 })
4689 .unwrap();
4690 let r = Checker::new(&s).check(&module).unwrap();
4691 assert!(r
4692 .violations
4693 .iter()
4694 .any(|v| v.principle == 2 && v.detail.contains("Nope")));
4695 }
4696
4697 fn identity_fn(s: &Store) -> NodeHash {
4700 let x = s.put(&Node::Ref("x".into())).unwrap();
4701 s.put(&Node::Function {
4702 name: "identity".into(),
4703 type_params: vec!["T".into()],
4704 params: vec![p("x", Type::Var("T".into()), Confidence::Structural)],
4705 produces: Produces {
4706 ty: Type::Var("T".into()),
4707 confidence: Confidence::Structural,
4708 },
4709 requires: BTreeSet::new(),
4710 on_failure: vec![],
4711 body: vec![],
4712 result: x,
4713 })
4714 .unwrap()
4715 }
4716
4717 #[test]
4718 fn a_generic_function_resolves_at_the_call_site() {
4719 let s = Store::open_in_memory().unwrap();
4720 let id = identity_fn(&s);
4721 let n = s.put(&Node::Ref("n".into())).unwrap();
4722 let call = s
4723 .put(&Node::Call {
4724 func: "identity".into(),
4725 args: vec![n],
4726 })
4727 .unwrap();
4728 let f = function(
4729 &s,
4730 "use_id",
4731 vec![p("n", Type::Number, Confidence::Structural)],
4732 produces(Type::Number, Confidence::Structural),
4733 BTreeSet::new(),
4734 vec![],
4735 vec![],
4736 call,
4737 );
4738 let m = s
4739 .put(&Node::Module {
4740 name: "m".into(),
4741 types: vec![],
4742 functions: vec![id, f],
4743 })
4744 .unwrap();
4745 let r = Checker::new(&s).check(&m).unwrap();
4746 assert!(r.ok(), "unexpected: {:?}", r.violations);
4747 }
4748
4749 #[test]
4750 fn a_generic_argument_conflict_is_a_principle_2_violation() {
4751 let s = Store::open_in_memory().unwrap();
4752 let aref = s.put(&Node::Ref("a".into())).unwrap();
4754 let pair = s
4755 .put(&Node::Function {
4756 name: "pair".into(),
4757 type_params: vec!["T".into()],
4758 params: vec![
4759 p("a", Type::Var("T".into()), Confidence::Structural),
4760 p("b", Type::Var("T".into()), Confidence::Structural),
4761 ],
4762 produces: Produces {
4763 ty: Type::Var("T".into()),
4764 confidence: Confidence::Structural,
4765 },
4766 requires: BTreeSet::new(),
4767 on_failure: vec![],
4768 body: vec![],
4769 result: aref,
4770 })
4771 .unwrap();
4772 let n1 = s.put(&Node::Ref("n".into())).unwrap();
4774 let n2 = s.put(&Node::Ref("n".into())).unwrap();
4775 let n3 = s.put(&Node::Ref("n".into())).unwrap();
4776 let cmp = s
4777 .put(&Node::BinOp {
4778 op: BinOp::Lt,
4779 lhs: n2,
4780 rhs: n3,
4781 })
4782 .unwrap();
4783 let call = s
4784 .put(&Node::Call {
4785 func: "pair".into(),
4786 args: vec![n1, cmp],
4787 })
4788 .unwrap();
4789 let g = function(
4790 &s,
4791 "g",
4792 vec![p("n", Type::Number, Confidence::Structural)],
4793 produces(Type::Number, Confidence::Structural),
4794 BTreeSet::new(),
4795 vec![],
4796 vec![],
4797 call,
4798 );
4799 let m = s
4800 .put(&Node::Module {
4801 name: "m".into(),
4802 types: vec![],
4803 functions: vec![pair, g],
4804 })
4805 .unwrap();
4806 let r = Checker::new(&s).check(&m).unwrap();
4807 assert!(r.violations.iter().any(|v| v.principle == 2));
4808 }
4809
4810 #[test]
4811 fn string_literal_and_str_len_typecheck() {
4812 let s = Store::open_in_memory().unwrap();
4813 let hello = s.put(&Node::Str("hello".into())).unwrap();
4815 let greet = function(
4816 &s,
4817 "greet",
4818 vec![],
4819 produces(Type::String, Confidence::Structural),
4820 BTreeSet::new(),
4821 vec![],
4822 vec![],
4823 hello,
4824 );
4825 let h2 = s.put(&Node::Str("hello".into())).unwrap();
4827 let sl = s.put(&Node::StrLen(h2)).unwrap();
4828 let size = function(
4829 &s,
4830 "size",
4831 vec![],
4832 produces(Type::Number, Confidence::Structural),
4833 BTreeSet::new(),
4834 vec![],
4835 vec![],
4836 sl,
4837 );
4838 let m = s
4839 .put(&Node::Module {
4840 name: "m".into(),
4841 types: vec![],
4842 functions: vec![greet, size],
4843 })
4844 .unwrap();
4845 let r = Checker::new(&s).check(&m).unwrap();
4846 assert!(r.ok(), "unexpected: {:?}", r.violations);
4847 }
4848
4849 #[test]
4850 fn str_len_on_a_number_is_a_principle_2_violation() {
4851 let s = Store::open_in_memory().unwrap();
4852 let n = s.put(&Node::Lit(3)).unwrap();
4853 let sl = s.put(&Node::StrLen(n)).unwrap();
4854 let f = function(
4855 &s,
4856 "f",
4857 vec![],
4858 produces(Type::Number, Confidence::Structural),
4859 BTreeSet::new(),
4860 vec![],
4861 vec![],
4862 sl,
4863 );
4864 let r = Checker::new(&s).check(&f).unwrap();
4865 assert!(r.violations.iter().any(|v| v.principle == 2));
4866 }
4867
4868 #[test]
4869 fn a_homogeneous_list_typechecks_and_a_mixed_one_does_not() {
4870 let s = Store::open_in_memory().unwrap();
4871 let e0 = s.put(&Node::Lit(1)).unwrap();
4873 let e1 = s.put(&Node::Lit(2)).unwrap();
4874 let lst = s.put(&Node::List(vec![e0, e1])).unwrap();
4875 let idx = s.put(&Node::Lit(0)).unwrap();
4876 let g = s
4877 .put(&Node::ListGet {
4878 list: lst,
4879 index: idx,
4880 })
4881 .unwrap();
4882 let ok = function(
4883 &s,
4884 "ok",
4885 vec![],
4886 produces(Type::Number, Confidence::Structural),
4887 BTreeSet::new(),
4888 vec![],
4889 vec![],
4890 g,
4891 );
4892 assert!(Checker::new(&s).check(&ok).unwrap().ok());
4893
4894 let n = s.put(&Node::Lit(1)).unwrap();
4896 let t = s.put(&Node::Str("x".into())).unwrap();
4897 let mixed = s.put(&Node::List(vec![n, t])).unwrap();
4898 let len = s.put(&Node::ListLen(mixed)).unwrap();
4899 let bad = function(
4900 &s,
4901 "bad",
4902 vec![],
4903 produces(Type::Number, Confidence::Structural),
4904 BTreeSet::new(),
4905 vec![],
4906 vec![],
4907 len,
4908 );
4909 let r = Checker::new(&s).check(&bad).unwrap();
4910 assert!(r.violations.iter().any(|v| v.principle == 2));
4911 }
4912}