mod support; use support::{checked, sources}; fn application(files: &[(&str, &str)]) -> dever_core::hir::Program { let root = support::temp::TemporaryDirectory::new(); for (relative, source) in files { let path = root.path().join("module").join(relative); std::fs::create_dir_all(path.parent().unwrap()).unwrap(); std::fs::write(path, source).unwrap(); } checked(&dever_core::source::SourceMap::load(&root.path().join("module")).unwrap()) } fn warnings(source: &str, code: &str) -> Vec { let sources = sources(source); let user_source = sources.files()[0].id(); checked(&sources) .warnings() .iter() .filter(|warning| warning.code == code && warning.primary.source == user_source) .cloned() .collect() } #[test] fn identical_private_implementations_are_reported_with_source_evidence() { let source = "first(value: Int) (answer: Int) { answer = value + 1 }\nsecond(renamed: Int) (answer: Int) { answer = renamed + 1 }"; let duplicates = warnings(source, "W003"); assert_eq!(duplicates.len(), 1); assert_eq!(duplicates[0].related.len(), 1); assert!(duplicates[0].message.contains("main.first")); assert!(duplicates[0].message.contains("main.second")); assert_ne!(duplicates[0].primary, duplicates[0].related[0].span); } #[test] fn visibility_contract_and_operator_differences_are_not_duplicates() { for source in [ "public first(value: Int) (answer: Int) { answer = value + 1 }\nsecond(value: Int) (answer: Int) { answer = value + 1 }", "first(value: Int) (answer: Int) pure { answer = value + 1 }\nsecond(value: Int) (answer: Int) { answer = value + 1 }", "first(value: Int) (answer: Int) { answer = value + 1 }\nsecond(value: Int) (answer: Int) { answer = value - 1 }", "first(value: Int) (answer: Int) { answer = value }\nsecond(value: Int) (different: Int) { different = value }", ] { assert!(warnings(source, "W003").is_empty(), "{source}"); } } #[test] fn repeated_pure_calls_require_unchanged_inputs_and_an_available_result() { let prefix = "calculate(value: Int) (answer: Int) { answer = value + 1 }\n"; let repeated = format!( "{prefix}main(value: Int) (answer: Int) {{ first = calculate(value)\nsecond = calculate(value)\nanswer = first + second }}" ); let diagnostics = warnings(&repeated, "W004"); assert_eq!(diagnostics.len(), 1); assert_eq!(diagnostics[0].related.len(), 1); for body in [ "first = calculate(value)\nvalue = value + 1\nsecond = calculate(value)\nanswer = first + second", "first = calculate(value)\nfirst = 0\nsecond = calculate(value)\nanswer = first + second", ] { assert!( warnings( &format!("{prefix}main(value: Int) (answer: Int) {{ {body} }}"), "W004" ) .is_empty() ); } } #[test] fn field_writes_and_effectful_calls_are_not_reuse_candidates() { let field = "public type Counter { value: Int }\ncalculate(value: Int) (answer: Int) { answer = value + 1 }\npublic main(counter: Counter) (answer: Int) { first = calculate(counter.value)\ncounter.value = counter.value + 1\nsecond = calculate(counter.value)\nanswer = first + second }"; assert!(warnings(field, "W004").is_empty()); let effects = "noisy(value: Int) (answer: Int) { dever.io.println(\"called\")\nanswer = value }\npublic main(value: Int) (answer: Int) { first = noisy(value)\nsecond = noisy(value)\nanswer = first + second }"; assert!(warnings(effects, "W004").is_empty()); } #[test] fn forwarding_warning_excludes_public_adapters_recovery_and_constrained_outputs() { let direct = "calculate(value: Int) (answer: Int) { answer = value + 1 }\nwrapper(value: Int) (answer: Int) { answer = calculate(value) }"; assert_eq!(warnings(direct, "W005").len(), 1); for source in [ "calculate(value: Int) (answer: Int) { answer = value + 1 }\npublic wrapper(value: Int) (answer: Int) { answer = calculate(value) }", "calculate(value: Int) (answer: Int) { answer = value + 1 }\nwrapper(value: Int) (answer: Int) recover(\"application policy\") { answer = calculate(value) }", "calculate() (answer: Int >= 1) { answer = 1 }\nwrapper() (answer: Int >= 1) { answer = calculate() }", "type PrivateValue { private value: Int }\ncreate(value: Int) (answer: PrivateValue) { answer = PrivateValue { value = value } }\nwrapper(value: Int) (answer: PrivateValue) { answer = create(value) }", ] { assert!(warnings(source, "W005").is_empty(), "{source}"); } } #[test] fn existing_unused_write_warning_is_not_duplicated_as_dead_computation() { let program = checked(&sources( "calculate(value: Int) (answer: Int) { answer = value + 1 }\npublic main() () { unused = calculate(1) }", )); assert!( program .warnings() .iter() .any(|warning| warning.code == "W001") ); assert!( !program .warnings() .iter() .any(|warning| warning.code == "W006") ); } #[test] fn application_rejects_reachable_private_copies_with_renamed_inputs() { let program = application(&[ ("sample/count/domain.dever", "first(value: Int) (answer: Int) pure { answer = value + 1 }\nsecond(renamed: Int) (answer: Int) pure { answer = renamed + 1 }"), ("sample/count/app.dever", "total(value: Int) (answer: Int) { first = domain.first(value)\nsecond = domain.second(value)\nanswer = first + second }"), ("sample/count/api.dever", "cmd total = app.total"), ]); let warnings: Vec<_> = program.warnings().iter().filter(|item| item.code == "W003").collect(); let errors = program.application_errors().unwrap(); assert_eq!(errors.len(), 1, "{errors:?}"); assert_eq!(errors[0].code, "C016"); assert!(errors[0].message.contains("same implementation and contract")); assert!(errors[0].message.contains("domain.first")); assert!(errors[0].message.contains("domain.second")); assert_eq!(errors[0].related.len(), 1); assert!(warnings.iter().any(|warning| warning.primary == errors[0].primary)); assert_ne!(errors[0].primary, errors[0].related[0].span); } #[test] fn application_rejects_repeated_pure_calls_using_the_warning_proof() { let program = application(&[ ("sample/count/domain.dever", "calculate(value: Int) (answer: Int) pure { answer = value + 1 }"), ("sample/count/app.dever", "total(value: Int) (answer: Int) { first = domain.calculate(value)\nsecond = domain.calculate(value)\nanswer = first + second }"), ("sample/count/api.dever", "cmd total = app.total"), ]); let errors = program.application_errors().unwrap(); assert_eq!(errors.len(), 1, "{errors:?}"); assert_eq!(errors[0].code, "C016"); assert!(errors[0].message.contains("earlier result is still available")); assert_eq!(errors[0].related.len(), 1); assert!(program.warnings().iter().any(|warning| { warning.code == "W004" && warning.primary == errors[0].primary })); } #[test] fn application_does_not_reject_changed_inputs_results_or_effectful_calls() { for (app, domain, api) in [ ( "total(value: Int) (answer: Int) { first = domain.calculate(value)\nvalue = value + 1\nsecond = domain.calculate(value)\nanswer = first + second }", "calculate(value: Int) (answer: Int) pure { answer = value + 1 }", "cmd total = app.total", ), ( "total(value: Int) (answer: Int) { first = domain.calculate(value)\nfirst = 0\nsecond = domain.calculate(value)\nanswer = first + second }", "calculate(value: Int) (answer: Int) pure { answer = value + 1 }", "cmd total = app.total", ), ( "type Counter { value: Int }\ntotal(counter: Counter) (answer: Int) { first = domain.calculate(counter.value)\ncounter.value = counter.value + 1\nsecond = domain.calculate(counter.value)\nanswer = first + second }", "calculate(value: Int) (answer: Int) pure { answer = value + 1 }", "cmd total = app.total", ), ( "noisy(value: Int) (answer: Int) { dever.io.println(\"called\")\nanswer = value }\ntotal(value: Int) (answer: Int) { first = noisy(value)\nsecond = noisy(value)\nanswer = first + second }", "", "cmd total = app.total", ), ] { let program = application(&[ ("sample/count/app.dever", app), ("sample/count/domain.dever", domain), ("sample/count/api.dever", api), ]); let errors = program.application_errors().unwrap(); assert!(errors.is_empty(), "{app}: {errors:?}"); } } #[test] fn application_preserves_different_contracts_and_owners() { for domain in [ "first(value: Int) (answer: Int) pure { answer = value }\nsecond(value: Int) (different: Int) pure { different = value }", "first(value: Int) (answer: Int) pure recover(\"first policy\") { answer = value }\nsecond(value: Int) (answer: Int) pure recover(\"second policy\") { answer = value }", "first() (answer: Int >= 1) pure { answer = 1 }\nsecond() (answer: Int >= 0) pure { answer = 1 }", ] { let inputs = if domain.contains("value:") { "1" } else { "" }; let app = format!("total() (answer: Int) {{ first = domain.first({inputs})\nsecond = domain.second({inputs})\nanswer = first + second }}"); let program = application(&[ ("sample/count/domain.dever", domain), ("sample/count/app.dever", &app), ("sample/count/api.dever", "cmd total = app.total"), ]); assert!(program.application_errors().unwrap().is_empty(), "{domain}"); } let program = application(&[ ("first/count/domain.dever", "calculate(value: Int) (answer: Int) pure { answer = value + 1 }"), ("first/count/app.dever", "total(value: Int) (answer: Int) { answer = domain.calculate(value) }"), ("first/count/api.dever", "cmd total = app.total"), ("second/count/domain.dever", "calculate(value: Int) (answer: Int) pure { answer = value + 1 }"), ("second/count/app.dever", "total(value: Int) (answer: Int) { answer = domain.calculate(value) }"), ("second/count/api.dever", "cmd total = app.total"), ]); assert!(program.application_errors().unwrap().is_empty()); } #[test] fn application_keeps_effectful_private_copies_and_adapter_redundancy_as_warnings() { let program = application(&[ ("sample/count/domain.dever", "emit(value: Int) (answer: Int) { dever.io.println(\"called\")\nanswer = value }\nfirst(value: Int) (answer: Int) { answer = emit(value) + 1 }\nsecond(renamed: Int) (answer: Int) { answer = emit(renamed) + 1 }"), ("sample/count/app.dever", "total(value: Int) (answer: Int) { first = domain.first(value)\nsecond = domain.second(value)\nanswer = first + second }"), ("sample/count/api.dever", "cmd total = app.total"), ]); assert!(program.warnings().iter().any(|warning| warning.code == "W003")); assert!(program.application_errors().unwrap().is_empty()); let program = application(&[ ("sample/count/port.dever", "read(value: Int) (answer: Int) fails dever.time.SleepResult"), ("sample/count/adapter.dever", "first(value: Int) (answer: Int) { answer = value + 1 }\nsecond(value: Int) (answer: Int) { answer = value + 1 }\nport.read(value: Int) (answer: Int) { one = first(value)\ntwo = second(value)\nthree = first(value)\nanswer = one + two + three }"), ("sample/count/app.dever", "read(value: Int) (answer: Int) { answer = port.read(value) }"), ("sample/count/api.dever", "cmd read = app.read"), ]); for code in ["W003", "W004"] { assert!(program.warnings().iter().any(|warning| warning.code == code)); } assert!(program.application_errors().unwrap().is_empty()); }