ERR/E5.1: bare failable fn-type param resolution + non-failable->failable widening
Two more E5.1 composition pieces: - inferExprType .call: a callee that's a local variable of bare type () now resolves to its declared return type (only was handled before), so / on the call see the failable result instead of . - createClosureToBareFnAdapter now widens: when a NON-failable closure literal flows into a failable bare slot (∅ ⊆ slot set, success type matches), the adapter wraps the value into the slot's tuple via lowerFailableSuccessReturn — previously rejected. The failable->non-failable and capturing->bare crossings stay rejected. Adapter generation fires for closure LITERALS flowing into a bare-fn slot; a pre-bound closure VARIABLE into a bare-fn slot is a separate coercion-site path, still unhandled (noted in CHECKPOINT-ERR). Regression: examples/1040-errors-failable-closure-composition. Suite: 329 passed.
This commit is contained in:
@@ -7891,10 +7891,13 @@ pub const Lowering = struct {
|
||||
if (self.target_type) |tt| {
|
||||
if (!tt.isBuiltin() and self.module.types.get(tt) == .function) {
|
||||
const slot_ret = self.module.types.get(tt).function.ret;
|
||||
const widen_ok = self.errorChannelOf(slot_ret) != null and self.errorChannelOf(ret_ty) == null and self.failableSuccessType(slot_ret) == ret_ty;
|
||||
if (capture_list.len > 0) {
|
||||
if (self.diagnostics) |d| d.addFmt(.err, lam.body.span, "a capturing closure cannot be passed as a bare function pointer; declare the parameter type as `Closure(...)` so its environment is carried", .{});
|
||||
} else if (ret_ty == slot_ret) {
|
||||
const adapter = self.createClosureToBareFnAdapter(func_id, self.module.types.get(tt).function);
|
||||
} else if (ret_ty == slot_ret or widen_ok) {
|
||||
// Matching ABI, or a non-failable closure widening into a
|
||||
// failable slot (∅ ⊆ slot set) — the adapter wraps {value, 0}.
|
||||
const adapter = self.createClosureToBareFnAdapter(func_id, self.module.types.get(tt).function, ret_ty, lam.body.span);
|
||||
return self.builder.emit(.{ .func_ref = adapter }, tt);
|
||||
} else if (self.errorChannelOf(ret_ty) != null and self.errorChannelOf(slot_ret) == null) {
|
||||
if (self.diagnostics) |d| d.addFmt(.err, lam.body.span, "failable closure cannot be assigned to a non-failable function-type slot; foreign code can't observe the error channel — handle the error in a wrapper closure that absorbs it", .{});
|
||||
@@ -8030,7 +8033,12 @@ pub const Lowering = struct {
|
||||
/// (`[ctx?] + user-params`) and forwards to the closure fn with a null env.
|
||||
/// Only sound for capture-free closures (a null env is correct iff the body
|
||||
/// reads no captures); the caller rejects capturing closures.
|
||||
fn createClosureToBareFnAdapter(self: *Lowering, closure_func_id: FuncId, fn_info: types.TypeInfo.FunctionInfo) FuncId {
|
||||
///
|
||||
/// When `closure_ret` differs from `fn_info.ret`, this is the ∅-widening
|
||||
/// case (a non-failable closure into a failable slot): the closure returns
|
||||
/// the success value and the adapter wraps it into the slot's `{value, 0}`
|
||||
/// failable tuple (ERR E5.1 non-failable→failable widening).
|
||||
fn createClosureToBareFnAdapter(self: *Lowering, closure_func_id: FuncId, fn_info: types.TypeInfo.FunctionInfo, closure_ret: TypeId, span: ast.Span) FuncId {
|
||||
var params = std.ArrayList(inst_mod.Function.Param).empty;
|
||||
defer params.deinit(self.alloc);
|
||||
const void_ptr_ty = self.module.types.ptrTo(.void);
|
||||
@@ -8074,11 +8082,17 @@ pub const Lowering = struct {
|
||||
call_args.append(self.alloc, Ref.fromIndex(@intCast(ctx_slots + i))) catch unreachable;
|
||||
}
|
||||
const owned_args = self.alloc.dupe(Ref, call_args.items) catch unreachable;
|
||||
const result = self.builder.emit(.{ .call = .{ .callee = closure_func_id, .args = owned_args } }, fn_info.ret);
|
||||
if (fn_info.ret != .void) {
|
||||
self.builder.ret(result, fn_info.ret);
|
||||
const result = self.builder.emit(.{ .call = .{ .callee = closure_func_id, .args = owned_args } }, closure_ret);
|
||||
if (closure_ret == fn_info.ret) {
|
||||
if (fn_info.ret != .void) {
|
||||
self.builder.ret(result, fn_info.ret);
|
||||
} else {
|
||||
self.builder.retVoid();
|
||||
}
|
||||
} else {
|
||||
self.builder.retVoid();
|
||||
// ∅-widening: closure returns the success value; wrap `{value, 0}`
|
||||
// into the slot's failable tuple.
|
||||
self.lowerFailableSuccessReturn(result, fn_info.ret, span);
|
||||
}
|
||||
self.builder.finalize();
|
||||
|
||||
@@ -14190,12 +14204,16 @@ pub const Lowering = struct {
|
||||
if (fd.return_type) |rt| return self.resolveType(rt);
|
||||
return .void;
|
||||
}
|
||||
// Check if callee is a local closure variable — extract return type
|
||||
// Check if callee is a local closure / function-type variable
|
||||
// (e.g. a `cb: Closure(...) -> R` or bare `cb: (T) -> R`
|
||||
// parameter) — extract its declared return type so `try` /
|
||||
// `catch` on the call see the (possibly failable) result.
|
||||
if (self.scope) |scope| {
|
||||
if (scope.lookup(bare_name)) |binding| {
|
||||
if (!binding.ty.isBuiltin()) {
|
||||
const ti = self.module.types.get(binding.ty);
|
||||
if (ti == .closure) return ti.closure.ret;
|
||||
if (ti == .function) return ti.function.ret;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user