// Effect System Error Test: E0402 - Effectful in Parallel Context
// ================================================================
//
// What this test validates:
// - Error E0402 is raised when effectful functions are used in parallel contexts
// - Parallel combinators (par_map, par_for, etc.) require pure functions
// - Non-determinism from effects would break parallel execution semantics
// - Both direct and transitive effects are caught
//
// Expected error:
// E0402: effectful function in parallel context
// - Parallel combinators require pure functions for deterministic results
// - Function has [IO/Msg/Mut] effect which is not safe for parallelism
gene Parallel.Violations @0.1.0 {
"""
Examples of incorrect parallel usage with effectful functions.
"""
// Pure function - OK for parallel
fn square(x: Int) -> Int {
x * x
}
// Effectful function - NOT OK for parallel
fn logged_square(x: Int) -> Int {
@io.println("Squaring: " + @fmt.int(x));
x * x
}
// ERROR E0402: par_map with IO effect
fn parallel_with_io(numbers: List[Int]) -> List[Int] {
@par.map(numbers, logged_square) // VIOLATION: logged_square has IO effect
}
// This is fine - pure function in parallel
fn parallel_pure(numbers: List[Int]) -> List[Int] {
@par.map(numbers, square) // OK: square is pure
}
}
gene Parallel.MessagingViolation @0.1.0 {
"""
Parallel context with messaging effects.
"""
fn send_progress(item: Int) -> Int {
@msg.send(@msg.self(), { type: "progress", item: item });
item * 2
}
// ERROR E0402: Messaging in parallel context
fn parallel_with_messaging(items: List[Int]) -> List[Int] {
@par.map(items, send_progress) // VIOLATION: Msg effect
}
// ERROR E0402: Parallel for-each with messaging
fn parallel_foreach_msg(items: List[Int]) -> Unit {
@par.for_each(items, |item| {
@msg.broadcast({ type: "processing", value: item }) // VIOLATION
})
}
}
gene Parallel.MutationViolation @0.1.0 {
"""
Parallel context with mutation effects.
This is especially dangerous as it leads to race conditions.
"""
mut counter: Int = 0
fn increment_and_process(x: Int) -> Int {
self.counter = self.counter + 1; // Race condition!
x * self.counter
}
// ERROR E0402: Mutation in parallel - race condition
fn parallel_with_mutation(items: List[Int]) -> List[Int] {
@par.map(items, increment_and_process) // VIOLATION: Mut effect
}
mut results: List[Int] = []
fn accumulate(x: Int) -> Unit {
self.results = @list.append(self.results, x) // Race condition!
}
// ERROR E0402: Mutating shared state in parallel
fn parallel_accumulate(items: List[Int]) -> Unit {
@par.for_each(items, accumulate) // VIOLATION: race on self.results
}
}
gene Parallel.TransitiveViolation @0.1.0 {
"""
Effectful functions hidden through call chains.
"""
fn deep_io_effect(x: Int) -> Int {
@io.println("deep");
x
}
fn wrapper_1(x: Int) -> Int {
deep_io_effect(x) + 1
}
fn wrapper_2(x: Int) -> Int {
wrapper_1(x) * 2
}
fn seemingly_pure(x: Int) -> Int {
wrapper_2(x) - 1
}
// ERROR E0402: Transitive IO effect through call chain
fn parallel_transitive(items: List[Int]) -> List[Int] {
@par.map(items, seemingly_pure) // VIOLATION: transitive IO effect
}
}
gene Parallel.ClosureViolation @0.1.0 {
"""
Closures capturing effectful operations.
"""
fn log(msg: String) -> Unit {
@io.println(msg)
}
// ERROR E0402: Closure performs IO
fn parallel_closure_io(items: List[Int]) -> List[Int] {
@par.map(items, |x| {
log("Processing: " + @fmt.int(x)); // VIOLATION
x * 2
})
}
mut state: Int = 0
// ERROR E0402: Closure captures and mutates
fn parallel_closure_mut(items: List[Int]) -> List[Int] {
@par.map(items, |x| {
self.state = self.state + x; // VIOLATION: captures mut state
x
})
}
}
gene Parallel.ValidUsage @0.1.0 {
"""
Examples of CORRECT parallel usage with pure functions.
These should NOT produce E0402 errors.
"""
fn double(x: Int) -> Int { x * 2 }
fn square(x: Int) -> Int { x * x }
fn negate(x: Int) -> Int { -x }
fn add_one(x: Int) -> Int { x + 1 }
// All of these are valid - pure functions
fn valid_map(items: List[Int]) -> List[Int] {
@par.map(items, double)
}
fn valid_map_chain(items: List[Int]) -> List[Int] {
let doubled = @par.map(items, double);
@par.map(doubled, square)
}
fn valid_reduce(items: List[Int]) -> Int {
@par.reduce(items, 0, |acc, x| acc + x) // Pure lambda
}
fn valid_filter(items: List[Int]) -> List[Int] {
@par.filter(items, |x| x > 0) // Pure predicate
}
// Pure closure capturing immutable value
fn valid_closure(items: List[Int], multiplier: Int) -> List[Int] {
@par.map(items, |x| x * multiplier) // OK: captures immutable
}
}
// Expected diagnostic output:
// error[E0402]: effectful function in parallel context
// --> e0402_parallel.dol:30:9
// |
// 30 | @par.map(numbers, logged_square)
// | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
// |
// = note: parallel combinators require pure functions
// = note: 'logged_square' has effect: IO
// = note: IO effects may produce non-deterministic ordering in parallel
// = help: remove effects from the function or use sequential processing
test expect_error {
// Direct effect violations
assert_error(E0402, Parallel.Violations.parallel_with_io);
assert_no_error(Parallel.Violations.parallel_pure);
// Messaging violations
assert_error(E0402, Parallel.MessagingViolation.parallel_with_messaging);
assert_error(E0402, Parallel.MessagingViolation.parallel_foreach_msg);
// Mutation violations
assert_error(E0402, Parallel.MutationViolation.parallel_with_mutation);
assert_error(E0402, Parallel.MutationViolation.parallel_accumulate);
// Transitive violations
assert_error(E0402, Parallel.TransitiveViolation.parallel_transitive);
// Closure violations
assert_error(E0402, Parallel.ClosureViolation.parallel_closure_io);
assert_error(E0402, Parallel.ClosureViolation.parallel_closure_mut);
// Valid usage - should not error
assert_no_error(Parallel.ValidUsage.valid_map);
assert_no_error(Parallel.ValidUsage.valid_map_chain);
assert_no_error(Parallel.ValidUsage.valid_reduce);
assert_no_error(Parallel.ValidUsage.valid_filter);
assert_no_error(Parallel.ValidUsage.valid_closure);
}