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 is_verify_fabricable_handle(canonical_type: &str) -> bool {
464 matches!(canonical_type, "Tcp.Connection")
465}
466
467pub fn oracle_signature(method: &str) -> Option<Type> {
476 let c = classify(method)?;
477 match c.dimension {
478 EffectDimension::Output => None,
479 EffectDimension::Snapshot => {
480 let params: Vec<Type> = c.runtime_params.iter().copied().map(runtime_type).collect();
481 Some(Type::Fn(
482 params,
483 Box::new(runtime_type(c.runtime_return)),
484 vec![],
485 ))
486 }
487 EffectDimension::Generative | EffectDimension::GenerativeOutput => {
488 let mut params = vec![Type::named(branch_path::TYPE_NAME.to_string()), Type::Int];
489 params.extend(c.runtime_params.iter().copied().map(runtime_type));
490 Some(Type::Fn(
491 params,
492 Box::new(runtime_type(c.runtime_return)),
493 vec![],
494 ))
495 }
496 }
497}
498
499#[cfg(test)]
500mod tests {
501 use super::*;
502
503 #[test]
504 fn classify_returns_none_for_unknown() {
505 assert!(classify("Nope.missing").is_none());
506 assert!(classify("Args.set").is_none());
507 }
508
509 #[test]
510 fn args_get_is_snapshot() {
511 let c = classify("Args.get").unwrap();
512 assert_eq!(c.dimension, EffectDimension::Snapshot);
513 }
514
515 #[test]
516 fn random_int_is_generative() {
517 let c = classify("Random.int").unwrap();
518 assert_eq!(c.dimension, EffectDimension::Generative);
519 }
520
521 #[test]
522 fn http_get_is_generative_output() {
523 let c = classify("Http.get").unwrap();
524 assert_eq!(c.dimension, EffectDimension::GenerativeOutput);
525 }
526
527 #[test]
528 fn disk_write_text_is_generative_output() {
529 let c = classify("Disk.writeText").unwrap();
530 assert_eq!(c.dimension, EffectDimension::GenerativeOutput);
531 }
532
533 #[test]
534 fn console_print_is_output() {
535 let c = classify("Console.print").unwrap();
536 assert_eq!(c.dimension, EffectDimension::Output);
537 }
538
539 #[test]
540 fn console_read_line_is_generative() {
541 let c = classify("Console.readLine").unwrap();
542 assert_eq!(c.dimension, EffectDimension::Generative);
543 }
544
545 #[test]
546 fn time_sleep_is_output() {
547 let c = classify("Time.sleep").unwrap();
548 assert_eq!(c.dimension, EffectDimension::Output);
549 }
550
551 #[test]
552 fn terminal_read_key_is_generative() {
553 let c = classify("Terminal.readKey").unwrap();
554 assert_eq!(c.dimension, EffectDimension::Generative);
555 }
556
557 #[test]
558 fn oracle_signature_for_random_int_is_branch_indexed() {
559 let sig = oracle_signature("Random.int").unwrap();
560 match sig {
562 Type::Fn(params, ret, _) => {
563 assert_eq!(params.len(), 4);
564 assert!(matches!(params[0], Type::Named { name: ref n, .. } if n == "BranchPath"));
565 assert_eq!(params[1], Type::Int);
566 assert_eq!(params[2], Type::Int);
567 assert_eq!(params[3], Type::Int);
568 assert_eq!(*ret, Type::Int);
569 }
570 other => panic!("expected Fn, got {:?}", other),
571 }
572 }
573
574 #[test]
575 fn oracle_signature_for_random_float_is_branch_indexed_no_extra_args() {
576 let sig = oracle_signature("Random.float").unwrap();
577 match sig {
579 Type::Fn(params, ret, _) => {
580 assert_eq!(params.len(), 2);
581 assert!(matches!(params[0], Type::Named { name: ref n, .. } if n == "BranchPath"));
582 assert_eq!(params[1], Type::Int);
583 assert_eq!(*ret, Type::Float);
584 }
585 other => panic!("expected Fn, got {:?}", other),
586 }
587 }
588
589 #[test]
590 fn oracle_signature_for_args_get_is_capability_reader() {
591 let sig = oracle_signature("Args.get").unwrap();
592 match sig {
594 Type::Fn(params, ret, _) => {
595 assert!(params.is_empty());
596 assert_eq!(*ret, Type::List(Box::new(Type::Str)));
597 }
598 other => panic!("expected Fn, got {:?}", other),
599 }
600 }
601
602 #[test]
603 fn oracle_signature_for_env_get_is_capability_reader() {
604 let sig = oracle_signature("Env.get").unwrap();
605 match sig {
607 Type::Fn(params, ret, _) => {
608 assert_eq!(params, vec![Type::Str]);
609 assert_eq!(*ret, Type::Option(Box::new(Type::Str)));
610 }
611 other => panic!("expected Fn, got {:?}", other),
612 }
613 }
614
615 #[test]
616 fn oracle_signature_for_http_get_is_branch_indexed() {
617 let sig = oracle_signature("Http.get").unwrap();
618 match sig {
620 Type::Fn(params, ret, _) => {
621 assert_eq!(params.len(), 3);
622 assert!(matches!(params[0], Type::Named { name: ref n, .. } if n == "BranchPath"));
623 assert_eq!(params[1], Type::Int);
624 assert_eq!(params[2], Type::Str);
625 match *ret {
626 Type::Result(ok, err) => {
627 assert!(
628 matches!(*ok, Type::Named { name: ref n, .. } if n == "HttpResponse")
629 );
630 assert_eq!(*err, Type::Str);
631 }
632 other => panic!("expected Result, got {:?}", other),
633 }
634 }
635 other => panic!("expected Fn, got {:?}", other),
636 }
637 }
638
639 #[test]
640 fn oracle_signature_for_console_read_line_is_branch_indexed() {
641 let sig = oracle_signature("Console.readLine").unwrap();
642 match sig {
644 Type::Fn(params, ret, _) => {
645 assert_eq!(params.len(), 2);
646 assert!(matches!(params[0], Type::Named { name: ref n, .. } if n == "BranchPath"));
647 assert_eq!(params[1], Type::Int);
648 assert_eq!(*ret, Type::Result(Box::new(Type::Str), Box::new(Type::Str)));
649 }
650 other => panic!("expected Fn, got {:?}", other),
651 }
652 }
653
654 #[test]
655 fn oracle_signature_for_disk_list_dir_returns_result_list_string() {
656 let sig = oracle_signature("Disk.listDir").unwrap();
657 match sig {
659 Type::Fn(params, ret, _) => {
660 assert_eq!(params.len(), 3);
661 assert!(matches!(params[0], Type::Named { name: ref n, .. } if n == "BranchPath"));
662 assert_eq!(params[1], Type::Int);
663 assert_eq!(params[2], Type::Str);
664 assert_eq!(
665 *ret,
666 Type::Result(
667 Box::new(Type::List(Box::new(Type::Str))),
668 Box::new(Type::Str)
669 )
670 );
671 }
672 other => panic!("expected Fn, got {:?}", other),
673 }
674 }
675
676 #[test]
677 fn oracle_signature_for_tcp_ping_returns_result_unit_string() {
678 let sig = oracle_signature("Tcp.ping").unwrap();
679 match sig {
681 Type::Fn(params, ret, _) => {
682 assert_eq!(params.len(), 4);
683 assert!(matches!(params[0], Type::Named { name: ref n, .. } if n == "BranchPath"));
684 assert_eq!(params[1], Type::Int);
685 assert_eq!(params[2], Type::Str);
686 assert_eq!(params[3], Type::Int);
687 assert_eq!(
688 *ret,
689 Type::Result(Box::new(Type::Unit), Box::new(Type::Str))
690 );
691 }
692 other => panic!("expected Fn, got {:?}", other),
693 }
694 }
695
696 #[test]
697 fn oracle_signature_for_disk_write_text_returns_result_unit_string() {
698 let sig = oracle_signature("Disk.writeText").unwrap();
699 match sig {
701 Type::Fn(params, ret, _) => {
702 assert_eq!(params.len(), 4);
703 assert!(matches!(params[0], Type::Named { name: ref n, .. } if n == "BranchPath"));
704 assert_eq!(params[1], Type::Int);
705 assert_eq!(params[2], Type::Str);
706 assert_eq!(params[3], Type::Str);
707 assert_eq!(
708 *ret,
709 Type::Result(Box::new(Type::Unit), Box::new(Type::Str))
710 );
711 }
712 other => panic!("expected Fn, got {:?}", other),
713 }
714 }
715
716 #[test]
717 fn oracle_signature_for_output_effect_is_none() {
718 assert!(oracle_signature("Console.print").is_none());
719 assert!(oracle_signature("Console.error").is_none());
720 assert!(oracle_signature("Console.warn").is_none());
721 assert!(oracle_signature("Time.sleep").is_none());
722 assert!(oracle_signature("Terminal.print").is_none());
723 }
724
725 #[test]
726 fn is_classified_covers_full_v1_set() {
727 for name in &[
728 "Args.get",
729 "Env.get",
730 "Random.int",
731 "Random.float",
732 "Time.now",
733 "Time.unixMs",
734 "Time.sleep",
735 "Disk.readText",
736 "Disk.exists",
737 "Disk.listDir",
738 "Disk.writeText",
739 "Disk.appendText",
740 "Disk.delete",
741 "Disk.deleteDir",
742 "Disk.makeDir",
743 "Console.readLine",
744 "Http.get",
745 "Http.head",
746 "Http.delete",
747 "Http.post",
748 "Http.put",
749 "Http.patch",
750 "Tcp.send",
751 "Tcp.ping",
752 "Console.print",
753 "Console.error",
754 "Console.warn",
755 "Terminal.clear",
756 "Terminal.moveTo",
757 "Terminal.print",
758 "Terminal.readKey",
759 "Terminal.hideCursor",
760 "Terminal.showCursor",
761 "Terminal.flush",
762 ] {
763 assert!(is_classified(name), "{} should be classified", name);
764 }
765 }
766
767 #[test]
768 fn oracle_signature_for_http_post_has_four_runtime_params() {
769 let sig = oracle_signature("Http.post").unwrap();
770 match sig {
772 Type::Fn(params, ret, _) => {
773 assert_eq!(params.len(), 6);
774 assert!(matches!(params[0], Type::Named { name: ref n, .. } if n == "BranchPath"));
775 assert_eq!(params[1], Type::Int);
776 assert_eq!(params[2], Type::Str);
777 assert_eq!(params[3], Type::Str);
778 assert_eq!(params[4], Type::Str);
779 match ¶ms[5] {
780 Type::Map(key, value) => {
781 assert_eq!(**key, Type::Str);
782 match &**value {
783 Type::List(inner) => assert_eq!(**inner, Type::Str),
784 other => panic!("expected Map<Str, List<Str>>, got {:?}", other),
785 }
786 }
787 other => panic!("expected Map<Str, List<Str>>, got {:?}", other),
788 }
789 match *ret {
790 Type::Result(ok, err) => {
791 assert!(
792 matches!(*ok, Type::Named { name: ref n, .. } if n == "HttpResponse")
793 );
794 assert_eq!(*err, Type::Str);
795 }
796 other => panic!("expected Result, got {:?}", other),
797 }
798 }
799 other => panic!("expected Fn, got {:?}", other),
800 }
801 }
802
803 #[test]
804 fn server_lifecycle_remains_unclassified() {
805 for name in &["HttpServer.listen", "HttpServer.listenWith"] {
809 assert!(!is_classified(name), "{} should NOT be classified", name);
810 }
811 }
812
813 #[test]
814 fn extended_oracle_v1_methods_classified() {
815 for name in &[
818 "Env.set",
819 "Terminal.enableRawMode",
820 "Terminal.disableRawMode",
821 "Terminal.setColor",
822 "Terminal.resetColor",
823 ] {
824 let c = classify(name).unwrap_or_else(|| panic!("{} should be classified", name));
825 assert_eq!(c.dimension, EffectDimension::Output);
826 }
827 for name in &["Tcp.connect", "Tcp.readLine", "Tcp.writeLine", "Tcp.close"] {
830 let c = classify(name).unwrap_or_else(|| panic!("{} should be classified", name));
831 assert_eq!(c.dimension, EffectDimension::GenerativeOutput);
832 }
833 }
834
835 #[test]
836 fn terminal_size_is_snapshot() {
837 let c = classify("Terminal.size").expect("Terminal.size should be classified");
838 assert_eq!(c.dimension, EffectDimension::Snapshot);
839 assert!(c.runtime_params.is_empty());
840 assert_eq!(c.runtime_return, RuntimeType::TerminalSize);
841
842 let sig = oracle_signature("Terminal.size").unwrap();
844 match sig {
845 Type::Fn(params, ret, effects) => {
846 assert!(params.is_empty());
847 assert_eq!(*ret, Type::named("Terminal.Size"));
848 assert!(effects.is_empty());
849 }
850 other => panic!("expected Fn, got {:?}", other),
851 }
852 }
853}