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 => Type::Result(
103 Box::new(Type::Named("HttpResponse".to_string())),
104 Box::new(Type::Str),
105 ),
106 RuntimeType::MapStrListStr => Type::Map(
107 Box::new(Type::Str),
108 Box::new(Type::List(Box::new(Type::Str))),
109 ),
110 RuntimeType::TerminalSize => Type::Named("Terminal.Size".to_string()),
111 RuntimeType::TcpConnection => Type::Named("Tcp.Connection".to_string()),
112 RuntimeType::ResultTcpConnectionStr => Type::Result(
113 Box::new(Type::Named("Tcp.Connection".to_string())),
114 Box::new(Type::Str),
115 ),
116 }
117 }
118}
119
120fn runtime_type(rt: RuntimeType) -> Type {
121 rt.as_type()
122}
123
124const CLASSIFICATIONS: &[EffectClassification] = &[
126 EffectClassification {
128 method: "Args.get",
129 dimension: EffectDimension::Snapshot,
130 runtime_params: &[],
131 runtime_return: RuntimeType::ListStr,
132 },
133 EffectClassification {
134 method: "Env.get",
135 dimension: EffectDimension::Snapshot,
136 runtime_params: &[RuntimeType::Str],
137 runtime_return: RuntimeType::OptionStr,
138 },
139 EffectClassification {
142 method: "Terminal.size",
143 dimension: EffectDimension::Snapshot,
144 runtime_params: &[],
145 runtime_return: RuntimeType::TerminalSize,
146 },
147 EffectClassification {
149 method: "Random.int",
150 dimension: EffectDimension::Generative,
151 runtime_params: &[RuntimeType::Int, RuntimeType::Int],
152 runtime_return: RuntimeType::Int,
153 },
154 EffectClassification {
155 method: "Random.float",
156 dimension: EffectDimension::Generative,
157 runtime_params: &[],
158 runtime_return: RuntimeType::Float,
159 },
160 EffectClassification {
161 method: "Time.now",
162 dimension: EffectDimension::Generative,
163 runtime_params: &[],
164 runtime_return: RuntimeType::Str,
165 },
166 EffectClassification {
167 method: "Time.unixMs",
168 dimension: EffectDimension::Generative,
169 runtime_params: &[],
170 runtime_return: RuntimeType::Int,
171 },
172 EffectClassification {
173 method: "Disk.readText",
174 dimension: EffectDimension::Generative,
175 runtime_params: &[RuntimeType::Str],
176 runtime_return: RuntimeType::ResultStrStr,
177 },
178 EffectClassification {
179 method: "Disk.exists",
180 dimension: EffectDimension::Generative,
181 runtime_params: &[RuntimeType::Str],
182 runtime_return: RuntimeType::Bool,
183 },
184 EffectClassification {
185 method: "Disk.listDir",
186 dimension: EffectDimension::Generative,
187 runtime_params: &[RuntimeType::Str],
188 runtime_return: RuntimeType::ResultListStrStr,
189 },
190 EffectClassification {
191 method: "Console.readLine",
192 dimension: EffectDimension::Generative,
193 runtime_params: &[],
194 runtime_return: RuntimeType::ResultStrStr,
195 },
196 EffectClassification {
198 method: "Http.get",
199 dimension: EffectDimension::GenerativeOutput,
200 runtime_params: &[RuntimeType::Str],
201 runtime_return: RuntimeType::HttpResponseResult,
202 },
203 EffectClassification {
204 method: "Http.head",
205 dimension: EffectDimension::GenerativeOutput,
206 runtime_params: &[RuntimeType::Str],
207 runtime_return: RuntimeType::HttpResponseResult,
208 },
209 EffectClassification {
210 method: "Http.delete",
211 dimension: EffectDimension::GenerativeOutput,
212 runtime_params: &[RuntimeType::Str],
213 runtime_return: RuntimeType::HttpResponseResult,
214 },
215 EffectClassification {
217 method: "Http.post",
218 dimension: EffectDimension::GenerativeOutput,
219 runtime_params: &[
220 RuntimeType::Str,
221 RuntimeType::Str,
222 RuntimeType::Str,
223 RuntimeType::MapStrListStr,
224 ],
225 runtime_return: RuntimeType::HttpResponseResult,
226 },
227 EffectClassification {
228 method: "Http.put",
229 dimension: EffectDimension::GenerativeOutput,
230 runtime_params: &[
231 RuntimeType::Str,
232 RuntimeType::Str,
233 RuntimeType::Str,
234 RuntimeType::MapStrListStr,
235 ],
236 runtime_return: RuntimeType::HttpResponseResult,
237 },
238 EffectClassification {
239 method: "Http.patch",
240 dimension: EffectDimension::GenerativeOutput,
241 runtime_params: &[
242 RuntimeType::Str,
243 RuntimeType::Str,
244 RuntimeType::Str,
245 RuntimeType::MapStrListStr,
246 ],
247 runtime_return: RuntimeType::HttpResponseResult,
248 },
249 EffectClassification {
253 method: "Disk.writeText",
254 dimension: EffectDimension::GenerativeOutput,
255 runtime_params: &[RuntimeType::Str, RuntimeType::Str],
256 runtime_return: RuntimeType::ResultUnitStr,
257 },
258 EffectClassification {
259 method: "Disk.appendText",
260 dimension: EffectDimension::GenerativeOutput,
261 runtime_params: &[RuntimeType::Str, RuntimeType::Str],
262 runtime_return: RuntimeType::ResultUnitStr,
263 },
264 EffectClassification {
265 method: "Disk.delete",
266 dimension: EffectDimension::GenerativeOutput,
267 runtime_params: &[RuntimeType::Str],
268 runtime_return: RuntimeType::ResultUnitStr,
269 },
270 EffectClassification {
271 method: "Disk.deleteDir",
272 dimension: EffectDimension::GenerativeOutput,
273 runtime_params: &[RuntimeType::Str],
274 runtime_return: RuntimeType::ResultUnitStr,
275 },
276 EffectClassification {
277 method: "Disk.makeDir",
278 dimension: EffectDimension::GenerativeOutput,
279 runtime_params: &[RuntimeType::Str],
280 runtime_return: RuntimeType::ResultUnitStr,
281 },
282 EffectClassification {
284 method: "Tcp.send",
285 dimension: EffectDimension::GenerativeOutput,
286 runtime_params: &[RuntimeType::Str, RuntimeType::Int, RuntimeType::Str],
287 runtime_return: RuntimeType::ResultStrStr,
288 },
289 EffectClassification {
290 method: "Tcp.ping",
291 dimension: EffectDimension::GenerativeOutput,
292 runtime_params: &[RuntimeType::Str, RuntimeType::Int],
293 runtime_return: RuntimeType::ResultUnitStr,
294 },
295 EffectClassification {
299 method: "Tcp.connect",
300 dimension: EffectDimension::GenerativeOutput,
301 runtime_params: &[RuntimeType::Str, RuntimeType::Int],
302 runtime_return: RuntimeType::ResultTcpConnectionStr,
303 },
304 EffectClassification {
305 method: "Tcp.readLine",
306 dimension: EffectDimension::GenerativeOutput,
307 runtime_params: &[RuntimeType::TcpConnection],
308 runtime_return: RuntimeType::ResultStrStr,
309 },
310 EffectClassification {
311 method: "Tcp.writeLine",
312 dimension: EffectDimension::GenerativeOutput,
313 runtime_params: &[RuntimeType::TcpConnection, RuntimeType::Str],
314 runtime_return: RuntimeType::ResultUnitStr,
315 },
316 EffectClassification {
317 method: "Tcp.close",
318 dimension: EffectDimension::GenerativeOutput,
319 runtime_params: &[RuntimeType::TcpConnection],
320 runtime_return: RuntimeType::ResultUnitStr,
321 },
322 EffectClassification {
328 method: "Env.set",
329 dimension: EffectDimension::Output,
330 runtime_params: &[RuntimeType::Str, RuntimeType::Str],
331 runtime_return: RuntimeType::Unit,
332 },
333 EffectClassification {
334 method: "Console.print",
335 dimension: EffectDimension::Output,
336 runtime_params: &[RuntimeType::FormattedValue],
337 runtime_return: RuntimeType::Unit,
338 },
339 EffectClassification {
340 method: "Console.error",
341 dimension: EffectDimension::Output,
342 runtime_params: &[RuntimeType::FormattedValue],
343 runtime_return: RuntimeType::Unit,
344 },
345 EffectClassification {
346 method: "Console.warn",
347 dimension: EffectDimension::Output,
348 runtime_params: &[RuntimeType::FormattedValue],
349 runtime_return: RuntimeType::Unit,
350 },
351 EffectClassification {
352 method: "Time.sleep",
353 dimension: EffectDimension::Output,
354 runtime_params: &[RuntimeType::Int],
355 runtime_return: RuntimeType::Unit,
356 },
357 EffectClassification {
358 method: "Terminal.clear",
359 dimension: EffectDimension::Output,
360 runtime_params: &[],
361 runtime_return: RuntimeType::Unit,
362 },
363 EffectClassification {
364 method: "Terminal.moveTo",
365 dimension: EffectDimension::Output,
366 runtime_params: &[RuntimeType::Int, RuntimeType::Int],
367 runtime_return: RuntimeType::Unit,
368 },
369 EffectClassification {
370 method: "Terminal.print",
371 dimension: EffectDimension::Output,
372 runtime_params: &[RuntimeType::FormattedValue],
373 runtime_return: RuntimeType::Unit,
374 },
375 EffectClassification {
376 method: "Terminal.readKey",
377 dimension: EffectDimension::Generative,
378 runtime_params: &[],
379 runtime_return: RuntimeType::OptionStr,
380 },
381 EffectClassification {
382 method: "Terminal.hideCursor",
383 dimension: EffectDimension::Output,
384 runtime_params: &[],
385 runtime_return: RuntimeType::Unit,
386 },
387 EffectClassification {
388 method: "Terminal.showCursor",
389 dimension: EffectDimension::Output,
390 runtime_params: &[],
391 runtime_return: RuntimeType::Unit,
392 },
393 EffectClassification {
394 method: "Terminal.flush",
395 dimension: EffectDimension::Output,
396 runtime_params: &[],
397 runtime_return: RuntimeType::Unit,
398 },
399 EffectClassification {
404 method: "Terminal.enableRawMode",
405 dimension: EffectDimension::Output,
406 runtime_params: &[],
407 runtime_return: RuntimeType::Unit,
408 },
409 EffectClassification {
410 method: "Terminal.disableRawMode",
411 dimension: EffectDimension::Output,
412 runtime_params: &[],
413 runtime_return: RuntimeType::Unit,
414 },
415 EffectClassification {
416 method: "Terminal.setColor",
417 dimension: EffectDimension::Output,
418 runtime_params: &[RuntimeType::Str],
419 runtime_return: RuntimeType::Unit,
420 },
421 EffectClassification {
422 method: "Terminal.resetColor",
423 dimension: EffectDimension::Output,
424 runtime_params: &[],
425 runtime_return: RuntimeType::Unit,
426 },
427];
428
429pub fn classify(method: &str) -> Option<&'static EffectClassification> {
431 CLASSIFICATIONS.iter().find(|c| c.method == method)
432}
433
434pub fn classifications_for_proof_subset() -> &'static [EffectClassification] {
437 CLASSIFICATIONS
438}
439
440pub fn is_classified(method: &str) -> bool {
442 classify(method).is_some()
443}
444
445pub fn oracle_signature(method: &str) -> Option<Type> {
454 let c = classify(method)?;
455 match c.dimension {
456 EffectDimension::Output => None,
457 EffectDimension::Snapshot => {
458 let params: Vec<Type> = c.runtime_params.iter().copied().map(runtime_type).collect();
459 Some(Type::Fn(
460 params,
461 Box::new(runtime_type(c.runtime_return)),
462 vec![],
463 ))
464 }
465 EffectDimension::Generative | EffectDimension::GenerativeOutput => {
466 let mut params = vec![Type::Named(branch_path::TYPE_NAME.to_string()), Type::Int];
467 params.extend(c.runtime_params.iter().copied().map(runtime_type));
468 Some(Type::Fn(
469 params,
470 Box::new(runtime_type(c.runtime_return)),
471 vec![],
472 ))
473 }
474 }
475}
476
477#[cfg(test)]
478mod tests {
479 use super::*;
480
481 #[test]
482 fn classify_returns_none_for_unknown() {
483 assert!(classify("Nope.missing").is_none());
484 assert!(classify("Args.set").is_none());
485 }
486
487 #[test]
488 fn args_get_is_snapshot() {
489 let c = classify("Args.get").unwrap();
490 assert_eq!(c.dimension, EffectDimension::Snapshot);
491 }
492
493 #[test]
494 fn random_int_is_generative() {
495 let c = classify("Random.int").unwrap();
496 assert_eq!(c.dimension, EffectDimension::Generative);
497 }
498
499 #[test]
500 fn http_get_is_generative_output() {
501 let c = classify("Http.get").unwrap();
502 assert_eq!(c.dimension, EffectDimension::GenerativeOutput);
503 }
504
505 #[test]
506 fn disk_write_text_is_generative_output() {
507 let c = classify("Disk.writeText").unwrap();
508 assert_eq!(c.dimension, EffectDimension::GenerativeOutput);
509 }
510
511 #[test]
512 fn console_print_is_output() {
513 let c = classify("Console.print").unwrap();
514 assert_eq!(c.dimension, EffectDimension::Output);
515 }
516
517 #[test]
518 fn console_read_line_is_generative() {
519 let c = classify("Console.readLine").unwrap();
520 assert_eq!(c.dimension, EffectDimension::Generative);
521 }
522
523 #[test]
524 fn time_sleep_is_output() {
525 let c = classify("Time.sleep").unwrap();
526 assert_eq!(c.dimension, EffectDimension::Output);
527 }
528
529 #[test]
530 fn terminal_read_key_is_generative() {
531 let c = classify("Terminal.readKey").unwrap();
532 assert_eq!(c.dimension, EffectDimension::Generative);
533 }
534
535 #[test]
536 fn oracle_signature_for_random_int_is_branch_indexed() {
537 let sig = oracle_signature("Random.int").unwrap();
538 match sig {
540 Type::Fn(params, ret, _) => {
541 assert_eq!(params.len(), 4);
542 assert!(matches!(params[0], Type::Named(ref n) if n == "BranchPath"));
543 assert_eq!(params[1], Type::Int);
544 assert_eq!(params[2], Type::Int);
545 assert_eq!(params[3], Type::Int);
546 assert_eq!(*ret, Type::Int);
547 }
548 other => panic!("expected Fn, got {:?}", other),
549 }
550 }
551
552 #[test]
553 fn oracle_signature_for_random_float_is_branch_indexed_no_extra_args() {
554 let sig = oracle_signature("Random.float").unwrap();
555 match sig {
557 Type::Fn(params, ret, _) => {
558 assert_eq!(params.len(), 2);
559 assert!(matches!(params[0], Type::Named(ref n) if n == "BranchPath"));
560 assert_eq!(params[1], Type::Int);
561 assert_eq!(*ret, Type::Float);
562 }
563 other => panic!("expected Fn, got {:?}", other),
564 }
565 }
566
567 #[test]
568 fn oracle_signature_for_args_get_is_capability_reader() {
569 let sig = oracle_signature("Args.get").unwrap();
570 match sig {
572 Type::Fn(params, ret, _) => {
573 assert!(params.is_empty());
574 assert_eq!(*ret, Type::List(Box::new(Type::Str)));
575 }
576 other => panic!("expected Fn, got {:?}", other),
577 }
578 }
579
580 #[test]
581 fn oracle_signature_for_env_get_is_capability_reader() {
582 let sig = oracle_signature("Env.get").unwrap();
583 match sig {
585 Type::Fn(params, ret, _) => {
586 assert_eq!(params, vec![Type::Str]);
587 assert_eq!(*ret, Type::Option(Box::new(Type::Str)));
588 }
589 other => panic!("expected Fn, got {:?}", other),
590 }
591 }
592
593 #[test]
594 fn oracle_signature_for_http_get_is_branch_indexed() {
595 let sig = oracle_signature("Http.get").unwrap();
596 match sig {
598 Type::Fn(params, ret, _) => {
599 assert_eq!(params.len(), 3);
600 assert!(matches!(params[0], Type::Named(ref n) if n == "BranchPath"));
601 assert_eq!(params[1], Type::Int);
602 assert_eq!(params[2], Type::Str);
603 match *ret {
604 Type::Result(ok, err) => {
605 assert!(matches!(*ok, Type::Named(ref n) if n == "HttpResponse"));
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(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(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(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(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(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!(matches!(*ok, Type::Named(ref n) if n == "HttpResponse"));
768 assert_eq!(*err, Type::Str);
769 }
770 other => panic!("expected Result, got {:?}", other),
771 }
772 }
773 other => panic!("expected Fn, got {:?}", other),
774 }
775 }
776
777 #[test]
778 fn server_lifecycle_remains_unclassified() {
779 for name in &["HttpServer.listen", "HttpServer.listenWith"] {
783 assert!(!is_classified(name), "{} should NOT be classified", name);
784 }
785 }
786
787 #[test]
788 fn extended_oracle_v1_methods_classified() {
789 for name in &[
792 "Env.set",
793 "Terminal.enableRawMode",
794 "Terminal.disableRawMode",
795 "Terminal.setColor",
796 "Terminal.resetColor",
797 ] {
798 let c = classify(name).unwrap_or_else(|| panic!("{} should be classified", name));
799 assert_eq!(c.dimension, EffectDimension::Output);
800 }
801 for name in &["Tcp.connect", "Tcp.readLine", "Tcp.writeLine", "Tcp.close"] {
804 let c = classify(name).unwrap_or_else(|| panic!("{} should be classified", name));
805 assert_eq!(c.dimension, EffectDimension::GenerativeOutput);
806 }
807 }
808
809 #[test]
810 fn terminal_size_is_snapshot() {
811 let c = classify("Terminal.size").expect("Terminal.size should be classified");
812 assert_eq!(c.dimension, EffectDimension::Snapshot);
813 assert!(c.runtime_params.is_empty());
814 assert_eq!(c.runtime_return, RuntimeType::TerminalSize);
815
816 let sig = oracle_signature("Terminal.size").unwrap();
818 match sig {
819 Type::Fn(params, ret, effects) => {
820 assert!(params.is_empty());
821 assert_eq!(*ret, Type::Named("Terminal.Size".to_string()));
822 assert!(effects.is_empty());
823 }
824 other => panic!("expected Fn, got {:?}", other),
825 }
826 }
827}