Files
sx/examples/closures/0311-closures-optional-closure.sx
agra 3605165398 fix: dispatch unwrapped optional-closure call g!() through call_closure (issue 0170)
Calling through an unwrapped optional closure (g!()) crashed with LLVM
'Called function must be a pointer!': the indirect-call catch-all else
arm emitted call_indirect on the whole {fn,env} closure struct with a
hardcoded .i64 return. The else arm now inspects inferExprType(callee):
a .closure callee dispatches through call_closure (threads env + ctx via
the [ctx, env, user_args] ABI, returns closure.ret); a plain fn pointer
uses call_indirect with the callee's real function.ret instead of i64.

The filed repro's ?(() -> void) spelling is a tuple-optional (now
diagnosed by the 0165 fix); the real ?Closure(...) layout was already
correct. Verified load-bearing (HEAD crashes) by 3 adversarial reviews,
suite 785/0. Regression: examples/closures/0311-closures-optional-closure.sx.
Filed adjacent bug 0177 (array-element closure direct call crashes).
2026-06-23 01:02:13 +03:00

67 lines
2.1 KiB
Plaintext

// Optional closures: `?Closure(...) -> R`.
//
// Runtime repr is the sentinel form — the optional IS the closure struct
// `{fn_ptr, env}`; has_value = `fn_ptr != null`, no separate flag word.
// Covers: present vs null truthiness, `== null` / `!= null`, force-unwrap +
// call-through (with and without captures), `??` coalesce, pass-as-param,
// return, and args + non-void return.
//
// Regression (issue 0170): `g!()` where `g : ?Closure(...)` previously lowered
// to a `call_indirect` that treated the closure `{fn,env}` struct as a bare
// fn pointer (LLVM "Called function must be a pointer!"); the indirect-call
// catch-all also hardcoded an `.i64` return type. The else-arm in
// `src/ir/lower/call.zig` now dispatches to `call_closure` when the callee
// expression's static type is a closure, and uses the real return type.
#import "modules/std.sx";
take :: (g: ?Closure() -> void) {
if g { g!(); } else { print("param-absent\n"); }
}
give :: () -> ?Closure() -> void {
return () => print("from-give\n");
}
main :: () {
// With captures.
n := 7;
a : ?Closure() -> void = () => print("a n={}\n", n);
if a { print("a-present\n"); } else { print("a-absent\n"); }
a!();
// Without captures.
b : ?Closure() -> void = () { print("b-called\n"); };
if b { b!(); }
// Null tests absent; == / != null.
c : ?Closure() -> void = null;
if c { print("c-present\n"); } else { print("c-absent\n"); }
print("c==null: {}\n", c == null);
print("c!=null: {}\n", c != null);
// Reassign: null -> value -> null.
c = () { print("c-reassigned\n"); };
if c { c!(); }
c = null;
if c { print("c2-present\n"); } else { print("c2-absent\n"); }
// Coalesce: null falls back, present uses self.
fallback := () { print("fallback\n"); };
(c ?? fallback)();
e : ?Closure() -> void = () { print("e-real\n"); };
(e ?? fallback)();
// Pass as param (present + null).
take(a);
take(c);
// Return an optional closure.
r := give();
if r { r!(); }
// Args + non-void return.
add : ?Closure(i64, i64) -> i64 = (x: i64, y: i64) => x + y;
if add { print("add: {}\n", add!(3, 4)); }
}