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).
67 lines
2.1 KiB
Plaintext
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)); }
|
|
}
|