docs: file issues 0185-0188
File four issue write-ups discovered alongside the 0179 work: - 0185: binary-op operand auto-unwrap silently miscompiles a NULL ?T - 0186: closure VALUE call does not coerce arg to ?T parameter - 0187: lambda with inferred return type + block body with early returns mis-infers its return type - 0188: closure-VALUE calls skip argument validation (arity + tuple spread)
This commit is contained in:
65
issues/0188-closure-value-call-arg-validation-gaps.md
Normal file
65
issues/0188-closure-value-call-arg-validation-gaps.md
Normal file
@@ -0,0 +1,65 @@
|
||||
# 0188 — closure-VALUE calls skip argument validation: no arity check + runtime-tuple spread not expanded
|
||||
|
||||
## Symptom
|
||||
|
||||
Calling a closure VALUE (a `:=`-bound lambda, struct-field closure, fn-pointer
|
||||
value) does NOT validate arguments the way a top-level function call does. Two
|
||||
distinct gaps, both pre-existing (surfaced during the adversarial review of the
|
||||
issue-0186 fix; 0186 fixed only arg COERCION for correctly-counted args):
|
||||
|
||||
1. **No arity check.** A closure value called with the WRONG number of args
|
||||
compiles and silently drops/ignores extras (or reads garbage for missing
|
||||
ones), exit 0. A top-level fn call diagnoses arity.
|
||||
2. **Runtime-tuple spread `f(..tuple)` is never expanded for a closure value.**
|
||||
The spread leaves a `Ref.none` placeholder (`call.zig` ~line 404) that the
|
||||
`call_closure` sites emit as `undef`, so the call passes garbage.
|
||||
|
||||
## Reproduction
|
||||
|
||||
```sx
|
||||
#import "modules/std.sx";
|
||||
main :: () {
|
||||
// (1) arity: extra arg silently dropped
|
||||
one := (a: i64) -> i64 => a;
|
||||
print("arity: {}\n", one(1, 2)); // prints 1 (no error; `2` dropped)
|
||||
|
||||
// (2) spread into a closure value → garbage
|
||||
add := (a: i64, b: i64) -> i64 => a + b;
|
||||
pair := (10, 20);
|
||||
print("spread: {}\n", add(..pair)); // prints garbage (e.g. -17590042754976), not 30
|
||||
}
|
||||
```
|
||||
|
||||
Expected: (1) an arity diagnostic (as for a top-level fn); (2) `add(..pair)`
|
||||
expands to `add(10, 20)` → `30`, OR a clear diagnostic that spread into a
|
||||
closure value is unsupported (never silent garbage).
|
||||
|
||||
## Root cause (hypothesis)
|
||||
|
||||
The closure-value call paths in `src/ir/lower/call.zig` (the three
|
||||
`call_closure` emission sites + the local `call_indirect` fn-pointer path) build
|
||||
the arg list and emit directly without (a) an arity check against
|
||||
`closure.params.len` / `function.params.len`, and (b) without running the
|
||||
runtime-slice/tuple spread expansion that the normal call path uses
|
||||
(`packVariadicCallArgs` / the `Ref.none` spread placeholder is never resolved
|
||||
for closures). The pack-spread `..xs` path (`packSpreadRefs`) handles comptime
|
||||
packs but not a runtime tuple value spread into a closure.
|
||||
|
||||
## Investigation prompt
|
||||
|
||||
In `src/ir/lower/call.zig`, for each closure-value / fn-pointer-value call site
|
||||
(grep `call_closure` and the local `call_indirect` path ~line 655):
|
||||
1. Add an arity check against the callee value's `closure.params` /
|
||||
`function.params` length (mirror `checkCallArity` used for top-level fns),
|
||||
accounting for the implicit `__sx_ctx` slot.
|
||||
2. Either expand a runtime-tuple/slice spread argument into positional args for
|
||||
closure values (as the normal call path does), or emit a located diagnostic
|
||||
that spread into a callable value is unsupported — never emit the `Ref.none`
|
||||
placeholder as `undef`.
|
||||
3. Regression: extend `examples/closures/0312-...` or add
|
||||
`examples/closures/03xx-closure-value-arity.sx` covering both the arity
|
||||
diagnostic and the spread behavior.
|
||||
|
||||
Unrelated to the arg-COERCION fix (issue 0186, already landed) — that fix
|
||||
correctly coerces a correctly-COUNTED arg; these gaps are about COUNT and
|
||||
spread expansion.
|
||||
Reference in New Issue
Block a user