✓
Passing This code compiles and runs correctly.
Code
// Test 521: Multiple resources - partial cleanup (MUST FAIL)
// Tests that the compiler catches when only ONE of TWO resources is cleaned
//
// Key points:
// - open_two() returns TWO files, each with [opened!]
// - We only close ONE file (file1)
// - ERROR: file2 still has cleanup obligation
// - This verifies each obligation is tracked independently
~import "$app/fs"
~app.fs:open_two(path1: "test1.txt", path2: "test2.txt")
| opened f |> app.fs:close(file: f.file1) // Close first file only
| closed |> _ // ERROR: f.file2 still uncleaned!
Error Verification
Expected Error Pattern
This test must fail because it opens TWO files but only closes ONE.
The second file (f.file2) still has a cleanup obligation at the terminator.Actual Compiler Output
[PHASE 2.4] Calling run_pass for transforms\n[PHASE 2.5] Executing comptime_main() - running comptime flows
[PHASE 2.5] Comptime flows complete (25 items)
[PHASE 2.6] Rescanning transformed AST (25 items)
[PHASE 2.6] Rescan complete: 25 comptime events found
[0] std.compiler:requires
[1] std.compiler:flag.declare
[2] std.compiler:command.declare
[3] std.compiler:coordinate
[4] std.compiler:context_create
[5] std.testing:test
[6] std.testing:validate_mocks
[7] std.testing:test.with_harness
[8] std.testing:test.harness
[9] std.testing:assert
[10] std.testing:test.property.equivalent
[11] std.deps:deps
[12] std.deps:requires.system
[13] std.deps:requires.zig
[14] std.control:if
[15] std.control:for
[16] std.control:capture
[17] std.control:const
[18] std.build:requires
[19] std.build:variants
[20] std.build:config
[21] std.build:command.sh
[22] std.build:command.zig
[23] std.build:step
[24] std.template:define
[PHANTOM-KORU] Starting phantom check proc...
[DEAD-STRIP] Removing event_decl: std.compiler:coordinate
[DEAD-STRIP] Removing event_decl: std.compiler:ast_dump
[DEAD-STRIP] Removing proc_decl: std.compiler:ast_dump
[DEAD-STRIP] Removing event_decl: std.compiler:transform_taps
[DEAD-STRIP] Removing proc_decl: std.compiler:transform_taps
[DEAD-STRIP] Removing event_decl: std.compiler:parse
[DEAD-STRIP] Removing proc_decl: std.compiler:parse
[DEAD-STRIP] Removing event_decl: std.testing:test
[DEAD-STRIP] Removing proc_decl: std.testing:test
[DEAD-STRIP] Removing event_decl: std.testing:test.with_harness
[DEAD-STRIP] Removing event_decl: std.testing:test.harness
[DEAD-STRIP] Removing event_decl: std.testing:assert
[DEAD-STRIP] Removing proc_decl: std.testing:assert
[DEAD-STRIP] Removing event_decl: std.testing:assert.ok
[DEAD-STRIP] Removing proc_decl: std.testing:assert.ok
[DEAD-STRIP] Removing event_decl: std.testing:assert.fail
[DEAD-STRIP] Removing proc_decl: std.testing:assert.fail
[DEAD-STRIP] Removing event_decl: std.testing:assert.eq
[DEAD-STRIP] Removing event_decl: std.testing:assert.contains
[DEAD-STRIP] Removing event_decl: std.testing:test.property.equivalent
[DEAD-STRIP] Removing event_decl: std.deps:deps
[DEAD-STRIP] Removing proc_decl: std.deps:deps
[DEAD-STRIP] Removing event_decl: std.deps:requires.zig
[DEAD-STRIP] Removing proc_decl: std.deps:requires.zig
[DEAD-STRIP] Removing event_decl: std.control:if
[DEAD-STRIP] Removing proc_decl: std.control:if
[DEAD-STRIP] Removing event_decl: std.control:for
[DEAD-STRIP] Removing proc_decl: std.control:for
[DEAD-STRIP] Removing event_decl: std.control:capture
[DEAD-STRIP] Removing proc_decl: std.control:capture
[DEAD-STRIP] Removing event_decl: std.control:const
[DEAD-STRIP] Removing proc_decl: std.control:const
[DEAD-STRIP] Removing event_decl: std.build:requires
[DEAD-STRIP] Removing proc_decl: std.build:requires
[DEAD-STRIP] Removing event_decl: std.build:variants
[DEAD-STRIP] Removing proc_decl: std.build:variants
[DEAD-STRIP] Removing event_decl: std.build:config
[DEAD-STRIP] Removing proc_decl: std.build:config
[DEAD-STRIP] Removing event_decl: std.build:command.sh
[DEAD-STRIP] Removing event_decl: std.build:command.zig
[DEAD-STRIP] Removing event_decl: std.build:command
[DEAD-STRIP] Removing event_decl: std.build:collect
[DEAD-STRIP] Removing proc_decl: std.build:collect
[DEAD-STRIP] Removing event_decl: std.compiler_types:__compiler_types_marker
[DEAD-STRIP] Stripped 44 unreachable items
[BUFFER DEBUG] After CodeEmitter.init:
[BUFFER DEBUG] code_emitter.pos = 0
[BUFFER DEBUG] First 50 bytes: [170][170][170][170][170][170][170][170][170][170][170][170][170][170][170][170][170][170][170][170][170][170][170][170][170][170][170][170][170][170][170][170][170][170][170][170][170][170][170][170][170][170][170][170][170][170][170][170][170][170]
[BUFFER DEBUG] After visitor_emitter.emit:
[BUFFER DEBUG] code_emitter.pos = 4797
[BUFFER DEBUG] First 50 bytes of buffer: // Access compiler flags from backend.zig via root
[BUFFER DEBUG] final_code length = 4797
[BUFFER DEBUG] First 50 bytes of final_code: // Access compiler flags from backend.zig via root
[INTER] inter:start called with 6 contexts
[INTER] --inter flag not set, skipping
🎯 Compiler coordination: Passes: 13 (flow-based: frontend, analysis, emission)
[MAIN DEBUG] Before file write:
[MAIN DEBUG] generated_code.len = 4797
[MAIN DEBUG] generated_code.ptr = u8@110524010
[MAIN DEBUG] emitted_file = output_emitted.zig
[MAIN DEBUG] emitted_file.ptr = u8@1009b845e
[MAIN DEBUG] First 50 bytes: // Access compiler flags from backend.zig via rootImported Files
const std = @import("std");
const File = struct { handle: i32 };
~pub event open_two { path1: []const u8, path2: []const u8 }
| opened { file1: *File[opened!], file2: *File[opened!] } // TWO obligations!
~proc open_two {
std.debug.print("Opening two files: {s}, {s}\n", .{path1, path2});
const allocator = std.heap.page_allocator;
const f1 = allocator.create(File) catch unreachable;
const f2 = allocator.create(File) catch unreachable;
f1.* = File{ .handle = 42 };
f2.* = File{ .handle = 43 };
return .{ .opened = .{ .file1 = f1, .file2 = f2 } };
}
~pub event close { file: *File[!opened] } // Consumes one obligation
| closed {}
~proc close {
std.debug.print("Closing file\n", .{});
return .{ .closed = .{} };
}
Test Configuration
MUST_FAIL