diff --git a/examples/201-closure-contextual-params.sx b/examples/201-closure-contextual-params.sx new file mode 100644 index 0000000..1a0e710 --- /dev/null +++ b/examples/201-closure-contextual-params.sx @@ -0,0 +1,28 @@ +// Step 2.5 — contextual typing for closure literals with N (heterogeneous) +// params. An untyped lambda `(a, b, c) => ...` takes each param's type +// positionally from the expected `Closure(T0, T1, T2) -> R` signature, in both +// assignment and argument position. (Previously only the first param — or +// all-same-typed params — resolved; trailing params silently defaulted to s64.) + +#import "modules/std.sx"; + +// argument-position: lambda typed from the parameter's closure type. +apply2 :: (f: Closure(s64, string) -> s64, x: s64, s: string) -> s64 { + return f(x, s); +} +apply3 :: (f: Closure(s64, s64, string) -> s64, a: s64, b: s64, c: string) -> s64 { + return f(a, b, c); +} + +main :: () -> s32 { + // assignment-position, mixed (s64, string) params — `b` is string. + cb : Closure(s64, string) -> s64 = (a, b) => a + b.len; + print("cb={}\n", cb(10, "hello")); // 10 + 5 = 15 + + // argument-position, 2 params. + print("r={}\n", apply2((a, b) => a + b.len, 10, "hello")); // 15 + + // argument-position, 3 params (s64, s64, string). + print("q={}\n", apply3((a, b, c) => a + b + c.len, 1, 2, "xyz")); // 1+2+3 = 6 + 0; +} diff --git a/src/ir/lower.zig b/src/ir/lower.zig index 51ab210..8b4e96b 100644 --- a/src/ir/lower.zig +++ b/src/ir/lower.zig @@ -7098,6 +7098,8 @@ pub const Lowering = struct { } break :blk null; } else null; + // User params follow the ctx (optional) + env slots in `params`. + const user_param_base: usize = (if (lambda_wants_ctx) @as(usize, 1) else 0) + 1; for (lam.params, 0..) |p, pi| { var pty = self.resolveParamType(&p); // Infer param type from target closure type if no annotation @@ -7136,8 +7138,8 @@ pub const Lowering = struct { var temp_scope = Scope.init(self.alloc, self.scope); const saved = self.scope; self.scope = &temp_scope; - for (lam.params) |p| { - const pty = self.resolveParamType(&p); + for (lam.params, 0..) |p, i| { + const pty = params.items[user_param_base + i].ty; temp_scope.put(p.name, .{ .ref = @enumFromInt(0), .ty = pty, .is_alloca = false }); } const inferred = self.inferExprType(lam.body); @@ -7210,9 +7212,12 @@ pub const Lowering = struct { } } - // Bind params (user args start at user_param_base_lam, shifted past ctx + env) + // Bind params (user args start at user_param_base_lam, shifted past ctx + env). + // Use the signature types computed above (`params`), which already + // applied contextual typing from the target closure to untyped params — + // `resolveParamType` alone would drop it and default each to s64. for (lam.params, 0..) |p, i| { - const pty = self.resolveParamType(&p); + const pty = params.items[user_param_base + i].ty; const slot = self.builder.alloca(pty); const param_ref = Ref.fromIndex(user_param_base_lam + @as(u32, @intCast(i))); self.builder.store(slot, param_ref); diff --git a/tests/expected/201-closure-contextual-params.exit b/tests/expected/201-closure-contextual-params.exit new file mode 100644 index 0000000..573541a --- /dev/null +++ b/tests/expected/201-closure-contextual-params.exit @@ -0,0 +1 @@ +0 diff --git a/tests/expected/201-closure-contextual-params.txt b/tests/expected/201-closure-contextual-params.txt new file mode 100644 index 0000000..adb901f --- /dev/null +++ b/tests/expected/201-closure-contextual-params.txt @@ -0,0 +1,3 @@ +cb=15 +r=15 +q=6