1use crate::check::{Checker, Report};
26use crate::node::{BinOp, MatchArm, Node, NodeHash, Param, Produces};
27use crate::store::Store;
28use crate::ty::{Confidence, Effect, Type};
29use serde::{Deserialize, Serialize};
30use std::collections::BTreeSet;
31
32#[derive(Clone, Debug, Serialize, Deserialize)]
36pub enum ExprSpec {
37 Lit(i64),
38 Float(f64),
40 FloatOp {
41 op: BinOp,
42 lhs: Box<ExprSpec>,
43 rhs: Box<ExprSpec>,
44 },
45 IntToFloat(Box<ExprSpec>),
46 FloatToInt(Box<ExprSpec>),
47 Decimal(f64),
50 DecimalOp {
51 op: BinOp,
52 lhs: Box<ExprSpec>,
53 rhs: Box<ExprSpec>,
54 },
55 IntToDecimal(Box<ExprSpec>),
56 DecimalToInt(Box<ExprSpec>),
57 DecimalRaw(Box<ExprSpec>),
58 Bool(bool),
59 Not(Box<ExprSpec>),
60 Str(String),
61 StrLen(Box<ExprSpec>),
62 StrLower(Box<ExprSpec>),
63 StrFromCode(Box<ExprSpec>),
64 NumberToStr(Box<ExprSpec>),
65 StrToNumber(Box<ExprSpec>),
66 StrToNumberOpt(Box<ExprSpec>),
67 StrConcat(Box<ExprSpec>, Box<ExprSpec>),
68 StrSlice {
69 s: Box<ExprSpec>,
70 start: Box<ExprSpec>,
71 len: Box<ExprSpec>,
72 },
73 StrEq(Box<ExprSpec>, Box<ExprSpec>),
74 StrContains {
75 haystack: Box<ExprSpec>,
76 needle: Box<ExprSpec>,
77 },
78 StrStartsWith {
79 s: Box<ExprSpec>,
80 prefix: Box<ExprSpec>,
81 },
82 StrIndexOf {
83 haystack: Box<ExprSpec>,
84 needle: Box<ExprSpec>,
85 },
86 Now,
87 List(Vec<ExprSpec>),
88 ListEmpty {
89 elem: Type,
90 },
91 ListCons {
92 head: Box<ExprSpec>,
93 tail: Box<ExprSpec>,
94 },
95 OptionSome(Box<ExprSpec>),
96 OptionNone {
97 elem: Type,
98 },
99 OptionElse {
100 opt: Box<ExprSpec>,
101 default: Box<ExprSpec>,
102 },
103 OptionMatch {
104 opt: Box<ExprSpec>,
105 some_bind: String,
106 some_body: Box<ExprSpec>,
107 none_body: Box<ExprSpec>,
108 },
109 ListTryGet {
110 list: Box<ExprSpec>,
111 index: Box<ExprSpec>,
112 },
113 ListLen(Box<ExprSpec>),
114 ListGet {
115 list: Box<ExprSpec>,
116 index: Box<ExprSpec>,
117 },
118 Map(Vec<(ExprSpec, ExprSpec)>),
119 MapGet {
120 map: Box<ExprSpec>,
121 key: Box<ExprSpec>,
122 },
123 MapTryGet {
124 map: Box<ExprSpec>,
125 key: Box<ExprSpec>,
126 },
127 MapLen(Box<ExprSpec>),
128 Log(Box<ExprSpec>),
129 Publish(Box<ExprSpec>),
130 SetHeader {
131 name: Box<ExprSpec>,
132 value: Box<ExprSpec>,
133 },
134 Rand,
135 MutNew(Box<ExprSpec>),
136 MutGet(Box<ExprSpec>),
137 MutSet {
138 cell: Box<ExprSpec>,
139 value: Box<ExprSpec>,
140 },
141 DiskWrite {
142 path: Box<ExprSpec>,
143 content: Box<ExprSpec>,
144 },
145 DiskRead(Box<ExprSpec>),
146 NetGet(Box<ExprSpec>),
147 DbQuery {
148 sql: Box<ExprSpec>,
149 params: Box<ExprSpec>,
150 },
151 Ref(String),
152 Call {
153 func: String,
154 args: Vec<ExprSpec>,
155 },
156 FuncRef(String),
157 CallValue {
158 callee: Box<ExprSpec>,
159 args: Vec<ExprSpec>,
160 },
161 Lambda {
164 params: Vec<(String, Type)>,
165 body: Box<ExprSpec>,
166 },
167 BinOp {
168 op: BinOp,
169 lhs: Box<ExprSpec>,
170 rhs: Box<ExprSpec>,
171 },
172 If {
173 cond: Box<ExprSpec>,
174 then_branch: Box<ExprSpec>,
175 else_branch: Box<ExprSpec>,
176 },
177 Fail(String),
178 Handle {
179 body: Box<ExprSpec>,
180 handlers: Vec<(String, ExprSpec)>,
181 },
182 Record {
183 type_name: String,
184 fields: Vec<(String, ExprSpec)>,
185 },
186 Field {
187 base: Box<ExprSpec>,
188 type_name: String,
189 field: String,
190 },
191 Variant {
192 type_name: String,
193 case: String,
194 fields: Vec<(String, ExprSpec)>,
195 },
196 Match {
197 scrutinee: Box<ExprSpec>,
198 type_name: String,
199 arms: Vec<(String, Vec<String>, ExprSpec)>,
201 },
202 Hole {
203 expects: String,
204 },
205}
206
207#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
210pub struct HoleInfo {
211 pub expects: String,
212 pub in_scope: Vec<String>,
213 pub effects_allowed: Vec<Effect>,
214}
215
216#[derive(Clone, Debug, Serialize)]
220pub struct SignatureInfo {
221 pub name: String,
222 pub type_params: Vec<String>,
223 pub params: Vec<Param>,
224 pub produces: Produces,
225 pub requires: Vec<Effect>,
226 pub on_failure: Vec<String>,
227 pub rendered: String,
229}
230
231#[derive(Clone, Debug, Serialize, Deserialize)]
233pub struct StepSpec {
234 pub binding: String,
235 pub value: ExprSpec,
236}
237
238#[derive(Clone, Debug, Serialize, Deserialize)]
242pub enum TypeDefSpec {
243 Record {
244 name: String,
245 fields: Vec<(String, Type)>,
246 },
247 Variant {
248 name: String,
249 cases: Vec<(String, Vec<(String, Type)>)>,
251 },
252}
253
254#[derive(Clone, Debug, Serialize, Deserialize)]
260pub struct ModuleSpec {
261 pub name: String,
262 #[serde(default)]
263 pub types: Vec<TypeDefSpec>,
264 #[serde(default)]
265 pub functions: Vec<FunctionSpec>,
266}
267
268#[derive(Clone, Debug, Serialize, Deserialize)]
271pub struct FunctionSpec {
272 pub name: String,
273 #[serde(default)]
274 pub type_params: Vec<String>,
275 #[serde(default)]
276 pub params: Vec<Param>,
277 pub produces: Produces,
278 #[serde(default)]
279 pub requires: BTreeSet<Effect>,
280 #[serde(default)]
281 pub on_failure: Vec<String>,
282 #[serde(default)]
283 pub steps: Vec<StepSpec>,
284 pub result: ExprSpec,
285}
286
287#[derive(Debug)]
288pub enum EditError {
289 Store(crate::store::Error),
290 NoTransaction,
292 TransactionOpen,
294 NoFunction,
296 FunctionExists,
298 ResultNotSet,
300 Lower(String),
301 Run(String),
302 NotAHole(NodeHash),
305}
306
307impl std::fmt::Display for EditError {
308 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
309 match self {
310 EditError::Store(e) => write!(f, "store error: {e}"),
311 EditError::NoTransaction => write!(f, "no edit transaction is open"),
312 EditError::TransactionOpen => write!(f, "an edit transaction is already open"),
313 EditError::NoFunction => write!(f, "no function in the draft; call create_function"),
314 EditError::FunctionExists => write!(f, "the draft already has a function"),
315 EditError::ResultNotSet => write!(f, "result not set; call set_yield before commit"),
316 EditError::Lower(e) => write!(f, "lower error: {e}"),
317 EditError::Run(e) => write!(f, "run error: {e}"),
318 EditError::NotAHole(h) => {
319 write!(f, "node {h} is not a hole; fill_hole only fills Node::Hole")
320 }
321 }
322 }
323}
324
325impl std::error::Error for EditError {}
326
327impl From<crate::store::Error> for EditError {
328 fn from(e: crate::store::Error) -> Self {
329 EditError::Store(e)
330 }
331}
332
333type Result<T> = std::result::Result<T, EditError>;
334
335#[derive(Clone)]
336struct FunctionDraft {
337 name: String,
338 type_params: Vec<String>,
339 params: Vec<Param>,
340 produces: Produces,
341 requires: BTreeSet<Effect>,
342 on_failure: Vec<String>,
343 steps: Vec<(String, ExprSpec)>,
344 result: Option<ExprSpec>,
345}
346
347pub struct Editor {
350 store: Store,
351 txn: Option<Option<FunctionDraft>>,
354}
355
356impl Editor {
357 pub fn new(store: Store) -> Self {
358 Self { store, txn: None }
359 }
360
361 pub fn store(&self) -> &Store {
363 &self.store
364 }
365
366 pub fn begin_edit(&mut self) -> Result<()> {
367 if self.txn.is_some() {
368 return Err(EditError::TransactionOpen);
369 }
370 self.txn = Some(None);
371 Ok(())
372 }
373
374 pub fn abort_edit(&mut self) {
375 self.txn = None;
376 }
377
378 pub fn create_function(&mut self, name: &str) -> Result<()> {
379 match self.txn.as_mut() {
380 None => Err(EditError::NoTransaction),
381 Some(Some(_)) => Err(EditError::FunctionExists),
382 Some(slot @ None) => {
383 *slot = Some(FunctionDraft {
384 name: name.to_string(),
385 type_params: Vec::new(),
386 params: Vec::new(),
387 produces: Produces {
388 ty: Type::Number,
389 confidence: Confidence::Structural,
390 },
391 requires: BTreeSet::new(),
392 on_failure: Vec::new(),
393 steps: Vec::new(),
394 result: None,
395 });
396 Ok(())
397 }
398 }
399 }
400
401 fn draft(&mut self) -> Result<&mut FunctionDraft> {
402 match self.txn.as_mut() {
403 None => Err(EditError::NoTransaction),
404 Some(None) => Err(EditError::NoFunction),
405 Some(Some(d)) => Ok(d),
406 }
407 }
408
409 pub fn add_param(&mut self, name: &str, ty: Type, min_confidence: Confidence) -> Result<()> {
410 self.draft()?.params.push(Param {
411 name: name.to_string(),
412 ty,
413 min_confidence,
414 });
415 Ok(())
416 }
417
418 pub fn set_type_params(&mut self, type_params: Vec<String>) -> Result<()> {
419 self.draft()?.type_params = type_params;
420 Ok(())
421 }
422
423 pub fn set_produces(&mut self, ty: Type, confidence: Confidence) -> Result<()> {
424 self.draft()?.produces = Produces { ty, confidence };
425 Ok(())
426 }
427
428 pub fn set_effects(&mut self, effects: BTreeSet<Effect>) -> Result<()> {
429 self.draft()?.requires = effects;
430 Ok(())
431 }
432
433 pub fn set_on_failure(&mut self, failures: Vec<String>) -> Result<()> {
434 self.draft()?.on_failure = failures;
435 Ok(())
436 }
437
438 pub fn add_step(&mut self, binding: &str, value: ExprSpec) -> Result<()> {
439 self.draft()?.steps.push((binding.to_string(), value));
440 Ok(())
441 }
442
443 pub fn set_yield(&mut self, value: ExprSpec) -> Result<()> {
444 self.draft()?.result = Some(value);
445 Ok(())
446 }
447
448 pub fn describe_hole(&mut self) -> Result<HoleInfo> {
452 let d = self.draft()?;
453 let mut in_scope: Vec<String> = d.params.iter().map(|p| p.name.clone()).collect();
454 in_scope.extend(d.steps.iter().map(|(b, _)| b.clone()));
455 Ok(HoleInfo {
456 expects: format!("{:?}", d.produces.ty),
457 in_scope,
458 effects_allowed: d.requires.iter().copied().collect(),
459 })
460 }
461
462 pub fn commit_edit(&mut self) -> Result<(NodeHash, Report)> {
467 let draft = match self.txn.take() {
468 None => return Err(EditError::NoTransaction),
469 Some(None) => return Err(EditError::NoFunction),
470 Some(Some(d)) => d,
471 };
472 let result_spec = draft.result.as_ref().ok_or(EditError::ResultNotSet)?;
473
474 let mut body = Vec::with_capacity(draft.steps.len());
475 for (binding, spec) in &draft.steps {
476 let value = self.put_expr(spec)?;
477 body.push(self.store.put(&Node::Step {
478 binding: binding.clone(),
479 value,
480 })?);
481 }
482 let result = self.put_expr(result_spec)?;
483
484 let fn_hash = self.store.put(&Node::Function {
485 name: draft.name.clone(),
486 type_params: draft.type_params.clone(),
487 params: draft.params.clone(),
488 produces: draft.produces.clone(),
489 requires: draft.requires.clone(),
490 on_failure: draft.on_failure.clone(),
491 body,
492 result,
493 })?;
494
495 let report = Checker::new(&self.store).check(&fn_hash)?;
496 Ok((fn_hash, report))
497 }
498
499 pub fn apply_function(&mut self, spec: &FunctionSpec) -> Result<(NodeHash, Report)> {
504 self.begin_edit()?;
505 self.create_function(&spec.name)?;
506 self.set_type_params(spec.type_params.clone())?;
507 for p in &spec.params {
508 self.add_param(&p.name, p.ty.clone(), p.min_confidence)?;
509 }
510 self.set_produces(spec.produces.ty.clone(), spec.produces.confidence)?;
511 self.set_effects(spec.requires.clone())?;
512 self.set_on_failure(spec.on_failure.clone())?;
513 for s in &spec.steps {
514 self.add_step(&s.binding, s.value.clone())?;
515 }
516 self.set_yield(spec.result.clone())?;
517 self.commit_edit()
518 }
519
520 pub fn define_type(&self, spec: &TypeDefSpec) -> Result<NodeHash> {
523 let node = match spec {
524 TypeDefSpec::Record { name, fields } => Node::RecordDef {
525 name: name.clone(),
526 fields: fields.clone(),
527 },
528 TypeDefSpec::Variant { name, cases } => Node::VariantDef {
529 name: name.clone(),
530 cases: cases.clone(),
531 },
532 };
533 Ok(self.store.put(&node)?)
534 }
535
536 fn materialize_function(&self, spec: &FunctionSpec) -> Result<NodeHash> {
541 let mut body = Vec::with_capacity(spec.steps.len());
542 for s in &spec.steps {
543 let value = self.put_expr(&s.value)?;
544 body.push(self.store.put(&Node::Step {
545 binding: s.binding.clone(),
546 value,
547 })?);
548 }
549 let result = self.put_expr(&spec.result)?;
550 Ok(self.store.put(&Node::Function {
551 name: spec.name.clone(),
552 type_params: spec.type_params.clone(),
553 params: spec.params.clone(),
554 produces: spec.produces.clone(),
555 requires: spec.requires.clone(),
556 on_failure: spec.on_failure.clone(),
557 body,
558 result,
559 })?)
560 }
561
562 pub fn apply_module(&self, spec: &ModuleSpec) -> Result<(NodeHash, Report)> {
570 let mut types = Vec::with_capacity(spec.types.len());
571 for t in &spec.types {
572 types.push(self.define_type(t)?);
573 }
574 let mut functions = Vec::with_capacity(spec.functions.len());
575 for f in &spec.functions {
576 functions.push(self.materialize_function(f)?);
577 }
578 let module = self.store.put(&Node::Module {
579 name: spec.name.clone(),
580 types,
581 functions,
582 })?;
583 let report = Checker::new(&self.store).check(&module)?;
584 Ok((module, report))
585 }
586
587 pub fn run_module(
591 &self,
592 module: &NodeHash,
593 name: &str,
594 args: &[i64],
595 ) -> Result<i64> {
596 let wasm = crate::wasm::lower(&self.store, module)
597 .map_err(|e| EditError::Lower(e.to_string()))?;
598 crate::wasm::run_i64(&wasm, name, args)
599 .map_err(|e| EditError::Run(e.to_string()))
600 }
601
602 #[allow(clippy::too_many_arguments)]
612 pub fn run_handler(
613 &self,
614 module: &NodeHash,
615 handler: &str,
616 db_path: &str,
617 method: &str,
618 path: &str,
619 body: &str,
620 headers: &str,
621 ) -> Result<crate::wasm::HttpResponse> {
622 let wasm = crate::wasm::lower(&self.store, module)
623 .map_err(|e| EditError::Lower(e.to_string()))?;
624 crate::wasm::serve_request_db_h(
625 &wasm, handler, db_path, method, path, body, headers,
626 )
627 .map_err(|e| EditError::Run(e.to_string()))
628 }
629
630 pub fn query_type(
634 &self,
635 module: &NodeHash,
636 name: &str,
637 ) -> Result<Option<SignatureInfo>> {
638 let Some(Node::Module { functions, .. }) = self.store.get(module)?
639 else {
640 return Ok(None);
641 };
642 for fh in &functions {
643 if let Some(Node::Function {
644 name: n,
645 type_params,
646 params,
647 produces,
648 requires,
649 on_failure,
650 ..
651 }) = self.store.get(fh)?
652 {
653 if n == name {
654 let rendered = crate::render::render(&self.store, fh)
655 .unwrap_or_else(|_| String::new());
656 return Ok(Some(SignatureInfo {
657 name: n,
658 type_params,
659 params,
660 produces,
661 requires: requires.into_iter().collect(),
662 on_failure,
663 rendered,
664 }));
665 }
666 }
667 }
668 Ok(None)
669 }
670
671 pub fn find_references(
680 &self,
681 root: &NodeHash,
682 target: &NodeHash,
683 ) -> Result<Vec<String>> {
684 let mut refs: BTreeSet<String> = BTreeSet::new();
685 let mut seen: std::collections::HashSet<NodeHash> =
686 std::collections::HashSet::new();
687 let mut stack = vec![root.clone()];
688 while let Some(h) = stack.pop() {
689 if !seen.insert(h.clone()) {
690 continue;
691 }
692 let Some(node) = self.store.get(&h)? else {
693 continue;
694 };
695 for child in crate::check::child_hashes(&node) {
696 if child == target {
697 refs.insert(h.to_string());
698 }
699 stack.push(child.clone());
700 }
701 }
702 Ok(refs.into_iter().collect())
703 }
704
705 pub fn replace_node(
715 &self,
716 root: &NodeHash,
717 target: &NodeHash,
718 replacement: &NodeHash,
719 ) -> Result<(NodeHash, Report)> {
720 let mut cache: std::collections::HashMap<NodeHash, NodeHash> =
721 std::collections::HashMap::new();
722 let new_root = self.rewrite(root, target, replacement, &mut cache)?;
723 let report = Checker::new(&self.store).check(&new_root)?;
724 Ok((new_root, report))
725 }
726
727 fn rewrite(
728 &self,
729 h: &NodeHash,
730 target: &NodeHash,
731 replacement: &NodeHash,
732 cache: &mut std::collections::HashMap<NodeHash, NodeHash>,
733 ) -> Result<NodeHash> {
734 if h == target {
735 return Ok(replacement.clone());
736 }
737 if let Some(c) = cache.get(h) {
738 return Ok(c.clone());
739 }
740 let Some(node) = self.store.get(h)? else {
741 return Ok(h.clone());
742 };
743 let kids = crate::check::child_hashes(&node);
744 let mut new_kids = Vec::with_capacity(kids.len());
745 let mut changed = false;
746 for c in kids {
747 let nc = self.rewrite(c, target, replacement, cache)?;
748 if &nc != c {
749 changed = true;
750 }
751 new_kids.push(nc);
752 }
753 let out = if changed {
754 let rebuilt = crate::check::with_child_hashes(&node, &new_kids);
755 self.store.put(&rebuilt)?
756 } else {
757 h.clone()
758 };
759 cache.insert(h.clone(), out.clone());
760 Ok(out)
761 }
762
763 pub fn fill_hole(
775 &self,
776 root: &NodeHash,
777 hole: &NodeHash,
778 replacement: &NodeHash,
779 ) -> Result<(NodeHash, Report)> {
780 match self.store.get(hole)? {
781 Some(Node::Hole { .. }) => {}
782 _ => return Err(EditError::NotAHole(hole.clone())),
783 }
784 let (candidate, report) = self.replace_node(root, hole, replacement)?;
785 if report.ok() {
786 Ok((candidate, report))
787 } else {
788 Ok((root.clone(), report))
789 }
790 }
791
792 pub fn put_expr(&self, spec: &ExprSpec) -> std::result::Result<NodeHash, crate::store::Error> {
799 let node = match spec {
800 ExprSpec::Lit(v) => Node::Lit(*v),
801 ExprSpec::Float(f) => Node::FloatLit(f.to_bits()),
802 ExprSpec::FloatOp { op, lhs, rhs } => Node::FloatOp {
803 op: *op,
804 lhs: self.put_expr(lhs)?,
805 rhs: self.put_expr(rhs)?,
806 },
807 ExprSpec::IntToFloat(a) => Node::IntToFloat(self.put_expr(a)?),
808 ExprSpec::FloatToInt(a) => Node::FloatToInt(self.put_expr(a)?),
809 ExprSpec::Decimal(v) => {
810 Node::DecimalLit((v * 10_000.0).round() as i64)
811 }
812 ExprSpec::DecimalOp { op, lhs, rhs } => Node::DecimalOp {
813 op: *op,
814 lhs: self.put_expr(lhs)?,
815 rhs: self.put_expr(rhs)?,
816 },
817 ExprSpec::IntToDecimal(a) => Node::IntToDecimal(self.put_expr(a)?),
818 ExprSpec::DecimalToInt(a) => Node::DecimalToInt(self.put_expr(a)?),
819 ExprSpec::DecimalRaw(a) => Node::DecimalRaw(self.put_expr(a)?),
820 ExprSpec::Bool(b) => Node::Bool(*b),
821 ExprSpec::Not(a) => Node::Not(self.put_expr(a)?),
822 ExprSpec::Str(s) => Node::Str(s.clone()),
823 ExprSpec::StrLen(a) => Node::StrLen(self.put_expr(a)?),
824 ExprSpec::StrLower(a) => Node::StrLower(self.put_expr(a)?),
825 ExprSpec::StrFromCode(a) => {
826 Node::StrFromCode(self.put_expr(a)?)
827 }
828 ExprSpec::NumberToStr(a) => Node::NumberToStr(self.put_expr(a)?),
829 ExprSpec::StrToNumber(a) => Node::StrToNumber(self.put_expr(a)?),
830 ExprSpec::StrToNumberOpt(a) => {
831 Node::StrToNumberOpt(self.put_expr(a)?)
832 }
833 ExprSpec::StrConcat(a, b) => {
834 Node::StrConcat(self.put_expr(a)?, self.put_expr(b)?)
835 }
836 ExprSpec::StrSlice { s, start, len } => Node::StrSlice {
837 s: self.put_expr(s)?,
838 start: self.put_expr(start)?,
839 len: self.put_expr(len)?,
840 },
841 ExprSpec::StrEq(a, b) => {
842 Node::StrEq(self.put_expr(a)?, self.put_expr(b)?)
843 }
844 ExprSpec::StrContains { haystack, needle } => Node::StrContains {
845 haystack: self.put_expr(haystack)?,
846 needle: self.put_expr(needle)?,
847 },
848 ExprSpec::StrStartsWith { s, prefix } => Node::StrStartsWith {
849 s: self.put_expr(s)?,
850 prefix: self.put_expr(prefix)?,
851 },
852 ExprSpec::StrIndexOf { haystack, needle } => Node::StrIndexOf {
853 haystack: self.put_expr(haystack)?,
854 needle: self.put_expr(needle)?,
855 },
856 ExprSpec::Now => Node::Now,
857 ExprSpec::List(es) => {
858 let mut hs = Vec::with_capacity(es.len());
859 for e in es {
860 hs.push(self.put_expr(e)?);
861 }
862 Node::List(hs)
863 }
864 ExprSpec::ListEmpty { elem } => Node::ListEmpty { elem: elem.clone() },
865 ExprSpec::ListCons { head, tail } => Node::ListCons {
866 head: self.put_expr(head)?,
867 tail: self.put_expr(tail)?,
868 },
869 ExprSpec::OptionSome(v) => Node::OptionSome(self.put_expr(v)?),
870 ExprSpec::OptionNone { elem } => Node::OptionNone {
871 elem: elem.clone(),
872 },
873 ExprSpec::OptionElse { opt, default } => Node::OptionElse {
874 opt: self.put_expr(opt)?,
875 default: self.put_expr(default)?,
876 },
877 ExprSpec::OptionMatch {
878 opt,
879 some_bind,
880 some_body,
881 none_body,
882 } => Node::OptionMatch {
883 opt: self.put_expr(opt)?,
884 some_bind: some_bind.clone(),
885 some_body: self.put_expr(some_body)?,
886 none_body: self.put_expr(none_body)?,
887 },
888 ExprSpec::ListTryGet { list, index } => Node::ListTryGet {
889 list: self.put_expr(list)?,
890 index: self.put_expr(index)?,
891 },
892 ExprSpec::ListLen(a) => Node::ListLen(self.put_expr(a)?),
893 ExprSpec::ListGet { list, index } => Node::ListGet {
894 list: self.put_expr(list)?,
895 index: self.put_expr(index)?,
896 },
897 ExprSpec::Map(pairs) => {
898 let mut hs = Vec::with_capacity(pairs.len());
899 for (k, v) in pairs {
900 hs.push((self.put_expr(k)?, self.put_expr(v)?));
901 }
902 Node::Map(hs)
903 }
904 ExprSpec::MapGet { map, key } => Node::MapGet {
905 map: self.put_expr(map)?,
906 key: self.put_expr(key)?,
907 },
908 ExprSpec::MapTryGet { map, key } => Node::MapTryGet {
909 map: self.put_expr(map)?,
910 key: self.put_expr(key)?,
911 },
912 ExprSpec::MapLen(a) => Node::MapLen(self.put_expr(a)?),
913 ExprSpec::Log(a) => Node::Log(self.put_expr(a)?),
914 ExprSpec::Publish(a) => Node::Publish(self.put_expr(a)?),
915 ExprSpec::SetHeader { name, value } => Node::SetHeader {
916 name: self.put_expr(name)?,
917 value: self.put_expr(value)?,
918 },
919 ExprSpec::Rand => Node::Rand,
920 ExprSpec::MutNew(v) => Node::MutNew(self.put_expr(v)?),
921 ExprSpec::MutGet(cl) => Node::MutGet(self.put_expr(cl)?),
922 ExprSpec::MutSet { cell, value } => Node::MutSet {
923 cell: self.put_expr(cell)?,
924 value: self.put_expr(value)?,
925 },
926 ExprSpec::DiskWrite { path, content } => Node::DiskWrite {
927 path: self.put_expr(path)?,
928 content: self.put_expr(content)?,
929 },
930 ExprSpec::DiskRead(p) => Node::DiskRead(self.put_expr(p)?),
931 ExprSpec::NetGet(u) => Node::NetGet(self.put_expr(u)?),
932 ExprSpec::DbQuery { sql, params } => Node::DbQuery {
933 sql: self.put_expr(sql)?,
934 params: self.put_expr(params)?,
935 },
936 ExprSpec::Ref(n) => Node::Ref(n.clone()),
937 ExprSpec::Hole { expects } => Node::Hole {
938 expects: expects.clone(),
939 },
940 ExprSpec::Call { func, args } => {
941 let mut hs = Vec::with_capacity(args.len());
942 for a in args {
943 hs.push(self.put_expr(a)?);
944 }
945 Node::Call {
946 func: func.clone(),
947 args: hs,
948 }
949 }
950 ExprSpec::Lambda { params, body } => {
951 let b = self.put_expr(body)?;
952 Node::Lambda {
953 params: params
954 .iter()
955 .map(|(n, t)| Param {
956 name: n.clone(),
957 ty: t.clone(),
958 min_confidence: Confidence::External,
959 })
960 .collect(),
961 body: b,
962 }
963 }
964 ExprSpec::FuncRef(name) => Node::FuncRef(name.clone()),
965 ExprSpec::CallValue { callee, args } => {
966 let cl = self.put_expr(callee)?;
967 let mut hs = Vec::with_capacity(args.len());
968 for a in args {
969 hs.push(self.put_expr(a)?);
970 }
971 Node::CallValue {
972 callee: cl,
973 args: hs,
974 }
975 }
976 ExprSpec::BinOp { op, lhs, rhs } => {
977 let l = self.put_expr(lhs)?;
978 let r = self.put_expr(rhs)?;
979 Node::BinOp {
980 op: *op,
981 lhs: l,
982 rhs: r,
983 }
984 }
985 ExprSpec::If {
986 cond,
987 then_branch,
988 else_branch,
989 } => {
990 let c = self.put_expr(cond)?;
991 let t = self.put_expr(then_branch)?;
992 let e = self.put_expr(else_branch)?;
993 Node::If {
994 cond: c,
995 then_branch: t,
996 else_branch: e,
997 }
998 }
999 ExprSpec::Fail(v) => Node::Fail(v.clone()),
1000 ExprSpec::Handle { body, handlers } => {
1001 let b = self.put_expr(body)?;
1002 let mut hs = Vec::with_capacity(handlers.len());
1003 for (variant, recover) in handlers {
1004 hs.push((variant.clone(), self.put_expr(recover)?));
1005 }
1006 Node::Handle {
1007 body: b,
1008 handlers: hs,
1009 }
1010 }
1011 ExprSpec::Record { type_name, fields } => {
1012 let mut fs = Vec::with_capacity(fields.len());
1013 for (n, v) in fields {
1014 fs.push((n.clone(), self.put_expr(v)?));
1015 }
1016 Node::Record {
1017 type_name: type_name.clone(),
1018 fields: fs,
1019 }
1020 }
1021 ExprSpec::Field {
1022 base,
1023 type_name,
1024 field,
1025 } => {
1026 let b = self.put_expr(base)?;
1027 Node::Field {
1028 base: b,
1029 type_name: type_name.clone(),
1030 field: field.clone(),
1031 }
1032 }
1033 ExprSpec::Variant {
1034 type_name,
1035 case,
1036 fields,
1037 } => {
1038 let mut fs = Vec::with_capacity(fields.len());
1039 for (n, v) in fields {
1040 fs.push((n.clone(), self.put_expr(v)?));
1041 }
1042 Node::Variant {
1043 type_name: type_name.clone(),
1044 case: case.clone(),
1045 fields: fs,
1046 }
1047 }
1048 ExprSpec::Match {
1049 scrutinee,
1050 type_name,
1051 arms,
1052 } => {
1053 let sc = self.put_expr(scrutinee)?;
1054 let mut ms = Vec::with_capacity(arms.len());
1055 for (case, bindings, body) in arms {
1056 ms.push(MatchArm {
1057 case: case.clone(),
1058 bindings: bindings.clone(),
1059 body: self.put_expr(body)?,
1060 });
1061 }
1062 Node::Match {
1063 scrutinee: sc,
1064 type_name: type_name.clone(),
1065 arms: ms,
1066 }
1067 }
1068 };
1069 self.store.put(&node)
1070 }
1071
1072 pub fn run(&self, func: &NodeHash, name: &str, args: &[i64]) -> Result<i64> {
1076 let module = self.store.put(&Node::Module {
1077 name: "main".into(),
1078 types: vec![],
1079 functions: vec![func.clone()],
1080 })?;
1081 let wasm = crate::wasm::lower(&self.store, &module)
1082 .map_err(|e| EditError::Lower(e.to_string()))?;
1083 crate::wasm::run_i64(&wasm, name, args).map_err(|e| EditError::Run(e.to_string()))
1084 }
1085}
1086
1087#[cfg(test)]
1088mod tests {
1089 use super::*;
1090 use crate::check::Status;
1091
1092 #[test]
1094 fn worked_session_author_check_and_run() {
1095 let s = Store::open_in_memory().unwrap();
1096 let mut e = Editor::new(s);
1097
1098 e.begin_edit().unwrap();
1099 e.create_function("id").unwrap();
1100 e.add_param("n", Type::Number, Confidence::External).unwrap();
1101 e.set_produces(Type::Number, Confidence::External).unwrap();
1103 e.set_effects(BTreeSet::new()).unwrap(); e.set_yield(ExprSpec::Ref("n".into())).unwrap();
1105
1106 let (h, report) = e.commit_edit().unwrap();
1107 assert!(report.ok(), "violations: {:?}", report.violations);
1108 assert_eq!(report.status, Status::Complete);
1109 assert_eq!(e.run(&h, "id", &[7]).unwrap(), 7);
1110 }
1111
1112 #[test]
1113 fn transactions_are_non_nesting() {
1114 let s = Store::open_in_memory().unwrap();
1115 let mut e = Editor::new(s);
1116 e.begin_edit().unwrap();
1117 assert!(matches!(e.begin_edit(), Err(EditError::TransactionOpen)));
1118 }
1119
1120 #[test]
1121 fn abort_discards_the_draft() {
1122 let s = Store::open_in_memory().unwrap();
1123 let mut e = Editor::new(s);
1124 e.begin_edit().unwrap();
1125 e.create_function("f").unwrap();
1126 e.abort_edit();
1127 assert!(matches!(e.commit_edit(), Err(EditError::NoTransaction)));
1128 }
1129
1130 #[test]
1131 fn ops_require_an_open_transaction() {
1132 let s = Store::open_in_memory().unwrap();
1133 let mut e = Editor::new(s);
1134 assert!(matches!(
1135 e.create_function("f"),
1136 Err(EditError::NoTransaction)
1137 ));
1138 }
1139
1140 #[test]
1141 fn describe_hole_reports_scope_and_effects() {
1142 let s = Store::open_in_memory().unwrap();
1143 let mut e = Editor::new(s);
1144 e.begin_edit().unwrap();
1145 e.create_function("f").unwrap();
1146 e.add_param("a", Type::Number, Confidence::Structural)
1147 .unwrap();
1148 e.add_step("b", ExprSpec::Lit(1)).unwrap();
1149 let mut db = BTreeSet::new();
1150 db.insert(Effect::Db);
1151 e.set_effects(db).unwrap();
1152 let info = e.describe_hole().unwrap();
1153 assert_eq!(info.in_scope, vec!["a".to_string(), "b".to_string()]);
1154 assert_eq!(info.effects_allowed, vec![Effect::Db]);
1155 assert_eq!(info.expects, "Number");
1156 }
1157
1158 #[test]
1159 fn committing_with_a_hole_is_incomplete_but_not_invalid_and_will_not_run() {
1160 let s = Store::open_in_memory().unwrap();
1161 let mut e = Editor::new(s);
1162 e.begin_edit().unwrap();
1163 e.create_function("f").unwrap();
1164 e.set_produces(Type::Number, Confidence::Structural).unwrap();
1165 e.set_yield(ExprSpec::Hole {
1166 expects: "Number".into(),
1167 })
1168 .unwrap();
1169 let (h, report) = e.commit_edit().unwrap();
1170 assert_eq!(report.status, Status::Incomplete);
1171 assert!(report.ok()); assert!(matches!(e.run(&h, "f", &[]), Err(EditError::Lower(_))));
1173 }
1174
1175 #[test]
1176 fn apply_function_matches_fine_grained() {
1177 let mut e1 = Editor::new(Store::open_in_memory().unwrap());
1179 e1.begin_edit().unwrap();
1180 e1.create_function("id").unwrap();
1181 e1.add_param("n", Type::Number, Confidence::External).unwrap();
1182 e1.set_produces(Type::Number, Confidence::External).unwrap();
1183 e1.set_effects(BTreeSet::new()).unwrap();
1184 e1.set_yield(ExprSpec::Ref("n".into())).unwrap();
1185 let (h1, r1) = e1.commit_edit().unwrap();
1186
1187 let spec = FunctionSpec {
1189 name: "id".into(),
1190 type_params: vec![],
1191 params: vec![Param {
1192 name: "n".into(),
1193 ty: Type::Number,
1194 min_confidence: Confidence::External,
1195 }],
1196 produces: Produces {
1197 ty: Type::Number,
1198 confidence: Confidence::External,
1199 },
1200 requires: BTreeSet::new(),
1201 on_failure: vec![],
1202 steps: vec![],
1203 result: ExprSpec::Ref("n".into()),
1204 };
1205 let mut e2 = Editor::new(Store::open_in_memory().unwrap());
1206 let (h2, r2) = e2.apply_function(&spec).unwrap();
1207
1208 assert_eq!(h1, h2);
1210 assert_eq!(r1.ok(), r2.ok());
1211 assert_eq!(r1.status, r2.status);
1212 }
1213
1214 #[test]
1215 fn commit_without_a_result_errors() {
1216 let s = Store::open_in_memory().unwrap();
1217 let mut e = Editor::new(s);
1218 e.begin_edit().unwrap();
1219 e.create_function("f").unwrap();
1220 assert!(matches!(e.commit_edit(), Err(EditError::ResultNotSet)));
1221 }
1222
1223 #[test]
1230 fn module_authoring_composes_types_and_functions() {
1231 let ext_num = || Produces {
1232 ty: Type::Number,
1233 confidence: Confidence::External,
1234 };
1235 let p = |name: &str, ty: Type| Param {
1236 name: name.into(),
1237 ty,
1238 min_confidence: Confidence::External,
1239 };
1240 let spec = ModuleSpec {
1241 name: "m".into(),
1242 types: vec![
1243 TypeDefSpec::Record {
1244 name: "Box".into(),
1245 fields: vec![("v".into(), Type::Number)],
1246 },
1247 TypeDefSpec::Variant {
1248 name: "Opt".into(),
1249 cases: vec![
1250 ("Some".into(), vec![("v".into(), Type::Number)]),
1251 ("None".into(), vec![]),
1252 ],
1253 },
1254 ],
1255 functions: vec![
1256 FunctionSpec {
1260 name: "demo".into(),
1261 type_params: vec![],
1262 params: vec![p("n", Type::Number)],
1263 produces: ext_num(),
1264 requires: BTreeSet::new(),
1265 on_failure: vec![],
1266 steps: vec![
1267 StepSpec {
1268 binding: "b".into(),
1269 value: ExprSpec::Call {
1270 func: "mk".into(),
1271 args: vec![ExprSpec::Ref("n".into())],
1272 },
1273 },
1274 StepSpec {
1275 binding: "x".into(),
1276 value: ExprSpec::Call {
1277 func: "unwrap".into(),
1278 args: vec![ExprSpec::Ref("b".into())],
1279 },
1280 },
1281 ],
1282 result: ExprSpec::Call {
1283 func: "get_or".into(),
1284 args: vec![
1285 ExprSpec::Variant {
1286 type_name: "Opt".into(),
1287 case: "Some".into(),
1288 fields: vec![(
1289 "v".into(),
1290 ExprSpec::Ref("x".into()),
1291 )],
1292 },
1293 ExprSpec::Lit(0),
1294 ],
1295 },
1296 },
1297 FunctionSpec {
1298 name: "mk".into(),
1299 type_params: vec![],
1300 params: vec![p("n", Type::Number)],
1301 produces: Produces {
1302 ty: Type::Named("Box".into()),
1303 confidence: Confidence::External,
1304 },
1305 requires: BTreeSet::new(),
1306 on_failure: vec![],
1307 steps: vec![],
1308 result: ExprSpec::Record {
1309 type_name: "Box".into(),
1310 fields: vec![("v".into(), ExprSpec::Ref("n".into()))],
1311 },
1312 },
1313 FunctionSpec {
1314 name: "unwrap".into(),
1315 type_params: vec![],
1316 params: vec![p("b", Type::Named("Box".into()))],
1317 produces: ext_num(),
1318 requires: BTreeSet::new(),
1319 on_failure: vec![],
1320 steps: vec![],
1321 result: ExprSpec::Field {
1322 base: Box::new(ExprSpec::Ref("b".into())),
1323 type_name: "Box".into(),
1324 field: "v".into(),
1325 },
1326 },
1327 FunctionSpec {
1328 name: "get_or".into(),
1329 type_params: vec![],
1330 params: vec![
1331 p("o", Type::Named("Opt".into())),
1332 p("d", Type::Number),
1333 ],
1334 produces: ext_num(),
1335 requires: BTreeSet::new(),
1336 on_failure: vec![],
1337 steps: vec![],
1338 result: ExprSpec::Match {
1339 scrutinee: Box::new(ExprSpec::Ref("o".into())),
1340 type_name: "Opt".into(),
1341 arms: vec![
1342 (
1343 "Some".into(),
1344 vec!["v".into()],
1345 ExprSpec::Ref("v".into()),
1346 ),
1347 ("None".into(), vec![], ExprSpec::Ref("d".into())),
1348 ],
1349 },
1350 },
1351 ],
1352 };
1353
1354 let e = Editor::new(Store::open_in_memory().unwrap());
1355 let (module, report) = e.apply_module(&spec).unwrap();
1356 assert!(report.ok(), "violations: {:?}", report.violations);
1357 assert_eq!(report.status, Status::Complete);
1358 assert_eq!(e.run_module(&module, "demo", &[7]).unwrap(), 7);
1360 }
1361
1362 #[test]
1368 fn find_references_is_the_structural_reference_set() {
1369 let s = Store::open_in_memory().unwrap();
1370 let two = s.put(&Node::Lit(2)).unwrap();
1371 let a = s
1372 .put(&Node::BinOp {
1373 op: BinOp::Add,
1374 lhs: two.clone(),
1375 rhs: two.clone(),
1376 })
1377 .unwrap();
1378 let b = s
1379 .put(&Node::BinOp {
1380 op: BinOp::Mul,
1381 lhs: a.clone(),
1382 rhs: two.clone(),
1383 })
1384 .unwrap();
1385 let root = s
1386 .put(&Node::BinOp {
1387 op: BinOp::Sub,
1388 lhs: b.clone(),
1389 rhs: a.clone(),
1390 })
1391 .unwrap();
1392 let e = Editor::new(s);
1393
1394 let mut want_a = vec![b.to_string(), root.to_string()];
1396 want_a.sort();
1397 assert_eq!(e.find_references(&root, &a).unwrap(), want_a);
1398
1399 let mut want_two = vec![a.to_string(), b.to_string()];
1402 want_two.sort();
1403 assert_eq!(e.find_references(&root, &two).unwrap(), want_two);
1404
1405 assert!(e.find_references(&root, &root).unwrap().is_empty());
1407 }
1408
1409 #[test]
1417 fn with_child_hashes_round_trips() {
1418 use crate::check::{child_hashes, with_child_hashes};
1419 let s = Store::open_in_memory().unwrap();
1420 let h = s.put(&Node::Lit(1)).unwrap();
1421 let h2 = s.put(&Node::Lit(2)).unwrap();
1422 let h3 = s.put(&Node::Lit(3)).unwrap();
1423 let prod = Produces {
1424 ty: Type::Number,
1425 confidence: Confidence::External,
1426 };
1427 let nodes = vec![
1428 Node::Lit(7),
1429 Node::Now,
1430 Node::Ref("x".into()),
1431 Node::Hole { expects: "Number".into() },
1432 Node::Not(h.clone()),
1433 Node::StrConcat(h.clone(), h2.clone()),
1434 Node::StrSlice { s: h.clone(), start: h2.clone(), len: h3.clone() },
1435 Node::If {
1436 cond: h.clone(),
1437 then_branch: h2.clone(),
1438 else_branch: h3.clone(),
1439 },
1440 Node::BinOp { op: BinOp::Add, lhs: h.clone(), rhs: h2.clone() },
1441 Node::List(vec![h.clone(), h2.clone(), h3.clone()]),
1442 Node::Map(vec![(h.clone(), h2.clone()), (h3.clone(), h.clone())]),
1443 Node::Record {
1444 type_name: "R".into(),
1445 fields: vec![("x".into(), h.clone()), ("y".into(), h2.clone())],
1446 },
1447 Node::Variant {
1448 type_name: "V".into(),
1449 case: "C".into(),
1450 fields: vec![("a".into(), h.clone())],
1451 },
1452 Node::Field {
1453 base: h.clone(),
1454 type_name: "R".into(),
1455 field: "x".into(),
1456 },
1457 Node::OptionMatch {
1458 opt: h.clone(),
1459 some_bind: "v".into(),
1460 some_body: h2.clone(),
1461 none_body: h3.clone(),
1462 },
1463 Node::Match {
1464 scrutinee: h.clone(),
1465 type_name: "V".into(),
1466 arms: vec![
1467 MatchArm { case: "C".into(), bindings: vec!["a".into()], body: h2.clone() },
1468 MatchArm { case: "D".into(), bindings: vec![], body: h3.clone() },
1469 ],
1470 },
1471 Node::Handle {
1472 body: h.clone(),
1473 handlers: vec![("E".into(), h2.clone()), ("F".into(), h3.clone())],
1474 },
1475 Node::Call { func: "g".into(), args: vec![h.clone(), h2.clone()] },
1476 Node::CallValue { callee: h.clone(), args: vec![h2.clone(), h3.clone()] },
1477 Node::Lambda { params: vec![], body: h.clone() },
1478 Node::Step { binding: "b".into(), value: h.clone() },
1479 Node::Function {
1480 name: "f".into(),
1481 type_params: vec![],
1482 params: vec![],
1483 produces: prod.clone(),
1484 requires: BTreeSet::new(),
1485 on_failure: vec![],
1486 body: vec![h.clone(), h2.clone()],
1487 result: h3.clone(),
1488 },
1489 Node::Module {
1490 name: "m".into(),
1491 types: vec![h.clone()],
1492 functions: vec![h2.clone(), h3.clone()],
1493 },
1494 ];
1495 for n in nodes {
1496 let kids: Vec<NodeHash> =
1497 child_hashes(&n).into_iter().cloned().collect();
1498 assert_eq!(
1499 with_child_hashes(&n, &kids),
1500 n,
1501 "round-trip failed for {n:?}"
1502 );
1503 }
1504 }
1505
1506 #[test]
1510 fn replace_node_rehashes_and_rechecks() {
1511 let spec = ModuleSpec {
1513 name: "m".into(),
1514 types: vec![],
1515 functions: vec![FunctionSpec {
1516 name: "f".into(),
1517 type_params: vec![],
1518 params: vec![],
1519 produces: Produces {
1520 ty: Type::Number,
1521 confidence: Confidence::External,
1522 },
1523 requires: BTreeSet::new(),
1524 on_failure: vec![],
1525 steps: vec![],
1526 result: ExprSpec::BinOp {
1527 op: BinOp::Add,
1528 lhs: Box::new(ExprSpec::Lit(2)),
1529 rhs: Box::new(ExprSpec::Lit(3)),
1530 },
1531 }],
1532 };
1533 let e = Editor::new(Store::open_in_memory().unwrap());
1534 let (m, report) = e.apply_module(&spec).unwrap();
1535 assert!(report.ok());
1536 assert_eq!(e.run_module(&m, "f", &[]).unwrap(), 5);
1537
1538 let three = e.store().put(&Node::Lit(3)).unwrap();
1541 let four = e.store().put(&Node::Lit(4)).unwrap();
1542 let (m2, r2) = e.replace_node(&m, &three, &four).unwrap();
1543 assert!(r2.ok() && r2.status == Status::Complete);
1544 assert_ne!(m2, m, "the spine must rehash");
1545 assert_eq!(e.run_module(&m2, "f", &[]).unwrap(), 6, "2 + 4");
1546 assert_eq!(e.run_module(&m, "f", &[]).unwrap(), 5);
1548
1549 let absent = e.store().put(&Node::Lit(999)).unwrap();
1551 let (m3, _) = e.replace_node(&m, &absent, &four).unwrap();
1552 assert_eq!(m3, m, "no-op replace returns the shared root");
1553 }
1554
1555 #[test]
1559 fn fill_hole_accepts_only_a_valid_fill_else_the_hole_remains() {
1560 let mut e = Editor::new(Store::open_in_memory().unwrap());
1562 e.begin_edit().unwrap();
1563 e.create_function("f").unwrap();
1564 e.set_produces(Type::Number, Confidence::Structural).unwrap();
1565 e.set_effects(BTreeSet::new()).unwrap();
1566 e.set_yield(ExprSpec::Hole {
1567 expects: "Number".into(),
1568 })
1569 .unwrap();
1570 let (h, report) = e.commit_edit().unwrap();
1571 assert_eq!(report.status, Status::Incomplete);
1572 let hole = e
1574 .store()
1575 .put(&Node::Hole {
1576 expects: "Number".into(),
1577 })
1578 .unwrap();
1579 assert!(matches!(e.run(&h, "f", &[]), Err(EditError::Lower(_))));
1581
1582 let bad = e.store().put(&Node::Str("x".into())).unwrap();
1586 let (hr, rr) = e.fill_hole(&h, &hole, &bad).unwrap();
1587 assert!(!rr.ok(), "a contract-violating fill must be rejected");
1588 assert!(!rr.violations.is_empty(), "the principle is reported");
1589 assert_eq!(hr, h, "rejected → the hole remains, root unchanged");
1590 assert!(matches!(e.run(&h, "f", &[]), Err(EditError::Lower(_))));
1591
1592 let good = e.store().put(&Node::Lit(42)).unwrap();
1596 let (h2, r2) = e.fill_hole(&h, &hole, &good).unwrap();
1597 assert!(r2.ok() && r2.status == Status::Complete);
1598 assert_ne!(h2, h);
1599 assert_eq!(e.run(&h2, "f", &[]).unwrap(), 42);
1600 assert!(matches!(e.run(&h, "f", &[]), Err(EditError::Lower(_))));
1601
1602 assert!(matches!(
1604 e.fill_hole(&h, &good, &good),
1605 Err(EditError::NotAHole(_))
1606 ));
1607 }
1608}