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