1use super::super::Type;
27use crate::types::branch_path;
28
29#[derive(Debug, Clone, Copy, PartialEq, Eq)]
32pub enum EffectDimension {
33 Snapshot,
35 Generative,
37 Output,
39 GenerativeOutput,
43}
44
45#[derive(Debug, Clone)]
49pub struct EffectClassification {
50 pub method: &'static str,
51 pub dimension: EffectDimension,
52 pub runtime_params: &'static [RuntimeType],
53 pub runtime_return: RuntimeType,
54}
55
56#[derive(Debug, Clone, Copy, PartialEq, Eq)]
60pub enum RuntimeType {
61 FormattedValue,
62 Unit,
63 Int,
64 Float,
65 Str,
66 Bool,
67 OptionStr,
68 ListStr,
69 ResultUnitStr,
70 ResultStrStr,
71 ResultListStrStr,
72 HttpResponseResult,
73 MapStrListStr,
77 TerminalSize,
79 TcpConnection,
81 ResultTcpConnectionStr,
83}
84
85impl RuntimeType {
86 fn as_type(self) -> Type {
87 match self {
88 RuntimeType::FormattedValue => Type::Var("FormattedValue".to_string()),
89 RuntimeType::Unit => Type::Unit,
90 RuntimeType::Int => Type::Int,
91 RuntimeType::Float => Type::Float,
92 RuntimeType::Str => Type::Str,
93 RuntimeType::Bool => Type::Bool,
94 RuntimeType::OptionStr => Type::Option(Box::new(Type::Str)),
95 RuntimeType::ListStr => Type::List(Box::new(Type::Str)),
96 RuntimeType::ResultUnitStr => Type::Result(Box::new(Type::Unit), Box::new(Type::Str)),
97 RuntimeType::ResultStrStr => Type::Result(Box::new(Type::Str), Box::new(Type::Str)),
98 RuntimeType::ResultListStrStr => Type::Result(
99 Box::new(Type::List(Box::new(Type::Str))),
100 Box::new(Type::Str),
101 ),
102 RuntimeType::HttpResponseResult => {
103 Type::Result(Box::new(Type::named("HttpResponse")), Box::new(Type::Str))
104 }
105 RuntimeType::MapStrListStr => Type::Map(
106 Box::new(Type::Str),
107 Box::new(Type::List(Box::new(Type::Str))),
108 ),
109 RuntimeType::TerminalSize => Type::named("Terminal.Size"),
110 RuntimeType::TcpConnection => Type::named("Tcp.Connection"),
111 RuntimeType::ResultTcpConnectionStr => {
112 Type::Result(Box::new(Type::named("Tcp.Connection")), Box::new(Type::Str))
113 }
114 }
115 }
116}
117
118fn runtime_type(rt: RuntimeType) -> Type {
119 rt.as_type()
120}
121
122const CLASSIFICATIONS: &[EffectClassification] = &[
124 EffectClassification {
126 method: "Args.get",
127 dimension: EffectDimension::Snapshot,
128 runtime_params: &[],
129 runtime_return: RuntimeType::ListStr,
130 },
131 EffectClassification {
132 method: "Env.get",
133 dimension: EffectDimension::Snapshot,
134 runtime_params: &[RuntimeType::Str],
135 runtime_return: RuntimeType::OptionStr,
136 },
137 EffectClassification {
140 method: "Terminal.size",
141 dimension: EffectDimension::Snapshot,
142 runtime_params: &[],
143 runtime_return: RuntimeType::TerminalSize,
144 },
145 EffectClassification {
147 method: "Random.int",
148 dimension: EffectDimension::Generative,
149 runtime_params: &[RuntimeType::Int, RuntimeType::Int],
150 runtime_return: RuntimeType::Int,
151 },
152 EffectClassification {
153 method: "Random.float",
154 dimension: EffectDimension::Generative,
155 runtime_params: &[],
156 runtime_return: RuntimeType::Float,
157 },
158 EffectClassification {
159 method: "Time.now",
160 dimension: EffectDimension::Generative,
161 runtime_params: &[],
162 runtime_return: RuntimeType::Str,
163 },
164 EffectClassification {
165 method: "Time.unixMs",
166 dimension: EffectDimension::Generative,
167 runtime_params: &[],
168 runtime_return: RuntimeType::Int,
169 },
170 EffectClassification {
171 method: "Disk.readText",
172 dimension: EffectDimension::Generative,
173 runtime_params: &[RuntimeType::Str],
174 runtime_return: RuntimeType::ResultStrStr,
175 },
176 EffectClassification {
177 method: "Disk.exists",
178 dimension: EffectDimension::Generative,
179 runtime_params: &[RuntimeType::Str],
180 runtime_return: RuntimeType::Bool,
181 },
182 EffectClassification {
183 method: "Disk.listDir",
184 dimension: EffectDimension::Generative,
185 runtime_params: &[RuntimeType::Str],
186 runtime_return: RuntimeType::ResultListStrStr,
187 },
188 EffectClassification {
189 method: "Console.readLine",
190 dimension: EffectDimension::Generative,
191 runtime_params: &[],
192 runtime_return: RuntimeType::ResultStrStr,
193 },
194 EffectClassification {
196 method: "Http.get",
197 dimension: EffectDimension::GenerativeOutput,
198 runtime_params: &[RuntimeType::Str],
199 runtime_return: RuntimeType::HttpResponseResult,
200 },
201 EffectClassification {
202 method: "Http.head",
203 dimension: EffectDimension::GenerativeOutput,
204 runtime_params: &[RuntimeType::Str],
205 runtime_return: RuntimeType::HttpResponseResult,
206 },
207 EffectClassification {
208 method: "Http.delete",
209 dimension: EffectDimension::GenerativeOutput,
210 runtime_params: &[RuntimeType::Str],
211 runtime_return: RuntimeType::HttpResponseResult,
212 },
213 EffectClassification {
215 method: "Http.post",
216 dimension: EffectDimension::GenerativeOutput,
217 runtime_params: &[
218 RuntimeType::Str,
219 RuntimeType::Str,
220 RuntimeType::Str,
221 RuntimeType::MapStrListStr,
222 ],
223 runtime_return: RuntimeType::HttpResponseResult,
224 },
225 EffectClassification {
226 method: "Http.put",
227 dimension: EffectDimension::GenerativeOutput,
228 runtime_params: &[
229 RuntimeType::Str,
230 RuntimeType::Str,
231 RuntimeType::Str,
232 RuntimeType::MapStrListStr,
233 ],
234 runtime_return: RuntimeType::HttpResponseResult,
235 },
236 EffectClassification {
237 method: "Http.patch",
238 dimension: EffectDimension::GenerativeOutput,
239 runtime_params: &[
240 RuntimeType::Str,
241 RuntimeType::Str,
242 RuntimeType::Str,
243 RuntimeType::MapStrListStr,
244 ],
245 runtime_return: RuntimeType::HttpResponseResult,
246 },
247 EffectClassification {
251 method: "Disk.writeText",
252 dimension: EffectDimension::GenerativeOutput,
253 runtime_params: &[RuntimeType::Str, RuntimeType::Str],
254 runtime_return: RuntimeType::ResultUnitStr,
255 },
256 EffectClassification {
257 method: "Disk.appendText",
258 dimension: EffectDimension::GenerativeOutput,
259 runtime_params: &[RuntimeType::Str, RuntimeType::Str],
260 runtime_return: RuntimeType::ResultUnitStr,
261 },
262 EffectClassification {
263 method: "Disk.delete",
264 dimension: EffectDimension::GenerativeOutput,
265 runtime_params: &[RuntimeType::Str],
266 runtime_return: RuntimeType::ResultUnitStr,
267 },
268 EffectClassification {
269 method: "Disk.deleteDir",
270 dimension: EffectDimension::GenerativeOutput,
271 runtime_params: &[RuntimeType::Str],
272 runtime_return: RuntimeType::ResultUnitStr,
273 },
274 EffectClassification {
275 method: "Disk.makeDir",
276 dimension: EffectDimension::GenerativeOutput,
277 runtime_params: &[RuntimeType::Str],
278 runtime_return: RuntimeType::ResultUnitStr,
279 },
280 EffectClassification {
282 method: "Tcp.send",
283 dimension: EffectDimension::GenerativeOutput,
284 runtime_params: &[RuntimeType::Str, RuntimeType::Int, RuntimeType::Str],
285 runtime_return: RuntimeType::ResultStrStr,
286 },
287 EffectClassification {
288 method: "Tcp.ping",
289 dimension: EffectDimension::GenerativeOutput,
290 runtime_params: &[RuntimeType::Str, RuntimeType::Int],
291 runtime_return: RuntimeType::ResultUnitStr,
292 },
293 EffectClassification {
297 method: "Tcp.connect",
298 dimension: EffectDimension::GenerativeOutput,
299 runtime_params: &[RuntimeType::Str, RuntimeType::Int],
300 runtime_return: RuntimeType::ResultTcpConnectionStr,
301 },
302 EffectClassification {
303 method: "Tcp.readLine",
304 dimension: EffectDimension::GenerativeOutput,
305 runtime_params: &[RuntimeType::TcpConnection],
306 runtime_return: RuntimeType::ResultStrStr,
307 },
308 EffectClassification {
309 method: "Tcp.writeLine",
310 dimension: EffectDimension::GenerativeOutput,
311 runtime_params: &[RuntimeType::TcpConnection, RuntimeType::Str],
312 runtime_return: RuntimeType::ResultUnitStr,
313 },
314 EffectClassification {
315 method: "Tcp.close",
316 dimension: EffectDimension::GenerativeOutput,
317 runtime_params: &[RuntimeType::TcpConnection],
318 runtime_return: RuntimeType::ResultUnitStr,
319 },
320 EffectClassification {
326 method: "Env.set",
327 dimension: EffectDimension::Output,
328 runtime_params: &[RuntimeType::Str, RuntimeType::Str],
329 runtime_return: RuntimeType::Unit,
330 },
331 EffectClassification {
332 method: "Console.print",
333 dimension: EffectDimension::Output,
334 runtime_params: &[RuntimeType::FormattedValue],
335 runtime_return: RuntimeType::Unit,
336 },
337 EffectClassification {
338 method: "Console.error",
339 dimension: EffectDimension::Output,
340 runtime_params: &[RuntimeType::FormattedValue],
341 runtime_return: RuntimeType::Unit,
342 },
343 EffectClassification {
344 method: "Console.warn",
345 dimension: EffectDimension::Output,
346 runtime_params: &[RuntimeType::FormattedValue],
347 runtime_return: RuntimeType::Unit,
348 },
349 EffectClassification {
350 method: "Time.sleep",
351 dimension: EffectDimension::Output,
352 runtime_params: &[RuntimeType::Int],
353 runtime_return: RuntimeType::Unit,
354 },
355 EffectClassification {
356 method: "Terminal.clear",
357 dimension: EffectDimension::Output,
358 runtime_params: &[],
359 runtime_return: RuntimeType::Unit,
360 },
361 EffectClassification {
362 method: "Terminal.moveTo",
363 dimension: EffectDimension::Output,
364 runtime_params: &[RuntimeType::Int, RuntimeType::Int],
365 runtime_return: RuntimeType::Unit,
366 },
367 EffectClassification {
368 method: "Terminal.print",
369 dimension: EffectDimension::Output,
370 runtime_params: &[RuntimeType::FormattedValue],
371 runtime_return: RuntimeType::Unit,
372 },
373 EffectClassification {
374 method: "Terminal.readKey",
375 dimension: EffectDimension::Generative,
376 runtime_params: &[],
377 runtime_return: RuntimeType::OptionStr,
378 },
379 EffectClassification {
380 method: "Terminal.hideCursor",
381 dimension: EffectDimension::Output,
382 runtime_params: &[],
383 runtime_return: RuntimeType::Unit,
384 },
385 EffectClassification {
386 method: "Terminal.showCursor",
387 dimension: EffectDimension::Output,
388 runtime_params: &[],
389 runtime_return: RuntimeType::Unit,
390 },
391 EffectClassification {
392 method: "Terminal.flush",
393 dimension: EffectDimension::Output,
394 runtime_params: &[],
395 runtime_return: RuntimeType::Unit,
396 },
397 EffectClassification {
402 method: "Terminal.enableRawMode",
403 dimension: EffectDimension::Output,
404 runtime_params: &[],
405 runtime_return: RuntimeType::Unit,
406 },
407 EffectClassification {
408 method: "Terminal.disableRawMode",
409 dimension: EffectDimension::Output,
410 runtime_params: &[],
411 runtime_return: RuntimeType::Unit,
412 },
413 EffectClassification {
414 method: "Terminal.setColor",
415 dimension: EffectDimension::Output,
416 runtime_params: &[RuntimeType::Str],
417 runtime_return: RuntimeType::Unit,
418 },
419 EffectClassification {
420 method: "Terminal.resetColor",
421 dimension: EffectDimension::Output,
422 runtime_params: &[],
423 runtime_return: RuntimeType::Unit,
424 },
425];
426
427pub fn classify(method: &str) -> Option<&'static EffectClassification> {
429 CLASSIFICATIONS.iter().find(|c| c.method == method)
430}
431
432pub fn classifications_for_proof_subset() -> &'static [EffectClassification] {
435 CLASSIFICATIONS
436}
437
438pub fn is_classified(method: &str) -> bool {
440 classify(method).is_some()
441}
442
443pub fn oracle_signature(method: &str) -> Option<Type> {
452 let c = classify(method)?;
453 match c.dimension {
454 EffectDimension::Output => None,
455 EffectDimension::Snapshot => {
456 let params: Vec<Type> = c.runtime_params.iter().copied().map(runtime_type).collect();
457 Some(Type::Fn(
458 params,
459 Box::new(runtime_type(c.runtime_return)),
460 vec![],
461 ))
462 }
463 EffectDimension::Generative | EffectDimension::GenerativeOutput => {
464 let mut params = vec![Type::named(branch_path::TYPE_NAME.to_string()), Type::Int];
465 params.extend(c.runtime_params.iter().copied().map(runtime_type));
466 Some(Type::Fn(
467 params,
468 Box::new(runtime_type(c.runtime_return)),
469 vec![],
470 ))
471 }
472 }
473}
474
475#[cfg(test)]
476mod tests {
477 use super::*;
478
479 #[test]
480 fn classify_returns_none_for_unknown() {
481 assert!(classify("Nope.missing").is_none());
482 assert!(classify("Args.set").is_none());
483 }
484
485 #[test]
486 fn args_get_is_snapshot() {
487 let c = classify("Args.get").unwrap();
488 assert_eq!(c.dimension, EffectDimension::Snapshot);
489 }
490
491 #[test]
492 fn random_int_is_generative() {
493 let c = classify("Random.int").unwrap();
494 assert_eq!(c.dimension, EffectDimension::Generative);
495 }
496
497 #[test]
498 fn http_get_is_generative_output() {
499 let c = classify("Http.get").unwrap();
500 assert_eq!(c.dimension, EffectDimension::GenerativeOutput);
501 }
502
503 #[test]
504 fn disk_write_text_is_generative_output() {
505 let c = classify("Disk.writeText").unwrap();
506 assert_eq!(c.dimension, EffectDimension::GenerativeOutput);
507 }
508
509 #[test]
510 fn console_print_is_output() {
511 let c = classify("Console.print").unwrap();
512 assert_eq!(c.dimension, EffectDimension::Output);
513 }
514
515 #[test]
516 fn console_read_line_is_generative() {
517 let c = classify("Console.readLine").unwrap();
518 assert_eq!(c.dimension, EffectDimension::Generative);
519 }
520
521 #[test]
522 fn time_sleep_is_output() {
523 let c = classify("Time.sleep").unwrap();
524 assert_eq!(c.dimension, EffectDimension::Output);
525 }
526
527 #[test]
528 fn terminal_read_key_is_generative() {
529 let c = classify("Terminal.readKey").unwrap();
530 assert_eq!(c.dimension, EffectDimension::Generative);
531 }
532
533 #[test]
534 fn oracle_signature_for_random_int_is_branch_indexed() {
535 let sig = oracle_signature("Random.int").unwrap();
536 match sig {
538 Type::Fn(params, ret, _) => {
539 assert_eq!(params.len(), 4);
540 assert!(matches!(params[0], Type::Named { name: ref n, .. } if n == "BranchPath"));
541 assert_eq!(params[1], Type::Int);
542 assert_eq!(params[2], Type::Int);
543 assert_eq!(params[3], Type::Int);
544 assert_eq!(*ret, Type::Int);
545 }
546 other => panic!("expected Fn, got {:?}", other),
547 }
548 }
549
550 #[test]
551 fn oracle_signature_for_random_float_is_branch_indexed_no_extra_args() {
552 let sig = oracle_signature("Random.float").unwrap();
553 match sig {
555 Type::Fn(params, ret, _) => {
556 assert_eq!(params.len(), 2);
557 assert!(matches!(params[0], Type::Named { name: ref n, .. } if n == "BranchPath"));
558 assert_eq!(params[1], Type::Int);
559 assert_eq!(*ret, Type::Float);
560 }
561 other => panic!("expected Fn, got {:?}", other),
562 }
563 }
564
565 #[test]
566 fn oracle_signature_for_args_get_is_capability_reader() {
567 let sig = oracle_signature("Args.get").unwrap();
568 match sig {
570 Type::Fn(params, ret, _) => {
571 assert!(params.is_empty());
572 assert_eq!(*ret, Type::List(Box::new(Type::Str)));
573 }
574 other => panic!("expected Fn, got {:?}", other),
575 }
576 }
577
578 #[test]
579 fn oracle_signature_for_env_get_is_capability_reader() {
580 let sig = oracle_signature("Env.get").unwrap();
581 match sig {
583 Type::Fn(params, ret, _) => {
584 assert_eq!(params, vec![Type::Str]);
585 assert_eq!(*ret, Type::Option(Box::new(Type::Str)));
586 }
587 other => panic!("expected Fn, got {:?}", other),
588 }
589 }
590
591 #[test]
592 fn oracle_signature_for_http_get_is_branch_indexed() {
593 let sig = oracle_signature("Http.get").unwrap();
594 match sig {
596 Type::Fn(params, ret, _) => {
597 assert_eq!(params.len(), 3);
598 assert!(matches!(params[0], Type::Named { name: ref n, .. } if n == "BranchPath"));
599 assert_eq!(params[1], Type::Int);
600 assert_eq!(params[2], Type::Str);
601 match *ret {
602 Type::Result(ok, err) => {
603 assert!(
604 matches!(*ok, Type::Named { name: ref n, .. } if n == "HttpResponse")
605 );
606 assert_eq!(*err, Type::Str);
607 }
608 other => panic!("expected Result, got {:?}", other),
609 }
610 }
611 other => panic!("expected Fn, got {:?}", other),
612 }
613 }
614
615 #[test]
616 fn oracle_signature_for_console_read_line_is_branch_indexed() {
617 let sig = oracle_signature("Console.readLine").unwrap();
618 match sig {
620 Type::Fn(params, ret, _) => {
621 assert_eq!(params.len(), 2);
622 assert!(matches!(params[0], Type::Named { name: ref n, .. } if n == "BranchPath"));
623 assert_eq!(params[1], Type::Int);
624 assert_eq!(*ret, Type::Result(Box::new(Type::Str), Box::new(Type::Str)));
625 }
626 other => panic!("expected Fn, got {:?}", other),
627 }
628 }
629
630 #[test]
631 fn oracle_signature_for_disk_list_dir_returns_result_list_string() {
632 let sig = oracle_signature("Disk.listDir").unwrap();
633 match sig {
635 Type::Fn(params, ret, _) => {
636 assert_eq!(params.len(), 3);
637 assert!(matches!(params[0], Type::Named { name: ref n, .. } if n == "BranchPath"));
638 assert_eq!(params[1], Type::Int);
639 assert_eq!(params[2], Type::Str);
640 assert_eq!(
641 *ret,
642 Type::Result(
643 Box::new(Type::List(Box::new(Type::Str))),
644 Box::new(Type::Str)
645 )
646 );
647 }
648 other => panic!("expected Fn, got {:?}", other),
649 }
650 }
651
652 #[test]
653 fn oracle_signature_for_tcp_ping_returns_result_unit_string() {
654 let sig = oracle_signature("Tcp.ping").unwrap();
655 match sig {
657 Type::Fn(params, ret, _) => {
658 assert_eq!(params.len(), 4);
659 assert!(matches!(params[0], Type::Named { name: ref n, .. } if n == "BranchPath"));
660 assert_eq!(params[1], Type::Int);
661 assert_eq!(params[2], Type::Str);
662 assert_eq!(params[3], Type::Int);
663 assert_eq!(
664 *ret,
665 Type::Result(Box::new(Type::Unit), Box::new(Type::Str))
666 );
667 }
668 other => panic!("expected Fn, got {:?}", other),
669 }
670 }
671
672 #[test]
673 fn oracle_signature_for_disk_write_text_returns_result_unit_string() {
674 let sig = oracle_signature("Disk.writeText").unwrap();
675 match sig {
677 Type::Fn(params, ret, _) => {
678 assert_eq!(params.len(), 4);
679 assert!(matches!(params[0], Type::Named { name: ref n, .. } if n == "BranchPath"));
680 assert_eq!(params[1], Type::Int);
681 assert_eq!(params[2], Type::Str);
682 assert_eq!(params[3], Type::Str);
683 assert_eq!(
684 *ret,
685 Type::Result(Box::new(Type::Unit), Box::new(Type::Str))
686 );
687 }
688 other => panic!("expected Fn, got {:?}", other),
689 }
690 }
691
692 #[test]
693 fn oracle_signature_for_output_effect_is_none() {
694 assert!(oracle_signature("Console.print").is_none());
695 assert!(oracle_signature("Console.error").is_none());
696 assert!(oracle_signature("Console.warn").is_none());
697 assert!(oracle_signature("Time.sleep").is_none());
698 assert!(oracle_signature("Terminal.print").is_none());
699 }
700
701 #[test]
702 fn is_classified_covers_full_v1_set() {
703 for name in &[
704 "Args.get",
705 "Env.get",
706 "Random.int",
707 "Random.float",
708 "Time.now",
709 "Time.unixMs",
710 "Time.sleep",
711 "Disk.readText",
712 "Disk.exists",
713 "Disk.listDir",
714 "Disk.writeText",
715 "Disk.appendText",
716 "Disk.delete",
717 "Disk.deleteDir",
718 "Disk.makeDir",
719 "Console.readLine",
720 "Http.get",
721 "Http.head",
722 "Http.delete",
723 "Http.post",
724 "Http.put",
725 "Http.patch",
726 "Tcp.send",
727 "Tcp.ping",
728 "Console.print",
729 "Console.error",
730 "Console.warn",
731 "Terminal.clear",
732 "Terminal.moveTo",
733 "Terminal.print",
734 "Terminal.readKey",
735 "Terminal.hideCursor",
736 "Terminal.showCursor",
737 "Terminal.flush",
738 ] {
739 assert!(is_classified(name), "{} should be classified", name);
740 }
741 }
742
743 #[test]
744 fn oracle_signature_for_http_post_has_four_runtime_params() {
745 let sig = oracle_signature("Http.post").unwrap();
746 match sig {
748 Type::Fn(params, ret, _) => {
749 assert_eq!(params.len(), 6);
750 assert!(matches!(params[0], Type::Named { name: ref n, .. } if n == "BranchPath"));
751 assert_eq!(params[1], Type::Int);
752 assert_eq!(params[2], Type::Str);
753 assert_eq!(params[3], Type::Str);
754 assert_eq!(params[4], Type::Str);
755 match ¶ms[5] {
756 Type::Map(key, value) => {
757 assert_eq!(**key, Type::Str);
758 match &**value {
759 Type::List(inner) => assert_eq!(**inner, Type::Str),
760 other => panic!("expected Map<Str, List<Str>>, got {:?}", other),
761 }
762 }
763 other => panic!("expected Map<Str, List<Str>>, got {:?}", other),
764 }
765 match *ret {
766 Type::Result(ok, err) => {
767 assert!(
768 matches!(*ok, Type::Named { name: ref n, .. } if n == "HttpResponse")
769 );
770 assert_eq!(*err, Type::Str);
771 }
772 other => panic!("expected Result, got {:?}", other),
773 }
774 }
775 other => panic!("expected Fn, got {:?}", other),
776 }
777 }
778
779 #[test]
780 fn server_lifecycle_remains_unclassified() {
781 for name in &["HttpServer.listen", "HttpServer.listenWith"] {
785 assert!(!is_classified(name), "{} should NOT be classified", name);
786 }
787 }
788
789 #[test]
790 fn extended_oracle_v1_methods_classified() {
791 for name in &[
794 "Env.set",
795 "Terminal.enableRawMode",
796 "Terminal.disableRawMode",
797 "Terminal.setColor",
798 "Terminal.resetColor",
799 ] {
800 let c = classify(name).unwrap_or_else(|| panic!("{} should be classified", name));
801 assert_eq!(c.dimension, EffectDimension::Output);
802 }
803 for name in &["Tcp.connect", "Tcp.readLine", "Tcp.writeLine", "Tcp.close"] {
806 let c = classify(name).unwrap_or_else(|| panic!("{} should be classified", name));
807 assert_eq!(c.dimension, EffectDimension::GenerativeOutput);
808 }
809 }
810
811 #[test]
812 fn terminal_size_is_snapshot() {
813 let c = classify("Terminal.size").expect("Terminal.size should be classified");
814 assert_eq!(c.dimension, EffectDimension::Snapshot);
815 assert!(c.runtime_params.is_empty());
816 assert_eq!(c.runtime_return, RuntimeType::TerminalSize);
817
818 let sig = oracle_signature("Terminal.size").unwrap();
820 match sig {
821 Type::Fn(params, ret, effects) => {
822 assert!(params.is_empty());
823 assert_eq!(*ret, Type::named("Terminal.Size"));
824 assert!(effects.is_empty());
825 }
826 other => panic!("expected Fn, got {:?}", other),
827 }
828 }
829}