use super::signature; use dever::compiler::{check, model::Program}; use dever::syntax::{diagnostic::Diagnostic, source::Source}; fn packages(files: &[(&str, &str)]) -> Result { let mut modules = Vec::new(); let mut identities = Vec::new(); for (index, (identity, text)) in files.iter().enumerate() { let path = if *identity == "dever.io" { "lib/dever/io.dever" } else { "case.dever" }; modules.push(dever::syntax::parse(&Source::new( index, path, (*text).into(), ))?); identities.push((*identity).into()); } check::check(modules, &identities) } const IO: &str = include_str!("../../../../lib/dever/io.dever"); const PORT: &str = "open(path: Text) (file: dever.system.File) fails dever.io.OpenResult"; const ADAPTER: &str = "port.open(path: Text) (file: dever.system.File) { file = open(path) }\nopen(path: Text) (file: dever.system.File) { file = dever.io.open(path) }"; #[test] fn boundary_retains_real_effects_without_permitting_unmediated_io() { let app = "open(path: Text) (file: dever.system.File) { file = port.open(path) }"; let program = packages(&[ ("shop.files.app", app), ("shop.files.port", PORT), ("shop.files.adapter", ADAPTER), ("dever.io", IO), ]) .unwrap(); let effects = signature(&program, "open").effects; assert!(effects.file && effects.blocking && !effects.unmediated_io); for body in [ "file = dever.io.open(path)", "old = port.open(path)\nfile = dever.io.open(path)", "files = each(dever.io.open, [path])\nfile = port.open(path)", "old = result(dever.io.open(path))\nfile = port.open(path)", ] { let app = format!("open(path: Text) (file: dever.system.File) {{ {body} }}"); assert!( packages(&[ ("shop.files.app", &app), ("shop.files.port", PORT), ("shop.files.adapter", ADAPTER), ("dever.io", IO) ]) .is_err(), "{body}" ); } let pure = app.replace(" {", " pure {"); assert_eq!( packages(&[ ("shop.files.app", &pure), ("shop.files.port", PORT), ("shop.files.adapter", ADAPTER), ("dever.io", IO) ]) .unwrap_err() .code, "C013" ); let scheduled = app.replace("port.open(path)", "blocking(port.open(path))"); assert!( packages(&[ ("shop.files.app", &scheduled), ("shop.files.port", PORT), ("shop.files.adapter", ADAPTER), ("dever.io", IO) ]) .is_ok() ); let asynchronous = app.replace( "file = port.open(path)", "other = blocking(port.open(path))\nfile = port.open(path)", ); assert_eq!( packages(&[ ("shop.files.app", &asynchronous), ("shop.files.port", PORT), ("shop.files.adapter", ADAPTER), ("dever.io", IO) ]) .unwrap_err() .code, "C013" ); } #[test] fn selection_cannot_hide_blocking_candidate_effects() { let app = "open(path: Text) (file: dever.system.File) { file = port.open(path) }"; let worker = "port.open(path: Text) (file: dever.system.File) { file = blocking(dever.io.open(path)) }"; assert_eq!( packages(&[ ("shop.files.app", app), ("shop.files.port", PORT), ("shop.files.adapter.inline", ADAPTER), ("shop.files.adapter.worker", worker), ("dever.io", IO), ]) .unwrap_err() .code, "C013" ); let program = packages(&[ ("shop.files.app", app), ("shop.files.port", PORT), ("shop.files.adapter.first", worker), ("shop.files.adapter.second", worker), ("dever.io", IO), ]) .unwrap(); let effects = signature(&program, "open").effects; assert!(effects.file && effects.suspend && effects.configuration); assert!(!effects.blocking && !effects.unmediated_io); }