use dever::{ compiler::{build, check, model::Program}, syntax::{diagnostic::Diagnostic, source::Source}, }; fn linked(text: &str) -> Result { let inputs = [ ("main", text), ("dever.io", include_str!("../../../lib/dever/io.dever")), ("dever.time", include_str!("../../../lib/dever/time.dever")), ("dever.task", include_str!("../../../lib/dever/task.dever")), ( "dever.net", include_str!("../../../lib/dever/net/socket.dever"), ), ( "dever.net", include_str!("../../../lib/dever/net/listener.dever"), ), ]; let modules = inputs .iter() .enumerate() .map(|(index, (_, text))| { dever::syntax::parse(&Source::new( index, if index == 0 { "main.dever".into() } else { format!("source{index}.dever") }, (*text).into(), )) }) .collect::, _>>()?; check::check( modules, &inputs .iter() .map(|(package, _)| (*package).into()) .collect::>(), ) } #[test] fn network_effects_and_runtime_features_match_the_actual_entry() { for (source, suspends, schedules, net) in [ ("main() (answer: Int) { answer = 42 }", false, false, false), ( "main() (socket: dever.system.Socket?) { socket = null }", false, false, true, ), ( "main(socket: dever.system.Socket) () { dever.net.close(socket) }", false, false, true, ), ( "main(listener: dever.system.Listener) (port: Int) { port = dever.net.port(listener) }", false, false, true, ), ( "main() (socket: dever.system.Socket) { socket = dever.net.connectTimeout(\"127.0.0.1\", 80, 1000) }", true, false, true, ), ( "main() (socket: dever.system.Socket) { socket = dever.net.connect(\"localhost\", 80) }", true, true, true, ), ( "main() (listener: dever.system.Listener) { listener = dever.net.listen(\"127.0.0.1\", 0) }", true, true, true, ), ] { let program = linked(source).unwrap(); let main = program .functions .iter() .position(|function| function.signature.name == "main") .unwrap(); let effects = program.functions[main].signature.effects; assert_eq!(effects.suspend, suspends, "{source}"); assert_eq!(effects.schedule, schedules, "{source}"); assert_eq!( build::runtime_features(&program, &program.reachable([main])).contains(&"net"), net, "{source}" ); } } #[test] fn network_resources_obey_visibility_equality_and_effect_boundaries() { for (source, code) in [ ("main() () { value = dever.system.tcpPort(null) }", "C006"), ( "main(socket: dever.system.Socket) (answer: Bool) { answer = socket == socket }", "C005", ), ( "main(values: List) (answer: Bool) { answer = values == values }", "C005", ), ( "main(listener: dever.system.Listener) (port: Int) pure { port = dever.net.port(listener) }", "C013", ), ("main(value: dever.net.SystemReadResult) () {}", "C006"), ( "main(socket: dever.system.Socket) () { value = dever.net.read(socket, true) }", "C005", ), ( "main(socket: dever.system.Socket) () { value = dever.net.read(socket, 1)\n dever.io.println(\"blocking\") }", "C013", ), ] { assert_eq!(linked(source).unwrap_err().code, code, "{source}"); } } #[test] fn lazy_network_streams_do_not_claim_unread_errors_but_captured_failures_remain_obligations() { for source in [ "main(socket: dever.system.Socket) () { close(dever.net.chunks(socket, 1)) }", "main(listener: dever.system.Listener) () { close(dever.net.connections(listener)) }", ] { assert!(linked(source).is_ok(), "{source}"); } for source in [ "main(socket: dever.system.Socket) () { lost = result(dever.net.read(socket, 1)) }", "main(socket: dever.system.Socket) () { lost = result(dever.net.chunks(socket, 1)) }", "main(socket: dever.system.Socket) () { lost = result(dever.net.close(socket)) }", ] { assert_eq!(linked(source).unwrap_err().code, "C012", "{source}"); } }